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.
iqm/iqm_client/models.py CHANGED
@@ -11,350 +11,216 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
- """This module contains the data models used by IQMClient."""
14
+ r"""This module contains the data models used by IQMClient.
15
15
 
16
- from __future__ import annotations
16
+ We currently support the following native operations for circuit execution,
17
+ represented by :class:`iqm.pulse.CircuitOperation`:
17
18
 
18
- from dataclasses import dataclass, field
19
- from enum import Enum
20
- from functools import cached_property
21
- import re
22
- from typing import Any
23
- from uuid import UUID
19
+ ================ =========== ======================================= ===========
20
+ name # of qubits args description
21
+ ================ =========== ======================================= ===========
22
+ measure >= 1 ``key: str``, ``feedback_key: str`` Measurement in the Z basis.
23
+ prx 1 ``angle: float``, ``phase: float`` Phased x-rotation gate.
24
+ cc_prx 1 ``angle: float``, ``phase: float``,
25
+ ``feedback_qubit: str``,
26
+ ``feedback_key: str`` Classically controlled PRX gate.
27
+ reset >= 1 Reset the qubit(s) to :math:`|0\rangle`.
28
+ cz 2 Controlled-Z gate.
29
+ move 2 Move a qubit state between a qubit and a
30
+ computational resonator, as long as
31
+ at least one of the components is
32
+ in the :math:`|0\rangle` state.
33
+ barrier >= 1 Execution barrier.
34
+ delay >= 1 ``duration: float`` Force a delay between circuit operations.
35
+ ================ =========== ======================================= ===========
24
36
 
25
- from pydantic import BaseModel, Field, StrictStr, TypeAdapter, field_validator
26
- from pydantic_core.core_schema import ValidationInfo
37
+ Measure
38
+ -------
27
39
 
40
+ :mod:`iqm.pulse.gates.measure`
28
41
 
29
- @dataclass(frozen=True)
30
- class NativeOperation:
31
- """Describes a native operation on the quantum computer."""
42
+ Measurement in the computational (Z) basis. The measurement results are the output of the circuit.
43
+ Takes two string arguments: ``key``, denoting the measurement key the returned results are labeled with,
44
+ and ``feedback_key``, which is only needed if the measurement result is used for classical control
45
+ within the circuit.
46
+ All the measurement keys and feedback keys used in a circuit must be unique (but the two groups of
47
+ keys are independent namespaces).
48
+ Each qubit may be measured multiple times, i.e. mid-circuit measurements are allowed.
32
49
 
33
- name: str
34
- """Name of the operation."""
35
- arity: int
36
- """Number of locus components (usually qubits) the operation acts on.
37
- Zero means the operation can be applied on any number of locus components."""
38
- args_required: dict[str, tuple[type, ...]] = field(default_factory=dict)
39
- """Maps names of required operation parameters to their allowed types."""
40
- args_not_required: dict[str, tuple[type, ...]] = field(default_factory=dict)
41
- """Maps names of optional operation parameters to their allowed types."""
42
- symmetric: bool = False
43
- """True iff the effect of operation is symmetric in the locus components it acts on.
44
- Only meaningful if :attr:`arity` != 1."""
45
- renamed_to: str = ""
46
- """If nonempty, indicates that this operation name is deprecated, and IQM client will
47
- auto-rename it to the new name."""
48
- factorizable: bool = False
49
- """Iff True, any multi-component instance of this operation can be broken down to
50
- single-component instances, and calibration data is specific to single-component loci."""
51
- no_calibration_needed: bool = False
52
- """Iff true, the operation is always allowed on all QPU loci regardless of calibration state.
53
- Typically a metaoperation like barrier."""
54
-
55
-
56
- _SUPPORTED_OPERATIONS: dict[str, NativeOperation] = {
57
- op.name: op
58
- for op in [
59
- NativeOperation("barrier", 0, symmetric=True, no_calibration_needed=True),
60
- NativeOperation("delay", 0, {"duration": (float,)}, symmetric=True, no_calibration_needed=True),
61
- NativeOperation("measure", 0, {"key": (str,)}, args_not_required={"feedback_key": (str,)}, factorizable=True),
62
- NativeOperation(
63
- "prx",
64
- 1,
65
- {
66
- "angle_t": (float, int),
67
- "phase_t": (float, int),
68
- },
69
- ),
70
- NativeOperation(
71
- "cc_prx",
72
- 1,
73
- {
74
- "angle_t": (float, int),
75
- "phase_t": (float, int),
76
- "feedback_key": (str,),
77
- "feedback_qubit": (str,),
78
- },
79
- ),
80
- # TODO reset does need calibration, but it inherits it from cc_prx and does not yet appear in the DQA itself.
81
- NativeOperation("reset", 0, symmetric=True, factorizable=True, no_calibration_needed=True),
82
- NativeOperation("cz", 2, symmetric=True),
83
- NativeOperation("move", 2),
84
- ]
85
- }
86
-
87
- Locus = tuple[StrictStr, ...]
88
- """Names of the QPU components (typically qubits) a quantum operation instance is acting on, e.g. `("QB1", "QB2")`."""
50
+ .. code-block:: python
51
+ :caption: Example
89
52
 
53
+ CircuitOperation(name='measure', locus=('alice', 'bob', 'charlie'), args={'key': 'm1'})
90
54
 
91
- class Instruction(BaseModel):
92
- r"""Native quantum operation instance with particular arguments and locus.
55
+ PRX
56
+ ---
93
57
 
