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

Potentially problematic release.


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

@@ -639,9 +639,18 @@ def validate_qasm_raise(src: str) -> None:
639
639
  Parser(toks).parse()
640
640
 
641
641
 
642
- def is_valid_qasm(src: str) -> bool | str:
642
+ def validate_qasm_count_qubits(src: str) -> int:
643
+ """Validate QASM and return the total number of qubits."""
644
+ toks = _lex(src)
645
+ parser = Parser(toks)
646
+ parser.parse()
647
+ # Sum all qubit register sizes to get total qubit count
648
+ return sum(parser.qregs.values())
649
+
650
+
651
+ def is_valid_qasm(src: str) -> int | str:
652
+ """Validate QASM and return the number of qubits if valid, or error message if invalid."""
643
653
  try:
644
- validate_qasm_raise(src)
645
- return True
654
+ return validate_qasm_count_qubits(src)
646
655
  except SyntaxError as e:
647
656
  return str(e)
divi/qprog/__init__.py CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  # isort: skip_file
6
6
  from .quantum_program import QuantumProgram
7
+ from .variational_quantum_algorithm import VariationalQuantumAlgorithm
7
8
  from .batch import ProgramBatch
8
9
  from .algorithms import (
9
10
  QAOA,
@@ -11,7 +11,7 @@ import pennylane as qml
11
11
 
12
12
 
13
13
  class Ansatz(ABC):
14
- """Abstract base class for all VQE ansaetze."""
14
+ """Abstract base class for all VQE ansätze."""
15
15
 
16
16
  @property
17
17
  def name(self) -> str:
@@ -25,15 +25,17 @@ class Ansatz(ABC):
25
25
  raise NotImplementedError
26
26
 
27
27
  @abstractmethod
28
- def build(self, params, n_qubits: int, n_layers: int, **kwargs):
28
+ def build(self, params, n_qubits: int, n_layers: int, **kwargs) -> None:
29
29
  """
30
- Builds the ansatz circuit.
30
+ Builds the ansatz circuit by adding operations to the active PennyLane
31
+ quantum function context.
31
32
 
32
- Args:
33
- params (array): The parameters (weights) for the ansatz.
34
- n_qubits (int): The number of qubits.
35
- n_layers (int): The number of layers.
36
- **kwargs: Additional arguments like n_electrons for chemistry ansaetze.
33
+ Note: This method is called within a PennyLane quantum function context
34
+ (qml.qnode or qml.tape.make_qscript). Operations are added via side effects
35
+ to the active quantum tape/script.
36
+
37
+ Returns:
38
+ None: Operations are added to the active PennyLane context
37
39
  """
38
40
  raise NotImplementedError
39
41
 
@@ -118,7 +120,7 @@ class GenericLayerAnsatz(Ansatz):
118
120
  per_qubit = sum(getattr(g, "num_params", 1) for g in self.gate_sequence)
119
121
  return per_qubit * n_qubits
120
122
 
121
- def build(self, params, n_qubits: int, n_layers: int, **kwargs):
123
+ def build(self, params, n_qubits: int, n_layers: int, **kwargs) -> None:
122
124
  # calculate how many params each gate needs per qubit
123
125
  gate_param_counts = [getattr(g, "num_params", 1) for g in self.gate_sequence]
124
126
  per_qubit = sum(gate_param_counts)
@@ -164,7 +166,7 @@ class QAOAAnsatz(Ansatz):
164
166
  """
165
167
  return qml.QAOAEmbedding.shape(n_layers=1, n_wires=n_qubits)[1]
166
168
 
167
- def build(self, params, n_qubits: int, n_layers: int, **kwargs):
169
+ def build(self, params, n_qubits: int, n_layers: int, **kwargs) -> None:
168
170
  """
169
171
  Build the QAOA ansatz circuit.
170
172
 
@@ -231,7 +233,7 @@ class UCCSDAnsatz(Ansatz):
231
233
  s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles)
232
234
  return len(s_wires) + len(d_wires)
233
235
 
234
- def build(self, params, n_qubits: int, n_layers: int, n_electrons: int, **kwargs):
236
+ def build(self, params, n_qubits: int, n_layers: int, **kwargs) -> None:
235
237
  """
236
238
  Build the UCCSD ansatz circuit.
237
239
 
@@ -239,9 +241,10 @@ class UCCSDAnsatz(Ansatz):
239
241
  params: Parameter array for excitation amplitudes.
240
242
  n_qubits (int): Number of qubits.
241
243
  n_layers (int): Number of UCCSD layers (repeats).
242
- n_electrons (int): Number of electrons in the system.
243
- **kwargs: Additional unused arguments.
244
+ **kwargs: Additional arguments:
245
+ n_electrons (int): Number of electrons in the system (required).
244
246
  """
