iqm-benchmarks 2.44__py3-none-any.whl → 2.46__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.
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
- objf_ += abs(E[o].conj() @ state - y[o, i]) ** 2
143
- return objf_ / m / n_povm
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
- D_ind = L @ X[k] @ R - y[o, n]
300
- dK_[k] += D_ind * K[k].conj() @ np.kron(L.reshape(pdim, pdim).T, R.reshape(pdim, pdim).T)
301
- return dK_.reshape(d, rK, pdim, pdim) * 2 / m / n_povm
302
-
303
-
304
- @njit(cache=True, parallel=False)
305
- def dK_dMdM(X, K, E, rho, J, y, d, r, rK):
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
- for i, _ in enumerate(j):
350
- k = j[i]
351
- C = contract(X, j[:i])
352
- R = contract(X, j[i + 1 :]) @ rho
353
- for o in range(n_povm):
354
- L = E[o].conj() @ C
355
- D_ind = L @ X[k] @ R - y[o, n]
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
- dM[o, k, :, :] += dM_loc
358
- dK_[k] += D_ind * dM_loc
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
- dM11 += np.kron(dM[o].conj().reshape(-1), dM[o].reshape(-1))
361
- dM10 += np.kron(dM[o].reshape(-1), dM[o].reshape(-1))
362
- return (dK_.reshape((d, rK, pdim, pdim)) * 2 / m / n_povm, 2 * dM10 / m / n_povm, 2 * dM11 / m / n_povm)
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
- D_ind = L @ X[k1] @ R - y[o, n]
467
+ p_ind = L @ X[k1] @ R
425
468
  elif i1 < i2:
426
- D_ind = L @ X[k1] @ C.reshape(r, r) @ X[k2] @ R - y[o, n]
469
+ p_ind = L @ X[k1] @ C.reshape(r, r) @ X[k2] @ R
427
470
  else:
428
- D_ind = L @ X[k2] @ C.reshape(r, r) @ X[k1] @ R - y[o, n]
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
- ddK[k1 * d + k2] += D_ind * ddK_loc
484
- dconjdK[k1 * d + k2] += D_ind * dconjdK_loc
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) * 2 / m / n_povm,
487
- dconjdK.reshape(d, d, rK, rK, pdim, pdim, pdim, pdim) * 2 / m / n_povm,
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(parallel=True, cache=True)
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
- for n in prange(m): # pylint: disable=not-an-iterable
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(parallel=True, cache=True)
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(parallel=True, cache=True)
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
- dMdM_step[o] += np.outer(dM, dM)
635
- dMconjdM_step[o] += np.outer(dM.conj(), dM)
636
- dA_step[o] += D_ind * dM
637
- dconjdA_step[o] += D_ind * np.kron(np.eye(pdim).astype(np.complex128), R.reshape(pdim, pdim).T)
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_ * 2 / m / n_povm, dMdM * 2 / m / n_povm, dMconjdM * 2 / m / n_povm, dconjdA * 2 / m / n_povm
703
+ return dA_, dMdM, dMconjdM, dconjdA
643
704
 
644
705
 
645
- @njit(parallel=True, cache=True)
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
- dMdM += np.outer(dM, dM)
697
- dMconjdM += np.outer(dM.conj(), dM)
698
-
699
- dB_ += D_ind * dM
700
- dconjdB += D_ind * np.kron(L.reshape(pdim, pdim), np.eye(pdim).astype(np.complex128))
701
- return dB_ * 2 / m / n_povm, dMdM * 2 / m / n_povm, dMconjdM * 2 / m / n_povm, dconjdB.T * 2 / m / n_povm
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 additional_fns, algorithm, low_level_jit
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