iqm-benchmarks 2.32__py3-none-any.whl → 2.34__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 +4 -0
- iqm/benchmarks/compressive_gst/gst_analysis.py +0 -1
- iqm/benchmarks/entanglement/graph_states.py +1 -1
- iqm/benchmarks/randomized_benchmarking/direct_rb/__init__.py +19 -0
- iqm/benchmarks/randomized_benchmarking/direct_rb/direct_rb.py +1000 -0
- iqm/benchmarks/randomized_benchmarking/eplg/__init__.py +19 -0
- iqm/benchmarks/randomized_benchmarking/eplg/eplg.py +409 -0
- iqm/benchmarks/randomized_benchmarking/mirror_rb/__init__.py +1 -1
- iqm/benchmarks/randomized_benchmarking/mirror_rb/mirror_rb.py +81 -163
- iqm/benchmarks/randomized_benchmarking/randomized_benchmarking_common.py +310 -71
- iqm/benchmarks/utils.py +138 -53
- iqm/benchmarks/utils_plots.py +189 -3
- {iqm_benchmarks-2.32.dist-info → iqm_benchmarks-2.34.dist-info}/METADATA +5 -4
- {iqm_benchmarks-2.32.dist-info → iqm_benchmarks-2.34.dist-info}/RECORD +17 -13
- {iqm_benchmarks-2.32.dist-info → iqm_benchmarks-2.34.dist-info}/WHEEL +0 -0
- {iqm_benchmarks-2.32.dist-info → iqm_benchmarks-2.34.dist-info}/licenses/LICENSE +0 -0
- {iqm_benchmarks-2.32.dist-info → iqm_benchmarks-2.34.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1000 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Direct Randomized Benchmarking.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import random
|
|
6
|
+
from time import strftime
|
|
7
|
+
from typing import Any, Dict, List, Literal, Optional, Sequence, Tuple, Type, cast
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from qiskit import ClassicalRegister, QuantumCircuit, transpile
|
|
11
|
+
from qiskit.quantum_info import Clifford, random_clifford
|
|
12
|
+
import xarray as xr
|
|
13
|
+
|
|
14
|
+
from iqm.benchmarks import (
|
|
15
|
+
Benchmark,
|
|
16
|
+
BenchmarkAnalysisResult,
|
|
17
|
+
BenchmarkCircuit,
|
|
18
|
+
BenchmarkRunResult,
|
|
19
|
+
CircuitGroup,
|
|
20
|
+
Circuits,
|
|
21
|
+
)
|
|
22
|
+
from iqm.benchmarks.benchmark import BenchmarkConfigurationBase
|
|
23
|
+
from iqm.benchmarks.benchmark_definition import (
|
|
24
|
+
BenchmarkObservation,
|
|
25
|
+
BenchmarkObservationIdentifier,
|
|
26
|
+
add_counts_to_dataset,
|
|
27
|
+
)
|
|
28
|
+
from iqm.benchmarks.logging_config import qcvv_logger
|
|
29
|
+
from iqm.benchmarks.randomized_benchmarking.randomized_benchmarking_common import (
|
|
30
|
+
compute_inverse_clifford,
|
|
31
|
+
edge_grab,
|
|
32
|
+
exponential_rb,
|
|
33
|
+
fit_decay_lmfit,
|
|
34
|
+
get_survival_probabilities,
|
|
35
|
+
import_native_gate_cliffords,
|
|
36
|
+
lmfit_minimizer,
|
|
37
|
+
plot_rb_decay,
|
|
38
|
+
relabel_qubits_array_from_zero,
|
|
39
|
+
submit_parallel_rb_job,
|
|
40
|
+
survival_probabilities_parallel,
|
|
41
|
+
)
|
|
42
|
+
from iqm.benchmarks.utils import (
|
|
43
|
+
get_iqm_backend,
|
|
44
|
+
retrieve_all_counts,
|
|
45
|
+
retrieve_all_job_metadata,
|
|
46
|
+
submit_execute,
|
|
47
|
+
timeit,
|
|
48
|
+
xrvariable_to_counts,
|
|
49
|
+
)
|
|
50
|
+
from iqm.qiskit_iqm import transpile_to_IQM
|
|
51
|
+
from iqm.qiskit_iqm.iqm_backend import IQMBackendBase
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@timeit
|
|
55
|
+
def generate_drb_circuits(
|
|
56
|
+
qubits: Sequence[int],
|
|
57
|
+
depth: int,
|
|
58
|
+
circ_samples: int,
|
|
59
|
+
backend_arg: IQMBackendBase | str,
|
|
60
|
+
density_2q_gates: float = 0.25,
|
|
61
|
+
two_qubit_gate_ensemble: Optional[Dict[str, float]] = None,
|
|
62
|
+
clifford_sqg_probability: float = 1.0,
|
|
63
|
+
sqg_gate_ensemble: Optional[Dict[str, float]] = None,
|
|
64
|
+
qiskit_optim_level: int = 1,
|
|
65
|
+
routing_method: Literal["basic", "lookahead", "stochastic", "sabre", "none"] = "basic",
|
|
66
|
+
) -> Dict[str, List[QuantumCircuit]]:
|
|
67
|
+
"""Generates lists of samples of Direct RB circuits, of structure:
|
|
68
|
+
Stabilizer preparation - Layers of canonical randomly sampled gates - Stabilizer measurement
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
qubits (List[int]): the qubits of the backend.
|
|
72
|
+
depth (int): the depth (number of canonical layers) of the circuit.
|
|
73
|
+
circ_samples (int): the number of circuit samples to generate.
|
|
74
|
+
backend_arg (IQMBackendBase | str): the backend.
|
|
75
|
+
density_2q_gates (float): the expected density of 2Q gates.
|
|
76
|
+
two_qubit_gate_ensemble (Optional[Dict[str, float]]): A dictionary with keys being str specifying 2Q gates, and values being corresponding probabilities.
|
|
77
|
+
* Default is None.
|
|
78
|
+
clifford_sqg_probability (float): Probability with which to uniformly sample Clifford 1Q gates.
|
|
79
|
+
* Default is 1.0.
|
|
80
|
+
sqg_gate_ensemble (Optional[Dict[str, float]]): A dictionary with keys being str specifying 1Q gates, and values being corresponding probabilities.
|
|
81
|
+
* Default is None.
|
|
82
|
+
qiskit_optim_level (int): Qiskit transpiler optimization level.
|
|
83
|
+
* Default is 1.
|
|
84
|
+
routing_method (Literal["basic", "lookahead", "stochastic", "sabre", "none"]): Qiskit transpiler routing method.
|
|
85
|
+
* Default is "basic".
|
|
86
|
+
Returns:
|
|
87
|
+
Dict[str, List[QuantumCircuit]]: a dictionary with keys "transpiled", "untranspiled" and values a list of respective DRB circuits.
|
|
88
|
+
"""
|
|
89
|
+
num_qubits = len(qubits)
|
|
90
|
+
|
|
91
|
+
# Retrieve backend
|
|
92
|
+
if isinstance(backend_arg, str):
|
|
93
|
+
retrieved_backend = get_iqm_backend(backend_arg)
|
|
94
|
+
else:
|
|
95
|
+
assert isinstance(backend_arg, IQMBackendBase)
|
|
96
|
+
retrieved_backend = backend_arg
|
|
97
|
+
|
|
98
|
+
# Check if backend includes MOVE gates and set coupling map
|
|
99
|
+
if retrieved_backend.has_resonators():
|
|
100
|
+
# All-to-all coupling map on the active qubits
|
|
101
|
+
effective_coupling_map = [[x, y] for x in qubits for y in qubits if x != y]
|
|
102
|
+
else:
|
|
103
|
+
effective_coupling_map = retrieved_backend.coupling_map
|
|
104
|
+
|
|
105
|
+
# Initialize the list of circuits
|
|
106
|
+
all_circuits = {}
|
|
107
|
+
drb_circuits_untranspiled: List[QuantumCircuit] = []
|
|
108
|
+
drb_circuits_transpiled: List[QuantumCircuit] = []
|
|
109
|
+
|
|
110
|
+
# simulator = AerSimulator(method=simulation_method)
|
|
111
|
+
|
|
112
|
+
for _ in range(circ_samples):
|
|
113
|
+
# Sample Clifford for stabilizer preparation
|
|
114
|
+
clifford_layer = random_clifford(num_qubits)
|
|
115
|
+
# NB: The DRB paper contains a more elaborated stabilizer compilation algorithm.
|
|
116
|
+
# Not having it WILL be an issue here for larger num qubits !
|
|
117
|
+
# Intended usage, however, is solely for 2-qubit DRB subroutines.
|
|
118
|
+
|
|
119
|
+
# Sample the layers using edge grab sampler - different samplers may be conditionally chosen here in the future
|
|
120
|
+
cycle_layers = edge_grab(
|
|
121
|
+
qubits,
|
|
122
|
+
depth,
|
|
123
|
+
backend_arg,
|
|
124
|
+
density_2q_gates,
|
|
125
|
+
two_qubit_gate_ensemble,
|
|
126
|
+
clifford_sqg_probability,
|
|
127
|
+
sqg_gate_ensemble,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Initialize the quantum circuit object
|
|
131
|
+
circ = QuantumCircuit(num_qubits)
|
|
132
|
+
|
|
133
|
+
# Add the edge Clifford
|
|
134
|
+
circ.compose(clifford_layer.to_instruction(), qubits=list(range(num_qubits)), inplace=True)
|
|
135
|
+
circ.barrier()
|
|
136
|
+
|
|
137
|
+
# Add the cycle layers
|
|
138
|
+
for k in range(depth):
|
|
139
|
+
circ.compose(cycle_layers[k], inplace=True)
|
|
140
|
+
circ.barrier()
|
|
141
|
+
|
|
142
|
+
# Add the inverse Clifford
|
|
143
|
+
circ.compose(
|
|
144
|
+
Clifford(circ.to_instruction().inverse()).to_instruction(), qubits=list(range(num_qubits)), inplace=True
|
|
145
|
+
)
|
|
146
|
+
# Similarly, here the DRB paper contains a stabilizer measurement, determined in a more elaborated way.
|
|
147
|
+
# The stabilizer measurement should effectively render the circuit to a Pauli gate (here always the identity).
|
|
148
|
+
# Would need to modify this for larger num qubits !
|
|
149
|
+
# Here, for 2-qubit DRB subroutines, it *should* suffice (in principle) to compile the inverse.
|
|
150
|
+
|
|
151
|
+
# Add measurements to untranspiled - after!
|
|
152
|
+
# THIS LINE IS ONLY NEEDED IF STABILIZER MEASUREMENT IS NOT TAKEN TO IDENTITY
|
|
153
|
+
# circ_untranspiled = transpile(Clifford(circ.copy()).to_circuit(), simulator)
|
|
154
|
+
circ_untranspiled = circ.copy()
|
|
155
|
+
circ_untranspiled.measure_all()
|
|
156
|
+
|
|
157
|
+
# Add measurements to transpiled - before!
|
|
158
|
+
circ.measure_all()
|
|
159
|
+
|
|
160
|
+
if retrieved_backend.has_resonators():
|
|
161
|
+
circ_transpiled = transpile_to_IQM(
|
|
162
|
+
circ,
|
|
163
|
+
backend=retrieved_backend,
|
|
164
|
+
coupling_map=effective_coupling_map,
|
|
165
|
+
optimization_level=qiskit_optim_level,
|
|
166
|
+
initial_layout=qubits,
|
|
167
|
+
routing_method=routing_method,
|
|
168
|
+
)
|
|
169
|
+
else:
|
|
170
|
+
circ_transpiled = transpile(
|
|
171
|
+
circ,
|
|
172
|
+
backend=retrieved_backend,
|
|
173
|
+
coupling_map=effective_coupling_map,
|
|
174
|
+
optimization_level=qiskit_optim_level,
|
|
175
|
+
initial_layout=qubits,
|
|
176
|
+
routing_method=routing_method,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
drb_circuits_untranspiled.append(circ_untranspiled)
|
|
180
|
+
drb_circuits_transpiled.append(circ_transpiled)
|
|
181
|
+
|
|
182
|
+
# Store the circuits
|
|
183
|
+
all_circuits.update(
|
|
184
|
+
{
|
|
185
|
+
"untranspiled": drb_circuits_untranspiled,
|
|
186
|
+
"transpiled": drb_circuits_transpiled,
|
|
187
|
+
}
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
return all_circuits
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@timeit
|
|
194
|
+
def generate_fixed_depth_parallel_drb_circuits( # pylint: disable=too-many-branches, too-many-statements
|
|
195
|
+
qubits_array: Sequence[Sequence[int]],
|
|
196
|
+
depth: int,
|
|
197
|
+
num_circuit_samples: int,
|
|
198
|
+
backend_arg: str | IQMBackendBase,
|
|
199
|
+
assigned_density_2q_gates: Dict[str, float],
|
|
200
|
+
assigned_two_qubit_gate_ensembles: Dict[str, Dict[str, float]],
|
|
201
|
+
assigned_clifford_sqg_probabilities: Dict[str, float],
|
|
202
|
+
assigned_sqg_gate_ensembles: Dict[str, Dict[str, float]],
|
|
203
|
+
cliffords_1q: Dict[str, QuantumCircuit],
|
|
204
|
+
cliffords_2q: Dict[str, QuantumCircuit],
|
|
205
|
+
qiskit_optim_level: int = 1,
|
|
206
|
+
routing_method: Literal["basic", "lookahead", "stochastic", "sabre", "none"] = "basic",
|
|
207
|
+
is_eplg: bool = False,
|
|
208
|
+
) -> Dict[str, List[QuantumCircuit]]:
|
|
209
|
+
"""Generates DRB circuits in parallel on multiple qubit layouts.
|
|
210
|
+
The circuits follow a layered pattern with barriers, taylored to measure EPLG (arXiv:2311.05933),
|
|
211
|
+
with layers of random Cliffords interleaved among sampled layers of 2Q gates and sequence inversion.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
qubits_array (Sequence[Sequence[int]]): The array of physical qubit layouts on which to generate parallel DRB circuits.
|
|
215
|
+
depth (int): The depth (number of canonical DRB layers) of the circuits.
|
|
216
|
+
num_circuit_samples (int): The number of DRB circuits to generate.
|
|
217
|
+
backend_arg (str | IQMBackendBase): The backend on which to generate the circuits.
|
|
218
|
+
assigned_density_2q_gates (Dict[str, float]): The expected densities of 2-qubit gates in the final circuits per qubit layout.
|
|
219
|
+
assigned_two_qubit_gate_ensembles (Dict[str, Dict[str, float]]): The two-qubit gate ensembles to use in the random DRB circuits per qubit layout.
|
|
220
|
+
assigned_clifford_sqg_probabilities (Dict[str, float]): Probability with which to uniformly sample Clifford 1Q gates per qubit layout.
|
|
221
|
+
assigned_sqg_gate_ensembles (Dict[str, Dict[str, float]]): A dictionary with keys being str specifying 1Q gates, and values being corresponding probabilities per qubit layout.
|
|
222
|
+
cliffords_1q (Dict[str, QuantumCircuit]): dictionary of 1-qubit Cliffords in terms of IQM-native r gates.
|
|
223
|
+
cliffords_2q (Dict[str, QuantumCircuit]): dictionary of 2-qubit Cliffords in terms of IQM-native r and CZ gates.
|
|
224
|
+
qiskit_optim_level (int): Qiskit transpiler optimization level.
|
|
225
|
+
* Defaults to 1.
|
|
226
|
+
routing_method (Literal["basic", "lookahead", "stochastic", "sabre", "none"]): Qiskit transpiler routing method.
|
|
227
|
+
* Default is "basic".
|
|
228
|
+
is_eplg (bool): Whether the circuits belong to an EPLG experiment.
|
|
229
|
+
* If True a single layer is generated.
|
|
230
|
+
* Default is False.
|
|
231
|
+
Returns:
|
|
232
|
+
Dict[str, List[QuantumCircuit]]: A dictionary of untranspiled and transpiled lists of parallel (simultaneous) DRB circuits.
|
|
233
|
+
"""
|
|
234
|
+
if isinstance(backend_arg, str):
|
|
235
|
+
backend = get_iqm_backend(backend_arg)
|
|
236
|
+
else:
|
|
237
|
+
backend = backend_arg
|
|
238
|
+
|
|
239
|
+
# Check if backend includes MOVE gates and set coupling map
|
|
240
|
+
flat_qubits_array = [x for y in qubits_array for x in y]
|
|
241
|
+
if backend.has_resonators():
|
|
242
|
+
# All-to-all coupling map on the active qubits
|
|
243
|
+
effective_coupling_map = [[x, y] for x in flat_qubits_array for y in flat_qubits_array if x != y]
|
|
244
|
+
is_circuit_native = False
|
|
245
|
+
else:
|
|
246
|
+
effective_coupling_map = backend.coupling_map
|
|
247
|
+
is_circuit_native = True
|
|
248
|
+
|
|
249
|
+
# Identify total amount of qubits
|
|
250
|
+
qubit_counts = [len(x) for x in qubits_array]
|
|
251
|
+
|
|
252
|
+
# Shuffle qubits_array: we don't want unnecessary qubit registers
|
|
253
|
+
shuffled_qubits_array = relabel_qubits_array_from_zero(qubits_array)
|
|
254
|
+
# The total amount of qubits the circuits will have
|
|
255
|
+
n_qubits = sum(qubit_counts)
|
|
256
|
+
|
|
257
|
+
# Get the keys of the Clifford dictionaries
|
|
258
|
+
clifford_1q_keys = list(cliffords_1q.keys())
|
|
259
|
+
# clifford_2q_keys = list(cliffords_2q.keys())
|
|
260
|
+
|
|
261
|
+
# Generate the circuit samples
|
|
262
|
+
# Initialize the list of circuits
|
|
263
|
+
all_circuits = {}
|
|
264
|
+
drb_circuits_untranspiled: List[QuantumCircuit] = []
|
|
265
|
+
drb_circuits_transpiled: List[QuantumCircuit] = []
|
|
266
|
+
|
|
267
|
+
cycle_layers = {}
|
|
268
|
+
|
|
269
|
+
# Generate the layer if EPLG:
|
|
270
|
+
# this will be repeated in all samples (and all depths)! So can be done outside the loop over circuit samples.
|
|
271
|
+
if is_eplg:
|
|
272
|
+
for q_idx, q in enumerate(shuffled_qubits_array):
|
|
273
|
+
original_qubits = str(qubits_array[q_idx])
|
|
274
|
+
if any(x != "CZGate" for x in assigned_two_qubit_gate_ensembles[original_qubits].keys()):
|
|
275
|
+
is_circuit_native = False
|
|
276
|
+
cycle_layers[str(q)] = edge_grab(
|
|
277
|
+
qubits_array[q_idx],
|
|
278
|
+
depth,
|
|
279
|
+
backend_arg,
|
|
280
|
+
assigned_density_2q_gates[original_qubits],
|
|
281
|
+
assigned_two_qubit_gate_ensembles[original_qubits],
|
|
282
|
+
assigned_clifford_sqg_probabilities[original_qubits],
|
|
283
|
+
assigned_sqg_gate_ensembles[original_qubits],
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
for _ in range(num_circuit_samples):
|
|
287
|
+
# Initialize the quantum circuit object
|
|
288
|
+
circ = QuantumCircuit(n_qubits)
|
|
289
|
+
|
|
290
|
+
# Generate small circuits to track inverses
|
|
291
|
+
local_circs = {str(q): QuantumCircuit(len(q)) for q in shuffled_qubits_array}
|
|
292
|
+
|
|
293
|
+
# Sample the layers if EPLG is False.
|
|
294
|
+
if not is_eplg:
|
|
295
|
+
for q_idx, q in enumerate(shuffled_qubits_array):
|
|
296
|
+
original_qubits = str(qubits_array[q_idx])
|
|
297
|
+
if any(x != "CZGate" for x in assigned_two_qubit_gate_ensembles[original_qubits].keys()):
|
|
298
|
+
is_circuit_native = False
|
|
299
|
+
cycle_layers[str(q)] = edge_grab(
|
|
300
|
+
qubits_array[q_idx],
|
|
301
|
+
depth,
|
|
302
|
+
backend_arg,
|
|
303
|
+
assigned_density_2q_gates[original_qubits],
|
|
304
|
+
assigned_two_qubit_gate_ensembles[original_qubits],
|
|
305
|
+
assigned_clifford_sqg_probabilities[original_qubits],
|
|
306
|
+
assigned_sqg_gate_ensembles[original_qubits],
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
# Add the cycle layers
|
|
310
|
+
for k in range(depth):
|
|
311
|
+
# Add the edge Clifford
|
|
312
|
+
# The DRB paper here contains a general stabilizer preparation.
|
|
313
|
+
# We will stick to 1Q Clifford gates for now.
|
|
314
|
+
for q in shuffled_qubits_array:
|
|
315
|
+
for idx, i in enumerate(q):
|
|
316
|
+
rand_key = random.choice(clifford_1q_keys)
|
|
317
|
+
rand_clif_1q = cast(dict, cliffords_1q)[rand_key]
|
|
318
|
+
# rand_clif = random_clifford(1)
|
|
319
|
+
circ.compose(rand_clif_1q, qubits=[i], inplace=True)
|
|
320
|
+
local_circs[str(q)].compose(rand_clif_1q, qubits=[idx], inplace=True)
|
|
321
|
+
circ.barrier()
|
|
322
|
+
|
|
323
|
+
for q in shuffled_qubits_array:
|
|
324
|
+
circ.compose(cycle_layers[str(q)][k], qubits=q, inplace=True)
|
|
325
|
+
local_circs[str(q)].compose(cycle_layers[str(q)][k], inplace=True)
|
|
326
|
+
circ.barrier()
|
|
327
|
+
|
|
328
|
+
# Add the inverse Clifford
|
|
329
|
+
for q in shuffled_qubits_array:
|
|
330
|
+
clifford_dict = cliffords_1q if len(q) == 1 else cliffords_2q
|
|
331
|
+
circ.compose(
|
|
332
|
+
compute_inverse_clifford(local_circs[str(q)], clifford_dict),
|
|
333
|
+
qubits=q,
|
|
334
|
+
inplace=True,
|
|
335
|
+
)
|
|
336
|
+
circ.barrier()
|
|
337
|
+
for q_idx, q in enumerate(shuffled_qubits_array):
|
|
338
|
+
original_qubits = str(qubits_array[q_idx])
|
|
339
|
+
local_register = ClassicalRegister(len(q), original_qubits)
|
|
340
|
+
circ.add_register(local_register)
|
|
341
|
+
circ.measure(q, local_register)
|
|
342
|
+
# Similarly, here the DRB paper contains a stabilizer measurement, determined in a more elaborated way.
|
|
343
|
+
# The stabilizer measurement should effectively render the circuit to a Pauli gate (here always the identity).
|
|
344
|
+
# Would need to modify this for larger num qubits !
|
|
345
|
+
# Here, for 2-qubit DRB subroutines, it *should* suffice (in principle) to compile the inverse.
|
|
346
|
+
|
|
347
|
+
circ_untranspiled = circ.copy()
|
|
348
|
+
|
|
349
|
+
if is_circuit_native: # Simply compose into a larger circuit
|
|
350
|
+
circ_transpiled = QuantumCircuit(backend.num_qubits)
|
|
351
|
+
circ_transpiled.compose(circ_untranspiled, qubits=flat_qubits_array, inplace=True)
|
|
352
|
+
else: # Do full qiskit transpile
|
|
353
|
+
if backend.has_resonators():
|
|
354
|
+
circ_transpiled = transpile_to_IQM(
|
|
355
|
+
circ,
|
|
356
|
+
backend=backend,
|
|
357
|
+
coupling_map=effective_coupling_map,
|
|
358
|
+
optimization_level=qiskit_optim_level,
|
|
359
|
+
initial_layout=flat_qubits_array,
|
|
360
|
+
routing_method=routing_method,
|
|
361
|
+
)
|
|
362
|
+
else:
|
|
363
|
+
circ_transpiled = transpile(
|
|
364
|
+
circ,
|
|
365
|
+
backend=backend,
|
|
366
|
+
coupling_map=effective_coupling_map,
|
|
367
|
+
optimization_level=qiskit_optim_level,
|
|
368
|
+
initial_layout=flat_qubits_array,
|
|
369
|
+
routing_method=routing_method,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
drb_circuits_untranspiled.append(circ_untranspiled)
|
|
373
|
+
drb_circuits_transpiled.append(circ_transpiled)
|
|
374
|
+
|
|
375
|
+
# Store the circuits
|
|
376
|
+
all_circuits.update(
|
|
377
|
+
{
|
|
378
|
+
"untranspiled": drb_circuits_untranspiled,
|
|
379
|
+
"transpiled": drb_circuits_transpiled,
|
|
380
|
+
}
|
|
381
|
+
)
|
|
382
|
+
return all_circuits
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def direct_rb_analysis(run: BenchmarkRunResult) -> BenchmarkAnalysisResult:
|
|
386
|
+
"""Direct RB analysis function
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
run (BenchmarkRunResult): The result of the benchmark run.
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
AnalysisResult corresponding to DRB.
|
|
393
|
+
"""
|
|
394
|
+
|
|
395
|
+
dataset = run.dataset.copy(deep=True)
|
|
396
|
+
observations: list[BenchmarkObservation] = []
|
|
397
|
+
obs_dict = {}
|
|
398
|
+
plots = {}
|
|
399
|
+
|
|
400
|
+
is_parallel_execution = dataset.attrs["parallel_execution"]
|
|
401
|
+
all_qubits_array = dataset.attrs["qubits_array"]
|
|
402
|
+
depths = dataset.attrs["depths"]
|
|
403
|
+
|
|
404
|
+
num_circuit_samples = dataset.attrs["num_circuit_samples"]
|
|
405
|
+
|
|
406
|
+
density_2q_gates = dataset.attrs["densities_2q_gates"]
|
|
407
|
+
two_qubit_gate_ensemble = dataset.attrs["two_qubit_gate_ensembles"]
|
|
408
|
+
|
|
409
|
+
is_eplg = dataset.attrs["is_eplg"]
|
|
410
|
+
|
|
411
|
+
all_noisy_counts: Dict[str, Dict[int, List[Dict[str, int]]]] = {}
|
|
412
|
+
|
|
413
|
+
if isinstance(all_qubits_array[0][0], int):
|
|
414
|
+
wrapped_all_qubits_array = [all_qubits_array]
|
|
415
|
+
flat_all_qubits_array_reshaped = [x for y in wrapped_all_qubits_array for x in y]
|
|
416
|
+
else:
|
|
417
|
+
wrapped_all_qubits_array = all_qubits_array
|
|
418
|
+
flat_all_qubits_array_reshaped = [x for y in all_qubits_array for x in y]
|
|
419
|
+
polarizations: Dict[str, Dict[int, List[float]]] = {str(q): {} for q in flat_all_qubits_array_reshaped}
|
|
420
|
+
|
|
421
|
+
for q_array_idx, qubits_array in enumerate(wrapped_all_qubits_array):
|
|
422
|
+
if is_parallel_execution:
|
|
423
|
+
qcvv_logger.info(f"Post-processing parallel Direct RB on qubits {qubits_array}.")
|
|
424
|
+
all_noisy_counts[str(qubits_array)] = {}
|
|
425
|
+
for depth in depths:
|
|
426
|
+
identifier = f"qubits_{str(qubits_array)}_depth_{str(depth)}"
|
|
427
|
+
all_noisy_counts[str(qubits_array)][depth] = xrvariable_to_counts(
|
|
428
|
+
dataset, identifier, num_circuit_samples
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
qcvv_logger.info(f"Depth {depth}")
|
|
432
|
+
|
|
433
|
+
# Retrieve the marginalized survival probabilities
|
|
434
|
+
all_survival_probabilities = survival_probabilities_parallel(
|
|
435
|
+
qubits_array, all_noisy_counts[str(qubits_array)][depth], separate_registers=True
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
# The marginalized survival probabilities will be arranged by qubit layouts
|
|
439
|
+
for qubits_str in all_survival_probabilities.keys():
|
|
440
|
+
polarizations[qubits_str][depth] = all_survival_probabilities[qubits_str]
|
|
441
|
+
# Remaining analysis is the same regardless of whether execution was in parallel or sequential
|
|
442
|
+
else: # sequential
|
|
443
|
+
qcvv_logger.info(f"Post-processing sequential Direct RB for qubits {qubits_array}")
|
|
444
|
+
for q in qubits_array:
|
|
445
|
+
all_noisy_counts[str(q)] = {}
|
|
446
|
+
num_qubits = len(q)
|
|
447
|
+
polarizations[str(q)] = {}
|
|
448
|
+
for depth in depths:
|
|
449
|
+
identifier = f"qubits_{str(q)}_depth_{str(depth)}"
|
|
450
|
+
all_noisy_counts[str(q)][depth] = xrvariable_to_counts(dataset, identifier, num_circuit_samples)
|
|
451
|
+
|
|
452
|
+
qcvv_logger.info(f"Qubits {q} and depth {depth}")
|
|
453
|
+
polarizations[str(q)][depth] = get_survival_probabilities(
|
|
454
|
+
num_qubits, all_noisy_counts[str(q)][depth]
|
|
455
|
+
)
|
|
456
|
+
# Remaining analysis is the same regardless of whether execution was in parallel or sequential
|
|
457
|
+
|
|
458
|
+
# All remaining (fitting & plotting) is done per qubit layout
|
|
459
|
+
for qubits_idx, qubits in enumerate(qubits_array):
|
|
460
|
+
# Fit decays
|
|
461
|
+
list_of_polarizations = list(polarizations[str(qubits)].values())
|
|
462
|
+
fit_data, fit_parameters = fit_decay_lmfit(exponential_rb, qubits, list_of_polarizations, "drb")
|
|
463
|
+
rb_fit_results = lmfit_minimizer(fit_parameters, fit_data, depths, exponential_rb)
|
|
464
|
+
|
|
465
|
+
average_polarizations = {d: np.mean(polarizations[str(qubits)][d]) for d in depths}
|
|
466
|
+
stddevs_from_mean = {
|
|
467
|
+
d: np.std(polarizations[str(qubits)][d]) / np.sqrt(num_circuit_samples) for d in depths
|
|
468
|
+
}
|
|
469
|
+
popt = {
|
|
470
|
+
"amplitude": rb_fit_results.params["amplitude_1"],
|
|
471
|
+
"offset": rb_fit_results.params["offset_1"],
|
|
472
|
+
"decay_rate": rb_fit_results.params["p_drb"],
|
|
473
|
+
}
|
|
474
|
+
fidelity = rb_fit_results.params["fidelity_drb"]
|
|
475
|
+
|
|
476
|
+
processed_results = {
|
|
477
|
+
"average_gate_fidelity": {"value": fidelity.value, "uncertainty": fidelity.stderr},
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
dataset.attrs[q_array_idx].update(
|
|
481
|
+
{
|
|
482
|
+
qubits_idx: {
|
|
483
|
+
"decay_rate": {"value": popt["decay_rate"].value, "uncertainty": popt["decay_rate"].stderr},
|
|
484
|
+
"fit_amplitude": {"value": popt["amplitude"].value, "uncertainty": popt["amplitude"].stderr},
|
|
485
|
+
"fit_offset": {"value": popt["offset"].value, "uncertainty": popt["offset"].stderr},
|
|
486
|
+
"polarizations": polarizations[str(qubits)],
|
|
487
|
+
"average_polarization_nominal_values": average_polarizations,
|
|
488
|
+
"average_polarization_stderr": stddevs_from_mean,
|
|
489
|
+
"fitting_method": str(rb_fit_results.method),
|
|
490
|
+
"num_function_evals": int(rb_fit_results.nfev),
|
|
491
|
+
"data_points": int(rb_fit_results.ndata),
|
|
492
|
+
"num_variables": int(rb_fit_results.nvarys),
|
|
493
|
+
"chi_square": float(rb_fit_results.chisqr),
|
|
494
|
+
"reduced_chi_square": float(rb_fit_results.redchi),
|
|
495
|
+
"Akaike_info_crit": float(rb_fit_results.aic),
|
|
496
|
+
"Bayesian_info_crit": float(rb_fit_results.bic),
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
obs_dict.update({qubits_idx: processed_results})
|
|
502
|
+
observations.extend(
|
|
503
|
+
[
|
|
504
|
+
BenchmarkObservation(
|
|
505
|
+
name=key,
|
|
506
|
+
identifier=BenchmarkObservationIdentifier(qubits),
|
|
507
|
+
value=values["value"],
|
|
508
|
+
uncertainty=values["uncertainty"],
|
|
509
|
+
)
|
|
510
|
+
for key, values in processed_results.items()
|
|
511
|
+
]
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
# Generate individual decay plots
|
|
515
|
+
fig_name, fig = plot_rb_decay(
|
|
516
|
+
identifier="drb",
|
|
517
|
+
qubits_array=[qubits],
|
|
518
|
+
dataset=dataset,
|
|
519
|
+
observations=obs_dict,
|
|
520
|
+
mrb_2q_density=density_2q_gates, # Misnomer coming from MRB - ignore
|
|
521
|
+
mrb_2q_ensemble=two_qubit_gate_ensemble,
|
|
522
|
+
is_eplg=is_eplg,
|
|
523
|
+
)
|
|
524
|
+
plots[fig_name] = fig
|
|
525
|
+
|
|
526
|
+
return BenchmarkAnalysisResult(dataset=dataset, observations=observations, plots=plots)
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
class DirectRandomizedBenchmarking(Benchmark):
|
|
530
|
+
"""Direct RB estimates the fidelity of layers of canonical gates"""
|
|
531
|
+
|
|
532
|
+
analysis_function = staticmethod(direct_rb_analysis)
|
|
533
|
+
|
|
534
|
+
name: str = "direct_rb"
|
|
535
|
+
|
|
536
|
+
def __init__(self, backend_arg: IQMBackendBase | str, configuration: "DirectRBConfiguration"):
|
|
537
|
+
"""Construct the DirectRandomizedBenchmarking class
|
|
538
|
+
|
|
539
|
+
Args:
|
|
540
|
+
backend_arg (IQMBackendBase | str): _description_
|
|
541
|
+
configuration (DirectRBConfiguration): _description_
|
|
542
|
+
"""
|
|
543
|
+
super().__init__(backend_arg, configuration)
|
|
544
|
+
|
|
545
|
+
# EXPERIMENT
|
|
546
|
+
self.backend_configuration_name = backend_arg if isinstance(backend_arg, str) else backend_arg.name
|
|
547
|
+
|
|
548
|
+
self.qubits_array = configuration.qubits_array
|
|
549
|
+
self.is_eplg = configuration.is_eplg
|
|
550
|
+
|
|
551
|
+
# Override if EPLG is True but parallel_execution was set to False
|
|
552
|
+
if self.is_eplg and not configuration.parallel_execution:
|
|
553
|
+
configuration.parallel_execution = True
|
|
554
|
+
|
|
555
|
+
self.parallel_execution = configuration.parallel_execution
|
|
556
|
+
self.depths = configuration.depths
|
|
557
|
+
self.num_circuit_samples = configuration.num_circuit_samples
|
|
558
|
+
|
|
559
|
+
self.two_qubit_gate_ensembles = configuration.two_qubit_gate_ensembles
|
|
560
|
+
self.densities_2q_gates = configuration.densities_2q_gates
|
|
561
|
+
self.clifford_sqg_probabilities = configuration.clifford_sqg_probabilities
|
|
562
|
+
self.sqg_gate_ensembles = configuration.sqg_gate_ensembles
|
|
563
|
+
|
|
564
|
+
self.qiskit_optim_level = configuration.qiskit_optim_level
|
|
565
|
+
|
|
566
|
+
self.session_timestamp = strftime("%Y%m%d-%H%M%S")
|
|
567
|
+
self.execution_timestamp = ""
|
|
568
|
+
|
|
569
|
+
# Initialize the variable to contain the circuits for each layout
|
|
570
|
+
self.untranspiled_circuits = BenchmarkCircuit("untranspiled_circuits")
|
|
571
|
+
self.transpiled_circuits = BenchmarkCircuit("transpiled_circuits")
|
|
572
|
+
|
|
573
|
+
def add_all_meta_to_dataset(self, dataset: xr.Dataset):
|
|
574
|
+
"""Adds all configuration metadata and circuits to the dataset variable
|
|
575
|
+
|
|
576
|
+
Args:
|
|
577
|
+
dataset (xr.Dataset): The xarray dataset
|
|
578
|
+
"""
|
|
579
|
+
dataset.attrs["session_timestamp"] = self.session_timestamp
|
|
580
|
+
dataset.attrs["execution_timestamp"] = self.execution_timestamp
|
|
581
|
+
dataset.attrs["backend_configuration_name"] = self.backend_configuration_name
|
|
582
|
+
dataset.attrs["backend_name"] = self.backend.name
|
|
583
|
+
|
|
584
|
+
for key, value in self.configuration:
|
|
585
|
+
if key == "benchmark": # Avoid saving the class object
|
|
586
|
+
dataset.attrs[key] = value.name
|
|
587
|
+
else:
|
|
588
|
+
dataset.attrs[key] = value
|
|
589
|
+
# Defined outside configuration - if any
|
|
590
|
+
dataset.attrs["two_qubit_gate_ensembles"] = self.two_qubit_gate_ensembles
|
|
591
|
+
dataset.attrs["densities_2q_gates"] = self.densities_2q_gates
|
|
592
|
+
dataset.attrs["clifford_sqg_probabilities"] = self.clifford_sqg_probabilities
|
|
593
|
+
dataset.attrs["sqg_gate_ensembles"] = self.sqg_gate_ensembles
|
|
594
|
+
|
|
595
|
+
def assign_inputs_to_qubits(self): # pylint: disable=too-many-branches, too-many-statements
|
|
596
|
+
"""Assigns all DRB inputs (Optional[Sequence[Any]]) to input qubit layouts."""
|
|
597
|
+
# Depths - can be modified as in MRB to be qubit layout-dependent
|
|
598
|
+
assigned_drb_depths = self.depths
|
|
599
|
+
|
|
600
|
+
if isinstance(self.qubits_array[0][0], int):
|
|
601
|
+
wrapped_qubits_array = [self.qubits_array]
|
|
602
|
+
flat_all_qubits = [x for y in wrapped_qubits_array for x in y]
|
|
603
|
+
else:
|
|
604
|
+
flat_all_qubits = [x for y in self.qubits_array for x in y]
|
|
605
|
+
|
|
606
|
+
# 2Q gate ensemble
|
|
607
|
+
if self.two_qubit_gate_ensembles is None:
|
|
608
|
+
# Assign native 2Qg with probability 1.0 - this is also default for EPLG
|
|
609
|
+
assigned_two_qubit_gate_ensembles = {str(q): {"CZGate": 1.0} for q in flat_all_qubits}
|
|
610
|
+
else:
|
|
611
|
+
if len(self.two_qubit_gate_ensembles) != len(flat_all_qubits):
|
|
612
|
+
if len(self.two_qubit_gate_ensembles) != 1:
|
|
613
|
+
qcvv_logger.warning(
|
|
614
|
+
f"The amount of 2Q gate ensembles ({len(self.two_qubit_gate_ensembles)}) is not the same "
|
|
615
|
+
f"as the total amount of qubit layout configurations ({len(flat_all_qubits)}):\n\tWill assign to all the first "
|
|
616
|
+
f"configuration: {self.two_qubit_gate_ensembles[0]} !"
|
|
617
|
+
)
|
|
618
|
+
assigned_two_qubit_gate_ensembles = {str(q): self.two_qubit_gate_ensembles[0] for q in flat_all_qubits}
|
|
619
|
+
else:
|
|
620
|
+
assigned_two_qubit_gate_ensembles = {
|
|
621
|
+
str(q): self.two_qubit_gate_ensembles[q_idx] for q_idx, q in enumerate(flat_all_qubits)
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
# Density 2Q gates
|
|
625
|
+
if self.densities_2q_gates is None and self.is_eplg:
|
|
626
|
+
# For EPLG, with density 2Qg of 0.5, the edge_grab will sample 2Qg with probability 1.0
|
|
627
|
+
assigned_density_2q_gates = {str(q): 0.5 for q in flat_all_qubits}
|
|
628
|
+
elif self.densities_2q_gates is None:
|
|
629
|
+
assigned_density_2q_gates = {str(q): 0.25 for q in flat_all_qubits}
|
|
630
|
+
else:
|
|
631
|
+
if len(self.densities_2q_gates) != len(flat_all_qubits):
|
|
632
|
+
if len(self.densities_2q_gates) != 1:
|
|
633
|
+
qcvv_logger.warning(
|
|
634
|
+
f"The amount of 2Q gate densities ({len(self.densities_2q_gates)}) is not the same "
|
|
635
|
+
f"as the amount of all qubit layout configurations ({len(flat_all_qubits)}):\n\tWill assign to all the first "
|
|
636
|
+
f"configuration: {self.densities_2q_gates[0]} !"
|
|
637
|
+
)
|
|
638
|
+
assigned_density_2q_gates = {str(q): self.densities_2q_gates[0] for q in flat_all_qubits}
|
|
639
|
+
else:
|
|
640
|
+
assigned_density_2q_gates = {
|
|
641
|
+
str(q): self.densities_2q_gates[q_idx] for q_idx, q in enumerate(flat_all_qubits)
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
# clifford_sqg_probabilities
|
|
645
|
+
if self.clifford_sqg_probabilities is None and self.is_eplg:
|
|
646
|
+
assigned_clifford_sqg_probabilities = {str(q): 0.0 for q in flat_all_qubits}
|
|
647
|
+
elif self.clifford_sqg_probabilities is None:
|
|
648
|
+
assigned_clifford_sqg_probabilities = {str(q): 1.0 for q in flat_all_qubits}
|
|
649
|
+
else:
|
|
650
|
+
if len(self.clifford_sqg_probabilities) != len(flat_all_qubits):
|
|
651
|
+
if len(self.clifford_sqg_probabilities) != 1:
|
|
652
|
+
qcvv_logger.warning(
|
|
653
|
+
f"The amount of Clifford 1Q gate sampling probabilities ({len(self.clifford_sqg_probabilities)}) is not the same "
|
|
654
|
+
f"as the amount of all qubit layout configurations ({len(flat_all_qubits)}):\n\tWill assign to all the first "
|
|
655
|
+
f"configuration: {self.clifford_sqg_probabilities[0]} !"
|
|
656
|
+
)
|
|
657
|
+
assigned_clifford_sqg_probabilities = {
|
|
658
|
+
str(q): self.clifford_sqg_probabilities[0] for q in flat_all_qubits
|
|
659
|
+
}
|
|
660
|
+
else:
|
|
661
|
+
assigned_clifford_sqg_probabilities = {
|
|
662
|
+
str(q): self.clifford_sqg_probabilities[q_idx] for q_idx, q in enumerate(flat_all_qubits)
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
# sqg_gate_ensembles
|
|
666
|
+
if self.sqg_gate_ensembles is not None:
|
|
667
|
+
if len(self.sqg_gate_ensembles) != len(flat_all_qubits):
|
|
668
|
+
if len(self.sqg_gate_ensembles) != 1:
|
|
669
|
+
qcvv_logger.warning(
|
|
670
|
+
f"The amount of 1Q gate ensembles ({len(self.sqg_gate_ensembles)}) is not the same "
|
|
671
|
+
f"as the amount of all qubit layout configurations ({len(flat_all_qubits)}):\n"
|
|
672
|
+
f"\tWill assign to all the first configuration: {self.sqg_gate_ensembles[0]} !"
|
|
673
|
+
)
|
|
674
|
+
assigned_sqg_gate_ensembles = {str(q): self.sqg_gate_ensembles[0] for q in flat_all_qubits}
|
|
675
|
+
else:
|
|
676
|
+
assigned_sqg_gate_ensembles = {
|
|
677
|
+
str(q): self.sqg_gate_ensembles[q_idx] for q_idx, q in enumerate(flat_all_qubits)
|
|
678
|
+
}
|
|
679
|
+
elif self.sqg_gate_ensembles is None and self.is_eplg: # No Cliffords and no 1Q gates in Cycle Layers
|
|
680
|
+
assigned_sqg_gate_ensembles = {str(q): {"IGate": 1.0} for q in flat_all_qubits}
|
|
681
|
+
elif self.sqg_gate_ensembles is None and assigned_clifford_sqg_probabilities == {
|
|
682
|
+
str(q): 1.0 for q in flat_all_qubits
|
|
683
|
+
}:
|
|
684
|
+
assigned_sqg_gate_ensembles = {str(q): None for q in flat_all_qubits}
|
|
685
|
+
# None (together with condition of clifford sqg probabilities 1) implies that the edge grab algorithm
|
|
686
|
+
# will only sample 1Q Clifford gates as 1Q gates when forming Cycle Layers
|
|
687
|
+
else:
|
|
688
|
+
# In this case, assign the rest to be either Cliffords or HGate with complementary probabilities
|
|
689
|
+
# Choice of HGate is arbitrary, could be any other (Clifford, unless looking for some danger) 1Q gate
|
|
690
|
+
assigned_sqg_gate_ensembles = {
|
|
691
|
+
str(q): {"HGate": 1.0 - assigned_clifford_sqg_probabilities[str(q)]} for q in flat_all_qubits
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
# Reset the configuration values to store in dataset
|
|
695
|
+
self.two_qubit_gate_ensembles = assigned_two_qubit_gate_ensembles
|
|
696
|
+
self.densities_2q_gates = assigned_density_2q_gates
|
|
697
|
+
self.clifford_sqg_probabilities = assigned_clifford_sqg_probabilities
|
|
698
|
+
self.sqg_gate_ensembles = assigned_sqg_gate_ensembles
|
|
699
|
+
|
|
700
|
+
return (
|
|
701
|
+
assigned_drb_depths,
|
|
702
|
+
assigned_two_qubit_gate_ensembles,
|
|
703
|
+
assigned_density_2q_gates,
|
|
704
|
+
assigned_clifford_sqg_probabilities,
|
|
705
|
+
assigned_sqg_gate_ensembles,
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
def submit_single_drb_job(
|
|
709
|
+
self,
|
|
710
|
+
backend_arg: IQMBackendBase,
|
|
711
|
+
qubits: Sequence[int],
|
|
712
|
+
depth: int,
|
|
713
|
+
sorted_transpiled_circuit_dicts: Dict[Tuple[int, ...], List[QuantumCircuit]],
|
|
714
|
+
) -> Dict[str, Any]:
|
|
715
|
+
"""
|
|
716
|
+
Submit fixed-depth DRB jobs for execution in the specified IQMBackend
|
|
717
|
+
|
|
718
|
+
Args:
|
|
719
|
+
backend_arg (IQMBackendBase): the IQM backend to submit the job to
|
|
720
|
+
qubits (Sequence[int]): the qubits to identify the submitted job
|
|
721
|
+
depth (int): the depth (number of canonical layers) of the circuits to identify the submitted job
|
|
722
|
+
sorted_transpiled_circuit_dicts (Dict[Tuple[int, ...], List[QuantumCircuit]]): A dictionary containing all MRB circuits
|
|
723
|
+
Returns:
|
|
724
|
+
Dict with qubit layout, depth, submitted job objects, and submission time
|
|
725
|
+
"""
|
|
726
|
+
# Submit
|
|
727
|
+
# Send to execute on backend
|
|
728
|
+
execution_jobs, time_submit = submit_execute(
|
|
729
|
+
sorted_transpiled_circuit_dicts,
|
|
730
|
+
backend_arg,
|
|
731
|
+
self.shots,
|
|
732
|
+
self.calset_id,
|
|
733
|
+
max_gates_per_batch=self.max_gates_per_batch,
|
|
734
|
+
max_circuits_per_batch=self.configuration.max_circuits_per_batch,
|
|
735
|
+
)
|
|
736
|
+
drb_submit_results = {
|
|
737
|
+
"qubits": qubits,
|
|
738
|
+
"depth": depth,
|
|
739
|
+
"jobs": execution_jobs,
|
|
740
|
+
"time_submit": time_submit,
|
|
741
|
+
}
|
|
742
|
+
return drb_submit_results
|
|
743
|
+
|
|
744
|
+
def execute(self, backend: IQMBackendBase) -> xr.Dataset: # pylint: disable=too-many-statements
|
|
745
|
+
"""Executes the Direct Randomized Benchmarking benchmark.
|
|
746
|
+
|
|
747
|
+
Args:
|
|
748
|
+
backend (IQMBackendBase): The IQM backend to execute the benchmark on
|
|
749
|
+
|
|
750
|
+
Returns:
|
|
751
|
+
xr.Dataset: Dataset containing benchmark results and metadata
|
|
752
|
+
"""
|
|
753
|
+
self.execution_timestamp = strftime("%Y%m%d-%H%M%S")
|
|
754
|
+
|
|
755
|
+
dataset = xr.Dataset()
|
|
756
|
+
|
|
757
|
+
(
|
|
758
|
+
assigned_drb_depths,
|
|
759
|
+
assigned_two_qubit_gate_ensembles,
|
|
760
|
+
assigned_density_2q_gates,
|
|
761
|
+
assigned_clifford_sqg_probabilities,
|
|
762
|
+
assigned_sqg_gate_ensembles,
|
|
763
|
+
) = self.assign_inputs_to_qubits()
|
|
764
|
+
|
|
765
|
+
self.add_all_meta_to_dataset(dataset)
|
|
766
|
+
|
|
767
|
+
clifford_1q_dict, clifford_2q_dict = import_native_gate_cliffords()
|
|
768
|
+
|
|
769
|
+
# Submit jobs for all qubit layouts
|
|
770
|
+
all_drb_jobs: List[Dict[str, Any]] = []
|
|
771
|
+
time_circuit_generation: Dict[str, float] = {}
|
|
772
|
+
|
|
773
|
+
# Auxiliary dict from str(qubits) to indices
|
|
774
|
+
qubit_idx: Dict[str, Any] = {}
|
|
775
|
+
|
|
776
|
+
# Main execution
|
|
777
|
+
if isinstance(self.qubits_array[0][0], int):
|
|
778
|
+
# If the qubits_array is a single qubit layout, wrap it in a list (so that the loop below proceeds at the right level)
|
|
779
|
+
wrapped_qubits_array = [self.qubits_array]
|
|
780
|
+
else:
|
|
781
|
+
wrapped_qubits_array = cast(
|
|
782
|
+
List[Sequence[Sequence[int]] | Sequence[Sequence[Sequence[int]]]], self.qubits_array
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
for qubits_seq_idx, loop_qubits_sequence in enumerate(wrapped_qubits_array):
|
|
786
|
+
if self.parallel_execution:
|
|
787
|
+
# Take the whole loop_qubits_sequence and do DRB in parallel on each loop_qubits_sequence element
|
|
788
|
+
parallel_drb_circuits = {}
|
|
789
|
+
qcvv_logger.info(
|
|
790
|
+
f"Executing parallel Direct RB on qubits {loop_qubits_sequence} (group {qubits_seq_idx+1}/{len(wrapped_qubits_array)})."
|
|
791
|
+
f" Will generate and submit all {self.num_circuit_samples} DRB circuits"
|
|
792
|
+
f" for each depth {self.depths}"
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
time_circuit_generation[str(loop_qubits_sequence)] = 0
|
|
796
|
+
# Generate and submit all circuits
|
|
797
|
+
for depth in self.depths:
|
|
798
|
+
qcvv_logger.info(f"Depth {depth}")
|
|
799
|
+
parallel_drb_circuits[depth], elapsed_time = generate_fixed_depth_parallel_drb_circuits(
|
|
800
|
+
qubits_array=loop_qubits_sequence,
|
|
801
|
+
depth=depth,
|
|
802
|
+
num_circuit_samples=self.num_circuit_samples,
|
|
803
|
+
backend_arg=backend,
|
|
804
|
+
assigned_density_2q_gates=assigned_density_2q_gates,
|
|
805
|
+
assigned_two_qubit_gate_ensembles=assigned_two_qubit_gate_ensembles,
|
|
806
|
+
assigned_clifford_sqg_probabilities=assigned_clifford_sqg_probabilities,
|
|
807
|
+
assigned_sqg_gate_ensembles=assigned_sqg_gate_ensembles,
|
|
808
|
+
cliffords_1q=clifford_1q_dict,
|
|
809
|
+
cliffords_2q=clifford_2q_dict,
|
|
810
|
+
qiskit_optim_level=self.qiskit_optim_level,
|
|
811
|
+
routing_method=self.routing_method,
|
|
812
|
+
is_eplg=self.is_eplg,
|
|
813
|
+
)
|
|
814
|
+
time_circuit_generation[str(loop_qubits_sequence)] += elapsed_time
|
|
815
|
+
|
|
816
|
+
# Submit all
|
|
817
|
+
flat_qubits_array = [x for y in loop_qubits_sequence for x in y]
|
|
818
|
+
sorted_transpiled_qc_list = {tuple(flat_qubits_array): parallel_drb_circuits[depth]["transpiled"]}
|
|
819
|
+
all_drb_jobs.append(
|
|
820
|
+
submit_parallel_rb_job(
|
|
821
|
+
backend,
|
|
822
|
+
loop_qubits_sequence,
|
|
823
|
+
depth,
|
|
824
|
+
sorted_transpiled_qc_list,
|
|
825
|
+
shots=self.shots,
|
|
826
|
+
calset_id=self.calset_id,
|
|
827
|
+
max_gates_per_batch=self.max_gates_per_batch,
|
|
828
|
+
max_circuits_per_batch=self.configuration.max_circuits_per_batch,
|
|
829
|
+
)
|
|
830
|
+
)
|
|
831
|
+
qcvv_logger.info(f"Job for depth {depth} submitted successfully!")
|
|
832
|
+
|
|
833
|
+
self.untranspiled_circuits.circuit_groups.append(
|
|
834
|
+
CircuitGroup(
|
|
835
|
+
name=f"{str(loop_qubits_sequence)}_depth_{depth}",
|
|
836
|
+
circuits=parallel_drb_circuits[depth]["untranspiled"],
|
|
837
|
+
)
|
|
838
|
+
)
|
|
839
|
+
self.transpiled_circuits.circuit_groups.append(
|
|
840
|
+
CircuitGroup(
|
|
841
|
+
name=f"{str(loop_qubits_sequence)}_depth_{depth}",
|
|
842
|
+
circuits=parallel_drb_circuits[depth]["transpiled"],
|
|
843
|
+
)
|
|
844
|
+
)
|
|
845
|
+
qubit_idx.update({str(loop_qubits_sequence): qubits_seq_idx})
|
|
846
|
+
dataset.attrs[f"parallel_all_{qubits_seq_idx}"] = {"qubits": loop_qubits_sequence}
|
|
847
|
+
dataset.attrs.update(
|
|
848
|
+
{qubits_seq_idx: {q_idx: {"qubits": q} for q_idx, q in enumerate(loop_qubits_sequence)}}
|
|
849
|
+
)
|
|
850
|
+
else: # if sequential
|
|
851
|
+
for qubits_idx, qubits in enumerate(loop_qubits_sequence):
|
|
852
|
+
qubit_idx[str(qubits)] = qubits_idx
|
|
853
|
+
|
|
854
|
+
qcvv_logger.info(
|
|
855
|
+
f"Executing DRB on qubits {qubits}."
|
|
856
|
+
f" Will generate and submit all {self.num_circuit_samples} DRB circuits"
|
|
857
|
+
f" for depths {assigned_drb_depths}"
|
|
858
|
+
)
|
|
859
|
+
drb_circuits = {}
|
|
860
|
+
drb_transpiled_circuits_lists: Dict[int, List[QuantumCircuit]] = {}
|
|
861
|
+
drb_untranspiled_circuits_lists: Dict[int, List[QuantumCircuit]] = {}
|
|
862
|
+
time_circuit_generation[str(qubits)] = 0
|
|
863
|
+
for depth in assigned_drb_depths:
|
|
864
|
+
qcvv_logger.info(f"Depth {depth} - Generating all circuits")
|
|
865
|
+
drb_circuits[depth], elapsed_time = generate_drb_circuits(
|
|
866
|
+
qubits,
|
|
867
|
+
depth=depth,
|
|
868
|
+
circ_samples=self.num_circuit_samples,
|
|
869
|
+
backend_arg=backend,
|
|
870
|
+
density_2q_gates=assigned_density_2q_gates[str(qubits)],
|
|
871
|
+
two_qubit_gate_ensemble=assigned_two_qubit_gate_ensembles[str(qubits)],
|
|
872
|
+
clifford_sqg_probability=assigned_clifford_sqg_probabilities[str(qubits)],
|
|
873
|
+
sqg_gate_ensemble=assigned_sqg_gate_ensembles[str(qubits)],
|
|
874
|
+
qiskit_optim_level=self.qiskit_optim_level,
|
|
875
|
+
routing_method=self.routing_method,
|
|
876
|
+
)
|
|
877
|
+
time_circuit_generation[str(qubits)] += elapsed_time
|
|
878
|
+
|
|
879
|
+
# Generated circuits at fixed depth are (dict) indexed by Pauli sample number, turn into List
|
|
880
|
+
drb_transpiled_circuits_lists[depth] = drb_circuits[depth]["transpiled"]
|
|
881
|
+
drb_untranspiled_circuits_lists[depth] = drb_circuits[depth]["untranspiled"]
|
|
882
|
+
|
|
883
|
+
# Submit
|
|
884
|
+
sorted_transpiled_qc_list = {
|
|
885
|
+
cast(Tuple[int, ...], tuple(qubits)): drb_transpiled_circuits_lists[depth]
|
|
886
|
+
}
|
|
887
|
+
all_drb_jobs.append(
|
|
888
|
+
self.submit_single_drb_job(
|
|
889
|
+
backend,
|
|
890
|
+
cast(Sequence[int], qubits),
|
|
891
|
+
depth,
|
|
892
|
+
cast(dict[tuple[int, ...], list[Any]], sorted_transpiled_qc_list),
|
|
893
|
+
)
|
|
894
|
+
)
|
|
895
|
+
|
|
896
|
+
qcvv_logger.info(f"Job for layout {qubits} & depth {depth} submitted successfully!")
|
|
897
|
+
|
|
898
|
+
self.untranspiled_circuits.circuit_groups.append(
|
|
899
|
+
CircuitGroup(
|
|
900
|
+
name=f"{str(qubits)}_depth_{depth}", circuits=drb_untranspiled_circuits_lists[depth]
|
|
901
|
+
)
|
|
902
|
+
)
|
|
903
|
+
self.transpiled_circuits.circuit_groups.append(
|
|
904
|
+
CircuitGroup(
|
|
905
|
+
name=f"{str(qubits)}_depth_{depth}", circuits=drb_transpiled_circuits_lists[depth]
|
|
906
|
+
)
|
|
907
|
+
)
|
|
908
|
+
|
|
909
|
+
dataset.attrs[f"{qubits_seq_idx}_{qubits_idx}"] = {"qubits": qubits}
|
|
910
|
+
|
|
911
|
+
# Retrieve counts of jobs for all qubit layouts
|
|
912
|
+
for job_dict in all_drb_jobs:
|
|
913
|
+
qubits = job_dict["qubits"]
|
|
914
|
+
depth = job_dict["depth"]
|
|
915
|
+
# Retrieve counts
|
|
916
|
+
execution_results, time_retrieve = retrieve_all_counts(
|
|
917
|
+
job_dict["jobs"], f"qubits_{str(qubits)}_depth_{str(depth)}"
|
|
918
|
+
)
|
|
919
|
+
# Retrieve all job meta data
|
|
920
|
+
all_job_metadata = retrieve_all_job_metadata(job_dict["jobs"])
|
|
921
|
+
# Export all to dataset
|
|
922
|
+
dataset.attrs[qubit_idx[str(qubits)]].update(
|
|
923
|
+
{
|
|
924
|
+
f"depth_{str(depth)}": {
|
|
925
|
+
"time_circuit_generation": time_circuit_generation[str(qubits)],
|
|
926
|
+
"time_submit": job_dict["time_submit"],
|
|
927
|
+
"time_retrieve": time_retrieve,
|
|
928
|
+
"all_job_metadata": all_job_metadata,
|
|
929
|
+
},
|
|
930
|
+
}
|
|
931
|
+
)
|
|
932
|
+
|
|
933
|
+
qcvv_logger.info(f"Adding counts of qubits {qubits} and depth {depth} run to the dataset")
|
|
934
|
+
dataset, _ = add_counts_to_dataset(execution_results, f"qubits_{str(qubits)}_depth_{str(depth)}", dataset)
|
|
935
|
+
|
|
936
|
+
self.circuits = Circuits([self.transpiled_circuits, self.untranspiled_circuits])
|
|
937
|
+
|
|
938
|
+
qcvv_logger.info(f"DRB experiment execution concluded!")
|
|
939
|
+
|
|
940
|
+
return dataset
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
class DirectRBConfiguration(BenchmarkConfigurationBase):
|
|
944
|
+
"""Direct RB configuration
|
|
945
|
+
|
|
946
|
+
Attributes:
|
|
947
|
+
benchmark (Type[Benchmark]): DirectRandomizedBenchmarking.
|
|
948
|
+
qubits_array (Sequence[Sequence[int]] | Sequence[Sequence[Sequence[int]]]): The array of physical qubits in which to execute DRB.
|
|
949
|
+
* It can be specified as a Sequence (e.g. list or tuple) of qubit-index registers, e.g., [[0, 1], [2, 3]],
|
|
950
|
+
or as Sequences of such Sequences, e.g., [[[0, 1], [2, 3]], [[0, 2], [1, 3]]].
|
|
951
|
+
In the second case, each Sequence[Sequence[int]] will execute sequentially, i.e.,
|
|
952
|
+
execution will be done for [[0, 1], [2, 3]] first, then for [[0, 2], [1, 3]],
|
|
953
|
+
each either in parallel or sequence, according to the (bool) value of parallel_execution.
|
|
954
|
+
is_eplg (bool): Whether the DRB experiment is executed as a EPLG subroutine.
|
|
955
|
+
* If True:
|
|
956
|
+
- default parallel_execution below is override to True.
|
|
957
|
+
- default two_qubit_gate_ensembles is {"CZGate": 1.0}.
|
|
958
|
+
- default densities_2q_gates is 0.5 (probability of sampling 2Q gates is 1).
|
|
959
|
+
- default clifford_sqg_probabilities is 0.0.
|
|
960
|
+
- default sqg_gate_ensembles is {"IGate": 1.0}.
|
|
961
|
+
* Default is False.
|
|
962
|
+
parallel_execution (bool): Whether DRB is executed in parallel for all qubit layouts in qubits_array.
|
|
963
|
+
* If is_eplg is False, it executes parallel DRB with MRB gate ensemble and density defaults.
|
|
964
|
+
* Default is False.
|
|
965
|
+
depths (Sequence[int]): The list of layer depths in which to execute DRB for all qubit layouts in qubits_array.
|
|
966
|
+
num_circuit_samples (int): The number of random-layer DRB circuits to generate.
|
|
967
|
+
shots (int): The number of measurement shots to execute per circuit.
|
|
968
|
+
qiskit_optim_level (int): The Qiskit-level of optimization to use in transpilation.
|
|
969
|
+
* Default is 1.
|
|
970
|
+
routing_method (Literal["basic", "lookahead", "stochastic", "sabre", "none"]): The routing method to use in transpilation.
|
|
971
|
+
* Default is "sabre".
|
|
972
|
+
two_qubit_gate_ensembles (Optional[Sequence[Dict[str, float]]]): The two-qubit gate ensembles to use in the random DRB circuits.
|
|
973
|
+
* Keys correspond to str names of qiskit circuit library gates, e.g., "CZGate" or "CXGate".
|
|
974
|
+
* Values correspond to the probability for the respective gate to be sampled.
|
|
975
|
+
* Each Dict[str,float] corresponds to each qubit layout in qubits_array.
|
|
976
|
+
* If len(two_qubit_gate_ensembles) != len(qubits_array), the first Dict is assigned by default.
|
|
977
|
+
* Default is None, which assigns {str(q): {"CZGate": 1.0} for q in qubits_array}.
|
|
978
|
+
densities_2q_gates (Optional[Sequence[float]]): The expected densities of 2-qubit gates in the final circuits per qubit layout.
|
|
979
|
+
* If len(densities_2q_gates) != len(qubits_array), the first density value is assigned by default.
|
|
980
|
+
* Default is None, which assigns 0.25 to all qubit layouts.
|
|
981
|
+
clifford_sqg_probabilities (Optional[Sequence[float]]): Probability with which to uniformly sample Clifford 1Q gates per qubit layout.
|
|
982
|
+
* Default is None, which assigns 1.0 to all qubit layouts.
|
|
983
|
+
sqg_gate_ensembles (Optional[Sequence[Dict[str, float]]]): A dictionary with keys being str specifying 1Q gates, and values being corresponding probabilities.
|
|
984
|
+
* If len(sqg_gate_ensembles) != len(qubits_array), the first ensemble is assigned by default.
|
|
985
|
+
* Default is None, which leaves only uniform sampling of 1Q Clifford gates.
|
|
986
|
+
|
|
987
|
+
"""
|
|
988
|
+
|
|
989
|
+
benchmark: Type[Benchmark] = DirectRandomizedBenchmarking
|
|
990
|
+
qubits_array: Sequence[Sequence[int]] | Sequence[Sequence[Sequence[int]]]
|
|
991
|
+
is_eplg: bool = False
|
|
992
|
+
parallel_execution: bool = False
|
|
993
|
+
depths: Sequence[int]
|
|
994
|
+
num_circuit_samples: int
|
|
995
|
+
qiskit_optim_level: int = 1
|
|
996
|
+
two_qubit_gate_ensembles: Optional[Sequence[Dict[str, float]]] = None
|
|
997
|
+
densities_2q_gates: Optional[Sequence[float]] = None
|
|
998
|
+
clifford_sqg_probabilities: Optional[Sequence[float]] = None
|
|
999
|
+
sqg_gate_ensembles: Optional[Sequence[Dict[str, float]]] = None
|
|
1000
|
+
routing_method: Literal["basic", "lookahead", "stochastic", "sabre", "none"] = "sabre"
|