iqm-pulse 9.20.0__py3-none-any.whl → 10.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.
@@ -24,6 +24,7 @@ Likewise, a single GateImplementation subclass can be sometimes used to implemen
24
24
  through different calibration data.
25
25
  """
26
26
 
27
+ import copy
27
28
  from dataclasses import replace
28
29
 
29
30
  from iqm.pulse.gate_implementation import GateImplementation
@@ -44,7 +45,7 @@ from iqm.pulse.gates.cz import (
44
45
  FluxPulseGate_CRF_CRF,
45
46
  FluxPulseGate_TGSS_CRF,
46
47
  )
47
- from iqm.pulse.gates.default_gates import _quantum_ops_library
48
+ from iqm.pulse.gates.default_gates import _default_implementations, _quantum_ops_library
48
49
  from iqm.pulse.gates.delay import Delay
49
50
  from iqm.pulse.gates.flux_multiplexer import FluxMultiplexer_SampleLinear
50
51
  from iqm.pulse.gates.measure import Measure_Constant, Measure_Constant_Qnd, Shelved_Measure_Constant
@@ -75,7 +76,7 @@ from iqm.pulse.gates.u import UGate, get_unitary_u
75
76
  from iqm.pulse.quantum_ops import QuantumOp, QuantumOpTable
76
77
 
77
78
  _exposed_implementations: dict[str, type[GateImplementation]] = {
78
- cls.__name__: cls # type: ignore[misc]
79
+ cls.__name__: cls
79
80
  for cls in (
80
81
  Barrier,
81
82
  Constant_PRX_with_smooth_rise_fall,
@@ -120,7 +121,7 @@ _exposed_implementations: dict[str, type[GateImplementation]] = {
120
121
 
121
122
  def get_implementation_class(class_name: str) -> type[GateImplementation] | None:
122
123
  """Get gate implementation class by class name."""
123
- return _exposed_implementations.get(class_name, None)
124
+ return _exposed_implementations.get(class_name)
124
125
 
125
126
 
126
127
  def expose_implementation(implementation: type[GateImplementation], overwrite: bool = False) -> None:
@@ -138,219 +139,102 @@ def expose_implementation(implementation: type[GateImplementation], overwrite: b
138
139
  _exposed_implementations[name] = implementation
139
140
 
140
141
 
141
- def _compare_operations(op1: QuantumOp, op2: QuantumOp) -> bool:
142
- """Compares two QuantumOp instances. Operations are different only if certain parameters do not match.
143
- (everything else except the implementations information which the user is allowed to modify and
144
- :attr:`QuatumOp.unitary` the equality of which cannot be validated as it is a free-form function).
145
-
146
- Args:
147
- op1: First QuantumOp instance
148
- op2: Second QuantumOp instance
149
-
150
- Returns:
151
- True if the operations have identical parameters, False otherwise
152
-
153
- """
154
- IGNORED_FIELDS = {"implementations", "defaults_for_locus", "unitary"}
155
- op1_dict = vars(op1)
156
- op2_dict = vars(op2)
157
- return all(
158
- (op1_dict[field] == op2_dict[field] or (field == "params" and tuple(op1_dict[field]) == tuple(op2_dict[field])))
159
- for field in op1_dict
160
- if field not in IGNORED_FIELDS
161
- )
162
-
163
-
164
- def _validate_operation(
165
- new_op: QuantumOp,
166
- gate_name: str,
167
- operations: QuantumOpTable,
168
- overwrite: bool = False,
169
- ) -> QuantumOp:
170
- """Validate new operation against existing operations and set unitary if needed.
142
+ def _validate_implementation(
143
+ op_name: str,
144
+ impl_name: str,
145
+ impl_class_name: str,
146
+ ) -> None:
147
+ """Check that canonical implementation names cannot be overridden.
171
148
 
172
149
  Args:
173
- new_op: Operation to validate
174
- gate_name: Name of the gate
175
- operations: Dictionary containing existing operations
176
- overwrite: Whether to allow overwriting existing operations
177
-
178
- Returns:
179
- Validated QuantumOp with unitary set if needed
150
+ op_name: Name of the operation.
151
+ impl_name: Name of the new implementation.
152
+ impl_class_name: Name of the GateImplementation class it maps to.
180
153
 
181
154
  Raises:
182
- ValueError: If operation exists with different parameters and overwrite is False
183
-
184
- """
185
- if not overwrite and gate_name in operations:
186
- old = operations.get(gate_name)
187
- same = _compare_operations(new_op, old) # type: ignore[arg-type]
188
- if not same:
189
- raise ValueError(f"{gate_name} already registered with different parameters")
190
-
191
- if gate_name in _quantum_ops_library:
192
- default = _quantum_ops_library.get(gate_name)
193
- same = _compare_operations(new_op, default) # type: ignore[arg-type]
194
- if not same:
195
- raise ValueError(f"{gate_name} conflicts with a canonical operation in iqm-pulse")
196
-
197
- if new_op.unitary is None:
198
- if gate_name in operations and operations[gate_name].unitary is not None:
199
- unitary = operations[gate_name].unitary
200
- elif gate_name in _quantum_ops_library and _quantum_ops_library[gate_name].unitary is not None:
201
- unitary = default.unitary # type: ignore[union-attr]
202
- else:
203
- unitary = None
204
- new_op = replace(new_op, unitary=unitary)
205
-
206
- return new_op
207
-
208
-
209
- def _register_gate(
210
- operations: QuantumOpTable,
211
- gate_name: str,
212
- impl_class: type[GateImplementation],
213
- quantum_op_specs: QuantumOp | dict | None = None,
214
- ) -> QuantumOp:
215
- """Create the quantum operation for the new gate.
216
-
217
- Args:
218
- operations: Known operations, mapping gate names to QuantumOps
219
- gate_name: Name of the gate to register
220
- impl_class: The implementation class
221
- quantum_op_specs: The quantum operation specifications
222
-
223
- Returns:
224
- The instance of the new quantum operation
155
+ ValueError: A canonical implementation name is being redefined.
225
156
 
226
157
  """
