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.

@@ -15,13 +15,13 @@
15
15
  """
16
16
  Common functions for Randomized Benchmarking-based techniques
17
17
  """
18
-
18
+ from copy import deepcopy
19
19
  from importlib import import_module
20
20
  from itertools import chain
21
21
  import os
22
22
  import pickle
23
23
  import random
24
- from typing import Any, Callable, Dict, List, Optional, Tuple, cast
24
+ from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, cast
25
25
 
26
26
  from lmfit import Parameters, minimize
27
27
  from lmfit.minimizer import MinimizerResult
@@ -30,7 +30,7 @@ from matplotlib.figure import Figure
30
30
  import matplotlib.pyplot as plt
31
31
  import numpy as np
32
32
  from qiskit import transpile
33
- from qiskit.quantum_info import Clifford
33
+ from qiskit.quantum_info import Clifford, random_clifford
34
34
  import xarray as xr
35
35
 
36
36
  from iqm.benchmarks.logging_config import qcvv_logger
@@ -42,6 +42,9 @@ from iqm.qiskit_iqm import optimize_single_qubit_gates
42
42
  from iqm.qiskit_iqm.iqm_backend import IQMBackendBase
43
43
 
44
44
 
45
+ # pylint: disable=too-many-lines
46
+
47
+
45
48
  def compute_inverse_clifford(qc_inv: QuantumCircuit, clifford_dictionary: Dict) -> Optional[QuantumCircuit]:
