qoro-divi 0.3.4__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.

@@ -143,11 +143,37 @@ class GenericLayerAnsatz(Ansatz):
143
143
 
144
144
 
145
145
  class QAOAAnsatz(Ansatz):
146
+ """
147
+ QAOA-style ansatz using PennyLane's QAOAEmbedding.
148
+
149
+ Implements a parameterized ansatz based on the Quantum Approximate Optimization
150
+ Algorithm structure, alternating between problem and mixer Hamiltonians.
151
+ """
152
+
146
153
  @staticmethod
147
154
  def n_params_per_layer(n_qubits: int, **kwargs) -> int:
155
+ """
156
+ Calculate the number of parameters per layer for QAOA ansatz.
157
+
158
+ Args:
159
+ n_qubits (int): Number of qubits in the circuit.
160
+ **kwargs: Additional unused arguments.
161
+
162
+ Returns:
163
+ int: Number of parameters needed per layer.
164
+ """
148
165
  return qml.QAOAEmbedding.shape(n_layers=1, n_wires=n_qubits)[1]
149
166
 
150
167
  def build(self, params, n_qubits: int, n_layers: int, **kwargs):
168
+ """
169
+ Build the QAOA ansatz circuit.
170
+
171
+ Args:
172
+ params: Parameter array to use for the ansatz.
173
+ n_qubits (int): Number of qubits.
174
+ n_layers (int): Number of QAOA layers.
175
+ **kwargs: Additional unused arguments.
176
+ """
151
177
  qml.QAOAEmbedding(
152
178
  features=[],
153
179
  weights=params.reshape(n_layers, -1),
@@ -156,11 +182,23 @@ class QAOAAnsatz(Ansatz):
156
182
 
157
183
 
158
184
  class HardwareEfficientAnsatz(Ansatz):
185
+ """
186
+ Hardware-efficient ansatz (not yet implemented).
187
+
188
+ This ansatz is designed to be easily implementable on near-term quantum hardware,
189
+ typically using native gate sets and connectivity patterns.
190
+
191
+ Note:
192
+ This class is a placeholder for future implementation.
193
+ """
194
+
159
195
  @staticmethod
160
196
  def n_params_per_layer(n_qubits: int, **kwargs) -> int:
197
+ """Not yet implemented."""
161
198
  raise NotImplementedError("HardwareEfficientAnsatz is not yet implemented.")
162
199
 
163
200
  def build(self, params, n_qubits: int, n_layers: int, **kwargs) -> None:
201
+ """Not yet implemented."""
164
202
  raise NotImplementedError("HardwareEfficientAnsatz is not yet implemented.")
165
203
 
166
204
 
@@ -168,13 +206,42 @@ class HardwareEfficientAnsatz(Ansatz):
168
206
 
169
207
 
170
208
  class UCCSDAnsatz(Ansatz):
209
+ """
210
+ Unitary Coupled Cluster Singles and Doubles (UCCSD) ansatz.
211
+
212
+ This ansatz is specifically designed for quantum chemistry calculations,
213
+ implementing the UCCSD approximation which includes all single and double
214
+ electron excitations from a reference state.
215
+ """
216
+
171
217
  @staticmethod
172
218
  def n_params_per_layer(n_qubits: int, n_electrons: int, **kwargs) -> int:
219
+ """
220
+ Calculate the number of parameters per layer for UCCSD ansatz.
221
+
222
+ Args:
223
+ n_qubits (int): Number of qubits in the circuit.
224
+ n_electrons (int): Number of electrons in the system.
225
+ **kwargs: Additional unused arguments.
226
+
227
+ Returns:
228
+ int: Number of parameters (number of single + double excitations).
229
+ """
173
230
  singles, doubles = qml.qchem.excitations(n_electrons, n_qubits)
174
231
  s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles)
175
232
  return len(s_wires) + len(d_wires)
176
233
 
177
234
  def build(self, params, n_qubits: int, n_layers: int, n_electrons: int, **kwargs):
235
+ """
236
+ Build the UCCSD ansatz circuit.
237
+
238
+ Args:
239
+ params: Parameter array for excitation amplitudes.
240
+ n_qubits (int): Number of qubits.
241
+ n_layers (int): Number of UCCSD layers (repeats).
242
+ n_electrons (int): Number of electrons in the system.
243
+ **kwargs: Additional unused arguments.
244
+ """
178
245
  singles, doubles = qml.qchem.excitations(n_electrons, n_qubits)
179
246
  s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles)
180
247
  hf_state = qml.qchem.hf_state(n_electrons, n_qubits)
@@ -190,12 +257,41 @@ class UCCSDAnsatz(Ansatz):
190
257
 
191
258
 
192
259
  class HartreeFockAnsatz(Ansatz):
