From 785404be2538243da7d78a7ce69dbfc7882d6586 Mon Sep 17 00:00:00 2001 From: Ville Bergholm Date: Thu, 27 Feb 2025 16:18:04 +0200 Subject: [PATCH 1/3] Bugfix: transpile() now accepts delay ops. --- CHANGELOG.rst | 6 +++ src/iqm/qiskit_iqm/iqm_transpilation.py | 4 +- tests/conftest.py | 54 ++++++++++++------------- tests/test_iqm_transpilation.py | 4 ++ 4 files changed, 39 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 626f68dd6..edc9e4a3d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,12 @@ Changelog ========= +Version 17.4 +============ + +* Bugfix: The ``delay`` operation is now accepted by the standard transpiler. + `#151 `_ + Version 17.3 ============ diff --git a/src/iqm/qiskit_iqm/iqm_transpilation.py b/src/iqm/qiskit_iqm/iqm_transpilation.py index 4d9b92df3..c839d12e4 100644 --- a/src/iqm/qiskit_iqm/iqm_transpilation.py +++ b/src/iqm/qiskit_iqm/iqm_transpilation.py @@ -88,7 +88,7 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: # angles results in fewest changes to the circuit. for qubit in node.qargs: rz_angles[dag.find_bit(qubit)[0]] = 0 - elif node.name == 'barrier': + elif node.name in {'barrier', 'delay'}: # TODO barriers are meant to restrict circuit optimization, so strictly speaking # we should output any accumulated ``rz_angles`` here as explicit z rotations (like # the final rz:s). However, ``rz_angles`` simply represents a choice of phases for the @@ -103,7 +103,7 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: pass # rz_angles are commute through CZ gates else: raise ValueError( - f'Unexpected operation {node.name} in circuit given to IQMOptimizeSingleQubitGates pass' + f"Unexpected operation '{node.name}' in circuit given to IQMOptimizeSingleQubitGates pass" ) if not self._drop_final_rz: diff --git a/tests/conftest.py b/tests/conftest.py index 6c195939d..86432729f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -45,15 +45,21 @@ def linear_3q_architecture_static(): ) +def _1q_loci(qubits: list[str]) -> tuple[tuple[str]]: + """One-qubit loci for the given qubits.""" + return tuple((q,) for q in qubits) + + @pytest.fixture def linear_3q_architecture(): + qubits = ['QB1', 'QB2', 'QB3'] return DynamicQuantumArchitecture( calibration_set_id=UUID('59478539-dcef-4b2e-80c8-122d7ec3fc89'), - qubits=['QB1', 'QB2', 'QB3'], + qubits=qubits, computational_resonators=[], gates={ 'prx': GateInfo( - implementations={'drag_gaussian': GateImplementationInfo(loci=(('QB1',), ('QB2',), ('QB3',)))}, + implementations={'drag_gaussian': GateImplementationInfo(loci=_1q_loci(qubits))}, default_implementation='drag_gaussian', override_default_implementation={}, ), @@ -63,7 +69,7 @@ def linear_3q_architecture(): override_default_implementation={}, ), 'measure': GateInfo( - implementations={'constant': GateImplementationInfo(loci=(('QB1',), ('QB2',), ('QB3',)))}, + implementations={'constant': GateImplementationInfo(loci=_1q_loci(qubits))}, default_implementation='constant', override_default_implementation={}, ), @@ -73,22 +79,19 @@ def linear_3q_architecture(): @pytest.fixture def adonis_architecture(): + qubits = ['QB1', 'QB2', 'QB3', 'QB4', 'QB5'] return DynamicQuantumArchitecture( calibration_set_id=UUID('59478539-dcef-4b2e-80c8-122d7ec3fc89'), - qubits=['QB1', 'QB2', 'QB3', 'QB4', 'QB5'], + qubits=qubits, computational_resonators=[], gates={ 'prx': GateInfo( - implementations={ - 'drag_gaussian': GateImplementationInfo(loci=(('QB1',), ('QB2',), ('QB3',), ('QB4',), ('QB5',))) - }, + implementations={'drag_gaussian': GateImplementationInfo(loci=_1q_loci(qubits))}, default_implementation='drag_gaussian', override_default_implementation={}, ), 'cc_prx': GateInfo( - implementations={ - 'prx_composite': GateImplementationInfo(loci=(('QB1',), ('QB2',), ('QB3',), ('QB4',), ('QB5',))) - }, + implementations={'prx_composite': GateImplementationInfo(loci=_1q_loci(qubits))}, default_implementation='prx_composite', override_default_implementation={}, ), @@ -102,9 +105,7 @@ def adonis_architecture(): override_default_implementation={}, ), 'measure': GateInfo( - implementations={ - 'constant': GateImplementationInfo(loci=(('QB1',), ('QB2',), ('QB3',), ('QB4',), ('QB5',))) - }, + implementations={'constant': GateImplementationInfo(loci=_1q_loci(qubits))}, default_implementation='constant', override_default_implementation={}, ), @@ -114,20 +115,24 @@ def adonis_architecture(): @pytest.fixture def move_architecture(): + qubits = ['QB1', 'QB2', 'QB3', 'QB4', 'QB5', 'QB6'] return DynamicQuantumArchitecture( calibration_set_id=UUID('26c5e70f-bea0-43af-bd37-6212ec7d04cb'), - qubits=['QB1', 'QB2', 'QB3', 'QB4', 'QB5', 'QB6'], + qubits=qubits, computational_resonators=['CR1'], gates={ 'prx': GateInfo( implementations={ - 'drag_gaussian': GateImplementationInfo( - loci=(('QB1',), ('QB2',), ('QB3',), ('QB4',), ('QB5',), ('QB6',)) - ), + 'drag_gaussian': GateImplementationInfo(loci=_1q_loci(qubits)), }, default_implementation='drag_gaussian', override_default_implementation={}, ), + 'cc_prx': GateInfo( + implementations={'prx_composite': GateImplementationInfo(loci=_1q_loci(qubits))}, + default_implementation='prx_composite', + override_default_implementation={}, + ), 'cz': GateInfo( implementations={ 'tgss': GateImplementationInfo( @@ -152,9 +157,7 @@ def move_architecture(): ), 'measure': GateInfo( implementations={ - 'constant': GateImplementationInfo( - loci=(('QB1',), ('QB2',), ('QB3',), ('QB4',), ('QB5',), ('QB6',)) - ), + 'constant': GateImplementationInfo(loci=_1q_loci(qubits)), }, default_implementation='constant', override_default_implementation={}, @@ -241,16 +244,15 @@ def hypothetical_fake_architecture(): @pytest.fixture def ndonis_architecture(): + qubits = ['QB1', 'QB2', 'QB3', 'QB4', 'QB5', 'QB6'] return DynamicQuantumArchitecture( calibration_set_id=UUID('26c5e70f-bea0-43af-bd37-6212ec7d04cb'), - qubits=['QB1', 'QB2', 'QB3', 'QB4', 'QB5', 'QB6'], + qubits=qubits, computational_resonators=['CR1'], gates={ 'prx': GateInfo( implementations={ - 'drag_gaussian': GateImplementationInfo( - loci=(('QB1',), ('QB2',), ('QB3',), ('QB4',), ('QB5',), ('QB6',)) - ), + 'drag_gaussian': GateImplementationInfo(loci=_1q_loci(qubits)), }, default_implementation='drag_gaussian', override_default_implementation={}, @@ -289,9 +291,7 @@ def ndonis_architecture(): ), 'measure': GateInfo( implementations={ - 'constant': GateImplementationInfo( - loci=(('QB1',), ('QB2',), ('QB3',), ('QB4',), ('QB5',), ('QB6',)) - ), + 'constant': GateImplementationInfo(loci=_1q_loci(qubits)), }, default_implementation='constant', override_default_implementation={}, diff --git a/tests/test_iqm_transpilation.py b/tests/test_iqm_transpilation.py index d27bff28d..8eb3793c0 100644 --- a/tests/test_iqm_transpilation.py +++ b/tests/test_iqm_transpilation.py @@ -150,8 +150,12 @@ def test_optimize_single_qubit_gates_preserves_layout(backend): def test_qiskit_native_transpiler(move_architecture, optimization_level): """Tests that a simple circuit is transpiled correctly using the Qiskit transpiler.""" backend, _ = get_mocked_backend(move_architecture) + # circuit should contain all our supported operations to make sure the transpiler can handle them qc = QuantumCircuit(2) qc.h(0) + qc.barrier(0, 1) + qc.delay(10, 0, unit='ns') + qc.reset(0) qc.cx(0, 1) qc.measure_all() transpiled_circuit = transpile(qc, backend=backend, optimization_level=optimization_level) From eaebbf30fbf819dfc51011eaa3c7fcc231489c92 Mon Sep 17 00:00:00 2001 From: Ville Bergholm Date: Thu, 27 Feb 2025 16:27:47 +0200 Subject: [PATCH 2/3] Fix. --- src/iqm/qiskit_iqm/iqm_transpilation.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/iqm/qiskit_iqm/iqm_transpilation.py b/src/iqm/qiskit_iqm/iqm_transpilation.py index c839d12e4..b1477e1a1 100644 --- a/src/iqm/qiskit_iqm/iqm_transpilation.py +++ b/src/iqm/qiskit_iqm/iqm_transpilation.py @@ -71,6 +71,7 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: dag = Optimize1qGatesDecomposition(self._intermediate_basis).run(dag) for node in dag.topological_op_nodes(): if node.name == 'u': + # convert into PRX + RZ qubit_index = dag.find_bit(node.qargs[0])[0] if math.isclose(node.op.params[0], 0, abs_tol=TOLERANCE): dag.remove_op_node(node) @@ -88,7 +89,7 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: # angles results in fewest changes to the circuit. for qubit in node.qargs: rz_angles[dag.find_bit(qubit)[0]] = 0 - elif node.name in {'barrier', 'delay'}: + elif node.name == 'barrier': # TODO barriers are meant to restrict circuit optimization, so strictly speaking # we should output any accumulated ``rz_angles`` here as explicit z rotations (like # the final rz:s). However, ``rz_angles`` simply represents a choice of phases for the @@ -97,10 +98,11 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: # makes no sense to convert it into active z rotations if we hit a barrier? pass elif node.name == 'move': + # acts like iSWAP with RZ, moving it to the other component qb, res = dag.find_bit(node.qargs[0])[0], dag.find_bit(node.qargs[1])[0] rz_angles[res], rz_angles[qb] = rz_angles[qb], rz_angles[res] - elif node.name == 'cz': - pass # rz_angles are commute through CZ gates + elif node.name in {'cz', 'delay'}: + pass # commutes with RZ gates else: raise ValueError( f"Unexpected operation '{node.name}' in circuit given to IQMOptimizeSingleQubitGates pass" From b2375be4286185e1a612cc1f9dd6b3894d0910bf Mon Sep 17 00:00:00 2001 From: Ville Bergholm Date: Mon, 3 Mar 2025 18:00:06 +0200 Subject: [PATCH 3/3] Mypy. --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 86432729f..004bb74c9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -45,7 +45,7 @@ def linear_3q_architecture_static(): ) -def _1q_loci(qubits: list[str]) -> tuple[tuple[str]]: +def _1q_loci(qubits: list[str]) -> tuple[tuple[str, ...], ...]: """One-qubit loci for the given qubits.""" return tuple((q,) for q in qubits)