46
49
  """Function to compute the inverse Clifford of a circuit
47
50
  Args:
@@ -55,6 +58,188 @@ def compute_inverse_clifford(qc_inv: QuantumCircuit, clifford_dictionary: Dict)
55
58
  return compiled_inverse
56
59
 
57
60
 
61
+ # pylint: disable=too-many-branches, too-many-statements
62
+ def edge_grab(
63
+ qubit_set: List[int] | Sequence[int],
64
+ n_layers: int,
65
+ backend_arg: IQMBackendBase | str,
66
+ density_2q_gates: float = 0.25,
67
+ two_qubit_gate_ensemble: Optional[Dict[str, float]] = None,
68
+ clifford_sqg_probability=1.0,
69
+ sqg_gate_ensemble: Optional[Dict[str, float]] = None,
70
+ ) -> List[QuantumCircuit]:
71
+ """Generate a list of random layers containing single-qubit Cliffords and two-qubit gates,
72
+ sampled according to the edge-grab algorithm (see arXiv:2204.07568 [quant-ph]).
73
+
74
+ Args:
75
+ qubit_set (List[int]): The set of qubits of the backend.
76
+ n_layers (int): The number of layers.
77
+ backend_arg (IQMBackendBase | str): IQM backend.
78
+ density_2q_gates (float): The expected density of 2Q gates in a circuit formed by subsequent application of layers.
79
+ * Default is 0.25
80
+ two_qubit_gate_ensemble (Optional[Dict[str, float]]): A dictionary with keys being str specifying 2Q gates, and values being corresponding probabilities.
81
+ * Default is None.
82
+ clifford_sqg_probability (float): Probability with which to uniformly sample Clifford 1Q gates.
83
+ * Default is 1.0.
84
+ sqg_gate_ensemble (Optional[Dict[str, float]]): A dictionary with keys being str specifying 1Q gates, and values being corresponding probabilities.
85
+ * Default is None.
86
+ Raises:
87
+ ValueError: if the probabilities in the gate ensembles do not add up to unity.
88
+ Returns:
89
+ List[QuantumCircuit]: the list of gate layers, in the form of quantum circuits.
90
+ """
91
+ # Check the ensemble of 2Q gates, otherwise assign
92
+ if two_qubit_gate_ensemble is None:
93
+ two_qubit_gate_ensemble = cast(Dict[str, float], {"CZGate": 1.0})
94
+ elif int(sum(two_qubit_gate_ensemble.values())) != 1:
95
+ raise ValueError("The 2Q gate ensemble probabilities must sum to 1.0")
96
+
97
+ # Check the ensemble of 1Q gates
98
+ if sqg_gate_ensemble is None:
99
+ if int(clifford_sqg_probability) != 1:
100
+ raise ValueError("If no 1Q gate ensemble is provided, clifford_sqg_probability must be 1.0.")
101
+ # Alternatively, a uniform set of native 1Q rotations could be provided as default with remaining probability
102
+ # Implement later if so wanted
103
+ elif int(sum(sqg_gate_ensemble.values()) + clifford_sqg_probability) != 1:
104
+ raise ValueError("The 1Q gate ensemble probabilities plus clifford_sqg_probability must sum to 1.0.")
105
+
106
+ # Validate 1Q gates and get native circuits
107
+ one_qubit_circuits = {"clifford": random_clifford(1).to_circuit()}
108
+ if sqg_gate_ensemble is not None:
109
+ for k in sqg_gate_ensemble.keys():
110
+ one_qubit_circuits[k] = validate_irb_gate(k, backend_arg, gate_params=None)
111
+
112
+ # Validate 2Q gates and get native circuits
113
+ two_qubit_circuits = {}
114
+ for k in two_qubit_gate_ensemble.keys():
115
+ two_qubit_circuits[k] = validate_irb_gate(k, backend_arg, gate_params=None)
116
+ # TODO: Admit parametrized 2Q gates! # pylint: disable=fixme
117
+
118
+ # Check backend and retrieve if necessary
119
+ if isinstance(backend_arg, str):
120
+ backend = get_iqm_backend(backend_arg)
121
+ else:
122
+ backend = backend_arg
123
+
124
+ # Definitions
125
+ num_qubits = len(qubit_set)
126
+ physical_to_virtual_map = {q: i for i, q in enumerate(qubit_set)}
127
+
128
+ # Get the possible edges where to place 2Q gates given the backend connectivity
129
+ twoq_edges = []
130
+ for i, q0 in enumerate(qubit_set):
131
+ for q1 in qubit_set[i + 1 :]:
132
+ if (q0, q1) in list(backend.coupling_map):
133
+ twoq_edges.append([q0, q1])
134
+ twoq_edges = list(sorted(twoq_edges))
135
+
136
+ # Generate the layers
137
+ layer_list = []
138
+ for _ in range(n_layers):
139
+ # Pick edges at random and store them in a new list "edge_list"
140
+ aux = deepcopy(twoq_edges)
141
+ edge_list = []
142
+ layer = QuantumCircuit(num_qubits)
143
+ # Take (and remove) edges from "aux", then add to "edge_list"
144
+ edge_qubits = []
145
+ while aux:
146
+ new_edge = random.choice(aux)
147
+ edge_list.append(new_edge)
148
+ edge_qubits = list(np.array(edge_list).flatten())
149
+ # Removes all edges which include either of the qubits in new_edge
150
+ aux = [e for e in aux if ((new_edge[0] not in e) and (new_edge[1] not in e))]
151
+
152
+ # Define the probability for adding 2Q gates, given the input density
153
+ if len(edge_list) != 0:
154
+ prob_2qgate = num_qubits * density_2q_gates / len(edge_list)
155
+ else:
156
+ prob_2qgate = 0
157
+
158
+ # Add gates in selected edges
159
+ for e in edge_list:
160
+ # Sample the 2Q gate
161
+ two_qubit_gate = random.choices(
162
+ list(two_qubit_gate_ensemble.keys()),
163
+ weights=list(two_qubit_gate_ensemble.values()),
164
+ k=1,
165
+ )[0]
166
+
167
+ # Pick whether to place the sampled 2Q gate according to the probability above
168
+ is_gate_placed = random.choices(
169
+ [True, False],
170
+ weights=[prob_2qgate, 1 - prob_2qgate],
171
+ k=1,
172
+ )[0]
173
+
174
+ if is_gate_placed:
175
+ if two_qubit_gate == "clifford":
176
+ layer.compose(
177
+ random_clifford(2).to_instruction(),
178
+ qubits=[
179
+ physical_to_virtual_map[e[0]],
180
+ physical_to_virtual_map[e[1]],
181
+ ],
182
+ inplace=True,
183
+ wrap=False,
184
+ )
185
+ else:
186
+ layer.compose(
187
+ two_qubit_circuits[two_qubit_gate],
188
+ qubits=[
189
+ physical_to_virtual_map[e[0]],
190
+ physical_to_virtual_map[e[1]],
191
+ ],
192
+ inplace=True,
193
+ wrap=False,
194
+ )
195
+ else:
196
+ # Sample a 1Q gate
197
+ one_qubit_gate = random.choices(
198
+ list(sqg_gate_ensemble.keys()) + ["clifford"] if sqg_gate_ensemble is not None else ["clifford"],
199
+ weights=(
200
+ list(sqg_gate_ensemble.values()) + [clifford_sqg_probability]
201
+ if sqg_gate_ensemble is not None
202
+ else [clifford_sqg_probability]
203
+ ),
204
+ k=2,
205
+ )
206
+ layer.compose(
207
+ one_qubit_circuits[one_qubit_gate[0]],
208
+ qubits=[physical_to_virtual_map[e[0]]],
209
+ inplace=True,
210
+ )
211
+ layer.compose(
212
+ one_qubit_circuits[one_qubit_gate[1]],
213
+ qubits=[physical_to_virtual_map[e[1]]],
214
+ inplace=True,
215
+ )
216
+
217
+ # Add 1Q gates in remaining qubits
218
+ remaining_qubits = [q for q in qubit_set if q not in edge_qubits]
219
+ while remaining_qubits:
220
+ for q in remaining_qubits:
221
+ # Sample the 1Q gate
222
+ one_qubit_gate = random.choices(
223
+ list(sqg_gate_ensemble.keys()) + ["clifford"] if sqg_gate_ensemble is not None else ["clifford"],
224
+ weights=(
225
+ list(sqg_gate_ensemble.values()) + [clifford_sqg_probability]
226
+ if sqg_gate_ensemble is not None
227
+ else [clifford_sqg_probability]
228
+ ),
229
+ k=1,
230
+ )
231
+ layer.compose(
232
+ one_qubit_circuits[one_qubit_gate[0]],
233
+ qubits=[physical_to_virtual_map[q]],
234
+ inplace=True,
235
+ )
236
+ remaining_qubits.remove(q)
237
+
238
+ layer_list.append(layer)
239
+
240
+ return layer_list
241
+
242
+
58
243
  def estimate_survival_probabilities(num_qubits: int, counts: List[Dict[str, int]]) -> List[float]:
59
244
  """Compute a result's probability of being on the ground state.
