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.
@@ -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:
@@ -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.dagcircuit import DAGCircuit
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
- self._validate_ops(dag)
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
- # convert into PRX + RZ
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 self._drop_final_rz:
132
+ if not decompose_rz_to_r:
124
133
  for qubit_index, rz_angle in enumerate(rz_angles):
125
- if rz_angle != 0:
126
- qubit = dag.qubits[qubit_index]
127
- dag.apply_operation_back(RGate(-np.pi, 0), qargs=(qubit,))
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:
@@ -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.circuit import ClassicalRegister, Clbit, QuantumRegister
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, qubit_index_to_name: dict[int, str], allowed_nonnative_gates: Collection[str] = ()
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: dict[Clbit, CircuitOperation] = {}
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
- # classically controlled gates (using the c_if method)
188
- # TODO we do not check anywhere if cc_prx is available for this locus!
189
- condition = instruction.condition
190
- if condition is not None:
191
- if native_inst.name != "prx":
192
- raise ValueError(
193
- f"This backend only supports conditionals on r, x, y, rx and ry gates, not on {instruction.name}"
194
- )
195
- native_inst.name = "cc_prx"
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
- clbit = creg[0]
205
- else:
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.r(angle, phase, locus[0]).c_if(fk_to_clbit[feedback_key], 1)
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 pass_manager_config.target is None:
67
- raise ValueError("PassManagerConfig must have a target backend set, unable to schedule MoveGate routing.")
68
- if self.move_gate_routing and isinstance(pass_manager_config.target, IQMTarget):
69
- scheduling.append(
70
- IQMNaiveResonatorMoving(
71
- target=pass_manager_config.target,
72
- existing_moves_handling=self.existing_move_handling,
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iqm-client
3
- Version: 32.0.0
3
+ Version: 32.1.1
4
4
  Summary: Client library for accessing an IQM quantum computer
5
5
  Author-email: IQM Finland Oy <developers@meetiqm.com>
6
6
  License: Apache License
@@ -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=jhTfvhrNDKt6NhhJg_3Y-5x6E1HRNzC_n4A27ZQTuvQ,12962
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=UyULiGMn6UJsyILBQiriso9KbhlmzP9TZItS2URaXWg,15832
39
- iqm/qiskit_iqm/iqm_transpilation.py,sha256=6_6Mri01_HQBV_GTX94WSvIbu-pDMLMzEU6zVMEt6Gc,9153
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=3Sm1gLELFLCLf6QiATZSiPzLzkjvzUakhU2N6KoBXC8,14948
43
- iqm/qiskit_iqm/transpiler_plugins.py,sha256=iuReGL42fCe5aOoH-KMUsb6t7Ok9qmIIj2S4yotJJ-U,8749
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.0.0.dist-info/AUTHORS.rst,sha256=qsxeK5A3-B_xK3hNbhFHEIkoHNpo7sdzYyRTs7Bdtm8,795
55
- iqm_client-32.0.0.dist-info/LICENSE.txt,sha256=2DXrmQtVVUV9Fc9RBFJidMiTEaQlG2oAtlC9PMrEwTk,11333
56
- iqm_client-32.0.0.dist-info/METADATA,sha256=TNcHTh2I8F7zKgtU-Lh1QfNWpmUH1WwhO1SY0pJCyDo,17887
57
- iqm_client-32.0.0.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
58
- iqm_client-32.0.0.dist-info/entry_points.txt,sha256=Kk2qfRwk8vbIJ7qCAvmaUogfRRn6t92_hBFhe6kqAE4,1317
59
- iqm_client-32.0.0.dist-info/top_level.txt,sha256=NB4XRfyDS6_wG9gMsyX-9LTU7kWnTQxNvkbzIxGv3-c,4
60
- iqm_client-32.0.0.dist-info/RECORD,,
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,,