94
- This class represents a native quantum operation
95
- acting on :attr:`qubits`, with the arguments :attr:`args`.
96
- The operation is determined by :attr:`name`.
58
+ :mod:`iqm.pulse.gates.prx`
97
59
 
98
- We currently support the following native operations:
60
+ Phased x-rotation gate, i.e. an x-rotation conjugated by a z-rotation.
61
+ Takes two arguments, the rotation angle ``angle`` and the phase angle ``phase``,
62
+ both measured in units of radians.
63
+ The gate is represented in the standard computational basis by the matrix
99
64
 
100
- ================ =========== ======================================= ===========
101
- name # of qubits args description
102
- ================ =========== ======================================= ===========
103
- measure >= 1 ``key: str``, ``feedback_key: str`` Measurement in the Z basis.
104
- prx 1 ``angle_t: float``, ``phase_t: float`` Phased x-rotation gate.
105
- cc_prx 1 ``angle_t: float``, ``phase_t: float``,
106
- ``feedback_qubit: str``,
107
- ``feedback_key: str`` Classically controlled PRX gate.
108
- reset >= 1 Reset the qubit(s) to :math:`|0\rangle`.
109
- cz 2 Controlled-Z gate.
110
- move 2 Move a qubit state between a qubit and a
111
- computational resonator, as long as
112
- at least one of the components is
113
- in the :math:`|0\rangle` state.
114
- barrier >= 1 Execution barrier.
115
- delay >= 1 ``duration: float`` Force a delay between circuit operations.
116
- ================ =========== ======================================= ===========
65
+ .. math::
66
+ \text{PRX}(\theta, \phi) = \exp(-i (X \cos (\phi) + Y \sin (\phi)) \: \theta/2)
67
+ = \text{RZ}(\phi) \: \text{RX}(\theta) \: \text{RZ}^\dagger(\phi),
117
68
 
118
- For each Instruction you may also optionally specify :attr:`~Instruction.implementation`,
119
- which contains the name of an implementation of the operation to use.
120
- Support for multiple implementations is currently experimental and in normal use the
121
- field should be omitted, this selects the default implementation for the operation for that locus.
69
+ where :math:`\theta` = ``angle``, :math:`\phi` = ``phase``,
70
+ and :math:`X` and :math:`Y` are Pauli matrices.
122
71
 
123
- Measure
124
- -------
72
+ .. code-block:: python
73
+ :caption: Example
125
74
 
126
- Measurement in the computational (Z) basis. The measurement results are the output of the circuit.
127
- Takes two string arguments: ``key``, denoting the measurement key the returned results are labeled with,
128
- and ``feedback_key``, which is only needed if the measurement result is used for classical control
129
- within the circuit.
130
- All the measurement keys and feedback keys used in a circuit must be unique (but the two groups of
131
- keys are independent namespaces).
132
- Each qubit may be measured multiple times, i.e. mid-circuit measurements are allowed.
75
+ CircuitOperation(name='prx', locus=('bob',), args={'angle': 1.4 * pi, 'phase': 0.5 * pi})
133
76
 
