qoro-divi 0.3.3__tar.gz → 0.3.4__tar.gz

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.

Files changed (69) hide show
  1. {qoro_divi-0.3.3 → qoro_divi-0.3.4}/PKG-INFO +1 -1
  2. {qoro_divi-0.3.3 → qoro_divi-0.3.4}/divi/__init__.py +1 -2
  3. qoro_divi-0.3.4/divi/backends/__init__.py +7 -0
  4. qoro_divi-0.3.3/divi/parallel_simulator.py → qoro_divi-0.3.4/divi/backends/_parallel_simulator.py +4 -3
  5. qoro_divi-0.3.3/divi/qoro_service.py → qoro_divi-0.3.4/divi/backends/_qoro_service.py +5 -6
  6. qoro_divi-0.3.4/divi/circuits/__init__.py +5 -0
  7. qoro_divi-0.3.3/divi/circuits.py → qoro_divi-0.3.4/divi/circuits/_core.py +6 -20
  8. {qoro_divi-0.3.3/divi → qoro_divi-0.3.4/divi/circuits}/qasm.py +2 -2
  9. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/cirq/_validator.py +9 -7
  10. qoro_divi-0.3.4/divi/qprog/__init__.py +26 -0
  11. qoro_divi-0.3.4/divi/qprog/algorithms/__init__.py +14 -0
  12. qoro_divi-0.3.4/divi/qprog/algorithms/_ansatze.py +215 -0
  13. {qoro_divi-0.3.3/divi/qprog → qoro_divi-0.3.4/divi/qprog/algorithms}/_qaoa.py +1 -1
  14. {qoro_divi-0.3.3/divi/qprog → qoro_divi-0.3.4/divi/qprog/algorithms}/_vqe.py +29 -128
  15. {qoro_divi-0.3.3 → qoro_divi-0.3.4}/divi/qprog/batch.py +2 -4
  16. {qoro_divi-0.3.3 → qoro_divi-0.3.4}/divi/qprog/optimizers.py +1 -2
  17. {qoro_divi-0.3.3 → qoro_divi-0.3.4}/divi/qprog/quantum_program.py +16 -9
  18. qoro_divi-0.3.4/divi/qprog/workflows/__init__.py +10 -0
  19. {qoro_divi-0.3.3/divi/qprog → qoro_divi-0.3.4/divi/qprog/workflows}/_graph_partitioning.py +2 -2
  20. {qoro_divi-0.3.3/divi/qprog → qoro_divi-0.3.4/divi/qprog/workflows}/_qubo_partitioning.py +2 -2
  21. {qoro_divi-0.3.3/divi/qprog → qoro_divi-0.3.4/divi/qprog/workflows}/_vqe_sweep.py +32 -20
  22. qoro_divi-0.3.4/divi/reporting/__init__.py +7 -0
  23. qoro_divi-0.3.3/divi/qlogger.py → qoro_divi-0.3.4/divi/reporting/_qlogger.py +2 -1
  24. {qoro_divi-0.3.3 → qoro_divi-0.3.4}/divi/utils.py +14 -6
  25. {qoro_divi-0.3.3 → qoro_divi-0.3.4}/pyproject.toml +1 -1
  26. qoro_divi-0.3.3/divi/qprog/__init__.py +0 -13
  27. {qoro_divi-0.3.3 → qoro_divi-0.3.4}/LICENSE +0 -0
  28. {qoro_divi-0.3.3 → qoro_divi-0.3.4}/LICENSES/.license-header +0 -0
  29. {qoro_divi-0.3.3 → qoro_divi-0.3.4}/LICENSES/Apache-2.0.txt +0 -0
  30. {qoro_divi-0.3.3 → qoro_divi-0.3.4}/README.md +0 -0
  31. /qoro_divi-0.3.3/divi/interfaces.py → /qoro_divi-0.3.4/divi/backends/_circuit_runner.py +0 -0
  32. /qoro_divi-0.3.3/divi/qpu_system.py → /qoro_divi-0.3.4/divi/backends/_qpu_system.py +0 -0
  33. {qoro_divi-0.3.3/divi → qoro_divi-0.3.4/divi/circuits}/qem.py +0 -0
  34. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/cirq/__init__.py +0 -0
  35. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/cirq/_lexer.py +0 -0
  36. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/cirq/_parser.py +0 -0
  37. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/cirq/_qasm_export.py +0 -0
  38. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/cirq/_qasm_import.py +0 -0
  39. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/cirq/exception.py +0 -0
  40. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/_cobyla.py +0 -0
  41. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/LICENCE.txt +0 -0
  42. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/__init__.py +0 -0
  43. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/cobyla/__init__.py +0 -0
  44. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/cobyla/cobyla.py +0 -0
  45. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/cobyla/cobylb.py +0 -0
  46. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/cobyla/geometry.py +0 -0
  47. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/cobyla/initialize.py +0 -0
  48. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/cobyla/trustregion.py +0 -0
  49. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/cobyla/update.py +0 -0
  50. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/__init__.py +0 -0
  51. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/_bounds.py +0 -0
  52. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/_linear_constraints.py +0 -0
  53. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/_nonlinear_constraints.py +0 -0
  54. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/_project.py +0 -0
  55. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/checkbreak.py +0 -0
  56. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/consts.py +0 -0
  57. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/evaluate.py +0 -0
  58. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/history.py +0 -0
  59. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/infos.py +0 -0
  60. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/linalg.py +0 -0
  61. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/message.py +0 -0
  62. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/powalg.py +0 -0
  63. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/preproc.py +0 -0
  64. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/present.py +0 -0
  65. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/ratio.py +0 -0
  66. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/redrho.py +0 -0
  67. {qoro_divi-0.3.3/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/selectx.py +0 -0
  68. {qoro_divi-0.3.3/divi → qoro_divi-0.3.4/divi/reporting}/_pbar.py +0 -0
  69. /qoro_divi-0.3.3/divi/reporter.py → /qoro_divi-0.3.4/divi/reporting/_reporter.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: qoro-divi
3
- Version: 0.3.3
3
+ Version: 0.3.4
4
4
  Summary: A Python library to automate generating, parallelizing, and executing quantum programs.
5
5
  Author: Ahmed Darwish
6
6
  Author-email: ahmed@qoroquantum.de
@@ -2,7 +2,6 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
- from .qlogger import enable_logging
6
- from .qoro_service import QoroService
5
+ from .reporting import enable_logging
7
6
 
8
7
  enable_logging()
@@ -0,0 +1,7 @@
1
+ # SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from ._circuit_runner import CircuitRunner
6
+ from ._parallel_simulator import ParallelSimulator
7
+ from ._qoro_service import JobStatus, JobType, QoroService
@@ -18,7 +18,7 @@ from qiskit.providers import Backend
18
18
  from qiskit_aer import AerSimulator
19
19
  from qiskit_aer.noise import NoiseModel
20
20
 
21
- from divi.interfaces import CircuitRunner
21
+ from divi.backends import CircuitRunner
22
22
 
23
23
  logger = logging.getLogger(__name__)
24
24
 
@@ -74,8 +74,9 @@ class ParallelSimulator(CircuitRunner):
74
74
  n_processes (int, optional): Number of parallel processes to use for simulation. Defaults to 2.
75
75
  shots (int, optional): Number of shots to perform. Defaults to 5000.
76
76
  simulation_seed (int, optional): Seed for the random number generator to ensure reproducibility. Defaults to None.
77
- backend (Backend or "auto, optional): A Qiskit backend to initiate the simulator from. If "auto" is passed,
78
- the best-fit most recent fake backend will be chosen for the given circuit. Defaults to None, resulting in noiseless simulation.
77
+ qiskit_backend (Backend | Literal["auto"] | None, optional): A Qiskit backend to initiate the simulator from.
78
+ If "auto" is passed, the best-fit most recent fake backend will be chosen for the given circuit.
79
+ Defaults to None, resulting in noiseless simulation.
79
80
  noise_model (NoiseModel, optional): Qiskit noise model to use in simulation. Defaults to None.
80
81
  """
81
82
  super().__init__(shots=shots)
@@ -15,9 +15,9 @@ import requests
15
15
  from dotenv import dotenv_values
16
16
  from requests.adapters import HTTPAdapter, Retry
17
17
 
18
- from divi.exp.cirq import validate_qasm_raise
19
- from divi.interfaces import CircuitRunner
20
- from divi.qpu_system import QPU, QPUSystem
18
+ from divi.backends import CircuitRunner
19
+ from divi.backends._qpu_system import QPU, QPUSystem
20
+ from divi.extern.cirq import is_valid_qasm
21
21
 
22
22
  API_URL = "https://app.qoroquantum.net/api"
23
23
  MAX_PAYLOAD_SIZE_MB = 0.95
@@ -241,9 +241,8 @@ class QoroService(CircuitRunner):
241
241
  raise ValueError("Only one circuit allowed for circuit-cutting jobs.")
242
242
 
243
243
  for key, circuit in circuits.items():
244
- validate_qasm_raise(circuit)
245
- # if not is_valid_qasm(circuit):
246
- # raise ValueError(f"Circuit {key} is not a valid QASM string.")
244
+ if not (err := is_valid_qasm(circuit)):
245
+ raise ValueError(f"Circuit '{key}' is not a valid QASM: {err}")
247
246
 
248
247
  circuit_chunks = self._split_circuits(circuits)
249
248
 
@@ -0,0 +1,5 @@
1
+ # SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from ._core import Circuit, MetaCircuit
@@ -10,10 +10,9 @@ from typing import Literal
10
10
  import dill
11
11
  import pennylane as qml
12
12
  from pennylane.transforms.core.transform_program import TransformProgram
13
- from qiskit.qasm2 import dumps
14
13
 
15
- from divi.qasm import to_openqasm
16
- from divi.qem import QEMProtocol
14
+ from divi.circuits.qasm import to_openqasm
15
+ from divi.circuits.qem import QEMProtocol
17
16
 
18
17
  TRANSFORM_PROGRAM = TransformProgram()
19
18
  TRANSFORM_PROGRAM.add_transform(qml.transforms.split_to_single_terms)
@@ -30,35 +29,22 @@ class Circuit:
30
29
  qasm_circuits: list[str] = None,
31
30
  ):
32
31
  self.main_circuit = main_circuit
33
- self.circuit_type = main_circuit.__module__.split(".")[0]
34
32
  self.tags = tags
35
33
 
36
34
  self.qasm_circuits = qasm_circuits
37
35
 
38
36
  if self.qasm_circuits is None:
39
- self.convert_to_qasm()
40
-
41
- self.circuit_id = Circuit._id_counter
42
- Circuit._id_counter += 1
43
-
44
- def __str__(self):
45
- return f"Circuit: {self.circuit_id}"
46
-
47
- def convert_to_qasm(self):
48
- if self.circuit_type == "pennylane":
49
37
  self.qasm_circuits = to_openqasm(
50
38
  self.main_circuit,
51
39
  measurement_groups=[self.main_circuit.measurements],
52
40
  return_measurements_separately=False,
53
41
  )
54
42
 
55
- elif self.circuit_type == "qiskit":
56
- self.qasm_circuits = [dumps(self.main_circuit)]
43
+ self.circuit_id = Circuit._id_counter
44
+ Circuit._id_counter += 1
57
45
 
58
- else:
59
- raise ValueError(
60
- f"Invalid circuit type. Circuit type {self.circuit_type} not currently supported."
61
- )
46
+ def __str__(self):
47
+ return f"Circuit: {self.circuit_id}"
62
48
 
63
49
 
64
50
  class MetaCircuit:
@@ -14,8 +14,8 @@ from pennylane.tape import QuantumScript
14
14
  from pennylane.wires import Wires
15
15
  from sympy import Symbol
16
16
 
17
- from divi.exp.cirq import cirq_circuit_from_qasm
18
- from divi.qem import QEMProtocol
17
+ from divi.circuits.qem import QEMProtocol
18
+ from divi.extern.cirq import cirq_circuit_from_qasm
19
19
 
20
20
  OPENQASM_GATES = {
21
21
  "CNOT": "cx",
@@ -289,7 +289,12 @@ class Parser:
289
289
  # ---- gate definitions ----
290
290
  def gate_def(self):
291
291
  self.match("GATE")
292
- gname = self.match("ID").value
292
+ gname_tok = self.match("ID")
293
+ gname = gname_tok.value
294
+ if gname in BUILTINS:
295
+ raise SyntaxError(
296
+ f"Cannot redefine built-in gate '{gname}' at {gname_tok.line}:{gname_tok.col}"
297
+ )
293
298
  if gname in self.user_gates:
294
299
  self._dupe(gname)
295
300
  params: tuple[str, ...] = ()
@@ -570,7 +575,6 @@ class Parser:
570
575
  if t.type == "PI":
571
576
  self.match("PI")
572
577
  return
573
- # ---- NEW BLOCK TO HANDLE MATH FUNCTIONS ----
574
578
  if t.type in _MATH_FUNCS:
575
579
  self.match(t.type) # Consume the function name (e.g., COS)
576
580
  self.match("LPAREN")
@@ -578,13 +582,11 @@ class Parser:
578
582
  # Note: QASM 2.0 math functions only take one argument
579
583
  self.match("RPAREN")
580
584
  return
581
- # --------------------------------------------
582
585
  if t.type == "ID":
583
586
  # function call or plain ID
584
587
  id_tok = self.match("ID")
585
588
  ident = id_tok.value
586
589
  if self.accept("LPAREN"):
587
- # This now correctly handles user-defined functions (if any)
588
590
  if self.peek().type != "RPAREN":
589
591
  self._expr(allow_id)
590
592
  while self.accept("COMMA"):
@@ -637,9 +639,9 @@ def validate_qasm_raise(src: str) -> None:
637
639
  Parser(toks).parse()
638
640
 
639
641
 
640
- def is_valid_qasm(src: str) -> bool:
642
+ def is_valid_qasm(src: str) -> bool | str:
641
643
  try:
642
644
  validate_qasm_raise(src)
643
645
  return True
644
- except SyntaxError:
645
- return False
646
+ except SyntaxError as e:
647
+ return str(e)
@@ -0,0 +1,26 @@
1
+ # SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ # isort: skip_file
6
+ from .quantum_program import QuantumProgram
7
+ from .batch import ProgramBatch
8
+ from .algorithms import (
9
+ QAOA,
10
+ GraphProblem,
11
+ VQE,
12
+ Ansatz,
13
+ UCCSDAnsatz,
14
+ QAOAAnsatz,
15
+ HardwareEfficientAnsatz,
16
+ HartreeFockAnsatz,
17
+ GenericLayerAnsatz,
18
+ )
19
+ from .workflows import (
20
+ GraphPartitioningQAOA,
21
+ PartitioningConfig,
22
+ QUBOPartitioningQAOA,
23
+ VQEHyperparameterSweep,
24
+ MoleculeTransformer,
25
+ )
26
+ from .optimizers import ScipyOptimizer, ScipyMethod, MonteCarloOptimizer
@@ -0,0 +1,14 @@
1
+ # SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from ._ansatze import (
6
+ Ansatz,
7
+ GenericLayerAnsatz,
8
+ HardwareEfficientAnsatz,
9
+ HartreeFockAnsatz,
10
+ QAOAAnsatz,
11
+ UCCSDAnsatz,
12
+ )
13
+ from ._qaoa import QAOA, GraphProblem, GraphProblemTypes, QUBOProblemTypes
14
+ from ._vqe import VQE
@@ -0,0 +1,215 @@
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 ansaetze."""
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(self, params, n_qubits: int, n_layers: int, **kwargs):
29
+ """
30
+ Builds the ansatz circuit.
31
+
32
+ Args:
33
+ params (array): The parameters (weights) for the ansatz.
34
+ n_qubits (int): The number of qubits.
35
+ n_layers (int): The number of layers.
36
+ **kwargs: Additional arguments like n_electrons for chemistry ansaetze.
37
+ """
38
+ raise NotImplementedError
39
+
40
+
41
+ # --- Template Ansaetze ---
42
+
43
+
44
+ class GenericLayerAnsatz(Ansatz):
45
+ """
46
+ A flexible ansatz alternating single-qubit gates with optional entanglers.
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ gate_sequence: list[qml.operation.Operator],
52
+ entangler: qml.operation.Operator | None = None,
53
+ entangling_layout: (
54
+ Literal["linear", "brick", "circular", "all-to-all"]
55
+ | Sequence[tuple[int, int]]
56
+ | None
57
+ ) = None,
58
+ ):
59
+ """
60
+ Args:
61
+ gate_sequence (list[Callable]): List of one-qubit gate classes (e.g., qml.RY, qml.Rot).
62
+ entangler (Callable): Two-qubit entangling gate class (e.g., qml.CNOT, qml.CZ).
63
+ If None, no entanglement is applied.
64
+ entangling_layout (str): Layout for entangling layer ("linear", "all_to_all", etc.).
65
+ """
66
+ if not all(
67
+ issubclass(g, qml.operation.Operator) and g.num_wires == 1
68
+ for g in gate_sequence
69
+ ):
70
+ raise ValueError(
71
+ "All elements in gate_sequence must be PennyLane one-qubit gate classes."
72
+ )
73
+ self.gate_sequence = gate_sequence
74
+
75
+ if entangler not in (None, qml.CNOT, qml.CZ):
76
+ raise ValueError("Only qml.CNOT and qml.CZ are supported as entanglers.")
77
+ self.entangler = entangler
78
+
79
+ self.entangling_layout = entangling_layout
80
+ if entangler is None and self.entangling_layout is not None:
81
+ warn("`entangling_layout` provided but `entangler` is None.")
82
+ match self.entangling_layout:
83
+ case None | "linear":
84
+ self.entangling_layout = "linear"
85
+
86
+ self._layout_fn = lambda n_qubits: zip(
87
+ range(n_qubits), range(1, n_qubits)
88
+ )
89
+ case "brick":
90
+ self._layout_fn = lambda n_qubits: [
91
+ (i, i + 1) for r in range(2) for i in range(r, n_qubits - 1, 2)
92
+ ]
93
+ case "circular":
94
+ self._layout_fn = lambda n_qubits: zip(
95
+ range(n_qubits), [(i + 1) % n_qubits for i in range(n_qubits)]
96
+ )
97
+ case "all_to_all":
98
+ self._layout_fn = lambda n_qubits: (
99
+ (i, j) for i in range(n_qubits) for j in range(i + 1, n_qubits)
100
+ )
101
+ case _:
102
+ if not all(
103
+ isinstance(ent, tuple)
104
+ and len(ent) == 2
105
+ and isinstance(ent[0], int)
106
+ and isinstance(ent[1], int)
107
+ for ent in entangling_layout
108
+ ):
109
+ raise ValueError(
110
+ "entangling_layout must be 'linear', 'circular', "
111
+ "'all_to_all', or a Sequence of tuples of integers."
112
+ )
113
+
114
+ self._layout_fn = lambda _: entangling_layout
115
+
116
+ def n_params_per_layer(self, n_qubits: int, **kwargs) -> int:
117
+ """Total parameters = sum of gate.num_params per qubit per layer."""
118
+ per_qubit = sum(getattr(g, "num_params", 1) for g in self.gate_sequence)
119
+ return per_qubit * n_qubits
120
+
121
+ def build(self, params, n_qubits: int, n_layers: int, **kwargs):
122
+ # calculate how many params each gate needs per qubit
123
+ gate_param_counts = [getattr(g, "num_params", 1) for g in self.gate_sequence]
124
+ per_qubit = sum(gate_param_counts)
125
+
126
+ # reshape into [layers, qubits, per_qubit]
127
+ params = params.reshape(n_layers, n_qubits, per_qubit)
128
+ layout_gen = iter(tee(self._layout_fn(n_qubits), n_layers))
129
+
130
+ def _layer(layer_params, wires):
131
+ for w, qubit_params in zip(wires, layer_params):
132
+ idx = 0
133
+ for gate, n_p in zip(self.gate_sequence, gate_param_counts):
134
+ theta = qubit_params[idx : idx + n_p]
135
+ gate(*theta, wires=w)
136
+ idx += n_p
137
+
138
+ if self.entangler is not None:
139
+ for wire_a, wire_b in next(layout_gen):
140
+ self.entangler(wires=[wire_a, wire_b])
141
+
142
+ qml.layer(_layer, n_layers, params, wires=range(n_qubits))
143
+
144
+
145
+ class QAOAAnsatz(Ansatz):
146
+ @staticmethod
147
+ def n_params_per_layer(n_qubits: int, **kwargs) -> int:
148
+ return qml.QAOAEmbedding.shape(n_layers=1, n_wires=n_qubits)[1]
149
+
150
+ def build(self, params, n_qubits: int, n_layers: int, **kwargs):
151
+ qml.QAOAEmbedding(
152
+ features=[],
153
+ weights=params.reshape(n_layers, -1),
154
+ wires=range(n_qubits),
155
+ )
156
+
157
+
158
+ class HardwareEfficientAnsatz(Ansatz):
159
+ @staticmethod
160
+ def n_params_per_layer(n_qubits: int, **kwargs) -> int:
161
+ raise NotImplementedError("HardwareEfficientAnsatz is not yet implemented.")
162
+
163
+ def build(self, params, n_qubits: int, n_layers: int, **kwargs) -> None:
164
+ raise NotImplementedError("HardwareEfficientAnsatz is not yet implemented.")
165
+
166
+
167
+ # --- Chemistry Ansaetze ---
168
+
169
+
170
+ class UCCSDAnsatz(Ansatz):
171
+ @staticmethod
172
+ def n_params_per_layer(n_qubits: int, n_electrons: int, **kwargs) -> int:
173
+ singles, doubles = qml.qchem.excitations(n_electrons, n_qubits)
174
+ s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles)
175
+ return len(s_wires) + len(d_wires)
176
+
177
+ def build(self, params, n_qubits: int, n_layers: int, n_electrons: int, **kwargs):
178
+ singles, doubles = qml.qchem.excitations(n_electrons, n_qubits)
179
+ s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles)
180
+ hf_state = qml.qchem.hf_state(n_electrons, n_qubits)
181
+
182
+ qml.UCCSD(
183
+ params.reshape(n_layers, -1),
184
+ wires=range(n_qubits),
185
+ s_wires=s_wires,
186
+ d_wires=d_wires,
187
+ init_state=hf_state,
188
+ n_repeats=n_layers,
189
+ )
190
+
191
+
192
+ class HartreeFockAnsatz(Ansatz):
193
+ @staticmethod
194
+ def n_params_per_layer(n_qubits: int, n_electrons: int, **kwargs) -> int:
195
+ singles, doubles = qml.qchem.excitations(n_electrons, n_qubits)
196
+ return len(singles) + len(doubles)
197
+
198
+ def build(self, params, n_qubits: int, n_layers: int, n_electrons: int, **kwargs):
199
+ singles, doubles = qml.qchem.excitations(n_electrons, n_qubits)
200
+ hf_state = qml.qchem.hf_state(n_electrons, n_qubits)
201
+
202
+ qml.layer(
203
+ qml.AllSinglesDoubles,
204
+ n_layers,
205
+ params.reshape(n_layers, -1),
206
+ wires=range(n_qubits),
207
+ hf_state=hf_state,
208
+ singles=singles,
209
+ doubles=doubles,
210
+ )
211
+
212
+ # Reset the BasisState operations after the first layer
213
+ # for behaviour similar to UCCSD ansatz
214
+ for op in qml.QueuingManager.active_context().queue[1:]:
215
+ op._hyperparameters["hf_state"] = 0
@@ -224,7 +224,7 @@ class QAOA(QuantumProgram):
224
224
  self.n_layers = n_layers
225
225
  self.max_iterations = max_iterations
226
226
  self.current_iteration = 0
227
- self.n_params = 2
227
+ self._n_params = 2
228
228
  self._is_compute_probabilites = False
229
229
  self.optimizer = optimizer if optimizer is not None else MonteCarloOptimizer()
230
230
 
@@ -2,7 +2,6 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
- from enum import Enum
6
5
  from warnings import warn
7
6
 
8
7
  import pennylane as qml
@@ -10,38 +9,10 @@ import sympy as sp
10
9
 
11
10
  from divi.circuits import MetaCircuit
12
11
  from divi.qprog import QuantumProgram
12
+ from divi.qprog.algorithms._ansatze import Ansatz, HartreeFockAnsatz
13
13
  from divi.qprog.optimizers import MonteCarloOptimizer, Optimizer
14
14
 
15
15
 
16
- class VQEAnsatz(Enum):
17
- UCCSD = "UCCSD"
18
- RY = "RY"
19
- RYRZ = "RYRZ"
20
- HW_EFFICIENT = "HW_EFFICIENT"
21
- QAOA = "QAOA"
22
- HARTREE_FOCK = "HF"
23
-
24
- def describe(self):
25
- return self.name, self.value
26
-
27
- def n_params(self, n_qubits, **kwargs):
28
- if self in (VQEAnsatz.UCCSD, VQEAnsatz.HARTREE_FOCK):
29
- singles, doubles = qml.qchem.excitations(
30
- kwargs.pop("n_electrons"), n_qubits
31
- )
32
- s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles)
33
-
34
- return len(s_wires) + len(d_wires)
35
- elif self == VQEAnsatz.RY:
36
- return n_qubits
37
- elif self == VQEAnsatz.RYRZ:
38
- return 2 * n_qubits
39
- elif self == VQEAnsatz.HW_EFFICIENT:
40
- raise NotImplementedError
41
- elif self == VQEAnsatz.QAOA:
42
- return qml.QAOAEmbedding.shape(n_layers=1, n_wires=n_qubits)[1]
43
-
44
-
45
16
  class VQE(QuantumProgram):
46
17
  def __init__(
47
18
  self,
@@ -49,7 +20,7 @@ class VQE(QuantumProgram):
49
20
  molecule: qml.qchem.Molecule | None = None,
50
21
  n_electrons: int | None = None,
51
22
  n_layers: int = 1,
52
- ansatz=VQEAnsatz.HARTREE_FOCK,
23
+ ansatz: Ansatz | None = None,
53
24
  optimizer: Optimizer | None = None,
54
25
  max_iterations=10,
55
26
  **kwargs,
@@ -62,15 +33,15 @@ class VQE(QuantumProgram):
62
33
  molecule (pennylane.qchem.Molecule, optional): The molecule representing the problem.
63
34
  n_electrons (int, optional): Number of electrons associated with the Hamiltonian.
64
35
  Only needs to be provided when a Hamiltonian is given.
65
- ansatz (VQEAnsatz): The ansatz to use for the VQE problem
36
+ ansatz (Ansatz): The ansatz to use for the VQE problem
66
37
  optimizer (Optimizers): The optimizer to use.
67
38
  max_iterations (int): Maximum number of iteration optimizers.
68
39
  """
69
40
 
70
41
  # Local Variables
42
+ self.ansatz = HartreeFockAnsatz() if ansatz is None else ansatz
71
43
  self.n_layers = n_layers
72
44
  self.results = {}
73
- self.ansatz = ansatz
74
45
  self.max_iterations = max_iterations
75
46
  self.current_iteration = 0
76
47
 
@@ -84,6 +55,13 @@ class VQE(QuantumProgram):
84
55
 
85
56
  self._meta_circuits = self._create_meta_circuits_dict()
86
57
 
58
+ @property
59
+ def n_params(self):
60
+ return (
61
+ self.ansatz.n_params_per_layer(self.n_qubits, n_electrons=self.n_electrons)
62
+ * self.n_layers
63
+ )
64
+
87
65
  def _process_problem_input(self, hamiltonian, molecule, n_electrons):
88
66
  if hamiltonian is None and molecule is None:
89
67
  raise ValueError(
@@ -91,13 +69,8 @@ class VQE(QuantumProgram):
91
69
  )
92
70
 
93
71
  if hamiltonian is not None:
94
- if not isinstance(n_electrons, int) or n_electrons < 0:
95
- raise ValueError(
96
- f"`n_electrons` is expected to be a non-negative integer. Got {n_electrons}."
97
- )
98
-
99
- self.n_electrons = n_electrons
100
72
  self.n_qubits = len(hamiltonian.wires)
73
+ self.n_electrons = n_electrons
101
74
 
102
75
  if molecule is not None:
103
76
  self.molecule = molecule
@@ -112,10 +85,6 @@ class VQE(QuantumProgram):
112
85
  UserWarning,
113
86
  )
114
87
 
115
- self.n_params = self.ansatz.n_params(
116
- self.n_qubits, n_electrons=self.n_electrons
117
- )
118
-
119
88
  self.cost_hamiltonian = self._clean_hamiltonian(hamiltonian)
120
89
 
121
90
  def _clean_hamiltonian(
@@ -148,9 +117,17 @@ class VQE(QuantumProgram):
148
117
  return hamiltonian.simplify()
149
118
 
150
119
  def _create_meta_circuits_dict(self) -> dict[str, MetaCircuit]:
151
- weights_syms = sp.symarray("w", (self.n_layers, self.n_params))
120
+ weights_syms = sp.symarray(
121
+ "w",
122
+ (
123
+ self.n_layers,
124
+ self.ansatz.n_params_per_layer(
125
+ self.n_qubits, n_electrons=self.n_electrons
126
+ ),
127
+ ),
128
+ )
152
129
 
153
- def _prepare_circuit(ansatz, hamiltonian, params):
130
+ def _prepare_circuit(hamiltonian, params):
154
131
  """
155
132
  Prepare the circuit for the VQE problem.
156
133
  Args:
@@ -158,7 +135,12 @@ class VQE(QuantumProgram):
158
135
  hamiltonian (qml.Hamiltonian): The Hamiltonian to use
159
136
  params (list): The parameters to use for the ansatz
160
137
  """
161
- self._set_ansatz(ansatz, params)
138
+ self.ansatz.build(
139
+ params,
140
+ n_qubits=self.n_qubits,
141
+ n_layers=self.n_layers,
142
+ n_electrons=self.n_electrons,
143
+ )
162
144
 
163
145
  # Even though in principle we want to sample from a state,
164
146
  # we are applying an `expval` operation here to make it compatible
@@ -169,93 +151,12 @@ class VQE(QuantumProgram):
169
151
  return {
170
152
  "cost_circuit": self._meta_circuit_factory(
171
153
  qml.tape.make_qscript(_prepare_circuit)(
172
- self.ansatz, self.cost_hamiltonian, weights_syms
154
+ self.cost_hamiltonian, weights_syms
173
155
  ),
174
156
  symbols=weights_syms.flatten(),
175
157
  )
176
158
  }
177
159
 
178
- def _set_ansatz(self, ansatz: VQEAnsatz, params):
179
- """
180
- Set the ansatz for the VQE problem.
181
- Args:
182
- ansatz (Ansatze): The ansatz to use
183
- params (list): The parameters to use for the ansatz
184
- n_layers (int): The number of layers to use for the ansatz
185
- """
186
-
187
- def _add_hw_efficient_ansatz(params):
188
- raise NotImplementedError
189
-
190
- def _add_qaoa_ansatz(params):
191
- # This infers layers automatically from the parameters shape
192
- qml.QAOAEmbedding(
193
- features=[],
194
- weights=params.reshape(self.n_layers, -1),
195
- wires=range(self.n_qubits),
196
- )
197
-
198
- def _add_ry_ansatz(params):
199
- qml.layer(
200
- qml.AngleEmbedding,
201
- self.n_layers,
202
- params.reshape(self.n_layers, -1),
203
- wires=range(self.n_qubits),
204
- rotation="Y",
205
- )
206
-
207
- def _add_ryrz_ansatz(params):
208
- def _ryrz(params, wires):
209
- ry_rots, rz_rots = params.reshape(2, -1)
210
- qml.AngleEmbedding(ry_rots, wires=wires, rotation="Y")
211
- qml.AngleEmbedding(rz_rots, wires=wires, rotation="Z")
212
-
213
- qml.layer(
214
- _ryrz,
215
- self.n_layers,
216
- params.reshape(self.n_layers, -1),
217
- wires=range(self.n_qubits),
218
- )
219
-
220
- def _add_uccsd_ansatz(params):
221
- hf_state = qml.qchem.hf_state(self.n_electrons, self.n_qubits)
222
-
223
- singles, doubles = qml.qchem.excitations(self.n_electrons, self.n_qubits)
224
- s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles)
225
-
226
- qml.UCCSD(
227
- params.reshape(self.n_layers, -1),
228
- wires=range(self.n_qubits),
229
- s_wires=s_wires,
230
- d_wires=d_wires,
231
- init_state=hf_state,
232
- n_repeats=self.n_layers,
233
- )
234
-
235
- def _add_hartree_fock_ansatz(params):
236
- singles, doubles = qml.qchem.excitations(self.n_electrons, self.n_qubits)
237
- hf_state = qml.qchem.hf_state(self.n_electrons, self.n_qubits)
238
-
239
- qml.layer(
240
- qml.AllSinglesDoubles,
241
- self.n_layers,
242
- params.reshape(self.n_layers, -1),
243
- wires=range(self.n_qubits),
244
- hf_state=hf_state,
245
- singles=singles,
246
- doubles=doubles,
247
- )
248
-
249
- # Reset the BasisState operations after the first layer
250
- # for behaviour similar to UCCSD ansatz
251
- for op in qml.QueuingManager.active_context().queue[1:]:
252
- op._hyperparameters["hf_state"] = 0
253
-
254
- if ansatz in VQEAnsatz:
255
- locals()[f"_add_{ansatz.name.lower()}_ansatz"](params)
256
- else:
257
- raise ValueError(f"Invalid Ansatz Value. Got {ansatz}.")
258
-
259
160
  def _generate_circuits(self):
260
161
  """
261
162
  Generate the circuits for the VQE problem.
@@ -16,11 +16,9 @@ from warnings import warn
16
16
  from rich.console import Console
17
17
  from rich.progress import Progress, TaskID
18
18
 
19
- from divi._pbar import make_progress_bar
20
- from divi.interfaces import CircuitRunner
21
- from divi.parallel_simulator import ParallelSimulator
22
- from divi.qlogger import disable_logging
19
+ from divi.backends import CircuitRunner, ParallelSimulator
23
20
  from divi.qprog.quantum_program import QuantumProgram
21
+ from divi.reporting import disable_logging, make_progress_bar
24
22
 
25
23
 
26
24
  def queue_listener(
@@ -5,12 +5,11 @@
5
5
  from abc import ABC, abstractmethod
6
6
  from collections.abc import Callable
7
7
  from enum import Enum
8
- from typing import Any
9
8
 
10
9
  import numpy as np
11
10
  from scipy.optimize import OptimizeResult, minimize
12
11
 
13
- from divi.exp.scipy._cobyla import _minimize_cobyla as cobyla_fn
12
+ from divi.extern.scipy._cobyla import _minimize_cobyla as cobyla_fn
14
13
 
15
14
 
16
15
  class ScipyMethod(Enum):
@@ -13,13 +13,11 @@ import numpy as np
13
13
  from pennylane.measurements import ExpectationMP
14
14
  from scipy.optimize import OptimizeResult
15
15
 
16
- from divi import QoroService
16
+ from divi.backends import CircuitRunner, JobStatus, QoroService
17
17
  from divi.circuits import Circuit, MetaCircuit
18
- from divi.interfaces import CircuitRunner
19
- from divi.qem import _NoMitigation
20
- from divi.qoro_service import JobStatus
18
+ from divi.circuits.qem import _NoMitigation
21
19
  from divi.qprog.optimizers import ScipyMethod, ScipyOptimizer
22
- from divi.reporter import LoggingProgressReporter, QueueProgressReporter
20
+ from divi.reporting import LoggingProgressReporter, QueueProgressReporter
23
21
 
24
22
  logger = logging.getLogger(__name__)
25
23
 
@@ -145,6 +143,10 @@ class QuantumProgram(ABC):
145
143
  def meta_circuits(self):
146
144
  return self._meta_circuits
147
145
 
146
+ @property
147
+ def n_params(self):
148
+ return self._n_params
149
+
148
150
  @abstractmethod
149
151
  def _create_meta_circuits_dict(self) -> dict[str, MetaCircuit]:
150
152
  pass
@@ -282,13 +284,18 @@ class QuantumProgram(ABC):
282
284
  for shots_dicts, curr_measurement_group in zip(
283
285
  shots_by_qem_idx, measurement_groups
284
286
  ):
287
+ if hasattr(self, "cost_hamiltonian"):
288
+ wire_order = tuple(reversed(self.cost_hamiltonian.wires))
289
+ else:
290
+ wire_order = tuple(
291
+ reversed(range(len(next(iter(shots_dicts[0].keys())))))
292
+ )
293
+
285
294
  curr_marginal_results = []
286
295
  for observable in curr_measurement_group:
296
+
287
297
  intermediate_exp_values = [
288
- ExpectationMP(observable).process_counts(
289
- shots_dict,
290
- tuple(reversed(range(len(next(iter(shots_dict.keys())))))),
291
- )
298
+ ExpectationMP(observable).process_counts(shots_dict, wire_order)
292
299
  for shots_dict in shots_dicts
293
300
  ]
294
301
 
@@ -0,0 +1,10 @@
1
+ # SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from ._graph_partitioning import (
6
+ GraphPartitioningQAOA,
7
+ PartitioningConfig,
8
+ )
9
+ from ._qubo_partitioning import QUBOPartitioningQAOA
10
+ from ._vqe_sweep import MoleculeTransformer, VQEHyperparameterSweep
@@ -20,9 +20,9 @@ import scipy.sparse.linalg as spla
20
20
  from pymetis import part_graph
21
21
  from sklearn.cluster import SpectralClustering
22
22
 
23
- from divi.interfaces import CircuitRunner
23
+ from divi.backends import CircuitRunner
24
24
  from divi.qprog import QAOA, ProgramBatch, QuantumProgram
25
- from divi.qprog._qaoa import (
25
+ from divi.qprog.algorithms._qaoa import (
26
26
  _SUPPORTED_INITIAL_STATES_LITERAL,
27
27
  GraphProblem,
28
28
  GraphProblemTypes,
@@ -12,8 +12,8 @@ import numpy as np
12
12
  import scipy.sparse as sps
13
13
  from dimod import BinaryQuadraticModel
14
14
 
15
- from divi.interfaces import CircuitRunner
16
- from divi.qprog._qaoa import QAOA, QUBOProblemTypes
15
+ from divi.backends import CircuitRunner
16
+ from divi.qprog.algorithms import QAOA, QUBOProblemTypes
17
17
  from divi.qprog.batch import ProgramBatch
18
18
  from divi.qprog.optimizers import MonteCarloOptimizer, Optimizer
19
19
  from divi.qprog.quantum_program import QuantumProgram
@@ -14,7 +14,7 @@ import matplotlib.pyplot as plt
14
14
  import numpy as np
15
15
  import pennylane as qml
16
16
 
17
- from divi.qprog import VQE, ProgramBatch, VQEAnsatz
17
+ from divi.qprog import VQE, Ansatz, ProgramBatch
18
18
  from divi.qprog.optimizers import MonteCarloOptimizer, Optimizer
19
19
 
20
20
 
@@ -392,7 +392,7 @@ class VQEHyperparameterSweep(ProgramBatch):
392
392
 
393
393
  def __init__(
394
394
  self,
395
- ansatze: Sequence[VQEAnsatz],
395
+ ansatze: Sequence[Ansatz],
396
396
  molecule_transformer: MoleculeTransformer,
397
397
  optimizer: Optimizer | None = None,
398
398
  max_iterations: int = 10,
@@ -469,37 +469,49 @@ class VQEHyperparameterSweep(ProgramBatch):
469
469
  if self._executor is not None:
470
470
  self.join()
471
471
 
472
- data = []
473
- colors = ["blue", "g", "r", "c", "m", "y", "k"]
472
+ # Get the unique ansatz objects that were actually run
473
+ # Assumes `self.ansatze` is a list of the ansatz instances used.
474
+ unique_ansatze = self.ansatze
474
475
 
475
- ansatz_list = list(VQEAnsatz)
476
+ # Create a stable color mapping for each unique ansatz object
477
+ colors = ["blue", "g", "r", "c", "m", "y", "k"]
478
+ color_map = {
479
+ ansatz: colors[i % len(colors)] for i, ansatz in enumerate(unique_ansatze)
480
+ }
476
481
 
477
482
  if graph_type == "scatter":
478
- for ansatz, modifier in self.programs.keys():
483
+ # Plot each ansatz's results as a separate series for clarity
484
+ for ansatz in unique_ansatze:
485
+ modifiers = []
479
486
  min_energies = []
480
-
481
- curr_energies = self.programs[(ansatz, modifier)].losses[-1]
482
- min_energies.append(
483
- (
484
- modifier,
485
- min(curr_energies.values()),
486
- colors[ansatz_list.index(ansatz)],
487
- )
487
+ for modifier in self.molecule_transformer.bond_modifiers:
488
+ program_key = (ansatz, modifier)
489
+ if program_key in self.programs:
490
+ modifiers.append(modifier)
491
+ curr_energies = self.programs[program_key].losses[-1]
492
+ min_energies.append(min(curr_energies.values()))
493
+
494
+ # Use the new .name property for the label and the color_map
495
+ plt.scatter(
496
+ modifiers,
497
+ min_energies,
498
+ color=color_map[ansatz],
499
+ label=ansatz.name,
488
500
  )
489
- data.extend(min_energies)
490
-
491
- x, y, z = zip(*data)
492
- plt.scatter(x, y, color=z, label=ansatz)
493
501
 
494
502
  elif graph_type == "line":
495
- for ansatz in self.ansatze:
503
+ for ansatz in unique_ansatze:
496
504
  energies = []
497
505
  for modifier in self.molecule_transformer.bond_modifiers:
498
506
  energies.append(
499
507
  min(self.programs[(ansatz, modifier)].losses[-1].values())
500
508
  )
509
+
501
510
  plt.plot(
502
- self.molecule_transformer.bond_modifiers, energies, label=ansatz
511
+ self.molecule_transformer.bond_modifiers,
512
+ energies,
513
+ label=ansatz.name,
514
+ color=color_map[ansatz],
503
515
  )
504
516
 
505
517
  plt.xlabel(
@@ -0,0 +1,7 @@
1
+ # SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from ._pbar import make_progress_bar
6
+ from ._qlogger import disable_logging, enable_logging
7
+ from ._reporter import LoggingProgressReporter, ProgressReporter, QueueProgressReporter
@@ -103,7 +103,8 @@ def enable_logging(level=logging.INFO):
103
103
  root_logger = logging.getLogger(__name__.split(".")[0])
104
104
 
105
105
  formatter = logging.Formatter(
106
- "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
106
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
107
+ datefmt="%Y-%m-%d %H:%M:%S",
107
108
  )
108
109
 
109
110
  handler = OverwriteStreamHandler(sys.stdout)
@@ -35,9 +35,10 @@ def convert_qubo_matrix_to_pennylane_ising(
35
35
  ) -> tuple[qml.operation.Operator, float]:
36
36
  """Convert QUBO matrix to Ising Hamiltonian in Pennylane.
37
37
 
38
- The conversion follows the mapping:
39
- - QUBO variables x_i ∈ {0,1} map to Ising variables s_i ∈ {-1,1} via s_i = 2x_i - 1
40
- - This transforms a QUBO problem into an equivalent Ising problem
38
+ The conversion follows the mapping from QUBO variables x_i ∈ {0,1} to
39
+ Ising variables σ_i ∈ {-1,1} via the transformation x_i = (1 - σ_i)/2. This
40
+ transforms a QUBO minimization problem into an equivalent Ising minimization
41
+ problem.
41
42
 
42
43
  Args:
43
44
  qubo_matrix: The QUBO matrix Q where the objective is to minimize x^T Q x
@@ -56,17 +57,24 @@ def convert_qubo_matrix_to_pennylane_ising(
56
57
  is_sparse = sps.issparse(qubo_matrix)
57
58
  backend = sps if is_sparse else np
58
59
 
60
+ symmetrized_qubo = (qubo_matrix + qubo_matrix.T) / 2
61
+
59
62
  # Gather non-zero indices in the upper triangle of the matrix
60
63
  triu_matrix = backend.triu(
61
- qubo_matrix,
64
+ symmetrized_qubo,
62
65
  **(
63
66
  {"format": qubo_matrix.format if qubo_matrix.format != "coo" else "csc"}
64
67
  if is_sparse
65
68
  else {}
66
69
  ),
67
70
  )
68
- rows, cols = triu_matrix.nonzero()
69
- values = triu_matrix[rows, cols].A1 if is_sparse else triu_matrix[rows, cols]
71
+
72
+ if is_sparse:
73
+ coo_mat = triu_matrix.tocoo()
74
+ rows, cols, values = coo_mat.row, coo_mat.col, coo_mat.data
75
+ else:
76
+ rows, cols = triu_matrix.nonzero()
77
+ values = triu_matrix[rows, cols]
70
78
 
71
79
  n = qubo_matrix.shape[0]
72
80
  linear_terms = np.zeros(n)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "qoro-divi"
3
- version = "0.3.3"
3
+ version = "0.3.4"
4
4
  description = "A Python library to automate generating, parallelizing, and executing quantum programs."
5
5
  authors = ["Ahmed Darwish <ahmed@qoroquantum.de>", "Stephen DiAdamo <stephen@qoroquantum.de>"]
6
6
  readme = "README.md"
@@ -1,13 +0,0 @@
1
- # SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
2
- #
3
- # SPDX-License-Identifier: Apache-2.0
4
-
5
- # isort: skip_file
6
- from .quantum_program import QuantumProgram
7
- from .batch import ProgramBatch
8
- from ._qaoa import QAOA, GraphProblem
9
- from ._vqe import VQE, VQEAnsatz
10
- from ._graph_partitioning import GraphPartitioningQAOA, PartitioningConfig
11
- from ._qubo_partitioning import QUBOPartitioningQAOA
12
- from ._vqe_sweep import VQEHyperparameterSweep, MoleculeTransformer
13
- from .optimizers import ScipyOptimizer, ScipyMethod, MonteCarloOptimizer
File without changes
File without changes