qoro-divi 0.2.0b1__py3-none-any.whl → 0.5.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.
- divi/__init__.py +1 -2
- divi/backends/__init__.py +9 -0
- divi/backends/_circuit_runner.py +70 -0
- divi/backends/_execution_result.py +70 -0
- divi/backends/_parallel_simulator.py +486 -0
- divi/backends/_qoro_service.py +663 -0
- divi/backends/_qpu_system.py +101 -0
- divi/backends/_results_processing.py +133 -0
- divi/circuits/__init__.py +8 -0
- divi/{exp/cirq → circuits/_cirq}/__init__.py +1 -2
- divi/circuits/_cirq/_parser.py +110 -0
- divi/circuits/_cirq/_qasm_export.py +78 -0
- divi/circuits/_core.py +369 -0
- divi/{qasm.py → circuits/_qasm_conversion.py} +73 -14
- divi/circuits/_qasm_validation.py +694 -0
- divi/qprog/__init__.py +24 -6
- divi/qprog/_expectation.py +181 -0
- divi/qprog/_hamiltonians.py +281 -0
- divi/qprog/algorithms/__init__.py +14 -0
- divi/qprog/algorithms/_ansatze.py +356 -0
- divi/qprog/algorithms/_qaoa.py +572 -0
- divi/qprog/algorithms/_vqe.py +249 -0
- divi/qprog/batch.py +383 -73
- divi/qprog/checkpointing.py +556 -0
- divi/qprog/exceptions.py +9 -0
- divi/qprog/optimizers.py +1014 -43
- divi/qprog/quantum_program.py +231 -413
- divi/qprog/variational_quantum_algorithm.py +995 -0
- divi/qprog/workflows/__init__.py +10 -0
- divi/qprog/{_graph_partitioning.py → workflows/_graph_partitioning.py} +139 -95
- divi/qprog/workflows/_qubo_partitioning.py +220 -0
- divi/qprog/workflows/_vqe_sweep.py +560 -0
- divi/reporting/__init__.py +7 -0
- divi/reporting/_pbar.py +127 -0
- divi/reporting/_qlogger.py +68 -0
- divi/reporting/_reporter.py +133 -0
- {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.5.0.dist-info}/METADATA +43 -15
- qoro_divi-0.5.0.dist-info/RECORD +43 -0
- {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.5.0.dist-info}/WHEEL +1 -1
- qoro_divi-0.5.0.dist-info/licenses/LICENSES/.license-header +3 -0
- divi/_pbar.py +0 -73
- divi/circuits.py +0 -139
- divi/exp/cirq/_lexer.py +0 -126
- divi/exp/cirq/_parser.py +0 -889
- divi/exp/cirq/_qasm_export.py +0 -37
- divi/exp/cirq/_qasm_import.py +0 -35
- divi/exp/cirq/exception.py +0 -21
- divi/exp/scipy/_cobyla.py +0 -342
- divi/exp/scipy/pyprima/LICENCE.txt +0 -28
- divi/exp/scipy/pyprima/__init__.py +0 -263
- divi/exp/scipy/pyprima/cobyla/__init__.py +0 -0
- divi/exp/scipy/pyprima/cobyla/cobyla.py +0 -599
- divi/exp/scipy/pyprima/cobyla/cobylb.py +0 -849
- divi/exp/scipy/pyprima/cobyla/geometry.py +0 -240
- divi/exp/scipy/pyprima/cobyla/initialize.py +0 -269
- divi/exp/scipy/pyprima/cobyla/trustregion.py +0 -540
- divi/exp/scipy/pyprima/cobyla/update.py +0 -331
- divi/exp/scipy/pyprima/common/__init__.py +0 -0
- divi/exp/scipy/pyprima/common/_bounds.py +0 -41
- divi/exp/scipy/pyprima/common/_linear_constraints.py +0 -46
- divi/exp/scipy/pyprima/common/_nonlinear_constraints.py +0 -64
- divi/exp/scipy/pyprima/common/_project.py +0 -224
- divi/exp/scipy/pyprima/common/checkbreak.py +0 -107
- divi/exp/scipy/pyprima/common/consts.py +0 -48
- divi/exp/scipy/pyprima/common/evaluate.py +0 -101
- divi/exp/scipy/pyprima/common/history.py +0 -39
- divi/exp/scipy/pyprima/common/infos.py +0 -30
- divi/exp/scipy/pyprima/common/linalg.py +0 -452
- divi/exp/scipy/pyprima/common/message.py +0 -336
- divi/exp/scipy/pyprima/common/powalg.py +0 -131
- divi/exp/scipy/pyprima/common/preproc.py +0 -393
- divi/exp/scipy/pyprima/common/present.py +0 -5
- divi/exp/scipy/pyprima/common/ratio.py +0 -56
- divi/exp/scipy/pyprima/common/redrho.py +0 -49
- divi/exp/scipy/pyprima/common/selectx.py +0 -346
- divi/interfaces.py +0 -25
- divi/parallel_simulator.py +0 -258
- divi/qlogger.py +0 -119
- divi/qoro_service.py +0 -343
- divi/qprog/_mlae.py +0 -182
- divi/qprog/_qaoa.py +0 -440
- divi/qprog/_vqe.py +0 -275
- divi/qprog/_vqe_sweep.py +0 -144
- divi/utils.py +0 -116
- qoro_divi-0.2.0b1.dist-info/RECORD +0 -58
- /divi/{qem.py → circuits/qem.py} +0 -0
- {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.5.0.dist-info/licenses}/LICENSE +0 -0
- {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.5.0.dist-info/licenses}/LICENSES/Apache-2.0.txt +0 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
from warnings import warn
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import numpy.typing as npt
|
|
10
|
+
import pennylane as qml
|
|
11
|
+
import sympy as sp
|
|
12
|
+
|
|
13
|
+
from divi.circuits import CircuitBundle, MetaCircuit
|
|
14
|
+
from divi.qprog._hamiltonians import _clean_hamiltonian
|
|
15
|
+
from divi.qprog.algorithms._ansatze import Ansatz, HartreeFockAnsatz
|
|
16
|
+
from divi.qprog.variational_quantum_algorithm import VariationalQuantumAlgorithm
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class VQE(VariationalQuantumAlgorithm):
|
|
20
|
+
"""Variational Quantum Eigensolver (VQE) implementation.
|
|
21
|
+
|
|
22
|
+
VQE is a hybrid quantum-classical algorithm used to find the ground state
|
|
23
|
+
energy of a given Hamiltonian. It works by preparing a parameterized quantum
|
|
24
|
+
state (ansatz) and optimizing the parameters to minimize the expectation
|
|
25
|
+
value of the Hamiltonian.
|
|
26
|
+
|
|
27
|
+
The algorithm can work with either:
|
|
28
|
+
- A molecular Hamiltonian (for quantum chemistry problems)
|
|
29
|
+
- A custom Hamiltonian operator
|
|
30
|
+
|
|
31
|
+
Attributes:
|
|
32
|
+
ansatz (Ansatz): The parameterized quantum circuit ansatz.
|
|
33
|
+
n_layers (int): Number of ansatz layers.
|
|
34
|
+
n_qubits (int): Number of qubits in the system.
|
|
35
|
+
n_electrons (int): Number of electrons (for molecular systems).
|
|
36
|
+
cost_hamiltonian (qml.operation.Operator): The Hamiltonian to minimize.
|
|
37
|
+
loss_constant (float): Constant term extracted from the Hamiltonian.
|
|
38
|
+
molecule (qml.qchem.Molecule): The molecule object (if applicable).
|
|
39
|
+
optimizer (Optimizer): Classical optimizer for parameter updates.
|
|
40
|
+
max_iterations (int): Maximum number of optimization iterations.
|
|
41
|
+
current_iteration (int): Current optimization iteration.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
hamiltonian: qml.operation.Operator | None = None,
|
|
47
|
+
molecule: qml.qchem.Molecule | None = None,
|
|
48
|
+
n_electrons: int | None = None,
|
|
49
|
+
n_layers: int = 1,
|
|
50
|
+
ansatz: Ansatz | None = None,
|
|
51
|
+
max_iterations=10,
|
|
52
|
+
**kwargs,
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Initialize the VQE problem.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
hamiltonian (qml.operation.Operator | None): A Hamiltonian representing the problem. Defaults to None.
|
|
58
|
+
molecule (qml.qchem.Molecule | None): The molecule representing the problem. Defaults to None.
|
|
59
|
+
n_electrons (int | None): Number of electrons associated with the Hamiltonian.
|
|
60
|
+
Only needed when a Hamiltonian is given. Defaults to None.
|
|
61
|
+
n_layers (int): Number of ansatz layers. Defaults to 1.
|
|
62
|
+
ansatz (Ansatz | None): The ansatz to use for the VQE problem.
|
|
63
|
+
Defaults to HartreeFockAnsatz.
|
|
64
|
+
max_iterations (int): Maximum number of optimization iterations. Defaults to 10.
|
|
65
|
+
**kwargs: Additional keyword arguments passed to the parent class.
|
|
66
|
+
"""
|
|
67
|
+
super().__init__(**kwargs)
|
|
68
|
+
|
|
69
|
+
self.ansatz = HartreeFockAnsatz() if ansatz is None else ansatz
|
|
70
|
+
self.n_layers = n_layers
|
|
71
|
+
self.results = {}
|
|
72
|
+
self.max_iterations = max_iterations
|
|
73
|
+
self.current_iteration = 0
|
|
74
|
+
|
|
75
|
+
self._eigenstate = None
|
|
76
|
+
|
|
77
|
+
self._process_problem_input(
|
|
78
|
+
hamiltonian=hamiltonian, molecule=molecule, n_electrons=n_electrons
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def cost_hamiltonian(self) -> qml.operation.Operator:
|
|
83
|
+
"""The cost Hamiltonian for the VQE problem."""
|
|
84
|
+
return self._cost_hamiltonian
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def n_params(self):
|
|
88
|
+
"""Get the total number of parameters for the VQE ansatz.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
int: Total number of parameters (n_params_per_layer * n_layers).
|
|
92
|
+
"""
|
|
93
|
+
return (
|
|
94
|
+
self.ansatz.n_params_per_layer(self.n_qubits, n_electrons=self.n_electrons)
|
|
95
|
+
* self.n_layers
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def eigenstate(self) -> npt.NDArray[np.int32] | None:
|
|
100
|
+
"""Get the computed eigenstate as a NumPy array.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
npt.NDArray[np.int32] | None: The array of bits of the lowest energy eigenstate,
|
|
104
|
+
or None if not computed.
|
|
105
|
+
"""
|
|
106
|
+
return self._eigenstate
|
|
107
|
+
|
|
108
|
+
def _process_problem_input(self, hamiltonian, molecule, n_electrons):
|
|
109
|
+
"""Process and validate the VQE problem input.
|
|
110
|
+
|
|
111
|
+
Handles both Hamiltonian-based and molecule-based problem specifications,
|
|
112
|
+
extracting the necessary information (n_qubits, n_electrons, hamiltonian).
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
hamiltonian: PennyLane Hamiltonian operator or None.
|
|
116
|
+
molecule: PennyLane Molecule object or None.
|
|
117
|
+
n_electrons: Number of electrons or None.
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
ValueError: If neither hamiltonian nor molecule is provided.
|
|
121
|
+
UserWarning: If n_electrons conflicts with the molecule's electron count.
|
|
122
|
+
"""
|
|
123
|
+
if hamiltonian is None and molecule is None:
|
|
124
|
+
raise ValueError(
|
|
125
|
+
"Either one of `molecule` and `hamiltonian` must be provided."
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if hamiltonian is not None:
|
|
129
|
+
self.n_qubits = len(hamiltonian.wires)
|
|
130
|
+
self.n_electrons = n_electrons
|
|
131
|
+
|
|
132
|
+
if molecule is not None:
|
|
133
|
+
self.molecule = molecule
|
|
134
|
+
hamiltonian, self.n_qubits = qml.qchem.molecular_hamiltonian(molecule)
|
|
135
|
+
self.n_electrons = molecule.n_electrons
|
|
136
|
+
|
|
137
|
+
if (n_electrons is not None) and self.n_electrons != n_electrons:
|
|
138
|
+
warn(
|
|
139
|
+
"`n_electrons` is provided but not consistent with the molecule's. "
|
|
140
|
+
f"Got {n_electrons}, but molecule has {self.n_electrons}. "
|
|
141
|
+
"The molecular value will be used.",
|
|
142
|
+
UserWarning,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
self._cost_hamiltonian, self.loss_constant = _clean_hamiltonian(hamiltonian)
|
|
146
|
+
if not self._cost_hamiltonian.operands:
|
|
147
|
+
raise ValueError("Hamiltonian contains only constant terms.")
|
|
148
|
+
|
|
149
|
+
def _create_meta_circuits_dict(self) -> dict[str, MetaCircuit]:
|
|
150
|
+
"""Create the meta-circuit dictionary for VQE.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
dict[str, MetaCircuit]: Dictionary containing the cost circuit template.
|
|
154
|
+
"""
|
|
155
|
+
weights_syms = sp.symarray(
|
|
156
|
+
"w",
|
|
157
|
+
(
|
|
158
|
+
self.n_layers,
|
|
159
|
+
self.ansatz.n_params_per_layer(
|
|
160
|
+
self.n_qubits, n_electrons=self.n_electrons
|
|
161
|
+
),
|
|
162
|
+
),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
ops = self.ansatz.build(
|
|
166
|
+
weights_syms,
|
|
167
|
+
n_qubits=self.n_qubits,
|
|
168
|
+
n_layers=self.n_layers,
|
|
169
|
+
n_electrons=self.n_electrons,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
"cost_circuit": self._meta_circuit_factory(
|
|
174
|
+
qml.tape.QuantumScript(
|
|
175
|
+
ops=ops, measurements=[qml.expval(self._cost_hamiltonian)]
|
|
176
|
+
),
|
|
177
|
+
symbols=weights_syms.flatten(),
|
|
178
|
+
),
|
|
179
|
+
"meas_circuit": self._meta_circuit_factory(
|
|
180
|
+
qml.tape.QuantumScript(ops=ops, measurements=[qml.probs()]),
|
|
181
|
+
symbols=weights_syms.flatten(),
|
|
182
|
+
grouping_strategy="wires",
|
|
183
|
+
),
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
def _generate_circuits(self) -> list[CircuitBundle]:
|
|
187
|
+
"""Generate the circuits for the VQE problem.
|
|
188
|
+
|
|
189
|
+
Generates circuits for each parameter set in the current parameters.
|
|
190
|
+
Each circuit is tagged with its parameter index for result processing.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
list[CircuitBundle]: List of CircuitBundle objects for execution.
|
|
194
|
+
"""
|
|
195
|
+
circuit_type = (
|
|
196
|
+
"cost_circuit" if not self._is_compute_probabilities else "meas_circuit"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
return [
|
|
200
|
+
self.meta_circuits[circuit_type].initialize_circuit_from_params(
|
|
201
|
+
params_group, tag_prefix=f"{p}"
|
|
202
|
+
)
|
|
203
|
+
for p, params_group in enumerate(self._curr_params)
|
|
204
|
+
]
|
|
205
|
+
|
|
206
|
+
def _perform_final_computation(self, **kwargs):
|
|
207
|
+
"""Extract the eigenstate corresponding to the lowest energy found."""
|
|
208
|
+
self.reporter.info(message="🏁 Computing Final Eigenstate 🏁", overwrite=True)
|
|
209
|
+
|
|
210
|
+
self._run_solution_measurement()
|
|
211
|
+
|
|
212
|
+
if self._best_probs:
|
|
213
|
+
best_measurement_probs = next(iter(self._best_probs.values()))
|
|
214
|
+
eigenstate_bitstring = max(
|
|
215
|
+
best_measurement_probs, key=best_measurement_probs.get
|
|
216
|
+
)
|
|
217
|
+
self._eigenstate = np.fromiter(eigenstate_bitstring, dtype=np.int32)
|
|
218
|
+
|
|
219
|
+
self.reporter.info(message="🏁 Computed Final Eigenstate! 🏁")
|
|
220
|
+
|
|
221
|
+
return self._total_circuit_count, self._total_run_time
|
|
222
|
+
|
|
223
|
+
def _save_subclass_state(self) -> dict[str, Any]:
|
|
224
|
+
"""Save VQE-specific runtime state."""
|
|
225
|
+
return {
|
|
226
|
+
"eigenstate": (
|
|
227
|
+
self._eigenstate.tolist() if self._eigenstate is not None else None
|
|
228
|
+
),
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
def _load_subclass_state(self, state: dict[str, Any]) -> None:
|
|
232
|
+
"""Load VQE-specific state.
|
|
233
|
+
|
|
234
|
+
Raises:
|
|
235
|
+
KeyError: If any required state key is missing (indicates checkpoint corruption).
|
|
236
|
+
"""
|
|
237
|
+
required_keys = ["eigenstate"]
|
|
238
|
+
missing_keys = [key for key in required_keys if key not in state]
|
|
239
|
+
if missing_keys:
|
|
240
|
+
raise KeyError(
|
|
241
|
+
f"Corrupted checkpoint: missing required state keys: {missing_keys}"
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# eigenstate can be None (if not computed yet), but the key must exist
|
|
245
|
+
eigenstate_list = state["eigenstate"]
|
|
246
|
+
if eigenstate_list is not None:
|
|
247
|
+
self._eigenstate = np.array(eigenstate_list, dtype=np.int32)
|
|
248
|
+
else:
|
|
249
|
+
self._eigenstate = None
|