247
+ n_electrons = kwargs.pop("n_electrons")
245
248
  singles, doubles = qml.qchem.excitations(n_electrons, n_qubits)
246
249
  s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles)
247
250
  hf_state = qml.qchem.hf_state(n_electrons, n_qubits)
@@ -281,7 +284,7 @@ class HartreeFockAnsatz(Ansatz):
281
284
  singles, doubles = qml.qchem.excitations(n_electrons, n_qubits)
282
285
  return len(singles) + len(doubles)
283
286
 
284
- def build(self, params, n_qubits: int, n_layers: int, n_electrons: int, **kwargs):
287
+ def build(self, params, n_qubits: int, n_layers: int, **kwargs) -> None:
285
288
  """
286
289
  Build the Hartree-Fock ansatz circuit.
287
290
 
@@ -289,9 +292,10 @@ class HartreeFockAnsatz(Ansatz):
289
292
  params: Parameter array for excitation amplitudes.
290
293
  n_qubits (int): Number of qubits.
291
294
  n_layers (int): Number of ansatz layers.
292
- n_electrons (int): Number of electrons in the system.
293
- **kwargs: Additional unused arguments.
295
+ **kwargs: Additional arguments:
296
+ n_electrons (int): Number of electrons in the system (required).
294
297
  """
298
+ n_electrons = kwargs.pop("n_electrons")
295
299
  singles, doubles = qml.qchem.excitations(n_electrons, n_qubits)
296
300
  hf_state = qml.qchem.hf_state(n_electrons, n_qubits)
297
301
 
@@ -21,9 +21,9 @@ from qiskit_optimization import QuadraticProgram
21
21
  from qiskit_optimization.converters import QuadraticProgramToQubo
22
22
  from qiskit_optimization.problems import VarType
23
23
 
24
- from divi.circuits import MetaCircuit
25
- from divi.qprog import QuantumProgram
24
+ from divi.circuits import Circuit, MetaCircuit
26
25
  from divi.qprog.optimizers import MonteCarloOptimizer, Optimizer
26
+ from divi.qprog.variational_quantum_algorithm import VariationalQuantumAlgorithm
27
27
  from divi.utils import convert_qubo_matrix_to_pennylane_ising
28
28
 
29
29
  logger = logging.getLogger(__name__)
@@ -32,15 +32,14 @@ GraphProblemTypes = nx.Graph | rx.PyGraph
32
32
  QUBOProblemTypes = list | np.ndarray | sps.spmatrix | QuadraticProgram
33
33
 
34
34
 
35
- def draw_graph_solution_nodes(main_graph, partition_nodes):
36
- """
37
- Visualize a graph with solution nodes highlighted.
35
+ def draw_graph_solution_nodes(main_graph: nx.Graph, partition_nodes):
36
+ """Visualize a graph with solution nodes highlighted.
38
37
 
39
38
  Draws the graph with nodes colored to distinguish solution nodes (red) from
40
39
  other nodes (light blue).
41
40
 
42
41
  Args:
43
- main_graph: NetworkX graph to visualize.
42
+ main_graph (nx.Graph): NetworkX graph to visualize.
44
43
  partition_nodes: Collection of node indices that are part of the solution.
