azure-quantum 3.4.1.dev0__py3-none-any.whl → 3.5.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.
- azure/quantum/_client/_client.py +0 -2
- azure/quantum/_client/_version.py +1 -1
- azure/quantum/_client/models/_enums.py +10 -0
- azure/quantum/_client/models/_models.py +4 -4
- azure/quantum/cirq/targets/ionq.py +5 -0
- azure/quantum/job/job.py +5 -4
- azure/quantum/qiskit/backends/_qiskit_ionq.py +377 -0
- azure/quantum/qiskit/backends/backend.py +370 -138
- azure/quantum/qiskit/backends/ionq.py +27 -34
- azure/quantum/qiskit/backends/qci.py +12 -14
- azure/quantum/qiskit/backends/quantinuum.py +46 -44
- azure/quantum/qiskit/backends/rigetti.py +10 -14
- azure/quantum/qiskit/job.py +21 -14
- azure/quantum/qiskit/provider.py +12 -6
- azure/quantum/target/pasqal/result.py +1 -1
- azure/quantum/target/rigetti/result.py +1 -1
- azure/quantum/target/target.py +4 -56
- azure/quantum/version.py +1 -1
- {azure_quantum-3.4.1.dev0.dist-info → azure_quantum-3.5.0.dist-info}/METADATA +5 -13
- {azure_quantum-3.4.1.dev0.dist-info → azure_quantum-3.5.0.dist-info}/RECORD +22 -21
- {azure_quantum-3.4.1.dev0.dist-info → azure_quantum-3.5.0.dist-info}/WHEEL +0 -0
- {azure_quantum-3.4.1.dev0.dist-info → azure_quantum-3.5.0.dist-info}/top_level.txt +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# Copyright (c) Microsoft Corporation.
|
|
3
3
|
# Licensed under the MIT License.
|
|
4
4
|
##
|
|
5
|
+
import copy
|
|
5
6
|
import os
|
|
6
7
|
import json
|
|
7
8
|
|
|
@@ -10,32 +11,51 @@ import warnings
|
|
|
10
11
|
|
|
11
12
|
logger = logging.getLogger(__name__)
|
|
12
13
|
|
|
13
|
-
from
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from functools import lru_cache
|
|
16
|
+
from typing import Any, Dict, Union, List, Optional, TYPE_CHECKING, Tuple, Mapping
|
|
14
17
|
from azure.quantum.version import __version__
|
|
15
18
|
from azure.quantum.qiskit.job import (
|
|
16
19
|
MICROSOFT_OUTPUT_DATA_FORMAT,
|
|
17
|
-
MICROSOFT_OUTPUT_DATA_FORMAT_V2,
|
|
18
20
|
AzureQuantumJob,
|
|
19
21
|
)
|
|
20
22
|
from abc import abstractmethod
|
|
21
23
|
from azure.quantum.job.session import SessionHost
|
|
22
24
|
|
|
25
|
+
BackendConfigurationType = None
|
|
26
|
+
QOBJ_TYPES: Tuple[type, ...] = tuple()
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from azure.quantum import Workspace
|
|
30
|
+
from azure.quantum.qiskit import AzureQuantumProvider
|
|
31
|
+
|
|
23
32
|
try:
|
|
24
33
|
from qiskit import QuantumCircuit
|
|
25
|
-
from qiskit.providers import
|
|
34
|
+
from qiskit.providers import BackendV2 as Backend
|
|
26
35
|
from qiskit.providers import Options
|
|
27
|
-
from qiskit.
|
|
28
|
-
from qiskit.
|
|
29
|
-
from qiskit.
|
|
30
|
-
import pyqir as pyqir
|
|
36
|
+
from qiskit.transpiler import Target
|
|
37
|
+
from qiskit.circuit import Instruction, Parameter
|
|
38
|
+
from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping
|
|
31
39
|
from qsharp.interop.qiskit import QSharpBackend
|
|
32
40
|
from qsharp import TargetProfile
|
|
33
|
-
|
|
34
|
-
except ImportError:
|
|
41
|
+
except ImportError as exc:
|
|
35
42
|
raise ImportError(
|
|
36
43
|
"Missing optional 'qiskit' dependencies. \
|
|
37
44
|
To install run: pip install azure-quantum[qiskit]"
|
|
38
|
-
)
|
|
45
|
+
) from exc
|
|
46
|
+
|
|
47
|
+
try: # Qiskit 1.x legacy support
|
|
48
|
+
from qiskit.providers.models import BackendConfiguration # type: ignore
|
|
49
|
+
BackendConfigurationType = BackendConfiguration
|
|
50
|
+
|
|
51
|
+
from qiskit.qobj import QasmQobj, PulseQobj # type: ignore
|
|
52
|
+
except ImportError: # Qiskit 2.0 removes qobj module
|
|
53
|
+
QasmQobj = None # type: ignore
|
|
54
|
+
PulseQobj = None # type: ignore
|
|
55
|
+
|
|
56
|
+
QOBJ_TYPES = tuple(
|
|
57
|
+
obj_type for obj_type in (QasmQobj, PulseQobj) if obj_type is not None
|
|
58
|
+
)
|
|
39
59
|
|
|
40
60
|
# barrier is handled by an extra flag which will transpile
|
|
41
61
|
# them away if the backend doesn't support them. This has
|
|
@@ -59,6 +79,7 @@ QIR_BASIS_GATES = [
|
|
|
59
79
|
"crz",
|
|
60
80
|
"h",
|
|
61
81
|
"s",
|
|
82
|
+
"sx",
|
|
62
83
|
"sdg",
|
|
63
84
|
"swap",
|
|
64
85
|
"t",
|
|
@@ -71,6 +92,200 @@ QIR_BASIS_GATES = [
|
|
|
71
92
|
]
|
|
72
93
|
|
|
73
94
|
|
|
95
|
+
@lru_cache(maxsize=None)
|
|
96
|
+
def _standard_gate_map() -> Dict[str, Instruction]:
|
|
97
|
+
mapping = get_standard_gate_name_mapping()
|
|
98
|
+
# Include both canonical and lowercase keys for easier lookup
|
|
99
|
+
lowered = {name.lower(): gate for name, gate in mapping.items()}
|
|
100
|
+
combined = {**mapping, **lowered}
|
|
101
|
+
return combined
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _custom_instruction_builders() -> Dict[str, Instruction]:
|
|
105
|
+
"""Provide Instruction stubs for backend-specific gates.
|
|
106
|
+
|
|
107
|
+
Azure Quantum targets expose native operations (for example, IonQ's
|
|
108
|
+
GPI-family gates or Quantinuum's multi-controlled primitives) that are not
|
|
109
|
+
part of Qiskit's standard gate catalogue. When we build a Target instance we
|
|
110
|
+
still need Instruction objects for these names so transpilation and circuit
|
|
111
|
+
validation can succeed. This helper returns lightweight Instruction
|
|
112
|
+
definitions that mirror each provider's gate signatures, ensuring
|
|
113
|
+
``Target.add_instruction`` has the metadata it requires even though the
|
|
114
|
+
operations themselves are executed remotely.
|
|
115
|
+
"""
|
|
116
|
+
param = Parameter
|
|
117
|
+
return {
|
|
118
|
+
"gpi": Instruction("gpi", 1, 0, params=[param("phi")]),
|
|
119
|
+
"gpi2": Instruction("gpi2", 1, 0, params=[param("phi")]),
|
|
120
|
+
"ms": Instruction(
|
|
121
|
+
"ms",
|
|
122
|
+
2,
|
|
123
|
+
0,
|
|
124
|
+
params=[param("phi0"), param("phi1"), param("angle")],
|
|
125
|
+
),
|
|
126
|
+
"zz": Instruction("zz", 2, 0, params=[param("angle")]),
|
|
127
|
+
"v": Instruction("v", 1, 0, params=[]),
|
|
128
|
+
"vdg": Instruction("vdg", 1, 0, params=[]),
|
|
129
|
+
"vi": Instruction("vi", 1, 0, params=[]),
|
|
130
|
+
"si": Instruction("si", 1, 0, params=[]),
|
|
131
|
+
"ti": Instruction("ti", 1, 0, params=[]),
|
|
132
|
+
"mcp": Instruction("mcp", 3, 0, params=[param("angle")]),
|
|
133
|
+
"mcphase": Instruction("mcphase", 3, 0, params=[param("angle")]),
|
|
134
|
+
"mct": Instruction("mct", 3, 0, params=[]),
|
|
135
|
+
"mcx": Instruction("mcx", 3, 0, params=[]),
|
|
136
|
+
"mcx_gray": Instruction("mcx_gray", 3, 0, params=[]),
|
|
137
|
+
"pauliexp": Instruction("pauliexp", 1, 0, params=[param("time")]),
|
|
138
|
+
"paulievolution": Instruction("PauliEvolution", 1, 0, params=[param("time")]),
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _resolve_instruction(gate_name: str) -> Optional[Instruction]:
|
|
143
|
+
mapping = _standard_gate_map()
|
|
144
|
+
instruction = mapping.get(gate_name)
|
|
145
|
+
if instruction is not None:
|
|
146
|
+
return instruction.copy()
|
|
147
|
+
|
|
148
|
+
lower_name = gate_name.lower()
|
|
149
|
+
instruction = mapping.get(lower_name)
|
|
150
|
+
if instruction is not None:
|
|
151
|
+
return instruction.copy()
|
|
152
|
+
|
|
153
|
+
custom_map = _custom_instruction_builders()
|
|
154
|
+
if gate_name in custom_map:
|
|
155
|
+
return custom_map[gate_name]
|
|
156
|
+
if lower_name in custom_map:
|
|
157
|
+
return custom_map[lower_name]
|
|
158
|
+
|
|
159
|
+
# Default to a single-qubit placeholder instruction.
|
|
160
|
+
return Instruction(gate_name, 1, 0, params=[])
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@dataclass
|
|
164
|
+
class AzureBackendConfig:
|
|
165
|
+
"""Lightweight configuration container for Azure Quantum backends."""
|
|
166
|
+
|
|
167
|
+
backend_name: Optional[str] = None
|
|
168
|
+
backend_version: Optional[str] = None
|
|
169
|
+
description: Optional[str] = None
|
|
170
|
+
n_qubits: Optional[int] = None
|
|
171
|
+
dt: Optional[float] = None
|
|
172
|
+
basis_gates: Tuple[str, ...] = field(default_factory=tuple)
|
|
173
|
+
azure: Dict[str, Any] = field(default_factory=dict)
|
|
174
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
175
|
+
|
|
176
|
+
def __post_init__(self) -> None:
|
|
177
|
+
self.azure = copy.deepcopy(self.azure or {})
|
|
178
|
+
self.metadata = dict(self.metadata or {})
|
|
179
|
+
if self.basis_gates is None:
|
|
180
|
+
self.basis_gates = tuple()
|
|
181
|
+
else:
|
|
182
|
+
self.basis_gates = tuple(self.basis_gates)
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def name(self) -> Optional[str]:
|
|
186
|
+
return self.backend_name
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def num_qubits(self) -> Optional[int]:
|
|
190
|
+
"""Backward-compatible alias for Qiskit's ``BackendConfiguration.num_qubits``."""
|
|
191
|
+
return self.n_qubits
|
|
192
|
+
|
|
193
|
+
@num_qubits.setter
|
|
194
|
+
def num_qubits(self, value: Optional[int]) -> None:
|
|
195
|
+
self.n_qubits = value
|
|
196
|
+
|
|
197
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
198
|
+
if key == "basis_gates":
|
|
199
|
+
return list(self.basis_gates)
|
|
200
|
+
if key == "azure":
|
|
201
|
+
return copy.deepcopy(self.azure)
|
|
202
|
+
if hasattr(self, key):
|
|
203
|
+
return getattr(self, key)
|
|
204
|
+
return self.metadata.get(key, default)
|
|
205
|
+
|
|
206
|
+
def __getattr__(self, name: str) -> Any:
|
|
207
|
+
if name == "max_experiments":
|
|
208
|
+
return 1
|
|
209
|
+
try:
|
|
210
|
+
return self.__dict__[name]
|
|
211
|
+
except KeyError as exc:
|
|
212
|
+
if name in self.metadata:
|
|
213
|
+
return self.metadata[name]
|
|
214
|
+
raise AttributeError(
|
|
215
|
+
f"'{type(self).__name__}' object has no attribute '{name}'"
|
|
216
|
+
) from exc
|
|
217
|
+
|
|
218
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
219
|
+
config_dict: Dict[str, Any] = {
|
|
220
|
+
"backend_name": self.backend_name,
|
|
221
|
+
"backend_version": self.backend_version,
|
|
222
|
+
"description": self.description,
|
|
223
|
+
"max_experiments": 1,
|
|
224
|
+
"n_qubits": self.n_qubits,
|
|
225
|
+
"dt": self.dt,
|
|
226
|
+
"basis_gates": list(self.basis_gates),
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
config_dict.update(self.metadata)
|
|
230
|
+
|
|
231
|
+
if self.azure:
|
|
232
|
+
config_dict["azure"] = copy.deepcopy(self.azure)
|
|
233
|
+
|
|
234
|
+
return config_dict
|
|
235
|
+
|
|
236
|
+
@classmethod
|
|
237
|
+
def from_dict(cls, data: Mapping[str, Any]) -> "AzureBackendConfig":
|
|
238
|
+
raw = dict(data)
|
|
239
|
+
azure_config = copy.deepcopy(raw.get("azure", {}))
|
|
240
|
+
basis_gates = raw.get("basis_gates") or []
|
|
241
|
+
|
|
242
|
+
known_keys = {
|
|
243
|
+
"backend_name",
|
|
244
|
+
"backend_version",
|
|
245
|
+
"description",
|
|
246
|
+
"n_qubits",
|
|
247
|
+
"dt",
|
|
248
|
+
"basis_gates",
|
|
249
|
+
"azure",
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
metadata = {k: v for k, v in raw.items() if k not in known_keys}
|
|
253
|
+
|
|
254
|
+
return cls(
|
|
255
|
+
backend_name=raw.get("backend_name"),
|
|
256
|
+
backend_version=raw.get("backend_version"),
|
|
257
|
+
description=raw.get("description"),
|
|
258
|
+
n_qubits=raw.get("n_qubits"),
|
|
259
|
+
dt=raw.get("dt"),
|
|
260
|
+
basis_gates=tuple(basis_gates),
|
|
261
|
+
azure=azure_config,
|
|
262
|
+
metadata=metadata,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
@classmethod
|
|
266
|
+
def from_backend_configuration(
|
|
267
|
+
cls, configuration: Any
|
|
268
|
+
) -> "AzureBackendConfig":
|
|
269
|
+
return cls.from_dict(configuration.to_dict())
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _ensure_backend_config(
|
|
273
|
+
configuration: Any
|
|
274
|
+
) -> AzureBackendConfig:
|
|
275
|
+
if isinstance(configuration, AzureBackendConfig):
|
|
276
|
+
return configuration
|
|
277
|
+
|
|
278
|
+
if BackendConfigurationType is not None and isinstance(
|
|
279
|
+
configuration, BackendConfigurationType
|
|
280
|
+
):
|
|
281
|
+
return AzureBackendConfig.from_backend_configuration(configuration)
|
|
282
|
+
|
|
283
|
+
if isinstance(configuration, Mapping):
|
|
284
|
+
return AzureBackendConfig.from_dict(configuration)
|
|
285
|
+
|
|
286
|
+
raise TypeError("Unsupported configuration type for Azure backends")
|
|
287
|
+
|
|
288
|
+
|
|
74
289
|
class AzureBackendBase(Backend, SessionHost):
|
|
75
290
|
|
|
76
291
|
# Name of the provider's input parameter which specifies number of shots for a submitted job.
|
|
@@ -80,11 +295,50 @@ class AzureBackendBase(Backend, SessionHost):
|
|
|
80
295
|
@abstractmethod
|
|
81
296
|
def __init__(
|
|
82
297
|
self,
|
|
83
|
-
configuration:
|
|
84
|
-
provider:
|
|
298
|
+
configuration: Any,
|
|
299
|
+
provider: "AzureQuantumProvider" = None,
|
|
85
300
|
**fields
|
|
86
301
|
):
|
|
87
|
-
|
|
302
|
+
if configuration is None:
|
|
303
|
+
raise ValueError("Backend configuration is required for Azure backends")
|
|
304
|
+
|
|
305
|
+
if BackendConfigurationType is not None and isinstance(
|
|
306
|
+
configuration, BackendConfigurationType
|
|
307
|
+
):
|
|
308
|
+
warnings.warn(
|
|
309
|
+
"The BackendConfiguration parameter is deprecated and will be removed from the SDK.",
|
|
310
|
+
DeprecationWarning,
|
|
311
|
+
stacklevel=2,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
config = _ensure_backend_config(configuration)
|
|
315
|
+
|
|
316
|
+
self._config = config
|
|
317
|
+
|
|
318
|
+
super().__init__(
|
|
319
|
+
provider=provider,
|
|
320
|
+
name=config.backend_name,
|
|
321
|
+
description=config.description,
|
|
322
|
+
backend_version=config.backend_version,
|
|
323
|
+
**fields,
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
self._target = self._build_target(config)
|
|
327
|
+
|
|
328
|
+
def _build_target(self, configuration: AzureBackendConfig) -> Target:
|
|
329
|
+
num_qubits = configuration.n_qubits
|
|
330
|
+
target = Target(
|
|
331
|
+
description=configuration.description,
|
|
332
|
+
num_qubits=num_qubits,
|
|
333
|
+
dt=configuration.dt,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
basis_gates: List[str] = list(configuration.basis_gates or [])
|
|
337
|
+
for gate_name in dict.fromkeys(basis_gates + ["measure", "reset"]):
|
|
338
|
+
instruction = _resolve_instruction(gate_name)
|
|
339
|
+
target.add_instruction(instruction)
|
|
340
|
+
|
|
341
|
+
return target
|
|
88
342
|
|
|
89
343
|
@abstractmethod
|
|
90
344
|
def run(
|
|
@@ -101,7 +355,7 @@ class AzureBackendBase(Backend, SessionHost):
|
|
|
101
355
|
|
|
102
356
|
Args:
|
|
103
357
|
run_input (QuantumCircuit or List[QuantumCircuit]): An individual or a
|
|
104
|
-
list of :class:`~qiskit.circuits.QuantumCircuit` to run on the backend.
|
|
358
|
+
list of one :class:`~qiskit.circuits.QuantumCircuit` to run on the backend.
|
|
105
359
|
shots (int, optional): Number of shots, defaults to None.
|
|
106
360
|
options: Any kwarg options to pass to the backend for running the
|
|
107
361
|
config. If a key is also present in the options
|
|
@@ -130,25 +384,32 @@ class AzureBackendBase(Backend, SessionHost):
|
|
|
130
384
|
def _azure_config(self) -> Dict[str, str]:
|
|
131
385
|
pass
|
|
132
386
|
|
|
387
|
+
def configuration(self) -> AzureBackendConfig:
|
|
388
|
+
warnings.warn(
|
|
389
|
+
"AzureBackendBase.configuration() is deprecated and will be removed from the SDK.",
|
|
390
|
+
DeprecationWarning,
|
|
391
|
+
stacklevel=2,
|
|
392
|
+
)
|
|
393
|
+
return self._config
|
|
394
|
+
|
|
395
|
+
@property
|
|
396
|
+
def target(self) -> Target:
|
|
397
|
+
return self._target
|
|
398
|
+
|
|
399
|
+
@property
|
|
400
|
+
def max_circuits(self) -> Optional[int]:
|
|
401
|
+
return 1
|
|
133
402
|
def retrieve_job(self, job_id) -> AzureQuantumJob:
|
|
134
403
|
"""Returns the Job instance associated with the given id."""
|
|
135
|
-
return self.
|
|
404
|
+
return self.provider.get_job(job_id)
|
|
136
405
|
|
|
137
406
|
def _get_output_data_format(self, options: Dict[str, Any] = {}) -> str:
|
|
138
|
-
config:
|
|
139
|
-
# output data format default depends on the number of experiments. QIR backends
|
|
140
|
-
# that don't define a default in their azure config will use this value
|
|
141
|
-
# Once more than one experiment is supported, we should always use the v2 format
|
|
142
|
-
default_output_data_format = (
|
|
143
|
-
MICROSOFT_OUTPUT_DATA_FORMAT
|
|
144
|
-
if config.max_experiments == 1
|
|
145
|
-
else MICROSOFT_OUTPUT_DATA_FORMAT_V2
|
|
146
|
-
)
|
|
407
|
+
config: AzureBackendConfig = self._config
|
|
147
408
|
|
|
148
|
-
azure_config: Dict[str, Any] = config.azure
|
|
409
|
+
azure_config: Dict[str, Any] = config.azure or {}
|
|
149
410
|
# if the backend defines an output format, use that over the default
|
|
150
411
|
azure_defined_override = azure_config.get(
|
|
151
|
-
"output_data_format",
|
|
412
|
+
"output_data_format", MICROSOFT_OUTPUT_DATA_FORMAT
|
|
152
413
|
)
|
|
153
414
|
# if the user specifies an output format, use that over the default azure config
|
|
154
415
|
output_data_format = options.pop("output_data_format", azure_defined_override)
|
|
@@ -171,7 +432,8 @@ class AzureBackendBase(Backend, SessionHost):
|
|
|
171
432
|
if shots is not None and options_shots is not None:
|
|
172
433
|
warnings.warn(
|
|
173
434
|
f"Parameter 'shots' conflicts with the '{self.__class__._SHOTS_PARAM_NAME}' parameter. "
|
|
174
|
-
"Please, provide only one option for setting shots. Defaulting to 'shots' parameter."
|
|
435
|
+
"Please, provide only one option for setting shots. Defaulting to 'shots' parameter.",
|
|
436
|
+
stacklevel=3,
|
|
175
437
|
)
|
|
176
438
|
final_shots = shots
|
|
177
439
|
|
|
@@ -180,7 +442,8 @@ class AzureBackendBase(Backend, SessionHost):
|
|
|
180
442
|
elif options_shots is not None:
|
|
181
443
|
warnings.warn(
|
|
182
444
|
f"Parameter '{self.__class__._SHOTS_PARAM_NAME}' is subject to change in future versions. "
|
|
183
|
-
"Please, use 'shots' parameter instead."
|
|
445
|
+
"Please, use 'shots' parameter instead.",
|
|
446
|
+
stacklevel=3,
|
|
184
447
|
)
|
|
185
448
|
final_shots = options_shots
|
|
186
449
|
|
|
@@ -213,15 +476,16 @@ class AzureBackendBase(Backend, SessionHost):
|
|
|
213
476
|
return input_params
|
|
214
477
|
|
|
215
478
|
def _run(self, job_name, input_data, input_params, metadata, **options):
|
|
216
|
-
logger.info(f"Submitting new job for backend {self.name
|
|
479
|
+
logger.info(f"Submitting new job for backend {self.name}")
|
|
217
480
|
|
|
218
481
|
# The default of these job parameters come from the AzureBackend configuration:
|
|
219
|
-
config = self.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
482
|
+
config = self._config
|
|
483
|
+
azure_config = config.azure or {}
|
|
484
|
+
blob_name = options.pop("blob_name", azure_config.get("blob_name"))
|
|
485
|
+
content_type = options.pop("content_type", azure_config.get("content_type"))
|
|
486
|
+
provider_id = options.pop("provider_id", azure_config.get("provider_id"))
|
|
223
487
|
input_data_format = options.pop(
|
|
224
|
-
"input_data_format",
|
|
488
|
+
"input_data_format", azure_config.get("input_data_format")
|
|
225
489
|
)
|
|
226
490
|
output_data_format = self._get_output_data_format(options)
|
|
227
491
|
|
|
@@ -238,13 +502,22 @@ class AzureBackendBase(Backend, SessionHost):
|
|
|
238
502
|
message += "To find a QIR capable backend, use the following code:"
|
|
239
503
|
message += os.linesep
|
|
240
504
|
message += (
|
|
241
|
-
f'\tprovider.get_backend("{self.name
|
|
505
|
+
f'\tprovider.get_backend("{self.name}", input_data_format: "qir.v1").'
|
|
242
506
|
)
|
|
243
507
|
raise ValueError(message)
|
|
244
508
|
|
|
509
|
+
|
|
510
|
+
# Update metadata with all remaining options values, then clear options
|
|
511
|
+
# JobDetails model will error if unknown keys are passed down which
|
|
512
|
+
# can happen with estiamtor and backend wrappers
|
|
513
|
+
if metadata is None:
|
|
514
|
+
metadata = {}
|
|
515
|
+
metadata.update(options)
|
|
516
|
+
options.clear()
|
|
517
|
+
|
|
245
518
|
job = AzureQuantumJob(
|
|
246
519
|
backend=self,
|
|
247
|
-
target=self.name
|
|
520
|
+
target=self.name,
|
|
248
521
|
name=job_name,
|
|
249
522
|
input_data=input_data,
|
|
250
523
|
blob_name=blob_name,
|
|
@@ -292,10 +565,10 @@ class AzureBackendBase(Backend, SessionHost):
|
|
|
292
565
|
raise ValueError("No input provided.")
|
|
293
566
|
|
|
294
567
|
def _get_azure_workspace(self) -> "Workspace":
|
|
295
|
-
return self.provider
|
|
568
|
+
return self.provider.get_workspace()
|
|
296
569
|
|
|
297
570
|
def _get_azure_target_id(self) -> str:
|
|
298
|
-
return self.name
|
|
571
|
+
return self.name
|
|
299
572
|
|
|
300
573
|
def _get_azure_provider_id(self) -> str:
|
|
301
574
|
return self._azure_config()["provider_id"]
|
|
@@ -304,7 +577,10 @@ class AzureBackendBase(Backend, SessionHost):
|
|
|
304
577
|
class AzureQirBackend(AzureBackendBase):
|
|
305
578
|
@abstractmethod
|
|
306
579
|
def __init__(
|
|
307
|
-
self,
|
|
580
|
+
self,
|
|
581
|
+
configuration: AzureBackendConfig,
|
|
582
|
+
provider: "AzureQuantumProvider" = None,
|
|
583
|
+
**fields,
|
|
308
584
|
):
|
|
309
585
|
super().__init__(configuration, provider, **fields)
|
|
310
586
|
|
|
@@ -334,7 +610,7 @@ class AzureQirBackend(AzureBackendBase):
|
|
|
334
610
|
|
|
335
611
|
Args:
|
|
336
612
|
run_input (QuantumCircuit or List[QuantumCircuit]): An individual or a
|
|
337
|
-
list of :class:`~qiskit.circuits.QuantumCircuit` to run on the backend.
|
|
613
|
+
list of one :class:`~qiskit.circuits.QuantumCircuit` to run on the backend.
|
|
338
614
|
shots (int, optional): Number of shots, defaults to None.
|
|
339
615
|
options: Any kwarg options to pass to the backend for running the
|
|
340
616
|
config. If a key is also present in the options
|
|
@@ -349,17 +625,16 @@ class AzureQirBackend(AzureBackendBase):
|
|
|
349
625
|
options.pop("run_input", None)
|
|
350
626
|
options.pop("circuit", None)
|
|
351
627
|
|
|
352
|
-
|
|
353
|
-
if isinstance(run_input,
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
)
|
|
628
|
+
circuit = run_input
|
|
629
|
+
if isinstance(run_input, list):
|
|
630
|
+
# just in case they passed a list, we only support single-experiment jobs
|
|
631
|
+
if len(run_input) != 1:
|
|
632
|
+
raise NotImplementedError(
|
|
633
|
+
f"This backend only supports running a single circuit per job."
|
|
634
|
+
)
|
|
635
|
+
circuit = run_input[0]
|
|
636
|
+
if not isinstance(circuit, QuantumCircuit):
|
|
637
|
+
raise ValueError("Invalid input: expected a QuantumCircuit.")
|
|
363
638
|
|
|
364
639
|
# config normalization
|
|
365
640
|
input_params = self._get_input_params(options, shots=shots)
|
|
@@ -369,53 +644,38 @@ class AzureQirBackend(AzureBackendBase):
|
|
|
369
644
|
if self._can_send_shots_input_param():
|
|
370
645
|
shots_count = input_params.get(self.__class__._SHOTS_PARAM_NAME)
|
|
371
646
|
|
|
372
|
-
job_name = ""
|
|
373
|
-
if len(circuits) > 1:
|
|
374
|
-
job_name = f"batch-{len(circuits)}"
|
|
375
|
-
if shots_count is not None:
|
|
376
|
-
job_name = f"{job_name}-{shots_count}"
|
|
377
|
-
else:
|
|
378
|
-
job_name = circuits[0].name
|
|
379
|
-
job_name = options.pop("job_name", job_name)
|
|
647
|
+
job_name = options.pop("job_name", circuit.name)
|
|
380
648
|
|
|
381
|
-
metadata = options.pop("metadata", self._prepare_job_metadata(
|
|
649
|
+
metadata = options.pop("metadata", self._prepare_job_metadata(circuit))
|
|
382
650
|
|
|
383
|
-
input_data = self._translate_input(
|
|
651
|
+
input_data = self._translate_input(circuit, input_params)
|
|
384
652
|
|
|
385
653
|
job = super()._run(job_name, input_data, input_params, metadata, **options)
|
|
386
654
|
logger.info(
|
|
387
|
-
|
|
655
|
+
"Submitted job with id '%s' with shot count of %s:",
|
|
656
|
+
job.id(),
|
|
657
|
+
shots_count,
|
|
388
658
|
)
|
|
389
659
|
|
|
390
660
|
return job
|
|
391
661
|
|
|
392
|
-
def _prepare_job_metadata(self,
|
|
662
|
+
def _prepare_job_metadata(self, circuit: QuantumCircuit) -> Dict[str, str]:
|
|
393
663
|
"""Returns the metadata relative to the given circuits that will be attached to the Job"""
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
return {}
|
|
407
|
-
|
|
408
|
-
def _generate_qir(
|
|
409
|
-
self, circuits: List[QuantumCircuit], target_profile: TargetProfile, **kwargs
|
|
410
|
-
) -> pyqir.Module:
|
|
411
|
-
|
|
412
|
-
if len(circuits) == 0:
|
|
413
|
-
raise ValueError("No QuantumCircuits provided")
|
|
414
|
-
|
|
415
|
-
config = self.configuration()
|
|
664
|
+
return {
|
|
665
|
+
"qiskit": str(True),
|
|
666
|
+
"name": circuit.name,
|
|
667
|
+
"num_qubits": circuit.num_qubits,
|
|
668
|
+
"metadata": json.dumps(circuit.metadata),
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
def _get_qir_str(
|
|
673
|
+
self, circuit: QuantumCircuit, target_profile: TargetProfile, **kwargs
|
|
674
|
+
) -> str:
|
|
675
|
+
config = self._config
|
|
416
676
|
# Barriers aren't removed by transpilation and must be explicitly removed in the Qiskit to QIR translation.
|
|
417
677
|
supports_barrier = "barrier" in config.basis_gates
|
|
418
|
-
skip_transpilation = kwargs.pop("skip_transpilation",
|
|
678
|
+
skip_transpilation = kwargs.pop("skip_transpilation", True)
|
|
419
679
|
|
|
420
680
|
backend = QSharpBackend(
|
|
421
681
|
qiskit_pass_options={"supports_barrier": supports_barrier},
|
|
@@ -424,73 +684,42 @@ class AzureQirBackend(AzureBackendBase):
|
|
|
424
684
|
**kwargs,
|
|
425
685
|
)
|
|
426
686
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
687
|
+
qir_str = backend.qir(circuit)
|
|
688
|
+
|
|
689
|
+
return qir_str
|
|
430
690
|
|
|
431
|
-
if isinstance(circuits, list):
|
|
432
|
-
for value in circuits:
|
|
433
|
-
if not isinstance(value, QuantumCircuit):
|
|
434
|
-
raise ValueError("Input must be List[QuantumCircuit]")
|
|
435
|
-
else:
|
|
436
|
-
raise ValueError("Input must be List[QuantumCircuit]")
|
|
437
|
-
|
|
438
|
-
context = pyqir.Context()
|
|
439
|
-
llvm_module = pyqir.qir_module(context, name)
|
|
440
|
-
for circuit in circuits:
|
|
441
|
-
qir_str = backend.qir(circuit)
|
|
442
|
-
module = pyqir.Module.from_ir(context, qir_str)
|
|
443
|
-
entry_point = next(filter(pyqir.is_entry_point, module.functions))
|
|
444
|
-
entry_point.name = circuit.name
|
|
445
|
-
llvm_module.link(module)
|
|
446
|
-
err = llvm_module.verify()
|
|
447
|
-
if err is not None:
|
|
448
|
-
raise Exception(err)
|
|
449
|
-
|
|
450
|
-
return llvm_module
|
|
451
|
-
|
|
452
|
-
def _get_qir_str(
|
|
453
|
-
self,
|
|
454
|
-
circuits: List[QuantumCircuit],
|
|
455
|
-
target_profile: TargetProfile,
|
|
456
|
-
**to_qir_kwargs,
|
|
457
|
-
) -> str:
|
|
458
|
-
module = self._generate_qir(circuits, target_profile, **to_qir_kwargs)
|
|
459
|
-
return str(module)
|
|
460
691
|
|
|
461
692
|
def _translate_input(
|
|
462
|
-
self,
|
|
693
|
+
self, circuit: QuantumCircuit, input_params: Dict[str, Any]
|
|
463
694
|
) -> bytes:
|
|
464
695
|
"""Translates the input values to the QIR expected by the Backend."""
|
|
465
696
|
logger.info(f"Using QIR as the job's payload format.")
|
|
466
|
-
if not (isinstance(circuits, list)):
|
|
467
|
-
circuits = [circuits]
|
|
468
697
|
|
|
469
698
|
target_profile = self._get_target_profile(input_params)
|
|
470
699
|
|
|
471
700
|
if logger.isEnabledFor(logging.DEBUG):
|
|
472
|
-
qir = self._get_qir_str(
|
|
701
|
+
qir = self._get_qir_str(circuit, target_profile, skip_transpilation=True)
|
|
473
702
|
logger.debug(f"QIR:\n{qir}")
|
|
474
703
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
704
|
+
skip_transpilation = input_params.pop("skipTranspile", True)
|
|
705
|
+
if not skip_transpilation:
|
|
706
|
+
warnings.warn(
|
|
707
|
+
"Transpilation is deprecated and will be removed in future versions. "
|
|
708
|
+
"Please, transpile circuits manually before passing them to the backend.",
|
|
709
|
+
category=DeprecationWarning,
|
|
710
|
+
stacklevel=3,
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
qir_str = self._get_qir_str(
|
|
714
|
+
circuit, target_profile, skip_transpilation=skip_transpilation
|
|
480
715
|
)
|
|
481
716
|
|
|
482
|
-
|
|
483
|
-
return func.name
|
|
484
|
-
|
|
485
|
-
entry_points = list(
|
|
486
|
-
map(get_func_name, filter(pyqir.is_entry_point, module.functions))
|
|
487
|
-
)
|
|
717
|
+
entry_points = ["ENTRYPOINT__main"]
|
|
488
718
|
|
|
489
719
|
if not skip_transpilation:
|
|
490
720
|
# We'll only log the QIR again if we performed a transpilation.
|
|
491
721
|
if logger.isEnabledFor(logging.DEBUG):
|
|
492
|
-
|
|
493
|
-
logger.debug(f"QIR (Post-transpilation):\n{qir}")
|
|
722
|
+
logger.debug(f"QIR (Post-transpilation):\n{qir_str}")
|
|
494
723
|
|
|
495
724
|
if "items" not in input_params:
|
|
496
725
|
arguments = input_params.pop("arguments", [])
|
|
@@ -498,7 +727,7 @@ class AzureQirBackend(AzureBackendBase):
|
|
|
498
727
|
{"entryPoint": name, "arguments": arguments} for name in entry_points
|
|
499
728
|
]
|
|
500
729
|
|
|
501
|
-
return
|
|
730
|
+
return qir_str.encode("utf-8")
|
|
502
731
|
|
|
503
732
|
def _get_target_profile(self, input_params) -> TargetProfile:
|
|
504
733
|
# Default to Adaptive_RI if not specified on the backend
|
|
@@ -528,7 +757,10 @@ class AzureBackend(AzureBackendBase):
|
|
|
528
757
|
|
|
529
758
|
@abstractmethod
|
|
530
759
|
def __init__(
|
|
531
|
-
self,
|
|
760
|
+
self,
|
|
761
|
+
configuration: AzureBackendConfig,
|
|
762
|
+
provider: "AzureQuantumProvider" = None,
|
|
763
|
+
**fields,
|
|
532
764
|
):
|
|
533
765
|
super().__init__(configuration, provider, **fields)
|
|
534
766
|
|
|
@@ -567,7 +799,7 @@ class AzureBackend(AzureBackendBase):
|
|
|
567
799
|
|
|
568
800
|
# If the circuit was created using qiskit.assemble,
|
|
569
801
|
# disassemble into QASM here
|
|
570
|
-
if
|
|
802
|
+
if QOBJ_TYPES and isinstance(circuit, QOBJ_TYPES):
|
|
571
803
|
from qiskit.assembler import disassemble
|
|
572
804
|
|
|
573
805
|
circuits, run, _ = disassemble(circuit)
|