iqm-benchmarks 2.31__py3-none-any.whl → 2.33__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 +191 -19
- {iqm_benchmarks-2.31.dist-info → iqm_benchmarks-2.33.dist-info}/METADATA +3 -1
- {iqm_benchmarks-2.31.dist-info → iqm_benchmarks-2.33.dist-info}/RECORD +17 -13
- {iqm_benchmarks-2.31.dist-info → iqm_benchmarks-2.33.dist-info}/WHEEL +0 -0
- {iqm_benchmarks-2.31.dist-info → iqm_benchmarks-2.33.dist-info}/licenses/LICENSE +0 -0
- {iqm_benchmarks-2.31.dist-info → iqm_benchmarks-2.33.dist-info}/top_level.txt +0 -0
|
@@ -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.
|
|
110
|
-
offset_guess = 0.
|
|
111
|
-
amplitude_guess = 0.
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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,
|
|
324
|
+
func,
|
|
325
|
+
fit_data,
|
|
326
|
+
initial_guesses=estimates,
|
|
327
|
+
constraints=None,
|
|
328
|
+
simultaneously_fit_vars=None,
|
|
140
329
|
)
|
|
141
|
-
params.add(f"
|
|
142
|
-
params.add(f"
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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:
|
|
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 (
|
|
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
|
-
"""
|
|
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
|
-
|
|
569
|
-
|
|
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
|
-
|
|
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
|
|
883
|
+
if identifier in ("mrb", "drb"):
|
|
657
884
|
depths[identifier] = {
|
|
658
|
-
str(q): list(
|
|
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):
|
|
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):
|
|
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):
|
|
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(
|
|
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):
|
|
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):
|
|
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):
|
|
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):
|
|
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):
|
|
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):
|
|
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(
|
|
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):
|
|
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):
|
|
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):
|
|
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):
|
|
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):
|
|
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):
|
|
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
|
|
867
|
-
plot_label = fr"$\overline{{F}}_{{
|
|
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
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
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
|
-
|
|
951
|
-
|
|
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(
|