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.

@@ -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"