iqm-client 29.14.0__py3-none-any.whl → 30.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iqm/iqm_client/cli/auth.py +1 -0
- iqm/iqm_client/cli/token_manager.py +5 -5
- iqm/iqm_client/iqm_client.py +22 -4
- iqm/iqm_client/util.py +1 -3
- iqm/qiskit_iqm/examples/resonance_example.py +2 -2
- iqm/qiskit_iqm/fake_backends/fake_adonis.py +0 -1
- iqm/qiskit_iqm/iqm_backend.py +44 -231
- iqm/qiskit_iqm/iqm_move_layout.py +7 -15
- iqm/qiskit_iqm/iqm_naive_move_pass.py +24 -21
- iqm/qiskit_iqm/iqm_provider.py +64 -34
- iqm/qiskit_iqm/iqm_target.py +372 -0
- {iqm_client-29.14.0.dist-info → iqm_client-30.0.0.dist-info}/METADATA +1 -1
- {iqm_client-29.14.0.dist-info → iqm_client-30.0.0.dist-info}/RECORD +18 -17
- {iqm_client-29.14.0.dist-info → iqm_client-30.0.0.dist-info}/AUTHORS.rst +0 -0
- {iqm_client-29.14.0.dist-info → iqm_client-30.0.0.dist-info}/LICENSE.txt +0 -0
- {iqm_client-29.14.0.dist-info → iqm_client-30.0.0.dist-info}/WHEEL +0 -0
- {iqm_client-29.14.0.dist-info → iqm_client-30.0.0.dist-info}/entry_points.txt +0 -0
- {iqm_client-29.14.0.dist-info → iqm_client-30.0.0.dist-info}/top_level.txt +0 -0
iqm/iqm_client/cli/auth.py
CHANGED
|
@@ -42,6 +42,7 @@ class GrantType(str, Enum):
|
|
|
42
42
|
|
|
43
43
|
class AuthRequest(BaseModel):
|
|
44
44
|
"""Request sent to authentication server for access token and refresh token, or for terminating the session.
|
|
45
|
+
|
|
45
46
|
* Token request with grant type ``'password'`` starts a new session in the authentication server.
|
|
46
47
|
It uses fields ``client_id``, ``grant_type``, ``username`` and ``password``.
|
|
47
48
|
* Token request with grant type ``'refresh_token'`` is used for maintaining an existing session.
|
|
@@ -51,6 +51,7 @@ def start_token_manager(cycle: int, config: ConfigFile, single_run: bool = False
|
|
|
51
51
|
"""Refresh tokens periodically.
|
|
52
52
|
|
|
53
53
|
For each refresh cycle new tokens are requested from auth server.
|
|
54
|
+
|
|
54
55
|
- If refresh is successful next refresh is attempted in the next cycle.
|
|
55
56
|
- If auth server does not respond refresh is attempted repeatedly until it succeeds or
|
|
56
57
|
the existing refresh token expires.
|
|
@@ -104,11 +105,10 @@ def refresh_tokens(config: ConfigFile, current_tokens: dict, cycle: int) -> tupl
|
|
|
104
105
|
cycle: refresh cycle length in seconds
|
|
105
106
|
|
|
106
107
|
Returns:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
sleep_time: time to sleep before next refresh attempt
|
|
108
|
+
* dict containing new tokens or current tokens if auth server could not be connected or
|
|
109
|
+
None if auth server refused to provide new tokens
|
|
110
|
+
* bool, True if tokens were refreshed successfully, False otherwise
|
|
111
|
+
* time to sleep before next refresh attempt
|
|
112
112
|
|
|
113
113
|
"""
|
|
114
114
|
access_token = current_tokens.get("access_token", "")
|
iqm/iqm_client/iqm_client.py
CHANGED
|
@@ -20,6 +20,7 @@ from functools import lru_cache
|
|
|
20
20
|
from http import HTTPStatus
|
|
21
21
|
from importlib.metadata import version
|
|
22
22
|
import json
|
|
23
|
+
import logging
|
|
23
24
|
import os
|
|
24
25
|
import platform
|
|
25
26
|
import time
|
|
@@ -77,6 +78,9 @@ DEFAULT_TIMEOUT_SECONDS = 900
|
|
|
77
78
|
SECONDS_BETWEEN_CALLS = float(os.environ.get("IQM_CLIENT_SECONDS_BETWEEN_CALLS", 1.0))
|
|
78
79
|
|
|
79
80
|
|
|
81
|
+
logger = logging.getLogger(__name__)
|
|
82
|
+
|
|
83
|
+
|
|
80
84
|
class IQMClient:
|
|
81
85
|
"""Provides access to IQM quantum computers.
|
|
82
86
|
|
|
@@ -907,11 +911,18 @@ class IQMClient:
|
|
|
907
911
|
# model = model_class.model_validate_json(response.text)
|
|
908
912
|
except json.decoder.JSONDecodeError as e:
|
|
909
913
|
raise EndpointRequestError(f"Invalid response: {response.text}, {e!r}") from e
|
|
914
|
+
|
|
910
915
|
return model
|
|
911
916
|
|
|
912
|
-
def
|
|
917
|
+
def get_calibration_quality_metrics(self, calibration_set_id: UUID | None = None) -> ObservationFinder:
|
|
913
918
|
"""Retrieve the given calibration set and related quality metrics from the server.
|
|
914
919
|
|
|
920
|
+
.. warning::
|
|
921
|
+
|
|
922
|
+
This method is an experimental interface to the quality metrics and calibration data.
|
|
923
|
+
The API may change considerably in the next versions *with no backwards compatibility*,
|
|
924
|
+
including the API of the ObservationFinder class.
|
|
925
|
+
|
|
915
926
|
Args:
|
|
916
927
|
calibration_set_id: ID of the calibration set to retrieve.
|
|
917
928
|
If ``None``, the current default calibration set is retrieved.
|
|
@@ -925,8 +936,16 @@ class IQMClient:
|
|
|
925
936
|
HTTPException: HTTP exceptions
|
|
926
937
|
|
|
927
938
|
"""
|
|
939
|
+
logger.warning(
|
|
940
|
+
"IQMClient.get_calibration_quality_metrics is an experimental method, and the API will likely change "
|
|
941
|
+
"in the future with no backwards compatibility."
|
|
942
|
+
)
|
|
943
|
+
return self._get_calibration_quality_metrics(calibration_set_id)
|
|
944
|
+
|
|
945
|
+
def _get_calibration_quality_metrics(self, calibration_set_id: UUID | None = None) -> ObservationFinder:
|
|
946
|
+
"""See :meth:`get_calibration_quality_metrics`."""
|
|
928
947
|
if isinstance(self._station_control, IqmServerClient):
|
|
929
|
-
raise ValueError("The
|
|
948
|
+
raise ValueError("The _get_calibration_quality_metrics method is not supported by IqmServerClient.")
|
|
930
949
|
|
|
931
950
|
if not calibration_set_id:
|
|
932
951
|
# find out the default calset id
|
|
@@ -936,5 +955,4 @@ class IQMClient:
|
|
|
936
955
|
calset_obs = self._station_control.get_observation_set_observations(calibration_set_id)
|
|
937
956
|
quality_metrics = self._station_control.get_calibration_set_quality_metrics(calibration_set_id)
|
|
938
957
|
qm_obs = quality_metrics.observations
|
|
939
|
-
|
|
940
|
-
return ObservationFinder(list(calset_obs) + qm_obs)
|
|
958
|
+
return ObservationFinder(calset_obs + qm_obs)
|
iqm/iqm_client/util.py
CHANGED
|
@@ -14,12 +14,10 @@
|
|
|
14
14
|
"""Helpful utilities that can be used together with IQMClient."""
|
|
15
15
|
|
|
16
16
|
from json import JSONEncoder, dumps, loads
|
|
17
|
-
from typing import Any
|
|
17
|
+
from typing import Any
|
|
18
18
|
|
|
19
19
|
import numpy as np
|
|
20
20
|
|
|
21
|
-
T = TypeVar("T")
|
|
22
|
-
|
|
23
21
|
|
|
24
22
|
class IQMJSONEncoder(JSONEncoder):
|
|
25
23
|
"""JSONEncoder that that adds support for some non-JSON datatypes"""
|
|
@@ -36,8 +36,8 @@ def resonance_example(server_url: str, api_token: str | None) -> dict[str, int]:
|
|
|
36
36
|
"""
|
|
37
37
|
SHOTS = 1000
|
|
38
38
|
|
|
39
|
-
# Initialize a backend
|
|
40
|
-
backend = IQMProvider(server_url, token=api_token).get_backend()
|
|
39
|
+
# Initialize a backend without metrics as IQMClient._get_calibration_quality_metrics is not supported by resonance
|
|
40
|
+
backend = IQMProvider(server_url, token=api_token).get_backend(use_metrics=False)
|
|
41
41
|
|
|
42
42
|
# Just to make sure that "get_static_quantum_architecture" method works
|
|
43
43
|
static_quantum_architecture = backend.client.get_static_quantum_architecture()
|
iqm/qiskit_iqm/iqm_backend.py
CHANGED
|
@@ -16,69 +16,89 @@
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
18
|
from abc import ABC
|
|
19
|
-
import
|
|
19
|
+
import logging
|
|
20
20
|
from typing import Final
|
|
21
21
|
|
|
22
22
|
from iqm.iqm_client import (
|
|
23
23
|
DynamicQuantumArchitecture,
|
|
24
|
-
|
|
25
|
-
GateInfo,
|
|
24
|
+
ObservationFinder,
|
|
26
25
|
)
|
|
27
|
-
from iqm.qiskit_iqm.
|
|
28
|
-
from qiskit.circuit import Delay, Parameter, Reset
|
|
29
|
-
from qiskit.circuit.library import CZGate, IGate, Measure, RGate
|
|
26
|
+
from iqm.qiskit_iqm.iqm_target import IQMTarget
|
|
30
27
|
from qiskit.providers import BackendV2
|
|
31
28
|
from qiskit.transpiler import Target
|
|
32
29
|
|
|
33
30
|
IQM_TO_QISKIT_GATE_NAME: Final[dict[str, str]] = {"prx": "r", "cz": "cz"}
|
|
34
|
-
|
|
35
|
-
Locus = tuple[str, ...]
|
|
36
|
-
LocusIdx = tuple[int, ...]
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
37
32
|
|
|
38
33
|
|
|
39
34
|
class IQMBackendBase(BackendV2, ABC):
|
|
40
35
|
"""Abstract base class for various IQM-specific backends.
|
|
41
36
|
|
|
42
37
|
Args:
|
|
43
|
-
architecture:
|
|
38
|
+
architecture: Dynamic quantum architecture associated with the backend instance.
|
|
39
|
+
metrics: Optional calibration data and related quality metrics for the transpilation target.
|
|
40
|
+
name: Optional name for the backend instance.
|
|
44
41
|
|
|
45
42
|
"""
|
|
46
43
|
|
|
47
44
|
def __init__(
|
|
48
45
|
self,
|
|
49
46
|
architecture: DynamicQuantumArchitecture,
|
|
47
|
+
*,
|
|
48
|
+
metrics: ObservationFinder | None = None,
|
|
49
|
+
name: str = "IQMBackend",
|
|
50
50
|
**kwargs,
|
|
51
51
|
):
|
|
52
|
-
super().__init__(**kwargs)
|
|
52
|
+
super().__init__(name=name, **kwargs)
|
|
53
53
|
self.architecture = architecture
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
self.metrics = metrics
|
|
56
55
|
# Qiskit uses integer indices to refer to qubits, so we need to map component names to indices.
|
|
57
56
|
# Because of the way the Target and the transpiler interact, the resonators need to have higher indices than
|
|
58
57
|
# qubits, or else transpiling with optimization_level=0 will fail because of lacking resonator indices.
|
|
59
58
|
qb_to_idx = {qb: idx for idx, qb in enumerate(architecture.qubits + architecture.computational_resonators)}
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
self.
|
|
63
|
-
|
|
60
|
+
# NOTE: both targets include fictional CZs
|
|
61
|
+
self._target = IQMTarget(
|
|
62
|
+
architecture=architecture,
|
|
63
|
+
component_to_idx=qb_to_idx,
|
|
64
|
+
include_resonators=False,
|
|
65
|
+
include_fictional_czs=True,
|
|
66
|
+
metrics=metrics,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
self._target_with_resonators = (
|
|
70
|
+
IQMTarget(
|
|
71
|
+
architecture=architecture,
|
|
72
|
+
component_to_idx=qb_to_idx,
|
|
73
|
+
include_resonators=True,
|
|
74
|
+
include_fictional_czs=True,
|
|
75
|
+
metrics=metrics,
|
|
76
|
+
)
|
|
64
77
|
if "move" in architecture.gates
|
|
65
78
|
else None
|
|
66
79
|
)
|
|
80
|
+
|
|
67
81
|
self._qb_to_idx = qb_to_idx
|
|
68
82
|
self._idx_to_qb = {v: k for k, v in qb_to_idx.items()}
|
|
69
|
-
self.name = "IQMBackend"
|
|
70
83
|
self._coupling_map = self.target.build_coupling_map()
|
|
71
84
|
|
|
72
85
|
@property
|
|
73
86
|
def target(self) -> Target:
|
|
87
|
+
"""Return the target without computational resonators."""
|
|
74
88
|
return self._target
|
|
75
89
|
|
|
76
90
|
@property
|
|
77
91
|
def target_with_resonators(self) -> Target:
|
|
78
|
-
"""Return the target with MOVE gates and resonators included.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
92
|
+
"""Return the target with MOVE gates and resonators included.
|
|
93
|
+
|
|
94
|
+
Raises:
|
|
95
|
+
ValueError: The backend does not have resonators.
|
|
96
|
+
|
|
97
|
+
"""
|
|
98
|
+
target = self._target_with_resonators
|
|
99
|
+
if target is None:
|
|
100
|
+
raise ValueError("The backend does not have computational resonators.")
|
|
101
|
+
return target
|
|
82
102
|
|
|
83
103
|
@property
|
|
84
104
|
def physical_qubits(self) -> list[str]:
|
|
@@ -90,12 +110,13 @@ class IQMBackendBase(BackendV2, ABC):
|
|
|
90
110
|
return bool(self.architecture.computational_resonators)
|
|
91
111
|
|
|
92
112
|
def get_real_target(self) -> Target:
|
|
93
|
-
"""Return the real physical target of the backend without
|
|
113
|
+
"""Return the real physical target of the backend without fictional CZ gates."""
|
|
94
114
|
return IQMTarget(
|
|
95
115
|
architecture=self.architecture,
|
|
96
116
|
component_to_idx=self._qb_to_idx,
|
|
97
117
|
include_resonators=True,
|
|
98
|
-
|
|
118
|
+
include_fictional_czs=False,
|
|
119
|
+
metrics=self.metrics,
|
|
99
120
|
)
|
|
100
121
|
|
|
101
122
|
def qubit_name_to_index(self, name: str) -> int:
|
|
@@ -135,211 +156,3 @@ class IQMBackendBase(BackendV2, ABC):
|
|
|
135
156
|
def get_scheduling_stage_plugin(self) -> str:
|
|
136
157
|
"""Return the plugin that should be used for scheduling the circuits on this backend."""
|
|
137
158
|
return "iqm_default_scheduling"
|
|
138
|
-
|
|
139
|
-
def restrict_to_qubits(
|
|
140
|
-
self,
|
|
141
|
-
qubits: list[int] | list[str],
|
|
142
|
-
include_resonators: bool = False,
|
|
143
|
-
include_fake_czs: bool = True,
|
|
144
|
-
) -> IQMTarget:
|
|
145
|
-
"""Generated a restricted transpilation target from this backend that only contains the given qubits.
|
|
146
|
-
|
|
147
|
-
Args:
|
|
148
|
-
qubits: Qubits to restrict the target to. Can be either a list of qubit indices or qubit names.
|
|
149
|
-
include_resonators: Whether to restrict `self.target` or `self.target_with_resonators`.
|
|
150
|
-
include_fake_czs: Whether to include virtual CZs that are unsupported, but could be routed via MOVE.
|
|
151
|
-
|
|
152
|
-
Returns:
|
|
153
|
-
restricted target
|
|
154
|
-
|
|
155
|
-
"""
|
|
156
|
-
qubits_str = [self._idx_to_qb[q] if isinstance(q, int) else str(q) for q in qubits]
|
|
157
|
-
return _restrict_dqa_to_qubits(self.architecture, qubits_str, include_resonators, include_fake_czs)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def _restrict_dqa_to_qubits(
|
|
161
|
-
architecture: DynamicQuantumArchitecture,
|
|
162
|
-
qubits: list[str],
|
|
163
|
-
include_resonators: bool,
|
|
164
|
-
include_fake_czs: bool = True,
|
|
165
|
-
) -> IQMTarget:
|
|
166
|
-
"""Generated a restricted transpilation target from this backend that only contains the given qubits.
|
|
167
|
-
|
|
168
|
-
Args:
|
|
169
|
-
architecture: The dynamic quantum architecture to restrict.
|
|
170
|
-
qubits: Qubits to restrict the target to. Can be either a list of qubit indices or qubit names.
|
|
171
|
-
include_resonators: Whether to include MOVE gates in the target.
|
|
172
|
-
include_fake_czs: Whether to include virtual CZs that are not natively supported, but could be routed via MOVE.
|
|
173
|
-
|
|
174
|
-
Returns:
|
|
175
|
-
restricted target
|
|
176
|
-
|
|
177
|
-
"""
|
|
178
|
-
new_gates = {}
|
|
179
|
-
for gate_name, gate_info in architecture.gates.items():
|
|
180
|
-
new_implementations = {}
|
|
181
|
-
for (
|
|
182
|
-
implementation_name,
|
|
183
|
-
implementation_info,
|
|
184
|
-
) in gate_info.implementations.items():
|
|
185
|
-
new_loci = tuple(locus for locus in implementation_info.loci if all(q in qubits for q in locus))
|
|
186
|
-
if new_loci:
|
|
187
|
-
new_implementations[implementation_name] = GateImplementationInfo(loci=new_loci)
|
|
188
|
-
if new_implementations:
|
|
189
|
-
new_gates[gate_name] = GateInfo(
|
|
190
|
-
implementations=new_implementations,
|
|
191
|
-
default_implementation=gate_info.default_implementation,
|
|
192
|
-
override_default_implementation=gate_info.override_default_implementation,
|
|
193
|
-
)
|
|
194
|
-
new_arch = DynamicQuantumArchitecture(
|
|
195
|
-
calibration_set_id=architecture.calibration_set_id,
|
|
196
|
-
qubits=[q for q in qubits if q in architecture.qubits],
|
|
197
|
-
computational_resonators=[q for q in qubits if q in architecture.computational_resonators],
|
|
198
|
-
gates=new_gates,
|
|
199
|
-
)
|
|
200
|
-
return IQMTarget(
|
|
201
|
-
new_arch,
|
|
202
|
-
component_to_idx={name: idx for idx, name in enumerate(qubits)},
|
|
203
|
-
include_resonators=include_resonators,
|
|
204
|
-
include_fake_czs=include_fake_czs,
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
class IQMTarget(Target):
|
|
209
|
-
"""Transpilation target for an IQM architecture.
|
|
210
|
-
|
|
211
|
-
Contains the mapping of physical qubit name on the device to qubit index in the Target.
|
|
212
|
-
|
|
213
|
-
Args:
|
|
214
|
-
architecture: Quantum architecture that defines the target.
|
|
215
|
-
component_to_idx: Mapping from QPU component names to integer indices used by Qiskit to refer to them.
|
|
216
|
-
include_resonators: Whether to include MOVE gates in the target.
|
|
217
|
-
include_fake_czs: Whether to include virtual CZs that are not natively supported, but could be routed via MOVE.
|
|
218
|
-
|
|
219
|
-
"""
|
|
220
|
-
|
|
221
|
-
def __init__(
|
|
222
|
-
self,
|
|
223
|
-
architecture: DynamicQuantumArchitecture,
|
|
224
|
-
component_to_idx: dict[str, int],
|
|
225
|
-
include_resonators: bool,
|
|
226
|
-
include_fake_czs: bool = True,
|
|
227
|
-
):
|
|
228
|
-
super().__init__()
|
|
229
|
-
# Using iqm as a prefix to avoid name clashes with the base class.
|
|
230
|
-
self.iqm_dqa = architecture
|
|
231
|
-
self.iqm_component_to_idx = component_to_idx
|
|
232
|
-
self.iqm_idx_to_component = {v: k for k, v in component_to_idx.items()}
|
|
233
|
-
self.iqm_includes_resonators = include_resonators
|
|
234
|
-
self.iqm_includes_fake_czs = include_fake_czs
|
|
235
|
-
self._add_connections_from_DQA()
|
|
236
|
-
|
|
237
|
-
def _add_connections_from_DQA(self):
|
|
238
|
-
"""Initializes the Target, making it represent the dynamic quantum architecture :attr:`iqm_dqa`."""
|
|
239
|
-
# mapping from op name to all its allowed loci
|
|
240
|
-
architecture = self.iqm_dqa
|
|
241
|
-
component_to_idx = self.iqm_component_to_idx
|
|
242
|
-
op_loci = {gate_name: gate_info.loci for gate_name, gate_info in architecture.gates.items()}
|
|
243
|
-
|
|
244
|
-
def locus_to_idx(locus: Locus) -> LocusIdx:
|
|
245
|
-
"""Map the given locus to use component indices instead of component names."""
|
|
246
|
-
return tuple(component_to_idx[component] for component in locus)
|
|
247
|
-
|
|
248
|
-
def create_properties(name: str, *, symmetrize: bool = False) -> dict[tuple[int, ...], None]:
|
|
249
|
-
"""Creates the Qiskit instruction properties dictionary for the given IQM native operation.
|
|
250
|
-
|
|
251
|
-
Currently we do not provide any actual properties for the operation, hence the all the
|
|
252
|
-
allowed loci map to None.
|
|
253
|
-
"""
|
|
254
|
-
if self.iqm_includes_resonators:
|
|
255
|
-
loci = op_loci[name]
|
|
256
|
-
else:
|
|
257
|
-
# Remove the loci that correspond to resonators.
|
|
258
|
-
loci = [
|
|
259
|
-
locus for locus in op_loci[name] if all(component in self.iqm_dqa.qubits for component in locus)
|
|
260
|
-
]
|
|
261
|
-
if symmetrize:
|
|
262
|
-
# symmetrize the loci
|
|
263
|
-
loci = tuple(permuted_locus for locus in loci for permuted_locus in itertools.permutations(locus))
|
|
264
|
-
return {locus_to_idx(locus): None for locus in loci}
|
|
265
|
-
|
|
266
|
-
# like barrier, delay is always available for all single-qubit loci
|
|
267
|
-
self.add_instruction(Delay(0), {locus_to_idx((q,)): None for q in architecture.qubits})
|
|
268
|
-
|
|
269
|
-
if "measure" in op_loci:
|
|
270
|
-
self.add_instruction(Measure(), create_properties("measure"))
|
|
271
|
-
|
|
272
|
-
# identity gate does nothing and is removed in serialization, so we may as well allow it everywhere
|
|
273
|
-
# Except if it is defined for the resonator, the graph is disconnected and the transpiler will fail.
|
|
274
|
-
if self.iqm_includes_resonators:
|
|
275
|
-
self.add_instruction(
|
|
276
|
-
IGate(),
|
|
277
|
-
{locus_to_idx((component,)): None for component in architecture.components},
|
|
278
|
-
)
|
|
279
|
-
else:
|
|
280
|
-
self.add_instruction(
|
|
281
|
-
IGate(),
|
|
282
|
-
{locus_to_idx((component,)): None for component in architecture.qubits},
|
|
283
|
-
)
|
|
284
|
-
|
|
285
|
-
if "prx" in op_loci:
|
|
286
|
-
self.add_instruction(
|
|
287
|
-
RGate(Parameter("theta"), Parameter("phi")),
|
|
288
|
-
create_properties("prx"),
|
|
289
|
-
)
|
|
290
|
-
|
|
291
|
-
# HACK reset gate shares cc_prx loci for now
|
|
292
|
-
if "cc_prx" in op_loci:
|
|
293
|
-
self.add_instruction(Reset(), create_properties("cc_prx"))
|
|
294
|
-
|
|
295
|
-
if self.iqm_includes_resonators and "move" in op_loci:
|
|
296
|
-
self.add_instruction(MoveGate(), create_properties("move"))
|
|
297
|
-
|
|
298
|
-
if "cz" in op_loci:
|
|
299
|
-
if self.iqm_includes_fake_czs and "move" in op_loci:
|
|
300
|
-
# CZ and MOVE: star
|
|
301
|
-
cz_connections: dict[LocusIdx, None] = {}
|
|
302
|
-
cz_loci = op_loci["cz"]
|
|
303
|
-
for c1, c2 in cz_loci:
|
|
304
|
-
if self.iqm_includes_resonators or all(component in self.iqm_dqa.qubits for component in (c1, c2)):
|
|
305
|
-
idx_locus = locus_to_idx((c1, c2))
|
|
306
|
-
cz_connections[idx_locus] = None
|
|
307
|
-
|
|
308
|
-
for c1, res in op_loci["move"]:
|
|
309
|
-
for c2 in architecture.qubits:
|
|
310
|
-
if c2 not in [c1, res]:
|
|
311
|
-
# loop over c2 that is not c1
|
|
312
|
-
if (c2, res) in cz_loci or (res, c2) in cz_loci:
|
|
313
|
-
# This is a fake CZ and can be bidirectional.
|
|
314
|
-
# cz routable via res between qubits, put into fake_cz_conn both ways
|
|
315
|
-
idx_locus = locus_to_idx((c1, c2))
|
|
316
|
-
cz_connections[idx_locus] = None
|
|
317
|
-
cz_connections[idx_locus[::-1]] = None
|
|
318
|
-
self.add_instruction(CZGate(), cz_connections)
|
|
319
|
-
else:
|
|
320
|
-
# CZ but no MOVE: crystal
|
|
321
|
-
self.add_instruction(CZGate(), create_properties("cz"))
|
|
322
|
-
|
|
323
|
-
@property
|
|
324
|
-
def physical_qubits(self) -> list[str]:
|
|
325
|
-
"""Return the ordered list of physical qubits in the backend."""
|
|
326
|
-
# Overrides the property from the superclass to contain the correct information.
|
|
327
|
-
return [self.iqm_idx_to_component[i] for i in range(self.num_qubits)]
|
|
328
|
-
|
|
329
|
-
def restrict_to_qubits(self, qubits: list[int] | list[str]) -> IQMTarget:
|
|
330
|
-
"""Generated a restricted transpilation target from this Target that only contains the given qubits.
|
|
331
|
-
|
|
332
|
-
Args:
|
|
333
|
-
qubits: Qubits to restrict the target to. Can be either a list of qubit indices or qubit names.
|
|
334
|
-
|
|
335
|
-
Returns:
|
|
336
|
-
restricted target
|
|
337
|
-
|
|
338
|
-
"""
|
|
339
|
-
qubits_str = [self.iqm_idx_to_component[q] if isinstance(q, int) else str(q) for q in qubits]
|
|
340
|
-
return _restrict_dqa_to_qubits(
|
|
341
|
-
self.iqm_dqa,
|
|
342
|
-
qubits_str,
|
|
343
|
-
self.iqm_includes_resonators,
|
|
344
|
-
self.iqm_includes_fake_czs,
|
|
345
|
-
)
|
|
@@ -11,12 +11,10 @@
|
|
|
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
|
-
"""Generate an initial layout for a quantum circuit
|
|
15
|
-
valid on the quantum architecture specification of the given backend.
|
|
16
|
-
"""
|
|
14
|
+
"""Generate an initial layout for a quantum circuit containing MOVE gates."""
|
|
17
15
|
|
|
18
16
|
from iqm.iqm_client import DynamicQuantumArchitecture
|
|
19
|
-
from iqm.qiskit_iqm.iqm_backend import
|
|
17
|
+
from iqm.qiskit_iqm.iqm_backend import IQMTarget
|
|
20
18
|
from qiskit import QuantumCircuit
|
|
21
19
|
from qiskit.circuit import Qubit
|
|
22
20
|
from qiskit.dagcircuit import DAGCircuit
|
|
@@ -228,26 +226,20 @@ class IQMMoveLayout(TrivialLayout):
|
|
|
228
226
|
|
|
229
227
|
|
|
230
228
|
def generate_initial_layout(
|
|
231
|
-
|
|
229
|
+
target: IQMTarget,
|
|
232
230
|
circuit: QuantumCircuit,
|
|
233
|
-
restrict_to_qubits: list[int] | list[str] | None = None,
|
|
234
231
|
) -> Layout:
|
|
235
|
-
"""
|
|
232
|
+
"""Generate an initial layout for the given circuit, for running on the given Star architecture target.
|
|
236
233
|
|
|
237
234
|
Args:
|
|
238
|
-
|
|
239
|
-
circuit:
|
|
240
|
-
restrict_to_qubits: Optional list of qubits to restrict the layout to.
|
|
235
|
+
target: IQM Star architecture target to run the circuit on, without fictional gates.
|
|
236
|
+
circuit: Quantum circuit for which a layout is to be generated.
|
|
241
237
|
|
|
242
238
|
Returns:
|
|
243
|
-
Layout that maps the logical qubits of ``circuit`` to the physical qubits of ``
|
|
239
|
+
Layout that maps the virtual/logical qubits of ``circuit`` to the physical qubits of ``target`` so that
|
|
244
240
|
all the gates in ``circuit`` are available on those loci.
|
|
245
241
|
|
|
246
242
|
"""
|
|
247
|
-
target = backend.get_real_target()
|
|
248
|
-
if restrict_to_qubits is not None:
|
|
249
|
-
target = target.restrict_to_qubits(restrict_to_qubits)
|
|
250
|
-
|
|
251
243
|
layout_gen = IQMMoveLayout(target)
|
|
252
244
|
pm = PassManager(layout_gen)
|
|
253
245
|
pm.run(circuit)
|
|
@@ -200,7 +200,7 @@ def _get_scheduling_method(
|
|
|
200
200
|
def transpile_to_IQM( # noqa: PLR0913
|
|
201
201
|
circuit: QuantumCircuit,
|
|
202
202
|
backend: IQMBackendBase,
|
|
203
|
-
|
|
203
|
+
*,
|
|
204
204
|
initial_layout: Layout | dict | list | None = None,
|
|
205
205
|
perform_move_routing: bool = True,
|
|
206
206
|
optimize_single_qubits: bool = True,
|
|
@@ -214,16 +214,14 @@ def transpile_to_IQM( # noqa: PLR0913
|
|
|
214
214
|
|
|
215
215
|
Works with both the Crystal and Star architectures.
|
|
216
216
|
|
|
217
|
-
Note: When transpiling a circuit with MOVE gates, you might need to set
|
|
218
|
-
If
|
|
217
|
+
Note: When transpiling a circuit with MOVE gates, you might need to set ``optimization_level`` lower.
|
|
218
|
+
If ``optimization_level`` is set too high, the transpiler might add single qubit gates onto the resonator,
|
|
219
219
|
which is not supported by the IQM Star architectures. If this in undesired, it is best to have the transpiler
|
|
220
220
|
add the MOVE gates automatically, rather than manually adding them to the circuit.
|
|
221
221
|
|
|
222
222
|
Args:
|
|
223
|
-
circuit: The circuit to
|
|
224
|
-
backend: The
|
|
225
|
-
target: An alternative target to compile to than the backend, using this option requires intimate knowledge
|
|
226
|
-
of the transpiler and thus it is not recommended to use.
|
|
223
|
+
circuit: The circuit to transpile.
|
|
224
|
+
backend: The backend to transpile to.
|
|
227
225
|
initial_layout: The initial layout to use for the transpilation, same as :func:`~qiskit.compiler.transpile`.
|
|
228
226
|
perform_move_routing: Whether to perform MOVE gate routing.
|
|
229
227
|
optimize_single_qubits: Whether to optimize single qubit gates away.
|
|
@@ -233,30 +231,35 @@ def transpile_to_IQM( # noqa: PLR0913
|
|
|
233
231
|
existing_moves_handling: How to handle existing MOVE gates in the circuit, required if the circuit contains
|
|
234
232
|
MOVE gates.
|
|
235
233
|
restrict_to_qubits: Restrict the transpilation to only use these specific physical qubits. Note that you will
|
|
236
|
-
have to pass this information to
|
|
234
|
+
also have to pass this information to :meth:`.IQMBackend.run` using the ``qubit_mapping`` parameter.
|
|
237
235
|
qiskit_transpiler_kwargs: Arguments to be passed to the Qiskit transpiler.
|
|
238
236
|
|
|
239
237
|
Returns:
|
|
240
238
|
Transpiled circuit ready for running on the backend.
|
|
241
239
|
|
|
242
240
|
"""
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
initial_layout = generate_initial_layout(backend, circuit, restrict_to_qubits)
|
|
252
|
-
if perform_move_routing and existing_moves_handling is None:
|
|
253
|
-
raise ValueError("The circuit contains MOVE gates but existing_moves_handling is not set.")
|
|
254
|
-
else:
|
|
255
|
-
target = backend.target
|
|
241
|
+
circuit_has_moves = circuit.count_ops().get("move", 0) > 0
|
|
242
|
+
# get the target from the backend
|
|
243
|
+
if circuit_has_moves:
|
|
244
|
+
target = backend.target_with_resonators
|
|
245
|
+
if perform_move_routing and existing_moves_handling is None:
|
|
246
|
+
raise ValueError("The circuit contains MOVE gates but existing_moves_handling is not set.")
|
|
247
|
+
else:
|
|
248
|
+
target = backend.target
|
|
256
249
|
|
|
257
250
|
if restrict_to_qubits is not None:
|
|
251
|
+
# qubit names to qiskit indices
|
|
252
|
+
restrict_to_qubits = [backend.qubit_name_to_index(q) if isinstance(q, str) else q for q in restrict_to_qubits]
|
|
258
253
|
target = target.restrict_to_qubits(restrict_to_qubits)
|
|
259
254
|
|
|
255
|
+
if circuit_has_moves and initial_layout is None:
|
|
256
|
+
# Create a sensible initial layout if none is provided, since
|
|
257
|
+
# the standard Qiskit transpile function does not do this well with resonators.
|
|
258
|
+
real_target = backend.get_real_target()
|
|
259
|
+
if restrict_to_qubits is not None:
|
|
260
|
+
real_target = real_target.restrict_to_qubits(restrict_to_qubits)
|
|
261
|
+
initial_layout = generate_initial_layout(real_target, circuit)
|
|
262
|
+
|
|
260
263
|
# Determine which scheduling method to use
|
|
261
264
|
scheduling_method = qiskit_transpiler_kwargs.pop("scheduling_method", None)
|
|
262
265
|
if scheduling_method is None:
|
iqm/qiskit_iqm/iqm_provider.py
CHANGED
|
@@ -30,7 +30,7 @@ from iqm.iqm_client import (
|
|
|
30
30
|
RunRequest,
|
|
31
31
|
)
|
|
32
32
|
from iqm.iqm_client.util import to_json_dict
|
|
33
|
-
from iqm.qiskit_iqm import IQMFakeAphrodite, IQMFakeApollo, IQMFakeDeneb
|
|
33
|
+
from iqm.qiskit_iqm import IQMFakeAphrodite, IQMFakeApollo, IQMFakeBackend, IQMFakeDeneb
|
|
34
34
|
from iqm.qiskit_iqm.fake_backends import IQMFakeAdonis
|
|
35
35
|
from iqm.qiskit_iqm.fake_backends.fake_garnet import IQMFakeGarnet
|
|
36
36
|
from iqm.qiskit_iqm.iqm_backend import IQMBackendBase
|
|
@@ -51,20 +51,31 @@ class IQMBackend(IQMBackendBase):
|
|
|
51
51
|
"""Backend for executing quantum circuits on IQM quantum computers.
|
|
52
52
|
|
|
53
53
|
Args:
|
|
54
|
-
client:
|
|
54
|
+
client: Client instance for communicating with an IQM server.
|
|
55
55
|
calibration_set_id: ID of the calibration set the backend will use.
|
|
56
56
|
``None`` means the IQM server will be queried for the current default
|
|
57
57
|
calibration set.
|
|
58
|
-
|
|
58
|
+
use_metrics: Iff True, the backend will query the server for calibration data and related
|
|
59
|
+
quality metrics, and pass these to the transpilation target(s).
|
|
60
|
+
kwargs: Optional arguments to be passed to the parent Backend initializer.
|
|
59
61
|
|
|
60
62
|
"""
|
|
61
63
|
|
|
62
|
-
def __init__(
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
client: IQMClient,
|
|
67
|
+
*,
|
|
68
|
+
calibration_set_id: str | UUID | None = None,
|
|
69
|
+
use_metrics: bool = True,
|
|
70
|
+
**kwargs,
|
|
71
|
+
):
|
|
63
72
|
if calibration_set_id is not None and not isinstance(calibration_set_id, UUID):
|
|
64
73
|
calibration_set_id = UUID(calibration_set_id)
|
|
74
|
+
|
|
65
75
|
self._use_default_calibration_set = calibration_set_id is None
|
|
66
76
|
architecture = client.get_dynamic_quantum_architecture(calibration_set_id)
|
|
67
|
-
|
|
77
|
+
metrics = client._get_calibration_quality_metrics(architecture.calibration_set_id) if use_metrics else None
|
|
78
|
+
super().__init__(architecture, metrics=metrics, **kwargs)
|
|
68
79
|
self.client: IQMClient = client
|
|
69
80
|
self._max_circuits: int | None = None
|
|
70
81
|
self.name = "IQM Backend"
|
|
@@ -266,10 +277,10 @@ class IQMBackend(IQMBackendBase):
|
|
|
266
277
|
return Circuit(name=circuit.name, instructions=instructions, metadata=metadata)
|
|
267
278
|
|
|
268
279
|
|
|
269
|
-
facade_names = {
|
|
280
|
+
facade_names: dict[str, IQMFakeBackend] = {
|
|
270
281
|
"facade_adonis": IQMFakeAdonis(),
|
|
271
|
-
"facade_aphrodite": IQMFakeAphrodite(),
|
|
272
282
|
"facade_apollo": IQMFakeApollo(),
|
|
283
|
+
"facade_aphrodite": IQMFakeAphrodite(),
|
|
273
284
|
"facade_deneb": IQMFakeDeneb(),
|
|
274
285
|
"facade_garnet": IQMFakeGarnet(),
|
|
275
286
|
}
|
|
@@ -280,17 +291,38 @@ class IQMFacadeBackend(IQMBackend):
|
|
|
280
291
|
|
|
281
292
|
Can be used to submit a circuit to a mock IQM server that has no real quantum hardware,
|
|
282
293
|
and if the mock execution is successful, simulates the circuit locally using an error model that
|
|
283
|
-
is representative of the mocked QPU. Finally returns the simulated results
|
|
294
|
+
is broadly representative of the mocked QPU. Finally returns the *simulated results*.
|
|
295
|
+
|
|
296
|
+
.. note::
|
|
297
|
+
|
|
298
|
+
There is usually no point in using a facade backend with a real quantum computer, since the computation
|
|
299
|
+
results will be replaced with a local simulation.
|
|
284
300
|
|
|
285
301
|
Args:
|
|
286
302
|
client: Client instance for communicating with an IQM server.
|
|
287
|
-
|
|
303
|
+
name: Name of the fake backend (simulator instance) to use. If None, will be determined automatically based
|
|
304
|
+
on the static quantum architecture of the server.
|
|
305
|
+
kwargs: Optional arguments to be passed to the parent class.
|
|
288
306
|
|
|
289
307
|
"""
|
|
290
308
|
|
|
291
|
-
def __init__(self, client: IQMClient, **kwargs):
|
|
309
|
+
def __init__(self, client: IQMClient, *, name: str | None = None, **kwargs):
|
|
292
310
|
super().__init__(client, **kwargs)
|
|
293
|
-
|
|
311
|
+
sqa = self.client.get_static_quantum_architecture()
|
|
312
|
+
if name is None:
|
|
313
|
+
# use a fake backend (local simulator) that matches the server
|
|
314
|
+
for backend in facade_names.values():
|
|
315
|
+
if backend.validate_compatible_architecture(sqa):
|
|
316
|
+
self._fake_backend = backend
|
|
317
|
+
return
|
|
318
|
+
raise ValueError("Quantum architecture of the server does not match any known IQMFakeBackend.")
|
|
319
|
+
else:
|
|
320
|
+
if name not in facade_names:
|
|
321
|
+
raise ValueError(f"Unknown facade backend: {name}")
|
|
322
|
+
backend = facade_names[name]
|
|
323
|
+
if not backend.validate_compatible_architecture(sqa):
|
|
324
|
+
raise ValueError("Quantum architecture of the server does not match the requested IQMFakeBackend.")
|
|
325
|
+
self._fake_backend = backend
|
|
294
326
|
|
|
295
327
|
def _validate_no_empty_cregs(self, circuit: QuantumCircuit) -> bool:
|
|
296
328
|
"""Returns True if given circuit has no empty (unused) classical registers, False otherwise."""
|
|
@@ -303,26 +335,20 @@ class IQMFacadeBackend(IQMBackend):
|
|
|
303
335
|
return False
|
|
304
336
|
return True
|
|
305
337
|
|
|
306
|
-
def _determine_facade_backend_from_sqa(self) -> IQMFacadeBackend:
|
|
307
|
-
for backend in facade_names.values():
|
|
308
|
-
if backend.validate_compatible_architecture(self.client.get_static_quantum_architecture()):
|
|
309
|
-
return backend
|
|
310
|
-
raise ValueError("Quantum architecture of the remote quantum computer does not match facade input.")
|
|
311
|
-
|
|
312
338
|
def run(self, run_input: QuantumCircuit | list[QuantumCircuit], **options) -> JobV1:
|
|
313
339
|
circuits = [run_input] if isinstance(run_input, QuantumCircuit) else run_input
|
|
314
340
|
circuits_validated_cregs: list[bool] = [self._validate_no_empty_cregs(circuit) for circuit in circuits]
|
|
315
341
|
if not all(circuits_validated_cregs):
|
|
316
342
|
raise ValueError(
|
|
317
|
-
"One or more circuits contain unused classical registers. This is not allowed for
|
|
318
|
-
"see user guide."
|
|
343
|
+
"One or more circuits contain unused classical registers. This is not allowed for facade simulation, "
|
|
344
|
+
"see the user guide."
|
|
319
345
|
)
|
|
320
346
|
|
|
321
347
|
iqm_backend_job = super().run(run_input, **options)
|
|
322
348
|
iqm_backend_job.result() # get and discard results
|
|
323
349
|
if iqm_backend_job.status() == JobStatus.ERROR:
|
|
324
350
|
raise RuntimeError("Remote execution did not succeed.")
|
|
325
|
-
return self.
|
|
351
|
+
return self._fake_backend.run(run_input, **options)
|
|
326
352
|
|
|
327
353
|
|
|
328
354
|
class IQMProvider:
|
|
@@ -334,7 +360,7 @@ class IQMProvider:
|
|
|
334
360
|
through to :class:`~iqm.iqm_client.iqm_client.IQMClient` as is, and are documented there.
|
|
335
361
|
|
|
336
362
|
Args:
|
|
337
|
-
url: URL of the IQM server (e.g. https://cocos.resonance.meetiqm.com/garnet)
|
|
363
|
+
url: URL of the IQM server (e.g. "https://cocos.resonance.meetiqm.com/garnet")
|
|
338
364
|
|
|
339
365
|
"""
|
|
340
366
|
|
|
@@ -343,24 +369,28 @@ class IQMProvider:
|
|
|
343
369
|
self.user_auth_args = user_auth_args
|
|
344
370
|
|
|
345
371
|
def get_backend(
|
|
346
|
-
self,
|
|
347
|
-
|
|
348
|
-
|
|
372
|
+
self,
|
|
373
|
+
name: str | None = None,
|
|
374
|
+
calibration_set_id: UUID | None = None,
|
|
375
|
+
*,
|
|
376
|
+
use_metrics: bool = True,
|
|
377
|
+
) -> IQMBackend:
|
|
378
|
+
"""IQMBackend instance associated with this provider.
|
|
349
379
|
|
|
350
380
|
Args:
|
|
351
|
-
name:
|
|
352
|
-
calibration_set_id: ID of the calibration set
|
|
381
|
+
name: Optional name of a facade backend to request, see :class:`IQMFacadeBackend`.
|
|
382
|
+
calibration_set_id: ID of the calibration set to be used with the backend.
|
|
383
|
+
Affects both the transpilation target and the circuit execution.
|
|
353
384
|
If None, the server default calibration set will be used.
|
|
385
|
+
use_metrics: Iff True, the backend will provide calibration data and related quality metrics
|
|
386
|
+
to the transpilation target to improve the transpilation.
|
|
387
|
+
|
|
388
|
+
Returns:
|
|
389
|
+
Backend instance for connecting to a quantum computer.
|
|
354
390
|
|
|
355
391
|
"""
|
|
356
392
|
client = IQMClient(self.url, **self.user_auth_args)
|
|
357
393
|
|
|
358
394
|
if name and name.startswith("facade_"):
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
raise ValueError("Quantum architecture of the remote quantum computer does not match facade input.")
|
|
362
|
-
return IQMFacadeBackend(client)
|
|
363
|
-
|
|
364
|
-
warnings.warn(f"Unknown facade backend: {name}. A regular backend associated with {self.url} will be used.")
|
|
365
|
-
|
|
366
|
-
return IQMBackend(client, calibration_set_id=calibration_set_id)
|
|
395
|
+
return IQMFacadeBackend(client, name=name, calibration_set_id=calibration_set_id, use_metrics=use_metrics)
|
|
396
|
+
return IQMBackend(client, calibration_set_id=calibration_set_id, use_metrics=use_metrics)
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
# Copyright 2022-2025 Qiskit on IQM 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
|
+
"""Transpilation target for IQM quantum computers."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from collections.abc import Iterable
|
|
19
|
+
import logging
|
|
20
|
+
from typing import TypeAlias
|
|
21
|
+
|
|
22
|
+
from iqm.iqm_client import (
|
|
23
|
+
DynamicQuantumArchitecture,
|
|
24
|
+
GateImplementationInfo,
|
|
25
|
+
GateInfo,
|
|
26
|
+
ObservationFinder,
|
|
27
|
+
)
|
|
28
|
+
from iqm.qiskit_iqm.move_gate import MoveGate
|
|
29
|
+
from qiskit.circuit import Delay, Gate, Parameter, Reset
|
|
30
|
+
from qiskit.circuit.library import CZGate, IGate, Measure, RGate
|
|
31
|
+
from qiskit.providers import QubitProperties
|
|
32
|
+
from qiskit.transpiler import InstructionProperties, Target
|
|
33
|
+
|
|
34
|
+
Locus: TypeAlias = tuple[str, ...]
|
|
35
|
+
"""Sequence of QPU component names on which a gate acts."""
|
|
36
|
+
LocusIdx: TypeAlias = tuple[int, ...]
|
|
37
|
+
"""Sequence of qubit indices on which a gate acts."""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
_QISKIT_IQM_GATE_MAP: dict[str, Gate] = {
|
|
41
|
+
"prx": RGate(Parameter("theta"), Parameter("phi")),
|
|
42
|
+
"measure": Measure(),
|
|
43
|
+
"reset": Reset(),
|
|
44
|
+
"cz": CZGate(),
|
|
45
|
+
"move": MoveGate(),
|
|
46
|
+
"id": IGate(),
|
|
47
|
+
}
|
|
48
|
+
"""Maps IQM native operation names to corresponding Qiskit gate objects."""
|
|
49
|
+
|
|
50
|
+
logger = logging.getLogger(__name__)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class IQMTarget(Target):
|
|
54
|
+
"""Transpilation target for an IQM architecture.
|
|
55
|
+
|
|
56
|
+
Contains the mapping of physical qubit name on the device to qubit index in the Target.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
architecture: Quantum architecture that defines the target.
|
|
60
|
+
component_to_idx: Mapping from QPU component names to integer indices used by Qiskit to refer to them.
|
|
61
|
+
include_resonators: Whether to include computational resonators (and MOVE gates) in the target,
|
|
62
|
+
if present in ``architecture``.
|
|
63
|
+
include_fictional_czs: Whether to include "fictional" CZs that are not natively supported,
|
|
64
|
+
but could be routed using MOVE.
|
|
65
|
+
metrics: Optional calibration data and related quality metrics to improve the transpilation.
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
*,
|
|
72
|
+
architecture: DynamicQuantumArchitecture,
|
|
73
|
+
component_to_idx: dict[str, int],
|
|
74
|
+
include_resonators: bool,
|
|
75
|
+
include_fictional_czs: bool,
|
|
76
|
+
metrics: ObservationFinder | None = None,
|
|
77
|
+
):
|
|
78
|
+
super().__init__()
|
|
79
|
+
# HACK: As of Qiskit 1.3, Target.__init__ does nothing with its args, instead Target.__new__
|
|
80
|
+
# handles them. IQMTarget.__init__ has different args than Target.__init__, hence we must either
|
|
81
|
+
# (1) define IQMTarget.__new__ with matching args that calls Target.__new__, or
|
|
82
|
+
# (2) make all the IQMTarget.__init__ args keyword-only with non-colliding names, and init
|
|
83
|
+
# the necessary superclass attributes ourselves.
|
|
84
|
+
# (1) seems to break pickling during concurrent transpilation using Qiskit's ``transpile``, so we use (2).
|
|
85
|
+
self.qubit_properties = self._create_qubit_properties(architecture.qubits, metrics)
|
|
86
|
+
|
|
87
|
+
# Using iqm_ as a prefix to avoid name clashes with the base class.
|
|
88
|
+
self.iqm_dqa = architecture
|
|
89
|
+
self.iqm_component_to_idx = component_to_idx
|
|
90
|
+
self.iqm_idx_to_component = {v: k for k, v in component_to_idx.items()}
|
|
91
|
+
self.iqm_include_resonators = include_resonators
|
|
92
|
+
self.iqm_include_fictional_czs = include_fictional_czs
|
|
93
|
+
self.iqm_metrics = metrics
|
|
94
|
+
self._add_instructions_from_DQA()
|
|
95
|
+
|
|
96
|
+
def _add_instructions_from_DQA(self):
|
|
97
|
+
"""Initializes the Target with instructions and properties that represent the
|
|
98
|
+
dynamic quantum architecture :attr:`iqm_dqa`.
|
|
99
|
+
|
|
100
|
+
"""
|
|
101
|
+
dqa = self.iqm_dqa
|
|
102
|
+
# mapping from op name to all its allowed loci
|
|
103
|
+
op_loci = {gate_name: gate_info.loci for gate_name, gate_info in dqa.gates.items()}
|
|
104
|
+
|
|
105
|
+
def add_gate(gate: str) -> None:
|
|
106
|
+
"""Adds ``gate`` instructions to the Target."""
|
|
107
|
+
props = {self.locus_to_idx(locus): self._create_gate_properties(gate, locus) for locus in op_loci[gate]}
|
|
108
|
+
self.add_instruction(_QISKIT_IQM_GATE_MAP[gate], props)
|
|
109
|
+
|
|
110
|
+
# identity gate does nothing and is removed in serialization, so we may as well allow it everywhere
|
|
111
|
+
# Except if it is defined for the resonator, the graph is disconnected and the transpiler will fail.
|
|
112
|
+
self.add_instruction(
|
|
113
|
+
IGate(),
|
|
114
|
+
{
|
|
115
|
+
self.locus_to_idx((component,)): None
|
|
116
|
+
for component in (dqa.components if self.iqm_include_resonators else dqa.qubits)
|
|
117
|
+
},
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# like barrier, delay is always available for all single-qubit loci
|
|
121
|
+
self.add_instruction(Delay(0), {self.locus_to_idx((q,)): None for q in dqa.qubits})
|
|
122
|
+
|
|
123
|
+
if "measure" in op_loci:
|
|
124
|
+
add_gate("measure")
|
|
125
|
+
|
|
126
|
+
if "prx" in op_loci:
|
|
127
|
+
add_gate("prx")
|
|
128
|
+
|
|
129
|
+
# HACK reset gate shares cc_prx loci for now, until reset is also in the DQA/metrics
|
|
130
|
+
if "cc_prx" in op_loci:
|
|
131
|
+
self.add_instruction(
|
|
132
|
+
_QISKIT_IQM_GATE_MAP["reset"],
|
|
133
|
+
{self.locus_to_idx(locus): None for locus in op_loci["cc_prx"]},
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if self.iqm_include_resonators and "move" in op_loci:
|
|
137
|
+
add_gate("move")
|
|
138
|
+
|
|
139
|
+
if "cz" in op_loci:
|
|
140
|
+
self._add_instructions_cz_gates(op_loci)
|
|
141
|
+
|
|
142
|
+
def _add_instructions_cz_gates(self, op_loci: dict[str, Iterable[Locus]]) -> None:
|
|
143
|
+
"""Adds CZ gate instructions to the Qiskit Target.
|
|
144
|
+
|
|
145
|
+
This method handles both "real" and "fictional" CZ gates based on the
|
|
146
|
+
provided operation loci.
|
|
147
|
+
|
|
148
|
+
1. **Real CZ Gates**:
|
|
149
|
+
A CZ gate for a locus is considered "real" if the locus
|
|
150
|
+
exists in ``dynamic_quantum_architecture.gates['cz']``.
|
|
151
|
+
A "real" CZ instruction is added if either the target is configured to
|
|
152
|
+
include resonators (:attr:`iqm_include_resonators` is True) or if all
|
|
153
|
+
the locus components are qubits.
|
|
154
|
+
|
|
155
|
+
2. **Fictional CZ Gates**: For STAR QPU architectures, a "fictional" CZ between
|
|
156
|
+
two qubits that are not physically connected is defined
|
|
157
|
+
iff there is a CZ gate from one qubit to a resonator, and a MOVE gate
|
|
158
|
+
from the other qubit to the resonator.
|
|
159
|
+
See :mod:`iqm.iqm_client.transpile`.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
op_loci: Mapping of operation names (e.g., "cz", "move") to their available loci.
|
|
163
|
+
|
|
164
|
+
"""
|
|
165
|
+
cz_props: dict[LocusIdx, InstructionProperties] = {}
|
|
166
|
+
cz_loci = op_loci["cz"]
|
|
167
|
+
|
|
168
|
+
# 1. Add real CZs
|
|
169
|
+
for locus in cz_loci:
|
|
170
|
+
if self.iqm_include_resonators or all(component in self.iqm_dqa.qubits for component in locus):
|
|
171
|
+
locus_idx = self.locus_to_idx(locus) # convert locus from IQM to Qiskit format
|
|
172
|
+
cz_props[locus_idx] = self._create_gate_properties("cz", locus)
|
|
173
|
+
|
|
174
|
+
# 2. Add fictional CZs if applicable
|
|
175
|
+
if self.iqm_include_fictional_czs and "move" in op_loci:
|
|
176
|
+
fictional_cz_loci = self._determine_fictional_cz_loci(op_loci["move"], cz_loci)
|
|
177
|
+
for q1, q2, res in fictional_cz_loci:
|
|
178
|
+
locus_idx = self.locus_to_idx((q1, q2))
|
|
179
|
+
props = self._create_fictional_cz_properties(q1, q2, res)
|
|
180
|
+
cz_props[locus_idx] = props
|
|
181
|
+
cz_props[locus_idx[::-1]] = props # fictional CZs are symmetric
|
|
182
|
+
# TODO: if the fictional CZ can be implemented using both CZ(a,r) and MOVE(b,r), and
|
|
183
|
+
# CZ(b,r) and MOVE(a,r), props will be only included for one implementation at random.
|
|
184
|
+
# This is not necessarily the implementation the transpiler chooses!
|
|
185
|
+
|
|
186
|
+
if cz_props:
|
|
187
|
+
self.add_instruction(_QISKIT_IQM_GATE_MAP["cz"], cz_props)
|
|
188
|
+
|
|
189
|
+
def _create_fictional_cz_properties(self, q1: str, q2: str, res: str) -> InstructionProperties:
|
|
190
|
+
"""Create ``InstructionProperties`` for a fictional CZ gate.
|
|
191
|
+
|
|
192
|
+
A fictional CZ gate is comprised of the sequence MOVE(q2, res), CZ(q1, res), MOVE(q2, res).
|
|
193
|
+
|
|
194
|
+
The duration and error for the fictional CZ gate are calculated as follows:
|
|
195
|
+
|
|
196
|
+
- Duration: 2 * duration_move(q2, res) + duration_cz(q1, res)
|
|
197
|
+
- Error: 1 - (1 - error_move(q2, res)))^2 * (1 - error_cz(q1, res))
|
|
198
|
+
|
|
199
|
+
See https://arxiv.org/pdf/2503.10903 for more details.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
q1: CZ qubit.
|
|
203
|
+
q2: MOVE qubit.
|
|
204
|
+
res: Shared computational resonator through which the fictional CZ gate is routed.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Instruction properties for the fictional CZ gate.
|
|
208
|
+
|
|
209
|
+
"""
|
|
210
|
+
if self.iqm_metrics is None:
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
props_cz = self._create_gate_properties("cz", (q1, res))
|
|
214
|
+
props_move = self._create_gate_properties("move", (q2, res))
|
|
215
|
+
|
|
216
|
+
if props_cz is None or props_move is None:
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
duration = None
|
|
220
|
+
if props_move.duration is not None and props_cz.duration is not None:
|
|
221
|
+
duration = 2 * props_move.duration + props_cz.duration
|
|
222
|
+
|
|
223
|
+
error: float | None = None
|
|
224
|
+
if props_move.error is not None and props_cz.error is not None:
|
|
225
|
+
error = 1 - (1 - props_move.error) ** 2 * (1 - props_cz.error)
|
|
226
|
+
|
|
227
|
+
return InstructionProperties(duration=duration, error=error)
|
|
228
|
+
|
|
229
|
+
@staticmethod
|
|
230
|
+
def _determine_fictional_cz_loci(move_loci: Iterable[Locus], cz_loci: Iterable[Locus]) -> list[Locus]:
|
|
231
|
+
"""Determine fictional CZ loci, i.e. pairs of qubits between which we can implement a CZ using MOVE gates and
|
|
232
|
+
an intermediate computational resonator.
|
|
233
|
+
|
|
234
|
+
Only applicable for the Star architecture.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
move_loci: The loci for which a MOVE operation is defined.
|
|
238
|
+
cz_loci: The loci for which a CZ operation is defined.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Loci for fictional CZ gates, *including the resonator*. Each locus is given as ``(q1, q2, res)``.
|
|
242
|
+
|
|
243
|
+
"""
|
|
244
|
+
fictional_cz_loci: list[Locus] = []
|
|
245
|
+
for q1, res1 in cz_loci:
|
|
246
|
+
for q2, res2 in move_loci:
|
|
247
|
+
if res1 == res2 and q1 != q2:
|
|
248
|
+
# shared resonator, different qubits
|
|
249
|
+
fictional_cz_loci.append((q1, q2, res1))
|
|
250
|
+
|
|
251
|
+
return fictional_cz_loci
|
|
252
|
+
|
|
253
|
+
def locus_to_idx(self, locus: Locus) -> LocusIdx:
|
|
254
|
+
"""Map the given locus to use component indices instead of component names."""
|
|
255
|
+
return tuple(self.iqm_component_to_idx[component] for component in locus)
|
|
256
|
+
|
|
257
|
+
@staticmethod
|
|
258
|
+
def _create_qubit_properties(
|
|
259
|
+
qubits: Iterable[str], metrics: ObservationFinder | None
|
|
260
|
+
) -> list[QubitProperties] | None:
|
|
261
|
+
"""Creates qubit properties from the quality metrics."""
|
|
262
|
+
if metrics is None:
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
qubit_props = []
|
|
266
|
+
t1_times, t2_times = metrics.get_coherence_times(qubits)
|
|
267
|
+
for q in qubits:
|
|
268
|
+
frequency = metrics.get_qubit_frequency(q)
|
|
269
|
+
qubit_props.append(
|
|
270
|
+
QubitProperties(
|
|
271
|
+
t1=t1_times.get(q),
|
|
272
|
+
t2=t2_times.get(q),
|
|
273
|
+
frequency=frequency,
|
|
274
|
+
)
|
|
275
|
+
)
|
|
276
|
+
return qubit_props
|
|
277
|
+
|
|
278
|
+
def _create_gate_properties(self, gate_name: str, locus: Locus) -> InstructionProperties | None:
|
|
279
|
+
"""Creates InstructionProperties for a single gate on a specific locus.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
gate_name: Name of the IQM native operation to look up properties for (e.g., 'cz' or 'move').
|
|
283
|
+
locus: Locus of the operation.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
Properties for the (default implementation of the) given gate at the given locus, or None if not available.
|
|
287
|
+
|
|
288
|
+
"""
|
|
289
|
+
if self.iqm_metrics is None:
|
|
290
|
+
return None
|
|
291
|
+
|
|
292
|
+
# the properties are for the default implementation (other implementations may be available also)
|
|
293
|
+
impl_name = self.iqm_dqa.gates[gate_name].get_default_implementation(locus)
|
|
294
|
+
duration = self.iqm_metrics.get_gate_duration(gate_name, impl_name, locus)
|
|
295
|
+
fidelity = self.iqm_metrics.get_gate_fidelity(gate_name, impl_name, locus)
|
|
296
|
+
return InstructionProperties(duration=duration, error=None if fidelity is None else 1 - fidelity)
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
def physical_qubits(self) -> list[str]:
|
|
300
|
+
"""Return the ordered list of physical qubits in the target."""
|
|
301
|
+
# Overrides the property from the superclass to contain the correct information.
|
|
302
|
+
return [self.iqm_idx_to_component[i] for i in range(self.num_qubits)]
|
|
303
|
+
|
|
304
|
+
def restrict_to_qubits(self, qubits: list[int] | list[str]) -> IQMTarget:
|
|
305
|
+
"""Generated a restricted transpilation target from this Target that only contains the given qubits.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
qubits: Qubits to restrict the target to. Can be either a list of qubit indices or qubit names.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
restricted target
|
|
312
|
+
|
|
313
|
+
"""
|
|
314
|
+
qubits_str = [self.iqm_idx_to_component[q] if isinstance(q, int) else str(q) for q in qubits]
|
|
315
|
+
return _restrict_dqa_to_qubits(
|
|
316
|
+
self.iqm_dqa,
|
|
317
|
+
qubits_str,
|
|
318
|
+
self.iqm_include_resonators,
|
|
319
|
+
self.iqm_include_fictional_czs,
|
|
320
|
+
metrics=self.iqm_metrics,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _restrict_dqa_to_qubits(
|
|
325
|
+
architecture: DynamicQuantumArchitecture,
|
|
326
|
+
components: list[str],
|
|
327
|
+
include_resonators: bool,
|
|
328
|
+
include_fictional_czs: bool,
|
|
329
|
+
metrics: ObservationFinder | None = None,
|
|
330
|
+
) -> IQMTarget:
|
|
331
|
+
"""Generated a restricted transpilation target that only contains the given QPU components.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
architecture: The dynamic quantum architecture to restrict.
|
|
335
|
+
components: QPU components to restrict the target to. Qubits first, then resonators.
|
|
336
|
+
include_resonators: Whether to include computational resonators (and MOVE gates) in the target.
|
|
337
|
+
include_fictional_czs: Whether to include fictional CZs that are not natively supported,
|
|
338
|
+
but could be routed via MOVE.
|
|
339
|
+
metrics: Optional calibration data and related quality metrics to improve the transpilation.
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
restricted target
|
|
343
|
+
|
|
344
|
+
"""
|
|
345
|
+
# include gate loci that only involve the given components
|
|
346
|
+
c_set = set(components)
|
|
347
|
+
new_gates = {}
|
|
348
|
+
for gate_name, gate_info in architecture.gates.items():
|
|
349
|
+
new_implementations = {}
|
|
350
|
+
for implementation_name, implementation_info in gate_info.implementations.items():
|
|
351
|
+
new_loci = tuple(locus for locus in implementation_info.loci if set(locus) <= c_set)
|
|
352
|
+
if new_loci:
|
|
353
|
+
new_implementations[implementation_name] = GateImplementationInfo(loci=new_loci)
|
|
354
|
+
if new_implementations:
|
|
355
|
+
new_gates[gate_name] = GateInfo(
|
|
356
|
+
implementations=new_implementations,
|
|
357
|
+
default_implementation=gate_info.default_implementation,
|
|
358
|
+
override_default_implementation=gate_info.override_default_implementation,
|
|
359
|
+
)
|
|
360
|
+
new_arch = DynamicQuantumArchitecture(
|
|
361
|
+
calibration_set_id=architecture.calibration_set_id,
|
|
362
|
+
qubits=[q for q in architecture.qubits if q in c_set],
|
|
363
|
+
computational_resonators=[r for r in architecture.computational_resonators if r in c_set],
|
|
364
|
+
gates=new_gates,
|
|
365
|
+
)
|
|
366
|
+
return IQMTarget(
|
|
367
|
+
architecture=new_arch,
|
|
368
|
+
component_to_idx={name: idx for idx, name in enumerate(components)},
|
|
369
|
+
include_resonators=include_resonators,
|
|
370
|
+
include_fictional_czs=include_fictional_czs,
|
|
371
|
+
metrics=metrics,
|
|
372
|
+
)
|
|
@@ -21,25 +21,26 @@ iqm/iqm_client/__init__.py,sha256=D-8W54EcQIxk_1JZo_86GYlR1YitHhPIiFwwLJ2IfGE,14
|
|
|
21
21
|
iqm/iqm_client/api.py,sha256=_c6OVuv2dyzBF7J2XlK_qxisTSPyOiI4gYokZPsuaJY,3083
|
|
22
22
|
iqm/iqm_client/authentication.py,sha256=kHFqPI6w3OAk9k5ioPxi-FrD2EP-vjn8Z_wZYccJVyE,12259
|
|
23
23
|
iqm/iqm_client/errors.py,sha256=ty2P-sg80zlAoL3_kC3PlprgDUv4PI-KFhmmxaaapS0,1429
|
|
24
|
-
iqm/iqm_client/iqm_client.py,sha256=
|
|
24
|
+
iqm/iqm_client/iqm_client.py,sha256=oEeosKvXd_lVreB0r7TwHnYs6AAOjJs1sUDNk-AtzAk,41793
|
|
25
25
|
iqm/iqm_client/models.py,sha256=Sdx_J7wBCM7E_arusU3eC6dQfqu5dpADywr-9JmFvsY,51597
|
|
26
26
|
iqm/iqm_client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
27
|
iqm/iqm_client/transpile.py,sha256=eEv9eY5QG94Lke7Xp6BkQapl1rvlmlVQ_IkQFopPNQ8,36981
|
|
28
|
-
iqm/iqm_client/util.py,sha256=
|
|
28
|
+
iqm/iqm_client/util.py,sha256=hGhl-r9afN3kNOjV9FoheSAPcQiKu7DdwX0FTFXxFl4,1489
|
|
29
29
|
iqm/iqm_client/validation.py,sha256=IKXBxnhLLnEuUCU2J0q6VEYn9k-O9z_MI4ys7mpFZdE,11912
|
|
30
30
|
iqm/iqm_client/cli/__init__.py,sha256=zzLDDz5rc3lJke3OKU8zxR5zQyQoM9oI2bLJ2YKk_zQ,692
|
|
31
|
-
iqm/iqm_client/cli/auth.py,sha256=
|
|
31
|
+
iqm/iqm_client/cli/auth.py,sha256=kESEK9-vpEhrjba3Lb6Wqx24yGfbjxUASeCArnVRYrw,6364
|
|
32
32
|
iqm/iqm_client/cli/cli.py,sha256=YhTgOGrvNXPwCdCKHcwLHeE_6C3n91MxFhvgQYT78C4,28633
|
|
33
33
|
iqm/iqm_client/cli/models.py,sha256=Hu-t6c_07Cth3AuQBo0CDTcWVQg1xbJCpy_94V0o64U,1199
|
|
34
|
-
iqm/iqm_client/cli/token_manager.py,sha256=
|
|
34
|
+
iqm/iqm_client/cli/token_manager.py,sha256=125uRj8kBzKlWAhQWNf-8n-aDG6fQridVd95qCktzD4,6867
|
|
35
35
|
iqm/qiskit_iqm/__init__.py,sha256=Mv9V_r8ZcmGC8Ke5S8-7yLOx02vjZ1qiVx8mtbOpwnY,1420
|
|
36
|
-
iqm/qiskit_iqm/iqm_backend.py,sha256=
|
|
36
|
+
iqm/qiskit_iqm/iqm_backend.py,sha256=HddizT6yHHq-MOG_U48n6ftE9AqmzaqbXYayEC1ljso,5548
|
|
37
37
|
iqm/qiskit_iqm/iqm_circuit.py,sha256=fFQW8SRlgZjqZUOLfyuJhhXEDp5I1jopFWa1k4rb7ac,1384
|
|
38
38
|
iqm/qiskit_iqm/iqm_circuit_validation.py,sha256=vE5CNyJOQ7OMRpQV-xsO1uf_NNFE8v6-TSzboFSrGYM,1721
|
|
39
39
|
iqm/qiskit_iqm/iqm_job.py,sha256=Q26hk4JuZP48Xw3qVk4b44LrHbgNQp-mq_itF9umkqg,11666
|
|
40
|
-
iqm/qiskit_iqm/iqm_move_layout.py,sha256=
|
|
41
|
-
iqm/qiskit_iqm/iqm_naive_move_pass.py,sha256=
|
|
42
|
-
iqm/qiskit_iqm/iqm_provider.py,sha256=
|
|
40
|
+
iqm/qiskit_iqm/iqm_move_layout.py,sha256=vIsuO-RUJF0ZxB7nPhfOJ2vaUt6mZCLZfQGc6PEBwnc,10486
|
|
41
|
+
iqm/qiskit_iqm/iqm_naive_move_pass.py,sha256=jhTfvhrNDKt6NhhJg_3Y-5x6E1HRNzC_n4A27ZQTuvQ,12962
|
|
42
|
+
iqm/qiskit_iqm/iqm_provider.py,sha256=AGXi2Knm035VTNr2lMgSr5oNAuhIHJqEWI6Z5c1L9Y8,17996
|
|
43
|
+
iqm/qiskit_iqm/iqm_target.py,sha256=fj18g2-kvHztCZxcc3tUeoc4XZvhy_EAkn-kD59D8vw,15816
|
|
43
44
|
iqm/qiskit_iqm/iqm_transpilation.py,sha256=2PyDAOukJpvSIVXmuYB8EWhmzlJHBUpzFPbW5VhfApg,9113
|
|
44
45
|
iqm/qiskit_iqm/move_gate.py,sha256=QU9RKKVvbGq33qcIi9AKLcvQVQMibkgW4ibjzk-oVy4,2808
|
|
45
46
|
iqm/qiskit_iqm/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -47,19 +48,19 @@ iqm/qiskit_iqm/qiskit_to_iqm.py,sha256=9JGcR_7K1Y5W6_PBP1bVCZqy7khCOa-BU9m1I9MJB
|
|
|
47
48
|
iqm/qiskit_iqm/transpiler_plugins.py,sha256=w0TXrAqtMZsPgGC1YcwiLvlBYVKDtpaCi_lnlLjTD2c,8717
|
|
48
49
|
iqm/qiskit_iqm/examples/__init__.py,sha256=M4ElQHCo-WxtVXK39bF3QiFT3IGXPtZ1khqexHiTBEc,20
|
|
49
50
|
iqm/qiskit_iqm/examples/bell_measure.py,sha256=iMZB_MNMf2XP6Eiv2XbhtNs4bXbMGQeMw7ohw2JWKS8,1903
|
|
50
|
-
iqm/qiskit_iqm/examples/resonance_example.py,sha256=
|
|
51
|
+
iqm/qiskit_iqm/examples/resonance_example.py,sha256=3eyHkmr6ZSLelSosjTSw1MLIOo3ioWhqBGRYXPFYRDA,2970
|
|
51
52
|
iqm/qiskit_iqm/examples/transpile_example.py,sha256=hUxGPD_eLaZFFHAbegcjT9lNq7UAvGCzHfZFnuF6cbE,2099
|
|
52
53
|
iqm/qiskit_iqm/fake_backends/__init__.py,sha256=fkw2UHT-3aJbAKvR1WYUN7_4N5Gdwpx9bm6vlWj1tm0,874
|
|
53
|
-
iqm/qiskit_iqm/fake_backends/fake_adonis.py,sha256=
|
|
54
|
+
iqm/qiskit_iqm/fake_backends/fake_adonis.py,sha256=g5QQ158vd49_SxzteDip97XMD3d50SJymx_rU7g7lzk,2188
|
|
54
55
|
iqm/qiskit_iqm/fake_backends/fake_aphrodite.py,sha256=3oWZwPmtcsYkYpqVr36-C5K7KtyQGk84RCvZpaL_kLg,15473
|
|
55
56
|
iqm/qiskit_iqm/fake_backends/fake_apollo.py,sha256=eT2vd3kQBi1rrvxCpePymBCfFK84dpwNpqGV_BkltwQ,6563
|
|
56
57
|
iqm/qiskit_iqm/fake_backends/fake_deneb.py,sha256=RzQXmLXmBARDiMKVxk5Aw9fVbc6IYlW0A5jibk9iYD0,3156
|
|
57
58
|
iqm/qiskit_iqm/fake_backends/fake_garnet.py,sha256=GI0xafTCj1Um09qVuccO6GPOGBm6ygul_O40Wu220Ys,5555
|
|
58
59
|
iqm/qiskit_iqm/fake_backends/iqm_fake_backend.py,sha256=wJtfsxjPYbDKmzaz5R4AuaXvvPHa21WyPtRgNctL9eY,16785
|
|
59
|
-
iqm_client-
|
|
60
|
-
iqm_client-
|
|
61
|
-
iqm_client-
|
|
62
|
-
iqm_client-
|
|
63
|
-
iqm_client-
|
|
64
|
-
iqm_client-
|
|
65
|
-
iqm_client-
|
|
60
|
+
iqm_client-30.0.0.dist-info/AUTHORS.rst,sha256=qsxeK5A3-B_xK3hNbhFHEIkoHNpo7sdzYyRTs7Bdtm8,795
|
|
61
|
+
iqm_client-30.0.0.dist-info/LICENSE.txt,sha256=2DXrmQtVVUV9Fc9RBFJidMiTEaQlG2oAtlC9PMrEwTk,11333
|
|
62
|
+
iqm_client-30.0.0.dist-info/METADATA,sha256=u9RkPSf_wx9QF8i5xPHP6KGq_V7uvP5KWfi2serE9q4,17692
|
|
63
|
+
iqm_client-30.0.0.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
|
|
64
|
+
iqm_client-30.0.0.dist-info/entry_points.txt,sha256=Kk2qfRwk8vbIJ7qCAvmaUogfRRn6t92_hBFhe6kqAE4,1317
|
|
65
|
+
iqm_client-30.0.0.dist-info/top_level.txt,sha256=NB4XRfyDS6_wG9gMsyX-9LTU7kWnTQxNvkbzIxGv3-c,4
|
|
66
|
+
iqm_client-30.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|