45
44
  """
46
45
  # Create a dictionary for node colors
@@ -65,6 +64,14 @@ def draw_graph_solution_nodes(main_graph, partition_nodes):
65
64
 
66
65
 
67
66
  class GraphProblem(Enum):
67
+ """Enumeration of supported graph problems for QAOA.
68
+
69
+ Each problem type defines:
70
+ - pl_string: The corresponding PennyLane function name
71
+ - constrained_initial_state: Recommended initial state for constrained problems
72
+ - unconstrained_initial_state: Recommended initial state for unconstrained problems
73
+ """
74
+
68
75
  MAX_CLIQUE = ("max_clique", "Zeros", "Superposition")
69
76
  MAX_INDEPENDENT_SET = ("max_independent_set", "Zeros", "Superposition")
70
77
  MAX_WEIGHT_CYCLE = ("max_weight_cycle", "Superposition", "Superposition")
@@ -80,6 +87,13 @@ class GraphProblem(Enum):
80
87
  constrained_initial_state: str,
81
88
  unconstrained_initial_state: str,
82
89
  ):
90
+ """Initialize the GraphProblem enum value.
91
+
92
+ Args:
93
+ pl_string (str): The corresponding PennyLane function name.
94
+ constrained_initial_state (str): Recommended initial state for constrained problems.
95
+ unconstrained_initial_state (str): Recommended initial state for unconstrained problems.
96
+ """
83
97
  self.pl_string = pl_string
84
98
 
85
99
  # Recommended initial state as per Pennylane's documentation.
@@ -94,6 +108,17 @@ _SUPPORTED_INITIAL_STATES_LITERAL = Literal[
94
108
 
95
109
 
96
110
  def _convert_quadratic_program_to_pennylane_ising(qp: QuadraticProgram):
111
+ """Convert a Qiskit QuadraticProgram to a PennyLane Ising Hamiltonian.
112
+
113
+ Args:
114
+ qp (QuadraticProgram): Qiskit QuadraticProgram to convert.
115
+
116
+ Returns:
117
+ tuple[qml.Hamiltonian, float, int]: (pennylane_ising, constant, n_qubits) where:
118
+ - pennylane_ising: The Ising Hamiltonian in PennyLane format
119
+ - constant: The constant term
120
+ - n_qubits: Number of qubits required
121
+ """
97
122
  qiskit_sparse_op, constant = qp.to_ising()
98
123
 
99
124
  pauli_list = qiskit_sparse_op.paulis
@@ -118,9 +143,16 @@ def _convert_quadratic_program_to_pennylane_ising(qp: QuadraticProgram):
118
143
  def _resolve_circuit_layers(
119
144
  initial_state, problem, graph_problem, **kwargs
120
145
  ) -> tuple[qml.operation.Operator, qml.operation.Operator, dict | None, str]:
121
- """
122
- Generates the cost and mixer hamiltonians for a given problem, in addition to
123
- optional metadata returned by Pennylane if applicable
146
+ """Generate the cost and mixer Hamiltonians for a given problem.
147
+
148
+ Args:
149
+ initial_state (str): The initial state specification.
150
+ problem (GraphProblemTypes | QUBOProblemTypes): The problem to solve (graph or QUBO).
151
+ graph_problem (GraphProblem | None): The graph problem type (if applicable).
152
+ **kwargs: Additional keyword arguments.
153
+
154
+ Returns:
155
+ tuple[qml.operation.Operator, qml.operation.Operator, dict | None, str]: (cost_hamiltonian, mixer_hamiltonian, metadata, resolved_initial_state)
124
156
  """
125
157
 
126
158
  if isinstance(problem, GraphProblemTypes):
@@ -159,7 +191,36 @@ def _resolve_circuit_layers(
159
191
  )
160
192
 
161
193
 
162
- class QAOA(QuantumProgram):
194
+ class QAOA(VariationalQuantumAlgorithm):
195
+ """Quantum Approximate Optimization Algorithm (QAOA) implementation.
196
+
197
+ QAOA is a hybrid quantum-classical algorithm designed to solve combinatorial
198
+ optimization problems. It alternates between applying a cost Hamiltonian
199
+ (encoding the problem) and a mixer Hamiltonian (enabling exploration).
200
+
201
+ The algorithm can solve:
202
+ - Graph problems (MaxCut, Max Clique, etc.)
203
+ - QUBO (Quadratic Unconstrained Binary Optimization) problems
204
+ - Quadratic programs (converted to QUBO)
205
+
206
+ Attributes:
207
+ problem (GraphProblemTypes | QUBOProblemTypes): The problem instance to solve.
208
+ graph_problem (GraphProblem | None): The graph problem type (if applicable).
209
+ n_layers (int): Number of QAOA layers.
210
+ n_qubits (int): Number of qubits required.
211
+ cost_hamiltonian (qml.Hamiltonian): The cost Hamiltonian encoding the problem.
212
+ mixer_hamiltonian (qml.Hamiltonian): The mixer Hamiltonian for exploration.
213
+ initial_state (str): The initial quantum state.
214
+ problem_metadata (dict | None): Additional metadata from problem setup.
215
+ loss_constant (float): Constant term from the problem.
216
+ optimizer (Optimizer): Classical optimizer for parameter updates.
217
+ max_iterations (int): Maximum number of optimization iterations.
218
+ current_iteration (int): Current optimization iteration.
219
+ _n_params (int): Number of parameters per layer (always 2 for QAOA).
220
+ _solution_nodes (list[int] | None): Solution nodes for graph problems.
221
+ _solution_bitstring (np.ndarray | None): Solution bitstring for QUBO problems.
222
+ """
223
+
163
224
  def __init__(
164
225
  self,
165
226
  problem: GraphProblemTypes | QUBOProblemTypes,
@@ -171,16 +232,18 @@ class QAOA(QuantumProgram):
171
232
  max_iterations: int = 10,
172
233
  **kwargs,
173
234
  ):
