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/low_level_jit.py
ADDED
|
@@ -0,0 +1,694 @@
|
|
|
1
|
+
"""
|
|
2
|
+
All functions compiled with numba, such as tensor contractions, derivatives...
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from numba import njit, prange
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def kill_files(folder):
|
|
12
|
+
"""Delete all files in the specified folder.
|
|
13
|
+
|
|
14
|
+
This function iterates over all files in the given folder and attempts to delete them.
|
|
15
|
+
If a file cannot be deleted, an error message is printed.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
folder : str
|
|
20
|
+
The path to the folder containing the files to be deleted.
|
|
21
|
+
"""
|
|
22
|
+
for the_file in os.listdir(folder):
|
|
23
|
+
file_path = os.path.join(folder, the_file)
|
|
24
|
+
try:
|
|
25
|
+
if os.path.isfile(file_path):
|
|
26
|
+
os.unlink(file_path)
|
|
27
|
+
except OSError as e:
|
|
28
|
+
print(f"Failed on filepath: {file_path}. Error: {e}")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def kill_numba_cache():
|
|
32
|
+
"""Delete all __pycache__ folders in the project directory tree.
|
|
33
|
+
|
|
34
|
+
This function iterates through the project directory tree, looking for __pycache__ folders.
|
|
35
|
+
When found, it attempts to delete all files within the __pycache__ folders using the
|
|
36
|
+
`kill_files` function. If the files cannot be deleted, an error message is printed.
|
|
37
|
+
"""
|
|
38
|
+
root_folder = os.path.realpath(__file__ + "/../../")
|
|
39
|
+
for root, dirnames, _ in os.walk(root_folder):
|
|
40
|
+
for dirname in dirnames:
|
|
41
|
+
if dirname == "__pycache__":
|
|
42
|
+
try:
|
|
43
|
+
kill_files(root + "/" + dirname)
|
|
44
|
+
except OSError as e:
|
|
45
|
+
print(f"Failed on {root}. Error: {e}")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@njit(cache=True)
|
|
49
|
+
def local_basis(x, b, length):
|
|
50
|
+
"""Convert a base-10 integer to an integer in a specified base with a fixed number of digits.
|
|
51
|
+
|
|
52
|
+
This function takes an integer `x` in base-10 and converts it to base `b`.
|
|
53
|
+
The result is returned as an array of length `length` with leading zeros.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
x : int
|
|
58
|
+
The input number in base-10 to be converted.
|
|
59
|
+
b : int
|
|
60
|
+
The target base to convert the input number to.
|
|
61
|
+
length : int
|
|
62
|
+
The number of output digits in the target base representation.
|
|
63
|
+
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
numpy.ndarray
|
|
67
|
+
A numpy array of integers representing the base-`b` digits of the converted number,
|
|
68
|
+
with leading zeros if necessary. The length of the array is `length`.
|
|
69
|
+
"""
|
|
70
|
+
r = np.zeros(length).astype(np.int32)
|
|
71
|
+
k = 1
|
|
72
|
+
while x > 0:
|
|
73
|
+
r[-k] = x % b
|
|
74
|
+
x //= b
|
|
75
|
+
k += 1
|
|
76
|
+
return r
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@njit(cache=True)
|
|
80
|
+
def contract(X, j_vec):
|
|
81
|
+
"""Contract a sequence of matrices in the given order.
|
|
82
|
+
|
|
83
|
+
This function computes the product of a sequence of matrices specified by
|
|
84
|
+
the indices in `j_vec`. The result is the contracted product of the matrices
|
|
85
|
+
in the given order.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
X : numpy.ndarray
|
|
90
|
+
A 3D array containing the input matrices, of shape (n_matrices, n_rows, n_columns).
|
|
91
|
+
j_vec : numpy.ndarray
|
|
92
|
+
A 1D array of indices specifying the order in which to contract the matrices in X.
|
|
93
|
+
|
|
94
|
+
Returns
|
|
95
|
+
-------
|
|
96
|
+
numpy.ndarray
|
|
97
|
+
The contracted product of the matrices specified by the indices in `j_vec`.
|
|
98
|
+
"""
|
|
99
|
+
j_vec = j_vec[j_vec >= 0]
|
|
100
|
+
res = np.eye(X[0].shape[0])
|
|
101
|
+
res = res.astype(np.complex128)
|
|
102
|
+
for j in j_vec:
|
|
103
|
+
res = res.dot(X[j])
|
|
104
|
+
return res
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@njit(cache=True, fastmath=True)
|
|
108
|
+
def objf(X, E, rho, J, y):
|
|
109
|
+
"""Calculate the objective function value for matrices, POVM elements, and target values.
|
|
110
|
+
|
|
111
|
+
This function computes the objective function value based on input matrices X, POVM elements E,
|
|
112
|
+
density matrix rho, and target values y.
|
|
113
|
+
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
X : numpy.ndarray
|
|
117
|
+
A 3D array containing the input matrices, of shape (n_matrices, n_rows, n_columns).
|
|
118
|
+
E : numpy.ndarray
|
|
119
|
+
A 2D array representing the POVM elements, of shape (n_povm, r).
|
|
120
|
+
rho : numpy.ndarray
|
|
121
|
+
A 1D array representing the density matrix.
|
|
122
|
+
J : numpy.ndarray
|
|
123
|
+
A 2D array representing the indices for which the objective function will be evaluated.
|
|
124
|
+
y : numpy.ndarray
|
|
125
|
+
A 2D array of shape (n_povm, len(J)) containing the target values.
|
|
126
|
+
|
|
127
|
+
Returns
|
|
128
|
+
-------
|
|
129
|
+
float
|
|
130
|
+
The objective function value for the given set of matrices, POVM elements,
|
|
131
|
+
and target values, normalized by m and n_povm.
|
|
132
|
+
"""
|
|
133
|
+
m = len(J)
|
|
134
|
+
n_povm = y.shape[0]
|
|
135
|
+
objf_ = 0
|
|
136
|
+
for i in prange(m): # pylint: disable=not-an-iterable
|
|
137
|
+
j = J[i][J[i] >= 0]
|
|
138
|
+
C = contract(X, j)
|
|
139
|
+
for o in range(n_povm):
|
|
140
|
+
objf_ += abs(E[o].conj() @ C @ rho - y[o, i]) ** 2
|
|
141
|
+
return objf_ / m / n_povm
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@njit(cache=True)
|
|
145
|
+
def MVE_lower(X_true, E_true, rho_true, X, E, rho, J, n_povm):
|
|
146
|
+
"""Compute the lower bound of the mean value error (MVE) between true and estimated parameters.
|
|
147
|
+
|
|
148
|
+
This function calculates the lower bound of the MVE between the true parameters (X_true,
|
|
149
|
+
E_true, rho_true) and the estimated parameters (X, E, rho) based on the provided J indices.
|
|
150
|
+
|
|
151
|
+
Parameters
|
|
152
|
+
----------
|
|
153
|
+
X_true : numpy.ndarray
|
|
154
|
+
A 3D array containing true input matrices, of shape (n_matrices, n_rows, n_columns).
|
|
155
|
+
E_true : numpy.ndarray
|
|
156
|
+
A 2D array representing the true POVM elements, of shape (n_povm, r).
|
|
157
|
+
rho_true : numpy.ndarray
|
|
158
|
+
A 1D array representing the true density matrix.
|
|
159
|
+
X : numpy.ndarray
|
|
160
|
+
A 3D array containing estimated input matrices, of shape (n_matrices, n_rows, n_columns).
|
|
161
|
+
E : numpy.ndarray
|
|
162
|
+
A 2D array representing the estimated POVM elements, of shape (n_povm, r).
|
|
163
|
+
rho : numpy.ndarray
|
|
164
|
+
A 1D array representing the estimated density matrix.
|
|
165
|
+
J : numpy.ndarray
|
|
166
|
+
A 2D array representing indices for which the objective function will be evaluated.
|
|
167
|
+
n_povm : int
|
|
168
|
+
The number of POVM elements.
|
|
169
|
+
|
|
170
|
+
Returns
|
|
171
|
+
-------
|
|
172
|
+
tuple of float
|
|
173
|
+
A tuple containing the lower bound of the mean value error and the maximum distance.
|
|
174
|
+
"""
|
|
175
|
+
m = len(J)
|
|
176
|
+
dist: float = 0
|
|
177
|
+
max_dist: float = 0
|
|
178
|
+
curr: float = 0
|
|
179
|
+
for i in range(m):
|
|
180
|
+
j = J[i]
|
|
181
|
+
C_t = contract(X_true, j)
|
|
182
|
+
C = contract(X, j)
|
|
183
|
+
curr = 0
|
|
184
|
+
for k in range(n_povm):
|
|
185
|
+
y_t = E_true[k].conj() @ C_t @ rho_true
|
|
186
|
+
y = E[k].conj() @ C @ rho
|
|
187
|
+
curr += np.abs(y_t - y)
|
|
188
|
+
curr = curr / 2
|
|
189
|
+
dist += curr
|
|
190
|
+
max_dist = max(max_dist, curr)
|
|
191
|
+
return dist / m, max_dist
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@njit(cache=True)
|
|
195
|
+
def Mp_norm_lower(X_true, E_true, rho_true, X, E, rho, J, n_povm, p):
|
|
196
|
+
"""Compute the Mp-norm lower bound of the distance between true and estimated parameters.
|
|
197
|
+
|
|
198
|
+
This function calculates the lower bound of the Mp-norm between the true parameters (X_true,
|
|
199
|
+
E_true, rho_true) and the estimated parameters (X, E, rho) based on the provided J indices.
|
|
200
|
+
|
|
201
|
+
Parameters
|
|
202
|
+
----------
|
|
203
|
+
X_true : numpy.ndarray
|
|
204
|
+
A 3D array containing true input matrices, of shape (n_matrices, n_rows, n_columns).
|
|
205
|
+
E_true : numpy.ndarray
|
|
206
|
+
A 2D array representing the true POVM elements, of shape (n_povm, r).
|
|
207
|
+
rho_true : numpy.ndarray
|
|
208
|
+
A 1D array representing the true density matrix.
|
|
209
|
+
X : numpy.ndarray
|
|
210
|
+
A 3D array containing estimated input matrices, of shape (n_matrices, n_rows, n_columns).
|
|
211
|
+
E : numpy.ndarray
|
|
212
|
+
A 2D array representing the estimated POVM elements, of shape (n_povm, r).
|
|
213
|
+
rho : numpy.ndarray
|
|
214
|
+
A 1D array representing the estimated density matrix.
|
|
215
|
+
J : numpy.ndarray
|
|
216
|
+
A 2D array representing indices for which the objective function will be evaluated.
|
|
217
|
+
n_povm : int
|
|
218
|
+
The number of POVM elements.
|
|
219
|
+
p : float
|
|
220
|
+
The order of the Mp-norm (p > 0).
|
|
221
|
+
|
|
222
|
+
Returns
|
|
223
|
+
-------
|
|
224
|
+
tuple of float
|
|
225
|
+
A tuple containing the Mp-norm lower bound and the maximum distance.
|
|
226
|
+
"""
|
|
227
|
+
m = len(J)
|
|
228
|
+
dist: float = 0
|
|
229
|
+
max_dist: float = 0
|
|
230
|
+
curr: float = 0
|
|
231
|
+
for i in range(m):
|
|
232
|
+
j = J[i]
|
|
233
|
+
C_t = contract(X_true, j)
|
|
234
|
+
C = contract(X, j)
|
|
235
|
+
for k in range(n_povm):
|
|
236
|
+
y_t = E_true[k].conj() @ C_t @ rho_true
|
|
237
|
+
y = E[k].conj() @ C @ rho
|
|
238
|
+
dist += np.abs(y_t - y) ** p
|
|
239
|
+
max_dist = max(max_dist, curr)
|
|
240
|
+
return dist ** (1 / p) / m / n_povm, max_dist ** (1 / p)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@njit(cache=True)
|
|
244
|
+
def dK(X, K, E, rho, J, y, d, r, rK):
|
|
245
|
+
"""Compute the derivative of the Kraus operator K with respect to its parameters.
|
|
246
|
+
|
|
247
|
+
This function calculates the derivative of the Kraus operator K, based on the
|
|
248
|
+
input matrices X, E, and rho, as well as the isometry condition.
|
|
249
|
+
|
|
250
|
+
Parameters
|
|
251
|
+
----------
|
|
252
|
+
X : numpy.ndarray
|
|
253
|
+
The input matrix X, of shape (pdim, pdim).
|
|
254
|
+
K : numpy.ndarray
|
|
255
|
+
The Kraus operator K, reshaped to (d, rK, -1).
|
|
256
|
+
E : numpy.ndarray
|
|
257
|
+
A 2D array representing the POVM elements, of shape (n_povm, r).
|
|
258
|
+
rho : numpy.ndarray
|
|
259
|
+
A 1D array representing the density matrix.
|
|
260
|
+
J : numpy.ndarray
|
|
261
|
+
A 2D array representing the indices for which the derivatives will be computed.
|
|
262
|
+
y : numpy.ndarray
|
|
263
|
+
A 2D array of shape (n_povm, len(J)) containing the target values.
|
|
264
|
+
d : int
|
|
265
|
+
The number of Kraus operators.
|
|
266
|
+
r : int
|
|
267
|
+
The rank of the problem.
|
|
268
|
+
rK : int
|
|
269
|
+
The number of rows in the reshaped Kraus operator K.
|
|
270
|
+
|
|
271
|
+
Returns
|
|
272
|
+
-------
|
|
273
|
+
numpy.ndarray
|
|
274
|
+
The derivative of the Kraus operator K with respect to its parameters,
|
|
275
|
+
reshaped to (d, rK, pdim, pdim), and scaled by 2/m/n_povm.
|
|
276
|
+
"""
|
|
277
|
+
K = K.reshape(d, rK, -1)
|
|
278
|
+
pdim = int(np.sqrt(r))
|
|
279
|
+
n_povm = y.shape[0]
|
|
280
|
+
dK_ = np.zeros((d, rK, r))
|
|
281
|
+
dK_ = np.ascontiguousarray(dK_.astype(np.complex128))
|
|
282
|
+
m = len(J)
|
|
283
|
+
for k in range(d):
|
|
284
|
+
for n in range(m):
|
|
285
|
+
j = J[n][J[n] >= 0]
|
|
286
|
+
for i, j_curr in enumerate(j):
|
|
287
|
+
if j_curr == k:
|
|
288
|
+
for o in range(n_povm):
|
|
289
|
+
L = E[o].conj() @ contract(X, j[:i])
|
|
290
|
+
R = contract(X, j[i + 1 :]) @ rho
|
|
291
|
+
D_ind = L @ X[k] @ R - y[o, n]
|
|
292
|
+
dK_[k] += D_ind * K[k].conj() @ np.kron(L.reshape(pdim, pdim).T, R.reshape(pdim, pdim).T)
|
|
293
|
+
return dK_.reshape(d, rK, pdim, pdim) * 2 / m / n_povm
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
@njit(cache=True)
|
|
297
|
+
def dK_dMdM(X, K, E, rho, J, y, d, r, rK):
|
|
298
|
+
"""Compute the derivatives of K, dM10, and dM11 for the given input parameters.
|
|
299
|
+
|
|
300
|
+
This function calculates the derivatives of K, dM10, and dM11 based on the input matrices X,
|
|
301
|
+
matrix K, POVM elements E, density matrix rho, and target values y.
|
|
302
|
+
|
|
303
|
+
Parameters
|
|
304
|
+
----------
|
|
305
|
+
X : numpy.ndarray
|
|
306
|
+
A 3D array containing input matrices, of shape (n_matrices, n_rows, n_columns).
|
|
307
|
+
K : numpy.ndarray
|
|
308
|
+
A 1D array representing the matrix K.
|
|
309
|
+
E : numpy.ndarray
|
|
310
|
+
A 2D array representing the POVM elements, of shape (n_povm, r).
|
|
311
|
+
rho : numpy.ndarray
|
|
312
|
+
A 1D array representing the density matrix.
|
|
313
|
+
J : numpy.ndarray
|
|
314
|
+
A 2D array representing indices for which the objective function will be evaluated.
|
|
315
|
+
y : numpy.ndarray
|
|
316
|
+
A 2D array of shape (n_povm, len(J)) containing target values.
|
|
317
|
+
d : int
|
|
318
|
+
The number of dimensions for the matrix K.
|
|
319
|
+
r : int
|
|
320
|
+
The number of rows for the matrix K.
|
|
321
|
+
rK : int
|
|
322
|
+
The number of columns for the matrix K.
|
|
323
|
+
|
|
324
|
+
Returns
|
|
325
|
+
-------
|
|
326
|
+
tuple of numpy.ndarray
|
|
327
|
+
A tuple containing the derivatives of K, dM10, and dM11, each of which is a numpy.ndarray.
|
|
328
|
+
"""
|
|
329
|
+
K = K.reshape(d, rK, -1)
|
|
330
|
+
pdim = int(np.sqrt(r))
|
|
331
|
+
n = d * rK * r
|
|
332
|
+
n_povm = y.shape[0]
|
|
333
|
+
dK_ = np.zeros((d, rK, r)).astype(np.complex128)
|
|
334
|
+
dM11 = np.zeros(n**2).astype(np.complex128)
|
|
335
|
+
dM10 = np.zeros(n**2).astype(np.complex128)
|
|
336
|
+
m = len(J)
|
|
337
|
+
for n in range(m):
|
|
338
|
+
j = J[n][J[n] >= 0]
|
|
339
|
+
dM = np.ascontiguousarray(np.zeros((n_povm, d, rK, r)).astype(np.complex128))
|
|
340
|
+
for i, _ in enumerate(j):
|
|
341
|
+
k = j[i]
|
|
342
|
+
C = contract(X, j[:i])
|
|
343
|
+
R = contract(X, j[i + 1 :]) @ rho
|
|
344
|
+
for o in range(n_povm):
|
|
345
|
+
L = E[o].conj() @ C
|
|
346
|
+
D_ind = L @ X[k] @ R - y[o, n]
|
|
347
|
+
dM_loc = K[k].conj() @ np.kron(L.reshape((pdim, pdim)).T, R.reshape((pdim, pdim)).T)
|
|
348
|
+
dM[o, k, :, :] += dM_loc
|
|
349
|
+
dK_[k] += D_ind * dM_loc
|
|
350
|
+
for o in range(n_povm):
|
|
351
|
+
dM11 += np.kron(dM[o].conj().reshape(-1), dM[o].reshape(-1))
|
|
352
|
+
dM10 += np.kron(dM[o].reshape(-1), dM[o].reshape(-1))
|
|
353
|
+
return (dK_.reshape((d, rK, pdim, pdim)) * 2 / m / n_povm, 2 * dM10 / m / n_povm, 2 * dM11 / m / n_povm)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
@njit(cache=True, parallel=False)
|
|
357
|
+
def ddM(X, K, E, rho, J, y, d, r, rK):
|
|
358
|
+
"""Compute the second derivative of the objective function with respect to matrix elements.
|
|
359
|
+
|
|
360
|
+
This function calculates the second derivative of the objective function for a given
|
|
361
|
+
set of input parameters.
|
|
362
|
+
|
|
363
|
+
Parameters
|
|
364
|
+
----------
|
|
365
|
+
X : ndarray
|
|
366
|
+
Array of input matrices.
|
|
367
|
+
K : ndarray
|
|
368
|
+
Array of Kraus operators.
|
|
369
|
+
E : ndarray
|
|
370
|
+
Array of measurement operators.
|
|
371
|
+
rho : ndarray
|
|
372
|
+
Array of quantum states.
|
|
373
|
+
J : ndarray
|
|
374
|
+
Array of indices corresponding to the sequence of operations.
|
|
375
|
+
y : ndarray
|
|
376
|
+
Array of observed probabilities.
|
|
377
|
+
d : int
|
|
378
|
+
Number of Kraus operators.
|
|
379
|
+
r : int
|
|
380
|
+
Dimension of the local basis.
|
|
381
|
+
rK : int
|
|
382
|
+
Number of rows in the Kraus operator matrix.
|
|
383
|
+
|
|
384
|
+
Returns
|
|
385
|
+
-------
|
|
386
|
+
ddK : ndarray, shape (d, d, rK, rK, pdim, pdim, pdim, pdim)
|
|
387
|
+
Second derivative of the objective function with respect to matrix elements, reshaped
|
|
388
|
+
for easier manipulation.
|
|
389
|
+
dconjdK : ndarray, shape (d, d, rK, rK, pdim, pdim, pdim, pdim)
|
|
390
|
+
Conjugate of the second derivative of the objective function with respect to matrix
|
|
391
|
+
elements, reshaped for easier manipulation.
|
|
392
|
+
"""
|
|
393
|
+
# pylint: disable=too-many-branches, too-many-nested-blocks
|
|
394
|
+
pdim = int(np.sqrt(r))
|
|
395
|
+
n_povm = y.shape[0]
|
|
396
|
+
ddK = np.zeros((d**2, rK**2, r, r))
|
|
397
|
+
ddK = np.ascontiguousarray(ddK.astype(np.complex128))
|
|
398
|
+
dconjdK = np.zeros((d**2, rK**2, r, r))
|
|
399
|
+
dconjdK = np.ascontiguousarray(ddK.astype(np.complex128))
|
|
400
|
+
m = len(J)
|
|
401
|
+
for k in range(d**2):
|
|
402
|
+
k1, k2 = local_basis(k, d, 2)
|
|
403
|
+
for n in range(m):
|
|
404
|
+
j = J[n][J[n] >= 0]
|
|
405
|
+
for i1, j_1 in enumerate(j):
|
|
406
|
+
if j_1 == k1:
|
|
407
|
+
for i2, j_2 in enumerate(j):
|
|
408
|
+
if j_2 == k2:
|
|
409
|
+
L0 = contract(X, j[: min(i1, i2)])
|
|
410
|
+
C = contract(X, j[min(i1, i2) + 1 : max(i1, i2)]).reshape(pdim, pdim, pdim, pdim)
|
|
411
|
+
R = contract(X, j[max(i1, i2) + 1 :]) @ rho
|
|
412
|
+
for o in range(n_povm):
|
|
413
|
+
L = E[o].conj() @ L0
|
|
414
|
+
if i1 == i2:
|
|
415
|
+
D_ind = L @ X[k1] @ R - y[o, n]
|
|
416
|
+
elif i1 < i2:
|
|
417
|
+
D_ind = L @ X[k1] @ C.reshape(r, r) @ X[k2] @ R - y[o, n]
|
|
418
|
+
else:
|
|
419
|
+
D_ind = L @ X[k2] @ C.reshape(r, r) @ X[k1] @ R - y[o, n]
|
|
420
|
+
|
|
421
|
+
ddK_loc = np.zeros((rK**2, r, r)).astype(np.complex128)
|
|
422
|
+
dconjdK_loc = np.zeros((rK**2, r, r)).astype(np.complex128)
|
|
423
|
+
for rk1 in range(rK):
|
|
424
|
+
for rk2 in range(rK):
|
|
425
|
+
if i1 < i2:
|
|
426
|
+
ddK_loc[rk1 * rK + rk2] = np.kron(
|
|
427
|
+
L.reshape(pdim, pdim) @ K[k1, rk1].conj(),
|
|
428
|
+
R.reshape(pdim, pdim) @ K[k2, rk2].T.conj(),
|
|
429
|
+
) @ np.ascontiguousarray(C.transpose(1, 3, 0, 2)).reshape(r, r)
|
|
430
|
+
|
|
431
|
+
ddK_loc[rk1 * rK + rk2] = np.ascontiguousarray(
|
|
432
|
+
ddK_loc[rk1 * rK + rk2]
|
|
433
|
+
.reshape(pdim, pdim, pdim, pdim)
|
|
434
|
+
.transpose(0, 3, 2, 1)
|
|
435
|
+
).reshape(r, r)
|
|
436
|
+
|
|
437
|
+
dconjdK_loc[rk1 * rK + rk2] = np.kron(
|
|
438
|
+
L.reshape(pdim, pdim) @ K[k1, rk1].conj(),
|
|
439
|
+
R.reshape(pdim, pdim).T @ K[k2, rk2].T,
|
|
440
|
+
) @ np.ascontiguousarray(C.transpose(1, 2, 3, 0)).reshape(r, r)
|
|
441
|
+
|
|
442
|
+
dconjdK_loc[rk1 * rK + rk2] = np.ascontiguousarray(
|
|
443
|
+
dconjdK_loc[rk1 * rK + rk2]
|
|
444
|
+
.reshape(pdim, pdim, pdim, pdim)
|
|
445
|
+
.transpose(0, 2, 3, 1)
|
|
446
|
+
).reshape(r, r)
|
|
447
|
+
|
|
448
|
+
elif i1 == i2:
|
|
449
|
+
dconjdK_loc[rk1 * rK + rk2] = np.outer(L, R)
|
|
450
|
+
|
|
451
|
+
elif i1 > i2:
|
|
452
|
+
ddK_loc[rk1 * rK + rk2] = np.kron(
|
|
453
|
+
L.reshape(pdim, pdim) @ K[k2, rk2].conj(),
|
|
454
|
+
R.reshape(pdim, pdim) @ K[k1, rk1].T.conj(),
|
|
455
|
+
) @ np.ascontiguousarray(C.transpose(1, 3, 0, 2)).reshape(r, r)
|
|
456
|
+
|
|
457
|
+
ddK_loc[rk1 * rK + rk2] = np.ascontiguousarray(
|
|
458
|
+
ddK_loc[rk1 * rK + rk2]
|
|
459
|
+
.reshape(pdim, pdim, pdim, pdim)
|
|
460
|
+
.transpose(3, 0, 1, 2)
|
|
461
|
+
).reshape(r, r)
|
|
462
|
+
|
|
463
|
+
dconjdK_loc[rk1 * rK + rk2] = np.kron(
|
|
464
|
+
L.reshape(pdim, pdim).T @ K[k2, rk2],
|
|
465
|
+
R.reshape(pdim, pdim) @ K[k1, rk1].T.conj(),
|
|
466
|
+
) @ np.ascontiguousarray(C.transpose((0, 3, 2, 1))).reshape(r, r)
|
|
467
|
+
|
|
468
|
+
dconjdK_loc[rk1 * rK + rk2] = np.ascontiguousarray(
|
|
469
|
+
dconjdK_loc[rk1 * rK + rk2]
|
|
470
|
+
.reshape(pdim, pdim, pdim, pdim)
|
|
471
|
+
.transpose(2, 0, 1, 3)
|
|
472
|
+
).reshape(r, r)
|
|
473
|
+
|
|
474
|
+
ddK[k1 * d + k2] += D_ind * ddK_loc
|
|
475
|
+
dconjdK[k1 * d + k2] += D_ind * dconjdK_loc
|
|
476
|
+
return (
|
|
477
|
+
ddK.reshape(d, d, rK, rK, pdim, pdim, pdim, pdim) * 2 / m / n_povm,
|
|
478
|
+
dconjdK.reshape(d, d, rK, rK, pdim, pdim, pdim, pdim) * 2 / m / n_povm,
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
@njit(parallel=True, cache=True)
|
|
483
|
+
def dA(X, A, B, J, y, r, pdim, n_povm):
|
|
484
|
+
"""Compute the derivative of A with respect to the objective function.
|
|
485
|
+
|
|
486
|
+
This function calculates the gradient of A for a given set of input parameters.
|
|
487
|
+
|
|
488
|
+
Parameters
|
|
489
|
+
----------
|
|
490
|
+
X : ndarray
|
|
491
|
+
Array of input matrices.
|
|
492
|
+
A : ndarray
|
|
493
|
+
Array of measurement operators.
|
|
494
|
+
B : ndarray
|
|
495
|
+
Array of quantum states.
|
|
496
|
+
J : ndarray
|
|
497
|
+
Array of indices corresponding to the sequence of operations.
|
|
498
|
+
y : ndarray
|
|
499
|
+
Array of observed probabilities.
|
|
500
|
+
r : int
|
|
501
|
+
Number of elements in each measurement operator.
|
|
502
|
+
pdim : int
|
|
503
|
+
Dimension of the density matrices.
|
|
504
|
+
n_povm : int
|
|
505
|
+
Number of measurement operators.
|
|
506
|
+
|
|
507
|
+
Returns
|
|
508
|
+
-------
|
|
509
|
+
dA : ndarray
|
|
510
|
+
Derivative of A with respect to the objective function.
|
|
511
|
+
"""
|
|
512
|
+
A = np.ascontiguousarray(A)
|
|
513
|
+
B = np.ascontiguousarray(B)
|
|
514
|
+
E = np.zeros((n_povm, r)).astype(np.complex128)
|
|
515
|
+
for k in range(n_povm):
|
|
516
|
+
E[k] = (A[k].T.conj() @ A[k]).reshape(-1)
|
|
517
|
+
rho = (B @ B.T.conj()).reshape(-1)
|
|
518
|
+
dA_ = np.zeros((n_povm, pdim, pdim))
|
|
519
|
+
dA_ = dA_.astype(np.complex128)
|
|
520
|
+
m = len(J)
|
|
521
|
+
for n in prange(m): # pylint: disable=not-an-iterable
|
|
522
|
+
jE = J[n][J[n] >= 0][0]
|
|
523
|
+
j = J[n][J[n] >= 0][1:]
|
|
524
|
+
inner_deriv = contract(X, j) @ rho
|
|
525
|
+
D_ind = E[jE].conj().dot(inner_deriv) - y[n]
|
|
526
|
+
dA_[jE] += D_ind * A[jE] @ inner_deriv.reshape(pdim, pdim).T.conj()
|
|
527
|
+
return dA_
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
@njit(parallel=True, cache=True)
|
|
531
|
+
def dB(X, A, B, J, y, pdim):
|
|
532
|
+
"""Compute the derivative of B with respect to the objective function.
|
|
533
|
+
|
|
534
|
+
This function calculates the gradient of B for a given set of input parameters.
|
|
535
|
+
|
|
536
|
+
Parameters
|
|
537
|
+
----------
|
|
538
|
+
X : ndarray
|
|
539
|
+
Array of input matrices.
|
|
540
|
+
A : ndarray
|
|
541
|
+
Array of measurement operators.
|
|
542
|
+
B : ndarray
|
|
543
|
+
Array of quantum states.
|
|
544
|
+
J : ndarray
|
|
545
|
+
Array of indices corresponding to the sequence of operations.
|
|
546
|
+
y : ndarray
|
|
547
|
+
Array of observed probabilities.
|
|
548
|
+
pdim : int
|
|
549
|
+
Dimension of the density matrices.
|
|
550
|
+
|
|
551
|
+
Returns
|
|
552
|
+
-------
|
|
553
|
+
dB : ndarray
|
|
554
|
+
Derivative of B with respect to the objective function.
|
|
555
|
+
"""
|
|
556
|
+
A = np.ascontiguousarray(A)
|
|
557
|
+
B = np.ascontiguousarray(B)
|
|
558
|
+
E = (A.T.conj() @ A).reshape(-1)
|
|
559
|
+
rho = (B @ B.T.conj()).reshape(-1)
|
|
560
|
+
dB_ = np.zeros((pdim, pdim))
|
|
561
|
+
dB_ = dB_.astype(np.complex128)
|
|
562
|
+
m = len(J)
|
|
563
|
+
for n in prange(m): # pylint: disable=not-an-iterable
|
|
564
|
+
jE = J[n][J[n] >= 0][0]
|
|
565
|
+
j = J[n][J[n] >= 0][1:]
|
|
566
|
+
inner_deriv = E[jE].conj().dot(contract(X, j))
|
|
567
|
+
D_ind = inner_deriv.dot(rho) - y[n]
|
|
568
|
+
dB_ += D_ind * inner_deriv.reshape(pdim, pdim).conj() @ B
|
|
569
|
+
return dB_
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
@njit(parallel=True, cache=True)
|
|
573
|
+
def ddA_derivs(X, A, B, J, y, r, pdim, n_povm):
|
|
574
|
+
"""Calculate the derivatives of a given POVM element with respect to its parameters.
|
|
575
|
+
|
|
576
|
+
This function computes the derivatives of the POVM element based on input matrices
|
|
577
|
+
A, B, and X, as well as the isometry condition. The derivatives are only dependent
|
|
578
|
+
on one POVM element, and different POVM elements are connected via the isometry condition.
|
|
579
|
+
|
|
580
|
+
Parameters
|
|
581
|
+
----------
|
|
582
|
+
X : numpy.ndarray
|
|
583
|
+
The input matrix X, of shape (pdim, pdim).
|
|
584
|
+
A : numpy.ndarray
|
|
585
|
+
A 3D array of shape (n_povm, pdim, pdim) representing the POVM elements.
|
|
586
|
+
B : numpy.ndarray
|
|
587
|
+
A 2D array of shape (pdim, pdim) representing the isometry matrix.
|
|
588
|
+
J : numpy.ndarray
|
|
589
|
+
A 2D array representing the indices for which the derivatives will be computed.
|
|
590
|
+
y : numpy.ndarray
|
|
591
|
+
A 2D array of shape (n_povm, len(J)) containing the target values.
|
|
592
|
+
r : int
|
|
593
|
+
The rank of the problem.
|
|
594
|
+
pdim : int
|
|
595
|
+
The dimension of the input matrices A and B.
|
|
596
|
+
n_povm : int
|
|
597
|
+
The number of POVM elements.
|
|
598
|
+
|
|
599
|
+
Returns
|
|
600
|
+
-------
|
|
601
|
+
tuple of numpy.ndarray
|
|
602
|
+
A tuple containing the computed derivatives:
|
|
603
|
+
- dA: The derivative of the POVM element A with respect to its parameters,
|
|
604
|
+
of shape (n_povm, pdim, pdim).
|
|
605
|
+
- dMdM: The product of the derivatives dM and dM, of shape (n_povm, r, r).
|
|
606
|
+
- dMconjdM: The product of the conjugate of dM and dM, of shape (n_povm, r, r).
|
|
607
|
+
- dconjdA: The product of the conjugate of dA, of shape (n_povm, r, r).
|
|
608
|
+
"""
|
|
609
|
+
A = np.ascontiguousarray(A)
|
|
610
|
+
B = np.ascontiguousarray(B)
|
|
611
|
+
E = np.zeros((n_povm, r)).astype(np.complex128)
|
|
612
|
+
for k in range(n_povm):
|
|
613
|
+
E[k] = (A[k].T.conj() @ A[k]).reshape(-1)
|
|
614
|
+
rho = (B @ B.T.conj()).reshape(-1)
|
|
615
|
+
dA_ = np.zeros((n_povm, pdim, pdim)).astype(np.complex128)
|
|
616
|
+
dM = np.zeros((pdim, pdim)).astype(np.complex128)
|
|
617
|
+
dMdM = np.zeros((n_povm, r, r)).astype(np.complex128)
|
|
618
|
+
dMconjdM = np.zeros((n_povm, r, r)).astype(np.complex128)
|
|
619
|
+
dconjdA = np.zeros((n_povm, r, r)).astype(np.complex128)
|
|
620
|
+
m = len(J)
|
|
621
|
+
for n in prange(m): # pylint: disable=not-an-iterable
|
|
622
|
+
j = J[n][J[n] >= 0]
|
|
623
|
+
R = contract(X, j) @ rho
|
|
624
|
+
for o in range(n_povm):
|
|
625
|
+
D_ind = E[o].conj() @ R - y[o, n]
|
|
626
|
+
dM = A[o].conj() @ R.reshape(pdim, pdim).T
|
|
627
|
+
dMdM[o] += np.outer(dM, dM)
|
|
628
|
+
dMconjdM[o] += np.outer(dM.conj(), dM)
|
|
629
|
+
dA_[o] += D_ind * dM
|
|
630
|
+
dconjdA[o] += D_ind * np.kron(np.eye(pdim).astype(np.complex128), R.reshape(pdim, pdim).T)
|
|
631
|
+
return dA_ * 2 / m / n_povm, dMdM * 2 / m / n_povm, dMconjdM * 2 / m / n_povm, dconjdA * 2 / m / n_povm
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
@njit(parallel=True, cache=True)
|
|
635
|
+
def ddB_derivs(X, A, B, J, y, r, pdim):
|
|
636
|
+
"""Calculate the derivatives of the isometry matrix B with respect to its parameters.
|
|
637
|
+
|
|
638
|
+
This function computes the derivatives of the isometry matrix B based on input matrices A and X,
|
|
639
|
+
as well as the isometry condition.
|
|
640
|
+
|
|
641
|
+
Parameters
|
|
642
|
+
----------
|
|
643
|
+
X : numpy.ndarray
|
|
644
|
+
The input matrix X, of shape (pdim, pdim).
|
|
645
|
+
A : numpy.ndarray
|
|
646
|
+
A 3D array of shape (n_povm, pdim, pdim) representing the POVM elements.
|
|
647
|
+
B : numpy.ndarray
|
|
648
|
+
A 2D array of shape (pdim, pdim) representing the isometry matrix.
|
|
649
|
+
J : numpy.ndarray
|
|
650
|
+
A 2D array representing the indices for which the derivatives will be computed.
|
|
651
|
+
y : numpy.ndarray
|
|
652
|
+
A 2D array of shape (n_povm, len(J)) containing the target values.
|
|
653
|
+
r : int
|
|
654
|
+
The rank of the problem.
|
|
655
|
+
pdim : int
|
|
656
|
+
The dimension of the input matrices A and B.
|
|
657
|
+
|
|
658
|
+
Returns
|
|
659
|
+
-------
|
|
660
|
+
tuple of numpy.ndarray
|
|
661
|
+
A tuple containing the computed derivatives:
|
|
662
|
+
- dB: The derivative of the isometry matrix B with respect to its parameters,
|
|
663
|
+
of shape (pdim, pdim).
|
|
664
|
+
- dMdM: The product of the derivatives dM and dM, of shape (r, r).
|
|
665
|
+
- dMconjdM: The product of the conjugate of dM and dM, of shape (r, r).
|
|
666
|
+
- dconjdB: The product of the conjugate of dB, of shape (r, r).
|
|
667
|
+
"""
|
|
668
|
+
n_povm = A.shape[0]
|
|
669
|
+
A = np.ascontiguousarray(A)
|
|
670
|
+
B = np.ascontiguousarray(B)
|
|
671
|
+
E = np.zeros((n_povm, r)).astype(np.complex128)
|
|
672
|
+
for k in range(n_povm):
|
|
673
|
+
E[k] = (A[k].T.conj() @ A[k]).reshape(-1)
|
|
674
|
+
rho = (B @ B.T.conj()).reshape(-1)
|
|
675
|
+
dB_ = np.zeros((pdim, pdim)).astype(np.complex128)
|
|
676
|
+
dM = np.zeros((pdim, pdim)).astype(np.complex128)
|
|
677
|
+
dMdM = np.zeros((r, r)).astype(np.complex128)
|
|
678
|
+
dMconjdM = np.zeros((r, r)).astype(np.complex128)
|
|
679
|
+
dconjdB = np.zeros((r, r)).astype(np.complex128)
|
|
680
|
+
m = len(J)
|
|
681
|
+
for n in prange(m): # pylint: disable=not-an-iterable
|
|
682
|
+
j = J[n][J[n] >= 0]
|
|
683
|
+
C = contract(X, j)
|
|
684
|
+
for o in range(n_povm):
|
|
685
|
+
L = E[o].conj() @ C
|
|
686
|
+
D_ind = L @ rho - y[o, n]
|
|
687
|
+
|
|
688
|
+
dM = L.reshape(pdim, pdim) @ B.conj()
|
|
689
|
+
dMdM += np.outer(dM, dM)
|
|
690
|
+
dMconjdM += np.outer(dM.conj(), dM)
|
|
691
|
+
|
|
692
|
+
dB_ += D_ind * dM
|
|
693
|
+
dconjdB += D_ind * np.kron(L.reshape(pdim, pdim), np.eye(pdim).astype(np.complex128))
|
|
694
|
+
return dB_ * 2 / m / n_povm, dMdM * 2 / m / n_povm, dMconjdM * 2 / m / n_povm, dconjdB.T * 2 / m / n_povm
|