Skip to content

Commit

Permalink
SW-803: Bugfix in IQMOptimizeSingleQubitGates where the angles are no…
Browse files Browse the repository at this point in the history
…t properly computed (#147)

* Bugfix in optimize_sqg where the angles are not properly computed for circuits with move gates

* removed print statements

* formatting

* test fix

---------

Co-authored-by: Arianne Meijer <[email protected]>
  • Loading branch information
Aerylia and Arianne Meijer authored Feb 21, 2025
1 parent 37c91df commit 160739d
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 10 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
Changelog
=========


Version 17.2
============

* Bugfix in :class:`IQMOptimizeSingleQubitGates`` where the angles are not properly computed for circuits
with prx gates on qubits holding a resonator state, i.e. circuits that require running without move gate validation.
`#147 <https://github.com/iqm-finland/qiskit-on-iqm/pull/147>`_


Version 17.1
============

Expand Down
22 changes: 19 additions & 3 deletions src/iqm/qiskit_iqm/iqm_transpilation.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Transpilation tool to optimize the decomposition of single-qubit gates tailored to IQM hardware."""
import math
import warnings

import numpy as np
Expand All @@ -23,6 +24,8 @@
from qiskit.transpiler.passes import BasisTranslator, Optimize1qGatesDecomposition, RemoveBarriers
from qiskit.transpiler.passmanager import PassManager

TOLERANCE = 1e-10 # The tolerance for equivalence checking against zero.


class IQMOptimizeSingleQubitGates(TransformationPass):
r"""Optimize the decomposition of single-qubit gates for the IQM gate set.
Expand Down Expand Up @@ -55,6 +58,7 @@ def __init__(self, drop_final_rz: bool = True, ignore_barriers: bool = False):
self._ignore_barriers = ignore_barriers

def run(self, dag: DAGCircuit) -> DAGCircuit:
# pylint: disable=too-many-branches
self._validate_ops(dag)
# accumulated RZ angles for each qubit, from the beginning of the circuit to the current gate
rz_angles: list[float] = [0] * dag.num_qubits()
Expand All @@ -68,9 +72,12 @@ def run(self, dag: DAGCircuit) -> DAGCircuit:
for node in dag.topological_op_nodes():
if node.name == 'u':
qubit_index = dag.find_bit(node.qargs[0])[0]
dag.substitute_node(
node, RGate(node.op.params[0], np.pi / 2 - node.op.params[2] - rz_angles[qubit_index])
)
if math.isclose(node.op.params[0], 0, abs_tol=TOLERANCE):
dag.remove_op_node(node)
else:
dag.substitute_node(
node, RGate(node.op.params[0], np.pi / 2 - node.op.params[2] - rz_angles[qubit_index])
)
phase = node.op.params[1] + node.op.params[2]
dag.global_phase += phase / 2
rz_angles[qubit_index] += phase
Expand All @@ -89,6 +96,15 @@ def run(self, dag: DAGCircuit) -> DAGCircuit:
# been transformed). This choice of local phases is in principle arbitrary, so maybe it
# makes no sense to convert it into active z rotations if we hit a barrier?
pass
elif node.name == 'move':
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
else:
raise ValueError(
f'Unexpected operation {node.name} in circuit given to IQMOptimizeSingleQubitGates pass'
)

if not self._drop_final_rz:
for qubit_index, rz_angle in enumerate(rz_angles):
Expand Down
15 changes: 9 additions & 6 deletions tests/fake_backends/test_fake_deneb.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,18 +204,21 @@ def test_qiskit_transpile_for_ghz_with_fake_deneb_noise_model():

def test_transpiling_works_but_backend_run_doesnt_with_unsupported_gates():
backend = IQMFakeDeneb()
num_qb = 6
num_qb = 1
qc_list = []
for _ in range(4):
qc_list.append(QuantumCircuit(num_qb))

qc_list[0].h(1)
qc_list[1].sdg(2)
qc_list[2].t(3)
qc_list[3].s(4)
qc_list[0].h(0)
qc_list[1].sdg(0)
qc_list[2].t(0)
qc_list[3].s(0)

for i in range(4):
qc_list[i].measure_all()

for qc in qc_list:
backend.run(transpile(qc, backend=backend), shots=1000)
backend.run(transpile_to_IQM(qc, backend=backend), shots=1000)

with pytest.raises(
ValueError,
Expand Down
28 changes: 27 additions & 1 deletion tests/test_iqm_transpilation.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,23 @@
"""Testing IQM transpilation.
"""

import math

import numpy as np
import pytest
from qiskit import QuantumCircuit, transpile
from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import BasisTranslator
from qiskit_aer import AerSimulator

from iqm.qiskit_iqm.fake_backends.fake_adonis import IQMFakeAdonis
from iqm.qiskit_iqm.fake_backends.fake_aphrodite import IQMFakeAphrodite
from iqm.qiskit_iqm.fake_backends.fake_deneb import IQMFakeDeneb
from iqm.qiskit_iqm.iqm_circuit_validation import validate_circuit
from iqm.qiskit_iqm.iqm_move_layout import generate_initial_layout
from iqm.qiskit_iqm.iqm_transpilation import optimize_single_qubit_gates
from iqm.qiskit_iqm.iqm_transpilation import TOLERANCE, IQMOptimizeSingleQubitGates, optimize_single_qubit_gates
from iqm.qiskit_iqm.move_gate import MoveGate
from tests.utils import get_mocked_backend


Expand Down Expand Up @@ -150,3 +156,23 @@ def test_qiskit_native_transpiler(move_architecture, optimization_level):
qc.measure_all()
transpiled_circuit = transpile(qc, backend=backend, optimization_level=optimization_level)
validate_circuit(transpiled_circuit, backend)


def test_optimize_single_qubit_gates_works_on_invalid_move_sandwich():
"""Tests that the optimization pass works on a circuit with an invalid MOVE sandwich.
In case the user is wanting to use the higher energy levels but also optimize the SQGs in the circuit."""
qc = QuantumCircuit(2)
qc.rz(0.5, 1)
qc.append(MoveGate(), [1, 0])
qc.x(1)
qc.append(MoveGate(), [1, 0])
basis_circuit = PassManager([BasisTranslator(SessionEquivalenceLibrary, ['r', 'cz', 'move'])]).run(qc)
transpiled_circuit = PassManager(
[BasisTranslator(SessionEquivalenceLibrary, ['r', 'cz', 'move']), IQMOptimizeSingleQubitGates(True, True)]
).run(basis_circuit)
assert transpiled_circuit.count_ops()['r'] == 1
assert transpiled_circuit.count_ops()['move'] == 2
for gate in transpiled_circuit:
if gate.operation.name == 'r':
assert math.isclose(gate.operation.params[0], np.pi, rel_tol=TOLERANCE)
assert math.isclose(gate.operation.params[1], 0, abs_tol=TOLERANCE)

0 comments on commit 160739d

Please sign in to comment.