qoro-divi 0.3.4__py3-none-any.whl → 0.3.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.

Potentially problematic release.


This version of qoro-divi might be problematic. Click here for more details.

@@ -5,23 +5,179 @@
5
5
  import logging
6
6
  import pickle
7
7
  from abc import ABC, abstractmethod
8
- from functools import partial
8
+ from functools import lru_cache, partial
9
9
  from itertools import groupby
10
10
  from queue import Queue
11
+ from threading import Event
11
12
 
12
13
  import numpy as np
13
- from pennylane.measurements import ExpectationMP
14
+ import pennylane as qml
14
15
  from scipy.optimize import OptimizeResult
15
16
 
16
17
  from divi.backends import CircuitRunner, JobStatus, QoroService
17
18
  from divi.circuits import Circuit, MetaCircuit
18
19
  from divi.circuits.qem import _NoMitigation
20
+ from divi.qprog.exceptions import _CancelledError
19
21
  from divi.qprog.optimizers import ScipyMethod, ScipyOptimizer
20
22
  from divi.reporting import LoggingProgressReporter, QueueProgressReporter
21
23
 
22
24
  logger = logging.getLogger(__name__)
23
25
 
24
26
 
27
+ def _get_structural_key(obs: qml.operation.Operation) -> tuple[str, ...]:
28
+ """Generates a hashable, wire-independent key from an observable's structure.
29
+
30
+ This function is used to create a canonical representation of an observable
31
+ based on its constituent Pauli operators, ignoring the wires they act on.
32
+ This key is ideal for caching computed eigenvalues, as observables with the
33
+ same structure (e.g., PauliX(0) @ PauliZ(1) and PauliX(2) @ PauliZ(3))
34
+ share the same eigenvalues. It maps PauliX and PauliY to PauliZ because
35
+ they are all isospectral (have eigenvalues [1, -1]).
36
+
37
+ Args:
38
+ obs: A PennyLane observable (e.g., qml.PauliZ(0), qml.PauliX(0) @ qml.PauliY(1)).
39
+
40
+ Returns:
41
+ A tuple of strings representing the structure of the observable,
42
+ e.g., ('PauliZ',) or ('PauliZ', 'PauliZ').
43
+ """
44
+
45
+ # Pennylane returns the same eigenvalues for PauliX and PauliY
46
+ # since it handles diagonalizing gates internally anyway
47
+ name_map = {
48
+ "PauliY": "PauliZ",
49
+ "PauliX": "PauliZ",
50
+ "PauliZ": "PauliZ",
51
+ "Identity": "Identity",
52
+ }
53
+
54
+ if isinstance(obs, qml.ops.Prod):
55
+ # Recursively build a tuple of operator names
56
+ return tuple(name_map[o.name] for o in obs.operands)
57
+
58
+ # For single operators, return a single-element tuple
59
+ return (name_map[obs.name],)
60
+
61
+
62
+ @lru_cache(maxsize=512)
63
+ def _get_eigvals_from_key(key: tuple[str, ...]) -> np.ndarray:
64
+ """Computes and caches eigenvalues based on a structural key.
65
+
66
+ This function takes a key generated by `_get_structural_key` and computes
67
+ the eigenvalues of the corresponding tensor product of operators. The results
68
+ are memoized using @lru_cache to avoid redundant calculations.
69
+
70
+ Args:
71
+ key: A tuple of strings representing the observable's structure.
72
+
73
+ Returns:
74
+ A NumPy array containing the eigenvalues of the observable.
75
+ """
76
+
77
+ # Define a mapping from name to the base eigenvalue array
78
+ eigvals_map = {
79
+ "PauliZ": np.array([1, -1], dtype=np.int8),
80
+ "Identity": np.array([1, 1], dtype=np.int8),
81
+ }
82
+
83
+ # Start with the eigenvalues of the first operator in the key
84
+ final_eigvals = eigvals_map[key[0]]
85
+
86
+ # Iteratively compute the kronecker product for the rest
87
+ for op_name in key[1:]:
88
+ final_eigvals = np.kron(final_eigvals, eigvals_map[op_name])
89
+
90
+ return final_eigvals
91
+
92
+
93
+ def _batched_expectation(shots_dicts, observables, wire_order):
94
+ """Efficiently calculates expectation values for multiple observables across multiple shot histograms.
95
+
96
+ This function is optimized to compute expectation values in a fully vectorized
97
+ manner, minimizing Python loops. It operates in four main steps:
98
+ 1. Aggregates all unique bitstrings measured across all histograms.
99
+ 2. Builds a "reduced" eigenvalue matrix corresponding only to the unique states.
100
+ 3. Builds a "reduced" probability matrix from the shot counts for each histogram.
101
+ 4. Computes all expectation values with a single matrix multiplication.
102
+
103
+ Args:
104
+ shots_dicts (list[dict[str, int]]): A list of shot dictionaries (histograms),
105
+ where each dictionary maps a measured bitstring to its count.
106
+ observables (list[qml.operation.Operation]): A list of PennyLane observables
107
+ for which to calculate expectation values.
108
+ wire_order (tuple[int, ...]): A tuple defining the order of wires, which maps
109
+ the bitstring to the qubits. Note: This is typically the reverse of the
110
+ qubit indices (e.g., (2, 1, 0) for a 3-qubit system).
111
+
112
+ Returns:
113
+ np.ndarray: A 2D NumPy array of shape (n_observables, n_shots) where
114
+ result[i, j] is the expectation value of observables[i] for the
115
+ histogram in shots_dicts[j].
116
+ """
117
+
118
+ n_histograms = len(shots_dicts)
119
+ n_total_wires = len(wire_order)
120
+ n_observables = len(observables)
121
+
122
+ # --- 1. Aggregate all unique measured states across all shots ---
123
+ all_measured_bitstrings = set()
124
+ for sd in shots_dicts:
125
+ all_measured_bitstrings.update(sd.keys())
126
+
127
+ unique_bitstrings = sorted(list(all_measured_bitstrings))
128
+ n_unique_states = len(unique_bitstrings)
129
+
130
+ bitstring_to_idx_map = {bs: i for i, bs in enumerate(unique_bitstrings)}
131
+
132
+ # --- 2. Build REDUCED Eigenvalue Matrix: (n_observables, n_unique_states) ---
133
+ unique_states_int = np.array(
134
+ [int(bs, 2) for bs in unique_bitstrings], dtype=np.uint64
135
+ )
136
+ reduced_eigvals_matrix = np.zeros((n_observables, n_unique_states))
137
+ wire_map = {w: i for i, w in enumerate(wire_order)}
138
+
139
+ powers_cache = {}
140
+
141
+ for obs_idx, observable in enumerate(observables):
142
+ obs_wires = observable.wires
143
+ n_obs_wires = len(obs_wires)
144
+
145
+ if n_obs_wires in powers_cache:
146
+ powers = powers_cache[n_obs_wires]
147
+ else:
148
+ powers = 2 ** np.arange(n_obs_wires - 1, -1, -1, dtype=np.intp)
149
+ powers_cache[n_obs_wires] = powers
150
+
151
+ obs_wire_indices = np.array([wire_map[w] for w in obs_wires], dtype=np.uint32)
152
+ eigvals = _get_eigvals_from_key(_get_structural_key(observable))
153
+
154
+ # Vectorized mapping, but on the *reduced* set of states
155
+ shifts = n_total_wires - 1 - obs_wire_indices
156
+ bits = ((unique_states_int[:, np.newaxis] >> shifts) & 1).astype(np.intp)
157
+ # powers = 2 ** np.arange(n_obs_wires - 1, -1, -1)
158
+
159
+ # obs_state_indices = (bits * powers).sum(axis=1).astype(np.intp)
160
+ obs_state_indices = np.dot(bits, powers)
161
+
162
+ reduced_eigvals_matrix[obs_idx, :] = eigvals[obs_state_indices]
163
+
164
+ # --- 3. Build REDUCED Probability Matrix: (n_shots, n_unique_states) ---
165
+ reduced_prob_matrix = np.zeros((n_histograms, n_unique_states), dtype=np.float32)
166
+ for i, shots_dict in enumerate(shots_dicts):
167
+ total = sum(shots_dict.values())
168
+
169
+ for bitstring, count in shots_dict.items():
170
+ col_idx = bitstring_to_idx_map[bitstring]
171
+ reduced_prob_matrix[i, col_idx] = count / total
172
+
173
+ # --- 4. Compute Final Expectation Values ---
174
+ # (n_shots, n_unique_states) @ (n_unique_states, n_observables)
175
+ result = reduced_prob_matrix @ reduced_eigvals_matrix.T
176
+
177
+ # Transpose to (n_observables, n_shots) as expected by the calling code
178
+ return result.T
179
+
180
+
25
181
  def _compute_parameter_shift_mask(n_params):