227
- if isinstance(quantum_op_specs, QuantumOp):
228
- new_op = quantum_op_specs
229
- elif quantum_op_specs is None and gate_name in operations:
230
- new_op = operations[gate_name]
231
- else:
232
- new_kwargs = {
233
- "name": gate_name,
234
- "arity": 1,
235
- "params": tuple(),
236
- "implementations": {},
237
- "symmetric": impl_class.symmetric,
238
- "factorizable": False,
239
- }
240
- if quantum_op_specs:
241
- new_kwargs |= quantum_op_specs
242
- new_kwargs["params"] = tuple(new_kwargs.get("params", ())) # type: ignore[arg-type]
158
+ default_implementations = _default_implementations.get(op_name, {})
243
159
 
244
- new_op = QuantumOp(**new_kwargs) # type: ignore[arg-type] # type: ignore[arg-type] # type: ignore[arg-type] # type: ignore[arg-type] # type: ignore[arg-type] # type: ignore[arg-type] # type: ignore[arg-type]
160
+ # check if the implementation name is canonical for this op
161
+ if (impl_class := default_implementations.get(impl_name)) is not None:
162
+ if impl_class_name != impl_class.__name__:
163
+ raise ValueError(
164
+ f"'{op_name}': '{impl_name}' is a reserved implementation name that refers to "
165
+ f"'{impl_class.__name__}', and cannot be overridden. "
166
+ "Consider renaming your implementation."
167
+ )
245
168
 
246
- return new_op
247
169
 
