iqm-pulse 11.2.0__py3-none-any.whl → 12.1.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/pulse/__init__.py CHANGED
@@ -15,6 +15,9 @@
15
15
 
16
16
  from importlib.metadata import PackageNotFoundError, version
17
17
 
18
+ from iqm.pulse.builder import CircuitOperation
19
+ from iqm.pulse.circuit_operations import Circuit
20
+
18
21
  try:
19
22
  DIST_NAME = "iqm-pulse"
20
23
  __version__ = version(DIST_NAME)
iqm/pulse/builder.py CHANGED
@@ -80,7 +80,36 @@ logger = logging.getLogger(__name__)
80
80
 
81
81
  @dataclass
82
82
  class CircuitOperation:
83
- """Specific quantum operation applied on a specific part of the QPU, e.g. in a quantum circuit."""
83
+ r"""Specific quantum operation applied on a specific part of the QPU, e.g. in a quantum circuit.
84
+
85
+ We currently support the following native operations for circuit execution:
86
+
87
+ ================ =========== ======================================= ===========
88
+ name # of qubits args description
89
+ ================ =========== ======================================= ===========
90
+ measure >= 1 ``key: str``, ``feedback_key: str`` Measurement in the Z basis.
91
+ prx 1 ``angle: float``, ``phase: float`` Phased x-rotation gate.
92
+ cc_prx 1 ``angle: float``, ``phase: float``,
93
+ ``feedback_qubit: str``,
94
+ ``feedback_key: str`` Classically controlled PRX gate.
95
+ reset >= 1 Reset the qubit(s) to :math:`|0\rangle`.
96
+ cz 2 Controlled-Z gate.
97
+ move 2 Move a qubit state between a qubit and a
98
+ computational resonator, as long as
99
+ at least one of the components is
100
+ in the :math:`|0\rangle` state.
101
+ barrier >= 1 Execution barrier.
102
+ delay >= 1 ``duration: float`` Force a delay between circuit operations.
103
+ ================ =========== ======================================= ===========
104
+
105
+ For each CircuitOperation you may also optionally specify :attr:`implementation`,
106
+ which contains the name of an implementation of the operation to use.
107
+ Support for multiple implementations is currently experimental and in normal use the
108
+ field should be omitted, this selects the default implementation for the operation for that locus.
109
+
110
+ See the submodules under :mod:`iqm.pulse.gates` for more details about each operation.
111
+
112
+ """
84
113
 
85
114
  name: str
86
115
  """name of the quantum operation"""
@@ -101,31 +130,54 @@ class CircuitOperation:
101
130
  ValueError: operation is not valid
102
131
 
103
132
  """
104
- # find the op type
105
133
  op_type = op_table.get(self.name)
106
134
  if op_type is None:
107
- raise ValueError(f"Unknown quantum operation '{self.name}'.")
108
-
109
- # find the implementation
110
- impl_name = self.implementation
111
- if impl_name is not None and impl_name not in op_type.implementations:
112
- raise ValueError(f"Unknown implementation '{impl_name}' for quantum operation '{self.name}'.")
113
-
114
- if 0 < op_type.arity != len(self.locus):
135
+ message = ", ".join(op_table)
136
+ raise ValueError(f"Unknown operation '{self.name}'. Supported operations are '{message}'.")
137
+ self._validate_implementation(op_type)
138
+ self._validate_locus(op_type)
139
+ self._validate_args(op_type)
140
+
141
+ def _validate_implementation(self, op_type: QuantumOp) -> None:
142
+ if self.implementation is not None:
143
+ if not self.implementation:
144
+ raise ValueError("Implementation of the instruction should be None, or a non-empty string")
145
+ if self.implementation not in op_type.implementations:
146
+ raise ValueError(f"Unknown implementation '{self.implementation}' for quantum operation '{self.name}'.")
147
+
148
+ def _validate_locus(self, op_type: QuantumOp) -> None:
149
+ arity = op_type.arity
150
+ if (0 < arity) and (arity != len(self.locus)):
115
151
  raise ValueError(
116
- f"The '{self.name}' operation acts on {op_type.arity} qubit(s), "
117
- f"but {len(self.locus)} were given: {self.locus}"
152
+ f"The '{self.name}' operation acts on {arity} qubit(s), but {len(self.locus)} were given: {self.locus}."
118
153
  )
119
-
120
154
  if len(self.locus) != len(set(self.locus)):
121
155
  raise ValueError(f"Repeated locus components: {self.locus}.")
122
156
 
123
- if not set(op_type.params).issubset(self.args):
157
+ def _validate_args(self, op_type: QuantumOp) -> None:
158
+ # Check argument names
159
+ submitted_arg_names = set(self.args)
160
+ allowed_arg_types = op_type.params | op_type.optional_params
161
+ if not set(op_type.params) <= submitted_arg_names:
124
162
  raise ValueError(
125
- f"The '{self.name}' operation requires the arguments {op_type.params}, "
126
- f"but {tuple(self.args)} were given."
163
+ f"The operation '{self.name}' requires "
164
+ f"the argument(s) {tuple(op_type.params)}, "
165
+ f"but {tuple(submitted_arg_names)} were given."
127
166
  )
128
167
 
168
+ if not submitted_arg_names <= set(allowed_arg_types):
169
+ allowed_arg_names = tuple(allowed_arg_types)
170
+ message = f"the arguments {allowed_arg_names}" if allowed_arg_names else "no arguments"
171
+ raise ValueError(f"The operation '{self.name}' allows {message}, but {submitted_arg_names} were given.")
172
+ # Check argument types
173
+ for arg_name, arg_value in self.args.items():
174
+ allowed_types = allowed_arg_types[arg_name]
175
+ if not isinstance(arg_value, allowed_types):
176
+ raise ValueError(
177
+ f"The argument '{arg_name}' should be of one of the following supported types"
178
+ f" {allowed_types}, but ({type(arg_value)}) was given."
179
+ )
180
+
129
181
 
130
182
  def load_config(path: str) -> tuple[QuantumOpTable, OpCalibrationDataTree]:
131
183
  """Load quantum operation definitions and calibration data from a YAML config file.
