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,356 @@
1
+ # SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from abc import ABC, abstractmethod
6
+ from itertools import tee
7
+ from typing import Literal, Sequence
8
+ from warnings import warn
9
+
10
+ import pennylane as qml
11
+
12
+
13
+ class Ansatz(ABC):
14
+ """Abstract base class for all VQE ansätze."""
15
+
16
+ @property
17
+ def name(self) -> str:
18
+ """Returns the human-readable name of the ansatz."""
19
+ return self.__class__.__name__
20
+
21
+ @staticmethod
22
+ @abstractmethod
23
+ def n_params_per_layer(n_qubits: int, **kwargs) -> int:
24
+ """Returns the number of parameters required by the ansatz for one layer."""
25
+ raise NotImplementedError
26
+
27
+ @abstractmethod
28
+ def build(
29
+ self, params, n_qubits: int, n_layers: int, **kwargs
30
+ ) -> list[qml.operation.Operator]:
31
+ """
32
+ Builds the ansatz circuit and returns a list of operations.
33
+
34
+ Args:
35
+ params: Parameter array for the ansatz.
36
+ n_qubits (int): Number of qubits in the circuit.
37
+ n_layers (int): Number of ansatz layers.
38
+ **kwargs: Additional arguments specific to the ansatz.
39
+
40
+ Returns:
41
+ list[qml.operation.Operator]: List of PennyLane operations representing the ansatz.
42
+ """
43
+ raise NotImplementedError
44
+
45
+
46
+ # --- Template Ansaetze ---
47
+
48
+
49
+ class GenericLayerAnsatz(Ansatz):
50
+ """
51
+ A flexible ansatz alternating single-qubit gates with optional entanglers.
52
+ """
53
+
54
+ def __init__(
55
+ self,
56
+ gate_sequence: list[qml.operation.Operator],
57
+ entangler: qml.operation.Operator | None = None,
58
+ entangling_layout: (
59
+ Literal["linear", "brick", "circular", "all-to-all"]
60
+ | Sequence[tuple[int, int]]
61
+ | None
62
+ ) = None,
63
+ ):
64
+ """
65
+ Args:
66
+ gate_sequence (list[Callable]): List of one-qubit gate classes (e.g., qml.RY, qml.Rot).
67
+ entangler (Callable): Two-qubit entangling gate class (e.g., qml.CNOT, qml.CZ).
68
+ If None, no entanglement is applied.
69
+ entangling_layout (str): Layout for entangling layer ("linear", "all_to_all", etc.).
70
+ """
71
+ if not all(
72
+ issubclass(g, qml.operation.Operator) and g.num_wires == 1
73
+ for g in gate_sequence
74
+ ):
75
+ raise ValueError(
76
+ "All elements in gate_sequence must be PennyLane one-qubit gate classes."
77
+ )
78
+ self.gate_sequence = gate_sequence
79
+
80
+ if entangler not in (None, qml.CNOT, qml.CZ):
81
+ raise ValueError("Only qml.CNOT and qml.CZ are supported as entanglers.")
82
+ self.entangler = entangler
83
+
84
+ self.entangling_layout = entangling_layout
85
+ if entangler is None and self.entangling_layout is not None:
86
+ warn("`entangling_layout` provided but `entangler` is None.")
87
+ match self.entangling_layout:
88
+ case None | "linear":
89
+ self.entangling_layout = "linear"
90
+
91
+ self._layout_fn = lambda n_qubits: zip(
92
+ range(n_qubits), range(1, n_qubits)
93
+ )
94
+ case "brick":
95
+ self._layout_fn = lambda n_qubits: [
96
+ (i, i + 1) for r in range(2) for i in range(r, n_qubits - 1, 2)
97
+ ]
98
+ case "circular":
99
+ self._layout_fn = lambda n_qubits: zip(
100
+ range(n_qubits), [(i + 1) % n_qubits for i in range(n_qubits)]
101
+ )
102
+ case "all_to_all":
103
+ self._layout_fn = lambda n_qubits: (
104
+ (i, j) for i in range(n_qubits) for j in range(i + 1, n_qubits)
105
+ )
106
+ case _:
107
+ if not all(
108
+ isinstance(ent, tuple)
109
+ and len(ent) == 2
110
+ and isinstance(ent[0], int)
111
+ and isinstance(ent[1], int)
112
+ for ent in entangling_layout
113
+ ):
114
+ raise ValueError(
115
+ "entangling_layout must be 'linear', 'circular', "
116
+ "'all_to_all', or a Sequence of tuples of integers."
117
+ )
118
+
119
+ self._layout_fn = lambda _: entangling_layout
120
+
121
+ def n_params_per_layer(self, n_qubits: int, **kwargs) -> int:
122
+ """Total parameters = sum of gate.num_params per qubit per layer."""
123
+ per_qubit = sum(getattr(g, "num_params", 1) for g in self.gate_sequence)
124
+ return per_qubit * n_qubits
125
+
126
+ def build(
127
+ self, params, n_qubits: int, n_layers: int, **kwargs
128
+ ) -> list[qml.operation.Operator]:
129
+ # calculate how many params each gate needs per qubit
130
+ gate_param_counts = [getattr(g, "num_params", 1) for g in self.gate_sequence]
131
+ per_qubit = sum(gate_param_counts)
132
+
133
+ # reshape into [layers, qubits, per_qubit]
134
+ params = params.reshape(n_layers, n_qubits, per_qubit)
135
+ layout_gen = iter(tee(self._layout_fn(n_qubits), n_layers))
136
+
137
+ operations = []
138
+ wires = list(range(n_qubits))
139
+
140
+ for layer_idx in range(n_layers):
141
+ layer_params = params[layer_idx]
142
+ # Single-qubit gates
143
+ for w, qubit_params in zip(wires, layer_params):
144
+ idx = 0
145
+ for gate, n_p in zip(self.gate_sequence, gate_param_counts):
146
+ theta = qubit_params[idx : idx + n_p]
147
+ if n_p == 0:
148
+ op = gate(wires=w)
149
+ elif n_p == 1:
150
+ op = gate(theta[0], wires=w)
151
+ else:
152
+ op = gate(*theta, wires=w)
153
+ operations.append(op)
154
+ idx += n_p
155
+
156
+ # Entangling gates
157
+ if self.entangler is not None:
158
+ for wire_a, wire_b in next(layout_gen):
159
+ op = self.entangler(wires=[wire_a, wire_b])
160
+ operations.append(op)
161
+
162
+ return operations
163
+
164
+
165
+ class QAOAAnsatz(Ansatz):
166
+ """
167
+ QAOA-style ansatz using PennyLane's QAOAEmbedding.
168
+
169
+ Implements a parameterized ansatz based on the Quantum Approximate Optimization
170
+ Algorithm structure, alternating between problem and mixer Hamiltonians.
171
+ """
172
+
173
+ @staticmethod
174
+ def n_params_per_layer(n_qubits: int, **kwargs) -> int:
175
+ """
176
+ Calculate the number of parameters per layer for QAOA ansatz.
177
+
178
+ Args:
179
+ n_qubits (int): Number of qubits in the circuit.
180
+ **kwargs: Additional unused arguments.
181
+
182
+ Returns:
183
+ int: Number of parameters needed per layer.
184
+ """
185
+ return qml.QAOAEmbedding.shape(n_layers=1, n_wires=n_qubits)[1]
186
+
187
+ def build(
188
+ self, params, n_qubits: int, n_layers: int, **kwargs
189
+ ) -> list[qml.operation.Operator]:
190
+ """
191
+ Build the QAOA ansatz circuit.
192
+
193
+ Args:
194
+ params: Parameter array to use for the ansatz.
195
+ n_qubits (int): Number of qubits.
196
+ n_layers (int): Number of QAOA layers.
197
+ **kwargs: Additional unused arguments.
198
+
199
+ Returns:
200
+ list[qml.operation.Operator]: List of operations representing the QAOA ansatz.
201
+ """
202
+ return qml.QAOAEmbedding.compute_decomposition(
203
+ features=[],
204
+ weights=params.reshape(n_layers, -1),
205
+ wires=range(n_qubits),
206
+ local_field=qml.RY,
207
+ )
208
+
209
+
210
+ class HardwareEfficientAnsatz(Ansatz):
211
+ """
212
+ Hardware-efficient ansatz (not yet implemented).
213
+
214
+ This ansatz is designed to be easily implementable on near-term quantum hardware,
215
+ typically using native gate sets and connectivity patterns.
216
+
217
+ Note:
218
+ This class is a placeholder for future implementation.
219
+ """
220
+
221
+ @staticmethod
222
+ def n_params_per_layer(n_qubits: int, **kwargs) -> int:
223
+ """Not yet implemented."""
224
+ raise NotImplementedError("HardwareEfficientAnsatz is not yet implemented.")
225
+
226
+ def build(
227
+ self, params, n_qubits: int, n_layers: int, **kwargs
228
+ ) -> list[qml.operation.Operator]:
229
+ """Not yet implemented."""
230
+ raise NotImplementedError("HardwareEfficientAnsatz is not yet implemented.")
231
+
232
+
233
+ # --- Chemistry Ansaetze ---
234
+
235
+
236
+ class UCCSDAnsatz(Ansatz):
237
+ """
238
+ Unitary Coupled Cluster Singles and Doubles (UCCSD) ansatz.
239
+
240
+ This ansatz is specifically designed for quantum chemistry calculations,
241
+ implementing the UCCSD approximation which includes all single and double
242
+ electron excitations from a reference state.
243
+ """
244
+
245
+ @staticmethod
246
+ def n_params_per_layer(n_qubits: int, n_electrons: int, **kwargs) -> int:
247
+ """
248
+ Calculate the number of parameters per layer for UCCSD ansatz.
249
+
250
+ Args:
251
+ n_qubits (int): Number of qubits in the circuit.
252
+ n_electrons (int): Number of electrons in the system.
253
+ **kwargs: Additional unused arguments.
254
+
255
+ Returns:
256
+ int: Number of parameters (number of single + double excitations).
257
+ """
258
+ singles, doubles = qml.qchem.excitations(n_electrons, n_qubits)
259
+ s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles)
260
+ return len(s_wires) + len(d_wires)
261
+
262
+ def build(
263
+ self, params, n_qubits: int, n_layers: int, **kwargs
264
+ ) -> list[qml.operation.Operator]:
265
+ """
266
+ Build the UCCSD ansatz circuit.
267
+
268
+ Args:
269
+ params: Parameter array for excitation amplitudes.
270
+ n_qubits (int): Number of qubits.
271
+ n_layers (int): Number of UCCSD layers (repeats).
272
+ **kwargs: Additional arguments:
273
+ n_electrons (int): Number of electrons in the system (required).
274
+
275
+ Returns:
276
+ list[qml.operation.Operator]: List of operations representing the UCCSD ansatz.
277
+ """
278
+ n_electrons = kwargs.pop("n_electrons")
279
+ singles, doubles = qml.qchem.excitations(n_electrons, n_qubits)
280
+ s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles)
281
+ hf_state = qml.qchem.hf_state(n_electrons, n_qubits)
282
+
283
+ return qml.UCCSD.compute_decomposition(
284
+ params.reshape(n_layers, -1),
285
+ wires=range(n_qubits),
286
+ s_wires=s_wires,
287
+ d_wires=d_wires,
288
+ init_state=hf_state,
289
+ n_repeats=n_layers,
290
+ )
291
+
292
+
293
+ class HartreeFockAnsatz(Ansatz):
294
+ """
295
+ Hartree-Fock-based ansatz for quantum chemistry.
296
+
297
+ This ansatz prepares the Hartree-Fock reference state and applies
298
+ parameterized single and double excitation gates. It's a simplified
299
+ alternative to UCCSD, often used as a starting point for VQE calculations.
300
+ """
301
+
302
+ @staticmethod
303
+ def n_params_per_layer(n_qubits: int, n_electrons: int, **kwargs) -> int:
304
+ """
305
+ Calculate the number of parameters per layer for Hartree-Fock ansatz.
306
+
307
+ Args:
308
+ n_qubits (int): Number of qubits in the circuit.
309
+ n_electrons (int): Number of electrons in the system.
310
+ **kwargs: Additional unused arguments.
311
+
312
+ Returns:
313
+ int: Number of parameters (number of single + double excitations).
314
+ """
315
+ singles, doubles = qml.qchem.excitations(n_electrons, n_qubits)
316
+ return len(singles) + len(doubles)
317
+
318
+ def build(
319
+ self, params, n_qubits: int, n_layers: int, **kwargs
320
+ ) -> list[qml.operation.Operator]:
321
+ """
322
+ Build the Hartree-Fock ansatz circuit.
323
+
324
+ Args:
325
+ params: Parameter array for excitation amplitudes.
326
+ n_qubits (int): Number of qubits.
327
+ n_layers (int): Number of ansatz layers.
328
+ **kwargs: Additional arguments:
329
+ n_electrons (int): Number of electrons in the system (required).
330
+
331
+ Returns:
332
+ list[qml.operation.Operator]: List of operations representing the Hartree-Fock ansatz.
333
+ """
334
+ n_electrons = kwargs.pop("n_electrons")
335
+ singles, doubles = qml.qchem.excitations(n_electrons, n_qubits)
336
+ hf_state = qml.qchem.hf_state(n_electrons, n_qubits)
337
+
338
+ operations = []
339
+ for layer_params in params.reshape(n_layers, -1):
340
+ operations.extend(
341
+ qml.AllSinglesDoubles.compute_decomposition(
342
+ layer_params,
343
+ wires=range(n_qubits),
344
+ hf_state=hf_state,
345
+ singles=singles,
346
+ doubles=doubles,
347
+ )
348
+ )
349
+
350
+ # Reset the BasisState operations after the first layer
351
+ # for behaviour similar to UCCSD ansatz
352
+ for op in operations[len(operations) // 2 :]:
353
+ if hasattr(op, "_hyperparameters") and "hf_state" in op._hyperparameters:
354
+ op._hyperparameters["hf_state"] = 0
355
+
356
+ return operations