248
-
249
- def _add_implementation(
170
+ def register_operation(
250
171
  operations: dict[str, QuantumOp],
251
- new_op: QuantumOp,
252
- impl_name: str,
253
- impl_class: type[GateImplementation],
254
- set_as_default: bool = False,
172
+ op: QuantumOp,
255
173
  overwrite: bool = False,
256
174
  ) -> None:
257
- """Register a new implementation for an existing quantum operation.
258
-
259
- Args:
260
- operations: Table of existing quantum operations
261
- new_op: The new quantum operation
262
- impl_name: The name for the implementation that is added
263
- impl_class: The GateImplementation class corresponding to the new implementation
264
- set_as_default: Whether to set as default implementation
265
- overwrite: If True, allows replacing existing implementation
266
-
267
- Returns:
268
- new_op: QuantumOp with the new implementation
269
-
270
- """
271
- new_op.implementations[impl_name] = impl_class
272
- _validate_implementation(operations, new_op, impl_name, impl_class)
273
- if set_as_default and len(new_op.implementations) >= 1:
274
- new_op.set_default_implementation(impl_name)
275
- if not get_implementation_class(impl_class.__name__):
276
- expose_implementation(impl_class, overwrite)
277
-
278
- return new_op # type: ignore[return-value]
279
-
280
-
281
- def _validate_implementation(
282
- operations: QuantumOpTable,
283
- new_op: QuantumOp,
284
- impl_name: str,
285
- impl_class: type[GateImplementation],
286
- ) -> None:
287
- """Validate new implementation against existing implementations.
175
+ """Register a new QuantumOp to the given operations table.
288
176
 
289
177
  Args:
290
- operations: Table of quantum operations
291
- new_op: Operation whose implementation we want to validate
292
- impl_name: Name of the new implementation
293
- impl_class: The GateImplementation class corresponding to the new
294
- implementation
178
+ operations: Known operations, to which the new operation is added.
179
+ op: Definition for the new operation.
180
+ overwrite: If True, allows replacing an existing operation in ``operations``.
295
181
 
296
182
  Raises:
297
- ValueError: If there is already an implementation with the same name, but
298
- corresponds to a different implementation class.
183
+ ValueError: ``op.name`` exists in ``operations`` and ``overwrite==False``.
184
+ ValueError: ``op.name`` is the name of a canonical operation in iqm-pulse.
299
185
 
300
186
  """
301
- if new_op.name in _quantum_ops_library:
302
- default_op = _quantum_ops_library[new_op.name]
303
- for default_impl_name, default_impl_cls in default_op.implementations.items():
304
- if impl_name == default_impl_name:
305
- default_impl_cls_name = (
306
- default_impl_cls if isinstance(default_impl_cls, str) else default_impl_cls.__name__
307
- )
308
- if impl_class.__name__ != default_impl_cls_name:
309
- raise ValueError(
310
- f"The implementation '{default_impl_name}' already exists in default implementations with '{default_impl_cls.__name__}' as corresponding GateImplementation class." # noqa: E501
311
- )
312
-
313
- if new_op.name in operations:
314
- existing_op = operations[new_op.name]
315
- for existing_impl_name, existing_impl_cls in existing_op.implementations.items():
316
- if impl_name == existing_impl_name:
317
- existing_impl_cls_name = (
318
- existing_impl_cls if isinstance(existing_impl_cls, str) else existing_impl_cls.__name__
319
- ) # noqa: E501
320
- if impl_class.__name__ != existing_impl_cls_name:
321
- raise ValueError(
322
- f"The implementation '{existing_impl_name}' already exists with '{existing_impl_cls.__name__}' as corresponding GateImplementation class." # noqa: E501
323
- )
187
+ if op.name in operations and not overwrite:
188
+ raise ValueError(f"'{op.name}' already registered.")
189
+ if op.name in _quantum_ops_library:
190
+ raise ValueError(f"'{op.name}' conflicts with a canonical operation in iqm-pulse. Use a different name.")
191
+
192
+ # make a deep copy since the dicts inside QuantumOp are mutable
193
+ operations[op.name] = copy.deepcopy(op)
324
194
 
325
195
 
326
196
  def register_implementation(
327
197
  operations: dict[str, QuantumOp],
328
- gate_name: str,
198
+ op_name: str,
329
199
  impl_name: str,
330
200
  impl_class: type[GateImplementation],
201
+ *,
331
202
  set_as_default: bool = False,
332
203
  overwrite: bool = False,
333
- quantum_op_specs: QuantumOp | dict | None = None,
334
204
  ) -> None:
335
- """Register a new gate implementation, and a new gate (operation) if needed.
205
+ """Register a new GateImplementation.
336
206
 
337
207
  Args:
338
- operations: Known operations, mapping gate names to QuantumOps
339
- gate_name: The gate name to register
340
- impl_name: The name for this implementation
341
- impl_class: The implementation class
342
- set_as_default: Whether to set as default implementation
343
- overwrite: If True, allows replacing existing operation/implementation
344
- quantum_op_specs: Specs for creating new quantum op if needed
208
+ operations: Known operations, to which the new implementation is added.
209
+ op_name: Name of the operation under which the implementation is registered.
210
+ impl_name: Name of the implementation to register.
211
+ impl_class: Implementation class to register.
212
+ set_as_default: Whether to set as the default implementation for ``op_name``.
213
+ overwrite: If True, allows replacing an existing implementation.
345
214
 
346
215
  Raises:
347
- ValueError: If operation/implementation exists and overwrite=False
216
+ ValueError: ``op_name`` does not exist in ``operations``.
217
+ ValueError: The implementation exists and ``overwrite==False``.
218
+
348
219
 
349
220
  """
350
- new_op = _register_gate(operations, gate_name, impl_class, quantum_op_specs)
221
+ if (op := operations.get(op_name)) is None:
222
+ raise ValueError(f"Operation '{op_name}' is not known, register it first.")
351
223
 
352
- new_op = _validate_operation(new_op, gate_name, operations, overwrite)
224
+ # canonical implementation names must not be overridden
225
+ _validate_implementation(op_name, impl_name, impl_class.__name__)
226
+
227
+ # only overwrite existing implementations with permission
228
+ if (old_class := op.implementations.get(impl_name)) is not None and not overwrite:
229
+ if old_class is not impl_class:
230
+ # cannot change an existing implementation name
231
+ raise ValueError(f"'{op_name}' already has an implementation named '{impl_name}'.")
232
+ else:
233
+ # add the new implementation
234
+ op.implementations[impl_name] = impl_class
353
235
 
354
- _add_implementation(operations, new_op, impl_name, impl_class, set_as_default, overwrite)
236
+ if set_as_default:
237
+ op.set_default_implementation(impl_name)
355
238
 
356
- operations[gate_name] = new_op
239
+ if get_implementation_class(impl_class.__name__) is None:
240
+ expose_implementation(impl_class, overwrite=overwrite)
@@ -32,6 +32,8 @@ class CCPRX_Composite(CompositeGate):
32
32
  Uses the default implementation of PRX underneath, so no extra calibration is needed.
33
33
  """
34
34
 
35
+ registered_gates = ("prx",)
36
+
35
37
  parameters = {"control_delays": Parameter("", "Control delays", "s", collection_type=CollectionType.NDARRAY)}
36
38
  """``control_delays`` contains the times it takes for the classical control signal from each
37
39
  probe line (readout instrument) to become usable for the drive AWG implementing the PRX gate.
@@ -81,7 +83,7 @@ class CCPRX_Composite(CompositeGate):
81
83
  pulse_instruction = ConditionalInstruction(
82
84
  duration=pulse.duration, condition=default_label, outcomes=(wait, pulse)
83
85
  )
84
- delays = self.builder.get_calibration(self.parent.name, self.name, self.locus)["control_delays"]
86
+ delays = self.calibration_data["control_delays"]
85
87
  if len(delays) == 0:
86
88
  raise ValueError(f"'control_delays' for '{self.name}' on {qubit} is empty (not calibrated).")
87
89
 
iqm/pulse/gates/cz.py CHANGED
@@ -100,7 +100,7 @@ class FluxPulseGate(GateImplementation):
100
100
  builder: ScheduleBuilder,
101
101
  ):
