qilisdk 0.1.3__py3-none-any.whl → 0.1.5__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 +118 -0
- qilisdk/analog/schedule.py +272 -79
- qilisdk/backends/__init__.py +45 -0
- qilisdk/{digital/digital_algorithm.py → backends/__init__.pyi} +3 -5
- qilisdk/backends/backend.py +117 -0
- qilisdk/{extras/cuda → backends}/cuda_backend.py +153 -161
- qilisdk/backends/qutip_backend.py +492 -0
- qilisdk/common/__init__.py +48 -2
- qilisdk/common/algorithm.py +2 -1
- qilisdk/{extras/qaas/qaas_settings.py → common/exceptions.py} +12 -6
- qilisdk/common/model.py +1019 -1
- qilisdk/common/parameterizable.py +75 -0
- qilisdk/common/qtensor.py +666 -0
- qilisdk/common/result.py +2 -1
- qilisdk/common/variables.py +1931 -0
- qilisdk/{extras/cuda/cuda_analog_result.py → cost_functions/__init__.py} +3 -4
- 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 +203 -160
- qilisdk/digital/circuit.py +81 -9
- qilisdk/digital/exceptions.py +12 -6
- qilisdk/digital/gates.py +228 -85
- qilisdk/{extras/qaas/qaas_analog_result.py → functionals/__init__.py} +14 -5
- qilisdk/functionals/functional.py +39 -0
- qilisdk/{extras/cuda/cuda_digital_result.py → functionals/functional_result.py} +3 -4
- 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/backend.py → optimizers/__init__.py} +2 -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/{extras → speqtrum}/__init__.py +7 -8
- 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 +432 -0
- qilisdk/speqtrum/speqtrum_models.py +300 -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 +161 -0
- qilisdk/utils/visualization/style.py +154 -0
- qilisdk/utils/visualization/themes.py +76 -0
- qilisdk/yaml.py +126 -0
- {qilisdk-0.1.3.dist-info → qilisdk-0.1.5.dist-info}/METADATA +180 -135
- qilisdk-0.1.5.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 -533
- 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/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.3.dist-info/RECORD +0 -51
- {qilisdk-0.1.3.dist-info → qilisdk-0.1.5.dist-info}/WHEEL +0 -0
- {qilisdk-0.1.3.dist-info → qilisdk-0.1.5.dist-info}/licenses/LICENCE +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
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 ClassVar, Generic, TypeVar
|
|
18
|
+
|
|
19
|
+
from qilisdk.common.parameterizable import Parameterizable
|
|
20
|
+
from qilisdk.functionals.functional_result import FunctionalResult
|
|
21
|
+
|
|
22
|
+
TResult_co = TypeVar("TResult_co", bound=FunctionalResult, covariant=True)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Functional(ABC):
|
|
26
|
+
"""
|
|
27
|
+
Abstract interface for executable routines that return a :class:`FunctionalResult`.
|
|
28
|
+
|
|
29
|
+
Subclasses detail the concrete `result_type` they generate.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
result_type: ClassVar[type[FunctionalResult]]
|
|
33
|
+
"""Concrete :class:`~qilisdk.functionals.functional_result.FunctionalResult` subclass returned."""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PrimitiveFunctional(Parameterizable, Functional, ABC, Generic[TResult_co]):
|
|
37
|
+
"""
|
|
38
|
+
Base class for functionals backed by a :class:`~qilisdk.common.parameterizable.Parameterizable` object.
|
|
39
|
+
"""
|
|
@@ -11,9 +11,8 @@
|
|
|
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 qilisdk.
|
|
15
|
-
from qilisdk.yaml import yaml
|
|
14
|
+
from qilisdk.common.result import Result
|
|
16
15
|
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
class
|
|
17
|
+
class FunctionalResult(Result):
|
|
18
|
+
"""Base class for artifacts produced by executing a :class:`Functional`."""
|
|
@@ -0,0 +1,81 @@
|
|
|
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 typing import ClassVar
|
|
15
|
+
|
|
16
|
+
from qilisdk.common.variables import RealNumber
|
|
17
|
+
from qilisdk.digital.circuit import Circuit
|
|
18
|
+
from qilisdk.functionals.functional import PrimitiveFunctional
|
|
19
|
+
from qilisdk.functionals.sampling_result import SamplingResult
|
|
20
|
+
from qilisdk.yaml import yaml
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@yaml.register_class
|
|
24
|
+
class Sampling(PrimitiveFunctional[SamplingResult]):
|
|
25
|
+
"""
|
|
26
|
+
Execute a digital circuit and collect bitstring samples.
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
.. code-block:: python
|
|
30
|
+
|
|
31
|
+
from qilisdk.digital.circuit import Circuit
|
|
32
|
+
from qilisdk.functionals.sampling import Sampling
|
|
33
|
+
|
|
34
|
+
circuit = Circuit(nqubits=2)
|
|
35
|
+
circuit.h(0)
|
|
36
|
+
sampler = Sampling(circuit, nshots=1024)
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
result_type: ClassVar[type[SamplingResult]] = SamplingResult
|
|
40
|
+
|
|
41
|
+
def __init__(self, circuit: Circuit, nshots: int = 1000) -> None:
|
|
42
|
+
"""
|
|
43
|
+
Args:
|
|
44
|
+
circuit (Circuit): Circuit to execute for sampling.
|
|
45
|
+
nshots (int, optional): Number of repetitions used to estimate probabilities. Defaults to 1000.
|
|
46
|
+
"""
|
|
47
|
+
self.circuit = circuit
|
|
48
|
+
self.nshots = nshots
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def nparameters(self) -> int:
|
|
52
|
+
"""Return the number of parameters exposed by the underlying circuit."""
|
|
53
|
+
return self.circuit.nparameters
|
|
54
|
+
|
|
55
|
+
def set_parameters(self, parameters: dict[str, RealNumber]) -> None:
|
|
56
|
+
"""Assign a subset of circuit parameters provided by label."""
|
|
57
|
+
self.circuit.set_parameters(parameters)
|
|
58
|
+
|
|
59
|
+
def get_parameters(self) -> dict[str, RealNumber]:
|
|
60
|
+
"""Return a mapping of all circuit parameters to their current value."""
|
|
61
|
+
return self.circuit.get_parameters()
|
|
62
|
+
|
|
63
|
+
def get_parameter_names(self) -> list[str]:
|
|
64
|
+
"""Return circuit parameter labels in deterministic order."""
|
|
65
|
+
return list(self.circuit.get_parameters().keys())
|
|
66
|
+
|
|
67
|
+
def get_parameter_values(self) -> list[RealNumber]:
|
|
68
|
+
"""Return the current parameter values following ``get_parameter_names`` order."""
|
|
69
|
+
return list(self.circuit.get_parameters().values())
|
|
70
|
+
|
|
71
|
+
def set_parameter_values(self, values: list[float]) -> None:
|
|
72
|
+
"""Update all circuit parameters using the order defined by ``get_parameter_names``."""
|
|
73
|
+
self.circuit.set_parameter_values(values)
|
|
74
|
+
|
|
75
|
+
def get_parameter_bounds(self) -> dict[str, tuple[float, float]]:
|
|
76
|
+
"""Return lower and upper bounds registered for each circuit parameter."""
|
|
77
|
+
return self.circuit.get_parameter_bounds()
|
|
78
|
+
|
|
79
|
+
def set_parameter_bounds(self, ranges: dict[str, tuple[float, float]]) -> None:
|
|
80
|
+
"""Update the admissible range for selected circuit parameters."""
|
|
81
|
+
self.circuit.set_parameter_bounds(ranges)
|
|
@@ -0,0 +1,92 @@
|
|
|
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 heapq
|
|
15
|
+
import operator
|
|
16
|
+
from pprint import pformat
|
|
17
|
+
|
|
18
|
+
from qilisdk.functionals.functional_result import FunctionalResult
|
|
19
|
+
from qilisdk.yaml import yaml
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@yaml.register_class
|
|
23
|
+
class SamplingResult(FunctionalResult):
|
|
24
|
+
"""Store shot counts and derived probabilities for a sampling experiment."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, nshots: int, samples: dict[str, int]) -> None:
|
|
27
|
+
"""
|
|
28
|
+
Args:
|
|
29
|
+
nshots (int): Total number of circuit evaluations.
|
|
30
|
+
samples (dict[str, int]): Mapping from bitstring to observed counts.
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
ValueError: If ``samples`` is empty or contains bitstrings with different length.
|
|
34
|
+
"""
|
|
35
|
+
self._nshots = nshots
|
|
36
|
+
self._samples = samples
|
|
37
|
+
|
|
38
|
+
# Ensure samples is not empty and is correct.
|
|
39
|
+
if not samples:
|
|
40
|
+
raise ValueError("The samples dictionary is empty.")
|
|
41
|
+
bitstrings = list(samples.keys())
|
|
42
|
+
nqubits = len(bitstrings[0])
|
|
43
|
+
if not all(len(bitstring) == nqubits for bitstring in bitstrings):
|
|
44
|
+
raise ValueError("Not all bitstring keys have the same length.")
|
|
45
|
+
|
|
46
|
+
# Assign nqubits to attribute
|
|
47
|
+
self._nqubits = nqubits
|
|
48
|
+
|
|
49
|
+
# Calculate probabilities
|
|
50
|
+
self._probabilities = {
|
|
51
|
+
bitstring: counts / self._nshots if self._nshots > 0 else 0.0 for bitstring, counts in self._samples.items()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def nshots(self) -> int:
|
|
56
|
+
"""Total number of repetitions used to gather samples."""
|
|
57
|
+
return self._nshots
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def nqubits(self) -> int:
|
|
61
|
+
"""Number of qubits inferred from the sample bitstrings."""
|
|
62
|
+
return self._nqubits
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def samples(self) -> dict[str, int]:
|
|
66
|
+
"""Return a copy of the raw sample counts."""
|
|
67
|
+
return dict(self._samples)
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def probabilities(self) -> dict[str, float]:
|
|
71
|
+
"""Return a copy of the estimated probability distribution."""
|
|
72
|
+
return dict(self._probabilities)
|
|
73
|
+
|
|
74
|
+
def get_probability(self, bitstring: str) -> float:
|
|
75
|
+
"""Return the probability associated with ``bitstring`` (0.0 if unseen)."""
|
|
76
|
+
return self._probabilities.get(bitstring, 0.0)
|
|
77
|
+
|
|
78
|
+
def get_probabilities(self, n: int | None = None) -> list[tuple[str, float]]:
|
|
79
|
+
"""
|
|
80
|
+
Args:
|
|
81
|
+
n (int | None): Maximum number of items to return. Defaults to all outcomes.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
list[tuple[str, float]]: the ``n`` most probable bitstrings in descending probability order.
|
|
85
|
+
"""
|
|
86
|
+
if n is None:
|
|
87
|
+
n = len(self._probabilities)
|
|
88
|
+
return heapq.nlargest(n, self._probabilities.items(), key=operator.itemgetter(1))
|
|
89
|
+
|
|
90
|
+
def __repr__(self) -> str:
|
|
91
|
+
class_name = self.__class__.__name__
|
|
92
|
+
return f"{class_name}(\n nshots={self.nshots},\n samples={pformat(self.samples)}\n)"
|
|
@@ -0,0 +1,98 @@
|
|
|
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 typing import ClassVar
|
|
15
|
+
|
|
16
|
+
from qilisdk.analog.hamiltonian import Hamiltonian, PauliOperator
|
|
17
|
+
from qilisdk.analog.schedule import Schedule
|
|
18
|
+
from qilisdk.common.qtensor import QTensor
|
|
19
|
+
from qilisdk.common.variables import RealNumber
|
|
20
|
+
from qilisdk.functionals.functional import PrimitiveFunctional
|
|
21
|
+
from qilisdk.functionals.time_evolution_result import TimeEvolutionResult
|
|
22
|
+
from qilisdk.yaml import yaml
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@yaml.register_class
|
|
26
|
+
class TimeEvolution(PrimitiveFunctional[TimeEvolutionResult]):
|
|
27
|
+
"""
|
|
28
|
+
Simulate the dynamics induced by a time-dependent Hamiltonian schedule.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
.. code-block:: python
|
|
32
|
+
|
|
33
|
+
from qilisdk.analog import Schedule, Hamiltonian, Z
|
|
34
|
+
from qilisdk.common import ket
|
|
35
|
+
from qilisdk.functionals.time_evolution import TimeEvolution
|
|
36
|
+
|
|
37
|
+
h0 = Z(0)
|
|
38
|
+
schedule = Schedule(T=10.0, hamiltonians={"h0": h0})
|
|
39
|
+
functional = TimeEvolution(schedule, observables=[Z(0), X(0)], initial_state=ket(0))
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
result_type: ClassVar[type[TimeEvolutionResult]] = TimeEvolutionResult
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
schedule: Schedule,
|
|
47
|
+
observables: list[PauliOperator | Hamiltonian],
|
|
48
|
+
initial_state: QTensor,
|
|
49
|
+
nshots: int = 1000,
|
|
50
|
+
store_intermediate_results: bool = False,
|
|
51
|
+
) -> None:
|
|
52
|
+
"""
|
|
53
|
+
Args:
|
|
54
|
+
schedule (Schedule): Annealing or control schedule describing the Hamiltonian evolution.
|
|
55
|
+
observables (list[PauliOperator | Hamiltonian]): Observables measured at the end of the evolution.
|
|
56
|
+
initial_state (QTensor): Quantum state used as the simulation starting point.
|
|
57
|
+
nshots (int, optional): Number of executions for statistical estimation. Defaults to 1000.
|
|
58
|
+
store_intermediate_results (bool, optional): Keep intermediate states if produced by the backend. Defaults to False.
|
|
59
|
+
"""
|
|
60
|
+
super().__init__()
|
|
61
|
+
self.initial_state = initial_state
|
|
62
|
+
self.schedule = schedule
|
|
63
|
+
self.observables = observables
|
|
64
|
+
self.nshots = nshots
|
|
65
|
+
self.store_intermediate_results = store_intermediate_results
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def nparameters(self) -> int:
|
|
69
|
+
"""Return the number of schedule parameters."""
|
|
70
|
+
return self.schedule.nparameters
|
|
71
|
+
|
|
72
|
+
def get_parameters(self) -> dict[str, RealNumber]:
|
|
73
|
+
"""Return the schedule parameters and their current value."""
|
|
74
|
+
return self.schedule.get_parameters()
|
|
75
|
+
|
|
76
|
+
def set_parameters(self, parameters: dict[str, RealNumber]) -> None:
|
|
77
|
+
"""Update a subset of schedule parameters."""
|
|
78
|
+
self.schedule.set_parameters(parameters)
|
|
79
|
+
|
|
80
|
+
def get_parameter_names(self) -> list[str]:
|
|
81
|
+
"""Return order-stable parameter labels from the schedule."""
|
|
82
|
+
return self.schedule.get_parameter_names()
|
|
83
|
+
|
|
84
|
+
def get_parameter_values(self) -> list[RealNumber]:
|
|
85
|
+
"""Return parameter values in the order provided by ``get_parameter_names``."""
|
|
86
|
+
return self.schedule.get_parameter_values()
|
|
87
|
+
|
|
88
|
+
def set_parameter_values(self, values: list[float]) -> None:
|
|
89
|
+
"""Assign all schedule parameters according to ``get_parameter_names`` order."""
|
|
90
|
+
self.schedule.set_parameter_values(values)
|
|
91
|
+
|
|
92
|
+
def get_parameter_bounds(self) -> dict[str, tuple[float, float]]:
|
|
93
|
+
"""Return current bounds for schedule parameters."""
|
|
94
|
+
return self.schedule.get_parameter_bounds()
|
|
95
|
+
|
|
96
|
+
def set_parameter_bounds(self, ranges: dict[str, tuple[float, float]]) -> None:
|
|
97
|
+
"""Update bounds for selected schedule parameters."""
|
|
98
|
+
self.schedule.set_parameter_bounds(ranges)
|
|
@@ -0,0 +1,84 @@
|
|
|
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 pprint import pformat
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
|
|
18
|
+
from qilisdk.common.model import Model
|
|
19
|
+
from qilisdk.common.qtensor import QTensor
|
|
20
|
+
from qilisdk.functionals.functional_result import FunctionalResult
|
|
21
|
+
from qilisdk.yaml import yaml
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@yaml.register_class
|
|
25
|
+
class TimeEvolutionResult(FunctionalResult):
|
|
26
|
+
"""Container for expectation values and states produced by a time-evolution simulation."""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
final_expected_values: np.ndarray | None = None,
|
|
31
|
+
expected_values: np.ndarray | None = None,
|
|
32
|
+
final_state: QTensor | None = None,
|
|
33
|
+
intermediate_states: list[QTensor] | None = None,
|
|
34
|
+
) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Args:
|
|
37
|
+
final_expected_values (np.ndarray | None, optional): Expectation values evaluated at the end of the evolution.
|
|
38
|
+
expected_values (np.ndarray | None, optional): Time series of expectation values, if recorded.
|
|
39
|
+
final_state (QTensor | None, optional): Final quantum state associated with the evolution.
|
|
40
|
+
intermediate_states (list[QTensor] | None, optional): Intermediate states captured during the evolution.
|
|
41
|
+
"""
|
|
42
|
+
super().__init__()
|
|
43
|
+
self._final_expected_values = final_expected_values if final_expected_values is not None else np.array([])
|
|
44
|
+
self._expected_values = expected_values if expected_values is not None else np.array([])
|
|
45
|
+
self._final_state = final_state
|
|
46
|
+
self._intermediate_states = intermediate_states or []
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def final_expected_values(self) -> np.ndarray:
|
|
50
|
+
"""Final expectation values measured at the end of the evolution."""
|
|
51
|
+
return self._final_expected_values
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def expected_values(self) -> np.ndarray:
|
|
55
|
+
"""Expectation values tracked over the course of the evolution."""
|
|
56
|
+
return self._expected_values
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def final_state(self) -> QTensor | None:
|
|
60
|
+
"""Final quantum state if provided by the backend."""
|
|
61
|
+
return self._final_state
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def intermediate_states(self) -> list[QTensor]:
|
|
65
|
+
"""List of intermediate quantum states captured during the simulation."""
|
|
66
|
+
return self._intermediate_states
|
|
67
|
+
|
|
68
|
+
def __repr__(self) -> str:
|
|
69
|
+
class_name = self.__class__.__name__
|
|
70
|
+
return (
|
|
71
|
+
f"{class_name}(\n"
|
|
72
|
+
+ f" final_expected_values={pformat(self.final_expected_values)},\n"
|
|
73
|
+
+ (f" expected_values={pformat(self.expected_values)}\n" if len(self.expected_values) > 0 else "")
|
|
74
|
+
+ (f" final_state={pformat(self.final_state)}\n" if self.final_state is not None else "")
|
|
75
|
+
+ (
|
|
76
|
+
f" intermediate_states={pformat(self.intermediate_states)}\n"
|
|
77
|
+
if len(self.intermediate_states) > 0
|
|
78
|
+
else ""
|
|
79
|
+
)
|
|
80
|
+
+ ")"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def compute_cost(self, cost_model: Model) -> float:
|
|
84
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,80 @@
|
|
|
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 typing import TYPE_CHECKING, ClassVar, Generic, TypeVar
|
|
17
|
+
|
|
18
|
+
from qilisdk.functionals.functional import Functional, PrimitiveFunctional
|
|
19
|
+
from qilisdk.functionals.functional_result import FunctionalResult
|
|
20
|
+
from qilisdk.functionals.variational_program_result import VariationalProgramResult
|
|
21
|
+
from qilisdk.yaml import yaml
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from qilisdk.cost_functions.cost_function import CostFunction
|
|
25
|
+
from qilisdk.optimizers.optimizer import Optimizer
|
|
26
|
+
|
|
27
|
+
TFunctional = TypeVar("TFunctional", bound=PrimitiveFunctional[FunctionalResult])
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@yaml.register_class
|
|
31
|
+
class VariationalProgram(Functional, Generic[TFunctional]):
|
|
32
|
+
"""
|
|
33
|
+
Bundle a parameterized functional, optimizer, and cost function into a variational loop.
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
.. code-block:: python
|
|
37
|
+
|
|
38
|
+
program = VariationalProgram(functional, optimizer, cost_function)
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
result_type: ClassVar[type[FunctionalResult]] = VariationalProgramResult
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
functional: TFunctional,
|
|
46
|
+
optimizer: Optimizer,
|
|
47
|
+
cost_function: CostFunction,
|
|
48
|
+
store_intermediate_results: bool = False,
|
|
49
|
+
) -> None:
|
|
50
|
+
"""
|
|
51
|
+
Args:
|
|
52
|
+
functional (PrimitiveFunctional): Parameterized functional to optimize.
|
|
53
|
+
optimizer (Optimizer): Optimization routine controlling parameter updates.
|
|
54
|
+
cost_function (CostFunction): Metric used to evaluate functional executions.
|
|
55
|
+
store_intermediate_results (bool, optional): Persist intermediate executions if requested by the optimizer.
|
|
56
|
+
"""
|
|
57
|
+
self._functional = functional
|
|
58
|
+
self._optimizer = optimizer
|
|
59
|
+
self._cost_function = cost_function
|
|
60
|
+
self._store_intermediate_results = store_intermediate_results
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def functional(self) -> TFunctional:
|
|
64
|
+
"""Return the wrapped functional that will be optimised."""
|
|
65
|
+
return self._functional
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def optimizer(self) -> Optimizer:
|
|
69
|
+
"""Return the optimizer responsible for parameter updates."""
|
|
70
|
+
return self._optimizer
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def cost_function(self) -> CostFunction:
|
|
74
|
+
"""Return the cost function applied to functional results."""
|
|
75
|
+
return self._cost_function
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def store_intermediate_results(self) -> bool:
|
|
79
|
+
"""Indicate whether intermediate execution data should be stored."""
|
|
80
|
+
return self._store_intermediate_results
|
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
|
|
16
|
+
from pprint import pformat
|
|
17
|
+
from typing import Generic, TypeVar
|
|
18
|
+
|
|
19
|
+
from qilisdk.functionals.functional_result import FunctionalResult
|
|
20
|
+
from qilisdk.optimizers.optimizer_result import OptimizerIntermediateResult, OptimizerResult
|
|
21
|
+
from qilisdk.yaml import yaml
|
|
22
|
+
|
|
23
|
+
TResult_co = TypeVar("TResult_co", bound=FunctionalResult, covariant=True)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@yaml.register_class
|
|
27
|
+
class VariationalProgramResult(FunctionalResult, Generic[TResult_co]):
|
|
28
|
+
"""Aggregate the optimizer summary and best functional result from a variational run."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, optimizer_result: OptimizerResult, result: TResult_co) -> None:
|
|
31
|
+
"""
|
|
32
|
+
Args:
|
|
33
|
+
optimizer_result (OptimizerResult): Summary produced by the optimiser.
|
|
34
|
+
result (TResult_co): Functional result evaluated at the final parameters.
|
|
35
|
+
"""
|
|
36
|
+
super().__init__()
|
|
37
|
+
self._optimizer_result = optimizer_result
|
|
38
|
+
self._result = result
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def optimal_cost(self) -> float:
|
|
42
|
+
"""Best cost reported by the optimiser."""
|
|
43
|
+
return self._optimizer_result.optimal_cost
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def optimal_execution_results(self) -> TResult_co:
|
|
47
|
+
"""Return the functional result evaluated at the optimal parameters."""
|
|
48
|
+
return self._result
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def optimal_parameters(self) -> list[float]:
|
|
52
|
+
"""Optimised parameter values."""
|
|
53
|
+
return self._optimizer_result.optimal_parameters
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def intermediate_results(self) -> list[OptimizerIntermediateResult]:
|
|
57
|
+
"""Sequence of intermediate optimiser snapshots, if recorded."""
|
|
58
|
+
return self._optimizer_result.intermediate_results
|
|
59
|
+
|
|
60
|
+
def __repr__(self) -> str:
|
|
61
|
+
class_name = self.__class__.__name__
|
|
62
|
+
return (
|
|
63
|
+
f"{class_name}(\n"
|
|
64
|
+
f" Optimal Cost={self.optimal_cost},\n"
|
|
65
|
+
f" Optimal Parameters={pformat(self.optimal_parameters)},\n"
|
|
66
|
+
f" Intermediate Results={pformat(self.intermediate_results)},\n"
|
|
67
|
+
f" Optimal Results={pformat(self.optimal_execution_results)}\n"
|
|
68
|
+
")"
|
|
69
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
sinks:
|
|
2
|
+
- sink: stderr
|
|
3
|
+
level: WARNING
|
|
4
|
+
format: "<fg #7f1cdb>QiliSDK</> | <green>{time:YYYY-MM-DD at HH:mm:ss}</green> | <lvl>{level}</> | <lvl>{level.icon} {message}</>"
|
|
5
|
+
filter: qilisdk
|
|
6
|
+
colorize: true
|
|
7
|
+
|
|
8
|
+
intercept_libraries:
|
|
9
|
+
- name: httpx
|
|
10
|
+
level: ERROR
|
|
11
|
+
- name: httpcore
|
|
12
|
+
level: ERROR
|
|
13
|
+
- name: keyring
|
|
14
|
+
level: ERROR
|
|
15
|
+
- name: matplotlib
|
|
16
|
+
level: ERROR
|
|
@@ -0,0 +1,39 @@
|
|
|
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 abc import ABC, abstractmethod
|
|
16
|
+
from typing import Callable
|
|
17
|
+
|
|
18
|
+
from .optimizer_result import OptimizerResult
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Optimizer(ABC):
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def optimize(
|
|
24
|
+
self,
|
|
25
|
+
cost_function: Callable[[list[float]], float],
|
|
26
|
+
init_parameters: list[float],
|
|
27
|
+
bounds: list[tuple[float, float]],
|
|
28
|
+
store_intermediate_results: bool = False,
|
|
29
|
+
) -> OptimizerResult:
|
|
30
|
+
"""optimize the cost function and return the optimal parameters.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
cost_function (Callable[[list[float]], float]): a function that takes in a list of parameters and returns the cost.
|
|
34
|
+
init_parameters (list[float]): the list of initial parameters. Note: the length of this list determines the number of parameters the optimizer will consider.
|
|
35
|
+
bounds (list[float, float]): a list of the variable value bounds.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
list[float]: the optimal set of parameters that minimize the cost function.
|
|
39
|
+
"""
|
|
@@ -13,17 +13,14 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
|
+
from qilisdk.common.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__(
|