Skip to content

Commit

Permalink
SW-1008 Use the native reset operation (#148)
Browse files Browse the repository at this point in the history
* Use the native reset operation.

---------

Co-authored-by: Ville Bergholm <[email protected]>
  • Loading branch information
smite and Ville Bergholm authored Feb 24, 2025
1 parent 79b333a commit 85641ad
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 73 deletions.
10 changes: 7 additions & 3 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,23 @@
Changelog
=========

Version 17.3
============

* Use the native ``reset`` operation to implement :class:`qiskit.circuit.Reset`.
`#148 <https://github.com/iqm-finland/qiskit-on-iqm/pull/148>`_

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

* Bugfix in :class:`IQMOptimizeSingleQubitGates`` where the angles are not properly computed for circuits
* 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
============

* Small note about IQM Client's API deprecation warning added to the user guide.
* Small note about IQM Client's API deprecation warning added to the user guide.
`#146 <https://github.com/iqm-finland/qiskit-on-iqm/pull/146>`_

Version 17.0
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ dependencies = [
"numpy",
"qiskit >= 1.0, < 1.3",
"qiskit-aer >= 0.13.1, < 0.16",
"iqm-client >= 22.0, < 23.0"
"iqm-client >= 22.3, < 23.0"
]

[project.urls]
Expand Down
2 changes: 1 addition & 1 deletion src/iqm/qiskit_iqm/iqm_backend.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2022-2023 Qiskit on IQM developers
# Copyright 2022-2025 Qiskit on IQM developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
5 changes: 1 addition & 4 deletions src/iqm/qiskit_iqm/iqm_job.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2022 Qiskit on IQM developers
# Copyright 2022-2025 Qiskit on IQM developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -101,9 +101,6 @@ def _format_measurement_results(
# Mapping from creg index (in the circuit) to an array with shape (shots, len(creg)) with the results.
formatted_results: dict[int, np.ndarray] = {}
for k, v in measurement_results.items():
if k.startswith('_reset'):
# HACK ignore keys associated with reset instructions
continue
# measurement keys encode data about the classical registers in the original Qiskit circuit
mk = MeasurementKey.from_string(k)
res = np.array(v, dtype=int)
Expand Down
89 changes: 36 additions & 53 deletions src/iqm/qiskit_iqm/qiskit_to_iqm.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,34 +171,7 @@ def serialize_instructions(
native_inst = Instruction(name='measure', qubits=qubit_names, args={'key': mk})
clbit_to_measure[clbit] = native_inst
elif instruction.name == 'reset':
# implemented using a measure instruction to measure the qubits, and
# one cc_prx per qubit to conditionally flip it to |0>
feedback_key = '_reset'
instructions.append(
Instruction(
name='measure',
qubits=qubit_names,
args={
# HACK to get something unique, remove when key can be omitted
'key': f'_reset_{len(instructions)}',
'feedback_key': feedback_key,
},
)
)
for q in qubit_names:
instructions.append(
Instruction(
name='cc_prx',
qubits=[q],
args={
'angle_t': 0.5,
'phase_t': 0.0,
'feedback_key': feedback_key,
'feedback_qubit': q,
},
)
)
continue
native_inst = Instruction(name='reset', qubits=qubit_names, args={})
elif instruction.name == 'id':
continue
elif instruction.name in allowed_nonnative_gates:
Expand All @@ -211,6 +184,7 @@ def serialize_instructions(
)

# classically controlled gates (using the c_if method)
# TODO we do not check anywhere if cc_prx is available for this locus!
condition = instruction.condition
if condition is not None:
if native_inst.name != 'prx':
Expand Down Expand Up @@ -262,21 +236,31 @@ def deserialize_instructions(
Returns:
Qiskit circuit represented by the given instructions.
"""
cl_bits: dict[str, int] = {}
# maps measurement key to the corresponding clbit
mk_to_clbit: dict[str, Clbit] = {}
# maps feedback key to the corresponding clbit
fk_to_clbit: dict[str, Clbit] = {}

# maps creg index to creg in the circuit
cl_regs: dict[int, ClassicalRegister] = {}

def register_key(key: str, mapping: dict[str, Clbit]) -> None:
"""Update the classical registers and the given key-to-clbit mapping with the given key."""
mk = MeasurementKey.from_string(key)
# find/create the corresponding creg
creg = cl_regs.setdefault(mk.creg_idx, ClassicalRegister(size=mk.creg_len, name=mk.creg_name))
# add the key to the given mapping
if mk.clbit_idx < len(creg):
mapping[str(mk)] = creg[mk.clbit_idx]
else:
raise IndexError(f'{mk}: Clbit index {mk.clbit_idx} is out of range for {creg}.')

for instr in instructions:
if instr.name == 'measure':
if instr.args['key'].startswith('_reset'): # HACK FIXME add the real reset instruction to iqm-client
continue
mk = MeasurementKey.from_string(instr.args['key'])
cl_regs[mk.creg_idx] = cl_regs.get(mk.creg_idx, ClassicalRegister(size=mk.creg_len, name=mk.creg_name))
if mk.clbit_idx < len(cl_regs[mk.creg_idx]):
cl_bits[str(mk)] = cl_regs[mk.creg_idx][mk.clbit_idx]
else:
raise IndexError(
f'Index {mk.clbit_idx} of classical bit is out of range for classical register with '
f'length {len(cl_regs[mk.creg_idx])}.'
)
register_key(instr.args['key'], mk_to_clbit)
if (key := instr.args.get('feedback_key')) is not None:
register_key(key, fk_to_clbit)

# Add resonators
n_qubits = len(layout.get_physical_bits())
n_resonators = len(qubit_name_to_index) - n_qubits
Expand All @@ -292,33 +276,32 @@ def deserialize_instructions(
*(cl_regs.get(i, ClassicalRegister(0)) for i in range(max(cl_regs) + 1 if cl_regs else 0)),
)
for instr in instructions:
loci = [index_to_qiskit_qubit[qubit_name_to_index[q]] for q in instr.qubits]
locus = [index_to_qiskit_qubit[qubit_name_to_index[q]] for q in instr.qubits]
if instr.name == 'prx':
angle_t = instr.args['angle_t'] * 2 * np.pi
phase_t = instr.args['phase_t'] * 2 * np.pi
circuit.r(angle_t, phase_t, loci[0])
circuit.r(angle_t, phase_t, locus[0])
elif instr.name == 'cz':
circuit.cz(loci[0], loci[1])
circuit.cz(*locus)
elif instr.name == 'move':
circuit.append(MoveGate(), loci)
circuit.append(MoveGate(), locus)
elif instr.name == 'measure':
if instr.args['key'].startswith('_reset'): # HACK FIXME add the real reset instruction to iqm-client
continue
mk = MeasurementKey.from_string(instr.args['key'])
circuit.measure(loci[0], cl_bits[str(mk)])
circuit.measure(locus[0], mk_to_clbit[str(mk)])
elif instr.name == 'barrier':
circuit.barrier(*loci)
circuit.barrier(*locus)
elif instr.name == 'delay':
duration = instr.args['duration']
circuit.delay(duration, loci, unit='s') # native delay instructions always use seconds
circuit.delay(duration, locus, unit='s') # native delay instructions always use seconds
elif instr.name == 'cc_prx':
if instr.args['feedback_key'] == '_reset': # HACK FIXME add the real reset instruction to iqm-client
circuit.reset(loci[0])
continue
angle_t = instr.args['angle_t'] * 2 * np.pi
phase_t = instr.args['phase_t'] * 2 * np.pi
feedback_key = instr.args['feedback_key']
circuit.r(angle_t, phase_t, loci[0]).c_if(cl_bits[feedback_key], 1)
# NOTE: 'feedback_qubit' is not needed, because in Qiskit you only have single-qubit measurements.
circuit.r(angle_t, phase_t, locus[0]).c_if(fk_to_clbit[feedback_key], 1)
elif instr.name == 'reset':
for qubit in locus:
circuit.reset(qubit)
else:
raise ValueError(f'Unsupported instruction {instr.name} in the circuit.')
return circuit
8 changes: 5 additions & 3 deletions tests/test_iqm_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,15 +379,17 @@ def test_serialize_circuit_reset(backend):
"""Test that the reset operation is accepted."""
qc = QuantumCircuit(2, 2)
qc.ry(np.pi / 2, 0)
qc.ry(np.pi / 2, 0)
qc.ry(np.pi / 2, 1)
qc.cz(0, 1)
qc.ry(-np.pi / 2, 0)
qc.reset(0)
# final measurement
qc.measure_all()
circuit_ser = backend.serialize_circuit(qc)
assert len(circuit_ser.instructions) == 9
check_measure_cc_prx_pair(circuit_ser.instructions[4], circuit_ser.instructions[5], False)

assert len(circuit_ser.instructions) == 8
reset = circuit_ser.instructions[4]
assert reset.name == 'reset'


def test_run_non_native_circuit(backend, circuit, job_id, run_request):
Expand Down
26 changes: 18 additions & 8 deletions tests/test_qiskit_to_iqm.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,14 @@ def test_deserialize_instructions_without_layout():
Instruction(name='cz', qubits=['QB1', 'QB2'], args={}),
Instruction(name='move', qubits=['QB1', 'CR1'], args={}),
Instruction(name='barrier', qubits=['QB1', 'QB2'], args={}),
Instruction(name='measure', qubits=['QB1'], args={'key': 'm_3_2_1'}),
Instruction(name='measure', qubits=['QB1'], args={'key': 'm_3_2_1', 'feedback_key': 'm_3_2_1'}),
Instruction(name='delay', qubits=['QB1'], args={'duration': 50e-9}),
Instruction(
name='cc_prx',
qubits=['QB1'],
args={'phase_t': 0.0, 'feedback_qubit': 'QB1', 'angle_t': 0.0, 'feedback_key': 'm_3_2_1'},
args={'phase_t': 0.0, 'angle_t': 0.0, 'feedback_qubit': 'QB1', 'feedback_key': 'm_3_2_1'},
),
Instruction(name='reset', qubits=['QB2'], args={}),
]
circuit = deserialize_instructions(instructions, {'QB1': 0, 'QB2': 1, 'CR1': 2}, Layout())
assert isinstance(circuit, QuantumCircuit)
Expand All @@ -127,23 +129,31 @@ def test_deserialize_instructions_without_layout():
assert circuit.num_ancillas == 0
assert circuit.num_clbits == 3
assert len(circuit.cregs) == 3
for circuit_instruction, name in zip(circuit.data, ['r', 'cz', 'move', 'barrier', 'measure', 'r']):
for circuit_instruction, name in zip(
circuit.data, ['r', 'cz', 'move', 'barrier', 'measure', 'delay', 'r', 'reset']
):
assert circuit_instruction.operation.name == name


def test_deserialize_instructions_roundtrip():
"""Check that instructions are retrieved after roundtrip."""
"""Check that native instructions are retrieved after a deserialize-serialize roundtrip."""
instructions = [
Instruction(name='prx', qubits=['QB1'], args={'phase_t': 0.0, 'angle_t': 0.0}),
Instruction(name='prx', qubits=['QB1'], args={'phase_t': 0.1, 'angle_t': 0.0}),
Instruction(name='cz', qubits=['QB1', 'QB2'], args={}),
Instruction(name='move', qubits=['QB1', 'CR1'], args={}),
Instruction(name='barrier', qubits=['QB1', 'QB2'], args={}),
Instruction(name='measure', qubits=['QB1'], args={'key': 'm_3_2_1'}),
Instruction(name='measure', qubits=['QB1'], args={'key': 'm_3_2_1', 'feedback_key': 'm_3_2_1'}),
Instruction(name='delay', qubits=['QB1'], args={'duration': 50e-9}),
Instruction(
name='cc_prx',
qubits=['QB2'],
args={'angle_t': 0.2, 'phase_t': 0.3, 'feedback_qubit': 'QB1', 'feedback_key': 'm_3_2_1'},
),
Instruction(name='reset', qubits=['QB2'], args={}),
]
circuit = deserialize_instructions(instructions, {'QB1': 0, 'QB2': 1, 'CR1': 2}, Layout())
new_instructions = serialize_instructions(circuit, qubit_index_to_name={0: 'QB1', 1: 'QB2', 2: 'CR1'})
for instruction, name in zip(new_instructions, ['prx', 'cz', 'move', 'barrier', 'measure']):
assert instruction.name == name
assert new_instructions == instructions


def test_deserialize_instructions_unsupported_instruction():
Expand Down

0 comments on commit 85641ad

Please sign in to comment.