134
- .. code-block:: python
135
- :caption: Example
77
+ CC_PRX
78
+ ------
136
79
 
137
- Instruction(name='measure', qubits=('alice', 'bob', 'charlie'), args={'key': 'm1'})
80
+ :mod:`iqm.pulse.gates.conditional`
138
81
 
139
- PRX
140
- ---
82
+ Classically controlled PRX gate. Takes four arguments. ``angle`` and ``phase`` are exactly as in PRX.
83
+ ``feedback_key`` is a string that identifies the ``measure`` instruction whose result controls
84
+ the gate (the one that shares the feedback key).
85
+ ``feedback_qubit`` is the name of the physical qubit within the ``measure`` instruction that produces the feedback.
86
+ If the measurement result is 1, the PRX gate is applied. If it is 0, an identity gate of similar time
87
+ duration gate is applied instead.
88
+ The measurement instruction must precede the classically controlled gate instruction in the quantum circuit.
141
89
 
142
- Phased x-rotation gate, i.e. an x-rotation conjugated by a z-rotation.
143
- Takes two arguments, the rotation angle ``angle_t`` and the phase angle ``phase_t``,
144
- both measured in units of full turns (:math:`2\pi` radians).
145
- The gate is represented in the standard computational basis by the matrix
90
+ Reset
91
+ -----
146
92
 
147
- .. math::
148
- \text{PRX}(\theta, \phi) = \exp(-i (X \cos (2 \pi \; \phi) + Y \sin (2 \pi \; \phi)) \: \pi \; \theta)
149
- = \text{RZ}(\phi) \: \text{RX}(\theta) \: \text{RZ}^\dagger(\phi),
93
+ :mod:`iqm.pulse.gates.reset`
150
94
 
151
- where :math:`\theta` = ``angle_t``, :math:`\phi` = ``phase_t``,
152
- and :math:`X` and :math:`Y` are Pauli matrices.
95
+ Resets the qubit(s) non-unitarily to the :math:`|0\rangle` state.
153
96
 
154
- .. code-block:: python
155
- :caption: Example
97
+ .. code-block:: python
98
+ :caption: Example
156
99
 
157
- Instruction(name='prx', qubits=('bob',), args={'angle_t': 0.7, 'phase_t': 0.25})
100
+ CircuitOperation(name='reset', locus=('alice', 'bob'), args={})
158
101
 
159
- CC_PRX
160
- ------
102
+ .. note:: Currently inherits its calibration from ``cc_prx`` and is only available when ``cc_prx`` is.
161
103
 
162
- Classically controlled PRX gate. Takes four arguments. ``angle_t`` and ``phase_t`` are exactly as in PRX.
163
- ``feedback_key`` is a string that identifies the ``measure`` instruction whose result controls
164
- the gate (the one that shares the feedback key).
165
- ``feedback_qubit`` is the name of the physical qubit within the ``measure`` instruction that produces the feedback.
166
- If the measurement result is 1, the PRX gate is applied. If it is 0, an identity gate of similar time
167
- duration gate is applied instead.
168
- The measurement instruction must precede the classically controlled gate instruction in the quantum circuit.
104
+ CZ
105
+ --
169
106
 
170
- Reset
171
- -----
107
+ :mod:`iqm.pulse.gates.cz`
172
108
 
173
- Resets the qubit(s) non-unitarily to the :math:`|0\rangle` state.
109
+ Controlled-Z gate. Represented in the standard computational basis by the matrix
174
110
 
175
- .. code-block:: python
176
- :caption: Example
111
+ .. math:: \text{CZ} = \text{diag}(1, 1, 1, -1).
177
112
 
178
- Instruction(name='reset', qubits=('alice', 'bob'), args={})
113
+ It is symmetric wrt. the qubits it's acting on, and takes no arguments.
179
114
 
180
- .. note:: Currently inherits its calibration from ``cc_prx`` and is only available when ``cc_prx`` is.
115
+ .. code-block:: python
116
+ :caption: Example
181
117
 
182
- CZ
183
- --
118
+ CircuitOperation(name='cz', locus=('alice', 'bob'), args={})
184
119
 
185
- Controlled-Z gate. Represented in the standard computational basis by the matrix
120
+ MOVE
121
+ ----
186
122
 
187
- .. math:: \text{CZ} = \text{diag}(1, 1, 1, -1).
123
+ :mod:`iqm.pulse.gates.move`
188
124
 
