iqm-client 32.0.0__py3-none-any.whl → 32.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iqm/qiskit_iqm/iqm_naive_move_pass.py +1 -1
- iqm/qiskit_iqm/iqm_target.py +8 -2
- iqm/qiskit_iqm/iqm_transpilation.py +219 -26
- iqm/qiskit_iqm/qiskit_to_iqm.py +110 -38
- iqm/qiskit_iqm/transpiler_plugins.py +11 -8
- {iqm_client-32.0.0.dist-info → iqm_client-32.1.1.dist-info}/METADATA +1 -1
- {iqm_client-32.0.0.dist-info → iqm_client-32.1.1.dist-info}/RECORD +12 -12
- {iqm_client-32.0.0.dist-info → iqm_client-32.1.1.dist-info}/AUTHORS.rst +0 -0
- {iqm_client-32.0.0.dist-info → iqm_client-32.1.1.dist-info}/LICENSE.txt +0 -0
- {iqm_client-32.0.0.dist-info → iqm_client-32.1.1.dist-info}/WHEEL +0 -0
- {iqm_client-32.0.0.dist-info → iqm_client-32.1.1.dist-info}/entry_points.txt +0 -0
- {iqm_client-32.0.0.dist-info → iqm_client-32.1.1.dist-info}/top_level.txt +0 -0
|
@@ -98,7 +98,7 @@ class IQMNaiveResonatorMoving(TransformationPass):
|
|
|
98
98
|
# Convert the circuit to the IQMClientCircuit format and run the transpiler.
|
|
99
99
|
iqm_circuit = IQMClientCircuit(
|
|
100
100
|
name="Transpiling Circuit",
|
|
101
|
-
instructions=tuple(serialize_instructions(circuit, self.idx_to_component)),
|
|
101
|
+
instructions=tuple(serialize_instructions(circuit, self.idx_to_component, overwrite_layout=layout)),
|
|
102
102
|
metadata=None,
|
|
103
103
|
)
|
|
104
104
|
try:
|
iqm/qiskit_iqm/iqm_target.py
CHANGED
|
@@ -26,7 +26,7 @@ from iqm.iqm_client import (
|
|
|
26
26
|
ObservationFinder,
|
|
27
27
|
)
|
|
28
28
|
from iqm.qiskit_iqm.move_gate import MoveGate
|
|
29
|
-
from qiskit.circuit import Delay, Gate, Parameter, Reset
|
|
29
|
+
from qiskit.circuit import Delay, Gate, IfElseOp, Parameter, Reset
|
|
30
30
|
from qiskit.circuit.library import CZGate, IGate, Measure, RGate
|
|
31
31
|
from qiskit.providers import QubitProperties
|
|
32
32
|
from qiskit.transpiler import InstructionProperties, Target
|
|
@@ -44,6 +44,7 @@ _QISKIT_IQM_GATE_MAP: dict[str, Gate] = {
|
|
|
44
44
|
"cz": CZGate(),
|
|
45
45
|
"move": MoveGate(),
|
|
46
46
|
"id": IGate(),
|
|
47
|
+
"if_else": IfElseOp,
|
|
47
48
|
}
|
|
48
49
|
"""Maps IQM native operation names to corresponding Qiskit gate objects."""
|
|
49
50
|
|
|
@@ -126,8 +127,13 @@ class IQMTarget(Target):
|
|
|
126
127
|
if "prx" in op_loci:
|
|
127
128
|
add_gate("prx")
|
|
128
129
|
|
|
129
|
-
# HACK reset gate shares cc_prx loci for now, until reset is also in the DQA/metrics
|
|
130
130
|
if "cc_prx" in op_loci:
|
|
131
|
+
# IfElseOp is a global 'gate' so it's slightly different from the others.
|
|
132
|
+
self.add_instruction(
|
|
133
|
+
instruction=_QISKIT_IQM_GATE_MAP["if_else"],
|
|
134
|
+
name="if_else",
|
|
135
|
+
)
|
|
136
|
+
# HACK reset gate shares cc_prx loci for now, until reset is also in the DQA/metrics
|
|
131
137
|
self.add_instruction(
|
|
132
138
|
_QISKIT_IQM_GATE_MAP["reset"],
|
|
133
139
|
{self.locus_to_idx(locus): None for locus in op_loci["cc_prx"]},
|
|
@@ -17,10 +17,14 @@ import math
|
|
|
17
17
|
import warnings
|
|
18
18
|
|
|
19
19
|
import numpy as np
|
|
20
|
+
from packaging.version import Version
|
|
20
21
|
from qiskit import QuantumCircuit
|
|
22
|
+
from qiskit import __version__ as qiskit_version
|
|
23
|
+
from qiskit.circuit.controlflow import IfElseOp
|
|
21
24
|
from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary
|
|
22
|
-
from qiskit.circuit.library import RGate, UnitaryGate
|
|
23
|
-
from qiskit.
|
|
25
|
+
from qiskit.circuit.library import RGate, RZGate, UnitaryGate
|
|
26
|
+
from qiskit.converters import circuit_to_dag, dag_to_circuit
|
|
27
|
+
from qiskit.dagcircuit import DAGCircuit, DAGOpNode
|
|
24
28
|
from qiskit.transpiler.basepasses import TransformationPass
|
|
25
29
|
from qiskit.transpiler.passes import (
|
|
26
30
|
BasisTranslator,
|
|
@@ -58,16 +62,33 @@ class IQMOptimizeSingleQubitGates(TransformationPass):
|
|
|
58
62
|
|
|
59
63
|
def __init__(self, drop_final_rz: bool = True, ignore_barriers: bool = False):
|
|
60
64
|
super().__init__()
|
|
61
|
-
self._basis = ["r", "cz", "move"]
|
|
62
|
-
self._intermediate_basis = ["u", "cz", "move"]
|
|
65
|
+
self._basis = ["r", "cz", "move", "if_else"]
|
|
66
|
+
self._intermediate_basis = ["u", "cz", "move", "if_else"]
|
|
63
67
|
self._drop_final_rz = drop_final_rz
|
|
64
68
|
self._ignore_barriers = ignore_barriers
|
|
65
69
|
|
|
66
|
-
def run(self, dag: DAGCircuit) -> DAGCircuit:
|
|
67
|
-
|
|
70
|
+
def run(self, dag: DAGCircuit, decompose_rz_to_r: bool = True) -> DAGCircuit:
|
|
71
|
+
"""Runs the single-qubit gate optimization pass.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
dag: The input DAG circuit to optimize.
|
|
75
|
+
decompose_rz_to_r: Whether to decompose RZ gates into R gates, or add the to the DAG as
|
|
76
|
+
RZ gates. This is used in recursive calls to communicate the accumulated RZ angles in ``rz_angles``.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
The optimized DAG circuit.
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
if decompose_rz_to_r:
|
|
83
|
+
self._validate_ops(dag)
|
|
68
84
|
# accumulated RZ angles for each qubit, from the beginning of the circuit to the current gate
|
|
69
85
|
rz_angles: list[float] = [0] * dag.num_qubits()
|
|
70
86
|
|
|
87
|
+
# Handle old conditional gates
|
|
88
|
+
if Version(qiskit_version) < Version("2.0"):
|
|
89
|
+
# This needs to be done before the BasisTranslation as that pass does not retain the condition.
|
|
90
|
+
dag = self._handle_c_if_blocks(dag)
|
|
91
|
+
|
|
71
92
|
if self._ignore_barriers:
|
|
72
93
|
dag = RemoveBarriers().run(dag)
|
|
73
94
|
# convert all gates in the circuit to U and CZ gates
|
|
@@ -76,21 +97,7 @@ class IQMOptimizeSingleQubitGates(TransformationPass):
|
|
|
76
97
|
dag = Optimize1qGatesDecomposition(self._intermediate_basis).run(dag)
|
|
77
98
|
for node in dag.topological_op_nodes():
|
|
78
99
|
if node.name == "u":
|
|
79
|
-
|
|
80
|
-
qubit_index = dag.find_bit(node.qargs[0]).index
|
|
81
|
-
if isinstance(node.op.params[0], float) and math.isclose(node.op.params[0], 0, abs_tol=TOLERANCE):
|
|
82
|
-
dag.remove_op_node(node)
|
|
83
|
-
else:
|
|
84
|
-
dag.substitute_node(
|
|
85
|
-
node,
|
|
86
|
-
RGate(
|
|
87
|
-
node.op.params[0],
|
|
88
|
-
np.pi / 2 - node.op.params[2] - rz_angles[qubit_index],
|
|
89
|
-
),
|
|
90
|
-
)
|
|
91
|
-
phase = node.op.params[1] + node.op.params[2]
|
|
92
|
-
dag.global_phase += phase / 2
|
|
93
|
-
rz_angles[qubit_index] += phase
|
|
100
|
+
dag, rz_angles = self._handle_u_gates(dag, node, rz_angles)
|
|
94
101
|
elif node.name in {"measure", "reset"}:
|
|
95
102
|
# measure and reset destroy phase information. The local phases before and after such
|
|
96
103
|
# an operation are in principle independent, and the local computational frame phases
|
|
@@ -115,21 +122,207 @@ class IQMOptimizeSingleQubitGates(TransformationPass):
|
|
|
115
122
|
rz_angles[res], rz_angles[qb] = rz_angles[qb], rz_angles[res]
|
|
116
123
|
elif node.name in {"cz", "delay"}:
|
|
117
124
|
pass # commutes with RZ gates
|
|
125
|
+
elif node.name == "if_else":
|
|
126
|
+
dag, rz_angles = self._handle_if_else_block(dag, node, rz_angles)
|
|
118
127
|
else:
|
|
119
128
|
raise ValueError(
|
|
120
129
|
f"Unexpected operation '{node.name}' in circuit given to IQMOptimizeSingleQubitGates pass"
|
|
121
130
|
)
|
|
122
131
|
|
|
123
|
-
if not
|
|
132
|
+
if not decompose_rz_to_r:
|
|
124
133
|
for qubit_index, rz_angle in enumerate(rz_angles):
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
dag.apply_operation_back(RGate(np.pi, rz_angle / 2), qargs=(qubit,))
|
|
134
|
+
dag.apply_operation_back(RZGate(rz_angle), qargs=(dag.qubits[qubit_index],))
|
|
135
|
+
elif not self._drop_final_rz:
|
|
136
|
+
dag, rz_angles = self._apply_final_r_gates(dag, rz_angles)
|
|
129
137
|
|
|
130
138
|
return dag
|
|
131
139
|
|
|
140
|
+
def _apply_final_r_gates(self, dag: DAGCircuit, rz_angles: list[float]) -> tuple[DAGCircuit, list[float]]:
|
|
141
|
+
"""Helper function that adds the final PRX/R gates to the circuit according to the accumulated angles.
|
|
142
|
+
|
|
143
|
+
Returns the updated dag and a list of zero angles since the final RZ rotations are already applied.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
dag: The input DAG circuit we are optimizing.
|
|
147
|
+
rz_angles: The accumulated RZ angles for each qubit.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
The updated DAG circuit and a list of zero angles.
|
|
151
|
+
|
|
152
|
+
"""
|
|
153
|
+
for qubit_index, rz_angle in enumerate(rz_angles):
|
|
154
|
+
if not math.isclose(rz_angle, 0, abs_tol=TOLERANCE):
|
|
155
|
+
qubit = dag.qubits[qubit_index]
|
|
156
|
+
dag.apply_operation_back(RGate(-np.pi, 0), qargs=(qubit,))
|
|
157
|
+
dag.apply_operation_back(RGate(np.pi, rz_angle / 2), qargs=(qubit,))
|
|
158
|
+
# Return resetted angles
|
|
159
|
+
return dag, [0.0] * dag.num_qubits()
|
|
160
|
+
|
|
161
|
+
def _handle_u_gates(
|
|
162
|
+
self, dag: DAGCircuit, node: DAGOpNode, rz_angles: list[float]
|
|
163
|
+
) -> tuple[DAGCircuit, list[float]]:
|
|
164
|
+
"""Helper function that converts U gates to PRXs and RZ gates,
|
|
165
|
+
so that the RZ gates can be commuted to the end of the circuit.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
dag: The input DAG circuit we are optimizing.
|
|
169
|
+
node: The DAG node containing the U gate to convert.
|
|
170
|
+
rz_angles: The accumulated RZ angles for each qubit.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
The updated DAG circuit and the updated list of accumulated RZ angles.
|
|
174
|
+
|
|
175
|
+
"""
|
|
176
|
+
qubit_index = dag.find_bit(node.qargs[0]).index
|
|
177
|
+
if isinstance(node.op.params[0], float) and math.isclose(node.op.params[0], 0, abs_tol=TOLERANCE):
|
|
178
|
+
dag.remove_op_node(node)
|
|
179
|
+
else:
|
|
180
|
+
dag.substitute_node(
|
|
181
|
+
node,
|
|
182
|
+
RGate(
|
|
183
|
+
node.op.params[0],
|
|
184
|
+
np.pi / 2 - node.op.params[2] - rz_angles[qubit_index],
|
|
185
|
+
),
|
|
186
|
+
)
|
|
187
|
+
phase = node.op.params[1] + node.op.params[2]
|
|
188
|
+
dag.global_phase += phase / 2
|
|
189
|
+
rz_angles[qubit_index] += phase
|
|
190
|
+
return dag, rz_angles
|
|
191
|
+
|
|
192
|
+
def _handle_if_else_block(
|
|
193
|
+
self, dag: DAGCircuit, node: DAGOpNode, rz_angles: list[float]
|
|
194
|
+
) -> tuple[DAGCircuit, list[float]]:
|
|
195
|
+
"""Call the optimization recursively on both branches of the if_else node.
|
|
196
|
+
|
|
197
|
+
The accumulated RZ angles are added to both branches before optimizing them.
|
|
198
|
+
The accumulated RZ angles after the optimization are taken from the else branch
|
|
199
|
+
and the adjoint is applied to the if branch to correct for the overrotation.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
dag: The input DAG circuit we are optimizing.
|
|
203
|
+
node: The DAG node containing the if_else block to optimize.
|
|
204
|
+
rz_angles: The accumulated RZ angles for each qubit.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
The updated DAG circuit and the updated list of accumulated RZ angles.
|
|
208
|
+
|
|
209
|
+
"""
|
|
210
|
+
# Add the Rz angles to each circuit block of the if_else node
|
|
211
|
+
# and run this pass recursively
|
|
212
|
+
sub_dags = []
|
|
213
|
+
for circuit_block in node.op.params:
|
|
214
|
+
new_circuit = QuantumCircuit(list(node.qargs + node.cargs))
|
|
215
|
+
# Prepend Rz angle to circuit block
|
|
216
|
+
for qubit in node.qargs:
|
|
217
|
+
new_circuit.append(RGate(-np.pi, 0), [qubit])
|
|
218
|
+
new_circuit.append(RGate(np.pi, rz_angles[dag.find_bit(qubit).index] / 2), [qubit])
|
|
219
|
+
if circuit_block is not None:
|
|
220
|
+
new_circuit.compose(circuit_block, node.qargs, node.cargs, inplace=True)
|
|
221
|
+
# Run optimization pass on the block
|
|
222
|
+
block_dag = circuit_to_dag(new_circuit)
|
|
223
|
+
block_dag = self.run(block_dag, decompose_rz_to_r=False)
|
|
224
|
+
sub_dags.append(block_dag)
|
|
225
|
+
# Pick up the final rotation
|
|
226
|
+
for qubit in node.qargs:
|
|
227
|
+
# Find the last node on the qubit
|
|
228
|
+
final_rzs = [list(block_dag.nodes_on_wire(qubit, only_ops=True))[-1] for block_dag in sub_dags]
|
|
229
|
+
# Assertions because this cannot go wrong by user error
|
|
230
|
+
assert len(final_rzs) == 2, "IfElseOp should have exactly two circuit blocks"
|
|
231
|
+
assert final_rzs[0].name == "rz" and final_rzs[1].name == "rz", (
|
|
232
|
+
"The last operation on each qubit in an IfElseOp should be an RZ gate, "
|
|
233
|
+
+ f"found {final_rzs[0].name} and {final_rzs[1].name} instead"
|
|
234
|
+
)
|
|
235
|
+
# Extract the angles
|
|
236
|
+
rz1, rz2 = final_rzs[0].op.params[0], final_rzs[1].op.params[0]
|
|
237
|
+
# We take the else_block rotation as the one to continue pushing through the circuit
|
|
238
|
+
# because we don't support else_blocks in the circuit at the moment.
|
|
239
|
+
# Update the rz_angle on this qubit with the one found
|
|
240
|
+
rz_angles[dag.find_bit(qubit).index] = rz2
|
|
241
|
+
# Remove the final rz from the dag in both circuit blocks
|
|
242
|
+
for block_dag, final_node in zip(sub_dags, final_rzs):
|
|
243
|
+
block_dag.remove_op_node(final_node)
|
|
244
|
+
# Fix the overrotation of the if_block when the final Rz does not match
|
|
245
|
+
if not math.isclose(rz1, rz2):
|
|
246
|
+
rz_angle = rz1 - rz2
|
|
247
|
+
sub_dags[0].apply_operation_back(RGate(-np.pi, 0), qargs=(qubit,))
|
|
248
|
+
sub_dags[0].apply_operation_back(RGate(np.pi, rz_angle / 2), qargs=(qubit,))
|
|
249
|
+
# Replace the params in the if_else node with the optimized circuits
|
|
250
|
+
new_params = []
|
|
251
|
+
for idx, sub_dag in enumerate(sub_dags):
|
|
252
|
+
# Optimize the PRXs on the block_dag, but now keep the final Rzs
|
|
253
|
+
block_dag = IQMOptimizeSingleQubitGates(drop_final_rz=False, ignore_barriers=self._ignore_barriers).run(
|
|
254
|
+
sub_dag
|
|
255
|
+
)
|
|
256
|
+
# Ensure the qubits act on the same qubits as before
|
|
257
|
+
if node.op.params[idx] is not None and block_dag.qubits != node.op.params[idx].qubits:
|
|
258
|
+
# Sometimes the circuit_block.qubits != node.qargs,
|
|
259
|
+
# so we need to make sure that they act on the same qubits as before
|
|
260
|
+
new_circuit = QuantumCircuit(list(node.op.params[idx].qubits + node.op.params[idx].clbits))
|
|
261
|
+
new_circuit.compose(
|
|
262
|
+
dag_to_circuit(block_dag),
|
|
263
|
+
node.op.params[idx].qubits,
|
|
264
|
+
node.op.params[idx].clbits,
|
|
265
|
+
inplace=True,
|
|
266
|
+
)
|
|
267
|
+
else:
|
|
268
|
+
new_circuit = dag_to_circuit(block_dag)
|
|
269
|
+
new_params.append(new_circuit)
|
|
270
|
+
dag.substitute_node(
|
|
271
|
+
node,
|
|
272
|
+
IfElseOp(
|
|
273
|
+
node.op.condition,
|
|
274
|
+
new_params[0],
|
|
275
|
+
false_body=new_params[1] if new_params[1].size() > 0 else None,
|
|
276
|
+
label=node.op.label,
|
|
277
|
+
),
|
|
278
|
+
)
|
|
279
|
+
return dag, rz_angles
|
|
280
|
+
|
|
281
|
+
def _handle_c_if_blocks(self, dag: DAGCircuit) -> DAGCircuit:
|
|
282
|
+
"""Helper function that replaces all classically controlled RGates with an if_else operator.
|
|
283
|
+
|
|
284
|
+
This is needed because the BasisTranslator pass does not retain the condition on the nodes.
|
|
285
|
+
This is only needed for Qiskit versions < 2.0.0.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
dag: The input DAG circuit we are optimizing.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
The updated DAG circuit with if_else blocks instead of R gates with a condition.
|
|
292
|
+
|
|
293
|
+
"""
|
|
294
|
+
for node in dag.topological_op_nodes():
|
|
295
|
+
if hasattr(node, "condition") and node.condition and node.name != "if_else":
|
|
296
|
+
# Manually parse the node to a circuit because helper functions don't exist
|
|
297
|
+
# NOTE if_block needs to have the same size as node or else it cannot be replaced later.
|
|
298
|
+
if_block = QuantumCircuit(list(node.qargs))
|
|
299
|
+
# NOTE Need to reconstruct the node.op manually because rust panics when using node.op directly
|
|
300
|
+
if node.op.name != "r":
|
|
301
|
+
raise ValueError(
|
|
302
|
+
f"Unexpected operation '{node.name}' in circuit given to IQMOptimizeSingleQubitGates pass"
|
|
303
|
+
)
|
|
304
|
+
if_block.append(RGate(node.op.params[0], node.op.params[1], label=node.op.label), node.qargs)
|
|
305
|
+
new_op = IfElseOp(
|
|
306
|
+
node.condition,
|
|
307
|
+
if_block,
|
|
308
|
+
)
|
|
309
|
+
dag.substitute_node(
|
|
310
|
+
node,
|
|
311
|
+
new_op,
|
|
312
|
+
)
|
|
313
|
+
return dag
|
|
314
|
+
|
|
132
315
|
def _validate_ops(self, dag: DAGCircuit): # noqa: ANN202
|
|
316
|
+
"""Helper function that validates that the operations in the circuit are compatible
|
|
317
|
+
with the IQMOptimizeSingleQubitGates pass.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
dag: The input DAG circuit to validate before optimization.
|
|
321
|
+
|
|
322
|
+
Raises:
|
|
323
|
+
ValueError: If an invalid operation is found in the circuit.
|
|
324
|
+
|
|
325
|
+
"""
|
|
133
326
|
valid_ops = self._basis + ["measure", "reset", "delay", "barrier"]
|
|
134
327
|
for node in dag.op_nodes():
|
|
135
328
|
if node.name not in valid_ops:
|
iqm/qiskit_iqm/qiskit_to_iqm.py
CHANGED
|
@@ -15,14 +15,17 @@
|
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
|
-
from collections.abc import Collection
|
|
18
|
+
from collections.abc import Collection, Iterable
|
|
19
19
|
from dataclasses import dataclass
|
|
20
20
|
from math import pi
|
|
21
21
|
import re
|
|
22
|
+
import warnings
|
|
22
23
|
|
|
23
24
|
from iqm.qiskit_iqm.move_gate import MoveGate
|
|
25
|
+
from packaging.version import Version
|
|
24
26
|
from qiskit import QuantumCircuit as QiskitQuantumCircuit
|
|
25
|
-
from qiskit
|
|
27
|
+
from qiskit import __version__ as qiskit_version
|
|
28
|
+
from qiskit.circuit import ClassicalRegister, Clbit, Operation, QuantumRegister
|
|
26
29
|
from qiskit.transpiler.layout import Layout
|
|
27
30
|
|
|
28
31
|
from iqm.pulse import CircuitOperation
|
|
@@ -93,8 +96,59 @@ class MeasurementKey:
|
|
|
93
96
|
return cls(creg.name, len(creg), creg_idx, clbit_idx)
|
|
94
97
|
|
|
95
98
|
|
|
99
|
+
def _apply_condition(
|
|
100
|
+
operation: Operation,
|
|
101
|
+
native_instructions: Iterable[CircuitOperation],
|
|
102
|
+
clbit_to_measure: dict[Clbit, CircuitOperation],
|
|
103
|
+
) -> None:
|
|
104
|
+
"""Apply a classical condition to circuit instructions.
|
|
105
|
+
|
|
106
|
+
Modifies the instructions in place.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
operation: Operation containing the classical condition.
|
|
110
|
+
native_instructions: Instructions to apply the condition to.
|
|
111
|
+
clbit_to_measure: Maps bits in the classical register to the measurement operation that
|
|
112
|
+
last wrote something into them.
|
|
113
|
+
|
|
114
|
+
"""
|
|
115
|
+
# check that the condition is supported
|
|
116
|
+
creg, value = operation.condition
|
|
117
|
+
if isinstance(creg, ClassicalRegister):
|
|
118
|
+
if len(creg) != 1:
|
|
119
|
+
raise ValueError(f"{operation.name} is conditioned on multiple bits, this is not supported.")
|
|
120
|
+
clbit = creg[0]
|
|
121
|
+
else:
|
|
122
|
+
clbit = creg # it is a Clbit
|
|
123
|
+
if value != 1:
|
|
124
|
+
raise ValueError(f"{operation.name} is conditioned on integer value {value}, only value 1 is supported.")
|
|
125
|
+
|
|
126
|
+
# Set up feedback routing.
|
|
127
|
+
# The latest "measure" instruction to write to that classical bit is modified, it is
|
|
128
|
+
# given an explicit feedback_key equal to its measurement key.
|
|
129
|
+
# The same feedback_key is given to the controlled instruction, along with the feedback qubit.
|
|
130
|
+
if (measure_inst := clbit_to_measure.get(clbit)) is None:
|
|
131
|
+
raise ValueError(f"{operation.name} conditioned on {clbit}, which does not contain a measurement result yet.")
|
|
132
|
+
feedback_key = measure_inst.args["key"]
|
|
133
|
+
measure_inst.args["feedback_key"] = feedback_key # this measure is used to provide feedback
|
|
134
|
+
physical_qubit_name = measure_inst.locus[0] # single-qubit measurement
|
|
135
|
+
|
|
136
|
+
for inst in native_instructions:
|
|
137
|
+
# TODO we do not check anywhere if cc_prx is available for this locus!
|
|
138
|
+
if inst.name != "prx":
|
|
139
|
+
raise ValueError(f"This backend only supports conditionals on r, x, y, rx and ry gates, not on {inst.name}")
|
|
140
|
+
inst.name = "cc_prx"
|
|
141
|
+
inst.args["feedback_key"] = feedback_key
|
|
142
|
+
inst.args["feedback_qubit"] = physical_qubit_name
|
|
143
|
+
|
|
144
|
+
|
|
96
145
|
def serialize_instructions( # noqa: PLR0912, PLR0915
|
|
97
|
-
circuit: QiskitQuantumCircuit,
|
|
146
|
+
circuit: QiskitQuantumCircuit,
|
|
147
|
+
qubit_index_to_name: dict[int, str],
|
|
148
|
+
allowed_nonnative_gates: Collection[str] = (),
|
|
149
|
+
*,
|
|
150
|
+
clbit_to_measure: dict[Clbit, CircuitOperation] | None = None,
|
|
151
|
+
overwrite_layout: Layout | None = None,
|
|
98
152
|
) -> list[CircuitOperation]:
|
|
99
153
|
"""Serialize a quantum circuit into the IQM data transfer format.
|
|
100
154
|
|
|
@@ -109,9 +163,13 @@ def serialize_instructions( # noqa: PLR0912, PLR0915
|
|
|
109
163
|
If such gates are present in the circuit, the caller must edit the result to be valid and executable.
|
|
110
164
|
Notably, since IQM transfer format requires named parameters and qiskit parameters don't have names, the
|
|
111
165
|
`i` th parameter of an unrecognized instruction is given the name ``"p<i>"``.
|
|
166
|
+
clbit_to_measure: Maps clbits to the latest "measure" instruction to store its result there, or
|
|
167
|
+
None if nothing has been measured yet.
|
|
168
|
+
overwrite_layout: A layout indicating the physical qubit mapping to use for the serialized instructions, this
|
|
169
|
+
overwrites the circuit's layout.
|
|
112
170
|
|
|
113
171
|
Returns:
|
|
114
|
-
list of instructions representing the circuit
|
|
172
|
+
list of IQM instructions representing the circuit
|
|
115
173
|
|
|
116
174
|
Raises:
|
|
117
175
|
ValueError: circuit contains an unsupported instruction or is not transpiled in general
|
|
@@ -119,7 +177,8 @@ def serialize_instructions( # noqa: PLR0912, PLR0915
|
|
|
119
177
|
"""
|
|
120
178
|
instructions: list[CircuitOperation] = []
|
|
121
179
|
# maps clbits to the latest "measure" instruction to store its result there
|
|
122
|
-
clbit_to_measure
|
|
180
|
+
if clbit_to_measure is None:
|
|
181
|
+
clbit_to_measure = {}
|
|
123
182
|
for circuit_instruction in circuit.data:
|
|
124
183
|
instruction = circuit_instruction.operation
|
|
125
184
|
qubit_names = tuple(qubit_index_to_name[circuit.find_bit(qubit).index] for qubit in circuit_instruction.qubits)
|
|
@@ -178,44 +237,56 @@ def serialize_instructions( # noqa: PLR0912, PLR0915
|
|
|
178
237
|
elif instruction.name in allowed_nonnative_gates:
|
|
179
238
|
args = {f"p{i}": param for i, param in enumerate(instruction.params)}
|
|
180
239
|
native_inst = CircuitOperation(name=instruction.name, locus=qubit_names, args=args)
|
|
240
|
+
elif instruction.name == "if_else":
|
|
241
|
+
if_block, else_block = instruction.params
|
|
242
|
+
if else_block is not None and len(else_block) > 0: # Non-empty circuit in else-block
|
|
243
|
+
raise ValueError("The use of an else-block with if_test is not supported.")
|
|
244
|
+
# Serialize the if-block.
|
|
245
|
+
# NOTE The if-block circuit has no qregs of its own, just references to the parent circuit qregs,
|
|
246
|
+
# and it does not always have every parent circuit qubit.
|
|
247
|
+
# Hence we need to make a new qubit index to name mapping for it.
|
|
248
|
+
|
|
249
|
+
# NOTE Sometimes the qubits in the if-block are in the circuit and
|
|
250
|
+
# sometimes they are in the layout depending on how the circuit was transpiled.
|
|
251
|
+
# So let's find out where to get the physical qubits from.
|
|
252
|
+
use_overwrite_layout = overwrite_layout is not None and all(
|
|
253
|
+
qb in overwrite_layout.get_physical_bits().values() for qb in if_block.qubits
|
|
254
|
+
)
|
|
255
|
+
if use_overwrite_layout:
|
|
256
|
+
physical_qubits = {q: i for i, q in overwrite_layout.get_physical_bits().items()} # type: ignore[union-attr]
|
|
257
|
+
q_index_to_name = {k: qubit_index_to_name[physical_qubits[q]] for k, q in enumerate(if_block.qubits)}
|
|
258
|
+
elif circuit.layout is None or all(qb in circuit.qubits for qb in if_block.qubits):
|
|
259
|
+
q_index_to_name = {
|
|
260
|
+
k: qubit_index_to_name[circuit.find_bit(q).index] for k, q in enumerate(if_block.qubits)
|
|
261
|
+
}
|
|
262
|
+
else:
|
|
263
|
+
physical_qubits = {q: i for i, q in circuit.layout.initial_layout.get_physical_bits().items()}
|
|
264
|
+
q_index_to_name = {k: qubit_index_to_name[physical_qubits[q]] for k, q in enumerate(if_block.qubits)}
|
|
265
|
+
|
|
266
|
+
if_instructions = serialize_instructions(
|
|
267
|
+
if_block, q_index_to_name, allowed_nonnative_gates, clbit_to_measure=clbit_to_measure
|
|
268
|
+
)
|
|
269
|
+
_apply_condition(instruction, if_instructions, clbit_to_measure)
|
|
270
|
+
instructions.extend(if_instructions)
|
|
271
|
+
continue # Skip the rest of the loop, as we already handled the instructions
|
|
181
272
|
else:
|
|
182
273
|
raise ValueError(
|
|
183
274
|
f"Instruction '{instruction.name}' in the circuit '{circuit.name}' is not natively supported. "
|
|
184
275
|
f"You need to transpile the circuit before execution."
|
|
185
276
|
)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
creg, value = condition
|
|
197
|
-
if isinstance(creg, ClassicalRegister):
|
|
198
|
-
if len(creg) != 1:
|
|
199
|
-
raise ValueError(f"{instruction} is conditioned on multiple bits, this is not supported.")
|
|
200
|
-
if value != 1:
|
|
201
|
-
raise ValueError(
|
|
202
|
-
f"{instruction} is conditioned on integer value {value}, only value 1 is supported."
|
|
277
|
+
# classically controlled gates (using the c_if method) need to be updated
|
|
278
|
+
if (
|
|
279
|
+
Version(qiskit_version) < Version("2.0.0") and instruction.condition is not None
|
|
280
|
+
): # None means no classical condition
|
|
281
|
+
if Version(qiskit_version) < Version("1.3.0"):
|
|
282
|
+
# Avoid double deprecation warnings.
|
|
283
|
+
warnings.warn(
|
|
284
|
+
DeprecationWarning(
|
|
285
|
+
"The use of Qiskit's `c_if` method is deprecated and will be removed in a future release"
|
|
286
|
+
"of IQM Client. Please use the `with circuit.if_test(...)` construction instead."
|
|
203
287
|
)
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
clbit = creg # it is a Clbit
|
|
207
|
-
|
|
208
|
-
# Set up feedback routing.
|
|
209
|
-
# The latest "measure" instruction to write to that classical bit is modified, it is
|
|
210
|
-
# given an explicit feedback_key equal to its measurement key.
|
|
211
|
-
# The same feedback_key is given to the controlled instruction, along with the feedback qubit.
|
|
212
|
-
measure_inst = clbit_to_measure[clbit]
|
|
213
|
-
feedback_key = measure_inst.args["key"]
|
|
214
|
-
measure_inst.args["feedback_key"] = feedback_key # this measure is used to provide feedback
|
|
215
|
-
physical_qubit_name = measure_inst.locus[0] # single-qubit measurement
|
|
216
|
-
native_inst.args["feedback_key"] = feedback_key
|
|
217
|
-
native_inst.args["feedback_qubit"] = physical_qubit_name
|
|
218
|
-
|
|
288
|
+
)
|
|
289
|
+
_apply_condition(instruction, [native_inst], clbit_to_measure)
|
|
219
290
|
instructions.append(native_inst)
|
|
220
291
|
return instructions
|
|
221
292
|
|
|
@@ -299,7 +370,8 @@ def deserialize_instructions(
|
|
|
299
370
|
phase = instr.args["phase"]
|
|
300
371
|
feedback_key = instr.args["feedback_key"]
|
|
301
372
|
# NOTE: 'feedback_qubit' is not needed, because in Qiskit you only have single-qubit measurements.
|
|
302
|
-
circuit.
|
|
373
|
+
with circuit.if_test((fk_to_clbit[feedback_key], 1)):
|
|
374
|
+
circuit.r(angle, phase, locus[0])
|
|
303
375
|
elif instr.name == "reset":
|
|
304
376
|
for qubit in locus:
|
|
305
377
|
circuit.reset(qubit)
|
|
@@ -63,15 +63,18 @@ class IQMSchedulingPlugin(PassManagerStagePlugin):
|
|
|
63
63
|
scheduling.append(
|
|
64
64
|
IQMOptimizeSingleQubitGates(drop_final_rz=self.drop_final_rz, ignore_barriers=self.ignore_barriers)
|
|
65
65
|
)
|
|
66
|
-
if
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
if self.move_gate_routing:
|
|
67
|
+
if pass_manager_config.target is None:
|
|
68
|
+
raise ValueError(
|
|
69
|
+
"PassManagerConfig must have a target backend set, unable to schedule MoveGate routing."
|
|
70
|
+
)
|
|
71
|
+
if isinstance(pass_manager_config.target, IQMTarget):
|
|
72
|
+
scheduling.append(
|
|
73
|
+
IQMNaiveResonatorMoving(
|
|
74
|
+
target=pass_manager_config.target,
|
|
75
|
+
existing_moves_handling=self.existing_move_handling,
|
|
76
|
+
)
|
|
73
77
|
)
|
|
74
|
-
)
|
|
75
78
|
return scheduling
|
|
76
79
|
|
|
77
80
|
|
|
@@ -33,14 +33,14 @@ iqm/qiskit_iqm/iqm_circuit.py,sha256=jaPo3zc5FC0vAIumh5d56fr44fDaJXXwcquBzQEy1Yg
|
|
|
33
33
|
iqm/qiskit_iqm/iqm_circuit_validation.py,sha256=9pneZKs-KjBDGeDI6RHj6lB-ACqugbnYr1BqkJwLcXg,1737
|
|
34
34
|
iqm/qiskit_iqm/iqm_job.py,sha256=YLkAF4oqQ44dOLWbsqMiYCyj7pd3Rk6NIZ_TI1XtcQ8,11700
|
|
35
35
|
iqm/qiskit_iqm/iqm_move_layout.py,sha256=ECf1BcRmXKeClc7AL0lHedvJbqtwV5rEHcOOFR8shKU,10534
|
|
36
|
-
iqm/qiskit_iqm/iqm_naive_move_pass.py,sha256=
|
|
36
|
+
iqm/qiskit_iqm/iqm_naive_move_pass.py,sha256=3wuZMGIFJ1UZnfvsmrBAhA_W3cYWdQ8xKZiaJZbk4x0,12987
|
|
37
37
|
iqm/qiskit_iqm/iqm_provider.py,sha256=KHF27Qk12-_OA7_UZ8Rd25qDfFq4np2jv11_9f4tchQ,18599
|
|
38
|
-
iqm/qiskit_iqm/iqm_target.py,sha256=
|
|
39
|
-
iqm/qiskit_iqm/iqm_transpilation.py,sha256=
|
|
38
|
+
iqm/qiskit_iqm/iqm_target.py,sha256=NHpfCmkE090eblH3asNKUtD361kGfX6ZeXzUb8iMku4,16098
|
|
39
|
+
iqm/qiskit_iqm/iqm_transpilation.py,sha256=RccAHY7lNXvg7TWKG7fBNBCgFu5Xd9B_J9V9uKLlmuY,18336
|
|
40
40
|
iqm/qiskit_iqm/move_gate.py,sha256=UbrQSfrpVV3QKGJ93TelxEfZkl1wY4uWL8IH_QDpGUw,2840
|
|
41
41
|
iqm/qiskit_iqm/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
|
-
iqm/qiskit_iqm/qiskit_to_iqm.py,sha256
|
|
43
|
-
iqm/qiskit_iqm/transpiler_plugins.py,sha256=
|
|
42
|
+
iqm/qiskit_iqm/qiskit_to_iqm.py,sha256=-CWYTxNTeBNXlgllStwyjNkCor5FjkM_qfjP37L6z8Y,18872
|
|
43
|
+
iqm/qiskit_iqm/transpiler_plugins.py,sha256=BrCkUFQUdi0m8K8V0ERYgtPAkvoX4VlYIJwnFKyCN9Q,8831
|
|
44
44
|
iqm/qiskit_iqm/examples/__init__.py,sha256=M4ElQHCo-WxtVXK39bF3QiFT3IGXPtZ1khqexHiTBEc,20
|
|
45
45
|
iqm/qiskit_iqm/examples/bell_measure.py,sha256=vlayliApHU70a2zTnN_gs6iKSSdaB4Jip-iQHJNYN5M,3064
|
|
46
46
|
iqm/qiskit_iqm/examples/transpile_example.py,sha256=cQmXXx3lqvmhgWYFK_U5HmHzMOKQqXUXPYfQhkyHH14,2135
|
|
@@ -51,10 +51,10 @@ iqm/qiskit_iqm/fake_backends/fake_apollo.py,sha256=6YSaKtUc6zZ4m3iAREbmxL4tRs_DW
|
|
|
51
51
|
iqm/qiskit_iqm/fake_backends/fake_deneb.py,sha256=_xpo_NJdu9t4ER8p5be-xTRmApkFY6kQw1UEGUPizk0,3193
|
|
52
52
|
iqm/qiskit_iqm/fake_backends/fake_garnet.py,sha256=GI0xafTCj1Um09qVuccO6GPOGBm6ygul_O40Wu220Ys,5555
|
|
53
53
|
iqm/qiskit_iqm/fake_backends/iqm_fake_backend.py,sha256=wJtfsxjPYbDKmzaz5R4AuaXvvPHa21WyPtRgNctL9eY,16785
|
|
54
|
-
iqm_client-32.
|
|
55
|
-
iqm_client-32.
|
|
56
|
-
iqm_client-32.
|
|
57
|
-
iqm_client-32.
|
|
58
|
-
iqm_client-32.
|
|
59
|
-
iqm_client-32.
|
|
60
|
-
iqm_client-32.
|
|
54
|
+
iqm_client-32.1.1.dist-info/AUTHORS.rst,sha256=qsxeK5A3-B_xK3hNbhFHEIkoHNpo7sdzYyRTs7Bdtm8,795
|
|
55
|
+
iqm_client-32.1.1.dist-info/LICENSE.txt,sha256=2DXrmQtVVUV9Fc9RBFJidMiTEaQlG2oAtlC9PMrEwTk,11333
|
|
56
|
+
iqm_client-32.1.1.dist-info/METADATA,sha256=KUS_M5HU81o7THuT1V1TG2-vtRiXCn2_S8umUJaXA04,17887
|
|
57
|
+
iqm_client-32.1.1.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
|
|
58
|
+
iqm_client-32.1.1.dist-info/entry_points.txt,sha256=Kk2qfRwk8vbIJ7qCAvmaUogfRRn6t92_hBFhe6kqAE4,1317
|
|
59
|
+
iqm_client-32.1.1.dist-info/top_level.txt,sha256=NB4XRfyDS6_wG9gMsyX-9LTU7kWnTQxNvkbzIxGv3-c,4
|
|
60
|
+
iqm_client-32.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|