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.
Files changed (58) hide show
  1. divi/__init__.py +8 -0
  2. divi/_pbar.py +73 -0
  3. divi/circuits.py +139 -0
  4. divi/exp/cirq/__init__.py +7 -0
  5. divi/exp/cirq/_lexer.py +126 -0
  6. divi/exp/cirq/_parser.py +889 -0
  7. divi/exp/cirq/_qasm_export.py +37 -0
  8. divi/exp/cirq/_qasm_import.py +35 -0
  9. divi/exp/cirq/exception.py +21 -0
  10. divi/exp/scipy/_cobyla.py +342 -0
  11. divi/exp/scipy/pyprima/LICENCE.txt +28 -0
  12. divi/exp/scipy/pyprima/__init__.py +263 -0
  13. divi/exp/scipy/pyprima/cobyla/__init__.py +0 -0
  14. divi/exp/scipy/pyprima/cobyla/cobyla.py +599 -0
  15. divi/exp/scipy/pyprima/cobyla/cobylb.py +849 -0
  16. divi/exp/scipy/pyprima/cobyla/geometry.py +240 -0
  17. divi/exp/scipy/pyprima/cobyla/initialize.py +269 -0
  18. divi/exp/scipy/pyprima/cobyla/trustregion.py +540 -0
  19. divi/exp/scipy/pyprima/cobyla/update.py +331 -0
  20. divi/exp/scipy/pyprima/common/__init__.py +0 -0
  21. divi/exp/scipy/pyprima/common/_bounds.py +41 -0
  22. divi/exp/scipy/pyprima/common/_linear_constraints.py +46 -0
  23. divi/exp/scipy/pyprima/common/_nonlinear_constraints.py +64 -0
  24. divi/exp/scipy/pyprima/common/_project.py +224 -0
  25. divi/exp/scipy/pyprima/common/checkbreak.py +107 -0
  26. divi/exp/scipy/pyprima/common/consts.py +48 -0
  27. divi/exp/scipy/pyprima/common/evaluate.py +101 -0
  28. divi/exp/scipy/pyprima/common/history.py +39 -0
  29. divi/exp/scipy/pyprima/common/infos.py +30 -0
  30. divi/exp/scipy/pyprima/common/linalg.py +452 -0
  31. divi/exp/scipy/pyprima/common/message.py +336 -0
  32. divi/exp/scipy/pyprima/common/powalg.py +131 -0
  33. divi/exp/scipy/pyprima/common/preproc.py +393 -0
  34. divi/exp/scipy/pyprima/common/present.py +5 -0
  35. divi/exp/scipy/pyprima/common/ratio.py +56 -0
  36. divi/exp/scipy/pyprima/common/redrho.py +49 -0
  37. divi/exp/scipy/pyprima/common/selectx.py +346 -0
  38. divi/interfaces.py +25 -0
  39. divi/parallel_simulator.py +258 -0
  40. divi/qasm.py +220 -0
  41. divi/qem.py +191 -0
  42. divi/qlogger.py +119 -0
  43. divi/qoro_service.py +343 -0
  44. divi/qprog/__init__.py +13 -0
  45. divi/qprog/_graph_partitioning.py +619 -0
  46. divi/qprog/_mlae.py +182 -0
  47. divi/qprog/_qaoa.py +440 -0
  48. divi/qprog/_vqe.py +275 -0
  49. divi/qprog/_vqe_sweep.py +144 -0
  50. divi/qprog/batch.py +235 -0
  51. divi/qprog/optimizers.py +75 -0
  52. divi/qprog/quantum_program.py +493 -0
  53. divi/utils.py +116 -0
  54. qoro_divi-0.2.0b1.dist-info/LICENSE +190 -0
  55. qoro_divi-0.2.0b1.dist-info/LICENSES/Apache-2.0.txt +73 -0
  56. qoro_divi-0.2.0b1.dist-info/METADATA +57 -0
  57. qoro_divi-0.2.0b1.dist-info/RECORD +58 -0
  58. qoro_divi-0.2.0b1.dist-info/WHEEL +4 -0
divi/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ # SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from .qlogger import enable_logging
6
+ from .qoro_service import QoroService
7
+
8
+ enable_logging()
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
@@ -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()