iqm-benchmarks 2.44__py3-none-any.whl → 2.45__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.
- iqm/benchmarks/benchmark_definition.py +1 -0
- iqm/benchmarks/compressive_gst/compressive_gst.py +100 -46
- iqm/benchmarks/compressive_gst/gst_analysis.py +480 -381
- iqm/benchmarks/entanglement/ghz.py +2 -2
- iqm/benchmarks/optimization/qscore.py +2 -2
- {iqm_benchmarks-2.44.dist-info → iqm_benchmarks-2.45.dist-info}/METADATA +2 -2
- {iqm_benchmarks-2.44.dist-info → iqm_benchmarks-2.45.dist-info}/RECORD +17 -17
- mGST/additional_fns.py +35 -20
- mGST/algorithm.py +73 -57
- mGST/low_level_jit.py +122 -56
- mGST/optimization.py +12 -6
- mGST/qiskit_interface.py +64 -87
- mGST/reporting/figure_gen.py +390 -57
- mGST/reporting/reporting.py +209 -11
- {iqm_benchmarks-2.44.dist-info → iqm_benchmarks-2.45.dist-info}/WHEEL +0 -0
- {iqm_benchmarks-2.44.dist-info → iqm_benchmarks-2.45.dist-info}/licenses/LICENSE +0 -0
- {iqm_benchmarks-2.44.dist-info → iqm_benchmarks-2.45.dist-info}/top_level.txt +0 -0
mGST/low_level_jit.py
CHANGED
|
@@ -104,8 +104,8 @@ def contract(X, j_vec):
|
|
|
104
104
|
return res
|
|
105
105
|
|
|
106
106
|
|
|
107
|
-
@njit(cache=True, fastmath=True, parallel=True)
|
|
108
|
-
def objf(X, E, rho, J, y):
|
|
107
|
+
@njit(cache=True, fastmath=True) # , parallel=True)
|
|
108
|
+
def objf(X, E, rho, J, y, mle=False):
|
|
109
109
|
"""Calculate the objective function value for matrices, POVM elements, and target values.
|
|
110
110
|
|
|
111
111
|
This function computes the objective function value based on input matrices X, POVM elements E,
|
|
@@ -123,6 +123,8 @@ def objf(X, E, rho, J, y):
|
|
|
123
123
|
A 2D array representing the indices for which the objective function will be evaluated.
|
|
124
124
|
y : numpy.ndarray
|
|
125
125
|
A 2D array of shape (n_povm, len(J)) containing the target values.
|
|
126
|
+
mle : bool
|
|
127
|
+
If True, the log-likelihood objective function is used, otherwise the least squares objective function is used
|
|
126
128
|
|
|
127
129
|
Returns
|
|
128
130
|
-------
|
|
@@ -139,8 +141,11 @@ def objf(X, E, rho, J, y):
|
|
|
139
141
|
for ind in j[::-1]:
|
|
140
142
|
state = X[ind] @ state
|
|
141
143
|
for o in range(n_povm):
|
|
142
|
-
|
|
143
|
-
|
|
144
|
+
if mle:
|
|
145
|
+
objf_ -= np.log(abs(E[o].conj() @ state)) * y[o, i]
|
|
146
|
+
else:
|
|
147
|
+
objf_ += abs(E[o].conj() @ state - y[o, i]) ** 2 / m / n_povm
|
|
148
|
+
return objf_
|
|
144
149
|
|
|
145
150
|
|
|
146
151
|
@njit(cache=True)
|
|
@@ -242,8 +247,8 @@ def Mp_norm_lower(X_true, E_true, rho_true, X, E, rho, J, n_povm, p):
|
|
|
242
247
|
return dist ** (1 / p) / m / n_povm, max_dist ** (1 / p)
|
|
243
248
|
|
|
244
249
|
|
|
245
|
-
@njit(cache=True, parallel=True)
|
|
246
|
-
def dK(X, K, E, rho, J, y, d, r, rK):
|
|
250
|
+
@njit(cache=True) # , parallel=True)
|
|
251
|
+
def dK(X, K, E, rho, J, y, d, r, rK, mle=False):
|
|
247
252
|
"""Compute the derivative of the objective function with respect to the Kraus tensor K.
|
|
248
253
|
|
|
249
254
|
This function calculates the derivative of the Kraus operator K, based on the
|
|
@@ -269,6 +274,8 @@ def dK(X, K, E, rho, J, y, d, r, rK):
|
|
|
269
274
|
The rank of the problem.
|
|
270
275
|
rK : int
|
|
271
276
|
The number of rows in the reshaped Kraus operator K.
|
|
277
|
+
mle : bool
|
|
278
|
+
If True, the log-likelihood objective function is used, otherwise the least squares objective function is used
|
|
272
279
|
|
|
273
280
|
Returns
|
|
274
281
|
-------
|
|
@@ -296,13 +303,29 @@ def dK(X, K, E, rho, J, y, d, r, rK):
|
|
|
296
303
|
L = E[o].conj()
|
|
297
304
|
for ind in j[:i]:
|
|
298
305
|
L = L @ X[ind]
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
+
if mle:
|
|
307
|
+
p_ind = L @ X[k] @ R
|
|
308
|
+
dK_[k] -= (
|
|
309
|
+
K[k].conj()
|
|
310
|
+
@ np.kron(L.reshape(pdim, pdim).T, R.reshape(pdim, pdim).T)
|
|
311
|
+
* y[o, n]
|
|
312
|
+
/ p_ind
|
|
313
|
+
)
|
|
314
|
+
else:
|
|
315
|
+
D_ind = L @ X[k] @ R - y[o, n]
|
|
316
|
+
dK_[k] += (
|
|
317
|
+
D_ind
|
|
318
|
+
* K[k].conj()
|
|
319
|
+
@ np.kron(L.reshape(pdim, pdim).T, R.reshape(pdim, pdim).T)
|
|
320
|
+
* 2
|
|
321
|
+
/ m
|
|
322
|
+
/ n_povm
|
|
323
|
+
)
|
|
324
|
+
return dK_.reshape(d, rK, pdim, pdim)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@njit(cache=True) # , parallel=False)
|
|
328
|
+
def dK_dMdM(X, K, E, rho, J, y, d, r, rK, mle=False):
|
|
306
329
|
"""Compute the derivatives of the objective function with respect to K and the
|
|
307
330
|
product of derivatives of the measurement map with respect to K.
|
|
308
331
|
|
|
@@ -329,6 +352,8 @@ def dK_dMdM(X, K, E, rho, J, y, d, r, rK):
|
|
|
329
352
|
The number of rows for the matrix K.
|
|
330
353
|
rK : int
|
|
331
354
|
The number of columns for the matrix K.
|
|
355
|
+
mle : bool
|
|
356
|
+
If True, the log-likelihood objective function is used, otherwise the least squares objective function is used
|
|
332
357
|
|
|
333
358
|
Returns
|
|
334
359
|
-------
|
|
@@ -346,24 +371,40 @@ def dK_dMdM(X, K, E, rho, J, y, d, r, rK):
|
|
|
346
371
|
for n in range(m):
|
|
347
372
|
j = J[n][J[n] >= 0]
|
|
348
373
|
dM = np.ascontiguousarray(np.zeros((n_povm, d, rK, r)).astype(np.complex128))
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
374
|
+
p_ind_array = np.zeros(n_povm).astype(np.complex128)
|
|
375
|
+
for o in range(n_povm):
|
|
376
|
+
for i, k in enumerate(j):
|
|
377
|
+
R = rho.copy()
|
|
378
|
+
for ind in j[i + 1 :][::-1]:
|
|
379
|
+
R = X[ind] @ R
|
|
380
|
+
L = E[o].conj().copy()
|
|
381
|
+
for ind in j[:i]:
|
|
382
|
+
L = L @ X[ind]
|
|
356
383
|
dM_loc = K[k].conj() @ np.kron(L.reshape((pdim, pdim)).T, R.reshape((pdim, pdim)).T)
|
|
357
|
-
|
|
358
|
-
|
|
384
|
+
p_ind = L @ X[k] @ R
|
|
385
|
+
if mle:
|
|
386
|
+
dM[o, k] += dM_loc
|
|
387
|
+
dK_[k] -= dM_loc * y[o, n] / p_ind
|
|
388
|
+
else:
|
|
389
|
+
dM[o, k] += dM_loc
|
|
390
|
+
D_ind = p_ind - y[o, n]
|
|
391
|
+
dK_[k] += D_ind * dM_loc * 2 / m / n_povm
|
|
392
|
+
if len(j) == 0:
|
|
393
|
+
p_ind_array[o] = E[o].conj() @ rho
|
|
394
|
+
else:
|
|
395
|
+
p_ind_array[o] = p_ind
|
|
359
396
|
for o in range(n_povm):
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
397
|
+
if mle:
|
|
398
|
+
dM11 += np.kron(dM[o].conj().reshape(-1), dM[o].reshape(-1)) * y[o, n] / p_ind_array[o] ** 2
|
|
399
|
+
dM10 += np.kron(dM[o].reshape(-1), dM[o].reshape(-1)) * y[o, n] / p_ind_array[o] ** 2
|
|
400
|
+
else:
|
|
401
|
+
dM11 += np.kron(dM[o].conj().reshape(-1), dM[o].reshape(-1)) * 2 / m / n_povm
|
|
402
|
+
dM10 += np.kron(dM[o].reshape(-1), dM[o].reshape(-1)) * 2 / m / n_povm
|
|
403
|
+
return (dK_.reshape((d, rK, pdim, pdim)), dM10, dM11)
|
|
363
404
|
|
|
364
405
|
|
|
365
|
-
@njit(cache=True, parallel=False)
|
|
366
|
-
def ddM(X, K, E, rho, J, y, d, r, rK):
|
|
406
|
+
@njit(cache=True) # , parallel=False)
|
|
407
|
+
def ddM(X, K, E, rho, J, y, d, r, rK, mle=False):
|
|
367
408
|
"""Compute the second derivative of the objective function with respect to the Kraus tensor K.
|
|
368
409
|
|
|
369
410
|
This function calculates the second derivative of the objective function for a given
|
|
@@ -389,6 +430,8 @@ def ddM(X, K, E, rho, J, y, d, r, rK):
|
|
|
389
430
|
Dimension of the local basis.
|
|
390
431
|
rK : int
|
|
391
432
|
Number of rows in the Kraus operator matrix.
|
|
433
|
+
mle : bool
|
|
434
|
+
If True, the log-likelihood objective function is used, otherwise the least squares objective function is used
|
|
392
435
|
|
|
393
436
|
Returns
|
|
394
437
|
-------
|
|
@@ -421,11 +464,12 @@ def ddM(X, K, E, rho, J, y, d, r, rK):
|
|
|
421
464
|
for o in range(n_povm):
|
|
422
465
|
L = E[o].conj() @ L0
|
|
423
466
|
if i1 == i2:
|
|
424
|
-
|
|
467
|
+
p_ind = L @ X[k1] @ R
|
|
425
468
|
elif i1 < i2:
|
|
426
|
-
|
|
469
|
+
p_ind = L @ X[k1] @ C.reshape(r, r) @ X[k2] @ R
|
|
427
470
|
else:
|
|
428
|
-
|
|
471
|
+
p_ind = L @ X[k2] @ C.reshape(r, r) @ X[k1] @ R
|
|
472
|
+
D_ind = p_ind - y[o, n]
|
|
429
473
|
|
|
430
474
|
ddK_loc = np.zeros((rK**2, r, r)).astype(np.complex128)
|
|
431
475
|
dconjdK_loc = np.zeros((rK**2, r, r)).astype(np.complex128)
|
|
@@ -479,16 +523,19 @@ def ddM(X, K, E, rho, J, y, d, r, rK):
|
|
|
479
523
|
.reshape(pdim, pdim, pdim, pdim)
|
|
480
524
|
.transpose(2, 0, 1, 3)
|
|
481
525
|
).reshape(r, r)
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
526
|
+
if mle:
|
|
527
|
+
ddK[k1 * d + k2] -= ddK_loc * y[o, n] / p_ind
|
|
528
|
+
dconjdK[k1 * d + k2] -= dconjdK_loc * y[o, n] / p_ind
|
|
529
|
+
else:
|
|
530
|
+
ddK[k1 * d + k2] += D_ind * ddK_loc * 2 / m / n_povm
|
|
531
|
+
dconjdK[k1 * d + k2] += D_ind * dconjdK_loc * 2 / m / n_povm
|
|
485
532
|
return (
|
|
486
|
-
ddK.reshape(d, d, rK, rK, pdim, pdim, pdim, pdim)
|
|
487
|
-
dconjdK.reshape(d, d, rK, rK, pdim, pdim, pdim, pdim)
|
|
533
|
+
ddK.reshape(d, d, rK, rK, pdim, pdim, pdim, pdim),
|
|
534
|
+
dconjdK.reshape(d, d, rK, rK, pdim, pdim, pdim, pdim),
|
|
488
535
|
)
|
|
489
536
|
|
|
490
537
|
|
|
491
|
-
@njit(
|
|
538
|
+
@njit(cache=True) # , parallel=True)
|
|
492
539
|
def dA(X, A, B, J, y, r, pdim, n_povm):
|
|
493
540
|
"""Compute the derivative of to the objective function with respect to the POVM tensor A
|
|
494
541
|
|
|
@@ -526,7 +573,8 @@ def dA(X, A, B, J, y, r, pdim, n_povm):
|
|
|
526
573
|
rho = (B @ B.T.conj()).reshape(-1)
|
|
527
574
|
dA_ = np.zeros((n_povm, pdim, pdim)).astype(np.complex128)
|
|
528
575
|
m = len(J)
|
|
529
|
-
|
|
576
|
+
# pylint: disable=not-an-iterable
|
|
577
|
+
for n in prange(m):
|
|
530
578
|
j = J[n][J[n] >= 0]
|
|
531
579
|
inner_deriv = contract(X, j) @ rho
|
|
532
580
|
dA_step = np.zeros((n_povm, pdim, pdim)).astype(np.complex128)
|
|
@@ -537,7 +585,7 @@ def dA(X, A, B, J, y, r, pdim, n_povm):
|
|
|
537
585
|
return dA_ * 2 / m / n_povm
|
|
538
586
|
|
|
539
587
|
|
|
540
|
-
@njit(
|
|
588
|
+
@njit(cache=True) # , parallel=True)
|
|
541
589
|
def dB(X, A, B, J, y, pdim):
|
|
542
590
|
"""Compute the derivative of the objective function with respect to the state tensor B.
|
|
543
591
|
|
|
@@ -577,8 +625,8 @@ def dB(X, A, B, J, y, pdim):
|
|
|
577
625
|
return dB_
|
|
578
626
|
|
|
579
627
|
|
|
580
|
-
@njit(
|
|
581
|
-
def ddA_derivs(X, A, B, J, y, r, pdim, n_povm):
|
|
628
|
+
@njit(cache=True) # , parallel=True)
|
|
629
|
+
def ddA_derivs(X, A, B, J, y, r, pdim, n_povm, mle=False):
|
|
582
630
|
"""Calculate all nonzero terms of the second derivatives with respect to the POVM tensor A.
|
|
583
631
|
|
|
584
632
|
Parameters
|
|
@@ -599,6 +647,8 @@ def ddA_derivs(X, A, B, J, y, r, pdim, n_povm):
|
|
|
599
647
|
The dimension of the input matrices A and B.
|
|
600
648
|
n_povm : int
|
|
601
649
|
The number of POVM elements.
|
|
650
|
+
mle : bool
|
|
651
|
+
If True, the log-likelihood objective function is used, otherwise the least squares objective function is used
|
|
602
652
|
|
|
603
653
|
Returns
|
|
604
654
|
-------
|
|
@@ -629,21 +679,32 @@ def ddA_derivs(X, A, B, J, y, r, pdim, n_povm):
|
|
|
629
679
|
dMconjdM_step = np.zeros((n_povm, r, r)).astype(np.complex128)
|
|
630
680
|
dconjdA_step = np.zeros((n_povm, r, r)).astype(np.complex128)
|
|
631
681
|
for o in range(n_povm):
|
|
632
|
-
D_ind = E[o].conj() @ R - y[o, n]
|
|
633
682
|
dM = A[o].conj() @ R.reshape(pdim, pdim).T
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
683
|
+
if mle:
|
|
684
|
+
p_ind = E[o].conj() @ R
|
|
685
|
+
dMdM_step[o] += np.outer(dM, dM) * y[o, n] / p_ind**2
|
|
686
|
+
dMconjdM_step[o] += np.outer(dM.conj(), dM) * y[o, n] / p_ind**2
|
|
687
|
+
dA_step[o] -= dM * y[o, n] / p_ind
|
|
688
|
+
dconjdA_step[o] -= (
|
|
689
|
+
np.kron(np.eye(pdim).astype(np.complex128), R.reshape(pdim, pdim).T) * y[o, n] / p_ind
|
|
690
|
+
)
|
|
691
|
+
else:
|
|
692
|
+
D_ind = E[o].conj() @ R - y[o, n]
|
|
693
|
+
dMdM_step[o] += np.outer(dM, dM) * 2 / m / n_povm
|
|
694
|
+
dMconjdM_step[o] += np.outer(dM.conj(), dM) * 2 / m / n_povm
|
|
695
|
+
dA_step[o] += D_ind * dM * 2 / m / n_povm
|
|
696
|
+
dconjdA_step[o] += (
|
|
697
|
+
D_ind * np.kron(np.eye(pdim).astype(np.complex128), R.reshape(pdim, pdim).T) * 2 / m / n_povm
|
|
698
|
+
)
|
|
638
699
|
dA_ += dA_step
|
|
639
700
|
dMdM += dMdM_step
|
|
640
701
|
dMconjdM += dMconjdM_step
|
|
641
702
|
dconjdA += dconjdA_step
|
|
642
|
-
return dA_
|
|
703
|
+
return dA_, dMdM, dMconjdM, dconjdA
|
|
643
704
|
|
|
644
705
|
|
|
645
|
-
@njit(
|
|
646
|
-
def ddB_derivs(X, A, B, J, y, r, pdim):
|
|
706
|
+
@njit(cache=True) # , parallel=True)
|
|
707
|
+
def ddB_derivs(X, A, B, J, y, r, pdim, mle=False):
|
|
647
708
|
"""Calculate all nonzero terms of the second derivative with respect to the state tensor B.
|
|
648
709
|
|
|
649
710
|
Parameters
|
|
@@ -690,12 +751,17 @@ def ddB_derivs(X, A, B, J, y, r, pdim):
|
|
|
690
751
|
C = contract(X, j)
|
|
691
752
|
for o in range(n_povm):
|
|
692
753
|
L = E[o].conj() @ C
|
|
693
|
-
D_ind = L @ rho - y[o, n]
|
|
694
|
-
|
|
695
754
|
dM = L.reshape(pdim, pdim) @ B.conj()
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
755
|
+
if mle:
|
|
756
|
+
p_ind = L @ rho
|
|
757
|
+
dMdM += np.outer(dM, dM) * y[o, n] / p_ind**2
|
|
758
|
+
dMconjdM += np.outer(dM.conj(), dM) * y[o, n] / p_ind**2
|
|
759
|
+
dB_ -= dM * y[o, n] / p_ind
|
|
760
|
+
dconjdB -= np.kron(L.reshape(pdim, pdim), np.eye(pdim).astype(np.complex128)) * y[o, n] / p_ind
|
|
761
|
+
else:
|
|
762
|
+
D_ind = L @ rho - y[o, n]
|
|
763
|
+
dMdM += np.outer(dM, dM) * 2 / m / n_povm
|
|
764
|
+
dMconjdM += np.outer(dM.conj(), dM) * 2 / m / n_povm
|
|
765
|
+
dB_ += D_ind * dM * 2 / m / n_povm
|
|
766
|
+
dconjdB += D_ind * np.kron(L.reshape(pdim, pdim), np.eye(pdim).astype(np.complex128)) * 2 / m / n_povm
|
|
767
|
+
return dB_, dMdM, dMconjdM, dconjdB.T
|
mGST/optimization.py
CHANGED
|
@@ -95,7 +95,7 @@ def update_K_geodesic(K, H, a):
|
|
|
95
95
|
return K_new.reshape(d, rK, pdim, pdim)
|
|
96
96
|
|
|
97
97
|
|
|
98
|
-
def lineobjf_isom_geodesic(a, H, K, E, rho, J, y):
|
|
98
|
+
def lineobjf_isom_geodesic(a, H, K, E, rho, J, y, mle):
|
|
99
99
|
"""Compute objective function at position on geodesic
|
|
100
100
|
|
|
101
101
|
Parameters
|
|
@@ -115,6 +115,8 @@ def lineobjf_isom_geodesic(a, H, K, E, rho, J, y):
|
|
|
115
115
|
y : numpy array
|
|
116
116
|
2D array of measurement outcomes for sequences in J;
|
|
117
117
|
The columns contain the outcome probabilities for different povm elements
|
|
118
|
+
mle : bool
|
|
119
|
+
If True, the log-likelihood objective function is used, otherwise the least squares objective function is used
|
|
118
120
|
|
|
119
121
|
Returns
|
|
120
122
|
-------
|
|
@@ -126,7 +128,7 @@ def lineobjf_isom_geodesic(a, H, K, E, rho, J, y):
|
|
|
126
128
|
r = pdim**2
|
|
127
129
|
K_test = update_K_geodesic(K, H, a)
|
|
128
130
|
X_test = np.einsum("ijkl,ijnm -> iknlm", K_test, K_test.conj()).reshape((d, r, r))
|
|
129
|
-
return objf(X_test, E, rho, J, y)
|
|
131
|
+
return objf(X_test, E, rho, J, y, mle=mle)
|
|
130
132
|
|
|
131
133
|
|
|
132
134
|
def update_A_geodesic(A, H, a):
|
|
@@ -196,7 +198,7 @@ def update_B_geodesic(B, H, a):
|
|
|
196
198
|
return B_temp.reshape(pdim, pdim)
|
|
197
199
|
|
|
198
200
|
|
|
199
|
-
def lineobjf_A_geodesic(a, H, X, A, rho, J, y):
|
|
201
|
+
def lineobjf_A_geodesic(a, H, X, A, rho, J, y, mle=False):
|
|
200
202
|
"""Compute objective function at position on geodesic for POVM parametrization
|
|
201
203
|
|
|
202
204
|
Parameters
|
|
@@ -216,6 +218,8 @@ def lineobjf_A_geodesic(a, H, X, A, rho, J, y):
|
|
|
216
218
|
y : numpy array
|
|
217
219
|
2D array of measurement outcomes for sequences in J;
|
|
218
220
|
The columns contain the outcome probabilities for different povm elements
|
|
221
|
+
mle : bool
|
|
222
|
+
If True, the log-likelihood objective function is used, otherwise the least squares objective function is used
|
|
219
223
|
|
|
220
224
|
Returns
|
|
221
225
|
-------
|
|
@@ -225,10 +229,10 @@ def lineobjf_A_geodesic(a, H, X, A, rho, J, y):
|
|
|
225
229
|
n_povm = A.shape[0]
|
|
226
230
|
A_test = update_A_geodesic(A, H, a)
|
|
227
231
|
E_test = np.array([(A_test[i].T.conj() @ A_test[i]).reshape(-1) for i in range(n_povm)])
|
|
228
|
-
return objf(X, E_test, rho, J, y)
|
|
232
|
+
return objf(X, E_test, rho, J, y, mle=mle)
|
|
229
233
|
|
|
230
234
|
|
|
231
|
-
def lineobjf_B_geodesic(a, H, X, E, B, J, y):
|
|
235
|
+
def lineobjf_B_geodesic(a, H, X, E, B, J, y, mle=False):
|
|
232
236
|
"""Compute objective function at position on geodesic for the initial state parametrization
|
|
233
237
|
|
|
234
238
|
Parameters
|
|
@@ -248,6 +252,8 @@ def lineobjf_B_geodesic(a, H, X, E, B, J, y):
|
|
|
248
252
|
y : numpy array
|
|
249
253
|
2D array of measurement outcomes for sequences in J;
|
|
250
254
|
The columns contain the outcome probabilities for different povm elements
|
|
255
|
+
mle : bool
|
|
256
|
+
If True, the log-likelihood objective function is used, otherwise the least squares objective function is used
|
|
251
257
|
|
|
252
258
|
Returns
|
|
253
259
|
-------
|
|
@@ -256,7 +262,7 @@ def lineobjf_B_geodesic(a, H, X, E, B, J, y):
|
|
|
256
262
|
"""
|
|
257
263
|
B_test = update_B_geodesic(B, H, a)
|
|
258
264
|
rho_test = (B_test @ B_test.T.conj()).reshape(-1)
|
|
259
|
-
return objf(X, E, rho_test, J, y)
|
|
265
|
+
return objf(X, E, rho_test, J, y, mle=mle)
|
|
260
266
|
|
|
261
267
|
|
|
262
268
|
def lineobjf_A_B(a, v, delta_v, X, C, y, J, argument):
|
mGST/qiskit_interface.py
CHANGED
|
@@ -9,9 +9,7 @@ from qiskit.circuit.library import IGate
|
|
|
9
9
|
from qiskit.quantum_info import Operator
|
|
10
10
|
|
|
11
11
|
from iqm.qiskit_iqm import IQMCircuit as QuantumCircuit
|
|
12
|
-
from mGST import
|
|
13
|
-
from mGST.compatibility import arrays_to_pygsti_model
|
|
14
|
-
from mGST.reporting.reporting import gauge_opt, quick_report
|
|
12
|
+
from mGST import low_level_jit
|
|
15
13
|
|
|
16
14
|
|
|
17
15
|
def qiskit_gate_to_operator(gate_set):
|
|
@@ -131,6 +129,69 @@ def get_qiskit_circuits(gate_sequences, gate_set, n_qubits, active_qubits):
|
|
|
131
129
|
return qiskit_circuits
|
|
132
130
|
|
|
133
131
|
|
|
132
|
+
def get_composed_qiskit_circuits(gate_sequences, gate_set, n_qubits, qubit_layouts, gate_context=None, parallel=False):
|
|
133
|
+
"""Turn GST sequences into Qiskit circuits, adding context gates if provided.
|
|
134
|
+
|
|
135
|
+
For each GST sequence, either a single circuit is created for all qubit layouts if `parallel=True`, or a separate circuit if
|
|
136
|
+
`parallel=False`.
|
|
137
|
+
|
|
138
|
+
Parameters
|
|
139
|
+
----------
|
|
140
|
+
gate_sequences : list of list of int
|
|
141
|
+
Sequences of gate indices to apply. Each integer corresponds to a gate in the gate_set.
|
|
142
|
+
gate_set : list
|
|
143
|
+
The gate set defined as a list of Qiskit quantum circuits.
|
|
144
|
+
n_qubits : int
|
|
145
|
+
Total number of qubits in the system.
|
|
146
|
+
qubit_layouts : list of list of int
|
|
147
|
+
Lists of qubits on which the GST experiment is run.
|
|
148
|
+
gate_context : QuantumCircuit or list of QuantumCircuit, optional
|
|
149
|
+
Optional context circuit(s) to apply during each gate on qubits that are not measured for GST.
|
|
150
|
+
parallel : bool, optional
|
|
151
|
+
Whether GST for all qubits layouts is done in parallel on the backend.
|
|
152
|
+
If True, applies gates to all qubit layouts in a single circuit.
|
|
153
|
+
If False, creates separate circuits for each layout. Default is False.
|
|
154
|
+
|
|
155
|
+
Returns
|
|
156
|
+
-------
|
|
157
|
+
list or list of list of QuantumCircuit
|
|
158
|
+
If parallel=True: A list of QuantumCircuits, one for each gate sequence.
|
|
159
|
+
If parallel=False: A list of lists of QuantumCircuits, where the outer list corresponds
|
|
160
|
+
to qubit layouts and the inner list corresponds to gate sequences.
|
|
161
|
+
"""
|
|
162
|
+
if gate_context is not None and not isinstance(gate_context, list):
|
|
163
|
+
gate_context = [gate_context] * len(gate_set)
|
|
164
|
+
qiskit_circuits = []
|
|
165
|
+
if parallel:
|
|
166
|
+
all_qubits = [q for qubits in qubit_layouts for q in qubits]
|
|
167
|
+
all_clbits = [i for i, _ in enumerate(all_qubits)]
|
|
168
|
+
for gate_sequence in gate_sequences:
|
|
169
|
+
qc = QuantumCircuit(n_qubits, len(all_clbits))
|
|
170
|
+
for gate_num in gate_sequence:
|
|
171
|
+
if gate_context is not None:
|
|
172
|
+
qc.compose(gate_context[gate_num], inplace=True)
|
|
173
|
+
for qubits in qubit_layouts:
|
|
174
|
+
qc.compose(gate_set[gate_num], qubits, inplace=True)
|
|
175
|
+
qc.barrier()
|
|
176
|
+
qc.measure(all_qubits, all_clbits)
|
|
177
|
+
qiskit_circuits.append(qc)
|
|
178
|
+
else:
|
|
179
|
+
for qubits in qubit_layouts:
|
|
180
|
+
clbits = [i for i, _ in enumerate(qubits)]
|
|
181
|
+
layout_circuits = []
|
|
182
|
+
for gate_sequence in gate_sequences:
|
|
183
|
+
qc = QuantumCircuit(n_qubits, len(clbits))
|
|
184
|
+
for gate_num in gate_sequence:
|
|
185
|
+
if gate_context is not None:
|
|
186
|
+
qc.compose(gate_context[gate_num], inplace=True)
|
|
187
|
+
qc.compose(gate_set[gate_num], qubits, inplace=True)
|
|
188
|
+
qc.barrier()
|
|
189
|
+
qc.measure(qubits, clbits)
|
|
190
|
+
layout_circuits.append(qc)
|
|
191
|
+
qiskit_circuits.append(layout_circuits)
|
|
192
|
+
return qiskit_circuits
|
|
193
|
+
|
|
194
|
+
|
|
134
195
|
def get_gate_sequence(sequence_number, sequence_length, gate_set_length):
|
|
135
196
|
"""Generate a set of random gate sequences.
|
|
136
197
|
|
|
@@ -196,87 +257,3 @@ def job_counts_to_mgst_format(active_qubits, n_povm, result_dict):
|
|
|
196
257
|
y.append(row / np.sum(row))
|
|
197
258
|
y = np.array(y).T
|
|
198
259
|
return y
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
def get_gate_estimation(gate_set, gate_sequences, sequence_results, shots, rK=4):
|
|
202
|
-
"""Estimate quantum gates using a modified Gate Set Tomography (mGST) algorithm.
|
|
203
|
-
|
|
204
|
-
This function simulates quantum gates, applies noise, and then uses the mGST algorithm
|
|
205
|
-
to estimate the gates. It calculates and prints the Mean Variation Error (MVE) of the
|
|
206
|
-
estimation.
|
|
207
|
-
|
|
208
|
-
Parameters
|
|
209
|
-
----------
|
|
210
|
-
gate_set : array_like
|
|
211
|
-
The set of quantum gates to be estimated.
|
|
212
|
-
gate_sequences : array_like
|
|
213
|
-
The sequences of gates applied in the quantum circuit.
|
|
214
|
-
sequence_results : array_like
|
|
215
|
-
The results of executing the gate sequences.
|
|
216
|
-
shots : int
|
|
217
|
-
The number of shots (repetitions) for each measurement.
|
|
218
|
-
"""
|
|
219
|
-
|
|
220
|
-
K_target = qiskit_gate_to_operator(gate_set)
|
|
221
|
-
gate_set_length = len(gate_set)
|
|
222
|
-
pdim = K_target.shape[-1] # Physical dimension
|
|
223
|
-
r = pdim**2 # Matrix dimension of gate superoperators
|
|
224
|
-
n_povm = pdim # Number of POVM elements
|
|
225
|
-
sequence_length = gate_sequences.shape[0]
|
|
226
|
-
|
|
227
|
-
X_target = np.einsum("ijkl,ijnm -> iknlm", K_target, K_target.conj()).reshape(
|
|
228
|
-
(gate_set_length, pdim**2, pdim**2)
|
|
229
|
-
) # tensor of superoperators
|
|
230
|
-
|
|
231
|
-
# Initial state |0>
|
|
232
|
-
rho_target = (
|
|
233
|
-
np.kron(additional_fns.basis(pdim, 0).T.conj(), additional_fns.basis(pdim, 0)).reshape(-1).astype(np.complex128)
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
# Computational basis measurement:
|
|
237
|
-
E_target = np.array(
|
|
238
|
-
[
|
|
239
|
-
np.kron(additional_fns.basis(pdim, i).T.conj(), additional_fns.basis(pdim, i)).reshape(-1)
|
|
240
|
-
for i in range(pdim)
|
|
241
|
-
]
|
|
242
|
-
).astype(np.complex128)
|
|
243
|
-
target_mdl = arrays_to_pygsti_model(X_target, E_target, rho_target, basis="std")
|
|
244
|
-
|
|
245
|
-
K_init = additional_fns.perturbed_target_init(X_target, rK)
|
|
246
|
-
|
|
247
|
-
bsize = 30 * pdim # Batch size for optimization
|
|
248
|
-
_, X, E, rho, _ = algorithm.run_mGST(
|
|
249
|
-
sequence_results,
|
|
250
|
-
gate_sequences,
|
|
251
|
-
sequence_length,
|
|
252
|
-
gate_set_length,
|
|
253
|
-
r,
|
|
254
|
-
rK,
|
|
255
|
-
n_povm,
|
|
256
|
-
bsize,
|
|
257
|
-
shots,
|
|
258
|
-
method="SFN",
|
|
259
|
-
max_inits=10,
|
|
260
|
-
max_iter=100,
|
|
261
|
-
final_iter=50,
|
|
262
|
-
target_rel_prec=1e-4,
|
|
263
|
-
init=[K_init, E_target, rho_target],
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
# Output the final mean variation error
|
|
267
|
-
mean_var_error = additional_fns.MVE(
|
|
268
|
-
X_target, E_target, rho_target, X, E, rho, gate_set_length, sequence_length, n_povm
|
|
269
|
-
)[0]
|
|
270
|
-
print(f"Mean variation error:", mean_var_error)
|
|
271
|
-
print(f"Optimizing gauge...")
|
|
272
|
-
weights = dict({f"G%i" % i: 1 for i in range(gate_set_length)}, **{"spam": 1})
|
|
273
|
-
X_opt, E_opt, rho_opt = gauge_opt(X, E, rho, target_mdl, weights)
|
|
274
|
-
print("Compressive GST routine complete")
|
|
275
|
-
|
|
276
|
-
# Making sense of the outcomes
|
|
277
|
-
df_g, df_o = quick_report(X_opt, E_opt, rho_opt, gate_sequences, sequence_results, target_mdl)
|
|
278
|
-
print("First results:")
|
|
279
|
-
print(df_g.to_string())
|
|
280
|
-
print(df_o.T.to_string())
|
|
281
|
-
|
|
282
|
-
return X_opt, E_opt, rho_opt
|