iqm-client 30.1.0__py3-none-any.whl → 31.0.0__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.
@@ -61,15 +61,15 @@ from collections.abc import Collection, Iterable, Sequence
61
61
  from enum import Enum
62
62
 
63
63
  from iqm.iqm_client import (
64
- Circuit,
65
64
  CircuitTranspilationError,
66
65
  CircuitValidationError,
67
66
  DynamicQuantumArchitecture,
68
- Instruction,
69
67
  )
70
68
  from iqm.iqm_client.models import GateImplementationInfo, GateInfo, Locus, _op_is_symmetric
71
69
  from iqm.iqm_client.validation import validate_circuit_moves, validate_instruction
72
70
 
71
+ from iqm.pulse import Circuit, CircuitOperation
72
+
73
73
  Resolution = tuple[str, str, str]
74
74
  """A (gate qubit, move qubit, resonator) triple that represents a resolution of a fictional
75
75
  qubit-qubit gate."""
@@ -93,10 +93,10 @@ class ExistingMoveHandlingOptions(str, Enum):
93
93
 
94
94
 
95
95
  def _map_loci(
96
- instructions: Iterable[Instruction],
96
+ instructions: Iterable[CircuitOperation],
97
97
  qubit_mapping: dict[str, str],
98
98
  inverse: bool = False,
99
- ) -> tuple[Instruction, ...]:
99
+ ) -> tuple[CircuitOperation, ...]:
100
100
  """Map the loci of the given instructions using the given qubit mapping, or its inverse.
101
101
 
102
102
  Args:
@@ -111,7 +111,13 @@ def _map_loci(
111
111
  if inverse:
112
112
  qubit_mapping = {phys: log for log, phys in qubit_mapping.items()}
113
113
  return tuple(
114
- inst.model_copy(update={"qubits": tuple(qubit_mapping[q] for q in inst.qubits)}) for inst in instructions
114
+ CircuitOperation(
115
+ name=inst.name,
116
+ locus=tuple(qubit_mapping[q] for q in inst.locus),
117
+ args=inst.args,
118
+ implementation=inst.implementation,
119
+ )
120
+ for inst in instructions
115
121
  )
116
122
 
117
123
 
@@ -212,7 +218,7 @@ class _ResonatorStateTracker:
212
218
  return cls.from_instructions(circuit.instructions)
213
219
 
214
220
  @classmethod
215
- def from_instructions(cls, instructions: Iterable[Instruction]) -> _ResonatorStateTracker:
221
+ def from_instructions(cls, instructions: Iterable[CircuitOperation]) -> _ResonatorStateTracker:
216
222
  """Constructor to make the _ResonatorStateTracker from a sequence of instructions.
217
223
 
218
224
  Infers the resonator connectivity from the MOVE gates.
@@ -227,7 +233,7 @@ class _ResonatorStateTracker:
227
233
  qr_gates: dict[str, dict[str, set[str]]] = {}
228
234
  for i in instructions:
229
235
  if i.name in cls.qr_gate_names:
230
- q, r = i.qubits
236
+ q, r = i.locus
231
237
  qr_gates.setdefault(i.name, {}).setdefault(q, set()).add(r)
232
238
  return cls(qr_gates)
233
239
 
@@ -270,7 +276,7 @@ class _ResonatorStateTracker:
270
276
  self,
271
277
  qubit: str,
272
278
  resonator: str,
273
- ) -> Iterable[Instruction]:
279
+ ) -> Iterable[CircuitOperation]:
274
280
  """MOVE instruction(s) to move the state of the given qubit into the given resonator,
275
281
  or back to the qubit.
276
282
 
@@ -292,7 +298,7 @@ class _ResonatorStateTracker:
292
298
  if owner not in [qubit, resonator]:
293
299
  locus = (owner, resonator)
294
300
  self.apply_move(*locus)
295
- yield Instruction(name=self.move_gate, qubits=locus, args={})
301
+ yield CircuitOperation(name=self.move_gate, locus=locus, args={})
296
302
 
297
303
  # if the qubit does not hold its own state, restore it, unless it's in the resonator
298
304
  # find where the qubit state is (it can be in at most one resonator)
@@ -300,17 +306,17 @@ class _ResonatorStateTracker:
300
306
  if res and (holder := res[0]) != resonator:
301
307
  locus = (qubit, holder)
302
308
  self.apply_move(*locus)
303
- yield Instruction(name=self.move_gate, qubits=locus, args={})
309
+ yield CircuitOperation(name=self.move_gate, locus=locus, args={})
304
310
 
305
311
  # move qubit state to resonator, or back to the qubit
306
312
  locus = (qubit, resonator)
307
313
  self.apply_move(*locus)
308
- yield Instruction(name=self.move_gate, qubits=locus, args={})
314
+ yield CircuitOperation(name=self.move_gate, locus=locus, args={})
309
315
 
310
316
  def restore_as_move_instructions(
311
317
  self,
312
318
  resonators: Iterable[str] | None = None,
313
- ) -> list[Instruction]:
319
+ ) -> list[CircuitOperation]:
314
320
  """MOVE instructions that move the states held in the given resonators back to their qubits.
