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.

Files changed (69) hide show
  1. divi/__init__.py +1 -2
  2. divi/backends/__init__.py +7 -0
  3. divi/{parallel_simulator.py → backends/_parallel_simulator.py} +4 -3
  4. divi/{qoro_service.py → backends/_qoro_service.py} +27 -15
  5. divi/circuits/__init__.py +5 -0
  6. divi/{circuits.py → circuits/_core.py} +6 -20
  7. divi/{qasm.py → circuits/qasm.py} +2 -2
  8. divi/{exp → extern}/cirq/__init__.py +1 -1
  9. divi/{exp → extern}/cirq/_validator.py +10 -8
  10. divi/qprog/__init__.py +19 -6
  11. divi/qprog/algorithms/__init__.py +14 -0
  12. divi/qprog/algorithms/_ansatze.py +215 -0
  13. divi/qprog/{_qaoa.py → algorithms/_qaoa.py} +16 -26
  14. divi/qprog/{_vqe.py → algorithms/_vqe.py} +35 -133
  15. divi/qprog/batch.py +25 -19
  16. divi/qprog/optimizers.py +170 -45
  17. divi/qprog/quantum_program.py +142 -200
  18. divi/qprog/workflows/__init__.py +10 -0
  19. divi/qprog/{_graph_partitioning.py → workflows/_graph_partitioning.py} +6 -9
  20. divi/qprog/{_qubo_partitioning.py → workflows/_qubo_partitioning.py} +6 -7
  21. divi/qprog/{_vqe_sweep.py → workflows/_vqe_sweep.py} +35 -24
  22. divi/reporting/__init__.py +7 -0
  23. divi/{_pbar.py → reporting/_pbar.py} +13 -14
  24. divi/{qlogger.py → reporting/_qlogger.py} +8 -6
  25. divi/{reporter.py → reporting/_reporter.py} +24 -7
  26. divi/utils.py +14 -6
  27. {qoro_divi-0.3.2b0.dist-info → qoro_divi-0.3.4.dist-info}/METADATA +2 -2
  28. qoro_divi-0.3.4.dist-info/RECORD +68 -0
  29. qoro_divi-0.3.2b0.dist-info/RECORD +0 -62
  30. /divi/{interfaces.py → backends/_circuit_runner.py} +0 -0
  31. /divi/{qpu_system.py → backends/_qpu_system.py} +0 -0
  32. /divi/{qem.py → circuits/qem.py} +0 -0
  33. /divi/{exp → extern}/cirq/_lexer.py +0 -0
  34. /divi/{exp → extern}/cirq/_parser.py +0 -0
  35. /divi/{exp → extern}/cirq/_qasm_export.py +0 -0
  36. /divi/{exp → extern}/cirq/_qasm_import.py +0 -0
  37. /divi/{exp → extern}/cirq/exception.py +0 -0
  38. /divi/{exp → extern}/scipy/_cobyla.py +0 -0
  39. /divi/{exp → extern}/scipy/pyprima/LICENCE.txt +0 -0
  40. /divi/{exp → extern}/scipy/pyprima/__init__.py +0 -0
  41. /divi/{exp → extern}/scipy/pyprima/cobyla/__init__.py +0 -0
  42. /divi/{exp → extern}/scipy/pyprima/cobyla/cobyla.py +0 -0
  43. /divi/{exp → extern}/scipy/pyprima/cobyla/cobylb.py +0 -0
  44. /divi/{exp → extern}/scipy/pyprima/cobyla/geometry.py +0 -0
  45. /divi/{exp → extern}/scipy/pyprima/cobyla/initialize.py +0 -0
  46. /divi/{exp → extern}/scipy/pyprima/cobyla/trustregion.py +0 -0
  47. /divi/{exp → extern}/scipy/pyprima/cobyla/update.py +0 -0
  48. /divi/{exp → extern}/scipy/pyprima/common/__init__.py +0 -0
  49. /divi/{exp → extern}/scipy/pyprima/common/_bounds.py +0 -0
  50. /divi/{exp → extern}/scipy/pyprima/common/_linear_constraints.py +0 -0
  51. /divi/{exp → extern}/scipy/pyprima/common/_nonlinear_constraints.py +0 -0
  52. /divi/{exp → extern}/scipy/pyprima/common/_project.py +0 -0
  53. /divi/{exp → extern}/scipy/pyprima/common/checkbreak.py +0 -0
  54. /divi/{exp → extern}/scipy/pyprima/common/consts.py +0 -0
  55. /divi/{exp → extern}/scipy/pyprima/common/evaluate.py +0 -0
  56. /divi/{exp → extern}/scipy/pyprima/common/history.py +0 -0
  57. /divi/{exp → extern}/scipy/pyprima/common/infos.py +0 -0
  58. /divi/{exp → extern}/scipy/pyprima/common/linalg.py +0 -0
  59. /divi/{exp → extern}/scipy/pyprima/common/message.py +0 -0
  60. /divi/{exp → extern}/scipy/pyprima/common/powalg.py +0 -0
  61. /divi/{exp → extern}/scipy/pyprima/common/preproc.py +0 -0
  62. /divi/{exp → extern}/scipy/pyprima/common/present.py +0 -0
  63. /divi/{exp → extern}/scipy/pyprima/common/ratio.py +0 -0
  64. /divi/{exp → extern}/scipy/pyprima/common/redrho.py +0 -0
  65. /divi/{exp → extern}/scipy/pyprima/common/selectx.py +0 -0
  66. {qoro_divi-0.3.2b0.dist-info → qoro_divi-0.3.4.dist-info}/LICENSE +0 -0
  67. {qoro_divi-0.3.2b0.dist-info → qoro_divi-0.3.4.dist-info}/LICENSES/.license-header +0 -0
  68. {qoro_divi-0.3.2b0.dist-info → qoro_divi-0.3.4.dist-info}/LICENSES/Apache-2.0.txt +0 -0
  69. {qoro_divi-0.3.2b0.dist-info → qoro_divi-0.3.4.dist-info}/WHEEL +0 -0
