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.
Files changed (88) hide show
  1. divi/__init__.py +1 -2
  2. divi/backends/__init__.py +9 -0
  3. divi/backends/_circuit_runner.py +70 -0
  4. divi/backends/_execution_result.py +70 -0
  5. divi/backends/_parallel_simulator.py +486 -0
  6. divi/backends/_qoro_service.py +663 -0
  7. divi/backends/_qpu_system.py +101 -0
  8. divi/backends/_results_processing.py +133 -0
  9. divi/circuits/__init__.py +8 -0
  10. divi/{exp/cirq → circuits/_cirq}/__init__.py +1 -2
  11. divi/circuits/_cirq/_parser.py +110 -0
  12. divi/circuits/_cirq/_qasm_export.py +78 -0
  13. divi/circuits/_core.py +369 -0
  14. divi/{qasm.py → circuits/_qasm_conversion.py} +73 -14
  15. divi/circuits/_qasm_validation.py +694 -0
  16. divi/qprog/__init__.py +24 -6
  17. divi/qprog/_expectation.py +181 -0
  18. divi/qprog/_hamiltonians.py +281 -0
  19. divi/qprog/algorithms/__init__.py +14 -0
  20. divi/qprog/algorithms/_ansatze.py +356 -0
  21. divi/qprog/algorithms/_qaoa.py +572 -0
  22. divi/qprog/algorithms/_vqe.py +249 -0
  23. divi/qprog/batch.py +383 -73
  24. divi/qprog/checkpointing.py +556 -0
  25. divi/qprog/exceptions.py +9 -0
  26. divi/qprog/optimizers.py +1014 -43
  27. divi/qprog/quantum_program.py +231 -413
  28. divi/qprog/variational_quantum_algorithm.py +995 -0
  29. divi/qprog/workflows/__init__.py +10 -0
  30. divi/qprog/{_graph_partitioning.py → workflows/_graph_partitioning.py} +139 -95
  31. divi/qprog/workflows/_qubo_partitioning.py +220 -0
  32. divi/qprog/workflows/_vqe_sweep.py +560 -0
  33. divi/reporting/__init__.py +7 -0
  34. divi/reporting/_pbar.py +127 -0
  35. divi/reporting/_qlogger.py +68 -0
  36. divi/reporting/_reporter.py +133 -0
  37. {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.5.0.dist-info}/METADATA +43 -15
  38. qoro_divi-0.5.0.dist-info/RECORD +43 -0
  39. {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.5.0.dist-info}/WHEEL +1 -1
  40. qoro_divi-0.5.0.dist-info/licenses/LICENSES/.license-header +3 -0
  41. divi/_pbar.py +0 -73
  42. divi/circuits.py +0 -139
  43. divi/exp/cirq/_lexer.py +0 -126
  44. divi/exp/cirq/_parser.py +0 -889
  45. divi/exp/cirq/_qasm_export.py +0 -37
  46. divi/exp/cirq/_qasm_import.py +0 -35
  47. divi/exp/cirq/exception.py +0 -21
  48. divi/exp/scipy/_cobyla.py +0 -342
  49. divi/exp/scipy/pyprima/LICENCE.txt +0 -28
  50. divi/exp/scipy/pyprima/__init__.py +0 -263
  51. divi/exp/scipy/pyprima/cobyla/__init__.py +0 -0
  52. divi/exp/scipy/pyprima/cobyla/cobyla.py +0 -599
  53. divi/exp/scipy/pyprima/cobyla/cobylb.py +0 -849
  54. divi/exp/scipy/pyprima/cobyla/geometry.py +0 -240
  55. divi/exp/scipy/pyprima/cobyla/initialize.py +0 -269
  56. divi/exp/scipy/pyprima/cobyla/trustregion.py +0 -540
  57. divi/exp/scipy/pyprima/cobyla/update.py +0 -331
  58. divi/exp/scipy/pyprima/common/__init__.py +0 -0
  59. divi/exp/scipy/pyprima/common/_bounds.py +0 -41
  60. divi/exp/scipy/pyprima/common/_linear_constraints.py +0 -46
  61. divi/exp/scipy/pyprima/common/_nonlinear_constraints.py +0 -64
  62. divi/exp/scipy/pyprima/common/_project.py +0 -224
  63. divi/exp/scipy/pyprima/common/checkbreak.py +0 -107
  64. divi/exp/scipy/pyprima/common/consts.py +0 -48
  65. divi/exp/scipy/pyprima/common/evaluate.py +0 -101
  66. divi/exp/scipy/pyprima/common/history.py +0 -39
  67. divi/exp/scipy/pyprima/common/infos.py +0 -30
  68. divi/exp/scipy/pyprima/common/linalg.py +0 -452
  69. divi/exp/scipy/pyprima/common/message.py +0 -336
  70. divi/exp/scipy/pyprima/common/powalg.py +0 -131
  71. divi/exp/scipy/pyprima/common/preproc.py +0 -393
  72. divi/exp/scipy/pyprima/common/present.py +0 -5
  73. divi/exp/scipy/pyprima/common/ratio.py +0 -56
  74. divi/exp/scipy/pyprima/common/redrho.py +0 -49
  75. divi/exp/scipy/pyprima/common/selectx.py +0 -346
  76. divi/interfaces.py +0 -25
  77. divi/parallel_simulator.py +0 -258
  78. divi/qlogger.py +0 -119
  79. divi/qoro_service.py +0 -343
  80. divi/qprog/_mlae.py +0 -182
  81. divi/qprog/_qaoa.py +0 -440
  82. divi/qprog/_vqe.py +0 -275
  83. divi/qprog/_vqe_sweep.py +0 -144
  84. divi/utils.py +0 -116
  85. qoro_divi-0.2.0b1.dist-info/RECORD +0 -58
  86. /divi/{qem.py → circuits/qem.py} +0 -0
  87. {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.5.0.dist-info/licenses}/LICENSE +0 -0
  88. {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