From f7ff14b853772b080673e1ddb38151493f7450bb Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 22 Nov 2022 18:52:49 +0000 Subject: [PATCH 1/3] Fix C3SXGate roundtrip in OpenQASM 2 Qiskit and its version of `qelib1.inc` give `C3SXGate` different names: `c3sx` and `c3sqrtx` respectively. At this point, changing either of these two names is likely to cause user pain, and it should not be difficult for the OpenQASM 2 exporter to simply know about this remapping, like it knows about other standard gate mappings. In practice, the structure of the exporter makes this rather difficult, but we need not expose that complexity to users. --- qiskit/circuit/library/standard_gates/x.py | 14 +++ qiskit/circuit/quantumcircuit.py | 2 +- qiskit/converters/ast_to_dag.py | 114 +++++++----------- .../fix-qasm2-c3sxgate-47171c9d17876219.yaml | 5 + test/python/circuit/test_circuit_qasm.py | 17 +++ 5 files changed, 79 insertions(+), 73 deletions(-) create mode 100644 releasenotes/notes/fix-qasm2-c3sxgate-47171c9d17876219.yaml diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index cc6eebc2f96e..2ebee778683e 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -569,6 +569,20 @@ def _define(self): self.definition = qc + def qasm(self): + # Gross hack to override the Qiskit name with the name this gate has in Terra's version of + # 'qelib1.inc'. In general, the larger exporter mechanism should know about this to do the + # mapping itself, but right now that's not possible without a complete rewrite of the OQ2 + # exporter code (low priority), or we would need to modify 'qelib1.inc' which would be + # needlessly disruptive this late in OQ2's lifecycle. The current OQ2 exporter _always_ + # outputs the `include 'qelib1.inc' line. ---Jake, 2022-11-21. + try: + old_name = self.name + self.name = "c3sqrtx" + return super().qasm() + finally: + self.name = old_name + class C3XGate(ControlledGate): r"""The X gate controlled on 3 qubits. diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 8a3201bd6837..61c77c0bd9b0 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -1638,7 +1638,7 @@ def qasm( "rccx", "rc3x", "c3x", - "c3sx", + "c3sx", # This is the Qiskit gate name, but the qelib1.inc name is 'c3sqrtx'. "c4x", ] diff --git a/qiskit/converters/ast_to_dag.py b/qiskit/converters/ast_to_dag.py index b8d99603378e..852bd22d669d 100644 --- a/qiskit/converters/ast_to_dag.py +++ b/qiskit/converters/ast_to_dag.py @@ -25,42 +25,7 @@ from qiskit.circuit.reset import Reset from qiskit.circuit.barrier import Barrier from qiskit.circuit.delay import Delay -from qiskit.circuit.library.standard_gates.x import CCXGate -from qiskit.circuit.library.standard_gates.swap import CSwapGate -from qiskit.circuit.library.standard_gates.x import CXGate -from qiskit.circuit.library.standard_gates.y import CYGate -from qiskit.circuit.library.standard_gates.z import CZGate -from qiskit.circuit.library.standard_gates.swap import SwapGate -from qiskit.circuit.library.standard_gates.h import HGate -from qiskit.circuit.library.standard_gates.i import IGate -from qiskit.circuit.library.standard_gates.s import SGate -from qiskit.circuit.library.standard_gates.s import SdgGate -from qiskit.circuit.library.standard_gates.sx import SXGate -from qiskit.circuit.library.standard_gates.sx import SXdgGate -from qiskit.circuit.library.standard_gates.t import TGate -from qiskit.circuit.library.standard_gates.t import TdgGate -from qiskit.circuit.library.standard_gates.p import PhaseGate -from qiskit.circuit.library.standard_gates.u1 import U1Gate -from qiskit.circuit.library.standard_gates.u2 import U2Gate -from qiskit.circuit.library.standard_gates.u3 import U3Gate -from qiskit.circuit.library.standard_gates.u import UGate -from qiskit.circuit.library.standard_gates.x import XGate -from qiskit.circuit.library.standard_gates.y import YGate -from qiskit.circuit.library.standard_gates.z import ZGate -from qiskit.circuit.library.standard_gates.rx import RXGate -from qiskit.circuit.library.standard_gates.ry import RYGate -from qiskit.circuit.library.standard_gates.rz import RZGate -from qiskit.circuit.library.standard_gates.rxx import RXXGate -from qiskit.circuit.library.standard_gates.rzz import RZZGate -from qiskit.circuit.library.standard_gates.p import CPhaseGate -from qiskit.circuit.library.standard_gates.u import CUGate -from qiskit.circuit.library.standard_gates.u1 import CU1Gate -from qiskit.circuit.library.standard_gates.u3 import CU3Gate -from qiskit.circuit.library.standard_gates.h import CHGate -from qiskit.circuit.library.standard_gates.rx import CRXGate -from qiskit.circuit.library.standard_gates.ry import CRYGate -from qiskit.circuit.library.standard_gates.rz import CRZGate -from qiskit.circuit.library.standard_gates.sx import CSXGate +from qiskit.circuit import library as lib def ast_to_dag(ast): @@ -105,43 +70,48 @@ class AstInterpreter: """Interprets an OpenQASM by expanding subroutines and unrolling loops.""" standard_extension = { - "u1": U1Gate, - "u2": U2Gate, - "u3": U3Gate, - "u": UGate, - "p": PhaseGate, - "x": XGate, - "y": YGate, - "z": ZGate, - "t": TGate, - "tdg": TdgGate, - "s": SGate, - "sdg": SdgGate, - "sx": SXGate, - "sxdg": SXdgGate, - "swap": SwapGate, - "rx": RXGate, - "rxx": RXXGate, - "ry": RYGate, - "rz": RZGate, - "rzz": RZZGate, - "id": IGate, - "h": HGate, - "cx": CXGate, - "cy": CYGate, - "cz": CZGate, - "ch": CHGate, - "crx": CRXGate, - "cry": CRYGate, - "crz": CRZGate, - "csx": CSXGate, - "cu1": CU1Gate, - "cp": CPhaseGate, - "cu": CUGate, - "cu3": CU3Gate, - "ccx": CCXGate, - "cswap": CSwapGate, + "u1": lib.U1Gate, + "u2": lib.U2Gate, + "u3": lib.U3Gate, + "u": lib.UGate, + "p": lib.PhaseGate, + "x": lib.XGate, + "y": lib.YGate, + "z": lib.ZGate, + "t": lib.TGate, + "tdg": lib.TdgGate, + "s": lib.SGate, + "sdg": lib.SdgGate, + "sx": lib.SXGate, + "sxdg": lib.SXdgGate, + "swap": lib.SwapGate, + "rx": lib.RXGate, + "rxx": lib.RXXGate, + "ry": lib.RYGate, + "rz": lib.RZGate, + "rzz": lib.RZZGate, + "id": lib.IGate, + "h": lib.HGate, + "cx": lib.CXGate, + "cy": lib.CYGate, + "cz": lib.CZGate, + "ch": lib.CHGate, + "crx": lib.CRXGate, + "cry": lib.CRYGate, + "crz": lib.CRZGate, + "csx": lib.CSXGate, + "cu1": lib.CU1Gate, + "cp": lib.CPhaseGate, + "cu": lib.CUGate, + "cu3": lib.CU3Gate, + "ccx": lib.CCXGate, + "cswap": lib.CSwapGate, "delay": Delay, + "rccx": lib.RCCXGate, + "rc3x": lib.RC3XGate, + "c3x": lib.C3XGate, + "c3sqrtx": lib.C3SXGate, + "c4x": lib.C4XGate, } def __init__(self, dag): diff --git a/releasenotes/notes/fix-qasm2-c3sxgate-47171c9d17876219.yaml b/releasenotes/notes/fix-qasm2-c3sxgate-47171c9d17876219.yaml new file mode 100644 index 000000000000..94b3ebd4da90 --- /dev/null +++ b/releasenotes/notes/fix-qasm2-c3sxgate-47171c9d17876219.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Circuits containing :class:`.C3SXGate` can now be output and read in again safely from the + OpenQASM 2.0 exporter (:meth:`.QuantumCircuit.qasm`) and parser (:meth:`.QuantumCircuit.from_qasm_str`). diff --git a/test/python/circuit/test_circuit_qasm.py b/test/python/circuit/test_circuit_qasm.py index 7c1d9c078c88..28e5bf235e3e 100644 --- a/test/python/circuit/test_circuit_qasm.py +++ b/test/python/circuit/test_circuit_qasm.py @@ -18,6 +18,7 @@ from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit from qiskit.test import QiskitTestCase from qiskit.circuit import Parameter, Qubit, Clbit +from qiskit.circuit.library import C3SXGate from qiskit.qasm.exceptions import QasmError # Regex pattern to match valid OpenQASM identifiers @@ -246,6 +247,22 @@ 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 + ('c3sx') to the name in Qiskit's version of qelib1.inc ('c3sqrtx') gate, which can lead to + resolution issues.""" + qc = QuantumCircuit(4) + qc.append(C3SXGate(), qc.qubits, []) + qasm = qc.qasm() + expected = """OPENQASM 2.0; +include "qelib1.inc"; +qreg q[4]; +c3sqrtx q[0],q[1],q[2],q[3]; +""" + self.assertEqual(qasm, expected) + parsed = QuantumCircuit.from_qasm_str(qasm) + self.assertIsInstance(parsed.data[0].operation, C3SXGate) + def test_unbound_circuit_raises(self): """Test circuits with unbound parameters raises.""" qc = QuantumCircuit(1) From fb9a8b9bc165a3ee83890e192f6826c6078b3336 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 22 Nov 2022 19:32:37 +0000 Subject: [PATCH 2/3] Correct namespace lookup --- qiskit/converters/ast_to_dag.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/converters/ast_to_dag.py b/qiskit/converters/ast_to_dag.py index 852bd22d669d..45bb3fddeba1 100644 --- a/qiskit/converters/ast_to_dag.py +++ b/qiskit/converters/ast_to_dag.py @@ -236,7 +236,7 @@ def _process_cnot(self, node): ) maxidx = max([len(id0), len(id1)]) for idx in range(maxidx): - cx_gate = CXGate() + cx_gate = lib.CXGate() cx_gate.condition = self.condition if len(id0) > 1 and len(id1) > 1: self.dag.apply_operation_back(cx_gate, [id0[idx], id1[idx]], []) From 79a392a198c03cefb32f6a76341b2f0f3eb61ad3 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Wed, 23 Nov 2022 15:01:39 +0000 Subject: [PATCH 3/3] Break import cycle --- qiskit/converters/ast_to_dag.py | 86 ++++++++++++++++----------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/qiskit/converters/ast_to_dag.py b/qiskit/converters/ast_to_dag.py index 45bb3fddeba1..2511ba97c029 100644 --- a/qiskit/converters/ast_to_dag.py +++ b/qiskit/converters/ast_to_dag.py @@ -25,7 +25,7 @@ from qiskit.circuit.reset import Reset from qiskit.circuit.barrier import Barrier from qiskit.circuit.delay import Delay -from qiskit.circuit import library as lib +from qiskit.circuit.library import standard_gates as std def ast_to_dag(ast): @@ -70,48 +70,48 @@ class AstInterpreter: """Interprets an OpenQASM by expanding subroutines and unrolling loops.""" standard_extension = { - "u1": lib.U1Gate, - "u2": lib.U2Gate, - "u3": lib.U3Gate, - "u": lib.UGate, - "p": lib.PhaseGate, - "x": lib.XGate, - "y": lib.YGate, - "z": lib.ZGate, - "t": lib.TGate, - "tdg": lib.TdgGate, - "s": lib.SGate, - "sdg": lib.SdgGate, - "sx": lib.SXGate, - "sxdg": lib.SXdgGate, - "swap": lib.SwapGate, - "rx": lib.RXGate, - "rxx": lib.RXXGate, - "ry": lib.RYGate, - "rz": lib.RZGate, - "rzz": lib.RZZGate, - "id": lib.IGate, - "h": lib.HGate, - "cx": lib.CXGate, - "cy": lib.CYGate, - "cz": lib.CZGate, - "ch": lib.CHGate, - "crx": lib.CRXGate, - "cry": lib.CRYGate, - "crz": lib.CRZGate, - "csx": lib.CSXGate, - "cu1": lib.CU1Gate, - "cp": lib.CPhaseGate, - "cu": lib.CUGate, - "cu3": lib.CU3Gate, - "ccx": lib.CCXGate, - "cswap": lib.CSwapGate, + "u1": std.U1Gate, + "u2": std.U2Gate, + "u3": std.U3Gate, + "u": std.UGate, + "p": std.PhaseGate, + "x": std.XGate, + "y": std.YGate, + "z": std.ZGate, + "t": std.TGate, + "tdg": std.TdgGate, + "s": std.SGate, + "sdg": std.SdgGate, + "sx": std.SXGate, + "sxdg": std.SXdgGate, + "swap": std.SwapGate, + "rx": std.RXGate, + "rxx": std.RXXGate, + "ry": std.RYGate, + "rz": std.RZGate, + "rzz": std.RZZGate, + "id": std.IGate, + "h": std.HGate, + "cx": std.CXGate, + "cy": std.CYGate, + "cz": std.CZGate, + "ch": std.CHGate, + "crx": std.CRXGate, + "cry": std.CRYGate, + "crz": std.CRZGate, + "csx": std.CSXGate, + "cu1": std.CU1Gate, + "cp": std.CPhaseGate, + "cu": std.CUGate, + "cu3": std.CU3Gate, + "ccx": std.CCXGate, + "cswap": std.CSwapGate, "delay": Delay, - "rccx": lib.RCCXGate, - "rc3x": lib.RC3XGate, - "c3x": lib.C3XGate, - "c3sqrtx": lib.C3SXGate, - "c4x": lib.C4XGate, + "rccx": std.RCCXGate, + "rc3x": std.RC3XGate, + "c3x": std.C3XGate, + "c3sqrtx": std.C3SXGate, + "c4x": std.C4XGate, } def __init__(self, dag): @@ -236,7 +236,7 @@ def _process_cnot(self, node): ) maxidx = max([len(id0), len(id1)]) for idx in range(maxidx): - cx_gate = lib.CXGate() + cx_gate = std.CXGate() cx_gate.condition = self.condition if len(id0) > 1 and len(id1) > 1: self.dag.apply_operation_back(cx_gate, [id0[idx], id1[idx]], [])