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/qasm.py
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from functools import partial
|
|
7
|
+
from itertools import product
|
|
8
|
+
from typing import Optional
|
|
9
|
+
from warnings import warn
|
|
10
|
+
|
|
11
|
+
import cirq
|
|
12
|
+
import numpy as np
|
|
13
|
+
import pennylane as qml
|
|
14
|
+
from pennylane.tape import QuantumScript
|
|
15
|
+
from pennylane.wires import Wires
|
|
16
|
+
from sympy import Symbol
|
|
17
|
+
|
|
18
|
+
from divi.exp.cirq import cirq_circuit_from_qasm
|
|
19
|
+
from divi.qem import QEMProtocol
|
|
20
|
+
|
|
21
|
+
OPENQASM_GATES = {
|
|
22
|
+
"CNOT": "cx",
|
|
23
|
+
"CZ": "cz",
|
|
24
|
+
"U3": "u3",
|
|
25
|
+
"U2": "u2",
|
|
26
|
+
"U1": "u1",
|
|
27
|
+
"Identity": "id",
|
|
28
|
+
"PauliX": "x",
|
|
29
|
+
"PauliY": "y",
|
|
30
|
+
"PauliZ": "z",
|
|
31
|
+
"Hadamard": "h",
|
|
32
|
+
"S": "s",
|
|
33
|
+
"Adjoint(S)": "sdg",
|
|
34
|
+
"T": "t",
|
|
35
|
+
"Adjoint(T)": "tdg",
|
|
36
|
+
"RX": "rx",
|
|
37
|
+
"RY": "ry",
|
|
38
|
+
"RZ": "rz",
|
|
39
|
+
"CRX": "crx",
|
|
40
|
+
"CRY": "cry",
|
|
41
|
+
"CRZ": "crz",
|
|
42
|
+
"SWAP": "swap",
|
|
43
|
+
"Toffoli": "ccx",
|
|
44
|
+
"CSWAP": "cswap",
|
|
45
|
+
"PhaseShift": "u1",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _ops_to_qasm(operations, precision, wires):
|
|
50
|
+
# create the QASM code representing the operations
|
|
51
|
+
qasm_str = ""
|
|
52
|
+
|
|
53
|
+
for op in operations:
|
|
54
|
+
try:
|
|
55
|
+
gate = OPENQASM_GATES[op.name]
|
|
56
|
+
except KeyError as e:
|
|
57
|
+
raise ValueError(
|
|
58
|
+
f"Operation {op.name} not supported by the QASM serializer"
|
|
59
|
+
) from e
|
|
60
|
+
|
|
61
|
+
wire_labels = ",".join([f"q[{wires.index(w)}]" for w in op.wires.tolist()])
|
|
62
|
+
params = ""
|
|
63
|
+
|
|
64
|
+
if op.num_params > 0:
|
|
65
|
+
# If the operation takes parameters, construct a string
|
|
66
|
+
# with parameter values.
|
|
67
|
+
if precision is not None:
|
|
68
|
+
params = (
|
|
69
|
+
"(" + ",".join([f"{p:.{precision}}" for p in op.parameters]) + ")"
|
|
70
|
+
)
|
|
71
|
+
else:
|
|
72
|
+
# use default precision
|
|
73
|
+
params = "(" + ",".join([str(p) for p in op.parameters]) + ")"
|
|
74
|
+
|
|
75
|
+
qasm_str += f"{gate}{params} {wire_labels};\n"
|
|
76
|
+
|
|
77
|
+
return qasm_str
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def to_openqasm(
|
|
81
|
+
main_qscript,
|
|
82
|
+
measurement_groups: list[list[qml.measurements.ExpectationMP]],
|
|
83
|
+
measure_all: bool = True,
|
|
84
|
+
precision: Optional[int] = None,
|
|
85
|
+
return_measurements_separately: bool = False,
|
|
86
|
+
symbols: list[Symbol] = None,
|
|
87
|
+
qem_protocol: Optional[QEMProtocol] = None,
|
|
88
|
+
) -> list[str] | tuple[str, list[str]]:
|
|
89
|
+
"""
|
|
90
|
+
Serialize the circuit as an OpenQASM 2.0 program.
|
|
91
|
+
|
|
92
|
+
A modified version of PennyLane's function that is more compatible with having
|
|
93
|
+
several measurements and incorporates modifications introduced by splitting transforms,
|
|
94
|
+
as well as error mitigation through folding.
|
|
95
|
+
|
|
96
|
+
The measurement outputs can be restricted to only those specified in the script by
|
|
97
|
+
setting ``measure_all=False``.
|
|
98
|
+
|
|
99
|
+
.. note::
|
|
100
|
+
|
|
101
|
+
The serialized OpenQASM program assumes that gate definitions
|
|
102
|
+
in ``qelib1.inc`` are available.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
main_qscript (QuantumScript): the quantum circuit to be converted, as a QuantumScript/QuantumTape object.
|
|
106
|
+
measurement_groups (list[list]): A list of list of commuting observables, generated by the grouping Pennylane transformation.
|
|
107
|
+
measure_all (bool): whether to perform a computational basis measurement on all qubits
|
|
108
|
+
or just those specified in the script
|
|
109
|
+
precision (int): decimal digits to display for parameters
|
|
110
|
+
return_measurements_separately (bool): whether to not append the measurement instructions
|
|
111
|
+
and their diagonalizations to the main circuit QASM code and return separately.
|
|
112
|
+
symbols (list): Sympy symbols present in the circuit. Needed for some QEM routines.
|
|
113
|
+
qem_protocol (QEMProtocol): An optional QEMProtocol object for error mitigation, which may modify the circuit.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
list[str] or tuple[str, list[str]]: OpenQASM serialization of the circuit
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
if qem_protocol and symbols is None:
|
|
120
|
+
raise ValueError(
|
|
121
|
+
"When passing a QEMProtocol instance, the Sympy symbols in the circuit should be provided for the openqasm 3 conversion."
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
wires = main_qscript.wires
|
|
125
|
+
|
|
126
|
+
_to_qasm = partial(_ops_to_qasm, precision=precision, wires=wires)
|
|
127
|
+
|
|
128
|
+
# Add the QASM headers
|
|
129
|
+
main_qasm_str = (
|
|
130
|
+
'OPENQASM 3.0;\ninclude "stdgates.inc";\n'
|
|
131
|
+
if qem_protocol
|
|
132
|
+
else 'OPENQASM 2.0;\ninclude "qelib1.inc";\n'
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if main_qscript.num_wires == 0:
|
|
136
|
+
# empty circuit
|
|
137
|
+
return main_qasm_str
|
|
138
|
+
|
|
139
|
+
if qem_protocol:
|
|
140
|
+
for symbol in symbols:
|
|
141
|
+
main_qasm_str += f"input angle[32] {str(symbol)};\n"
|
|
142
|
+
|
|
143
|
+
# create the quantum and classical registers
|
|
144
|
+
main_qasm_str += (
|
|
145
|
+
f"qubit[{len(wires)}] q;\n" if qem_protocol else f"qreg q[{len(wires)}];\n"
|
|
146
|
+
)
|
|
147
|
+
main_qasm_str += (
|
|
148
|
+
f"bit[{len(wires)}] c;\n" if qem_protocol else f"creg c[{len(wires)}];\n"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Wrapping Sympy Symbols in a numpy object to bypass
|
|
152
|
+
# Pennylane's sanitization
|
|
153
|
+
for op in main_qscript.operations:
|
|
154
|
+
if qml.math.get_interface(*op.data) == "sympy":
|
|
155
|
+
op.data = np.array(op.data)
|
|
156
|
+
|
|
157
|
+
[transformed_tape], _ = qml.transforms.convert_to_numpy_parameters(main_qscript)
|
|
158
|
+
operations = transformed_tape.operations
|
|
159
|
+
|
|
160
|
+
# Decompose the queue
|
|
161
|
+
just_ops = QuantumScript(operations)
|
|
162
|
+
[decomposed_tape], _ = qml.transforms.decompose(
|
|
163
|
+
just_ops, gate_set=lambda obj: obj.name in OPENQASM_GATES
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
main_qasm_str += _to_qasm(decomposed_tape.operations)
|
|
167
|
+
|
|
168
|
+
main_qasm_strs = []
|
|
169
|
+
if qem_protocol:
|
|
170
|
+
for circ in qem_protocol.modify_circuit(cirq_circuit_from_qasm(main_qasm_str)):
|
|
171
|
+
# Convert back to QASM2.0 code, with the symbolic parameters
|
|
172
|
+
qasm_str = cirq.qasm(circ)
|
|
173
|
+
# Remove redundant newlines
|
|
174
|
+
qasm_str = re.sub(r"\n+", "\n", qasm_str)
|
|
175
|
+
# Remove comments
|
|
176
|
+
qasm_str = re.sub(r"^//.*\n?", "", qasm_str, flags=re.MULTILINE)
|
|
177
|
+
# Add missing classical reg
|
|
178
|
+
qasm_str = re.sub(r"qreg q\[(\d+)\];", r"qreg q[\1];creg c[\1];", qasm_str)
|
|
179
|
+
|
|
180
|
+
main_qasm_strs.append(qasm_str)
|
|
181
|
+
else:
|
|
182
|
+
main_qasm_strs.append(main_qasm_str)
|
|
183
|
+
|
|
184
|
+
qasm_circuits = []
|
|
185
|
+
measurement_qasms = []
|
|
186
|
+
|
|
187
|
+
# Create a copy of the program for every measurement that we have
|
|
188
|
+
for meas_group in measurement_groups:
|
|
189
|
+
curr_diag_qasm_str = (
|
|
190
|
+
_to_qasm(diag_ops)
|
|
191
|
+
if (diag_ops := QuantumScript(measurements=meas_group).diagonalizing_gates)
|
|
192
|
+
else ""
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
measure_qasm_str = ""
|
|
196
|
+
if measure_all:
|
|
197
|
+
for wire in range(len(wires)):
|
|
198
|
+
measure_qasm_str += f"measure q[{wire}] -> c[{wire}];\n"
|
|
199
|
+
else:
|
|
200
|
+
measured_wires = Wires.all_wires(
|
|
201
|
+
[m.wires for m in main_qscript.measurements]
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
for w in measured_wires:
|
|
205
|
+
wire_indx = main_qscript.wires.index(w)
|
|
206
|
+
measure_qasm_str += f"measure q[{wire_indx}] -> c[{wire_indx}];\n"
|
|
207
|
+
|
|
208
|
+
measurement_qasms.append(curr_diag_qasm_str + measure_qasm_str)
|
|
209
|
+
|
|
210
|
+
if not return_measurements_separately:
|
|
211
|
+
qasm_circuits.extend(product(main_qasm_strs, measurement_qasms))
|
|
212
|
+
|
|
213
|
+
if len(measurement_groups) == 0:
|
|
214
|
+
warn(
|
|
215
|
+
"No measurement groups provided. Returning the QASM of the circuit operations only."
|
|
216
|
+
)
|
|
217
|
+
qasm_circuits.extend(np.atleast_1d(main_qasm_strs).tolist())
|
|
218
|
+
return qasm_circuits
|
|
219
|
+
|
|
220
|
+
return qasm_circuits or (np.atleast_1d(main_qasm_strs).tolist(), measurement_qasms)
|
divi/qem.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
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 collections.abc import Callable, Sequence
|
|
7
|
+
from functools import partial
|
|
8
|
+
|
|
9
|
+
from cirq.circuits.circuit import Circuit
|
|
10
|
+
from mitiq.zne import combine_results, construct_circuits
|
|
11
|
+
from mitiq.zne.inference import Factory
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class QEMProtocol(ABC):
|
|
15
|
+
"""
|
|
16
|
+
Abstract Base Class for Quantum Error Mitigation (QEM) protocols.
|
|
17
|
+
|
|
18
|
+
All concrete QEM protocols should inherit from this class and implement
|
|
19
|
+
the abstract methods and properties. This ensures a consistent interface
|
|
20
|
+
across different mitigation techniques.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def name(self) -> str:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def modify_circuit(self, cirq_circuit: Circuit) -> Sequence[Circuit]:
|
|
30
|
+
"""
|
|
31
|
+
Modifies a given Cirq circuit into one or more new circuits
|
|
32
|
+
required by the QEM protocol.
|
|
33
|
+
|
|
34
|
+
For example, a Zero Noise Extrapolation (ZNE) protocol might
|
|
35
|
+
produce multiple scaled versions of the input circuit. A simple
|
|
36
|
+
mitigation protocol might return the original circuit unchanged.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
cirq_circuit (cirq.Circuit): The input quantum circuit to be modified.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Sequence[cirq.Circuit]: A sequence (e.g., list or tuple) of
|
|
43
|
+
Cirq circuits to be executed.
|
|
44
|
+
"""
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def postprocess_results(self, results: Sequence[float]) -> float:
|
|
49
|
+
"""
|
|
50
|
+
Applies post-processing (e.g., extrapolation, filtering) to the
|
|
51
|
+
results obtained from executing the modified circuits.
|
|
52
|
+
|
|
53
|
+
This method takes the raw output from quantum circuit executions
|
|
54
|
+
(typically a sequence of expectation values or probabilities) and
|
|
55
|
+
applies the core error mitigation logic to produce a single,
|
|
56
|
+
mitigated result.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
results (Sequence[float]): A sequence of floating-point results,
|
|
60
|
+
corresponding to the executions of the
|
|
61
|
+
circuits returned by `modify_circuit`.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
float: The single, mitigated result after post-processing.
|
|
65
|
+
"""
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class _NoMitigation(QEMProtocol):
|
|
70
|
+
"""
|
|
71
|
+
A dummy default mitigation protocol.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def name(self) -> str:
|
|
76
|
+
return "NoMitigation"
|
|
77
|
+
|
|
78
|
+
def modify_circuit(self, cirq_circuit: Circuit) -> Sequence[Circuit]:
|
|
79
|
+
# Identity, do nothing
|
|
80
|
+
return [cirq_circuit]
|
|
81
|
+
|
|
82
|
+
def postprocess_results(self, results: Sequence[float]) -> float:
|
|
83
|
+
"""
|
|
84
|
+
Returns the single result provided, ensuring only one result is given.
|
|
85
|
+
|
|
86
|
+
If multiple results are provided, it raises a RuntimeError, as this
|
|
87
|
+
protocol expects a single measurement outcome for its input circuit.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
results (Sequence[float]): A sequence containing a single floating-point result.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
float: The single result from the sequence.
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
RuntimeError: If more than one result is provided.
|
|
97
|
+
"""
|
|
98
|
+
if len(results) > 1:
|
|
99
|
+
raise RuntimeError("NoMitigation class received multiple partial results.")
|
|
100
|
+
|
|
101
|
+
return results[0]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class ZNE(QEMProtocol):
|
|
105
|
+
"""
|
|
106
|
+
Implements the Zero Noise Extrapolation (ZNE) quantum error mitigation protocol.
|
|
107
|
+
|
|
108
|
+
This protocol uses `Mitiq`'s functionalities to construct noise-scaled
|
|
109
|
+
circuits and then extrapolate to the zero-noise limit based on the
|
|
110
|
+
obtained results.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __init__(
|
|
114
|
+
self,
|
|
115
|
+
scale_factors: Sequence[float],
|
|
116
|
+
folding_fn: Callable,
|
|
117
|
+
extrapolation_factory: Factory,
|
|
118
|
+
):
|
|
119
|
+
"""
|
|
120
|
+
Initializes a ZNE protocol instance.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
scale_factors (Sequence[float]): A sequence of noise scale factors
|
|
124
|
+
to be applied to the circuits. These
|
|
125
|
+
factors typically range from 1.0 upwards.
|
|
126
|
+
folding_fn (Callable): A callable (e.g., a `functools.partial` object)
|
|
127
|
+
that defines how the circuit should be "folded"
|
|
128
|
+
to increase noise. This function must accept
|
|
129
|
+
a `cirq.Circuit` and a `float` (scale factor)
|
|
130
|
+
as its first two arguments.
|
|
131
|
+
extrapolation_factory (mitiq.zne.inference.Factory): An instance of
|
|
132
|
+
`Mitiq`'s `Factory`
|
|
133
|
+
class, which provides
|
|
134
|
+
the extrapolation method.
|
|
135
|
+
|
|
136
|
+
Raises:
|
|
137
|
+
ValueError: If `scale_factors` is not a sequence of numbers,
|
|
138
|
+
`folding_fn` is not callable, or `extrapolation_factory`
|
|
139
|
+
is not an instance of `mitiq.zne.inference.Factory`.
|
|
140
|
+
"""
|
|
141
|
+
if (
|
|
142
|
+
not isinstance(scale_factors, Sequence)
|
|
143
|
+
or not all(isinstance(elem, (int, float)) for elem in scale_factors)
|
|
144
|
+
or not all(elem >= 1.0 for elem in scale_factors)
|
|
145
|
+
):
|
|
146
|
+
raise ValueError(
|
|
147
|
+
"scale_factors is expected to be a sequence of real numbers >=1."
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if not isinstance(folding_fn, partial):
|
|
151
|
+
raise ValueError(
|
|
152
|
+
"folding_fn is expected to be of type partial with all parameters "
|
|
153
|
+
"except for the circuit object and the scale factor already set."
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if not isinstance(extrapolation_factory, Factory):
|
|
157
|
+
raise ValueError("extrapolation_fn is expected to be of Factory.")
|
|
158
|
+
|
|
159
|
+
self._scale_factors = scale_factors
|
|
160
|
+
self._folding_fn = folding_fn
|
|
161
|
+
self._extrapolation_factory = extrapolation_factory
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def name(self) -> str:
|
|
165
|
+
return "zne"
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def scale_factors(self) -> Sequence[float]:
|
|
169
|
+
return self._scale_factors
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def folding_fn(self):
|
|
173
|
+
return self._folding_fn
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def extrapolation_factory(self):
|
|
177
|
+
return self._extrapolation_factory
|
|
178
|
+
|
|
179
|
+
def modify_circuit(self, cirq_circuit: Circuit) -> Sequence[Circuit]:
|
|
180
|
+
return construct_circuits(
|
|
181
|
+
cirq_circuit,
|
|
182
|
+
scale_factors=self._scale_factors,
|
|
183
|
+
scale_method=self._folding_fn,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def postprocess_results(self, results: Sequence[float]) -> float:
|
|
187
|
+
return combine_results(
|
|
188
|
+
scale_factors=self._scale_factors,
|
|
189
|
+
results=results,
|
|
190
|
+
extrapolation_method=self._extrapolation_factory.extrapolate,
|
|
191
|
+
)
|
divi/qlogger.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import shutil
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _is_jupyter():
|
|
11
|
+
"""
|
|
12
|
+
Checks if the code is running inside a Jupyter Notebook or IPython environment.
|
|
13
|
+
"""
|
|
14
|
+
try:
|
|
15
|
+
from IPython import get_ipython
|
|
16
|
+
|
|
17
|
+
# Check if get_ipython() returns a shell instance (not None)
|
|
18
|
+
# and if the shell class is 'ZMQInteractiveShell' for Jupyter notebooks/qtconsole
|
|
19
|
+
# or 'TerminalInteractiveShell' for IPython console.
|
|
20
|
+
shell = get_ipython().__class__.__name__
|
|
21
|
+
if shell == "ZMQInteractiveShell":
|
|
22
|
+
return True # Jupyter notebook or qtconsole
|
|
23
|
+
elif shell == "TerminalInteractiveShell":
|
|
24
|
+
return False # IPython terminal
|
|
25
|
+
else:
|
|
26
|
+
return False # Other IPython environment (less common for typical Jupyter detection)
|
|
27
|
+
except NameError:
|
|
28
|
+
return False # Not running in IPython
|
|
29
|
+
except ImportError:
|
|
30
|
+
return False # IPython is not installed
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class OverwriteStreamHandler(logging.StreamHandler):
|
|
34
|
+
def __init__(self, stream=None):
|
|
35
|
+
super().__init__(stream)
|
|
36
|
+
|
|
37
|
+
self._last_record = ""
|
|
38
|
+
self._last_message = ""
|
|
39
|
+
|
|
40
|
+
# Worst case: 2 complex emojis (8 chars each) + buffer = 21 extra chars
|
|
41
|
+
self._emoji_buffer = 21
|
|
42
|
+
|
|
43
|
+
self._is_jupyter = _is_jupyter()
|
|
44
|
+
|
|
45
|
+
def emit(self, record):
|
|
46
|
+
msg = self.format(record)
|
|
47
|
+
|
|
48
|
+
if record.message.startswith(r"\c"):
|
|
49
|
+
sep = r"\c"
|
|
50
|
+
msg = f"{msg.split(sep)[0]}{self._last_record} [{record.message[2:-2]}]\r"
|
|
51
|
+
|
|
52
|
+
if msg.endswith("\r\n"):
|
|
53
|
+
overwrite_and_newline = True
|
|
54
|
+
clean_msg = msg[:-2]
|
|
55
|
+
|
|
56
|
+
if not record.message.startswith("\c"):
|
|
57
|
+
self._last_record = record.message[:-2]
|
|
58
|
+
elif msg.endswith("\r"):
|
|
59
|
+
overwrite_and_newline = False
|
|
60
|
+
clean_msg = msg[:-1]
|
|
61
|
+
|
|
62
|
+
if not record.message.startswith(r"\c"):
|
|
63
|
+
self._last_record = record.message[:-1]
|
|
64
|
+
else:
|
|
65
|
+
# Normal message - no overwriting
|
|
66
|
+
self.stream.write(msg + "\n")
|
|
67
|
+
self.stream.flush()
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
# Clear previous line if needed
|
|
71
|
+
if len(self._last_message) > 0:
|
|
72
|
+
if self._is_jupyter:
|
|
73
|
+
clear_length = len(self._last_message) + self._emoji_buffer + 50
|
|
74
|
+
else:
|
|
75
|
+
clear_length = min(
|
|
76
|
+
len(self._last_message) + self._emoji_buffer,
|
|
77
|
+
shutil.get_terminal_size().columns,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
self.stream.write("\r" + " " * clear_length + "\r")
|
|
81
|
+
self.stream.flush()
|
|
82
|
+
|
|
83
|
+
# Write message with appropriate ending
|
|
84
|
+
if overwrite_and_newline:
|
|
85
|
+
self.stream.write(clean_msg + "\n")
|
|
86
|
+
self._last_message = ""
|
|
87
|
+
else:
|
|
88
|
+
self.stream.write(clean_msg + "\r")
|
|
89
|
+
self._last_message = self._strip_ansi(clean_msg)
|
|
90
|
+
|
|
91
|
+
self.stream.flush()
|
|
92
|
+
|
|
93
|
+
def _strip_ansi(self, text):
|
|
94
|
+
"""Remove ANSI escape sequences for accurate length calculation"""
|
|
95
|
+
import re
|
|
96
|
+
|
|
97
|
+
ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
|
|
98
|
+
return ansi_escape.sub("", text)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def enable_logging(level=logging.INFO):
|
|
102
|
+
root_logger = logging.getLogger(__name__.split(".")[0])
|
|
103
|
+
|
|
104
|
+
formatter = logging.Formatter(
|
|
105
|
+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
handler = OverwriteStreamHandler(sys.stdout)
|
|
109
|
+
handler.setFormatter(formatter)
|
|
110
|
+
|
|
111
|
+
root_logger.setLevel(level)
|
|
112
|
+
root_logger.handlers.clear()
|
|
113
|
+
root_logger.addHandler(handler)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def disable_logging():
|
|
117
|
+
root_logger = logging.getLogger(__name__.split(".")[0])
|
|
118
|
+
root_logger.handlers.clear()
|
|
119
|
+
root_logger.setLevel(logging.CRITICAL + 1)
|