qoro-divi 0.2.0b1__py3-none-any.whl → 0.6.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.
Files changed (92) hide show
  1. divi/__init__.py +1 -2
  2. divi/backends/__init__.py +10 -0
  3. divi/backends/_backend_properties_conversion.py +227 -0
  4. divi/backends/_circuit_runner.py +70 -0
  5. divi/backends/_execution_result.py +70 -0
  6. divi/backends/_parallel_simulator.py +486 -0
  7. divi/backends/_qoro_service.py +663 -0
  8. divi/backends/_qpu_system.py +101 -0
  9. divi/backends/_results_processing.py +133 -0
  10. divi/circuits/__init__.py +13 -0
  11. divi/{exp/cirq → circuits/_cirq}/__init__.py +1 -2
  12. divi/circuits/_cirq/_parser.py +110 -0
  13. divi/circuits/_cirq/_qasm_export.py +78 -0
  14. divi/circuits/_core.py +391 -0
  15. divi/{qasm.py → circuits/_qasm_conversion.py} +73 -14
  16. divi/circuits/_qasm_validation.py +694 -0
  17. divi/qprog/__init__.py +27 -8
  18. divi/qprog/_expectation.py +181 -0
  19. divi/qprog/_hamiltonians.py +281 -0
  20. divi/qprog/algorithms/__init__.py +16 -0
  21. divi/qprog/algorithms/_ansatze.py +368 -0
  22. divi/qprog/algorithms/_custom_vqa.py +263 -0
  23. divi/qprog/algorithms/_pce.py +262 -0
  24. divi/qprog/algorithms/_qaoa.py +579 -0
  25. divi/qprog/algorithms/_vqe.py +262 -0
  26. divi/qprog/batch.py +387 -74
  27. divi/qprog/checkpointing.py +556 -0
  28. divi/qprog/exceptions.py +9 -0
  29. divi/qprog/optimizers.py +1014 -43
  30. divi/qprog/quantum_program.py +243 -412
  31. divi/qprog/typing.py +62 -0
  32. divi/qprog/variational_quantum_algorithm.py +1208 -0
  33. divi/qprog/workflows/__init__.py +10 -0
  34. divi/qprog/{_graph_partitioning.py → workflows/_graph_partitioning.py} +139 -95
  35. divi/qprog/workflows/_qubo_partitioning.py +221 -0
  36. divi/qprog/workflows/_vqe_sweep.py +560 -0
  37. divi/reporting/__init__.py +7 -0
  38. divi/reporting/_pbar.py +127 -0
  39. divi/reporting/_qlogger.py +68 -0
  40. divi/reporting/_reporter.py +155 -0
  41. {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.6.0.dist-info}/METADATA +43 -15
  42. qoro_divi-0.6.0.dist-info/RECORD +47 -0
  43. {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.6.0.dist-info}/WHEEL +1 -1
  44. qoro_divi-0.6.0.dist-info/licenses/LICENSES/.license-header +3 -0
  45. divi/_pbar.py +0 -73
  46. divi/circuits.py +0 -139
  47. divi/exp/cirq/_lexer.py +0 -126
  48. divi/exp/cirq/_parser.py +0 -889
  49. divi/exp/cirq/_qasm_export.py +0 -37
  50. divi/exp/cirq/_qasm_import.py +0 -35
  51. divi/exp/cirq/exception.py +0 -21
  52. divi/exp/scipy/_cobyla.py +0 -342
  53. divi/exp/scipy/pyprima/LICENCE.txt +0 -28
  54. divi/exp/scipy/pyprima/__init__.py +0 -263
  55. divi/exp/scipy/pyprima/cobyla/__init__.py +0 -0
  56. divi/exp/scipy/pyprima/cobyla/cobyla.py +0 -599
  57. divi/exp/scipy/pyprima/cobyla/cobylb.py +0 -849
  58. divi/exp/scipy/pyprima/cobyla/geometry.py +0 -240
  59. divi/exp/scipy/pyprima/cobyla/initialize.py +0 -269
  60. divi/exp/scipy/pyprima/cobyla/trustregion.py +0 -540
  61. divi/exp/scipy/pyprima/cobyla/update.py +0 -331
  62. divi/exp/scipy/pyprima/common/__init__.py +0 -0
  63. divi/exp/scipy/pyprima/common/_bounds.py +0 -41
  64. divi/exp/scipy/pyprima/common/_linear_constraints.py +0 -46
  65. divi/exp/scipy/pyprima/common/_nonlinear_constraints.py +0 -64
  66. divi/exp/scipy/pyprima/common/_project.py +0 -224
  67. divi/exp/scipy/pyprima/common/checkbreak.py +0 -107
  68. divi/exp/scipy/pyprima/common/consts.py +0 -48
  69. divi/exp/scipy/pyprima/common/evaluate.py +0 -101
  70. divi/exp/scipy/pyprima/common/history.py +0 -39
  71. divi/exp/scipy/pyprima/common/infos.py +0 -30
  72. divi/exp/scipy/pyprima/common/linalg.py +0 -452
  73. divi/exp/scipy/pyprima/common/message.py +0 -336
  74. divi/exp/scipy/pyprima/common/powalg.py +0 -131
  75. divi/exp/scipy/pyprima/common/preproc.py +0 -393
  76. divi/exp/scipy/pyprima/common/present.py +0 -5
  77. divi/exp/scipy/pyprima/common/ratio.py +0 -56
  78. divi/exp/scipy/pyprima/common/redrho.py +0 -49
  79. divi/exp/scipy/pyprima/common/selectx.py +0 -346
  80. divi/interfaces.py +0 -25
  81. divi/parallel_simulator.py +0 -258
  82. divi/qlogger.py +0 -119
  83. divi/qoro_service.py +0 -343
  84. divi/qprog/_mlae.py +0 -182
  85. divi/qprog/_qaoa.py +0 -440
  86. divi/qprog/_vqe.py +0 -275
  87. divi/qprog/_vqe_sweep.py +0 -144
  88. divi/utils.py +0 -116
  89. qoro_divi-0.2.0b1.dist-info/RECORD +0 -58
  90. /divi/{qem.py → circuits/qem.py} +0 -0
  91. {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.6.0.dist-info/licenses}/LICENSE +0 -0
  92. {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.6.0.dist-info/licenses}/LICENSES/Apache-2.0.txt +0 -0