174
- """
175
- Initialize the QAOA problem.
235
+ """Initialize the QAOA problem.
176
236
 
177
237
  Args:
178
- problem: The problem to solve, can either be a graph or a QUBO.
238
+ problem (GraphProblemTypes | QUBOProblemTypes): The problem to solve, can either be a graph or a QUBO.
179
239
  For graph inputs, the graph problem to solve must be provided
180
240
  through the `graph_problem` variable.
181
- graph_problem (str): The graph problem to solve.
182
- n_layers (int): number of QAOA layers
183
- initial_state (str): The initial state of the circuit
241
+ graph_problem (GraphProblem | None): The graph problem to solve. Defaults to None.
242
+ n_layers (int): Number of QAOA layers. Defaults to 1.
243
+ initial_state (_SUPPORTED_INITIAL_STATES_LITERAL): The initial state of the circuit. Defaults to "Recommended".
244
+ optimizer (Optimizer | None): The optimizer to use. Defaults to MonteCarloOptimizer.
245
+ max_iterations (int): Maximum number of optimization iterations. Defaults to 10.
246
+ **kwargs: Additional keyword arguments passed to the parent class.
184
247
  """
185
248
 
186
249
  if isinstance(problem, QUBOProblemTypes):
@@ -235,16 +298,14 @@ class QAOA(QuantumProgram):
235
298
  self.max_iterations = max_iterations
236
299
  self.current_iteration = 0
237
300
  self._n_params = 2
238
- self._is_compute_probabilites = False
239
301
  self.optimizer = optimizer if optimizer is not None else MonteCarloOptimizer()
240
302
 
241
- self._probs = {}
242
303
  self._solution_nodes = []
243
304
  self._solution_bitstring = []
244
305
 
245
306
  (
246
- self.cost_hamiltonian,
247
- self.mixer_hamiltonian,
307
+ self._cost_hamiltonian,
308
+ self._mixer_hamiltonian,
248
309
  *problem_metadata,
249
310
  self.initial_state,
250
311
  ) = _resolve_circuit_layers(
@@ -269,13 +330,22 @@ class QAOA(QuantumProgram):
269
330
 
270
331
  self._meta_circuits = self._create_meta_circuits_dict()
271
332
 
333
+ @property
334
+ def cost_hamiltonian(self) -> qml.operation.Operator:
335
+ """The cost Hamiltonian for the QAOA problem."""
336
+ return self._cost_hamiltonian
337
+
338
+ @property
339
+ def mixer_hamiltonian(self) -> qml.operation.Operator:
340
+ """The mixer Hamiltonian for the QAOA problem."""
341
+ return self._mixer_hamiltonian
342
+
272
343
  @property
273
344
  def solution(self):
274
- """
275
- Get the solution found by QAOA optimization.
345
+ """Get the solution found by QAOA optimization.
276
346
 
277
347
  Returns:
278
- list: For graph problems, returns a list of selected node indices.
348
+ list[int] | np.ndarray: For graph problems, returns a list of selected node indices.
279
349
  For QUBO problems, returns a list/array of binary values.
280
350
  """
281
351
  return (
@@ -284,28 +354,15 @@ class QAOA(QuantumProgram):
284
354
  else self._solution_bitstring
285
355
  )
286
356
 
287
- @property
288
- def probs(self) -> dict:
289
- """
290
- Get a copy of the probability distributions from final measurements.
291
-
292
- Returns:
293
- dict: Copy of the probability distributions dictionary. Keys are
294
- circuit tags, values are probability distributions.
295
- """
296
- return self._probs.copy()
297
-
298
- @probs.setter
299
- def probs(self, value: dict):
300
- """Set the probability distributions."""
301
- self._probs = value
302
-
303
357
  def _create_meta_circuits_dict(self) -> dict[str, MetaCircuit]:
304
- """
305
- Generate the meta circuits for the QAOA problem.
358
+ """Generate the meta circuits for the QAOA problem.
306
359
 