315
321
 
316
322
  Applies the returned MOVE instructions on the tracker state.
@@ -327,13 +333,13 @@ class _ResonatorStateTracker:
327
333
  if resonators is None:
328
334
  resonators = self.resonators
329
335
 
330
- instructions: list[Instruction] = []
336
+ instructions: list[CircuitOperation] = []
331
337
  for r in resonators:
332
338
  q = self.res_state_owner[r]
333
339
  # if the state in r does not belong to r, restore it to its owner
334
340
  if q != r:
335
341
  locus = (q, r)
336
- instructions.append(Instruction(name=self.move_gate, qubits=locus, args={}))
342
+ instructions.append(CircuitOperation(name=self.move_gate, locus=locus, args={}))
337
343
  self.apply_move(*locus)
338
344
  return instructions
339
345
 
@@ -367,7 +373,7 @@ class _ResonatorStateTracker:
367
373
  """
368
374
  return tuple(self.res_state_owner.get(q, q) for q in locus)
369
375
 
370
- def find_resolutions(self, inst: Instruction) -> list[Resolution]:
376
+ def find_resolutions(self, inst: CircuitOperation) -> list[Resolution]:
371
377
  """Find all the possible resolutions for the given fictional qubit-qubit gate.
372
378
 
373
379
  Given a fictional gate G acting on qubits (a, b), finds all resonators r for which the current DQA
@@ -390,10 +396,10 @@ class _ResonatorStateTracker:
390
396
  return gate_q2r.get(g, set()) & self.move_q2r.get(m, set())
391
397
 
392
398
  # G is assumed symmetric, hence we may reverse the locus order for more resolutions
393
- a, b = inst.qubits
399
+ a, b = inst.locus
394
400
  return [(a, b, r) for r in get_resonators(a, b)] + [(b, a, r) for r in get_resonators(b, a)]
395
401
 
396
- def find_best_resolution(self, inst: Instruction, lookahead: Iterable[Instruction]) -> Resolution | None:
402
+ def find_best_resolution(self, inst: CircuitOperation, lookahead: Iterable[CircuitOperation]) -> Resolution | None:
397
403
  """Find the best resolution for the fictional qubit-qubit gate instruction ``inst``
398
404
  using the available native qubit-resonator gates.
399
405
 
@@ -419,12 +425,12 @@ class _ResonatorStateTracker:
419
425
  # but that would scale exponentially in n. Instead we do some heuristics:
420
426
  # Look ahead until we find the instructions that target the locus qubits next, and include
421
427
  # their costs in the badness calculation.
422
- followers: dict[str, Instruction] = {}
428
+ followers: dict[str, CircuitOperation] = {}
423
429
  for follower in lookahead:
424
430
  if len(followers) == 2:
425
431
  break # found a follower for both locus qubits
426
- for q in follower.qubits:
427
- if q in inst.qubits:
432
+ for q in follower.locus:
433
+ if q in inst.locus:
428
434
  followers.setdefault(q, follower)
429
435
 
430
436
  def get_badness(res: Resolution, g_holder: str, m_holder: str, r_owner: str) -> int:
@@ -499,7 +505,7 @@ class _ResonatorStateTracker:
499
505
  else:
500
506
  # follower needs to use same resonator but different move qubit
501
507
  badness += 2
502
- elif len(m_follower.qubits) == 1:
508
+ elif len(m_follower.locus) == 1:
503
509
  # Not a resolvable QR gate. For 1q gates on m, state must be restored.
504
510
  badness += 1
505
511
 
@@ -529,7 +535,7 @@ class _ResonatorStateTracker:
529
535
  # return the best option
530
536
  return min(options, key=lambda x: x[1])[0]
531
537
 
532
- def get_sequence(self, resolution: Resolution, inst: Instruction) -> list[Instruction]:
538
+ def get_sequence(self, resolution: Resolution, inst: CircuitOperation) -> list[CircuitOperation]:
533
539
  """Apply a fictional two-qubit gate using a sequence of native qubit-resonator gates.
