qoro-divi 0.3.2b0__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.
- {qoro_divi-0.3.2b0 → qoro_divi-0.3.4}/PKG-INFO +2 -2
- {qoro_divi-0.3.2b0 → qoro_divi-0.3.4}/divi/__init__.py +1 -2
- qoro_divi-0.3.4/divi/backends/__init__.py +7 -0
- qoro_divi-0.3.2b0/divi/parallel_simulator.py → qoro_divi-0.3.4/divi/backends/_parallel_simulator.py +4 -3
- qoro_divi-0.3.2b0/divi/qoro_service.py → qoro_divi-0.3.4/divi/backends/_qoro_service.py +27 -15
- qoro_divi-0.3.4/divi/circuits/__init__.py +5 -0
- qoro_divi-0.3.2b0/divi/circuits.py → qoro_divi-0.3.4/divi/circuits/_core.py +6 -20
- {qoro_divi-0.3.2b0/divi → qoro_divi-0.3.4/divi/circuits}/qasm.py +2 -2
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/cirq/__init__.py +1 -1
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/cirq/_validator.py +10 -8
- qoro_divi-0.3.4/divi/qprog/__init__.py +26 -0
- qoro_divi-0.3.4/divi/qprog/algorithms/__init__.py +14 -0
- qoro_divi-0.3.4/divi/qprog/algorithms/_ansatze.py +215 -0
- {qoro_divi-0.3.2b0/divi/qprog → qoro_divi-0.3.4/divi/qprog/algorithms}/_qaoa.py +16 -26
- {qoro_divi-0.3.2b0/divi/qprog → qoro_divi-0.3.4/divi/qprog/algorithms}/_vqe.py +35 -133
- {qoro_divi-0.3.2b0 → qoro_divi-0.3.4}/divi/qprog/batch.py +25 -19
- qoro_divi-0.3.4/divi/qprog/optimizers.py +200 -0
- {qoro_divi-0.3.2b0 → qoro_divi-0.3.4}/divi/qprog/quantum_program.py +142 -200
- qoro_divi-0.3.4/divi/qprog/workflows/__init__.py +10 -0
- {qoro_divi-0.3.2b0/divi/qprog → qoro_divi-0.3.4/divi/qprog/workflows}/_graph_partitioning.py +6 -9
- {qoro_divi-0.3.2b0/divi/qprog → qoro_divi-0.3.4/divi/qprog/workflows}/_qubo_partitioning.py +6 -7
- {qoro_divi-0.3.2b0/divi/qprog → qoro_divi-0.3.4/divi/qprog/workflows}/_vqe_sweep.py +35 -24
- qoro_divi-0.3.4/divi/reporting/__init__.py +7 -0
- {qoro_divi-0.3.2b0/divi → qoro_divi-0.3.4/divi/reporting}/_pbar.py +13 -14
- qoro_divi-0.3.2b0/divi/qlogger.py → qoro_divi-0.3.4/divi/reporting/_qlogger.py +8 -6
- qoro_divi-0.3.2b0/divi/reporter.py → qoro_divi-0.3.4/divi/reporting/_reporter.py +24 -7
- {qoro_divi-0.3.2b0 → qoro_divi-0.3.4}/divi/utils.py +14 -6
- {qoro_divi-0.3.2b0 → qoro_divi-0.3.4}/pyproject.toml +2 -2
- qoro_divi-0.3.2b0/divi/qprog/__init__.py +0 -13
- qoro_divi-0.3.2b0/divi/qprog/optimizers.py +0 -75
- {qoro_divi-0.3.2b0 → qoro_divi-0.3.4}/LICENSE +0 -0
- {qoro_divi-0.3.2b0 → qoro_divi-0.3.4}/LICENSES/.license-header +0 -0
- {qoro_divi-0.3.2b0 → qoro_divi-0.3.4}/LICENSES/Apache-2.0.txt +0 -0
- {qoro_divi-0.3.2b0 → qoro_divi-0.3.4}/README.md +0 -0
- /qoro_divi-0.3.2b0/divi/interfaces.py → /qoro_divi-0.3.4/divi/backends/_circuit_runner.py +0 -0
- /qoro_divi-0.3.2b0/divi/qpu_system.py → /qoro_divi-0.3.4/divi/backends/_qpu_system.py +0 -0
- {qoro_divi-0.3.2b0/divi → qoro_divi-0.3.4/divi/circuits}/qem.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/cirq/_lexer.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/cirq/_parser.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/cirq/_qasm_export.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/cirq/_qasm_import.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/cirq/exception.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/_cobyla.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/LICENCE.txt +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/__init__.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/cobyla/__init__.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/cobyla/cobyla.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/cobyla/cobylb.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/cobyla/geometry.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/cobyla/initialize.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/cobyla/trustregion.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/cobyla/update.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/__init__.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/_bounds.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/_linear_constraints.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/_nonlinear_constraints.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/_project.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/checkbreak.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/consts.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/evaluate.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/history.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/infos.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/linalg.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/message.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/powalg.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/preproc.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/present.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/ratio.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/redrho.py +0 -0
- {qoro_divi-0.3.2b0/divi/exp → qoro_divi-0.3.4/divi/extern}/scipy/pyprima/common/selectx.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: qoro-divi
|
|
3
|
-
Version: 0.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
|
|
@@ -21,7 +21,7 @@ Requires-Dist: python-dotenv (>=1.1.1,<2.0.0)
|
|
|
21
21
|
Requires-Dist: qiskit (<2.0)
|
|
22
22
|
Requires-Dist: qiskit-aer (>=0.17.1,<0.18.0)
|
|
23
23
|
Requires-Dist: qiskit-ibm-runtime (>=0.37,<0.38)
|
|
24
|
-
Requires-Dist: qiskit-optimization (>=0.
|
|
24
|
+
Requires-Dist: qiskit-optimization[cplex] (>=0.7.0,<0.8.0)
|
|
25
25
|
Requires-Dist: requests (>=2.32.4,<3.0.0)
|
|
26
26
|
Requires-Dist: rich (>=14.0.0,<15.0.0)
|
|
27
27
|
Requires-Dist: scikit-learn (>=1.7.0,<2.0.0)
|
|
@@ -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
|
qoro_divi-0.3.2b0/divi/parallel_simulator.py → qoro_divi-0.3.4/divi/backends/_parallel_simulator.py
RENAMED
|
@@ -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.
|
|
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
|
-
|
|
78
|
-
|
|
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.
|
|
19
|
-
from divi.
|
|
20
|
-
from divi.
|
|
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,8 +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
|
-
if not is_valid_qasm(circuit):
|
|
245
|
-
raise ValueError(f"Circuit {key} is not a valid QASM
|
|
244
|
+
if not (err := is_valid_qasm(circuit)):
|
|
245
|
+
raise ValueError(f"Circuit '{key}' is not a valid QASM: {err}")
|
|
246
246
|
|
|
247
247
|
circuit_chunks = self._split_circuits(circuits)
|
|
248
248
|
|
|
@@ -342,7 +342,7 @@ class QoroService(CircuitRunner):
|
|
|
342
342
|
loop_until_complete: bool = False,
|
|
343
343
|
on_complete: Callable | None = None,
|
|
344
344
|
verbose: bool = True,
|
|
345
|
-
|
|
345
|
+
poll_callback: Callable[[int, str], None] | None = None,
|
|
346
346
|
):
|
|
347
347
|
"""
|
|
348
348
|
Get the status of a job and optionally execute function *on_complete* on the results
|
|
@@ -354,13 +354,28 @@ class QoroService(CircuitRunner):
|
|
|
354
354
|
on_complete (optional): A function to be called when the job is completed
|
|
355
355
|
polling_interval (optional): The time to wait between retries
|
|
356
356
|
verbose (optional): A flag to print the when retrying
|
|
357
|
-
|
|
357
|
+
poll_callback (optional): A function for updating progress bars while polling.
|
|
358
|
+
Definition should be `poll_callback(retry_count: int, status: str) -> None`.
|
|
358
359
|
Returns:
|
|
359
360
|
status: The status of the job
|
|
360
361
|
"""
|
|
361
362
|
if not isinstance(job_ids, list):
|
|
362
363
|
job_ids = [job_ids]
|
|
363
364
|
|
|
365
|
+
# Decide once at the start
|
|
366
|
+
if poll_callback:
|
|
367
|
+
update_fn = poll_callback
|
|
368
|
+
elif verbose:
|
|
369
|
+
CYAN = "\033[36m"
|
|
370
|
+
RESET = "\033[0m"
|
|
371
|
+
|
|
372
|
+
update_fn = lambda retry_count, status: logger.info(
|
|
373
|
+
rf"Job {CYAN}{job_ids[0].split('-')[0]}{RESET} is {status}. Polling attempt {retry_count} / {self.max_retries}\r",
|
|
374
|
+
extra={"append": True},
|
|
375
|
+
)
|
|
376
|
+
else:
|
|
377
|
+
update_fn = lambda _, __: None
|
|
378
|
+
|
|
364
379
|
if not loop_until_complete:
|
|
365
380
|
statuses = [
|
|
366
381
|
self._make_request(
|
|
@@ -386,7 +401,10 @@ class QoroService(CircuitRunner):
|
|
|
386
401
|
timeout=200,
|
|
387
402
|
)
|
|
388
403
|
|
|
389
|
-
if response.json()["status"]
|
|
404
|
+
if response.json()["status"] in (
|
|
405
|
+
JobStatus.COMPLETED.value,
|
|
406
|
+
JobStatus.FAILED.value,
|
|
407
|
+
):
|
|
390
408
|
pending_job_ids.remove(job_id)
|
|
391
409
|
responses.append(response)
|
|
392
410
|
|
|
@@ -396,13 +414,7 @@ class QoroService(CircuitRunner):
|
|
|
396
414
|
|
|
397
415
|
time.sleep(self.polling_interval)
|
|
398
416
|
|
|
399
|
-
|
|
400
|
-
if pbar_update_fn:
|
|
401
|
-
pbar_update_fn(retry_count)
|
|
402
|
-
else:
|
|
403
|
-
logger.info(
|
|
404
|
-
rf"\cPolling {retry_count} / {self.max_retries} retries\r"
|
|
405
|
-
)
|
|
417
|
+
update_fn(retry_count, response.json()["status"])
|
|
406
418
|
|
|
407
419
|
if not pending_job_ids:
|
|
408
420
|
if on_complete:
|
|
@@ -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
|
-
|
|
56
|
-
|
|
43
|
+
self.circuit_id = Circuit._id_counter
|
|
44
|
+
Circuit._id_counter += 1
|
|
57
45
|
|
|
58
|
-
|
|
59
|
-
|
|
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.
|
|
18
|
-
from divi.
|
|
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",
|
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
# TODO: delete whole module once Cirq properly supports parameters in openqasm 3.0
|
|
6
6
|
from . import _qasm_export # Does nothing, just initiates the patch
|
|
7
7
|
from ._qasm_import import cirq_circuit_from_qasm
|
|
8
|
-
from ._validator import is_valid_qasm
|
|
8
|
+
from ._validator import is_valid_qasm, validate_qasm_raise
|
|
@@ -289,7 +289,12 @@ class Parser:
|
|
|
289
289
|
# ---- gate definitions ----
|
|
290
290
|
def gate_def(self):
|
|
291
291
|
self.match("GATE")
|
|
292
|
-
|
|
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, ...] = ()
|
|
@@ -558,7 +563,7 @@ class Parser:
|
|
|
558
563
|
self._expr_power(allow_id)
|
|
559
564
|
|
|
560
565
|
def _expr_unary(self, allow_id: bool):
|
|
561
|
-
|
|
566
|
+
while self.peek().type in ("PLUS", "MINUS"):
|
|
562
567
|
self.match(self.peek().type)
|
|
563
568
|
self._expr_atom(allow_id)
|
|
564
569
|
|
|
@@ -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
|
|
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
|
|
@@ -23,7 +23,7 @@ from qiskit_optimization.problems import VarType
|
|
|
23
23
|
|
|
24
24
|
from divi.circuits import MetaCircuit
|
|
25
25
|
from divi.qprog import QuantumProgram
|
|
26
|
-
from divi.qprog.optimizers import Optimizer
|
|
26
|
+
from divi.qprog.optimizers import MonteCarloOptimizer, Optimizer
|
|
27
27
|
from divi.utils import convert_qubo_matrix_to_pennylane_ising
|
|
28
28
|
|
|
29
29
|
logger = logging.getLogger(__name__)
|
|
@@ -153,10 +153,11 @@ class QAOA(QuantumProgram):
|
|
|
153
153
|
def __init__(
|
|
154
154
|
self,
|
|
155
155
|
problem: GraphProblemTypes | QUBOProblemTypes,
|
|
156
|
+
*,
|
|
156
157
|
graph_problem: GraphProblem | None = None,
|
|
157
158
|
n_layers: int = 1,
|
|
158
159
|
initial_state: _SUPPORTED_INITIAL_STATES_LITERAL = "Recommended",
|
|
159
|
-
optimizer: Optimizer =
|
|
160
|
+
optimizer: Optimizer | None = None,
|
|
160
161
|
max_iterations: int = 10,
|
|
161
162
|
**kwargs,
|
|
162
163
|
):
|
|
@@ -221,11 +222,11 @@ class QAOA(QuantumProgram):
|
|
|
221
222
|
|
|
222
223
|
# Local Variables
|
|
223
224
|
self.n_layers = n_layers
|
|
224
|
-
self.optimizer = optimizer
|
|
225
225
|
self.max_iterations = max_iterations
|
|
226
226
|
self.current_iteration = 0
|
|
227
|
-
self.
|
|
227
|
+
self._n_params = 2
|
|
228
228
|
self._is_compute_probabilites = False
|
|
229
|
+
self.optimizer = optimizer if optimizer is not None else MonteCarloOptimizer()
|
|
229
230
|
|
|
230
231
|
# Shared Variables
|
|
231
232
|
self.probs = kwargs.pop("probs", {})
|
|
@@ -245,10 +246,17 @@ class QAOA(QuantumProgram):
|
|
|
245
246
|
)
|
|
246
247
|
self.problem_metadata = problem_metadata[0] if problem_metadata else {}
|
|
247
248
|
|
|
248
|
-
|
|
249
|
+
if "constant" in self.problem_metadata:
|
|
250
|
+
self.loss_constant = self.problem_metadata.get("constant")
|
|
251
|
+
try:
|
|
252
|
+
self.loss_constant = self.loss_constant.item()
|
|
253
|
+
except AttributeError:
|
|
254
|
+
pass
|
|
255
|
+
else:
|
|
256
|
+
self.loss_constant = 0.0
|
|
249
257
|
|
|
250
258
|
kwargs.pop("is_constrained", None)
|
|
251
|
-
super().__init__(**kwargs)
|
|
259
|
+
super().__init__(has_final_computation=True, **kwargs)
|
|
252
260
|
|
|
253
261
|
self._meta_circuits = self._create_meta_circuits_dict()
|
|
254
262
|
|
|
@@ -387,16 +395,7 @@ class QAOA(QuantumProgram):
|
|
|
387
395
|
- float: The total runtime of the optimization process.
|
|
388
396
|
"""
|
|
389
397
|
|
|
390
|
-
|
|
391
|
-
self._progress_queue.put(
|
|
392
|
-
{
|
|
393
|
-
"job_id": self.job_id,
|
|
394
|
-
"message": "🏁 Computing Final Solution 🏁",
|
|
395
|
-
"progress": 0,
|
|
396
|
-
}
|
|
397
|
-
)
|
|
398
|
-
else:
|
|
399
|
-
logger.info("🏁 Computing Final Solution 🏁")
|
|
398
|
+
self.reporter.info(message="🏁 Computing Final Solution 🏁")
|
|
400
399
|
|
|
401
400
|
# Convert losses dict to list to apply ordinal operations
|
|
402
401
|
final_losses_list = list(self.losses[-1].values())
|
|
@@ -440,16 +439,7 @@ class QAOA(QuantumProgram):
|
|
|
440
439
|
m.start() for m in re.finditer("1", best_solution_bitstring)
|
|
441
440
|
]
|
|
442
441
|
|
|
443
|
-
|
|
444
|
-
self._progress_queue.put(
|
|
445
|
-
{
|
|
446
|
-
"job_id": self.job_id,
|
|
447
|
-
"progress": 0,
|
|
448
|
-
"final_status": "Success",
|
|
449
|
-
}
|
|
450
|
-
)
|
|
451
|
-
else:
|
|
452
|
-
logger.info(f"Computed Solution!")
|
|
442
|
+
self.reporter.info(message="Computed Final Solution!")
|
|
453
443
|
|
|
454
444
|
return self._total_circuit_count, self._total_run_time
|
|
455
445
|
|