qilisdk 0.1.0__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 +47 -0
- qilisdk/__init__.pyi +30 -0
- qilisdk/_optionals.py +105 -0
- qilisdk/analog/__init__.py +17 -0
- qilisdk/analog/algorithms.py +111 -0
- qilisdk/analog/analog_backend.py +43 -0
- qilisdk/analog/analog_result.py +114 -0
- qilisdk/analog/exceptions.py +19 -0
- qilisdk/analog/hamiltonian.py +706 -0
- qilisdk/analog/quantum_objects.py +486 -0
- qilisdk/analog/schedule.py +311 -0
- qilisdk/common/__init__.py +20 -0
- qilisdk/common/algorithm.py +17 -0
- qilisdk/common/backend.py +16 -0
- qilisdk/common/model.py +16 -0
- qilisdk/common/optimizer.py +136 -0
- qilisdk/common/optimizer_result.py +110 -0
- qilisdk/common/result.py +17 -0
- qilisdk/digital/__init__.py +66 -0
- qilisdk/digital/ansatz.py +143 -0
- qilisdk/digital/circuit.py +106 -0
- qilisdk/digital/digital_algorithm.py +20 -0
- qilisdk/digital/digital_backend.py +90 -0
- qilisdk/digital/digital_result.py +145 -0
- qilisdk/digital/exceptions.py +31 -0
- qilisdk/digital/gates.py +989 -0
- qilisdk/digital/vqe.py +165 -0
- qilisdk/extras/__init__.py +13 -0
- qilisdk/extras/cuda/__init__.py +18 -0
- qilisdk/extras/cuda/cuda_analog_result.py +19 -0
- qilisdk/extras/cuda/cuda_backend.py +398 -0
- qilisdk/extras/cuda/cuda_digital_result.py +19 -0
- qilisdk/extras/qaas/__init__.py +13 -0
- qilisdk/extras/qaas/keyring.py +54 -0
- qilisdk/extras/qaas/models.py +57 -0
- qilisdk/extras/qaas/qaas_backend.py +154 -0
- qilisdk/extras/qaas/qaas_digital_result.py +20 -0
- qilisdk/extras/qaas/qaas_settings.py +23 -0
- qilisdk/py.typed +0 -0
- qilisdk/utils/__init__.py +27 -0
- qilisdk/utils/openqasm2.py +215 -0
- qilisdk/utils/serialization.py +128 -0
- qilisdk/yaml.py +71 -0
- qilisdk-0.1.0.dist-info/METADATA +237 -0
- qilisdk-0.1.0.dist-info/RECORD +47 -0
- qilisdk-0.1.0.dist-info/WHEEL +4 -0
- qilisdk-0.1.0.dist-info/licenses/LICENCE +201 -0
|
@@ -0,0 +1,311 @@
|
|
|
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 Callable
|
|
17
|
+
from warnings import warn
|
|
18
|
+
|
|
19
|
+
from qilisdk.analog.hamiltonian import Hamiltonian
|
|
20
|
+
from qilisdk.yaml import yaml
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@yaml.register_class
|
|
24
|
+
class Schedule:
|
|
25
|
+
"""
|
|
26
|
+
Represents a time-dependent schedule for Hamiltonian coefficients in an annealing process.
|
|
27
|
+
|
|
28
|
+
A Schedule defines the evolution of a system by associating time steps with a set
|
|
29
|
+
of Hamiltonian coefficients. It maintains a dictionary of Hamiltonian objects and a
|
|
30
|
+
corresponding schedule that specifies the coefficients (weights) for each Hamiltonian
|
|
31
|
+
at discrete time steps.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
_T (float): The total annealing time.
|
|
35
|
+
_dt (float): The time step duration. Total time must be divisible by dt.
|
|
36
|
+
_hamiltonians (dict[str, Hamiltonian]): A mapping of labels to Hamiltonian objects.
|
|
37
|
+
_schedule (dict[int, dict[str, float]]): A mapping of time steps to coefficient dictionaries.
|
|
38
|
+
_nqubits (int): The maximum number of qubits among the Hamiltonians.
|
|
39
|
+
iter_time_step (int): Internal counter for iteration over time steps.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
T: float,
|
|
45
|
+
dt: float,
|
|
46
|
+
hamiltonians: dict[str, Hamiltonian] | None = None,
|
|
47
|
+
schedule: dict[int, dict[str, float]] | None = None,
|
|
48
|
+
) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Initialize a Schedule object.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
T (float): The total annealing time.
|
|
54
|
+
dt (float): The time step for the annealing process. Note that T needs to be divisible by dt.
|
|
55
|
+
hamiltonians (dict[str, Hamiltonian], optional): A dictionary mapping labels to Hamiltonian objects.
|
|
56
|
+
Defaults to an empty dictionary if None.
|
|
57
|
+
schedule (dict[int, dict[str, float]], optional): A dictionary where keys are time step indices (integers)
|
|
58
|
+
and values are dictionaries mapping Hamiltonian labels to their coefficients at that time step.
|
|
59
|
+
Defaults to {0: {}} if None.
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
ValueError: If the provided schedule references Hamiltonians that have not been defined.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
self._hamiltonians: dict[str, Hamiltonian] = hamiltonians if hamiltonians is not None else {}
|
|
66
|
+
self._schedule: dict[int, dict[str, float]] = schedule if schedule is not None else {0: {}}
|
|
67
|
+
self._T = T
|
|
68
|
+
self._dt = dt
|
|
69
|
+
self.iter_time_step = 0
|
|
70
|
+
self._nqubits = 0
|
|
71
|
+
for hamiltonian in self._hamiltonians.values():
|
|
72
|
+
self._nqubits = max(self._nqubits, hamiltonian.nqubits)
|
|
73
|
+
|
|
74
|
+
if 0 not in self._schedule:
|
|
75
|
+
self._schedule[0] = dict.fromkeys(self._hamiltonians, 0.0)
|
|
76
|
+
else:
|
|
77
|
+
for label in self._hamiltonians:
|
|
78
|
+
if label not in self._schedule[0]:
|
|
79
|
+
self._schedule[0][label] = 0
|
|
80
|
+
|
|
81
|
+
for time_step in self._schedule.values():
|
|
82
|
+
if not all(s in self._hamiltonians for s in time_step):
|
|
83
|
+
raise ValueError(
|
|
84
|
+
"All hamiltonians defined in the schedule need to be declared in the hamiltonians dictionary."
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def hamiltonians(self) -> dict[str, Hamiltonian]:
|
|
89
|
+
"""
|
|
90
|
+
Get the dictionary of Hamiltonians known to this schedule.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
dict[str, Hamiltonian]: A mapping of Hamiltonian labels to Hamiltonian objects.
|
|
94
|
+
"""
|
|
95
|
+
return self._hamiltonians
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def schedule(self) -> dict[int, dict[str, float]]:
|
|
99
|
+
"""
|
|
100
|
+
Get the full schedule of Hamiltonian coefficients.
|
|
101
|
+
|
|
102
|
+
The schedule is returned as a dictionary sorted by time step.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
dict[int, dict[str, float]]: The mapping of time steps to coefficient dictionaries.
|
|
106
|
+
"""
|
|
107
|
+
return dict(sorted(self._schedule.items()))
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def T(self) -> float:
|
|
111
|
+
"""
|
|
112
|
+
Get the total annealing time.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
float: The total time T.
|
|
116
|
+
"""
|
|
117
|
+
return self._T
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def dt(self) -> float:
|
|
121
|
+
"""
|
|
122
|
+
Get the time step duration.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
float: The duration of each time step.
|
|
126
|
+
"""
|
|
127
|
+
return self._dt
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def nqubits(self) -> int:
|
|
131
|
+
"""
|
|
132
|
+
Get the maximum number of qubits among all Hamiltonians in the schedule.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
int: The number of qubits.
|
|
136
|
+
"""
|
|
137
|
+
return self._nqubits
|
|
138
|
+
|
|
139
|
+
def add_hamiltonian(
|
|
140
|
+
self, label: str, hamiltonian: Hamiltonian, schedule: Callable | None = None, **kwargs: dict
|
|
141
|
+
) -> None:
|
|
142
|
+
"""
|
|
143
|
+
Add a Hamiltonian to the schedule with an optional coefficient schedule function.
|
|
144
|
+
|
|
145
|
+
If a Hamiltonian with the given label already exists, a warning is issued and only
|
|
146
|
+
the schedule is updated if a callable is provided.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
label (str): The unique label to identify the Hamiltonian.
|
|
150
|
+
hamiltonian (Hamiltonian): The Hamiltonian object to add.
|
|
151
|
+
schedule (Callable, optional): A function that returns the coefficient of the Hamiltonian at time t.
|
|
152
|
+
It should accept time (and any additional keyword arguments) and return a float.
|
|
153
|
+
**kwargs (dict): Additional keyword arguments to pass to the schedule function.
|
|
154
|
+
"""
|
|
155
|
+
if label not in self._hamiltonians:
|
|
156
|
+
self._hamiltonians[label] = hamiltonian
|
|
157
|
+
self._schedule[0][label] = 0
|
|
158
|
+
self._nqubits = max(self._nqubits, hamiltonian.nqubits)
|
|
159
|
+
else:
|
|
160
|
+
warn(
|
|
161
|
+
(
|
|
162
|
+
f"label {label} is already assigned to a hamiltonian, "
|
|
163
|
+
+ "ignoring new hamiltonian and updating schedule of existing hamiltonian."
|
|
164
|
+
),
|
|
165
|
+
RuntimeWarning,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
if schedule is not None:
|
|
169
|
+
for t in range(int(self.T / self.dt) + 1):
|
|
170
|
+
self.update_hamiltonian_coefficient_at_time_step(t, label, schedule(t, **kwargs))
|
|
171
|
+
|
|
172
|
+
def add_schedule_step(self, time_step: int, hamiltonian_coefficient_list: dict[str, float]) -> None:
|
|
173
|
+
"""
|
|
174
|
+
Add or update a schedule step with specified Hamiltonian coefficients.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
time_step (int): The time step index at which the Hamiltonian coefficients are updated.
|
|
178
|
+
The actual time is computed as dt * time_step.
|
|
179
|
+
hamiltonian_coefficient_list (dict[str, float]): A dictionary mapping Hamiltonian labels to their
|
|
180
|
+
coefficient values at this time step.
|
|
181
|
+
If a Hamiltonian is not included in the dictionary, it is assumed its coefficient remains unchanged.
|
|
182
|
+
|
|
183
|
+
Raises:
|
|
184
|
+
ValueError: If hamiltonian_coefficient_list references a Hamiltonian that is not defined in the schedule.
|
|
185
|
+
"""
|
|
186
|
+
if time_step in self._schedule:
|
|
187
|
+
warn(
|
|
188
|
+
f"time step {time_step} is already defined in the schedule, the values are going to be overwritten.",
|
|
189
|
+
RuntimeWarning,
|
|
190
|
+
)
|
|
191
|
+
for key in hamiltonian_coefficient_list:
|
|
192
|
+
if key not in self._hamiltonians:
|
|
193
|
+
raise ValueError(f"trying to reference a hamiltonian {key} that is not defined in this schedule.")
|
|
194
|
+
self._schedule[time_step] = hamiltonian_coefficient_list
|
|
195
|
+
|
|
196
|
+
def update_hamiltonian_coefficient_at_time_step(
|
|
197
|
+
self, time_step: int, hamiltonian_label: str, new_coefficient: float
|
|
198
|
+
) -> None:
|
|
199
|
+
"""
|
|
200
|
+
Update the coefficient value of a specific Hamiltonian at a given time step.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
time_step (int): The time step (as an integer multiple of dt) at which to update the coefficient.
|
|
204
|
+
hamiltonian_label (str): The label of the Hamiltonian to update.
|
|
205
|
+
new_coefficient (float): The new coefficient value.
|
|
206
|
+
|
|
207
|
+
Raises:
|
|
208
|
+
ValueError: If the specified time step exceeds the total annealing time.
|
|
209
|
+
"""
|
|
210
|
+
if not (time_step * self.dt <= self.T):
|
|
211
|
+
raise ValueError("Can't add a time step which happens after the end of the annealing process.")
|
|
212
|
+
|
|
213
|
+
if time_step not in self._schedule:
|
|
214
|
+
self._schedule[time_step] = {}
|
|
215
|
+
self._schedule[time_step][hamiltonian_label] = new_coefficient
|
|
216
|
+
|
|
217
|
+
def __getitem__(self, time_step: int) -> Hamiltonian:
|
|
218
|
+
"""
|
|
219
|
+
Retrieve the effective Hamiltonian at a given time step.
|
|
220
|
+
|
|
221
|
+
The effective Hamiltonian is computed by summing the contributions of all Hamiltonians,
|
|
222
|
+
using the latest defined coefficients at or before the given time step.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
time_step (int): The time step index for which to retrieve the Hamiltonian.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Hamiltonian: The effective Hamiltonian at the specified time step.
|
|
229
|
+
"""
|
|
230
|
+
ham = Hamiltonian()
|
|
231
|
+
read_labels = []
|
|
232
|
+
|
|
233
|
+
if time_step not in self._schedule:
|
|
234
|
+
while time_step > 0:
|
|
235
|
+
time_step -= 1
|
|
236
|
+
if time_step in self._schedule:
|
|
237
|
+
for ham_label in self._schedule[time_step]:
|
|
238
|
+
ham += self._schedule[time_step][ham_label] * self._hamiltonians[ham_label]
|
|
239
|
+
read_labels.append(ham_label)
|
|
240
|
+
break
|
|
241
|
+
else:
|
|
242
|
+
for ham_label in self._schedule[time_step]:
|
|
243
|
+
ham += self._schedule[time_step][ham_label] * self._hamiltonians[ham_label]
|
|
244
|
+
read_labels.append(ham_label)
|
|
245
|
+
if len(read_labels) < len(self._hamiltonians):
|
|
246
|
+
all_labels = self._hamiltonians.keys()
|
|
247
|
+
remaining_labels = list(filter(lambda x: x not in read_labels, all_labels))
|
|
248
|
+
for label in remaining_labels:
|
|
249
|
+
current_time = time_step
|
|
250
|
+
while current_time > 0:
|
|
251
|
+
current_time -= 1
|
|
252
|
+
if current_time in self._schedule and label in self._schedule[current_time]:
|
|
253
|
+
ham += self._schedule[current_time][label] * self._hamiltonians[label]
|
|
254
|
+
break
|
|
255
|
+
return ham
|
|
256
|
+
|
|
257
|
+
def get_coefficient(self, time_step: float, hamiltonian_key: str) -> float:
|
|
258
|
+
"""
|
|
259
|
+
Retrieve the coefficient of a specified Hamiltonian at a given time.
|
|
260
|
+
|
|
261
|
+
This function searches backwards in time (by multiples of dt) until it finds a defined
|
|
262
|
+
coefficient for the given Hamiltonian.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
time_step (float): The time (in the same units as T) at which to query the coefficient.
|
|
266
|
+
hamiltonian_key (str): The label of the Hamiltonian.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
float: The coefficient of the Hamiltonian at the specified time, or 0 if not defined.
|
|
270
|
+
"""
|
|
271
|
+
time_idx = int(time_step / self.dt)
|
|
272
|
+
while time_idx >= 0:
|
|
273
|
+
if time_idx in self._schedule and hamiltonian_key in self._schedule[time_idx]:
|
|
274
|
+
return self._schedule[time_idx][hamiltonian_key]
|
|
275
|
+
time_idx -= 1
|
|
276
|
+
return 0
|
|
277
|
+
|
|
278
|
+
def __len__(self) -> int:
|
|
279
|
+
"""
|
|
280
|
+
Get the total number of discrete time steps in the annealing process.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
int: The number of time steps, calculated as T / dt.
|
|
284
|
+
"""
|
|
285
|
+
return int(self.T / self.dt)
|
|
286
|
+
|
|
287
|
+
def __iter__(self) -> Schedule:
|
|
288
|
+
"""
|
|
289
|
+
Return an iterator over the schedule's time steps.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
Schedule: The schedule instance itself as an iterator.
|
|
293
|
+
"""
|
|
294
|
+
self.iter_time_step = 0
|
|
295
|
+
return self
|
|
296
|
+
|
|
297
|
+
def __next__(self) -> Hamiltonian:
|
|
298
|
+
"""
|
|
299
|
+
Retrieve the next effective Hamiltonian in the schedule during iteration.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
Hamiltonian: The effective Hamiltonian at the current time step.
|
|
303
|
+
|
|
304
|
+
Raises:
|
|
305
|
+
StopIteration: When the iteration has reached beyond the total number of time steps.
|
|
306
|
+
"""
|
|
307
|
+
if self.iter_time_step <= self.__len__():
|
|
308
|
+
result = self[self.iter_time_step]
|
|
309
|
+
self.iter_time_step += 1
|
|
310
|
+
return result
|
|
311
|
+
raise StopIteration
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Copyright 2025 Qilimanjaro Quantum Tech
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from .algorithm import Algorithm
|
|
16
|
+
from .model import Model
|
|
17
|
+
from .optimizer import Optimizer, SciPyOptimizer
|
|
18
|
+
from .result import Result
|
|
19
|
+
|
|
20
|
+
__all__ = ["Algorithm", "Model", "Optimizer", "Result", "SciPyOptimizer"]
|
|
@@ -0,0 +1,17 @@
|
|
|
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 abc import ABC
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Algorithm(ABC): ...
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
class Backend: ...
|
qilisdk/common/model.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
class Model: ...
|
|
@@ -0,0 +1,136 @@
|
|
|
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 Any, Callable
|
|
17
|
+
|
|
18
|
+
from scipy import optimize as scipy_optimize
|
|
19
|
+
from scipy.optimize import OptimizeResult
|
|
20
|
+
|
|
21
|
+
from qilisdk.yaml import yaml
|
|
22
|
+
|
|
23
|
+
from .optimizer_result import OptimizerIntermediateResult, OptimizerResult
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Optimizer(ABC):
|
|
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
|
+
"""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@yaml.register_class
|
|
50
|
+
class SciPyOptimizer(Optimizer):
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
method: str | Callable | None = None,
|
|
54
|
+
**kwargs: dict[str, Any],
|
|
55
|
+
) -> None:
|
|
56
|
+
"""Create a new Gradient Based optimizer instance.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
method (str | Callable | None, optional):Type of solver. Should be one of
|
|
60
|
+
- 'Nelder-Mead
|
|
61
|
+
- 'Powell'
|
|
62
|
+
- 'CG'
|
|
63
|
+
- 'BFGS'
|
|
64
|
+
- 'Newton-CG'
|
|
65
|
+
- 'L-BFGS-B'
|
|
66
|
+
- 'TNC'
|
|
67
|
+
- 'COBYLA'
|
|
68
|
+
- 'COBYQA'
|
|
69
|
+
- 'SLSQP'
|
|
70
|
+
- 'trust-constr
|
|
71
|
+
- 'dogleg'
|
|
72
|
+
- 'trust-ncg'
|
|
73
|
+
- 'trust-exact
|
|
74
|
+
- 'trust-krylov
|
|
75
|
+
- custom - a callable object, see `scipy.optimize.minimize <https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html>`__ for description.
|
|
76
|
+
|
|
77
|
+
If not given, chosen to be one of ``BFGS``, ``L-BFGS-B``, ``SLSQP``,
|
|
78
|
+
depending on whether or not the problem has constraints or bounds.
|
|
79
|
+
bounds (list[tuple[int, int]] | None, optional):
|
|
80
|
+
Bounds on variables for Nelder-Mead, L-BFGS-B, TNC, SLSQP, Powell,
|
|
81
|
+
trust-constr, COBYLA, and COBYQA methods. To specify it you can provide a sequence of ``(min, max)`` pairs
|
|
82
|
+
for each element in parameter list.
|
|
83
|
+
|
|
84
|
+
Extra Args:
|
|
85
|
+
Any argument supported by `scipy.optimize.minimize <https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html>`__ can be passed.
|
|
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.
|
|
87
|
+
"""
|
|
88
|
+
super().__init__()
|
|
89
|
+
self.method = method
|
|
90
|
+
self.extra_arguments = kwargs
|
|
91
|
+
|
|
92
|
+
def optimize(
|
|
93
|
+
self,
|
|
94
|
+
cost_function: Callable[[list[float]], float],
|
|
95
|
+
init_parameters: list[float],
|
|
96
|
+
store_intermediate_results: bool = False,
|
|
97
|
+
) -> OptimizerResult:
|
|
98
|
+
"""optimize the cost function and return the optimal parameters.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
cost_function (Callable[[list[float]], float]): a function that takes in a list of parameters and returns the cost.
|
|
102
|
+
init_parameters (list[float]): the list of initial parameters. Note: the length of this list determines the number of parameters the optimizer will consider.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
list[float]: the optimal set of parameters that minimize the cost function.
|
|
106
|
+
"""
|
|
107
|
+
intermediate_results: list[OptimizerIntermediateResult] = []
|
|
108
|
+
|
|
109
|
+
def callback_fun(intermediate_result: OptimizeResult) -> None:
|
|
110
|
+
# Create an OptimizerResult for this intermediate iteration.
|
|
111
|
+
intermediate_results.append(
|
|
112
|
+
OptimizerIntermediateResult(cost=intermediate_result.fun, parameters=intermediate_result.x.tolist())
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Only pass the callback if we want to store intermediate results.
|
|
116
|
+
callback = callback_fun if store_intermediate_results else None
|
|
117
|
+
|
|
118
|
+
res = scipy_optimize.minimize(
|
|
119
|
+
cost_function,
|
|
120
|
+
x0=init_parameters,
|
|
121
|
+
method=self.method,
|
|
122
|
+
jac=self.extra_arguments.get("jac", None),
|
|
123
|
+
hess=self.extra_arguments.get("hess", None),
|
|
124
|
+
hessp=self.extra_arguments.get("hessp", None),
|
|
125
|
+
bounds=self.extra_arguments.get("bounds", None),
|
|
126
|
+
constraints=self.extra_arguments.get("constraints", ()),
|
|
127
|
+
tol=self.extra_arguments.get("tol", None),
|
|
128
|
+
options=self.extra_arguments.get("options", None),
|
|
129
|
+
callback=callback,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
return OptimizerResult(
|
|
133
|
+
optimal_cost=res.fun,
|
|
134
|
+
optimal_parameters=res.x.tolist(),
|
|
135
|
+
intermediate_results=intermediate_results,
|
|
136
|
+
)
|
|
@@ -0,0 +1,110 @@
|
|
|
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 qilisdk.yaml import yaml
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@yaml.register_class
|
|
20
|
+
class OptimizerIntermediateResult:
|
|
21
|
+
"""
|
|
22
|
+
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
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
cost: float,
|
|
32
|
+
parameters: list[float],
|
|
33
|
+
) -> None:
|
|
34
|
+
self._cost = cost
|
|
35
|
+
self._parameters = parameters
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def cost(self) -> float:
|
|
39
|
+
"""Return the optimal cost value."""
|
|
40
|
+
return self._cost
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def parameters(self) -> list[float]:
|
|
44
|
+
"""Return the optimal parameters as a list of floats."""
|
|
45
|
+
return list(self._parameters)
|
|
46
|
+
|
|
47
|
+
def __repr__(self) -> str:
|
|
48
|
+
"""Return a formatted string representation for debugging."""
|
|
49
|
+
return f"OptimizerIntermediateResult(cost={self._cost}, parameters={self._parameters})"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@yaml.register_class
|
|
53
|
+
class OptimizerResult:
|
|
54
|
+
"""
|
|
55
|
+
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
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
optimal_cost: float,
|
|
67
|
+
optimal_parameters: list[float],
|
|
68
|
+
intermediate_results: list[OptimizerIntermediateResult] | None = None,
|
|
69
|
+
) -> None:
|
|
70
|
+
"""
|
|
71
|
+
Initialize an OptimizerResult.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
optimal_cost (float): The optimal cost value.
|
|
75
|
+
optimal_parameters (List[float]): The parameters corresponding to the optimal cost.
|
|
76
|
+
intermediate_results (Optional[List[OptimizerResult]]): (Optional) A list of intermediate results recorded during optimization.
|
|
77
|
+
Each intermediate result is an OptimizerResult. Defaults to an empty list if not provided.
|
|
78
|
+
"""
|
|
79
|
+
self._optimal_cost = optimal_cost
|
|
80
|
+
self._optimal_parameters = optimal_parameters
|
|
81
|
+
self._intermediate_results = intermediate_results or []
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def optimal_cost(self) -> float:
|
|
85
|
+
"""Return the optimal cost value."""
|
|
86
|
+
return self._optimal_cost
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def optimal_parameters(self) -> list[float]:
|
|
90
|
+
"""Return the optimal parameters as a list of floats."""
|
|
91
|
+
return list(self._optimal_parameters)
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def intermediate_results(self) -> list[OptimizerIntermediateResult]:
|
|
95
|
+
"""
|
|
96
|
+
Return the list of intermediate results.
|
|
97
|
+
|
|
98
|
+
Each intermediate result is an instance of OptimizerResult containing:
|
|
99
|
+
- optimal_cost: The cost computed at that iteration.
|
|
100
|
+
- optimal_parameters: The parameters corresponding to that iteration.
|
|
101
|
+
"""
|
|
102
|
+
return list(self._intermediate_results)
|
|
103
|
+
|
|
104
|
+
def __repr__(self) -> str:
|
|
105
|
+
"""Return a formatted string representation for debugging."""
|
|
106
|
+
return (
|
|
107
|
+
f"OptimizerResult(optimal_cost={self._optimal_cost}, "
|
|
108
|
+
f"optimal_parameters={self._optimal_parameters}, "
|
|
109
|
+
f"intermediate_results={self._intermediate_results})"
|
|
110
|
+
)
|
qilisdk/common/result.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
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 abc import ABC
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Result(ABC): ...
|