26
182
  """
27
183
  Generate a binary matrix mask for the parameter shift rule.
@@ -56,7 +212,6 @@ class QuantumProgram(ABC):
56
212
  backend: CircuitRunner,
57
213
  seed: int | None = None,
58
214
  progress_queue: Queue | None = None,
59
- has_final_computation: bool = False,
60
215
  **kwargs,
61
216
  ):
62
217
  """
@@ -77,30 +232,22 @@ class QuantumProgram(ABC):
77
232
  be used for the parameter initialization.
78
233
  Defaults to None.
79
234
  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.
82
235
 
83
236
  **kwargs: Additional keyword arguments that influence behaviour.
84
237
  - grouping_strategy (Literal["default", "wires", "qwc"]): A strategy for grouping operations, used in Pennylane's transforms.
85
238
  Defaults to None.
86
239
  - qem_protocol (QEMProtocol, optional): the quantum error mitigation protocol to apply.
87
240
  Must be of type QEMProtocol. Defaults to None.
88
-
89
- The following key values are reserved for internal use and should not be set by the user:
90
- - losses (list, optional): A list to initialize the `losses` attribute. Defaults to an empty list.
91
- - final_params (list, optional): A list to initialize the `final_params` attribute. Defaults to an empty list.
92
-
93
241
  """
94
242
 
95
- # Shared Variables
96
- self.losses = kwargs.pop("losses", [])
97
- self.final_params = kwargs.pop("final_params", [])
243
+ self._losses = []
244
+ self._final_params = []
98
245
 
99
- self.circuits: list[Circuit] = []
246
+ self._circuits: list[Circuit] = []
100
247
 
101
248
  self._total_circuit_count = 0
102
249
  self._total_run_time = 0.0
103
- self._curr_params = []
250
+ self._curr_params = None
104
251
 
105
252
  self._seed = seed
106
253
  self._rng = np.random.default_rng(self._seed)
@@ -114,9 +261,7 @@ class QuantumProgram(ABC):
114
261
  self.job_id = kwargs.get("job_id", None)
115
262
  self._progress_queue = progress_queue
116
263
  if progress_queue and self.job_id:
117
- self.reporter = QueueProgressReporter(
118
- self.job_id, progress_queue, has_final_computation=has_final_computation
119
- )
264
+ self.reporter = QueueProgressReporter(self.job_id, progress_queue)
120
265
  else:
121
266
  self.reporter = LoggingProgressReporter()
122
267
 
@@ -125,6 +270,8 @@ class QuantumProgram(ABC):
125
270
 
126
271
  self._qem_protocol = kwargs.pop("qem_protocol", None) or _NoMitigation()
127
272
 
273
+ self._cancellation_event = None
274
+
128
275
  self._meta_circuit_factory = partial(
129
276
  MetaCircuit,
130
277
  grouping_strategy=self._grouping_strategy,
@@ -133,20 +280,113 @@ class QuantumProgram(ABC):
133
280
 
134
281
  @property
135
282
  def total_circuit_count(self):
283
+ """
284
+ Get the total number of circuits executed so far.
285
+
286
+ Returns:
287
+ int: Cumulative count of circuits submitted for execution.
288
+ """
136
289
  return self._total_circuit_count
137
290
 
138
291
  @property
139
292
  def total_run_time(self):
293
+ """
294
+ Get the total runtime across all circuit executions.
295
+
296
+ Returns:
297
+ float: Cumulative execution time in seconds.
298
+ """
140
299
  return self._total_run_time
141
300
 
142
301
  @property
143
302
  def meta_circuits(self):
303
+ """
304
+ Get the meta-circuit templates used by this program.
305
+
306
+ Returns:
307
+ dict[str, MetaCircuit]: Dictionary mapping circuit names to their
308
+ MetaCircuit templates.
309
+ """
144
310
  return self._meta_circuits
145
311
 
146
312
  @property
147
313
  def n_params(self):
314
+ """
315
+ Get the total number of parameters in the quantum circuit.
316
+
317
+ Returns:
318
+ int: Total number of trainable parameters (n_layers * n_params_per_layer).
319
+ """
148
320
  return self._n_params
149
321
 
322
+ @property
323
+ def circuits(self) -> list[Circuit]:
324
+ """
325
+ Get a copy of the generated circuits list.
326
+
327
+ Returns:
328
+ list[Circuit]: Copy of the circuits list. Modifications to this list
329
+ will not affect the internal state.
330
+ """
331
+ return self._circuits.copy()
332
+
333
+ @property
334
+ def losses(self) -> list[dict]:
335
+ """
336
+ Get a copy of the optimization loss history.
337
+
338
+ Each entry is a dictionary mapping parameter indices to loss values.
339
+
340
+ Returns:
341
+ list[dict]: Copy of the loss history. Modifications to this list
342
+ will not affect the internal state.
343
+ """
344
+ return self._losses.copy()
345
+
346
+ @property
347
+ def final_params(self) -> list:
348
+ """
349
+ Get a copy of the final optimized parameters.
350
+
351
+ Returns:
352
+ list: Copy of the final parameters. Modifications to this list
353
+ will not affect the internal state.
354
+ """
355
+ return self._final_params.copy()
356
+
357
+ @property
358
+ def initial_params(self) -> np.ndarray:
359
+ """
360
+ Get the current initial parameters.
361
+
362
+ Returns:
363
+ np.ndarray: Current initial parameters. If not yet initialized,
364
+ they will be generated automatically.
365
+ """
366
+ if self._curr_params is None:
367
+ self._initialize_params()
368
+ return self._curr_params.copy()
369
+
370
+ @initial_params.setter
371
+ def initial_params(self, value: np.ndarray | None):
372
+ """
373
+ Set initial parameters.
374
+
375
+ Args:
376
+ value (np.ndarray | None): Initial parameters with shape
377
+ (n_param_sets, n_layers * n_params), or None to reset
378
+ to uninitialized state.
379
+
380
+ Raises:
381
+ ValueError: If parameters have incorrect shape.
382
+ """
383
+ if value is not None:
384
+ self._validate_initial_params(value)
385
+ self._curr_params = value.copy()
386
+ else:
387
+ # Reset to uninitialized state
388
+ self._curr_params = None
389
+
150
390
  @abstractmethod
151
391
  def _create_meta_circuits_dict(self) -> dict[str, MetaCircuit]:
152
392
  pass
@@ -155,16 +395,60 @@ class QuantumProgram(ABC):
155
395
  def _generate_circuits(self, **kwargs):
156
396
  pass
157
397
 
398
+ def _set_cancellation_event(self, event: Event):
399
+ """
400
+ Set a cancellation event for graceful program termination.
401
+
402
+ This internal method is called by a batch runner to provide a mechanism
403
+ for stopping the optimization loop cleanly when requested.
404
+
405
+ Args:
406
+ event (Event): Threading Event object that signals cancellation when set.
407
+ """
408
+ self._cancellation_event = event
409
+
410
+ def get_expected_param_shape(self) -> tuple[int, int]:
411
+ """
412
+ Get the expected shape for initial parameters.
413
+
414
+ Returns:
415
+ tuple[int, int]: Shape (n_param_sets, n_layers * n_params) that
416
+ initial parameters should have for this quantum program.
417
+ """
418
+ return (self.optimizer.n_param_sets, self.n_layers * self.n_params)
419
+
420
+ def _validate_initial_params(self, params: np.ndarray):
421
+ """
422
+ Validate user-provided initial parameters.
423
+
424
+ Args:
425
+ params (np.ndarray): Parameters to validate.
426
+
427
+ Raises:
428
+ ValueError: If parameters have incorrect shape.
429
+ """
430
+ expected_shape = self.get_expected_param_shape()
431
+
432
+ if params.shape != expected_shape:
433
+ raise ValueError(
434
+ f"Initial parameters must have shape {expected_shape}, "
435
+ f"got {params.shape}"
436
+ )
437
+
158
438
  def _initialize_params(self):
159
- self._curr_params = np.array(
160
- [
161
- self._rng.uniform(0, 2 * np.pi, self.n_layers * self.n_params)
162
- for _ in range(self.optimizer.n_param_sets)
163
- ]
439
+ """
440
+ Initialize the circuit parameters randomly.
441
+
442
+ Generates random parameters with values uniformly distributed between
443
+ 0 and 2π. The number of parameter sets depends on the optimizer being used.
444
+ """
445
+ total_params = self.n_layers * self.n_params
446
+ self._curr_params = self._rng.uniform(
447
+ 0, 2 * np.pi, (self.optimizer.n_param_sets, total_params)
164
448
  )
165
449
 
166
450
  def _run_optimization_circuits(self, store_data, data_file):
167
- self.circuits[:] = []
451
+ self._circuits[:] = []
168
452
 
169
453
  self._generate_circuits()
170
454
 
@@ -177,7 +461,7 @@ class QuantumProgram(ABC):
177
461
  def _prepare_and_send_circuits(self):
178
462
  job_circuits = {}
179
463
 
180
- for circuit in self.circuits:
464
+ for circuit in self._circuits:
181
465
  for tag, qasm_circuit in zip(circuit.tags, circuit.qasm_circuits):
182
466
  job_circuits[tag] = qasm_circuit
183
467
 
@@ -261,6 +545,10 @@ class QuantumProgram(ABC):
261
545
  (dict) The energies for each parameter set grouping, where the dict keys
262
546
  correspond to the parameter indices.
263
547
  """