534
540
 
535
541
  See :mod:`~iqm.iqm_client.transpile`.
@@ -547,7 +553,7 @@ class _ResonatorStateTracker:
547
553
 
548
554
  """
549
555
  g, m, r = resolution
550
- seq: list[Instruction] = []
556
+ seq: list[CircuitOperation] = []
551
557
  # does m state need to be moved to the resonator?
552
558
  m_holder = self.qubit_state_holder.get(m, m)
553
559
  if m_holder != r:
@@ -557,14 +563,14 @@ class _ResonatorStateTracker:
557
563
  if g_holder != g:
558
564
  seq += self.restore_as_move_instructions([g_holder])
559
565
  # apply G(g, r)
560
- seq.append(inst.model_copy(update={"qubits": (g, r)}))
566
+ seq.append(CircuitOperation(name=inst.name, locus=(g, r), args=inst.args, implementation=inst.implementation))
561
567
  return seq
562
568
 
563
569
  def insert_moves(
564
570
  self,
565
- instructions: Sequence[Instruction],
571
+ instructions: Sequence[CircuitOperation],
566
572
  arch: DynamicQuantumArchitecture,
567
- ) -> list[Instruction]:
573
+ ) -> list[CircuitOperation]:
568
574
  """Convert a simplified architecture circuit into a equivalent Star architecture circuit with
569
575
  resonators and MOVE gates.
570
576
 
@@ -587,10 +593,10 @@ class _ResonatorStateTracker:
587
593
  """
588
594
  # This method can handle real single- and two-qubit gates, real q-r gates including MOVE,
589
595
  # and fictional two-qubit gates which it decomposes into real q-r gates.
590
- new_instructions: list[Instruction] = []
596
+ new_instructions: list[CircuitOperation] = []
591
597
 
592
598
  for idx, inst in enumerate(instructions):
593
- locus = inst.qubits
599
+ locus = inst.locus
594
600
  try:
595
601
  validate_instruction(architecture=arch, instruction=inst)
596
602
  # inst can be applied as is on locus, but we may first need to use MOVEs to make
@@ -828,11 +834,11 @@ def transpile_remove_moves(circuit: Circuit) -> Circuit:
828
834
  for inst in circuit.instructions:
829
835
  if inst.name == tracker.move_gate:
830
836
  # update the state tracking, drop the MOVE
831
- tracker.apply_move(*inst.qubits)
837
+ tracker.apply_move(*inst.locus)
832
838
  else:
833
839
  # map the instruction locus
834
- new_qubits = tracker.map_resonators_in_locus(inst.qubits)
840
+ new_locus = tracker.map_resonators_in_locus(inst.locus)
835
841
  new_instructions.append(
836
- Instruction(name=inst.name, implementation=inst.implementation, qubits=new_qubits, args=inst.args)
842
+ CircuitOperation(name=inst.name, implementation=inst.implementation, locus=new_locus, args=inst.args)
837
843
  )
838
844
  return Circuit(name=circuit.name, instructions=tuple(new_instructions), metadata=circuit.metadata)
iqm/iqm_client/util.py CHANGED
@@ -22,7 +22,7 @@ import numpy as np
22
22
  class IQMJSONEncoder(JSONEncoder):
23
23
  """JSONEncoder that that adds support for some non-JSON datatypes"""
24
24
 
25
- def default(self, o: Any):
25
+ def default(self, o: Any): # noqa: ANN201
26
26
  if isinstance(o, np.ndarray):
27
27
  return o.tolist()
28
28
  return JSONEncoder.default(self, o)
@@ -13,14 +13,14 @@ import itertools
13
13
  from iqm.iqm_client.errors import CircuitValidationError
14
14
  from iqm.iqm_client.models import (
15
15
  _SUPPORTED_OPERATIONS,
16
- Circuit,
17
16
  CircuitBatch,
18
17
  DynamicQuantumArchitecture,
19
- Instruction,
20
18
  MoveGateValidationMode,
21
19
  QIRCode,
22
20
  )
23
21
 
22
+ from iqm.pulse import Circuit, CircuitOperation
23
+
24
24
 
25
25
  def validate_qubit_mapping(
26
26
  architecture: DynamicQuantumArchitecture,
@@ -52,7 +52,7 @@ def validate_qubit_mapping(
52
52
  for i, circuit in enumerate(circuits):
53
53
  if isinstance(circuit, (QIRCode)):
54
54
  continue
55
- diff = circuit.all_qubits() - set(qubit_mapping)
55
+ diff = circuit.all_locus_components() - set(qubit_mapping)
56
56
  if diff:
57
57
  raise CircuitValidationError(
58
58
  f"The qubits {diff} in circuit '{circuit.name}' at index {i} "
@@ -112,7 +112,7 @@ def validate_circuit_instructions(
112
112
 
113
113
  def validate_instruction(
114
114
  architecture: DynamicQuantumArchitecture,
115
- instruction: Instruction,
115
+ instruction: CircuitOperation,
116
116
  qubit_mapping: dict[str, str] | None = None,
117
117
  ) -> None:
118
118
  """Validate an instruction against the dynamic quantum architecture.