307
- In this method, we generate the scaffolding for the circuits that will be
308
- executed during optimization.
360
+ Creates both cost and measurement circuits for the QAOA algorithm.
361
+ The cost circuit is used during optimization, while the measurement
362
+ circuit is used for final solution extraction.
363
+
364
+ Returns:
365
+ dict[str, MetaCircuit]: Dictionary containing cost_circuit and meas_circuit.
309
366
  """
310
367
 
311
368
  betas = sp.symarray("β", self.n_layers)
@@ -315,14 +372,16 @@ class QAOA(QuantumProgram):
315
372
 
316
373
  def _qaoa_layer(params):
317
374
  gamma, beta = params
318
- pqaoa.cost_layer(gamma, self.cost_hamiltonian)
319
- pqaoa.mixer_layer(beta, self.mixer_hamiltonian)
375
+ pqaoa.cost_layer(gamma, self._cost_hamiltonian)
376
+ pqaoa.mixer_layer(beta, self._mixer_hamiltonian)
320
377
 
321
378
  def _prepare_circuit(hamiltonian, params, final_measurement):
322
- """
323
- Prepare the circuit for the QAOA problem.
379
+ """Prepare the circuit for the QAOA problem.
380
+
324
381
  Args:
325
- hamiltonian (qml.Hamiltonian): The Hamiltonian term to measure
382
+ hamiltonian (qml.Hamiltonian): The Hamiltonian term to measure.
383
+ params (np.ndarray): The QAOA parameters (betas and gammas).
384
+ final_measurement (bool): Whether to perform final measurement.
326
385
  """
327
386
 
328
387
  # Note: could've been done as qml.[Insert Gate](wires=range(self.n_qubits))
@@ -345,105 +404,86 @@ class QAOA(QuantumProgram):
345
404
  return {
346
405
  "cost_circuit": self._meta_circuit_factory(
347
406
  qml.tape.make_qscript(_prepare_circuit)(
348
- self.cost_hamiltonian, sym_params, final_measurement=False
407
+ self._cost_hamiltonian, sym_params, final_measurement=False
349
408
  ),
350
409
  symbols=sym_params.flatten(),
351
410
  ),
352
411
  "meas_circuit": self._meta_circuit_factory(
353
412
  qml.tape.make_qscript(_prepare_circuit)(
354
- self.cost_hamiltonian, sym_params, final_measurement=True
413
+ self._cost_hamiltonian, sym_params, final_measurement=True
355
414
  ),
356
415
  symbols=sym_params.flatten(),
416
+ grouping_strategy="wires",
357
417
  ),
358
418
  }
359
419
 
360
- def _generate_circuits(self):
361
- """
362
- Generate the circuits for the QAOA problem.
420
+ def _generate_circuits(self) -> list[Circuit]:
421
+ """Generate the circuits for the QAOA problem.
363
422
 
364
- In this method, we generate bulk circuits based on the selected parameters.
365
- """
423
+ Generates circuits for each parameter set in the current parameters.
424
+ The circuit type depends on whether we're computing probabilities
425
+ (for final solution extraction) or just expectation values (for optimization).
366
426
 
427
+ Returns:
428
+ list[Circuit]: List of Circuit objects for execution.
429
+ """
367
430
  circuit_type = (
368
431
  "cost_circuit" if not self._is_compute_probabilites else "meas_circuit"
369
432
  )
370
433
 
