qilisdk 0.1.5__py3-none-any.whl → 0.1.7__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.
- qilisdk/analog/__init__.py +1 -2
- qilisdk/analog/hamiltonian.py +4 -71
- qilisdk/analog/schedule.py +291 -313
- qilisdk/backends/backend.py +5 -1
- qilisdk/backends/cuda_backend.py +10 -6
- qilisdk/backends/qutip_backend.py +24 -32
- qilisdk/{common → core}/__init__.py +4 -0
- qilisdk/core/interpolator.py +406 -0
- qilisdk/{common → core}/model.py +7 -7
- qilisdk/core/parameterizable.py +131 -0
- qilisdk/{common → core}/qtensor.py +1 -1
- qilisdk/{common → core}/variables.py +192 -11
- qilisdk/cost_functions/cost_function.py +1 -1
- qilisdk/cost_functions/model_cost_function.py +5 -5
- qilisdk/cost_functions/observable_cost_function.py +2 -2
- qilisdk/digital/ansatz.py +0 -3
- qilisdk/digital/circuit.py +3 -2
- qilisdk/digital/circuit_transpiler.py +46 -0
- qilisdk/digital/circuit_transpiler_passes/__init__.py +18 -0
- qilisdk/digital/circuit_transpiler_passes/circuit_transpiler_pass.py +36 -0
- qilisdk/digital/circuit_transpiler_passes/decompose_multi_controlled_gates_pass.py +216 -0
- qilisdk/digital/circuit_transpiler_passes/numeric_helpers.py +82 -0
- qilisdk/digital/gates.py +15 -5
- qilisdk/{speqtrum/experiments → experiments}/__init__.py +13 -2
- qilisdk/{speqtrum/experiments → experiments}/experiment_functional.py +90 -2
- qilisdk/{speqtrum/experiments → experiments}/experiment_result.py +16 -0
- qilisdk/functionals/functional.py +2 -2
- qilisdk/functionals/functional_result.py +1 -1
- qilisdk/functionals/sampling.py +8 -1
- qilisdk/functionals/time_evolution.py +8 -4
- qilisdk/functionals/time_evolution_result.py +2 -2
- qilisdk/functionals/variational_program.py +58 -0
- qilisdk/optimizers/optimizer_result.py +1 -1
- qilisdk/speqtrum/__init__.py +2 -0
- qilisdk/speqtrum/speqtrum.py +537 -152
- qilisdk/speqtrum/speqtrum_models.py +258 -2
- qilisdk/utils/openfermion/__init__.py +38 -0
- qilisdk/{common/algorithm.py → utils/openfermion/__init__.pyi} +2 -3
- qilisdk/utils/openfermion/openfermion.py +45 -0
- qilisdk/utils/visualization/schedule_renderers.py +22 -9
- {qilisdk-0.1.5.dist-info → qilisdk-0.1.7.dist-info}/METADATA +89 -39
- qilisdk-0.1.7.dist-info/RECORD +76 -0
- {qilisdk-0.1.5.dist-info → qilisdk-0.1.7.dist-info}/WHEEL +1 -1
- qilisdk/analog/linear_schedule.py +0 -118
- qilisdk/common/parameterizable.py +0 -75
- qilisdk-0.1.5.dist-info/RECORD +0 -69
- /qilisdk/{common → core}/exceptions.py +0 -0
- /qilisdk/{common → core}/result.py +0 -0
- {qilisdk-0.1.5.dist-info → qilisdk-0.1.7.dist-info}/licenses/LICENCE +0 -0
qilisdk/speqtrum/speqtrum.py
CHANGED
|
@@ -14,19 +14,37 @@
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
16
|
import base64
|
|
17
|
+
import binascii
|
|
17
18
|
import json
|
|
18
19
|
import time
|
|
19
20
|
from base64 import urlsafe_b64encode
|
|
20
21
|
from datetime import datetime, timezone
|
|
21
|
-
from typing import TYPE_CHECKING, Callable, cast
|
|
22
|
+
from typing import TYPE_CHECKING, Any, Callable, Generator, TypeAlias, TypeVar, cast, overload
|
|
22
23
|
|
|
23
24
|
import httpx
|
|
24
25
|
from loguru import logger
|
|
25
|
-
from pydantic import TypeAdapter
|
|
26
|
-
|
|
27
|
-
from qilisdk.
|
|
26
|
+
from pydantic import TypeAdapter, ValidationError
|
|
27
|
+
|
|
28
|
+
from qilisdk.experiments import (
|
|
29
|
+
RabiExperiment,
|
|
30
|
+
RabiExperimentResult,
|
|
31
|
+
T1Experiment,
|
|
32
|
+
T1ExperimentResult,
|
|
33
|
+
T2Experiment,
|
|
34
|
+
T2ExperimentResult,
|
|
35
|
+
TwoTonesExperiment,
|
|
36
|
+
TwoTonesExperimentResult,
|
|
37
|
+
)
|
|
38
|
+
from qilisdk.functionals import (
|
|
39
|
+
Sampling,
|
|
40
|
+
SamplingResult,
|
|
41
|
+
TimeEvolution,
|
|
42
|
+
TimeEvolutionResult,
|
|
43
|
+
VariationalProgram,
|
|
44
|
+
VariationalProgramResult,
|
|
45
|
+
)
|
|
46
|
+
from qilisdk.functionals.functional_result import FunctionalResult
|
|
28
47
|
from qilisdk.settings import get_settings
|
|
29
|
-
from qilisdk.speqtrum.experiments import ExperimentFunctional, RabiExperiment, T1Experiment
|
|
30
48
|
|
|
31
49
|
from .keyring import delete_credentials, load_credentials, store_credentials
|
|
32
50
|
from .speqtrum_models import (
|
|
@@ -34,6 +52,7 @@ from .speqtrum_models import (
|
|
|
34
52
|
ExecutePayload,
|
|
35
53
|
ExecuteType,
|
|
36
54
|
JobDetail,
|
|
55
|
+
JobHandle,
|
|
37
56
|
JobId,
|
|
38
57
|
JobInfo,
|
|
39
58
|
JobStatus,
|
|
@@ -41,8 +60,11 @@ from .speqtrum_models import (
|
|
|
41
60
|
RabiExperimentPayload,
|
|
42
61
|
SamplingPayload,
|
|
43
62
|
T1ExperimentPayload,
|
|
63
|
+
T2ExperimentPayload,
|
|
44
64
|
TimeEvolutionPayload,
|
|
45
65
|
Token,
|
|
66
|
+
TwoTonesExperimentPayload,
|
|
67
|
+
TypedJobDetail,
|
|
46
68
|
VariationalProgramPayload,
|
|
47
69
|
)
|
|
48
70
|
|
|
@@ -50,27 +72,199 @@ if TYPE_CHECKING:
|
|
|
50
72
|
from qilisdk.functionals.functional import Functional, PrimitiveFunctional
|
|
51
73
|
|
|
52
74
|
|
|
75
|
+
TFunctionalResult = TypeVar("TFunctionalResult", bound=FunctionalResult)
|
|
76
|
+
JSONValue: TypeAlias = dict[str, "JSONValue"] | list["JSONValue"] | str | int | float | bool | None
|
|
77
|
+
|
|
78
|
+
_SKIP_ENSURE_OK_EXTENSION = "qilisdk.skip_ensure_ok"
|
|
79
|
+
_CONTEXT_EXTENSION = "qilisdk.request_context"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class SpeQtrumAPIError(httpx.HTTPStatusError):
|
|
83
|
+
"""Raised when the SpeQtrum API responds with a non-success HTTP status."""
|
|
84
|
+
|
|
85
|
+
def __init__(self, message: str, *, request: httpx.Request, response: httpx.Response) -> None:
|
|
86
|
+
super().__init__(message, request=request, response=response)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _safe_json_loads(value: str, *, context: str) -> JSONValue | None:
|
|
90
|
+
try:
|
|
91
|
+
result = json.loads(value)
|
|
92
|
+
return cast("JSONValue", result)
|
|
93
|
+
except json.JSONDecodeError as exc:
|
|
94
|
+
logger.warning("Failed to decode JSON for {}: {}", context, exc)
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _safe_b64_decode(value: str, *, context: str) -> str | None:
|
|
99
|
+
try:
|
|
100
|
+
decoded_bytes = base64.b64decode(value)
|
|
101
|
+
except (binascii.Error, ValueError) as exc:
|
|
102
|
+
logger.warning("Failed to base64 decode {}: {}", context, exc)
|
|
103
|
+
return None
|
|
104
|
+
try:
|
|
105
|
+
return decoded_bytes.decode("utf-8")
|
|
106
|
+
except UnicodeDecodeError as exc:
|
|
107
|
+
logger.warning("Failed to UTF-8 decode {}: {}", context, exc)
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _safe_b64_json(value: str, *, context: str) -> JSONValue | None:
|
|
112
|
+
decoded_text = _safe_b64_decode(value, context=context)
|
|
113
|
+
if decoded_text is None:
|
|
114
|
+
return None
|
|
115
|
+
return _safe_json_loads(decoded_text, context=context)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _request_extensions(*, context: str | None = None, skip_ensure_ok: bool = False) -> dict[str, Any] | None:
|
|
119
|
+
extensions: dict[str, Any] = {}
|
|
120
|
+
if context:
|
|
121
|
+
extensions[_CONTEXT_EXTENSION] = context
|
|
122
|
+
if skip_ensure_ok:
|
|
123
|
+
extensions[_SKIP_ENSURE_OK_EXTENSION] = True
|
|
124
|
+
return extensions or None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _response_context(response: httpx.Response) -> str:
|
|
128
|
+
request = response.request
|
|
129
|
+
if request is None:
|
|
130
|
+
return "SpeQtrum API call"
|
|
131
|
+
context = request.extensions.get(_CONTEXT_EXTENSION)
|
|
132
|
+
if isinstance(context, str) and context.strip():
|
|
133
|
+
return context
|
|
134
|
+
return f"{request.method} {request.url}"
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _stringify_payload(payload: JSONValue | None) -> str | None:
|
|
138
|
+
if payload is None:
|
|
139
|
+
return None
|
|
140
|
+
if isinstance(payload, dict):
|
|
141
|
+
for key in ("message", "detail", "error", "error_description", "title"):
|
|
142
|
+
value = payload.get(key)
|
|
143
|
+
if isinstance(value, str) and value.strip():
|
|
144
|
+
code = payload.get("code") or payload.get("error_code") or payload.get("errorCode")
|
|
145
|
+
code_suffix = f" (code={code})" if isinstance(code, (str, int)) else ""
|
|
146
|
+
return value.strip() + code_suffix
|
|
147
|
+
try:
|
|
148
|
+
return json.dumps(payload, sort_keys=True)
|
|
149
|
+
except (TypeError, ValueError):
|
|
150
|
+
return str(payload)
|
|
151
|
+
if isinstance(payload, list):
|
|
152
|
+
preview = ", ".join(str(item) for item in payload[:3])
|
|
153
|
+
return preview or str(payload)
|
|
154
|
+
if isinstance(payload, (str, int, float, bool)):
|
|
155
|
+
return str(payload)
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _summarize_error_payload(response: httpx.Response) -> str:
|
|
160
|
+
context = _response_context(response)
|
|
161
|
+
try:
|
|
162
|
+
body_text = response.text or ""
|
|
163
|
+
except httpx.ResponseNotRead:
|
|
164
|
+
try:
|
|
165
|
+
response.read() # ensure body is buffered so we can reuse it later
|
|
166
|
+
body_text = response.text or ""
|
|
167
|
+
except Exception as exc: # noqa: BLE001
|
|
168
|
+
logger.debug("Failed to read response body for {}: {}", context, exc)
|
|
169
|
+
body_text = ""
|
|
170
|
+
payload = _safe_json_loads(body_text, context=f"{context} error body") if body_text else None
|
|
171
|
+
detail = _stringify_payload(payload)
|
|
172
|
+
if detail:
|
|
173
|
+
return detail
|
|
174
|
+
if body_text.strip():
|
|
175
|
+
return body_text.strip()
|
|
176
|
+
return "no response body"
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _ensure_ok(response: httpx.Response) -> None:
|
|
180
|
+
request = response.request
|
|
181
|
+
if request is not None and request.extensions.get(_SKIP_ENSURE_OK_EXTENSION):
|
|
182
|
+
return
|
|
183
|
+
if response.status_code == httpx.codes.UNAUTHORIZED:
|
|
184
|
+
return
|
|
185
|
+
try:
|
|
186
|
+
response.raise_for_status()
|
|
187
|
+
except httpx.HTTPStatusError:
|
|
188
|
+
context = _response_context(response)
|
|
189
|
+
detail = _summarize_error_payload(response)
|
|
190
|
+
logger.error(
|
|
191
|
+
"{} failed with status {} {}: {}",
|
|
192
|
+
context,
|
|
193
|
+
response.status_code,
|
|
194
|
+
response.reason_phrase,
|
|
195
|
+
detail,
|
|
196
|
+
)
|
|
197
|
+
message = f"{context} failed with status {response.status_code}: {detail}"
|
|
198
|
+
raise SpeQtrumAPIError(message, request=response.request, response=response) from None
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class _BearerAuth(httpx.Auth):
|
|
202
|
+
"""Bearer token auth handler with automatic refresh support."""
|
|
203
|
+
|
|
204
|
+
requires_response_body = True
|
|
205
|
+
|
|
206
|
+
def __init__(self, client: SpeQtrum) -> None:
|
|
207
|
+
self._client = client
|
|
208
|
+
|
|
209
|
+
def auth_flow(self, request: httpx.Request) -> Generator[httpx.Request, httpx.Response, None]:
|
|
210
|
+
request.headers["Authorization"] = f"Bearer {self._client.token.access_token}"
|
|
211
|
+
response = yield request
|
|
212
|
+
|
|
213
|
+
if response.status_code == httpx.codes.UNAUTHORIZED:
|
|
214
|
+
settings = get_settings()
|
|
215
|
+
refresh_request = httpx.Request(
|
|
216
|
+
"POST",
|
|
217
|
+
settings.speqtrum_api_url + "/authorisation-tokens/refresh",
|
|
218
|
+
headers={"Authorization": f"Bearer {self._client.token.refresh_token}"},
|
|
219
|
+
extensions=_request_extensions(context="Refreshing SpeQtrum token", skip_ensure_ok=True),
|
|
220
|
+
)
|
|
221
|
+
refresh_response = yield refresh_request
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
refresh_response.raise_for_status()
|
|
225
|
+
except httpx.HTTPStatusError as exc:
|
|
226
|
+
logger.error(
|
|
227
|
+
"Token refresh failed with status {} {}",
|
|
228
|
+
exc.response.status_code,
|
|
229
|
+
exc.response.reason_phrase,
|
|
230
|
+
)
|
|
231
|
+
raise
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
payload = refresh_response.json()
|
|
235
|
+
except json.JSONDecodeError as exc:
|
|
236
|
+
logger.error("Token refresh returned invalid JSON: {}", exc)
|
|
237
|
+
raise RuntimeError("SpeQtrum token refresh failed: invalid JSON payload") from exc
|
|
238
|
+
|
|
239
|
+
if not isinstance(payload, dict):
|
|
240
|
+
logger.error("Token refresh returned non-object payload: {}", type(payload).__name__)
|
|
241
|
+
raise RuntimeError("SpeQtrum token refresh failed: malformed token payload")
|
|
242
|
+
|
|
243
|
+
payload.pop("refreshToken", None)
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
token = Token(**payload, refreshToken=self._client.token.refresh_token)
|
|
247
|
+
except (TypeError, ValidationError) as exc:
|
|
248
|
+
logger.error("Token refresh returned malformed payload: {}", exc)
|
|
249
|
+
raise RuntimeError("SpeQtrum token refresh failed: malformed token payload") from exc
|
|
250
|
+
|
|
251
|
+
self._client.token = token
|
|
252
|
+
store_credentials(self._client.username, self._client.token)
|
|
253
|
+
request.headers["Authorization"] = f"Bearer {self._client.token.access_token}"
|
|
254
|
+
yield request
|
|
255
|
+
|
|
256
|
+
|
|
53
257
|
class SpeQtrum:
|
|
54
258
|
"""Synchronous client for the Qilimanjaro SpeQtrum API."""
|
|
55
259
|
|
|
56
260
|
def __init__(self) -> None:
|
|
57
|
-
logger.debug("Initializing
|
|
261
|
+
logger.debug("Initializing SpeQtrum client")
|
|
58
262
|
credentials = load_credentials()
|
|
59
263
|
if credentials is None:
|
|
60
|
-
logger.error("No
|
|
61
|
-
raise RuntimeError("Missing
|
|
62
|
-
self.
|
|
63
|
-
|
|
64
|
-
Sampling: lambda f, device: self._submit_sampling(cast("Sampling", f), device),
|
|
65
|
-
TimeEvolution: lambda f, device: self._submit_time_evolution(cast("TimeEvolution", f), device),
|
|
66
|
-
VariationalProgram: lambda f, device: self._submit_variational_program(
|
|
67
|
-
cast("VariationalProgram", f), device
|
|
68
|
-
),
|
|
69
|
-
RabiExperiment: lambda f, device: self._submit_rabi_program(cast("RabiExperiment", f), device),
|
|
70
|
-
T1Experiment: lambda f, device: self._submit_t1_program(cast("T1Experiment", f), device),
|
|
71
|
-
}
|
|
72
|
-
self._settings = get_settings()
|
|
73
|
-
logger.success("QaaS client initialised for user '{}'", self._username)
|
|
264
|
+
logger.error("No credentials found. Call `SpeQtrum.login()` before instantiation.")
|
|
265
|
+
raise RuntimeError("Missing credentials - invoke SpeQtrum.login() first.")
|
|
266
|
+
self.username, self.token = credentials
|
|
267
|
+
logger.success("SpeQtrum client initialised for user '{}'", self.username)
|
|
74
268
|
|
|
75
269
|
@classmethod
|
|
76
270
|
def _get_headers(cls) -> dict:
|
|
@@ -78,8 +272,15 @@ class SpeQtrum:
|
|
|
78
272
|
|
|
79
273
|
return {"User-Agent": f"qilisdk/{__version__}"}
|
|
80
274
|
|
|
81
|
-
def
|
|
82
|
-
|
|
275
|
+
def _create_client(self) -> httpx.Client:
|
|
276
|
+
"""Return a freshly configured HTTP client for SpeQtrum interactions."""
|
|
277
|
+
settings = get_settings()
|
|
278
|
+
return httpx.Client(
|
|
279
|
+
base_url=settings.speqtrum_api_url,
|
|
280
|
+
headers=self._get_headers(),
|
|
281
|
+
auth=_BearerAuth(self),
|
|
282
|
+
event_hooks={"response": [_ensure_ok]},
|
|
283
|
+
)
|
|
83
284
|
|
|
84
285
|
@classmethod
|
|
85
286
|
def login(
|
|
@@ -108,7 +309,7 @@ class SpeQtrum:
|
|
|
108
309
|
apikey = apikey or settings.speqtrum_apikey
|
|
109
310
|
|
|
110
311
|
if not username or not apikey:
|
|
111
|
-
logger.
|
|
312
|
+
logger.error("Login called without credentials.")
|
|
112
313
|
return False
|
|
113
314
|
|
|
114
315
|
# Send login request to QaaS
|
|
@@ -121,23 +322,24 @@ class SpeQtrum:
|
|
|
121
322
|
"iat": int(datetime.now(timezone.utc).timestamp()),
|
|
122
323
|
}
|
|
123
324
|
encoded_assertion = urlsafe_b64encode(json.dumps(assertion, indent=2).encode("utf-8")).decode("utf-8")
|
|
124
|
-
with httpx.Client(
|
|
325
|
+
with httpx.Client(
|
|
326
|
+
base_url=settings.speqtrum_api_url,
|
|
327
|
+
headers=cls._get_headers(),
|
|
328
|
+
timeout=10.0,
|
|
329
|
+
event_hooks={"response": [_ensure_ok]},
|
|
330
|
+
) as client:
|
|
125
331
|
response = client.post(
|
|
126
|
-
|
|
332
|
+
"/authorisation-tokens",
|
|
127
333
|
json={
|
|
128
334
|
"grantType": "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
|
129
335
|
"assertion": encoded_assertion,
|
|
130
336
|
"scope": "user profile",
|
|
131
337
|
},
|
|
132
|
-
|
|
338
|
+
extensions=_request_extensions(context="Authenticating user"),
|
|
133
339
|
)
|
|
134
340
|
response.raise_for_status()
|
|
135
341
|
token = Token(**response.json())
|
|
136
|
-
except httpx.
|
|
137
|
-
logger.error("Login failed - server returned {} {}", exc.response.status_code, exc.response.reason_phrase)
|
|
138
|
-
return False
|
|
139
|
-
except httpx.RequestError:
|
|
140
|
-
logger.exception("Network error while logging in to QaaS")
|
|
342
|
+
except httpx.HTTPError:
|
|
141
343
|
return False
|
|
142
344
|
|
|
143
345
|
store_credentials(username=username, token=token)
|
|
@@ -161,12 +363,9 @@ class SpeQtrum:
|
|
|
161
363
|
A list of :class:`~qilisdk.models.Device` objects.
|
|
162
364
|
"""
|
|
163
365
|
logger.debug("Fetching device list from server…")
|
|
164
|
-
with
|
|
165
|
-
response = client.get(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
devices = TypeAdapter(list[Device]).validate_python(response.json()["items"])
|
|
169
|
-
|
|
366
|
+
with self._create_client() as client:
|
|
367
|
+
response = client.get("/devices", extensions=_request_extensions(context="Fetching device list"))
|
|
368
|
+
devices = TypeAdapter(list[Device]).validate_python(response.json()["items"])
|
|
170
369
|
logger.success("{} devices retrieved", len(devices))
|
|
171
370
|
return [d for d in devices if where(d)] if where else devices
|
|
172
371
|
|
|
@@ -182,30 +381,33 @@ class SpeQtrum:
|
|
|
182
381
|
A list of :class:`~qilisdk.models.JobInfo` objects.
|
|
183
382
|
"""
|
|
184
383
|
logger.debug("Fetching job list…")
|
|
185
|
-
with
|
|
186
|
-
response = client.get(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
jobs = TypeAdapter(list[JobInfo]).validate_python(response.json()["items"])
|
|
190
|
-
|
|
384
|
+
with self._create_client() as client:
|
|
385
|
+
response = client.get("/jobs", extensions=_request_extensions(context="Fetching job list"))
|
|
386
|
+
jobs = TypeAdapter(list[JobInfo]).validate_python(response.json()["items"])
|
|
191
387
|
logger.success("{} jobs retrieved", len(jobs))
|
|
192
388
|
return [j for j in jobs if where(j)] if where else jobs
|
|
193
389
|
|
|
194
|
-
|
|
195
|
-
|
|
390
|
+
@overload
|
|
391
|
+
def get_job(self, job: JobHandle[TFunctionalResult]) -> TypedJobDetail[TFunctionalResult]: ...
|
|
392
|
+
|
|
393
|
+
@overload
|
|
394
|
+
def get_job(self, job: int) -> JobDetail: ...
|
|
395
|
+
|
|
396
|
+
def get_job(self, job: int | JobHandle[Any]) -> JobDetail | TypedJobDetail[Any]:
|
|
397
|
+
"""Fetch the complete record of *job*.
|
|
196
398
|
|
|
197
399
|
Args:
|
|
198
|
-
|
|
400
|
+
job: Either the integer identifier or a previously returned `JobHandle`.
|
|
199
401
|
|
|
200
402
|
Returns:
|
|
201
|
-
A :class:`~qilisdk.models.JobDetail`
|
|
202
|
-
result
|
|
403
|
+
A :class:`~qilisdk.models.JobDetail` snapshot. When a handle is supplied the
|
|
404
|
+
result is wrapped in :class:`~qilisdk.models.TypedJobDetail` to expose typed accessors.
|
|
203
405
|
"""
|
|
204
|
-
|
|
205
|
-
|
|
406
|
+
job_id = job.id if isinstance(job, JobHandle) else job
|
|
407
|
+
logger.debug("Retrieving job {} details", job_id)
|
|
408
|
+
with self._create_client() as client:
|
|
206
409
|
response = client.get(
|
|
207
|
-
f"
|
|
208
|
-
headers=self._get_authorized_headers(),
|
|
410
|
+
f"/jobs/{job_id}",
|
|
209
411
|
params={
|
|
210
412
|
"payload": True,
|
|
211
413
|
"result": True,
|
|
@@ -213,55 +415,75 @@ class SpeQtrum:
|
|
|
213
415
|
"error_logs": True,
|
|
214
416
|
"error": True,
|
|
215
417
|
},
|
|
418
|
+
extensions=_request_extensions(context=f"Fetching job {job_id}"),
|
|
216
419
|
)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if raw_payload is not None:
|
|
222
|
-
data["payload"] = json.loads(raw_payload)
|
|
420
|
+
data = response.json()
|
|
421
|
+
raw_payload = data.get("payload")
|
|
422
|
+
if isinstance(raw_payload, str):
|
|
423
|
+
data["payload"] = _safe_json_loads(raw_payload, context=f"job {job_id} payload")
|
|
223
424
|
|
|
224
425
|
raw_result = data.get("result")
|
|
225
|
-
if raw_result
|
|
226
|
-
|
|
227
|
-
text_result = decoded_result.decode("utf-8")
|
|
228
|
-
data["result"] = json.loads(text_result)
|
|
426
|
+
if isinstance(raw_result, str):
|
|
427
|
+
data["result"] = _safe_b64_json(raw_result, context=f"job {job_id} result")
|
|
229
428
|
|
|
230
429
|
raw_error = data.get("error")
|
|
231
|
-
if raw_error
|
|
232
|
-
|
|
233
|
-
data["error"] = decoded_error.decode("utf-8")
|
|
430
|
+
if isinstance(raw_error, str):
|
|
431
|
+
data["error"] = _safe_b64_decode(raw_error, context=f"job {job_id} error")
|
|
234
432
|
|
|
235
433
|
raw_logs = data.get("logs")
|
|
236
|
-
if raw_logs
|
|
237
|
-
|
|
238
|
-
|
|
434
|
+
if isinstance(raw_logs, str):
|
|
435
|
+
data["logs"] = _safe_b64_decode(raw_logs, context=f"job {job_id} logs")
|
|
436
|
+
|
|
437
|
+
raw_error_logs = data.get("error_logs")
|
|
438
|
+
if isinstance(raw_error_logs, str):
|
|
439
|
+
data["error_logs"] = _safe_b64_decode(raw_error_logs, context=f"job {job_id} error logs")
|
|
239
440
|
|
|
240
441
|
job_detail = TypeAdapter(JobDetail).validate_python(data)
|
|
241
|
-
logger.debug("Job {} details retrieved (status {})",
|
|
442
|
+
logger.debug("Job {} details retrieved (status {})", job_id, job_detail.status.value)
|
|
443
|
+
if isinstance(job, JobHandle):
|
|
444
|
+
return job.bind(job_detail)
|
|
242
445
|
return job_detail
|
|
243
446
|
|
|
447
|
+
@overload
|
|
448
|
+
def wait_for_job(
|
|
449
|
+
self,
|
|
450
|
+
job: JobHandle[TFunctionalResult],
|
|
451
|
+
*,
|
|
452
|
+
poll_interval: float = 5.0,
|
|
453
|
+
timeout: float | None = None,
|
|
454
|
+
) -> TypedJobDetail[TFunctionalResult]: ...
|
|
455
|
+
|
|
456
|
+
@overload
|
|
457
|
+
def wait_for_job(
|
|
458
|
+
self,
|
|
459
|
+
job: int,
|
|
460
|
+
*,
|
|
461
|
+
poll_interval: float = 5.0,
|
|
462
|
+
timeout: float | None = None,
|
|
463
|
+
) -> JobDetail: ...
|
|
464
|
+
|
|
244
465
|
def wait_for_job(
|
|
245
466
|
self,
|
|
246
|
-
|
|
467
|
+
job: int | JobHandle[Any],
|
|
247
468
|
*,
|
|
248
469
|
poll_interval: float = 5.0,
|
|
249
470
|
timeout: float | None = None,
|
|
250
|
-
) -> JobDetail:
|
|
251
|
-
"""Block until *
|
|
471
|
+
) -> JobDetail | TypedJobDetail[Any]:
|
|
472
|
+
"""Block until the job referenced by *job* reaches a terminal state.
|
|
252
473
|
|
|
253
474
|
Args:
|
|
254
|
-
|
|
475
|
+
job: Either the integer job identifier or a previously returned `JobHandle`.
|
|
255
476
|
poll_interval: Seconds between successive polls. Defaults to ``5``.
|
|
256
477
|
timeout: Maximum wait time in seconds. ``None`` waits indefinitely.
|
|
257
478
|
|
|
258
479
|
Returns:
|
|
259
|
-
Final :class:`~qilisdk.models.JobDetail` snapshot.
|
|
480
|
+
Final :class:`~qilisdk.models.JobDetail` snapshot, optionally wrapped with type-safe accessors.
|
|
260
481
|
|
|
261
482
|
Raises:
|
|
262
483
|
TimeoutError: If *timeout* elapses before the job finishes.
|
|
263
484
|
"""
|
|
264
|
-
|
|
485
|
+
job_id = job.id if isinstance(job, JobHandle) else job
|
|
486
|
+
logger.info("Waiting for job {} (poll={}s, timeout={}s)…", job_id, poll_interval, timeout)
|
|
265
487
|
start_t = time.monotonic()
|
|
266
488
|
terminal_states = {
|
|
267
489
|
JobStatus.COMPLETED,
|
|
@@ -271,24 +493,65 @@ class SpeQtrum:
|
|
|
271
493
|
|
|
272
494
|
# poll until we hit a terminal state or timeout
|
|
273
495
|
while True:
|
|
274
|
-
current = self.
|
|
496
|
+
current = self.get_job(job_id)
|
|
275
497
|
|
|
276
498
|
if current.status in terminal_states:
|
|
277
|
-
logger.success("Job {} reached terminal state {}",
|
|
499
|
+
logger.success("Job {} reached terminal state {}", job_id, current.status.value)
|
|
500
|
+
if isinstance(job, JobHandle):
|
|
501
|
+
return job.bind(current)
|
|
278
502
|
return current
|
|
279
503
|
|
|
280
504
|
if timeout is not None and (time.monotonic() - start_t) >= timeout:
|
|
281
505
|
logger.error(
|
|
282
|
-
"Timeout while waiting for job {} after {}s (last status {})",
|
|
506
|
+
"Timeout while waiting for job {} after {}s (last status {})",
|
|
507
|
+
job_id,
|
|
508
|
+
timeout,
|
|
509
|
+
current.status.value,
|
|
283
510
|
)
|
|
284
511
|
raise TimeoutError(
|
|
285
|
-
f"Timed out after {timeout}s while waiting for job {
|
|
512
|
+
f"Timed out after {timeout}s while waiting for job {job_id} (last status {current.status.value!r})"
|
|
286
513
|
)
|
|
287
514
|
|
|
288
|
-
logger.debug("Job {} still {}, sleeping {}s",
|
|
515
|
+
logger.debug("Job {} still {}, sleeping {}s", job_id, current.status.value, poll_interval)
|
|
289
516
|
time.sleep(poll_interval)
|
|
290
517
|
|
|
291
|
-
|
|
518
|
+
@overload
|
|
519
|
+
def submit(self, functional: Sampling, device: str, job_name: str | None = None) -> JobHandle[SamplingResult]: ...
|
|
520
|
+
|
|
521
|
+
@overload
|
|
522
|
+
def submit(
|
|
523
|
+
self, functional: TimeEvolution, device: str, job_name: str | None = None
|
|
524
|
+
) -> JobHandle[TimeEvolutionResult]: ...
|
|
525
|
+
|
|
526
|
+
@overload
|
|
527
|
+
def submit(
|
|
528
|
+
self, functional: VariationalProgram[Sampling], device: str, job_name: str | None = None
|
|
529
|
+
) -> JobHandle[VariationalProgramResult[SamplingResult]]: ...
|
|
530
|
+
|
|
531
|
+
@overload
|
|
532
|
+
def submit(
|
|
533
|
+
self, functional: VariationalProgram[TimeEvolution], device: str, job_name: str | None = None
|
|
534
|
+
) -> JobHandle[VariationalProgramResult[TimeEvolutionResult]]: ...
|
|
535
|
+
|
|
536
|
+
@overload
|
|
537
|
+
def submit(
|
|
538
|
+
self,
|
|
539
|
+
functional: VariationalProgram[PrimitiveFunctional[TFunctionalResult]],
|
|
540
|
+
device: str,
|
|
541
|
+
job_name: str | None = None,
|
|
542
|
+
) -> JobHandle[VariationalProgramResult[TFunctionalResult]]: ...
|
|
543
|
+
|
|
544
|
+
@overload
|
|
545
|
+
def submit(
|
|
546
|
+
self, functional: RabiExperiment, device: str, job_name: str | None = None
|
|
547
|
+
) -> JobHandle[RabiExperimentResult]: ...
|
|
548
|
+
|
|
549
|
+
@overload
|
|
550
|
+
def submit(
|
|
551
|
+
self, functional: T1Experiment, device: str, job_name: str | None = None
|
|
552
|
+
) -> JobHandle[T1ExperimentResult]: ...
|
|
553
|
+
|
|
554
|
+
def submit(self, functional: Functional, device: str, job_name: str | None = None) -> JobHandle[FunctionalResult]:
|
|
292
555
|
"""
|
|
293
556
|
Submit a quantum functional for execution on the selected device.
|
|
294
557
|
|
|
@@ -299,6 +562,9 @@ class SpeQtrum:
|
|
|
299
562
|
|
|
300
563
|
* :class:`~qilisdk.functionals.sampling.Sampling`
|
|
301
564
|
* :class:`~qilisdk.functionals.time_evolution.TimeEvolution`
|
|
565
|
+
* :class:`~qilisdk.functionals.variational_program.VariationalProgram`
|
|
566
|
+
* :class:`~qilisdk.speqtrum.experiments.experiment_functional.RabiExperiment`
|
|
567
|
+
* :class:`~qilisdk.speqtrum.experiments.experiment_functional.T1Experiment`
|
|
302
568
|
|
|
303
569
|
A backend device must be selected beforehand with
|
|
304
570
|
:py:meth:`set_device`.
|
|
@@ -308,112 +574,223 @@ class SpeQtrum:
|
|
|
308
574
|
``Sampling`` or ``TimeEvolution``) that defines the quantum
|
|
309
575
|
workload to be executed.
|
|
310
576
|
device: Device code returned by :py:meth:`list_devices`.
|
|
577
|
+
job_name (optional): The name of the job, this can help you identify different jobs easier. Default: None.
|
|
311
578
|
|
|
312
579
|
Returns:
|
|
313
|
-
|
|
580
|
+
JobHandle: A typed handle carrying the numeric job identifier and result type metadata.
|
|
314
581
|
|
|
315
582
|
Raises:
|
|
316
583
|
NotImplementedError: If *functional* is not of a supported type.
|
|
317
584
|
"""
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
585
|
+
if isinstance(functional, VariationalProgram):
|
|
586
|
+
inner = functional.functional
|
|
587
|
+
if isinstance(inner, Sampling):
|
|
588
|
+
return self._submit_variational_program(
|
|
589
|
+
cast("VariationalProgram[Sampling]", functional), device, job_name
|
|
590
|
+
)
|
|
591
|
+
if isinstance(inner, TimeEvolution):
|
|
592
|
+
return self._submit_variational_program(
|
|
593
|
+
cast("VariationalProgram[TimeEvolution]", functional), device, job_name
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
# Fallback to untyped handle for custom primitives.
|
|
597
|
+
job_handle = self._submit_variational_program(cast("VariationalProgram[Any]", functional), device, job_name)
|
|
598
|
+
return cast("JobHandle[FunctionalResult]", job_handle)
|
|
599
|
+
|
|
600
|
+
if isinstance(functional, Sampling):
|
|
601
|
+
return self._submit_sampling(functional, device, job_name)
|
|
602
|
+
|
|
603
|
+
if isinstance(functional, TimeEvolution):
|
|
604
|
+
return self._submit_time_evolution(functional, device, job_name)
|
|
605
|
+
|
|
606
|
+
if isinstance(functional, RabiExperiment):
|
|
607
|
+
return self._submit_rabi(functional, device, job_name)
|
|
608
|
+
|
|
609
|
+
if isinstance(functional, T1Experiment):
|
|
610
|
+
return self._submit_t1(functional, device, job_name)
|
|
611
|
+
|
|
612
|
+
if isinstance(functional, T2Experiment):
|
|
613
|
+
return self._submit_t2(functional, device, job_name)
|
|
614
|
+
|
|
615
|
+
if isinstance(functional, TwoTonesExperiment):
|
|
616
|
+
return self._submit_two_tones(functional, device, job_name)
|
|
617
|
+
|
|
618
|
+
logger.error("Unsupported functional type: {}", type(functional).__qualname__)
|
|
619
|
+
raise NotImplementedError(f"{type(self).__qualname__} does not support {type(functional).__qualname__}")
|
|
620
|
+
|
|
621
|
+
def _submit_sampling(
|
|
622
|
+
self, sampling: Sampling, device: str, job_name: str | None = None
|
|
623
|
+
) -> JobHandle[SamplingResult]:
|
|
332
624
|
payload = ExecutePayload(
|
|
333
625
|
type=ExecuteType.SAMPLING,
|
|
334
626
|
sampling_payload=SamplingPayload(sampling=sampling),
|
|
335
627
|
)
|
|
336
|
-
json = {
|
|
628
|
+
json = {
|
|
629
|
+
"device_code": device,
|
|
630
|
+
"payload": payload.model_dump_json(),
|
|
631
|
+
"job_type": JobType.DIGITAL,
|
|
632
|
+
"meta": {},
|
|
633
|
+
}
|
|
634
|
+
if job_name:
|
|
635
|
+
json["name"] = job_name
|
|
337
636
|
logger.debug("Executing Sampling on device {}", device)
|
|
338
|
-
with
|
|
339
|
-
response = client.post(
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
def _submit_rabi_program(self, rabi_experiment: RabiExperiment, device: str) -> int:
|
|
637
|
+
with self._create_client() as client:
|
|
638
|
+
response = client.post("/execute", json=json, extensions=_request_extensions(context="Executing Sampling"))
|
|
639
|
+
job = JobId(**response.json())
|
|
640
|
+
logger.info("Sampling job submitted: {}", job.id)
|
|
641
|
+
return JobHandle.sampling(job.id)
|
|
642
|
+
|
|
643
|
+
def _submit_rabi(
|
|
644
|
+
self, rabi_experiment: RabiExperiment, device: str, job_name: str | None = None
|
|
645
|
+
) -> JobHandle[RabiExperimentResult]:
|
|
349
646
|
payload = ExecutePayload(
|
|
350
647
|
type=ExecuteType.RABI_EXPERIMENT,
|
|
351
648
|
rabi_experiment_payload=RabiExperimentPayload(rabi_experiment=rabi_experiment),
|
|
352
649
|
)
|
|
353
|
-
json = {
|
|
650
|
+
json = {
|
|
651
|
+
"device_code": device,
|
|
652
|
+
"payload": payload.model_dump_json(),
|
|
653
|
+
"job_type": JobType.PULSE,
|
|
654
|
+
"meta": {},
|
|
655
|
+
}
|
|
656
|
+
if job_name:
|
|
657
|
+
json["name"] = job_name
|
|
354
658
|
logger.debug("Executing Rabi experiment on device {}", device)
|
|
355
|
-
with
|
|
659
|
+
with self._create_client() as client:
|
|
356
660
|
response = client.post(
|
|
357
|
-
|
|
358
|
-
headers=self._get_authorized_headers(),
|
|
661
|
+
"/execute",
|
|
359
662
|
json=json,
|
|
663
|
+
extensions=_request_extensions(context="Executing Rabi experiment"),
|
|
360
664
|
)
|
|
361
|
-
|
|
362
|
-
job = JobId(**response.json())
|
|
665
|
+
job = JobId(**response.json())
|
|
363
666
|
logger.info("Rabi experiment job submitted: {}", job.id)
|
|
364
|
-
return job.id
|
|
667
|
+
return JobHandle.rabi_experiment(job.id)
|
|
365
668
|
|
|
366
|
-
def
|
|
669
|
+
def _submit_t1(
|
|
670
|
+
self, t1_experiment: T1Experiment, device: str, job_name: str | None = None
|
|
671
|
+
) -> JobHandle[T1ExperimentResult]:
|
|
367
672
|
payload = ExecutePayload(
|
|
368
673
|
type=ExecuteType.T1_EXPERIMENT,
|
|
369
674
|
t1_experiment_payload=T1ExperimentPayload(t1_experiment=t1_experiment),
|
|
370
675
|
)
|
|
371
|
-
json = {
|
|
676
|
+
json = {
|
|
677
|
+
"device_code": device,
|
|
678
|
+
"payload": payload.model_dump_json(),
|
|
679
|
+
"job_type": JobType.PULSE,
|
|
680
|
+
"meta": {},
|
|
681
|
+
}
|
|
682
|
+
if job_name:
|
|
683
|
+
json["name"] = job_name
|
|
372
684
|
logger.debug("Executing T1 experiment on device {}", device)
|
|
373
|
-
with
|
|
685
|
+
with self._create_client() as client:
|
|
374
686
|
response = client.post(
|
|
375
|
-
|
|
376
|
-
headers=self._get_authorized_headers(),
|
|
687
|
+
"/execute",
|
|
377
688
|
json=json,
|
|
689
|
+
extensions=_request_extensions(context="Executing T1 experiment"),
|
|
378
690
|
)
|
|
379
|
-
|
|
380
|
-
job = JobId(**response.json())
|
|
691
|
+
job = JobId(**response.json())
|
|
381
692
|
logger.info("T1 experiment job submitted: {}", job.id)
|
|
382
|
-
return job.id
|
|
693
|
+
return JobHandle.t1_experiment(job.id)
|
|
383
694
|
|
|
384
|
-
def
|
|
695
|
+
def _submit_t2(
|
|
696
|
+
self, t2_experiment: T2Experiment, device: str, job_name: str | None = None
|
|
697
|
+
) -> JobHandle[T2ExperimentResult]:
|
|
698
|
+
payload = ExecutePayload(
|
|
699
|
+
type=ExecuteType.T2_EXPERIMENT,
|
|
700
|
+
t2_experiment_payload=T2ExperimentPayload(t2_experiment=t2_experiment),
|
|
701
|
+
)
|
|
702
|
+
json = {
|
|
703
|
+
"device_code": device,
|
|
704
|
+
"payload": payload.model_dump_json(),
|
|
705
|
+
"job_type": JobType.PULSE,
|
|
706
|
+
"meta": {},
|
|
707
|
+
}
|
|
708
|
+
if job_name:
|
|
709
|
+
json["name"] = job_name
|
|
710
|
+
logger.debug("Executing T2 experiment on device {}", device)
|
|
711
|
+
with self._create_client() as client:
|
|
712
|
+
response = client.post(
|
|
713
|
+
"/execute",
|
|
714
|
+
json=json,
|
|
715
|
+
extensions=_request_extensions(context="Executing T2 experiment"),
|
|
716
|
+
)
|
|
717
|
+
job = JobId(**response.json())
|
|
718
|
+
logger.info("T2 experiment job submitted: {}", job.id)
|
|
719
|
+
return JobHandle.t2_experiment(job.id)
|
|
720
|
+
|
|
721
|
+
def _submit_two_tones(
|
|
722
|
+
self, two_tones_experiment: TwoTonesExperiment, device: str, job_name: str | None = None
|
|
723
|
+
) -> JobHandle[TwoTonesExperimentResult]:
|
|
724
|
+
payload = ExecutePayload(
|
|
725
|
+
type=ExecuteType.TWO_TONES_EXPERIMENT,
|
|
726
|
+
two_tones_experiment_payload=TwoTonesExperimentPayload(two_tones_experiment=two_tones_experiment),
|
|
727
|
+
)
|
|
728
|
+
json = {
|
|
729
|
+
"device_code": device,
|
|
730
|
+
"payload": payload.model_dump_json(),
|
|
731
|
+
"job_type": JobType.PULSE,
|
|
732
|
+
"meta": {},
|
|
733
|
+
}
|
|
734
|
+
if job_name:
|
|
735
|
+
json["name"] = job_name
|
|
736
|
+
logger.debug("Executing Two-Tones experiment on device {}", device)
|
|
737
|
+
with self._create_client() as client:
|
|
738
|
+
response = client.post(
|
|
739
|
+
"/execute",
|
|
740
|
+
json=json,
|
|
741
|
+
extensions=_request_extensions(context="Executing Two-Tones experiment"),
|
|
742
|
+
)
|
|
743
|
+
job = JobId(**response.json())
|
|
744
|
+
logger.info("Two-Tones experiment job submitted: {}", job.id)
|
|
745
|
+
return JobHandle.two_tones_experiment(job.id)
|
|
746
|
+
|
|
747
|
+
def _submit_time_evolution(
|
|
748
|
+
self, time_evolution: TimeEvolution, device: str, job_name: str | None = None
|
|
749
|
+
) -> JobHandle[TimeEvolutionResult]:
|
|
385
750
|
payload = ExecutePayload(
|
|
386
751
|
type=ExecuteType.TIME_EVOLUTION,
|
|
387
752
|
time_evolution_payload=TimeEvolutionPayload(time_evolution=time_evolution),
|
|
388
753
|
)
|
|
389
|
-
json = {
|
|
754
|
+
json = {
|
|
755
|
+
"device_code": device,
|
|
756
|
+
"payload": payload.model_dump_json(),
|
|
757
|
+
"job_type": JobType.ANALOG,
|
|
758
|
+
"meta": {},
|
|
759
|
+
}
|
|
760
|
+
if job_name:
|
|
761
|
+
json["name"] = job_name
|
|
390
762
|
logger.debug("Executing time evolution on device {}", device)
|
|
391
|
-
with
|
|
763
|
+
with self._create_client() as client:
|
|
392
764
|
response = client.post(
|
|
393
|
-
|
|
394
|
-
headers=self._get_authorized_headers(),
|
|
765
|
+
"/execute",
|
|
395
766
|
json=json,
|
|
767
|
+
extensions=_request_extensions(context="Executing time evolution"),
|
|
396
768
|
)
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
def _submit_variational_program(
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
769
|
+
job = JobId(**response.json())
|
|
770
|
+
logger.info("Time Evolution job submitted: {}", job.id)
|
|
771
|
+
return JobHandle.time_evolution(job.id)
|
|
772
|
+
|
|
773
|
+
@overload
|
|
774
|
+
def _submit_variational_program(
|
|
775
|
+
self, variational_program: VariationalProgram[Sampling], device: str, job_name: str | None = None
|
|
776
|
+
) -> JobHandle[VariationalProgramResult[SamplingResult]]: ...
|
|
777
|
+
|
|
778
|
+
@overload
|
|
779
|
+
def _submit_variational_program(
|
|
780
|
+
self, variational_program: VariationalProgram[TimeEvolution], device: str, job_name: str | None = None
|
|
781
|
+
) -> JobHandle[VariationalProgramResult[TimeEvolutionResult]]: ...
|
|
782
|
+
|
|
783
|
+
@overload
|
|
784
|
+
def _submit_variational_program(
|
|
785
|
+
self, variational_program: VariationalProgram[Any], device: str, job_name: str | None = None
|
|
786
|
+
) -> JobHandle[VariationalProgramResult]: ...
|
|
787
|
+
|
|
788
|
+
def _submit_variational_program(
|
|
789
|
+
self, variational_program: VariationalProgram[Any], device: str, job_name: str | None = None
|
|
790
|
+
) -> JobHandle[VariationalProgramResult]:
|
|
412
791
|
payload = ExecutePayload(
|
|
413
792
|
type=ExecuteType.VARIATIONAL_PROGRAM,
|
|
414
|
-
variational_program_payload=VariationalProgramPayload(
|
|
415
|
-
variational_program=variational_program,
|
|
416
|
-
),
|
|
793
|
+
variational_program_payload=VariationalProgramPayload(variational_program=variational_program),
|
|
417
794
|
)
|
|
418
795
|
json = {
|
|
419
796
|
"device_code": device,
|
|
@@ -421,12 +798,20 @@ class SpeQtrum:
|
|
|
421
798
|
"job_type": JobType.VARIATIONAL,
|
|
422
799
|
"meta": {},
|
|
423
800
|
}
|
|
424
|
-
|
|
801
|
+
if job_name:
|
|
802
|
+
json["name"] = job_name
|
|
803
|
+
logger.debug("Executing variational program on device {}", device)
|
|
804
|
+
with self._create_client() as client:
|
|
425
805
|
response = client.post(
|
|
426
|
-
|
|
427
|
-
headers=self._get_authorized_headers(),
|
|
806
|
+
"/execute",
|
|
428
807
|
json=json,
|
|
808
|
+
extensions=_request_extensions(context="Executing variational program"),
|
|
429
809
|
)
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
810
|
+
job = JobId(**response.json())
|
|
811
|
+
logger.info("Variational program job submitted: {}", job.id)
|
|
812
|
+
inner = variational_program.functional
|
|
813
|
+
if isinstance(inner, Sampling):
|
|
814
|
+
return JobHandle.variational_program(job.id, result_type=SamplingResult)
|
|
815
|
+
if isinstance(inner, TimeEvolution):
|
|
816
|
+
return JobHandle.variational_program(job.id, result_type=TimeEvolutionResult)
|
|
817
|
+
return JobHandle.variational_program(job.id)
|