qoro-divi 0.3.3__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.
- divi/__init__.py +1 -2
- divi/backends/__init__.py +7 -0
- divi/backends/_circuit_runner.py +46 -0
- divi/{parallel_simulator.py → backends/_parallel_simulator.py} +136 -53
- divi/backends/_qoro_service.py +531 -0
- divi/circuits/__init__.py +5 -0
- divi/circuits/_core.py +226 -0
- divi/{qasm.py → circuits/qasm.py} +21 -2
- divi/{exp → extern}/cirq/_validator.py +9 -7
- divi/qprog/__init__.py +18 -5
- divi/qprog/algorithms/__init__.py +14 -0
- divi/qprog/algorithms/_ansatze.py +311 -0
- divi/qprog/{_qaoa.py → algorithms/_qaoa.py} +69 -41
- divi/qprog/{_vqe.py → algorithms/_vqe.py} +79 -135
- divi/qprog/batch.py +239 -55
- divi/qprog/exceptions.py +9 -0
- divi/qprog/optimizers.py +219 -18
- divi/qprog/quantum_program.py +389 -57
- divi/qprog/workflows/__init__.py +10 -0
- divi/qprog/{_graph_partitioning.py → workflows/_graph_partitioning.py} +3 -34
- divi/qprog/{_qubo_partitioning.py → workflows/_qubo_partitioning.py} +42 -25
- divi/qprog/{_vqe_sweep.py → workflows/_vqe_sweep.py} +59 -26
- divi/reporting/__init__.py +7 -0
- divi/reporting/_pbar.py +112 -0
- divi/{qlogger.py → reporting/_qlogger.py} +37 -2
- divi/{reporter.py → reporting/_reporter.py} +8 -14
- divi/utils.py +49 -10
- {qoro_divi-0.3.3.dist-info → qoro_divi-0.3.5.dist-info}/METADATA +2 -1
- qoro_divi-0.3.5.dist-info/RECORD +69 -0
- divi/_pbar.py +0 -70
- divi/circuits.py +0 -139
- divi/interfaces.py +0 -25
- divi/qoro_service.py +0 -425
- qoro_divi-0.3.3.dist-info/RECORD +0 -62
- /divi/{qpu_system.py → backends/_qpu_system.py} +0 -0
- /divi/{qem.py → circuits/qem.py} +0 -0
- /divi/{exp → extern}/cirq/__init__.py +0 -0
- /divi/{exp → extern}/cirq/_lexer.py +0 -0
- /divi/{exp → extern}/cirq/_parser.py +0 -0
- /divi/{exp → extern}/cirq/_qasm_export.py +0 -0
- /divi/{exp → extern}/cirq/_qasm_import.py +0 -0
- /divi/{exp → extern}/cirq/exception.py +0 -0
- /divi/{exp → extern}/scipy/_cobyla.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/LICENCE.txt +0 -0
- /divi/{exp → extern}/scipy/pyprima/__init__.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/cobyla/__init__.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/cobyla/cobyla.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/cobyla/cobylb.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/cobyla/geometry.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/cobyla/initialize.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/cobyla/trustregion.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/cobyla/update.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/__init__.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/_bounds.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/_linear_constraints.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/_nonlinear_constraints.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/_project.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/checkbreak.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/consts.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/evaluate.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/history.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/infos.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/linalg.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/message.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/powalg.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/preproc.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/present.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/ratio.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/redrho.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/selectx.py +0 -0
- {qoro_divi-0.3.3.dist-info → qoro_divi-0.3.5.dist-info}/LICENSE +0 -0
- {qoro_divi-0.3.3.dist-info → qoro_divi-0.3.5.dist-info}/LICENSES/.license-header +0 -0
- {qoro_divi-0.3.3.dist-info → qoro_divi-0.3.5.dist-info}/LICENSES/Apache-2.0.txt +0 -0
- {qoro_divi-0.3.3.dist-info → qoro_divi-0.3.5.dist-info}/WHEEL +0 -0
|
@@ -33,6 +33,16 @@ QUBOProblemTypes = list | np.ndarray | sps.spmatrix | QuadraticProgram
|
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def draw_graph_solution_nodes(main_graph, partition_nodes):
|
|
36
|
+
"""
|
|
37
|
+
Visualize a graph with solution nodes highlighted.
|
|
38
|
+
|
|
39
|
+
Draws the graph with nodes colored to distinguish solution nodes (red) from
|
|
40
|
+
other nodes (light blue).
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
main_graph: NetworkX graph to visualize.
|
|
44
|
+
partition_nodes: Collection of node indices that are part of the solution.
|
|
45
|
+
"""
|
|
36
46
|
# Create a dictionary for node colors
|
|
37
47
|
node_colors = [
|
|
38
48
|
"red" if node in partition_nodes else "lightblue" for node in main_graph.nodes()
|
|
@@ -224,14 +234,13 @@ class QAOA(QuantumProgram):
|
|
|
224
234
|
self.n_layers = n_layers
|
|
225
235
|
self.max_iterations = max_iterations
|
|
226
236
|
self.current_iteration = 0
|
|
227
|
-
self.
|
|
237
|
+
self._n_params = 2
|
|
228
238
|
self._is_compute_probabilites = False
|
|
229
239
|
self.optimizer = optimizer if optimizer is not None else MonteCarloOptimizer()
|
|
230
240
|
|
|
231
|
-
|
|
232
|
-
self.
|
|
233
|
-
self.
|
|
234
|
-
self._solution_bitstring = kwargs.pop("solution_bitstring", [])
|
|
241
|
+
self._probs = {}
|
|
242
|
+
self._solution_nodes = []
|
|
243
|
+
self._solution_bitstring = []
|
|
235
244
|
|
|
236
245
|
(
|
|
237
246
|
self.cost_hamiltonian,
|
|
@@ -256,18 +265,41 @@ class QAOA(QuantumProgram):
|
|
|
256
265
|
self.loss_constant = 0.0
|
|
257
266
|
|
|
258
267
|
kwargs.pop("is_constrained", None)
|
|
259
|
-
super().__init__(
|
|
268
|
+
super().__init__(**kwargs)
|
|
260
269
|
|
|
261
270
|
self._meta_circuits = self._create_meta_circuits_dict()
|
|
262
271
|
|
|
263
272
|
@property
|
|
264
273
|
def solution(self):
|
|
274
|
+
"""
|
|
275
|
+
Get the solution found by QAOA optimization.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
list: For graph problems, returns a list of selected node indices.
|
|
279
|
+
For QUBO problems, returns a list/array of binary values.
|
|
280
|
+
"""
|
|
265
281
|
return (
|
|
266
282
|
self._solution_nodes
|
|
267
283
|
if self.graph_problem is not None
|
|
268
284
|
else self._solution_bitstring
|
|
269
285
|
)
|
|
270
286
|
|
|
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
|
+
|
|
271
303
|
def _create_meta_circuits_dict(self) -> dict[str, MetaCircuit]:
|
|
272
304
|
"""
|
|
273
305
|
Generate the meta circuits for the QAOA problem.
|
|
@@ -341,7 +373,7 @@ class QAOA(QuantumProgram):
|
|
|
341
373
|
params_group, tag_prefix=f"{p}"
|
|
342
374
|
)
|
|
343
375
|
|
|
344
|
-
self.
|
|
376
|
+
self._circuits.append(circuit)
|
|
345
377
|
|
|
346
378
|
def _post_process_results(self, results):
|
|
347
379
|
"""
|
|
@@ -365,19 +397,26 @@ class QAOA(QuantumProgram):
|
|
|
365
397
|
return losses
|
|
366
398
|
|
|
367
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
|
+
"""
|
|
368
407
|
self._is_compute_probabilites = True
|
|
369
408
|
|
|
370
|
-
self._curr_params = np.array(self.
|
|
409
|
+
self._curr_params = np.array(self._final_params)
|
|
371
410
|
|
|
372
|
-
self.
|
|
411
|
+
self._circuits[:] = []
|
|
373
412
|
|
|
374
413
|
self._generate_circuits()
|
|
375
414
|
|
|
376
|
-
self.
|
|
415
|
+
self._probs.update(self._dispatch_circuits_and_process_results())
|
|
377
416
|
|
|
378
417
|
self._is_compute_probabilites = False
|
|
379
418
|
|
|
380
|
-
def
|
|
419
|
+
def _perform_final_computation(self):
|
|
381
420
|
"""
|
|
382
421
|
Computes and extracts the final solution from the QAOA optimization process.
|
|
383
422
|
This method performs the following steps:
|
|
@@ -397,37 +436,14 @@ class QAOA(QuantumProgram):
|
|
|
397
436
|
|
|
398
437
|
self.reporter.info(message="🏁 Computing Final Solution 🏁")
|
|
399
438
|
|
|
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
439
|
self._run_final_measurement()
|
|
411
440
|
|
|
412
|
-
|
|
413
|
-
pattern = re.compile(rf"^{best_solution_idx}(?:_[^_]*)*_0$")
|
|
414
|
-
matching_keys = [k for k in self.probs.keys() if pattern.match(k)]
|
|
415
|
-
|
|
416
|
-
# Some minor sanity checks
|
|
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.")
|
|
441
|
+
final_measurement_probs = next(iter(self._probs.values()))
|
|
421
442
|
|
|
422
|
-
best_solution_key = matching_keys[0]
|
|
423
|
-
# Retrieve the probability distribution dictionary of the best solution
|
|
424
|
-
best_solution_probs = self.probs[best_solution_key]
|
|
425
|
-
|
|
426
|
-
# Retrieve the bitstring with the actual best solution
|
|
427
443
|
# Reverse to account for the endianness difference
|
|
428
|
-
best_solution_bitstring = max(
|
|
429
|
-
|
|
430
|
-
]
|
|
444
|
+
best_solution_bitstring = max(
|
|
445
|
+
final_measurement_probs, key=final_measurement_probs.get
|
|
446
|
+
)[::-1]
|
|
431
447
|
|
|
432
448
|
if isinstance(self.problem, QUBOProblemTypes):
|
|
433
449
|
self._solution_bitstring[:] = np.fromiter(
|
|
@@ -439,17 +455,29 @@ class QAOA(QuantumProgram):
|
|
|
439
455
|
m.start() for m in re.finditer("1", best_solution_bitstring)
|
|
440
456
|
]
|
|
441
457
|
|
|
442
|
-
self.reporter.info(message="Computed Final Solution!")
|
|
443
|
-
|
|
444
458
|
return self._total_circuit_count, self._total_run_time
|
|
445
459
|
|
|
446
460
|
def draw_solution(self):
|
|
461
|
+
"""
|
|
462
|
+
Visualize the solution found by QAOA for graph problems.
|
|
463
|
+
|
|
464
|
+
Draws the graph with solution nodes highlighted in red and other nodes
|
|
465
|
+
in light blue. If the solution hasn't been computed yet, it will be
|
|
466
|
+
calculated first.
|
|
467
|
+
|
|
468
|
+
Raises:
|
|
469
|
+
RuntimeError: If called on a QUBO problem instead of a graph problem.
|
|
470
|
+
|
|
471
|
+
Note:
|
|
472
|
+
This method only works for graph problems. For QUBO problems, access
|
|
473
|
+
the solution directly via the `solution` property.
|
|
474
|
+
"""
|
|
447
475
|
if self.graph_problem is None:
|
|
448
476
|
raise RuntimeError(
|
|
449
477
|
"The problem is not a graph problem. Cannot draw solution."
|
|
450
478
|
)
|
|
451
479
|
|
|
452
480
|
if not self._solution_nodes:
|
|
453
|
-
self.
|
|
481
|
+
self._perform_final_computation()
|
|
454
482
|
|
|
455
483
|
draw_graph_solution_nodes(self.problem, self._solution_nodes)
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
5
|
-
from enum import Enum
|
|
6
5
|
from warnings import warn
|
|
7
6
|
|
|
8
7
|
import pennylane as qml
|
|
@@ -10,38 +9,10 @@ import sympy as sp
|
|
|
10
9
|
|
|
11
10
|
from divi.circuits import MetaCircuit
|
|
12
11
|
from divi.qprog import QuantumProgram
|
|
12
|
+
from divi.qprog.algorithms._ansatze import Ansatz, HartreeFockAnsatz
|
|
13
13
|
from divi.qprog.optimizers import MonteCarloOptimizer, Optimizer
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
class VQEAnsatz(Enum):
|
|
17
|
-
UCCSD = "UCCSD"
|
|
18
|
-
RY = "RY"
|
|
19
|
-
RYRZ = "RYRZ"
|
|
20
|
-
HW_EFFICIENT = "HW_EFFICIENT"
|
|
21
|
-
QAOA = "QAOA"
|
|
22
|
-
HARTREE_FOCK = "HF"
|
|
23
|
-
|
|
24
|
-
def describe(self):
|
|
25
|
-
return self.name, self.value
|
|
26
|
-
|
|
27
|
-
def n_params(self, n_qubits, **kwargs):
|
|
28
|
-
if self in (VQEAnsatz.UCCSD, VQEAnsatz.HARTREE_FOCK):
|
|
29
|
-
singles, doubles = qml.qchem.excitations(
|
|
30
|
-
kwargs.pop("n_electrons"), n_qubits
|
|
31
|
-
)
|
|
32
|
-
s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles)
|
|
33
|
-
|
|
34
|
-
return len(s_wires) + len(d_wires)
|
|
35
|
-
elif self == VQEAnsatz.RY:
|
|
36
|
-
return n_qubits
|
|
37
|
-
elif self == VQEAnsatz.RYRZ:
|
|
38
|
-
return 2 * n_qubits
|
|
39
|
-
elif self == VQEAnsatz.HW_EFFICIENT:
|
|
40
|
-
raise NotImplementedError
|
|
41
|
-
elif self == VQEAnsatz.QAOA:
|
|
42
|
-
return qml.QAOAEmbedding.shape(n_layers=1, n_wires=n_qubits)[1]
|
|
43
|
-
|
|
44
|
-
|
|
45
16
|
class VQE(QuantumProgram):
|
|
46
17
|
def __init__(
|
|
47
18
|
self,
|
|
@@ -49,7 +20,7 @@ class VQE(QuantumProgram):
|
|
|
49
20
|
molecule: qml.qchem.Molecule | None = None,
|
|
50
21
|
n_electrons: int | None = None,
|
|
51
22
|
n_layers: int = 1,
|
|
52
|
-
ansatz=
|
|
23
|
+
ansatz: Ansatz | None = None,
|
|
53
24
|
optimizer: Optimizer | None = None,
|
|
54
25
|
max_iterations=10,
|
|
55
26
|
**kwargs,
|
|
@@ -58,19 +29,26 @@ class VQE(QuantumProgram):
|
|
|
58
29
|
Initialize the VQE problem.
|
|
59
30
|
|
|
60
31
|
Args:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
32
|
+
hamiltonian (pennylane.operation.Operator, optional): A Hamiltonian
|
|
33
|
+
representing the problem.
|
|
34
|
+
molecule (pennylane.qchem.Molecule, optional): The molecule representing
|
|
35
|
+
the problem.
|
|
36
|
+
n_electrons (int, optional): Number of electrons associated with the
|
|
37
|
+
Hamiltonian. Only needs to be provided when a Hamiltonian is given.
|
|
38
|
+
n_layers (int, optional): Number of ansatz layers. Defaults to 1.
|
|
39
|
+
ansatz (Ansatz, optional): The ansatz to use for the VQE problem.
|
|
40
|
+
Defaults to HartreeFockAnsatz.
|
|
41
|
+
optimizer (Optimizer, optional): The optimizer to use. Defaults to
|
|
42
|
+
MonteCarloOptimizer.
|
|
43
|
+
max_iterations (int, optional): Maximum number of optimization iterations.
|
|
44
|
+
Defaults to 10.
|
|
45
|
+
**kwargs: Additional keyword arguments passed to the parent QuantumProgram.
|
|
68
46
|
"""
|
|
69
47
|
|
|
70
48
|
# Local Variables
|
|
49
|
+
self.ansatz = HartreeFockAnsatz() if ansatz is None else ansatz
|
|
71
50
|
self.n_layers = n_layers
|
|
72
51
|
self.results = {}
|
|
73
|
-
self.ansatz = ansatz
|
|
74
52
|
self.max_iterations = max_iterations
|
|
75
53
|
self.current_iteration = 0
|
|
76
54
|
|
|
@@ -84,20 +62,43 @@ class VQE(QuantumProgram):
|
|
|
84
62
|
|
|
85
63
|
self._meta_circuits = self._create_meta_circuits_dict()
|
|
86
64
|
|
|
65
|
+
@property
|
|
66
|
+
def n_params(self):
|
|
67
|
+
"""
|
|
68
|
+
Get the total number of parameters for the VQE ansatz.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
int: Total number of parameters (n_params_per_layer * n_layers).
|
|
72
|
+
"""
|
|
73
|
+
return (
|
|
74
|
+
self.ansatz.n_params_per_layer(self.n_qubits, n_electrons=self.n_electrons)
|
|
75
|
+
* self.n_layers
|
|
76
|
+
)
|
|
77
|
+
|
|
87
78
|
def _process_problem_input(self, hamiltonian, molecule, n_electrons):
|
|
79
|
+
"""
|
|
80
|
+
Process and validate the VQE problem input.
|
|
81
|
+
|
|
82
|
+
Handles both Hamiltonian-based and molecule-based problem specifications,
|
|
83
|
+
extracting the necessary information (n_qubits, n_electrons, hamiltonian).
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
hamiltonian: PennyLane Hamiltonian operator or None.
|
|
87
|
+
molecule: PennyLane Molecule object or None.
|
|
88
|
+
n_electrons: Number of electrons or None.
|
|
89
|
+
|
|
90
|
+
Raises:
|
|
91
|
+
ValueError: If neither hamiltonian nor molecule is provided.
|
|
92
|
+
UserWarning: If n_electrons conflicts with the molecule's electron count.
|
|
93
|
+
"""
|
|
88
94
|
if hamiltonian is None and molecule is None:
|
|
89
95
|
raise ValueError(
|
|
90
96
|
"Either one of `molecule` and `hamiltonian` must be provided."
|
|
91
97
|
)
|
|
92
98
|
|
|
93
99
|
if hamiltonian is not None:
|
|
94
|
-
if not isinstance(n_electrons, int) or n_electrons < 0:
|
|
95
|
-
raise ValueError(
|
|
96
|
-
f"`n_electrons` is expected to be a non-negative integer. Got {n_electrons}."
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
self.n_electrons = n_electrons
|
|
100
100
|
self.n_qubits = len(hamiltonian.wires)
|
|
101
|
+
self.n_electrons = n_electrons
|
|
101
102
|
|
|
102
103
|
if molecule is not None:
|
|
103
104
|
self.molecule = molecule
|
|
@@ -112,10 +113,6 @@ class VQE(QuantumProgram):
|
|
|
112
113
|
UserWarning,
|
|
113
114
|
)
|
|
114
115
|
|
|
115
|
-
self.n_params = self.ansatz.n_params(
|
|
116
|
-
self.n_qubits, n_electrons=self.n_electrons
|
|
117
|
-
)
|
|
118
|
-
|
|
119
116
|
self.cost_hamiltonian = self._clean_hamiltonian(hamiltonian)
|
|
120
117
|
|
|
121
118
|
def _clean_hamiltonian(
|
|
@@ -148,9 +145,17 @@ class VQE(QuantumProgram):
|
|
|
148
145
|
return hamiltonian.simplify()
|
|
149
146
|
|
|
150
147
|
def _create_meta_circuits_dict(self) -> dict[str, MetaCircuit]:
|
|
151
|
-
weights_syms = sp.symarray(
|
|
148
|
+
weights_syms = sp.symarray(
|
|
149
|
+
"w",
|
|
150
|
+
(
|
|
151
|
+
self.n_layers,
|
|
152
|
+
self.ansatz.n_params_per_layer(
|
|
153
|
+
self.n_qubits, n_electrons=self.n_electrons
|
|
154
|
+
),
|
|
155
|
+
),
|
|
156
|
+
)
|
|
152
157
|
|
|
153
|
-
def _prepare_circuit(
|
|
158
|
+
def _prepare_circuit(hamiltonian, params):
|
|
154
159
|
"""
|
|
155
160
|
Prepare the circuit for the VQE problem.
|
|
156
161
|
Args:
|
|
@@ -158,7 +163,12 @@ class VQE(QuantumProgram):
|
|
|
158
163
|
hamiltonian (qml.Hamiltonian): The Hamiltonian to use
|
|
159
164
|
params (list): The parameters to use for the ansatz
|
|
160
165
|
"""
|
|
161
|
-
self.
|
|
166
|
+
self.ansatz.build(
|
|
167
|
+
params,
|
|
168
|
+
n_qubits=self.n_qubits,
|
|
169
|
+
n_layers=self.n_layers,
|
|
170
|
+
n_electrons=self.n_electrons,
|
|
171
|
+
)
|
|
162
172
|
|
|
163
173
|
# Even though in principle we want to sample from a state,
|
|
164
174
|
# we are applying an `expval` operation here to make it compatible
|
|
@@ -169,93 +179,12 @@ class VQE(QuantumProgram):
|
|
|
169
179
|
return {
|
|
170
180
|
"cost_circuit": self._meta_circuit_factory(
|
|
171
181
|
qml.tape.make_qscript(_prepare_circuit)(
|
|
172
|
-
self.
|
|
182
|
+
self.cost_hamiltonian, weights_syms
|
|
173
183
|
),
|
|
174
184
|
symbols=weights_syms.flatten(),
|
|
175
185
|
)
|
|
176
186
|
}
|
|
177
187
|
|
|
178
|
-
def _set_ansatz(self, ansatz: VQEAnsatz, params):
|
|
179
|
-
"""
|
|
180
|
-
Set the ansatz for the VQE problem.
|
|
181
|
-
Args:
|
|
182
|
-
ansatz (Ansatze): The ansatz to use
|
|
183
|
-
params (list): The parameters to use for the ansatz
|
|
184
|
-
n_layers (int): The number of layers to use for the ansatz
|
|
185
|
-
"""
|
|
186
|
-
|
|
187
|
-
def _add_hw_efficient_ansatz(params):
|
|
188
|
-
raise NotImplementedError
|
|
189
|
-
|
|
190
|
-
def _add_qaoa_ansatz(params):
|
|
191
|
-
# This infers layers automatically from the parameters shape
|
|
192
|
-
qml.QAOAEmbedding(
|
|
193
|
-
features=[],
|
|
194
|
-
weights=params.reshape(self.n_layers, -1),
|
|
195
|
-
wires=range(self.n_qubits),
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
def _add_ry_ansatz(params):
|
|
199
|
-
qml.layer(
|
|
200
|
-
qml.AngleEmbedding,
|
|
201
|
-
self.n_layers,
|
|
202
|
-
params.reshape(self.n_layers, -1),
|
|
203
|
-
wires=range(self.n_qubits),
|
|
204
|
-
rotation="Y",
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
def _add_ryrz_ansatz(params):
|
|
208
|
-
def _ryrz(params, wires):
|
|
209
|
-
ry_rots, rz_rots = params.reshape(2, -1)
|
|
210
|
-
qml.AngleEmbedding(ry_rots, wires=wires, rotation="Y")
|
|
211
|
-
qml.AngleEmbedding(rz_rots, wires=wires, rotation="Z")
|
|
212
|
-
|
|
213
|
-
qml.layer(
|
|
214
|
-
_ryrz,
|
|
215
|
-
self.n_layers,
|
|
216
|
-
params.reshape(self.n_layers, -1),
|
|
217
|
-
wires=range(self.n_qubits),
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
def _add_uccsd_ansatz(params):
|
|
221
|
-
hf_state = qml.qchem.hf_state(self.n_electrons, self.n_qubits)
|
|
222
|
-
|
|
223
|
-
singles, doubles = qml.qchem.excitations(self.n_electrons, self.n_qubits)
|
|
224
|
-
s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles)
|
|
225
|
-
|
|
226
|
-
qml.UCCSD(
|
|
227
|
-
params.reshape(self.n_layers, -1),
|
|
228
|
-
wires=range(self.n_qubits),
|
|
229
|
-
s_wires=s_wires,
|
|
230
|
-
d_wires=d_wires,
|
|
231
|
-
init_state=hf_state,
|
|
232
|
-
n_repeats=self.n_layers,
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
def _add_hartree_fock_ansatz(params):
|
|
236
|
-
singles, doubles = qml.qchem.excitations(self.n_electrons, self.n_qubits)
|
|
237
|
-
hf_state = qml.qchem.hf_state(self.n_electrons, self.n_qubits)
|
|
238
|
-
|
|
239
|
-
qml.layer(
|
|
240
|
-
qml.AllSinglesDoubles,
|
|
241
|
-
self.n_layers,
|
|
242
|
-
params.reshape(self.n_layers, -1),
|
|
243
|
-
wires=range(self.n_qubits),
|
|
244
|
-
hf_state=hf_state,
|
|
245
|
-
singles=singles,
|
|
246
|
-
doubles=doubles,
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
# Reset the BasisState operations after the first layer
|
|
250
|
-
# for behaviour similar to UCCSD ansatz
|
|
251
|
-
for op in qml.QueuingManager.active_context().queue[1:]:
|
|
252
|
-
op._hyperparameters["hf_state"] = 0
|
|
253
|
-
|
|
254
|
-
if ansatz in VQEAnsatz:
|
|
255
|
-
locals()[f"_add_{ansatz.name.lower()}_ansatz"](params)
|
|
256
|
-
else:
|
|
257
|
-
raise ValueError(f"Invalid Ansatz Value. Got {ansatz}.")
|
|
258
|
-
|
|
259
188
|
def _generate_circuits(self):
|
|
260
189
|
"""
|
|
261
190
|
Generate the circuits for the VQE problem.
|
|
@@ -274,9 +203,24 @@ class VQE(QuantumProgram):
|
|
|
274
203
|
"cost_circuit"
|
|
275
204
|
].initialize_circuit_from_params(params_group, tag_prefix=f"{p}")
|
|
276
205
|
|
|
277
|
-
self.
|
|
206
|
+
self._circuits.append(circuit)
|
|
278
207
|
|
|
279
208
|
def _run_optimization_circuits(self, store_data, data_file):
|
|
209
|
+
"""
|
|
210
|
+
Execute the circuits for the current optimization iteration.
|
|
211
|
+
|
|
212
|
+
Validates that the Hamiltonian is properly set before running circuits.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
store_data (bool): Whether to save iteration data.
|
|
216
|
+
data_file (str): Path to file for saving data.
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
dict: Loss values for each parameter set.
|
|
220
|
+
|
|
221
|
+
Raises:
|
|
222
|
+
RuntimeError: If the cost Hamiltonian is not set or empty.
|
|
223
|
+
"""
|
|
280
224
|
if self.cost_hamiltonian is None or len(self.cost_hamiltonian) == 0:
|
|
281
225
|
raise RuntimeError(
|
|
282
226
|
"Hamiltonian operators must be generated before running the VQE"
|