@@ -134,11 +134,11 @@ def validate_instruction(
134
134
  raise CircuitValidationError(f"Unknown quantum operation '{instruction.name}'.")
135
135
 
136
136
  # apply the qubit mapping if any
137
- mapped_qubits = tuple(qubit_mapping[q] for q in instruction.qubits) if qubit_mapping else instruction.qubits
137
+ mapped_qubits = tuple(qubit_mapping[q] for q in instruction.locus) if qubit_mapping else instruction.locus
138
138
 
139
139
  def check_locus_components(allowed_components: Iterable[str], msg: str) -> None:
140
140
  """Checks that the instruction locus consists of the allowed components only."""
141
- for q, mapped_q in zip(instruction.qubits, mapped_qubits):
141
+ for q, mapped_q in zip(instruction.locus, mapped_qubits):
142
142
  if mapped_q not in allowed_components:
143
143
  raise CircuitValidationError(
144
144
  f"{instruction!r}: Component {q} = {mapped_q} {msg}."
@@ -146,10 +146,14 @@ def validate_instruction(
146
146
  else f"{instruction!r}: Component {q} {msg}."
147
147
  )
148
148
 
149
- if op_info.no_calibration_needed:
150
- # all QPU loci are allowed
151
- check_locus_components(architecture.components, msg="does not exist on the QPU")
152
- return
149
+ impl_name = instruction.implementation or _SUPPORTED_OPERATIONS[
150
+ instruction.name
151
+ ].get_default_implementation_for_locus(instruction.locus)
152
+ if (impl := op_info.implementations.get(impl_name)) is not None:
153
+ if not impl.needs_calibration():
154
+ # all QPU loci are allowed
155
+ check_locus_components(architecture.components, msg="does not exist on the QPU")
156
+ return
153
157
 
154
158
  gate_info = architecture.gates.get(instruction.name)
155
159
  if gate_info is None:
@@ -187,13 +191,13 @@ def validate_instruction(
187
191
  )
188
192
  if mapped_qubits not in all_loci:
189
193
  raise CircuitValidationError(
190
- f"{instruction.qubits} = {tuple(mapped_qubits)} is not allowed as locus for '{instruction_name}'"
194
+ f"{instruction.locus} = {tuple(mapped_qubits)} is not allowed as locus for '{instruction_name}'"
191
195
  if qubit_mapping
192
- else f"{instruction.qubits} is not allowed as locus for '{instruction_name}'"
196
+ else f"{instruction.locus} is not allowed as locus for '{instruction_name}'"
193
197
  )
194
198
 
195
199
 
196
- def validate_circuit_moves(
200
+ def validate_circuit_moves( # noqa: PLR0912
197
201
  architecture: DynamicQuantumArchitecture,
198
202
  circuit: Circuit,
199
203
  qubit_mapping: dict[str, str] | None = None,
@@ -243,17 +247,17 @@ def validate_circuit_moves(
243
247
 
244
248
  for inst in circuit.instructions:
245
249
  if inst.name == "move":
246
- qubit, resonator = inst.qubits
250
+ qubit, resonator = inst.locus
247
251
  if not (qubit in all_qubits and resonator in all_resonators):
248
252
  raise CircuitValidationError(
249
- f"MOVE instructions are only allowed between qubit and resonator, not {inst.qubits}."
253
+ f"MOVE instructions are only allowed between qubit and resonator, not {inst.locus}."
250
254
  )
251
255
 
252
256
  if (resonator_qubit := resonator_occupations.get(resonator)) is None:
253
257
  # Beginning MOVE: check that the qubit hasn't been moved to another resonator
254
258
  if qubit in moved_qubits:
255
259
  raise CircuitValidationError(
256
- f"MOVE instruction {inst.qubits}: state of {qubit} is "
260
+ f"MOVE instruction {inst.locus}: state of {qubit} is "
257
261
  f"in another resonator: {resonator_occupations}."
258
262
  )
259
263
  resonator_occupations[resonator] = qubit
@@ -262,16 +266,16 @@ def validate_circuit_moves(
262
266
  # Ending MOVE: check that the qubit matches to the qubit that was moved to the resonator
263
267
  if resonator_qubit != qubit:
264
268
  raise CircuitValidationError(
265
- f"MOVE instruction {inst.qubits} to an already occupied resonator: {resonator_occupations}."
269
+ f"MOVE instruction {inst.locus} to an already occupied resonator: {resonator_occupations}."
266
270
  )
267
271
  del resonator_occupations[resonator]
268
272
  moved_qubits.remove(qubit)
269
273
  elif moved_qubits:
270
274
  # Validate that moved qubits are not used during MOVE operations
271
275
  if inst.name not in allowed_gates:
272
- if overlap := set(inst.qubits) & moved_qubits:
276
+ if overlap := set(inst.locus) & moved_qubits:
273
277
  raise CircuitValidationError(
274
- f"Instruction {inst.name} acts on {inst.qubits} while the state(s) of {overlap} "
278
+ f"Instruction {inst.name} acts on {inst.locus} while the state(s) of {overlap} "
275
279
  f"are in a resonator. Current resonator occupation: {resonator_occupations}."
276
280
  )
277
281
 
@@ -22,7 +22,7 @@ from qiskit import QuantumCircuit
22
22
  class IQMCircuit(QuantumCircuit):
23
23
  """Extends the QuantumCircuit class, adding a shortcut for applying the MOVE gate."""
24
24
 
25
- def move(self, qubit: int, resonator: int):
25
+ def move(self, qubit: int, resonator: int): # noqa: ANN201
26
26
  """Applies the MOVE gate to the circuit.
27
27
 
28
28
  Note: at this point the circuit layout is only guaranteed to work if the order
@@ -21,7 +21,7 @@ from iqm.qiskit_iqm.qiskit_to_iqm import serialize_instructions
21
21
  from qiskit import QuantumCircuit
22
22
 
23
23
 
24
- def validate_circuit(
24
+ def validate_circuit( # noqa: ANN201
25
25
  circuit: QuantumCircuit,
26
26
  backend: IQMBackendBase,
27
27
  validate_moves: MoveGateValidationMode | None = None,
iqm/qiskit_iqm/iqm_job.py CHANGED
@@ -24,7 +24,6 @@ import warnings
24
24
  from iqm.iqm_client import (
25
25
  DEFAULT_TIMEOUT_SECONDS,
26
26
  APITimeoutError,
27
- Circuit,
28
27
  CircuitMeasurementResults,
29
28
  HeraldingMode,
30
29
  IQMClient,
@@ -38,6 +37,8 @@ import numpy as np
38
37
  from qiskit.providers import JobStatus, JobV1
39
38
  from qiskit.result import Counts, Result
40
39
 
40
+ from iqm.pulse import Circuit
41
+
41
42
  if TYPE_CHECKING:
42
43
  from iqm.qiskit_iqm.iqm_provider import IQMBackend
43
44
 
@@ -140,7 +141,7 @@ class IQMJob(JobV1):
140
141
  for s in range(shots)
141
142
  ]
142
143
 
143
- def submit(self):
144
+ def submit(self): # noqa: ANN201
144
145
  raise NotImplementedError(
145
146
  "You should never have to submit jobs by calling this method. When running circuits through "
146
147
  "RemoteIQMBackend, the submission will happen under the hood. The job instance that you get is only for "
@@ -101,7 +101,7 @@ class IQMMoveLayout(TrivialLayout):
101
101
  return False
102
102
  return True
103
103
 
104
- def run(self, dag: DAGCircuit):
104
+ def run(self, dag: DAGCircuit): # noqa: ANN201
105
105
  """Creates a valid layout for the given quantum circuit.
106
106
 
107
107
  Args:
@@ -194,7 +194,7 @@ class IQMMoveLayout(TrivialLayout):
194
194
  resonators: set[int] = set()
195
195
  qubit_to_idx: dict[Qubit, int] = {qubit: log_idx for log_idx, qubit in enumerate(dag.qubits)}
196
196
 
197
- def _require_qubit_type(qubit: Qubit, required_type: str):
197
+ def _require_qubit_type(qubit: Qubit, required_type: str): # noqa: ANN202
198
198
  """Add a requirement for the given qubit."""
199
199
  log_idx = qubit_to_idx[qubit]
200
200
  if log_idx in resonators:
@@ -204,7 +204,7 @@ class IQMMoveLayout(TrivialLayout):
204
204
  )
205
205
  reqs.setdefault(log_idx, set()).add(required_type)
206
206
 
207
- def _require_resonator(qubit: Qubit):
207
+ def _require_resonator(qubit: Qubit): # noqa: ANN202
208
208
  """Add a requirement for the given resonator."""
209
209
  log_idx = qubit_to_idx[qubit]
210
210
  if log_idx in reqs:
@@ -22,7 +22,6 @@ from uuid import UUID
22
22
  import warnings
23
23
 
24
24
  from iqm.iqm_client import (
25
- Circuit,
26
25
  CircuitBatch,
27
26
  CircuitCompilationOptions,
28
27
  CircuitValidationError,
@@ -39,6 +38,8 @@ from iqm.qiskit_iqm.qiskit_to_iqm import serialize_instructions
39
38
  from qiskit import QuantumCircuit
40
39
  from qiskit.providers import JobStatus, JobV1, Options
41
40
 
41
+ from iqm.pulse import Circuit
42
+
42
43
  try:
43
44
  __version__ = version("qiskit-iqm")
44
45
  except PackageNotFoundError: # pragma: no cover
@@ -93,7 +93,7 @@ class IQMTarget(Target):
93
93
  self.iqm_metrics = metrics
94
94
  self._add_instructions_from_DQA()
95
95
 
96
- def _add_instructions_from_DQA(self):
96
+ def _add_instructions_from_DQA(self): # noqa: ANN202
97
97
  """Initializes the Target with instructions and properties that represent the
98
98
  dynamic quantum architecture :attr:`iqm_dqa`.
99
99
 
@@ -129,7 +129,7 @@ class IQMOptimizeSingleQubitGates(TransformationPass):
129
129
 
130
130
  return dag
131
131
 
132
- def _validate_ops(self, dag: DAGCircuit):
132
+ def _validate_ops(self, dag: DAGCircuit): # noqa: ANN202
133
133
  valid_ops = self._basis + ["measure", "reset", "delay", "barrier"]
134
134
  for node in dag.op_nodes():
135
135
  if node.name not in valid_ops:
@@ -184,7 +184,7 @@ class IQMReplaceGateWithUnitaryPass(TransformationPass):
184
184
  self.gate = gate
185
185
  self.unitary = unitary
186
186
 
187
- def run(self, dag):
187
+ def run(self, dag): # noqa: ANN001, ANN201
188
188
  for node in dag.op_nodes():
189
189
  if node.name == self.gate:
190
190
  dag.substitute_node(node, UnitaryGate(self.unitary))
@@ -53,12 +53,12 @@ class MoveGate(Gate):
53
53
  order ``[qubit, resonator]``, regardless of which component is currently holding the state.
54
54
  """
55
55
 
56
- def __init__(self, label=None):
56
+ def __init__(self, label=None): # noqa: ANN001
57
57
  """Initializes the move gate"""
58
58
  super().__init__("move", 2, [], label=label)
59
59
  self.unitary = qi.Operator(MOVE_GATE_UNITARY)
60
60
 
61
- def _define(self):
61
+ def _define(self): # noqa: ANN202
62
62
  """This function is purposefully not defined so that that the Qiskit transpiler cannot accidentally
63
63
  decompose the MOVE gate into a sequence of other gates, instead it will throw an error.
64
64
  """
@@ -17,15 +17,16 @@ from __future__ import annotations
17
17
 
18
18
  from collections.abc import Collection
19
19
  from dataclasses import dataclass
20
+ from math import pi
20
21
  import re
21
22
 
22
- from iqm.iqm_client import Instruction
23
23
  from iqm.qiskit_iqm.move_gate import MoveGate
24
- import numpy as np
25
24
  from qiskit import QuantumCircuit as QiskitQuantumCircuit
26
25
  from qiskit.circuit import ClassicalRegister, Clbit, QuantumRegister
27
26
  from qiskit.transpiler.layout import Layout
28
27
 
28
+ from iqm.pulse import CircuitOperation
29
+
29
30
 
30
31
  class InstructionNotSupportedError(RuntimeError):
31
32
  """Raised when a given instruction is not supported by the IQM server."""
@@ -94,7 +95,7 @@ class MeasurementKey:
94
95
 
95
96
  def serialize_instructions( # noqa: PLR0912, PLR0915
96
97
  circuit: QiskitQuantumCircuit, qubit_index_to_name: dict[int, str], allowed_nonnative_gates: Collection[str] = ()
97
- ) -> list[Instruction]:
98
+ ) -> list[CircuitOperation]:
98
99
  """Serialize a quantum circuit into the IQM data transfer format.
99
100
 
100
101
  This is IQM's internal helper for :meth:`.IQMBackend.serialize_circuit` that gives slightly more control.
@@ -116,32 +117,32 @@ def serialize_instructions( # noqa: PLR0912, PLR0915
116
117
  ValueError: circuit contains an unsupported instruction or is not transpiled in general
117
118
 
118
119
  """
119
- instructions: list[Instruction] = []
120
+ instructions: list[CircuitOperation] = []
120
121
  # maps clbits to the latest "measure" instruction to store its result there
121
- clbit_to_measure: dict[Clbit, Instruction] = {}
122
+ clbit_to_measure: dict[Clbit, CircuitOperation] = {}
122
123
  for circuit_instruction in circuit.data:
123
124
  instruction = circuit_instruction.operation
124
- qubit_names = [qubit_index_to_name[circuit.find_bit(qubit).index] for qubit in circuit_instruction.qubits]
125
+ qubit_names = tuple(qubit_index_to_name[circuit.find_bit(qubit).index] for qubit in circuit_instruction.qubits)
125
126
  if instruction.name == "r":
126
- angle_t = float(instruction.params[0] / (2 * np.pi))
127
- phase_t = float(instruction.params[1] / (2 * np.pi))
128
- native_inst = Instruction(name="prx", qubits=qubit_names, args={"angle_t": angle_t, "phase_t": phase_t})
127
+ angle = float(instruction.params[0])
128
+ phase = float(instruction.params[1])
129
+ native_inst = CircuitOperation(name="prx", locus=qubit_names, args={"angle": angle, "phase": phase})
129
130
  elif instruction.name == "x":
130
- native_inst = Instruction(name="prx", qubits=qubit_names, args={"angle_t": 0.5, "phase_t": 0.0})
131
+ native_inst = CircuitOperation(name="prx", locus=qubit_names, args={"angle": pi, "phase": 0.0})
131
132
  elif instruction.name == "rx":
132
- angle_t = float(instruction.params[0] / (2 * np.pi))
133
- native_inst = Instruction(name="prx", qubits=qubit_names, args={"angle_t": angle_t, "phase_t": 0.0})
133
+ angle = float(instruction.params[0])
134
+ native_inst = CircuitOperation(name="prx", locus=qubit_names, args={"angle": angle, "phase": 0.0})
134
135
  elif instruction.name == "y":
135
- native_inst = Instruction(name="prx", qubits=qubit_names, args={"angle_t": 0.5, "phase_t": 0.25})
136
+ native_inst = CircuitOperation(name="prx", locus=qubit_names, args={"angle": pi, "phase": 0.5 * pi})
136
137
  elif instruction.name == "ry":
137
- angle_t = float(instruction.params[0] / (2 * np.pi))
138
- native_inst = Instruction(name="prx", qubits=qubit_names, args={"angle_t": angle_t, "phase_t": 0.25})
138
+ angle = float(instruction.params[0])
139
+ native_inst = CircuitOperation(name="prx", locus=qubit_names, args={"angle": angle, "phase": 0.5 * pi})
139
140
  elif instruction.name == "cz":
140
- native_inst = Instruction(name="cz", qubits=qubit_names, args={})
141
+ native_inst = CircuitOperation(name="cz", locus=qubit_names, args={})
141
142
  elif instruction.name == "move":
142
- native_inst = Instruction(name="move", qubits=qubit_names, args={})
143
+ native_inst = CircuitOperation(name="move", locus=qubit_names, args={})
143
144
  elif instruction.name == "barrier":
144
- native_inst = Instruction(name="barrier", qubits=qubit_names, args={})
145
+ native_inst = CircuitOperation(name="barrier", locus=qubit_names, args={})
145
146
  elif instruction.name == "delay":
146
147
  duration = float(instruction.params[0])
147
148
  # convert duration to seconds
@@ -160,7 +161,7 @@ def serialize_instructions( # noqa: PLR0912, PLR0915
160
161
  duration *= 1e-12
161
162
  else:
162
163
  raise ValueError(f"Delay: Unsupported unit '{unit}'")
163
- native_inst = Instruction(name="delay", qubits=qubit_names, args={"duration": duration})
164
+ native_inst = CircuitOperation(name="delay", locus=qubit_names, args={"duration": duration})
164
165
  elif instruction.name == "measure":
165
166
  if len(circuit_instruction.clbits) != 1:
166
167
  raise ValueError(
@@ -168,15 +169,15 @@ def serialize_instructions( # noqa: PLR0912, PLR0915
168
169
  )
169
170
  clbit = circuit_instruction.clbits[0] # always a single-qubit measurement
170
171
  mk = str(MeasurementKey.from_clbit(clbit, circuit))
171
- native_inst = Instruction(name="measure", qubits=qubit_names, args={"key": mk})
172
+ native_inst = CircuitOperation(name="measure", locus=qubit_names, args={"key": mk})
172
173
  clbit_to_measure[clbit] = native_inst
173
174
  elif instruction.name == "reset":
174
- native_inst = Instruction(name="reset", qubits=qubit_names, args={})
175
+ native_inst = CircuitOperation(name="reset", locus=qubit_names, args={})
175
176
  elif instruction.name == "id":
176
177
  continue
177
178
  elif instruction.name in allowed_nonnative_gates:
178
179
  args = {f"p{i}": param for i, param in enumerate(instruction.params)}
179
- native_inst = Instruction.model_construct(name=instruction.name, qubits=tuple(qubit_names), args=args)
180
+ native_inst = CircuitOperation(name=instruction.name, locus=qubit_names, args=args)
180
181
  else:
181
182
  raise ValueError(
182
183
  f"Instruction '{instruction.name}' in the circuit '{circuit.name}' is not natively supported. "
@@ -211,7 +212,7 @@ def serialize_instructions( # noqa: PLR0912, PLR0915
211
212
  measure_inst = clbit_to_measure[clbit]
212
213
  feedback_key = measure_inst.args["key"]
213
214
  measure_inst.args["feedback_key"] = feedback_key # this measure is used to provide feedback
214
- physical_qubit_name = measure_inst.qubits[0] # single-qubit measurement
215
+ physical_qubit_name = measure_inst.locus[0] # single-qubit measurement
215
216
  native_inst.args["feedback_key"] = feedback_key
216
217
  native_inst.args["feedback_qubit"] = physical_qubit_name
217
218
 
@@ -220,7 +221,7 @@ def serialize_instructions( # noqa: PLR0912, PLR0915
220
221
 
221
222
 
222
223
  def deserialize_instructions(
223
- instructions: list[Instruction], qubit_name_to_index: dict[str, int], layout: Layout
224
+ instructions: list[CircuitOperation], qubit_name_to_index: dict[str, int], layout: Layout
224
225
  ) -> QiskitQuantumCircuit:
225
226
  """Helper function to turn a list of IQM Instructions into a Qiskit QuantumCircuit.
226
227
 
@@ -276,11 +277,11 @@ def deserialize_instructions(
276
277
  *(cl_regs.get(i, ClassicalRegister(0)) for i in range(max(cl_regs) + 1 if cl_regs else 0)),
277
278
  )
278
279
  for instr in instructions:
279
- locus = [index_to_qiskit_qubit[qubit_name_to_index[q]] for q in instr.qubits]
280
+ locus = [index_to_qiskit_qubit[qubit_name_to_index[q]] for q in instr.locus]
280
281
  if instr.name == "prx":
281
- angle_t = instr.args["angle_t"] * 2 * np.pi
282
- phase_t = instr.args["phase_t"] * 2 * np.pi
283
- circuit.r(angle_t, phase_t, locus[0])
282
+ angle = instr.args["angle"]
283
+ phase = instr.args["phase"]
284
+ circuit.r(angle, phase, locus[0])
284
285
  elif instr.name == "cz":
285
286
  circuit.cz(*locus)
286
287
  elif instr.name == "move":
@@ -294,11 +295,11 @@ def deserialize_instructions(
294
295
  duration = instr.args["duration"]
295
296
  circuit.delay(duration, locus, unit="s") # native delay instructions always use seconds
296
297
  elif instr.name == "cc_prx":
297
- angle_t = instr.args["angle_t"] * 2 * np.pi
298
- phase_t = instr.args["phase_t"] * 2 * np.pi
298
+ angle = instr.args["angle"]
299
+ phase = instr.args["phase"]
299
300
  feedback_key = instr.args["feedback_key"]
300
301
  # NOTE: 'feedback_qubit' is not needed, because in Qiskit you only have single-qubit measurements.
301
- circuit.r(angle_t, phase_t, locus[0]).c_if(fk_to_clbit[feedback_key], 1)
302
+ circuit.r(angle, phase, locus[0]).c_if(fk_to_clbit[feedback_key], 1)
302
303
  elif instr.name == "reset":
303
304
  for qubit in locus:
304
305
  circuit.reset(qubit)