iqm-benchmarks 1.6__py3-none-any.whl → 1.8__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/compressive_gst/compressive_gst.py +147 -720
- iqm/benchmarks/compressive_gst/gst_analysis.py +851 -0
- iqm/benchmarks/entanglement/ghz.py +181 -119
- iqm/benchmarks/randomized_benchmarking/clifford_1q.pkl +0 -0
- iqm/benchmarks/randomized_benchmarking/clifford_2q.pkl +0 -0
- iqm/benchmarks/randomized_benchmarking/randomized_benchmarking_common.py +6 -4
- iqm/benchmarks/readout_mitigation.py +3 -8
- iqm/benchmarks/utils.py +0 -31
- {iqm_benchmarks-1.6.dist-info → iqm_benchmarks-1.8.dist-info}/METADATA +19 -13
- {iqm_benchmarks-1.6.dist-info → iqm_benchmarks-1.8.dist-info}/RECORD +13 -12
- {iqm_benchmarks-1.6.dist-info → iqm_benchmarks-1.8.dist-info}/LICENSE +0 -0
- {iqm_benchmarks-1.6.dist-info → iqm_benchmarks-1.8.dist-info}/WHEEL +0 -0
- {iqm_benchmarks-1.6.dist-info → iqm_benchmarks-1.8.dist-info}/top_level.txt +0 -0
|
@@ -14,44 +14,49 @@
|
|
|
14
14
|
|
|
15
15
|
"""
|
|
16
16
|
Compressive gate set tomography
|
|
17
|
+
|
|
18
|
+
The full benchmark executes the following steps:
|
|
19
|
+
1) Generation of circuits to measure
|
|
20
|
+
2) Running of said circuits on the backend
|
|
21
|
+
3) Conversion of the ideal gate set into numpy arrays to be processed by mGST
|
|
22
|
+
4) Optional generation of an initialization for mGST
|
|
23
|
+
5) Running mGST to get first gate set estimates
|
|
24
|
+
6) Gauge optimization to the target gates using pyGSTi's gauge optimizer
|
|
25
|
+
7) Optional rerun of mGST on bootstrap data to produce error bars
|
|
26
|
+
8) Generation of error measures and result tables
|
|
27
|
+
9) Generation of matrix plots for the reconstructed gate set
|
|
17
28
|
"""
|
|
18
29
|
|
|
19
|
-
from
|
|
20
|
-
from time import perf_counter, strftime
|
|
21
|
-
from typing import Dict, List, Tuple, Type, Union
|
|
30
|
+
from typing import Any, Dict, List, Tuple, Type, Union
|
|
22
31
|
|
|
23
|
-
import matplotlib.pyplot as plt
|
|
24
32
|
import numpy as np
|
|
25
|
-
from pandas import DataFrame
|
|
26
|
-
from pygsti.tools import change_basis
|
|
27
33
|
from qiskit import QuantumCircuit
|
|
28
34
|
from qiskit.circuit.library import CZGate, RGate
|
|
35
|
+
import xarray as xr
|
|
29
36
|
|
|
30
|
-
from iqm.benchmarks.benchmark import
|
|
37
|
+
from iqm.benchmarks.benchmark import BenchmarkConfigurationBase
|
|
38
|
+
from iqm.benchmarks.benchmark_definition import Benchmark, add_counts_to_dataset
|
|
39
|
+
from iqm.benchmarks.compressive_gst.gst_analysis import mgst_analysis
|
|
31
40
|
from iqm.benchmarks.logging_config import qcvv_logger
|
|
32
41
|
from iqm.benchmarks.utils import (
|
|
33
42
|
perform_backend_transpilation,
|
|
34
43
|
retrieve_all_counts,
|
|
35
|
-
retrieve_all_job_metadata,
|
|
36
44
|
set_coupling_map,
|
|
37
45
|
submit_execute,
|
|
38
46
|
timeit,
|
|
39
47
|
)
|
|
40
48
|
from iqm.qiskit_iqm.iqm_backend import IQMBackendBase
|
|
41
|
-
from mGST import additional_fns,
|
|
42
|
-
from mGST.
|
|
43
|
-
from mGST.qiskit_interface import add_idle_gates, qiskit_gate_to_operator, remove_idle_wires
|
|
44
|
-
from mGST.reporting import figure_gen, reporting
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
# pylint: disable=too-many-lines
|
|
49
|
+
from mGST import additional_fns, qiskit_interface
|
|
50
|
+
from mGST.qiskit_interface import add_idle_gates, remove_idle_wires
|
|
48
51
|
|
|
49
52
|
|
|
50
|
-
class
|
|
53
|
+
class CompressiveGST(Benchmark):
|
|
51
54
|
"""
|
|
52
55
|
SPAM-robust characterization of a set of quantum gates
|
|
53
56
|
"""
|
|
54
57
|
|
|
58
|
+
analysis_function = staticmethod(mgst_analysis)
|
|
59
|
+
|
|
55
60
|
def __init__(self, backend: IQMBackendBase, configuration: "GSTConfiguration"):
|
|
56
61
|
"""Construct the compressive_gst class.
|
|
57
62
|
|
|
@@ -61,27 +66,17 @@ class compressive_GST(BenchmarkBase):
|
|
|
61
66
|
"""
|
|
62
67
|
super().__init__(backend, configuration)
|
|
63
68
|
|
|
64
|
-
self.
|
|
65
|
-
self.num_qubits = len(self.
|
|
69
|
+
self.qubit_layouts = parse_layouts(configuration.qubit_layouts)
|
|
70
|
+
self.num_qubits = len(self.qubit_layouts[0])
|
|
66
71
|
self.pdim = 2**self.num_qubits
|
|
67
72
|
self.num_povm = self.pdim
|
|
68
|
-
|
|
69
|
-
self.
|
|
70
|
-
self.num_circuits = configuration.num_circuits
|
|
71
|
-
self.shots = configuration.shots
|
|
72
|
-
self.rank = configuration.rank
|
|
73
|
-
self.from_init = configuration.from_init
|
|
74
|
-
self.max_inits = configuration.max_inits
|
|
75
|
-
self.convergence_criteria = configuration.convergence_criteria
|
|
76
|
-
self.bootstrap_samples = configuration.bootstrap_samples
|
|
77
|
-
self.weights = dict({f"G%i" % i: 1 for i in range(self.num_gates)}, **{"spam": 1})
|
|
78
|
-
self.max_gates_per_batch = configuration.max_gates_per_batch
|
|
79
|
-
self.timestamp = strftime("%Y%m%d-%H%M%S")
|
|
73
|
+
|
|
74
|
+
self.gate_set, self.gate_labels, self.num_gates = parse_gate_set(configuration, self.num_qubits)
|
|
80
75
|
|
|
81
76
|
if configuration.opt_method not in ["GD", "SFN", "auto"]:
|
|
82
77
|
raise ValueError("Invalid optimization method, valid options are: GD, SFN, auto")
|
|
83
78
|
if configuration.opt_method == "auto":
|
|
84
|
-
if (self.num_qubits == 2 and
|
|
79
|
+
if (self.num_qubits == 2 and configuration.rank > 2) or self.num_qubits > 2:
|
|
85
80
|
self.opt_method = "GD"
|
|
86
81
|
else:
|
|
87
82
|
self.opt_method = "SFN"
|
|
@@ -100,35 +95,24 @@ class compressive_GST(BenchmarkBase):
|
|
|
100
95
|
else:
|
|
101
96
|
self.batch_size = configuration.batch_size
|
|
102
97
|
|
|
103
|
-
Pauli_labels_loc = ["I", "X", "Y", "Z"]
|
|
104
|
-
Pauli_labels_rep = [Pauli_labels_loc for _ in range(int(np.log2(self.pdim)))]
|
|
105
|
-
separator = ""
|
|
106
|
-
self.Pauli_labels = [separator.join(map(str, x)) for x in product(*Pauli_labels_rep)]
|
|
107
|
-
|
|
108
|
-
std_labels_loc = ["0", "1"]
|
|
109
|
-
std_labels_rep = [std_labels_loc for _ in range(int(np.log2(self.pdim)))]
|
|
110
|
-
self.std_labels = [separator.join(map(str, x)) for x in product(*std_labels_rep)]
|
|
111
|
-
|
|
112
98
|
self.y, self.J = (
|
|
113
|
-
np.empty((self.num_povm, self.num_circuits)),
|
|
114
|
-
np.empty((self.num_circuits, self.num_povm)),
|
|
99
|
+
np.empty((self.num_povm, self.configuration.num_circuits)),
|
|
100
|
+
np.empty((self.configuration.num_circuits, self.num_povm)),
|
|
115
101
|
) # format used by mGST
|
|
116
102
|
self.bootstrap_results = List[Tuple[np.ndarray]] # List of GST outcomes from bootstrapping
|
|
117
|
-
self.raw_qc_list = List[QuantumCircuit] # List of circuits before transpilation
|
|
118
|
-
self.transpiled_qc_list = List[QuantumCircuit] # List of transpiled circuits to be executed
|
|
119
103
|
|
|
120
104
|
@staticmethod
|
|
121
105
|
def name() -> str:
|
|
122
106
|
return "compressive_GST"
|
|
123
107
|
|
|
124
108
|
@timeit
|
|
125
|
-
def generate_meas_circuits(self) ->
|
|
109
|
+
def generate_meas_circuits(self) -> None:
|
|
126
110
|
"""Generate random circuits from the gate set
|
|
127
111
|
|
|
128
112
|
The random circuits are distributed among different depths ranging from L_MIN
|
|
129
|
-
to L_MAX, both are configurable and stored in self.seq_len_list.
|
|
113
|
+
to L_MAX, both are configurable and stored in self.configuration.seq_len_list.
|
|
130
114
|
No transpilation other than mapping to the desired qubits is performed,
|
|
131
|
-
as the gates need to be executed
|
|
115
|
+
as the gates need to be executed axactly as described for GST to give
|
|
132
116
|
meaningful results
|
|
133
117
|
|
|
134
118
|
Returns:
|
|
@@ -136,11 +120,10 @@ class compressive_GST(BenchmarkBase):
|
|
|
136
120
|
The time it took to generate and transpile the circuits
|
|
137
121
|
|
|
138
122
|
"""
|
|
139
|
-
start_timer_data = perf_counter()
|
|
140
123
|
# Calculate number of short and long circuits
|
|
141
|
-
N_short = int(np.ceil(self.num_circuits / 2))
|
|
142
|
-
N_long = int(np.floor(self.num_circuits / 2))
|
|
143
|
-
L_MIN, L_CUT, L_MAX = self.seq_len_list
|
|
124
|
+
N_short = int(np.ceil(self.configuration.num_circuits / 2))
|
|
125
|
+
N_long = int(np.floor(self.configuration.num_circuits / 2))
|
|
126
|
+
L_MIN, L_CUT, L_MAX = self.configuration.seq_len_list
|
|
144
127
|
|
|
145
128
|
# Generate random circuits
|
|
146
129
|
J = additional_fns.random_seq_design(self.num_gates, L_MIN, L_CUT, L_MAX, N_short, N_long)
|
|
@@ -152,668 +135,79 @@ class compressive_GST(BenchmarkBase):
|
|
|
152
135
|
self.J = np.array(J)[:, ::-1]
|
|
153
136
|
|
|
154
137
|
unmapped_qubits = list(np.arange(self.num_qubits))
|
|
155
|
-
|
|
138
|
+
raw_qc_list = qiskit_interface.get_qiskit_circuits(
|
|
156
139
|
gate_circuits, self.gate_set, self.num_qubits, unmapped_qubits
|
|
157
140
|
)
|
|
158
141
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
# Perform transpilation to backend
|
|
162
|
-
qcvv_logger.info(f"Will transpile all {self.num_circuits} circuits according to fixed physical layout")
|
|
163
|
-
self.transpiled_qc_list, _ = perform_backend_transpilation(
|
|
164
|
-
self.raw_qc_list,
|
|
165
|
-
self.backend,
|
|
166
|
-
self.qubits,
|
|
167
|
-
coupling_map=coupling_map,
|
|
168
|
-
qiskit_optim_level=0,
|
|
169
|
-
optimize_sqg=False,
|
|
170
|
-
drop_final_rz=False,
|
|
171
|
-
)
|
|
172
|
-
circuit_gen_transp_time = perf_counter() - start_timer_data
|
|
173
|
-
|
|
174
|
-
# Saving raw and transpiled circuits in a consistent format with other benchmarks
|
|
175
|
-
self.untranspiled_circuits.update({str(self.qubits): {str(self.seq_len_list[-1]): self.raw_qc_list}})
|
|
176
|
-
self.transpiled_circuits.update({str(self.qubits): {str(self.seq_len_list[-1]): self.transpiled_qc_list}})
|
|
177
|
-
return circuit_gen_transp_time
|
|
178
|
-
|
|
179
|
-
def job_counts_to_mGST_format(self, result_dict):
|
|
180
|
-
"""Turns the dictionary of outcomes obtained from qiskit backend
|
|
181
|
-
into the format which is used in mGST
|
|
182
|
-
|
|
183
|
-
Parameters
|
|
184
|
-
----------
|
|
185
|
-
result_dict: (dict of str: int)
|
|
186
|
-
|
|
187
|
-
Returns
|
|
188
|
-
-------
|
|
189
|
-
y : numpy array
|
|
190
|
-
2D array of measurement outcomes for sequences in J;
|
|
191
|
-
Each column contains the outcome probabilities for a fixed sequence
|
|
192
|
-
|
|
193
|
-
"""
|
|
194
|
-
basis_dict_list = []
|
|
195
|
-
for result in result_dict:
|
|
196
|
-
# Translate dictionary entries of bitstring on the full system to the decimal representation of bitstrings on the active qubits
|
|
197
|
-
basis_dict = {
|
|
198
|
-
entry: int("".join([entry[::-1][i] for i in range(self.num_qubits)][::-1]), 2) for entry in result
|
|
199
|
-
}
|
|
200
|
-
# Sort by index:
|
|
201
|
-
basis_dict = dict(sorted(basis_dict.items(), key=lambda item: item[1]))
|
|
202
|
-
basis_dict_list.append(basis_dict)
|
|
203
|
-
y = []
|
|
204
|
-
for i in range(len(result_dict)):
|
|
205
|
-
row = [result_dict[i][key] for key in basis_dict_list[i]]
|
|
206
|
-
if len(row) < self.num_povm:
|
|
207
|
-
missing_entries = list(np.arange(self.num_povm))
|
|
208
|
-
for given_entry in basis_dict_list[i].values():
|
|
209
|
-
missing_entries.remove(given_entry)
|
|
210
|
-
for missing_entry in missing_entries:
|
|
211
|
-
row.insert(missing_entry, 0) # 0 measurement outcomes in not recorded entry
|
|
212
|
-
y.append(row / np.sum(row))
|
|
213
|
-
y = np.array(y).T
|
|
214
|
-
return y
|
|
215
|
-
|
|
216
|
-
def run_circuits(self):
|
|
217
|
-
"""
|
|
218
|
-
Runs circuits on simulator/hardware and collects counts using utils functions.
|
|
219
|
-
|
|
220
|
-
Returns:
|
|
221
|
-
time_submit: float
|
|
222
|
-
The time it took to submit to jobs.
|
|
223
|
-
time_retrieve: float
|
|
224
|
-
The time it took to retrieve counts.
|
|
225
|
-
"""
|
|
226
|
-
|
|
227
|
-
qcvv_logger.info(f"Now executing the corresponding circuit batch")
|
|
228
|
-
# Submit all circuits to execute
|
|
229
|
-
all_jobs, time_submit = submit_execute(
|
|
230
|
-
{tuple(self.qubits): self.transpiled_qc_list},
|
|
231
|
-
self.backend,
|
|
232
|
-
self.shots,
|
|
233
|
-
self.calset_id,
|
|
234
|
-
max_gates_per_batch=self.max_gates_per_batch,
|
|
235
|
-
)
|
|
236
|
-
|
|
237
|
-
# Retrieve counts - the precise outputs do not matter
|
|
238
|
-
all_counts, time_retrieve = retrieve_all_counts(all_jobs)
|
|
239
|
-
# Save counts - ensures counts were received and can be inspected
|
|
240
|
-
self.raw_data = all_counts
|
|
241
|
-
self.y = self.job_counts_to_mGST_format(self.raw_data)
|
|
242
|
-
# Retrieve and save all job metadata
|
|
243
|
-
all_job_metadata = retrieve_all_job_metadata(all_jobs)
|
|
244
|
-
self.job_meta = all_job_metadata
|
|
245
|
-
|
|
246
|
-
return time_submit, time_retrieve
|
|
247
|
-
|
|
248
|
-
def dataframe_to_figure(self, df, row_labels=None, col_width=2, fontsize=12):
|
|
249
|
-
"""Turns a pandas DataFrame into a figure
|
|
250
|
-
This is needed to conform with the standard file saving routine of QCVV.
|
|
251
|
-
|
|
252
|
-
Args:
|
|
253
|
-
df: Pandas DataFrame
|
|
254
|
-
A dataframe table containing GST results
|
|
255
|
-
row_labels: List[str]
|
|
256
|
-
The row labels for the dataframe
|
|
257
|
-
precision: int
|
|
258
|
-
The number of digits for entries in the dataframe
|
|
259
|
-
col_width: int
|
|
260
|
-
Used to control cell width in the table
|
|
261
|
-
fontsize: int
|
|
262
|
-
Font size of text/numbers in table cells
|
|
263
|
-
|
|
264
|
-
Returns:
|
|
265
|
-
figure: Matplotlib figure object
|
|
266
|
-
A figure representing the dataframe.
|
|
267
|
-
"""
|
|
268
|
-
|
|
269
|
-
if row_labels is None:
|
|
270
|
-
row_labels = np.arange(df.shape[0])
|
|
271
|
-
|
|
272
|
-
row_height = fontsize / 70 * 2
|
|
273
|
-
n_cols = df.shape[1]
|
|
274
|
-
n_rows = df.shape[0]
|
|
275
|
-
figsize = np.array([n_cols + 1, n_rows + 1]) * np.array([col_width, row_height])
|
|
276
|
-
|
|
277
|
-
fig, ax = plt.subplots(figsize=figsize)
|
|
278
|
-
|
|
279
|
-
fig.patch.set_visible(False)
|
|
280
|
-
ax.axis("off")
|
|
281
|
-
ax.axis("tight")
|
|
282
|
-
data_array = (df.to_numpy(dtype="str")).copy()
|
|
283
|
-
column_names = [str(column) for column in df.columns].copy()
|
|
284
|
-
table = ax.table(
|
|
285
|
-
cellText=data_array,
|
|
286
|
-
colLabels=column_names,
|
|
287
|
-
rowLabels=row_labels,
|
|
288
|
-
cellLoc="center",
|
|
289
|
-
colColours=["#7FA1C3" for _ in range(n_cols)],
|
|
290
|
-
bbox=[0, 0, 1, 1],
|
|
291
|
-
)
|
|
292
|
-
table.set_fontsize(fontsize)
|
|
293
|
-
table.set_figure(fig)
|
|
294
|
-
return fig
|
|
142
|
+
for qubits in self.qubit_layouts:
|
|
143
|
+
coupling_map = set_coupling_map(qubits, self.backend, physical_layout="fixed")
|
|
295
144
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
Parametric bootstrapping uses the estimated gate set to create a newly sampled data set.
|
|
300
|
-
Non-parametric bootstrapping uses the initial dataset and resamples according to the
|
|
301
|
-
corresp. outcome probabilities.
|
|
302
|
-
Each bootstrap run is initialized with the estimated gate set in order to save processing time.
|
|
303
|
-
|
|
304
|
-
Parameters
|
|
305
|
-
----------
|
|
306
|
-
K : numpy array
|
|
307
|
-
Each subarray along the first axis contains a set of Kraus operators.
|
|
308
|
-
The second axis enumerates Kraus operators for a gate specified by the first axis.
|
|
309
|
-
X : 3D numpy array
|
|
310
|
-
Array where reconstructed CPT superoperators in standard basis are stacked along the first axis.
|
|
311
|
-
E : numpy array
|
|
312
|
-
Current POVM estimate
|
|
313
|
-
rho : numpy array
|
|
314
|
-
Current initial state estimate
|
|
315
|
-
target_mdl : pygsti model object
|
|
316
|
-
The target gate set
|
|
317
|
-
parametric : bool
|
|
318
|
-
If set to True, parametric bootstrapping is used, else non-parametric bootstrapping. Default: False
|
|
319
|
-
|
|
320
|
-
Returns
|
|
321
|
-
-------
|
|
322
|
-
X_array : numpy array
|
|
323
|
-
Array containing all estimated gate tensors of different bootstrapping repetitions along first axis
|
|
324
|
-
E_array : numpy array
|
|
325
|
-
Array containing all estimated POVM tensors of different bootstrapping repetitions along first axis
|
|
326
|
-
rho_array : numpy array
|
|
327
|
-
Array containing all estimated initial states of different bootstrapping repetitions along first axis
|
|
328
|
-
df_g_array : numpy array
|
|
329
|
-
Contains gate quality measures of bootstrapping repetitions
|
|
330
|
-
df_o_array : numpy array
|
|
331
|
-
Contains SPAM and other quality measures of bootstrapping repetitions
|
|
332
|
-
|
|
333
|
-
"""
|
|
334
|
-
if parametric:
|
|
335
|
-
y = np.real(np.array([[E[i].conj() @ contract(X, j) @ rho for j in self.J] for i in range(self.num_povm)]))
|
|
336
|
-
else:
|
|
337
|
-
y = self.y
|
|
338
|
-
X_array = np.zeros((self.bootstrap_samples, *X.shape)).astype(complex)
|
|
339
|
-
E_array = np.zeros((self.bootstrap_samples, *E.shape)).astype(complex)
|
|
340
|
-
rho_array = np.zeros((self.bootstrap_samples, *rho.shape)).astype(complex)
|
|
341
|
-
df_g_list = []
|
|
342
|
-
df_o_list = []
|
|
343
|
-
|
|
344
|
-
for i in range(self.bootstrap_samples):
|
|
345
|
-
y_sampled = additional_fns.sampled_measurements(y, self.shots).copy()
|
|
346
|
-
_, X_, E_, rho_, _ = algorithm.run_mGST(
|
|
347
|
-
y_sampled,
|
|
348
|
-
self.J,
|
|
349
|
-
self.seq_len_list[-1],
|
|
350
|
-
self.num_gates,
|
|
351
|
-
self.pdim**2,
|
|
352
|
-
self.rank,
|
|
353
|
-
self.num_povm,
|
|
354
|
-
self.batch_size,
|
|
355
|
-
self.shots,
|
|
356
|
-
method=self.opt_method,
|
|
357
|
-
max_inits=self.max_inits,
|
|
358
|
-
max_iter=0,
|
|
359
|
-
final_iter=self.max_iterations[1],
|
|
360
|
-
threshold_multiplier=self.convergence_criteria[0],
|
|
361
|
-
target_rel_prec=self.convergence_criteria[1],
|
|
362
|
-
init=[K, E, rho],
|
|
363
|
-
testing=False,
|
|
145
|
+
# Perform transpilation to backend
|
|
146
|
+
qcvv_logger.info(
|
|
147
|
+
f"Will transpile all {self.configuration.num_circuits} circuits according to fixed physical layout"
|
|
364
148
|
)
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
X_array[i] = X_opt_pp
|
|
374
|
-
E_array[i] = E_opt_pp
|
|
375
|
-
rho_array[i] = rho_opt_pp
|
|
376
|
-
|
|
377
|
-
return (X_array, E_array, rho_array, np.array(df_g_list), np.array(df_o_list))
|
|
378
|
-
|
|
379
|
-
def generate_non_gate_results(self, df_o):
|
|
380
|
-
"""
|
|
381
|
-
Creates error bars (if bootstrapping was used) and formats results for non-gate errors.
|
|
382
|
-
The resulting tables are also turned into figures, so that they can be saved automatically.
|
|
383
|
-
|
|
384
|
-
Args:
|
|
385
|
-
df_o: Pandas DataFrame
|
|
386
|
-
A dataframe containing the non-gate quality metrics (SPAM errors and fit quality)
|
|
387
|
-
|
|
388
|
-
Returns:
|
|
389
|
-
df_o_final: Oandas DataFrame
|
|
390
|
-
The final formatted results
|
|
391
|
-
"""
|
|
392
|
-
# filename = f"{self.results_dir}{self.device_id}_{self.timestamp}_spam_errors.html"
|
|
393
|
-
if self.bootstrap_samples > 0:
|
|
394
|
-
_, _, _, _, df_o_array = self.bootstrap_results
|
|
395
|
-
df_o_array[df_o_array == -1] = np.nan
|
|
396
|
-
percentiles_o_low, percentiles_o_high = np.nanpercentile(df_o_array, [2.5, 97.5], axis=0)
|
|
397
|
-
df_o_final = DataFrame(
|
|
398
|
-
{
|
|
399
|
-
f"Mean TVD: estimate - data": reporting.number_to_str(
|
|
400
|
-
df_o.values[0, 1].copy(), [percentiles_o_high[0, 1], percentiles_o_low[0, 1]], precision=5
|
|
401
|
-
),
|
|
402
|
-
f"Mean TVD: target - data": reporting.number_to_str(
|
|
403
|
-
df_o.values[0, 2].copy(), [percentiles_o_high[0, 2], percentiles_o_low[0, 2]], precision=5
|
|
404
|
-
),
|
|
405
|
-
f"POVM - diamond dist.": reporting.number_to_str(
|
|
406
|
-
df_o.values[0, 3].copy(), [percentiles_o_high[0, 3], percentiles_o_low[0, 3]], precision=5
|
|
407
|
-
),
|
|
408
|
-
f"State - trace dist.": reporting.number_to_str(
|
|
409
|
-
df_o.values[0, 4].copy(), [percentiles_o_high[0, 4], percentiles_o_low[0, 4]], precision=5
|
|
410
|
-
),
|
|
411
|
-
},
|
|
412
|
-
index=[""],
|
|
149
|
+
transpiled_qc_list, _ = perform_backend_transpilation(
|
|
150
|
+
raw_qc_list,
|
|
151
|
+
self.backend,
|
|
152
|
+
qubits,
|
|
153
|
+
coupling_map=coupling_map,
|
|
154
|
+
qiskit_optim_level=0,
|
|
155
|
+
optimize_sqg=False,
|
|
156
|
+
drop_final_rz=False,
|
|
413
157
|
)
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
f"Mean TVD: estimate - data": reporting.number_to_str(df_o.values[0, 1].copy(), precision=5),
|
|
418
|
-
f"Mean TVD: target - data": reporting.number_to_str(df_o.values[0, 2].copy(), precision=5),
|
|
419
|
-
f"POVM - diamond dist.": reporting.number_to_str(df_o.values[0, 3].copy(), precision=5),
|
|
420
|
-
f"State - trace dist.": reporting.number_to_str(df_o.values[0, 4].copy(), precision=5),
|
|
421
|
-
},
|
|
422
|
-
index=[""],
|
|
423
|
-
)
|
|
424
|
-
# fig = self.dataframe_to_figure(df_o_final, [""])#self.dataframe_to_figure(df_o_final, [""])
|
|
425
|
-
# self.figures["spam_and_outcome_errors"] = fig
|
|
426
|
-
return df_o_final
|
|
158
|
+
# Saving raw and transpiled circuits in a consistent format with other benchmarks
|
|
159
|
+
self.untranspiled_circuits.update({str(qubits): raw_qc_list})
|
|
160
|
+
self.transpiled_circuits.update({str(qubits): transpiled_qc_list})
|
|
427
161
|
|
|
428
|
-
def
|
|
162
|
+
def add_configuration_to_dataset(self, dataset): # CHECK
|
|
429
163
|
"""
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
This includes parameters of the Hamiltonian generators in the Pauli basis for all gates,
|
|
433
|
-
as well as the usual performance metrics (Fidelities and Diamond distances). If bootstrapping
|
|
434
|
-
data is available, error bars will also be generated.
|
|
164
|
+
Creates an xarray.Dataset and adds the circuits and configuration metadata to it
|
|
435
165
|
|
|
436
166
|
Args:
|
|
437
|
-
|
|
438
|
-
The dataframe with properly formatted results
|
|
439
|
-
X_opt: 3D numpy array
|
|
440
|
-
The gate set after gauge optimization
|
|
441
|
-
K_target: 4D numpy array
|
|
442
|
-
The Kraus operators of all target gates, used to compute distance measures.
|
|
443
|
-
|
|
167
|
+
self: Source class
|
|
444
168
|
Returns:
|
|
445
|
-
|
|
446
|
-
The dataframe with properly formatted results of standard gate errors
|
|
447
|
-
df_g_rotation Pandas DataFrame
|
|
448
|
-
A dataframe containing Hamiltonian (rotation) parameters
|
|
449
|
-
|
|
169
|
+
dataset: xarray.Dataset to be used for further data storage
|
|
450
170
|
"""
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
df_g.values[i, 0], [percentiles_g_high[i, 0], percentiles_g_low[i, 0]], precision=5
|
|
461
|
-
)
|
|
462
|
-
for i in range(len(self.gate_labels))
|
|
463
|
-
],
|
|
464
|
-
r"Diamond distance": [
|
|
465
|
-
reporting.number_to_str(
|
|
466
|
-
df_g.values[i, 1], [percentiles_g_high[i, 1], percentiles_g_low[i, 1]], precision=5
|
|
467
|
-
)
|
|
468
|
-
for i in range(self.num_gates)
|
|
469
|
-
],
|
|
470
|
-
}
|
|
471
|
-
)
|
|
472
|
-
|
|
473
|
-
U_opt = reporting.phase_opt(X_opt, K_target)
|
|
474
|
-
pauli_coeffs = reporting.compute_sparsest_Pauli_Hamiltonian(U_opt)
|
|
475
|
-
|
|
476
|
-
bootstrap_pauli_coeffs = np.zeros((len(X_array), self.num_gates, self.pdim**2))
|
|
477
|
-
for i, X_ in enumerate(X_array):
|
|
478
|
-
X_std, _, _ = compatibility.pp2std(X_, E_array[i], rho_array[i])
|
|
479
|
-
U_opt_ = reporting.phase_opt(
|
|
480
|
-
np.array([change_basis(X_std[j], "pp", "std") for j in range(self.num_gates)]), K_target
|
|
481
|
-
)
|
|
482
|
-
pauli_coeffs_ = reporting.compute_sparsest_Pauli_Hamiltonian(U_opt_)
|
|
483
|
-
bootstrap_pauli_coeffs[i, :, :] = pauli_coeffs_
|
|
484
|
-
pauli_coeffs_low, pauli_coeffs_high = np.nanpercentile(bootstrap_pauli_coeffs, [2.5, 97.5], axis=0)
|
|
485
|
-
|
|
486
|
-
df_g_rotation = DataFrame(
|
|
487
|
-
np.array(
|
|
488
|
-
[
|
|
489
|
-
[
|
|
490
|
-
reporting.number_to_str(
|
|
491
|
-
pauli_coeffs[i, j], [pauli_coeffs_high[i, j], pauli_coeffs_low[i, j]], precision=5
|
|
492
|
-
)
|
|
493
|
-
for i in range(self.num_gates)
|
|
494
|
-
]
|
|
495
|
-
for j in range(self.pdim**2)
|
|
496
|
-
]
|
|
497
|
-
).T
|
|
498
|
-
)
|
|
499
|
-
|
|
500
|
-
df_g_rotation.columns = [f"h_%s" % label for label in self.Pauli_labels]
|
|
501
|
-
df_g_rotation.rename(index=self.gate_labels, inplace=True)
|
|
502
|
-
|
|
503
|
-
else:
|
|
504
|
-
df_g_final = DataFrame(
|
|
505
|
-
{
|
|
506
|
-
"Avg. gate fidelity": [
|
|
507
|
-
reporting.number_to_str(df_g.values[i, 0], precision=5) for i in range(self.num_gates)
|
|
508
|
-
],
|
|
509
|
-
"Diamond distance": [
|
|
510
|
-
reporting.number_to_str(df_g.values[i, 1], precision=5) for i in range(self.num_gates)
|
|
511
|
-
],
|
|
512
|
-
}
|
|
513
|
-
)
|
|
514
|
-
U_opt = reporting.phase_opt(X_opt, K_target)
|
|
515
|
-
pauli_coeffs = reporting.compute_sparsest_Pauli_Hamiltonian(U_opt)
|
|
516
|
-
|
|
517
|
-
df_g_rotation = DataFrame(
|
|
518
|
-
np.array(
|
|
519
|
-
[
|
|
520
|
-
[reporting.number_to_str(pauli_coeffs[i, j], precision=5) for i in range(self.num_gates)]
|
|
521
|
-
for j in range(self.pdim**2)
|
|
522
|
-
]
|
|
523
|
-
).T
|
|
524
|
-
)
|
|
525
|
-
df_g_rotation.columns = [f"h_%s" % label for label in self.Pauli_labels]
|
|
526
|
-
df_g_final.rename(index=self.gate_labels, inplace=True)
|
|
527
|
-
|
|
528
|
-
# fig_g = self.dataframe_to_figure(df_g_final, self.gate_labels)
|
|
529
|
-
# fig_rotation = self.dataframe_to_figure(df_g_rotation, self.gate_labels)
|
|
530
|
-
# self.figures["gate_errors"] = fig_g
|
|
531
|
-
# self.figures["hamiltonian_parameters"] = fig_rotation
|
|
532
|
-
return df_g_final, df_g_rotation
|
|
533
|
-
|
|
534
|
-
def generate_gate_results(self, df_g, X_opt, E_opt, rho_opt, max_evals=6):
|
|
535
|
-
"""
|
|
536
|
-
Produces all result tables for arbitrary Kraus rank estimates and turns them into figures.
|
|
537
|
-
|
|
538
|
-
Args:
|
|
539
|
-
df_g: Pandas DataFrame
|
|
540
|
-
The dataframe with properly formatted results
|
|
541
|
-
X_opt: 3D numpy array
|
|
542
|
-
The gate set after gauge optimization
|
|
543
|
-
E_opt: 3D numpy array
|
|
544
|
-
An array containg all the POVM elements as matrices after gauge optimization
|
|
545
|
-
rho_opt: 2D numpy array
|
|
546
|
-
The density matrix after gauge optmization
|
|
547
|
-
max_evals: int
|
|
548
|
-
The maximum number of eigenvalues of the Choi matrices which are returned.
|
|
549
|
-
|
|
550
|
-
Returns:
|
|
551
|
-
df_g_final: Pandas DataFrame
|
|
552
|
-
The dataframe with properly formatted results of standard gate errors
|
|
553
|
-
df_g_evals Pandas DataFrame
|
|
554
|
-
A dataframe containing eigenvalues of the Choi matrices for each gate
|
|
555
|
-
|
|
556
|
-
"""
|
|
557
|
-
n_evals = np.min([max_evals, self.pdim**2])
|
|
558
|
-
X_opt_pp, _, _ = compatibility.std2pp(X_opt, E_opt, rho_opt)
|
|
559
|
-
df_g_evals = reporting.generate_Choi_EV_table(X_opt, n_evals, self.gate_labels).copy()
|
|
560
|
-
|
|
561
|
-
if self.bootstrap_samples > 0:
|
|
562
|
-
X_array, E_array, rho_array, df_g_array, _ = self.bootstrap_results
|
|
563
|
-
df_g_array[df_g_array == -1] = np.nan
|
|
564
|
-
percentiles_g_low, percentiles_g_high = np.nanpercentile(df_g_array, [2.5, 97.5], axis=0)
|
|
565
|
-
bootstrap_unitarities = np.array([reporting.unitarities(X_array[i]) for i in range(self.bootstrap_samples)])
|
|
566
|
-
percentiles_u_low, percentiles_u_high = np.nanpercentile(bootstrap_unitarities, [2.5, 97.5], axis=0)
|
|
567
|
-
X_array_std = [
|
|
568
|
-
compatibility.pp2std(X_array[i], E_array[i], rho_array[i])[0] for i in range(self.bootstrap_samples)
|
|
569
|
-
]
|
|
570
|
-
bootstrap_evals = np.array(
|
|
571
|
-
[
|
|
572
|
-
reporting.generate_Choi_EV_table(X_array_std[i], n_evals, self.gate_labels)
|
|
573
|
-
for i in range(self.bootstrap_samples)
|
|
574
|
-
]
|
|
575
|
-
)
|
|
576
|
-
percentiles_evals_low, percentiles_evals_high = np.nanpercentile(bootstrap_evals, [2.5, 97.5], axis=0)
|
|
577
|
-
eval_strs = [
|
|
578
|
-
[
|
|
579
|
-
reporting.number_to_str(
|
|
580
|
-
df_g_evals.values[i, j],
|
|
581
|
-
[percentiles_evals_high[i, j], percentiles_evals_low[i, j]],
|
|
582
|
-
precision=5,
|
|
583
|
-
)
|
|
584
|
-
for i in range(self.num_gates)
|
|
585
|
-
]
|
|
586
|
-
for j in range(n_evals)
|
|
587
|
-
]
|
|
588
|
-
|
|
589
|
-
df_g_final = DataFrame(
|
|
590
|
-
{
|
|
591
|
-
r"Avg. gate fidelity": [
|
|
592
|
-
reporting.number_to_str(
|
|
593
|
-
df_g.values[i, 0], [percentiles_g_high[i, 0], percentiles_g_low[i, 0]], precision=5
|
|
594
|
-
)
|
|
595
|
-
for i in range(self.num_gates)
|
|
596
|
-
],
|
|
597
|
-
r"Diamond distance": [
|
|
598
|
-
reporting.number_to_str(
|
|
599
|
-
df_g.values[i, 1], [percentiles_g_high[i, 1], percentiles_g_low[i, 1]], precision=5
|
|
600
|
-
)
|
|
601
|
-
for i in range(self.num_gates)
|
|
602
|
-
],
|
|
603
|
-
r"Unitarity": [
|
|
604
|
-
reporting.number_to_str(
|
|
605
|
-
reporting.unitarities(X_opt_pp)[i],
|
|
606
|
-
[percentiles_u_high[i], percentiles_u_low[i]],
|
|
607
|
-
precision=5,
|
|
608
|
-
)
|
|
609
|
-
for i in range(self.num_gates)
|
|
610
|
-
],
|
|
611
|
-
}
|
|
612
|
-
)
|
|
613
|
-
|
|
614
|
-
else:
|
|
615
|
-
df_g_final = DataFrame(
|
|
616
|
-
{
|
|
617
|
-
"Avg. gate fidelity": [
|
|
618
|
-
reporting.number_to_str(df_g.values[i, 0].copy(), precision=5)
|
|
619
|
-
for i in range(len(self.gate_labels))
|
|
620
|
-
],
|
|
621
|
-
"Diamond distance": [
|
|
622
|
-
reporting.number_to_str(df_g.values[i, 1].copy(), precision=5)
|
|
623
|
-
for i in range(len(self.gate_labels))
|
|
624
|
-
],
|
|
625
|
-
"Unitarity": [
|
|
626
|
-
reporting.number_to_str(reporting.unitarities(X_opt_pp)[i], precision=5)
|
|
627
|
-
for i in range(len(self.gate_labels))
|
|
628
|
-
],
|
|
629
|
-
# "Entanglement fidelity to depol. channel": [reporting.number_to_str(reporting.eff_depol_params(X_opt_pp)[i], precision=5)
|
|
630
|
-
# for i in range(len(gate_labels))],
|
|
631
|
-
# "Min. spectral distances": [number_to_str(df_g.values[i, 2], precision=5) for i in range(len(gate_labels))]
|
|
632
|
-
}
|
|
633
|
-
)
|
|
634
|
-
eval_strs = [
|
|
635
|
-
[reporting.number_to_str(df_g_evals.values[i, j].copy(), precision=5) for i in range(self.num_gates)]
|
|
636
|
-
for j in range(n_evals)
|
|
637
|
-
]
|
|
638
|
-
|
|
639
|
-
df_g_evals_final = DataFrame(eval_strs).T
|
|
640
|
-
df_g_evals_final.rename(index=self.gate_labels, inplace=True)
|
|
641
|
-
|
|
642
|
-
# fig_g = self.dataframe_to_figure(df_g_final, self.gate_labels)
|
|
643
|
-
# fig_choi = self.dataframe_to_figure(df_g_evals_final, self.gate_labels)
|
|
644
|
-
# self.figures["gate_errors"] = fig_g
|
|
645
|
-
# self.figures["choi_evals"] = fig_choi
|
|
646
|
-
return df_g_final, df_g_evals_final
|
|
171
|
+
# Adding configuration entries and class variables, prioritizing the latter in case of conflicts
|
|
172
|
+
for key, value in (self.configuration.__dict__ | self.__dict__).items():
|
|
173
|
+
if key == "benchmark": # Avoid saving the class objects
|
|
174
|
+
dataset.attrs[key] = value.name()
|
|
175
|
+
elif key == "backend":
|
|
176
|
+
dataset.attrs[key] = value.name
|
|
177
|
+
else:
|
|
178
|
+
dataset.attrs[key] = value
|
|
179
|
+
dataset.attrs["gauge_weights"] = dict({f"G%i" % i: 1 for i in range(self.num_gates)}, **{"spam": 0.1})
|
|
647
180
|
|
|
648
|
-
|
|
649
|
-
def execute_full_benchmark(self):
|
|
181
|
+
def execute(self, backend) -> xr.Dataset:
|
|
650
182
|
"""
|
|
651
183
|
The main GST execution routine
|
|
652
|
-
|
|
653
|
-
The following steps are executed:
|
|
654
|
-
1) Generation of circuits to measure
|
|
655
|
-
2) Running of said circuits on the backend
|
|
656
|
-
3) Conversion of the ideal gate set into numpy arrays to be processed by mGST
|
|
657
|
-
4) Optional generation of an initialization for mGST
|
|
658
|
-
5) Running mGST to get first gate set estimates
|
|
659
|
-
6) Gauge optimization to the target gates using pyGSTi"s gauge optimizer
|
|
660
|
-
7) Generation of error measures and result tables
|
|
661
|
-
8) Generation of matrix plots for the reconstructed gate set
|
|
662
184
|
"""
|
|
663
185
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
#
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
)
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
[
|
|
687
|
-
np.kron(additional_fns.basis(self.pdim, i).T.conj(), additional_fns.basis(self.pdim, i)).reshape(-1)
|
|
688
|
-
for i in range(self.pdim)
|
|
689
|
-
]
|
|
690
|
-
).astype(np.complex128)
|
|
691
|
-
target_mdl = compatibility.arrays_to_pygsti_model(X_target, E_target, rho_target, basis="std")
|
|
692
|
-
|
|
693
|
-
# Run mGST
|
|
694
|
-
if self.from_init:
|
|
695
|
-
K_init = additional_fns.perturbed_target_init(X_target, self.rank)
|
|
696
|
-
init_params = [K_init, E_target, rho_target]
|
|
697
|
-
else:
|
|
698
|
-
init_params = None
|
|
699
|
-
|
|
700
|
-
K, X, E, rho, _ = algorithm.run_mGST(
|
|
701
|
-
self.y,
|
|
702
|
-
self.J,
|
|
703
|
-
self.seq_len_list[-1],
|
|
704
|
-
self.num_gates,
|
|
705
|
-
self.pdim**2,
|
|
706
|
-
self.rank,
|
|
707
|
-
self.num_povm,
|
|
708
|
-
self.batch_size,
|
|
709
|
-
self.shots,
|
|
710
|
-
method=self.opt_method,
|
|
711
|
-
max_inits=self.max_inits,
|
|
712
|
-
max_iter=self.max_iterations[0],
|
|
713
|
-
final_iter=self.max_iterations[1],
|
|
714
|
-
threshold_multiplier=self.convergence_criteria[0],
|
|
715
|
-
target_rel_prec=self.convergence_criteria[1],
|
|
716
|
-
init=init_params,
|
|
717
|
-
testing=False,
|
|
718
|
-
)
|
|
719
|
-
|
|
720
|
-
main_mGST_time = perf_counter() - start_timer_GST
|
|
721
|
-
start_timer_gauge = perf_counter()
|
|
722
|
-
|
|
723
|
-
# Gauge optimization
|
|
724
|
-
X_opt, E_opt, rho_opt = reporting.gauge_opt(X, E, rho, target_mdl, self.weights)
|
|
725
|
-
gauge_optimization_time = perf_counter() - start_timer_gauge
|
|
726
|
-
|
|
727
|
-
# Quick report
|
|
728
|
-
start_timer_report = perf_counter()
|
|
729
|
-
df_g, df_o = reporting.quick_report(X_opt, E_opt, rho_opt, self.J, self.y, target_mdl, self.gate_labels)
|
|
730
|
-
report_gen_time = perf_counter() - start_timer_report
|
|
731
|
-
|
|
732
|
-
### Bootstrap
|
|
733
|
-
if self.bootstrap_samples > 0:
|
|
734
|
-
self.bootstrap_results = self.bootstrap_errors(K, X, E, rho, target_mdl)
|
|
735
|
-
|
|
736
|
-
_, df_o_full = reporting.report(X_opt, E_opt, rho_opt, self.J, self.y, target_mdl, self.gate_labels)
|
|
737
|
-
df_o_final = self.generate_non_gate_results(df_o_full)
|
|
738
|
-
|
|
739
|
-
### Result table generation
|
|
740
|
-
if self.rank == 1:
|
|
741
|
-
df_g_final, df_g_rotation = self.generate_unit_rank_gate_results(df_g, X_opt, K_target)
|
|
742
|
-
self.results.update({"hamiltonian_parameters": df_g_rotation.to_dict()})
|
|
743
|
-
else:
|
|
744
|
-
df_g_final, df_g_evals = self.generate_gate_results(df_g, X_opt, E_opt, rho_opt)
|
|
745
|
-
self.results.update({"choi_evals": df_g_evals.to_dict()})
|
|
746
|
-
|
|
747
|
-
# Saving results
|
|
748
|
-
self.results.update(
|
|
749
|
-
{
|
|
750
|
-
"qubit_set": self.qubits,
|
|
751
|
-
"quick_metrics": {"Gates": df_g.to_dict(), "Outcomes and SPAM": df_o.to_dict()},
|
|
752
|
-
"full_metrics": {"Gates": df_g_final.to_dict(), "Outcomes and SPAM": df_o_final.to_dict()},
|
|
753
|
-
"main_mGST_time": main_mGST_time,
|
|
754
|
-
"submit_time": submit_time,
|
|
755
|
-
"retrieve_time": retrieve_time,
|
|
756
|
-
"gauge_optimization_time": gauge_optimization_time,
|
|
757
|
-
"circuit_generation_time": circuit_gen_transp_time,
|
|
758
|
-
}
|
|
759
|
-
)
|
|
760
|
-
|
|
761
|
-
self.raw_results.update(
|
|
762
|
-
{
|
|
763
|
-
"raw_Kraus_operators": K,
|
|
764
|
-
"raw_gates": X,
|
|
765
|
-
"raw_POVM": E.reshape((self.num_povm, self.pdim, self.pdim)),
|
|
766
|
-
"raw_state": rho.reshape((self.pdim, self.pdim)),
|
|
767
|
-
"gauge_opt_gates": X_opt,
|
|
768
|
-
"gauge_opt_POVM": E_opt.reshape((self.num_povm, self.pdim, self.pdim)),
|
|
769
|
-
"gauge_opt_state": rho_opt.reshape((self.pdim, self.pdim)),
|
|
770
|
-
"mGST_circuits": self.J,
|
|
771
|
-
"mGST_outcome_probs": self.y,
|
|
772
|
-
}
|
|
773
|
-
)
|
|
774
|
-
|
|
775
|
-
### Process matrix plots
|
|
776
|
-
X_opt_pp, _, _ = compatibility.std2pp(X_opt, E_opt, rho_opt)
|
|
777
|
-
X_target_pp, _, _ = compatibility.std2pp(X_target, E_target, rho_target)
|
|
778
|
-
|
|
779
|
-
figures = figure_gen.generate_gate_err_pdf(
|
|
780
|
-
"", X_opt_pp, X_target_pp, basis_labels=self.Pauli_labels, gate_labels=self.gate_labels, return_fig=True
|
|
781
|
-
)
|
|
782
|
-
for i, figure in enumerate(figures):
|
|
783
|
-
self.figures[f"process_mat_plot_%i" % i] = figure
|
|
784
|
-
|
|
785
|
-
self.figures["SPAM_matrices_real"] = figure_gen.generate_spam_err_std_pdf(
|
|
786
|
-
"",
|
|
787
|
-
E_opt.real,
|
|
788
|
-
rho_opt.real,
|
|
789
|
-
E_target.real,
|
|
790
|
-
rho_target.real,
|
|
791
|
-
basis_labels=self.std_labels,
|
|
792
|
-
title=f"Real part of state and measurement effects in the standard basis",
|
|
793
|
-
return_fig=True,
|
|
794
|
-
)
|
|
795
|
-
self.figures["SPAM_matrices_imag"] = figure_gen.generate_spam_err_std_pdf(
|
|
796
|
-
"",
|
|
797
|
-
E_opt.imag,
|
|
798
|
-
rho_opt.imag,
|
|
799
|
-
E_target.imag,
|
|
800
|
-
rho_target.imag,
|
|
801
|
-
basis_labels=self.std_labels,
|
|
802
|
-
title=f"Imaginary part of state and measurement effects in the standard basis",
|
|
803
|
-
return_fig=True,
|
|
804
|
-
)
|
|
805
|
-
# plt.show()
|
|
806
|
-
plt.close("all")
|
|
186
|
+
dataset = xr.Dataset()
|
|
187
|
+
qcvv_logger.info(f"Now generating {self.configuration.num_circuits} random GST circuits...")
|
|
188
|
+
|
|
189
|
+
# Generate circuits
|
|
190
|
+
self.generate_meas_circuits()
|
|
191
|
+
|
|
192
|
+
# Submit all
|
|
193
|
+
all_jobs: Dict = {}
|
|
194
|
+
for qubit_layout in self.qubit_layouts:
|
|
195
|
+
transpiled_circuit_dict = {tuple(qubit_layout): self.transpiled_circuits[str(qubit_layout)]}
|
|
196
|
+
all_jobs[str(qubit_layout)], _ = submit_execute(
|
|
197
|
+
transpiled_circuit_dict,
|
|
198
|
+
backend,
|
|
199
|
+
self.configuration.shots,
|
|
200
|
+
self.calset_id,
|
|
201
|
+
max_gates_per_batch=self.configuration.max_gates_per_batch,
|
|
202
|
+
)
|
|
203
|
+
# Retrieve all
|
|
204
|
+
qcvv_logger.info(f"Now executing the corresponding circuit batch")
|
|
205
|
+
for qubit_layout in self.qubit_layouts:
|
|
206
|
+
counts, _ = retrieve_all_counts(all_jobs[str(qubit_layout)])
|
|
207
|
+
dataset, _ = add_counts_to_dataset(counts, str(qubit_layout), dataset)
|
|
807
208
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
qcvv_logger.info(
|
|
811
|
-
f"Results for {self.backend.name} with qubits "
|
|
812
|
-
f"{self.qubits}\nTotal GST post-processing time: {total_pp_time / 60:.2f} min\nOverview results: \n"
|
|
813
|
-
+ df_g.to_string()
|
|
814
|
-
+ f"\n"
|
|
815
|
-
+ df_o.to_string()
|
|
816
|
-
)
|
|
209
|
+
self.add_configuration_to_dataset(dataset)
|
|
210
|
+
return dataset
|
|
817
211
|
|
|
818
212
|
|
|
819
213
|
class GSTConfiguration(BenchmarkConfigurationBase):
|
|
@@ -871,36 +265,66 @@ class GSTConfiguration(BenchmarkConfigurationBase):
|
|
|
871
265
|
the uncertainty via bootstrapping.
|
|
872
266
|
"""
|
|
873
267
|
|
|
874
|
-
benchmark: Type[
|
|
875
|
-
|
|
876
|
-
gate_set: Union[str, List[
|
|
268
|
+
benchmark: Type[Benchmark] = CompressiveGST
|
|
269
|
+
qubit_layouts: Union[List[int], List[List[int]]]
|
|
270
|
+
gate_set: Union[str, List[Any]]
|
|
877
271
|
num_circuits: int
|
|
878
|
-
shots: int
|
|
879
272
|
rank: int
|
|
880
|
-
|
|
881
|
-
|
|
273
|
+
shots: int = 2**10
|
|
274
|
+
gate_labels: Union[List, None] = None
|
|
275
|
+
seq_len_list: list[int] = [1, 8, 14]
|
|
882
276
|
from_init: bool = True
|
|
883
|
-
max_inits: int =
|
|
277
|
+
max_inits: int = 20
|
|
884
278
|
opt_method: str = "auto"
|
|
885
279
|
max_iterations: Union[str, List[int]] = "auto"
|
|
886
|
-
convergence_criteria: List[float] = [4, 1e-4]
|
|
280
|
+
convergence_criteria: Union[str, List[float]] = [4, 1e-4]
|
|
887
281
|
batch_size: Union[str, int] = "auto"
|
|
888
282
|
bootstrap_samples: int = 0
|
|
889
283
|
|
|
890
284
|
|
|
891
|
-
def
|
|
285
|
+
def parse_layouts(qubit_layouts: Union[List[int], List[List[int]]]) -> List[List[int]]:
|
|
286
|
+
"""Checks for correct setting of qubit_layouts in the configuration and return a correct type
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
qubit_layouts: List[List[[int]] or List[int]
|
|
290
|
+
The qubit_layouts on the backend where the gates are defined on
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
qubit_layouts: List[List[[int]]
|
|
294
|
+
A properly typed qubit_layout if no Error was raised
|
|
295
|
+
"""
|
|
296
|
+
if all(isinstance(qubits, int) for qubits in qubit_layouts):
|
|
297
|
+
return [qubit_layouts] # type: ignore
|
|
298
|
+
if all(isinstance(qubits, List) for qubits in qubit_layouts):
|
|
299
|
+
if all(len(x) == len(qubit_layouts[0]) for x in qubit_layouts): # type: ignore
|
|
300
|
+
return qubit_layouts # type: ignore
|
|
301
|
+
raise ValueError(
|
|
302
|
+
"Qubit layouts are of different size, currently only specifying "
|
|
303
|
+
"qubit layouts with the same number of qubits is supported."
|
|
304
|
+
)
|
|
305
|
+
raise ValueError(
|
|
306
|
+
"The qubit_layouts parameters needs to be one of List[int] for a single layout"
|
|
307
|
+
" or List[List[int]] for multiple layouts."
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def parse_gate_set(
|
|
312
|
+
configuration: GSTConfiguration, num_qubits
|
|
313
|
+
) -> Tuple[List[QuantumCircuit], Dict[str, Dict[int, str]], int]:
|
|
892
314
|
"""
|
|
893
315
|
Handles different gate set inputs and produces a valid gate set
|
|
894
316
|
|
|
895
317
|
Args:
|
|
896
318
|
configuration: BenchmarkConfigurationBase
|
|
897
319
|
Configuration class containing variables
|
|
320
|
+
num_qubits: int
|
|
321
|
+
The number of qubits on which the gate set is defined
|
|
898
322
|
|
|
899
323
|
Returns:
|
|
900
324
|
gate_set: List[QuantumCircuit]
|
|
901
325
|
A list of gates defined as quantum circuit objects
|
|
902
|
-
gate_labels: Dict[int
|
|
903
|
-
A dictionary with gate names
|
|
326
|
+
gate_labels: List[Dict[int, str]]
|
|
327
|
+
A dictionary with gate names for each layout
|
|
904
328
|
num_gates: int
|
|
905
329
|
The number of gates in the gate set
|
|
906
330
|
|
|
@@ -916,8 +340,7 @@ def parse_gate_set(configuration):
|
|
|
916
340
|
"1QXYI, 2QXYCZ, 2QXYCZ_extended, 3QXYCZ."
|
|
917
341
|
)
|
|
918
342
|
if configuration.gate_set in ["1QXYI", "2QXYCZ", "2QXYCZ_extended", "3QXYCZ"]:
|
|
919
|
-
gate_set, gate_labels = create_predefined_gate_set(configuration.gate_set,
|
|
920
|
-
num_gates = len(gate_set)
|
|
343
|
+
gate_set, gate_labels, num_gates = create_predefined_gate_set(configuration.gate_set, num_qubits)
|
|
921
344
|
return gate_set, gate_labels, num_gates
|
|
922
345
|
|
|
923
346
|
if isinstance(configuration.gate_set, list):
|
|
@@ -932,7 +355,7 @@ def parse_gate_set(configuration):
|
|
|
932
355
|
f"The number of gate labels (%i) does not match the number of gates (%i)"
|
|
933
356
|
% (len(configuration.gate_labels), num_gates)
|
|
934
357
|
)
|
|
935
|
-
gate_labels = configuration.gate_labels
|
|
358
|
+
gate_labels = dict(enumerate(configuration.gate_labels))
|
|
936
359
|
return gate_set, gate_labels, num_gates
|
|
937
360
|
|
|
938
361
|
raise ValueError(
|
|
@@ -941,25 +364,23 @@ def parse_gate_set(configuration):
|
|
|
941
364
|
)
|
|
942
365
|
|
|
943
366
|
|
|
944
|
-
def create_predefined_gate_set(gate_set,
|
|
367
|
+
def create_predefined_gate_set(gate_set, num_qubits) -> Tuple[List[QuantumCircuit], Dict, int]:
|
|
945
368
|
"""Create a list of quantum circuits corresponding to a predefined gate set.
|
|
946
369
|
|
|
947
|
-
The circuits are assigned to the specified
|
|
370
|
+
The circuits are assigned to the specified qubit_layouts on the backend only during transipilation, so the qubit labels
|
|
948
371
|
at this stage may not represent the actual qubit labels on the backend.
|
|
949
372
|
Args:
|
|
950
373
|
gate_set: str
|
|
951
374
|
The name of the gate set
|
|
952
|
-
qubits: List[int]
|
|
953
|
-
The qubits on the backend where the gates are defined on
|
|
954
375
|
Returns:
|
|
955
376
|
gates: List[QuantumCircuit]
|
|
956
377
|
The gate set as a list of circuits
|
|
957
|
-
gate_labels_dict: Dict[str]
|
|
378
|
+
gate_labels_dict: Dict[int, str]
|
|
958
379
|
The names of gates, i.e. "Rx(pi/2)" for a pi/2 rotation around the x-axis.
|
|
380
|
+
num_gates: int
|
|
381
|
+
The number of gates in the gate set
|
|
959
382
|
|
|
960
383
|
"""
|
|
961
|
-
num_qubits = len(qubits)
|
|
962
|
-
qubit_mapping = dict(enumerate(qubits))
|
|
963
384
|
unmapped_qubits = list(np.arange(num_qubits))
|
|
964
385
|
|
|
965
386
|
if gate_set == "1QXYI":
|
|
@@ -1021,9 +442,15 @@ def create_predefined_gate_set(gate_set, qubits) -> Tuple[List[QuantumCircuit],
|
|
|
1021
442
|
for i, gate in enumerate(gate_list):
|
|
1022
443
|
gates[i].append(gate, gate_qubits[i])
|
|
1023
444
|
gate_labels = ["Rx(pi/2)", "Rx(pi/2)", "Rx(pi/2)", "Ry(pi/2)", "Ry(pi/2)", "Ry(pi/2)", "CZ", "CZ"]
|
|
445
|
+
else:
|
|
446
|
+
raise ValueError(
|
|
447
|
+
f"Invalid gate set, choose among 1QXYI, 2QXYCZ, 2QXYCZ_extended,"
|
|
448
|
+
f" 3QXYCZ or provide a list of Qiskti circuits to define the gates."
|
|
449
|
+
)
|
|
450
|
+
|
|
1024
451
|
gates = add_idle_gates(gates, unmapped_qubits, gate_qubits)
|
|
1025
452
|
gates = [remove_idle_wires(qc) for qc in gates]
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
return gates, gate_label_dict
|
|
453
|
+
|
|
454
|
+
gate_label_dict = dict(enumerate(gate_labels))
|
|
455
|
+
|
|
456
|
+
return gates, gate_label_dict, len(gates)
|