iqm-client 32.1.1__py3-none-any.whl → 33.0.1__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/devices/iqm_device_metadata.py +2 -1
- iqm/cirq_iqm/examples/demo_common.py +1 -1
- iqm/cirq_iqm/examples/demo_iqm_execution.py +3 -3
- iqm/cirq_iqm/iqm_sampler.py +47 -29
- iqm/cirq_iqm/serialize.py +1 -1
- iqm/cirq_iqm/transpiler.py +3 -1
- iqm/iqm_client/__init__.py +0 -2
- iqm/iqm_client/errors.py +6 -17
- iqm/iqm_client/iqm_client.py +199 -602
- iqm/iqm_client/models.py +20 -611
- iqm/iqm_client/transpile.py +11 -8
- iqm/iqm_client/validation.py +18 -9
- iqm/iqm_server_client/__init__.py +14 -0
- iqm/iqm_server_client/errors.py +6 -0
- iqm/iqm_server_client/iqm_server_client.py +755 -0
- iqm/iqm_server_client/models.py +179 -0
- iqm/iqm_server_client/py.typed +0 -0
- iqm/qiskit_iqm/__init__.py +8 -0
- iqm/qiskit_iqm/examples/bell_measure.py +5 -5
- iqm/qiskit_iqm/examples/transpile_example.py +13 -6
- iqm/qiskit_iqm/fake_backends/fake_adonis.py +2 -1
- iqm/qiskit_iqm/fake_backends/fake_aphrodite.py +2 -1
- iqm/qiskit_iqm/fake_backends/fake_apollo.py +2 -1
- iqm/qiskit_iqm/fake_backends/fake_deneb.py +2 -1
- iqm/qiskit_iqm/fake_backends/iqm_fake_backend.py +8 -7
- iqm/qiskit_iqm/iqm_backend.py +3 -4
- iqm/qiskit_iqm/iqm_circuit_validation.py +8 -7
- iqm/qiskit_iqm/iqm_job.py +106 -88
- iqm/qiskit_iqm/iqm_move_layout.py +2 -1
- iqm/qiskit_iqm/iqm_naive_move_pass.py +114 -55
- iqm/qiskit_iqm/iqm_provider.py +49 -36
- iqm/qiskit_iqm/iqm_target.py +4 -6
- iqm/qiskit_iqm/qiskit_to_iqm.py +62 -25
- {iqm_client-32.1.1.dist-info → iqm_client-33.0.1.dist-info}/METADATA +17 -24
- iqm_client-33.0.1.dist-info/RECORD +63 -0
- iqm/iqm_client/api.py +0 -90
- iqm/iqm_client/authentication.py +0 -206
- iqm_client-32.1.1.dist-info/RECORD +0 -60
- {iqm_client-32.1.1.dist-info → iqm_client-33.0.1.dist-info}/AUTHORS.rst +0 -0
- {iqm_client-32.1.1.dist-info → iqm_client-33.0.1.dist-info}/LICENSE.txt +0 -0
- {iqm_client-32.1.1.dist-info → iqm_client-33.0.1.dist-info}/WHEEL +0 -0
- {iqm_client-32.1.1.dist-info → iqm_client-33.0.1.dist-info}/entry_points.txt +0 -0
- {iqm_client-32.1.1.dist-info → iqm_client-33.0.1.dist-info}/top_level.txt +0 -0
iqm/qiskit_iqm/iqm_provider.py
CHANGED
|
@@ -21,13 +21,7 @@ from typing import Any
|
|
|
21
21
|
from uuid import UUID
|
|
22
22
|
import warnings
|
|
23
23
|
|
|
24
|
-
from iqm.iqm_client import
|
|
25
|
-
CircuitBatch,
|
|
26
|
-
CircuitCompilationOptions,
|
|
27
|
-
CircuitValidationError,
|
|
28
|
-
IQMClient,
|
|
29
|
-
RunRequest,
|
|
30
|
-
)
|
|
24
|
+
from iqm.iqm_client import CircuitCompilationOptions, CircuitValidationError, IQMClient
|
|
31
25
|
from iqm.iqm_client.util import to_json_dict
|
|
32
26
|
from iqm.qiskit_iqm import IQMFakeAphrodite, IQMFakeApollo, IQMFakeBackend, IQMFakeDeneb
|
|
33
27
|
from iqm.qiskit_iqm.fake_backends import IQMFakeAdonis
|
|
@@ -39,6 +33,7 @@ from qiskit import QuantumCircuit
|
|
|
39
33
|
from qiskit.providers import JobStatus, JobV1, Options
|
|
40
34
|
|
|
41
35
|
from iqm.pulse import Circuit
|
|
36
|
+
from iqm.station_control.interface.models import CircuitBatch, RunRequest
|
|
42
37
|
|
|
43
38
|
try:
|
|
44
39
|
__version__ = version("qiskit-iqm")
|
|
@@ -52,9 +47,9 @@ class IQMBackend(IQMBackendBase):
|
|
|
52
47
|
"""Backend for executing quantum circuits on IQM quantum computers.
|
|
53
48
|
|
|
54
49
|
Args:
|
|
55
|
-
client: Client instance for communicating with an IQM
|
|
50
|
+
client: Client instance for communicating with an IQM Server.
|
|
56
51
|
calibration_set_id: ID of the calibration set the backend will use.
|
|
57
|
-
``None`` means the IQM
|
|
52
|
+
``None`` means the IQM Server will be queried for the current default
|
|
58
53
|
calibration set.
|
|
59
54
|
use_metrics: If True, the backend will query the server for calibration data and related
|
|
60
55
|
quality metrics, and pass these to the transpilation target(s). The default value is set
|
|
@@ -80,7 +75,6 @@ class IQMBackend(IQMBackendBase):
|
|
|
80
75
|
super().__init__(architecture, metrics=metrics, **kwargs)
|
|
81
76
|
self.client: IQMClient = client
|
|
82
77
|
self._max_circuits: int | None = None
|
|
83
|
-
self.name = "IQM Backend"
|
|
84
78
|
self._calibration_set_id = architecture.calibration_set_id
|
|
85
79
|
|
|
86
80
|
@classmethod
|
|
@@ -110,12 +104,16 @@ class IQMBackend(IQMBackendBase):
|
|
|
110
104
|
def run(
|
|
111
105
|
self,
|
|
112
106
|
run_input: QuantumCircuit | list[QuantumCircuit],
|
|
107
|
+
*,
|
|
108
|
+
use_timeslot: bool = False,
|
|
113
109
|
**options,
|
|
114
110
|
) -> IQMJob:
|
|
115
111
|
"""Run a quantum circuit or a list of quantum circuits on the IQM quantum computer represented by this backend.
|
|
116
112
|
|
|
117
113
|
Args:
|
|
118
114
|
run_input: The circuits to run.
|
|
115
|
+
use_timeslot: Submits the job to the timeslot queue if set to ``True``. If set to ``False``,
|
|
116
|
+
the job is submitted to the normal on-demand queue.
|
|
119
117
|
options: Keyword arguments passed on to :meth:`create_run_request`, and documented there.
|
|
120
118
|
|
|
121
119
|
Returns:
|
|
@@ -123,8 +121,8 @@ class IQMBackend(IQMBackendBase):
|
|
|
123
121
|
|
|
124
122
|
"""
|
|
125
123
|
run_request = self.create_run_request(run_input, **options)
|
|
126
|
-
|
|
127
|
-
job = IQMJob(self,
|
|
124
|
+
circuit_job = self.client.submit_run_request(run_request, use_timeslot=use_timeslot)
|
|
125
|
+
job = IQMJob(self, circuit_job)
|
|
128
126
|
job.circuit_metadata = [c.metadata if isinstance(c, Circuit) else {} for c in run_request.circuits]
|
|
129
127
|
return job
|
|
130
128
|
|
|
@@ -134,7 +132,7 @@ class IQMBackend(IQMBackendBase):
|
|
|
134
132
|
shots: int = 1024,
|
|
135
133
|
circuit_compilation_options: CircuitCompilationOptions | None = None,
|
|
136
134
|
circuit_callback: Callable[[list[QuantumCircuit]], Any] | None = None,
|
|
137
|
-
|
|
135
|
+
qubit_index_to_name: dict[int, str] | None = None,
|
|
138
136
|
**unknown_options,
|
|
139
137
|
) -> RunRequest:
|
|
140
138
|
"""Creates a run request without submitting it for execution.
|
|
@@ -161,8 +159,8 @@ class IQMBackend(IQMBackendBase):
|
|
|
161
159
|
As a side effect, you can also use this callback to modify the transpiled circuits
|
|
162
160
|
in-place, just before execution; however, we do not recommend to use it for this
|
|
163
161
|
purpose.
|
|
164
|
-
|
|
165
|
-
:attr:`.IQMBackendBase.index_to_qubit_name` will be used.
|
|
162
|
+
qubit_index_to_name: Mapping from qubit indices in the circuit to qubit names on the device.
|
|
163
|
+
If ``None``, :attr:`.IQMBackendBase.index_to_qubit_name` will be used.
|
|
166
164
|
|
|
167
165
|
Returns:
|
|
168
166
|
The created run request object
|
|
@@ -195,7 +193,9 @@ class IQMBackend(IQMBackendBase):
|
|
|
195
193
|
if circuit_callback:
|
|
196
194
|
circuit_callback(circuits)
|
|
197
195
|
|
|
198
|
-
circuits_serialized: CircuitBatch = [
|
|
196
|
+
circuits_serialized: CircuitBatch = [
|
|
197
|
+
self.serialize_circuit(circuit, qubit_index_to_name) for circuit in circuits
|
|
198
|
+
]
|
|
199
199
|
|
|
200
200
|
if self._use_default_calibration_set:
|
|
201
201
|
default_calset_id = self.client.get_dynamic_quantum_architecture(None).calibration_set_id
|
|
@@ -230,13 +230,10 @@ class IQMBackend(IQMBackendBase):
|
|
|
230
230
|
corresponding job
|
|
231
231
|
|
|
232
232
|
"""
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
def close_client(self) -> None:
|
|
236
|
-
"""Close IQMClient's session with the authentication server."""
|
|
237
|
-
self.client.close_auth_session()
|
|
233
|
+
circuit_job = self.client.get_job(UUID(job_id))
|
|
234
|
+
return IQMJob(self, circuit_job)
|
|
238
235
|
|
|
239
|
-
def serialize_circuit(self, circuit: QuantumCircuit,
|
|
236
|
+
def serialize_circuit(self, circuit: QuantumCircuit, qubit_index_to_name: dict[int, str] | None = None) -> Circuit:
|
|
240
237
|
"""Serialize a quantum circuit into the IQM data transfer format.
|
|
241
238
|
|
|
242
239
|
Serializing is not strictly bound to the native gateset, i.e. some gates that are not explicitly mentioned in
|
|
@@ -254,8 +251,8 @@ class IQMBackend(IQMBackendBase):
|
|
|
254
251
|
|
|
255
252
|
Args:
|
|
256
253
|
circuit: quantum circuit to serialize
|
|
257
|
-
|
|
258
|
-
:attr:`.IQMBackendBase.index_to_qubit_name` will be used.
|
|
254
|
+
qubit_index_to_name: Mapping from qubit indices in the circuit to qubit names on the device.
|
|
255
|
+
If ``None``, :attr:`.IQMBackendBase.index_to_qubit_name` will be used.
|
|
259
256
|
|
|
260
257
|
Returns:
|
|
261
258
|
data transfer object representing the circuit
|
|
@@ -264,9 +261,9 @@ class IQMBackend(IQMBackendBase):
|
|
|
264
261
|
ValueError: circuit contains an unsupported instruction or is not transpiled in general
|
|
265
262
|
|
|
266
263
|
"""
|
|
267
|
-
if
|
|
268
|
-
|
|
269
|
-
instructions = tuple(serialize_instructions(circuit, qubit_index_to_name=
|
|
264
|
+
if qubit_index_to_name is None:
|
|
265
|
+
qubit_index_to_name = self._idx_to_qb
|
|
266
|
+
instructions = tuple(serialize_instructions(circuit, qubit_index_to_name=qubit_index_to_name))
|
|
270
267
|
|
|
271
268
|
try:
|
|
272
269
|
metadata = to_json_dict(circuit.metadata)
|
|
@@ -291,7 +288,7 @@ facade_names: dict[str, IQMFakeBackend] = {
|
|
|
291
288
|
class IQMFacadeBackend(IQMBackend):
|
|
292
289
|
"""Simulates locally the execution of quantum circuits on a remote mock IQM quantum computer.
|
|
293
290
|
|
|
294
|
-
This backend is meant to be used to run circuits on a mock IQM
|
|
291
|
+
This backend is meant to be used to run circuits on a mock IQM Server that has no real quantum hardware,
|
|
295
292
|
and if the mock execution is successful, simulate the circuits locally using an error model that
|
|
296
293
|
is broadly representative of the mocked QPU. Finally it returns the *simulated results*.
|
|
297
294
|
|
|
@@ -299,13 +296,13 @@ class IQMFacadeBackend(IQMBackend):
|
|
|
299
296
|
|
|
300
297
|
.. important::
|
|
301
298
|
|
|
302
|
-
When using a facade backend, the IQM
|
|
299
|
+
When using a facade backend, the IQM Server URL of :class:`IQMProvider` should always point to a mock environment
|
|
303
300
|
rather than a real quantum computer, as the execution results from the server will be discarded and replaced by
|
|
304
301
|
a locally simulated result generated by Qiskit Aer. If you use a real quantum computer with a facade backend,
|
|
305
302
|
you will just waste your credits and/or computation time.
|
|
306
303
|
|
|
307
304
|
Args:
|
|
308
|
-
client: Client instance for communicating with an IQM
|
|
305
|
+
client: Client instance for communicating with an IQM Server.
|
|
309
306
|
name: Name of the fake backend (simulator instance) to use. If None, will be determined automatically based
|
|
310
307
|
on the static quantum architecture of the server.
|
|
311
308
|
kwargs: Optional arguments to be passed to the parent class.
|
|
@@ -341,7 +338,13 @@ class IQMFacadeBackend(IQMBackend):
|
|
|
341
338
|
return False
|
|
342
339
|
return True
|
|
343
340
|
|
|
344
|
-
def run(
|
|
341
|
+
def run(
|
|
342
|
+
self,
|
|
343
|
+
run_input: QuantumCircuit | list[QuantumCircuit],
|
|
344
|
+
*,
|
|
345
|
+
use_timeslot: bool = False,
|
|
346
|
+
**options,
|
|
347
|
+
) -> JobV1:
|
|
345
348
|
circuits = [run_input] if isinstance(run_input, QuantumCircuit) else run_input
|
|
346
349
|
circuits_validated_cregs: list[bool] = [self._validate_no_empty_cregs(circuit) for circuit in circuits]
|
|
347
350
|
if not all(circuits_validated_cregs):
|
|
@@ -350,7 +353,7 @@ class IQMFacadeBackend(IQMBackend):
|
|
|
350
353
|
"see the user guide."
|
|
351
354
|
)
|
|
352
355
|
|
|
353
|
-
iqm_backend_job = super().run(run_input, **options)
|
|
356
|
+
iqm_backend_job = super().run(run_input, use_timeslot=use_timeslot, **options)
|
|
354
357
|
iqm_backend_job.result() # get and discard results
|
|
355
358
|
if iqm_backend_job.status() == JobStatus.ERROR:
|
|
356
359
|
raise RuntimeError("Remote execution did not succeed.")
|
|
@@ -360,18 +363,28 @@ class IQMFacadeBackend(IQMBackend):
|
|
|
360
363
|
class IQMProvider:
|
|
361
364
|
"""Provider for IQM backends.
|
|
362
365
|
|
|
363
|
-
IQMProvider connects to a quantum computer through an IQM
|
|
366
|
+
IQMProvider connects to a quantum computer through an IQM Server.
|
|
364
367
|
If the server requires user authentication, you can provide it either using environment
|
|
365
368
|
variables, or as keyword arguments to IQMProvider. The user authentication kwargs are passed
|
|
366
369
|
through to :class:`~iqm.iqm_client.iqm_client.IQMClient` as is, and are documented there.
|
|
367
370
|
|
|
368
371
|
Args:
|
|
369
|
-
url: URL of the IQM
|
|
372
|
+
url: URL of the IQM Server (e.g. "https://resonance.meetiqm.com/").
|
|
373
|
+
quantum_computer: ID or alias of the quantum computer to connect to, if the IQM Server
|
|
374
|
+
instance controls more than one (e.g. "garnet"). ``None`` means connect to the
|
|
375
|
+
default one.
|
|
370
376
|
|
|
371
377
|
"""
|
|
372
378
|
|
|
373
|
-
def __init__(
|
|
379
|
+
def __init__(
|
|
380
|
+
self,
|
|
381
|
+
url: str,
|
|
382
|
+
*,
|
|
383
|
+
quantum_computer: str | None = None,
|
|
384
|
+
**user_auth_args, # contains keyword args token or tokens_file
|
|
385
|
+
):
|
|
374
386
|
self.url = url
|
|
387
|
+
self.quantum_computer = quantum_computer
|
|
375
388
|
self.user_auth_args = user_auth_args
|
|
376
389
|
|
|
377
390
|
def get_backend(
|
|
@@ -396,7 +409,7 @@ class IQMProvider:
|
|
|
396
409
|
Backend instance for connecting to a quantum computer.
|
|
397
410
|
|
|
398
411
|
"""
|
|
399
|
-
client = IQMClient(self.url, **self.user_auth_args)
|
|
412
|
+
client = IQMClient(self.url, quantum_computer=self.quantum_computer, **self.user_auth_args)
|
|
400
413
|
|
|
401
414
|
if name and name.startswith("facade_"):
|
|
402
415
|
return IQMFacadeBackend(client, name=name, calibration_set_id=calibration_set_id, use_metrics=use_metrics)
|
iqm/qiskit_iqm/iqm_target.py
CHANGED
|
@@ -19,18 +19,15 @@ from collections.abc import Iterable
|
|
|
19
19
|
import logging
|
|
20
20
|
from typing import TypeAlias
|
|
21
21
|
|
|
22
|
-
from iqm.iqm_client import
|
|
23
|
-
DynamicQuantumArchitecture,
|
|
24
|
-
GateImplementationInfo,
|
|
25
|
-
GateInfo,
|
|
26
|
-
ObservationFinder,
|
|
27
|
-
)
|
|
22
|
+
from iqm.iqm_client import ObservationFinder
|
|
28
23
|
from iqm.qiskit_iqm.move_gate import MoveGate
|
|
29
24
|
from qiskit.circuit import Delay, Gate, IfElseOp, Parameter, Reset
|
|
30
25
|
from qiskit.circuit.library import CZGate, IGate, Measure, RGate
|
|
31
26
|
from qiskit.providers import QubitProperties
|
|
32
27
|
from qiskit.transpiler import InstructionProperties, Target
|
|
33
28
|
|
|
29
|
+
from iqm.station_control.interface.models import DynamicQuantumArchitecture, GateImplementationInfo, GateInfo
|
|
30
|
+
|
|
34
31
|
Locus: TypeAlias = tuple[str, ...]
|
|
35
32
|
"""Sequence of QPU component names on which a gate acts."""
|
|
36
33
|
LocusIdx: TypeAlias = tuple[int, ...]
|
|
@@ -83,6 +80,7 @@ class IQMTarget(Target):
|
|
|
83
80
|
# (2) make all the IQMTarget.__init__ args keyword-only with non-colliding names, and init
|
|
84
81
|
# the necessary superclass attributes ourselves.
|
|
85
82
|
# (1) seems to break pickling during concurrent transpilation using Qiskit's ``transpile``, so we use (2).
|
|
83
|
+
# (2) is broken from Qiskit 2.2 onwards because Target.__init__ no longer has **kwargs nor *args.
|
|
86
84
|
self.qubit_properties = self._create_qubit_properties(architecture.qubits, metrics)
|
|
87
85
|
|
|
88
86
|
# Using iqm_ as a prefix to avoid name clashes with the base class.
|
iqm/qiskit_iqm/qiskit_to_iqm.py
CHANGED
|
@@ -25,14 +25,14 @@ from iqm.qiskit_iqm.move_gate import MoveGate
|
|
|
25
25
|
from packaging.version import Version
|
|
26
26
|
from qiskit import QuantumCircuit as QiskitQuantumCircuit
|
|
27
27
|
from qiskit import __version__ as qiskit_version
|
|
28
|
-
from qiskit.circuit import ClassicalRegister, Clbit, Operation, QuantumRegister
|
|
28
|
+
from qiskit.circuit import ClassicalRegister, Clbit, Operation, QuantumRegister, Qubit
|
|
29
29
|
from qiskit.transpiler.layout import Layout
|
|
30
30
|
|
|
31
31
|
from iqm.pulse import CircuitOperation
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
class InstructionNotSupportedError(RuntimeError):
|
|
35
|
-
"""Raised when a given instruction is not supported by the IQM
|
|
35
|
+
"""Raised when a given instruction is not supported by the IQM Server."""
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
@dataclass(frozen=True)
|
|
@@ -55,7 +55,7 @@ class MeasurementKey:
|
|
|
55
55
|
``IQMJob``, since otherwise users will not be able to retrieve results from a detached Python
|
|
56
56
|
environment solely based on the job id. Another option is to use measurement key strings to
|
|
57
57
|
store the required info. Qiskit does not use measurement keys, so we are free to use them
|
|
58
|
-
internally in the communication with the IQM
|
|
58
|
+
internally in the communication with the IQM Server, and can encode the necessary information in
|
|
59
59
|
them.
|
|
60
60
|
|
|
61
61
|
This class encapsulates the necessary info, and provides methods to transform between this
|
|
@@ -142,6 +142,54 @@ def _apply_condition(
|
|
|
142
142
|
inst.args["feedback_qubit"] = physical_qubit_name
|
|
143
143
|
|
|
144
144
|
|
|
145
|
+
def _calculate_ifblock_idx2name_mapping(
|
|
146
|
+
circuit: QiskitQuantumCircuit,
|
|
147
|
+
if_block_qubits: list[Qubit],
|
|
148
|
+
overwrite_layout: Layout | None,
|
|
149
|
+
qubit_index_to_name: dict[int, str],
|
|
150
|
+
) -> dict[int, str]:
|
|
151
|
+
"""Calculate mapping from if-block qubit registers to physical qubit names.
|
|
152
|
+
|
|
153
|
+
The if-block circuit has no qregs of its own, just references to the parent circuit qubits.
|
|
154
|
+
Depending on how the circuit was transpiled, the if-block Qubit Objects might
|
|
155
|
+
not be present in the parent Circuit. This method finds the appropriate physical qubits
|
|
156
|
+
to make a new qubit index to name mapping for it.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
circuit: The parent quantum circuit containing the if-block.
|
|
160
|
+
if_block_qubits: The qubits used in the if-block.
|
|
161
|
+
overwrite_layout: An alternative layout indicating the physical qubit mapping to use for the serialized
|
|
162
|
+
instructions, this overwrites the circuit's layout.
|
|
163
|
+
qubit_index_to_name: Mapping from qubit indices to the corresponding qubit names as obtained from a backend.
|
|
164
|
+
|
|
165
|
+
"""
|
|
166
|
+
# Check if we can use the overwrite layout
|
|
167
|
+
use_overwrite_layout = overwrite_layout is not None and all(
|
|
168
|
+
qb in overwrite_layout.get_physical_bits().values() for qb in if_block_qubits
|
|
169
|
+
)
|
|
170
|
+
if use_overwrite_layout:
|
|
171
|
+
physical_qubits = {q: i for i, q in overwrite_layout.get_physical_bits().items()} # type: ignore[union-attr]
|
|
172
|
+
return {k: qubit_index_to_name[physical_qubits[q]] for k, q in enumerate(if_block_qubits)}
|
|
173
|
+
# The if-block qubits are not in the circuit, so we hope they are in the layout
|
|
174
|
+
use_circuit_layout_guess = circuit.layout is not None and all(
|
|
175
|
+
qb in circuit.layout.initial_layout.get_physical_bits().values() for qb in if_block_qubits
|
|
176
|
+
)
|
|
177
|
+
if use_circuit_layout_guess:
|
|
178
|
+
physical_qubits = {q: i for i, q in circuit.layout.initial_layout.get_physical_bits().items()}
|
|
179
|
+
return {k: qubit_index_to_name[physical_qubits[q]] for k, q in enumerate(if_block_qubits)}
|
|
180
|
+
|
|
181
|
+
# Hope that we can find the qubits in the circuit - can be wrong.
|
|
182
|
+
use_circuit_find_bit = all(qb in circuit.qubits for qb in if_block_qubits)
|
|
183
|
+
if use_circuit_find_bit:
|
|
184
|
+
return {k: qubit_index_to_name[circuit.find_bit(q).index] for k, q in enumerate(if_block_qubits)}
|
|
185
|
+
# Catch-all: we cannot determine the mapping, this should never happen.
|
|
186
|
+
raise ValueError(
|
|
187
|
+
"Could not determine the physical locations for if-block qubits. "
|
|
188
|
+
"The if-block uses {if_block_qubits} qubits, but the parent circuit has qubits {circuit.qubits}, "
|
|
189
|
+
"the circuit layout is {circuit.layout.initial_layout}, and the overwrite layout is {overwrite_layout}."
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
145
193
|
def serialize_instructions( # noqa: PLR0912, PLR0915
|
|
146
194
|
circuit: QiskitQuantumCircuit,
|
|
147
195
|
qubit_index_to_name: dict[int, str],
|
|
@@ -179,9 +227,16 @@ def serialize_instructions( # noqa: PLR0912, PLR0915
|
|
|
179
227
|
# maps clbits to the latest "measure" instruction to store its result there
|
|
180
228
|
if clbit_to_measure is None:
|
|
181
229
|
clbit_to_measure = {}
|
|
230
|
+
invalid_layout = circuit.layout is None or circuit.layout.initial_layout.get_registers() != set(circuit.qregs)
|
|
182
231
|
for circuit_instruction in circuit.data:
|
|
183
232
|
instruction = circuit_instruction.operation
|
|
184
|
-
|
|
233
|
+
if invalid_layout:
|
|
234
|
+
qubit_names = tuple(
|
|
235
|
+
qubit_index_to_name[circuit.find_bit(qubit).index] for qubit in circuit_instruction.qubits
|
|
236
|
+
)
|
|
237
|
+
else:
|
|
238
|
+
physical_qubits = {q: i for i, q in circuit.layout.initial_layout.get_physical_bits().items()}
|
|
239
|
+
qubit_names = tuple(qubit_index_to_name[physical_qubits[qubit]] for qubit in circuit_instruction.qubits)
|
|
185
240
|
if instruction.name == "r":
|
|
186
241
|
angle = float(instruction.params[0])
|
|
187
242
|
phase = float(instruction.params[1])
|
|
@@ -241,28 +296,10 @@ def serialize_instructions( # noqa: PLR0912, PLR0915
|
|
|
241
296
|
if_block, else_block = instruction.params
|
|
242
297
|
if else_block is not None and len(else_block) > 0: # Non-empty circuit in else-block
|
|
243
298
|
raise ValueError("The use of an else-block with if_test is not supported.")
|
|
244
|
-
#
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
# Hence we need to make a new qubit index to name mapping for it.
|
|
248
|
-
|
|
249
|
-
# NOTE Sometimes the qubits in the if-block are in the circuit and
|
|
250
|
-
# sometimes they are in the layout depending on how the circuit was transpiled.
|
|
251
|
-
# So let's find out where to get the physical qubits from.
|
|
252
|
-
use_overwrite_layout = overwrite_layout is not None and all(
|
|
253
|
-
qb in overwrite_layout.get_physical_bits().values() for qb in if_block.qubits
|
|
299
|
+
# Recursively serialize the if-block.
|
|
300
|
+
q_index_to_name = _calculate_ifblock_idx2name_mapping(
|
|
301
|
+
circuit, if_block.qubits, overwrite_layout, qubit_index_to_name
|
|
254
302
|
)
|
|
255
|
-
if use_overwrite_layout:
|
|
256
|
-
physical_qubits = {q: i for i, q in overwrite_layout.get_physical_bits().items()} # type: ignore[union-attr]
|
|
257
|
-
q_index_to_name = {k: qubit_index_to_name[physical_qubits[q]] for k, q in enumerate(if_block.qubits)}
|
|
258
|
-
elif circuit.layout is None or all(qb in circuit.qubits for qb in if_block.qubits):
|
|
259
|
-
q_index_to_name = {
|
|
260
|
-
k: qubit_index_to_name[circuit.find_bit(q).index] for k, q in enumerate(if_block.qubits)
|
|
261
|
-
}
|
|
262
|
-
else:
|
|
263
|
-
physical_qubits = {q: i for i, q in circuit.layout.initial_layout.get_physical_bits().items()}
|
|
264
|
-
q_index_to_name = {k: qubit_index_to_name[physical_qubits[q]] for k, q in enumerate(if_block.qubits)}
|
|
265
|
-
|
|
266
303
|
if_instructions = serialize_instructions(
|
|
267
304
|
if_block, q_index_to_name, allowed_nonnative_gates, clbit_to_measure=clbit_to_measure
|
|
268
305
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: iqm-client
|
|
3
|
-
Version:
|
|
3
|
+
Version: 33.0.1
|
|
4
4
|
Summary: Client library for accessing an IQM quantum computer
|
|
5
5
|
Author-email: IQM Finland Oy <developers@meetiqm.com>
|
|
6
6
|
License: Apache License
|
|
@@ -216,35 +216,25 @@ Requires-Python: <3.13,>=3.10
|
|
|
216
216
|
Description-Content-Type: text/x-rst
|
|
217
217
|
License-File: LICENSE.txt
|
|
218
218
|
License-File: AUTHORS.rst
|
|
219
|
-
Requires-Dist: iqm-station-control-client <12,>=11
|
|
220
|
-
Requires-Dist: iqm-exa-common <28,>=27
|
|
221
|
-
Requires-Dist: iqm-pulse <13,>=12
|
|
222
219
|
Requires-Dist: numpy <3.0,>=1.26.4
|
|
223
220
|
Requires-Dist: packaging ==24.1
|
|
224
221
|
Requires-Dist: pydantic <3.0,>=2.9.2
|
|
225
222
|
Requires-Dist: requests ==2.32.3
|
|
223
|
+
Requires-Dist: iqm-pulse <13,>=12.7.1
|
|
224
|
+
Requires-Dist: iqm-station-control-client <13,>=12.0.1
|
|
226
225
|
Provides-Extra: cirq
|
|
227
|
-
Requires-Dist: iqm-station-control-client <12,>=11 ; extra == 'cirq'
|
|
228
|
-
Requires-Dist: iqm-exa-common <28,>=27 ; extra == 'cirq'
|
|
229
|
-
Requires-Dist: iqm-pulse <13,>=12 ; extra == 'cirq'
|
|
230
226
|
Requires-Dist: cirq-core[contrib] ~=1.2 ; extra == 'cirq'
|
|
231
227
|
Requires-Dist: ply ==3.11 ; extra == 'cirq'
|
|
232
228
|
Requires-Dist: llvmlite >=0.44.0 ; extra == 'cirq'
|
|
233
229
|
Requires-Dist: numba >=0.61.0 ; extra == 'cirq'
|
|
234
230
|
Provides-Extra: cli
|
|
235
|
-
Requires-Dist: iqm-station-control-client <12,>=11 ; extra == 'cli'
|
|
236
|
-
Requires-Dist: iqm-exa-common <28,>=27 ; extra == 'cli'
|
|
237
|
-
Requires-Dist: iqm-pulse <13,>=12 ; extra == 'cli'
|
|
238
231
|
Requires-Dist: click <9,>=8.1.6 ; extra == 'cli'
|
|
239
232
|
Requires-Dist: jsonschema >=4.6.0 ; extra == 'cli'
|
|
240
233
|
Requires-Dist: psutil >=5.9.2 ; extra == 'cli'
|
|
241
234
|
Requires-Dist: types-psutil ; extra == 'cli'
|
|
242
235
|
Requires-Dist: python-daemon >=2.3.0 ; extra == 'cli'
|
|
243
236
|
Provides-Extra: qiskit
|
|
244
|
-
Requires-Dist:
|
|
245
|
-
Requires-Dist: iqm-exa-common <28,>=27 ; extra == 'qiskit'
|
|
246
|
-
Requires-Dist: iqm-pulse <13,>=12 ; extra == 'qiskit'
|
|
247
|
-
Requires-Dist: qiskit <=1.4.2,>=1.0 ; extra == 'qiskit'
|
|
237
|
+
Requires-Dist: qiskit <2.2,>=1.0 ; extra == 'qiskit'
|
|
248
238
|
Requires-Dist: qiskit-aer <0.18,>=0.13.1 ; extra == 'qiskit'
|
|
249
239
|
|
|
250
240
|
IQM Client
|
|
@@ -260,16 +250,15 @@ adapters for `IQM's <https://www.meetiqm.com>`_ quantum computers, which allow y
|
|
|
260
250
|
(currently only the Qiskit adapter contains IQM noise models)
|
|
261
251
|
* Run quantum circuits on an IQM quantum computer
|
|
262
252
|
|
|
263
|
-
Also includes a
|
|
264
|
-
authentication when using IQM quantum computers.
|
|
253
|
+
Also includes a CLI utility for managing user authentication when using IQM quantum computers.
|
|
265
254
|
|
|
266
255
|
Installation
|
|
267
256
|
============
|
|
268
257
|
|
|
269
|
-
For executing code on an IQM quantum computer, you can use for example
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
258
|
+
For executing code on an IQM quantum computer, you can use for example the
|
|
259
|
+
Qiskit on IQM or Cirq on IQM user guides found in the documentation.
|
|
260
|
+
Qiskit on IQM and Cirq on IQM are optional features that can be installed alongside the base IQM Client library.
|
|
261
|
+
An example showing how to install from the public Python Package Index (PyPI):
|
|
273
262
|
|
|
274
263
|
.. code-block:: bash
|
|
275
264
|
|
|
@@ -281,8 +270,7 @@ features of IQM Client from the Python Package Index (PyPI), e.g.:
|
|
|
281
270
|
Python environment, you should first uninstall them with ``$ pip uninstall qiskit-iqm cirq-iqm``.
|
|
282
271
|
In this case, you should also include the ``--force-reinstall`` option in the ``iqm-client`` installation command.
|
|
283
272
|
|
|
284
|
-
The
|
|
285
|
-
be installed as an optional feature:
|
|
273
|
+
The CLI utility for managing user authentication can also be installed as an optional feature:
|
|
286
274
|
|
|
287
275
|
.. code-block:: bash
|
|
288
276
|
|
|
@@ -304,10 +292,15 @@ Documentation
|
|
|
304
292
|
|
|
305
293
|
Documentation for the latest version is `available online <https://docs.meetiqm.com/iqm-client/>`_.
|
|
306
294
|
You can build documentation for any older version locally by downloading the corresponding package from PyPI,
|
|
307
|
-
and running the docs builder. For versions 20.12
|
|
308
|
-
``iqm-client`` root directory, and for earlier versions by running ``tox run -e docs``.
|
|
295
|
+
and running the docs builder. For versions greater than equal to 20.12 but less than 33.0.0 this is done by
|
|
296
|
+
running ``./docbuild`` in the ``iqm-client`` root directory, and for earlier versions by running ``tox run -e docs``.
|
|
309
297
|
|
|
310
298
|
``./docbuild`` or ``tox run -e docs`` will build the documentation at ``./build/sphinx/html``.
|
|
299
|
+
|
|
300
|
+
Versions greater than or equal to 33.0.0 use the command:
|
|
301
|
+
``sphinx-build -q -d build/.doctrees/iqm-client iqm-client/docs build/docs/iqm-client``
|
|
302
|
+
(``build/docs/`` directory has to be created first).
|
|
303
|
+
|
|
311
304
|
These commands require installing the ``sphinx`` and ``sphinx-book-theme`` Python packages and
|
|
312
305
|
`graphviz <https://graphviz.org/>`_.
|
|
313
306
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
iqm/cirq_iqm/__init__.py,sha256=1zTyxtF39OD11D00ZujgqciJ_9GBDYe-PVBLqZp4NaA,831
|
|
2
|
+
iqm/cirq_iqm/extended_qasm_parser.py,sha256=csDzfHLhy_9maGbappLbnFo2NHQjQeyd-1F8P380Mbk,1917
|
|
3
|
+
iqm/cirq_iqm/iqm_gates.py,sha256=xnZex5ZfNOk_WSsFjVCRybc14FlGNbmwOs3mIfOE_F8,2488
|
|
4
|
+
iqm/cirq_iqm/iqm_sampler.py,sha256=b5CpcD7tgUAWKYdAKcFrshfv1I1GN5eC7mfMyXoGF_8,12606
|
|
5
|
+
iqm/cirq_iqm/optimizers.py,sha256=Jcb6W7-M9wYa5mZztq33jQpWuIzD5ulE16ZperIc-wU,8566
|
|
6
|
+
iqm/cirq_iqm/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
iqm/cirq_iqm/serialize.py,sha256=2w_8sk-X18qWb2FFocBP_MXJALoNmosZIs5Ilp7ueR8,8516
|
|
8
|
+
iqm/cirq_iqm/transpiler.py,sha256=zjJzZb5HyjTfqhL0rMI3dt_3GPGLdVYah9UlGoC8hcc,2173
|
|
9
|
+
iqm/cirq_iqm/devices/__init__.py,sha256=WxbvNAqmeoV4mNy9M9IpjwmyNDpAP7ec1XFdjN7w_YA,840
|
|
10
|
+
iqm/cirq_iqm/devices/adonis.py,sha256=ZWaA4_OfO6fBcrLYHONTjtW8aSlVq2nwy9IiW529gQw,1390
|
|
11
|
+
iqm/cirq_iqm/devices/aphrodite.py,sha256=0K8zRo6gVVadQo7LJfs6laUVLzS3fNkrXMrPlwieyJ8,4018
|
|
12
|
+
iqm/cirq_iqm/devices/apollo.py,sha256=_k7L45zyHZnpdZWMMXO6zci3XY3UEnBj2RmvxM6D7Mg,2238
|
|
13
|
+
iqm/cirq_iqm/devices/iqm_device.py,sha256=CyAP0kU9vfovNWvF4CVX8Wp6_QLxLN8fy9I2YZkHHwM,15425
|
|
14
|
+
iqm/cirq_iqm/devices/iqm_device_metadata.py,sha256=3ONFzPa1G_nE4nSAldsd_Ly8i1__n2Sll43qN9yNQUw,7161
|
|
15
|
+
iqm/cirq_iqm/examples/demo_adonis.py,sha256=rPd8VYqccYijG6t92hfvKsk8RAaMGHOmm4JelYjt3rw,1728
|
|
16
|
+
iqm/cirq_iqm/examples/demo_apollo.py,sha256=vnDwcEXqEyew0cf4SDDQ2budP-giizSNJU3dgOw15H0,1751
|
|
17
|
+
iqm/cirq_iqm/examples/demo_common.py,sha256=k0MAeLDNxZJGv8FNrgMEwaQZsuXeDY5NBM2rLSIQxvU,8745
|
|
18
|
+
iqm/cirq_iqm/examples/demo_iqm_execution.py,sha256=_Wf6rsL_q93M5Ww6eurbXL8VPngGkxtpZBAbNpnn2ek,2440
|
|
19
|
+
iqm/cirq_iqm/examples/usage.ipynb,sha256=Kyfyu_MwqzTavHVNjgrWJo1tZPeJwTw7ExcU0MFYNRk,34208
|
|
20
|
+
iqm/iqm_client/__init__.py,sha256=43TjxuxX_iFl5h2GbQqyIkxmuraWkX4OyvF444Hlyvk,1306
|
|
21
|
+
iqm/iqm_client/errors.py,sha256=weKTljBvDzJxK58NHCXaZXoITVMBvsOkvYP0abs5Dfc,1226
|
|
22
|
+
iqm/iqm_client/iqm_client.py,sha256=OGKp6QDwtIY2RjJ8_uG44UJlMpm7VH_HTgqZFPPN2YA,22632
|
|
23
|
+
iqm/iqm_client/models.py,sha256=UHlkdG0bjaksvyARYB4RZYx1EJ4nLMyHiPS_7-Bwr14,15888
|
|
24
|
+
iqm/iqm_client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
+
iqm/iqm_client/transpile.py,sha256=ZicsOJiT5zEaaYMWuFddnJT2e4e9Oyu7B5pO8Z_ah7M,37334
|
|
26
|
+
iqm/iqm_client/util.py,sha256=obzh1g6PNEXOj7k3gUkiylNUhyqutbWlxlEpfyyU_fk,1505
|
|
27
|
+
iqm/iqm_client/validation.py,sha256=SyshUOxmnlwb0haWEAVkgynGOYUMV7Os281sUe29tpE,12621
|
|
28
|
+
iqm/iqm_server_client/__init__.py,sha256=hqfe65DmdYu5qDbYsg3Uq94DRe2zkbMJP_XFYsMbGSQ,645
|
|
29
|
+
iqm/iqm_server_client/errors.py,sha256=I48dRY_Sies2vQzyjzQxhHm_J5RopyL5hqAmlnBy67w,192
|
|
30
|
+
iqm/iqm_server_client/iqm_server_client.py,sha256=en2C4hZ4VzHtg07EtxpuC-cFEw2kzTi1_GzfYKk-NWY,31365
|
|
31
|
+
iqm/iqm_server_client/models.py,sha256=YpyppxEu2obSIaUvuc3_mRKcGscs8YLrcWrTk7EHKzM,5597
|
|
32
|
+
iqm/iqm_server_client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
|
+
iqm/qiskit_iqm/__init__.py,sha256=VPmcMV5nBOEIhVsOceIUkdZtWJ3tGlaKrDFP-FRuOCs,1667
|
|
34
|
+
iqm/qiskit_iqm/iqm_backend.py,sha256=3ydSroylh_Us-5RJfOQgOkL7eqF4fWe5ejXueDTcAj8,5584
|
|
35
|
+
iqm/qiskit_iqm/iqm_circuit.py,sha256=jaPo3zc5FC0vAIumh5d56fr44fDaJXXwcquBzQEy1Yg,1400
|
|
36
|
+
iqm/qiskit_iqm/iqm_circuit_validation.py,sha256=SzfCJclUh1kK6Kcz5puKlCtE0hZfWxYYKF35EagU204,1750
|
|
37
|
+
iqm/qiskit_iqm/iqm_job.py,sha256=SdyZeQi5CoGxBeJyfIvOM7DA0ONJ5tbTHpk4hajpUFk,12395
|
|
38
|
+
iqm/qiskit_iqm/iqm_move_layout.py,sha256=vFdba0DStu8Pebi3qxZBYOLCYC2Atl28YlSzbSGER4Y,10557
|
|
39
|
+
iqm/qiskit_iqm/iqm_naive_move_pass.py,sha256=GcS2DyYWwG-qMyczXqTXxMxNqRB4IVeWNP3CzguKpBw,15639
|
|
40
|
+
iqm/qiskit_iqm/iqm_provider.py,sha256=pK1CcAOQKG5jXYfjxshuVSmMLWQG1-k0cyghRmGpWN8,19273
|
|
41
|
+
iqm/qiskit_iqm/iqm_target.py,sha256=nqurRgM_p2nOw8ty8ncw38vSErEdXpHSlbp7Rpavnc4,16232
|
|
42
|
+
iqm/qiskit_iqm/iqm_transpilation.py,sha256=RccAHY7lNXvg7TWKG7fBNBCgFu5Xd9B_J9V9uKLlmuY,18336
|
|
43
|
+
iqm/qiskit_iqm/move_gate.py,sha256=UbrQSfrpVV3QKGJ93TelxEfZkl1wY4uWL8IH_QDpGUw,2840
|
|
44
|
+
iqm/qiskit_iqm/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
45
|
+
iqm/qiskit_iqm/qiskit_to_iqm.py,sha256=imMmuOi_tuujIrGzBFnGOtxpGHcIkncXutr5QFekJl4,20677
|
|
46
|
+
iqm/qiskit_iqm/transpiler_plugins.py,sha256=BrCkUFQUdi0m8K8V0ERYgtPAkvoX4VlYIJwnFKyCN9Q,8831
|
|
47
|
+
iqm/qiskit_iqm/examples/__init__.py,sha256=M4ElQHCo-WxtVXK39bF3QiFT3IGXPtZ1khqexHiTBEc,20
|
|
48
|
+
iqm/qiskit_iqm/examples/bell_measure.py,sha256=pwSGeggb8HuUWh4SYUkbLDeZI00gxRmVw0svA_hbn-Y,3056
|
|
49
|
+
iqm/qiskit_iqm/examples/transpile_example.py,sha256=FNpHy6nxJgaXhgbl7_SavHRNaNMyk4lSkd_0a2GSKh4,2410
|
|
50
|
+
iqm/qiskit_iqm/fake_backends/__init__.py,sha256=fkw2UHT-3aJbAKvR1WYUN7_4N5Gdwpx9bm6vlWj1tm0,874
|
|
51
|
+
iqm/qiskit_iqm/fake_backends/fake_adonis.py,sha256=DWftCPu6ElE4P47Z-y3bojg0unDJ6F5ur2aDqfoY85s,2248
|
|
52
|
+
iqm/qiskit_iqm/fake_backends/fake_aphrodite.py,sha256=oB5wFtZ2VYy0TxoL-eBgYnVAwamGPYlzIQK89npwQUg,15533
|
|
53
|
+
iqm/qiskit_iqm/fake_backends/fake_apollo.py,sha256=C05u3knnCsA8DCuWU31EwUvJ6xilef36J06GoE2vAe4,6623
|
|
54
|
+
iqm/qiskit_iqm/fake_backends/fake_deneb.py,sha256=HstlV-PSbyCttY1uEyWr-YgsMNkv9Ntf6zffav837_4,3216
|
|
55
|
+
iqm/qiskit_iqm/fake_backends/fake_garnet.py,sha256=GI0xafTCj1Um09qVuccO6GPOGBm6ygul_O40Wu220Ys,5555
|
|
56
|
+
iqm/qiskit_iqm/fake_backends/iqm_fake_backend.py,sha256=IfVKxlPlAVCHTgli2scRP1gkVCXIiSuL6-QjjsZp83Q,16808
|
|
57
|
+
iqm_client-33.0.1.dist-info/AUTHORS.rst,sha256=qsxeK5A3-B_xK3hNbhFHEIkoHNpo7sdzYyRTs7Bdtm8,795
|
|
58
|
+
iqm_client-33.0.1.dist-info/LICENSE.txt,sha256=2DXrmQtVVUV9Fc9RBFJidMiTEaQlG2oAtlC9PMrEwTk,11333
|
|
59
|
+
iqm_client-33.0.1.dist-info/METADATA,sha256=yJ-Xub8FOJ-nI248Vlv15cG_8H38DvRxSp6JxFx3ucY,17435
|
|
60
|
+
iqm_client-33.0.1.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
|
|
61
|
+
iqm_client-33.0.1.dist-info/entry_points.txt,sha256=Kk2qfRwk8vbIJ7qCAvmaUogfRRn6t92_hBFhe6kqAE4,1317
|
|
62
|
+
iqm_client-33.0.1.dist-info/top_level.txt,sha256=NB4XRfyDS6_wG9gMsyX-9LTU7kWnTQxNvkbzIxGv3-c,4
|
|
63
|
+
iqm_client-33.0.1.dist-info/RECORD,,
|
iqm/iqm_client/api.py
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# Copyright 2024 IQM client developers
|
|
2
|
-
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
# you may not use this file except in compliance with the License.
|
|
5
|
-
# You may obtain a copy of the License at
|
|
6
|
-
#
|
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
#
|
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
# See the License for the specific language governing permissions and
|
|
13
|
-
# limitations under the License.
|
|
14
|
-
"""This module contains definitions of IQM Server API endpoints."""
|
|
15
|
-
|
|
16
|
-
from enum import Enum, auto
|
|
17
|
-
from posixpath import join
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class APIEndpoint(Enum):
|
|
21
|
-
"""Supported API endpoints."""
|
|
22
|
-
|
|
23
|
-
GET_JOB_RESULT = auto()
|
|
24
|
-
GET_JOB_REQUEST_PARAMETERS = auto()
|
|
25
|
-
GET_JOB_CALIBRATION_SET_ID = auto()
|
|
26
|
-
GET_JOB_CIRCUITS_BATCH = auto()
|
|
27
|
-
GET_JOB_ERROR_LOG = auto()
|
|
28
|
-
SUBMIT_JOB = auto()
|
|
29
|
-
GET_JOB_COUNTS = auto()
|
|
30
|
-
GET_JOB_STATUS = auto()
|
|
31
|
-
GET_JOB_TIMELINE = auto()
|
|
32
|
-
ABORT_JOB = auto()
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class APIConfig:
|
|
36
|
-
"""Provides supported API endpoints for a given API variant."""
|
|
37
|
-
|
|
38
|
-
def __init__(self, station_control_url: str):
|
|
39
|
-
"""Args:
|
|
40
|
-
station_control_url: URL of the IQM server,
|
|
41
|
-
e.g. https://test.qc.iqm.fi/station or https://cocos.resonance.meetiqm.com/garnet
|
|
42
|
-
|
|
43
|
-
"""
|
|
44
|
-
self.station_control_url = station_control_url
|
|
45
|
-
self.urls = self._get_api_urls()
|
|
46
|
-
|
|
47
|
-
@staticmethod
|
|
48
|
-
def _get_api_urls() -> dict[APIEndpoint, str]:
|
|
49
|
-
"""Returns:
|
|
50
|
-
Relative URLs for each supported API endpoints.
|
|
51
|
-
|
|
52
|
-
"""
|
|
53
|
-
return {
|
|
54
|
-
# TODO SW-1434: Use StationControlClient methods for communication instead of REST endpoints
|
|
55
|
-
APIEndpoint.GET_JOB_RESULT: "jobs/%s/measurements",
|
|
56
|
-
APIEndpoint.GET_JOB_REQUEST_PARAMETERS: "jobs/%s/request_parameters",
|
|
57
|
-
APIEndpoint.GET_JOB_CALIBRATION_SET_ID: "jobs/%s/calibration_set_id",
|
|
58
|
-
APIEndpoint.GET_JOB_CIRCUITS_BATCH: "jobs/%s/circuits_batch",
|
|
59
|
-
APIEndpoint.GET_JOB_ERROR_LOG: "jobs/%s/error_log",
|
|
60
|
-
APIEndpoint.SUBMIT_JOB: "circuits",
|
|
61
|
-
APIEndpoint.GET_JOB_COUNTS: "circuits/%s/counts",
|
|
62
|
-
APIEndpoint.GET_JOB_STATUS: "jobs/%s/status",
|
|
63
|
-
APIEndpoint.GET_JOB_TIMELINE: "jobs/%s/timeline",
|
|
64
|
-
APIEndpoint.ABORT_JOB: "jobs/%s/abort",
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
def is_supported(self, endpoint: APIEndpoint) -> bool:
|
|
68
|
-
"""Args:
|
|
69
|
-
endpoint: API endpoint.
|
|
70
|
-
|
|
71
|
-
Returns:
|
|
72
|
-
True if the endpoint is supported, False otherwise.
|
|
73
|
-
|
|
74
|
-
"""
|
|
75
|
-
return endpoint in self.urls
|
|
76
|
-
|
|
77
|
-
def url(self, endpoint: APIEndpoint, *args) -> str:
|
|
78
|
-
"""Args:
|
|
79
|
-
endpoint: API endpoint.
|
|
80
|
-
args: Arguments to be passed to the URL.
|
|
81
|
-
|
|
82
|
-
Returns:
|
|
83
|
-
URL for the given endpoint.
|
|
84
|
-
|
|
85
|
-
Raises:
|
|
86
|
-
ValueError: If the endpoint is not supported.
|
|
87
|
-
|
|
88
|
-
"""
|
|
89
|
-
url = self.urls.get(endpoint, "")
|
|
90
|
-
return join(self.station_control_url, url % args)
|