qoro-divi 0.3.2b0__py3-none-any.whl → 0.3.4__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.
Potentially problematic release.
This version of qoro-divi might be problematic. Click here for more details.
- divi/__init__.py +1 -2
- divi/backends/__init__.py +7 -0
- divi/{parallel_simulator.py → backends/_parallel_simulator.py} +4 -3
- divi/{qoro_service.py → backends/_qoro_service.py} +27 -15
- divi/circuits/__init__.py +5 -0
- divi/{circuits.py → circuits/_core.py} +6 -20
- divi/{qasm.py → circuits/qasm.py} +2 -2
- divi/{exp → extern}/cirq/__init__.py +1 -1
- divi/{exp → extern}/cirq/_validator.py +10 -8
- divi/qprog/__init__.py +19 -6
- divi/qprog/algorithms/__init__.py +14 -0
- divi/qprog/algorithms/_ansatze.py +215 -0
- divi/qprog/{_qaoa.py → algorithms/_qaoa.py} +16 -26
- divi/qprog/{_vqe.py → algorithms/_vqe.py} +35 -133
- divi/qprog/batch.py +25 -19
- divi/qprog/optimizers.py +170 -45
- divi/qprog/quantum_program.py +142 -200
- divi/qprog/workflows/__init__.py +10 -0
- divi/qprog/{_graph_partitioning.py → workflows/_graph_partitioning.py} +6 -9
- divi/qprog/{_qubo_partitioning.py → workflows/_qubo_partitioning.py} +6 -7
- divi/qprog/{_vqe_sweep.py → workflows/_vqe_sweep.py} +35 -24
- divi/reporting/__init__.py +7 -0
- divi/{_pbar.py → reporting/_pbar.py} +13 -14
- divi/{qlogger.py → reporting/_qlogger.py} +8 -6
- divi/{reporter.py → reporting/_reporter.py} +24 -7
- divi/utils.py +14 -6
- {qoro_divi-0.3.2b0.dist-info → qoro_divi-0.3.4.dist-info}/METADATA +2 -2
- qoro_divi-0.3.4.dist-info/RECORD +68 -0
- qoro_divi-0.3.2b0.dist-info/RECORD +0 -62
- /divi/{interfaces.py → backends/_circuit_runner.py} +0 -0
- /divi/{qpu_system.py → backends/_qpu_system.py} +0 -0
- /divi/{qem.py → circuits/qem.py} +0 -0
- /divi/{exp → extern}/cirq/_lexer.py +0 -0
- /divi/{exp → extern}/cirq/_parser.py +0 -0
- /divi/{exp → extern}/cirq/_qasm_export.py +0 -0
- /divi/{exp → extern}/cirq/_qasm_import.py +0 -0
- /divi/{exp → extern}/cirq/exception.py +0 -0
- /divi/{exp → extern}/scipy/_cobyla.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/LICENCE.txt +0 -0
- /divi/{exp → extern}/scipy/pyprima/__init__.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/cobyla/__init__.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/cobyla/cobyla.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/cobyla/cobylb.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/cobyla/geometry.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/cobyla/initialize.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/cobyla/trustregion.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/cobyla/update.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/__init__.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/_bounds.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/_linear_constraints.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/_nonlinear_constraints.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/_project.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/checkbreak.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/consts.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/evaluate.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/history.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/infos.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/linalg.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/message.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/powalg.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/preproc.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/present.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/ratio.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/redrho.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/selectx.py +0 -0
- {qoro_divi-0.3.2b0.dist-info → qoro_divi-0.3.4.dist-info}/LICENSE +0 -0
- {qoro_divi-0.3.2b0.dist-info → qoro_divi-0.3.4.dist-info}/LICENSES/.license-header +0 -0
- {qoro_divi-0.3.2b0.dist-info → qoro_divi-0.3.4.dist-info}/LICENSES/Apache-2.0.txt +0 -0
- {qoro_divi-0.3.2b0.dist-info → qoro_divi-0.3.4.dist-info}/WHEEL +0 -0
divi/qprog/quantum_program.py
CHANGED
|
@@ -6,29 +6,57 @@ import logging
|
|
|
6
6
|
import pickle
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
8
|
from functools import partial
|
|
9
|
+
from itertools import groupby
|
|
9
10
|
from queue import Queue
|
|
10
11
|
|
|
11
12
|
import numpy as np
|
|
12
13
|
from pennylane.measurements import ExpectationMP
|
|
13
|
-
from scipy.optimize import OptimizeResult
|
|
14
|
+
from scipy.optimize import OptimizeResult
|
|
14
15
|
|
|
15
|
-
from divi import QoroService
|
|
16
|
+
from divi.backends import CircuitRunner, JobStatus, QoroService
|
|
16
17
|
from divi.circuits import Circuit, MetaCircuit
|
|
17
|
-
from divi.
|
|
18
|
-
from divi.
|
|
19
|
-
from divi.
|
|
20
|
-
from divi.qoro_service import JobStatus
|
|
21
|
-
from divi.qprog.optimizers import Optimizer
|
|
18
|
+
from divi.circuits.qem import _NoMitigation
|
|
19
|
+
from divi.qprog.optimizers import ScipyMethod, ScipyOptimizer
|
|
20
|
+
from divi.reporting import LoggingProgressReporter, QueueProgressReporter
|
|
22
21
|
|
|
23
22
|
logger = logging.getLogger(__name__)
|
|
24
23
|
|
|
25
24
|
|
|
25
|
+
def _compute_parameter_shift_mask(n_params):
|
|
26
|
+
"""
|
|
27
|
+
Generate a binary matrix mask for the parameter shift rule.
|
|
28
|
+
This mask is used to determine the shifts to apply to each parameter
|
|
29
|
+
when computing gradients via the parameter shift rule in quantum algorithms.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
n_params (int): The number of parameters in the quantum circuit.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
np.ndarray: A (2 * n_params, n_params) matrix where each row encodes
|
|
36
|
+
the shift to apply to each parameter for a single evaluation.
|
|
37
|
+
The values are multiples of 0.5 * pi, with alternating signs.
|
|
38
|
+
"""
|
|
39
|
+
mask_arr = np.arange(0, 2 * n_params, 2)
|
|
40
|
+
mask_arr[0] = 1
|
|
41
|
+
|
|
42
|
+
binary_matrix = ((mask_arr[:, np.newaxis] & (1 << np.arange(n_params))) > 0).astype(
|
|
43
|
+
np.float64
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
binary_matrix = binary_matrix.repeat(2, axis=0)
|
|
47
|
+
binary_matrix[1::2] *= -1
|
|
48
|
+
binary_matrix *= 0.5 * np.pi
|
|
49
|
+
|
|
50
|
+
return binary_matrix
|
|
51
|
+
|
|
52
|
+
|
|
26
53
|
class QuantumProgram(ABC):
|
|
27
54
|
def __init__(
|
|
28
55
|
self,
|
|
29
56
|
backend: CircuitRunner,
|
|
30
57
|
seed: int | None = None,
|
|
31
58
|
progress_queue: Queue | None = None,
|
|
59
|
+
has_final_computation: bool = False,
|
|
32
60
|
**kwargs,
|
|
33
61
|
):
|
|
34
62
|
"""
|
|
@@ -49,6 +77,8 @@ class QuantumProgram(ABC):
|
|
|
49
77
|
be used for the parameter initialization.
|
|
50
78
|
Defaults to None.
|
|
51
79
|
progress_queue (Queue): a queue for progress bar updates.
|
|
80
|
+
has_final_computation (bool): Whether the program includes a final
|
|
81
|
+
computation step after optimization. This affects progress reporting.
|
|
52
82
|
|
|
53
83
|
**kwargs: Additional keyword arguments that influence behaviour.
|
|
54
84
|
- grouping_strategy (Literal["default", "wires", "qwc"]): A strategy for grouping operations, used in Pennylane's transforms.
|
|
@@ -80,9 +110,15 @@ class QuantumProgram(ABC):
|
|
|
80
110
|
self._grad_mode = False
|
|
81
111
|
|
|
82
112
|
self.backend = backend
|
|
83
|
-
self.job_id = kwargs.get("job_id", None)
|
|
84
113
|
|
|
114
|
+
self.job_id = kwargs.get("job_id", None)
|
|
85
115
|
self._progress_queue = progress_queue
|
|
116
|
+
if progress_queue and self.job_id:
|
|
117
|
+
self.reporter = QueueProgressReporter(
|
|
118
|
+
self.job_id, progress_queue, has_final_computation=has_final_computation
|
|
119
|
+
)
|
|
120
|
+
else:
|
|
121
|
+
self.reporter = LoggingProgressReporter()
|
|
86
122
|
|
|
87
123
|
# Needed for Pennylane's transforms
|
|
88
124
|
self._grouping_strategy = kwargs.pop("grouping_strategy", None)
|
|
@@ -107,6 +143,10 @@ class QuantumProgram(ABC):
|
|
|
107
143
|
def meta_circuits(self):
|
|
108
144
|
return self._meta_circuits
|
|
109
145
|
|
|
146
|
+
@property
|
|
147
|
+
def n_params(self):
|
|
148
|
+
return self._n_params
|
|
149
|
+
|
|
110
150
|
@abstractmethod
|
|
111
151
|
def _create_meta_circuits_dict(self) -> dict[str, MetaCircuit]:
|
|
112
152
|
pass
|
|
@@ -134,27 +174,6 @@ class QuantumProgram(ABC):
|
|
|
134
174
|
|
|
135
175
|
return losses
|
|
136
176
|
|
|
137
|
-
def _update_mc_params(self):
|
|
138
|
-
"""
|
|
139
|
-
Updates the parameters based on previous MC iteration.
|
|
140
|
-
"""
|
|
141
|
-
|
|
142
|
-
if self.current_iteration == 0:
|
|
143
|
-
self._initialize_params()
|
|
144
|
-
|
|
145
|
-
self.current_iteration += 1
|
|
146
|
-
|
|
147
|
-
return
|
|
148
|
-
|
|
149
|
-
self._curr_params = self.optimizer.compute_new_parameters(
|
|
150
|
-
self._curr_params,
|
|
151
|
-
self.current_iteration,
|
|
152
|
-
losses=self.losses[-1],
|
|
153
|
-
rng=self._rng,
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
self.current_iteration += 1
|
|
157
|
-
|
|
158
177
|
def _prepare_and_send_circuits(self):
|
|
159
178
|
job_circuits = {}
|
|
160
179
|
|
|
@@ -187,26 +206,25 @@ class QuantumProgram(ABC):
|
|
|
187
206
|
if isinstance(response, dict):
|
|
188
207
|
self._total_run_time += float(response["run_time"])
|
|
189
208
|
elif isinstance(response, list):
|
|
190
|
-
self._total_run_time += sum(
|
|
209
|
+
self._total_run_time += sum(
|
|
210
|
+
float(r.json()["run_time"]) for r in response
|
|
211
|
+
)
|
|
191
212
|
|
|
192
213
|
if isinstance(self.backend, QoroService):
|
|
214
|
+
update_function = lambda n_polls, status: self.reporter.info(
|
|
215
|
+
message="",
|
|
216
|
+
poll_attempt=n_polls,
|
|
217
|
+
max_retries=self.backend.max_retries,
|
|
218
|
+
service_job_id=self._curr_service_job_id,
|
|
219
|
+
job_status=status,
|
|
220
|
+
)
|
|
221
|
+
|
|
193
222
|
status = self.backend.poll_job_status(
|
|
194
223
|
self._curr_service_job_id,
|
|
195
224
|
loop_until_complete=True,
|
|
196
225
|
on_complete=add_run_time,
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
"pbar_update_fn": lambda n_polls: self._progress_queue.put(
|
|
200
|
-
{
|
|
201
|
-
"job_id": self.job_id,
|
|
202
|
-
"progress": 0,
|
|
203
|
-
"poll_attempt": n_polls,
|
|
204
|
-
}
|
|
205
|
-
)
|
|
206
|
-
}
|
|
207
|
-
if self._progress_queue is not None
|
|
208
|
-
else {}
|
|
209
|
-
),
|
|
226
|
+
verbose=False, # Disable the default logger in QoroService
|
|
227
|
+
poll_callback=update_function, # Use the new, more generic name
|
|
210
228
|
)
|
|
211
229
|
|
|
212
230
|
if status != JobStatus.COMPLETED:
|
|
@@ -247,27 +265,38 @@ class QuantumProgram(ABC):
|
|
|
247
265
|
losses = {}
|
|
248
266
|
measurement_groups = self._meta_circuits["cost_circuit"].measurement_groups
|
|
249
267
|
|
|
250
|
-
for
|
|
251
|
-
|
|
252
|
-
|
|
268
|
+
# Define key functions for both levels of grouping
|
|
269
|
+
get_param_id = lambda item: int(item[0].split("_")[0])
|
|
270
|
+
get_qem_id = lambda item: int(item[0].split("_")[1].split(":")[1])
|
|
271
|
+
|
|
272
|
+
# Group the pre-sorted results by parameter ID.
|
|
273
|
+
for p, param_group_iterator in groupby(results.items(), key=get_param_id):
|
|
274
|
+
param_group_iterator = list(param_group_iterator)
|
|
275
|
+
|
|
276
|
+
shots_by_qem_idx = zip(
|
|
277
|
+
*{
|
|
278
|
+
gid: [value for _, value in group]
|
|
279
|
+
for gid, group in groupby(param_group_iterator, key=get_qem_id)
|
|
280
|
+
}.values()
|
|
281
|
+
)
|
|
253
282
|
|
|
254
|
-
# Compute the marginal results for each observable
|
|
255
283
|
marginal_results = []
|
|
256
|
-
for
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
284
|
+
for shots_dicts, curr_measurement_group in zip(
|
|
285
|
+
shots_by_qem_idx, measurement_groups
|
|
286
|
+
):
|
|
287
|
+
if hasattr(self, "cost_hamiltonian"):
|
|
288
|
+
wire_order = tuple(reversed(self.cost_hamiltonian.wires))
|
|
289
|
+
else:
|
|
290
|
+
wire_order = tuple(
|
|
291
|
+
reversed(range(len(next(iter(shots_dicts[0].keys())))))
|
|
292
|
+
)
|
|
262
293
|
|
|
263
294
|
curr_marginal_results = []
|
|
264
295
|
for observable in curr_measurement_group:
|
|
296
|
+
|
|
265
297
|
intermediate_exp_values = [
|
|
266
|
-
ExpectationMP(observable).process_counts(
|
|
267
|
-
|
|
268
|
-
tuple(reversed(range(len(next(iter(shots_dict.keys())))))),
|
|
269
|
-
)
|
|
270
|
-
for shots_dict in group_results.values()
|
|
298
|
+
ExpectationMP(observable).process_counts(shots_dict, wire_order)
|
|
299
|
+
for shots_dict in shots_dicts
|
|
271
300
|
]
|
|
272
301
|
|
|
273
302
|
mitigated_exp_value = self._qem_protocol.postprocess_results(
|
|
@@ -301,170 +330,83 @@ class QuantumProgram(ABC):
|
|
|
301
330
|
data_file (str): The file to store the data in
|
|
302
331
|
"""
|
|
303
332
|
|
|
304
|
-
|
|
305
|
-
self.
|
|
306
|
-
|
|
307
|
-
"job_id": self.job_id,
|
|
308
|
-
"message": "Finished Setup",
|
|
309
|
-
"progress": 0,
|
|
310
|
-
}
|
|
333
|
+
def cost_fn(params):
|
|
334
|
+
self.reporter.info(
|
|
335
|
+
message="💸 Computing Cost 💸", iteration=self.current_iteration
|
|
311
336
|
)
|
|
312
|
-
else:
|
|
313
|
-
logger.info("Finished Setup")
|
|
314
337
|
|
|
315
|
-
|
|
316
|
-
while self.current_iteration < self.max_iterations:
|
|
338
|
+
self._curr_params = np.atleast_2d(params)
|
|
317
339
|
|
|
318
|
-
|
|
340
|
+
losses = self._run_optimization_circuits(store_data, data_file)
|
|
319
341
|
|
|
320
|
-
|
|
321
|
-
self._progress_queue.put(
|
|
322
|
-
{
|
|
323
|
-
"job_id": self.job_id,
|
|
324
|
-
"message": f"⛰️ Sampling from Loss Lansdscape ⛰️",
|
|
325
|
-
"progress": 0,
|
|
326
|
-
}
|
|
327
|
-
)
|
|
328
|
-
else:
|
|
329
|
-
logger.info(
|
|
330
|
-
f"Running Iteration #{self.current_iteration} circuits\r"
|
|
331
|
-
)
|
|
342
|
+
losses = np.fromiter(losses.values(), dtype=np.float64)
|
|
332
343
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
{
|
|
338
|
-
"job_id": self.job_id,
|
|
339
|
-
"progress": 1,
|
|
340
|
-
}
|
|
341
|
-
)
|
|
342
|
-
else:
|
|
343
|
-
logger.info(f"Finished Iteration #{self.current_iteration}\r\n")
|
|
344
|
-
|
|
345
|
-
self.losses.append(curr_losses)
|
|
346
|
-
|
|
347
|
-
self.final_params[:] = np.atleast_2d(self._curr_params)
|
|
344
|
+
if params.ndim > 1:
|
|
345
|
+
return losses
|
|
346
|
+
else:
|
|
347
|
+
return losses.item()
|
|
348
348
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
Optimizer.COBYLA,
|
|
353
|
-
):
|
|
349
|
+
self._grad_shift_mask = _compute_parameter_shift_mask(
|
|
350
|
+
self.n_layers * self.n_params
|
|
351
|
+
)
|
|
354
352
|
|
|
355
|
-
|
|
356
|
-
|
|
353
|
+
def grad_fn(params):
|
|
354
|
+
self._grad_mode = True
|
|
357
355
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
"job_id": self.job_id,
|
|
362
|
-
"message": task_name,
|
|
363
|
-
"progress": 0,
|
|
364
|
-
}
|
|
365
|
-
)
|
|
366
|
-
else:
|
|
367
|
-
logger.info(
|
|
368
|
-
f"Running Iteration #{self.current_iteration + 1} circuits: {task_name}\r"
|
|
369
|
-
)
|
|
356
|
+
self.reporter.info(
|
|
357
|
+
message="📈 Computing Gradients 📈", iteration=self.current_iteration
|
|
358
|
+
)
|
|
370
359
|
|
|
371
|
-
|
|
360
|
+
self._curr_params = self._grad_shift_mask + params
|
|
372
361
|
|
|
373
|
-
|
|
362
|
+
exp_vals = self._run_optimization_circuits(store_data, data_file)
|
|
363
|
+
exp_vals_arr = np.fromiter(exp_vals.values(), dtype=np.float64)
|
|
374
364
|
|
|
375
|
-
|
|
365
|
+
pos_shifts = exp_vals_arr[::2]
|
|
366
|
+
neg_shifts = exp_vals_arr[1::2]
|
|
367
|
+
grads = 0.5 * (pos_shifts - neg_shifts)
|
|
376
368
|
|
|
377
|
-
|
|
378
|
-
self._grad_mode = True
|
|
369
|
+
self._grad_mode = False
|
|
379
370
|
|
|
380
|
-
|
|
371
|
+
return grads
|
|
381
372
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
)
|
|
390
|
-
else:
|
|
391
|
-
logger.info(
|
|
392
|
-
f"Running Iteration #{self.current_iteration + 1} circuits: {task_name}\r"
|
|
373
|
+
def _iteration_counter(intermediate_result: OptimizeResult):
|
|
374
|
+
self.losses.append(
|
|
375
|
+
dict(
|
|
376
|
+
zip(
|
|
377
|
+
range(len(intermediate_result.x)),
|
|
378
|
+
np.atleast_1d(intermediate_result.fun),
|
|
393
379
|
)
|
|
380
|
+
)
|
|
381
|
+
)
|
|
394
382
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
self._curr_params = shift_mask + params
|
|
398
|
-
|
|
399
|
-
exp_vals = self._run_optimization_circuits(store_data, data_file)
|
|
400
|
-
|
|
401
|
-
grads = np.zeros_like(params)
|
|
402
|
-
for i in range(len(params)):
|
|
403
|
-
grads[i] = 0.5 * (exp_vals[2 * i] - exp_vals[2 * i + 1])
|
|
404
|
-
|
|
405
|
-
self._grad_mode = False
|
|
406
|
-
|
|
407
|
-
return grads
|
|
383
|
+
self.final_params[:] = np.atleast_2d(intermediate_result.x)
|
|
408
384
|
|
|
409
|
-
|
|
410
|
-
self.losses.append({0: intermediate_result.fun})
|
|
385
|
+
self.current_iteration += 1
|
|
411
386
|
|
|
412
|
-
|
|
387
|
+
self.reporter.update(iteration=self.current_iteration)
|
|
413
388
|
|
|
414
|
-
|
|
389
|
+
if (
|
|
390
|
+
isinstance(self.optimizer, ScipyOptimizer)
|
|
391
|
+
and self.optimizer.method == ScipyMethod.COBYLA
|
|
392
|
+
and intermediate_result.nit + 1 == self.max_iterations
|
|
393
|
+
):
|
|
394
|
+
raise StopIteration
|
|
415
395
|
|
|
416
|
-
|
|
417
|
-
self._progress_queue.put(
|
|
418
|
-
{
|
|
419
|
-
"job_id": self.job_id,
|
|
420
|
-
"progress": 1,
|
|
421
|
-
}
|
|
422
|
-
)
|
|
423
|
-
else:
|
|
424
|
-
logger.info(f"Finished Iteration #{self.current_iteration}\r\n")
|
|
425
|
-
|
|
426
|
-
if (
|
|
427
|
-
self.optimizer == Optimizer.COBYLA
|
|
428
|
-
and intermediate_result.nit + 1 == self.max_iterations
|
|
429
|
-
):
|
|
430
|
-
raise StopIteration
|
|
431
|
-
|
|
432
|
-
if self.max_iterations is None or self.optimizer == Optimizer.COBYLA:
|
|
433
|
-
# COBYLA perceive maxiter as maxfev so we need
|
|
434
|
-
# to use the callback fn for counting instead.
|
|
435
|
-
maxiter = None
|
|
436
|
-
else:
|
|
437
|
-
# Need to add one more iteration for Nelder-Mead's simplex initialization step
|
|
438
|
-
maxiter = (
|
|
439
|
-
self.max_iterations + 1
|
|
440
|
-
if self.optimizer == Optimizer.NELDER_MEAD
|
|
441
|
-
else self.max_iterations
|
|
442
|
-
)
|
|
396
|
+
self.reporter.info(message="Finished Setup")
|
|
443
397
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
callback=_iteration_counter,
|
|
455
|
-
options={"maxiter": maxiter},
|
|
456
|
-
)
|
|
398
|
+
self._initialize_params()
|
|
399
|
+
self._minimize_res = self.optimizer.optimize(
|
|
400
|
+
cost_fn=cost_fn,
|
|
401
|
+
initial_params=self._curr_params,
|
|
402
|
+
callback_fn=_iteration_counter,
|
|
403
|
+
jac=grad_fn,
|
|
404
|
+
maxiter=self.max_iterations,
|
|
405
|
+
rng=self._rng,
|
|
406
|
+
)
|
|
407
|
+
self.final_params[:] = np.atleast_2d(self._minimize_res.x)
|
|
457
408
|
|
|
458
|
-
|
|
459
|
-
self._progress_queue.put(
|
|
460
|
-
{
|
|
461
|
-
"job_id": self.job_id,
|
|
462
|
-
"progress": 0,
|
|
463
|
-
"final_status": "Success",
|
|
464
|
-
}
|
|
465
|
-
)
|
|
466
|
-
else:
|
|
467
|
-
logger.info(f"Finished Optimization!")
|
|
409
|
+
self.reporter.info(message="Finished Optimization!")
|
|
468
410
|
|
|
469
411
|
return self._total_circuit_count, self._total_run_time
|
|
470
412
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
from ._graph_partitioning import (
|
|
6
|
+
GraphPartitioningQAOA,
|
|
7
|
+
PartitioningConfig,
|
|
8
|
+
)
|
|
9
|
+
from ._qubo_partitioning import QUBOPartitioningQAOA
|
|
10
|
+
from ._vqe_sweep import MoleculeTransformer, VQEHyperparameterSweep
|
|
@@ -20,17 +20,15 @@ import scipy.sparse.linalg as spla
|
|
|
20
20
|
from pymetis import part_graph
|
|
21
21
|
from sklearn.cluster import SpectralClustering
|
|
22
22
|
|
|
23
|
-
from divi.
|
|
24
|
-
from divi.qprog import QAOA, ProgramBatch
|
|
25
|
-
from divi.qprog._qaoa import (
|
|
23
|
+
from divi.backends import CircuitRunner
|
|
24
|
+
from divi.qprog import QAOA, ProgramBatch, QuantumProgram
|
|
25
|
+
from divi.qprog.algorithms._qaoa import (
|
|
26
26
|
_SUPPORTED_INITIAL_STATES_LITERAL,
|
|
27
27
|
GraphProblem,
|
|
28
28
|
GraphProblemTypes,
|
|
29
29
|
draw_graph_solution_nodes,
|
|
30
30
|
)
|
|
31
|
-
from divi.qprog.
|
|
32
|
-
|
|
33
|
-
from .optimizers import Optimizer
|
|
31
|
+
from divi.qprog.optimizers import MonteCarloOptimizer, Optimizer
|
|
34
32
|
|
|
35
33
|
AggregateFn = Callable[
|
|
36
34
|
[list[int], str, nx.Graph | rx.PyGraph, dict[int, int]], list[int]
|
|
@@ -390,7 +388,6 @@ def dominance_aggregation(
|
|
|
390
388
|
|
|
391
389
|
|
|
392
390
|
def _run_and_compute_solution(program: QuantumProgram):
|
|
393
|
-
|
|
394
391
|
program.run()
|
|
395
392
|
|
|
396
393
|
final_sol_circuit_count, final_sol_run_time = program.compute_final_solution()
|
|
@@ -408,7 +405,7 @@ class GraphPartitioningQAOA(ProgramBatch):
|
|
|
408
405
|
partitioning_config: PartitioningConfig,
|
|
409
406
|
initial_state: _SUPPORTED_INITIAL_STATES_LITERAL = "Recommended",
|
|
410
407
|
aggregate_fn: AggregateFn = linear_aggregation,
|
|
411
|
-
optimizer=
|
|
408
|
+
optimizer: Optimizer | None = None,
|
|
412
409
|
max_iterations=10,
|
|
413
410
|
**kwargs,
|
|
414
411
|
):
|
|
@@ -452,7 +449,7 @@ class GraphPartitioningQAOA(ProgramBatch):
|
|
|
452
449
|
QAOA,
|
|
453
450
|
initial_state=initial_state,
|
|
454
451
|
graph_problem=graph_problem,
|
|
455
|
-
optimizer=optimizer,
|
|
452
|
+
optimizer=optimizer if optimizer is not None else MonteCarloOptimizer(),
|
|
456
453
|
max_iterations=self.max_iterations,
|
|
457
454
|
backend=self.backend,
|
|
458
455
|
n_layers=n_layers,
|
|
@@ -12,10 +12,10 @@ import numpy as np
|
|
|
12
12
|
import scipy.sparse as sps
|
|
13
13
|
from dimod import BinaryQuadraticModel
|
|
14
14
|
|
|
15
|
-
from divi.
|
|
16
|
-
from divi.qprog.
|
|
15
|
+
from divi.backends import CircuitRunner
|
|
16
|
+
from divi.qprog.algorithms import QAOA, QUBOProblemTypes
|
|
17
17
|
from divi.qprog.batch import ProgramBatch
|
|
18
|
-
from divi.qprog.optimizers import Optimizer
|
|
18
|
+
from divi.qprog.optimizers import MonteCarloOptimizer, Optimizer
|
|
19
19
|
from divi.qprog.quantum_program import QuantumProgram
|
|
20
20
|
|
|
21
21
|
|
|
@@ -50,7 +50,6 @@ def _sanitize_problem_input(qubo: T) -> tuple[T, BinaryQuadraticModel]:
|
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
def _run_and_compute_solution(program: QuantumProgram):
|
|
53
|
-
|
|
54
53
|
program.run()
|
|
55
54
|
|
|
56
55
|
final_sol_circuit_count, final_sol_run_time = program.compute_final_solution()
|
|
@@ -66,8 +65,8 @@ class QUBOPartitioningQAOA(ProgramBatch):
|
|
|
66
65
|
n_layers: int,
|
|
67
66
|
backend: CircuitRunner,
|
|
68
67
|
composer: hybrid.traits.SubsamplesComposer = hybrid.SplatComposer(),
|
|
69
|
-
optimizer=
|
|
70
|
-
max_iterations=10,
|
|
68
|
+
optimizer: Optimizer | None = None,
|
|
69
|
+
max_iterations: int = 10,
|
|
71
70
|
**kwargs,
|
|
72
71
|
):
|
|
73
72
|
"""
|
|
@@ -101,7 +100,7 @@ class QUBOPartitioningQAOA(ProgramBatch):
|
|
|
101
100
|
|
|
102
101
|
self._constructor = partial(
|
|
103
102
|
QAOA,
|
|
104
|
-
optimizer=optimizer,
|
|
103
|
+
optimizer=optimizer if optimizer is not None else MonteCarloOptimizer(),
|
|
105
104
|
max_iterations=self.max_iterations,
|
|
106
105
|
backend=self.backend,
|
|
107
106
|
n_layers=n_layers,
|
|
@@ -14,9 +14,8 @@ import matplotlib.pyplot as plt
|
|
|
14
14
|
import numpy as np
|
|
15
15
|
import pennylane as qml
|
|
16
16
|
|
|
17
|
-
from divi.qprog import VQE,
|
|
18
|
-
|
|
19
|
-
from .optimizers import Optimizer
|
|
17
|
+
from divi.qprog import VQE, Ansatz, ProgramBatch
|
|
18
|
+
from divi.qprog.optimizers import MonteCarloOptimizer, Optimizer
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
def _ctor_attrs(obj):
|
|
@@ -393,9 +392,9 @@ class VQEHyperparameterSweep(ProgramBatch):
|
|
|
393
392
|
|
|
394
393
|
def __init__(
|
|
395
394
|
self,
|
|
396
|
-
ansatze: Sequence[
|
|
395
|
+
ansatze: Sequence[Ansatz],
|
|
397
396
|
molecule_transformer: MoleculeTransformer,
|
|
398
|
-
optimizer: Optimizer =
|
|
397
|
+
optimizer: Optimizer | None = None,
|
|
399
398
|
max_iterations: int = 10,
|
|
400
399
|
**kwargs,
|
|
401
400
|
):
|
|
@@ -424,7 +423,7 @@ class VQEHyperparameterSweep(ProgramBatch):
|
|
|
424
423
|
|
|
425
424
|
self._constructor = partial(
|
|
426
425
|
VQE,
|
|
427
|
-
optimizer=optimizer,
|
|
426
|
+
optimizer=optimizer if optimizer is not None else MonteCarloOptimizer(),
|
|
428
427
|
max_iterations=self.max_iterations,
|
|
429
428
|
backend=self.backend,
|
|
430
429
|
**kwargs,
|
|
@@ -470,37 +469,49 @@ class VQEHyperparameterSweep(ProgramBatch):
|
|
|
470
469
|
if self._executor is not None:
|
|
471
470
|
self.join()
|
|
472
471
|
|
|
473
|
-
|
|
474
|
-
|
|
472
|
+
# Get the unique ansatz objects that were actually run
|
|
473
|
+
# Assumes `self.ansatze` is a list of the ansatz instances used.
|
|
474
|
+
unique_ansatze = self.ansatze
|
|
475
475
|
|
|
476
|
-
|
|
476
|
+
# Create a stable color mapping for each unique ansatz object
|
|
477
|
+
colors = ["blue", "g", "r", "c", "m", "y", "k"]
|
|
478
|
+
color_map = {
|
|
479
|
+
ansatz: colors[i % len(colors)] for i, ansatz in enumerate(unique_ansatze)
|
|
480
|
+
}
|
|
477
481
|
|
|
478
482
|
if graph_type == "scatter":
|
|
479
|
-
|
|
483
|
+
# Plot each ansatz's results as a separate series for clarity
|
|
484
|
+
for ansatz in unique_ansatze:
|
|
485
|
+
modifiers = []
|
|
480
486
|
min_energies = []
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
min(curr_energies.values())
|
|
487
|
-
|
|
488
|
-
|
|
487
|
+
for modifier in self.molecule_transformer.bond_modifiers:
|
|
488
|
+
program_key = (ansatz, modifier)
|
|
489
|
+
if program_key in self.programs:
|
|
490
|
+
modifiers.append(modifier)
|
|
491
|
+
curr_energies = self.programs[program_key].losses[-1]
|
|
492
|
+
min_energies.append(min(curr_energies.values()))
|
|
493
|
+
|
|
494
|
+
# Use the new .name property for the label and the color_map
|
|
495
|
+
plt.scatter(
|
|
496
|
+
modifiers,
|
|
497
|
+
min_energies,
|
|
498
|
+
color=color_map[ansatz],
|
|
499
|
+
label=ansatz.name,
|
|
489
500
|
)
|
|
490
|
-
data.extend(min_energies)
|
|
491
|
-
|
|
492
|
-
x, y, z = zip(*data)
|
|
493
|
-
plt.scatter(x, y, color=z, label=ansatz)
|
|
494
501
|
|
|
495
502
|
elif graph_type == "line":
|
|
496
|
-
for ansatz in
|
|
503
|
+
for ansatz in unique_ansatze:
|
|
497
504
|
energies = []
|
|
498
505
|
for modifier in self.molecule_transformer.bond_modifiers:
|
|
499
506
|
energies.append(
|
|
500
507
|
min(self.programs[(ansatz, modifier)].losses[-1].values())
|
|
501
508
|
)
|
|
509
|
+
|
|
502
510
|
plt.plot(
|
|
503
|
-
self.molecule_transformer.bond_modifiers,
|
|
511
|
+
self.molecule_transformer.bond_modifiers,
|
|
512
|
+
energies,
|
|
513
|
+
label=ansatz.name,
|
|
514
|
+
color=color_map[ansatz],
|
|
504
515
|
)
|
|
505
516
|
|
|
506
517
|
plt.xlabel(
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
from ._pbar import make_progress_bar
|
|
6
|
+
from ._qlogger import disable_logging, enable_logging
|
|
7
|
+
from ._reporter import LoggingProgressReporter, ProgressReporter, QueueProgressReporter
|