diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index f35864d2d60d..abdc18958fa8 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -43,7 +43,6 @@ from qiskit.circuit.instruction import Instruction from qiskit.circuit.gate import Gate from qiskit.circuit.parameter import Parameter -from qiskit.qasm.qasm import Qasm from qiskit.qasm.exceptions import QasmError from qiskit.circuit.exceptions import CircuitError from .parameterexpression import ParameterExpression, ParameterValueType @@ -2504,11 +2503,23 @@ def from_qasm_file(path: str) -> "QuantumCircuit": Args: path (str): Path to the file for a QASM program + Return: QuantumCircuit: The QuantumCircuit object for the input QASM + + See also: + :func:`.qasm2.load`: the complete interface to the OpenQASM 2 importer. """ - qasm = Qasm(filename=path) - return _circuit_from_qasm(qasm) + # pylint: disable=cyclic-import + from qiskit import qasm2 + + return qasm2.load( + path, + include_path=qasm2.LEGACY_INCLUDE_PATH, + custom_instructions=qasm2.LEGACY_CUSTOM_INSTRUCTIONS, + custom_classical=qasm2.LEGACY_CUSTOM_CLASSICAL, + strict=False, + ) @staticmethod def from_qasm_str(qasm_str: str) -> "QuantumCircuit": @@ -2518,9 +2529,20 @@ def from_qasm_str(qasm_str: str) -> "QuantumCircuit": qasm_str (str): A QASM program string Return: QuantumCircuit: The QuantumCircuit object for the input QASM + + See also: + :func:`.qasm2.loads`: the complete interface to the OpenQASM 2 importer. """ - qasm = Qasm(data=qasm_str) - return _circuit_from_qasm(qasm) + # pylint: disable=cyclic-import + from qiskit import qasm2 + + return qasm2.loads( + qasm_str, + include_path=qasm2.LEGACY_INCLUDE_PATH, + custom_instructions=qasm2.LEGACY_CUSTOM_INSTRUCTIONS, + custom_classical=qasm2.LEGACY_CUSTOM_CLASSICAL, + strict=False, + ) @property def global_phase(self) -> ParameterValueType: @@ -4858,16 +4880,6 @@ def qubit_stop_time(self, *qubits: Union[Qubit, int]) -> float: return 0 # If there are no instructions over bits -def _circuit_from_qasm(qasm: Qasm) -> "QuantumCircuit": - # pylint: disable=cyclic-import - from qiskit.converters import ast_to_dag - from qiskit.converters import dag_to_circuit - - ast = qasm.parse() - dag = ast_to_dag(ast) - return dag_to_circuit(dag, copy_operations=False) - - def _standard_compare(value1, value2): if value1 < value2: return -1 diff --git a/releasenotes/notes/new-circuit-qasm2-methods-b1a06ee2859e2cce.yaml b/releasenotes/notes/new-circuit-qasm2-methods-b1a06ee2859e2cce.yaml new file mode 100644 index 000000000000..36b4cba14f16 --- /dev/null +++ b/releasenotes/notes/new-circuit-qasm2-methods-b1a06ee2859e2cce.yaml @@ -0,0 +1,18 @@ +--- +upgrade: + - | + The OpenQASM 2 constructor methods on :class:`.QuantumCircuit` + (:meth:`~.QuantumCircuit.from_qasm_str` and :meth:`~.QuantumCircuit.from_qasm_file`) have been + switched to use the Rust-based parser added in Qiskit Terra 0.24. This should result in + significantly faster parsing times (10 times or more is not uncommon) and massively reduced + intermediate memory usage. + + The :class:`.QuantumCircuit` methods are kept with the same interface for continuity; the + preferred way to access the OpenQASM 2 importer is to use :func:`.qasm2.load` and + :func:`.qasm2.loads`, which offer an expanded interface to control the parsing and construction. +fixes: + - | + The OpenQASM 2 circuit-constructor methods (:meth:`.QuantumCircuit.from_qasm_str` and + :meth:`~.QuantumCircuit.from_qasm_file`) will no longer error when encountering a ``gate`` + definition that contains ``U`` or ``CX`` instructions. See `#5536 + `__. diff --git a/test/python/circuit/test_circuit_load_from_qasm.py b/test/python/qasm2/test_circuit_methods.py similarity index 97% rename from test/python/circuit/test_circuit_load_from_qasm.py rename to test/python/qasm2/test_circuit_methods.py index 9c264b8782e4..fdd30211b73f 100644 --- a/test/python/circuit/test_circuit_load_from_qasm.py +++ b/test/python/qasm2/test_circuit_methods.py @@ -120,14 +120,6 @@ def test_fail_qasm_file(self): """ self.assertRaises(QiskitError, QuantumCircuit.from_qasm_file, "") - def test_fail_qasm_string(self): - """ - Test fail_qasm_string. - - If all is correct we should get a QiskitError - """ - self.assertRaises(QiskitError, QuantumCircuit.from_qasm_str, "") - def test_qasm_text(self): """ Test qasm_text and get_circuit. @@ -492,6 +484,21 @@ def test_from_qasm_str_delay(self): expected.delay(172, qr[0]) self.assertEqualUnroll("u", circuit, expected) + def test_definition_with_u_cx(self): + """Test that gate-definition bodies can use U and CX.""" + qasm_string = """ +OPENQASM 2.0; +gate bell q0, q1 { U(pi/2, 0, pi) q0; CX q0, q1; } +qreg q[2]; +bell q[0], q[1]; +""" + circuit = QuantumCircuit.from_qasm_str(qasm_string) + qr = QuantumRegister(2, "q") + expected = QuantumCircuit(qr) + expected.h(0) + expected.cx(0, 1) + self.assertEqualUnroll(["u", "cx"], circuit, expected) + def assertEqualUnroll(self, basis, circuit, expected): """Compares the dags after unrolling to basis""" circuit_dag = circuit_to_dag(circuit) diff --git a/test/python/qasm2/test_legacy_importer.py b/test/python/qasm2/test_legacy_importer.py new file mode 100644 index 000000000000..070646b1d9ea --- /dev/null +++ b/test/python/qasm2/test_legacy_importer.py @@ -0,0 +1,508 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + + +"""Test cases for the legacy OpenQASM 2 parser.""" + +# pylint: disable=missing-function-docstring + + +import os + +from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister +from qiskit.circuit import Gate, Parameter +from qiskit.converters import ast_to_dag, dag_to_circuit +from qiskit.exceptions import QiskitError +from qiskit.qasm import Qasm +from qiskit.test import QiskitTestCase +from qiskit.transpiler.passes import Unroller +from qiskit.converters.circuit_to_dag import circuit_to_dag + + +def from_qasm_str(qasm_str): + return dag_to_circuit(ast_to_dag(Qasm(data=qasm_str).parse())) + + +def from_qasm_file(path): + return dag_to_circuit(ast_to_dag(Qasm(filename=path).parse())) + + +class LoadFromQasmTest(QiskitTestCase): + """Test circuit.from_qasm_* set of methods.""" + + def setUp(self): + super().setUp() + self.qasm_file_name = "entangled_registers.qasm" + self.qasm_dir = os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm" + ) + self.qasm_file_path = os.path.join(self.qasm_dir, self.qasm_file_name) + + def test_qasm_file(self): + """ + Test qasm_file and get_circuit. + + If all is correct we should get the qasm file loaded in _qasm_file_path + """ + q_circuit = from_qasm_file(self.qasm_file_path) + qr_a = QuantumRegister(4, "a") + qr_b = QuantumRegister(4, "b") + cr_c = ClassicalRegister(4, "c") + cr_d = ClassicalRegister(4, "d") + q_circuit_2 = QuantumCircuit(qr_a, qr_b, cr_c, cr_d) + q_circuit_2.h(qr_a) + q_circuit_2.cx(qr_a, qr_b) + q_circuit_2.barrier(qr_a) + q_circuit_2.barrier(qr_b) + q_circuit_2.measure(qr_a, cr_c) + q_circuit_2.measure(qr_b, cr_d) + self.assertEqual(q_circuit, q_circuit_2) + + def test_loading_all_qelib1_gates(self): + """Test setting up a circuit with all gates defined in qiskit/qasm/libs/qelib1.inc.""" + from qiskit.circuit.library import U1Gate, U2Gate, U3Gate, CU1Gate, CU3Gate, UGate + + all_gates_qasm = os.path.join(self.qasm_dir, "all_gates.qasm") + qasm_circuit = from_qasm_file(all_gates_qasm) + + ref_circuit = QuantumCircuit(3, 3) + + # abstract gates (legacy) + ref_circuit.append(UGate(0.2, 0.1, 0.6), [0]) + ref_circuit.cx(0, 1) + # the hardware primitives + ref_circuit.append(U3Gate(0.2, 0.1, 0.6), [0]) + ref_circuit.append(U2Gate(0.1, 0.6), [0]) + ref_circuit.append(U1Gate(0.6), [0]) + ref_circuit.id(0) + ref_circuit.cx(0, 1) + # the standard single qubit gates + ref_circuit.u(0.2, 0.1, 0.6, 0) + ref_circuit.p(0.6, 0) + ref_circuit.x(0) + ref_circuit.y(0) + ref_circuit.z(0) + ref_circuit.h(0) + ref_circuit.s(0) + ref_circuit.t(0) + ref_circuit.sdg(0) + ref_circuit.tdg(0) + ref_circuit.sx(0) + ref_circuit.sxdg(0) + # the standard rotations + ref_circuit.rx(0.1, 0) + ref_circuit.ry(0.1, 0) + ref_circuit.rz(0.1, 0) + # the barrier + ref_circuit.barrier() + # the standard user-defined gates + ref_circuit.swap(0, 1) + ref_circuit.cswap(0, 1, 2) + ref_circuit.cy(0, 1) + ref_circuit.cz(0, 1) + ref_circuit.ch(0, 1) + ref_circuit.csx(0, 1) + ref_circuit.append(CU1Gate(0.6), [0, 1]) + ref_circuit.append(CU3Gate(0.2, 0.1, 0.6), [0, 1]) + ref_circuit.cp(0.6, 0, 1) + ref_circuit.cu(0.2, 0.1, 0.6, 0, 0, 1) + ref_circuit.ccx(0, 1, 2) + ref_circuit.crx(0.6, 0, 1) + ref_circuit.cry(0.6, 0, 1) + ref_circuit.crz(0.6, 0, 1) + ref_circuit.rxx(0.2, 0, 1) + ref_circuit.rzz(0.2, 0, 1) + ref_circuit.measure([0, 1, 2], [0, 1, 2]) + + self.assertEqual(qasm_circuit, ref_circuit) + + def test_fail_qasm_file(self): + """ + Test fail_qasm_file. + + If all is correct we should get a QiskitError + """ + self.assertRaises(QiskitError, from_qasm_file, "") + + def test_qasm_text(self): + """ + Test qasm_text and get_circuit. + + If all is correct we should get the qasm file loaded from the string + """ + qasm_string = "// A simple 8 qubit example\nOPENQASM 2.0;\n" + qasm_string += 'include "qelib1.inc";\nqreg a[4];\n' + qasm_string += "qreg b[4];\ncreg c[4];\ncreg d[4];\nh a;\ncx a, b;\n" + qasm_string += "barrier a;\nbarrier b;\nmeasure a[0]->c[0];\n" + qasm_string += "measure a[1]->c[1];\nmeasure a[2]->c[2];\n" + qasm_string += "measure a[3]->c[3];\nmeasure b[0]->d[0];\n" + qasm_string += "measure b[1]->d[1];\nmeasure b[2]->d[2];\n" + qasm_string += "measure b[3]->d[3];" + q_circuit = from_qasm_str(qasm_string) + + qr_a = QuantumRegister(4, "a") + qr_b = QuantumRegister(4, "b") + cr_c = ClassicalRegister(4, "c") + cr_d = ClassicalRegister(4, "d") + ref = QuantumCircuit(qr_a, qr_b, cr_c, cr_d) + ref.h(qr_a[3]) + ref.cx(qr_a[3], qr_b[3]) + ref.h(qr_a[2]) + ref.cx(qr_a[2], qr_b[2]) + ref.h(qr_a[1]) + ref.cx(qr_a[1], qr_b[1]) + ref.h(qr_a[0]) + ref.cx(qr_a[0], qr_b[0]) + ref.barrier(qr_b) + ref.measure(qr_b, cr_d) + ref.barrier(qr_a) + ref.measure(qr_a, cr_c) + + self.assertEqual(len(q_circuit.cregs), 2) + self.assertEqual(len(q_circuit.qregs), 2) + self.assertEqual(q_circuit, ref) + + def test_qasm_text_conditional(self): + """ + Test qasm_text and get_circuit when conditionals are present. + """ + qasm_string = ( + "\n".join( + [ + "OPENQASM 2.0;", + 'include "qelib1.inc";', + "qreg q[1];", + "creg c0[4];", + "creg c1[4];", + "x q[0];", + "if(c1==4) x q[0];", + ] + ) + + "\n" + ) + q_circuit = from_qasm_str(qasm_string) + + qr = QuantumRegister(1, "q") + cr0 = ClassicalRegister(4, "c0") + cr1 = ClassicalRegister(4, "c1") + ref = QuantumCircuit(qr, cr0, cr1) + ref.x(qr[0]) + ref.x(qr[0]).c_if(cr1, 4) + + self.assertEqual(len(q_circuit.cregs), 2) + self.assertEqual(len(q_circuit.qregs), 1) + self.assertEqual(q_circuit, ref) + + def test_opaque_gate(self): + """ + Test parse an opaque gate + + See https://github.com/Qiskit/qiskit-terra/issues/1566. + """ + + qasm_string = ( + "\n".join( + [ + "OPENQASM 2.0;", + 'include "qelib1.inc";', + "opaque my_gate(theta,phi,lambda) a,b;", + "qreg q[3];", + "my_gate(1,2,3) q[1],q[2];", + ] + ) + + "\n" + ) + circuit = from_qasm_str(qasm_string) + + qr = QuantumRegister(3, "q") + expected = QuantumCircuit(qr) + expected.append(Gate(name="my_gate", num_qubits=2, params=[1, 2, 3]), [qr[1], qr[2]]) + + self.assertEqual(circuit, expected) + + def test_qasm_example_file(self): + """Loads qasm/example.qasm.""" + qasm_filename = os.path.join(self.qasm_dir, "example.qasm") + expected_circuit = from_qasm_str( + "\n".join( + [ + "OPENQASM 2.0;", + 'include "qelib1.inc";', + "qreg q[3];", + "qreg r[3];", + "creg c[3];", + "creg d[3];", + "h q[2];", + "cx q[2],r[2];", + "measure r[2] -> d[2];", + "h q[1];", + "cx q[1],r[1];", + "measure r[1] -> d[1];", + "h q[0];", + "cx q[0],r[0];", + "measure r[0] -> d[0];", + "barrier q[0],q[1],q[2];", + "measure q[2] -> c[2];", + "measure q[1] -> c[1];", + "measure q[0] -> c[0];", + ] + ) + + "\n" + ) + + q_circuit = from_qasm_file(qasm_filename) + + self.assertEqual(q_circuit, expected_circuit) + self.assertEqual(len(q_circuit.cregs), 2) + self.assertEqual(len(q_circuit.qregs), 2) + + def test_qasm_qas_string_order(self): + """Test that gates are returned in qasm in ascending order.""" + expected_qasm = ( + "\n".join( + [ + "OPENQASM 2.0;", + 'include "qelib1.inc";', + "qreg q[3];", + "h q[0];", + "h q[1];", + "h q[2];", + ] + ) + + "\n" + ) + qasm_string = """OPENQASM 2.0; + include "qelib1.inc"; + qreg q[3]; + h q;""" + q_circuit = from_qasm_str(qasm_string) + + self.assertEqual(q_circuit.qasm(), expected_qasm) + + def test_from_qasm_str_custom_gate1(self): + """Test load custom gates (simple case)""" + qasm_string = """OPENQASM 2.0; + include "qelib1.inc"; + gate rinv q {sdg q; h q; sdg q; h q; } + qreg qr[1]; + rinv qr[0];""" + circuit = from_qasm_str(qasm_string) + + rinv_q = QuantumRegister(1, name="q") + rinv_gate = QuantumCircuit(rinv_q, name="rinv") + rinv_gate.sdg(rinv_q) + rinv_gate.h(rinv_q) + rinv_gate.sdg(rinv_q) + rinv_gate.h(rinv_q) + rinv = rinv_gate.to_instruction() + qr = QuantumRegister(1, name="qr") + expected = QuantumCircuit(qr, name="circuit") + expected.append(rinv, [qr[0]]) + + self.assertEqualUnroll(["sdg", "h"], circuit, expected) + + def test_from_qasm_str_custom_gate2(self): + """Test load custom gates (no so simple case, different bit order) + See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-551307250 + """ + qasm_string = """OPENQASM 2.0; + include "qelib1.inc"; + gate swap2 a,b { + cx a,b; + cx b,a; // different bit order + cx a,b; + } + qreg qr[3]; + swap2 qr[0], qr[1]; + swap2 qr[1], qr[2];""" + circuit = from_qasm_str(qasm_string) + + ab_args = QuantumRegister(2, name="ab") + swap_gate = QuantumCircuit(ab_args, name="swap2") + swap_gate.cx(ab_args[0], ab_args[1]) + swap_gate.cx(ab_args[1], ab_args[0]) + swap_gate.cx(ab_args[0], ab_args[1]) + swap = swap_gate.to_instruction() + + qr = QuantumRegister(3, name="qr") + expected = QuantumCircuit(qr, name="circuit") + expected.append(swap, [qr[0], qr[1]]) + expected.append(swap, [qr[1], qr[2]]) + + self.assertEqualUnroll(["cx"], expected, circuit) + + def test_from_qasm_str_custom_gate3(self): + """Test load custom gates (no so simple case, different bit count) + See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-551307250 + """ + qasm_string = """OPENQASM 2.0; + include "qelib1.inc"; + gate cswap2 a,b,c + { + cx c,b; // different bit count + ccx a,b,c; //previously defined gate + cx c,b; + } + qreg qr[3]; + cswap2 qr[1], qr[0], qr[2];""" + circuit = from_qasm_str(qasm_string) + + abc_args = QuantumRegister(3, name="abc") + cswap_gate = QuantumCircuit(abc_args, name="cswap2") + cswap_gate.cx(abc_args[2], abc_args[1]) + cswap_gate.ccx(abc_args[0], abc_args[1], abc_args[2]) + cswap_gate.cx(abc_args[2], abc_args[1]) + cswap = cswap_gate.to_instruction() + + qr = QuantumRegister(3, name="qr") + expected = QuantumCircuit(qr, name="circuit") + expected.append(cswap, [qr[1], qr[0], qr[2]]) + + self.assertEqualUnroll(["cx", "h", "tdg", "t"], circuit, expected) + + def test_from_qasm_str_custom_gate4(self): + """Test load custom gates (parameterized) + See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-551307250 + """ + qasm_string = """OPENQASM 2.0; + include "qelib1.inc"; + gate my_gate(phi,lambda) q {u(1.5707963267948966,phi,lambda) q;} + qreg qr[1]; + my_gate(pi, pi) qr[0];""" + circuit = from_qasm_str(qasm_string) + + my_gate_circuit = QuantumCircuit(1, name="my_gate") + phi = Parameter("phi") + lam = Parameter("lambda") + my_gate_circuit.u(1.5707963267948966, phi, lam, 0) + my_gate = my_gate_circuit.to_gate() + + qr = QuantumRegister(1, name="qr") + expected = QuantumCircuit(qr, name="circuit") + expected.append(my_gate, [qr[0]]) + expected = expected.bind_parameters({phi: 3.141592653589793, lam: 3.141592653589793}) + + self.assertEqualUnroll("u", circuit, expected) + + def test_from_qasm_str_custom_gate5(self): + """Test load custom gates (parameterized, with biop and constant) + See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-551307250 + """ + qasm_string = """OPENQASM 2.0; + include "qelib1.inc"; + gate my_gate(phi,lambda) q {u(pi/2,phi,lambda) q;} // biop with pi + qreg qr[1]; + my_gate(pi, pi) qr[0];""" + circuit = from_qasm_str(qasm_string) + + my_gate_circuit = QuantumCircuit(1, name="my_gate") + phi = Parameter("phi") + lam = Parameter("lambda") + my_gate_circuit.u(1.5707963267948966, phi, lam, 0) + my_gate = my_gate_circuit.to_gate() + + qr = QuantumRegister(1, name="qr") + expected = QuantumCircuit(qr, name="circuit") + expected.append(my_gate, [qr[0]]) + expected = expected.bind_parameters({phi: 3.141592653589793, lam: 3.141592653589793}) + + self.assertEqualUnroll("u", circuit, expected) + + def test_from_qasm_str_custom_gate6(self): + """Test load custom gates (parameters used in expressions) + See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-591668924 + """ + qasm_string = """OPENQASM 2.0; + include "qelib1.inc"; + gate my_gate(phi,lambda) q + {rx(phi+pi) q; ry(lambda/2) q;} // parameters used in expressions + qreg qr[1]; + my_gate(pi, pi) qr[0];""" + circuit = from_qasm_str(qasm_string) + + my_gate_circuit = QuantumCircuit(1, name="my_gate") + phi = Parameter("phi") + lam = Parameter("lambda") + my_gate_circuit.rx(phi + 3.141592653589793, 0) + my_gate_circuit.ry(lam / 2, 0) + my_gate = my_gate_circuit.to_gate() + + qr = QuantumRegister(1, name="qr") + expected = QuantumCircuit(qr, name="circuit") + expected.append(my_gate, [qr[0]]) + expected = expected.bind_parameters({phi: 3.141592653589793, lam: 3.141592653589793}) + + self.assertEqualUnroll(["rx", "ry"], circuit, expected) + + def test_from_qasm_str_custom_gate7(self): + """Test load custom gates (build in functions) + See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-592208951 + """ + qasm_string = """OPENQASM 2.0; + include "qelib1.inc"; + gate my_gate(phi,lambda) q + {u(asin(cos(phi)/2), phi+pi, lambda/2) q;} // build func + qreg qr[1]; + my_gate(pi, pi) qr[0];""" + circuit = from_qasm_str(qasm_string) + + qr = QuantumRegister(1, name="qr") + expected = QuantumCircuit(qr, name="circuit") + expected.u(-0.5235987755982988, 6.283185307179586, 1.5707963267948966, qr[0]) + self.assertEqualUnroll("u", circuit, expected) + + def test_from_qasm_str_nested_custom_gate(self): + """Test chain of custom gates + See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-592261942 + """ + qasm_string = """OPENQASM 2.0; + include "qelib1.inc"; + gate my_other_gate(phi,lambda) q + {u(asin(cos(phi)/2), phi+pi, lambda/2) q;} + gate my_gate(phi) r + {my_other_gate(phi, phi+pi) r;} + qreg qr[1]; + my_gate(pi) qr[0];""" + circuit = from_qasm_str(qasm_string) + + qr = QuantumRegister(1, name="qr") + expected = QuantumCircuit(qr, name="circuit") + expected.u(-0.5235987755982988, 6.283185307179586, 3.141592653589793, qr[0]) + self.assertEqualUnroll("u", circuit, expected) + + def test_from_qasm_str_delay(self): + """Test delay instruction/opaque-gate + See: https://github.com/Qiskit/qiskit-terra/issues/6510 + """ + qasm_string = """OPENQASM 2.0; + include "qelib1.inc"; + + opaque delay(time) q; + + qreg q[1]; + delay(172) q[0];""" + circuit = from_qasm_str(qasm_string) + + qr = QuantumRegister(1, name="q") + expected = QuantumCircuit(qr, name="circuit") + expected.delay(172, qr[0]) + self.assertEqualUnroll("u", circuit, expected) + + def assertEqualUnroll(self, basis, circuit, expected): + """Compares the dags after unrolling to basis""" + circuit_dag = circuit_to_dag(circuit) + expected_dag = circuit_to_dag(expected) + + circuit_result = Unroller(basis).run(circuit_dag) + expected_result = Unroller(basis).run(expected_dag) + + self.assertEqual(circuit_result, expected_result)