qoro-divi 0.3.4__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.
- divi/backends/__init__.py +1 -1
- divi/backends/_circuit_runner.py +42 -0
- divi/backends/_parallel_simulator.py +145 -49
- divi/backends/_qoro_service.py +451 -182
- divi/backends/_qpu_system.py +77 -3
- divi/circuits/_core.py +124 -4
- divi/circuits/qasm.py +20 -3
- divi/extern/cirq/_validator.py +12 -3
- divi/qprog/__init__.py +1 -0
- divi/qprog/algorithms/_ansatze.py +112 -12
- divi/qprog/algorithms/_qaoa.py +179 -110
- divi/qprog/algorithms/_vqe.py +192 -58
- divi/qprog/batch.py +270 -51
- divi/qprog/exceptions.py +9 -0
- divi/qprog/optimizers.py +336 -51
- divi/qprog/quantum_program.py +162 -339
- divi/qprog/variational_quantum_algorithm.py +786 -0
- divi/qprog/workflows/_graph_partitioning.py +43 -38
- divi/qprog/workflows/_qubo_partitioning.py +41 -24
- divi/qprog/workflows/_vqe_sweep.py +67 -39
- divi/reporting/_pbar.py +51 -9
- divi/reporting/_qlogger.py +35 -1
- divi/reporting/_reporter.py +11 -20
- divi/utils.py +100 -4
- {qoro_divi-0.3.4.dist-info → qoro_divi-0.4.0.dist-info}/METADATA +16 -1
- {qoro_divi-0.3.4.dist-info → qoro_divi-0.4.0.dist-info}/RECORD +30 -28
- {qoro_divi-0.3.4.dist-info → qoro_divi-0.4.0.dist-info}/LICENSE +0 -0
- {qoro_divi-0.3.4.dist-info → qoro_divi-0.4.0.dist-info}/LICENSES/.license-header +0 -0
- {qoro_divi-0.3.4.dist-info → qoro_divi-0.4.0.dist-info}/LICENSES/Apache-2.0.txt +0 -0
- {qoro_divi-0.3.4.dist-info → qoro_divi-0.4.0.dist-info}/WHEEL +0 -0
divi/qprog/algorithms/_qaoa.py
CHANGED
|
@@ -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,7 +32,16 @@ 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):
|
|
35
|
+
def draw_graph_solution_nodes(main_graph: nx.Graph, partition_nodes):
|
|
36
|
+
"""Visualize a graph with solution nodes highlighted.
|
|
37
|
+
|
|
38
|
+
Draws the graph with nodes colored to distinguish solution nodes (red) from
|
|
39
|
+
other nodes (light blue).
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
main_graph (nx.Graph): NetworkX graph to visualize.
|
|
43
|
+
partition_nodes: Collection of node indices that are part of the solution.
|
|
44
|
+
"""
|
|
36
45
|
# Create a dictionary for node colors
|
|
37
46
|
node_colors = [
|
|
38
47
|
"red" if node in partition_nodes else "lightblue" for node in main_graph.nodes()
|
|
@@ -55,6 +64,14 @@ def draw_graph_solution_nodes(main_graph, partition_nodes):
|
|
|
55
64
|
|
|
56
65
|
|
|
57
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
|
+
|
|
58
75
|
MAX_CLIQUE = ("max_clique", "Zeros", "Superposition")
|
|
59
76
|
MAX_INDEPENDENT_SET = ("max_independent_set", "Zeros", "Superposition")
|
|
60
77
|
MAX_WEIGHT_CYCLE = ("max_weight_cycle", "Superposition", "Superposition")
|
|
@@ -70,6 +87,13 @@ class GraphProblem(Enum):
|
|
|
70
87
|
constrained_initial_state: str,
|
|
71
88
|
unconstrained_initial_state: str,
|
|
72
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
|
+
"""
|
|
73
97
|
self.pl_string = pl_string
|
|
74
98
|
|
|
75
99
|
# Recommended initial state as per Pennylane's documentation.
|
|
@@ -84,6 +108,17 @@ _SUPPORTED_INITIAL_STATES_LITERAL = Literal[
|
|
|
84
108
|
|
|
85
109
|
|
|
86
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
|
+
"""
|
|
87
122
|
qiskit_sparse_op, constant = qp.to_ising()
|
|
88
123
|
|
|
89
124
|
pauli_list = qiskit_sparse_op.paulis
|
|
@@ -108,9 +143,16 @@ def _convert_quadratic_program_to_pennylane_ising(qp: QuadraticProgram):
|
|
|
108
143
|
def _resolve_circuit_layers(
|
|
109
144
|
initial_state, problem, graph_problem, **kwargs
|
|
110
145
|
) -> tuple[qml.operation.Operator, qml.operation.Operator, dict | None, str]:
|
|
111
|
-
"""
|
|
112
|
-
|
|
113
|
-
|
|
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)
|
|
114
156
|
"""
|
|
115
157
|
|
|
116
158
|
if isinstance(problem, GraphProblemTypes):
|
|
@@ -149,7 +191,36 @@ def _resolve_circuit_layers(
|
|
|
149
191
|
)
|
|
150
192
|
|
|
151
193
|
|
|
152
|
-
class QAOA(
|
|
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
|
+
|
|
153
224
|
def __init__(
|
|
154
225
|
self,
|
|
155
226
|
problem: GraphProblemTypes | QUBOProblemTypes,
|
|
@@ -161,16 +232,18 @@ class QAOA(QuantumProgram):
|
|
|
161
232
|
max_iterations: int = 10,
|
|
162
233
|
**kwargs,
|
|
163
234
|
):
|
|
164
|
-
"""
|
|
165
|
-
Initialize the QAOA problem.
|
|
235
|
+
"""Initialize the QAOA problem.
|
|
166
236
|
|
|
167
237
|
Args:
|
|
168
|
-
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.
|
|
169
239
|
For graph inputs, the graph problem to solve must be provided
|
|
170
240
|
through the `graph_problem` variable.
|
|
171
|
-
graph_problem (
|
|
172
|
-
n_layers (int):
|
|
173
|
-
initial_state (
|
|
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.
|
|
174
247
|
"""
|
|
175
248
|
|
|
176
249
|
if isinstance(problem, QUBOProblemTypes):
|
|
@@ -225,17 +298,14 @@ class QAOA(QuantumProgram):
|
|
|
225
298
|
self.max_iterations = max_iterations
|
|
226
299
|
self.current_iteration = 0
|
|
227
300
|
self._n_params = 2
|
|
228
|
-
self._is_compute_probabilites = False
|
|
229
301
|
self.optimizer = optimizer if optimizer is not None else MonteCarloOptimizer()
|
|
230
302
|
|
|
231
|
-
|
|
232
|
-
self.
|
|
233
|
-
self._solution_nodes = kwargs.pop("solution_nodes", [])
|
|
234
|
-
self._solution_bitstring = kwargs.pop("solution_bitstring", [])
|
|
303
|
+
self._solution_nodes = []
|
|
304
|
+
self._solution_bitstring = []
|
|
235
305
|
|
|
236
306
|
(
|
|
237
|
-
self.
|
|
238
|
-
self.
|
|
307
|
+
self._cost_hamiltonian,
|
|
308
|
+
self._mixer_hamiltonian,
|
|
239
309
|
*problem_metadata,
|
|
240
310
|
self.initial_state,
|
|
241
311
|
) = _resolve_circuit_layers(
|
|
@@ -256,12 +326,28 @@ class QAOA(QuantumProgram):
|
|
|
256
326
|
self.loss_constant = 0.0
|
|
257
327
|
|
|
258
328
|
kwargs.pop("is_constrained", None)
|
|
259
|
-
super().__init__(
|
|
329
|
+
super().__init__(**kwargs)
|
|
260
330
|
|
|
261
331
|
self._meta_circuits = self._create_meta_circuits_dict()
|
|
262
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
|
+
|
|
263
343
|
@property
|
|
264
344
|
def solution(self):
|
|
345
|
+
"""Get the solution found by QAOA optimization.
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
list[int] | np.ndarray: For graph problems, returns a list of selected node indices.
|
|
349
|
+
For QUBO problems, returns a list/array of binary values.
|
|
350
|
+
"""
|
|
265
351
|
return (
|
|
266
352
|
self._solution_nodes
|
|
267
353
|
if self.graph_problem is not None
|
|
@@ -269,11 +355,14 @@ class QAOA(QuantumProgram):
|
|
|
269
355
|
)
|
|
270
356
|
|
|
271
357
|
def _create_meta_circuits_dict(self) -> dict[str, MetaCircuit]:
|
|
272
|
-
"""
|
|
273
|
-
|
|
358
|
+
"""Generate the meta circuits for the QAOA problem.
|
|
359
|
+
|
|
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.
|
|
274
363
|
|
|
275
|
-
|
|
276
|
-
|
|
364
|
+
Returns:
|
|
365
|
+
dict[str, MetaCircuit]: Dictionary containing cost_circuit and meas_circuit.
|
|
277
366
|
"""
|
|
278
367
|
|
|
279
368
|
betas = sp.symarray("β", self.n_layers)
|
|
@@ -283,14 +372,16 @@ class QAOA(QuantumProgram):
|
|
|
283
372
|
|
|
284
373
|
def _qaoa_layer(params):
|
|
285
374
|
gamma, beta = params
|
|
286
|
-
pqaoa.cost_layer(gamma, self.
|
|
287
|
-
pqaoa.mixer_layer(beta, self.
|
|
375
|
+
pqaoa.cost_layer(gamma, self._cost_hamiltonian)
|
|
376
|
+
pqaoa.mixer_layer(beta, self._mixer_hamiltonian)
|
|
288
377
|
|
|
289
378
|
def _prepare_circuit(hamiltonian, params, final_measurement):
|
|
290
|
-
"""
|
|
291
|
-
|
|
379
|
+
"""Prepare the circuit for the QAOA problem.
|
|
380
|
+
|
|
292
381
|
Args:
|
|
293
|
-
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.
|
|
294
385
|
"""
|
|
295
386
|
|
|
296
387
|
# Note: could've been done as qml.[Insert Gate](wires=range(self.n_qubits))
|
|
@@ -313,121 +404,86 @@ class QAOA(QuantumProgram):
|
|
|
313
404
|
return {
|
|
314
405
|
"cost_circuit": self._meta_circuit_factory(
|
|
315
406
|
qml.tape.make_qscript(_prepare_circuit)(
|
|
316
|
-
self.
|
|
407
|
+
self._cost_hamiltonian, sym_params, final_measurement=False
|
|
317
408
|
),
|
|
318
409
|
symbols=sym_params.flatten(),
|
|
319
410
|
),
|
|
320
411
|
"meas_circuit": self._meta_circuit_factory(
|
|
321
412
|
qml.tape.make_qscript(_prepare_circuit)(
|
|
322
|
-
self.
|
|
413
|
+
self._cost_hamiltonian, sym_params, final_measurement=True
|
|
323
414
|
),
|
|
324
415
|
symbols=sym_params.flatten(),
|
|
416
|
+
grouping_strategy="wires",
|
|
325
417
|
),
|
|
326
418
|
}
|
|
327
419
|
|
|
328
|
-
def _generate_circuits(self):
|
|
329
|
-
"""
|
|
330
|
-
Generate the circuits for the QAOA problem.
|
|
420
|
+
def _generate_circuits(self) -> list[Circuit]:
|
|
421
|
+
"""Generate the circuits for the QAOA problem.
|
|
331
422
|
|
|
332
|
-
|
|
333
|
-
|
|
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).
|
|
334
426
|
|
|
427
|
+
Returns:
|
|
428
|
+
list[Circuit]: List of Circuit objects for execution.
|
|
429
|
+
"""
|
|
335
430
|
circuit_type = (
|
|
336
431
|
"cost_circuit" if not self._is_compute_probabilites else "meas_circuit"
|
|
337
432
|
)
|
|
338
433
|
|
|
339
|
-
|
|
340
|
-
|
|
434
|
+
return [
|
|
435
|
+
self._meta_circuits[circuit_type].initialize_circuit_from_params(
|
|
341
436
|
params_group, tag_prefix=f"{p}"
|
|
342
437
|
)
|
|
438
|
+
for p, params_group in enumerate(self._curr_params)
|
|
439
|
+
]
|
|
343
440
|
|
|
344
|
-
|
|
441
|
+
def _post_process_results(self, results, **kwargs):
|
|
442
|
+
"""Post-process the results of the QAOA problem.
|
|
345
443
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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.
|
|
349
449
|
|
|
350
450
|
Returns:
|
|
351
|
-
|
|
451
|
+
dict[str, dict[str, float]] | dict[int, float]: The losses for each parameter set grouping, or probability
|
|
452
|
+
distributions if computing probabilities.
|
|
352
453
|
"""
|
|
353
454
|
|
|
354
455
|
if self._is_compute_probabilites:
|
|
355
|
-
return
|
|
356
|
-
outer_k: {
|
|
357
|
-
inner_k: inner_v / self.backend.shots
|
|
358
|
-
for inner_k, inner_v in outer_v.items()
|
|
359
|
-
}
|
|
360
|
-
for outer_k, outer_v in results.items()
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
losses = super()._post_process_results(results)
|
|
456
|
+
return self._process_probability_results(results)
|
|
364
457
|
|
|
458
|
+
losses = super()._post_process_results(results, **kwargs)
|
|
365
459
|
return losses
|
|
366
460
|
|
|
367
|
-
def
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
self._curr_params = np.array(self.final_params)
|
|
371
|
-
|
|
372
|
-
self.circuits[:] = []
|
|
373
|
-
|
|
374
|
-
self._generate_circuits()
|
|
375
|
-
|
|
376
|
-
self.probs.update(self._dispatch_circuits_and_process_results())
|
|
461
|
+
def _perform_final_computation(self, **kwargs):
|
|
462
|
+
"""Extract the optimal solution from the QAOA optimization process.
|
|
377
463
|
|
|
378
|
-
self._is_compute_probabilites = False
|
|
379
|
-
|
|
380
|
-
def compute_final_solution(self):
|
|
381
|
-
"""
|
|
382
|
-
Computes and extracts the final solution from the QAOA optimization process.
|
|
383
464
|
This method performs the following steps:
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
- For graph problems, stores the solution as a list of node indices corresponding to '1's in the bitstring.
|
|
390
|
-
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.
|
|
391
470
|
|
|
392
471
|
Returns:
|
|
393
|
-
tuple: A tuple containing:
|
|
394
|
-
|
|
395
|
-
|
|
472
|
+
tuple[int, float]: A tuple containing:
|
|
473
|
+
- int: The total number of circuits executed.
|
|
474
|
+
- float: The total runtime of the optimization process.
|
|
396
475
|
"""
|
|
397
476
|
|
|
398
|
-
self.reporter.info(message="🏁 Computing Final Solution
|
|
399
|
-
|
|
400
|
-
# Convert losses dict to list to apply ordinal operations
|
|
401
|
-
final_losses_list = list(self.losses[-1].values())
|
|
402
|
-
|
|
403
|
-
# Get the index of the smallest loss in the last operation
|
|
404
|
-
best_solution_idx = min(
|
|
405
|
-
range(len(final_losses_list)),
|
|
406
|
-
key=lambda x: final_losses_list.__getitem__(x),
|
|
407
|
-
)
|
|
408
|
-
|
|
409
|
-
# Insert the measurement circuit here
|
|
410
|
-
self._run_final_measurement()
|
|
411
|
-
|
|
412
|
-
# Find the key matching the best_solution_idx with possible metadata in between
|
|
413
|
-
pattern = re.compile(rf"^{best_solution_idx}(?:_[^_]*)*_0$")
|
|
414
|
-
matching_keys = [k for k in self.probs.keys() if pattern.match(k)]
|
|
477
|
+
self.reporter.info(message="🏁 Computing Final Solution 🏁\r")
|
|
415
478
|
|
|
416
|
-
|
|
417
|
-
if len(matching_keys) == 0:
|
|
418
|
-
raise RuntimeError("No matching key found.")
|
|
419
|
-
if len(matching_keys) > 1:
|
|
420
|
-
raise RuntimeError(f"More than one matching key found.")
|
|
479
|
+
self._run_solution_measurement()
|
|
421
480
|
|
|
422
|
-
|
|
423
|
-
# Retrieve the probability distribution dictionary of the best solution
|
|
424
|
-
best_solution_probs = self.probs[best_solution_key]
|
|
481
|
+
best_measurement_probs = next(iter(self._best_probs.values()))
|
|
425
482
|
|
|
426
|
-
#
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
]
|
|
483
|
+
# Endianness is corrected in _post_process_results
|
|
484
|
+
best_solution_bitstring = max(
|
|
485
|
+
best_measurement_probs, key=best_measurement_probs.get
|
|
486
|
+
)
|
|
431
487
|
|
|
432
488
|
if isinstance(self.problem, QUBOProblemTypes):
|
|
433
489
|
self._solution_bitstring[:] = np.fromiter(
|
|
@@ -439,17 +495,30 @@ class QAOA(QuantumProgram):
|
|
|
439
495
|
m.start() for m in re.finditer("1", best_solution_bitstring)
|
|
440
496
|
]
|
|
441
497
|
|
|
442
|
-
self.reporter.info(message="Computed Final Solution!")
|
|
498
|
+
self.reporter.info(message="🏁 Computed Final Solution! 🏁\r\n")
|
|
443
499
|
|
|
444
500
|
return self._total_circuit_count, self._total_run_time
|
|
445
501
|
|
|
446
502
|
def draw_solution(self):
|
|
503
|
+
"""Visualize the solution found by QAOA for graph problems.
|
|
504
|
+
|
|
505
|
+
Draws the graph with solution nodes highlighted in red and other nodes
|
|
506
|
+
in light blue. If the solution hasn't been computed yet, it will be
|
|
507
|
+
calculated first.
|
|
508
|
+
|
|
509
|
+
Raises:
|
|
510
|
+
RuntimeError: If called on a QUBO problem instead of a graph problem.
|
|
511
|
+
|
|
512
|
+
Note:
|
|
513
|
+
This method only works for graph problems. For QUBO problems, access
|
|
514
|
+
the solution directly via the `solution` property.
|
|
515
|
+
"""
|
|
447
516
|
if self.graph_problem is None:
|
|
448
517
|
raise RuntimeError(
|
|
449
518
|
"The problem is not a graph problem. Cannot draw solution."
|
|
450
519
|
)
|
|
451
520
|
|
|
452
521
|
if not self._solution_nodes:
|
|
453
|
-
self.
|
|
522
|
+
self._perform_final_computation()
|
|
454
523
|
|
|
455
524
|
draw_graph_solution_nodes(self.problem, self._solution_nodes)
|