qoro-divi 0.5.0__py3-none-any.whl → 0.6.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.
- divi/backends/__init__.py +2 -1
- divi/backends/_backend_properties_conversion.py +227 -0
- divi/backends/_parallel_simulator.py +7 -7
- divi/circuits/__init__.py +8 -3
- divi/circuits/_core.py +36 -14
- divi/qprog/__init__.py +4 -3
- divi/qprog/algorithms/__init__.py +4 -2
- divi/qprog/algorithms/_ansatze.py +18 -6
- divi/qprog/algorithms/_custom_vqa.py +263 -0
- divi/qprog/algorithms/_pce.py +262 -0
- divi/qprog/algorithms/_qaoa.py +43 -36
- divi/qprog/algorithms/_vqe.py +16 -3
- divi/qprog/batch.py +5 -2
- divi/qprog/quantum_program.py +15 -2
- divi/qprog/typing.py +62 -0
- divi/qprog/variational_quantum_algorithm.py +283 -70
- divi/qprog/workflows/_qubo_partitioning.py +3 -2
- divi/reporting/_reporter.py +23 -1
- {qoro_divi-0.5.0.dist-info → qoro_divi-0.6.0.dist-info}/METADATA +5 -5
- {qoro_divi-0.5.0.dist-info → qoro_divi-0.6.0.dist-info}/RECORD +24 -20
- {qoro_divi-0.5.0.dist-info → qoro_divi-0.6.0.dist-info}/WHEEL +0 -0
- {qoro_divi-0.5.0.dist-info → qoro_divi-0.6.0.dist-info}/licenses/LICENSE +0 -0
- {qoro_divi-0.5.0.dist-info → qoro_divi-0.6.0.dist-info}/licenses/LICENSES/.license-header +0 -0
- {qoro_divi-0.5.0.dist-info → qoro_divi-0.6.0.dist-info}/licenses/LICENSES/Apache-2.0.txt +0 -0
divi/backends/__init__.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
1
|
+
# SPDX-FileCopyrightText: 2025-2026 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
5
|
+
from ._backend_properties_conversion import create_backend_from_properties
|
|
5
6
|
from ._circuit_runner import CircuitRunner
|
|
6
7
|
from ._execution_result import ExecutionResult
|
|
7
8
|
from ._parallel_simulator import ParallelSimulator
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025-2026 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""Utilities for working with Qiskit BackendProperties and BackendV2 conversion."""
|
|
6
|
+
|
|
7
|
+
import datetime
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from qiskit.providers.fake_provider import GenericBackendV2
|
|
11
|
+
from qiskit_ibm_runtime.models.backend_properties import BackendProperties
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _normalize_properties(
|
|
15
|
+
properties: dict[str, Any],
|
|
16
|
+
default_date: datetime.datetime | None = None,
|
|
17
|
+
) -> dict[str, Any]:
|
|
18
|
+
"""
|
|
19
|
+
Preprocess an incomplete BackendProperties dictionary by filling in missing
|
|
20
|
+
required fields with sensible defaults.
|
|
21
|
+
|
|
22
|
+
This function makes it easier to create BackendProperties dictionaries by
|
|
23
|
+
allowing you to omit fields that have obvious defaults, such as:
|
|
24
|
+
- Missing top-level fields: `backend_name`, `backend_version`, `last_update_date`
|
|
25
|
+
- Missing `unit` field for dimensionless parameters (e.g., gate_error)
|
|
26
|
+
- Missing `general` field (empty list)
|
|
27
|
+
- Missing `gates` field (empty list)
|
|
28
|
+
- Missing `qubits` field (empty list)
|
|
29
|
+
- Missing `date` fields in Nduv objects
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
properties: Incomplete BackendProperties dictionary. Can omit:
|
|
33
|
+
- `unit` field in parameter/qubit Nduv objects (defaults to "" for
|
|
34
|
+
dimensionless quantities like gate_error, or inferred from name)
|
|
35
|
+
- `general` field (defaults to empty list)
|
|
36
|
+
- `gates` field (defaults to empty list)
|
|
37
|
+
- `qubits` field (defaults to empty list)
|
|
38
|
+
- `date` field in Nduv objects (defaults to current time or provided default)
|
|
39
|
+
default_date: Optional datetime to use for missing date fields.
|
|
40
|
+
If None, uses current time.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Complete BackendProperties dictionary ready for BackendProperties.from_dict()
|
|
44
|
+
|
|
45
|
+
Example:
|
|
46
|
+
>>> props = {
|
|
47
|
+
... "backend_name": "test",
|
|
48
|
+
... "gates": [{
|
|
49
|
+
... "gate": "sx",
|
|
50
|
+
... "qubits": [0],
|
|
51
|
+
... "parameters": [{
|
|
52
|
+
... "name": "gate_error",
|
|
53
|
+
... "value": 0.01,
|
|
54
|
+
... # unit and date will be added automatically
|
|
55
|
+
... }]
|
|
56
|
+
... }]
|
|
57
|
+
... }
|
|
58
|
+
>>> normalized = _normalize_properties(props)
|
|
59
|
+
>>> backend_props = BackendProperties.from_dict(normalized)
|
|
60
|
+
"""
|
|
61
|
+
if default_date is None:
|
|
62
|
+
default_date = datetime.datetime.now()
|
|
63
|
+
|
|
64
|
+
# Create a shallow copy to avoid mutating the input
|
|
65
|
+
# (nested structures are rebuilt below to ensure no mutation)
|
|
66
|
+
normalized = properties.copy()
|
|
67
|
+
|
|
68
|
+
# Add missing required top-level fields
|
|
69
|
+
if "backend_name" not in normalized:
|
|
70
|
+
normalized["backend_name"] = "custom_backend"
|
|
71
|
+
if "backend_version" not in normalized:
|
|
72
|
+
normalized["backend_version"] = "1.0.0"
|
|
73
|
+
if "last_update_date" not in normalized:
|
|
74
|
+
normalized["last_update_date"] = default_date
|
|
75
|
+
|
|
76
|
+
# Add missing general field
|
|
77
|
+
if "general" not in normalized:
|
|
78
|
+
normalized["general"] = []
|
|
79
|
+
|
|
80
|
+
# Add missing gates field (required by BackendProperties)
|
|
81
|
+
if "gates" not in normalized:
|
|
82
|
+
normalized["gates"] = []
|
|
83
|
+
|
|
84
|
+
# Add missing qubits field (required by BackendProperties)
|
|
85
|
+
if "qubits" not in normalized:
|
|
86
|
+
normalized["qubits"] = []
|
|
87
|
+
|
|
88
|
+
# Normalize qubits (list of lists of Nduv objects)
|
|
89
|
+
if "qubits" in normalized:
|
|
90
|
+
normalized["qubits"] = [
|
|
91
|
+
[_normalize_nduv(param, default_date) for param in qubit_params]
|
|
92
|
+
for qubit_params in normalized["qubits"]
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
# Normalize gates (list of gate dicts with parameters)
|
|
96
|
+
if "gates" in normalized:
|
|
97
|
+
normalized["gates"] = [
|
|
98
|
+
{
|
|
99
|
+
**gate,
|
|
100
|
+
"parameters": [
|
|
101
|
+
_normalize_nduv(param, default_date)
|
|
102
|
+
for param in gate.get("parameters", [])
|
|
103
|
+
],
|
|
104
|
+
}
|
|
105
|
+
for gate in normalized["gates"]
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
# Normalize general (list of Nduv objects)
|
|
109
|
+
if "general" in normalized and normalized["general"]:
|
|
110
|
+
normalized["general"] = [
|
|
111
|
+
_normalize_nduv(param, default_date) for param in normalized["general"]
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
return normalized
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _normalize_nduv(
|
|
118
|
+
nduv: dict[str, Any], default_date: datetime.datetime
|
|
119
|
+
) -> dict[str, Any]:
|
|
120
|
+
"""
|
|
121
|
+
Normalize a single Nduv (Name, Date, Unit, Value) object by adding
|
|
122
|
+
missing required fields.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
nduv: Nduv dictionary (may be incomplete)
|
|
126
|
+
default_date: Default date to use if missing
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Complete Nduv dictionary
|
|
130
|
+
"""
|
|
131
|
+
normalized = nduv.copy()
|
|
132
|
+
|
|
133
|
+
# Add missing date field
|
|
134
|
+
if "date" not in normalized:
|
|
135
|
+
normalized["date"] = default_date
|
|
136
|
+
|
|
137
|
+
# Add missing unit field
|
|
138
|
+
if "unit" not in normalized:
|
|
139
|
+
name = normalized.get("name", "").lower()
|
|
140
|
+
# Dimensionless quantities
|
|
141
|
+
if name in ("gate_error", "readout_error", "prob"):
|
|
142
|
+
normalized["unit"] = ""
|
|
143
|
+
# Time-based quantities
|
|
144
|
+
elif name in ("t1", "t2", "gate_length", "readout_length"):
|
|
145
|
+
# Infer unit from common patterns, default to "ns" for gate_length
|
|
146
|
+
if name == "gate_length":
|
|
147
|
+
normalized["unit"] = "ns"
|
|
148
|
+
elif name in ("t1", "t2"):
|
|
149
|
+
normalized["unit"] = "us" # microseconds is common
|
|
150
|
+
else:
|
|
151
|
+
normalized["unit"] = "ns"
|
|
152
|
+
# Frequency-based quantities
|
|
153
|
+
elif name in ("frequency", "freq"):
|
|
154
|
+
normalized["unit"] = "GHz"
|
|
155
|
+
# Default to empty string for unknown quantities
|
|
156
|
+
else:
|
|
157
|
+
normalized["unit"] = ""
|
|
158
|
+
|
|
159
|
+
return normalized
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def create_backend_from_properties(
|
|
163
|
+
properties: dict[str, Any],
|
|
164
|
+
n_qubits: int | None = None,
|
|
165
|
+
default_date: datetime.datetime | None = None,
|
|
166
|
+
) -> GenericBackendV2:
|
|
167
|
+
"""
|
|
168
|
+
Create a populated GenericBackendV2 from a BackendProperties dictionary.
|
|
169
|
+
|
|
170
|
+
This function handles the complete workflow:
|
|
171
|
+
1. Normalizes the properties dictionary (fills in missing fields)
|
|
172
|
+
2. Infers the number of qubits from the properties if not provided
|
|
173
|
+
3. Creates a GenericBackendV2 backend
|
|
174
|
+
4. Populates it with the normalized properties
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
properties: BackendProperties dictionary.
|
|
178
|
+
Missing fields will be filled automatically.
|
|
179
|
+
n_qubits: Optional number of qubits. If None, will be inferred from the
|
|
180
|
+
length of the "qubits" list in the properties dictionary.
|
|
181
|
+
default_date: Optional datetime to use for missing date fields.
|
|
182
|
+
If None, uses current time.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
GenericBackendV2 backend populated with the provided properties.
|
|
186
|
+
|
|
187
|
+
Raises:
|
|
188
|
+
ValueError: If n_qubits is not provided and cannot be inferred from properties
|
|
189
|
+
(i.e., qubits list is empty or missing), or if n_qubits is less than 1.
|
|
190
|
+
|
|
191
|
+
Example:
|
|
192
|
+
>>> props = {
|
|
193
|
+
... "backend_name": "test",
|
|
194
|
+
... "qubits": [[{"name": "T1", "value": 100.0}]], # 1 qubit
|
|
195
|
+
... "gates": [{"gate": "sx", "qubits": [0], "parameters": []}]
|
|
196
|
+
... }
|
|
197
|
+
>>> # Infer qubit count from properties (will be 1)
|
|
198
|
+
>>> backend = create_backend_from_properties(props)
|
|
199
|
+
>>> backend.n_qubits
|
|
200
|
+
1
|
|
201
|
+
>>> # Override qubit count if needed
|
|
202
|
+
>>> backend_large = create_backend_from_properties(props, n_qubits=120)
|
|
203
|
+
>>> backend_large.n_qubits
|
|
204
|
+
120
|
|
205
|
+
"""
|
|
206
|
+
# Normalize the properties first
|
|
207
|
+
normalized_properties = _normalize_properties(properties, default_date)
|
|
208
|
+
|
|
209
|
+
# Infer number of qubits from qubits list length if not provided
|
|
210
|
+
if n_qubits is None:
|
|
211
|
+
n_qubits = len(normalized_properties.get("qubits", []))
|
|
212
|
+
if n_qubits == 0:
|
|
213
|
+
raise ValueError(
|
|
214
|
+
"n_qubits must be provided when properties dictionary has no qubits, "
|
|
215
|
+
"or qubits list must contain at least one qubit"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
if n_qubits < 1:
|
|
219
|
+
raise ValueError("n_qubits must be at least 1")
|
|
220
|
+
|
|
221
|
+
# Create the backend
|
|
222
|
+
backend = GenericBackendV2(num_qubits=n_qubits)
|
|
223
|
+
|
|
224
|
+
# Populate with properties
|
|
225
|
+
backend._properties = BackendProperties.from_dict(normalized_properties)
|
|
226
|
+
|
|
227
|
+
return backend
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
1
|
+
# SPDX-FileCopyrightText: 2025-2026 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
@@ -16,6 +16,7 @@ from qiskit import QuantumCircuit, transpile
|
|
|
16
16
|
from qiskit.converters import circuit_to_dag
|
|
17
17
|
from qiskit.dagcircuit import DAGOpNode
|
|
18
18
|
from qiskit.providers import Backend
|
|
19
|
+
from qiskit.transpiler.exceptions import TranspilerError
|
|
19
20
|
from qiskit_aer import AerSimulator
|
|
20
21
|
from qiskit_aer.noise import NoiseModel
|
|
21
22
|
|
|
@@ -270,7 +271,7 @@ class ParallelSimulator(CircuitRunner):
|
|
|
270
271
|
circuit_simulator = self._create_simulator(resolved_backend)
|
|
271
272
|
|
|
272
273
|
if self.simulation_seed is not None:
|
|
273
|
-
circuit_simulator.
|
|
274
|
+
circuit_simulator.set_options(seed_simulator=self.simulation_seed)
|
|
274
275
|
|
|
275
276
|
# Run the single circuit
|
|
276
277
|
job = circuit_simulator.run(transpiled_circuit, shots=self.shots)
|
|
@@ -421,7 +422,7 @@ class ParallelSimulator(CircuitRunner):
|
|
|
421
422
|
)
|
|
422
423
|
|
|
423
424
|
total_run_time_s = 0.0
|
|
424
|
-
durations = resolved_backend.
|
|
425
|
+
durations = resolved_backend.target.durations()
|
|
425
426
|
|
|
426
427
|
for node in circuit_to_dag(transpiled_circuit).longest_path():
|
|
427
428
|
if not isinstance(node, DAGOpNode) or not node.num_qubits:
|
|
@@ -429,10 +430,9 @@ class ParallelSimulator(CircuitRunner):
|
|
|
429
430
|
|
|
430
431
|
try:
|
|
431
432
|
idx = tuple(q._index for q in node.qargs)
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
except KeyError:
|
|
433
|
+
duration = durations.get(node.name, idx, unit="s")
|
|
434
|
+
total_run_time_s += duration
|
|
435
|
+
except TranspilerError:
|
|
436
436
|
if node.name != "barrier":
|
|
437
437
|
warn(f"Instruction duration not found: {node.name}")
|
|
438
438
|
|
divi/circuits/__init__.py
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
1
|
+
# SPDX-FileCopyrightText: 2025-2026 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
5
|
-
# isort: skip_file
|
|
6
5
|
from ._qasm_conversion import to_openqasm
|
|
7
6
|
from ._qasm_validation import is_valid_qasm, validate_qasm, validate_qasm_count_qubits
|
|
8
|
-
from ._core import
|
|
7
|
+
from ._core import (
|
|
8
|
+
CircuitBundle,
|
|
9
|
+
ExecutableQASMCircuit,
|
|
10
|
+
MetaCircuit,
|
|
11
|
+
CircuitTag,
|
|
12
|
+
format_circuit_tag,
|
|
13
|
+
)
|
divi/circuits/_core.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
1
|
+
# SPDX-FileCopyrightText: 2025-2026 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
5
5
|
import re
|
|
6
|
+
from collections.abc import Callable
|
|
6
7
|
from dataclasses import dataclass, field
|
|
7
8
|
from itertools import product
|
|
8
|
-
from typing import
|
|
9
|
+
from typing import Literal, NamedTuple
|
|
9
10
|
|
|
10
11
|
import dill
|
|
11
12
|
import numpy as np
|
|
@@ -96,11 +97,25 @@ def _create_final_postprocessing_fn(coefficients, partition_indices, num_total_o
|
|
|
96
97
|
return final_postprocessing_fn
|
|
97
98
|
|
|
98
99
|
|
|
100
|
+
class CircuitTag(NamedTuple):
|
|
101
|
+
"""Structured tag for identifying circuit executions."""
|
|
102
|
+
|
|
103
|
+
param_id: int
|
|
104
|
+
qem_name: str
|
|
105
|
+
qem_id: int
|
|
106
|
+
meas_id: int
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def format_circuit_tag(tag: CircuitTag) -> str:
|
|
110
|
+
"""Format a CircuitTag into its wire-safe string representation."""
|
|
111
|
+
return f"{tag.param_id}_{tag.qem_name}:{tag.qem_id}_{tag.meas_id}"
|
|
112
|
+
|
|
113
|
+
|
|
99
114
|
@dataclass(frozen=True)
|
|
100
115
|
class ExecutableQASMCircuit:
|
|
101
116
|
"""Represents a single, executable QASM circuit with its associated tag."""
|
|
102
117
|
|
|
103
|
-
tag:
|
|
118
|
+
tag: CircuitTag
|
|
104
119
|
qasm: str
|
|
105
120
|
|
|
106
121
|
|
|
@@ -129,7 +144,7 @@ class CircuitBundle:
|
|
|
129
144
|
return f"CircuitBundle ({len(self.executables)} executables)"
|
|
130
145
|
|
|
131
146
|
@property
|
|
132
|
-
def tags(self) -> list[
|
|
147
|
+
def tags(self) -> list[CircuitTag]:
|
|
133
148
|
"""A list of tags for all executables in the bundle."""
|
|
134
149
|
return [e.tag for e in self.executables]
|
|
135
150
|
|
|
@@ -312,7 +327,10 @@ class MetaCircuit:
|
|
|
312
327
|
self.__dict__.update(state)
|
|
313
328
|
|
|
314
329
|
def initialize_circuit_from_params(
|
|
315
|
-
self,
|
|
330
|
+
self,
|
|
331
|
+
param_list: npt.NDArray[np.floating] | list[float],
|
|
332
|
+
param_idx: int = 0,
|
|
333
|
+
precision: int | None = None,
|
|
316
334
|
) -> CircuitBundle:
|
|
317
335
|
"""
|
|
318
336
|
Instantiate a concrete CircuitBundle by substituting symbolic parameters with values.
|
|
@@ -322,10 +340,11 @@ class MetaCircuit:
|
|
|
322
340
|
concrete numerical values.
|
|
323
341
|
|
|
324
342
|
Args:
|
|
325
|
-
param_list: Array of numerical
|
|
343
|
+
param_list (npt.NDArray[np.floating] | list[float]): Array of numerical
|
|
344
|
+
parameter values to substitute for symbols.
|
|
326
345
|
Must match the length and order of self.symbols.
|
|
327
|
-
|
|
328
|
-
|
|
346
|
+
param_idx (int, optional): Parameter set index used for structured tags.
|
|
347
|
+
Defaults to 0.
|
|
329
348
|
precision (int | None, optional): Number of decimal places for parameter values
|
|
330
349
|
in the QASM output. If None, uses the precision set on this MetaCircuit instance.
|
|
331
350
|
Defaults to None.
|
|
@@ -354,16 +373,19 @@ class MetaCircuit:
|
|
|
354
373
|
]
|
|
355
374
|
|
|
356
375
|
executables = []
|
|
376
|
+
param_id = param_idx
|
|
357
377
|
for (i, body_str), (j, meas_str) in product(
|
|
358
378
|
enumerate(final_qasm_bodies), enumerate(self._measurements)
|
|
359
379
|
):
|
|
360
380
|
qasm_circuit = body_str + meas_str
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
381
|
+
tag = CircuitTag(
|
|
382
|
+
param_id=param_id,
|
|
383
|
+
qem_name=(
|
|
384
|
+
self.qem_protocol.name if self.qem_protocol else "NoMitigation"
|
|
385
|
+
),
|
|
386
|
+
qem_id=i,
|
|
387
|
+
meas_id=j,
|
|
388
|
+
)
|
|
367
389
|
executables.append(ExecutableQASMCircuit(tag=tag, qasm=qasm_circuit))
|
|
368
390
|
|
|
369
391
|
return CircuitBundle(executables=tuple(executables))
|
divi/qprog/__init__.py
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
1
|
+
# SPDX-FileCopyrightText: 2025-2026 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
5
|
-
# isort: skip_file
|
|
6
5
|
from .quantum_program import QuantumProgram
|
|
7
|
-
from .variational_quantum_algorithm import VariationalQuantumAlgorithm
|
|
6
|
+
from .variational_quantum_algorithm import VariationalQuantumAlgorithm, SolutionEntry
|
|
8
7
|
from .batch import ProgramBatch
|
|
9
8
|
from .algorithms import (
|
|
10
9
|
QAOA,
|
|
11
10
|
GraphProblem,
|
|
12
11
|
VQE,
|
|
12
|
+
PCE,
|
|
13
|
+
CustomVQA,
|
|
13
14
|
Ansatz,
|
|
14
15
|
UCCSDAnsatz,
|
|
15
16
|
QAOAAnsatz,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
1
|
+
# SPDX-FileCopyrightText: 2025-2026 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
@@ -10,5 +10,7 @@ from ._ansatze import (
|
|
|
10
10
|
QAOAAnsatz,
|
|
11
11
|
UCCSDAnsatz,
|
|
12
12
|
)
|
|
13
|
-
from .
|
|
13
|
+
from ._custom_vqa import CustomVQA
|
|
14
|
+
from ._qaoa import QAOA, GraphProblem
|
|
14
15
|
from ._vqe import VQE
|
|
16
|
+
from ._pce import PCE
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
1
|
+
# SPDX-FileCopyrightText: 2025-2026 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
@@ -10,6 +10,15 @@ from warnings import warn
|
|
|
10
10
|
import pennylane as qml
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
def _require_trainable_params(n_params: int, ansatz_name: str) -> int:
|
|
14
|
+
if n_params <= 0:
|
|
15
|
+
raise ValueError(
|
|
16
|
+
f"{ansatz_name} must define at least one trainable parameter. "
|
|
17
|
+
"Parameter-free circuits are not supported."
|
|
18
|
+
)
|
|
19
|
+
return n_params
|
|
20
|
+
|
|
21
|
+
|
|
13
22
|
class Ansatz(ABC):
|
|
14
23
|
"""Abstract base class for all VQE ansätze."""
|
|
15
24
|
|
|
@@ -99,7 +108,7 @@ class GenericLayerAnsatz(Ansatz):
|
|
|
99
108
|
self._layout_fn = lambda n_qubits: zip(
|
|
100
109
|
range(n_qubits), [(i + 1) % n_qubits for i in range(n_qubits)]
|
|
101
110
|
)
|
|
102
|
-
case "
|
|
111
|
+
case "all-to-all":
|
|
103
112
|
self._layout_fn = lambda n_qubits: (
|
|
104
113
|
(i, j) for i in range(n_qubits) for j in range(i + 1, n_qubits)
|
|
105
114
|
)
|
|
@@ -121,7 +130,7 @@ class GenericLayerAnsatz(Ansatz):
|
|
|
121
130
|
def n_params_per_layer(self, n_qubits: int, **kwargs) -> int:
|
|
122
131
|
"""Total parameters = sum of gate.num_params per qubit per layer."""
|
|
123
132
|
per_qubit = sum(getattr(g, "num_params", 1) for g in self.gate_sequence)
|
|
124
|
-
return per_qubit * n_qubits
|
|
133
|
+
return _require_trainable_params(per_qubit * n_qubits, self.name)
|
|
125
134
|
|
|
126
135
|
def build(
|
|
127
136
|
self, params, n_qubits: int, n_layers: int, **kwargs
|
|
@@ -182,7 +191,8 @@ class QAOAAnsatz(Ansatz):
|
|
|
182
191
|
Returns:
|
|
183
192
|
int: Number of parameters needed per layer.
|
|
184
193
|
"""
|
|
185
|
-
|
|
194
|
+
n_params = qml.QAOAEmbedding.shape(n_layers=1, n_wires=n_qubits)[1]
|
|
195
|
+
return _require_trainable_params(n_params, QAOAAnsatz.__name__)
|
|
186
196
|
|
|
187
197
|
def build(
|
|
188
198
|
self, params, n_qubits: int, n_layers: int, **kwargs
|
|
@@ -257,7 +267,8 @@ class UCCSDAnsatz(Ansatz):
|
|
|
257
267
|
"""
|
|
258
268
|
singles, doubles = qml.qchem.excitations(n_electrons, n_qubits)
|
|
259
269
|
s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles)
|
|
260
|
-
|
|
270
|
+
n_params = len(s_wires) + len(d_wires)
|
|
271
|
+
return _require_trainable_params(n_params, UCCSDAnsatz.__name__)
|
|
261
272
|
|
|
262
273
|
def build(
|
|
263
274
|
self, params, n_qubits: int, n_layers: int, **kwargs
|
|
@@ -313,7 +324,8 @@ class HartreeFockAnsatz(Ansatz):
|
|
|
313
324
|
int: Number of parameters (number of single + double excitations).
|
|
314
325
|
"""
|
|
315
326
|
singles, doubles = qml.qchem.excitations(n_electrons, n_qubits)
|
|
316
|
-
|
|
327
|
+
n_params = len(singles) + len(doubles)
|
|
328
|
+
return _require_trainable_params(n_params, HartreeFockAnsatz.__name__)
|
|
317
329
|
|
|
318
330
|
def build(
|
|
319
331
|
self, params, n_qubits: int, n_layers: int, **kwargs
|