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,810 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mirror Randomized Benchmarking.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
import random
|
|
7
|
+
from time import strftime
|
|
8
|
+
from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, cast
|
|
9
|
+
import warnings
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
from qiskit import QuantumCircuit, transpile
|
|
13
|
+
from qiskit.quantum_info import random_clifford, random_pauli
|
|
14
|
+
from qiskit_aer import Aer
|
|
15
|
+
from scipy.spatial.distance import hamming
|
|
16
|
+
import xarray as xr
|
|
17
|
+
|
|
18
|
+
from iqm.benchmarks import AnalysisResult, Benchmark, RunResult
|
|
19
|
+
from iqm.benchmarks.benchmark import BenchmarkConfigurationBase
|
|
20
|
+
from iqm.benchmarks.benchmark_definition import add_counts_to_dataset
|
|
21
|
+
from iqm.benchmarks.logging_config import qcvv_logger
|
|
22
|
+
from iqm.benchmarks.randomized_benchmarking.randomized_benchmarking_common import (
|
|
23
|
+
exponential_rb,
|
|
24
|
+
fit_decay_lmfit,
|
|
25
|
+
lmfit_minimizer,
|
|
26
|
+
plot_rb_decay,
|
|
27
|
+
validate_irb_gate,
|
|
28
|
+
)
|
|
29
|
+
from iqm.benchmarks.utils import (
|
|
30
|
+
get_iqm_backend,
|
|
31
|
+
retrieve_all_counts,
|
|
32
|
+
retrieve_all_job_metadata,
|
|
33
|
+
submit_execute,
|
|
34
|
+
timeit,
|
|
35
|
+
xrvariable_to_counts,
|
|
36
|
+
)
|
|
37
|
+
from iqm.qiskit_iqm.iqm_backend import IQMBackendBase
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def compute_polarizations(
|
|
41
|
+
num_qubits: int,
|
|
42
|
+
noisy_counts: List[Dict[str, int]],
|
|
43
|
+
ideal_counts: List[Dict[str, int]],
|
|
44
|
+
num_circ_samples: int,
|
|
45
|
+
num_pauli_samples: int,
|
|
46
|
+
) -> List[float]:
|
|
47
|
+
"""Estimates the polarization for a list of noisy counts with respect to corresponding ideal counts
|
|
48
|
+
The polarization here is a rescaling of the average fidelity, that corrects for few-qubit effects
|
|
49
|
+
|
|
50
|
+
Arguments:
|
|
51
|
+
num_qubits (int): the number of qubits being benchmarked
|
|
52
|
+
noisy_counts (List[Dict[str, int]]): the list of counts coming from real execution
|
|
53
|
+
ideal_counts (List[Dict[str, int]]): the list of counts coming from simulated, ideal execution
|
|
54
|
+
num_circ_samples (int): the number circuit of samples used to estimate the polarization
|
|
55
|
+
num_pauli_samples (int): the number of pauli samples per circuit sample used to estimate the polarization
|
|
56
|
+
Returns:
|
|
57
|
+
List[float]: the polarizations for each circuit sample of the given sequence length
|
|
58
|
+
"""
|
|
59
|
+
ideal_counts_matrix = list_to_numcircuit_times_numpauli_matrix(ideal_counts, num_circ_samples, num_pauli_samples)
|
|
60
|
+
noisy_counts_matrix = list_to_numcircuit_times_numpauli_matrix(noisy_counts, num_circ_samples, num_pauli_samples)
|
|
61
|
+
|
|
62
|
+
polarizations: List = []
|
|
63
|
+
for ideal_m, noisy_m in zip(ideal_counts_matrix, noisy_counts_matrix):
|
|
64
|
+
polarization_pauli = []
|
|
65
|
+
for ideal_pauli, noisy_pauli in zip(ideal_m, noisy_m):
|
|
66
|
+
ideal_bitstrings = list(ideal_pauli.keys())
|
|
67
|
+
noisy_bitstrings = list(noisy_pauli.keys())
|
|
68
|
+
|
|
69
|
+
# Get the counts for each Hamming distance
|
|
70
|
+
hamming_distances = {i: 0 for i in range(num_qubits + 1)}
|
|
71
|
+
for s_n in noisy_bitstrings:
|
|
72
|
+
for s_i in ideal_bitstrings:
|
|
73
|
+
k_index = int(hamming(list(s_n), list(s_i)) * num_qubits)
|
|
74
|
+
hamming_distances[k_index] += noisy_pauli[s_n]
|
|
75
|
+
|
|
76
|
+
# Compute polarizations
|
|
77
|
+
no_shots = sum(noisy_pauli.values())
|
|
78
|
+
weighted_hamming = sum(((-1 / 2) ** k) * (hamming_distances[k] / no_shots) for k in range(num_qubits + 1))
|
|
79
|
+
polarization_c = ((4**num_qubits) * weighted_hamming - 1) / (4**num_qubits - 1)
|
|
80
|
+
polarization_pauli.append(polarization_c)
|
|
81
|
+
|
|
82
|
+
# Average over the Pauli samples and add the circuit sample
|
|
83
|
+
polarizations.append(np.mean(polarization_pauli))
|
|
84
|
+
|
|
85
|
+
return polarizations
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# TODO: Let edge_grab also admit a 1Q gate ensemble! Currently uniform Clifford by default # pylint: disable=fixme
|
|
89
|
+
# pylint: disable=too-many-branches
|
|
90
|
+
def edge_grab(
|
|
91
|
+
qubit_set: List[int],
|
|
92
|
+
n_layers: int,
|
|
93
|
+
backend_arg: IQMBackendBase | str,
|
|
94
|
+
density_2q_gates: float = 0.25,
|
|
95
|
+
two_qubit_gate_ensemble: Optional[Dict[str, float]] = None,
|
|
96
|
+
) -> List[QuantumCircuit]:
|
|
97
|
+
"""Generate a list of random layers containing single-qubit Cliffords and two-qubit gates,
|
|
98
|
+
sampled according to the edge-grab algorithm (see arXiv:2204.07568 [quant-ph]).
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
qubit_set (List[int]): The set of qubits of the backend.
|
|
102
|
+
n_layers (int): The number of layers.
|
|
103
|
+
backend_arg (IQMBackendBase | str): IQM backend.
|
|
104
|
+
density_2q_gates (float): The expected density of 2Q gates in a circuit formed by subsequent application of layers
|
|
105
|
+
two_qubit_gate_ensemble (Dict[str, float]): A dictionary with keys being str specifying 2Q gates, and values being corresponding probabilities
|
|
106
|
+
Raises:
|
|
107
|
+
ValueError: if the probabilities in the gate ensembles do not add up to unity.
|
|
108
|
+
Returns:
|
|
109
|
+
List[QuantumCircuit]: the list of gate layers, in the form of quantum circuits.
|
|
110
|
+
"""
|
|
111
|
+
# Check the ensemble of 2Q gates, otherwise assign
|
|
112
|
+
if two_qubit_gate_ensemble is None:
|
|
113
|
+
two_qubit_gate_ensemble = cast(Dict[str, float], {"CZGate": 1.0})
|
|
114
|
+
elif sum(two_qubit_gate_ensemble.values()) != 1.0:
|
|
115
|
+
raise ValueError("The 2Q gate ensemble probabilities must sum to 1.0")
|
|
116
|
+
|
|
117
|
+
# Validate 2Q gates and get circuits
|
|
118
|
+
two_qubit_circuits = {}
|
|
119
|
+
for k in two_qubit_gate_ensemble.keys():
|
|
120
|
+
two_qubit_circuits[k] = validate_irb_gate(k, backend_arg, gate_params=None)
|
|
121
|
+
# TODO: Admit parametrized 2Q gates! # pylint: disable=fixme
|
|
122
|
+
|
|
123
|
+
# Check backend and retrieve if necessary
|
|
124
|
+
if isinstance(backend_arg, str):
|
|
125
|
+
backend = get_iqm_backend(backend_arg)
|
|
126
|
+
else:
|
|
127
|
+
backend = backend_arg
|
|
128
|
+
|
|
129
|
+
# Definitions
|
|
130
|
+
num_qubits = len(qubit_set)
|
|
131
|
+
physical_to_virtual_map = {q: i for i, q in enumerate(qubit_set)}
|
|
132
|
+
|
|
133
|
+
# Get the possible edges where to place 2Q gates given the backend connectivity
|
|
134
|
+
twoq_edges = []
|
|
135
|
+
for i, q0 in enumerate(qubit_set):
|
|
136
|
+
for q1 in qubit_set[i + 1 :]:
|
|
137
|
+
if (q0, q1) in list(backend.coupling_map):
|
|
138
|
+
twoq_edges.append([q0, q1])
|
|
139
|
+
twoq_edges = list(sorted(twoq_edges))
|
|
140
|
+
|
|
141
|
+
# Generate the layers
|
|
142
|
+
layer_list = []
|
|
143
|
+
for _ in range(n_layers):
|
|
144
|
+
# Pick edges at random and store them in a new list "edge_list"
|
|
145
|
+
aux = deepcopy(twoq_edges)
|
|
146
|
+
edge_list = []
|
|
147
|
+
layer = QuantumCircuit(num_qubits)
|
|
148
|
+
# Take (and remove) edges from "aux", then add to "edge_list"
|
|
149
|
+
edge_qubits = []
|
|
150
|
+
while aux:
|
|
151
|
+
new_edge = random.choice(aux)
|
|
152
|
+
edge_list.append(new_edge)
|
|
153
|
+
edge_qubits = list(np.array(edge_list).flatten())
|
|
154
|
+
# Removes all edges which include either of the qubits in new_edge
|
|
155
|
+
aux = [e for e in aux if ((new_edge[0] not in e) and (new_edge[1] not in e))]
|
|
156
|
+
|
|
157
|
+
# Define the probability for adding 2Q gates, given the input density
|
|
158
|
+
if len(edge_list) != 0:
|
|
159
|
+
prob_2qgate = num_qubits * density_2q_gates / len(edge_list)
|
|
160
|
+
else:
|
|
161
|
+
prob_2qgate = 0
|
|
162
|
+
|
|
163
|
+
# Add gates in selected edges
|
|
164
|
+
for e in edge_list:
|
|
165
|
+
# Sample the 2Q gate
|
|
166
|
+
two_qubit_gate = random.choices(
|
|
167
|
+
list(two_qubit_gate_ensemble.keys()),
|
|
168
|
+
weights=list(two_qubit_gate_ensemble.values()),
|
|
169
|
+
k=1,
|
|
170
|
+
)[0]
|
|
171
|
+
|
|
172
|
+
# Pick whether to place the sampled 2Q gate according to the probability above
|
|
173
|
+
is_gate_placed = random.choices(
|
|
174
|
+
[True, False],
|
|
175
|
+
weights=[prob_2qgate, 1 - prob_2qgate],
|
|
176
|
+
k=1,
|
|
177
|
+
)[0]
|
|
178
|
+
|
|
179
|
+
if is_gate_placed:
|
|
180
|
+
if two_qubit_gate == "clifford":
|
|
181
|
+
layer.compose(
|
|
182
|
+
random_clifford(2).to_instruction(),
|
|
183
|
+
qubits=[
|
|
184
|
+
physical_to_virtual_map[e[0]],
|
|
185
|
+
physical_to_virtual_map[e[1]],
|
|
186
|
+
],
|
|
187
|
+
inplace=True,
|
|
188
|
+
)
|
|
189
|
+
else:
|
|
190
|
+
layer.append(
|
|
191
|
+
two_qubit_circuits[two_qubit_gate],
|
|
192
|
+
[
|
|
193
|
+
physical_to_virtual_map[e[0]],
|
|
194
|
+
physical_to_virtual_map[e[1]],
|
|
195
|
+
],
|
|
196
|
+
)
|
|
197
|
+
else:
|
|
198
|
+
layer.compose(
|
|
199
|
+
random_clifford(1).to_instruction(),
|
|
200
|
+
qubits=[physical_to_virtual_map[e[0]]],
|
|
201
|
+
inplace=True,
|
|
202
|
+
)
|
|
203
|
+
layer.compose(
|
|
204
|
+
random_clifford(1).to_instruction(),
|
|
205
|
+
qubits=[physical_to_virtual_map[e[1]]],
|
|
206
|
+
inplace=True,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Add 1Q gates in remaining qubits
|
|
210
|
+
remaining_qubits = [q for q in qubit_set if q not in edge_qubits]
|
|
211
|
+
while remaining_qubits:
|
|
212
|
+
for q in remaining_qubits:
|
|
213
|
+
layer.compose(
|
|
214
|
+
random_clifford(1).to_instruction(),
|
|
215
|
+
qubits=[physical_to_virtual_map[q]],
|
|
216
|
+
inplace=True,
|
|
217
|
+
)
|
|
218
|
+
remaining_qubits.remove(q)
|
|
219
|
+
|
|
220
|
+
layer_list.append(layer)
|
|
221
|
+
|
|
222
|
+
return layer_list
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def generate_pauli_dressed_mrb_circuits(
|
|
226
|
+
qubits: List[int],
|
|
227
|
+
pauli_samples_per_circ: int,
|
|
228
|
+
depth: int,
|
|
229
|
+
backend_arg: IQMBackendBase | str,
|
|
230
|
+
density_2q_gates: float = 0.25,
|
|
231
|
+
two_qubit_gate_ensemble: Optional[Dict[str, float]] = None,
|
|
232
|
+
qiskit_optim_level: int = 1,
|
|
233
|
+
routing_method: str = "basic",
|
|
234
|
+
) -> Dict[str, List[QuantumCircuit]]:
|
|
235
|
+
"""Samples a mirror circuit and generates samples of "Pauli-dressed" circuits,
|
|
236
|
+
where for each circuit, random Pauli layers are interleaved between each layer of the circuit
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
qubits (List[int]): the qubits of the backend
|
|
240
|
+
pauli_samples_per_circ (int): the number of pauli samples per circuit
|
|
241
|
+
depth (int): the depth (number of canonical layers) of the circuit
|
|
242
|
+
backend_arg (IQMBackendBase | str): the backend
|
|
243
|
+
density_2q_gates (float): the expected density of 2Q gates
|
|
244
|
+
two_qubit_gate_ensemble (Optional[Dict[str, float]]):
|
|
245
|
+
qiskit_optim_level (int):
|
|
246
|
+
routing_method (str):
|
|
247
|
+
Returns:
|
|
248
|
+
|
|
249
|
+
"""
|
|
250
|
+
num_qubits = len(qubits)
|
|
251
|
+
|
|
252
|
+
# Sample the layers using edge grab sampler - different samplers may be conditionally chosen here in the future
|
|
253
|
+
cycle_layers = edge_grab(qubits, depth, backend_arg, density_2q_gates, two_qubit_gate_ensemble)
|
|
254
|
+
|
|
255
|
+
# Sample the edge (initial/final) random Single-qubit Clifford layer
|
|
256
|
+
clifford_layer = [random_clifford(1) for _ in range(num_qubits)]
|
|
257
|
+
|
|
258
|
+
# Initialize the list of circuits
|
|
259
|
+
all_circuits = {}
|
|
260
|
+
pauli_dressed_circuits_untranspiled: List[QuantumCircuit] = []
|
|
261
|
+
pauli_dressed_circuits_transpiled: List[QuantumCircuit] = []
|
|
262
|
+
|
|
263
|
+
for _ in range(pauli_samples_per_circ):
|
|
264
|
+
# Initialize the quantum circuit object
|
|
265
|
+
circ = QuantumCircuit(num_qubits)
|
|
266
|
+
# Sample all the random Paulis
|
|
267
|
+
paulis = [random_pauli(num_qubits) for _ in range(depth + 1)]
|
|
268
|
+
|
|
269
|
+
# Add the edge product of Cliffords
|
|
270
|
+
for i in range(num_qubits):
|
|
271
|
+
circ.compose(clifford_layer[i].to_instruction(), qubits=[i], inplace=True)
|
|
272
|
+
circ.barrier()
|
|
273
|
+
|
|
274
|
+
# Add the cycle layers
|
|
275
|
+
for k in range(depth):
|
|
276
|
+
circ.compose(
|
|
277
|
+
paulis[k].to_instruction(),
|
|
278
|
+
qubits=list(range(num_qubits)),
|
|
279
|
+
inplace=True,
|
|
280
|
+
)
|
|
281
|
+
circ.barrier()
|
|
282
|
+
circ.compose(cycle_layers[k], inplace=True)
|
|
283
|
+
circ.barrier()
|
|
284
|
+
|
|
285
|
+
# Apply middle Pauli
|
|
286
|
+
circ.compose(
|
|
287
|
+
paulis[depth].to_instruction(),
|
|
288
|
+
qubits=list(range(num_qubits)),
|
|
289
|
+
inplace=True,
|
|
290
|
+
)
|
|
291
|
+
circ.barrier()
|
|
292
|
+
|
|
293
|
+
# Add the mirror layers
|
|
294
|
+
for k in range(depth):
|
|
295
|
+
circ.compose(cycle_layers[depth - k - 1].inverse(), inplace=True)
|
|
296
|
+
circ.barrier()
|
|
297
|
+
circ.compose(
|
|
298
|
+
paulis[depth - k - 1].to_instruction(),
|
|
299
|
+
qubits=list(range(num_qubits)),
|
|
300
|
+
inplace=True,
|
|
301
|
+
)
|
|
302
|
+
circ.barrier()
|
|
303
|
+
|
|
304
|
+
# Add the inverse edge product of Cliffords
|
|
305
|
+
for i in range(num_qubits):
|
|
306
|
+
circ.compose(clifford_layer[i].to_instruction().inverse(), qubits=[i], inplace=True)
|
|
307
|
+
|
|
308
|
+
# Add measurements
|
|
309
|
+
circ.measure_all()
|
|
310
|
+
|
|
311
|
+
# Transpile to backend - no optimize SQG should be used!
|
|
312
|
+
if isinstance(backend_arg, str):
|
|
313
|
+
retrieved_backend = get_iqm_backend(backend_arg)
|
|
314
|
+
else:
|
|
315
|
+
assert isinstance(backend_arg, IQMBackendBase)
|
|
316
|
+
retrieved_backend = backend_arg
|
|
317
|
+
|
|
318
|
+
circ_transpiled = transpile(
|
|
319
|
+
circ,
|
|
320
|
+
backend=retrieved_backend,
|
|
321
|
+
initial_layout=qubits,
|
|
322
|
+
optimization_level=qiskit_optim_level,
|
|
323
|
+
routing_method=routing_method,
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
pauli_dressed_circuits_untranspiled.append(circ)
|
|
327
|
+
pauli_dressed_circuits_transpiled.append(circ_transpiled)
|
|
328
|
+
|
|
329
|
+
# Store the circuit
|
|
330
|
+
all_circuits.update(
|
|
331
|
+
{
|
|
332
|
+
"untranspiled": pauli_dressed_circuits_untranspiled,
|
|
333
|
+
"transpiled": pauli_dressed_circuits_transpiled,
|
|
334
|
+
}
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
return all_circuits
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
@timeit
|
|
341
|
+
def generate_fixed_depth_mrb_circuits(
|
|
342
|
+
qubits: List[int],
|
|
343
|
+
circ_samples: int,
|
|
344
|
+
pauli_samples_per_circ: int,
|
|
345
|
+
depth: int,
|
|
346
|
+
backend_arg: IQMBackendBase | str,
|
|
347
|
+
density_2q_gates: float = 0.25,
|
|
348
|
+
two_qubit_gate_ensemble: Optional[Dict[str, float]] = None,
|
|
349
|
+
qiskit_optim_level: int = 1,
|
|
350
|
+
routing_method: str = "basic",
|
|
351
|
+
) -> Dict[int, Dict[str, List[QuantumCircuit]]]:
|
|
352
|
+
"""Generates a dictionary MRB circuits at fixed depth, indexed by sample number
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
qubits (List[int]): A list of integers specifying physical qubit labels
|
|
356
|
+
circ_samples (int): The number of sets of Pauli-dressed circuit samples
|
|
357
|
+
pauli_samples_per_circ (int): the number of pauli samples per circuit
|
|
358
|
+
depth (int): the depth (number of canonical layers) of the circuits
|
|
359
|
+
backend_arg (IQMBackendBase | str): the backend
|
|
360
|
+
density_2q_gates (float):
|
|
361
|
+
two_qubit_gate_ensemble (Optional[Dict[str, float]]):
|
|
362
|
+
qiskit_optim_level (int):
|
|
363
|
+
routing_method (str):
|
|
364
|
+
Returns:
|
|
365
|
+
A dictionary of lists of Pauli-dressed quantum circuits corresponding to the circuit sample index
|
|
366
|
+
"""
|
|
367
|
+
|
|
368
|
+
circuits = {} # The dict with a Dict of random mirror circuits
|
|
369
|
+
for p_sample in range(circ_samples):
|
|
370
|
+
circuits[p_sample] = generate_pauli_dressed_mrb_circuits(
|
|
371
|
+
qubits,
|
|
372
|
+
pauli_samples_per_circ,
|
|
373
|
+
depth,
|
|
374
|
+
backend_arg,
|
|
375
|
+
density_2q_gates,
|
|
376
|
+
two_qubit_gate_ensemble,
|
|
377
|
+
qiskit_optim_level,
|
|
378
|
+
routing_method,
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
return circuits
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def list_to_numcircuit_times_numpauli_matrix(
|
|
385
|
+
input_list: List[Any], num_circ_samples: int, num_pauli_samples: int
|
|
386
|
+
) -> List[List[Any]]:
|
|
387
|
+
"""Convert a flat list to a matrix of shape (num_circ_samples, num_pauli_samples).
|
|
388
|
+
Args:
|
|
389
|
+
input_list (List[Any]): the input flat list
|
|
390
|
+
num_circ_samples (int): the number of sets of Pauli-dressed circuit samples
|
|
391
|
+
num_pauli_samples (int): the number of Pauli samples per circuit
|
|
392
|
+
Raises:
|
|
393
|
+
ValueError: Length of passed list is not (num_circ_samples * num_pauli_samples).
|
|
394
|
+
Returns:
|
|
395
|
+
List[List[Any]]: the matrix
|
|
396
|
+
"""
|
|
397
|
+
if len(input_list) != num_circ_samples * num_pauli_samples:
|
|
398
|
+
raise ValueError(
|
|
399
|
+
f"Length of passed list {len(input_list)} is not"
|
|
400
|
+
f" (num_circ_samples * num_pauli_samples) = {num_circ_samples * num_pauli_samples}"
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
return np.reshape(input_list, (num_circ_samples, num_pauli_samples)).tolist()
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
# pylint: disable=too-many-statements
|
|
407
|
+
def mrb_analysis(run: RunResult) -> AnalysisResult:
|
|
408
|
+
"""Analysis function for a MRB experiment
|
|
409
|
+
|
|
410
|
+
Args:
|
|
411
|
+
run (RunResult): A MRB experiment run for which analysis result is created
|
|
412
|
+
Returns:
|
|
413
|
+
AnalysisResult corresponding to MRB
|
|
414
|
+
"""
|
|
415
|
+
plots = {}
|
|
416
|
+
observations = {}
|
|
417
|
+
dataset = run.dataset
|
|
418
|
+
|
|
419
|
+
shots = dataset.attrs["shots"]
|
|
420
|
+
num_circuit_samples = dataset.attrs["num_circuit_samples"]
|
|
421
|
+
num_pauli_samples = dataset.attrs["num_pauli_samples"]
|
|
422
|
+
|
|
423
|
+
density_2q_gates = dataset.attrs["density_2q_gates"]
|
|
424
|
+
two_qubit_gate_ensemble = dataset.attrs["two_qubit_gate_ensemble"]
|
|
425
|
+
|
|
426
|
+
max_gates_per_batch = dataset.attrs["max_gates_per_batch"]
|
|
427
|
+
|
|
428
|
+
# Analyze the results for each qubit layout of the experiment dataset
|
|
429
|
+
qubits_array = dataset.attrs["qubits_array"]
|
|
430
|
+
depths_array = dataset.attrs["depths_array"]
|
|
431
|
+
|
|
432
|
+
assigned_mrb_depths = {}
|
|
433
|
+
if len(qubits_array) != len(depths_array):
|
|
434
|
+
# If user did not specify a list of depth for each list of qubits, assign the first
|
|
435
|
+
# If the len is not one, the input was incorrect
|
|
436
|
+
if len(depths_array) != 1:
|
|
437
|
+
qcvv_logger.info(
|
|
438
|
+
f"The amount of qubit layouts ({len(qubits_array)}) is not the same "
|
|
439
|
+
f"as the amount of depth configurations ({len(depths_array)}):\n\tWill assign to all the first "
|
|
440
|
+
f"configuration: {depths_array[0]} !"
|
|
441
|
+
)
|
|
442
|
+
assigned_mrb_depths = {str(q): [2 * m for m in depths_array[0]] for q in qubits_array}
|
|
443
|
+
else:
|
|
444
|
+
assigned_mrb_depths = {str(qubits_array[i]): [2 * m for m in depths_array[i]] for i in range(len(depths_array))}
|
|
445
|
+
|
|
446
|
+
transpiled_circuits = dataset.attrs["transpiled_circuits"]
|
|
447
|
+
simulator = Aer.get_backend("qasm_simulator")
|
|
448
|
+
|
|
449
|
+
all_noisy_counts: Dict[str, Dict[int, List[Dict[str, int]]]] = {}
|
|
450
|
+
all_noiseless_counts: Dict[str, Dict[int, List[Dict[str, int]]]] = {}
|
|
451
|
+
time_retrieve_noiseless: Dict[str, Dict[int, float]] = {}
|
|
452
|
+
# Need to loop over each set of qubits, and within, over each depth
|
|
453
|
+
for qubits_idx, qubits in enumerate(qubits_array):
|
|
454
|
+
polarizations = {}
|
|
455
|
+
num_qubits = len(qubits)
|
|
456
|
+
all_noisy_counts[str(qubits)] = {}
|
|
457
|
+
all_noiseless_counts[str(qubits)] = {}
|
|
458
|
+
time_retrieve_noiseless[str(qubits)] = {}
|
|
459
|
+
qcvv_logger.info(f"Post-processing MRB for qubits {qubits}")
|
|
460
|
+
for depth in assigned_mrb_depths[str(qubits)]:
|
|
461
|
+
# Retrieve counts
|
|
462
|
+
identifier = f"qubits_{str(qubits)}_depth_{str(depth)}"
|
|
463
|
+
all_noisy_counts[str(qubits)][depth] = xrvariable_to_counts(
|
|
464
|
+
dataset, identifier, num_circuit_samples * num_pauli_samples
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
qcvv_logger.info(f"Depth {depth}")
|
|
468
|
+
# Execute the quantum circuits on the simulated, ideal backend
|
|
469
|
+
# pylint: disable=unbalanced-tuple-unpacking
|
|
470
|
+
all_noiseless_jobs, _ = submit_execute(
|
|
471
|
+
{tuple(qubits): transpiled_circuits[str(qubits)][depth]},
|
|
472
|
+
simulator,
|
|
473
|
+
shots,
|
|
474
|
+
calset_id=None,
|
|
475
|
+
max_gates_per_batch=max_gates_per_batch,
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
# Retrieve counts
|
|
479
|
+
all_noiseless_counts[str(qubits)][depth], time_retrieve_noiseless[str(qubits)][depth] = retrieve_all_counts(
|
|
480
|
+
all_noiseless_jobs
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
# Compute polarizations for the current depth
|
|
484
|
+
polarizations[depth] = compute_polarizations(
|
|
485
|
+
num_qubits,
|
|
486
|
+
all_noisy_counts[str(qubits)][depth],
|
|
487
|
+
all_noiseless_counts[str(qubits)][depth],
|
|
488
|
+
num_circuit_samples,
|
|
489
|
+
num_pauli_samples,
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
# Fit decay and extract parameters
|
|
493
|
+
list_of_polarizations = list(polarizations.values())
|
|
494
|
+
fit_data, fit_parameters = fit_decay_lmfit(exponential_rb, qubits, list_of_polarizations, "mrb")
|
|
495
|
+
rb_fit_results = lmfit_minimizer(fit_parameters, fit_data, assigned_mrb_depths[str(qubits)], exponential_rb)
|
|
496
|
+
|
|
497
|
+
average_polarizations = {d: np.mean(polarizations[d]) for d in assigned_mrb_depths[str(qubits)]}
|
|
498
|
+
stddevs_from_mean = {
|
|
499
|
+
d: np.std(polarizations[d]) / np.sqrt(num_circuit_samples * num_pauli_samples)
|
|
500
|
+
for d in assigned_mrb_depths[str(qubits)]
|
|
501
|
+
}
|
|
502
|
+
popt = {
|
|
503
|
+
"amplitude": rb_fit_results.params["amplitude_1"],
|
|
504
|
+
"offset": rb_fit_results.params["offset_1"],
|
|
505
|
+
"decay_rate": rb_fit_results.params["p_mrb"],
|
|
506
|
+
}
|
|
507
|
+
fidelity = rb_fit_results.params["fidelity_mrb"]
|
|
508
|
+
|
|
509
|
+
processed_results = {
|
|
510
|
+
"avg_gate_fidelity": {"value": fidelity.value, "uncertainty": fidelity.stderr},
|
|
511
|
+
"decay_rate": {"value": popt["decay_rate"].value, "uncertainty": popt["decay_rate"].stderr},
|
|
512
|
+
"fit_amplitude": {"value": popt["amplitude"].value, "uncertainty": popt["amplitude"].stderr},
|
|
513
|
+
"fit_offset": {"value": popt["offset"].value, "uncertainty": popt["offset"].stderr},
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
dataset.attrs[qubits_idx].update(
|
|
517
|
+
{
|
|
518
|
+
"polarizations": polarizations,
|
|
519
|
+
"avg_polarization_nominal_values": average_polarizations,
|
|
520
|
+
"avg_polatization_stderr": stddevs_from_mean,
|
|
521
|
+
"fitting_method": str(rb_fit_results.method),
|
|
522
|
+
"num_function_evals": int(rb_fit_results.nfev),
|
|
523
|
+
"data_points": int(rb_fit_results.ndata),
|
|
524
|
+
"num_variables": int(rb_fit_results.nvarys),
|
|
525
|
+
"chi_square": float(rb_fit_results.chisqr),
|
|
526
|
+
"reduced_chi_square": float(rb_fit_results.redchi),
|
|
527
|
+
"Akaike_info_crit": float(rb_fit_results.aic),
|
|
528
|
+
"Bayesian_info_crit": float(rb_fit_results.bic),
|
|
529
|
+
}
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
# Update observations
|
|
533
|
+
observations.update({qubits_idx: processed_results})
|
|
534
|
+
|
|
535
|
+
# Generate plots
|
|
536
|
+
fig_name, fig = plot_rb_decay(
|
|
537
|
+
"mrb",
|
|
538
|
+
[qubits],
|
|
539
|
+
dataset,
|
|
540
|
+
observations,
|
|
541
|
+
mrb_2q_density=density_2q_gates,
|
|
542
|
+
mrb_2q_ensemble=two_qubit_gate_ensemble,
|
|
543
|
+
)
|
|
544
|
+
plots[fig_name] = fig
|
|
545
|
+
|
|
546
|
+
# Generate the combined plot
|
|
547
|
+
fig_name, fig = plot_rb_decay(
|
|
548
|
+
"mrb",
|
|
549
|
+
qubits_array,
|
|
550
|
+
dataset,
|
|
551
|
+
observations,
|
|
552
|
+
mrb_2q_density=density_2q_gates,
|
|
553
|
+
mrb_2q_ensemble=two_qubit_gate_ensemble,
|
|
554
|
+
)
|
|
555
|
+
plots[fig_name] = fig
|
|
556
|
+
|
|
557
|
+
return AnalysisResult(dataset=dataset, plots=plots, observations=observations)
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
class MirrorRandomizedBenchmarking(Benchmark):
|
|
561
|
+
"""
|
|
562
|
+
Mirror RB estimates the fidelity of ensembles of n-qubit layers
|
|
563
|
+
"""
|
|
564
|
+
|
|
565
|
+
analysis_function = staticmethod(mrb_analysis)
|
|
566
|
+
|
|
567
|
+
name: str = "mrb"
|
|
568
|
+
|
|
569
|
+
def __init__(self, backend_arg: IQMBackendBase | str, configuration: "MirrorRBConfiguration"):
|
|
570
|
+
"""Construct the MirrorRandomizedBenchmarking class
|
|
571
|
+
|
|
572
|
+
Args:
|
|
573
|
+
backend_arg (IQMBackendBase | str): _description_
|
|
574
|
+
configuration (MirrorRBConfiguration): _description_
|
|
575
|
+
"""
|
|
576
|
+
super().__init__(backend_arg, configuration)
|
|
577
|
+
|
|
578
|
+
# EXPERIMENT
|
|
579
|
+
self.backend_configuration_name = backend_arg if isinstance(backend_arg, str) else backend_arg.name
|
|
580
|
+
|
|
581
|
+
self.qubits_array = configuration.qubits_array
|
|
582
|
+
self.depths_array = configuration.depths_array
|
|
583
|
+
self.num_circuit_samples = configuration.num_circuit_samples
|
|
584
|
+
self.num_pauli_samples = configuration.num_pauli_samples
|
|
585
|
+
self.two_qubit_gate_ensemble = configuration.two_qubit_gate_ensemble
|
|
586
|
+
self.density_2q_gates = configuration.density_2q_gates
|
|
587
|
+
|
|
588
|
+
self.qiskit_optim_level = configuration.qiskit_optim_level
|
|
589
|
+
|
|
590
|
+
self.simulator = Aer.get_backend("qasm_simulator")
|
|
591
|
+
|
|
592
|
+
self.session_timestamp = strftime("%Y%m%d-%H%M%S")
|
|
593
|
+
self.execution_timestamp = ""
|
|
594
|
+
|
|
595
|
+
def add_all_meta_to_dataset(self, dataset: xr.Dataset):
|
|
596
|
+
"""Adds all configuration metadata and circuits to the dataset variable
|
|
597
|
+
|
|
598
|
+
Args:
|
|
599
|
+
dataset (xr.Dataset): The xarray dataset
|
|
600
|
+
"""
|
|
601
|
+
dataset.attrs["session_timestamp"] = self.session_timestamp
|
|
602
|
+
dataset.attrs["execution_timestamp"] = self.execution_timestamp
|
|
603
|
+
dataset.attrs["backend_configuration_name"] = self.backend_configuration_name
|
|
604
|
+
dataset.attrs["backend_name"] = self.backend.name
|
|
605
|
+
|
|
606
|
+
for key, value in self.configuration:
|
|
607
|
+
if key == "benchmark": # Avoid saving the class object
|
|
608
|
+
dataset.attrs[key] = value.name
|
|
609
|
+
else:
|
|
610
|
+
dataset.attrs[key] = value
|
|
611
|
+
# Defined outside configuration - if any
|
|
612
|
+
|
|
613
|
+
@timeit
|
|
614
|
+
def add_all_circuits_to_dataset(self, dataset: xr.Dataset):
|
|
615
|
+
"""Adds all generated circuits during execution to the dataset variable
|
|
616
|
+
Args:
|
|
617
|
+
dataset (xr.Dataset): The xarray dataset
|
|
618
|
+
"""
|
|
619
|
+
qcvv_logger.info(f"Adding all circuits to the dataset")
|
|
620
|
+
dataset.attrs["untranspiled_circuits"] = self.untranspiled_circuits
|
|
621
|
+
dataset.attrs["transpiled_circuits"] = self.transpiled_circuits
|
|
622
|
+
|
|
623
|
+
def submit_single_mrb_job(
|
|
624
|
+
self,
|
|
625
|
+
backend_arg: IQMBackendBase,
|
|
626
|
+
qubits: Sequence[int],
|
|
627
|
+
depth: int,
|
|
628
|
+
sorted_transpiled_circuit_dicts: Dict[Tuple[int, ...], List[QuantumCircuit]],
|
|
629
|
+
) -> Dict[str, Any]:
|
|
630
|
+
"""
|
|
631
|
+
Submit fixed-depth MRB jobs for execution in the specified IQMBackend
|
|
632
|
+
Args:
|
|
633
|
+
backend_arg (IQMBackendBase): the IQM backend to submit the job
|
|
634
|
+
qubits (Sequence[int]): the qubits to identify the submitted job
|
|
635
|
+
depth (int): the depth (number of canonical layers) of the circuits to identify the submitted job
|
|
636
|
+
sorted_transpiled_circuit_dicts (Dict[str, List[QuantumCircuit]]): A dictionary containing all MRB circuits
|
|
637
|
+
Returns:
|
|
638
|
+
Dict with qubit layout, submitted job objects, type (vanilla/DD) and submission time
|
|
639
|
+
"""
|
|
640
|
+
# Submit
|
|
641
|
+
# Send to execute on backend
|
|
642
|
+
execution_jobs, time_submit = submit_execute(
|
|
643
|
+
sorted_transpiled_circuit_dicts,
|
|
644
|
+
backend_arg,
|
|
645
|
+
self.shots,
|
|
646
|
+
self.calset_id,
|
|
647
|
+
max_gates_per_batch=self.max_gates_per_batch,
|
|
648
|
+
)
|
|
649
|
+
mrb_submit_results = {
|
|
650
|
+
"qubits": qubits,
|
|
651
|
+
"depth": depth,
|
|
652
|
+
"jobs": execution_jobs,
|
|
653
|
+
"time_submit": time_submit,
|
|
654
|
+
}
|
|
655
|
+
return mrb_submit_results
|
|
656
|
+
|
|
657
|
+
def execute(self, backend: IQMBackendBase) -> xr.Dataset:
|
|
658
|
+
"""Executes the benchmark"""
|
|
659
|
+
|
|
660
|
+
self.execution_timestamp = strftime("%Y%m%d-%H%M%S")
|
|
661
|
+
|
|
662
|
+
dataset = xr.Dataset()
|
|
663
|
+
self.add_all_meta_to_dataset(dataset)
|
|
664
|
+
|
|
665
|
+
# Submit jobs for all qubit layouts
|
|
666
|
+
all_mrb_jobs: List[Dict[str, Any]] = []
|
|
667
|
+
time_circuit_generation: Dict[str, float] = {}
|
|
668
|
+
|
|
669
|
+
# Initialize the variable to contain the circuits for each layout
|
|
670
|
+
self.untranspiled_circuits: Dict[str, Dict[int | str, List[QuantumCircuit]]] = {}
|
|
671
|
+
self.transpiled_circuits: Dict[str, Dict[int | str, List[QuantumCircuit]]] = {}
|
|
672
|
+
|
|
673
|
+
# The depths should be assigned to each set of qubits!
|
|
674
|
+
# The real final MRB depths are twice the originally specified, must be taken into account here!
|
|
675
|
+
assigned_mrb_depths = {}
|
|
676
|
+
if len(self.qubits_array) != len(self.depths_array):
|
|
677
|
+
# If user did not specify a list of depth for each list of qubits, assign the first
|
|
678
|
+
# If the len is not one, the input was incorrect
|
|
679
|
+
if len(self.depths_array) != 1:
|
|
680
|
+
warnings.warn(
|
|
681
|
+
f"The amount of qubit layouts ({len(self.qubits_array)}) is not the same "
|
|
682
|
+
f"as the amount of depth configurations ({len(self.depths_array)}):\n\tWill assign to all the first "
|
|
683
|
+
f"configuration: {self.depths_array[0]} !"
|
|
684
|
+
)
|
|
685
|
+
assigned_mrb_depths = {str(q): [2 * m for m in self.depths_array[0]] for q in self.qubits_array}
|
|
686
|
+
else:
|
|
687
|
+
assigned_mrb_depths = {
|
|
688
|
+
str(self.qubits_array[i]): [2 * m for m in self.depths_array[i]] for i in range(len(self.depths_array))
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
# Auxiliary dict from str(qubits) to indices
|
|
692
|
+
qubit_idx: Dict[str, Any] = {}
|
|
693
|
+
for qubits_idx, qubits in enumerate(self.qubits_array):
|
|
694
|
+
qubit_idx[str(qubits)] = qubits_idx
|
|
695
|
+
self.untranspiled_circuits[str(qubits)] = {}
|
|
696
|
+
self.transpiled_circuits[str(qubits)] = {}
|
|
697
|
+
|
|
698
|
+
qcvv_logger.info(
|
|
699
|
+
f"Executing MRB on qubits {qubits}."
|
|
700
|
+
f" Will generate and submit all {self.num_circuit_samples}x{self.num_pauli_samples} MRB circuits"
|
|
701
|
+
f" for each depth {assigned_mrb_depths[str(qubits)]}"
|
|
702
|
+
)
|
|
703
|
+
mrb_circuits = {}
|
|
704
|
+
mrb_transpiled_circuits_lists: Dict[int, List[QuantumCircuit]] = {}
|
|
705
|
+
mrb_untranspiled_circuits_lists: Dict[int, List[QuantumCircuit]] = {}
|
|
706
|
+
time_circuit_generation[str(qubits)] = 0
|
|
707
|
+
for depth in assigned_mrb_depths[str(qubits)]:
|
|
708
|
+
qcvv_logger.info(f"Depth {depth}")
|
|
709
|
+
mrb_circuits[depth], elapsed_time = generate_fixed_depth_mrb_circuits(
|
|
710
|
+
qubits,
|
|
711
|
+
self.num_circuit_samples,
|
|
712
|
+
self.num_pauli_samples,
|
|
713
|
+
int(depth / 2),
|
|
714
|
+
backend,
|
|
715
|
+
self.density_2q_gates,
|
|
716
|
+
self.two_qubit_gate_ensemble,
|
|
717
|
+
self.qiskit_optim_level,
|
|
718
|
+
self.routing_method,
|
|
719
|
+
)
|
|
720
|
+
time_circuit_generation[str(qubits)] += elapsed_time
|
|
721
|
+
|
|
722
|
+
# Generated circuits at fixed depth are (dict) indexed by Pauli sample number, turn into List
|
|
723
|
+
mrb_transpiled_circuits_lists[depth] = []
|
|
724
|
+
mrb_untranspiled_circuits_lists[depth] = []
|
|
725
|
+
for c_s in range(self.num_circuit_samples):
|
|
726
|
+
mrb_transpiled_circuits_lists[depth].extend(mrb_circuits[depth][c_s]["transpiled"])
|
|
727
|
+
for c_s in range(self.num_circuit_samples):
|
|
728
|
+
mrb_untranspiled_circuits_lists[depth].extend(mrb_circuits[depth][c_s]["untranspiled"])
|
|
729
|
+
|
|
730
|
+
# Submit
|
|
731
|
+
sorted_transpiled_qc_list = {tuple(qubits): mrb_transpiled_circuits_lists[depth]}
|
|
732
|
+
all_mrb_jobs.append(self.submit_single_mrb_job(backend, qubits, depth, sorted_transpiled_qc_list))
|
|
733
|
+
qcvv_logger.info(f"Job for layout {qubits} & depth {depth} submitted successfully!")
|
|
734
|
+
|
|
735
|
+
self.untranspiled_circuits[str(qubits)] = {
|
|
736
|
+
d: mrb_untranspiled_circuits_lists[d] for d in assigned_mrb_depths[str(qubits)]
|
|
737
|
+
}
|
|
738
|
+
self.transpiled_circuits[str(qubits)] = {
|
|
739
|
+
d: mrb_transpiled_circuits_lists[d] for d in assigned_mrb_depths[str(qubits)]
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
dataset.attrs[qubits_idx] = {"qubits": qubits}
|
|
743
|
+
|
|
744
|
+
# Retrieve counts of jobs for all qubit layouts
|
|
745
|
+
all_job_metadata = {}
|
|
746
|
+
for job_dict in all_mrb_jobs:
|
|
747
|
+
qubits = job_dict["qubits"]
|
|
748
|
+
depth = job_dict["depth"]
|
|
749
|
+
# Retrieve counts
|
|
750
|
+
execution_results, time_retrieve = retrieve_all_counts(
|
|
751
|
+
job_dict["jobs"], f"qubits_{str(qubits)}_depth_{str(depth)}"
|
|
752
|
+
)
|
|
753
|
+
# Retrieve all job meta data
|
|
754
|
+
all_job_metadata = retrieve_all_job_metadata(job_dict["jobs"])
|
|
755
|
+
# Export all to dataset
|
|
756
|
+
dataset.attrs[qubit_idx[str(qubits)]].update(
|
|
757
|
+
{
|
|
758
|
+
f"depth_{str(depth)}": {
|
|
759
|
+
"time_circuit_generation": time_circuit_generation[str(qubits)],
|
|
760
|
+
"time_submit": job_dict["time_submit"],
|
|
761
|
+
"time_retrieve": time_retrieve,
|
|
762
|
+
"all_job_metadata": all_job_metadata,
|
|
763
|
+
},
|
|
764
|
+
}
|
|
765
|
+
)
|
|
766
|
+
|
|
767
|
+
qcvv_logger.info(f"Adding counts of qubits {qubits} and depth {depth} run to the dataset")
|
|
768
|
+
dataset, _ = add_counts_to_dataset(execution_results, f"qubits_{str(qubits)}_depth_{str(depth)}", dataset)
|
|
769
|
+
|
|
770
|
+
self.add_all_circuits_to_dataset(dataset)
|
|
771
|
+
|
|
772
|
+
qcvv_logger.info(f"MRB experiment execution concluded !")
|
|
773
|
+
|
|
774
|
+
return dataset
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
class MirrorRBConfiguration(BenchmarkConfigurationBase):
|
|
778
|
+
"""Mirror RB configuration.
|
|
779
|
+
|
|
780
|
+
Attributes:
|
|
781
|
+
benchmark (Type[Benchmark]): MirrorRandomizedBenchmarking.
|
|
782
|
+
qubits_array (Sequence[Sequence[int]]): The array of physical qubits in which to execute MRB.
|
|
783
|
+
depths_array (Sequence[Sequence[int]]): The array of physical depths in which to execute MRB for a corresponding qubit list.
|
|
784
|
+
* If len is the same as that of qubits_array, each Sequence[int] corresponds to the depths for the corresponding layout of qubits.
|
|
785
|
+
* If len is different from that of qubits_array, assigns the first Sequence[int].
|
|
786
|
+
num_circuit_samples (int): The number of random-layer mirror circuits to generate.
|
|
787
|
+
num_pauli_samples (int): The number of random Pauli layers to interleave per mirror circuit.
|
|
788
|
+
shots (int): The number of measurement shots to execute per circuit.
|
|
789
|
+
qiskit_optim_level (int): The Qiskit-level of optimization to use in transpilation.
|
|
790
|
+
* Default is 1.
|
|
791
|
+
routing_method (Literal["basic", "lookahead", "stochastic", "sabre", "none"]): The routing method to use in transpilation.
|
|
792
|
+
* Default is "sabre".
|
|
793
|
+
two_qubit_gate_ensemble (Dict[str, float]): The two-qubit gate ensemble to use in the random mirror circuits.
|
|
794
|
+
* Keys correspond to str names of qiskit circuit library gates, e.g., "CZGate" or "CXGate".
|
|
795
|
+
* Values correspond to the probability for the respective gate to be sampled.
|
|
796
|
+
* Default is {"CZGate": 1.0}.
|
|
797
|
+
density_2q_gates (float): The expected density of 2-qubit gates in the final circuits.
|
|
798
|
+
* Default is 0.25.
|
|
799
|
+
"""
|
|
800
|
+
|
|
801
|
+
benchmark: Type[Benchmark] = MirrorRandomizedBenchmarking
|
|
802
|
+
qubits_array: Sequence[Sequence[int]]
|
|
803
|
+
depths_array: Sequence[Sequence[int]]
|
|
804
|
+
num_circuit_samples: int
|
|
805
|
+
num_pauli_samples: int
|
|
806
|
+
qiskit_optim_level: int = 1
|
|
807
|
+
two_qubit_gate_ensemble: Dict[str, float] = {
|
|
808
|
+
"CZGate": 1.0,
|
|
809
|
+
}
|
|
810
|
+
density_2q_gates: float = 0.25
|