iqm-benchmarks 1.3__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.
Potentially problematic release.
This version of iqm-benchmarks might be problematic. Click here for more details.
- iqm/benchmarks/__init__.py +31 -0
- iqm/benchmarks/benchmark.py +109 -0
- iqm/benchmarks/benchmark_definition.py +264 -0
- iqm/benchmarks/benchmark_experiment.py +163 -0
- iqm/benchmarks/compressive_gst/__init__.py +20 -0
- iqm/benchmarks/compressive_gst/compressive_gst.py +1029 -0
- iqm/benchmarks/entanglement/__init__.py +18 -0
- iqm/benchmarks/entanglement/ghz.py +802 -0
- iqm/benchmarks/logging_config.py +29 -0
- iqm/benchmarks/optimization/__init__.py +18 -0
- iqm/benchmarks/optimization/qscore.py +719 -0
- iqm/benchmarks/quantum_volume/__init__.py +21 -0
- iqm/benchmarks/quantum_volume/clops.py +726 -0
- iqm/benchmarks/quantum_volume/quantum_volume.py +854 -0
- iqm/benchmarks/randomized_benchmarking/__init__.py +18 -0
- iqm/benchmarks/randomized_benchmarking/clifford_1q.pkl +0 -0
- iqm/benchmarks/randomized_benchmarking/clifford_2q.pkl +0 -0
- iqm/benchmarks/randomized_benchmarking/clifford_rb/__init__.py +19 -0
- iqm/benchmarks/randomized_benchmarking/clifford_rb/clifford_rb.py +386 -0
- iqm/benchmarks/randomized_benchmarking/interleaved_rb/__init__.py +19 -0
- iqm/benchmarks/randomized_benchmarking/interleaved_rb/interleaved_rb.py +555 -0
- iqm/benchmarks/randomized_benchmarking/mirror_rb/__init__.py +19 -0
- iqm/benchmarks/randomized_benchmarking/mirror_rb/mirror_rb.py +810 -0
- iqm/benchmarks/randomized_benchmarking/multi_lmfit.py +86 -0
- iqm/benchmarks/randomized_benchmarking/randomized_benchmarking_common.py +892 -0
- iqm/benchmarks/readout_mitigation.py +290 -0
- iqm/benchmarks/utils.py +521 -0
- iqm_benchmarks-1.3.dist-info/LICENSE +205 -0
- iqm_benchmarks-1.3.dist-info/METADATA +190 -0
- iqm_benchmarks-1.3.dist-info/RECORD +42 -0
- iqm_benchmarks-1.3.dist-info/WHEEL +5 -0
- iqm_benchmarks-1.3.dist-info/top_level.txt +2 -0
- mGST/LICENSE +21 -0
- mGST/README.md +54 -0
- mGST/additional_fns.py +962 -0
- mGST/algorithm.py +733 -0
- mGST/compatibility.py +238 -0
- mGST/low_level_jit.py +694 -0
- mGST/optimization.py +349 -0
- mGST/qiskit_interface.py +282 -0
- mGST/reporting/figure_gen.py +334 -0
- mGST/reporting/reporting.py +710 -0
mGST/algorithm.py
ADDED
|
@@ -0,0 +1,733 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The main algorithm and functions that perform iteration steps
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from decimal import Decimal
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
from warnings import warn
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
import numpy.linalg as la
|
|
12
|
+
from scipy.linalg import eig, eigh
|
|
13
|
+
from scipy.optimize import minimize
|
|
14
|
+
from tqdm import tqdm
|
|
15
|
+
|
|
16
|
+
from mGST.additional_fns import batch, random_gs, transp
|
|
17
|
+
from mGST.low_level_jit import ddA_derivs, ddB_derivs, ddM, dK, dK_dMdM, objf
|
|
18
|
+
from mGST.optimization import (
|
|
19
|
+
lineobjf_A_geodesic,
|
|
20
|
+
lineobjf_B_geodesic,
|
|
21
|
+
lineobjf_isom_geodesic,
|
|
22
|
+
tangent_proj,
|
|
23
|
+
update_A_geodesic,
|
|
24
|
+
update_B_geodesic,
|
|
25
|
+
update_K_geodesic,
|
|
26
|
+
)
|
|
27
|
+
from mGST.reporting.figure_gen import plot_objf
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def A_SFN_riem_Hess(K, A, B, y, J, d, r, n_povm, lam=1e-3):
|
|
31
|
+
"""Riemannian saddle free Newton step on the POVM parametrization
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
K : numpy array
|
|
36
|
+
Each subarray along the first axis contains a set of Kraus operators.
|
|
37
|
+
The second axis enumerates Kraus operators for a gate specified by the first axis.
|
|
38
|
+
A : numpy array
|
|
39
|
+
Current POVM parametrization
|
|
40
|
+
B : numpy array
|
|
41
|
+
Current initial state parametrization
|
|
42
|
+
y : numpy array
|
|
43
|
+
2D array of measurement outcomes for sequences in J;
|
|
44
|
+
Each column contains the outcome probabilities for a fixed sequence
|
|
45
|
+
J : numpy array
|
|
46
|
+
2D array where each row contains the gate indices of a gate sequence
|
|
47
|
+
length : int
|
|
48
|
+
Length of the test sequences
|
|
49
|
+
d : int
|
|
50
|
+
Number of different gates in the gate set
|
|
51
|
+
r : int
|
|
52
|
+
Superoperator dimension of the gates given by the square of the physical dimension
|
|
53
|
+
rK : int
|
|
54
|
+
Target Kraus rank
|
|
55
|
+
n_povm : int
|
|
56
|
+
Number of POVM-Elements
|
|
57
|
+
lam : float
|
|
58
|
+
Damping parameter for dampled Newton method; Default: 1e-3
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
A_new : numpy array
|
|
63
|
+
Updated POVM parametrization
|
|
64
|
+
"""
|
|
65
|
+
pdim = int(np.sqrt(r))
|
|
66
|
+
n = n_povm * pdim
|
|
67
|
+
nt = n_povm * r
|
|
68
|
+
rho = (B @ B.T.conj()).reshape(-1)
|
|
69
|
+
H = np.zeros((2, nt, 2, nt)).astype(np.complex128)
|
|
70
|
+
P_T = np.zeros((2, nt, 2, nt)).astype(np.complex128)
|
|
71
|
+
Fyconjy = np.zeros((n_povm, r, n_povm, r)).astype(np.complex128)
|
|
72
|
+
Fyy = np.zeros((n_povm, r, n_povm, r)).astype(np.complex128)
|
|
73
|
+
|
|
74
|
+
X = np.einsum("ijkl,ijnm -> iknlm", K, K.conj()).reshape((d, r, r))
|
|
75
|
+
dA_, dMdM, dMconjdM, dconjdA = ddA_derivs(X, A, B, J, y, r, pdim, n_povm)
|
|
76
|
+
|
|
77
|
+
# Second derivatives
|
|
78
|
+
for i in range(n_povm):
|
|
79
|
+
Fyconjy[i, :, i, :] = dMconjdM[i] + dconjdA[i]
|
|
80
|
+
Fyy[i, :, i, :] = dMdM[i]
|
|
81
|
+
|
|
82
|
+
# derivative
|
|
83
|
+
Fy = dA_.reshape(n, pdim)
|
|
84
|
+
Y = A.reshape(n, pdim)
|
|
85
|
+
rGrad = Fy.conj() - Y @ Fy.T @ Y
|
|
86
|
+
G = np.array([rGrad, rGrad.conj()]).reshape(-1)
|
|
87
|
+
|
|
88
|
+
P = np.eye(n) - Y @ Y.T.conj()
|
|
89
|
+
T = transp(n, pdim)
|
|
90
|
+
|
|
91
|
+
# Hessian assembly
|
|
92
|
+
H00 = (
|
|
93
|
+
-(np.kron(Y, Y.T)) @ T @ Fyy.reshape(nt, nt).T
|
|
94
|
+
+ Fyconjy.reshape(nt, nt).T.conj()
|
|
95
|
+
- (np.kron(np.eye(n), Y.T @ Fy)) / 2
|
|
96
|
+
- (np.kron(Y @ Fy.T, np.eye(pdim))) / 2
|
|
97
|
+
- (np.kron(P, Fy.T.conj() @ Y.conj())) / 2
|
|
98
|
+
)
|
|
99
|
+
H01 = (
|
|
100
|
+
Fyy.reshape(nt, nt).T.conj()
|
|
101
|
+
- np.kron(Y, Y.T) @ T @ Fyconjy.reshape(nt, nt).T
|
|
102
|
+
+ (np.kron(Fy.conj(), Y.T) @ T) / 2
|
|
103
|
+
+ (np.kron(Y, Fy.T.conj()) @ T) / 2
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
H[0, :, 0, :] = H00
|
|
107
|
+
H[0, :, 1, :] = H01
|
|
108
|
+
H[1, :, 0, :] = H01.conj()
|
|
109
|
+
H[1, :, 1, :] = H00.conj()
|
|
110
|
+
|
|
111
|
+
P_T[0, :, 0, :] = np.eye(nt) - np.kron(Y @ Y.T.conj(), np.eye(pdim)) / 2
|
|
112
|
+
P_T[0, :, 1, :] = -np.kron(Y, Y.T) @ T / 2
|
|
113
|
+
P_T[1, :, 0, :] = P_T[0, :, 1, :].conj()
|
|
114
|
+
P_T[1, :, 1, :] = P_T[0, :, 0, :].conj()
|
|
115
|
+
|
|
116
|
+
H = H.reshape(2 * nt, 2 * nt) @ P_T.reshape(2 * nt, 2 * nt)
|
|
117
|
+
|
|
118
|
+
# saddle free newton method
|
|
119
|
+
H = (H + H.T.conj()) / 2
|
|
120
|
+
evals, U = eigh(H)
|
|
121
|
+
|
|
122
|
+
# Disregarding gauge directions (zero eigenvalues of the Hessian)
|
|
123
|
+
# inv_diag = evals.copy()
|
|
124
|
+
# inv_diag[np.abs(evals)<1e-14] = 1
|
|
125
|
+
# inv_diag[np.abs(evals)>1e-14] = np.abs(inv_diag[np.abs(evals)>1e-14]) + lam
|
|
126
|
+
# H_abs_inv = U@np.diag(1/inv_diag)@U.T.conj()
|
|
127
|
+
|
|
128
|
+
# Damping all eigenvalues
|
|
129
|
+
H_abs_inv = U @ np.diag(1 / (np.abs(evals) + lam)) @ U.T.conj()
|
|
130
|
+
|
|
131
|
+
Delta_A = ((H_abs_inv @ G)[:nt]).reshape(n, pdim)
|
|
132
|
+
|
|
133
|
+
Delta = tangent_proj(A, Delta_A, 1, n_povm)[0]
|
|
134
|
+
|
|
135
|
+
a = minimize(lineobjf_A_geodesic, 1e-9, args=(Delta, X, A, rho, J, y), method="COBYLA").x
|
|
136
|
+
A_new = update_A_geodesic(A, Delta, a)
|
|
137
|
+
return A_new
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def B_SFN_riem_Hess(K, A, B, y, J, d, r, n_povm, lam=1e-3):
|
|
141
|
+
"""Riemannian saddle free Newton step on the initial state parametrization
|
|
142
|
+
|
|
143
|
+
Parameters
|
|
144
|
+
----------
|
|
145
|
+
K : numpy array
|
|
146
|
+
Each subarray along the first axis contains a set of Kraus operators.
|
|
147
|
+
The second axis enumerates Kraus operators for a gate specified by the first axis.
|
|
148
|
+
A : numpy array
|
|
149
|
+
Current POVM parametrization
|
|
150
|
+
B : numpy array
|
|
151
|
+
Current initial state parametrization
|
|
152
|
+
y : numpy array
|
|
153
|
+
2D array of measurement outcomes for sequences in J;
|
|
154
|
+
Each column contains the outcome probabilities for a fixed sequence
|
|
155
|
+
J : numpy array
|
|
156
|
+
2D array where each row contains the gate indices of a gate sequence
|
|
157
|
+
length : int
|
|
158
|
+
Length of the test sequences
|
|
159
|
+
d : int
|
|
160
|
+
Number of different gates in the gate set
|
|
161
|
+
r : int
|
|
162
|
+
Superoperator dimension of the gates given by the square of the physical dimension
|
|
163
|
+
rK : int
|
|
164
|
+
Target Kraus rank
|
|
165
|
+
n_povm : int
|
|
166
|
+
Number of POVM-Elements
|
|
167
|
+
lam : float
|
|
168
|
+
Damping parameter for dampled Newton method; Default: 1e-3
|
|
169
|
+
|
|
170
|
+
Returns
|
|
171
|
+
-------
|
|
172
|
+
B_new : numpy array
|
|
173
|
+
Updated initial state parametrization
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
pdim = int(np.sqrt(r))
|
|
177
|
+
n = r
|
|
178
|
+
nt = r
|
|
179
|
+
E = np.array([(A[i].T.conj() @ A[i]).reshape(-1) for i in range(n_povm)])
|
|
180
|
+
H = np.zeros((2, nt, 2, nt)).astype(np.complex128)
|
|
181
|
+
P_T = np.zeros((2, nt, 2, nt)).astype(np.complex128)
|
|
182
|
+
Fyconjy = np.zeros((r, r)).astype(np.complex128)
|
|
183
|
+
Fyy = np.zeros((r, r)).astype(np.complex128)
|
|
184
|
+
|
|
185
|
+
X = np.einsum("ijkl,ijnm -> iknlm", K, K.conj()).reshape((d, r, r))
|
|
186
|
+
dB_, dMdM, dMconjdM, dconjdB = ddB_derivs(X, A, B, J, y, r, pdim)
|
|
187
|
+
|
|
188
|
+
# Second derivatives
|
|
189
|
+
Fyconjy = dMconjdM + dconjdB
|
|
190
|
+
Fyy = dMdM
|
|
191
|
+
|
|
192
|
+
# derivative
|
|
193
|
+
Fy = dB_.reshape(n)
|
|
194
|
+
Y = B.reshape(n)
|
|
195
|
+
rGrad = Fy.conj() - Y * (Fy.T @ Y)
|
|
196
|
+
G = np.array([rGrad, rGrad.conj()]).reshape(-1)
|
|
197
|
+
|
|
198
|
+
P = np.eye(n) - np.outer(Y, Y.T.conj())
|
|
199
|
+
|
|
200
|
+
# Hessian assembly
|
|
201
|
+
H00 = (
|
|
202
|
+
-(np.outer(Y, Y.T)) @ Fyy.reshape(nt, nt).T
|
|
203
|
+
+ Fyconjy.reshape(nt, nt).T.conj()
|
|
204
|
+
- np.eye(n) * (Y.T @ Fy) / 2
|
|
205
|
+
- np.outer(Y, Fy.T) / 2
|
|
206
|
+
- P * (Fy.T.conj() @ Y.conj()) / 2
|
|
207
|
+
)
|
|
208
|
+
H01 = (
|
|
209
|
+
Fyy.reshape(nt, nt).T.conj()
|
|
210
|
+
- np.outer(Y, Y.T) @ Fyconjy.reshape(nt, nt).T
|
|
211
|
+
+ np.outer(Fy.conj(), Y.T) / 2
|
|
212
|
+
+ np.outer(Y, Fy.T.conj()) / 2
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
H[0, :, 0, :] = H00
|
|
216
|
+
H[0, :, 1, :] = H01
|
|
217
|
+
H[1, :, 0, :] = H01.conj()
|
|
218
|
+
H[1, :, 1, :] = H00.conj()
|
|
219
|
+
|
|
220
|
+
P_T[0, :, 0, :] = np.eye(nt) - np.outer(Y, Y.T.conj()) / 2
|
|
221
|
+
P_T[0, :, 1, :] = -np.outer(Y, Y.T) / 2
|
|
222
|
+
P_T[1, :, 0, :] = P_T[0, :, 1, :].conj()
|
|
223
|
+
P_T[1, :, 1, :] = P_T[0, :, 0, :].conj()
|
|
224
|
+
|
|
225
|
+
H = H.reshape(2 * nt, 2 * nt) @ P_T.reshape(2 * nt, 2 * nt)
|
|
226
|
+
|
|
227
|
+
# saddle free newton method
|
|
228
|
+
H = (H + H.T.conj()) / 2
|
|
229
|
+
evals, U = eigh(H)
|
|
230
|
+
|
|
231
|
+
# Disregarding gauge directions (zero eigenvalues of the Hessian)
|
|
232
|
+
# inv_diag = evals.copy()
|
|
233
|
+
# inv_diag[np.abs(evals)<1e-14] = 1
|
|
234
|
+
# inv_diag[np.abs(evals)>1e-14] = np.abs(inv_diag[np.abs(evals)>1e-14]) + lam
|
|
235
|
+
# H_abs_inv = U@np.diag(1/inv_diag)@U.T.conj()
|
|
236
|
+
|
|
237
|
+
# Damping all eigenvalues
|
|
238
|
+
H_abs_inv = U @ np.diag(1 / (np.abs(evals) + lam)) @ U.T.conj()
|
|
239
|
+
|
|
240
|
+
Delta = (H_abs_inv @ G)[:nt]
|
|
241
|
+
# Projection onto tangent space
|
|
242
|
+
Delta = Delta - Y * (Y.T.conj() @ Delta + Delta.T.conj() @ Y) / 2
|
|
243
|
+
res = minimize(lineobjf_B_geodesic, 1e-9, args=(Delta, X, E, B, J, y), method="COBYLA", options={"maxiter": 20})
|
|
244
|
+
a = res.x
|
|
245
|
+
|
|
246
|
+
B_new = update_B_geodesic(B, Delta, a)
|
|
247
|
+
return B_new
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def gd(K, E, rho, y, J, d, r, rK, fixed_gates, ls="COBYLA"):
|
|
251
|
+
"""Do Riemannian gradient descent optimization step on gates
|
|
252
|
+
|
|
253
|
+
Parameters
|
|
254
|
+
----------
|
|
255
|
+
K : numpy array
|
|
256
|
+
Each subarray along the first axis contains a set of Kraus operators.
|
|
257
|
+
The second axis enumerates Kraus operators for a gate specified by the first axis.
|
|
258
|
+
E : numpy array
|
|
259
|
+
Current POVM estimate
|
|
260
|
+
rho : numpy array
|
|
261
|
+
Current initial state estimate
|
|
262
|
+
y : numpy array
|
|
263
|
+
2D array of measurement outcomes for sequences in J;
|
|
264
|
+
Each column contains the outcome probabilities for a fixed sequence
|
|
265
|
+
J : numpy array
|
|
266
|
+
2D array where each row contains the gate indices of a gate sequence
|
|
267
|
+
length : int
|
|
268
|
+
Length of the test sequences
|
|
269
|
+
d : int
|
|
270
|
+
Number of different gates in the gate set
|
|
271
|
+
r : int
|
|
272
|
+
Superoperator dimension of the gates given by the square of the physical dimension
|
|
273
|
+
rK : int
|
|
274
|
+
Target Kraus rank
|
|
275
|
+
ls : {"COBYLA", ...}
|
|
276
|
+
Line search method, takes "method" arguments of scipy.optimize.minimize
|
|
277
|
+
|
|
278
|
+
Returns
|
|
279
|
+
-------
|
|
280
|
+
K_new : numpy array
|
|
281
|
+
Updated Kraus parametrizations
|
|
282
|
+
|
|
283
|
+
Notes:
|
|
284
|
+
Gradient descent using the Riemannian gradient and updating along the geodesic.
|
|
285
|
+
The step size is determined by minimizing the objective function in the step size parameter.
|
|
286
|
+
"""
|
|
287
|
+
# setup
|
|
288
|
+
pdim = int(np.sqrt(r))
|
|
289
|
+
n = rK * pdim
|
|
290
|
+
Delta = np.zeros((d, n, pdim)).astype(np.complex128)
|
|
291
|
+
X = np.einsum("ijkl,ijnm -> iknlm", K, K.conj()).reshape((d, r, r))
|
|
292
|
+
|
|
293
|
+
dK_ = dK(X, K, E, rho, J, y, d, r, rK)
|
|
294
|
+
for k in np.where(~fixed_gates)[0]:
|
|
295
|
+
# derivative
|
|
296
|
+
Fy = dK_[k].reshape(n, pdim)
|
|
297
|
+
Y = K[k].reshape(n, pdim)
|
|
298
|
+
# Riem. gradient taken from conjugate derivative
|
|
299
|
+
rGrad = Fy.conj() - Y @ Fy.T @ Y
|
|
300
|
+
Delta[k] = rGrad
|
|
301
|
+
|
|
302
|
+
Delta = tangent_proj(K, Delta, d, rK)
|
|
303
|
+
res = minimize(lineobjf_isom_geodesic, 1e-8, args=(Delta, K, E, rho, J, y), method=ls, options={"maxiter": 200})
|
|
304
|
+
a = res.x
|
|
305
|
+
K_new = update_K_geodesic(K, Delta, a)
|
|
306
|
+
|
|
307
|
+
return K_new
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def SFN_riem_Hess(K, E, rho, y, J, d, r, rK, lam=1e-3, ls="COBYLA", fixed_gates=None):
|
|
311
|
+
"""Riemannian saddle free Newton step on each gate individually
|
|
312
|
+
|
|
313
|
+
Parameters
|
|
314
|
+
----------
|
|
315
|
+
K : numpy array
|
|
316
|
+
Each subarray along the first axis contains a set of Kraus operators.
|
|
317
|
+
The second axis enumerates Kraus operators for a gate specified by the first axis.
|
|
318
|
+
E : numpy array
|
|
319
|
+
Current POVM estimate
|
|
320
|
+
rho : numpy array
|
|
321
|
+
Current initial state estimate
|
|
322
|
+
y : numpy array
|
|
323
|
+
2D array of measurement outcomes for sequences in J;
|
|
324
|
+
Each column contains the outcome probabilities for a fixed sequence
|
|
325
|
+
J : numpy array
|
|
326
|
+
2D array where each row contains the gate indices of a gate sequence
|
|
327
|
+
length : int
|
|
328
|
+
Length of the test sequences
|
|
329
|
+
d : int
|
|
330
|
+
Number of different gates in the gate set
|
|
331
|
+
r : int
|
|
332
|
+
Superoperator dimension of the gates given by the square of the physical dimension
|
|
333
|
+
rK : int
|
|
334
|
+
Target Kraus rank
|
|
335
|
+
lam : float
|
|
336
|
+
Damping parameter for dampled Newton method; Default: 1e-3
|
|
337
|
+
ls : {"COBYLA", ...}
|
|
338
|
+
Line search method, takes "method" arguments of scipy.optimize.minimize
|
|
339
|
+
fixed_gates : List
|
|
340
|
+
List of gate indices which are not optimized over and assumed as fixed
|
|
341
|
+
|
|
342
|
+
Returns
|
|
343
|
+
-------
|
|
344
|
+
K_new : numpy array
|
|
345
|
+
Updated Kraus parametrizations
|
|
346
|
+
"""
|
|
347
|
+
# setup
|
|
348
|
+
pdim = int(np.sqrt(r))
|
|
349
|
+
n = rK * pdim
|
|
350
|
+
nt = rK * r
|
|
351
|
+
H = np.zeros((2 * nt, 2 * nt)).astype(np.complex128)
|
|
352
|
+
P_T = np.zeros((2 * nt, 2 * nt)).astype(np.complex128)
|
|
353
|
+
Delta_K = np.zeros((d, rK, pdim, pdim)).astype(np.complex128)
|
|
354
|
+
X = np.einsum("ijkl,ijnm -> iknlm", K, K.conj()).reshape((d, r, r))
|
|
355
|
+
if not fixed_gates:
|
|
356
|
+
fixed_gates = []
|
|
357
|
+
|
|
358
|
+
# compute derivatives
|
|
359
|
+
dK_, dM10, dM11 = dK_dMdM(X, K, E, rho, J, y, d, r, rK)
|
|
360
|
+
dd, dconjd = ddM(X, K, E, rho, J, y, d, r, rK)
|
|
361
|
+
|
|
362
|
+
# Second derivatives
|
|
363
|
+
Fyconjy = dM11.reshape(d, nt, d, nt) + np.einsum("ijklmnop->ikmojlnp", dconjd).reshape((d, nt, d, nt))
|
|
364
|
+
Fyy = dM10.reshape(d, nt, d, nt) + np.einsum("ijklmnop->ikmojlnp", dd).reshape((d, nt, d, nt))
|
|
365
|
+
|
|
366
|
+
for k in np.where(~fixed_gates)[0]:
|
|
367
|
+
Fy = dK_[k].reshape(n, pdim)
|
|
368
|
+
Y = K[k].reshape(n, pdim)
|
|
369
|
+
# riemannian gradient, taken from conjugate derivative
|
|
370
|
+
rGrad = Fy.conj() - Y @ Fy.T @ Y
|
|
371
|
+
G = np.array([rGrad, rGrad.conj()]).reshape(-1)
|
|
372
|
+
|
|
373
|
+
P = np.eye(n) - Y @ Y.T.conj()
|
|
374
|
+
T = transp(n, pdim)
|
|
375
|
+
|
|
376
|
+
# Riemannian Hessian with correction terms
|
|
377
|
+
H00 = (
|
|
378
|
+
-(np.kron(Y, Y.T)) @ T @ Fyy[k, :, k, :].T
|
|
379
|
+
+ Fyconjy[k, :, k, :].T.conj()
|
|
380
|
+
- (np.kron(np.eye(n), Y.T @ Fy)) / 2
|
|
381
|
+
- (np.kron(Y @ Fy.T, np.eye(pdim))) / 2
|
|
382
|
+
- (np.kron(P, Fy.T.conj() @ Y.conj())) / 2
|
|
383
|
+
)
|
|
384
|
+
H01 = (
|
|
385
|
+
Fyy[k, :, k, :].T.conj()
|
|
386
|
+
- np.kron(Y, Y.T) @ T @ Fyconjy[k, :, k, :].T
|
|
387
|
+
+ (np.kron(Fy.conj(), Y.T) @ T) / 2
|
|
388
|
+
+ (np.kron(Y, Fy.T.conj()) @ T) / 2
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
H[:nt, :nt] = H00
|
|
392
|
+
H[:nt, nt:] = H01
|
|
393
|
+
H[nt:, :nt] = H[:nt, nt:].conj()
|
|
394
|
+
H[nt:, nt:] = H[:nt, :nt].conj()
|
|
395
|
+
|
|
396
|
+
# Tangent space projection
|
|
397
|
+
P_T[:nt, :nt] = np.eye(nt) - np.kron(Y @ Y.T.conj(), np.eye(pdim)) / 2
|
|
398
|
+
P_T[:nt, nt:] = -np.kron(Y, Y.T) @ T / 2
|
|
399
|
+
P_T[nt:, :nt] = P_T[:nt, nt:].conj()
|
|
400
|
+
P_T[nt:, nt:] = P_T[:nt, :nt].conj()
|
|
401
|
+
|
|
402
|
+
H = H @ P_T
|
|
403
|
+
|
|
404
|
+
# saddle free newton method
|
|
405
|
+
evals, S = eig(H)
|
|
406
|
+
|
|
407
|
+
H_abs_inv = S @ np.diag(1 / (np.abs(evals) + lam)) @ la.inv(S)
|
|
408
|
+
Delta_K[k] = ((H_abs_inv @ G)[:nt]).reshape(rK, pdim, pdim)
|
|
409
|
+
|
|
410
|
+
Delta = tangent_proj(K, Delta_K, d, rK)
|
|
411
|
+
|
|
412
|
+
res = minimize(lineobjf_isom_geodesic, 1e-8, args=(Delta, K, E, rho, J, y), method=ls, options={"maxiter": 200})
|
|
413
|
+
a = res.x
|
|
414
|
+
K_new = update_K_geodesic(K, Delta, a)
|
|
415
|
+
|
|
416
|
+
return K_new
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def SFN_riem_Hess_full(K, E, rho, y, J, d, r, rK, lam=1e-3, ls="COBYLA"):
|
|
420
|
+
"""Riemannian saddle free Newton step on product manifold of all gates
|
|
421
|
+
|
|
422
|
+
Parameters
|
|
423
|
+
----------
|
|
424
|
+
K : numpy array
|
|
425
|
+
Each subarray along the first axis contains a set of Kraus operators.
|
|
426
|
+
The second axis enumerates Kraus operators for a gate specified by the first axis.
|
|
427
|
+
E : numpy array
|
|
428
|
+
Current POVM estimate
|
|
429
|
+
rho : numpy array
|
|
430
|
+
Current initial state estimate
|
|
431
|
+
y : numpy array
|
|
432
|
+
2D array of measurement outcomes for sequences in J;
|
|
433
|
+
Each column contains the outcome probabilities for a fixed sequence
|
|
434
|
+
J : numpy array
|
|
435
|
+
2D array where each row contains the gate indices of a gate sequence
|
|
436
|
+
length : int
|
|
437
|
+
Length of the test sequences
|
|
438
|
+
d : int
|
|
439
|
+
Number of different gates in the gate set
|
|
440
|
+
r : int
|
|
441
|
+
Superoperator dimension of the gates given by the square of the physical dimension
|
|
442
|
+
rK : int
|
|
443
|
+
Target Kraus rank
|
|
444
|
+
lam : float
|
|
445
|
+
Damping parameter for dampled Newton method; Default: 1e-3
|
|
446
|
+
ls : {"COBYLA", ...}
|
|
447
|
+
Line search method, takes "method" arguments of scipy.optimize.minimize
|
|
448
|
+
|
|
449
|
+
Returns
|
|
450
|
+
-------
|
|
451
|
+
K_new : numpy array
|
|
452
|
+
Updated Kraus parametrizations
|
|
453
|
+
"""
|
|
454
|
+
pdim = int(np.sqrt(r))
|
|
455
|
+
n = rK * pdim
|
|
456
|
+
nt = rK * r
|
|
457
|
+
H = np.zeros((2, d, nt, 2, d, nt)).astype(np.complex128)
|
|
458
|
+
P_T = np.zeros((2, d, nt, 2, d, nt)).astype(np.complex128)
|
|
459
|
+
G = np.zeros((2, d, nt)).astype(np.complex128)
|
|
460
|
+
X = np.einsum("ijkl,ijnm -> iknlm", K, K.conj()).reshape((d, r, r))
|
|
461
|
+
|
|
462
|
+
# compute derivatives
|
|
463
|
+
dK_, dM10, dM11 = dK_dMdM(X, K, E, rho, J, y, d, r, rK)
|
|
464
|
+
dd, dconjd = ddM(X, K, E, rho, J, y, d, r, rK)
|
|
465
|
+
|
|
466
|
+
# Second derivatives
|
|
467
|
+
Fyconjy = dM11.reshape(d, nt, d, nt) + np.einsum("ijklmnop->ikmojlnp", dconjd).reshape((d, nt, d, nt))
|
|
468
|
+
Fyy = dM10.reshape(d, nt, d, nt) + np.einsum("ijklmnop->ikmojlnp", dd).reshape((d, nt, d, nt))
|
|
469
|
+
|
|
470
|
+
for k in range(d):
|
|
471
|
+
Fy = dK_[k].reshape((n, pdim))
|
|
472
|
+
Y = K[k].reshape((n, pdim))
|
|
473
|
+
rGrad = Fy.conj() - Y @ Fy.T @ Y
|
|
474
|
+
|
|
475
|
+
G[0, k, :] = rGrad.reshape(-1)
|
|
476
|
+
G[1, k, :] = rGrad.conj().reshape(-1)
|
|
477
|
+
|
|
478
|
+
P = np.eye(n) - Y @ Y.T.conj()
|
|
479
|
+
T = transp(n, pdim)
|
|
480
|
+
H00 = (
|
|
481
|
+
-(np.kron(Y, Y.T)) @ T @ Fyy[k, :, k, :].T
|
|
482
|
+
+ Fyconjy[k, :, k, :].T.conj()
|
|
483
|
+
- (np.kron(np.eye(n), Y.T @ Fy)) / 2
|
|
484
|
+
- (np.kron(Y @ Fy.T, np.eye(pdim))) / 2
|
|
485
|
+
- (np.kron(P, Fy.T.conj() @ Y.conj())) / 2
|
|
486
|
+
)
|
|
487
|
+
H01 = (
|
|
488
|
+
Fyy[k, :, k, :].T.conj()
|
|
489
|
+
- np.kron(Y, Y.T) @ T @ Fyconjy[k, :, k, :].T
|
|
490
|
+
+ (np.kron(Fy.conj(), Y.T) @ T) / 2
|
|
491
|
+
+ (np.kron(Y, Fy.T.conj()) @ T) / 2
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
# Riemannian Hessian with correction terms
|
|
495
|
+
H[0, k, :, 0, k, :] = H00
|
|
496
|
+
H[0, k, :, 1, k, :] = H01
|
|
497
|
+
H[1, k, :, 0, k, :] = H01.conj()
|
|
498
|
+
H[1, k, :, 1, k, :] = H00.conj()
|
|
499
|
+
|
|
500
|
+
# Tangent space projection
|
|
501
|
+
P_T[0, k, :, 0, k, :] = np.eye(nt) - np.kron(Y @ Y.T.conj(), np.eye(pdim)) / 2
|
|
502
|
+
P_T[0, k, :, 1, k, :] = -np.kron(Y, Y.T) @ T / 2
|
|
503
|
+
P_T[1, k, :, 0, k, :] = P_T[0, k, :, 1, k, :].conj()
|
|
504
|
+
P_T[1, k, :, 1, k, :] = P_T[0, k, :, 0, k, :].conj()
|
|
505
|
+
|
|
506
|
+
for k2 in range(d):
|
|
507
|
+
if k2 != k:
|
|
508
|
+
Yk2 = K[k2].reshape(n, pdim)
|
|
509
|
+
H[0, k2, :, 0, k, :] = Fyconjy[k, :, k2, :].T.conj() - np.kron(Yk2, Yk2.T) @ T @ Fyy[k, :, k2, :].T
|
|
510
|
+
H[0, k2, :, 1, k, :] = Fyy[k, :, k2, :].T.conj() - np.kron(Yk2, Yk2.T) @ T @ Fyconjy[k, :, k2, :].T
|
|
511
|
+
H[1, k2, :, 0, k, :] = H[0, k2, :, 1, k, :].conj()
|
|
512
|
+
H[1, k2, :, 1, k, :] = H[0, k2, :, 0, k, :].conj()
|
|
513
|
+
|
|
514
|
+
H = H.reshape(2 * d * nt, -1) @ P_T.reshape((2 * d * nt, -1))
|
|
515
|
+
|
|
516
|
+
# application of saddle free newton method
|
|
517
|
+
H = (H + H.T.conj()) / 2
|
|
518
|
+
evals, U = eigh(H)
|
|
519
|
+
|
|
520
|
+
# Damping all eigenvalues
|
|
521
|
+
H_abs_inv = U @ np.diag(1 / (np.abs(evals) + lam)) @ U.T.conj()
|
|
522
|
+
Delta_K = ((H_abs_inv @ G.reshape(-1))[: d * nt]).reshape((d, rK, pdim, pdim))
|
|
523
|
+
|
|
524
|
+
# Delta_K is already in tangent space but not to sufficient numerical accuracy
|
|
525
|
+
Delta = tangent_proj(K, Delta_K, d, rK)
|
|
526
|
+
res = minimize(lineobjf_isom_geodesic, 1e-8, args=(Delta, K, E, rho, J, y), method=ls, options={"maxiter": 20})
|
|
527
|
+
a = res.x
|
|
528
|
+
K_new = update_K_geodesic(K, Delta, a)
|
|
529
|
+
return K_new
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def optimize(y, J, d, r, rK, n_povm, method, K, rho, A, B, fixed_elements):
|
|
533
|
+
"""Full gate set optimization update alternating on E, K and rho
|
|
534
|
+
|
|
535
|
+
Parameters
|
|
536
|
+
----------
|
|
537
|
+
y : numpy array
|
|
538
|
+
2D array of measurement outcomes for sequences in J;
|
|
539
|
+
Each column contains the outcome probabilities for a fixed sequence
|
|
540
|
+
J : numpy array
|
|
541
|
+
2D array where each row contains the gate indices of a gate sequence
|
|
542
|
+
d : int
|
|
543
|
+
Number of different gates in the gate set
|
|
544
|
+
r : int
|
|
545
|
+
Superoperator dimension of the gates given by the square of the physical dimension
|
|
546
|
+
rK : int
|
|
547
|
+
Target Kraus rank
|
|
548
|
+
n_povm : int
|
|
549
|
+
Number of POVM-Elements
|
|
550
|
+
method : {"SFN", "GD"}
|
|
551
|
+
Optimization method, Default: "SFN"
|
|
552
|
+
K : numpy array
|
|
553
|
+
Current estimates of Kraus operators
|
|
554
|
+
E : numpy array
|
|
555
|
+
Current POVM estimate
|
|
556
|
+
rho : numpy array
|
|
557
|
+
Current initial state estimate
|
|
558
|
+
A : numpy array
|
|
559
|
+
Current POVM parametrization
|
|
560
|
+
B : numpy array
|
|
561
|
+
Current initial state parametrization
|
|
562
|
+
|
|
563
|
+
Returns
|
|
564
|
+
-------
|
|
565
|
+
K_new : numpy array
|
|
566
|
+
Updated estimates of Kraus operators
|
|
567
|
+
X_new : numpy array
|
|
568
|
+
Updated estimates of superoperatos corresponding to K_new
|
|
569
|
+
E_new : numpy array
|
|
570
|
+
Updated POVM estimate
|
|
571
|
+
rho_new : numpy array
|
|
572
|
+
Updated initial state estimate
|
|
573
|
+
A_new : numpy array
|
|
574
|
+
Updated POVM parametrization
|
|
575
|
+
B_new : numpy array
|
|
576
|
+
Updated initial state parametrization
|
|
577
|
+
"""
|
|
578
|
+
if "E" in fixed_elements:
|
|
579
|
+
A_new = A
|
|
580
|
+
E_new = np.array([(A_new[i].T.conj() @ A_new[i]).reshape(-1) for i in range(n_povm)])
|
|
581
|
+
else:
|
|
582
|
+
A_new = A_SFN_riem_Hess(K, A, B, y, J, d, r, n_povm)
|
|
583
|
+
E_new = np.array([(A_new[i].T.conj() @ A_new[i]).reshape(-1) for i in range(n_povm)])
|
|
584
|
+
|
|
585
|
+
if any(((f"G%i" % i in fixed_elements) for i in range(d))):
|
|
586
|
+
fixed_gates = np.array([(f"G%i" % i in fixed_elements) for i in range(d)])
|
|
587
|
+
if method == "SFN":
|
|
588
|
+
K_new = SFN_riem_Hess(K, E_new, rho, y, J, d, r, rK, lam=1e-3, ls="COBYLA", fixed_gates=fixed_gates)
|
|
589
|
+
else:
|
|
590
|
+
K_new = gd(K, E_new, rho, y, J, d, r, rK, ls="COBYLA", fixed_gates=fixed_gates)
|
|
591
|
+
else:
|
|
592
|
+
if method == "SFN":
|
|
593
|
+
K_new = SFN_riem_Hess_full(K, E_new, rho, y, J, d, r, rK, lam=1e-3, ls="COBYLA")
|
|
594
|
+
else:
|
|
595
|
+
fixed_gates = np.array([(f"G%i" % i in fixed_elements) for i in range(d)])
|
|
596
|
+
K_new = gd(K, E_new, rho, y, J, d, r, rK, fixed_gates=fixed_gates, ls="COBYLA")
|
|
597
|
+
|
|
598
|
+
if "rho" in fixed_elements:
|
|
599
|
+
rho_new = rho
|
|
600
|
+
B_new = B
|
|
601
|
+
else:
|
|
602
|
+
B_new = B_SFN_riem_Hess(K_new, A_new, B, y, J, d, r, n_povm, lam=1e-3)
|
|
603
|
+
rho_new = (B_new @ B_new.T.conj()).reshape(-1)
|
|
604
|
+
X_new = np.einsum("ijkl,ijnm -> iknlm", K_new, K_new.conj()).reshape((d, r, r))
|
|
605
|
+
return K_new, X_new, E_new, rho_new, A_new, B_new
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def run_mGST(
|
|
609
|
+
*args,
|
|
610
|
+
method="SFN",
|
|
611
|
+
max_inits=10,
|
|
612
|
+
max_iter=200,
|
|
613
|
+
final_iter=70,
|
|
614
|
+
target_rel_prec=1e-4,
|
|
615
|
+
threshold_multiplier=3,
|
|
616
|
+
fixed_elements=None,
|
|
617
|
+
init=None,
|
|
618
|
+
testing=False,
|
|
619
|
+
): # pylint: disable=too-many-branches
|
|
620
|
+
"""Main mGST routine
|
|
621
|
+
|
|
622
|
+
Parameters
|
|
623
|
+
----------
|
|
624
|
+
y : numpy array
|
|
625
|
+
2D array of measurement outcomes for sequences in J;
|
|
626
|
+
Each column contains the outcome probabilities for a fixed sequence
|
|
627
|
+
J : numpy array
|
|
628
|
+
2D array where each row contains the gate indices of a gate sequence
|
|
629
|
+
length : int
|
|
630
|
+
Length of the test sequences
|
|
631
|
+
d : int
|
|
632
|
+
Number of different gates in the gate set
|
|
633
|
+
r : int
|
|
634
|
+
Superoperator dimension of the gates given by the square of the physical dimension
|
|
635
|
+
rK : int
|
|
636
|
+
Target Kraus rank
|
|
637
|
+
n_povm : int
|
|
638
|
+
Number of POVM-Elements
|
|
639
|
+
bsize : int
|
|
640
|
+
Size of the batch (number of sequences)
|
|
641
|
+
meas_samples : int
|
|
642
|
+
Number of samples taken per gate sequence to obtain measurement array y
|
|
643
|
+
method : {"SFN", "GD"}
|
|
644
|
+
Optimization method, Default: "SFN"
|
|
645
|
+
max_iter : int
|
|
646
|
+
Maximum number of iterations on batches; Default: 200
|
|
647
|
+
final_iter : int
|
|
648
|
+
Maximum number of iterations on full data set; Default: 70
|
|
649
|
+
target_rel_prec : float
|
|
650
|
+
Target precision relative to stopping value at which the final iteration loop breaks
|
|
651
|
+
init : [ , , ]
|
|
652
|
+
List of 3 numpy arrays in the format [X,E,rho], that can be used as an initialization;
|
|
653
|
+
If no initialization is given a random initialization is used
|
|
654
|
+
|
|
655
|
+
Returns
|
|
656
|
+
-------
|
|
657
|
+
K : numpy array
|
|
658
|
+
Updated estimates of Kraus operators
|
|
659
|
+
X : numpy array
|
|
660
|
+
Updated estimates of superoperatos corresponding to K_new
|
|
661
|
+
E : numpy array
|
|
662
|
+
Updated POVM estimate
|
|
663
|
+
rho : numpy array
|
|
664
|
+
Updated initial state estimate
|
|
665
|
+
res_list : list
|
|
666
|
+
Collected objective function values after each iteration
|
|
667
|
+
"""
|
|
668
|
+
y, J, _, d, r, rK, n_povm, bsize, meas_samples = args
|
|
669
|
+
t0 = time.time()
|
|
670
|
+
pdim = int(np.sqrt(r))
|
|
671
|
+
# stopping criterion (Factor 3 can be increased if model mismatch is high)
|
|
672
|
+
delta = threshold_multiplier * (1 - y.reshape(-1)) @ y.reshape(-1) / len(J) / n_povm / meas_samples
|
|
673
|
+
|
|
674
|
+
if not fixed_elements:
|
|
675
|
+
fixed_elements = []
|
|
676
|
+
|
|
677
|
+
if any(((f"G%i" % i in fixed_elements) for i in range(d))) and method == "SFN":
|
|
678
|
+
warn(
|
|
679
|
+
f"The SFN method with fixed gates is currently only implemented via \n"
|
|
680
|
+
f"iterative updates over individual gates and might lead to a slower converges \n"
|
|
681
|
+
f"compared to the default SFN method.",
|
|
682
|
+
stacklevel=2,
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
success = False
|
|
686
|
+
print(f"Starting mGST optimization...")
|
|
687
|
+
|
|
688
|
+
if init:
|
|
689
|
+
K, E = (init[0], init[1])
|
|
690
|
+
# offset small negative eigenvalues for stability
|
|
691
|
+
rho = init[2] + 1e-14 * np.eye(pdim).reshape(-1)
|
|
692
|
+
max_inits = 0
|
|
693
|
+
else:
|
|
694
|
+
K, _, E, rho = random_gs(d, r, rK, n_povm)
|
|
695
|
+
|
|
696
|
+
A = np.array([la.cholesky(E[k].reshape(pdim, pdim) + 1e-14 * np.eye(pdim)).T.conj() for k in range(n_povm)])
|
|
697
|
+
B = la.cholesky(rho.reshape(pdim, pdim))
|
|
698
|
+
X = np.einsum("ijkl,ijnm -> iknlm", K, K.conj()).reshape((d, r, r))
|
|
699
|
+
res_list = [objf(X, E, rho, J, y)]
|
|
700
|
+
|
|
701
|
+
for i in range(max_inits):
|
|
702
|
+
for _ in tqdm(range(max_iter), file=sys.stdout):
|
|
703
|
+
yb, Jb = batch(y, J, bsize)
|
|
704
|
+
K, X, E, rho, A, B = optimize(yb, Jb, d, r, rK, n_povm, method, K, rho, A, B, fixed_elements)
|
|
705
|
+
res_list.append(objf(X, E, rho, J, y))
|
|
706
|
+
if res_list[-1] < delta:
|
|
707
|
+
print(f"Batch optimization successful, improving estimate over full data....")
|
|
708
|
+
success = True
|
|
709
|
+
break
|
|
710
|
+
if testing:
|
|
711
|
+
plot_objf(res_list, delta, f"Objective function for batch optimization")
|
|
712
|
+
if success:
|
|
713
|
+
break
|
|
714
|
+
print(f"Run ", i, f"failed, trying new initialization...")
|
|
715
|
+
|
|
716
|
+
if not success and max_inits > 0:
|
|
717
|
+
print(f"Success threshold not reached, attempting optimization over full data set...")
|
|
718
|
+
for _ in tqdm(range(final_iter), file=sys.stdout):
|
|
719
|
+
K, X, E, rho, A, B = optimize(y, J, d, r, rK, n_povm, method, K, rho, A, B, fixed_elements)
|
|
720
|
+
res_list.append(objf(X, E, rho, J, y))
|
|
721
|
+
if np.abs(res_list[-2] - res_list[-1]) < delta * target_rel_prec:
|
|
722
|
+
break
|
|
723
|
+
if testing:
|
|
724
|
+
plot_objf(res_list, delta, f"Objective function over batches and full data")
|
|
725
|
+
if success or (res_list[-1] < delta):
|
|
726
|
+
print(f"\t Convergence criterion satisfied")
|
|
727
|
+
else:
|
|
728
|
+
print(f"\t Convergence criterion not satisfied,", f"try increasing max_iter or using new initializations.")
|
|
729
|
+
print(
|
|
730
|
+
f"\t Final objective {Decimal(res_list[-1]):.2e}",
|
|
731
|
+
f"in time {(time.time() - t0):.2f}s",
|
|
732
|
+
)
|
|
733
|
+
return K, X, E, rho, res_list
|