qoro-divi 0.2.0b1__py3-none-any.whl → 0.5.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/__init__.py +1 -2
- divi/backends/__init__.py +9 -0
- divi/backends/_circuit_runner.py +70 -0
- divi/backends/_execution_result.py +70 -0
- divi/backends/_parallel_simulator.py +486 -0
- divi/backends/_qoro_service.py +663 -0
- divi/backends/_qpu_system.py +101 -0
- divi/backends/_results_processing.py +133 -0
- divi/circuits/__init__.py +8 -0
- divi/{exp/cirq → circuits/_cirq}/__init__.py +1 -2
- divi/circuits/_cirq/_parser.py +110 -0
- divi/circuits/_cirq/_qasm_export.py +78 -0
- divi/circuits/_core.py +369 -0
- divi/{qasm.py → circuits/_qasm_conversion.py} +73 -14
- divi/circuits/_qasm_validation.py +694 -0
- divi/qprog/__init__.py +24 -6
- divi/qprog/_expectation.py +181 -0
- divi/qprog/_hamiltonians.py +281 -0
- divi/qprog/algorithms/__init__.py +14 -0
- divi/qprog/algorithms/_ansatze.py +356 -0
- divi/qprog/algorithms/_qaoa.py +572 -0
- divi/qprog/algorithms/_vqe.py +249 -0
- divi/qprog/batch.py +383 -73
- divi/qprog/checkpointing.py +556 -0
- divi/qprog/exceptions.py +9 -0
- divi/qprog/optimizers.py +1014 -43
- divi/qprog/quantum_program.py +231 -413
- divi/qprog/variational_quantum_algorithm.py +995 -0
- divi/qprog/workflows/__init__.py +10 -0
- divi/qprog/{_graph_partitioning.py → workflows/_graph_partitioning.py} +139 -95
- divi/qprog/workflows/_qubo_partitioning.py +220 -0
- divi/qprog/workflows/_vqe_sweep.py +560 -0
- divi/reporting/__init__.py +7 -0
- divi/reporting/_pbar.py +127 -0
- divi/reporting/_qlogger.py +68 -0
- divi/reporting/_reporter.py +133 -0
- {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.5.0.dist-info}/METADATA +43 -15
- qoro_divi-0.5.0.dist-info/RECORD +43 -0
- {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.5.0.dist-info}/WHEEL +1 -1
- qoro_divi-0.5.0.dist-info/licenses/LICENSES/.license-header +3 -0
- divi/_pbar.py +0 -73
- divi/circuits.py +0 -139
- divi/exp/cirq/_lexer.py +0 -126
- divi/exp/cirq/_parser.py +0 -889
- divi/exp/cirq/_qasm_export.py +0 -37
- divi/exp/cirq/_qasm_import.py +0 -35
- divi/exp/cirq/exception.py +0 -21
- divi/exp/scipy/_cobyla.py +0 -342
- divi/exp/scipy/pyprima/LICENCE.txt +0 -28
- divi/exp/scipy/pyprima/__init__.py +0 -263
- divi/exp/scipy/pyprima/cobyla/__init__.py +0 -0
- divi/exp/scipy/pyprima/cobyla/cobyla.py +0 -599
- divi/exp/scipy/pyprima/cobyla/cobylb.py +0 -849
- divi/exp/scipy/pyprima/cobyla/geometry.py +0 -240
- divi/exp/scipy/pyprima/cobyla/initialize.py +0 -269
- divi/exp/scipy/pyprima/cobyla/trustregion.py +0 -540
- divi/exp/scipy/pyprima/cobyla/update.py +0 -331
- divi/exp/scipy/pyprima/common/__init__.py +0 -0
- divi/exp/scipy/pyprima/common/_bounds.py +0 -41
- divi/exp/scipy/pyprima/common/_linear_constraints.py +0 -46
- divi/exp/scipy/pyprima/common/_nonlinear_constraints.py +0 -64
- divi/exp/scipy/pyprima/common/_project.py +0 -224
- divi/exp/scipy/pyprima/common/checkbreak.py +0 -107
- divi/exp/scipy/pyprima/common/consts.py +0 -48
- divi/exp/scipy/pyprima/common/evaluate.py +0 -101
- divi/exp/scipy/pyprima/common/history.py +0 -39
- divi/exp/scipy/pyprima/common/infos.py +0 -30
- divi/exp/scipy/pyprima/common/linalg.py +0 -452
- divi/exp/scipy/pyprima/common/message.py +0 -336
- divi/exp/scipy/pyprima/common/powalg.py +0 -131
- divi/exp/scipy/pyprima/common/preproc.py +0 -393
- divi/exp/scipy/pyprima/common/present.py +0 -5
- divi/exp/scipy/pyprima/common/ratio.py +0 -56
- divi/exp/scipy/pyprima/common/redrho.py +0 -49
- divi/exp/scipy/pyprima/common/selectx.py +0 -346
- divi/interfaces.py +0 -25
- divi/parallel_simulator.py +0 -258
- divi/qlogger.py +0 -119
- divi/qoro_service.py +0 -343
- divi/qprog/_mlae.py +0 -182
- divi/qprog/_qaoa.py +0 -440
- divi/qprog/_vqe.py +0 -275
- divi/qprog/_vqe_sweep.py +0 -144
- divi/utils.py +0 -116
- qoro_divi-0.2.0b1.dist-info/RECORD +0 -58
- /divi/{qem.py → circuits/qem.py} +0 -0
- {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.5.0.dist-info/licenses}/LICENSE +0 -0
- {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.5.0.dist-info/licenses}/LICENSES/Apache-2.0.txt +0 -0
|
@@ -1,346 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
This module provides subroutines that ensure the returned X is optimal among all the calculated
|
|
3
|
-
points in the sense that no other point achieves both lower function value and lower constraint
|
|
4
|
-
violation at the same time. This module is needed only in the constrained case.
|
|
5
|
-
|
|
6
|
-
Translated from Zaikun Zhang's modern-Fortran reference implementation in PRIMA.
|
|
7
|
-
|
|
8
|
-
Dedicated to late Professor M. J. D. Powell FRS (1936--2015).
|
|
9
|
-
|
|
10
|
-
Python translation by Nickolai Belakovski.
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
import numpy as np
|
|
14
|
-
import numpy.typing as npt
|
|
15
|
-
|
|
16
|
-
from .consts import CONSTRMAX, DEBUGGING, EPS, FUNCMAX, REALMAX
|
|
17
|
-
from .present import present
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def isbetter(f1: float, c1: float, f2: float, c2: float, ctol: float) -> bool:
|
|
21
|
-
"""
|
|
22
|
-
This function compares whether FC1 = (F1, C1) is (strictly) better than FC2 = (F2, C2), which
|
|
23
|
-
basically means that (F1 < F2 and C1 <= C2) or (F1 <= F2 and C1 < C2).
|
|
24
|
-
It takes care of the cases where some of these values are NaN or Inf, even though some cases
|
|
25
|
-
should never happen due to the moderated extreme barrier.
|
|
26
|
-
At return, BETTER = TRUE if and only if (F1, C1) is better than (F2, C2).
|
|
27
|
-
Here, C means constraint violation, which is a nonnegative number.
|
|
28
|
-
"""
|
|
29
|
-
|
|
30
|
-
# Preconditions
|
|
31
|
-
if DEBUGGING:
|
|
32
|
-
assert not any(np.isnan([f1, c1]) | np.isposinf([f2, c2]))
|
|
33
|
-
assert not any(np.isnan([f2, c2]) | np.isposinf([f2, c2]))
|
|
34
|
-
assert c1 >= 0 and c2 >= 0
|
|
35
|
-
assert ctol >= 0
|
|
36
|
-
|
|
37
|
-
# ====================#
|
|
38
|
-
# Calculation starts #
|
|
39
|
-
# ====================#
|
|
40
|
-
|
|
41
|
-
is_better = False
|
|
42
|
-
# Even though NaN/+Inf should not occur in FC1 or FC2 due to the moderated extreme barrier, for
|
|
43
|
-
# security and robustness, the code below does not make this assumption.
|
|
44
|
-
is_better = is_better or (
|
|
45
|
-
any(np.isnan([f1, c1]) | np.isposinf([f1, c1]))
|
|
46
|
-
and not any(np.isnan([f2, c2]) | np.isposinf([f2, c2]))
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
is_better = is_better or (f1 < f2 and c1 <= c2)
|
|
50
|
-
is_better = is_better or (f1 <= f2 and c1 < c2)
|
|
51
|
-
|
|
52
|
-
# If C1 <= CTOL and C2 is significantly larger/worse than CTOL, i.e., C2 > MAX(CTOL,CREF),
|
|
53
|
-
# then FC1 is better than FC2 as long as F1 < REALMAX. Normally CREF >= CTOL so MAX(CTOL, CREF)
|
|
54
|
-
# is indeed CREF. However, this may not be true if CTOL > 1E-1*CONSTRMAX.
|
|
55
|
-
cref = 10 * max(EPS, min(ctol, 1.0e-2 * CONSTRMAX)) # The MIN avoids overflow.
|
|
56
|
-
is_better = is_better or (
|
|
57
|
-
f1 < REALMAX and c1 <= ctol and (c2 > max(ctol, cref) or np.isnan(c2))
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
# ==================#
|
|
61
|
-
# Calculation ends #
|
|
62
|
-
# ==================#
|
|
63
|
-
|
|
64
|
-
# Postconditions
|
|
65
|
-
if DEBUGGING:
|
|
66
|
-
assert not (is_better and f1 >= f2 and c1 >= c2)
|
|
67
|
-
assert is_better or not (f1 <= f2 and c1 < c2)
|
|
68
|
-
assert is_better or not (f1 < f2 and c1 <= c2)
|
|
69
|
-
|
|
70
|
-
return is_better
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def savefilt(
|
|
74
|
-
cstrv, ctol, cweight, f, x, nfilt, cfilt, ffilt, xfilt, constr=None, confilt=None
|
|
75
|
-
):
|
|
76
|
-
"""
|
|
77
|
-
This subroutine saves X, F, and CSTRV in XFILT, FFILT, and CFILT (and CONSTR in CONFILT
|
|
78
|
-
if they are present), unless a vector in XFILT[:, :NFILT] is better than X.
|
|
79
|
-
If X is better than some vectors in XFILT[:, :NFILT] then these vectors will be
|
|
80
|
-
removed. If X is not better than any of XFILT[:, :NFILT], but NFILT == MAXFILT,
|
|
81
|
-
then we remove a column from XFILT according to the merit function
|
|
82
|
-
PHI = FFILT + CWEIGHT * max(CFILT - CTOL, 0)
|
|
83
|
-
N.B.:
|
|
84
|
-
1. Only XFILT[:, :NFILT] and FFILT[:, :NFILT] etc contains valid information,
|
|
85
|
-
while XFILT[:, NFILT+1:MAXFILT] and FFILT[:, NFILT+1:MAXFILT] etc are not
|
|
86
|
-
initialized yet.
|
|
87
|
-
2. We decide whether and X is better than another by the ISBETTER function
|
|
88
|
-
"""
|
|
89
|
-
|
|
90
|
-
# Sizes
|
|
91
|
-
if present(constr):
|
|
92
|
-
num_constraints = len(constr)
|
|
93
|
-
else:
|
|
94
|
-
num_constraints = 0
|
|
95
|
-
num_vars = len(x)
|
|
96
|
-
maxfilt = len(ffilt)
|
|
97
|
-
|
|
98
|
-
# Preconditions
|
|
99
|
-
if DEBUGGING:
|
|
100
|
-
# Check the size of X.
|
|
101
|
-
assert num_vars >= 1
|
|
102
|
-
# Check CWEIGHT and CTOL
|
|
103
|
-
assert cweight >= 0
|
|
104
|
-
assert ctol >= 0
|
|
105
|
-
# Check NFILT
|
|
106
|
-
assert nfilt >= 0 and nfilt <= maxfilt
|
|
107
|
-
# Check the sizes of XFILT, FFILT, CFILT.
|
|
108
|
-
assert maxfilt >= 1
|
|
109
|
-
assert np.size(xfilt, 0) == num_vars and np.size(xfilt, 1) == maxfilt
|
|
110
|
-
assert np.size(cfilt) == maxfilt
|
|
111
|
-
# Check the values of XFILT, FFILT, CFILT.
|
|
112
|
-
assert not (np.isnan(xfilt[:, :nfilt])).any()
|
|
113
|
-
assert not any(np.isnan(ffilt[:nfilt]) | np.isposinf(ffilt[:nfilt]))
|
|
114
|
-
assert not any(
|
|
115
|
-
cfilt[:nfilt] < 0 | np.isnan(cfilt[:nfilt]) | np.isposinf(cfilt[:nfilt])
|
|
116
|
-
)
|
|
117
|
-
# Check the values of X, F, CSTRV.
|
|
118
|
-
# X does not contain NaN if X0 does not and the trust-region/geometry steps are proper.
|
|
119
|
-
assert not any(np.isnan(x))
|
|
120
|
-
# F cannot be NaN/+Inf due to the moderated extreme barrier.
|
|
121
|
-
assert not (np.isnan(f) | np.isposinf(f))
|
|
122
|
-
# CSTRV cannot be NaN/+Inf due to the moderated extreme barrier.
|
|
123
|
-
assert not (cstrv < 0 | np.isnan(cstrv) | np.isposinf(cstrv))
|
|
124
|
-
# Check CONSTR and CONFILT.
|
|
125
|
-
assert present(constr) == present(confilt)
|
|
126
|
-
if present(constr):
|
|
127
|
-
# CONSTR cannot contain NaN/-Inf due to the moderated extreme barrier.
|
|
128
|
-
assert not any(np.isnan(constr) | np.isneginf(constr))
|
|
129
|
-
assert (
|
|
130
|
-
np.size(confilt, 0) == num_constraints
|
|
131
|
-
and np.size(confilt, 1) == maxfilt
|
|
132
|
-
)
|
|
133
|
-
assert not (
|
|
134
|
-
np.isnan(confilt[:, :nfilt]) | np.isneginf(confilt[:, :nfilt])
|
|
135
|
-
).any()
|
|
136
|
-
|
|
137
|
-
# ====================#
|
|
138
|
-
# Calculation starts #
|
|
139
|
-
# ====================#
|
|
140
|
-
|
|
141
|
-
# Return immediately if any column of XFILT is better than X.
|
|
142
|
-
if any(
|
|
143
|
-
(
|
|
144
|
-
isbetter(ffilt_i, cfilt_i, f, cstrv, ctol)
|
|
145
|
-
for ffilt_i, cfilt_i in zip(ffilt[:nfilt], cfilt[:nfilt])
|
|
146
|
-
)
|
|
147
|
-
) or any(np.logical_and(ffilt[:nfilt] <= f, cfilt[:nfilt] <= cstrv)):
|
|
148
|
-
return nfilt, cfilt, ffilt, xfilt, confilt
|
|
149
|
-
|
|
150
|
-
# Decide which columns of XFILT to keep.
|
|
151
|
-
keep = np.logical_not(
|
|
152
|
-
[
|
|
153
|
-
isbetter(f, cstrv, ffilt_i, cfilt_i, ctol)
|
|
154
|
-
for ffilt_i, cfilt_i in zip(ffilt[:nfilt], cfilt[:nfilt])
|
|
155
|
-
]
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
# If NFILT == MAXFILT and X is not better than any column of XFILT, then we remove the worst column
|
|
159
|
-
# of XFILT according to the merit function PHI = FFILT + CWEIGHT * MAX(CFILT - CTOL, ZERO).
|
|
160
|
-
if (
|
|
161
|
-
sum(keep) == maxfilt
|
|
162
|
-
): # In this case, NFILT = SIZE(KEEP) = COUNT(KEEP) = MAXFILT > 0.
|
|
163
|
-
cfilt_shifted = np.maximum(cfilt - ctol, 0)
|
|
164
|
-
if cweight <= 0:
|
|
165
|
-
phi = ffilt
|
|
166
|
-
elif np.isposinf(cweight):
|
|
167
|
-
phi = cfilt_shifted
|
|
168
|
-
# We should not use CFILT here; if MAX(CFILT_SHIFTED) is attained at multiple indices, then
|
|
169
|
-
# we will check FFILT to exhaust the remaining degree of freedom.
|
|
170
|
-
else:
|
|
171
|
-
phi = np.maximum(ffilt, -REALMAX)
|
|
172
|
-
phi = np.nan_to_num(
|
|
173
|
-
phi, nan=-REALMAX
|
|
174
|
-
) # Replace NaN with -REALMAX and +/- inf with large numbers
|
|
175
|
-
phi += cweight * cfilt_shifted
|
|
176
|
-
# We select X to maximize PHI. In case there are multiple maximizers, we take the one with the
|
|
177
|
-
# largest CSTRV_SHIFTED; if there are more than one choices, we take the one with the largest F;
|
|
178
|
-
# if there are several candidates, we take the one with the largest CSTRV; if the last comparison
|
|
179
|
-
# still leads to more than one possibilities, then they are equally bad and we choose the first.
|
|
180
|
-
# N.B.:
|
|
181
|
-
# 1. This process is the opposite of selecting KOPT in SELECTX.
|
|
182
|
-
# 2. In finite-precision arithmetic, PHI_1 == PHI_2 and CSTRV_SHIFTED_1 == CSTRV_SHIFTED_2 do
|
|
183
|
-
# not ensure that F_1 == F_2!
|
|
184
|
-
phimax = max(phi)
|
|
185
|
-
cref = max(cfilt_shifted[phi >= phimax])
|
|
186
|
-
fref = max(ffilt[cfilt_shifted >= cref])
|
|
187
|
-
kworst = np.ma.array(cfilt, mask=(ffilt > fref)).argmax()
|
|
188
|
-
if kworst < 0 or kworst >= len(keep): # For security. Should not happen.
|
|
189
|
-
kworst = 0
|
|
190
|
-
keep[kworst] = False
|
|
191
|
-
|
|
192
|
-
# Keep the good xfilt values and remove all the ones that are strictly worse than the new x.
|
|
193
|
-
nfilt = sum(keep)
|
|
194
|
-
index_to_keep = np.where(keep)[0]
|
|
195
|
-
xfilt[:, :nfilt] = xfilt[:, index_to_keep]
|
|
196
|
-
ffilt[:nfilt] = ffilt[index_to_keep]
|
|
197
|
-
cfilt[:nfilt] = cfilt[index_to_keep]
|
|
198
|
-
if confilt is not None and constr is not None:
|
|
199
|
-
confilt[:, :nfilt] = confilt[:, index_to_keep]
|
|
200
|
-
|
|
201
|
-
# Once we have removed all the vectors that are strictly worse than x,
|
|
202
|
-
# we add x to the filter.
|
|
203
|
-
xfilt[:, nfilt] = x
|
|
204
|
-
ffilt[nfilt] = f
|
|
205
|
-
cfilt[nfilt] = cstrv
|
|
206
|
-
if confilt is not None and constr is not None:
|
|
207
|
-
confilt[:, nfilt] = constr
|
|
208
|
-
nfilt += 1 # In Python we need to increment the index afterwards
|
|
209
|
-
|
|
210
|
-
# ==================#
|
|
211
|
-
# Calculation ends #
|
|
212
|
-
# ==================#
|
|
213
|
-
|
|
214
|
-
# Postconditions
|
|
215
|
-
if DEBUGGING:
|
|
216
|
-
# Check NFILT and the sizes of XFILT, FFILT, CFILT.
|
|
217
|
-
assert nfilt >= 1 and nfilt <= maxfilt
|
|
218
|
-
assert np.size(xfilt, 0) == num_vars and np.size(xfilt, 1) == maxfilt
|
|
219
|
-
assert np.size(ffilt) == maxfilt
|
|
220
|
-
assert np.size(cfilt) == maxfilt
|
|
221
|
-
# Check the values of XFILT, FFILT, CFILT.
|
|
222
|
-
assert not (np.isnan(xfilt[:, :nfilt])).any()
|
|
223
|
-
assert not any(np.isnan(ffilt[:nfilt]) | np.isposinf(ffilt[:nfilt]))
|
|
224
|
-
assert not any(
|
|
225
|
-
cfilt[:nfilt] < 0 | np.isnan(cfilt[:nfilt]) | np.isposinf(cfilt[:nfilt])
|
|
226
|
-
)
|
|
227
|
-
# Check that no point in the filter is better than X, and X is better than no point.
|
|
228
|
-
assert not any(
|
|
229
|
-
[
|
|
230
|
-
isbetter(ffilt_i, cfilt_i, f, cstrv, ctol)
|
|
231
|
-
for ffilt_i, cfilt_i in zip(ffilt[:nfilt], cfilt[:nfilt])
|
|
232
|
-
]
|
|
233
|
-
)
|
|
234
|
-
assert not any(
|
|
235
|
-
[
|
|
236
|
-
isbetter(f, cstrv, ffilt_i, cfilt_i, ctol)
|
|
237
|
-
for ffilt_i, cfilt_i in zip(ffilt[:nfilt], cfilt[:nfilt])
|
|
238
|
-
]
|
|
239
|
-
)
|
|
240
|
-
# Check CONFILT.
|
|
241
|
-
if present(confilt):
|
|
242
|
-
assert (
|
|
243
|
-
np.size(confilt, 0) == num_constraints
|
|
244
|
-
and np.size(confilt, 1) == maxfilt
|
|
245
|
-
)
|
|
246
|
-
assert not (
|
|
247
|
-
np.isnan(confilt[:, :nfilt]) | np.isneginf(confilt[:, :nfilt])
|
|
248
|
-
).any()
|
|
249
|
-
|
|
250
|
-
return nfilt, cfilt, ffilt, xfilt, confilt
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
def selectx(fhist: npt.NDArray, chist: npt.NDArray, cweight: float, ctol: float):
|
|
254
|
-
"""
|
|
255
|
-
This subroutine selects X according to FHIST and CHIST, which represents (a part of) history
|
|
256
|
-
of F and CSTRV. Normally, FHIST and CHIST are not the full history but only a filter, e.g. ffilt
|
|
257
|
-
and CFILT generated by SAVEFILT. However, we name them as FHIST and CHIST because the [F, CSTRV]
|
|
258
|
-
in a filter should not dominate each other, but this subroutine does NOT assume such a property.
|
|
259
|
-
N.B.: CTOL is the tolerance of the constraint violation (CSTRV). A point is considered feasible if
|
|
260
|
-
its constraint violation is at most CTOL. Not that CTOL is absolute, not relative.
|
|
261
|
-
"""
|
|
262
|
-
|
|
263
|
-
# Sizes
|
|
264
|
-
nhist = len(fhist)
|
|
265
|
-
|
|
266
|
-
# Preconditions
|
|
267
|
-
if DEBUGGING:
|
|
268
|
-
assert nhist >= 1
|
|
269
|
-
assert np.size(chist) == nhist
|
|
270
|
-
assert not any(np.isnan(fhist) | np.isposinf(fhist))
|
|
271
|
-
assert not any(chist < 0 | np.isnan(chist) | np.isposinf(chist))
|
|
272
|
-
assert cweight >= 0
|
|
273
|
-
assert ctol >= 0
|
|
274
|
-
|
|
275
|
-
# ====================#
|
|
276
|
-
# Calculation starts #
|
|
277
|
-
# ====================#
|
|
278
|
-
|
|
279
|
-
# We select X among the points with F < FREF and CSTRV < CREF.
|
|
280
|
-
# Do NOT use F <= FREF, because F == FREF (FUNCMAX or REALMAX) may mean F == INF in practice!
|
|
281
|
-
if any(np.logical_and(fhist < FUNCMAX, chist < CONSTRMAX)):
|
|
282
|
-
fref = FUNCMAX
|
|
283
|
-
cref = CONSTRMAX
|
|
284
|
-
elif any(np.logical_and(fhist < REALMAX, chist < CONSTRMAX)):
|
|
285
|
-
fref = REALMAX
|
|
286
|
-
cref = CONSTRMAX
|
|
287
|
-
elif any(np.logical_and(fhist < FUNCMAX, chist < REALMAX)):
|
|
288
|
-
fref = FUNCMAX
|
|
289
|
-
cref = REALMAX
|
|
290
|
-
else:
|
|
291
|
-
fref = REALMAX
|
|
292
|
-
cref = REALMAX
|
|
293
|
-
|
|
294
|
-
if not any(np.logical_and(fhist < fref, chist < cref)):
|
|
295
|
-
kopt = nhist - 1
|
|
296
|
-
else:
|
|
297
|
-
# Shift the constraint violations by ctol, so that cstrv <= ctol is regarded as no violation.
|
|
298
|
-
chist_shifted = np.maximum(chist - ctol, 0)
|
|
299
|
-
# cmin is the minimal shift constraint violation attained in the history.
|
|
300
|
-
cmin = np.min(chist_shifted[fhist < fref])
|
|
301
|
-
# We consider only the points whose shifted constraint violations are at most the cref below.
|
|
302
|
-
# N.B.: Without taking np.maximum(EPS, .), cref would be 0 if cmin = 0. In that case, asking for
|
|
303
|
-
# cstrv_shift < cref would be WRONG!
|
|
304
|
-
cref = np.maximum(EPS, 2 * cmin)
|
|
305
|
-
# We use the following phi as our merit function to select X.
|
|
306
|
-
if cweight <= 0:
|
|
307
|
-
phi = fhist
|
|
308
|
-
elif np.isposinf(cweight):
|
|
309
|
-
phi = chist_shifted
|
|
310
|
-
# We should not use chist here; if np.minimum(chist_shifted) is attained at multiple indices, then
|
|
311
|
-
# we will check fhist to exhaust the remaining degree of freedom.
|
|
312
|
-
else:
|
|
313
|
-
phi = np.maximum(fhist, -REALMAX) + cweight * chist_shifted
|
|
314
|
-
# np.maximum(fhist, -REALMAX) makes sure that phi will not contain NaN (unless there is a bug).
|
|
315
|
-
|
|
316
|
-
# We select X to minimize phi subject to f < fref and cstrv_shift <= cref (see the comments
|
|
317
|
-
# above for the reason of taking "<" and "<=" in these two constraints). In case there are
|
|
318
|
-
# multiple minimizers, we take the one with the least cstrv_shift; if there is more than one
|
|
319
|
-
# choice, we take the one with the least f; if there are several candidates, we take the one
|
|
320
|
-
# with the least cstrv; if the last comparison still leads to more than one possibility, then
|
|
321
|
-
# they are equally good and we choose the first.
|
|
322
|
-
# N.B.:
|
|
323
|
-
# 1. This process is the opposite of selecting kworst in savefilt
|
|
324
|
-
# 2. In finite-precision arithmetic, phi_2 == phi_2 and cstrv_shift_1 == cstrv_shifted_2 do
|
|
325
|
-
# not ensure thatn f_1 == f_2!
|
|
326
|
-
phimin = np.min(phi[np.logical_and(fhist < fref, chist_shifted <= cref)])
|
|
327
|
-
cref = np.min(chist_shifted[np.logical_and(fhist < fref, phi <= phimin)])
|
|
328
|
-
fref = np.min(fhist[chist_shifted <= cref])
|
|
329
|
-
# Can't use argmin here because using it with a mask throws off the index
|
|
330
|
-
kopt = np.ma.array(chist, mask=(fhist > fref)).argmin()
|
|
331
|
-
|
|
332
|
-
# ==================#
|
|
333
|
-
# Calculation ends #
|
|
334
|
-
# ==================#
|
|
335
|
-
|
|
336
|
-
# Postconditions
|
|
337
|
-
if DEBUGGING:
|
|
338
|
-
assert kopt >= 0 and kopt < nhist
|
|
339
|
-
assert not any(
|
|
340
|
-
[
|
|
341
|
-
isbetter(fhisti, chisti, fhist[kopt], chist[kopt], ctol)
|
|
342
|
-
for fhisti, chisti in zip(fhist[:nhist], chist[:nhist])
|
|
343
|
-
]
|
|
344
|
-
)
|
|
345
|
-
|
|
346
|
-
return kopt
|
divi/interfaces.py
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
class CircuitRunner(ABC):
|
|
9
|
-
"""
|
|
10
|
-
A generic interface for anything that can "run" quantum circuits.
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
def __init__(self, shots: int):
|
|
14
|
-
if shots <= 0:
|
|
15
|
-
raise ValueError(f"Shots must be a positive integer. Got {shots}.")
|
|
16
|
-
|
|
17
|
-
self._shots = shots
|
|
18
|
-
|
|
19
|
-
@property
|
|
20
|
-
def shots(self):
|
|
21
|
-
return self._shots
|
|
22
|
-
|
|
23
|
-
@abstractmethod
|
|
24
|
-
def submit_circuits(self, circuits: dict[str, str], **kwargs):
|
|
25
|
-
pass
|
divi/parallel_simulator.py
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
2
|
-
#
|
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
|
|
5
|
-
import bisect
|
|
6
|
-
import heapq
|
|
7
|
-
import logging
|
|
8
|
-
from functools import partial
|
|
9
|
-
from multiprocessing import Pool
|
|
10
|
-
from typing import Literal, Optional
|
|
11
|
-
from warnings import warn
|
|
12
|
-
|
|
13
|
-
import qiskit_ibm_runtime.fake_provider as fk_prov
|
|
14
|
-
from qiskit import QuantumCircuit, transpile
|
|
15
|
-
from qiskit.converters import circuit_to_dag
|
|
16
|
-
from qiskit.dagcircuit import DAGOpNode
|
|
17
|
-
from qiskit.providers import Backend
|
|
18
|
-
from qiskit_aer import AerSimulator
|
|
19
|
-
from qiskit_aer.noise import NoiseModel
|
|
20
|
-
|
|
21
|
-
from divi.interfaces import CircuitRunner
|
|
22
|
-
|
|
23
|
-
logger = logging.getLogger(__name__)
|
|
24
|
-
|
|
25
|
-
FAKE_BACKENDS = {
|
|
26
|
-
5: [
|
|
27
|
-
fk_prov.FakeManilaV2,
|
|
28
|
-
fk_prov.FakeBelemV2,
|
|
29
|
-
fk_prov.FakeLimaV2,
|
|
30
|
-
fk_prov.FakeQuitoV2,
|
|
31
|
-
],
|
|
32
|
-
7: [
|
|
33
|
-
fk_prov.FakeOslo,
|
|
34
|
-
fk_prov.FakePerth,
|
|
35
|
-
fk_prov.FakeLagosV2,
|
|
36
|
-
fk_prov.FakeNairobiV2,
|
|
37
|
-
],
|
|
38
|
-
15: [fk_prov.FakeMelbourneV2],
|
|
39
|
-
16: [fk_prov.FakeGuadalupeV2],
|
|
40
|
-
20: [
|
|
41
|
-
fk_prov.FakeAlmadenV2,
|
|
42
|
-
fk_prov.FakeJohannesburgV2,
|
|
43
|
-
fk_prov.FakeSingaporeV2,
|
|
44
|
-
fk_prov.FakeBoeblingenV2,
|
|
45
|
-
],
|
|
46
|
-
27: [
|
|
47
|
-
fk_prov.FakeGeneva,
|
|
48
|
-
fk_prov.FakePeekskill,
|
|
49
|
-
fk_prov.FakeAuckland,
|
|
50
|
-
fk_prov.FakeCairoV2,
|
|
51
|
-
],
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def _find_best_fake_backend(circuit: QuantumCircuit):
|
|
56
|
-
keys = sorted(FAKE_BACKENDS.keys())
|
|
57
|
-
pos = bisect.bisect_left(keys, circuit.num_qubits)
|
|
58
|
-
return FAKE_BACKENDS[keys[pos]] if pos < len(keys) else None
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
class ParallelSimulator(CircuitRunner):
|
|
62
|
-
def __init__(
|
|
63
|
-
self,
|
|
64
|
-
n_processes: int = 2,
|
|
65
|
-
shots: int = 5000,
|
|
66
|
-
simulation_seed: Optional[int] = None,
|
|
67
|
-
qiskit_backend: Backend | Literal["auto"] | None = None,
|
|
68
|
-
noise_model: Optional[NoiseModel] = None,
|
|
69
|
-
):
|
|
70
|
-
"""
|
|
71
|
-
A multi-process wrapper around Qiskit's AerSimulator.
|
|
72
|
-
|
|
73
|
-
Args:
|
|
74
|
-
n_processes (int, optional): Number of parallel processes to use for simulation. Defaults to 2.
|
|
75
|
-
shots (int, optional): Number of shots to perform. Defaults to 5000.
|
|
76
|
-
simulation_seed (Optional[int], optional): Seed for the random number generator to ensure reproducibility. Defaults to None.
|
|
77
|
-
backend (Backend or "auto, optional): A Qiskit backend to initiate the simulator from. If "auto" is passed,
|
|
78
|
-
the best-fit most recent fake backend will be chosen for the given circuit. Defaults to None, resulting in noiseless simulation.
|
|
79
|
-
noise_model (NoiseModel, optional): Qiskit noise model to use in simulation. Defaults to None.
|
|
80
|
-
"""
|
|
81
|
-
super().__init__(shots=shots)
|
|
82
|
-
|
|
83
|
-
if qiskit_backend and noise_model:
|
|
84
|
-
warn(
|
|
85
|
-
"Both `qiskit_backend` and `noise_model` have been provided."
|
|
86
|
-
" `noise_model` will be ignored and the model from the backend will be used instead."
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
self.n_processes = n_processes
|
|
90
|
-
self.engine = "qiskit"
|
|
91
|
-
self.simulation_seed = simulation_seed
|
|
92
|
-
self.qiskit_backend = qiskit_backend
|
|
93
|
-
self.noise_model = noise_model
|
|
94
|
-
|
|
95
|
-
@staticmethod
|
|
96
|
-
def simulate_circuit(
|
|
97
|
-
circuit_data: tuple[str, str],
|
|
98
|
-
shots: int,
|
|
99
|
-
simulation_seed: Optional[int] = None,
|
|
100
|
-
qiskit_backend: Optional[Backend] = None,
|
|
101
|
-
noise_model: Optional[NoiseModel] = None,
|
|
102
|
-
):
|
|
103
|
-
circuit_label, circuit = circuit_data
|
|
104
|
-
|
|
105
|
-
qiskit_circuit = QuantumCircuit.from_qasm_str(circuit)
|
|
106
|
-
|
|
107
|
-
resolved_backend = (
|
|
108
|
-
_find_best_fake_backend(qiskit_circuit)[-1]()
|
|
109
|
-
if qiskit_backend == "auto"
|
|
110
|
-
else qiskit_backend
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
aer_simulator = (
|
|
114
|
-
AerSimulator.from_backend(resolved_backend)
|
|
115
|
-
if qiskit_backend
|
|
116
|
-
else AerSimulator(noise_model=noise_model)
|
|
117
|
-
)
|
|
118
|
-
transpiled_circuit = transpile(qiskit_circuit, aer_simulator)
|
|
119
|
-
|
|
120
|
-
aer_simulator.set_option("seed_simulator", simulation_seed)
|
|
121
|
-
job = aer_simulator.run(transpiled_circuit, shots=shots)
|
|
122
|
-
|
|
123
|
-
result = job.result()
|
|
124
|
-
counts = result.get_counts(0)
|
|
125
|
-
|
|
126
|
-
return {"label": circuit_label, "results": dict(counts)}
|
|
127
|
-
|
|
128
|
-
def set_seed(self, seed: int):
|
|
129
|
-
self.simulation_seed = seed
|
|
130
|
-
|
|
131
|
-
def submit_circuits(self, circuits: dict[str, str]):
|
|
132
|
-
logger.debug(
|
|
133
|
-
f"Simulating {len(circuits)} circuits with {self.n_processes} processes"
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
with Pool(processes=self.n_processes) as pool:
|
|
137
|
-
results = pool.starmap(
|
|
138
|
-
self.simulate_circuit,
|
|
139
|
-
[
|
|
140
|
-
(
|
|
141
|
-
circuit,
|
|
142
|
-
self.shots,
|
|
143
|
-
self.simulation_seed,
|
|
144
|
-
self.qiskit_backend,
|
|
145
|
-
self.noise_model,
|
|
146
|
-
)
|
|
147
|
-
for circuit in circuits.items()
|
|
148
|
-
],
|
|
149
|
-
)
|
|
150
|
-
return results
|
|
151
|
-
|
|
152
|
-
@staticmethod
|
|
153
|
-
def estimate_run_time_single_circuit(
|
|
154
|
-
circuit: str,
|
|
155
|
-
qiskit_backend: Backend | Literal["auto"],
|
|
156
|
-
**transpilation_kwargs,
|
|
157
|
-
) -> float:
|
|
158
|
-
"""
|
|
159
|
-
Estimate the execution time of a quantum circuit on a given backend, accounting for parallel gate execution.
|
|
160
|
-
|
|
161
|
-
Parameters:
|
|
162
|
-
circuit: The quantum circuit to estimate execution time for as a QASM string.
|
|
163
|
-
qiskit_backend: A Qiskit backend to use for gate time estimation.
|
|
164
|
-
|
|
165
|
-
Returns:
|
|
166
|
-
float: Estimated execution time in seconds.
|
|
167
|
-
"""
|
|
168
|
-
qiskit_circuit = QuantumCircuit.from_qasm_str(circuit)
|
|
169
|
-
|
|
170
|
-
resolved_backend = (
|
|
171
|
-
_find_best_fake_backend(qiskit_circuit)[-1]()
|
|
172
|
-
if qiskit_backend == "auto"
|
|
173
|
-
else qiskit_backend
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
transpiled_circuit = transpile(
|
|
177
|
-
qiskit_circuit, resolved_backend, **transpilation_kwargs
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
dag = circuit_to_dag(transpiled_circuit)
|
|
181
|
-
|
|
182
|
-
total_run_time_s = 0.0
|
|
183
|
-
for node in dag.longest_path():
|
|
184
|
-
if not isinstance(node, DAGOpNode):
|
|
185
|
-
continue
|
|
186
|
-
|
|
187
|
-
op_name = node.name
|
|
188
|
-
|
|
189
|
-
if node.num_clbits == 1:
|
|
190
|
-
idx = (node.cargs[0]._index,)
|
|
191
|
-
|
|
192
|
-
if op_name != "measure" and node.num_qubits > 0:
|
|
193
|
-
idx = tuple(qarg._index for qarg in node.qargs)
|
|
194
|
-
|
|
195
|
-
try:
|
|
196
|
-
total_run_time_s += (
|
|
197
|
-
qiskit_backend.instruction_durations.duration_by_name_qubits[
|
|
198
|
-
(op_name, idx)
|
|
199
|
-
][0]
|
|
200
|
-
)
|
|
201
|
-
except KeyError:
|
|
202
|
-
if op_name == "barrier":
|
|
203
|
-
continue
|
|
204
|
-
warn(f"Instruction duration not found: {op_name}")
|
|
205
|
-
|
|
206
|
-
return total_run_time_s
|
|
207
|
-
|
|
208
|
-
@staticmethod
|
|
209
|
-
def estimate_run_time_batch(
|
|
210
|
-
circuits: Optional[list[str]] = None,
|
|
211
|
-
precomputed_duration: Optional[list[float]] = None,
|
|
212
|
-
n_qpus: int = 5,
|
|
213
|
-
**transpilation_kwargs,
|
|
214
|
-
) -> float:
|
|
215
|
-
"""
|
|
216
|
-
Estimate the execution time of a quantum circuit on a given backend, accounting for parallel gate execution.
|
|
217
|
-
|
|
218
|
-
Parameters:
|
|
219
|
-
circuits (list[str]): The quantum circuits to estimate execution time for, as QASM strings.
|
|
220
|
-
precomputed_durations (list[float]): A list of precomputed durations to use.
|
|
221
|
-
n_qpus (int): Number of QPU nodes in the pre-supposed cluster we are estimating runtime against.
|
|
222
|
-
|
|
223
|
-
Returns:
|
|
224
|
-
float: Estimated execution time in seconds.
|
|
225
|
-
"""
|
|
226
|
-
|
|
227
|
-
# Compute the run time estimates for each given circuit, in descending order
|
|
228
|
-
if precomputed_duration is None:
|
|
229
|
-
with Pool() as p:
|
|
230
|
-
estimated_run_times = p.map(
|
|
231
|
-
partial(
|
|
232
|
-
ParallelSimulator.estimate_run_time_single_circuit,
|
|
233
|
-
qiskit_backend="auto",
|
|
234
|
-
**transpilation_kwargs,
|
|
235
|
-
),
|
|
236
|
-
circuits,
|
|
237
|
-
)
|
|
238
|
-
estimated_run_times_sorted = sorted(estimated_run_times, reverse=True)
|
|
239
|
-
else:
|
|
240
|
-
estimated_run_times_sorted = sorted(precomputed_duration, reverse=True)
|
|
241
|
-
|
|
242
|
-
# Just return the longest run time if there are enough QPUs
|
|
243
|
-
if n_qpus >= len(estimated_run_times_sorted):
|
|
244
|
-
return estimated_run_times_sorted[0]
|
|
245
|
-
|
|
246
|
-
# Initialize processor queue with (total_run_time, processor_id)
|
|
247
|
-
# Using a min heap to always get the processor that will be free first
|
|
248
|
-
processors = [(0, i) for i in range(n_qpus)]
|
|
249
|
-
heapq.heapify(processors)
|
|
250
|
-
|
|
251
|
-
# Assign each task to the processor that will be free first
|
|
252
|
-
for run_time in estimated_run_times_sorted:
|
|
253
|
-
current_run_time, processor_id = heapq.heappop(processors)
|
|
254
|
-
new_run_time = current_run_time + run_time
|
|
255
|
-
heapq.heappush(processors, (new_run_time, processor_id))
|
|
256
|
-
|
|
257
|
-
# The total run time is the maximum run time across all processors
|
|
258
|
-
return max(run_time for run_time, _ in processors)
|