189
- It is symmetric wrt. the qubits it's acting on, and takes no arguments.
125
+ The MOVE operation is a unitary population exchange operation between a qubit and a resonator.
126
+ Its effect is only defined in the invariant subspace :math:`S = \text{span}\{|00\rangle, |01\rangle, |10\rangle\}`,
127
+ where it swaps the populations of the states :math:`|01\rangle` and :math:`|10\rangle`.
128
+ Its effect on the orthogonal subspace is undefined.
190
129
 
191
- .. code-block:: python
192
- :caption: Example
130
+ MOVE has the following presentation in the subspace :math:`S`:
193
131
 
194
- Instruction(name='cz', qubits=('alice', 'bob'), args={})
132
+ .. math:: \text{MOVE}_S = |00\rangle \langle 00| + a |10\rangle \langle 01| + a^{-1} |01\rangle \langle 10|,
195
133
 
196
- MOVE
197
- ----
134
+ where :math:`a` is an undefined complex phase that is canceled when the MOVE gate is applied a second time.
198
135
 
199
- The MOVE operation is a unitary population exchange operation between a qubit and a resonator.
200
- Its effect is only defined in the invariant subspace :math:`S = \text{span}\{|00\rangle, |01\rangle, |10\rangle\}`,
201
- where it swaps the populations of the states :math:`|01\rangle` and :math:`|10\rangle`.
202
- Its effect on the orthogonal subspace is undefined.
136
+ To ensure that the state of the qubit and resonator has no overlap with :math:`|11\rangle`, it is
137
+ recommended that no single qubit gates are applied to the qubit in between a
138
+ pair of MOVE operations.
203
139
 
204
- MOVE has the following presentation in the subspace :math:`S`:
140
+ .. code-block:: python
141
+ :caption: Example
205
142
 
206
- .. math:: \text{MOVE}_S = |00\rangle \langle 00| + a |10\rangle \langle 01| + a^{-1} |01\rangle \langle 10|,
143
+ CircuitOperation(name='move', locus=('alice', 'resonator'), args={})
207
144
 
208
- where :math:`a` is an undefined complex phase that is canceled when the MOVE gate is applied a second time.
145
+ .. note:: MOVE is only available in quantum computers with the IQM Star architecture.
209
146
 
210
- To ensure that the state of the qubit and resonator has no overlap with :math:`|11\rangle`, it is
211
- recommended that no single qubit gates are applied to the qubit in between a
212
- pair of MOVE operations.
147
+ Barrier
148
+ -------
213
149
 
214
- .. code-block:: python
215
- :caption: Example
150
+ :mod:`iqm.pulse.gates.barrier`
216
151
 
217
- Instruction(name='move', qubits=('alice', 'resonator'), args={})
152
+ Affects the physical execution order of the instructions elsewhere in the
153
+ circuit that act on qubits spanned by the barrier.
154
+ It ensures that any such instructions that succeed the barrier are only executed after
155
+ all such instructions that precede the barrier have been completed.
156
+ Hence it can be used to guarantee a specific causal order for the other instructions.
157
+ It takes no arguments, and has no other effect.
218
158
 
219
- .. note:: MOVE is only available in quantum computers with the IQM Star architecture.
159
+ .. code-block:: python
160
+ :caption: Example
220
161
 
221
- Barrier
222
- -------
162
+ CircuitOperation(name='barrier', locus=('alice', 'bob'), args={})
223
163
 
224
- Affects the physical execution order of the instructions elsewhere in the
225
- circuit that act on qubits spanned by the barrier.
226
- It ensures that any such instructions that succeed the barrier are only executed after
227
- all such instructions that precede the barrier have been completed.
228
- Hence it can be used to guarantee a specific causal order for the other instructions.
229
- It takes no arguments, and has no other effect.
164
+ .. note::
230
165
 
231
- .. code-block:: python
232
- :caption: Example
166
+ One-qubit barriers will not have any effect on circuit's compilation and execution. Higher layers
167
+ that sit on top of IQM Client can make actual use of one-qubit barriers (e.g. during circuit optimization),
168
+ therefore having them is allowed.
233
169
 
234
- Instruction(name='barrier', qubits=('alice', 'bob'), args={})
170
+ Delay
171
+ -----
235
172
 