@@ -0,0 +1,262 @@
1
+ # SPDX-FileCopyrightText: 2025-2026 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, param_idx=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
+
209
+ This method performs the following steps:
210
+ 1. Executes measurement circuits with the best parameters (those that achieved the lowest loss).
211
+ 2. Retrieves the bitstring representing the eigenstate with the highest probability,
212
+ correcting for endianness.
213
+ 3. Converts the bitstring to a NumPy array of integers (int32) representing the eigenstate.
214
+ 4. Stores the eigenstate in the `_eigenstate` attribute.
215
+
216
+ Returns:
217
+ tuple[int, float]: A tuple containing:
218
+ - int: The total number of circuits executed.
219
+ - float: The total runtime of the optimization process.
220
+ """
221
+ self.reporter.info(message="🏁 Computing Final Eigenstate 🏁", overwrite=True)
222
+
223
+ self._run_solution_measurement()
224
+
225
+ if self._best_probs:
226
+ best_measurement_probs = next(iter(self._best_probs.values()))
227
+ eigenstate_bitstring = max(
228
+ best_measurement_probs, key=best_measurement_probs.get
229
+ )
230
+ self._eigenstate = np.fromiter(eigenstate_bitstring, dtype=np.int32)
231
+
232
+ self.reporter.info(message="🏁 Computed Final Eigenstate! 🏁")
233
+
234
+ return self._total_circuit_count, self._total_run_time
235
+
236
+ def _save_subclass_state(self) -> dict[str, Any]:
237
+ """Save VQE-specific runtime state."""
238
+ return {
239
+ "eigenstate": (
240
+ self._eigenstate.tolist() if self._eigenstate is not None else None
241
+ ),
242
+ }
243
+
244
+ def _load_subclass_state(self, state: dict[str, Any]) -> None:
245
+ """Load VQE-specific state.
246
+
247
+ Raises:
248
+ KeyError: If any required state key is missing (indicates checkpoint corruption).
249
+ """
250
+ required_keys = ["eigenstate"]
251
+ missing_keys = [key for key in required_keys if key not in state]
252
+ if missing_keys:
253
+ raise KeyError(
254
+ f"Corrupted checkpoint: missing required state keys: {missing_keys}"
255
+ )
256
+
257
+ # eigenstate can be None (if not computed yet), but the key must exist
258
+ eigenstate_list = state["eigenstate"]
259
+ if eigenstate_list is not None:
260
+ self._eigenstate = np.array(eigenstate_list, dtype=np.int32)
261
+ else:
262
+ self._eigenstate = None