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/aio/target/ionq.py
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
##
|
|
2
|
-
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
-
# Licensed under the MIT License.
|
|
4
|
-
##
|
|
5
|
-
from typing import Any, Dict
|
|
6
|
-
|
|
7
|
-
from azure.quantum.aio.target.target import Target
|
|
8
|
-
from azure.quantum.aio.job.job import Job
|
|
9
|
-
from azure.quantum.target import IonQ as SyncIonQ
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class IonQ(Target, SyncIonQ):
|
|
13
|
-
"""IonQ target."""
|
|
14
|
-
|
|
15
|
-
async def submit(
|
|
16
|
-
self,
|
|
17
|
-
circuit: Dict[str, Any],
|
|
18
|
-
name: str = "ionq-job",
|
|
19
|
-
num_shots: int = None,
|
|
20
|
-
input_params: Dict[str, Any] = None,
|
|
21
|
-
**kwargs
|
|
22
|
-
) -> Job:
|
|
23
|
-
"""Submit an IonQ circuit (JSON format)
|
|
24
|
-
|
|
25
|
-
:param circuit: Quantum circuit in IonQ JSON format
|
|
26
|
-
:type circuit: Dict[str, Any]
|
|
27
|
-
:param name: Job name
|
|
28
|
-
:type name: str
|
|
29
|
-
:param num_shots: Number of shots, defaults to None
|
|
30
|
-
:type num_shots: int
|
|
31
|
-
:param input_params: Optional input params dict
|
|
32
|
-
:type input_params: Dict[str, Any]
|
|
33
|
-
:return: Azure Quantum job
|
|
34
|
-
:rtype: Job
|
|
35
|
-
"""
|
|
36
|
-
if input_params is None:
|
|
37
|
-
input_params = {}
|
|
38
|
-
if num_shots is not None:
|
|
39
|
-
input_params = input_params.copy()
|
|
40
|
-
input_params["shots"] = num_shots
|
|
41
|
-
|
|
42
|
-
return await super().submit(
|
|
43
|
-
input_data=circuit,
|
|
44
|
-
name=name,
|
|
45
|
-
input_params=input_params,
|
|
46
|
-
**kwargs
|
|
47
|
-
)
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
##
|
|
2
|
-
# Copyright (c) Microsoft Corporation.
|
|
3
|
-
# Licensed under the MIT License.
|
|
4
|
-
##
|
|
5
|
-
from typing import Any, Dict
|
|
6
|
-
|
|
7
|
-
from azure.quantum.aio.target.target import Target
|
|
8
|
-
from azure.quantum.aio.job.job import Job
|
|
9
|
-
from azure.quantum.target import Quantinuum as SyncQuantinuum
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class Quantinuum(Target, SyncQuantinuum):
|
|
13
|
-
"""Quantinuum target."""
|
|
14
|
-
|
|
15
|
-
async def submit(
|
|
16
|
-
self,
|
|
17
|
-
circuit: str,
|
|
18
|
-
name: str = "quantinuum-job",
|
|
19
|
-
num_shots: int = None,
|
|
20
|
-
input_params: Dict[str, Any] = None,
|
|
21
|
-
**kwargs
|
|
22
|
-
) -> Job:
|
|
23
|
-
"""Submit a Quantinuum program (OpenQASM 2.0 format)
|
|
24
|
-
|
|
25
|
-
:param circuit: Quantum circuit in Quantinuum OpenQASM 2.0 format
|
|
26
|
-
:type circuit: str
|
|
27
|
-
:param name: Job name
|
|
28
|
-
:type name: str
|
|
29
|
-
:param num_shots: Number of shots, defaults to None
|
|
30
|
-
:type num_shots: int
|
|
31
|
-
:param input_params: Optional input params dict
|
|
32
|
-
:type input_params: Dict[str, Any]
|
|
33
|
-
:return: Azure Quantum job
|
|
34
|
-
:rtype: Job
|
|
35
|
-
"""
|
|
36
|
-
if input_params is None:
|
|
37
|
-
input_params = {}
|
|
38
|
-
if num_shots is not None:
|
|
39
|
-
input_params = input_params.copy()
|
|
40
|
-
input_params["count"] = num_shots
|
|
41
|
-
|
|
42
|
-
return await super().submit(
|
|
43
|
-
input_data=circuit,
|
|
44
|
-
name=name,
|
|
45
|
-
input_params=input_params,
|
|
46
|
-
**kwargs
|
|
47
|
-
)
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
##
|
|
2
|
-
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
-
# Licensed under the MIT License.
|
|
4
|
-
##
|
|
5
|
-
import logging
|
|
6
|
-
|
|
7
|
-
from typing import TYPE_CHECKING, Union
|
|
8
|
-
from azure.quantum.aio import Workspace, Job
|
|
9
|
-
from azure.quantum.aio.target.target import Target
|
|
10
|
-
from azure.quantum.target.solvers import Solver as SyncSolver
|
|
11
|
-
from azure.quantum.aio.job.base_job import DEFAULT_TIMEOUT
|
|
12
|
-
|
|
13
|
-
if TYPE_CHECKING:
|
|
14
|
-
from azure.quantum.aio.optimization.problem import Problem
|
|
15
|
-
|
|
16
|
-
logger = logging.getLogger(__name__)
|
|
17
|
-
|
|
18
|
-
__all__ = [
|
|
19
|
-
"RangeSchedule",
|
|
20
|
-
"Solver",
|
|
21
|
-
]
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class Solver(Target, SyncSolver):
|
|
25
|
-
|
|
26
|
-
workspace: Workspace
|
|
27
|
-
|
|
28
|
-
async def submit(
|
|
29
|
-
self, problem: Union[str, "Problem"]
|
|
30
|
-
) -> Job:
|
|
31
|
-
"""Submits a job to execution to the associated
|
|
32
|
-
Azure Quantum Workspace.
|
|
33
|
-
|
|
34
|
-
:param problem:
|
|
35
|
-
The Problem to solve. It can be an instance of a Problem,
|
|
36
|
-
or the URL of an Azure Storage Blob where the serialized version
|
|
37
|
-
of a Problem has been uploaded.
|
|
38
|
-
"""
|
|
39
|
-
from azure.quantum.aio.optimization.problem import Problem
|
|
40
|
-
if isinstance(problem, Problem):
|
|
41
|
-
# Create job from input data
|
|
42
|
-
return await super().submit(
|
|
43
|
-
input_data=problem,
|
|
44
|
-
name=problem.name,
|
|
45
|
-
input_params=self.params,
|
|
46
|
-
blob_name="inputData",
|
|
47
|
-
content_type = problem.content_type
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
else:
|
|
51
|
-
if hasattr(problem, "uploaded_blob_uri"):
|
|
52
|
-
name = problem.name
|
|
53
|
-
problem_uri = problem.uploaded_blob_uri
|
|
54
|
-
|
|
55
|
-
elif isinstance(problem, str):
|
|
56
|
-
name = "Optimization problem"
|
|
57
|
-
problem_uri = problem
|
|
58
|
-
|
|
59
|
-
else:
|
|
60
|
-
raise ValueError("Cannot submit problem: should be of type str, Problem or have uploaded_blob_uri attribute.")
|
|
61
|
-
|
|
62
|
-
# Create job from storage URI
|
|
63
|
-
job = await Job.from_storage_uri(
|
|
64
|
-
workspace=self.workspace,
|
|
65
|
-
name=name,
|
|
66
|
-
target=self.name,
|
|
67
|
-
input_data_uri=problem_uri,
|
|
68
|
-
provider_id=self.provider_id,
|
|
69
|
-
input_data_format=self.input_data_format,
|
|
70
|
-
output_data_format=self.output_data_format,
|
|
71
|
-
input_params=self.params
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
return job
|
|
75
|
-
|
|
76
|
-
async def optimize(self, problem: Union[str, "Problem"], timeout_secs: int=DEFAULT_TIMEOUT):
|
|
77
|
-
"""[Submits the Problem to the associated
|
|
78
|
-
Azure Quantum Workspace and get the results.
|
|
79
|
-
|
|
80
|
-
:param problem:
|
|
81
|
-
The Problem to solve. It can be an instance of a Problem,
|
|
82
|
-
or the URL of an Azure Storage Blob where the serialized version
|
|
83
|
-
of a Problem has been uploaded.
|
|
84
|
-
:type problem: Union[str, Problem]
|
|
85
|
-
:param timeout_secs: Timeout in seconds, defaults to 300
|
|
86
|
-
:type timeout_secs: int
|
|
87
|
-
:return: Job results
|
|
88
|
-
:rtype: dict
|
|
89
|
-
"""
|
|
90
|
-
if not isinstance(problem, str):
|
|
91
|
-
self.check_submission_warnings(problem)
|
|
92
|
-
|
|
93
|
-
job = await self.submit(problem)
|
|
94
|
-
logger.info(f"Submitted job: '{job.id}'")
|
|
95
|
-
|
|
96
|
-
return await job.get_results(timeout_secs=timeout_secs)
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
##
|
|
2
|
-
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
-
# Licensed under the MIT License.
|
|
4
|
-
##
|
|
5
|
-
import abc
|
|
6
|
-
from typing import Any, Dict
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
from azure.quantum.aio.job.job import Job
|
|
10
|
-
from azure.quantum.target import Target as SyncTarget
|
|
11
|
-
from azure.quantum.aio.workspace import Workspace
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class Target(SyncTarget, abc.ABC):
|
|
15
|
-
"""Azure Quantum Target."""
|
|
16
|
-
workspace: Workspace
|
|
17
|
-
|
|
18
|
-
async def submit(
|
|
19
|
-
self,
|
|
20
|
-
input_data: Any,
|
|
21
|
-
name: str = "azure-quantum-job",
|
|
22
|
-
input_params: Dict[str, Any] = None,
|
|
23
|
-
**kwargs
|
|
24
|
-
) -> Job:
|
|
25
|
-
"""Submit input data and return Job
|
|
26
|
-
|
|
27
|
-
:param input_data: Input data
|
|
28
|
-
:type input_data: Any
|
|
29
|
-
:param name: Job name
|
|
30
|
-
:type name: str
|
|
31
|
-
:param input_params: Input parameters
|
|
32
|
-
:type input_params: Dict[str, Any]
|
|
33
|
-
:return: Azure Quantum job
|
|
34
|
-
:rtype: Job
|
|
35
|
-
"""
|
|
36
|
-
input_params = input_params or {}
|
|
37
|
-
input_data_format = kwargs.pop("input_data_format", self.input_data_format)
|
|
38
|
-
output_data_format = kwargs.pop("output_data_format", self.output_data_format)
|
|
39
|
-
content_type = kwargs.pop("content_type", self.content_type)
|
|
40
|
-
encoding = kwargs.pop("encoding", self.encoding)
|
|
41
|
-
blob = self._encode_input_data(data=input_data)
|
|
42
|
-
|
|
43
|
-
return await Job.from_input_data(
|
|
44
|
-
workspace=self.workspace,
|
|
45
|
-
name=name,
|
|
46
|
-
target=self.name,
|
|
47
|
-
input_data=blob,
|
|
48
|
-
content_type=content_type,
|
|
49
|
-
encoding=encoding,
|
|
50
|
-
provider_id=self.provider_id,
|
|
51
|
-
input_data_format=input_data_format,
|
|
52
|
-
output_data_format=output_data_format,
|
|
53
|
-
input_params=input_params,
|
|
54
|
-
**kwargs
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
async def refresh(self):
|
|
58
|
-
"""Update the target availability and queue time"""
|
|
59
|
-
targets = await self.workspace._get_target_status(self.name, self.provider_id)
|
|
60
|
-
if len(targets) > 0:
|
|
61
|
-
_, target_status = targets[0]
|
|
62
|
-
self._current_availability = target_status.current_availability
|
|
63
|
-
self._average_queue_time = target_status.average_queue_time
|
|
64
|
-
else:
|
|
65
|
-
raise ValueError(
|
|
66
|
-
f"Cannot refresh the Target status: \
|
|
67
|
-
target '{self.name}' of provider '{self.provider_id}' not found."
|
|
68
|
-
)
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
##
|
|
2
|
-
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
-
# Licensed under the MIT License.
|
|
4
|
-
##
|
|
5
|
-
from typing import Dict, List, TYPE_CHECKING, Union
|
|
6
|
-
from azure.quantum.aio.target import Target as AsyncTarget
|
|
7
|
-
from azure.quantum.aio.target import *
|
|
8
|
-
from azure.quantum.target.target_factory import TargetFactory as SyncTargetFactory
|
|
9
|
-
|
|
10
|
-
if TYPE_CHECKING:
|
|
11
|
-
from azure.quantum.aio import Workspace
|
|
12
|
-
|
|
13
|
-
# Target ID keyword for parameter-free solvers
|
|
14
|
-
PARAMETER_FREE = "parameterfree"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class TargetFactory(SyncTargetFactory):
|
|
18
|
-
"""Factory class for generating a Target based on a
|
|
19
|
-
provider and target name
|
|
20
|
-
"""
|
|
21
|
-
__instance = None
|
|
22
|
-
def __new__(cls, *args, **kwargs):
|
|
23
|
-
if cls.__instance is None:
|
|
24
|
-
cls.__instance = super().__new__(cls, *args, **kwargs)
|
|
25
|
-
return cls.__instance
|
|
26
|
-
|
|
27
|
-
@staticmethod
|
|
28
|
-
def _get_all_target_cls() -> Dict[str, Target]:
|
|
29
|
-
"""Get all target classes by target name"""
|
|
30
|
-
return {
|
|
31
|
-
name: _t for t in AsyncTarget.__subclasses__()
|
|
32
|
-
for _t in [t] + t.__subclasses__()
|
|
33
|
-
for name in _t.target_names
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async def get_targets(
|
|
37
|
-
self,
|
|
38
|
-
workspace: "Workspace",
|
|
39
|
-
name: str,
|
|
40
|
-
provider_id: str,
|
|
41
|
-
**kwargs
|
|
42
|
-
) -> Union[Target, List[Target]]:
|
|
43
|
-
"""Create targets that are available to this workspace
|
|
44
|
-
filtered by name and provider ID.
|
|
45
|
-
|
|
46
|
-
:param name: Target name
|
|
47
|
-
:type name: str
|
|
48
|
-
:param workspace: Workspace
|
|
49
|
-
:type workspace: Workspace
|
|
50
|
-
:param provider_id: Provider name
|
|
51
|
-
:type provider_id: str
|
|
52
|
-
:return: One or more Target objects
|
|
53
|
-
:rtype: Union[Target, List[Target]]
|
|
54
|
-
"""
|
|
55
|
-
target_statuses = await workspace._get_target_status(name, provider_id, **kwargs)
|
|
56
|
-
|
|
57
|
-
if len(target_statuses) == 1:
|
|
58
|
-
_, status = target_statuses[0]
|
|
59
|
-
return self.from_target_status(workspace, status=status)
|
|
60
|
-
|
|
61
|
-
else:
|
|
62
|
-
# Don't return redundant parameter-free targets
|
|
63
|
-
return [
|
|
64
|
-
self.from_target_status(_provider_id, status, **kwargs)
|
|
65
|
-
for _provider_id, status in target_statuses
|
|
66
|
-
if PARAMETER_FREE not in status.id
|
|
67
|
-
and (
|
|
68
|
-
_provider_id.lower() in self._default_targets
|
|
69
|
-
or status.id in self._all_targets
|
|
70
|
-
)
|
|
71
|
-
]
|
|
72
|
-
|
azure/quantum/aio/workspace.py
DELETED
|
@@ -1,337 +0,0 @@
|
|
|
1
|
-
##
|
|
2
|
-
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
-
# Licensed under the MIT License.
|
|
4
|
-
##
|
|
5
|
-
from datetime import datetime
|
|
6
|
-
import logging
|
|
7
|
-
import re
|
|
8
|
-
import os
|
|
9
|
-
|
|
10
|
-
from typing import Iterable, List, Optional, Dict, Any, TYPE_CHECKING, Tuple, Union
|
|
11
|
-
|
|
12
|
-
from azure.quantum.aio._authentication._default import _DefaultAzureCredential
|
|
13
|
-
from azure.quantum._client.aio import QuantumClient
|
|
14
|
-
from azure.quantum._client.models import BlobDetails, JobStatus
|
|
15
|
-
from azure.quantum.aio.job import Job
|
|
16
|
-
from azure.quantum.aio.storage import create_container_using_client, get_container_uri, ContainerClient
|
|
17
|
-
|
|
18
|
-
from azure.quantum.workspace import BASE_URL, ARM_BASE_URL, USER_AGENT_APPID_ENV_VAR_NAME
|
|
19
|
-
|
|
20
|
-
if TYPE_CHECKING:
|
|
21
|
-
from azure.quantum.aio.target import Target
|
|
22
|
-
from azure.quantum._client.models import TargetStatus
|
|
23
|
-
|
|
24
|
-
logger = logging.getLogger(__name__)
|
|
25
|
-
|
|
26
|
-
DEFAULT_CONTAINER_NAME_FORMAT = "job-{job_id}"
|
|
27
|
-
|
|
28
|
-
__all__ = ["Workspace"]
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class Workspace:
|
|
32
|
-
"""Represents an Azure Quantum workspace.
|
|
33
|
-
|
|
34
|
-
When creating a Workspace object, callers have two options for identifying
|
|
35
|
-
the Azure Quantum workspace:
|
|
36
|
-
1. specify a valid resource ID, or
|
|
37
|
-
2. specify a valid subscription ID, resource group, and workspace name.
|
|
38
|
-
|
|
39
|
-
If the Azure Quantum workspace does not have linked storage, the caller
|
|
40
|
-
must also pass a valid Azure storage account connection string.
|
|
41
|
-
|
|
42
|
-
:param subscription_id:
|
|
43
|
-
The Azure subscription ID. Ignored if resource_id is specified.
|
|
44
|
-
|
|
45
|
-
:param resource_group:
|
|
46
|
-
The Azure resource group name. Ignored if resource_id is specified.
|
|
47
|
-
|
|
48
|
-
:param name:
|
|
49
|
-
The Azure Quantum workspace name. Ignored if resource_id is specified.
|
|
50
|
-
|
|
51
|
-
:param storage:
|
|
52
|
-
The Azure storage account connection string.
|
|
53
|
-
Required only if the specified Azure Quantum
|
|
54
|
-
workspace does not have linked storage.
|
|
55
|
-
|
|
56
|
-
:param resource_id:
|
|
57
|
-
The resource ID of the Azure Quantum workspace.
|
|
58
|
-
|
|
59
|
-
:param location:
|
|
60
|
-
The Azure region where the Azure Quantum workspace is provisioned.
|
|
61
|
-
This may be specified as a region name such as
|
|
62
|
-
\"East US\" or a location name such as \"eastus\".
|
|
63
|
-
|
|
64
|
-
:param credential:
|
|
65
|
-
The credential to use to connect to Azure services.
|
|
66
|
-
Normally one of the credential types from Azure.Identity (https://docs.microsoft.com/python/api/overview/azure/identity-readme?view=azure-python#credential-classes).
|
|
67
|
-
|
|
68
|
-
Defaults to \"DefaultAzureCredential\", which will attempt multiple
|
|
69
|
-
forms of authentication.
|
|
70
|
-
|
|
71
|
-
:param user_agent:
|
|
72
|
-
Add the specified value as a prefix to the HTTP User-Agent header when communicating to the Azure Quantum service.
|
|
73
|
-
"""
|
|
74
|
-
|
|
75
|
-
credentials = None
|
|
76
|
-
|
|
77
|
-
def __init__(
|
|
78
|
-
self,
|
|
79
|
-
subscription_id: Optional[str] = None,
|
|
80
|
-
resource_group: Optional[str] = None,
|
|
81
|
-
name: Optional[str] = None,
|
|
82
|
-
storage: Optional[str] = None,
|
|
83
|
-
resource_id: Optional[str] = None,
|
|
84
|
-
location: Optional[str] = None,
|
|
85
|
-
credential: Optional[object] = None,
|
|
86
|
-
user_agent: Optional[str] = None,
|
|
87
|
-
):
|
|
88
|
-
if resource_id is not None:
|
|
89
|
-
# A valid resource ID looks like:
|
|
90
|
-
# /subscriptions/f846b2bd-d0e2-4a1d-8141-4c6944a9d387/resourceGroups/
|
|
91
|
-
# RESOURCE_GROUP_NAME/providers/Microsoft.Quantum/Workspaces/WORKSPACE_NAME
|
|
92
|
-
regex = r"^/subscriptions/([a-fA-F0-9-]*)/resourceGroups/([^\s/]*)/providers/Microsoft\.Quantum/Workspaces/([^\s/]*)$"
|
|
93
|
-
match = re.search(regex, resource_id, re.IGNORECASE)
|
|
94
|
-
if match:
|
|
95
|
-
# match should contain four groups:
|
|
96
|
-
# -> match.group(0):
|
|
97
|
-
# The full resource ID for the Azure Quantum workspace
|
|
98
|
-
# -> match.group(1): The Azure subscription ID
|
|
99
|
-
# -> match.group(2): The Azure resource group name
|
|
100
|
-
# -> match.group(3): The Azure Quantum workspace name
|
|
101
|
-
subscription_id = match.group(1)
|
|
102
|
-
resource_group = match.group(2)
|
|
103
|
-
name = match.group(3)
|
|
104
|
-
|
|
105
|
-
if not subscription_id or not resource_group or not name:
|
|
106
|
-
raise ValueError(
|
|
107
|
-
"Azure Quantum workspace not fully specified."
|
|
108
|
-
+ "Please specify either a valid resource ID "
|
|
109
|
-
+ "or a valid combination of subscription ID,"
|
|
110
|
-
+ "resource group name, and workspace name."
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
if not location:
|
|
114
|
-
raise ValueError(
|
|
115
|
-
"Azure Quantum workspace does not have an associated location. " +
|
|
116
|
-
"Please specify the location associated with your workspace.")
|
|
117
|
-
|
|
118
|
-
# Temporarily using a custom _DefaultAzureCredential
|
|
119
|
-
# instead of Azure.Identity.DefaultAzureCredential
|
|
120
|
-
# See _DefaultAzureCredential documentation for more info.
|
|
121
|
-
if credential is None:
|
|
122
|
-
credential = _DefaultAzureCredential(exclude_interactive_browser_credential=False,
|
|
123
|
-
exclude_shared_token_cache_credential=True,
|
|
124
|
-
subscription_id=subscription_id,
|
|
125
|
-
arm_base_url=ARM_BASE_URL)
|
|
126
|
-
|
|
127
|
-
self.credentials = credential
|
|
128
|
-
self.name = name
|
|
129
|
-
self.resource_group = resource_group
|
|
130
|
-
self.subscription_id = subscription_id
|
|
131
|
-
self.storage = storage
|
|
132
|
-
self._user_agent = user_agent
|
|
133
|
-
self.append_user_agent("async")
|
|
134
|
-
|
|
135
|
-
# Convert user-provided location into names
|
|
136
|
-
# recognized by Azure resource manager.
|
|
137
|
-
# For example, a customer-provided value of
|
|
138
|
-
# "West US" should be converted to "westus".
|
|
139
|
-
self.location = "".join(location.split()).lower()
|
|
140
|
-
|
|
141
|
-
def _create_client(self) -> QuantumClient:
|
|
142
|
-
endpoint = BASE_URL(self.location)
|
|
143
|
-
logger.debug(
|
|
144
|
-
f"Creating client for: subs:{self.subscription_id},"
|
|
145
|
-
+ f"rg={self.resource_group}, ws={self.name}, frontdoor={endpoint}"
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
client = QuantumClient(
|
|
149
|
-
credential=self.credentials,
|
|
150
|
-
subscription_id=self.subscription_id,
|
|
151
|
-
resource_group_name=self.resource_group,
|
|
152
|
-
workspace_name=self.name,
|
|
153
|
-
endpoint=endpoint,
|
|
154
|
-
user_agent=self.user_agent
|
|
155
|
-
)
|
|
156
|
-
return client
|
|
157
|
-
|
|
158
|
-
@property
|
|
159
|
-
def user_agent(self):
|
|
160
|
-
"""
|
|
161
|
-
Get the Workspace's UserAgent that is sent to the service via the header.
|
|
162
|
-
Uses the value specified during initialization and appends the environment
|
|
163
|
-
variable AZURE_QUANTUM_PYTHON_APPID if specified.
|
|
164
|
-
"""
|
|
165
|
-
full_user_agent = self._user_agent
|
|
166
|
-
env_app_id = os.environ.get(USER_AGENT_APPID_ENV_VAR_NAME)
|
|
167
|
-
if env_app_id:
|
|
168
|
-
full_user_agent = f"{full_user_agent}-{env_app_id}" if full_user_agent else env_app_id
|
|
169
|
-
return full_user_agent
|
|
170
|
-
|
|
171
|
-
def append_user_agent(self, value: str):
|
|
172
|
-
"""
|
|
173
|
-
Append a new value to the Workspace's UserAgent and re-initialize the
|
|
174
|
-
QuantumClient. The values are appended using a dash.
|
|
175
|
-
|
|
176
|
-
:param value: UserAgent value to add, e.g. "azure-quantum-<plugin>"
|
|
177
|
-
"""
|
|
178
|
-
if value not in (self._user_agent or ""):
|
|
179
|
-
new_user_agent = f"{self._user_agent}-{value}" if self._user_agent else value
|
|
180
|
-
if new_user_agent != self._user_agent:
|
|
181
|
-
self._user_agent = new_user_agent
|
|
182
|
-
|
|
183
|
-
async def _get_linked_storage_sas_uri(
|
|
184
|
-
self, container_name: str, blob_name: str = None
|
|
185
|
-
) -> str:
|
|
186
|
-
"""
|
|
187
|
-
Calls the service and returns a container sas url
|
|
188
|
-
"""
|
|
189
|
-
blob_details = BlobDetails(
|
|
190
|
-
container_name=container_name, blob_name=blob_name
|
|
191
|
-
)
|
|
192
|
-
client = self._create_client()
|
|
193
|
-
storage = client.storage
|
|
194
|
-
container_uri = await storage.sas_uri(blob_details=blob_details)
|
|
195
|
-
await client.close()
|
|
196
|
-
logger.debug(f"Container URI from service: {container_uri}")
|
|
197
|
-
return container_uri.sas_uri
|
|
198
|
-
|
|
199
|
-
async def submit_job(self, job: Job) -> Job:
|
|
200
|
-
client = self._create_client()
|
|
201
|
-
details = await client.jobs.create(
|
|
202
|
-
job.details.id, job.details
|
|
203
|
-
)
|
|
204
|
-
await client.close()
|
|
205
|
-
return Job(self, details)
|
|
206
|
-
|
|
207
|
-
async def cancel_job(self, job: Job) -> Job:
|
|
208
|
-
client = self._create_client()
|
|
209
|
-
await client.jobs.cancel(job.details.id)
|
|
210
|
-
details = await client.jobs.get(job.id)
|
|
211
|
-
await client.close()
|
|
212
|
-
return Job(self, details)
|
|
213
|
-
|
|
214
|
-
async def get_job(self, job_id: str) -> Job:
|
|
215
|
-
"""Returns the job corresponding to the given id."""
|
|
216
|
-
client = self._create_client()
|
|
217
|
-
details = await client.jobs.get(job_id)
|
|
218
|
-
await client.close()
|
|
219
|
-
return Job(self, details)
|
|
220
|
-
|
|
221
|
-
async def list_jobs(
|
|
222
|
-
self,
|
|
223
|
-
name_match: str = None,
|
|
224
|
-
status: Optional[JobStatus] = None,
|
|
225
|
-
created_after: Optional[datetime] = None
|
|
226
|
-
) -> List[Job]:
|
|
227
|
-
"""Returns list of jobs that meet optional (limited) filter criteria.
|
|
228
|
-
:param name_match: regex expression for job name matching
|
|
229
|
-
:param status: filter by job status
|
|
230
|
-
:param created_after: filter jobs after time of job creation
|
|
231
|
-
"""
|
|
232
|
-
client = self._create_client()
|
|
233
|
-
jobs = client.jobs.list()
|
|
234
|
-
|
|
235
|
-
result = []
|
|
236
|
-
async for j in jobs:
|
|
237
|
-
deserialized_job = Job(self, j)
|
|
238
|
-
if deserialized_job.matches_filter(name_match, status, created_after):
|
|
239
|
-
result.append(deserialized_job)
|
|
240
|
-
await client.close()
|
|
241
|
-
return result
|
|
242
|
-
|
|
243
|
-
async def _get_target_status(self, name: str, provider_id: str) -> List[Tuple[str, "TargetStatus"]]:
|
|
244
|
-
"""Get provider ID and status for targets"""
|
|
245
|
-
client = self._create_client()
|
|
246
|
-
result = [
|
|
247
|
-
(provider.id, target)
|
|
248
|
-
async for provider in client.providers.get_status()
|
|
249
|
-
for target in provider.targets
|
|
250
|
-
if (provider_id is None or provider.id.lower() == provider_id.lower())
|
|
251
|
-
and (name is None or target.id.lower() == name.lower())
|
|
252
|
-
]
|
|
253
|
-
await client.close()
|
|
254
|
-
return result
|
|
255
|
-
|
|
256
|
-
async def get_targets(
|
|
257
|
-
self,
|
|
258
|
-
name: str = None,
|
|
259
|
-
provider_id: str = None,
|
|
260
|
-
**kwargs
|
|
261
|
-
) -> Union["Target", Iterable["Target"]]:
|
|
262
|
-
"""Returns all available targets for this workspace filtered by name and provider ID.
|
|
263
|
-
|
|
264
|
-
:param name: Optional target name to filter by, defaults to None
|
|
265
|
-
:type name: str, optional
|
|
266
|
-
:param provider_id: Optional provider Id to filter by, defaults to None
|
|
267
|
-
:type provider_id: str, optional
|
|
268
|
-
:return: Targets
|
|
269
|
-
:rtype: Iterable[Target]
|
|
270
|
-
"""
|
|
271
|
-
from azure.quantum.aio.target.target_factory import TargetFactory
|
|
272
|
-
from azure.quantum.aio.target import Target
|
|
273
|
-
|
|
274
|
-
target_factory = TargetFactory(
|
|
275
|
-
base_cls=Target,
|
|
276
|
-
workspace=self
|
|
277
|
-
)
|
|
278
|
-
|
|
279
|
-
return await target_factory.get_targets(
|
|
280
|
-
workspace=self,
|
|
281
|
-
name=name,
|
|
282
|
-
provider_id=provider_id
|
|
283
|
-
)
|
|
284
|
-
|
|
285
|
-
async def get_quotas(self) -> List[Dict[str, Any]]:
|
|
286
|
-
"""Get a list of job quotas for the given workspace.
|
|
287
|
-
|
|
288
|
-
:return: Job quotas
|
|
289
|
-
:rtype: List[Dict[str, Any]]
|
|
290
|
-
"""
|
|
291
|
-
client = self._create_client()
|
|
292
|
-
result = [q.as_dict() async for q in client.quotas.list()]
|
|
293
|
-
await client.close()
|
|
294
|
-
return result
|
|
295
|
-
|
|
296
|
-
async def get_container_uri(
|
|
297
|
-
self,
|
|
298
|
-
job_id: str = None,
|
|
299
|
-
container_name: str = None,
|
|
300
|
-
container_name_format: str = DEFAULT_CONTAINER_NAME_FORMAT
|
|
301
|
-
) -> str:
|
|
302
|
-
"""Get container URI based on job ID or container name.
|
|
303
|
-
Creates a new container if it does not yet exist.
|
|
304
|
-
|
|
305
|
-
:param job_id: Job ID, defaults to None
|
|
306
|
-
:type job_id: str, optional
|
|
307
|
-
:param container_name: Container name, defaults to None
|
|
308
|
-
:type container_name: str, optional
|
|
309
|
-
:param container_name_format: Container name format, defaults to "job-{job_id}"
|
|
310
|
-
:type container_name_format: str, optional
|
|
311
|
-
:return: Container URI
|
|
312
|
-
:rtype: str
|
|
313
|
-
"""
|
|
314
|
-
if container_name is None:
|
|
315
|
-
if job_id is not None:
|
|
316
|
-
container_name = container_name_format.format(job_id=job_id)
|
|
317
|
-
elif job_id is None:
|
|
318
|
-
container_name = f"{self.name}-data"
|
|
319
|
-
# Create container URI and get container client
|
|
320
|
-
if self.storage is None:
|
|
321
|
-
# Get linked storage account from the service, create
|
|
322
|
-
# a new container if it does not yet exist
|
|
323
|
-
container_uri = await self._get_linked_storage_sas_uri(
|
|
324
|
-
container_name
|
|
325
|
-
)
|
|
326
|
-
container_client = ContainerClient.from_container_url(
|
|
327
|
-
container_uri
|
|
328
|
-
)
|
|
329
|
-
await create_container_using_client(container_client)
|
|
330
|
-
await container_client.close()
|
|
331
|
-
else:
|
|
332
|
-
# Use the storage acount specified to generate container URI,
|
|
333
|
-
# create a new container if it does not yet exist
|
|
334
|
-
container_uri = await get_container_uri(
|
|
335
|
-
self.storage, container_name
|
|
336
|
-
)
|
|
337
|
-
return container_uri
|