60
245
  Args:
@@ -106,21 +291,21 @@ def fit_decay_lmfit(
106
291
  """
107
292
  n_qubits = len(qubit_set)
108
293
 
109
- fidelity_guess = 0.988**n_qubits
110
- offset_guess = 0.2 if n_qubits == 2 else 0.65
111
- amplitude_guess = 0.8 if n_qubits == 2 else 0.35
294
+ fidelity_guess = 0.99**n_qubits
295
+ offset_guess = 0.3 if n_qubits == 2 else 0.65
296
+ amplitude_guess = 0.7 if n_qubits == 2 else 0.35
112
297
 
113
298
  estimates = {
114
299
  "depolarization_probability": 2 * (1 - fidelity_guess),
115
300
  "offset": offset_guess,
116
301
  "amplitude": amplitude_guess + offset_guess,
117
302
  }
118
- constraints = {
119
- "depolarization_probability": {"min": 0, "max": 1},
120
- "offset": {"min": 0, "max": 1},
121
- "amplitude": {"min": 0, "max": 1},
122
- }
123
- if rb_identifier in ("clifford", "mrb"):
303
+ # constraints = {
304
+ # "depolarization_probability": {"min": 0.0, "max": 1.0},
305
+ # "offset": {"min": 0.0, "max": 1.0},
306
+ # "amplitude": {"min": 0.0, "max": 1.0},
307
+ # }
308
+ if rb_identifier in ("clifford", "mrb", "drb"):
124
309
  fit_data = np.array([np.mean(data, axis=1)])
125
310
  if rb_identifier == "clifford":
126
311
  params = create_multi_dataset_params(
@@ -136,10 +321,14 @@ def fit_decay_lmfit(
136
321
  params.add("fidelity_per_native_sqg", expr=f"1 - (1 - (p_rb + (1 - p_rb) / (2**{n_qubits})))/1.875")
137
322
  else:
138
323
  params = create_multi_dataset_params(
139
- func, fit_data, initial_guesses=None, constraints=constraints, simultaneously_fit_vars=None
324
+ func,
325
+ fit_data,
326
+ initial_guesses=estimates,
327
+ constraints=None,
328
+ simultaneously_fit_vars=None,
140
329
  )
141
- params.add(f"p_mrb", expr=f"1-depolarization_probability_{1}")
142
- params.add(f"fidelity_mrb", expr=f"1 - (1 - p_mrb) * (1 - 1 / (4 ** {n_qubits}))")
330
+ params.add(f"p_{rb_identifier}", expr=f"1-depolarization_probability_{1}")
331
+ params.add(f"fidelity_{rb_identifier}", expr=f"1 - (1 - p_{rb_identifier}) * (1 - 1 / (4 ** {n_qubits}))")
143
332
  else:
144
333
  fit_data = np.array([np.mean(data[0], axis=1), np.mean(data[1], axis=1)])
145
334
  params = create_multi_dataset_params(
@@ -215,13 +404,13 @@ def generate_fixed_depth_parallel_rb_circuits(
215
404
  """Generates parallel RB circuits, before and after transpilation, at fixed depth
216
405
 
217
406
  Args:
218
- qubits_array (List[List[int]]): the qubits entering the quantum circuits
219
- cliffords_1q (Dict[str, QuantumCircuit]): dictionary of 1-qubit Cliffords in terms of IQM-native r and CZ gates
220
- cliffords_2q (Dict[str, QuantumCircuit]): dictionary of 2-qubit Cliffords in terms of IQM-native r and CZ gates
221
- sequence_length (int): the number of random Cliffords in the circuits
222
- num_samples (int): the number of circuit samples
223
- backend_arg (IQMBackendBase | str): the backend to transpile the circuits to
224
- interleaved_gate (Optional[QuantumCircuit]): whether the circuits should have interleaved gates
407
+ qubits_array (List[List[int]]): the qubits entering the quantum circuits.
408
+ cliffords_1q (Dict[str, QuantumCircuit]): dictionary of 1-qubit Cliffords in terms of IQM-native r gates.
409
+ cliffords_2q (Dict[str, QuantumCircuit]): dictionary of 2-qubit Cliffords in terms of IQM-native r and CZ gates.
410
+ sequence_length (int): the number of random Cliffords in the circuits.
411
+ num_samples (int): the number of circuit samples.
412
+ backend_arg (IQMBackendBase | str): the backend to transpile the circuits to.
413
+ interleaved_gate (Optional[QuantumCircuit]): whether the circuits should have interleaved gates.
225
414
  Returns:
226
415
  A list of QuantumCircuits of given RB sequence length for parallel RB
227
416
  """
@@ -409,7 +598,7 @@ def import_native_gate_cliffords(
409
598
  with open(os.path.join(os.path.dirname(__file__), "clifford_2q.pkl"), "rb") as f2q:
410
599
  clifford_2q_dict = pickle.load(f2q)
411
600
 
412
- qcvv_logger.info(f"Clifford dictionaries for {system_size or 'both systems'} imported successfully!")
601
+ qcvv_logger.info(f"Clifford dictionaries for {system_size or 'both 1 & 2 qubits'} imported successfully!")
413
602
 
414
603
  if system_size == "1q":
415
604
  return clifford_1q_dict
@@ -438,27 +627,43 @@ def lmfit_minimizer(
438
627
  )
439
628
 
440
629
 
441
- def relabel_qubits_array_from_zero(arr: List[List[int]]) -> List[List[int]]:
630
+ def relabel_qubits_array_from_zero(
631
+ arr: List[List[int]], separate_registers: bool = False, reversed_arr: bool = False
632
+ ) -> List[List[int]]:
442
633
  """Helper function to relabel a qubits array to an increasingly ordered one starting from zero
443
634
  e.g., [[2,3], [5], [7,8]] -> [[0,1], [2], [3,4]]
444
635
  Note: this assumes the input array is sorted in increasing order!
636
+
637
+ Args:
638
+ arr (List[List[int]]): the qubits array to relabel.
639
+ separate_registers (bool): whether the clbits were generated in separate registers.
640
+ * This has the effect of skipping one value in between each sublist, e.g., [[2,3], [5], [7,8]] -> [[0,1], [3], [5,6]]
641
+ * Default is False.
642
+ reversed_arr (bool): whether the input array is reversed.
643
+ * This has the effect of reversing the output array, e.g., [[2,3], [5,7], [8]] -> [[0], [1,2], [3,4]]
644
+ * Default is False.
645
+
646
+ Returns:
647
+ List[List[int]]: the relabeled qubits array.
445
648
  """
446
649
  # Flatten the original array
447
650
  flat_list = [item for sublist in arr for item in sublist]
448
651
  # Generate a list of ordered numbers with the same length as the flattened array
449
- ordered_indices = list(range(len(flat_list)))
652
+ # If separate_registers is True, ordered_indices has to skip one value in between each sublist (as if the clbits were generated in separate registers)
653
+ # e.g. [[2,3], [5], [7,8]] -> [[0,1],[3],[5,6]] if separate_registers=True
654
+ ordered_indices = list(range(len(flat_list) + len(arr) - 1)) if separate_registers else list(range(len(flat_list)))
450
655
  # Reconstruct the list of lists structure
451
656
  result = []
452
657
  index = 0
453
- for sublist in arr:
658
+ for sublist in reversed(arr) if reversed_arr else arr:
454
659
  result.append(ordered_indices[index : index + len(sublist)])
455
- index += len(sublist)
660
+ index += len(sublist) + 1 if separate_registers else len(sublist)
456
661
  return result
457
662
 
458
663
 
459
664
  def submit_parallel_rb_job(
460
665
  backend_arg: IQMBackendBase,
461
- qubits_array: List[List[int]],
666
+ qubits_array: Sequence[Sequence[int]],
462
667
  depth: int,
463
668
  sorted_transpiled_circuit_dicts: Dict[Tuple[int, ...], List[QuantumCircuit]],
464
669
  shots: int,
@@ -469,7 +674,7 @@ def submit_parallel_rb_job(
469
674
  """Submit fixed-depth parallel MRB jobs for execution in the specified IQMBackend
470
675
  Args:
471
676
  backend_arg (IQMBackendBase): the IQM backend to submit the job
472
- qubits_array (List[int]): the qubits to identify the submitted job
677
+ qubits_array (Sequence[Sequence[int]]): the qubits to identify the submitted job
473
678
  depth (int): the depth (number of canonical layers) of the circuits to identify the submitted job
474
679
  sorted_transpiled_circuit_dicts (Dict[Tuple[int,...], List[QuantumCircuit]]): A dictionary containing all MRB circuits
475
680
  shots (int): the number of shots to submit the job
@@ -553,25 +758,33 @@ def submit_sequential_rb_jobs(
553
758
 
554
759
 
555
760
  def survival_probabilities_parallel(
556
- qubits_array: List[List[int]], counts: List[Dict[str, int]]
761
+ qubits_array: List[List[int]], counts: List[Dict[str, int]], separate_registers: bool = False
557
762
  ) -> Dict[str, List[float]]:
558
- """Estimates marginalized survival probabilities from a parallel RB execution (at fixed depth)
763
+ """
764
+ Estimates marginalized survival probabilities from a parallel RB execution (at fixed depth).
765
+
559
766
  Args:
560
767
  qubits_array (List[int]): List of qubits in which the experiment was performed
561
768
  counts (Dict[str, int]): The measurement counts for corresponding bitstrings
769
+ separate_registers (bool): Whether the clbits were generated in separate registers
770
+ * If True, the bit strings will be separated by a space, e.g., '00 10' means '00' belongs to one register and '10' to another.
771
+ * Default is False.
772
+
562
773
  Returns:
563
- Dict[str, List[float]]: The survival probabilities for each qubit
774
+ Dict[str, List[float]]: The survival probabilities for each qubit.
775
+
564
776
  """
565
777
  # Global probability estimations
566
778
  global_probabilities = [{k: v / sum(c.values()) for k, v in c.items()} for c in counts]
567
779
 
568
- # The bit indices
569
- all_bit_indices = relabel_qubits_array_from_zero(qubits_array)
780
+ all_bit_indices = relabel_qubits_array_from_zero(
781
+ qubits_array, separate_registers=separate_registers, reversed_arr=True
782
+ ) # Need reversed_arr because count bitstrings get reversed!
570
783
 
571
784
  # Estimate all marginal probabilities
572
785
  marginal_probabilities: Dict[str, List[Dict[str, float]]] = {str(q): [] for q in qubits_array}
573
- for position, indices in enumerate(all_bit_indices):
574
- marginal_probabilities[str(qubits_array[position])] = [
786
+ for position, indices in reversed(list(enumerate(all_bit_indices))):
787
+ marginal_probabilities[str(qubits_array[len(all_bit_indices) - 1 - position])] = [
575
788
  marginal_distribution(global_probability, indices) for global_probability in global_probabilities
576
789
  ]
577
790
 
@@ -600,8 +813,9 @@ def plot_rb_decay(
600
813
  shade_meanerror: bool = False,
601
814
  logscale: bool = True,
602
815
  interleaved_gate: Optional[str] = None,
603
- mrb_2q_density: Optional[float] = None,
604
- mrb_2q_ensemble: Optional[Dict[str, float]] = None,
816
+ mrb_2q_density: Optional[float | Dict[str, float]] = None,
817
+ mrb_2q_ensemble: Optional[Dict[str, Dict[str, float]]] = None,
818
+ is_eplg: bool = False,
605
819
  ) -> Tuple[str, Figure]:
606
820
  """Plot the fidelity decay and the fit to the model.
607
821
 
@@ -619,6 +833,7 @@ def plot_rb_decay(
619
833
  interleaved_gate (Optional[str]): The label or the interleaved gate. Defaults to None
620
834
  mrb_2q_density (Optional[float], optional): Density of MRB 2Q gates. Defaults to None.
621
835
  mrb_2q_ensemble (Optional[Dict[str, float]], optional): MRB ensemble of 2Q gates. Defaults to None.
836
+ is_eplg (bool, optional): Whether the experiment is EPLG or not. Defaults to False.
622
837
 
623
838
  Returns:
624
839
  Tuple[str, Figure]: the plot title and the figure
@@ -632,9 +847,21 @@ def plot_rb_decay(
632
847
  backend_name = dataset.attrs["backend_name"]
633
848
  # If only one layout is passed, the index to retrieve results must be shifted!
634
849
  qubits_index = 0
850
+ dataset_attrs = dataset.attrs
635
851
  if len(qubits_array) == 1:
636
852
  config_qubits_array = dataset.attrs["qubits_array"]
637
- qubits_index = config_qubits_array.index(qubits_array[0])
853
+ if isinstance(config_qubits_array[0][0], int):
854
+ qubits_index = config_qubits_array.index(qubits_array[0])
855
+ # dataset_attrs = dataset.attrs
856
+ else:
857
+ # Find the subarray that contains the qubits_array
858
+ for c_idx, c in enumerate(config_qubits_array):
859
+ if qubits_array[0] in c:
860
+ group_idx = c_idx
861
+ qubits_index = c.index(qubits_array[0])
862
+ dataset_attrs = dataset.attrs[group_idx]
863
+ # flat_config_qubits_array = [item for sublist in config_qubits_array for item in sublist]
864
+ # qubits_index = flat_config_qubits_array.index(qubits_array[0])
638
865
 
639
866
  # Fetch the relevant observations indexed by qubit layouts
640
867
  depths = {}
@@ -653,36 +880,36 @@ def plot_rb_decay(
653
880
  if identifier != "irb":
654
881
  rb_type_keys = [identifier]
655
882
  colors = [cmap(i) for i in np.linspace(start=1, stop=0, num=len(qubits_array)).tolist()]
656
- if identifier == "mrb":
883
+ if identifier in ("mrb", "drb"):
657
884
  depths[identifier] = {
658
- str(q): list(dataset.attrs[q_idx]["polarizations"].keys())
885
+ str(q): list(dataset_attrs[q_idx]["polarizations"].keys())
659
886
  for q_idx, q in enumerate(qubits_array, qubits_index)
660
887
  }
661
888
  polarizations[identifier] = {
662
- str(q): dataset.attrs[q_idx]["polarizations"] for q_idx, q in enumerate(qubits_array, qubits_index)
889
+ str(q): dataset_attrs[q_idx]["polarizations"] for q_idx, q in enumerate(qubits_array, qubits_index)
663
890
  }
664
891
  average_polarizations[identifier] = {
665
- str(q): dataset.attrs[q_idx]["average_polarization_nominal_values"]
892
+ str(q): dataset_attrs[q_idx]["average_polarization_nominal_values"]
666
893
  for q_idx, q in enumerate(qubits_array, qubits_index)
667
894
  }
668
895
  stddevs_from_mean[identifier] = {
669
- str(q): dataset.attrs[q_idx]["average_polatization_stderr"]
896
+ str(q): dataset_attrs[q_idx]["average_polarization_stderr"]
670
897
  for q_idx, q in enumerate(qubits_array, qubits_index)
671
898
  }
672
899
  else: # identifier == "clifford"
673
900
  depths[identifier] = {
674
- str(q): list(dataset.attrs[q_idx]["fidelities"].keys())
901
+ str(q): list(dataset_attrs[q_idx]["fidelities"].keys())
675
902
  for q_idx, q in enumerate(qubits_array, qubits_index)
676
903
  }
677
904
  polarizations[identifier] = {
678
- str(q): dataset.attrs[q_idx]["fidelities"] for q_idx, q in enumerate(qubits_array, qubits_index)
905
+ str(q): dataset_attrs[q_idx]["fidelities"] for q_idx, q in enumerate(qubits_array, qubits_index)
679
906
  }
680
907
  average_polarizations[identifier] = {
681
- str(q): dataset.attrs[q_idx]["average_fidelities_nominal_values"]
908
+ str(q): dataset_attrs[q_idx]["average_fidelities_nominal_values"]
682
909
  for q_idx, q in enumerate(qubits_array, qubits_index)
683
910
  }
684
911
  stddevs_from_mean[identifier] = {
685
- str(q): dataset.attrs[q_idx]["average_fidelities_stderr"]
912
+ str(q): dataset_attrs[q_idx]["average_fidelities_stderr"]
686
913
  for q_idx, q in enumerate(qubits_array, qubits_index)
687
914
  }
688
915
  fidelity_native1q_value[identifier] = {
@@ -703,32 +930,32 @@ def plot_rb_decay(
703
930
  for q_idx, q in enumerate(qubits_array, qubits_index)
704
931
  }
705
932
  decay_rate[identifier] = {
706
- str(q): dataset.attrs[q_idx]["decay_rate"]["value"] for q_idx, q in enumerate(qubits_array, qubits_index)
933
+ str(q): dataset_attrs[q_idx]["decay_rate"]["value"] for q_idx, q in enumerate(qubits_array, qubits_index)
707
934
  }
708
935
  offset[identifier] = {
709
- str(q): dataset.attrs[q_idx]["fit_offset"]["value"] for q_idx, q in enumerate(qubits_array, qubits_index)
936
+ str(q): dataset_attrs[q_idx]["fit_offset"]["value"] for q_idx, q in enumerate(qubits_array, qubits_index)
710
937
  }
711
938
  amplitude[identifier] = {
712
- str(q): dataset.attrs[q_idx]["fit_amplitude"]["value"] for q_idx, q in enumerate(qubits_array, qubits_index)
939
+ str(q): dataset_attrs[q_idx]["fit_amplitude"]["value"] for q_idx, q in enumerate(qubits_array, qubits_index)
713
940
  }
714
941
  else: # id IRB
715
942
  rb_type_keys = list(observations[0].keys())
716
943
  colors = [cmap(i) for i in np.linspace(start=1, stop=0, num=len(rb_type_keys)).tolist()]
717
944
  for rb_type in rb_type_keys:
718
945
  depths[rb_type] = {
719
- str(q): list(dataset.attrs[q_idx][rb_type]["fidelities"].keys())
946
+ str(q): list(dataset_attrs[q_idx][rb_type]["fidelities"].keys())
720
947
  for q_idx, q in enumerate(qubits_array, qubits_index)
721
948
  }
722
949
  polarizations[rb_type] = {
723
- str(q): dataset.attrs[q_idx][rb_type]["fidelities"]
950
+ str(q): dataset_attrs[q_idx][rb_type]["fidelities"]
724
951
  for q_idx, q in enumerate(qubits_array, qubits_index)
725
952
  }
726
953
  average_polarizations[rb_type] = {
727
- str(q): dataset.attrs[q_idx][rb_type]["average_fidelities_nominal_values"]
954
+ str(q): dataset_attrs[q_idx][rb_type]["average_fidelities_nominal_values"]
728
955
  for q_idx, q in enumerate(qubits_array, qubits_index)
729
956
  }
730
957
  stddevs_from_mean[rb_type] = {
731
- str(q): dataset.attrs[q_idx][rb_type]["average_fidelities_stderr"]
958
+ str(q): dataset_attrs[q_idx][rb_type]["average_fidelities_stderr"]
732
959
  for q_idx, q in enumerate(qubits_array, qubits_index)
733
960
  }
734
961
  fidelity_value[rb_type] = {
@@ -740,15 +967,15 @@ def plot_rb_decay(
740
967
  for q_idx, q in enumerate(qubits_array, qubits_index)
741
968
  }
742
969
  decay_rate[rb_type] = {
743
- str(q): dataset.attrs[q_idx][rb_type]["decay_rate"]["value"]
970
+ str(q): dataset_attrs[q_idx][rb_type]["decay_rate"]["value"]
744
971
  for q_idx, q in enumerate(qubits_array, qubits_index)
745
972
  }
746
973
  offset[rb_type] = {
747
- str(q): dataset.attrs[q_idx][rb_type]["fit_offset"]["value"]
974
+ str(q): dataset_attrs[q_idx][rb_type]["fit_offset"]["value"]
748
975
  for q_idx, q in enumerate(qubits_array, qubits_index)
749
976
  }
750
977
  amplitude[rb_type] = {
751
- str(q): dataset.attrs[q_idx][rb_type]["fit_amplitude"]["value"]
978
+ str(q): dataset_attrs[q_idx][rb_type]["fit_amplitude"]["value"]
752
979
  for q_idx, q in enumerate(qubits_array, qubits_index)
753
980
  }
754
981
 
@@ -863,8 +1090,8 @@ def plot_rb_decay(
863
1090
  if fidelity_stderr[key][str(qubits)] is None:
864
1091
  fidelity_stderr[key][str(qubits)] = np.nan
865
1092
 
866
- if identifier == "mrb":
867
- plot_label = fr"$\overline{{F}}_{{MRB}} (n={len(qubits)})$ = {100.0 * fidelity_value[key][str(qubits)]:.2f} +/- {100.0 * fidelity_stderr[key][str(qubits)]:.2f} (%)"
1093
+ if identifier in ("mrb", "drb"):
1094
+ plot_label = fr"$\overline{{F}}_{{{identifier.upper()}}} (n={len(qubits)})$ = {100.0 * fidelity_value[key][str(qubits)]:.2f} +/- {100.0 * fidelity_stderr[key][str(qubits)]:.2f} (%)"
868
1095
  elif key == "interleaved":
869
1096
  plot_label = fr"$\overline{{F}}_{{{interleaved_gate}}}$ = {100.0 * fidelity_value[key][str(qubits)]:.2f} +/- {100.0 * fidelity_stderr[key][str(qubits)]:.2f} (%)"
870
1097
  else: # if id is "clifford"
@@ -895,12 +1122,25 @@ def plot_rb_decay(
895
1122
  elif identifier == "clifford":
896
1123
  ax.set_title(f"{identifier.capitalize()} experiment {on_qubits}\nbackend: {backend_name} --- {timestamp}")
897
1124
  else:
898
- ax.set_title(
899
- f"{identifier.upper()} experiment {on_qubits}\n"
900
- f"2Q gate density: {mrb_2q_density}, ensemble {mrb_2q_ensemble}\n"
901
- f"backend: {backend_name} --- {timestamp}"
902
- )
903
- if identifier == "mrb":
1125
+ if not is_eplg:
1126
+ if identifier == "drb" and len(qubits_array) == 1:
1127
+ density = cast(dict, mrb_2q_density)[str(qubits_array[0])]
1128
+ ensemble = cast(dict, mrb_2q_ensemble)[str(qubits_array[0])]
1129
+ else:
1130
+ density = mrb_2q_density
1131
+ ensemble = mrb_2q_ensemble
1132
+ ax.set_title(
1133
+ f"{identifier.upper()} experiment {on_qubits}\n"
1134
+ f"2Q gate density: {density}, ensemble {ensemble}\n"
1135
+ f"backend: {backend_name} --- {timestamp}"
1136
+ )
1137
+ else:
1138
+ ax.set_title(
1139
+ f"EPLG parallel {identifier.upper()} experiment {on_qubits}\n"
1140
+ f"backend: {backend_name} --- {timestamp}"
1141
+ )
1142
+
1143
+ if identifier in ("mrb", "drb"):
904
1144
  ax.set_ylabel("Polarization")
905
1145
  ax.set_xlabel("Layer Depth")
906
1146
  else:
@@ -914,13 +1154,11 @@ def plot_rb_decay(
914
1154
  all_depths = [x for d in depths.values() for w in d.values() for x in w]
915
1155
 
916
1156
  xticks = sorted(list(set(all_depths)))
917
- ax.set_xticks(xticks, labels=xticks)
1157
+ ax.set_xticks(xticks, labels=xticks, rotation=45)
918
1158
 
919
1159
  plt.legend(fontsize=8)
920
1160
  ax.grid()
921
1161
 
922
- plt.gcf().set_dpi(250)
923
-
924
1162
  plt.close()
925
1163
 
926
1164
  return fig_name, fig
@@ -947,8 +1185,9 @@ def validate_rb_qubits(qubits_array: List[List[int]], backend_arg: str | IQMBack
947
1185
  raise ValueError("Please specify qubit layouts with only n=1 or n=2 qubits. Run MRB for n>2 instead.")
948
1186
  pairs_qubits = [qubits_array[i] for i, n in enumerate(qubit_counts) if n == 2]
949
1187
  # Qubits should be connected
950
- if any(tuple(x) not in backend.coupling_map for x in pairs_qubits):
951
- raise ValueError("Some specified pairs of qubits are not connected")
1188
+ for x in pairs_qubits:
1189
+ if tuple(x) not in list(backend.coupling_map) and tuple(reversed(x)) not in list(backend.coupling_map):
1190
+ raise ValueError(f"Input qubits {tuple(x)} are not connected")
952
1191
 
953
1192
 
954
1193
  def validate_irb_gate(