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/optimization.py ADDED
@@ -0,0 +1,349 @@
1
+ """
2
+ Functions related to optimization on manifolds
3
+ """
4
+
5
+ import numpy as np
6
+ from scipy.linalg import eigh
7
+
8
+ from mGST.low_level_jit import ddM, dK_dMdM, objf
9
+
10
+
11
+ def eigy_expm(A):
12
+ """Custom Matrix exponential using the eigendecomposition of numpy.linalg
13
+
14
+ Parameters
15
+ ----------
16
+ A : numpy array
17
+ Matrix to be exponentiated
18
+
19
+ Returns
20
+ -------
21
+ M: numpy array
22
+ Matrix exponential of A
23
+ """
24
+ vals, vects = np.linalg.eig(A)
25
+ return np.einsum("...ik, ...k, ...kj -> ...ij", vects, np.exp(vals), np.linalg.inv(vects))
26
+
27
+
28
+ def tangent_proj(K, Z, d, rK):
29
+ """Projection onto the local tangent space
30
+
31
+ Parameters
32
+ ----------
33
+ K : numpy array
34
+ Current position
35
+ Z : numpy array
36
+ Element of the ambient space to be projected onto the tangent space at K
37
+ d : int
38
+ Number of matrices which are projected (i.e. number of gates in the gate set)
39
+ r : int
40
+ Superoperator dimension of the gates given by the square of the physical dimension
41
+
42
+ Returns
43
+ -------
44
+ G: numpy array
45
+ Projection of Z onto the tangent space of the Stiefel manifold at position K
46
+
47
+ Notes:
48
+ The projection is done with respect to the canonical metrik.
49
+ """
50
+ pdim = K.shape[2]
51
+ n = pdim * rK
52
+ K = K.reshape(d, n, pdim)
53
+ Z = Z.reshape(d, n, pdim)
54
+ G = np.ascontiguousarray(np.zeros((d, n, pdim)).astype(np.complex128))
55
+ for i in range(d):
56
+ G[i] += Z[i] - (K[i] @ K[i].T.conj() @ Z[i] + K[i] @ Z[i].T.conj() @ K[i]) / 2
57
+ return G
58
+
59
+
60
+ def update_K_geodesic(K, H, a):
61
+ """Compute a new point on the geodesic
62
+
63
+ G = np.ascontiguousarray(np.zeros((d,n,pdim)).astype(np.complex128))
64
+
65
+ Compute a new point on the geodesic
66
+
67
+ Parameters
68
+ ----------
69
+ K : numpy array
70
+ Current position
71
+ H : numpy array
72
+ Element of the tangent space at K and local direction of the geodesic
73
+ a : float
74
+ Geodesic curve parameter
75
+
76
+ Returns
77
+ -------
78
+ K_new: numpy array
79
+ New position given by K_new = K(a) with K(a) being a geodesic with K(0) = K, [d/dt K](0) = H
80
+ """
81
+ d = K.shape[0]
82
+ rK = K.shape[1]
83
+ pdim = K.shape[2]
84
+ n = pdim * rK
85
+ K = K.reshape(d, n, pdim)
86
+ K_new = K.copy()
87
+ AR_mat = np.zeros((2 * pdim, 2 * pdim)).astype(np.complex128)
88
+ for i in range(d):
89
+ Q, R = np.linalg.qr((np.eye(n) - K[i] @ K[i].T.conj()) @ H[i])
90
+ AR_mat[:pdim, :pdim] = K[i].T.conj() @ H[i]
91
+ AR_mat[pdim:, :pdim] = R
92
+ AR_mat[:pdim, pdim:] = -R.T.conj()
93
+ MN = eigy_expm(-a * AR_mat) @ np.eye(2 * pdim, pdim)
94
+ K_new[i] = K[i] @ MN[:pdim, :] + Q @ MN[pdim:, :]
95
+ return K_new.reshape(d, rK, pdim, pdim)
96
+
97
+
98
+ def lineobjf_isom_geodesic(a, H, K, E, rho, J, y):
99
+ """Compute objective function at position on geodesic
100
+
101
+ Parameters
102
+ ----------
103
+ a : float
104
+ Geodesic curve parameter
105
+ H : numpy array
106
+ Element of the tangent space at K and local direction of the geodesic
107
+ K : numpy array
108
+ Current position
109
+ E : numpy array
110
+ Current POVM estimate
111
+ rho : numpy array
112
+ Current initial state estimate
113
+ J : numpy array
114
+ 2D array where each row contains the gate indices of a gate sequence
115
+ y : numpy array
116
+ 2D array of measurement outcomes for sequences in J;
117
+ The columns contain the outcome probabilities for different povm elements
118
+
119
+ Returns
120
+ -------
121
+ f(a): float
122
+ Objective function value at new position along the geodesic
123
+ """
124
+ d = K.shape[0]
125
+ pdim = K.shape[2]
126
+ r = pdim**2
127
+ K_test = update_K_geodesic(K, H, a)
128
+ 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)
130
+
131
+
132
+ def update_A_geodesic(A, H, a):
133
+ """Compute a new point on the geodesic for the POVM parametrization
134
+
135
+ Parameters
136
+ ----------
137
+ A : numpy array
138
+ Current position
139
+ H : numpy array
140
+ Element of the tangent space at A and local direction of the geodesic
141
+ a : float
142
+ Geodesic curve parameter
143
+
144
+ Returns
145
+ -------
146
+ A_new: numpy array
147
+ New position given by A_new = A(a) with A(a) being a geodesic with A(0) = A, [d/dt A](0) = H
148
+ """
149
+ n_povm = A.shape[0]
150
+ pdim = A.shape[1]
151
+ n = pdim * n_povm
152
+ A = A.reshape(n, pdim)
153
+ H = H.reshape(n, pdim)
154
+ A_new = A.copy()
155
+ AR_mat = np.zeros((2 * pdim, 2 * pdim)).astype(np.complex128)
156
+ Q, R = np.linalg.qr((np.eye(n) - A @ A.T.conj()) @ H)
157
+ AR_mat[:pdim, :pdim] = A.T.conj() @ H
158
+ AR_mat[pdim:, :pdim] = R
159
+ AR_mat[:pdim, pdim:] = -R.T.conj()
160
+ MN = eigy_expm(-a * AR_mat) @ np.eye(2 * pdim, pdim)
161
+ A_new = A @ MN[:pdim, :] + Q @ MN[pdim:, :]
162
+ return A_new.reshape(n_povm, pdim, pdim)
163
+
164
+
165
+ def update_B_geodesic(B, H, a):
166
+ """Compute a new point on the geodesic for the initial state parametrization
167
+
168
+ Parameters
169
+ ----------
170
+ B : numpy array
171
+ Current initial state parametrization
172
+ H : numpy array
173
+ Element of the tangent space at B and local direction of the geodesic
174
+ a : float
175
+ Geodesic curve parameter
176
+
177
+ Returns
178
+ -------
179
+ A_new: numpy array
180
+ New position given by B_new = B(a) with B(a) being a geodesic with B(0) = B,
181
+ [d/dt B](t=0) = H
182
+ """
183
+ pdim = B.shape[0]
184
+ n = pdim**2
185
+ B = B.reshape(n)
186
+ H = H.reshape(n)
187
+ B_temp = B.copy()
188
+ AR_mat = np.zeros((2, 2)).astype(np.complex128)
189
+ R = np.linalg.norm((np.eye(n) - np.outer(B, B.T.conj())) @ H)
190
+ Q = ((np.eye(n) - np.outer(B, B.T.conj())) @ H) / R
191
+ AR_mat[0, 0] = B.T.conj() @ H
192
+ AR_mat[1, 0] = R
193
+ AR_mat[0, 1] = -R.T.conj()
194
+ MN = eigy_expm(-a * AR_mat) @ np.array([1, 0])
195
+ B_temp = B * MN[0] + Q * MN[1]
196
+ return B_temp.reshape(pdim, pdim)
197
+
198
+
199
+ def lineobjf_A_geodesic(a, H, X, A, rho, J, y):
200
+ """Compute objective function at position on geodesic for POVM parametrization
201
+
202
+ Parameters
203
+ ----------
204
+ a : float
205
+ Geodesic curve parameter
206
+ H : numpy array
207
+ Element of the tangent space at A and local direction of the geodesic
208
+ X : numpy array
209
+ Current gate estimate
210
+ A : numpy array
211
+ Current position
212
+ rho : numpy array
213
+ Current initial state estimate
214
+ J : numpy array
215
+ 2D array where each row contains the gate indices of a gate sequence
216
+ y : numpy array
217
+ 2D array of measurement outcomes for sequences in J;
218
+ The columns contain the outcome probabilities for different povm elements
219
+
220
+ Returns
221
+ -------
222
+ f(a): float
223
+ Objective function value at new position along the geodesic
224
+ """
225
+ n_povm = A.shape[0]
226
+ A_test = update_A_geodesic(A, H, a)
227
+ 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)
229
+
230
+
231
+ def lineobjf_B_geodesic(a, H, X, E, B, J, y):
232
+ """Compute objective function at position on geodesic for the initial state parametrization
233
+
234
+ Parameters
235
+ ----------
236
+ a : float
237
+ Geodesic curve parameter
238
+ H : numpy array
239
+ Element of the tangent space at B and local direction of the geodesic
240
+ X : numpy array
241
+ Current gate estimate
242
+ E : numpy array
243
+ Current POVM estimate
244
+ B : numpy array
245
+ Current initial state parametrization
246
+ J : numpy array
247
+ 2D array where each row contains the gate indices of a gate sequence
248
+ y : numpy array
249
+ 2D array of measurement outcomes for sequences in J;
250
+ The columns contain the outcome probabilities for different povm elements
251
+
252
+ Returns
253
+ -------
254
+ f(a): float
255
+ Objective function value at new position along the geodesic
256
+ """
257
+ B_test = update_B_geodesic(B, H, a)
258
+ rho_test = (B_test @ B_test.T.conj()).reshape(-1)
259
+ return objf(X, E, rho_test, J, y)
260
+
261
+
262
+ def lineobjf_A_B(a, v, delta_v, X, C, y, J, argument):
263
+ """Compute objective function at translated position
264
+
265
+ Parameters
266
+ ----------
267
+ a : float
268
+ Step size
269
+ v : numpy array
270
+ Current position
271
+ delta_v : numpy array
272
+ Step direction
273
+ X : numpy array
274
+ Current gate estimate
275
+ C : numpy array
276
+ Current initial state/POVM estimate that is not updated
277
+ y : numpy array
278
+ 2D array of measurement outcomes for sequences in J;
279
+ The columns contain the outcome probabilities for different povm elements
280
+ J : numpy array
281
+ 2D array where each row contains the gate indices of a gate sequence
282
+ argument: string
283
+ Takes the following options: "E" or "rho", and indicates which gate set
284
+ component is optimized over
285
+
286
+ Returns
287
+ -------
288
+ f(a): float
289
+ Objective function value at new position v + a*delta_v
290
+
291
+ Notes
292
+ -------
293
+ This function is used for the line search with linear updates v_new = v + a*delta_v,
294
+ where v can be either the POVM estimate or the state estimate.
295
+ """
296
+ v_test = v - a * delta_v
297
+ if argument == "rho":
298
+ rho_test = (v_test @ v_test.T.conj()).reshape(-1)
299
+ return objf(X, C, rho_test, J, y)
300
+ if argument == "E":
301
+ E_test = (v_test @ v_test.T.conj()).reshape(-1)
302
+ return objf(X, E_test, C, J, y)
303
+ raise ValueError("The <argument> variable takes either E or rho")
304
+
305
+
306
+ def Hess_evals(K, E, rho, y, J):
307
+ """Compute eigenvalues of the euclidean Hessian
308
+
309
+ Parameters
310
+ ----------
311
+ K : numpy array
312
+ Current position
313
+ E : numpy array
314
+ Current POVM estimate
315
+ rho : numpy array
316
+ Current initial state estimate
317
+ y : numpy array
318
+ 2D array of measurement outcomes for sequences in J;
319
+ The columns contain the outcome probabilities for different povm elements
320
+ J : numpy array
321
+ 2D array where each row contains the gate indices of a gate sequence
322
+
323
+ Returns
324
+ -------
325
+ evals: 1D numpy array
326
+ Eigenvalues of the euclidean Hessian for the Kraus operators at position (K,E,rho)
327
+ """
328
+ d = K.shape[0]
329
+ rK = K.shape[1]
330
+ pdim = K.shape[2]
331
+ r = pdim**2
332
+ n = d * rK * r
333
+ H = np.zeros((2 * n, 2 * n)).astype(np.complex128)
334
+ X = np.einsum("ijkl,ijnm -> iknlm", K, K.conj()).reshape((d, r, r))
335
+ _, dM10, dM11 = dK_dMdM(X, K, E, rho, J, y, d, r, rK)
336
+ dd, dconjd = ddM(X, K, E, rho, J, y, d, r, rK)
337
+
338
+ A00 = dM11.reshape(n, n) + np.einsum("ijklmnop->ikmojlnp", dconjd).reshape(n, n)
339
+ A10 = dM10.reshape(n, n) + np.einsum("ijklmnop->ikmojlnp", dd).reshape(n, n)
340
+ A11 = A00.conj()
341
+ A01 = A10.conj()
342
+
343
+ H[:n, :n] = A00
344
+ H[:n, n:] = A01
345
+ H[n:, :n] = A10
346
+ H[n:, n:] = A11
347
+
348
+ evals, _ = eigh(H)
349
+ return evals
@@ -0,0 +1,282 @@
1
+ """
2
+ Bridge between mGST and Qiskit
3
+ """
4
+
5
+ import random
6
+
7
+ import numpy as np
8
+ from qiskit import QuantumCircuit
9
+ from qiskit.circuit.library import IGate
10
+ from qiskit.quantum_info import Operator
11
+
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
15
+
16
+
17
+ def qiskit_gate_to_operator(gate_set):
18
+ """Convert a set of Qiskit gates to their unitary operators.
19
+
20
+ This function takes a list of Qiskit gate objects and them into operators, returned as a NumPy
21
+ array of matrices.
22
+
23
+ Parameters
24
+ ----------
25
+ gate_set : list
26
+ A list of Qiskit gate objects. Each gate in the list is converted to its
27
+ corresponding unitary.
28
+
29
+ Returns
30
+ -------
31
+ numpy.ndarray
32
+ An array of process matrices. Each element in the array is a 2D NumPy array.
33
+ """
34
+ return np.array([[Operator(gate).to_matrix()] for gate in gate_set])
35
+
36
+
37
+ def add_idle_gates(gate_set, active_qubits, gate_qubits):
38
+ """Add additional idle gates to a gate set
39
+ Each gate in the output gate_set acts on exactly the number of qubits
40
+ on which the GST experiment ist defined. For instance if the GST
41
+ experiment is supposed to be run on qubits [0,1,2], then a X-Gate
42
+ on the 0-th qubit turns into X otimes Idle otimes Idle.
43
+ This representation is needed for the internal handling in mGST.
44
+
45
+ Parameters
46
+ ----------
47
+ gate_set : list of Qiskit Circuits
48
+ Each element is a gate to be used for GST, stored as a circuit.
49
+ active_qubits : list integers
50
+ Specifies the list of qubits on which GST-circuits are run.
51
+ gate_qubits : list of lists of integers
52
+ The i-th elements in the active_qubit list specifies on which qubits
53
+ the original i-th gate in the input gate set is supposed to act.
54
+ Returns
55
+ -------
56
+ gate_set : list of Qiskit Circuits
57
+ The output gate set with added idle gates.
58
+ """
59
+ d = len(gate_qubits)
60
+ for i in range(d):
61
+ idle_qubits = active_qubits.copy()
62
+ for j in gate_qubits[i]:
63
+ idle_qubits.remove(j)
64
+ if idle_qubits:
65
+ for l in idle_qubits:
66
+ gate_set[i].append(IGate(), [l])
67
+ return gate_set
68
+
69
+
70
+ def remove_idle_wires(qc):
71
+ """Removes all wires on which no gate acts
72
+ Credit to: https://quantumcomputing.stackexchange.com/a/37192
73
+ This shrinks the circuit for the conversion to quantum channels.
74
+
75
+ Parameters
76
+ ----------
77
+ qc Qiskit quantum circuit
78
+
79
+ Returns
80
+ -------
81
+ qc_out Qiskit quantum circuit
82
+ The circuit with idle wires removed.
83
+ """
84
+ qc_out = qc.copy()
85
+ gate_count = {qubit: 0 for qubit in qc.qubits}
86
+ for gate in qc.data:
87
+ for qubit in gate.qubits:
88
+ gate_count[qubit] += 1
89
+ for qubit, count in gate_count.items():
90
+ if count == 0:
91
+ qc_out.qubits.remove(qubit)
92
+ return qc_out
93
+
94
+
95
+ def get_qiskit_circuits(gate_sequences, gate_set, n_qubits, active_qubits):
96
+ """
97
+ Generate a set of Qiskit quantum circuits from specified gate sequences.
98
+
99
+ This function creates a list of quantum circuits based on the provided sequences
100
+ of gate indices. Each gate index corresponds to a gate in the provided `gate_set`.
101
+ The gates are appended to a quantum circuit of a specified length and then measured.
102
+
103
+ Parameters
104
+ ----------
105
+ gate_sequences : list of list of int
106
+ A list where each element is a sequence of integers representing gate indices. Each integer
107
+ corresponds to a gate in `gate_set`.
108
+ gate_set : list
109
+ A list of Qiskit gate objects. The indices in `gate_sequences` refer to gates in this list.
110
+ n_qubits : int
111
+ The total number of qubits in the system.
112
+ active_qubits : list of int
113
+ The qubits on which the circuits are run.
114
+
115
+
116
+ Returns
117
+ -------
118
+ list of QuantumCircuit
119
+ A list of Qiskit QuantumCircuit objects. Each circuit corresponds to one sequence in
120
+ `gate_sequences`, with gates applied to the first qubit.
121
+ """
122
+ qiskit_circuits = []
123
+
124
+ for gate_sequence in gate_sequences:
125
+ qc = QuantumCircuit(n_qubits, n_qubits)
126
+ for gate_num in gate_sequence:
127
+ qc.compose(gate_set[gate_num], active_qubits, inplace=True)
128
+ qc.barrier(active_qubits)
129
+ qc.measure(active_qubits, active_qubits)
130
+ qiskit_circuits.append(qc)
131
+ return qiskit_circuits
132
+
133
+
134
+ def get_gate_sequence(sequence_number, sequence_length, gate_set_length):
135
+ """Generate a set of random gate sequences.
136
+
137
+ This function creates a specified number of random gate sequences, each of a given length.
138
+ The gates are represented by numerical indices corresponding to elements in `gate_set`.
139
+ Each sequence is a random combination of these indices.
140
+
141
+ Parameters
142
+ ----------
143
+ sequence_number : int
144
+ The number of gate sequences to generate.
145
+ sequence_length : int
146
+ The length of each gate sequence.
147
+ gate_set_length : int
148
+ The length of the set of gates to be used
149
+
150
+ Returns
151
+ -------
152
+ numpy.ndarray
153
+ An array of shape (sequence_number, sequence_length), where each row represents a
154
+ randomly generated gate sequence.
155
+ """
156
+ J_rand = np.array(random.sample(range(gate_set_length**sequence_length), sequence_number))
157
+ gate_sequences = np.array([low_level_jit.local_basis(ind, gate_set_length, sequence_length) for ind in J_rand])
158
+ return gate_sequences
159
+
160
+
161
+ def job_counts_to_mgst_format(active_qubits, n_povm, result_dict):
162
+ """Turns the dictionary of outcomes obtained from qiskit backend
163
+ into the format which is used in mGST
164
+
165
+ Parameters
166
+ ----------
167
+ active_qubits : list of int
168
+ The qubits on which the circuits are run.
169
+ n_povm : int
170
+ Number of measurement outcomes, n_povm = physical dimension for basis measurements
171
+ result_dict: (dict of str: int)
172
+ Dictionary of outcomes from circuits run in a job
173
+ Returns
174
+ -------
175
+ y : numpy array
176
+ 2D array of measurement outcomes for sequences in J;
177
+ Each column contains the outcome probabilities for a fixed sequence
178
+
179
+ """
180
+ basis_dict_list = []
181
+ for result in result_dict:
182
+ # Translate dictionary entries of bitstring on the full system to the decimal representation of bitstrings on the active qubits
183
+ basis_dict = {entry: int("".join([entry[::-1][i] for i in active_qubits][::-1]), 2) for entry in result}
184
+ # Sort by index:
185
+ basis_dict = dict(sorted(basis_dict.items(), key=lambda item: item[1]))
186
+ basis_dict_list.append(basis_dict)
187
+ y = []
188
+ for i in range(len(result_dict)):
189
+ row = [result_dict[i][key] for key in basis_dict_list[i]]
190
+ if len(row) < n_povm:
191
+ missing_entries = list(np.arange(n_povm))
192
+ for given_entry in basis_dict_list[i].values():
193
+ missing_entries.remove(given_entry)
194
+ for missing_entry in missing_entries:
195
+ row.insert(missing_entry, 0) # 0 measurement outcomes in not recorded entry
196
+ y.append(row / np.sum(row))
197
+ y = np.array(y).T
198
+ 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