From 5ce80ab45bfdec0f300d4f2095d4fc8dfe3eaae6 Mon Sep 17 00:00:00 2001 From: Daniel Puzzuoli Date: Fri, 10 Mar 2023 11:49:56 -0800 Subject: [PATCH 1/5] Implementing CouplingMap.__eq__ (#9766) * implementing Coupling.__eq__ * Update qiskit/transpiler/coupling.py Co-authored-by: Matthew Treinish * adding comments to test, and adding release note * black formatting * updating CouplingMap.__eq__ to check edge list equality instead of isomorphism * updating release notes * updating test doc string * Update test/python/transpiler/test_coupling.py Co-authored-by: Matthew Treinish * Update test/python/transpiler/test_coupling.py Co-authored-by: Matthew Treinish * Update test/python/transpiler/test_coupling.py Co-authored-by: Matthew Treinish * Update test/python/transpiler/test_coupling.py Co-authored-by: Matthew Treinish --------- Co-authored-by: Matthew Treinish --- qiskit/transpiler/coupling.py | 16 ++++++++++++++++ .../coupling-map-eq-b0507b703d62a5f3.yaml | 8 ++++++++ test/python/transpiler/test_coupling.py | 19 +++++++++++++++++++ 3 files changed, 43 insertions(+) create mode 100644 releasenotes/notes/coupling-map-eq-b0507b703d62a5f3.yaml diff --git a/qiskit/transpiler/coupling.py b/qiskit/transpiler/coupling.py index 7d8dc6e5467b..26d4052aa02b 100644 --- a/qiskit/transpiler/coupling.py +++ b/qiskit/transpiler/coupling.py @@ -412,6 +412,22 @@ def __str__(self): string += "]" return string + def __eq__(self, other): + """Check if the graph in ``other`` has the same node labels and edges as the graph in + ``self``. + + This function assumes that the graphs in :class:`.CouplingMap` instances are connected. + + Args: + other (CouplingMap): The other coupling map. + + Returns: + bool: Whether or not other is isomorphic to self. + """ + if not isinstance(other, CouplingMap): + return False + return set(self.graph.edge_list()) == set(other.graph.edge_list()) + def draw(self): """Draws the coupling map. diff --git a/releasenotes/notes/coupling-map-eq-b0507b703d62a5f3.yaml b/releasenotes/notes/coupling-map-eq-b0507b703d62a5f3.yaml new file mode 100644 index 000000000000..72f6fecd974b --- /dev/null +++ b/releasenotes/notes/coupling-map-eq-b0507b703d62a5f3.yaml @@ -0,0 +1,8 @@ +--- +upgrade: + - | + The :meth:`.CouplingMap.__eq__`` method has been updated to check that the edge lists of the + underlying graphs contain the same elements. Under the assumption that the underlying graphs are + connected, this check additionally ensures that the graphs have the same number of nodes with + the same labels. Any code using ``CouplingMap() == CouplingMap()`` to check object equality + should be updated to ``CouplingMap() is CouplingMap()``. \ No newline at end of file diff --git a/test/python/transpiler/test_coupling.py b/test/python/transpiler/test_coupling.py index cffc22c59844..b15c389353e5 100644 --- a/test/python/transpiler/test_coupling.py +++ b/test/python/transpiler/test_coupling.py @@ -448,6 +448,25 @@ def test_implements_iter(self): expected = [(0, 1), (1, 0), (1, 2), (2, 1)] self.assertEqual(sorted(coupling), expected) + def test_equality(self): + """Test that equality checks that the graphs have the same nodes, node labels, and edges.""" + + # two coupling maps with 4 nodes and the same edges + coupling0 = CouplingMap([(0, 1), (0, 2), (2, 3)]) + coupling1 = CouplingMap([(0, 1), (0, 2), (2, 3)]) + self.assertEqual(coupling0, coupling1) + + # coupling map with 5 nodes not equal to the previous 2 + coupling2 = CouplingMap([(0, 1), (0, 2), (2, 4)]) + self.assertNotEqual(coupling0, coupling2) + + # coupling map isomorphic to coupling0, but with cyclically shifted labels + coupling3 = CouplingMap([(1, 2), (1, 3), (3, 0)]) + self.assertNotEqual(coupling0, coupling3) + + # additional test for comparison to a non-CouplingMap object + self.assertNotEqual(coupling0, 1) + class CouplingVisualizationTest(QiskitVisualizationTestCase): @unittest.skipUnless(optionals.HAS_GRAPHVIZ, "Graphviz not installed") From 5a62949dd086ccdd530959539d67da473a07cded Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Fri, 10 Mar 2023 23:40:07 +0100 Subject: [PATCH 2/5] Qiskit gates not `qelib1.inc` are now defined when dumped (#9777) * non-standard gates * Qiskit gates not qelib1.inc are now defined when dumped --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- qiskit/circuit/quantumcircuit.py | 3 - .../notes/fix_9559-ec05304e52ff841f.yaml | 8 +++ test/python/circuit/test_circuit_qasm.py | 62 +++++++++++++++++-- 3 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/fix_9559-ec05304e52ff841f.yaml diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 1ac4e8448edf..8132630b1dbc 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -1623,7 +1623,6 @@ def qasm( "sx", "sxdg", "cz", - "ccz", "cy", "swap", "ch", @@ -1636,8 +1635,6 @@ def qasm( "cp", "cu3", "csx", - "cs", - "csdg", "cu", "rxx", "rzz", diff --git a/releasenotes/notes/fix_9559-ec05304e52ff841f.yaml b/releasenotes/notes/fix_9559-ec05304e52ff841f.yaml new file mode 100644 index 000000000000..4e0cb1918f6c --- /dev/null +++ b/releasenotes/notes/fix_9559-ec05304e52ff841f.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + The Qiskit gates :class:`~.CCZGate`, :class:`~.CSGate`, :class:`~.CSdgGate` are not defined in + ``qelib1.inc`` and, therefore, when dump as OpenQASM 2.0, their definition should be inserted in the file. + Fixes `#9559 `__, + `#9721 `__, and + `#9722 `__. diff --git a/test/python/circuit/test_circuit_qasm.py b/test/python/circuit/test_circuit_qasm.py index b8089e7756f0..8924bd1f4051 100644 --- a/test/python/circuit/test_circuit_qasm.py +++ b/test/python/circuit/test_circuit_qasm.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Test Qiskit's QuantumCircuit class.""" +"""Test Qiskit's gates in QASM2.""" import unittest from math import pi @@ -19,7 +19,7 @@ from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit from qiskit.test import QiskitTestCase from qiskit.circuit import Parameter, Qubit, Clbit, Gate -from qiskit.circuit.library import C3SXGate +from qiskit.circuit.library import C3SXGate, CCZGate, CSGate, CSdgGate, RZXGate from qiskit.qasm.exceptions import QasmError # Regex pattern to match valid OpenQASM identifiers @@ -27,7 +27,7 @@ class TestCircuitQasm(QiskitTestCase): - """QuantumCircuit Qasm tests.""" + """QuantumCircuit QASM2 tests.""" def test_circuit_qasm(self): """Test circuit qasm() method.""" @@ -249,7 +249,9 @@ def test_circuit_qasm_with_composite_circuit_with_many_params_and_qubits(self): self.assertEqual(original_str, qc.qasm()) def test_c3sxgate_roundtrips(self): - """Test that C3SXGate correctly round trips. Qiskit gives this gate a different name + """Test that C3SXGate correctly round trips. + + Qiskit gives this gate a different name ('c3sx') to the name in Qiskit's version of qelib1.inc ('c3sqrtx') gate, which can lead to resolution issues.""" qc = QuantumCircuit(4) @@ -264,6 +266,58 @@ def test_c3sxgate_roundtrips(self): parsed = QuantumCircuit.from_qasm_str(qasm) self.assertIsInstance(parsed.data[0].operation, C3SXGate) + def test_cczgate_qasm(self): + """Test that CCZ dumps definition as a non-qelib1 gate.""" + qc = QuantumCircuit(3) + qc.append(CCZGate(), qc.qubits, []) + qasm = qc.qasm() + expected = """OPENQASM 2.0; +include "qelib1.inc"; +gate ccz q0,q1,q2 { h q2; ccx q0,q1,q2; h q2; } +qreg q[3]; +ccz q[0],q[1],q[2]; +""" + self.assertEqual(qasm, expected) + + def test_csgate_qasm(self): + """Test that CS dumps definition as a non-qelib1 gate.""" + qc = QuantumCircuit(2) + qc.append(CSGate(), qc.qubits, []) + qasm = qc.qasm() + expected = """OPENQASM 2.0; +include "qelib1.inc"; +gate cs q0,q1 { p(pi/4) q0; cx q0,q1; p(-pi/4) q1; cx q0,q1; p(pi/4) q1; } +qreg q[2]; +cs q[0],q[1]; +""" + self.assertEqual(qasm, expected) + + def test_csdggate_qasm(self): + """Test that CSdg dumps definition as a non-qelib1 gate.""" + qc = QuantumCircuit(2) + qc.append(CSdgGate(), qc.qubits, []) + qasm = qc.qasm() + expected = """OPENQASM 2.0; +include "qelib1.inc"; +gate csdg q0,q1 { p(-pi/4) q0; cx q0,q1; p(pi/4) q1; cx q0,q1; p(-pi/4) q1; } +qreg q[2]; +csdg q[0],q[1]; +""" + self.assertEqual(qasm, expected) + + def test_rzxgate_qasm(self): + """Test that RZX dumps definition as a non-qelib1 gate.""" + qc = QuantumCircuit(2) + qc.append(RZXGate(0), qc.qubits, []) + qasm = qc.qasm() + expected = """OPENQASM 2.0; +include "qelib1.inc"; +gate rzx(param0) q0,q1 { h q1; cx q0,q1; rz(0) q1; cx q0,q1; h q1; } +qreg q[2]; +rzx(0) q[0],q[1]; +""" + self.assertEqual(qasm, expected) + def test_unbound_circuit_raises(self): """Test circuits with unbound parameters raises.""" qc = QuantumCircuit(1) From 6829bb18cf791960896fe72b9be9611aac44155a Mon Sep 17 00:00:00 2001 From: Kazuki Tsuoka Date: Sun, 12 Mar 2023 16:27:55 +0900 Subject: [PATCH 3/5] Fix `Parameter.is_real()` (#9664) * fix bug * add reno * Update releasenotes/notes/fix-parameter-is_real-8b8f99811e58075e.yaml --------- Co-authored-by: Julien Gacon --- qiskit/circuit/parameterexpression.py | 9 ++++----- .../fix-parameter-is_real-8b8f99811e58075e.yaml | 6 ++++++ test/python/circuit/test_parameters.py | 16 +++++++++++++++- 3 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/fix-parameter-is_real-8b8f99811e58075e.yaml diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index 68f093c1125e..31879cf57e31 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -535,11 +535,10 @@ def is_real(self): # expression's is_real attribute returns false that we have a # non-zero imaginary if _optionals.HAS_SYMENGINE: - if symbol_expr.imag != 0.0: - return False - else: - return False - return True + if symbol_expr.imag == 0.0: + return True + return False + return symbol_expr.is_real def sympify(self): """Return symbolic expression as a raw Sympy or Symengine object. diff --git a/releasenotes/notes/fix-parameter-is_real-8b8f99811e58075e.yaml b/releasenotes/notes/fix-parameter-is_real-8b8f99811e58075e.yaml new file mode 100644 index 000000000000..9ac6d184beae --- /dev/null +++ b/releasenotes/notes/fix-parameter-is_real-8b8f99811e58075e.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixed a bug where :meth:`.Parameter.is_real` did not return ``None`` when + the parameter is not bound. + Fixed `#8619 `__. diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index 74ac785d9448..5f49f026a7a5 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -1759,10 +1759,24 @@ def test_parameter_expression_grad(self): def test_bound_expression_is_real(self): """Test is_real on bound parameters.""" x = Parameter("x") + self.assertEqual(x.is_real(), None) + self.assertEqual((1j * x).is_real(), None) + expr = 1j * x bound = expr.bind({x: 2}) + self.assertEqual(bound.is_real(), False) + + bound = x.bind({x: 0 + 0j}) + self.assertEqual(bound.is_real(), True) + + bound = x.bind({x: 0 + 1j}) + self.assertEqual(bound.is_real(), False) + + bound = x.bind({x: 1 + 0j}) + self.assertEqual(bound.is_real(), True) - self.assertFalse(bound.is_real()) + bound = x.bind({x: 1 + 1j}) + self.assertEqual(bound.is_real(), False) class TestParameterEquality(QiskitTestCase): From 2ce129a14279a746d309f00e311b930ddbfe633c Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Mon, 13 Mar 2023 22:08:35 +0900 Subject: [PATCH 4/5] Fix unitary synthesis module to get proper error value when it's empty. (#9774) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../transpiler/passes/synthesis/unitary_synthesis.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index 898998d08c63..48bd0b0f8b38 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -660,7 +660,10 @@ def is_controlled(gate): if props is None: basis_2q_fidelity = 1.0 else: - basis_2q_fidelity = 1 - getattr(props, "error", 0.0) + error = getattr(props, "error", 0.0) + if error is None: + error = 0.0 + basis_2q_fidelity = 1 - error if approximation_degree is not None: basis_2q_fidelity *= approximation_degree decomposer = TwoQubitBasisDecomposer( @@ -682,7 +685,10 @@ def is_controlled(gate): if props is None: basis_2q_fidelity[strength] = 1.0 else: - basis_2q_fidelity[strength] = 1 - getattr(props, "error", 0.0) + error = getattr(props, "error", 0.0) + if error is None: + error = 0.0 + basis_2q_fidelity[strength] = 1 - error # rewrite XX of the same strength in terms of it embodiment = XXEmbodiments[type(v)] if len(embodiment.parameters) == 1: From c2affb1459b80a55e62ea7693df849eae3699e10 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Mon, 13 Mar 2023 16:38:36 +0000 Subject: [PATCH 5/5] Fix explicitly calibrated gates in `GateDirection` (#9786) If there is an explicit calibration given, this overrides the generic information from the `CouplingMap` or the `Target` for that particular instance, since one can use pulse-level control to define gates on a circuit-by-circuit basis that are not generically available in a way that can be specified in the coupling or target. --- .../transpiler/passes/utils/gate_direction.py | 4 +++ ...irection-calibration-c51202358d86e18f.yaml | 5 +++ test/python/transpiler/test_gate_direction.py | 31 +++++++++++++++++-- 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/fix-gate-direction-calibration-c51202358d86e18f.yaml diff --git a/qiskit/transpiler/passes/utils/gate_direction.py b/qiskit/transpiler/passes/utils/gate_direction.py index 12cb4ecdcf01..323f19bf7fe9 100644 --- a/qiskit/transpiler/passes/utils/gate_direction.py +++ b/qiskit/transpiler/passes/utils/gate_direction.py @@ -166,6 +166,8 @@ def _run_coupling_map(self, dag, wire_map, edges=None): continue if len(node.qargs) != 2: continue + if dag.has_calibration_for(node): + continue qargs = (wire_map[node.qargs[0]], wire_map[node.qargs[1]]) if qargs not in edges and (qargs[1], qargs[0]) not in edges: raise TranspilerError( @@ -209,6 +211,8 @@ def _run_target(self, dag, wire_map): continue if len(node.qargs) != 2: continue + if dag.has_calibration_for(node): + continue qargs = (wire_map[node.qargs[0]], wire_map[node.qargs[1]]) swapped = (qargs[1], qargs[0]) if node.name in self._static_replacements: diff --git a/releasenotes/notes/fix-gate-direction-calibration-c51202358d86e18f.yaml b/releasenotes/notes/fix-gate-direction-calibration-c51202358d86e18f.yaml new file mode 100644 index 000000000000..140ff5b71028 --- /dev/null +++ b/releasenotes/notes/fix-gate-direction-calibration-c51202358d86e18f.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + The :class:`.GateDirection` transpiler pass will no longer reject gates that have been given + explicit calibrations, but do not exist in the generic coupling map or target. diff --git a/test/python/transpiler/test_gate_direction.py b/test/python/transpiler/test_gate_direction.py index 3ccb5aa958f4..06c1d814b147 100644 --- a/test/python/transpiler/test_gate_direction.py +++ b/test/python/transpiler/test_gate_direction.py @@ -17,8 +17,8 @@ import ddt -from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit -from qiskit.circuit import Parameter +from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit, pulse +from qiskit.circuit import Parameter, Gate from qiskit.circuit.library import ( CXGate, CZGate, @@ -439,6 +439,33 @@ def test_target_control_flow(self): pass_ = GateDirection(None, target) self.assertEqual(pass_(circuit), expected) + def test_allows_calibrated_gates_coupling_map(self): + """Test that the gate direction pass allows a gate that's got a calibration to pass through + without error.""" + cm = CouplingMap([(1, 0)]) + + gate = Gate("my_2q_gate", 2, []) + circuit = QuantumCircuit(2) + circuit.append(gate, (0, 1)) + circuit.add_calibration(gate, (0, 1), pulse.ScheduleBlock()) + + pass_ = GateDirection(cm) + self.assertEqual(pass_(circuit), circuit) + + def test_allows_calibrated_gates_target(self): + """Test that the gate direction pass allows a gate that's got a calibration to pass through + without error.""" + target = Target(num_qubits=2) + target.add_instruction(CXGate(), properties={(0, 1): None}) + + gate = Gate("my_2q_gate", 2, []) + circuit = QuantumCircuit(2) + circuit.append(gate, (0, 1)) + circuit.add_calibration(gate, (0, 1), pulse.ScheduleBlock()) + + pass_ = GateDirection(None, target) + self.assertEqual(pass_(circuit), circuit) + if __name__ == "__main__": unittest.main()