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.
- {qilisdk-0.1.1 → qilisdk-0.1.2}/CHANGELOG.md +9 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/PKG-INFO +1 -1
- {qilisdk-0.1.1 → qilisdk-0.1.2}/pyproject.toml +1 -1
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/digital/ansatz.py +18 -1
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/digital/vqe.py +1 -0
- qilisdk-0.1.2/src/qilisdk/extras/qaas/models.py +132 -0
- qilisdk-0.1.2/src/qilisdk/extras/qaas/qaas_backend.py +254 -0
- qilisdk-0.1.2/src/qilisdk/extras/qaas/qaas_time_evolution_result.py +20 -0
- qilisdk-0.1.2/src/qilisdk/extras/qaas/qaas_vqe_result.py +20 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/yaml.py +34 -2
- {qilisdk-0.1.1 → qilisdk-0.1.2}/uv.lock +865 -865
- qilisdk-0.1.1/src/qilisdk/extras/qaas/models.py +0 -57
- qilisdk-0.1.1/src/qilisdk/extras/qaas/qaas_backend.py +0 -154
- {qilisdk-0.1.1 → qilisdk-0.1.2}/.github/workflows/code_quality.yml +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/.github/workflows/publish.yml +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/.github/workflows/tests.yml +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/.gitignore +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/.pre-commit-config.yaml +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/.python-version +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/LICENCE +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/README.md +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/__init__.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/__init__.pyi +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/_optionals.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/analog/__init__.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/analog/algorithms.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/analog/analog_backend.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/analog/analog_result.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/analog/exceptions.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/analog/hamiltonian.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/analog/quantum_objects.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/analog/schedule.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/common/__init__.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/common/algorithm.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/common/backend.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/common/model.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/common/optimizer.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/common/optimizer_result.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/common/result.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/digital/__init__.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/digital/circuit.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/digital/digital_algorithm.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/digital/digital_backend.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/digital/digital_result.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/digital/exceptions.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/digital/gates.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/extras/__init__.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/extras/__init__.pyi +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/extras/cuda/__init__.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/extras/cuda/cuda_analog_result.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/extras/cuda/cuda_backend.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/extras/cuda/cuda_digital_result.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/extras/qaas/__init__.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/extras/qaas/keyring.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/extras/qaas/qaas_analog_result.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/extras/qaas/qaas_digital_result.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/extras/qaas/qaas_settings.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/py.typed +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/utils/__init__.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/utils/openqasm2.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/src/qilisdk/utils/serialization.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/__init__.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/analog/__init__.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/analog/test_analog_result.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/analog/test_hamiltionian.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/analog/test_quantum_objects.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/analog/test_schedule.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/analog/test_time_evolution.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/common/__init__.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/common/test_scipy_optimizer.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/digital/__init__.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/digital/test_ansatz.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/digital/test_circuit.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/digital/test_gates.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/digital/test_vqe.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/extras/__init__.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/extras/test_cuda_backend.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/test_placeholder.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/utils/__init__.py +0 -0
- {qilisdk-0.1.1 → qilisdk-0.1.2}/tests/utils/test_openqasm2.py +0 -0
- {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.
|
|
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.
|
|
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
|
"""
|
|
@@ -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)
|