iqm-client 30.2.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/cirq_iqm/serialize.py +50 -41
- iqm/iqm_client/iqm_client.py +1 -1
- iqm/iqm_client/models.py +152 -361
- iqm/iqm_client/transpile.py +37 -31
- iqm/iqm_client/validation.py +23 -19
- iqm/qiskit_iqm/iqm_job.py +2 -1
- iqm/qiskit_iqm/iqm_provider.py +2 -1
- iqm/qiskit_iqm/qiskit_to_iqm.py +32 -31
- {iqm_client-30.2.0.dist-info → iqm_client-31.0.0.dist-info}/METADATA +5 -1
- {iqm_client-30.2.0.dist-info → iqm_client-31.0.0.dist-info}/RECORD +15 -15
- {iqm_client-30.2.0.dist-info → iqm_client-31.0.0.dist-info}/AUTHORS.rst +0 -0
- {iqm_client-30.2.0.dist-info → iqm_client-31.0.0.dist-info}/LICENSE.txt +0 -0
- {iqm_client-30.2.0.dist-info → iqm_client-31.0.0.dist-info}/WHEEL +0 -0
- {iqm_client-30.2.0.dist-info → iqm_client-31.0.0.dist-info}/entry_points.txt +0 -0
- {iqm_client-30.2.0.dist-info → iqm_client-31.0.0.dist-info}/top_level.txt +0 -0
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
|
-
|
|
16
|
+
We currently support the following native operations for circuit execution,
|
|
17
|
+
represented by :class:`iqm.pulse.CircuitOperation`:
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
26
|
-
|
|
37
|
+
Measure
|
|
38
|
+
-------
|
|
27
39
|
|
|
40
|
+
:mod:`iqm.pulse.gates.measure`
|
|
28
41
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
92
|
-
|
|
55
|
+
PRX
|
|
56
|
+
---
|
|
93
57
|
|
|
94
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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
|
-
|
|
124
|
-
|
|
72
|
+
.. code-block:: python
|
|
73
|
+
:caption: Example
|
|
125
74
|
|
|
126
|
-
|
|
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
|
-
|
|
135
|
-
|
|
77
|
+
CC_PRX
|
|
78
|
+
------
|
|
136
79
|
|
|
137
|
-
|
|
80
|
+
:mod:`iqm.pulse.gates.conditional`
|
|
138
81
|
|
|
139
|
-
|
|
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
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
155
|
-
|
|
97
|
+
.. code-block:: python
|
|
98
|
+
:caption: Example
|
|
156
99
|
|
|
157
|
-
|
|
100
|
+
CircuitOperation(name='reset', locus=('alice', 'bob'), args={})
|
|
158
101
|
|
|
159
|
-
|
|
160
|
-
------
|
|
102
|
+
.. note:: Currently inherits its calibration from ``cc_prx`` and is only available when ``cc_prx`` is.
|
|
161
103
|
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
171
|
-
-----
|
|
107
|
+
:mod:`iqm.pulse.gates.cz`
|
|
172
108
|
|
|
173
|
-
|
|
109
|
+
Controlled-Z gate. Represented in the standard computational basis by the matrix
|
|
174
110
|
|
|
175
|
-
|
|
176
|
-
:caption: Example
|
|
111
|
+
.. math:: \text{CZ} = \text{diag}(1, 1, 1, -1).
|
|
177
112
|
|
|
178
|
-
|
|
113
|
+
It is symmetric wrt. the qubits it's acting on, and takes no arguments.
|
|
179
114
|
|
|
180
|
-
|
|
115
|
+
.. code-block:: python
|
|
116
|
+
:caption: Example
|
|
181
117
|
|
|
182
|
-
|
|
183
|
-
--
|
|
118
|
+
CircuitOperation(name='cz', locus=('alice', 'bob'), args={})
|
|
184
119
|
|
|
185
|
-
|
|
120
|
+
MOVE
|
|
121
|
+
----
|
|
186
122
|
|
|
187
|
-
|
|
123
|
+
:mod:`iqm.pulse.gates.move`
|
|
188
124
|
|
|
189
|
-
|
|
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
|
-
|
|
192
|
-
:caption: Example
|
|
130
|
+
MOVE has the following presentation in the subspace :math:`S`:
|
|
193
131
|
|
|
194
|
-
|
|
132
|
+
.. math:: \text{MOVE}_S = |00\rangle \langle 00| + a |10\rangle \langle 01| + a^{-1} |01\rangle \langle 10|,
|
|
195
133
|
|
|
196
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
140
|
+
.. code-block:: python
|
|
141
|
+
:caption: Example
|
|
205
142
|
|
|
206
|
-
|
|
143
|
+
CircuitOperation(name='move', locus=('alice', 'resonator'), args={})
|
|
207
144
|
|
|
208
|
-
|
|
145
|
+
.. note:: MOVE is only available in quantum computers with the IQM Star architecture.
|
|
209
146
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
pair of MOVE operations.
|
|
147
|
+
Barrier
|
|
148
|
+
-------
|
|
213
149
|
|
|
214
|
-
|
|
215
|
-
:caption: Example
|
|
150
|
+
:mod:`iqm.pulse.gates.barrier`
|
|
216
151
|
|
|
217
|
-
|
|
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
|
-
|
|
159
|
+
.. code-block:: python
|
|
160
|
+
:caption: Example
|
|
220
161
|
|
|
221
|
-
|
|
222
|
-
-------
|
|
162
|
+
CircuitOperation(name='barrier', locus=('alice', 'bob'), args={})
|
|
223
163
|
|
|
224
|
-
|
|
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
|
-
|
|
232
|
-
|
|
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
|
-
|
|
170
|
+
Delay
|
|
171
|
+
-----
|
|
235
172
|
|
|
236
|
-
|
|
173
|
+
:mod:`iqm.pulse.gates.delay`
|
|
237
174
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
243
|
-
|
|
180
|
+
.. code-block:: python
|
|
181
|
+
:caption: Example
|
|
244
182
|
|
|
245
|
-
|
|
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
|
-
|
|
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
|
-
|
|
192
|
+
.. code-block:: python
|
|
257
193
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
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
|
-
|
|
272
|
-
hardware granularity, even though only 1 ns was requested for `alice`.
|
|
273
|
-
"""
|
|
206
|
+
from __future__ import annotations
|
|
274
207
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
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
|
-
|
|
290
|
-
|
|
291
|
-
def name_validator(cls, value): # noqa: ANN001
|
|
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): # noqa: ANN001
|
|
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): # noqa: ANN001
|
|
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): # noqa: ANN001
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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): # noqa: ANN001
|
|
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): # noqa: ANN001
|
|
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
|
|
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
|
-
|
|
271
|
+
ValueError: validation failed
|
|
477
272
|
|
|
478
273
|
"""
|
|
479
274
|
if isinstance(circuit, Circuit):
|
|
480
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|