548
+ if not (self._cancellation_event and self._cancellation_event.is_set()):
549
+ self.reporter.info(
550
+ message="Post-processing output", iteration=self.current_iteration
551
+ )
264
552
 
265
553
  losses = {}
266
554
  measurement_groups = self._meta_circuits["cost_circuit"].measurement_groups
@@ -291,18 +579,17 @@ class QuantumProgram(ABC):
291
579
  reversed(range(len(next(iter(shots_dicts[0].keys())))))
292
580
  )
293
581
 
294
- curr_marginal_results = []
295
- for observable in curr_measurement_group:
296
-
297
- intermediate_exp_values = [
298
- ExpectationMP(observable).process_counts(shots_dict, wire_order)
299
- for shots_dict in shots_dicts
300
- ]
582
+ expectation_matrix = _batched_expectation(
583
+ shots_dicts, curr_measurement_group, wire_order
584
+ )
301
585
 
586
+ # expectation_matrix[i, j] = expectation value for observable i, histogram j
587
+ curr_marginal_results = []
588
+ for obs_idx in range(len(curr_measurement_group)):
589
+ intermediate_exp_values = expectation_matrix[obs_idx, :]
302
590
  mitigated_exp_value = self._qem_protocol.postprocess_results(
303
591
  intermediate_exp_values
304
592
  )