102
102
  super().__init__(parent, name, locus, calibration_data, builder)
103
- duration = calibration_data["duration"]
103
+ duration = calibration_data["duration"] # shared between all pulses
104
104
  flux_pulses = {}
105
105
 
106
106
  def build_flux_pulse(waveform_class: type[Waveform], component_name: str, cal_node_name: str) -> None:
@@ -110,14 +110,11 @@ class FluxPulseGate(GateImplementation):
110
110
  calibration_data[cal_node_name],
111
111
  self.parameters[cal_node_name], # type: ignore[arg-type]
112
112
  builder.channels[flux_channel],
113
- duration,
113
+ duration=duration,
114
114
  )
115
- # TODO convert_calibration_data should be able to do this too
116
- n_samples = builder.channels[flux_channel].duration_to_int_samples(duration) if duration > 0 else 0
117
- params["n_samples"] = n_samples
118
115
  amplitude = params.pop("amplitude")
119
116
  flux_pulses[flux_channel] = FluxPulse(
120
- duration=n_samples,
117
+ duration=params["n_samples"],
121
118
  wave=waveform_class(**params),
122
119
  scale=amplitude,
123
120
  )
@@ -283,8 +280,7 @@ class CouplerFluxPulseQubitACStarkPulseGate(GateImplementation):
283
280
  builder: ScheduleBuilder,