@@ -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, minimize
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.exp.scipy._cobyla import _minimize_cobyla as cobyla_fn
18
- from divi.interfaces import CircuitRunner
19
- from divi.qem import _NoMitigation
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(float(r["run_time"]) for r in response)
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 p in range(self._curr_params.shape[0]):
251
- # Extract relevant entries from the execution results dict
252
- param_results = {k: v for k, v in results.items() if k.startswith(f"{p}_")}
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 group_idx, curr_measurement_group in enumerate(measurement_groups):
257
- group_results = {
258
- k: v
259
- for k, v in param_results.items()
260
- if k.endswith(f"_{group_idx}")
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
- shots_dict,
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
- if self._progress_queue is not None:
305
- self._progress_queue.put(
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
- if self.optimizer == Optimizer.MONTE_CARLO:
316
- while self.current_iteration < self.max_iterations:
338
+ self._curr_params = np.atleast_2d(params)
317
339
 
318
- self._update_mc_params()
340
+ losses = self._run_optimization_circuits(store_data, data_file)
319
341
 
320
- if self._progress_queue is not None:
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
- curr_losses = self._run_optimization_circuits(store_data, data_file)
334
-
335
- if self._progress_queue is not None:
336
- self._progress_queue.put(
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
- elif self.optimizer in (
350
- Optimizer.NELDER_MEAD,
351
- Optimizer.L_BFGS_B,
352
- Optimizer.COBYLA,
353
- ):
349
+ self._grad_shift_mask = _compute_parameter_shift_mask(
350
+ self.n_layers * self.n_params
351
+ )
354
352
 
355
- def cost_fn(params):
356
- task_name = "💸 Computing Cost 💸"
353
+ def grad_fn(params):
354
+ self._grad_mode = True
357
355
 
358
- if self._progress_queue is not None:
359
- self._progress_queue.put(
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
- self._curr_params = np.atleast_2d(params)
360
+ self._curr_params = self._grad_shift_mask + params
372
361
 
373
- losses = self._run_optimization_circuits(store_data, data_file)
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
- return losses[0]
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
- def grad_fn(params):
378
- self._grad_mode = True
369
+ self._grad_mode = False
379
370
 
380
- task_name = "📈 Computing Gradients 📈"
371
+ return grads
381
372
 
382
- if self._progress_queue is not None:
383
- self._progress_queue.put(
384
- {
385
- "job_id": self.job_id,
386
- "message": task_name,
387
- "progress": 0,
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
- shift_mask = self.optimizer.compute_parameter_shift_mask(len(params))
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
- def _iteration_counter(intermediate_result: OptimizeResult):
410
- self.losses.append({0: intermediate_result.fun})
385
+ self.current_iteration += 1
411
386
 
412
- self.final_params[:] = np.atleast_2d(intermediate_result.x)
387
+ self.reporter.update(iteration=self.current_iteration)
413
388
 
414
- self.current_iteration += 1
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
- if self._progress_queue is not None:
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
- self._initialize_params()
445
- self._minimize_res = minimize(
446
- fun=cost_fn,
447
- x0=self._curr_params[0],
448
- method=(
449
- cobyla_fn
450
- if self.optimizer == Optimizer.COBYLA
451
- else self.optimizer.value
452
- ),
453
- jac=grad_fn if self.optimizer == Optimizer.L_BFGS_B else None,
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
- if self._progress_queue:
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.interfaces import CircuitRunner
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.quantum_program import QuantumProgram
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=Optimizer.MONTE_CARLO,
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.interfaces import CircuitRunner
16
- from divi.qprog._qaoa import QAOA, QUBOProblemTypes
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=Optimizer.MONTE_CARLO,
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, ProgramBatch, VQEAnsatz
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[VQEAnsatz],
395
+ ansatze: Sequence[Ansatz],
397
396
  molecule_transformer: MoleculeTransformer,
398
- optimizer: Optimizer = Optimizer.MONTE_CARLO,
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
- data = []
474
- colors = ["blue", "g", "r", "c", "m", "y", "k"]
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
- ansatz_list = list(VQEAnsatz)
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
- for ansatz, modifier in self.programs.keys():
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
- curr_energies = self.programs[(ansatz, modifier)].losses[-1]
483
- min_energies.append(
484
- (
485
- modifier,
486
- min(curr_energies.values()),
487
- colors[ansatz_list.index(ansatz)],
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 self.ansatze:
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, energies, label=ansatz
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