236
- .. note::
173
+ :mod:`iqm.pulse.gates.delay`
237
174
 
238
- One-qubit barriers will not have any effect on circuit's compilation and execution. Higher layers
239
- that sit on top of IQM Client can make actual use of one-qubit barriers (e.g. during circuit optimization),
240
- therefore having them is allowed.
175
+ Forces a delay between the preceding and following circuit operations.
176
+ It can be applied to any number of qubits. Takes one argument, ``duration``, which is the minimum
177
+ duration of the delay in seconds. It will be rounded up to the nearest possible duration the
178
+ hardware can handle.
241
179
 
242
- Delay
243
- -----
180
+ .. code-block:: python
181
+ :caption: Example
244
182
 
245
- Forces a delay between the preceding and following circuit operations.
246
- It can be applied to any number of qubits. Takes one argument, ``duration``, which is the minimum
247
- duration of the delay in seconds. It will be rounded up to the nearest possible duration the
248
- hardware can handle.
183
+ CircuitOperation(name='delay', locus=('alice', 'bob'), args={'duration': 80e-9})
249
184
 
250
- .. code-block:: python
251
- :caption: Example
252
185
 
253
- Instruction(name='delay', qubits=('alice', 'bob'), args={'duration': 80e-9})
186
+ .. note::
254
187
 
188
+ We can only guarantee that the delay is *at least* of the requested duration, due to both
189
+ hardware and practical constraints, but could be much more depending on the other operations
190
+ in the circuit. To see why, consider e.g. the circuit
255
191
 
256
- .. note::
192
+ .. code-block:: python
257
193
 
258
- We can only guarantee that the delay is *at least* of the requested duration, due to both
259
- hardware and practical constraints, but could be much more depending on the other operations
260
- in the circuit. To see why, consider e.g. the circuit
194
+ (
195
+ CircuitOperation(name='cz', locus=('alice', 'bob'), args={}),
196
+ CircuitOperation(name='delay', locus=('alice',), args={'duration': 1e-9}),
197
+ CircuitOperation(name='delay', locus=('bob',), args={'duration': 100e-9}),
198
+ CircuitOperation(name='cz', locus=('alice', 'bob'), args={}),
199
+ )
261
200
 
262
- .. code-block:: python
201
+ In this case the actual delay between the two CZ gates will be 100 ns rounded up to
202
+ hardware granularity, even though only 1 ns was requested for `alice`.
263
203
 
264
- [
265
- Instruction(name='cz', qubits=('alice', 'bob'), args={}),
266
- Instruction(name='delay', qubits=('alice',), args={'duration': 1e-9}),
267
- Instruction(name='delay', qubits=('bob',), args={'duration': 100e-9}),
268
- Instruction(name='cz', qubits=('alice', 'bob'), args={}),
269
- ]
204
+ """
270
205
 
271
- In this case the actual delay between the two CZ gates will be 100 ns rounded up to
272
- hardware granularity, even though only 1 ns was requested for `alice`.
273
- """
206
+ from __future__ import annotations
274
207
 
275
- name: str = Field(examples=["measure"])
276
- """name of the quantum operation"""
277
- implementation: StrictStr | None = Field(None)
278
- """name of the implementation, for experimental use only"""
279
- qubits: Locus = Field(examples=[("alice",)])
280
- """names of the locus components (typically qubits) the operation acts on"""
281
- args: dict[str, Any] = Field(default_factory=dict, examples=[{"key": "m"}])
282
- """arguments for the operation"""
208
+ from dataclasses import dataclass
209
+ from enum import Enum
210
+ from functools import cached_property
211
+ import re
212
+ from typing import Any, TypeAlias
213
+ from uuid import UUID
283
214
 
284
- def __init__(self, **data):
285
- super().__init__(**data)
286
- # Auto-convert name if a deprecated name is used
287
- self.name = _op_current_name(self.name)
215
+ from pydantic import BaseModel, Field, StrictStr, TypeAdapter, field_validator
288
216
 