284
281
  ):
285
282
  super().__init__(parent, name, locus, calibration_data, builder)
286
-
287
- duration = calibration_data["duration"]
283
+ duration = calibration_data["duration"] # shared between all pulses
288
284
  flux_pulses = {}
289
285
  qubit_drive_pulses = {}
290
286
  rz = calibration_data["rz"]
@@ -296,13 +292,11 @@ class CouplerFluxPulseQubitACStarkPulseGate(GateImplementation):
296
292
  calibration_data[cal_node_name],
297
293
  self.parameters[cal_node_name], # type: ignore[arg-type]
298
294
  builder.channels[flux_channel],
299
- duration,
295
+ duration=duration,
300
296
  )
301
- n_samples = builder.channels[flux_channel].duration_to_int_samples(duration) if duration > 0 else 0
302
- params["n_samples"] = n_samples
303
297
  amplitude = params.pop("amplitude")
304
298
  flux_pulses[flux_channel] = FluxPulse(
305
- duration=n_samples,
299
+ duration=params["n_samples"],
306
300
  wave=waveform_class(**params),
307
301
  scale=amplitude,
308
302
  )
@@ -314,10 +308,8 @@ class CouplerFluxPulseQubitACStarkPulseGate(GateImplementation):
314
308
  calibration_data[cal_node_name],
315
309
  self.parameters[cal_node_name], # type: ignore[arg-type]
316
310
  builder.channels[drive_channel],
317
- duration,
311
+ duration=duration,
318
312
  )
319
- n_samples = builder.channels[drive_channel].duration_to_int_samples(duration) if duration > 0 else 0
320
- params["n_samples"] = n_samples
321
313
  params["phase_increment"] = rz[component_name]
322
314
  qubit_drive_pulses[drive_channel] = self._ac_stark_pulse(**params)
323
315
 
@@ -11,7 +11,11 @@
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 file defines the default quantum gates and operations for IQM's pulse control system"""
14
+ """Canonical quantum operations and implementations provided by iqm-pulse."""
15
+
16
+ from __future__ import annotations
17
+
18
+ from typing import TYPE_CHECKING
15
19
 
16
20
  import numpy as np
17
21
 
@@ -53,11 +57,13 @@ from iqm.pulse.gates.rz import (
53
57
  )
54
58
  from iqm.pulse.gates.sx import SXGate
55
59
  from iqm.pulse.gates.u import UGate, get_unitary_u
56
- from iqm.pulse.quantum_ops import QuantumOp, QuantumOpTable
60
+ from iqm.pulse.quantum_ops import QuantumOp
57
61
 
