qilisdk 0.1.4__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.
Files changed (83) hide show
  1. qilisdk/__init__.py +11 -2
  2. qilisdk/__init__.pyi +2 -3
  3. qilisdk/_logging.py +135 -0
  4. qilisdk/_optionals.py +5 -7
  5. qilisdk/analog/__init__.py +3 -18
  6. qilisdk/analog/exceptions.py +2 -4
  7. qilisdk/analog/hamiltonian.py +455 -110
  8. qilisdk/analog/linear_schedule.py +118 -0
  9. qilisdk/analog/schedule.py +272 -79
  10. qilisdk/backends/__init__.py +45 -0
  11. qilisdk/{digital/digital_algorithm.py → backends/__init__.pyi} +3 -5
  12. qilisdk/backends/backend.py +117 -0
  13. qilisdk/{extras/cuda → backends}/cuda_backend.py +152 -159
  14. qilisdk/backends/qutip_backend.py +492 -0
  15. qilisdk/common/__init__.py +48 -2
  16. qilisdk/common/algorithm.py +2 -1
  17. qilisdk/{extras/qaas/qaas_settings.py → common/exceptions.py} +12 -6
  18. qilisdk/common/model.py +1019 -1
  19. qilisdk/common/parameterizable.py +75 -0
  20. qilisdk/common/qtensor.py +666 -0
  21. qilisdk/common/result.py +2 -1
  22. qilisdk/common/variables.py +1931 -0
  23. qilisdk/{extras/cuda/cuda_analog_result.py → cost_functions/__init__.py} +3 -4
  24. qilisdk/cost_functions/cost_function.py +77 -0
  25. qilisdk/cost_functions/model_cost_function.py +145 -0
  26. qilisdk/cost_functions/observable_cost_function.py +109 -0
  27. qilisdk/digital/__init__.py +3 -22
  28. qilisdk/digital/ansatz.py +203 -160
  29. qilisdk/digital/circuit.py +81 -9
  30. qilisdk/digital/exceptions.py +12 -6
  31. qilisdk/digital/gates.py +228 -85
  32. qilisdk/{extras/qaas/qaas_analog_result.py → functionals/__init__.py} +14 -5
  33. qilisdk/functionals/functional.py +39 -0
  34. qilisdk/{extras/cuda/cuda_digital_result.py → functionals/functional_result.py} +3 -4
  35. qilisdk/functionals/sampling.py +81 -0
  36. qilisdk/functionals/sampling_result.py +92 -0
  37. qilisdk/functionals/time_evolution.py +98 -0
  38. qilisdk/functionals/time_evolution_result.py +84 -0
  39. qilisdk/functionals/variational_program.py +80 -0
  40. qilisdk/functionals/variational_program_result.py +69 -0
  41. qilisdk/logging_config.yaml +16 -0
  42. qilisdk/{common/backend.py → optimizers/__init__.py} +2 -1
  43. qilisdk/optimizers/optimizer.py +39 -0
  44. qilisdk/{common → optimizers}/optimizer_result.py +3 -12
  45. qilisdk/{common/optimizer.py → optimizers/scipy_optimizer.py} +10 -28
  46. qilisdk/settings.py +78 -0
  47. qilisdk/{extras → speqtrum}/__init__.py +7 -8
  48. qilisdk/{extras → speqtrum}/__init__.pyi +3 -3
  49. qilisdk/speqtrum/experiments/__init__.py +25 -0
  50. qilisdk/speqtrum/experiments/experiment_functional.py +124 -0
  51. qilisdk/speqtrum/experiments/experiment_result.py +231 -0
  52. qilisdk/{extras/qaas → speqtrum}/keyring.py +8 -4
  53. qilisdk/speqtrum/speqtrum.py +432 -0
  54. qilisdk/speqtrum/speqtrum_models.py +300 -0
  55. qilisdk/utils/__init__.py +0 -14
  56. qilisdk/utils/openqasm2.py +1 -1
  57. qilisdk/utils/serialization.py +1 -1
  58. qilisdk/utils/visualization/PlusJakartaSans-SemiBold.ttf +0 -0
  59. qilisdk/utils/visualization/__init__.py +24 -0
  60. qilisdk/utils/visualization/circuit_renderers.py +781 -0
  61. qilisdk/utils/visualization/schedule_renderers.py +161 -0
  62. qilisdk/utils/visualization/style.py +154 -0
  63. qilisdk/utils/visualization/themes.py +76 -0
  64. qilisdk/yaml.py +126 -0
  65. {qilisdk-0.1.4.dist-info → qilisdk-0.1.5.dist-info}/METADATA +180 -134
  66. qilisdk-0.1.5.dist-info/RECORD +69 -0
  67. qilisdk/analog/algorithms.py +0 -111
  68. qilisdk/analog/analog_backend.py +0 -43
  69. qilisdk/analog/analog_result.py +0 -114
  70. qilisdk/analog/quantum_objects.py +0 -596
  71. qilisdk/digital/digital_backend.py +0 -90
  72. qilisdk/digital/digital_result.py +0 -145
  73. qilisdk/digital/vqe.py +0 -166
  74. qilisdk/extras/cuda/__init__.py +0 -13
  75. qilisdk/extras/qaas/__init__.py +0 -13
  76. qilisdk/extras/qaas/models.py +0 -132
  77. qilisdk/extras/qaas/qaas_backend.py +0 -255
  78. qilisdk/extras/qaas/qaas_digital_result.py +0 -20
  79. qilisdk/extras/qaas/qaas_time_evolution_result.py +0 -20
  80. qilisdk/extras/qaas/qaas_vqe_result.py +0 -20
  81. qilisdk-0.1.4.dist-info/RECORD +0 -51
  82. {qilisdk-0.1.4.dist-info → qilisdk-0.1.5.dist-info}/WHEEL +0 -0
  83. {qilisdk-0.1.4.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.digital.digital_result import DigitalResult
15
- from qilisdk.yaml import yaml
14
+ from qilisdk.common.result import Result
16
15
 
17
16
 
18
- @yaml.register_class
19
- class CudaDigitalResult(DigitalResult): ...
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
@@ -12,5 +12,6 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from .scipy_optimizer import SciPyOptimizer
15
16
 
16
- class Backend: ...
17
+ __all__ = ["SciPyOptimizer"]
@@ -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__(