iqm-benchmarks 1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of iqm-benchmarks might be problematic. Click here for more details.
- iqm/benchmarks/__init__.py +31 -0
- iqm/benchmarks/benchmark.py +109 -0
- iqm/benchmarks/benchmark_definition.py +264 -0
- iqm/benchmarks/benchmark_experiment.py +163 -0
- iqm/benchmarks/compressive_gst/__init__.py +20 -0
- iqm/benchmarks/compressive_gst/compressive_gst.py +1029 -0
- iqm/benchmarks/entanglement/__init__.py +18 -0
- iqm/benchmarks/entanglement/ghz.py +802 -0
- iqm/benchmarks/logging_config.py +29 -0
- iqm/benchmarks/optimization/__init__.py +18 -0
- iqm/benchmarks/optimization/qscore.py +719 -0
- iqm/benchmarks/quantum_volume/__init__.py +21 -0
- iqm/benchmarks/quantum_volume/clops.py +726 -0
- iqm/benchmarks/quantum_volume/quantum_volume.py +854 -0
- iqm/benchmarks/randomized_benchmarking/__init__.py +18 -0
- iqm/benchmarks/randomized_benchmarking/clifford_1q.pkl +0 -0
- iqm/benchmarks/randomized_benchmarking/clifford_2q.pkl +0 -0
- iqm/benchmarks/randomized_benchmarking/clifford_rb/__init__.py +19 -0
- iqm/benchmarks/randomized_benchmarking/clifford_rb/clifford_rb.py +386 -0
- iqm/benchmarks/randomized_benchmarking/interleaved_rb/__init__.py +19 -0
- iqm/benchmarks/randomized_benchmarking/interleaved_rb/interleaved_rb.py +555 -0
- iqm/benchmarks/randomized_benchmarking/mirror_rb/__init__.py +19 -0
- iqm/benchmarks/randomized_benchmarking/mirror_rb/mirror_rb.py +810 -0
- iqm/benchmarks/randomized_benchmarking/multi_lmfit.py +86 -0
- iqm/benchmarks/randomized_benchmarking/randomized_benchmarking_common.py +892 -0
- iqm/benchmarks/readout_mitigation.py +290 -0
- iqm/benchmarks/utils.py +521 -0
- iqm_benchmarks-1.3.dist-info/LICENSE +205 -0
- iqm_benchmarks-1.3.dist-info/METADATA +190 -0
- iqm_benchmarks-1.3.dist-info/RECORD +42 -0
- iqm_benchmarks-1.3.dist-info/WHEEL +5 -0
- iqm_benchmarks-1.3.dist-info/top_level.txt +2 -0
- mGST/LICENSE +21 -0
- mGST/README.md +54 -0
- mGST/additional_fns.py +962 -0
- mGST/algorithm.py +733 -0
- mGST/compatibility.py +238 -0
- mGST/low_level_jit.py +694 -0
- mGST/optimization.py +349 -0
- mGST/qiskit_interface.py +282 -0
- mGST/reporting/figure_gen.py +334 -0
- mGST/reporting/reporting.py +710 -0
mGST/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
|
mGST/qiskit_interface.py
ADDED
|
@@ -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
|