58
- """A collection of mappings between default implementation names and their GateImplementation classes,
59
- for different gates."""
60
- _default_implementations = {
62
+ if TYPE_CHECKING:
63
+ from iqm.pulse.gate_implementation import GateImplementation
64
+
65
+
66
+ _default_implementations: dict[str, dict[str, type[GateImplementation]]] = {
61
67
  "barrier": {"": Barrier},
62
68
  "delay": {"wait": Delay},
63
69
  "measure": {
@@ -99,119 +105,10 @@ _default_implementations = {
99
105
  "reset_wait": {"reset_wait": Reset_Wait},
100
106
  "flux_multiplexer": {"sample_linear": FluxMultiplexer_SampleLinear},
101
107
  }
108
+ """Canonical implementation names for the canonical quantum operations.
109
+ A collection of mappings between default implementation names and their GateImplementation classes,
110
+ for different gates."""
102
111
 
103
- """A table of quantum operations (`_default_operations`) defining characteristics:
104
- - Number of qubits involved (arity)
105
- - Required parameters
106
- - GateImplementation classes
107
- - Unitary matrices where applicable
108
- - Properties like symmetry and factorizability
109
- This table is here so that we retain information about operations, even though they might
110
- be deleted in the future.
111
- """
112
- _default_operations: QuantumOpTable = {
113
- op.name: op
114
- for op in [
115
- QuantumOp(
116
- "barrier",
117
- 0,
118
- implementations=_default_implementations["barrier"], # type: ignore[arg-type]
119
- symmetric=True,
120
- ),
121
- QuantumOp(
122
- "delay",
123
- 0,
124
- ("duration",),
125
- implementations=_default_implementations["delay"], # type: ignore[arg-type]
126
- symmetric=True,
127
- ),
128
- QuantumOp(
129
- "measure",
130
- 0,
131
- ("key",),
132
- implementations=_default_implementations["measure"], # type: ignore[arg-type]
133
- factorizable=True,
134
- ),
135
- QuantumOp(
136
- "prx",
137
- 1,
138
- ("angle", "phase"),
139
- implementations=_default_implementations["prx"], # type: ignore[arg-type]
140
- unitary=get_unitary_prx,
141
- ),
142
- QuantumOp(
143
- "prx_12",
144
- 1,
145
- ("angle", "phase"),
146
- implementations=_default_implementations["prx_12"], # type: ignore[arg-type]
147
- ),
148
- QuantumOp(
149
- "u",
150
- 1,
151
- ("theta", "phi", "lam"),
152
- implementations=_default_implementations["u"], # type: ignore[arg-type]
153
- unitary=get_unitary_u,
154
- ),
155
- QuantumOp(
156
- "sx",
157
- 1,
158
- implementations=_default_implementations["sx"], # type: ignore[arg-type]
159
- unitary=lambda: get_unitary_prx(np.pi / 2, 0),
160
- ),
161
- QuantumOp(
162
- "rz",
163
- 1,
164
- ("angle",),
165
- implementations=_default_implementations["rz"], # type: ignore[arg-type]
166
- unitary=get_unitary_rz,
167
- ),
168
- QuantumOp(
169
- "rz_physical",
170
- 1,
171
- implementations=_default_implementations["rz_physical"], # type: ignore[arg-type]
172
- ),
173
- QuantumOp(
174
- "cz",
175
- 2,
176
- (),
177
- implementations=_default_implementations["cz"], # type: ignore[arg-type]
178
- symmetric=True,
179
- unitary=lambda: np.diag([1.0, 1.0, 1.0, -1.0]),
180
- ),
181
- QuantumOp(
182
- "move",
183
- 2,
184
- implementations=_default_implementations["move"], # type: ignore[arg-type]
185
- ),
186
- QuantumOp(
187
- "cc_prx",
188
- 1,
189
- ("angle", "phase", "feedback_qubit", "feedback_key"),
190
- implementations=_default_implementations["cc_prx"], # type: ignore[arg-type]
191
- ),
192
- QuantumOp(
193
- "reset",
194
- 0,
195
- implementations=_default_implementations["reset"], # type: ignore[arg-type]
196
- symmetric=True,
197
- factorizable=True,
198
- ),
199
- QuantumOp(
200
- "reset_wait",
201
- 0,
202
- implementations=_default_implementations["reset_wait"], # type: ignore[arg-type]
203
- symmetric=True,
204
- factorizable=True,
205
- ),
206
- QuantumOp(
207
- "flux_multiplexer",
208
- 0,
209
- implementations=_default_implementations["flux_multiplexer"], # type: ignore[arg-type]
210
- ),
211
- ]
212
- }
213
-
214
- """A library for all canonical Quantum Operations (gates)"""
215
112
  _quantum_ops_library = {
216
113
  op.name: op
217
114
  for op in [
@@ -313,3 +210,6 @@ _quantum_ops_library = {
313
210
  ),
314
211
  ]
315
212
  }
213
+ """Library of the current canonical quantum operations provided by iqm-pulse."""
214
+ # NOTE If a canonical operation is removed in the future, consider adding a second dict for former
215
+ # canonical operations so that we retain information about them.
@@ -9,7 +9,7 @@
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
- from collections.abc import Iterable
12
+ from collections.abc import Iterable, Mapping
13
13
  from copy import deepcopy
14
14
 
15
15
  from iqm.models.playlist.waveforms import Samples
@@ -170,7 +170,7 @@ class FluxMultiplexer_SampleLinear(GateImplementation):
170
170
 
171
171
  @classmethod
172
172
  def get_custom_locus_mapping(
173
- cls, chip_topology: ChipTopology, component_to_channels: dict[str, Iterable[str]]
173
+ cls, chip_topology: ChipTopology, component_to_channels: Mapping[str, Iterable[str]]
174
174
  ) -> dict[tuple[str, ...] | frozenset[str], tuple[str, ...]] | None:
175
175
  """Locus is "global" (the whole QPU) represented by an empty tuple for now."""
176
176
  # pylint: disable=unused-argument