azure-quantum 0.29.2__py3-none-any.whl → 1.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.
- azure/quantum/_client/_version.py +1 -1
- azure/quantum/cirq/service.py +7 -0
- azure/quantum/cirq/targets/quantinuum.py +1 -1
- azure/quantum/job/__init__.py +1 -0
- azure/quantum/job/base_job.py +41 -15
- azure/quantum/job/job.py +35 -1
- azure/quantum/job/job_failed_with_results_error.py +41 -0
- azure/quantum/qiskit/backends/backend.py +130 -35
- azure/quantum/qiskit/backends/ionq.py +65 -5
- azure/quantum/qiskit/backends/qci.py +35 -2
- azure/quantum/qiskit/backends/quantinuum.py +25 -4
- azure/quantum/qiskit/backends/rigetti.py +8 -1
- azure/quantum/qiskit/job.py +7 -16
- azure/quantum/qiskit/provider.py +18 -2
- azure/quantum/storage.py +2 -1
- azure/quantum/target/__init__.py +1 -0
- azure/quantum/target/ionq.py +37 -12
- azure/quantum/target/microsoft/elements/dft/__init__.py +4 -0
- azure/quantum/target/microsoft/elements/dft/job.py +46 -0
- azure/quantum/target/microsoft/elements/dft/target.py +66 -0
- azure/quantum/target/microsoft/target.py +36 -9
- azure/quantum/target/params.py +1 -1
- azure/quantum/target/pasqal/target.py +16 -2
- azure/quantum/target/quantinuum.py +34 -9
- azure/quantum/target/rigetti/target.py +21 -3
- azure/quantum/target/solvers.py +7 -1
- azure/quantum/target/target.py +82 -0
- azure/quantum/target/target_factory.py +0 -2
- azure/quantum/version.py +1 -1
- azure/quantum/workspace.py +11 -8
- {azure_quantum-0.29.2.dist-info → azure_quantum-1.0.0.dist-info}/METADATA +3 -5
- azure_quantum-1.0.0.dist-info/RECORD +86 -0
- azure/quantum/_client/aio/__init__.py +0 -23
- azure/quantum/_client/aio/_client.py +0 -124
- azure/quantum/_client/aio/_configuration.py +0 -89
- azure/quantum/_client/aio/_patch.py +0 -20
- azure/quantum/_client/aio/operations/__init__.py +0 -29
- azure/quantum/_client/aio/operations/_operations.py +0 -1291
- azure/quantum/_client/aio/operations/_patch.py +0 -20
- azure/quantum/aio/__init__.py +0 -14
- azure/quantum/aio/_authentication/__init__.py +0 -9
- azure/quantum/aio/_authentication/_chained.py +0 -94
- azure/quantum/aio/_authentication/_default.py +0 -212
- azure/quantum/aio/_authentication/_token.py +0 -81
- azure/quantum/aio/job/__init__.py +0 -1
- azure/quantum/aio/job/base_job.py +0 -326
- azure/quantum/aio/job/job.py +0 -104
- azure/quantum/aio/optimization/__init__.py +0 -11
- azure/quantum/aio/optimization/online_problem.py +0 -17
- azure/quantum/aio/optimization/problem.py +0 -102
- azure/quantum/aio/optimization/streaming_problem.py +0 -280
- azure/quantum/aio/storage.py +0 -390
- azure/quantum/aio/target/__init__.py +0 -19
- azure/quantum/aio/target/ionq.py +0 -47
- azure/quantum/aio/target/quantinuum.py +0 -47
- azure/quantum/aio/target/solvers.py +0 -96
- azure/quantum/aio/target/target.py +0 -68
- azure/quantum/aio/target/target_factory.py +0 -72
- azure/quantum/aio/target/toshiba.py +0 -6
- azure/quantum/aio/workspace.py +0 -337
- azure_quantum-0.29.2.dist-info/RECORD +0 -110
- {azure_quantum-0.29.2.dist-info → azure_quantum-1.0.0.dist-info}/WHEEL +0 -0
- {azure_quantum-0.29.2.dist-info → azure_quantum-1.0.0.dist-info}/top_level.txt +0 -0
azure/quantum/cirq/service.py
CHANGED
|
@@ -43,6 +43,13 @@ class AzureQuantumService:
|
|
|
43
43
|
:param default_target: Default target name, defaults to None
|
|
44
44
|
:type default_target: Optional[str], optional
|
|
45
45
|
"""
|
|
46
|
+
if kwargs is not None and len(kwargs) > 0:
|
|
47
|
+
from warnings import warn
|
|
48
|
+
warn(f"""Consider passing \"workspace\" argument explicitly.
|
|
49
|
+
The ability to initialize AzureQuantumService with arguments {', '.join(f'"{argName}"' for argName in kwargs)} is going to be deprecated in future versions.""",
|
|
50
|
+
DeprecationWarning,
|
|
51
|
+
stacklevel=2)
|
|
52
|
+
|
|
46
53
|
if workspace is None:
|
|
47
54
|
workspace = Workspace(**kwargs)
|
|
48
55
|
|
azure/quantum/job/__init__.py
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
##
|
|
5
5
|
|
|
6
6
|
from azure.quantum.job.job import Job
|
|
7
|
+
from azure.quantum.job.job_failed_with_results_error import JobFailedWithResultsError
|
|
7
8
|
from azure.quantum.job.workspace_item import WorkspaceItem
|
|
8
9
|
from azure.quantum.job.workspace_item_factory import WorkspaceItemFactory
|
|
9
10
|
from azure.quantum.job.session import Session, SessionHost
|
azure/quantum/job/base_job.py
CHANGED
|
@@ -11,7 +11,7 @@ from urllib.parse import urlparse
|
|
|
11
11
|
from typing import Any, Dict, Optional, TYPE_CHECKING
|
|
12
12
|
from azure.storage.blob import BlobClient
|
|
13
13
|
|
|
14
|
-
from azure.quantum.storage import upload_blob, download_blob, ContainerClient
|
|
14
|
+
from azure.quantum.storage import upload_blob, download_blob, download_blob_properties, ContainerClient
|
|
15
15
|
from azure.quantum._client.models import JobDetails
|
|
16
16
|
from azure.quantum.job.workspace_item import WorkspaceItem
|
|
17
17
|
|
|
@@ -26,6 +26,7 @@ DEFAULT_TIMEOUT = 300 # Default timeout for waiting for job to complete
|
|
|
26
26
|
|
|
27
27
|
class ContentType(str, Enum):
|
|
28
28
|
json = "application/json"
|
|
29
|
+
text_plain = "text/plain"
|
|
29
30
|
|
|
30
31
|
class BaseJob(WorkspaceItem):
|
|
31
32
|
# Optionally override these to create a Provider-specific Job subclass
|
|
@@ -261,6 +262,7 @@ class BaseJob(WorkspaceItem):
|
|
|
261
262
|
)
|
|
262
263
|
return uploaded_blob_uri
|
|
263
264
|
|
|
265
|
+
|
|
264
266
|
def download_data(self, blob_uri: str) -> dict:
|
|
265
267
|
"""Download file from blob uri
|
|
266
268
|
|
|
@@ -269,23 +271,26 @@ class BaseJob(WorkspaceItem):
|
|
|
269
271
|
:return: Payload from blob
|
|
270
272
|
:rtype: dict
|
|
271
273
|
"""
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
# get sas url from service
|
|
276
|
-
blob_client = BlobClient.from_blob_url(
|
|
277
|
-
blob_uri
|
|
278
|
-
)
|
|
279
|
-
blob_uri = self.workspace._get_linked_storage_sas_uri(
|
|
280
|
-
blob_client.container_name, blob_client.blob_name
|
|
281
|
-
)
|
|
282
|
-
payload = download_blob(blob_uri)
|
|
283
|
-
else:
|
|
284
|
-
# blob_uri contains SAS token, use it
|
|
285
|
-
payload = download_blob(blob_uri)
|
|
274
|
+
|
|
275
|
+
blob_uri_with_sas_token = self._get_blob_uri_with_sas_token(blob_uri)
|
|
276
|
+
payload = download_blob(blob_uri_with_sas_token)
|
|
286
277
|
|
|
287
278
|
return payload
|
|
288
279
|
|
|
280
|
+
|
|
281
|
+
def download_blob_properties(self, blob_uri: str):
|
|
282
|
+
"""Download Blob properties
|
|
283
|
+
|
|
284
|
+
:param blob_uri: Blob URI
|
|
285
|
+
:type blob_uri: str
|
|
286
|
+
:return: Blob properties
|
|
287
|
+
:rtype: dict
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
blob_uri_with_sas_token = self._get_blob_uri_with_sas_token(blob_uri)
|
|
291
|
+
return download_blob_properties(blob_uri_with_sas_token)
|
|
292
|
+
|
|
293
|
+
|
|
289
294
|
def upload_attachment(
|
|
290
295
|
self,
|
|
291
296
|
name: str,
|
|
@@ -345,3 +350,24 @@ class BaseJob(WorkspaceItem):
|
|
|
345
350
|
blob_client = container_client.get_blob_client(name)
|
|
346
351
|
response = blob_client.download_blob().readall()
|
|
347
352
|
return response
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _get_blob_uri_with_sas_token(self, blob_uri: str) -> str:
|
|
356
|
+
"""Get Blob URI with SAS-token if one was not specified in blob_uri parameter
|
|
357
|
+
:param blob_uri: Blob URI
|
|
358
|
+
:type blob_uri: str
|
|
359
|
+
:return: Blob URI with SAS-token
|
|
360
|
+
:rtype: str
|
|
361
|
+
"""
|
|
362
|
+
url = urlparse(blob_uri)
|
|
363
|
+
if url.query.find("se=") == -1:
|
|
364
|
+
# blob_uri does not contains SAS token,
|
|
365
|
+
# get sas url from service
|
|
366
|
+
blob_client = BlobClient.from_blob_url(
|
|
367
|
+
blob_uri
|
|
368
|
+
)
|
|
369
|
+
blob_uri = self.workspace._get_linked_storage_sas_uri(
|
|
370
|
+
blob_client.container_name, blob_client.blob_name
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
return blob_uri
|
azure/quantum/job/job.py
CHANGED
|
@@ -11,6 +11,7 @@ import json
|
|
|
11
11
|
from typing import TYPE_CHECKING
|
|
12
12
|
|
|
13
13
|
from azure.quantum._client.models import JobDetails
|
|
14
|
+
from azure.quantum.job.job_failed_with_results_error import JobFailedWithResultsError
|
|
14
15
|
from azure.quantum.job.base_job import BaseJob, ContentType, DEFAULT_TIMEOUT
|
|
15
16
|
from azure.quantum.job.filtered_job import FilteredJob
|
|
16
17
|
|
|
@@ -117,6 +118,12 @@ class Job(BaseJob, FilteredJob):
|
|
|
117
118
|
self.wait_until_completed(timeout_secs=timeout_secs)
|
|
118
119
|
|
|
119
120
|
if not self.details.status == "Succeeded":
|
|
121
|
+
if self.details.status == "Failed" and self._allow_failure_results():
|
|
122
|
+
job_blob_properties = self.download_blob_properties(self.details.output_data_uri)
|
|
123
|
+
if job_blob_properties.size > 0:
|
|
124
|
+
job_failure_data = self.download_data(self.details.output_data_uri)
|
|
125
|
+
raise JobFailedWithResultsError("An error occurred during job execution.", job_failure_data)
|
|
126
|
+
|
|
120
127
|
raise RuntimeError(
|
|
121
128
|
f'{"Cannot retrieve results as job execution failed"}'
|
|
122
129
|
+ f"(status: {self.details.status}."
|
|
@@ -126,7 +133,34 @@ class Job(BaseJob, FilteredJob):
|
|
|
126
133
|
payload = self.download_data(self.details.output_data_uri)
|
|
127
134
|
try:
|
|
128
135
|
payload = payload.decode("utf8")
|
|
129
|
-
|
|
136
|
+
results = json.loads(payload)
|
|
137
|
+
|
|
138
|
+
if self.details.output_data_format == "microsoft.quantum-results.v1":
|
|
139
|
+
if "Histogram" not in results:
|
|
140
|
+
raise f"\"Histogram\" array was expected to be in the Job results for \"{self.details.output_data_format}\" output format."
|
|
141
|
+
|
|
142
|
+
histogram_values = results["Histogram"]
|
|
143
|
+
|
|
144
|
+
if len(histogram_values) % 2 == 0:
|
|
145
|
+
# Re-mapping {'Histogram': ['[0]', 0.50, '[1]', 0.50] } to {'[0]': 0.50, '[1]': 0.50}
|
|
146
|
+
return {histogram_values[i]: histogram_values[i + 1] for i in range(0, len(histogram_values), 2)}
|
|
147
|
+
else:
|
|
148
|
+
raise f"\"Histogram\" array has invalid format. Even number of items is expected."
|
|
149
|
+
|
|
150
|
+
return results
|
|
130
151
|
except:
|
|
131
152
|
# If errors decoding the data, return the raw payload:
|
|
132
153
|
return payload
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
def _allow_failure_results(cls) -> bool:
|
|
158
|
+
"""
|
|
159
|
+
Allow to download job results even if the Job status is "Failed".
|
|
160
|
+
|
|
161
|
+
This method can be overridden in derived classes to alter the default
|
|
162
|
+
behaviour.
|
|
163
|
+
|
|
164
|
+
The default is False.
|
|
165
|
+
"""
|
|
166
|
+
return False
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any, Dict, Union
|
|
3
|
+
|
|
4
|
+
class JobFailedWithResultsError(RuntimeError):
|
|
5
|
+
"""
|
|
6
|
+
Error produced when Job completes with status "Failed" and the Job
|
|
7
|
+
supports producing failure results.
|
|
8
|
+
|
|
9
|
+
The failure results can be accessed with get_failure_results() method
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, message: str, failure_results: Any, *args: object) -> None:
|
|
13
|
+
self._set_error_details(message, failure_results)
|
|
14
|
+
super().__init__(message, *args)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _set_error_details(self, message: str, failure_results: Any) -> None:
|
|
18
|
+
self._message = message
|
|
19
|
+
try:
|
|
20
|
+
decoded_failure_results = failure_results.decode("utf8")
|
|
21
|
+
self._failure_results: Dict[str, Any] = json.loads(decoded_failure_results)
|
|
22
|
+
except:
|
|
23
|
+
self._failure_results = failure_results
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_message(self) -> str:
|
|
27
|
+
"""
|
|
28
|
+
Get error message.
|
|
29
|
+
"""
|
|
30
|
+
return self._message
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_failure_results(self) -> Union[Dict[str, Any], str]:
|
|
34
|
+
"""
|
|
35
|
+
Get failure results produced by the job.
|
|
36
|
+
"""
|
|
37
|
+
return self._failure_results
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def __str__(self) -> str:
|
|
41
|
+
return f"{self._message}\nFailure results: {self._failure_results}"
|
|
@@ -10,7 +10,7 @@ import warnings
|
|
|
10
10
|
|
|
11
11
|
logger = logging.getLogger(__name__)
|
|
12
12
|
|
|
13
|
-
from typing import Any, Dict, Tuple, Union, List
|
|
13
|
+
from typing import Any, Dict, Tuple, Union, List, Optional
|
|
14
14
|
from azure.quantum.version import __version__
|
|
15
15
|
from azure.quantum.qiskit.job import (
|
|
16
16
|
MICROSOFT_OUTPUT_DATA_FORMAT,
|
|
@@ -38,6 +38,11 @@ To install run: pip install azure-quantum[qiskit]"
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
class AzureBackendBase(Backend, SessionHost):
|
|
41
|
+
|
|
42
|
+
# Name of the provider's input parameter which specifies number of shots for a submitted job.
|
|
43
|
+
# If None, backend will not pass this input parameter.
|
|
44
|
+
_SHOTS_PARAM_NAME = None
|
|
45
|
+
|
|
41
46
|
@abstractmethod
|
|
42
47
|
def __init__(
|
|
43
48
|
self,
|
|
@@ -46,6 +51,41 @@ class AzureBackendBase(Backend, SessionHost):
|
|
|
46
51
|
**fields
|
|
47
52
|
):
|
|
48
53
|
super().__init__(configuration, provider, **fields)
|
|
54
|
+
|
|
55
|
+
@abstractmethod
|
|
56
|
+
def run(
|
|
57
|
+
self,
|
|
58
|
+
run_input: Union[QuantumCircuit, List[QuantumCircuit]] = [],
|
|
59
|
+
shots: int = None,
|
|
60
|
+
**options,
|
|
61
|
+
) -> AzureQuantumJob:
|
|
62
|
+
"""Run on the backend.
|
|
63
|
+
|
|
64
|
+
This method returns a
|
|
65
|
+
:class:`~azure.quantum.qiskit.job.AzureQuantumJob` object
|
|
66
|
+
that runs circuits.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
run_input (QuantumCircuit or List[QuantumCircuit]): An individual or a
|
|
70
|
+
list of :class:`~qiskit.circuits.QuantumCircuit` to run on the backend.
|
|
71
|
+
shots (int, optional): Number of shots, defaults to None.
|
|
72
|
+
options: Any kwarg options to pass to the backend for running the
|
|
73
|
+
config. If a key is also present in the options
|
|
74
|
+
attribute/object then the expectation is that the value
|
|
75
|
+
specified will be used instead of what's set in the options
|
|
76
|
+
object.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Job: The job object for the run
|
|
80
|
+
"""
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def _can_send_shots_input_param(cls) -> bool:
|
|
85
|
+
"""
|
|
86
|
+
Tells if provider's backend class is able to specify shots number for its jobs.
|
|
87
|
+
"""
|
|
88
|
+
return cls._SHOTS_PARAM_NAME is not None
|
|
49
89
|
|
|
50
90
|
@classmethod
|
|
51
91
|
@abstractmethod
|
|
@@ -80,30 +120,47 @@ class AzureBackendBase(Backend, SessionHost):
|
|
|
80
120
|
output_data_format = options.pop("output_data_format", azure_defined_override)
|
|
81
121
|
|
|
82
122
|
return output_data_format
|
|
83
|
-
|
|
84
|
-
def _get_input_params(self, options) -> Dict[str, Any]:
|
|
123
|
+
|
|
124
|
+
def _get_input_params(self, options, shots: int = None) -> Dict[str, Any]:
|
|
85
125
|
# Backend options are mapped to input_params.
|
|
86
126
|
input_params: Dict[str, Any] = vars(self.options).copy()
|
|
87
127
|
|
|
88
|
-
#
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
128
|
+
# Determine shots number, if needed.
|
|
129
|
+
if self._can_send_shots_input_param():
|
|
130
|
+
options_shots = options.pop(self.__class__._SHOTS_PARAM_NAME, None)
|
|
131
|
+
|
|
132
|
+
# First we check for the explicitly specified 'shots' parameter, then for a provider-specific
|
|
133
|
+
# field in options, then for a backend's default value.
|
|
134
|
+
|
|
135
|
+
# Warn abount options conflict, default to 'shots'.
|
|
136
|
+
if shots is not None and options_shots is not None:
|
|
137
|
+
warnings.warn(
|
|
138
|
+
f"Parameter 'shots' conflicts with the '{self.__class__._SHOTS_PARAM_NAME}' parameter. "
|
|
139
|
+
"Please, provide only one option for setting shots. Defaulting to 'shots' parameter."
|
|
140
|
+
)
|
|
141
|
+
final_shots = shots
|
|
142
|
+
|
|
143
|
+
elif shots is not None:
|
|
144
|
+
final_shots = shots
|
|
145
|
+
else:
|
|
146
|
+
warnings.warn(
|
|
147
|
+
f"Parameter '{self.__class__._SHOTS_PARAM_NAME}' is subject to change in future versions. "
|
|
148
|
+
"Please, use 'shots' parameter instead."
|
|
149
|
+
)
|
|
150
|
+
final_shots = options_shots
|
|
151
|
+
|
|
152
|
+
# If nothing is found, try to get from default values.
|
|
153
|
+
if final_shots is None:
|
|
154
|
+
final_shots = input_params.get(self.__class__._SHOTS_PARAM_NAME)
|
|
155
|
+
|
|
156
|
+
# Also add all possible shots options into input_params to make sure
|
|
157
|
+
# that all backends covered.
|
|
158
|
+
# TODO: Double check all backends for shots options in order to remove this extra check.
|
|
159
|
+
input_params["shots"] = final_shots
|
|
160
|
+
input_params["count"] = final_shots
|
|
102
161
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
options.pop("shots", None)
|
|
106
|
-
options.pop("count", None)
|
|
162
|
+
input_params[self.__class__._SHOTS_PARAM_NAME] = final_shots
|
|
163
|
+
|
|
107
164
|
|
|
108
165
|
if "items" in options:
|
|
109
166
|
input_params["items"] = options.pop("items")
|
|
@@ -114,9 +171,6 @@ class AzureBackendBase(Backend, SessionHost):
|
|
|
114
171
|
if opt in input_params:
|
|
115
172
|
input_params[opt] = options.pop(opt)
|
|
116
173
|
|
|
117
|
-
input_params["count"] = shots_count
|
|
118
|
-
input_params["shots"] = shots_count
|
|
119
|
-
|
|
120
174
|
return input_params
|
|
121
175
|
|
|
122
176
|
def _run(self, job_name, input_data, input_params, metadata, **options):
|
|
@@ -223,18 +277,21 @@ class AzureQirBackend(AzureBackendBase):
|
|
|
223
277
|
}
|
|
224
278
|
|
|
225
279
|
def run(
|
|
226
|
-
self,
|
|
280
|
+
self,
|
|
281
|
+
run_input: Union[QuantumCircuit, List[QuantumCircuit]] = [],
|
|
282
|
+
shots: int = None,
|
|
283
|
+
**options,
|
|
227
284
|
) -> AzureQuantumJob:
|
|
228
285
|
"""Run on the backend.
|
|
229
286
|
|
|
230
287
|
This method returns a
|
|
231
288
|
:class:`~azure.quantum.qiskit.job.AzureQuantumJob` object
|
|
232
|
-
that runs circuits.
|
|
289
|
+
that runs circuits.
|
|
233
290
|
|
|
234
291
|
Args:
|
|
235
292
|
run_input (QuantumCircuit or List[QuantumCircuit]): An individual or a
|
|
236
293
|
list of :class:`~qiskit.circuits.QuantumCircuit` to run on the backend.
|
|
237
|
-
|
|
294
|
+
shots (int, optional): Number of shots, defaults to None.
|
|
238
295
|
options: Any kwarg options to pass to the backend for running the
|
|
239
296
|
config. If a key is also present in the options
|
|
240
297
|
attribute/object then the expectation is that the value
|
|
@@ -261,13 +318,18 @@ class AzureQirBackend(AzureBackendBase):
|
|
|
261
318
|
)
|
|
262
319
|
|
|
263
320
|
# config normalization
|
|
264
|
-
input_params = self._get_input_params(options)
|
|
321
|
+
input_params = self._get_input_params(options, shots=shots)
|
|
322
|
+
|
|
323
|
+
shots_count = None
|
|
265
324
|
|
|
266
|
-
|
|
325
|
+
if self._can_send_shots_input_param():
|
|
326
|
+
shots_count = input_params.get(self.__class__._SHOTS_PARAM_NAME)
|
|
267
327
|
|
|
268
328
|
job_name = ""
|
|
269
329
|
if len(circuits) > 1:
|
|
270
|
-
job_name = f"batch-{len(circuits)}
|
|
330
|
+
job_name = f"batch-{len(circuits)}"
|
|
331
|
+
if shots_count is not None:
|
|
332
|
+
job_name = f"{job_name}-{shots_count}"
|
|
271
333
|
else:
|
|
272
334
|
job_name = circuits[0].name
|
|
273
335
|
job_name = options.pop("job_name", job_name)
|
|
@@ -386,9 +448,13 @@ class AzureBackend(AzureBackendBase):
|
|
|
386
448
|
def _translate_input(self, circuit):
|
|
387
449
|
pass
|
|
388
450
|
|
|
389
|
-
def run(
|
|
390
|
-
|
|
391
|
-
|
|
451
|
+
def run(
|
|
452
|
+
self,
|
|
453
|
+
run_input: Union[QuantumCircuit, List[QuantumCircuit]] = [],
|
|
454
|
+
shots: int = None,
|
|
455
|
+
**options,
|
|
456
|
+
):
|
|
457
|
+
"""Submits the given circuit to run on an Azure Quantum backend."""
|
|
392
458
|
circuit = self._normalize_run_input_params(run_input, **options)
|
|
393
459
|
options.pop("run_input", None)
|
|
394
460
|
options.pop("circuit", None)
|
|
@@ -417,16 +483,45 @@ class AzureBackend(AzureBackendBase):
|
|
|
417
483
|
job_name = options.pop("job_name", circuit.name)
|
|
418
484
|
metadata = options.pop("metadata", self._prepare_job_metadata(circuit))
|
|
419
485
|
|
|
420
|
-
input_params = self._get_input_params(options)
|
|
486
|
+
input_params = self._get_input_params(options, shots=shots)
|
|
421
487
|
|
|
422
488
|
input_data = self._translate_input(circuit)
|
|
423
489
|
|
|
424
490
|
job = super()._run(job_name, input_data, input_params, metadata, **options)
|
|
425
491
|
|
|
426
|
-
shots_count =
|
|
492
|
+
shots_count = None
|
|
493
|
+
if self._can_send_shots_input_param():
|
|
494
|
+
shots_count = input_params.get(self.__class__._SHOTS_PARAM_NAME)
|
|
495
|
+
|
|
427
496
|
logger.info(
|
|
428
497
|
f"Submitted job with id '{job.id()}' for circuit '{circuit.name}' with shot count of {shots_count}:"
|
|
429
498
|
)
|
|
430
499
|
logger.info(input_data)
|
|
431
500
|
|
|
432
501
|
return job
|
|
502
|
+
|
|
503
|
+
def _get_shots_or_deprecated_count_input_param(
|
|
504
|
+
param_name: str,
|
|
505
|
+
shots: int = None,
|
|
506
|
+
count: int = None,
|
|
507
|
+
) -> Optional[int]:
|
|
508
|
+
"""
|
|
509
|
+
This helper function checks if the deprecated 'count' option is specified.
|
|
510
|
+
In earlier versions it was possible to pass this option to specify shots number for a job,
|
|
511
|
+
but now we only check for it for compatibility reasons.
|
|
512
|
+
"""
|
|
513
|
+
|
|
514
|
+
final_shots = None
|
|
515
|
+
|
|
516
|
+
if shots is not None:
|
|
517
|
+
final_shots = shots
|
|
518
|
+
|
|
519
|
+
elif count is not None:
|
|
520
|
+
final_shots = count
|
|
521
|
+
warnings.warn(
|
|
522
|
+
"The 'count' parameter will be deprecated. "
|
|
523
|
+
f"Please, use '{param_name}' parameter instead.",
|
|
524
|
+
category=DeprecationWarning,
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
return final_shots
|
|
@@ -2,12 +2,19 @@
|
|
|
2
2
|
# Copyright (c) Microsoft Corporation.
|
|
3
3
|
# Licensed under the MIT License.
|
|
4
4
|
##
|
|
5
|
-
from typing import TYPE_CHECKING, Dict
|
|
5
|
+
from typing import TYPE_CHECKING, Dict, List, Union
|
|
6
6
|
from azure.quantum import __version__
|
|
7
|
+
from azure.quantum.qiskit.job import AzureQuantumJob
|
|
7
8
|
from azure.quantum.target.ionq import IonQ
|
|
8
9
|
from abc import abstractmethod
|
|
9
10
|
|
|
10
|
-
from
|
|
11
|
+
from qiskit import QuantumCircuit
|
|
12
|
+
|
|
13
|
+
from .backend import (
|
|
14
|
+
AzureBackend,
|
|
15
|
+
AzureQirBackend,
|
|
16
|
+
_get_shots_or_deprecated_count_input_param
|
|
17
|
+
)
|
|
11
18
|
|
|
12
19
|
from qiskit.providers.models import BackendConfiguration
|
|
13
20
|
from qiskit.providers import Options, Provider
|
|
@@ -42,10 +49,14 @@ __all__ = [
|
|
|
42
49
|
"IonQForteNativeBackend",
|
|
43
50
|
]
|
|
44
51
|
|
|
52
|
+
_IONQ_SHOTS_INPUT_PARAM_NAME = "shots"
|
|
53
|
+
_DEFAULT_SHOTS_COUNT = 500
|
|
45
54
|
|
|
46
55
|
class IonQQirBackendBase(AzureQirBackend):
|
|
47
56
|
"""Base class for interfacing with an IonQ QIR backend"""
|
|
48
57
|
|
|
58
|
+
_SHOTS_PARAM_NAME = _IONQ_SHOTS_INPUT_PARAM_NAME
|
|
59
|
+
|
|
49
60
|
@abstractmethod
|
|
50
61
|
def __init__(
|
|
51
62
|
self, configuration: BackendConfiguration, provider: Provider = None, **fields
|
|
@@ -54,7 +65,12 @@ class IonQQirBackendBase(AzureQirBackend):
|
|
|
54
65
|
|
|
55
66
|
@classmethod
|
|
56
67
|
def _default_options(cls) -> Options:
|
|
57
|
-
return Options(
|
|
68
|
+
return Options(
|
|
69
|
+
**{
|
|
70
|
+
cls._SHOTS_PARAM_NAME: _DEFAULT_SHOTS_COUNT,
|
|
71
|
+
},
|
|
72
|
+
targetCapability="BasicExecution",
|
|
73
|
+
)
|
|
58
74
|
|
|
59
75
|
def _azure_config(self) -> Dict[str, str]:
|
|
60
76
|
config = super()._azure_config()
|
|
@@ -64,6 +80,25 @@ class IonQQirBackendBase(AzureQirBackend):
|
|
|
64
80
|
}
|
|
65
81
|
)
|
|
66
82
|
return config
|
|
83
|
+
|
|
84
|
+
def run(
|
|
85
|
+
self,
|
|
86
|
+
run_input: Union[QuantumCircuit, List[QuantumCircuit]] = [],
|
|
87
|
+
shots: int = None,
|
|
88
|
+
**options,
|
|
89
|
+
) -> AzureQuantumJob:
|
|
90
|
+
|
|
91
|
+
# In earlier versions, backends for all providers accepted the 'count' option,
|
|
92
|
+
# but now we accept it only for a compatibility reasons and do not recommend using it.
|
|
93
|
+
count = options.pop("count", None)
|
|
94
|
+
|
|
95
|
+
final_shots = _get_shots_or_deprecated_count_input_param(
|
|
96
|
+
param_name=self.__class__._SHOTS_PARAM_NAME,
|
|
97
|
+
shots=shots,
|
|
98
|
+
count=count,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return super().run(run_input, shots=final_shots, **options)
|
|
67
102
|
|
|
68
103
|
|
|
69
104
|
class IonQSimulatorQirBackend(IonQQirBackendBase):
|
|
@@ -199,15 +234,40 @@ class IonQBackend(AzureBackend):
|
|
|
199
234
|
|
|
200
235
|
backend_name = None
|
|
201
236
|
|
|
237
|
+
_SHOTS_PARAM_NAME = _IONQ_SHOTS_INPUT_PARAM_NAME
|
|
238
|
+
|
|
202
239
|
@abstractmethod
|
|
203
240
|
def __init__(
|
|
204
241
|
self, configuration: BackendConfiguration, provider: Provider = None, **fields
|
|
205
242
|
):
|
|
206
243
|
super().__init__(configuration, provider, **fields)
|
|
207
244
|
|
|
245
|
+
def run(
|
|
246
|
+
self,
|
|
247
|
+
run_input=None,
|
|
248
|
+
shots: int = None,
|
|
249
|
+
**options,
|
|
250
|
+
) -> AzureQuantumJob:
|
|
251
|
+
|
|
252
|
+
# In earlier versions, backends for all providers accepted the 'count' option,
|
|
253
|
+
# but now we accept it only for a compatibility reasons and do not recommend using it.
|
|
254
|
+
count = options.pop("count", None)
|
|
255
|
+
|
|
256
|
+
final_shots = _get_shots_or_deprecated_count_input_param(
|
|
257
|
+
param_name=self.__class__._SHOTS_PARAM_NAME,
|
|
258
|
+
shots=shots,
|
|
259
|
+
count=count,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
return super().run(run_input, shots=final_shots, **options)
|
|
263
|
+
|
|
208
264
|
@classmethod
|
|
209
265
|
def _default_options(cls):
|
|
210
|
-
return Options(
|
|
266
|
+
return Options(
|
|
267
|
+
**{
|
|
268
|
+
cls._SHOTS_PARAM_NAME: _DEFAULT_SHOTS_COUNT,
|
|
269
|
+
},
|
|
270
|
+
)
|
|
211
271
|
|
|
212
272
|
def _azure_config(self) -> Dict[str, str]:
|
|
213
273
|
return {
|
|
@@ -249,7 +309,7 @@ class IonQBackend(AzureBackend):
|
|
|
249
309
|
}
|
|
250
310
|
workspace = self.provider().get_workspace()
|
|
251
311
|
target = workspace.get_targets(self.name())
|
|
252
|
-
return target.estimate_cost(input_data,
|
|
312
|
+
return target.estimate_cost(input_data, shots=shots)
|
|
253
313
|
|
|
254
314
|
|
|
255
315
|
class IonQSimulatorBackend(IonQBackend):
|
|
@@ -5,8 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
from typing import TYPE_CHECKING, Dict
|
|
7
7
|
from azure.quantum.version import __version__
|
|
8
|
+
from azure.quantum.qiskit.job import AzureQuantumJob
|
|
8
9
|
from abc import abstractmethod
|
|
9
|
-
from .backend import
|
|
10
|
+
from .backend import (
|
|
11
|
+
AzureQirBackend,
|
|
12
|
+
_get_shots_or_deprecated_count_input_param,
|
|
13
|
+
)
|
|
10
14
|
|
|
11
15
|
from qiskit.providers.models import BackendConfiguration
|
|
12
16
|
from qiskit.providers import Options, Provider
|
|
@@ -43,7 +47,12 @@ logger = logging.getLogger(__name__)
|
|
|
43
47
|
__all__ = ["QCISimulatorBackend" "QCIQPUBackend"]
|
|
44
48
|
|
|
45
49
|
|
|
50
|
+
_DEFAULT_SHOTS_COUNT = 500
|
|
51
|
+
|
|
46
52
|
class QCIBackend(AzureQirBackend):
|
|
53
|
+
|
|
54
|
+
_SHOTS_PARAM_NAME = "shots"
|
|
55
|
+
|
|
47
56
|
@abstractmethod
|
|
48
57
|
def __init__(
|
|
49
58
|
self, configuration: BackendConfiguration, provider: Provider = None, **fields
|
|
@@ -52,7 +61,12 @@ class QCIBackend(AzureQirBackend):
|
|
|
52
61
|
|
|
53
62
|
@classmethod
|
|
54
63
|
def _default_options(cls) -> Options:
|
|
55
|
-
return Options(
|
|
64
|
+
return Options(
|
|
65
|
+
**{
|
|
66
|
+
cls._SHOTS_PARAM_NAME: _DEFAULT_SHOTS_COUNT,
|
|
67
|
+
},
|
|
68
|
+
targetCapability="AdaptiveExecution",
|
|
69
|
+
)
|
|
56
70
|
|
|
57
71
|
def _azure_config(self) -> Dict[str, str]:
|
|
58
72
|
config = super()._azure_config()
|
|
@@ -62,6 +76,25 @@ class QCIBackend(AzureQirBackend):
|
|
|
62
76
|
}
|
|
63
77
|
)
|
|
64
78
|
return config
|
|
79
|
+
|
|
80
|
+
def run(
|
|
81
|
+
self,
|
|
82
|
+
run_input=None,
|
|
83
|
+
shots: int = None,
|
|
84
|
+
**options,
|
|
85
|
+
) -> AzureQuantumJob:
|
|
86
|
+
|
|
87
|
+
# In earlier versions, backends for all providers accepted the 'count' option,
|
|
88
|
+
# but now we accept it only for a compatibility reasons and do not recommend using it.
|
|
89
|
+
count = options.pop("count", None)
|
|
90
|
+
|
|
91
|
+
final_shots = _get_shots_or_deprecated_count_input_param(
|
|
92
|
+
param_name=self.__class__._SHOTS_PARAM_NAME,
|
|
93
|
+
shots=shots,
|
|
94
|
+
count=count,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return super().run(run_input, shots=final_shots, **options)
|
|
65
98
|
|
|
66
99
|
|
|
67
100
|
class QCISimulatorBackend(QCIBackend):
|