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
|
@@ -0,0 +1,1029 @@
|
|
|
1
|
+
# Copyright 2024 IQM Benchmarks developers
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
Compressive gate set tomography
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from itertools import product
|
|
20
|
+
from time import perf_counter, strftime
|
|
21
|
+
from typing import Dict, List, Tuple, Type, Union
|
|
22
|
+
|
|
23
|
+
import matplotlib.pyplot as plt
|
|
24
|
+
import numpy as np
|
|
25
|
+
from pandas import DataFrame
|
|
26
|
+
from pygsti.tools import change_basis
|
|
27
|
+
from qiskit import QuantumCircuit
|
|
28
|
+
from qiskit.circuit.library import CZGate, RGate
|
|
29
|
+
|
|
30
|
+
from iqm.benchmarks.benchmark import BenchmarkBase, BenchmarkConfigurationBase
|
|
31
|
+
from iqm.benchmarks.logging_config import qcvv_logger
|
|
32
|
+
from iqm.benchmarks.utils import (
|
|
33
|
+
perform_backend_transpilation,
|
|
34
|
+
retrieve_all_counts,
|
|
35
|
+
retrieve_all_job_metadata,
|
|
36
|
+
set_coupling_map,
|
|
37
|
+
submit_execute,
|
|
38
|
+
timeit,
|
|
39
|
+
)
|
|
40
|
+
from iqm.qiskit_iqm.iqm_backend import IQMBackendBase
|
|
41
|
+
from mGST import additional_fns, algorithm, compatibility, qiskit_interface
|
|
42
|
+
from mGST.low_level_jit import contract
|
|
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
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class compressive_GST(BenchmarkBase):
|
|
51
|
+
"""
|
|
52
|
+
SPAM-robust characterization of a set of quantum gates
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, backend: IQMBackendBase, configuration: "GSTConfiguration"):
|
|
56
|
+
"""Construct the compressive_gst class.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
backend (IQMBackendBase): the backend to execute the benchmark on
|
|
60
|
+
configuration (GSTConfiguration): the configuration of the benchmark
|
|
61
|
+
"""
|
|
62
|
+
super().__init__(backend, configuration)
|
|
63
|
+
|
|
64
|
+
self.qubits = configuration.qubits
|
|
65
|
+
self.num_qubits = len(self.qubits)
|
|
66
|
+
self.pdim = 2**self.num_qubits
|
|
67
|
+
self.num_povm = self.pdim
|
|
68
|
+
self.gate_set, self.gate_labels, self.num_gates = parse_gate_set(configuration)
|
|
69
|
+
self.seq_len_list = configuration.seq_len_list
|
|
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")
|
|
80
|
+
|
|
81
|
+
if configuration.opt_method not in ["GD", "SFN", "auto"]:
|
|
82
|
+
raise ValueError("Invalid optimization method, valid options are: GD, SFN, auto")
|
|
83
|
+
if configuration.opt_method == "auto":
|
|
84
|
+
if (self.num_qubits == 2 and self.rank > 2) or self.num_qubits > 2:
|
|
85
|
+
self.opt_method = "GD"
|
|
86
|
+
else:
|
|
87
|
+
self.opt_method = "SFN"
|
|
88
|
+
else:
|
|
89
|
+
self.opt_method = configuration.opt_method
|
|
90
|
+
|
|
91
|
+
if configuration.max_iterations == "auto":
|
|
92
|
+
if self.opt_method == "SFN":
|
|
93
|
+
self.max_iterations = [100, 100]
|
|
94
|
+
if self.opt_method == "GD":
|
|
95
|
+
self.max_iterations = [250, 250]
|
|
96
|
+
elif isinstance(configuration.max_iterations, list):
|
|
97
|
+
self.max_iterations = configuration.max_iterations
|
|
98
|
+
if configuration.batch_size == "auto":
|
|
99
|
+
self.batch_size = 30 * self.pdim
|
|
100
|
+
else:
|
|
101
|
+
self.batch_size = configuration.batch_size
|
|
102
|
+
|
|
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
|
+
self.y, self.J = (
|
|
113
|
+
np.empty((self.num_povm, self.num_circuits)),
|
|
114
|
+
np.empty((self.num_circuits, self.num_povm)),
|
|
115
|
+
) # format used by mGST
|
|
116
|
+
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
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def name() -> str:
|
|
122
|
+
return "compressive_GST"
|
|
123
|
+
|
|
124
|
+
@timeit
|
|
125
|
+
def generate_meas_circuits(self) -> float:
|
|
126
|
+
"""Generate random circuits from the gate set
|
|
127
|
+
|
|
128
|
+
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.
|
|
130
|
+
No transpilation other than mapping to the desired qubits is performed,
|
|
131
|
+
as the gates need to be executed exactly as described for GST to give
|
|
132
|
+
meaningful results
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
circuit_gen_transp_time: float
|
|
136
|
+
The time it took to generate and transpile the circuits
|
|
137
|
+
|
|
138
|
+
"""
|
|
139
|
+
start_timer_data = perf_counter()
|
|
140
|
+
# 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
|
|
144
|
+
|
|
145
|
+
# Generate random circuits
|
|
146
|
+
J = additional_fns.random_seq_design(self.num_gates, L_MIN, L_CUT, L_MAX, N_short, N_long)
|
|
147
|
+
|
|
148
|
+
# Reduce circuits to exclude negative placeholder values
|
|
149
|
+
gate_circuits = [list(seq[seq >= 0]) for seq in J]
|
|
150
|
+
|
|
151
|
+
# Sequences in GST are applied to the state from right to left
|
|
152
|
+
self.J = np.array(J)[:, ::-1]
|
|
153
|
+
|
|
154
|
+
unmapped_qubits = list(np.arange(self.num_qubits))
|
|
155
|
+
self.raw_qc_list = qiskit_interface.get_qiskit_circuits(
|
|
156
|
+
gate_circuits, self.gate_set, self.num_qubits, unmapped_qubits
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
coupling_map = set_coupling_map(self.qubits, self.backend, physical_layout="fixed")
|
|
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
|
|
295
|
+
|
|
296
|
+
def bootstrap_errors(self, K, X, E, rho, target_mdl, parametric=False):
|
|
297
|
+
"""Resamples circuit outcomes a number of times and computes GST estimates for each repetition
|
|
298
|
+
All results are then returned in order to compute bootstrap-error bars for GST estimates.
|
|
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,
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
X_opt, E_opt, rho_opt = reporting.gauge_opt(X_, E_, rho_, target_mdl, self.weights)
|
|
367
|
+
df_g, df_o = reporting.report(X_opt, E_opt, rho_opt, self.J, y_sampled, target_mdl, self.gate_labels)
|
|
368
|
+
df_g_list.append(df_g.values)
|
|
369
|
+
df_o_list.append(df_o.values)
|
|
370
|
+
|
|
371
|
+
X_opt_pp, E_opt_pp, rho_opt_pp = compatibility.std2pp(X_opt, E_opt, rho_opt)
|
|
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=[""],
|
|
413
|
+
)
|
|
414
|
+
else:
|
|
415
|
+
df_o_final = DataFrame(
|
|
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
|
|
427
|
+
|
|
428
|
+
def generate_unit_rank_gate_results(self, df_g, X_opt, K_target):
|
|
429
|
+
"""
|
|
430
|
+
Produces all result tables for Kraus rank 1 estimates and turns them into figures.
|
|
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.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
df_g: Pandas DataFrame
|
|
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
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
df_g_final: Pandas DataFrame
|
|
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
|
+
|
|
450
|
+
"""
|
|
451
|
+
if self.bootstrap_samples > 0:
|
|
452
|
+
X_array, E_array, rho_array, df_g_array, _ = self.bootstrap_results
|
|
453
|
+
df_g_array[df_g_array == -1] = np.nan
|
|
454
|
+
percentiles_g_low, percentiles_g_high = np.nanpercentile(df_g_array, [2.5, 97.5], axis=0)
|
|
455
|
+
|
|
456
|
+
df_g_final = DataFrame(
|
|
457
|
+
{
|
|
458
|
+
r"Avg. gate fidelity": [
|
|
459
|
+
reporting.number_to_str(
|
|
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
|
|
647
|
+
|
|
648
|
+
@timeit
|
|
649
|
+
def execute_full_benchmark(self):
|
|
650
|
+
"""
|
|
651
|
+
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
|
+
"""
|
|
663
|
+
|
|
664
|
+
qcvv_logger.info(f"Now generating {self.num_circuits} random circuits on qubits {self.qubits}")
|
|
665
|
+
|
|
666
|
+
# Generate and run circuits on backend
|
|
667
|
+
circuit_gen_transp_time = self.generate_meas_circuits()
|
|
668
|
+
|
|
669
|
+
submit_time, retrieve_time = self.run_circuits()
|
|
670
|
+
# data_collection_time = submit_time + retrieve_time
|
|
671
|
+
|
|
672
|
+
start_timer_GST = perf_counter()
|
|
673
|
+
K_target = qiskit_gate_to_operator(self.gate_set)
|
|
674
|
+
X_target = np.einsum("ijkl,ijnm -> iknlm", K_target, K_target.conj()).reshape(
|
|
675
|
+
(self.num_gates, self.pdim**2, self.pdim**2)
|
|
676
|
+
) # tensor of superoperators
|
|
677
|
+
|
|
678
|
+
rho_target = (
|
|
679
|
+
np.kron(additional_fns.basis(self.pdim, 0).T.conj(), additional_fns.basis(self.pdim, 0))
|
|
680
|
+
.reshape(-1)
|
|
681
|
+
.astype(np.complex128)
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
# Computational basis measurement:
|
|
685
|
+
E_target = np.array(
|
|
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")
|
|
807
|
+
|
|
808
|
+
## Result display
|
|
809
|
+
total_pp_time = main_mGST_time + gauge_optimization_time + report_gen_time
|
|
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
|
+
)
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
class GSTConfiguration(BenchmarkConfigurationBase):
|
|
820
|
+
"""Compressive GST configuration base.
|
|
821
|
+
|
|
822
|
+
Attributes:
|
|
823
|
+
benchmark (Type[Benchmark]): GHZBenchmark
|
|
824
|
+
qubit_layouts (Sequence[Sequence[int]]): A sequence (e.g., Tuple or List) of sequences of
|
|
825
|
+
physical qubit layouts, as specified by integer labels, where the benchmark is meant to be run.
|
|
826
|
+
gate_set (Union[str, List[Type[QuantumCircuit]]]): The gate set in given either as a list of qiskit quantum
|
|
827
|
+
cirucuits, or as one of the predefined gate sets "1QXYI", "2QXYCZ", "2QXYCZ_extended", "3QXYCZ".
|
|
828
|
+
A gate set should be tomographically complete, meaning from the specified gates and the vacuum state, one
|
|
829
|
+
should be able to prepare states that form a frame for the state space. A practical sufficient condition
|
|
830
|
+
is that the gate set is able the generate all combinations of local Pauli eigenstates.
|
|
831
|
+
num_circuits (int): How many random circuits are generated from the gate set. Guidelines on choosing this value:
|
|
832
|
+
- At least 50 circuits for a single qubit gate set with 3 gates.
|
|
833
|
+
- At least 400 circuits for a two qubit gate set with 6 gates.
|
|
834
|
+
- At least 2000 circuits for a three qubit gate set with 9 gates.
|
|
835
|
+
The number of random circuits needed is expected to grow linearly in the number of gates and exponentially
|
|
836
|
+
in the number of qubits.
|
|
837
|
+
rank (int): The Kraus rank of the reconstruction. Choose rank=1 for coherent error analysis and rank<=dim**2
|
|
838
|
+
generally.
|
|
839
|
+
shots (int): The number of measurement shots per circuit.
|
|
840
|
+
* Default: 1024
|
|
841
|
+
gate_labels (Union[Dict, None]): Names of the gates in the gate set. Used for plots and result tables.
|
|
842
|
+
* Default: None
|
|
843
|
+
seq_len_list (list[int]): Three numbers controling the depth of the random sequences. The first is the minimal
|
|
844
|
+
depth, the last is the maximal depth and the middle number specifies a cutoff depth below which all possible
|
|
845
|
+
sequences are selected.
|
|
846
|
+
* Default: [1, 8, 14]
|
|
847
|
+
from_init (bool): Whether the target gate set is used as an initialization to the mGST algorithm.
|
|
848
|
+
* Default: True
|
|
849
|
+
max_inits (int): If from_init = False, random initial points are tried and this parameter limits the amount of
|
|
850
|
+
retries.
|
|
851
|
+
* Default: 20
|
|
852
|
+
opt_method (str): Which optimization method is used, can be either of "GD" or "SFN", for gradient descent or
|
|
853
|
+
saddle free Newton, respectively.
|
|
854
|
+
* Default: "auto" (Method is automatically selected based on qubit count and rank)
|
|
855
|
+
max_iterations (Union[str, List[int]]): How many iterations to run the optimization algorithm for. Accepted
|
|
856
|
+
values are either "auto" or a list of two integers. The first specifies the number of iterations for the
|
|
857
|
+
optimization on batches or circuit data, while the second specifies the number of iterations on the full
|
|
858
|
+
data for all circuits.
|
|
859
|
+
* Default: "auto" (Numbers are automatically selected based on qubit count and rank)
|
|
860
|
+
convergence_criteria (Union[str, List[float]]): Two parameters which determine when the optimization algorithm
|
|
861
|
+
terminates. The first is a multiplier which specifies how close the cost function should get to a threshold
|
|
862
|
+
for the given shot noise in order for the optimization to be considered "successful". Values in [2,10] are
|
|
863
|
+
usually sensible. The second parameter sets the relative change in cost function between
|
|
864
|
+
two consecutive iterations, below which the algorithm terminates.
|
|
865
|
+
* Default: [4, 1e-4]
|
|
866
|
+
batch_size (Union[str, int]): The number of circuits per batch in the optimization. This hyperparamters is
|
|
867
|
+
automatically set and determines the convergence behaviour. A smaller batch size reduces runtime but can
|
|
868
|
+
lead to erratic jumps in parameter space and lack of convergence.
|
|
869
|
+
* Default: "auto"
|
|
870
|
+
bootstrap_samples (int): The number of times the optimization algorithm is repeated on fake data to estimate
|
|
871
|
+
the uncertainty via bootstrapping.
|
|
872
|
+
"""
|
|
873
|
+
|
|
874
|
+
benchmark: Type[BenchmarkBase] = compressive_GST
|
|
875
|
+
qubits: List[Union[int, List[int]]]
|
|
876
|
+
gate_set: Union[str, List[Type[QuantumCircuit]]]
|
|
877
|
+
num_circuits: int
|
|
878
|
+
shots: int
|
|
879
|
+
rank: int
|
|
880
|
+
gate_labels: Union[Dict, None] = None
|
|
881
|
+
seq_len_list: list = [1, 8, 14]
|
|
882
|
+
from_init: bool = True
|
|
883
|
+
max_inits: int = 10
|
|
884
|
+
opt_method: str = "auto"
|
|
885
|
+
max_iterations: Union[str, List[int]] = "auto"
|
|
886
|
+
convergence_criteria: List[float] = [4, 1e-4]
|
|
887
|
+
batch_size: Union[str, int] = "auto"
|
|
888
|
+
bootstrap_samples: int = 0
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
def parse_gate_set(configuration):
|
|
892
|
+
"""
|
|
893
|
+
Handles different gate set inputs and produces a valid gate set
|
|
894
|
+
|
|
895
|
+
Args:
|
|
896
|
+
configuration: BenchmarkConfigurationBase
|
|
897
|
+
Configuration class containing variables
|
|
898
|
+
|
|
899
|
+
Returns:
|
|
900
|
+
gate_set: List[QuantumCircuit]
|
|
901
|
+
A list of gates defined as quantum circuit objects
|
|
902
|
+
gate_labels: Dict[int: str]
|
|
903
|
+
A dictionary with gate names
|
|
904
|
+
num_gates: int
|
|
905
|
+
The number of gates in the gate set
|
|
906
|
+
|
|
907
|
+
"""
|
|
908
|
+
if isinstance(configuration.gate_set, str) and configuration.gate_set not in [
|
|
909
|
+
"1QXYI",
|
|
910
|
+
"2QXYCZ",
|
|
911
|
+
"2QXYCZ_extended",
|
|
912
|
+
"3QXYCZ",
|
|
913
|
+
]:
|
|
914
|
+
raise ValueError(
|
|
915
|
+
"No gate set of the specified name is implemented, please choose among "
|
|
916
|
+
"1QXYI, 2QXYCZ, 2QXYCZ_extended, 3QXYCZ."
|
|
917
|
+
)
|
|
918
|
+
if configuration.gate_set in ["1QXYI", "2QXYCZ", "2QXYCZ_extended", "3QXYCZ"]:
|
|
919
|
+
gate_set, gate_labels = create_predefined_gate_set(configuration.gate_set, configuration.qubits)
|
|
920
|
+
num_gates = len(gate_set)
|
|
921
|
+
return gate_set, gate_labels, num_gates
|
|
922
|
+
|
|
923
|
+
if isinstance(configuration.gate_set, list):
|
|
924
|
+
gate_set = configuration.gate_set
|
|
925
|
+
num_gates = len(gate_set)
|
|
926
|
+
if configuration.gate_labels is None:
|
|
927
|
+
gate_labels = {i: f"Gate %i" % i for i in range(num_gates)}
|
|
928
|
+
else:
|
|
929
|
+
if configuration.gate_labels:
|
|
930
|
+
if len(configuration.gate_labels) != num_gates:
|
|
931
|
+
raise ValueError(
|
|
932
|
+
f"The number of gate labels (%i) does not match the number of gates (%i)"
|
|
933
|
+
% (len(configuration.gate_labels), num_gates)
|
|
934
|
+
)
|
|
935
|
+
gate_labels = configuration.gate_labels
|
|
936
|
+
return gate_set, gate_labels, num_gates
|
|
937
|
+
|
|
938
|
+
raise ValueError(
|
|
939
|
+
f"Invalid gate set, choose among 1QXYI, 2QXYCZ, 2QXYCZ_extended,"
|
|
940
|
+
f" 3QXYCZ or provide a list of Qiskti circuits to define the gates."
|
|
941
|
+
)
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
def create_predefined_gate_set(gate_set, qubits) -> Tuple[List[QuantumCircuit], Dict]:
|
|
945
|
+
"""Create a list of quantum circuits corresponding to a predefined gate set.
|
|
946
|
+
|
|
947
|
+
The circuits are assigned to the specified qubits on the backend only during transipilation, so the qubit labels
|
|
948
|
+
at this stage may not represent the actual qubit labels on the backend.
|
|
949
|
+
Args:
|
|
950
|
+
gate_set: str
|
|
951
|
+
The name of the gate set
|
|
952
|
+
qubits: List[int]
|
|
953
|
+
The qubits on the backend where the gates are defined on
|
|
954
|
+
Returns:
|
|
955
|
+
gates: List[QuantumCircuit]
|
|
956
|
+
The gate set as a list of circuits
|
|
957
|
+
gate_labels_dict: Dict[str]
|
|
958
|
+
The names of gates, i.e. "Rx(pi/2)" for a pi/2 rotation around the x-axis.
|
|
959
|
+
|
|
960
|
+
"""
|
|
961
|
+
num_qubits = len(qubits)
|
|
962
|
+
qubit_mapping = dict(enumerate(qubits))
|
|
963
|
+
unmapped_qubits = list(np.arange(num_qubits))
|
|
964
|
+
|
|
965
|
+
if gate_set == "1QXYI":
|
|
966
|
+
gate_list = [RGate(1e-10, 0), RGate(0.5 * np.pi, 0), RGate(0.5 * np.pi, np.pi / 2)]
|
|
967
|
+
gates = [QuantumCircuit(num_qubits, 0) for _ in range(len(gate_list))]
|
|
968
|
+
gate_qubits = [[0], [0], [0]]
|
|
969
|
+
for i, gate in enumerate(gate_list):
|
|
970
|
+
gates[i].append(gate, gate_qubits[i])
|
|
971
|
+
gate_labels = ["Idle", "Rx(pi/2)", "Ry(pi/2)"]
|
|
972
|
+
elif gate_set == "2QXYCZ":
|
|
973
|
+
gate_qubits = [[0], [1], [0], [1], [0, 1]]
|
|
974
|
+
gates = [QuantumCircuit(num_qubits, 0) for _ in range(5)]
|
|
975
|
+
gates[0].append(RGate(0.5 * np.pi, 0), [0])
|
|
976
|
+
gates[1].append(RGate(0.5 * np.pi, 0), [1])
|
|
977
|
+
gates[2].append(RGate(0.5 * np.pi, np.pi / 2), [0])
|
|
978
|
+
gates[3].append(RGate(0.5 * np.pi, np.pi / 2), [1])
|
|
979
|
+
gates[4].append(CZGate(), [0, 1])
|
|
980
|
+
gate_labels = ["Rx(pi/2)", "Rx(pi/2)", "Ry(pi/2)", "Ry(pi/2)", "CZ"]
|
|
981
|
+
elif gate_set == "2QXYCZ_extended":
|
|
982
|
+
gate_qubits = [[0], [1], [0], [1], [0, 1], [0, 1], [0, 1], [0, 1], [0, 1]]
|
|
983
|
+
gates = [QuantumCircuit(num_qubits, 0) for _ in range(9)]
|
|
984
|
+
gates[0].append(RGate(0.5 * np.pi, 0), [0])
|
|
985
|
+
gates[1].append(RGate(0.5 * np.pi, 0), [1])
|
|
986
|
+
gates[2].append(RGate(0.5 * np.pi, np.pi / 2), [0])
|
|
987
|
+
gates[3].append(RGate(0.5 * np.pi, np.pi / 2), [1])
|
|
988
|
+
gates[4].append(RGate(0.5 * np.pi, 0), [0])
|
|
989
|
+
gates[4].append(RGate(0.5 * np.pi, 0), [1])
|
|
990
|
+
gates[5].append(RGate(0.5 * np.pi, 0), [0])
|
|
991
|
+
gates[5].append(RGate(0.5 * np.pi, np.pi / 2), [1])
|
|
992
|
+
gates[6].append(RGate(0.5 * np.pi, np.pi / 2), [0])
|
|
993
|
+
gates[6].append(RGate(0.5 * np.pi, 0), [1])
|
|
994
|
+
gates[7].append(RGate(0.5 * np.pi, np.pi / 2), [0])
|
|
995
|
+
gates[7].append(RGate(0.5 * np.pi, np.pi / 2), [1])
|
|
996
|
+
gates[8].append(CZGate(), [[0], [1]])
|
|
997
|
+
gate_labels = [
|
|
998
|
+
"Rx(pi/2)",
|
|
999
|
+
"Rx(pi/2)",
|
|
1000
|
+
"Ry(pi/2)",
|
|
1001
|
+
"Ry(pi/2)",
|
|
1002
|
+
"Rx(pi/2)--Rx(pi/2)",
|
|
1003
|
+
"Rx(pi/2)--Ry(pi/2)",
|
|
1004
|
+
"Ry(pi/2)--Rx(pi/2)",
|
|
1005
|
+
"Ry(pi/2)--Ry(pi/2)",
|
|
1006
|
+
"CZ",
|
|
1007
|
+
]
|
|
1008
|
+
elif gate_set == "3QXYCZ":
|
|
1009
|
+
gate_list = [
|
|
1010
|
+
RGate(0.5 * np.pi, 0),
|
|
1011
|
+
RGate(0.5 * np.pi, 0),
|
|
1012
|
+
RGate(0.5 * np.pi, 0),
|
|
1013
|
+
RGate(0.5 * np.pi, np.pi / 2),
|
|
1014
|
+
RGate(0.5 * np.pi, np.pi / 2),
|
|
1015
|
+
RGate(0.5 * np.pi, np.pi / 2),
|
|
1016
|
+
CZGate(),
|
|
1017
|
+
CZGate(),
|
|
1018
|
+
]
|
|
1019
|
+
gates = [QuantumCircuit(num_qubits, 0) for _ in range(len(gate_list))]
|
|
1020
|
+
gate_qubits = [[0], [1], [2], [0], [1], [2], [0, 1], [0, 2]]
|
|
1021
|
+
for i, gate in enumerate(gate_list):
|
|
1022
|
+
gates[i].append(gate, gate_qubits[i])
|
|
1023
|
+
gate_labels = ["Rx(pi/2)", "Rx(pi/2)", "Rx(pi/2)", "Ry(pi/2)", "Ry(pi/2)", "Ry(pi/2)", "CZ", "CZ"]
|
|
1024
|
+
gates = add_idle_gates(gates, unmapped_qubits, gate_qubits)
|
|
1025
|
+
gates = [remove_idle_wires(qc) for qc in gates]
|
|
1026
|
+
gate_qubits_mapped = [[qubit_mapping[i] for i in qlist] for qlist in gate_qubits]
|
|
1027
|
+
gate_labels = [gate_labels[i] + ":" + str(gate_qubits_mapped[i]) for i in range(len(gates))]
|
|
1028
|
+
gate_label_dict = {i: gate_labels[i] for i in range(len(gate_labels))}
|
|
1029
|
+
return gates, gate_label_dict
|