qilisdk 0.1.4__py3-none-any.whl → 0.1.6__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/__init__.py +11 -2
- qilisdk/__init__.pyi +2 -3
- qilisdk/_logging.py +135 -0
- qilisdk/_optionals.py +5 -7
- qilisdk/analog/__init__.py +3 -18
- qilisdk/analog/exceptions.py +2 -4
- qilisdk/analog/hamiltonian.py +455 -110
- qilisdk/analog/linear_schedule.py +121 -0
- qilisdk/analog/schedule.py +275 -79
- qilisdk/{extras → backends}/__init__.py +9 -4
- qilisdk/{common/model.py → backends/__init__.pyi} +3 -1
- qilisdk/backends/backend.py +117 -0
- qilisdk/{extras/cuda → backends}/cuda_backend.py +152 -159
- qilisdk/backends/qutip_backend.py +473 -0
- qilisdk/core/__init__.py +63 -0
- qilisdk/{common → core}/algorithm.py +2 -1
- qilisdk/{extras/qaas/qaas_settings.py → core/exceptions.py} +12 -6
- qilisdk/core/model.py +1034 -0
- qilisdk/core/parameterizable.py +75 -0
- qilisdk/core/qtensor.py +666 -0
- qilisdk/{common → core}/result.py +2 -1
- qilisdk/core/variables.py +1969 -0
- qilisdk/cost_functions/__init__.py +18 -0
- qilisdk/cost_functions/cost_function.py +77 -0
- qilisdk/cost_functions/model_cost_function.py +145 -0
- qilisdk/cost_functions/observable_cost_function.py +109 -0
- qilisdk/digital/__init__.py +3 -22
- qilisdk/digital/ansatz.py +200 -160
- qilisdk/digital/circuit.py +81 -9
- qilisdk/digital/exceptions.py +12 -6
- qilisdk/digital/gates.py +229 -86
- qilisdk/{extras/qaas/qaas_analog_result.py → functionals/__init__.py} +14 -5
- qilisdk/functionals/functional.py +39 -0
- qilisdk/{common/backend.py → functionals/functional_result.py} +3 -1
- qilisdk/functionals/sampling.py +81 -0
- qilisdk/functionals/sampling_result.py +92 -0
- qilisdk/functionals/time_evolution.py +98 -0
- qilisdk/functionals/time_evolution_result.py +84 -0
- qilisdk/functionals/variational_program.py +80 -0
- qilisdk/functionals/variational_program_result.py +69 -0
- qilisdk/logging_config.yaml +16 -0
- qilisdk/{common → optimizers}/__init__.py +1 -1
- qilisdk/optimizers/optimizer.py +39 -0
- qilisdk/{common → optimizers}/optimizer_result.py +3 -12
- qilisdk/{common/optimizer.py → optimizers/scipy_optimizer.py} +10 -28
- qilisdk/settings.py +78 -0
- qilisdk/speqtrum/__init__.py +41 -0
- qilisdk/{extras → speqtrum}/__init__.pyi +3 -3
- qilisdk/speqtrum/experiments/__init__.py +25 -0
- qilisdk/speqtrum/experiments/experiment_functional.py +124 -0
- qilisdk/speqtrum/experiments/experiment_result.py +231 -0
- qilisdk/{extras/qaas → speqtrum}/keyring.py +8 -4
- qilisdk/speqtrum/speqtrum.py +587 -0
- qilisdk/speqtrum/speqtrum_models.py +467 -0
- qilisdk/utils/__init__.py +0 -14
- qilisdk/utils/openqasm2.py +1 -1
- qilisdk/utils/serialization.py +1 -1
- qilisdk/utils/visualization/PlusJakartaSans-SemiBold.ttf +0 -0
- qilisdk/utils/visualization/__init__.py +24 -0
- qilisdk/utils/visualization/circuit_renderers.py +781 -0
- qilisdk/utils/visualization/schedule_renderers.py +166 -0
- qilisdk/utils/visualization/style.py +154 -0
- qilisdk/utils/visualization/themes.py +76 -0
- qilisdk/yaml.py +126 -0
- {qilisdk-0.1.4.dist-info → qilisdk-0.1.6.dist-info}/METADATA +186 -140
- qilisdk-0.1.6.dist-info/RECORD +69 -0
- qilisdk/analog/algorithms.py +0 -111
- qilisdk/analog/analog_backend.py +0 -43
- qilisdk/analog/analog_result.py +0 -114
- qilisdk/analog/quantum_objects.py +0 -596
- qilisdk/digital/digital_algorithm.py +0 -20
- qilisdk/digital/digital_backend.py +0 -90
- qilisdk/digital/digital_result.py +0 -145
- qilisdk/digital/vqe.py +0 -166
- qilisdk/extras/cuda/__init__.py +0 -13
- qilisdk/extras/cuda/cuda_analog_result.py +0 -19
- qilisdk/extras/cuda/cuda_digital_result.py +0 -19
- qilisdk/extras/qaas/__init__.py +0 -13
- qilisdk/extras/qaas/models.py +0 -132
- qilisdk/extras/qaas/qaas_backend.py +0 -255
- qilisdk/extras/qaas/qaas_digital_result.py +0 -20
- qilisdk/extras/qaas/qaas_time_evolution_result.py +0 -20
- qilisdk/extras/qaas/qaas_vqe_result.py +0 -20
- qilisdk-0.1.4.dist-info/RECORD +0 -51
- {qilisdk-0.1.4.dist-info → qilisdk-0.1.6.dist-info}/WHEEL +0 -0
- {qilisdk-0.1.4.dist-info → qilisdk-0.1.6.dist-info}/licenses/LICENCE +0 -0
|
@@ -13,17 +13,14 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
|
+
from qilisdk.core.result import Result
|
|
16
17
|
from qilisdk.yaml import yaml
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
@yaml.register_class
|
|
20
|
-
class OptimizerIntermediateResult:
|
|
21
|
+
class OptimizerIntermediateResult(Result):
|
|
21
22
|
"""
|
|
22
23
|
Represents an intermediate result.
|
|
23
|
-
|
|
24
|
-
Attributes:
|
|
25
|
-
cost (float): The optimal cost value (e.g., minimum energy) found.
|
|
26
|
-
parameters (List[float]): The parameters that yield the optimal cost.
|
|
27
24
|
"""
|
|
28
25
|
|
|
29
26
|
def __init__(
|
|
@@ -50,15 +47,9 @@ class OptimizerIntermediateResult:
|
|
|
50
47
|
|
|
51
48
|
|
|
52
49
|
@yaml.register_class
|
|
53
|
-
class OptimizerResult:
|
|
50
|
+
class OptimizerResult(Result):
|
|
54
51
|
"""
|
|
55
52
|
Represents the result of an optimization run.
|
|
56
|
-
|
|
57
|
-
Attributes:
|
|
58
|
-
optimal_cost (float): The optimal cost value (e.g., minimum energy) found.
|
|
59
|
-
optimal_parameters (List[float]): The parameters that yield the optimal cost.
|
|
60
|
-
intermediate_results (List[OptimizerResult]): A list of intermediate optimization results.
|
|
61
|
-
Each intermediate result is an instance of OptimizerResult containing the current cost and parameters.
|
|
62
53
|
"""
|
|
63
54
|
|
|
64
55
|
def __init__(
|
|
@@ -11,39 +11,19 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
+
from __future__ import annotations
|
|
14
15
|
|
|
15
|
-
from
|
|
16
|
-
from typing import Any, Callable
|
|
16
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
17
17
|
|
|
18
18
|
from scipy import optimize as scipy_optimize
|
|
19
|
-
from scipy.optimize import OptimizeResult
|
|
20
19
|
|
|
21
20
|
from qilisdk.yaml import yaml
|
|
22
21
|
|
|
22
|
+
from .optimizer import Optimizer
|
|
23
23
|
from .optimizer_result import OptimizerIntermediateResult, OptimizerResult
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@abstractmethod
|
|
28
|
-
def optimize(
|
|
29
|
-
self,
|
|
30
|
-
cost_function: Callable[[list[float]], float],
|
|
31
|
-
init_parameters: list[float],
|
|
32
|
-
store_intermediate_results: bool = False,
|
|
33
|
-
) -> OptimizerResult:
|
|
34
|
-
"""
|
|
35
|
-
Optimize the cost function and return an OptimizerResult.
|
|
36
|
-
|
|
37
|
-
Args:
|
|
38
|
-
cost_function (Callable[[List[float]], float]): A function that takes a list of parameters and returns the cost.
|
|
39
|
-
init_parameters (List[float]): The initial parameters for the optimization.
|
|
40
|
-
store_intermediate_results (bool, optional): If True, stores a list of intermediate optimization results.
|
|
41
|
-
Each intermediate result is recorded as an OptimizerResult containing the parameters and cost at that iteration.
|
|
42
|
-
Defaults to False.
|
|
43
|
-
|
|
44
|
-
Returns:
|
|
45
|
-
OptimizerResult: An object containing the optimal cost, optimal parameters, and, if requested, the intermediate results.
|
|
46
|
-
"""
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from scipy.optimize import OptimizeResult
|
|
47
27
|
|
|
48
28
|
|
|
49
29
|
@yaml.register_class
|
|
@@ -82,8 +62,8 @@ class SciPyOptimizer(Optimizer):
|
|
|
82
62
|
for each element in parameter list.
|
|
83
63
|
|
|
84
64
|
Extra Args:
|
|
85
|
-
Any argument supported by `scipy.optimize.minimize <https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html>`
|
|
86
|
-
Note: the parameters, cost function and the ``args``that are passed to this function will be specified in the optimize method. Moreover, callbacks are not supported for the moment.
|
|
65
|
+
Any argument supported by `scipy.optimize.minimize <https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html>` can be passed.
|
|
66
|
+
Note: the parameters, cost function and the ``args`` that are passed to this function will be specified in the optimize method. Moreover, callbacks are not supported for the moment.
|
|
87
67
|
"""
|
|
88
68
|
super().__init__()
|
|
89
69
|
self.method = method
|
|
@@ -93,6 +73,7 @@ class SciPyOptimizer(Optimizer):
|
|
|
93
73
|
self,
|
|
94
74
|
cost_function: Callable[[list[float]], float],
|
|
95
75
|
init_parameters: list[float],
|
|
76
|
+
bounds: list[tuple[float, float]],
|
|
96
77
|
store_intermediate_results: bool = False,
|
|
97
78
|
) -> OptimizerResult:
|
|
98
79
|
"""optimize the cost function and return the optimal parameters.
|
|
@@ -100,6 +81,7 @@ class SciPyOptimizer(Optimizer):
|
|
|
100
81
|
Args:
|
|
101
82
|
cost_function (Callable[[list[float]], float]): a function that takes in a list of parameters and returns the cost.
|
|
102
83
|
init_parameters (list[float]): the list of initial parameters. Note: the length of this list determines the number of parameters the optimizer will consider.
|
|
84
|
+
bounds (list[float, float]): a list of the variable value bounds.
|
|
103
85
|
|
|
104
86
|
Returns:
|
|
105
87
|
list[float]: the optimal set of parameters that minimize the cost function.
|
|
@@ -119,10 +101,10 @@ class SciPyOptimizer(Optimizer):
|
|
|
119
101
|
cost_function,
|
|
120
102
|
x0=init_parameters,
|
|
121
103
|
method=self.method,
|
|
104
|
+
bounds=bounds,
|
|
122
105
|
jac=self.extra_arguments.get("jac", None),
|
|
123
106
|
hess=self.extra_arguments.get("hess", None),
|
|
124
107
|
hessp=self.extra_arguments.get("hessp", None),
|
|
125
|
-
bounds=self.extra_arguments.get("bounds", None),
|
|
126
108
|
constraints=self.extra_arguments.get("constraints", ()),
|
|
127
109
|
tol=self.extra_arguments.get("tol", None),
|
|
128
110
|
options=self.extra_arguments.get("options", None),
|
qilisdk/settings.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
from functools import lru_cache
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from pydantic import Field
|
|
20
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def default_logging_config_path() -> Path:
|
|
24
|
+
return Path(__file__).with_name("logging_config.yaml").resolve()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Precision(str, Enum):
|
|
28
|
+
COMPLEX_64 = "COMPLEX_64"
|
|
29
|
+
COMPLEX_128 = "COMPLEX_128"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class QiliSDKSettings(BaseSettings):
|
|
33
|
+
"""
|
|
34
|
+
Environment-based configuration settings for QiliSDK.
|
|
35
|
+
|
|
36
|
+
These settings are automatically loaded from environment variables
|
|
37
|
+
prefixed with `QILISDK_`, or from a local `.env` file if present.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
model_config = SettingsConfigDict(env_prefix="qilisdk_", env_file=".env", env_file_encoding="utf-8")
|
|
41
|
+
|
|
42
|
+
arithmetic_precision: Precision = Field(
|
|
43
|
+
default=Precision.COMPLEX_128, description="[env: QILISDK_ARITHMETIC_PRECISION]"
|
|
44
|
+
)
|
|
45
|
+
logging_config_path: Path = Field(
|
|
46
|
+
default_factory=default_logging_config_path,
|
|
47
|
+
description="YAML file used for logging configuration. [env: QILISDK_LOGGING_CONFIG_PATH]",
|
|
48
|
+
)
|
|
49
|
+
speqtrum_username: str | None = Field(
|
|
50
|
+
default=None,
|
|
51
|
+
description="SpeQtrum username used for authentication. [env: QILISDK_SPEQTRUM_USERNAME]",
|
|
52
|
+
)
|
|
53
|
+
speqtrum_apikey: str | None = Field(
|
|
54
|
+
default=None,
|
|
55
|
+
description="SpeQtrum API key associated with the user account. [env: QILISDK_SPEQTRUM_APIKEY]",
|
|
56
|
+
)
|
|
57
|
+
speqtrum_api_url: str = Field(
|
|
58
|
+
default="https://qilimanjaro.ddns.net/public-api/api/v1",
|
|
59
|
+
description="Base URL of the SpeQtrum API endpoint. [env: QILISDK_SPEQTRUM_API_URL]",
|
|
60
|
+
)
|
|
61
|
+
speqtrum_audience: str = Field(
|
|
62
|
+
default="urn:qilimanjaro.tech:public-api:beren",
|
|
63
|
+
description="Audience claim expected in the JWT used for authentication. [env: QILISDK_SPEQTRUM_AUDIENCE]",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@lru_cache(maxsize=1)
|
|
68
|
+
def get_settings() -> QiliSDKSettings:
|
|
69
|
+
"""
|
|
70
|
+
Returns a singleton instance of QiliSDKSettings.
|
|
71
|
+
|
|
72
|
+
This function caches the parsed environment-based settings to avoid
|
|
73
|
+
redundant re-parsing across the application lifecycle.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
QiliSDKSettings: The cached configuration object populated from environment variables.
|
|
77
|
+
"""
|
|
78
|
+
return QiliSDKSettings()
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
import sys
|
|
15
|
+
|
|
16
|
+
from qilisdk._optionals import ImportedFeature, OptionalFeature, Symbol, import_optional_dependencies
|
|
17
|
+
|
|
18
|
+
__all__ = []
|
|
19
|
+
|
|
20
|
+
OPTIONAL_FEATURES: list[OptionalFeature] = [
|
|
21
|
+
OptionalFeature(
|
|
22
|
+
name="speqtrum",
|
|
23
|
+
dependencies=["httpx", "keyring", "keyrings-alt"],
|
|
24
|
+
symbols=[
|
|
25
|
+
Symbol(path="qilisdk.speqtrum.speqtrum", name="SpeQtrum"),
|
|
26
|
+
Symbol(path="qilisdk.speqtrum.speqtrum_models", name="DeviceStatus"),
|
|
27
|
+
Symbol(path="qilisdk.speqtrum.speqtrum_models", name="DeviceType"),
|
|
28
|
+
Symbol(path="qilisdk.speqtrum.speqtrum_models", name="JobHandle"),
|
|
29
|
+
Symbol(path="qilisdk.speqtrum.speqtrum_models", name="TypedJobDetail"),
|
|
30
|
+
],
|
|
31
|
+
),
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
current_module = sys.modules[__name__]
|
|
35
|
+
|
|
36
|
+
# Dynamically import (or stub) each feature's symbols and attach them
|
|
37
|
+
for feature in OPTIONAL_FEATURES:
|
|
38
|
+
imported_feature: ImportedFeature = import_optional_dependencies(feature)
|
|
39
|
+
for symbol_name, symbol_obj in imported_feature.symbols.items():
|
|
40
|
+
setattr(current_module, symbol_name, symbol_obj)
|
|
41
|
+
__all__ += [symbol_name] # noqa: PLE0604
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
from .
|
|
16
|
-
from .
|
|
15
|
+
from .speqtrum import SpeQtrum
|
|
16
|
+
from .speqtrum_models import DeviceStatus, DeviceType
|
|
17
17
|
|
|
18
|
-
__all__ = ["
|
|
18
|
+
__all__ = ["DeviceStatus", "DeviceType", "SpeQtrum"]
|
|
@@ -0,0 +1,25 @@
|
|
|
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 .experiment_functional import ExperimentFunctional, RabiExperiment, T1Experiment
|
|
15
|
+
from .experiment_result import Dimension, ExperimentResult, RabiExperimentResult, T1ExperimentResult
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"Dimension",
|
|
19
|
+
"ExperimentFunctional",
|
|
20
|
+
"ExperimentResult",
|
|
21
|
+
"RabiExperiment",
|
|
22
|
+
"RabiExperimentResult",
|
|
23
|
+
"T1Experiment",
|
|
24
|
+
"T1ExperimentResult",
|
|
25
|
+
]
|
|
@@ -0,0 +1,124 @@
|
|
|
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
|
+
from abc import ABC
|
|
17
|
+
from typing import TYPE_CHECKING, ClassVar, Generic, TypeVar
|
|
18
|
+
|
|
19
|
+
from qilisdk.functionals.functional import Functional
|
|
20
|
+
from qilisdk.speqtrum.experiments.experiment_result import (
|
|
21
|
+
ExperimentResult,
|
|
22
|
+
RabiExperimentResult,
|
|
23
|
+
T1ExperimentResult,
|
|
24
|
+
)
|
|
25
|
+
from qilisdk.yaml import yaml
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
import numpy as np
|
|
29
|
+
|
|
30
|
+
TResult_co = TypeVar("TResult_co", bound=ExperimentResult, covariant=True)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@yaml.register_class
|
|
34
|
+
class ExperimentFunctional(Functional, ABC, Generic[TResult_co]):
|
|
35
|
+
"""Abstract base class for single-qubit experiment functionals.
|
|
36
|
+
|
|
37
|
+
This class serves as a generic interface for defining quantum
|
|
38
|
+
characterization experiments such as Rabi or T1. Each subclass
|
|
39
|
+
specifies a concrete `ExperimentResult` type and the corresponding
|
|
40
|
+
sweep parameters.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, qubit: int) -> None:
|
|
44
|
+
"""Initialize the experiment functional.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
qubit (int): The physical qubit index on which the experiment is performed.
|
|
48
|
+
"""
|
|
49
|
+
self._qubit = qubit
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def qubit(self) -> int:
|
|
53
|
+
"""The physical qubit index on which the experiment is performed.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
int: Index of the qubit.
|
|
57
|
+
"""
|
|
58
|
+
return self._qubit
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@yaml.register_class
|
|
62
|
+
class RabiExperiment(ExperimentFunctional[RabiExperimentResult]):
|
|
63
|
+
"""Rabi experiment functional for a single qubit.
|
|
64
|
+
|
|
65
|
+
This functional defines a standard Rabi oscillation experiment where
|
|
66
|
+
the drive pulse duration is swept to measure the oscillatory response
|
|
67
|
+
of the qubit under continuous driving.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
result_type: ClassVar[type[RabiExperimentResult]] = RabiExperimentResult
|
|
71
|
+
"""Result type returned by this functional."""
|
|
72
|
+
|
|
73
|
+
def __init__(self, qubit: int, drive_duration_values: np.ndarray) -> None:
|
|
74
|
+
"""Initialize a Rabi experiment functional.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
qubit (int): The physical qubit index on which the experiment is performed.
|
|
78
|
+
drive_duration_values (np.ndarray): Array of drive pulse durations (in nanoseconds)
|
|
79
|
+
used to sweep the experiment.
|
|
80
|
+
"""
|
|
81
|
+
super().__init__(qubit=qubit)
|
|
82
|
+
self._drive_duration_values = drive_duration_values
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def drive_duration_values(self) -> np.ndarray:
|
|
86
|
+
"""Drive pulse duration sweep values.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
np.ndarray: The set of drive durations (in nanoseconds) used in the Rabi experiment.
|
|
90
|
+
"""
|
|
91
|
+
return self._drive_duration_values
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@yaml.register_class
|
|
95
|
+
class T1Experiment(ExperimentFunctional[T1ExperimentResult]):
|
|
96
|
+
"""T1 relaxation experiment functional for a single qubit.
|
|
97
|
+
|
|
98
|
+
This functional defines a standard T1 (energy relaxation) experiment,
|
|
99
|
+
where the delay between excitation and measurement is varied to extract
|
|
100
|
+
the relaxation time constant of the qubit.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
result_type: ClassVar[type[T1ExperimentResult]] = T1ExperimentResult
|
|
104
|
+
"""Result type returned by this functional."""
|
|
105
|
+
|
|
106
|
+
def __init__(self, qubit: int, wait_duration_values: np.ndarray) -> None:
|
|
107
|
+
"""Initialize a T1 experiment functional.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
qubit (int): The physical qubit index on which the experiment is performed.
|
|
111
|
+
wait_duration_values (np.ndarray): Array of waiting times (in nanoseconds)
|
|
112
|
+
between excitation and measurement.
|
|
113
|
+
"""
|
|
114
|
+
super().__init__(qubit=qubit)
|
|
115
|
+
self._wait_duration_values: np.ndarray = wait_duration_values
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def wait_duration_values(self) -> np.ndarray:
|
|
119
|
+
"""Waiting time sweep values.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
np.ndarray: The set of delay durations (in nanoseconds) used in the T1 experiment.
|
|
123
|
+
"""
|
|
124
|
+
return self._wait_duration_values
|
|
@@ -0,0 +1,231 @@
|
|
|
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 pathlib import Path
|
|
15
|
+
from typing import ClassVar
|
|
16
|
+
|
|
17
|
+
import matplotlib.pyplot as plt
|
|
18
|
+
import numpy as np
|
|
19
|
+
from matplotlib.figure import Figure
|
|
20
|
+
|
|
21
|
+
from qilisdk.functionals.functional_result import FunctionalResult
|
|
22
|
+
from qilisdk.yaml import yaml
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@yaml.register_class
|
|
26
|
+
class Dimension:
|
|
27
|
+
"""Represents a labeled dimension in an experiment sweep.
|
|
28
|
+
|
|
29
|
+
A `Dimension` defines one or more sweep parameters, such as drive
|
|
30
|
+
amplitude, frequency, or delay time, together with their associated
|
|
31
|
+
numerical values.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
labels (list[str]): Human-readable labels for the sweep parameters.
|
|
35
|
+
values (list[np.ndarray]): Numeric arrays representing the values
|
|
36
|
+
corresponding to each label.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, labels: list[str], values: list[np.ndarray]) -> None:
|
|
40
|
+
"""Initialize a Dimension object.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
labels (list[str]): Labels describing each dimension (e.g. ``["Drive amplitude"]``).
|
|
44
|
+
values (list[np.ndarray]): Numerical arrays for the corresponding parameter values.
|
|
45
|
+
"""
|
|
46
|
+
self.labels = labels
|
|
47
|
+
self.values = values
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@yaml.register_class
|
|
51
|
+
class ExperimentResult(FunctionalResult):
|
|
52
|
+
"""Base class for storing and visualizing experiment results.
|
|
53
|
+
|
|
54
|
+
This class defines common utilities for handling experimental data,
|
|
55
|
+
including computation of S21 parameters and automatic 1D or 2D plotting.
|
|
56
|
+
Subclasses provide specific sweep parameters and plot titles.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
plot_title: ClassVar[str]
|
|
60
|
+
"""Default plot title; subclasses provide the concrete label."""
|
|
61
|
+
|
|
62
|
+
def __init__(self, qubit: int, data: np.ndarray, dims: list[Dimension]) -> None:
|
|
63
|
+
"""Initialize an experiment result.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
qubit (int): The qubit index on which the experiment was performed.
|
|
67
|
+
data (np.ndarray): Raw experimental data array.
|
|
68
|
+
dims (list[Dimension]): Sweep dimensions of the experiment.
|
|
69
|
+
"""
|
|
70
|
+
self.qubit = qubit
|
|
71
|
+
self.data = data
|
|
72
|
+
self.dims = dims
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def s21(self) -> np.ndarray:
|
|
76
|
+
"""Complex S21 transmission parameter.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
np.ndarray: The complex-valued S21 response computed as ``Re + i * Im``.
|
|
80
|
+
"""
|
|
81
|
+
return self.data[..., 0] + 1j * self.data[..., 1]
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def s21_modulus(self) -> np.ndarray:
|
|
85
|
+
"""Magnitude of the S21 parameter.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
np.ndarray: The absolute value of the S21 parameter.
|
|
89
|
+
"""
|
|
90
|
+
return np.abs(self.s21)
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def s21_db(self) -> np.ndarray:
|
|
94
|
+
"""Magnitude of S21 in decibels (dB).
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
np.ndarray: ``20 * log10(abs(S21))`` expressed in dB.
|
|
98
|
+
"""
|
|
99
|
+
return 20 * np.log10(self.s21_modulus)
|
|
100
|
+
|
|
101
|
+
def plot(self, save_to: str | None = None) -> None:
|
|
102
|
+
"""Plot the S21 parameter from experiment results.
|
|
103
|
+
|
|
104
|
+
Automatically detects whether the dataset is 1D or 2D and creates
|
|
105
|
+
the appropriate figure. Optionally saves the figure to disk.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
save_to (str | None): Optional path or directory to save the
|
|
109
|
+
generated plot. If a directory is provided, the filename is
|
|
110
|
+
automatically generated as ``{plot_title}_qubit{qubit}.png``.
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
NotImplementedError: If the experiment data has more than 2 dimensions.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
def save_figure(figure: Figure, save_to: str | Path) -> None:
|
|
117
|
+
save_to = Path(save_to)
|
|
118
|
+
|
|
119
|
+
# If a directory was given, append the default filename
|
|
120
|
+
if save_to.is_dir():
|
|
121
|
+
save_to /= f"{self.plot_title}_qubit{self.qubit}.png"
|
|
122
|
+
|
|
123
|
+
save_to.parent.mkdir(parents=True, exist_ok=True)
|
|
124
|
+
figure.savefig(save_to)
|
|
125
|
+
|
|
126
|
+
def plot_1d(s21: np.ndarray, dims: list[Dimension]) -> None:
|
|
127
|
+
"""Plot 1d"""
|
|
128
|
+
x_labels, x_values = dims[0].labels, dims[0].values
|
|
129
|
+
|
|
130
|
+
fig, ax1 = plt.subplots()
|
|
131
|
+
ax1.set_title(f"{self.plot_title} - Qubit {self.qubit}")
|
|
132
|
+
ax1.set_xlabel(x_labels[0])
|
|
133
|
+
ax1.set_ylabel(r"$|S_{21}|$")
|
|
134
|
+
ax1.plot(x_values[0], s21, ".")
|
|
135
|
+
|
|
136
|
+
if len(x_labels) > 1:
|
|
137
|
+
# Create secondary x-axis
|
|
138
|
+
ax2 = ax1.twiny()
|
|
139
|
+
|
|
140
|
+
# Set labels
|
|
141
|
+
ax2.set_xlabel(x_labels[1])
|
|
142
|
+
ax2.set_xlim(min(x_values[1]), max(x_values[1]))
|
|
143
|
+
|
|
144
|
+
# Set tick locations
|
|
145
|
+
ax2_ticks = np.linspace(min(x_values[1]), max(x_values[1]), num=6)
|
|
146
|
+
ax2.set_xticks(ax2_ticks)
|
|
147
|
+
|
|
148
|
+
# Force scientific notation
|
|
149
|
+
ax2.ticklabel_format(axis="x", style="sci", scilimits=(-3, 3))
|
|
150
|
+
|
|
151
|
+
if save_to:
|
|
152
|
+
save_figure(fig, save_to)
|
|
153
|
+
|
|
154
|
+
plt.show()
|
|
155
|
+
|
|
156
|
+
# pylint: disable=too-many-locals
|
|
157
|
+
def plot_2d(s21: np.ndarray, dims: list[Dimension]) -> None:
|
|
158
|
+
"""Plot 2d"""
|
|
159
|
+
x_labels, x_values = dims[0].labels, dims[0].values
|
|
160
|
+
y_labels, y_values = dims[1].labels, dims[1].values
|
|
161
|
+
|
|
162
|
+
# Create x and y edge arrays by extrapolating the edges
|
|
163
|
+
x_edges = np.linspace(x_values[0].min(), x_values[0].max(), len(x_values[0]) + 1)
|
|
164
|
+
y_edges = np.linspace(y_values[0].min(), y_values[0].max(), len(y_values[0]) + 1)
|
|
165
|
+
|
|
166
|
+
fig, ax1 = plt.subplots()
|
|
167
|
+
ax1.set_title(f"{self.plot_title} - Qubit {self.qubit}")
|
|
168
|
+
ax1.set_xlabel(x_labels[0])
|
|
169
|
+
ax1.set_ylabel(y_labels[0])
|
|
170
|
+
|
|
171
|
+
# Force scientific notation
|
|
172
|
+
ax1.ticklabel_format(axis="both", style="sci", scilimits=(-3, 3))
|
|
173
|
+
|
|
174
|
+
mesh = ax1.pcolormesh(x_edges, y_edges, s21.T, cmap="viridis", shading="auto")
|
|
175
|
+
fig.colorbar(mesh, ax=ax1)
|
|
176
|
+
|
|
177
|
+
if len(x_labels) > 1:
|
|
178
|
+
# Create secondary x-axis
|
|
179
|
+
ax2 = ax1.twiny()
|
|
180
|
+
|
|
181
|
+
# Set labels
|
|
182
|
+
ax2.set_xlabel(x_labels[1])
|
|
183
|
+
ax2.set_xlim(min(x_values[1]), max(x_values[1]))
|
|
184
|
+
|
|
185
|
+
# Set tick locations
|
|
186
|
+
ax2_ticks = np.linspace(min(x_values[1]), max(x_values[1]), num=6)
|
|
187
|
+
ax2.set_xticks(ax2_ticks)
|
|
188
|
+
|
|
189
|
+
# Force scientific notation
|
|
190
|
+
ax2.ticklabel_format(axis="x", style="sci", scilimits=(-3, 3))
|
|
191
|
+
if len(y_labels) > 1:
|
|
192
|
+
ax3 = ax1.twinx()
|
|
193
|
+
ax3.set_ylabel(y_labels[1])
|
|
194
|
+
ax3.set_ylim(min(y_values[1]), max(y_values[1]))
|
|
195
|
+
|
|
196
|
+
# Set tick locations
|
|
197
|
+
ax3_ticks = np.linspace(min(y_values[1]), max(y_values[1]), num=6)
|
|
198
|
+
ax3.set_xticks(ax3_ticks)
|
|
199
|
+
|
|
200
|
+
# Force scientific notation
|
|
201
|
+
ax3.ticklabel_format(axis="y", style="sci", scilimits=(-3, 3))
|
|
202
|
+
|
|
203
|
+
if save_to:
|
|
204
|
+
save_figure(fig, save_to)
|
|
205
|
+
|
|
206
|
+
plt.tight_layout()
|
|
207
|
+
plt.show()
|
|
208
|
+
|
|
209
|
+
n_dimensions = len(self.s21_modulus.shape)
|
|
210
|
+
if n_dimensions == 1:
|
|
211
|
+
plot_1d(self.s21_modulus, self.dims)
|
|
212
|
+
elif n_dimensions == 2: # noqa: PLR2004
|
|
213
|
+
plot_2d(self.s21_modulus, self.dims)
|
|
214
|
+
else:
|
|
215
|
+
raise NotImplementedError("3D and higher dimension plots are not supported yet.")
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@yaml.register_class
|
|
219
|
+
class RabiExperimentResult(ExperimentResult):
|
|
220
|
+
"""Result container for Rabi experiments."""
|
|
221
|
+
|
|
222
|
+
plot_title: ClassVar[str] = "Rabi"
|
|
223
|
+
"""Default title for Rabi experiment plots."""
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@yaml.register_class
|
|
227
|
+
class T1ExperimentResult(ExperimentResult):
|
|
228
|
+
"""Result container for T1 relaxation experiments."""
|
|
229
|
+
|
|
230
|
+
plot_title: ClassVar[str] = "T1"
|
|
231
|
+
"""Default title for T1 experiment plots."""
|
|
@@ -13,11 +13,12 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
import keyring
|
|
16
|
+
from keyring.errors import PasswordDeleteError
|
|
16
17
|
from pydantic import ValidationError
|
|
17
18
|
|
|
18
|
-
from .
|
|
19
|
+
from .speqtrum_models import Token
|
|
19
20
|
|
|
20
|
-
KEYRING_IDENTIFIER = "
|
|
21
|
+
KEYRING_IDENTIFIER = "SpeQtrumKeyring"
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
def store_credentials(username: str, token: Token) -> None:
|
|
@@ -32,8 +33,11 @@ def delete_credentials() -> None:
|
|
|
32
33
|
"""
|
|
33
34
|
Delete username and token from the keyring.
|
|
34
35
|
"""
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
try:
|
|
37
|
+
keyring.delete_password(KEYRING_IDENTIFIER, "username")
|
|
38
|
+
keyring.delete_password(KEYRING_IDENTIFIER, "token")
|
|
39
|
+
except PasswordDeleteError:
|
|
40
|
+
return
|
|
37
41
|
|
|
38
42
|
|
|
39
43
|
def load_credentials() -> tuple[str, Token] | None:
|