305
-
306
593
  curr_marginal_results.append(mitigated_exp_value)
307
594
 
308
595
  marginal_results.append(
@@ -321,6 +608,20 @@ class QuantumProgram(ABC):
321
608
 
322
609
  return losses
323
610
 
611
+ def _perform_final_computation(self):
612
+ """
613
+ Perform final computations after optimization completes.
614
+
615
+ This is an optional hook method that subclasses can override to perform
616
+ any post-optimization processing, such as extracting solutions, running
617
+ final measurements, or computing additional metrics.
618
+
619
+ Note:
620
+ The default implementation does nothing. Subclasses should override
621
+ this method if they need post-optimization processing.
622
+ """
623
+ pass
624
+
324
625
  def run(self, store_data=False, data_file=None):
325
626
  """
326
627
  Run the QAOA problem. The outputs are stored in the QAOA object. Optionally, the data can be stored in a file.
@@ -371,7 +672,8 @@ class QuantumProgram(ABC):
371
672
  return grads
372
673
 
373
674
  def _iteration_counter(intermediate_result: OptimizeResult):
374
- self.losses.append(
675
+
676
+ self._losses.append(
375
677
  dict(
376
678
  zip(
377
679
  range(len(intermediate_result.x)),
@@ -380,12 +682,13 @@ class QuantumProgram(ABC):
380
682
  )
381
683
  )
382
684
 
383
- self.final_params[:] = np.atleast_2d(intermediate_result.x)
384
-
385
685
  self.current_iteration += 1
386
686
 
387
687
  self.reporter.update(iteration=self.current_iteration)
388
688
 
689
+ if self._cancellation_event and self._cancellation_event.is_set():
690
+ raise _CancelledError("Cancellation requested by batch.")
691
+
389
692
  if (
390
693
  isinstance(self.optimizer, ScipyOptimizer)
391
694
  and self.optimizer.method == ScipyMethod.COBYLA
@@ -396,26 +699,42 @@ class QuantumProgram(ABC):
396
699
  self.reporter.info(message="Finished Setup")
397
700
 
398
701
  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)
408
702
 
409
- self.reporter.info(message="Finished Optimization!")
703
+ try:
704
+ self._minimize_res = self.optimizer.optimize(
705
+ cost_fn=cost_fn,
706
+ initial_params=self._curr_params,
707
+ callback_fn=_iteration_counter,
708
+ jac=grad_fn,
709
+ maxiter=self.max_iterations,
710
+ rng=self._rng,
711
+ )
712
+ except _CancelledError:
713
+ # The optimizer was stopped by our callback. This is not a real
714
+ # error, just a signal to exit this task cleanly.
715
+ return self._total_circuit_count, self._total_run_time
716
+
717
+ self._final_params[:] = np.atleast_2d(self._minimize_res.x)
718
+
719
+ self._perform_final_computation()
720
+
721
+ self.reporter.info(message="Finished successfully!")
410
722
 
411
723
  return self._total_circuit_count, self._total_run_time
412
724
 
413
725
  def save_iteration(self, data_file):
414
726
  """
415
- Save the current iteration of the program to a file.
727
+ Save the current state of the quantum program to a file.
728
+
729
+ Serializes the entire QuantumProgram instance including parameters,
730
+ losses, and circuit history using pickle.
416
731
 
417
732
  Args:
418
- data_file (str): The file to save the iteration to.
733
+ data_file (str): Path to the file where the program state will be saved.
734
+
735
+ Note:
736
+ The file is written in binary mode and can be restored using
737
+ `import_iteration()`.
419
738
  """
420
739
 
421
740
  with open(data_file, "wb") as f:
@@ -424,10 +743,16 @@ class QuantumProgram(ABC):
424
743
  @staticmethod
425
744
  def import_iteration(data_file):
426
745
  """
427
- Import an iteration of the program from a file.
746
+ Load a previously saved quantum program state from a file.
747
+
748
+ Deserializes a QuantumProgram instance that was saved using `save_iteration()`.
428
749
 
429
750
  Args:
430
- data_file (str): The file to import the iteration from.
751
+ data_file (str): Path to the file containing the saved program state.
752
+
753
+ Returns:
754
+ QuantumProgram: The restored QuantumProgram instance with all its state,
755
+ including parameters, losses, and circuit history.
431
756
  """
432
757
 
433
758
  with open(data_file, "rb") as f:
@@ -387,14 +387,6 @@ def dominance_aggregation(
387
387
  return curr_solution
388
388
 
389
389
 
390
- def _run_and_compute_solution(program: QuantumProgram):
391
- program.run()
392
-
393
- final_sol_circuit_count, final_sol_run_time = program.compute_final_solution()
394
-
395
- return final_sol_circuit_count, final_sol_run_time
396
-
397
-
398
390
  class GraphPartitioningQAOA(ProgramBatch):
399
391
  def __init__(
400
392
  self,
@@ -443,8 +435,6 @@ class GraphPartitioningQAOA(ProgramBatch):
443
435
  self.solution = None
444
436
  self.aggregate_fn = aggregate_fn
445
437
 
446
- self._task_fn = _run_and_compute_solution
447
-
448
438
  self._constructor = partial(
449
439
  QAOA,
450
440
  initial_state=initial_state,
@@ -499,33 +489,12 @@ class GraphPartitioningQAOA(ProgramBatch):
499
489
  self.reverse_index_maps[prog_id] = {v: k for k, v in index_map.items()}
500
490
 
501
491
  _subgraph = nx.relabel_nodes(subgraph, index_map)
502
- self.programs[prog_id] = self._constructor(
492
+ self._programs[prog_id] = self._constructor(
503
493
  job_id=prog_id,
504
494
  problem=_subgraph,
505
- losses=self._manager.list(),
506
- probs=self._manager.dict(),
507
- final_params=self._manager.list(),
508
- solution_nodes=self._manager.list(),
509
495
  progress_queue=self._queue,
510
496
  )
511
497
 
512
- def compute_final_solutions(self):
513
- if self._executor is not None:
514
- self.join()
515
-
516
- if self._executor is not None:
517
- raise RuntimeError("A batch is already being run.")
518
-
519
- if len(self.programs) == 0:
520
- raise RuntimeError("No programs to run.")
521
-
522
- self._executor = ProcessPoolExecutor()
523
-
524
- self.futures = [
525
- self._executor.submit(program.compute_final_solution)
526
- for program in self.programs.values()
527
- ]
528
-
529
498
  def aggregate_results(self):
530
499
  """
531
500
  Aggregates the results from all QAOA subprograms to form a global solution.