qilisdk 0.1.1__tar.gz → 0.1.2__tar.gz

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.
Files changed (81) hide show
  1. {qilisdk-0.1.1 → qilisdk-0.1.2}/CHANGELOG.md +9 -0
  2. {qilisdk-0.1.1 → qilisdk-0.1.2}/PKG-INFO +1 -1
  3. {qilisdk-0.1.1 → qilisdk-0.1.2}/pyproject.toml +1 -1
  4. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/digital/ansatz.py +18 -1
  5. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/digital/vqe.py +1 -0
  6. qilisdk-0.1.2/src/qilisdk/extras/qaas/models.py +132 -0
  7. qilisdk-0.1.2/src/qilisdk/extras/qaas/qaas_backend.py +254 -0
  8. qilisdk-0.1.2/src/qilisdk/extras/qaas/qaas_time_evolution_result.py +20 -0
  9. qilisdk-0.1.2/src/qilisdk/extras/qaas/qaas_vqe_result.py +20 -0
  10. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/yaml.py +34 -2
  11. {qilisdk-0.1.1 → qilisdk-0.1.2}/uv.lock +865 -865
  12. qilisdk-0.1.1/src/qilisdk/extras/qaas/models.py +0 -57
  13. qilisdk-0.1.1/src/qilisdk/extras/qaas/qaas_backend.py +0 -154
  14. {qilisdk-0.1.1 → qilisdk-0.1.2}/.github/workflows/code_quality.yml +0 -0
  15. {qilisdk-0.1.1 → qilisdk-0.1.2}/.github/workflows/publish.yml +0 -0
  16. {qilisdk-0.1.1 → qilisdk-0.1.2}/.github/workflows/tests.yml +0 -0
  17. {qilisdk-0.1.1 → qilisdk-0.1.2}/.gitignore +0 -0
  18. {qilisdk-0.1.1 → qilisdk-0.1.2}/.pre-commit-config.yaml +0 -0
  19. {qilisdk-0.1.1 → qilisdk-0.1.2}/.python-version +0 -0
  20. {qilisdk-0.1.1 → qilisdk-0.1.2}/LICENCE +0 -0
  21. {qilisdk-0.1.1 → qilisdk-0.1.2}/README.md +0 -0
  22. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/__init__.py +0 -0
  23. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/__init__.pyi +0 -0
  24. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/_optionals.py +0 -0
  25. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/analog/__init__.py +0 -0
  26. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/analog/algorithms.py +0 -0
  27. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/analog/analog_backend.py +0 -0
  28. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/analog/analog_result.py +0 -0
  29. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/analog/exceptions.py +0 -0
  30. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/analog/hamiltonian.py +0 -0
  31. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/analog/quantum_objects.py +0 -0
  32. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/analog/schedule.py +0 -0
  33. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/common/__init__.py +0 -0
  34. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/common/algorithm.py +0 -0
  35. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/common/backend.py +0 -0
  36. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/common/model.py +0 -0
  37. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/common/optimizer.py +0 -0
  38. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/common/optimizer_result.py +0 -0
  39. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/common/result.py +0 -0
  40. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/digital/__init__.py +0 -0
  41. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/digital/circuit.py +0 -0
  42. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/digital/digital_algorithm.py +0 -0
  43. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/digital/digital_backend.py +0 -0
  44. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/digital/digital_result.py +0 -0
  45. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/digital/exceptions.py +0 -0
  46. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/digital/gates.py +0 -0
  47. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/extras/__init__.py +0 -0
  48. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/extras/__init__.pyi +0 -0
  49. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/extras/cuda/__init__.py +0 -0
  50. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/extras/cuda/cuda_analog_result.py +0 -0
  51. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/extras/cuda/cuda_backend.py +0 -0
  52. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/extras/cuda/cuda_digital_result.py +0 -0
  53. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/extras/qaas/__init__.py +0 -0
  54. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/extras/qaas/keyring.py +0 -0
  55. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/extras/qaas/qaas_analog_result.py +0 -0
  56. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/extras/qaas/qaas_digital_result.py +0 -0
  57. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/extras/qaas/qaas_settings.py +0 -0
  58. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/py.typed +0 -0
  59. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/utils/__init__.py +0 -0
  60. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/utils/openqasm2.py +0 -0
  61. {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/utils/serialization.py +0 -0
  62. {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/__init__.py +0 -0
  63. {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/analog/__init__.py +0 -0
  64. {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/analog/test_analog_result.py +0 -0
  65. {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/analog/test_hamiltionian.py +0 -0
  66. {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/analog/test_quantum_objects.py +0 -0
  67. {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/analog/test_schedule.py +0 -0
  68. {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/analog/test_time_evolution.py +0 -0
  69. {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/common/__init__.py +0 -0
  70. {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/common/test_scipy_optimizer.py +0 -0
  71. {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/digital/__init__.py +0 -0
  72. {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/digital/test_ansatz.py +0 -0
  73. {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/digital/test_circuit.py +0 -0
  74. {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/digital/test_gates.py +0 -0
  75. {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/digital/test_vqe.py +0 -0
  76. {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/extras/__init__.py +0 -0
  77. {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/extras/test_cuda_backend.py +0 -0
  78. {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/test_placeholder.py +0 -0
  79. {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/utils/__init__.py +0 -0
  80. {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/utils/test_openqasm2.py +0 -0
  81. {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/utils/test_serialization.py +0 -0
@@ -1,3 +1,12 @@
1
+ # Qilisdk 0.1.2 (2025-04-22)
2
+
3
+ ### Misc
4
+
5
+ - Improved `QaaSBacked` functionality to include methods for executing digital and analog algorithms.
6
+
7
+ [PR #27](https://github.com/qilimanjaro-tech/qilisdk/pulls/27)
8
+
9
+
1
10
  # Qilisdk 0.1.1 (2025-04-11)
2
11
 
3
12
  ### Misc
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qilisdk
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: qilisdk is a Python framework for writing digital and analog quantum algorithms and executing them across multiple quantum backends. Its modular design streamlines the development process and enables easy integration with a variety of quantum platforms.
5
5
  Author-email: Qilimanjaro Quantum Tech <info@qilimanjaro.tech>
6
6
  License-File: LICENCE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "qilisdk"
3
- version = "0.1.1"
3
+ version = "0.1.2"
4
4
  description = "qilisdk is a Python framework for writing digital and analog quantum algorithms and executing them across multiple quantum backends. Its modular design streamlines the development process and enables easy integration with a variety of quantum platforms."
5
5
  readme = "README.md"
6
6
  authors = [{name = "Qilimanjaro Quantum Tech", email = "info@qilimanjaro.tech"}]
@@ -12,7 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  from abc import ABC, abstractmethod
15
- from typing import ClassVar, Literal, Union
15
+ from typing import Any, ClassVar, Literal, Union
16
16
 
17
17
  from qilisdk.digital.circuit import Circuit
18
18
  from qilisdk.digital.gates import CNOT, CZ, U1, U2, U3, M
@@ -103,6 +103,23 @@ class HardwareEfficientAnsatz(Ansatz):
103
103
  "grouped": self._construct_layer_grouped,
104
104
  }
105
105
 
106
+ def __getstate__(self) -> dict[str, Any]:
107
+ state = self.__dict__.copy()
108
+ # Exclude the mapping that contains bound methods (not serializable).
109
+ state.pop("construct_layer_handlers", None)
110
+ return state
111
+
112
+ def __setstate__(self, state) -> None: # noqa: ANN001
113
+ """
114
+ Restore the object's state after deserialization and reinitialize any attributes that were omitted.
115
+ """
116
+ self.__dict__.update(state)
117
+ # Reconstruct the mapping with the proper bound methods.
118
+ self.construct_layer_handlers = {
119
+ "interposed": self._construct_layer_interposed,
120
+ "grouped": self._construct_layer_grouped,
121
+ }
122
+
106
123
  @property
107
124
  def nparameters(self) -> int:
108
125
  """
@@ -83,6 +83,7 @@ class VQEResult(Result):
83
83
  )
84
84
 
85
85
 
86
+ @yaml.register_class
86
87
  class VQE(DigitalAlgorithm):
87
88
  """
88
89
  Implements the Variational Quantum Eigensolver (VQE) algorithm.
@@ -0,0 +1,132 @@
1
+ # Copyright 2025 Qilimanjaro Quantum Tech
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from enum import Enum
16
+
17
+ from pydantic import BaseModel, ConfigDict, Field
18
+
19
+ from qilisdk.analog import Hamiltonian, QuantumObject, Schedule, TimeEvolution
20
+ from qilisdk.analog.hamiltonian import PauliOperator
21
+ from qilisdk.common.optimizer import Optimizer
22
+ from qilisdk.digital import VQE, Circuit
23
+ from qilisdk.yaml import yaml
24
+
25
+ from .qaas_analog_result import QaaSAnalogResult
26
+ from .qaas_digital_result import QaaSDigitalResult
27
+ from .qaas_time_evolution_result import QaaSTimeEvolutionResult
28
+ from .qaas_vqe_result import QaaSVQEResult
29
+
30
+
31
+ class QaaSModel(BaseModel):
32
+ model_config = ConfigDict(validate_by_name=True, validate_by_alias=True, arbitrary_types_allowed=True)
33
+
34
+
35
+ class LoginPayload(BaseModel): ...
36
+
37
+
38
+ class Token(QaaSModel):
39
+ """
40
+ Represents the structure of the login response:
41
+ {
42
+ "accessToken": "...",
43
+ "expiresIn": 123456789,
44
+ "issuedAt": "123456789",
45
+ "refreshToken": "...",
46
+ "tokenType": "bearer"
47
+ }
48
+ """
49
+
50
+ access_token: str = Field(alias="accessToken")
51
+ expires_in: int = Field(alias="expiresIn")
52
+ issued_at: str = Field(alias="issuedAt")
53
+ refresh_token: str = Field(alias="refreshToken")
54
+ token_type: str = Field(alias="tokenType")
55
+
56
+
57
+ class DeviceStatus(str, Enum):
58
+ """Device status typing for posting"""
59
+
60
+ ONLINE = "online"
61
+ MAINTENANCE = "maintenance"
62
+ OFFLINE = "offline"
63
+
64
+
65
+ class DeviceType(str, Enum):
66
+ """Device type"""
67
+
68
+ QUANTUM_DIGITAL = "quantum_device"
69
+ QUANTUM_ANALOG = "quantum_analog_device"
70
+ SIMULATOR = "simulator_device"
71
+
72
+
73
+ @yaml.register_class
74
+ class Device(QaaSModel):
75
+ id: int = Field(...)
76
+ name: str = Field(...)
77
+ status: DeviceStatus = Field(...)
78
+ type: DeviceType = Field(...)
79
+
80
+
81
+ class ExecutePayloadType(str, Enum):
82
+ DIGITAL = "digital"
83
+ ANALOG = "analog"
84
+ VQE = "vqe"
85
+ TIME_EVOLUTION = "time_evolution"
86
+
87
+
88
+ @yaml.register_class
89
+ class DigitalPayload(QaaSModel):
90
+ circuit: Circuit = Field(...)
91
+ nshots: int = Field(...)
92
+
93
+
94
+ @yaml.register_class
95
+ class AnalogPayload(QaaSModel):
96
+ schedule: Schedule = Field(...)
97
+ initial_state: QuantumObject = Field(...)
98
+ observables: list[PauliOperator | Hamiltonian] = Field(...)
99
+ store_intermediate_results: bool = Field(...)
100
+
101
+
102
+ @yaml.register_class
103
+ class VQEPayload(QaaSModel):
104
+ vqe: VQE = Field(...)
105
+ optimizer: Optimizer = Field(...)
106
+ nshots: int = Field(...)
107
+ store_intermediate_results: bool = Field(...)
108
+
109
+
110
+ @yaml.register_class
111
+ class TimeEvolutionPayload(QaaSModel):
112
+ time_evolution: TimeEvolution = Field()
113
+ store_intermediate_results: bool = Field()
114
+
115
+
116
+ @yaml.register_class
117
+ class ExecutePayload(QaaSModel):
118
+ type: ExecutePayloadType = Field(...)
119
+ device_id: int = Field(...)
120
+ digital_payload: DigitalPayload | None = None
121
+ analog_payload: AnalogPayload | None = None
122
+ vqe_payload: VQEPayload | None = None
123
+ time_evolution_payload: TimeEvolutionPayload | None = None
124
+
125
+
126
+ @yaml.register_class
127
+ class ExecuteResponse(QaaSModel):
128
+ type: ExecutePayloadType = Field(...)
129
+ digital_result: QaaSDigitalResult | None = None
130
+ analog_result: QaaSAnalogResult | None = None
131
+ vqe_result: QaaSVQEResult | None = None
132
+ time_evolution_result: QaaSTimeEvolutionResult | None = None
@@ -0,0 +1,254 @@
1
+ # Copyright 2025 Qilimanjaro Quantum Tech
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import logging
18
+ from base64 import urlsafe_b64encode
19
+ from datetime import datetime, timezone
20
+ from typing import TYPE_CHECKING, cast
21
+
22
+ import httpx
23
+ from pydantic import TypeAdapter, ValidationError
24
+
25
+ from qilisdk.analog.analog_backend import AnalogBackend
26
+ from qilisdk.digital.digital_backend import DigitalBackend
27
+
28
+ from .keyring import delete_credentials, load_credentials, store_credentials
29
+ from .models import (
30
+ AnalogPayload,
31
+ Device,
32
+ DigitalPayload,
33
+ ExecutePayload,
34
+ ExecutePayloadType,
35
+ ExecuteResponse,
36
+ TimeEvolutionPayload,
37
+ Token,
38
+ VQEPayload,
39
+ )
40
+ from .qaas_settings import QaaSSettings
41
+
42
+ if TYPE_CHECKING:
43
+ from qilisdk.analog import Hamiltonian, QuantumObject, Schedule, TimeEvolution
44
+ from qilisdk.analog.hamiltonian import PauliOperator
45
+ from qilisdk.common.optimizer import Optimizer
46
+ from qilisdk.digital import VQE, Circuit
47
+
48
+ from .qaas_analog_result import QaaSAnalogResult
49
+ from .qaas_digital_result import QaaSDigitalResult
50
+ from .qaas_time_evolution_result import QaaSTimeEvolutionResult
51
+ from .qaas_vqe_result import QaaSVQEResult
52
+
53
+ logging.basicConfig(
54
+ format="%(levelname)s [%(asctime)s] %(name)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S", level=logging.DEBUG
55
+ )
56
+
57
+
58
+ class QaaSBackend(DigitalBackend, AnalogBackend):
59
+ """
60
+ Manages communication with a hypothetical QaaS service via synchronous HTTP calls.
61
+
62
+ Credentials to log in can come from:
63
+ a) method parameters,
64
+ b) environment (via Pydantic),
65
+ c) keyring (fallback).
66
+ """
67
+
68
+ _api_url: str = "https://qilimanjaroqaas.ddns.net:8080/api/v1"
69
+
70
+ def __init__(self) -> None:
71
+ """
72
+ Normally, you won't call __init__() directly.
73
+ Instead, use QaaSBackend.login(...) to create a logged-in instance.
74
+ """ # noqa: DOC501
75
+ credentials = load_credentials()
76
+ if credentials is None:
77
+ raise RuntimeError(
78
+ "No valid QaaS credentials found in keyring."
79
+ "Please call QaaSBackend.login(username, apikey) or ensure environment variables are set."
80
+ )
81
+ self._username, self._token = credentials
82
+ self._selected_device: Device | None = None
83
+
84
+ @property
85
+ def selected_device(self) -> Device | None:
86
+ return self._selected_device
87
+
88
+ def set_device(self, device: Device) -> None:
89
+ self._selected_device = device
90
+
91
+ @classmethod
92
+ def login(
93
+ cls,
94
+ username: str | None = None,
95
+ apikey: str | None = None,
96
+ ) -> bool:
97
+ # Use provided parameters or fall back to environment variables via Settings()
98
+ if not username or not apikey:
99
+ try:
100
+ # Load environment variables into the settings object.
101
+ settings = QaaSSettings() # type: ignore[call-arg]
102
+ username = username or settings.username
103
+ apikey = apikey or settings.apikey
104
+ except ValidationError:
105
+ # Environment credentials could not be validated.
106
+ # Optionally, log error details here.
107
+ return False
108
+
109
+ if not username or not apikey:
110
+ # Insufficient credentials provided.
111
+ return False
112
+
113
+ # Send login request to QaaS
114
+ try:
115
+ assertion = {
116
+ "username": username,
117
+ "api_key": apikey,
118
+ "user_id": None,
119
+ "audience": QaaSBackend._api_url,
120
+ "iat": int(datetime.now(timezone.utc).timestamp()),
121
+ }
122
+ encoded_assertion = urlsafe_b64encode(json.dumps(assertion, indent=2).encode("utf-8")).decode("utf-8")
123
+ with httpx.Client(timeout=10.0) as client:
124
+ response = client.post(
125
+ QaaSBackend._api_url + "/authorisation-tokens",
126
+ json={
127
+ "grantType": "urn:ietf:params:oauth:grant-type:jwt-bearer",
128
+ "assertion": encoded_assertion,
129
+ "scope": "user profile",
130
+ },
131
+ headers={"X-Client-Version": "0.23.2"},
132
+ )
133
+ response.raise_for_status()
134
+ # Suppose QaaS returns {"token": "..."} in JSON
135
+ token = Token(**response.json())
136
+ except httpx.RequestError:
137
+ # Log error message
138
+ return False
139
+
140
+ store_credentials(username=username, token=token)
141
+ return True
142
+
143
+ @classmethod
144
+ def logout(cls) -> None:
145
+ delete_credentials()
146
+
147
+ def list_devices(self) -> list[Device]:
148
+ with httpx.Client(timeout=20.0) as client:
149
+ response = client.get(
150
+ QaaSBackend._api_url + "/devices",
151
+ headers={"X-Client-Version": "0.23.2", "Authorization": f"Bearer {self._token.access_token}"},
152
+ )
153
+ response.raise_for_status()
154
+
155
+ devices_list_adapter = TypeAdapter(list[Device])
156
+ devices = devices_list_adapter.validate_python(response.json()["items"])
157
+
158
+ # Previous two lines are the same as doing:
159
+ # response_json = response.json()
160
+ # devices = [Device(**item) for item in response_json["items"]]
161
+
162
+ return devices
163
+
164
+ def _ensure_device_selected(self) -> Device:
165
+ if self._selected_device is None:
166
+ raise ValueError("Device not selected.")
167
+ return self._selected_device
168
+
169
+ def execute(self, circuit: Circuit, nshots: int = 1000) -> QaaSDigitalResult:
170
+ device = self._ensure_device_selected()
171
+ payload = ExecutePayload(
172
+ type=ExecutePayloadType.DIGITAL,
173
+ device_id=device.id,
174
+ digital_payload=DigitalPayload(circuit=circuit, nshots=nshots),
175
+ )
176
+ with httpx.Client(timeout=20.0) as client:
177
+ response = client.post(
178
+ QaaSBackend._api_url + "/execute",
179
+ headers={"X-Client-Version": "0.23.2", "Authorization": f"Bearer {self._token.access_token}"},
180
+ json=payload.model_dump_json(),
181
+ )
182
+ response.raise_for_status()
183
+ execute_response = ExecuteResponse(**response.json())
184
+ return cast("QaaSDigitalResult", execute_response.digital_result)
185
+
186
+ def evolve(
187
+ self,
188
+ schedule: Schedule,
189
+ initial_state: QuantumObject,
190
+ observables: list[PauliOperator | Hamiltonian],
191
+ store_intermediate_results: bool = False,
192
+ ) -> QaaSAnalogResult:
193
+ device = self._ensure_device_selected()
194
+ payload = ExecutePayload(
195
+ type=ExecutePayloadType.ANALOG,
196
+ device_id=device.id,
197
+ analog_payload=AnalogPayload(
198
+ schedule=schedule,
199
+ initial_state=initial_state,
200
+ observables=observables,
201
+ store_intermediate_results=store_intermediate_results,
202
+ ),
203
+ )
204
+ with httpx.Client(timeout=20.0) as client:
205
+ response = client.post(
206
+ QaaSBackend._api_url + "/execute",
207
+ headers={"X-Client-Version": "0.23.2", "Authorization": f"Bearer {self._token.access_token}"},
208
+ json=payload.model_dump_json(),
209
+ )
210
+ response.raise_for_status()
211
+ execute_response = ExecuteResponse(**response.json())
212
+ return cast("QaaSAnalogResult", execute_response.analog_result)
213
+
214
+ def run_vqe(
215
+ self, vqe: VQE, optimizer: Optimizer, nshots: int = 1000, store_intermediate_results: bool = False
216
+ ) -> QaaSVQEResult:
217
+ device = self._ensure_device_selected()
218
+ payload = ExecutePayload(
219
+ type=ExecutePayloadType.VQE,
220
+ device_id=device.id,
221
+ vqe_payload=VQEPayload(
222
+ vqe=vqe, optimizer=optimizer, nshots=nshots, store_intermediate_results=store_intermediate_results
223
+ ),
224
+ )
225
+ with httpx.Client(timeout=20.0) as client:
226
+ response = client.post(
227
+ QaaSBackend._api_url + "/execute",
228
+ headers={"X-Client-Version": "0.23.2", "Authorization": f"Bearer {self._token.access_token}"},
229
+ json=payload.model_dump_json(),
230
+ )
231
+ response.raise_for_status()
232
+ execute_response = ExecuteResponse(**response.json())
233
+ return cast("QaaSVQEResult", execute_response.vqe_result)
234
+
235
+ def run_time_evolution(
236
+ self, time_evolution: TimeEvolution, store_intermediate_results: bool = False
237
+ ) -> QaaSTimeEvolutionResult:
238
+ device = self._ensure_device_selected()
239
+ payload = ExecutePayload(
240
+ type=ExecutePayloadType.TIME_EVOLUTION,
241
+ device_id=device.id,
242
+ time_evolution_payload=TimeEvolutionPayload(
243
+ time_evolution=time_evolution, store_intermediate_results=store_intermediate_results
244
+ ),
245
+ )
246
+ with httpx.Client(timeout=20.0) as client:
247
+ response = client.post(
248
+ QaaSBackend._api_url + "/execute",
249
+ headers={"X-Client-Version": "0.23.2", "Authorization": f"Bearer {self._token.access_token}"},
250
+ json=payload.model_dump_json(),
251
+ )
252
+ response.raise_for_status()
253
+ execute_response = ExecuteResponse(**response.json())
254
+ return cast("QaaSTimeEvolutionResult", execute_response.time_evolution_result)
@@ -0,0 +1,20 @@
1
+ # Copyright 2025 Qilimanjaro Quantum Tech
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from qilisdk.analog.analog_result import AnalogResult
16
+ from qilisdk.yaml import yaml
17
+
18
+
19
+ @yaml.register_class
20
+ class QaaSTimeEvolutionResult(AnalogResult): ...
@@ -0,0 +1,20 @@
1
+ # Copyright 2025 Qilimanjaro Quantum Tech
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from qilisdk.digital.vqe import VQEResult
16
+ from qilisdk.yaml import yaml
17
+
18
+
19
+ @yaml.register_class
20
+ class QaaSVQEResult(VQEResult): ...
@@ -19,6 +19,7 @@ import types
19
19
 
20
20
  import numpy as np
21
21
  from dill import dumps, loads
22
+ from pydantic import BaseModel
22
23
  from ruamel.yaml import YAML
23
24
 
24
25
 
@@ -39,7 +40,7 @@ def ndarray_constructor(constructor, node):
39
40
 
40
41
  def function_representer(representer, data):
41
42
  """Represent a non-lambda function by serializing it."""
42
- serialized_function = base64.b64encode(dumps(data)).decode("utf-8")
43
+ serialized_function = base64.b64encode(dumps(data, recurse=True)).decode("utf-8")
43
44
  return representer.represent_scalar("!function", serialized_function)
44
45
 
45
46
 
@@ -51,7 +52,7 @@ def function_constructor(constructor, node):
51
52
 
52
53
  def lambda_representer(representer, data):
53
54
  """Represent a lambda function by serializing its code."""
54
- serialized_lambda = base64.b64encode(dumps(data)).decode("utf-8")
55
+ serialized_lambda = base64.b64encode(dumps(data, recurse=True)).decode("utf-8")
55
56
  return representer.represent_scalar("!lambda", serialized_lambda)
56
57
 
57
58
 
@@ -62,6 +63,33 @@ def lambda_constructor(constructor, node):
62
63
  return loads(serialized_lambda) # noqa: S301
63
64
 
64
65
 
66
+ def pydantic_model_representer(representer, data):
67
+ """Representer for Pydantic Models."""
68
+ value = {"type": f"{data.__class__.__module__}.{data.__class__.__name__}", "data": data.model_dump()}
69
+ return representer.represent_mapping("!PydanticModel", value)
70
+
71
+
72
+ def pydantic_model_constructor(constructor, node):
73
+ """Constructor for Pydantic Models."""
74
+ mapping = constructor.construct_mapping(node, deep=True)
75
+ model_type_str = mapping["type"]
76
+ data = mapping["data"]
77
+ module_name, class_name = model_type_str.rsplit(".", 1)
78
+ mod = __import__(module_name, fromlist=[class_name])
79
+ model_cls = getattr(mod, class_name)
80
+ return model_cls.model_validate(data)
81
+
82
+
83
+ def complex_representer(representer, data: complex):
84
+ value = {"real": data.real, "imag": data.imag}
85
+ return representer.represent_mapping("!complex", value)
86
+
87
+
88
+ def complex_constructor(constructor, node):
89
+ mapping = constructor.construct_mapping(node, deep=True)
90
+ return complex(mapping["real"], mapping["imag"])
91
+
92
+
65
93
  yaml = YAML(typ="unsafe")
66
94
  yaml.representer.add_representer(np.ndarray, ndarray_representer)
67
95
  yaml.constructor.add_constructor("!ndarray", ndarray_constructor)
@@ -69,3 +97,7 @@ yaml.representer.add_representer(types.FunctionType, function_representer)
69
97
  yaml.constructor.add_constructor("!function", function_constructor)
70
98
  yaml.representer.add_representer(types.LambdaType, lambda_representer)
71
99
  yaml.constructor.add_constructor("!lambda", lambda_constructor)
100
+ yaml.representer.add_representer(BaseModel, pydantic_model_representer)
101
+ yaml.constructor.add_constructor("!PydanticModel", pydantic_model_constructor)
102
+ yaml.representer.add_representer(complex, complex_representer)
103
+ yaml.constructor.add_constructor("!complex", complex_constructor)