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.

Files changed (42) hide show
  1. iqm/benchmarks/__init__.py +31 -0
  2. iqm/benchmarks/benchmark.py +109 -0
  3. iqm/benchmarks/benchmark_definition.py +264 -0
  4. iqm/benchmarks/benchmark_experiment.py +163 -0
  5. iqm/benchmarks/compressive_gst/__init__.py +20 -0
  6. iqm/benchmarks/compressive_gst/compressive_gst.py +1029 -0
  7. iqm/benchmarks/entanglement/__init__.py +18 -0
  8. iqm/benchmarks/entanglement/ghz.py +802 -0
  9. iqm/benchmarks/logging_config.py +29 -0
  10. iqm/benchmarks/optimization/__init__.py +18 -0
  11. iqm/benchmarks/optimization/qscore.py +719 -0
  12. iqm/benchmarks/quantum_volume/__init__.py +21 -0
  13. iqm/benchmarks/quantum_volume/clops.py +726 -0
  14. iqm/benchmarks/quantum_volume/quantum_volume.py +854 -0
  15. iqm/benchmarks/randomized_benchmarking/__init__.py +18 -0
  16. iqm/benchmarks/randomized_benchmarking/clifford_1q.pkl +0 -0
  17. iqm/benchmarks/randomized_benchmarking/clifford_2q.pkl +0 -0
  18. iqm/benchmarks/randomized_benchmarking/clifford_rb/__init__.py +19 -0
  19. iqm/benchmarks/randomized_benchmarking/clifford_rb/clifford_rb.py +386 -0
  20. iqm/benchmarks/randomized_benchmarking/interleaved_rb/__init__.py +19 -0
  21. iqm/benchmarks/randomized_benchmarking/interleaved_rb/interleaved_rb.py +555 -0
  22. iqm/benchmarks/randomized_benchmarking/mirror_rb/__init__.py +19 -0
  23. iqm/benchmarks/randomized_benchmarking/mirror_rb/mirror_rb.py +810 -0
  24. iqm/benchmarks/randomized_benchmarking/multi_lmfit.py +86 -0
  25. iqm/benchmarks/randomized_benchmarking/randomized_benchmarking_common.py +892 -0
  26. iqm/benchmarks/readout_mitigation.py +290 -0
  27. iqm/benchmarks/utils.py +521 -0
  28. iqm_benchmarks-1.3.dist-info/LICENSE +205 -0
  29. iqm_benchmarks-1.3.dist-info/METADATA +190 -0
  30. iqm_benchmarks-1.3.dist-info/RECORD +42 -0
  31. iqm_benchmarks-1.3.dist-info/WHEEL +5 -0
  32. iqm_benchmarks-1.3.dist-info/top_level.txt +2 -0
  33. mGST/LICENSE +21 -0
  34. mGST/README.md +54 -0
  35. mGST/additional_fns.py +962 -0
  36. mGST/algorithm.py +733 -0
  37. mGST/compatibility.py +238 -0
  38. mGST/low_level_jit.py +694 -0
  39. mGST/optimization.py +349 -0
  40. mGST/qiskit_interface.py +282 -0
  41. mGST/reporting/figure_gen.py +334 -0
  42. 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