@@ -11,13 +11,14 @@ from __future__ import annotations
11
11
 
12
12
  from collections import Counter
13
13
  from collections.abc import Iterable, Sequence
14
+ from dataclasses import dataclass
14
15
  from functools import lru_cache
15
- from typing import Self
16
+ from typing import Any, Self
16
17
 
17
18
  import numpy as np
18
19
 
19
20
  from iqm.pulse.builder import CircuitOperation, build_quantum_ops
20
- from iqm.pulse.quantum_ops import QuantumOpTable
21
+ from iqm.pulse.quantum_ops import QuantumOp, QuantumOpTable
21
22
 
22
23
 
23
24
  def reorder(A: np.ndarray, perm: list[int]) -> np.ndarray:
@@ -363,8 +364,15 @@ class CircuitOperationList(list):
363
364
  params = self.table[name].params
364
365
  if len(params) != len(args):
365
366
  raise TypeError(
366
- f"Operation {name} has the following arguments: {params}, but {len(args)} values were provided."
367
+ f"Operation {name} has the following arguments: {tuple(params)}, but {len(args)} values were provided."
367
368
  )
369
+ # Convert int args to floats, so that one can pass e.g. 0 instead of 0.0 for the arg.
370
+ # Note that this conversion is not exact if the int value is too large, but such values are not realistic
371
+ # for the current parameter types.
372
+ args = tuple(
373
+ float(arg) if (float in param_types and int not in param_types) and isinstance(arg, int) else arg
374
+ for arg, param_types in zip(args, params.values())
375
+ )
368
376
  new_op = CircuitOperation(name=name, args=dict(zip(params, args)), implementation=impl_name, locus=locus)
369
377
  new_op.validate(self.table)
370
378
  self.append(new_op)
@@ -503,3 +511,55 @@ class CircuitOperationList(list):
503
511
  f" optionally to fix the implementation of the operation in iqm-pulse."
504
512
  )
505
513
  setattr(CircuitOperationList, name, _add_specific_op)