260
+ """
261
+ Hartree-Fock-based ansatz for quantum chemistry.
262
+
263
+ This ansatz prepares the Hartree-Fock reference state and applies
264
+ parameterized single and double excitation gates. It's a simplified
265
+ alternative to UCCSD, often used as a starting point for VQE calculations.
266
+ """
267
+
193
268
  @staticmethod
194
269
  def n_params_per_layer(n_qubits: int, n_electrons: int, **kwargs) -> int:
270
+ """
271
+ Calculate the number of parameters per layer for Hartree-Fock ansatz.
272
+
273
+ Args:
274
+ n_qubits (int): Number of qubits in the circuit.
275
+ n_electrons (int): Number of electrons in the system.
276
+ **kwargs: Additional unused arguments.
277
+
278
+ Returns:
279
+ int: Number of parameters (number of single + double excitations).
280
+ """
195
281
  singles, doubles = qml.qchem.excitations(n_electrons, n_qubits)
196
282
  return len(singles) + len(doubles)
197
283
 
198
284
  def build(self, params, n_qubits: int, n_layers: int, n_electrons: int, **kwargs):
285
+ """
286
+ Build the Hartree-Fock ansatz circuit.
287
+
288
+ Args:
289
+ params: Parameter array for excitation amplitudes.
290
+ n_qubits (int): Number of qubits.
291
+ n_layers (int): Number of ansatz layers.
292
+ n_electrons (int): Number of electrons in the system.
293
+ **kwargs: Additional unused arguments.
294
+ """
199
295
  singles, doubles = qml.qchem.excitations(n_electrons, n_qubits)
200
296
  hf_state = qml.qchem.hf_state(n_electrons, n_qubits)
201
297
 
@@ -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()
@@ -228,10 +238,9 @@ class QAOA(QuantumProgram):
228
238
  self._is_compute_probabilites = False
229
239
  self.optimizer = optimizer if optimizer is not None else MonteCarloOptimizer()
230
240
 
231
- # Shared Variables
232
- self.probs = kwargs.pop("probs", {})
233
- self._solution_nodes = kwargs.pop("solution_nodes", [])
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__(has_final_computation=True, **kwargs)
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.circuits.append(circuit)
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.final_params)
409
+ self._curr_params = np.array(self._final_params)
371
410
 
372
- self.circuits[:] = []
411
+ self._circuits[:] = []
373
412
 
374
413
  self._generate_circuits()
375
414
 
376
- self.probs.update(self._dispatch_circuits_and_process_results())
415
+ self._probs.update(self._dispatch_circuits_and_process_results())
377
416
 
378
417
  self._is_compute_probabilites = False
379
418
 
380
- def compute_final_solution(self):
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
- # Find the key matching the best_solution_idx with possible metadata in between
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(best_solution_probs, key=best_solution_probs.get)[
429
- ::-1
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.compute_final_solution()
481
+ self._perform_final_computation()
454
482
 
455
483
  draw_graph_solution_nodes(self.problem, self._solution_nodes)
@@ -29,13 +29,20 @@ class VQE(QuantumProgram):
29
29
  Initialize the VQE problem.
30
30
 
31
31
  Args:
32
- hamiltonain (pennylane.operation.Operator, optional): A Hamiltonian representing the problem.
33
- molecule (pennylane.qchem.Molecule, optional): The molecule representing the problem.
34
- n_electrons (int, optional): Number of electrons associated with the Hamiltonian.
35
- Only needs to be provided when a Hamiltonian is given.
36
- ansatz (Ansatz): The ansatz to use for the VQE problem
37
- optimizer (Optimizers): The optimizer to use.
38
- max_iterations (int): Maximum number of iteration optimizers.
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.
39
46
  """
40
47
 
41
48
  # Local Variables
@@ -57,12 +64,33 @@ class VQE(QuantumProgram):
57
64
 
58
65
  @property
59
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
+ """
60
73
  return (
61
74
  self.ansatz.n_params_per_layer(self.n_qubits, n_electrons=self.n_electrons)
62
75
  * self.n_layers
63
76
  )
64
77
 
65
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
+ """
66
94
  if hamiltonian is None and molecule is None:
67
95
  raise ValueError(
68
96
  "Either one of `molecule` and `hamiltonian` must be provided."
@@ -175,9 +203,24 @@ class VQE(QuantumProgram):
175
203
  "cost_circuit"
176
204
  ].initialize_circuit_from_params(params_group, tag_prefix=f"{p}")
177
205
 
178
- self.circuits.append(circuit)
206
+ self._circuits.append(circuit)
179
207
 
180
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
+ """
181
224
  if self.cost_hamiltonian is None or len(self.cost_hamiltonian) == 0:
182
225
  raise RuntimeError(
183
226
  "Hamiltonian operators must be generated before running the VQE"