289
- @field_validator("name")
290
- @classmethod
291
- def name_validator(cls, value):
292
- """Check if the name of instruction is set to one of the supported quantum operations."""
293
- name = value
294
- if name not in _SUPPORTED_OPERATIONS:
295
- message = ", ".join(_SUPPORTED_OPERATIONS)
296
- raise ValueError(f'Unknown operation "{name}". Supported operations are "{message}"')
297
- return name
298
-
299
- @field_validator("implementation")
300
- @classmethod
301
- def implementation_validator(cls, value):
302
- """Check if the implementation of the instruction is set to a non-empty string."""
303
- implementation = value
304
- if isinstance(implementation, str):
305
- if not implementation:
306
- raise ValueError("Implementation of the instruction should be None, or a non-empty string")
307
- return implementation
308
-
309
- @field_validator("qubits")
310
- @classmethod
311
- def qubits_validator(cls, value, info: ValidationInfo):
312
- """Check if the instruction has the correct number of qubits for its operation."""
313
- qubits = value
314
- name = info.data.get("name")
315
- if not name:
316
- raise ValueError("Could not validate qubits because the name of the instruction did not pass validation")
317
- arity = _SUPPORTED_OPERATIONS[name].arity
318
- if (0 < arity) and (arity != len(qubits)):
319
- raise ValueError(f'The "{name}" operation acts on {arity} qubit(s), but {len(qubits)} were given: {qubits}')
320
- return qubits
321
-
322
- @field_validator("args")
323
- @classmethod
324
- def args_validator(cls, value, info: ValidationInfo):
325
- """Check argument names and types for a given instruction"""
326
- args = value
327
- name = info.data.get("name")
328
- if not name:
329
- raise ValueError("Could not validate args because the name of the instruction did not pass validation")
330
-
331
- # Check argument names
332
- submitted_arg_names = set(args)
333
- required_arg_names = set(_SUPPORTED_OPERATIONS[name].args_required)
334
- allowed_arg_types = _SUPPORTED_OPERATIONS[name].args_required | _SUPPORTED_OPERATIONS[name].args_not_required
335
- allowed_arg_names = set(allowed_arg_types)
336
- if not required_arg_names <= submitted_arg_names:
337
- raise ValueError(
338
- f'The operation "{name}" requires '
339
- f"{tuple(required_arg_names)} argument(s), "
340
- f"but {tuple(submitted_arg_names)} were given"
341
- )
342
- if not submitted_arg_names <= allowed_arg_names:
343
- message = tuple(allowed_arg_names) if allowed_arg_names else "no"
344
- raise ValueError(
345
- f'The operation "{name}" allows {message} argument(s), but {tuple(submitted_arg_names)} were given'
346
- )
217
+ from iqm.pulse import Circuit
218
+ from iqm.pulse.builder import build_quantum_ops
347
219
 
348
- # Check argument types
349
- for arg_name, arg_value in args.items():
350
- allowed_types = allowed_arg_types[arg_name]
351
- if not isinstance(arg_value, allowed_types):
352
- raise TypeError(
353
- f'The argument "{arg_name}" should be of one of the following supported types'
354
- f" {allowed_types}, but ({type(arg_value)}) was given"
355
- )
220
+ _SUPPORTED_OPERATIONS = build_quantum_ops({})
356
221
 
357
- return value
222
+ Locus: TypeAlias = tuple[StrictStr, ...]
223
+ """Names of the QPU components (typically qubits) a quantum operation instance is acting on, e.g. `("QB1", "QB2")`."""
358
224
 
359
225
 
360
226
  def _op_is_symmetric(name: str) -> bool:
@@ -388,96 +254,25 @@ def _op_arity(name: str) -> int:
388
254
  return _SUPPORTED_OPERATIONS[name].arity
389
255
 
390
256
 
