qoro-divi 0.3.3__py3-none-any.whl → 0.3.4__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.

Files changed (69) hide show
  1. divi/__init__.py +1 -2
  2. divi/backends/__init__.py +7 -0
  3. divi/{parallel_simulator.py → backends/_parallel_simulator.py} +4 -3
  4. divi/{qoro_service.py → backends/_qoro_service.py} +5 -6
  5. divi/circuits/__init__.py +5 -0
  6. divi/{circuits.py → circuits/_core.py} +6 -20
  7. divi/{qasm.py → circuits/qasm.py} +2 -2
  8. divi/{exp → extern}/cirq/_validator.py +9 -7
  9. divi/qprog/__init__.py +18 -5
  10. divi/qprog/algorithms/__init__.py +14 -0
  11. divi/qprog/algorithms/_ansatze.py +215 -0
  12. divi/qprog/{_qaoa.py → algorithms/_qaoa.py} +1 -1
  13. divi/qprog/{_vqe.py → algorithms/_vqe.py} +29 -128
  14. divi/qprog/batch.py +2 -4
  15. divi/qprog/optimizers.py +1 -2
  16. divi/qprog/quantum_program.py +16 -9
  17. divi/qprog/workflows/__init__.py +10 -0
  18. divi/qprog/{_graph_partitioning.py → workflows/_graph_partitioning.py} +2 -2
  19. divi/qprog/{_qubo_partitioning.py → workflows/_qubo_partitioning.py} +2 -2
  20. divi/qprog/{_vqe_sweep.py → workflows/_vqe_sweep.py} +32 -20
  21. divi/reporting/__init__.py +7 -0
  22. divi/{qlogger.py → reporting/_qlogger.py} +2 -1
  23. divi/utils.py +14 -6
  24. {qoro_divi-0.3.3.dist-info → qoro_divi-0.3.4.dist-info}/METADATA +1 -1
  25. qoro_divi-0.3.4.dist-info/RECORD +68 -0
  26. qoro_divi-0.3.3.dist-info/RECORD +0 -62
  27. /divi/{interfaces.py → backends/_circuit_runner.py} +0 -0
  28. /divi/{qpu_system.py → backends/_qpu_system.py} +0 -0
  29. /divi/{qem.py → circuits/qem.py} +0 -0
  30. /divi/{exp → extern}/cirq/__init__.py +0 -0
  31. /divi/{exp → extern}/cirq/_lexer.py +0 -0
  32. /divi/{exp → extern}/cirq/_parser.py +0 -0
  33. /divi/{exp → extern}/cirq/_qasm_export.py +0 -0
  34. /divi/{exp → extern}/cirq/_qasm_import.py +0 -0
  35. /divi/{exp → extern}/cirq/exception.py +0 -0
  36. /divi/{exp → extern}/scipy/_cobyla.py +0 -0
  37. /divi/{exp → extern}/scipy/pyprima/LICENCE.txt +0 -0
  38. /divi/{exp → extern}/scipy/pyprima/__init__.py +0 -0
  39. /divi/{exp → extern}/scipy/pyprima/cobyla/__init__.py +0 -0
  40. /divi/{exp → extern}/scipy/pyprima/cobyla/cobyla.py +0 -0
  41. /divi/{exp → extern}/scipy/pyprima/cobyla/cobylb.py +0 -0
  42. /divi/{exp → extern}/scipy/pyprima/cobyla/geometry.py +0 -0
  43. /divi/{exp → extern}/scipy/pyprima/cobyla/initialize.py +0 -0
  44. /divi/{exp → extern}/scipy/pyprima/cobyla/trustregion.py +0 -0
  45. /divi/{exp → extern}/scipy/pyprima/cobyla/update.py +0 -0
  46. /divi/{exp → extern}/scipy/pyprima/common/__init__.py +0 -0
  47. /divi/{exp → extern}/scipy/pyprima/common/_bounds.py +0 -0
  48. /divi/{exp → extern}/scipy/pyprima/common/_linear_constraints.py +0 -0
  49. /divi/{exp → extern}/scipy/pyprima/common/_nonlinear_constraints.py +0 -0
  50. /divi/{exp → extern}/scipy/pyprima/common/_project.py +0 -0
  51. /divi/{exp → extern}/scipy/pyprima/common/checkbreak.py +0 -0
  52. /divi/{exp → extern}/scipy/pyprima/common/consts.py +0 -0
  53. /divi/{exp → extern}/scipy/pyprima/common/evaluate.py +0 -0
  54. /divi/{exp → extern}/scipy/pyprima/common/history.py +0 -0
  55. /divi/{exp → extern}/scipy/pyprima/common/infos.py +0 -0
  56. /divi/{exp → extern}/scipy/pyprima/common/linalg.py +0 -0
  57. /divi/{exp → extern}/scipy/pyprima/common/message.py +0 -0
  58. /divi/{exp → extern}/scipy/pyprima/common/powalg.py +0 -0
  59. /divi/{exp → extern}/scipy/pyprima/common/preproc.py +0 -0
  60. /divi/{exp → extern}/scipy/pyprima/common/present.py +0 -0
  61. /divi/{exp → extern}/scipy/pyprima/common/ratio.py +0 -0
  62. /divi/{exp → extern}/scipy/pyprima/common/redrho.py +0 -0
  63. /divi/{exp → extern}/scipy/pyprima/common/selectx.py +0 -0
  64. /divi/{_pbar.py → reporting/_pbar.py} +0 -0
  65. /divi/{reporter.py → reporting/_reporter.py} +0 -0
  66. {qoro_divi-0.3.3.dist-info → qoro_divi-0.3.4.dist-info}/LICENSE +0 -0
  67. {qoro_divi-0.3.3.dist-info → qoro_divi-0.3.4.dist-info}/LICENSES/.license-header +0 -0
  68. {qoro_divi-0.3.3.dist-info → qoro_divi-0.3.4.dist-info}/LICENSES/Apache-2.0.txt +0 -0
  69. {qoro_divi-0.3.3.dist-info → qoro_divi-0.3.4.dist-info}/WHEEL +0 -0
divi/__init__.py CHANGED
@@ -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)
divi/qprog/__init__.py CHANGED
@@ -5,9 +5,22 @@
5
5
  # isort: skip_file
6
6
  from .quantum_program import QuantumProgram
7
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
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
+ )
13
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