qoro-divi 0.2.0b1__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/__init__.py +8 -0
- divi/_pbar.py +73 -0
- divi/circuits.py +139 -0
- divi/exp/cirq/__init__.py +7 -0
- divi/exp/cirq/_lexer.py +126 -0
- divi/exp/cirq/_parser.py +889 -0
- divi/exp/cirq/_qasm_export.py +37 -0
- divi/exp/cirq/_qasm_import.py +35 -0
- divi/exp/cirq/exception.py +21 -0
- divi/exp/scipy/_cobyla.py +342 -0
- divi/exp/scipy/pyprima/LICENCE.txt +28 -0
- divi/exp/scipy/pyprima/__init__.py +263 -0
- divi/exp/scipy/pyprima/cobyla/__init__.py +0 -0
- divi/exp/scipy/pyprima/cobyla/cobyla.py +599 -0
- divi/exp/scipy/pyprima/cobyla/cobylb.py +849 -0
- divi/exp/scipy/pyprima/cobyla/geometry.py +240 -0
- divi/exp/scipy/pyprima/cobyla/initialize.py +269 -0
- divi/exp/scipy/pyprima/cobyla/trustregion.py +540 -0
- divi/exp/scipy/pyprima/cobyla/update.py +331 -0
- divi/exp/scipy/pyprima/common/__init__.py +0 -0
- divi/exp/scipy/pyprima/common/_bounds.py +41 -0
- divi/exp/scipy/pyprima/common/_linear_constraints.py +46 -0
- divi/exp/scipy/pyprima/common/_nonlinear_constraints.py +64 -0
- divi/exp/scipy/pyprima/common/_project.py +224 -0
- divi/exp/scipy/pyprima/common/checkbreak.py +107 -0
- divi/exp/scipy/pyprima/common/consts.py +48 -0
- divi/exp/scipy/pyprima/common/evaluate.py +101 -0
- divi/exp/scipy/pyprima/common/history.py +39 -0
- divi/exp/scipy/pyprima/common/infos.py +30 -0
- divi/exp/scipy/pyprima/common/linalg.py +452 -0
- divi/exp/scipy/pyprima/common/message.py +336 -0
- divi/exp/scipy/pyprima/common/powalg.py +131 -0
- divi/exp/scipy/pyprima/common/preproc.py +393 -0
- divi/exp/scipy/pyprima/common/present.py +5 -0
- divi/exp/scipy/pyprima/common/ratio.py +56 -0
- divi/exp/scipy/pyprima/common/redrho.py +49 -0
- divi/exp/scipy/pyprima/common/selectx.py +346 -0
- divi/interfaces.py +25 -0
- divi/parallel_simulator.py +258 -0
- divi/qasm.py +220 -0
- divi/qem.py +191 -0
- divi/qlogger.py +119 -0
- divi/qoro_service.py +343 -0
- divi/qprog/__init__.py +13 -0
- divi/qprog/_graph_partitioning.py +619 -0
- divi/qprog/_mlae.py +182 -0
- divi/qprog/_qaoa.py +440 -0
- divi/qprog/_vqe.py +275 -0
- divi/qprog/_vqe_sweep.py +144 -0
- divi/qprog/batch.py +235 -0
- divi/qprog/optimizers.py +75 -0
- divi/qprog/quantum_program.py +493 -0
- divi/utils.py +116 -0
- qoro_divi-0.2.0b1.dist-info/LICENSE +190 -0
- qoro_divi-0.2.0b1.dist-info/LICENSES/Apache-2.0.txt +73 -0
- qoro_divi-0.2.0b1.dist-info/METADATA +57 -0
- qoro_divi-0.2.0b1.dist-info/RECORD +58 -0
- qoro_divi-0.2.0b1.dist-info/WHEEL +4 -0
divi/__init__.py
ADDED
divi/_pbar.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from rich.progress import (
|
|
8
|
+
BarColumn,
|
|
9
|
+
MofNCompleteColumn,
|
|
10
|
+
Progress,
|
|
11
|
+
ProgressColumn,
|
|
12
|
+
SpinnerColumn,
|
|
13
|
+
TextColumn,
|
|
14
|
+
)
|
|
15
|
+
from rich.text import Text
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ConditionalSpinnerColumn(ProgressColumn):
|
|
19
|
+
def __init__(self):
|
|
20
|
+
super().__init__()
|
|
21
|
+
self.spinner = SpinnerColumn("point")
|
|
22
|
+
|
|
23
|
+
def render(self, task):
|
|
24
|
+
status = task.fields.get("final_status")
|
|
25
|
+
|
|
26
|
+
if status in ("Success", "Failed"):
|
|
27
|
+
return Text("")
|
|
28
|
+
|
|
29
|
+
return self.spinner.render(task)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class PhaseStatusColumn(ProgressColumn):
|
|
33
|
+
def __init__(self, max_retries: int, table_column=None):
|
|
34
|
+
super().__init__(table_column)
|
|
35
|
+
|
|
36
|
+
self._max_retries = max_retries
|
|
37
|
+
self._last_message = ""
|
|
38
|
+
|
|
39
|
+
def render(self, task):
|
|
40
|
+
final_status = task.fields.get("final_status")
|
|
41
|
+
|
|
42
|
+
if final_status == "Success":
|
|
43
|
+
return Text("• Success! ✅", style="bold green")
|
|
44
|
+
elif final_status == "Failed":
|
|
45
|
+
return Text("• Failed! ❌", style="bold red")
|
|
46
|
+
|
|
47
|
+
message = task.fields.get("message")
|
|
48
|
+
if message != "":
|
|
49
|
+
self._last_message = message
|
|
50
|
+
|
|
51
|
+
poll_attempt = task.fields.get("poll_attempt")
|
|
52
|
+
if poll_attempt > 0:
|
|
53
|
+
return Text(
|
|
54
|
+
f"[{self._last_message}] Polling {poll_attempt}/{self._max_retries}"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
return Text(f"[{self._last_message}]")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def make_progress_bar(
|
|
61
|
+
max_retries: Optional[int] = None, is_jupyter: bool = False
|
|
62
|
+
) -> Progress:
|
|
63
|
+
return Progress(
|
|
64
|
+
TextColumn("[bold blue]{task.fields[job_name]}"),
|
|
65
|
+
BarColumn(),
|
|
66
|
+
MofNCompleteColumn(),
|
|
67
|
+
ConditionalSpinnerColumn(),
|
|
68
|
+
PhaseStatusColumn(max_retries=max_retries),
|
|
69
|
+
# For jupyter notebooks, refresh manually instead
|
|
70
|
+
auto_refresh=not is_jupyter,
|
|
71
|
+
# Give a dummy positive value if is_jupyter
|
|
72
|
+
refresh_per_second=10 if not is_jupyter else 999,
|
|
73
|
+
)
|
divi/circuits.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from copy import deepcopy
|
|
7
|
+
from itertools import product
|
|
8
|
+
from typing import Literal, Optional
|
|
9
|
+
|
|
10
|
+
import dill
|
|
11
|
+
import pennylane as qml
|
|
12
|
+
from pennylane.transforms.core.transform_program import TransformProgram
|
|
13
|
+
from qiskit.qasm2 import dumps
|
|
14
|
+
|
|
15
|
+
from divi.qasm import to_openqasm
|
|
16
|
+
from divi.qem import QEMProtocol
|
|
17
|
+
|
|
18
|
+
TRANSFORM_PROGRAM = TransformProgram()
|
|
19
|
+
TRANSFORM_PROGRAM.add_transform(qml.transforms.split_to_single_terms)
|
|
20
|
+
TRANSFORM_PROGRAM.add_transform(qml.transforms.split_non_commuting)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Circuit:
|
|
24
|
+
_id_counter = 0
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
main_circuit,
|
|
29
|
+
tags: list[str],
|
|
30
|
+
qasm_circuits: list[str] = None,
|
|
31
|
+
):
|
|
32
|
+
self.main_circuit = main_circuit
|
|
33
|
+
self.circuit_type = main_circuit.__module__.split(".")[0]
|
|
34
|
+
self.tags = tags
|
|
35
|
+
|
|
36
|
+
self.qasm_circuits = qasm_circuits
|
|
37
|
+
|
|
38
|
+
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
|
+
self.qasm_circuits = to_openqasm(
|
|
50
|
+
self.main_circuit,
|
|
51
|
+
measurement_groups=[self.main_circuit.measurements],
|
|
52
|
+
return_measurements_separately=False,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
elif self.circuit_type == "qiskit":
|
|
56
|
+
self.qasm_circuits = [dumps(self.main_circuit)]
|
|
57
|
+
|
|
58
|
+
else:
|
|
59
|
+
raise ValueError(
|
|
60
|
+
f"Invalid circuit type. Circuit type {self.circuit_type} not currently supported."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class MetaCircuit:
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
main_circuit,
|
|
68
|
+
symbols,
|
|
69
|
+
grouping_strategy: Optional[Literal["wires", "default", "qwc"]] = None,
|
|
70
|
+
qem_protocol: Optional[QEMProtocol] = None,
|
|
71
|
+
):
|
|
72
|
+
self.main_circuit = main_circuit
|
|
73
|
+
self.symbols = symbols
|
|
74
|
+
self.qem_protocol = qem_protocol
|
|
75
|
+
|
|
76
|
+
transform_program = deepcopy(TRANSFORM_PROGRAM)
|
|
77
|
+
transform_program[1].kwargs["grouping_strategy"] = grouping_strategy
|
|
78
|
+
|
|
79
|
+
qscripts, self.postprocessing_fn = transform_program((main_circuit,))
|
|
80
|
+
|
|
81
|
+
self.compiled_circuits_bodies, self.measurements = to_openqasm(
|
|
82
|
+
main_circuit,
|
|
83
|
+
measurement_groups=[qsc.measurements for qsc in qscripts],
|
|
84
|
+
return_measurements_separately=True,
|
|
85
|
+
# TODO: optimize later
|
|
86
|
+
measure_all=True,
|
|
87
|
+
symbols=self.symbols,
|
|
88
|
+
qem_protocol=qem_protocol,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Need to store the measurement groups for computing
|
|
92
|
+
# expectation values later on, stripped of the `qml.expval` wrapper
|
|
93
|
+
self.measurement_groups = [
|
|
94
|
+
[meas.obs for meas in qsc.measurements] for qsc in qscripts
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
def __getstate__(self):
|
|
98
|
+
state = self.__dict__.copy()
|
|
99
|
+
state["postprocessing_fn"] = dill.dumps(self.postprocessing_fn)
|
|
100
|
+
return state
|
|
101
|
+
|
|
102
|
+
def __setstate__(self, state):
|
|
103
|
+
state["postprocessing_fn"] = dill.loads(state["postprocessing_fn"])
|
|
104
|
+
|
|
105
|
+
self.__dict__.update(state)
|
|
106
|
+
|
|
107
|
+
def initialize_circuit_from_params(
|
|
108
|
+
self, param_list, tag_prefix: str = "", precision: int = 8
|
|
109
|
+
) -> Circuit:
|
|
110
|
+
mapping = dict(
|
|
111
|
+
zip(
|
|
112
|
+
map(lambda x: re.escape(str(x)), self.symbols),
|
|
113
|
+
map(lambda x: f"{x:.{precision}f}", param_list),
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
pattern = re.compile("|".join(k for k in mapping.keys()))
|
|
117
|
+
|
|
118
|
+
final_qasm_strs = []
|
|
119
|
+
for circuit_body in self.compiled_circuits_bodies:
|
|
120
|
+
final_qasm_strs.append(
|
|
121
|
+
pattern.sub(lambda match: mapping[match.group(0)], circuit_body)
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
tags = []
|
|
125
|
+
qasm_circuits = []
|
|
126
|
+
for (i, body_str), (j, meas_str) in product(
|
|
127
|
+
enumerate(final_qasm_strs), enumerate(self.measurements)
|
|
128
|
+
):
|
|
129
|
+
qasm_circuits.append(body_str + meas_str)
|
|
130
|
+
|
|
131
|
+
nonempty_subtags = filter(
|
|
132
|
+
None,
|
|
133
|
+
[tag_prefix, f"{self.qem_protocol.name}:{i}", str(j)],
|
|
134
|
+
)
|
|
135
|
+
tags.append("_".join(nonempty_subtags))
|
|
136
|
+
|
|
137
|
+
# Note: The main circuit's parameters are still in symbol form.
|
|
138
|
+
# Not sure if it is necessary for any useful application to parameterize them.
|
|
139
|
+
return Circuit(self.main_circuit, qasm_circuits=qasm_circuits, tags=tags)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
# TODO: delete whole module once Cirq properly supports parameters in openqasm 3.0
|
|
6
|
+
from . import _qasm_export # Does nothing, just initiates the patch
|
|
7
|
+
from ._qasm_import import cirq_circuit_from_qasm
|
divi/exp/cirq/_lexer.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Copyright 2018 The Cirq Developers
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import re
|
|
18
|
+
|
|
19
|
+
import ply.lex as lex
|
|
20
|
+
|
|
21
|
+
from .exception import QasmException
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class QasmLexer:
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self.lex = lex.lex(object=self, debug=False)
|
|
27
|
+
|
|
28
|
+
literals = "{}[]();,+/*-^="
|
|
29
|
+
|
|
30
|
+
reserved = {
|
|
31
|
+
"qubit": "QUBIT",
|
|
32
|
+
"qreg": "QREG",
|
|
33
|
+
"bit": "BIT",
|
|
34
|
+
"creg": "CREG",
|
|
35
|
+
"measure": "MEASURE",
|
|
36
|
+
"reset": "RESET",
|
|
37
|
+
"gate": "GATE",
|
|
38
|
+
"if": "IF",
|
|
39
|
+
"pi": "PI",
|
|
40
|
+
"input": "INPUT", # <-- Add this
|
|
41
|
+
"angle": "ANGLE", # <-- Add this
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
tokens = [
|
|
45
|
+
"FORMAT_SPEC",
|
|
46
|
+
"NUMBER",
|
|
47
|
+
"NATURAL_NUMBER",
|
|
48
|
+
"STDGATESINC",
|
|
49
|
+
"QELIBINC",
|
|
50
|
+
"ID",
|
|
51
|
+
"ARROW",
|
|
52
|
+
"EQ",
|
|
53
|
+
] + list(reserved.values())
|
|
54
|
+
|
|
55
|
+
def t_newline(self, t):
|
|
56
|
+
r"""\n+"""
|
|
57
|
+
t.lexer.lineno += len(t.value)
|
|
58
|
+
|
|
59
|
+
t_ignore = " \t"
|
|
60
|
+
|
|
61
|
+
# all numbers except NATURAL_NUMBERs:
|
|
62
|
+
# it's useful to have this separation to be able to handle indices
|
|
63
|
+
# separately. In case of the parameter expressions, we are "OR"-ing
|
|
64
|
+
# them together (see p_term in _parser.py)
|
|
65
|
+
def t_NUMBER(self, t):
|
|
66
|
+
r"""(
|
|
67
|
+
(
|
|
68
|
+
[0-9]+\.?|
|
|
69
|
+
[0-9]?\.[0-9]+
|
|
70
|
+
)
|
|
71
|
+
[eE][+-]?[0-9]+
|
|
72
|
+
)|
|
|
73
|
+
(
|
|
74
|
+
([0-9]+)?\.[0-9]+|
|
|
75
|
+
[0-9]+\.)"""
|
|
76
|
+
t.value = float(t.value)
|
|
77
|
+
return t
|
|
78
|
+
|
|
79
|
+
def t_NATURAL_NUMBER(self, t):
|
|
80
|
+
r"""\d+"""
|
|
81
|
+
t.value = int(t.value)
|
|
82
|
+
return t
|
|
83
|
+
|
|
84
|
+
def t_FORMAT_SPEC(self, t):
|
|
85
|
+
r"""OPENQASM(\s+)([^\s\t\;]*);"""
|
|
86
|
+
match = re.match(r"""OPENQASM(\s+)([^\s\t;]*);""", t.value)
|
|
87
|
+
t.value = match.groups()[1]
|
|
88
|
+
return t
|
|
89
|
+
|
|
90
|
+
def t_QELIBINC(self, t):
|
|
91
|
+
r"""include(\s+)"qelib1.inc";"""
|
|
92
|
+
return t
|
|
93
|
+
|
|
94
|
+
def t_STDGATESINC(self, t):
|
|
95
|
+
r"""include(\s+)"stdgates.inc";"""
|
|
96
|
+
return t
|
|
97
|
+
|
|
98
|
+
def t_ARROW(self, t):
|
|
99
|
+
"""->"""
|
|
100
|
+
return t
|
|
101
|
+
|
|
102
|
+
def t_EQ(self, t):
|
|
103
|
+
"""=="""
|
|
104
|
+
return t
|
|
105
|
+
|
|
106
|
+
def t_ID(self, t):
|
|
107
|
+
r"""[^\W\d_][\w_]*"""
|
|
108
|
+
# This regex matches any Unicode letter (not digit/underscore) at the start,
|
|
109
|
+
# followed by any number of Unicode word characters or underscores.
|
|
110
|
+
if t.value in QasmLexer.reserved:
|
|
111
|
+
t.type = QasmLexer.reserved[t.value]
|
|
112
|
+
return t
|
|
113
|
+
|
|
114
|
+
t_ID.__doc__ = r"[^\W\d_][\w_]*"
|
|
115
|
+
|
|
116
|
+
def t_COMMENT(self, t):
|
|
117
|
+
r"""//.*"""
|
|
118
|
+
|
|
119
|
+
def t_error(self, t):
|
|
120
|
+
raise QasmException(f"Illegal character '{t.value[0]}' at line {t.lineno}")
|
|
121
|
+
|
|
122
|
+
def input(self, qasm):
|
|
123
|
+
self.lex.input(qasm)
|
|
124
|
+
|
|
125
|
+
def token(self) -> lex.Token | None:
|
|
126
|
+
return self.lex.token()
|