391
- def _op_current_name(name: str) -> str:
392
- """Checks if the operation name has been deprecated and returns the new name if it is;
393
- otherwise, just returns the name as-is.
394
-
395
- Args:
396
- name: name of the operation
397
-
398
- Returns:
399
- current name of the operation
400
- Raises:
401
- KeyError: ``name`` is unknown
402
-
403
- """
404
- return _SUPPORTED_OPERATIONS[name].renamed_to or name
405
-
406
-
407
- class Circuit(BaseModel):
408
- """Quantum circuit to be executed.
409
-
410
- Consists of native quantum operations, each represented by an instance of the :class:`Instruction` class.
411
- """
412
-
413
- name: str = Field(..., examples=["test circuit"])
414
- """name of the circuit"""
415
- instructions: list[Instruction] | tuple[Instruction, ...] = Field(...)
416
- """instructions comprising the circuit"""
417
- metadata: dict[str, Any] | None = Field(None)
418
- """arbitrary metadata associated with the circuit"""
419
-
420
- def all_qubits(self) -> set[str]:
421
- """Return the names of all qubits in the circuit."""
422
- qubits: set[str] = set()
423
- for instruction in self.instructions:
424
- qubits.update(instruction.qubits)
425
- return qubits
426
-
427
- @field_validator("name")
428
- @classmethod
429
- def name_validator(cls, value):
430
- """Check if the circuit name is a non-empty string"""
431
- name = value
432
- if len(name) == 0:
433
- raise ValueError("A circuit should have a non-empty string for a name.")
434
- return name
435
-
436
- @field_validator("instructions")
437
- @classmethod
438
- def instructions_validator(cls, value):
439
- """Check the container of instructions and each instruction within"""
440
- instructions = value
441
-
442
- # Check container type
443
- if not isinstance(instructions, (list, tuple)):
444
- raise ValueError("Instructions of a circuit should be packed in a tuple")
445
-
446
- # Check if any instructions are present
447
- if len(value) == 0:
448
- raise ValueError("Each circuit should have at least one instruction.")
449
-
450
- # Check each instruction explicitly, because automatic validation for Instruction
451
- # is only called when we create a new instance of Instruction, but not if we modify
452
- # an existing instance.
453
- for instruction in instructions:
454
- if isinstance(instruction, Instruction):
455
- Instruction.model_validate(instruction.__dict__)
456
- else:
457
- raise ValueError("Every instruction in a circuit should be of type <Instruction>")
458
-
459
- return instructions
460
-
461
-
462
- QIRCode = str
257
+ QIRCode: TypeAlias = str
463
258
  """QIR program code in string representation"""
464
259
 
465
- CircuitBatch = list[Circuit | QIRCode]
260
+ CircuitBatch: TypeAlias = list[Circuit | QIRCode]
466
261
  """Type that represents a list of quantum circuits to be executed together in a single batch."""
467
262
 
468
263
 
469
264
  def validate_circuit(circuit: Circuit) -> None:
470
- """Validates a submitted quantum circuit using Pydantic tooling.
265
+ """Validates a submitted quantum circuit.
471
266
 
472
267
  Args:
473
268
  circuit: a circuit that needs validation
474
269
 
475
270
  Raises:
476
- pydantic.error_wrappers.ValidationError: validation failed
271
+ ValueError: validation failed
477
272
 
478
273
  """
479
274
  if isinstance(circuit, Circuit):
480
- Circuit.model_validate(circuit.__dict__)
275
+ circuit.validate(_SUPPORTED_OPERATIONS)
481
276
  elif isinstance(circuit, QIRCode):
482
277
  pass
483
278
  else:
@@ -531,14 +326,10 @@ class QuantumArchitectureSpecification(BaseModel):
531
326
  qubit_connectivity = data.get("qubit_connectivity")
532
327
  # add all possible loci for the ops
533
328
  data["operations"] = {
534
- _op_current_name(op): (
535
- qubit_connectivity if _op_arity(_op_current_name(op)) == 2 else [[qb] for qb in qubits]
536
- )
537
- for op in operations
329
+ op: (qubit_connectivity if _op_arity(op) == 2 else [[qb] for qb in qubits]) for op in operations
538
330
  }
539
331
 
540
332
  super().__init__(**data)
541
- self.operations = {_op_current_name(k): v for k, v in self.operations.items()}
542
333
 
543
334
  def has_equivalent_operations(self, other: QuantumArchitectureSpecification) -> bool:
544
335
  """Compares the given operation sets defined by the quantum architecture against
@@ -668,7 +459,7 @@ class GateInfo(BaseModel):
668
459
  default_implementation: str = Field(...)
669
460
  """default implementation for the gate, used unless overridden by :attr:`override_default_implementation`
670
461
  or unless the user requests a specific implementation for a particular gate in the circuit using
671
- :attr:`.Instruction.implementation`"""
462
+ :attr:`iqm.pulse.CircuitOperation.implementation`"""
672
463
  override_default_implementation: dict[Locus, str] = Field(...)
673
464
  """mapping of loci to implementation names that override ``default_implementation`` for those loci"""
674
465