371
- for p, params_group in enumerate(self._curr_params):
372
- circuit = self._meta_circuits[circuit_type].initialize_circuit_from_params(
434
+ return [
435
+ self._meta_circuits[circuit_type].initialize_circuit_from_params(
373
436
  params_group, tag_prefix=f"{p}"
374
437
  )
438
+ for p, params_group in enumerate(self._curr_params)
439
+ ]
375
440
 
376
- self._circuits.append(circuit)
441
+ def _post_process_results(self, results, **kwargs):
442
+ """Post-process the results of the QAOA problem.
377
443
 
378
- def _post_process_results(self, results):
379
- """
380
- Post-process the results of the QAOA problem.
444
+ Args:
445
+ results (dict[str, dict[str, int]]): Raw results from circuit execution.
446
+ **kwargs: Additional keyword arguments.
447
+ ham_ops (str): The Hamiltonian operators to measure, semicolon-separated.
448
+ Only needed when the backend supports expval.
381
449
 
382
450
  Returns:
383
- (dict) The losses for each parameter set grouping.
451
+ dict[str, dict[str, float]] | dict[int, float]: The losses for each parameter set grouping, or probability
452
+ distributions if computing probabilities.
384
453
  """
385
454
 
386
455
  if self._is_compute_probabilites:
387
- return {
388
- outer_k: {
389
- inner_k: inner_v / self.backend.shots
390
- for inner_k, inner_v in outer_v.items()
391
- }
392
- for outer_k, outer_v in results.items()
393
- }
394
-
395
- losses = super()._post_process_results(results)
456
+ return self._process_probability_results(results)
396
457
 
458
+ losses = super()._post_process_results(results, **kwargs)
397
459
  return losses
398
460
 
399
- def _run_final_measurement(self):
400
- """
401
- Execute final measurement circuits to obtain probability distributions.
402
-
403
- Runs the optimized circuit with final parameters to get the full probability
404
- distribution over all measurement outcomes, which is used to extract the
405
- solution bitstring.
406
- """
407
- self._is_compute_probabilites = True
408
-
409
- self._curr_params = np.array(self._final_params)
410
-
411
- self._circuits[:] = []
412
-
413
- self._generate_circuits()
461
+ def _perform_final_computation(self, **kwargs):
462
+ """Extract the optimal solution from the QAOA optimization process.
414
463
 
415
- self._probs.update(self._dispatch_circuits_and_process_results())
416
-
417
- self._is_compute_probabilites = False
418
-
419
- def _perform_final_computation(self):
420
- """
421
- Computes and extracts the final solution from the QAOA optimization process.
422
464
  This method performs the following steps:
423
- 1. Identifies the best solution index based on the lowest loss value from the last optimization step.
424
- 2. Executes the final measurement circuit to obtain the probability distributions of solutions.
425
- 3. Retrieves the bitstring representing the best solution, correcting for endianness.
426
- 4. Depending on the problem type:
427
- - For QUBO problems, stores the solution as a NumPy array of bits.
428
- - For graph problems, stores the solution as a list of node indices corresponding to '1's in the bitstring.
429
- 5. Returns the total circuit count and total runtime for the optimization process.
465
+ 1. Executes measurement circuits with the best parameters (those that achieved the lowest loss).
466
+ 2. Retrieves the bitstring representing the best solution, correcting for endianness.
467
+ 3. Depending on the problem type:
468
+ - For QUBO problems, stores the solution as a NumPy array of bits.
469
+ - For graph problems, stores the solution as a list of node indices corresponding to '1's in the bitstring.
430
470
 
431
471
  Returns:
432
- tuple: A tuple containing:
433
- - int: The total number of circuits executed.
434
- - float: The total runtime of the optimization process.
472
+ tuple[int, float]: A tuple containing:
473
+ - int: The total number of circuits executed.
474
+ - float: The total runtime of the optimization process.
435
475
  """
436
476
 
437
- self.reporter.info(message="🏁 Computing Final Solution 🏁")
477
+ self.reporter.info(message="🏁 Computing Final Solution 🏁\r")
438
478
 
439
- self._run_final_measurement()
479
+ self._run_solution_measurement()
440
480
 
441
- final_measurement_probs = next(iter(self._probs.values()))
481
+ best_measurement_probs = next(iter(self._best_probs.values()))
442
482
 
443
- # Reverse to account for the endianness difference
483
+ # Endianness is corrected in _post_process_results
444
484
  best_solution_bitstring = max(
445
- final_measurement_probs, key=final_measurement_probs.get
446
- )[::-1]
485
+ best_measurement_probs, key=best_measurement_probs.get
486
+ )
447
487
 
448
488
  if isinstance(self.problem, QUBOProblemTypes):
449
489
  self._solution_bitstring[:] = np.fromiter(
@@ -455,11 +495,12 @@ class QAOA(QuantumProgram):
455
495
  m.start() for m in re.finditer("1", best_solution_bitstring)
456
496
  ]
457
497
 
498
+ self.reporter.info(message="🏁 Computed Final Solution! 🏁\r\n")
499
+
458
500
  return self._total_circuit_count, self._total_run_time
459
501
 
460
502
  def draw_solution(self):
461
- """
462
- Visualize the solution found by QAOA for graph problems.
503
+ """Visualize the solution found by QAOA for graph problems.
463
504
 
464
505
  Draws the graph with solution nodes highlighted in red and other nodes
465
506
  in light blue. If the solution hasn't been computed yet, it will be