514
+
515
+
516
+ @dataclass
517
+ class Circuit:
518
+ """Quantum circuit.
519
+
520
+ Used e.g. for client-server communication.
521
+
522
+ Consists of a sequence of native quantum operations, each represented by an instance of the
523
+ :class:`CircuitOperation` class.
524
+ """
525
+
526
+ name: str
527
+ """name of the circuit"""
528
+ instructions: tuple[CircuitOperation, ...]
529
+ """operations comprising the circuit"""
530
+ metadata: dict[str, Any] | None = None
531
+ """optional metadata for the circuit"""
532
+
533
+ def all_locus_components(self) -> set[str]:
534
+ """Return the names of all locus components (typically qubits) in the circuit."""
535
+ components: set[str] = set()
536
+ for instruction in self.instructions:
537
+ components.update(instruction.locus)
538
+ return components
539
+
540
+ def validate(self, supported_operations: dict[str, QuantumOp]) -> None:
541
+ """Validate the circuit against the supported quantum operations.
542
+
543
+ Args:
544
+ supported_operations: mapping of supported quantum operation names to their definitions
545
+
546
+ Raises:
547
+ ValueError: circuit is not valid
548
+
549
+ """
550
+ self._validate_name()
551
+ self._validate_instructions(supported_operations)
552
+
553
+ def _validate_name(self) -> None:
554
+ if not self.name:
555
+ raise ValueError("A circuit should have a non-empty string for a name.")
556
+
557
+ def _validate_instructions(self, supported_operations: dict[str, QuantumOp]) -> None:
558
+ if not self.instructions:
559
+ raise ValueError("Each circuit should have at least one instruction.")
560
+
561
+ for instruction in self.instructions:
562
+ if isinstance(instruction, CircuitOperation):
563
+ instruction.validate(supported_operations)
564
+ else:
565
+ raise ValueError("Every instruction in a circuit should be of type <CircuitOperation>")
@@ -125,41 +125,52 @@ _quantum_ops_library = {
125
125
  QuantumOp(
126
126
  "delay",
127
127
  0,
128
- ("duration",),
128
+ {"duration": (float,)},
129
129
  implementations=_implementation_library["delay"],
130
130
  symmetric=True,
131
131
  ),
132
132
  QuantumOp(
133
133
  "measure",
134
134
  0,
135
- ("key",),
135
+ {"key": (str,)},
136
+ optional_params={"feedback_key": (str,)},
136
137
  implementations=_implementation_library["measure"],
137
138
  factorizable=True,
138
139
  ),
139
140
  QuantumOp(
140
141
  "measure_fidelity",
141
142
  0,
142
- ("key",),
143
+ {"key": (str,)},
143
144
  implementations=_implementation_library["measure_fidelity"],
144
145
  factorizable=True,
145
146
  ),
146
147
  QuantumOp(
147
148
  "prx",
148
149
  1,
149
- ("angle", "phase"),
150
+ {
151
+ "angle": (float,),
152
+ "phase": (float,),
153
+ },
150
154
  implementations=_implementation_library["prx"],
151
155
  unitary=get_unitary_prx,
152
156
  ),
153
157
  QuantumOp(
154
158
  "prx_12",
155
159
  1,
156
- ("angle", "phase"),
160
+ {
161
+ "angle": (float,),
162
+ "phase": (float,),
163
+ },
157
164
  implementations=_implementation_library["prx_12"],
158
165
  ),
159
166
  QuantumOp(
160
167
  "u",
161
168
  1,
162
- ("theta", "phi", "lam"),
169
+ {
170
+ "theta": (float,),
171
+ "phi": (float,),
172
+ "lam": (float,),
173
+ },
163
174
  implementations=_implementation_library["u"],
164
175
  unitary=get_unitary_u,
165
176
  ),
@@ -172,7 +183,7 @@ _quantum_ops_library = {
172
183
  QuantumOp(
173
184
  "rz",
174
185
  1,
175
- ("angle",),
186
+ {"angle": (float,)},
176
187
  implementations=_implementation_library["rz"],
177
188
  unitary=get_unitary_rz,
178
189
  ),
@@ -184,7 +195,7 @@ _quantum_ops_library = {
184
195
  QuantumOp(
185
196
  "cz",
186
197
  2,
187
- (),
198
+ {},
188
199
  implementations=_implementation_library["cz"],
189
200
  symmetric=True,
190
201
  unitary=lambda: np.diag([1.0, 1.0, 1.0, -1.0]),
@@ -197,7 +208,12 @@ _quantum_ops_library = {
197
208
  QuantumOp(
198
209
  "cc_prx",
199
210
  1,
200
- ("angle", "phase", "feedback_qubit", "feedback_key"),
211
+ {
212
+ "angle": (float,),
213
+ "phase": (float,),
214
+ "feedback_qubit": (str,),
215
+ "feedback_key": (str,),
216
+ },
201
217
  implementations=_implementation_library["cc_prx"],
202
218
  ),
203
219
  QuantumOp(
iqm/pulse/quantum_ops.py CHANGED
@@ -78,8 +78,10 @@ class QuantumOp:
78
78
  Each locus component corresponds to a quantum subsystem in the definition of the operation.
79
79
  The computational subspace always consists of the lowest two levels of the subsystem.
80
80
  Zero means the operation can be applied on any number of locus components."""
81
- params: tuple[str, ...] = ()
82
- """Names of required operation parameters, if any."""
81
+ params: dict[str, tuple[type, ...]] = field(default_factory=dict)
82
+ """Maps names of required operation parameters to their allowed types."""
83
+ optional_params: dict[str, tuple[type, ...]] = field(default_factory=dict)
84
+ """Maps names of optional operation parameters to their allowed types."""
83
85
  implementations: dict[str, type[GateImplementation]] = field(default_factory=dict)
84
86
  """Maps implementation names to :class:`.GateImplementation` classes that provide them.
85
87
  Each such class should describe the implementation in detail in its docstring.
@@ -100,7 +102,8 @@ class QuantumOp:
100
102
  """Unitary matrix that represents the effect of this quantum operation in the computational basis, or ``None``
101
103
  if the quantum operation is not unitary or the exact unitary is not known.
102
104
  The Callable needs to take exactly the arguments given in :attr:`params`, for example if
103
- ``params=('angle','phase')``, the function must have signature ``f(angle:float, phase: float) -> np.ndarray``.
105
+ ``params={'angle': (float,), 'phase': (float,)}``, the function must have signature
106
+ ``f(angle: float, phase: float) -> np.ndarray``.
104
107
  For operations acting on more than 1 qubit, unitary should be given in the big-endian order, i.e. in the basis
105
108
  ``np.kron(first_qubit_basis_ket, second_qubit_basis_ket)``."""
106
109
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iqm-pulse
3
- Version: 11.2.0
3
+ Version: 12.1.0
4
4
  Summary: A Python-based project for providing interface and implementations for control pulses.
5
5
  Author-email: IQM Finland Oy <info@meetiqm.com>
6
6
  License: Apache License
@@ -213,7 +213,7 @@ Classifier: Intended Audience :: Science/Research
213
213
  Requires-Python: >=3.11
214
214
  Description-Content-Type: text/x-rst
215
215
  License-File: LICENSE.txt
216
- Requires-Dist: iqm-exa-common <27,>=26
216
+ Requires-Dist: iqm-exa-common <28,>=27
217
217
  Requires-Dist: iqm-data-definitions <3.0,>=2.13
218
218
  Requires-Dist: python-rapidjson ==1.20
219
219
  Requires-Dist: jinja2 ==3.0.3
@@ -1,10 +1,10 @@
1
- iqm/pulse/__init__.py,sha256=FxgHlv3bF8bQCREwOJ1yu_nsrfJq0g0HdovvT1wUnCI,881
1
+ iqm/pulse/__init__.py,sha256=aQhtfbLQgr_1IvbZLH1MCuf0aFomokeu7DSjxuDWZC4,978
2
2
  iqm/pulse/base_utils.py,sha256=ll3k8wbVxjHi6XcVo3PEWIjHx5QjG1C-yH218zTG8jU,2657
3
- iqm/pulse/builder.py,sha256=MhBAw2NHTDGR79ARGGF44EkGDxaXlp6KHoNnPEQuyEY,74712
4
- iqm/pulse/circuit_operations.py,sha256=V568uC2LJBs6BlDbioQCh9iCBYhgkTiAbto9VSbmTgQ,22499
3
+ iqm/pulse/builder.py,sha256=uL2MsAvRs1dJfe2mVNrrHEqUoBBTJlABt0KmBtB6hl0,78294
4
+ iqm/pulse/circuit_operations.py,sha256=P83gevhGcaY4DaopGKO3CHN_CnWAPfEu-MHoLkqhu5Y,24847
5
5
  iqm/pulse/gate_implementation.py,sha256=yyj2d1aDL0K_qouo0qLHnZ49_1qHsJEShKW14398t8Q,39546
6
6
  iqm/pulse/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- iqm/pulse/quantum_ops.py,sha256=UTQ08Eo5vFpV_lOoQNtlwRTchES6BdGHQ5pzSdb1ToY,14584
7
+ iqm/pulse/quantum_ops.py,sha256=cV13V1t2rj9lgNKXgGZXMcNgEPJ7oraHL6eJJTlLvWk,14825
8
8
  iqm/pulse/scheduler.py,sha256=62gH3AcxmsgmShcmtNaiCqFLRl1Wll6Q24zIkhw_-XM,22768
9
9
  iqm/pulse/timebox.py,sha256=Yi8I43KiSPs2s_l1r0rMVm4mw0EpSCrpq3BCUNk5vyE,16618
10
10
  iqm/pulse/utils.py,sha256=0J4KQNvKtZahme7sP7vLa1FtENepC-eb9_A5WaghvVU,3710
@@ -13,7 +13,7 @@ iqm/pulse/gates/__init__.py,sha256=l07iD52FiKKQyVFwswRapZMqEE0XfILSc4aZ5sXGJo4,8
13
13
  iqm/pulse/gates/barrier.py,sha256=WhYV70lf4lh4Wa9UZuMk2lp9JbUQIu8lzewRC2P7pNE,2546
14
14
  iqm/pulse/gates/conditional.py,sha256=NE-1GYlFcfaCmRuhqGEIhtoJmBArhyY5KhVnO4y4zng,6289
15
15
  iqm/pulse/gates/cz.py,sha256=72KO8OINZRVOye4g4quHAEzwNplOlIZE0hU1IjE8Gfk,33366
16
- iqm/pulse/gates/default_gates.py,sha256=G76Nz2XAuwH2i5KIrzSp4pTBSbe9vcAjfT12PfPYMYA,7645
16
+ iqm/pulse/gates/default_gates.py,sha256=JwdrCh_Blee4mMWZ8v8ymw3Ep0fZBvw9wuaLR3rjDXA,8073
17
17
  iqm/pulse/gates/delay.py,sha256=nST9dY2JFp_mpKhiSfsYa5yL4hFKcNJSAyCzXjhauQg,3767
18
18
  iqm/pulse/gates/enums.py,sha256=ATwb6vZYpfgQ1gQyFPW53JyIKrdAP3FPHm6jV-t9OAk,2532
19
19
  iqm/pulse/gates/flux_multiplexer.py,sha256=sk84ItEvkx7Z0pCHt8MCCczBe7_BHNvqS4_oeNghZw8,9757
@@ -39,8 +39,8 @@ iqm/pulse/playlist/visualisation/templates/static/logo.png,sha256=rbfQZ6_UEaztpY
39
39
  iqm/pulse/playlist/visualisation/templates/static/moment.min.js,sha256=4iQZ6BVL4qNKlQ27TExEhBN1HFPvAvAMbFavKKosSWQ,53324
40
40
  iqm/pulse/playlist/visualisation/templates/static/vis-timeline-graph2d.min.css,sha256=svzNasPg1yR5gvEaRei2jg-n4Pc3sVyMUWeS6xRAh6U,19837
41
41
  iqm/pulse/playlist/visualisation/templates/static/vis-timeline-graph2d.min.js,sha256=OqCqCyA6JnxPz3PGXq_P_9VuSqWptgNt5Ev3T-xjefQ,570288
42
- iqm_pulse-11.2.0.dist-info/LICENSE.txt,sha256=R6Q7eUrLyoCQgWYorQ8WJmVmWKYU3dxA3jYUp0wwQAw,11332
43
- iqm_pulse-11.2.0.dist-info/METADATA,sha256=LwwqFnDeeW1USz6YKNHEJjkxIkkwenUhpOV0UmvOwIg,14551
44
- iqm_pulse-11.2.0.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
45
- iqm_pulse-11.2.0.dist-info/top_level.txt,sha256=NB4XRfyDS6_wG9gMsyX-9LTU7kWnTQxNvkbzIxGv3-c,4
46
- iqm_pulse-11.2.0.dist-info/RECORD,,
42
+ iqm_pulse-12.1.0.dist-info/LICENSE.txt,sha256=R6Q7eUrLyoCQgWYorQ8WJmVmWKYU3dxA3jYUp0wwQAw,11332
43
+ iqm_pulse-12.1.0.dist-info/METADATA,sha256=1DUFvxXliKMNqyJIFb_LVrTQnupzgw3Erhy0BZTN4k4,14551
44
+ iqm_pulse-12.1.0.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
45
+ iqm_pulse-12.1.0.dist-info/top_level.txt,sha256=NB4XRfyDS6_wG9gMsyX-9LTU7kWnTQxNvkbzIxGv3-c,4
46
+ iqm_pulse-12.1.0.dist-info/RECORD,,