iqm-benchmarks 1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of iqm-benchmarks might be problematic. Click here for more details.
- iqm/benchmarks/__init__.py +31 -0
- iqm/benchmarks/benchmark.py +109 -0
- iqm/benchmarks/benchmark_definition.py +264 -0
- iqm/benchmarks/benchmark_experiment.py +163 -0
- iqm/benchmarks/compressive_gst/__init__.py +20 -0
- iqm/benchmarks/compressive_gst/compressive_gst.py +1029 -0
- iqm/benchmarks/entanglement/__init__.py +18 -0
- iqm/benchmarks/entanglement/ghz.py +802 -0
- iqm/benchmarks/logging_config.py +29 -0
- iqm/benchmarks/optimization/__init__.py +18 -0
- iqm/benchmarks/optimization/qscore.py +719 -0
- iqm/benchmarks/quantum_volume/__init__.py +21 -0
- iqm/benchmarks/quantum_volume/clops.py +726 -0
- iqm/benchmarks/quantum_volume/quantum_volume.py +854 -0
- iqm/benchmarks/randomized_benchmarking/__init__.py +18 -0
- iqm/benchmarks/randomized_benchmarking/clifford_1q.pkl +0 -0
- iqm/benchmarks/randomized_benchmarking/clifford_2q.pkl +0 -0
- iqm/benchmarks/randomized_benchmarking/clifford_rb/__init__.py +19 -0
- iqm/benchmarks/randomized_benchmarking/clifford_rb/clifford_rb.py +386 -0
- iqm/benchmarks/randomized_benchmarking/interleaved_rb/__init__.py +19 -0
- iqm/benchmarks/randomized_benchmarking/interleaved_rb/interleaved_rb.py +555 -0
- iqm/benchmarks/randomized_benchmarking/mirror_rb/__init__.py +19 -0
- iqm/benchmarks/randomized_benchmarking/mirror_rb/mirror_rb.py +810 -0
- iqm/benchmarks/randomized_benchmarking/multi_lmfit.py +86 -0
- iqm/benchmarks/randomized_benchmarking/randomized_benchmarking_common.py +892 -0
- iqm/benchmarks/readout_mitigation.py +290 -0
- iqm/benchmarks/utils.py +521 -0
- iqm_benchmarks-1.3.dist-info/LICENSE +205 -0
- iqm_benchmarks-1.3.dist-info/METADATA +190 -0
- iqm_benchmarks-1.3.dist-info/RECORD +42 -0
- iqm_benchmarks-1.3.dist-info/WHEEL +5 -0
- iqm_benchmarks-1.3.dist-info/top_level.txt +2 -0
- mGST/LICENSE +21 -0
- mGST/README.md +54 -0
- mGST/additional_fns.py +962 -0
- mGST/algorithm.py +733 -0
- mGST/compatibility.py +238 -0
- mGST/low_level_jit.py +694 -0
- mGST/optimization.py +349 -0
- mGST/qiskit_interface.py +282 -0
- mGST/reporting/figure_gen.py +334 -0
- mGST/reporting/reporting.py +710 -0
|
@@ -0,0 +1,892 @@
|
|
|
1
|
+
# Copyright 2024 IQM Benchmarks developers
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
Common functions for Randomized Benchmarking-based techniques
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from importlib import import_module
|
|
20
|
+
from itertools import chain
|
|
21
|
+
import os
|
|
22
|
+
import pickle
|
|
23
|
+
import random
|
|
24
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, cast
|
|
25
|
+
|
|
26
|
+
from lmfit import Parameters, minimize
|
|
27
|
+
from lmfit.minimizer import MinimizerResult
|
|
28
|
+
from matplotlib.collections import PolyCollection
|
|
29
|
+
from matplotlib.figure import Figure
|
|
30
|
+
import matplotlib.pyplot as plt
|
|
31
|
+
import numpy as np
|
|
32
|
+
from qiskit import QuantumCircuit, transpile
|
|
33
|
+
from qiskit.quantum_info import Clifford
|
|
34
|
+
import xarray as xr
|
|
35
|
+
|
|
36
|
+
from iqm.benchmarks.logging_config import qcvv_logger
|
|
37
|
+
from iqm.benchmarks.randomized_benchmarking.multi_lmfit import create_multi_dataset_params, multi_dataset_residual
|
|
38
|
+
from iqm.benchmarks.utils import get_iqm_backend, marginal_distribution, submit_execute, timeit
|
|
39
|
+
from iqm.qiskit_iqm import optimize_single_qubit_gates
|
|
40
|
+
from iqm.qiskit_iqm.iqm_backend import IQMBackendBase
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def compute_inverse_clifford(qc_inv: QuantumCircuit, clifford_dictionary: Dict) -> Optional[QuantumCircuit]:
|
|
44
|
+
"""Function to compute the inverse Clifford of a circuit
|
|
45
|
+
Args:
|
|
46
|
+
qc_inv (QuantumCircuit): The Clifford circuit to be inverted
|
|
47
|
+
clifford_dictionary (Dict): A dictionary of Clifford gates labeled by (de)stabilizers
|
|
48
|
+
Returns:
|
|
49
|
+
Optional[QuantumCircuit]: A Clifford circuit
|
|
50
|
+
"""
|
|
51
|
+
label_inv = str(Clifford(qc_inv).adjoint().to_labels(mode="B"))
|
|
52
|
+
compiled_inverse = cast(dict, clifford_dictionary)[label_inv]
|
|
53
|
+
return compiled_inverse
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def estimate_survival_probabilities(num_qubits: int, counts: List[Dict[str, int]]) -> List[float]:
|
|
57
|
+
"""Compute a result's probability of being on the ground state.
|
|
58
|
+
Args:
|
|
59
|
+
num_qubits (int): the number of qubits
|
|
60
|
+
counts (List[Dict[str, int]]): the result of the execution of a list of quantum circuits (counts)
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
List[float]: the ground state probabilities of the RB sequence
|
|
64
|
+
"""
|
|
65
|
+
return [c["0" * num_qubits] / sum(c.values()) if "0" * num_qubits in c.keys() else 0 for c in counts]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def exponential_rb(
|
|
69
|
+
depths: np.ndarray, depolarization_probability: float, offset: float, amplitude: float
|
|
70
|
+
) -> np.ndarray:
|
|
71
|
+
"""Fit function for interleaved/non-interleaved RB
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
depths (np.ndarray): the depths of the RB experiment
|
|
75
|
+
depolarization_probability (float): the depolarization value (1-p) of the RB decay
|
|
76
|
+
offset (float): the offset of the RB decay
|
|
77
|
+
amplitude (float): the amplitude of the RB decay
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
np.ndarray: the exponential fit function
|
|
81
|
+
"""
|
|
82
|
+
return (amplitude - offset) * (1 - depolarization_probability) ** depths + offset
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def fit_decay_lmfit(
|
|
86
|
+
func: Callable,
|
|
87
|
+
qubit_set: List[int],
|
|
88
|
+
data: List[List[float]] | List[List[List[float]]],
|
|
89
|
+
rb_identifier: str,
|
|
90
|
+
simultaneous_fit_vars: Optional[List[str]] = None,
|
|
91
|
+
) -> Tuple[np.ndarray, Parameters]:
|
|
92
|
+
"""Perform a fitting routine for 0th-order (Ap^m+B) RB using lmfit
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
func (Callable): the model function for fitting
|
|
96
|
+
qubit_set (List[int]): the qubits entering the model
|
|
97
|
+
data (List[List[float]] | List[List[List[float]]]): the data to be fitted
|
|
98
|
+
rb_identifier (str): the RB identifier, either "stdrb", "irb" or "mrb"
|
|
99
|
+
simultaneous_fit_vars (List[str], optional): the list of variables used to fit simultaneously
|
|
100
|
+
Returns:
|
|
101
|
+
A tuple of fitting data (list of lists of average fidelities or polarizations) and MRB fit parameters
|
|
102
|
+
"""
|
|
103
|
+
n_qubits = len(qubit_set)
|
|
104
|
+
|
|
105
|
+
fidelity_guess = 0.988**n_qubits
|
|
106
|
+
offset_guess = 0.2 if n_qubits == 2 else 0.65
|
|
107
|
+
amplitude_guess = 0.8 if n_qubits == 2 else 0.35
|
|
108
|
+
|
|
109
|
+
estimates = {
|
|
110
|
+
"depolarization_probability": 2 * (1 - fidelity_guess),
|
|
111
|
+
"offset": offset_guess,
|
|
112
|
+
"amplitude": amplitude_guess + offset_guess,
|
|
113
|
+
}
|
|
114
|
+
constraints = {
|
|
115
|
+
"depolarization_probability": {"min": 0, "max": 1},
|
|
116
|
+
"offset": {"min": 0, "max": 1},
|
|
117
|
+
"amplitude": {"min": 0, "max": 1},
|
|
118
|
+
}
|
|
119
|
+
if rb_identifier in ("clifford", "mrb"):
|
|
120
|
+
fit_data = np.array([np.mean(data, axis=1)])
|
|
121
|
+
if rb_identifier == "clifford":
|
|
122
|
+
params = create_multi_dataset_params(
|
|
123
|
+
func, fit_data, initial_guesses=estimates, constraints=None, simultaneously_fit_vars=None
|
|
124
|
+
)
|
|
125
|
+
params.add(f"p_rb", expr=f"1-depolarization_probability_{1}")
|
|
126
|
+
params.add(f"fidelity_per_clifford", expr=f"p_rb + (1 - p_rb) / (2**{n_qubits})")
|
|
127
|
+
else:
|
|
128
|
+
params = create_multi_dataset_params(
|
|
129
|
+
func, fit_data, initial_guesses=None, constraints=constraints, simultaneously_fit_vars=None
|
|
130
|
+
)
|
|
131
|
+
params.add(f"p_mrb", expr=f"1-depolarization_probability_{1}")
|
|
132
|
+
params.add(f"fidelity_mrb", expr=f"1 - (1 - p_mrb) * (1 - 1 / (4 ** {n_qubits}))")
|
|
133
|
+
else:
|
|
134
|
+
fit_data = np.array([np.mean(data[0], axis=1), np.mean(data[1], axis=1)])
|
|
135
|
+
params = create_multi_dataset_params(
|
|
136
|
+
func,
|
|
137
|
+
fit_data,
|
|
138
|
+
initial_guesses=estimates,
|
|
139
|
+
constraints=None,
|
|
140
|
+
simultaneously_fit_vars=simultaneous_fit_vars,
|
|
141
|
+
)
|
|
142
|
+
params.add(f"p_rb", expr=f"1-depolarization_probability_{1}")
|
|
143
|
+
params.add(f"fidelity_per_clifford", expr=f"p_rb + (1 - p_rb) / (2**{n_qubits})")
|
|
144
|
+
params.add(f"p_irb", expr=f"1-depolarization_probability_{2}")
|
|
145
|
+
params.add(f"interleaved_fidelity", expr=f"p_irb / p_rb + (1 - p_irb / p_rb) / (2**{n_qubits})")
|
|
146
|
+
|
|
147
|
+
return fit_data, params
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@timeit
|
|
151
|
+
def generate_all_rb_circuits(
|
|
152
|
+
qubits: List[int],
|
|
153
|
+
sequence_lengths: List[int],
|
|
154
|
+
clifford_dict: Dict[str, QuantumCircuit],
|
|
155
|
+
num_circuit_samples: int,
|
|
156
|
+
backend_arg: str | IQMBackendBase,
|
|
157
|
+
interleaved_gate: Optional[QuantumCircuit],
|
|
158
|
+
) -> Tuple[Dict[int, List[QuantumCircuit]], Dict[int, List[QuantumCircuit]]]:
|
|
159
|
+
"""
|
|
160
|
+
Args:
|
|
161
|
+
qubits (List[int]): List of qubits
|
|
162
|
+
sequence_lengths (List[int]): List of sequence lengths
|
|
163
|
+
clifford_dict (Dict[str, QuantumCircuit]): the dictionary of Clifford circuits
|
|
164
|
+
num_circuit_samples (int): the number of circuits samples
|
|
165
|
+
backend_arg (str | IQMBackendBase): the backend fir which to generate the circuits.
|
|
166
|
+
interleaved_gate (str): the name of the interleaved gate
|
|
167
|
+
Returns:
|
|
168
|
+
Tuple of untranspiled and transpiled circuits for all class-defined sequence lengths
|
|
169
|
+
"""
|
|
170
|
+
untranspiled = {}
|
|
171
|
+
transpiled = {}
|
|
172
|
+
for s in sequence_lengths:
|
|
173
|
+
qcvv_logger.info(f"Now at sequence length {s}")
|
|
174
|
+
untranspiled[s], transpiled[s] = generate_random_clifford_seq_circuits(
|
|
175
|
+
qubits, clifford_dict, s, num_circuit_samples, backend_arg, interleaved_gate
|
|
176
|
+
)
|
|
177
|
+
return untranspiled, transpiled
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# pylint: disable=too-many-branches, disable=too-many-statements
|
|
181
|
+
@timeit
|
|
182
|
+
def generate_fixed_depth_parallel_rb_circuits(
|
|
183
|
+
qubits_array: List[List[int]],
|
|
184
|
+
cliffords_1q: Dict[str, QuantumCircuit],
|
|
185
|
+
cliffords_2q: Dict[str, QuantumCircuit],
|
|
186
|
+
sequence_length: int,
|
|
187
|
+
num_samples: int,
|
|
188
|
+
backend_arg: IQMBackendBase | str,
|
|
189
|
+
interleaved_gate: Optional[QuantumCircuit] = None,
|
|
190
|
+
) -> Tuple[List[QuantumCircuit], List[QuantumCircuit]]:
|
|
191
|
+
"""Generates parallel RB circuits, before and after transpilation, at fixed depth
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
qubits_array (List[List[int]]): the qubits entering the quantum circuits
|
|
195
|
+
cliffords_1q (Dict[str, QuantumCircuit]): dictionary of 1-qubit Cliffords in terms of IQM-native r and CZ gates
|
|
196
|
+
cliffords_2q (Dict[str, QuantumCircuit]): dictionary of 2-qubit Cliffords in terms of IQM-native r and CZ gates
|
|
197
|
+
sequence_length (int): the number of random Cliffords in the circuits
|
|
198
|
+
num_samples (int): the number of circuit samples
|
|
199
|
+
backend_arg (IQMBackendBase | str): the backend to transpile the circuits to
|
|
200
|
+
interleaved_gate (Optional[QuantumCircuit]): whether the circuits should have interleaved gates
|
|
201
|
+
Returns:
|
|
202
|
+
A list of QuantumCircuits of given RB sequence length for parallel RB
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
if isinstance(backend_arg, str):
|
|
206
|
+
backend = get_iqm_backend(backend_arg)
|
|
207
|
+
else:
|
|
208
|
+
backend = backend_arg
|
|
209
|
+
|
|
210
|
+
# Identify total amount of qubits
|
|
211
|
+
qubit_counts = [len(x) for x in qubits_array]
|
|
212
|
+
|
|
213
|
+
# Shuffle qubits_array: we don't want unnecessary qubit registers
|
|
214
|
+
shuffled_qubits_array = relabel_qubits_array_from_zero(qubits_array)
|
|
215
|
+
# The total amount of qubits the circuits will have
|
|
216
|
+
n_qubits = sum(qubit_counts)
|
|
217
|
+
|
|
218
|
+
# Get the keys of the Clifford dictionaries
|
|
219
|
+
clifford_1q_keys = list(cliffords_1q.keys())
|
|
220
|
+
clifford_2q_keys = list(cliffords_2q.keys())
|
|
221
|
+
|
|
222
|
+
# Generate the circuit samples
|
|
223
|
+
circuits_list: List[QuantumCircuit] = []
|
|
224
|
+
circuits_transpiled_list: List[QuantumCircuit] = []
|
|
225
|
+
for _ in range(num_samples):
|
|
226
|
+
circuit = QuantumCircuit(n_qubits)
|
|
227
|
+
# Need to track inverses on each qubit, or each pair of qubits, separately
|
|
228
|
+
circ_inverses = [QuantumCircuit(n) for n in qubit_counts]
|
|
229
|
+
# Place Clifford sequences on each
|
|
230
|
+
for _ in range(sequence_length):
|
|
231
|
+
# Sample the random Cliffords for each qubit or qubits
|
|
232
|
+
cliffords = []
|
|
233
|
+
for n in qubit_counts:
|
|
234
|
+
if n == 1:
|
|
235
|
+
rand_key = random.choice(clifford_1q_keys)
|
|
236
|
+
c_1q = cast(dict, cliffords_1q)[rand_key]
|
|
237
|
+
cliffords.append(c_1q)
|
|
238
|
+
else:
|
|
239
|
+
rand_key = random.choice(clifford_2q_keys)
|
|
240
|
+
c_2q = cast(dict, cliffords_2q)[rand_key]
|
|
241
|
+
cliffords.append(c_2q)
|
|
242
|
+
# Compose the sampled Cliffords in the circuit and add barrier
|
|
243
|
+
for n, shuffled_qubits in enumerate(shuffled_qubits_array):
|
|
244
|
+
circuit.compose(cliffords[n], qubits=shuffled_qubits, inplace=True)
|
|
245
|
+
circ_inverses[n].compose(cliffords[n], qubits=list(range(qubit_counts[n])), inplace=True)
|
|
246
|
+
circuit.barrier() # NB: mind the barriers!
|
|
247
|
+
|
|
248
|
+
if interleaved_gate is not None:
|
|
249
|
+
for n, shuffled_qubits in enumerate(shuffled_qubits_array):
|
|
250
|
+
circuit.compose(interleaved_gate, qubits=shuffled_qubits, inplace=True)
|
|
251
|
+
circ_inverses[n].compose(interleaved_gate, qubits=list(range(qubit_counts[n])), inplace=True)
|
|
252
|
+
circuit.barrier()
|
|
253
|
+
|
|
254
|
+
# Compile and compose the inverse
|
|
255
|
+
# compiled_inverse_clifford = []
|
|
256
|
+
for n, shuffled_qubits in enumerate(shuffled_qubits_array):
|
|
257
|
+
clifford_dict = cliffords_1q if qubit_counts[n] == 1 else cliffords_2q
|
|
258
|
+
circuit.compose(
|
|
259
|
+
compute_inverse_clifford(circ_inverses[n], clifford_dict),
|
|
260
|
+
qubits=shuffled_qubits,
|
|
261
|
+
inplace=True,
|
|
262
|
+
)
|
|
263
|
+
# Add measurement
|
|
264
|
+
circuit.measure_all()
|
|
265
|
+
|
|
266
|
+
circuits_list.append(circuit)
|
|
267
|
+
# Transpilation here only means to the layout - avoid using automated transpilers!
|
|
268
|
+
circuit_transpiled = QuantumCircuit(backend.num_qubits)
|
|
269
|
+
qubits_array_flat = [x for y in qubits_array for x in y]
|
|
270
|
+
circuit_transpiled.compose(circuit, qubits=qubits_array_flat, inplace=True)
|
|
271
|
+
circuits_transpiled_list.append(circuit_transpiled)
|
|
272
|
+
|
|
273
|
+
return circuits_list, circuits_transpiled_list
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def generate_random_clifford_seq_circuits(
|
|
277
|
+
qubits: List[int],
|
|
278
|
+
clifford_dict: Dict[str, QuantumCircuit],
|
|
279
|
+
seq_length: int,
|
|
280
|
+
num_circ_samples: int,
|
|
281
|
+
backend_arg: str | IQMBackendBase,
|
|
282
|
+
interleaved_gate: Optional[QuantumCircuit] = None,
|
|
283
|
+
) -> Tuple[List[QuantumCircuit], List[QuantumCircuit]]:
|
|
284
|
+
"""Generate random Clifford circuits in native gates for a given sequence length.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
qubits (List[int]): the list of qubits
|
|
288
|
+
clifford_dict (Dict[str, QuantumCircuit]): A dictionary of Clifford gates labeled by (de)stabilizers
|
|
289
|
+
seq_length (int): the sequence length
|
|
290
|
+
num_circ_samples (int): the number of samples
|
|
291
|
+
backend_arg (str | IQMBackendBase):
|
|
292
|
+
interleaved_gate (Optional[QuantumCircuit]): Clifford native gate to be interleaved - None by default
|
|
293
|
+
Returns:
|
|
294
|
+
List[QuantumCircuit]: the list of `self.num_samples` random Clifford quantum circuits
|
|
295
|
+
"""
|
|
296
|
+
if isinstance(backend_arg, str):
|
|
297
|
+
backend = get_iqm_backend(backend_arg)
|
|
298
|
+
else:
|
|
299
|
+
backend = backend_arg
|
|
300
|
+
|
|
301
|
+
qc_list = [] # List of random Clifford circuits
|
|
302
|
+
qc_list_transpiled = []
|
|
303
|
+
num_qubits = len(qubits)
|
|
304
|
+
logic_qubits = list(range(num_qubits))
|
|
305
|
+
|
|
306
|
+
if num_qubits > 2:
|
|
307
|
+
raise ValueError("Please specify qubit layouts with only n=1 or n=2 qubits. Run MRB for n>2 instead.")
|
|
308
|
+
|
|
309
|
+
clifford_keys = []
|
|
310
|
+
if clifford_dict is not None:
|
|
311
|
+
clifford_keys = list(clifford_dict.keys())
|
|
312
|
+
|
|
313
|
+
for _ in range(num_circ_samples):
|
|
314
|
+
qc = QuantumCircuit(num_qubits)
|
|
315
|
+
qc_inv = QuantumCircuit(num_qubits)
|
|
316
|
+
# Compose circuit with random Clifford gates
|
|
317
|
+
for _ in range(seq_length):
|
|
318
|
+
rand_key = random.choice(clifford_keys)
|
|
319
|
+
clifford = cast(dict, clifford_dict)[rand_key]
|
|
320
|
+
# Append clifford
|
|
321
|
+
qc.compose(clifford, qubits=logic_qubits, inplace=True)
|
|
322
|
+
# Append clifford gate to circuit to invert
|
|
323
|
+
qc_inv.compose(clifford, qubits=logic_qubits, inplace=True)
|
|
324
|
+
qc.barrier()
|
|
325
|
+
# Append interleaved if not None
|
|
326
|
+
if interleaved_gate is not None:
|
|
327
|
+
qc.compose(interleaved_gate, qubits=logic_qubits, inplace=True)
|
|
328
|
+
qc_inv.compose(interleaved_gate, qubits=logic_qubits, inplace=True)
|
|
329
|
+
qc.barrier()
|
|
330
|
+
# Append the inverse
|
|
331
|
+
qc.compose(
|
|
332
|
+
compute_inverse_clifford(qc_inv, clifford_dict),
|
|
333
|
+
qubits=logic_qubits,
|
|
334
|
+
inplace=True,
|
|
335
|
+
)
|
|
336
|
+
qc.measure_all()
|
|
337
|
+
|
|
338
|
+
qc_list.append(qc)
|
|
339
|
+
qc_transpiled = QuantumCircuit(backend.num_qubits)
|
|
340
|
+
qc_transpiled.compose(qc, qubits=qubits, inplace=True)
|
|
341
|
+
qc_list_transpiled.append(qc_transpiled)
|
|
342
|
+
|
|
343
|
+
return qc_list, qc_list_transpiled
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def get_survival_probabilities(num_qubits: int, counts: List[Dict[str, int]]) -> List[float]:
|
|
347
|
+
"""Compute a result's probability of being on the ground state.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
num_qubits (int): the number of qubits
|
|
351
|
+
counts (List[Dict[str, int]]): the result of the execution of a list of quantum circuits (counts)
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
List[float]: the ground state probabilities of the RB sequence
|
|
355
|
+
"""
|
|
356
|
+
return [c["0" * num_qubits] / sum(c.values()) if "0" * num_qubits in c.keys() else 0 for c in counts]
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def import_native_gate_cliffords() -> Tuple[Dict[str, QuantumCircuit], Dict[str, QuantumCircuit]]:
|
|
360
|
+
"""Import native gate Clifford dictionaries
|
|
361
|
+
Returns:
|
|
362
|
+
Dictionaries of 1Q and 2Q Clifford gates
|
|
363
|
+
"""
|
|
364
|
+
# Import the native-gate Cliffords
|
|
365
|
+
with open(os.path.join(os.path.dirname(__file__), "clifford_1q.pkl"), "rb") as f1q:
|
|
366
|
+
clifford_1q_dict = pickle.load(f1q)
|
|
367
|
+
with open(os.path.join(os.path.dirname(__file__), "clifford_2q.pkl"), "rb") as f2q:
|
|
368
|
+
clifford_2q_dict = pickle.load(f2q)
|
|
369
|
+
qcvv_logger.info("Clifford dictionaries imported successfully !")
|
|
370
|
+
return clifford_1q_dict, clifford_2q_dict
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def lmfit_minimizer(
|
|
374
|
+
fit_parameters: Parameters, fit_data: np.ndarray, depths: List[int], func: Callable
|
|
375
|
+
) -> MinimizerResult:
|
|
376
|
+
"""
|
|
377
|
+
Args:
|
|
378
|
+
fit_parameters (Parameters): the parameters to fit
|
|
379
|
+
fit_data (np.ndarray): the data to fit
|
|
380
|
+
depths (List[int]): the depths of the RB experiment
|
|
381
|
+
func (Callable): the model function for fitting
|
|
382
|
+
Returns:
|
|
383
|
+
MinimizerResult: the result of the minimization
|
|
384
|
+
"""
|
|
385
|
+
return minimize(
|
|
386
|
+
fcn=multi_dataset_residual,
|
|
387
|
+
params=fit_parameters,
|
|
388
|
+
args=(depths, fit_data, func),
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def relabel_qubits_array_from_zero(arr: List[List[int]]) -> List[List[int]]:
|
|
393
|
+
"""Helper function to relabel a qubits array to an increasingly ordered one starting from zero
|
|
394
|
+
e.g., [[2,3], [5], [7,8]] -> [[0,1], [2], [3,4]]
|
|
395
|
+
Note: this assumes the input array is sorted in increasing order!
|
|
396
|
+
"""
|
|
397
|
+
# Flatten the original array
|
|
398
|
+
flat_list = [item for sublist in arr for item in sublist]
|
|
399
|
+
# Generate a list of ordered numbers with the same length as the flattened array
|
|
400
|
+
ordered_indices = list(range(len(flat_list)))
|
|
401
|
+
# Reconstruct the list of lists structure
|
|
402
|
+
result = []
|
|
403
|
+
index = 0
|
|
404
|
+
for sublist in arr:
|
|
405
|
+
result.append(ordered_indices[index : index + len(sublist)])
|
|
406
|
+
index += len(sublist)
|
|
407
|
+
return result
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def submit_parallel_rb_job(
|
|
411
|
+
backend_arg: IQMBackendBase,
|
|
412
|
+
qubits_array: List[List[int]],
|
|
413
|
+
depth: int,
|
|
414
|
+
sorted_transpiled_circuit_dicts: Dict[Tuple[int, ...], List[QuantumCircuit]],
|
|
415
|
+
shots: int,
|
|
416
|
+
calset_id: Optional[str],
|
|
417
|
+
max_gates_per_batch: Optional[str],
|
|
418
|
+
) -> Dict[str, Any]:
|
|
419
|
+
"""Submit fixed-depth parallel MRB jobs for execution in the specified IQMBackend
|
|
420
|
+
Args:
|
|
421
|
+
backend_arg (IQMBackendBase): the IQM backend to submit the job
|
|
422
|
+
qubits_array (List[int]): the qubits to identify the submitted job
|
|
423
|
+
depth (int): the depth (number of canonical layers) of the circuits to identify the submitted job
|
|
424
|
+
sorted_transpiled_circuit_dicts (Dict[Tuple[int,...], List[QuantumCircuit]]): A dictionary containing all MRB circuits
|
|
425
|
+
shots (int): the number of shots to submit the job
|
|
426
|
+
calset_id (Optional[str]): the calibration identifier
|
|
427
|
+
max_gates_per_batch (Optional[str]): the maximum number of gates per batch to submit the job
|
|
428
|
+
Returns:
|
|
429
|
+
Dict with qubit layout, submitted job objects, type (vanilla/DD) and submission time
|
|
430
|
+
"""
|
|
431
|
+
# Submit
|
|
432
|
+
# Send to execute on backend
|
|
433
|
+
# pylint: disable=unbalanced-tuple-unpacking
|
|
434
|
+
execution_jobs, time_submit = submit_execute(
|
|
435
|
+
sorted_transpiled_circuit_dicts, backend_arg, shots, calset_id, max_gates_per_batch=max_gates_per_batch
|
|
436
|
+
)
|
|
437
|
+
rb_submit_results = {
|
|
438
|
+
"qubits": qubits_array,
|
|
439
|
+
"depth": depth,
|
|
440
|
+
"jobs": execution_jobs,
|
|
441
|
+
"time_submit": time_submit,
|
|
442
|
+
}
|
|
443
|
+
return rb_submit_results
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def submit_sequential_rb_jobs(
|
|
447
|
+
qubits: List[int],
|
|
448
|
+
transpiled_circuits: Dict[int, List[QuantumCircuit]],
|
|
449
|
+
shots: int,
|
|
450
|
+
backend_arg: str | IQMBackendBase,
|
|
451
|
+
calset_id: Optional[str] = None,
|
|
452
|
+
max_gates_per_batch: Optional[int] = None,
|
|
453
|
+
) -> List[Dict[str, Any]]:
|
|
454
|
+
"""Submit sequential RB jobs for execution in the specified IQMBackend
|
|
455
|
+
Args:
|
|
456
|
+
qubits (List[int]): the qubits to identify the submitted job
|
|
457
|
+
transpiled_circuits (Dict[str, List[QuantumCircuit]]): A dictionary containing all MRB circuits
|
|
458
|
+
shots (int): the number of shots to submit per job
|
|
459
|
+
backend_arg (IQMBackendBase): the IQM backend to submit the job
|
|
460
|
+
calset_id (Optional[str]): the calibration identifier
|
|
461
|
+
max_gates_per_batch (Optional[int]): the maximum number of gates per batch
|
|
462
|
+
Returns:
|
|
463
|
+
Dict with qubit layout, submitted job objects, type (vanilla/DD) and submission time
|
|
464
|
+
"""
|
|
465
|
+
if isinstance(backend_arg, str):
|
|
466
|
+
backend = get_iqm_backend(backend_arg)
|
|
467
|
+
else:
|
|
468
|
+
backend = backend_arg
|
|
469
|
+
|
|
470
|
+
all_submit_results = []
|
|
471
|
+
rb_submit_results = {}
|
|
472
|
+
for depth in transpiled_circuits.keys():
|
|
473
|
+
# Submit - send to execute on backend
|
|
474
|
+
# pylint: disable=unbalanced-tuple-unpacking
|
|
475
|
+
execution_jobs, time_submit = submit_execute(
|
|
476
|
+
{tuple(qubits): transpiled_circuits[depth]}, backend, shots, calset_id, max_gates_per_batch
|
|
477
|
+
)
|
|
478
|
+
rb_submit_results[depth] = {
|
|
479
|
+
"qubits": qubits,
|
|
480
|
+
"depth": depth,
|
|
481
|
+
"jobs": execution_jobs,
|
|
482
|
+
"time_submit": time_submit,
|
|
483
|
+
}
|
|
484
|
+
all_submit_results.append(rb_submit_results[depth])
|
|
485
|
+
|
|
486
|
+
return all_submit_results
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def survival_probabilities_parallel(
|
|
490
|
+
qubits_array: List[List[int]], counts: List[Dict[str, int]]
|
|
491
|
+
) -> Dict[str, List[float]]:
|
|
492
|
+
"""Estimates marginalized survival probabilities from a parallel RB execution (at fixed depth)
|
|
493
|
+
Args:
|
|
494
|
+
qubits_array (List[int]): List of qubits in which the experiment was performed
|
|
495
|
+
counts (Dict[str, int]): The measurement counts for corresponding bitstrings
|
|
496
|
+
Returns:
|
|
497
|
+
Dict[str, List[float]]: The survival probabilities for each qubit
|
|
498
|
+
"""
|
|
499
|
+
# Global probability estimations
|
|
500
|
+
global_probabilities = [{k: v / sum(c.values()) for k, v in c.items()} for c in counts]
|
|
501
|
+
|
|
502
|
+
# The bit indices
|
|
503
|
+
all_bit_indices = relabel_qubits_array_from_zero(qubits_array)
|
|
504
|
+
|
|
505
|
+
# Estimate all marginal probabilities
|
|
506
|
+
marginal_probabilities: Dict[str, List[Dict[str, float]]] = {str(q): [] for q in qubits_array}
|
|
507
|
+
for position, indices in enumerate(all_bit_indices):
|
|
508
|
+
marginal_probabilities[str(qubits_array[position])] = [
|
|
509
|
+
marginal_distribution(global_probability, indices) for global_probability in global_probabilities
|
|
510
|
+
]
|
|
511
|
+
|
|
512
|
+
# Estimate the survival probabilities in the marginal distributions
|
|
513
|
+
marginal_survival_probabilities: Dict[str, List[float]] = {str(q): [] for q in qubits_array}
|
|
514
|
+
for q in qubits_array:
|
|
515
|
+
n_qubits = len(q)
|
|
516
|
+
marginal_survival_probabilities[str(q)] = [
|
|
517
|
+
c["0" * n_qubits] / sum(c.values()) if "0" * n_qubits in c.keys() else 0
|
|
518
|
+
for c in marginal_probabilities[str(q)]
|
|
519
|
+
]
|
|
520
|
+
|
|
521
|
+
return marginal_survival_probabilities
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
# pylint: disable=too-many-statements
|
|
525
|
+
def plot_rb_decay(
|
|
526
|
+
identifier: str,
|
|
527
|
+
qubits_array: List[List[int]],
|
|
528
|
+
dataset: xr.Dataset,
|
|
529
|
+
observations: Dict[int, Dict[str, Any]],
|
|
530
|
+
violin: bool = True,
|
|
531
|
+
scatter: bool = True,
|
|
532
|
+
bars: bool = False,
|
|
533
|
+
shade_stdev: bool = False,
|
|
534
|
+
shade_meanerror: bool = False,
|
|
535
|
+
logscale: bool = True,
|
|
536
|
+
interleaved_gate: Optional[str] = None,
|
|
537
|
+
mrb_2q_density: Optional[float] = None,
|
|
538
|
+
mrb_2q_ensemble: Optional[Dict[str, float]] = None,
|
|
539
|
+
) -> Tuple[str, Figure]:
|
|
540
|
+
"""Plot the fidelity decay and the fit to the model.
|
|
541
|
+
|
|
542
|
+
Args:
|
|
543
|
+
identifier (str): the type of RB experiment
|
|
544
|
+
qubits_array (List[List[int]]): Array of sets of qubits for which to plot decays
|
|
545
|
+
dataset (xr.dataset): the dataset from the experiment
|
|
546
|
+
observations (Dict[str, Dict[str, Any]]): the corresponding observations from the experiment
|
|
547
|
+
bars (bool, optional): Whether error bars are plotted or not. Defaults to False
|
|
548
|
+
violin (bool, optional): Whether violins are plotted or not. Defaults to True
|
|
549
|
+
scatter (bool, optional): Whether all individual points are plotted or not. Defaults to True
|
|
550
|
+
shade_stdev (bool, optional): Whether standard deviations are shaded or not. Defaults to False
|
|
551
|
+
shade_meanerror (bool, optional): Whether to shade standard deviations. Defaults to False
|
|
552
|
+
logscale (bool, optional): Whether x-axis uses logscale. Defaults to True
|
|
553
|
+
interleaved_gate (Optional[str]):
|
|
554
|
+
mrb_2q_density (Optional[float], optional): Density of MRB 2Q gates. Defaults to None.
|
|
555
|
+
mrb_2q_ensemble (Optional[Dict[str, float]], optional): MRB ensemble of 2Q gates. Defaults to None.
|
|
556
|
+
|
|
557
|
+
Returns:
|
|
558
|
+
Tuple[str, Figure]: the plot title and the figure
|
|
559
|
+
"""
|
|
560
|
+
fig, ax = plt.subplots()
|
|
561
|
+
|
|
562
|
+
cmap = plt.get_cmap("winter")
|
|
563
|
+
|
|
564
|
+
num_circuit_samples = dataset.attrs["num_circuit_samples"]
|
|
565
|
+
timestamp = dataset.attrs["execution_timestamp"]
|
|
566
|
+
backend_name = dataset.attrs["backend_name"]
|
|
567
|
+
|
|
568
|
+
# Fetch the relevant observations indexed by qubit layouts
|
|
569
|
+
depths = {}
|
|
570
|
+
polarizations = {}
|
|
571
|
+
average_polarizations = {}
|
|
572
|
+
stddevs_from_mean = {}
|
|
573
|
+
fidelity_value = {}
|
|
574
|
+
fidelity_stderr = {}
|
|
575
|
+
decay_rate = {}
|
|
576
|
+
offset = {}
|
|
577
|
+
amplitude = {}
|
|
578
|
+
rb_type_keys = []
|
|
579
|
+
colors = []
|
|
580
|
+
if identifier != "irb":
|
|
581
|
+
rb_type_keys = [identifier]
|
|
582
|
+
colors = [cmap(i) for i in np.linspace(start=1, stop=0, num=len(qubits_array)).tolist()]
|
|
583
|
+
if identifier == "mrb":
|
|
584
|
+
depths[identifier] = {
|
|
585
|
+
str(q): list(dataset.attrs[q_idx]["polarizations"].keys()) for q_idx, q in enumerate(qubits_array)
|
|
586
|
+
}
|
|
587
|
+
polarizations[identifier] = {
|
|
588
|
+
str(q): dataset.attrs[q_idx]["polarizations"] for q_idx, q in enumerate(qubits_array)
|
|
589
|
+
}
|
|
590
|
+
average_polarizations[identifier] = {
|
|
591
|
+
str(q): dataset.attrs[q_idx]["avg_polarization_nominal_values"] for q_idx, q in enumerate(qubits_array)
|
|
592
|
+
}
|
|
593
|
+
stddevs_from_mean[identifier] = {
|
|
594
|
+
str(q): dataset.attrs[q_idx]["avg_polatization_stderr"] for q_idx, q in enumerate(qubits_array)
|
|
595
|
+
}
|
|
596
|
+
else:
|
|
597
|
+
depths[identifier] = {
|
|
598
|
+
str(q): list(dataset.attrs[q_idx]["fidelities"].keys()) for q_idx, q in enumerate(qubits_array)
|
|
599
|
+
}
|
|
600
|
+
polarizations[identifier] = {
|
|
601
|
+
str(q): dataset.attrs[q_idx]["fidelities"] for q_idx, q in enumerate(qubits_array)
|
|
602
|
+
}
|
|
603
|
+
average_polarizations[identifier] = {
|
|
604
|
+
str(q): dataset.attrs[q_idx]["avg_fidelities_nominal_values"] for q_idx, q in enumerate(qubits_array)
|
|
605
|
+
}
|
|
606
|
+
stddevs_from_mean[identifier] = {
|
|
607
|
+
str(q): dataset.attrs[q_idx]["avg_fidelities_stderr"] for q_idx, q in enumerate(qubits_array)
|
|
608
|
+
}
|
|
609
|
+
# These are common to both MRB and standard Clifford
|
|
610
|
+
fidelity_value[identifier] = {
|
|
611
|
+
str(q): observations[q_idx]["avg_gate_fidelity"]["value"] for q_idx, q in enumerate(qubits_array)
|
|
612
|
+
}
|
|
613
|
+
fidelity_stderr[identifier] = {
|
|
614
|
+
str(q): observations[q_idx]["avg_gate_fidelity"]["uncertainty"] for q_idx, q in enumerate(qubits_array)
|
|
615
|
+
}
|
|
616
|
+
decay_rate[identifier] = {
|
|
617
|
+
str(q): observations[q_idx]["decay_rate"]["value"] for q_idx, q in enumerate(qubits_array)
|
|
618
|
+
}
|
|
619
|
+
offset[identifier] = {
|
|
620
|
+
str(q): observations[q_idx]["fit_offset"]["value"] for q_idx, q in enumerate(qubits_array)
|
|
621
|
+
}
|
|
622
|
+
amplitude[identifier] = {
|
|
623
|
+
str(q): observations[q_idx]["fit_amplitude"]["value"] for q_idx, q in enumerate(qubits_array)
|
|
624
|
+
}
|
|
625
|
+
else:
|
|
626
|
+
rb_type_keys = list(observations[0].keys())
|
|
627
|
+
colors = [cmap(i) for i in np.linspace(start=1, stop=0, num=len(rb_type_keys)).tolist()]
|
|
628
|
+
for rb_type in rb_type_keys:
|
|
629
|
+
depths[rb_type] = {
|
|
630
|
+
str(q): list(dataset.attrs[q_idx][rb_type]["fidelities"].keys()) for q_idx, q in enumerate(qubits_array)
|
|
631
|
+
}
|
|
632
|
+
polarizations[rb_type] = {
|
|
633
|
+
str(q): dataset.attrs[q_idx][rb_type]["fidelities"] for q_idx, q in enumerate(qubits_array)
|
|
634
|
+
}
|
|
635
|
+
average_polarizations[rb_type] = {
|
|
636
|
+
str(q): dataset.attrs[q_idx][rb_type]["avg_fidelities_nominal_values"]
|
|
637
|
+
for q_idx, q in enumerate(qubits_array)
|
|
638
|
+
}
|
|
639
|
+
stddevs_from_mean[rb_type] = {
|
|
640
|
+
str(q): dataset.attrs[q_idx][rb_type]["avg_fidelities_stderr"] for q_idx, q in enumerate(qubits_array)
|
|
641
|
+
}
|
|
642
|
+
fidelity_value[rb_type] = {
|
|
643
|
+
str(q): observations[q_idx][rb_type]["avg_gate_fidelity"]["value"]
|
|
644
|
+
for q_idx, q in enumerate(qubits_array)
|
|
645
|
+
}
|
|
646
|
+
fidelity_stderr[rb_type] = {
|
|
647
|
+
str(q): observations[q_idx][rb_type]["avg_gate_fidelity"]["uncertainty"]
|
|
648
|
+
for q_idx, q in enumerate(qubits_array)
|
|
649
|
+
}
|
|
650
|
+
decay_rate[rb_type] = {
|
|
651
|
+
str(q): observations[q_idx][rb_type]["decay_rate"]["value"] for q_idx, q in enumerate(qubits_array)
|
|
652
|
+
}
|
|
653
|
+
offset[rb_type] = {
|
|
654
|
+
str(q): observations[q_idx][rb_type]["fit_offset"]["value"] for q_idx, q in enumerate(qubits_array)
|
|
655
|
+
}
|
|
656
|
+
amplitude[rb_type] = {
|
|
657
|
+
str(q): observations[q_idx][rb_type]["fit_amplitude"]["value"] for q_idx, q in enumerate(qubits_array)
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
for index_irb, key in enumerate(rb_type_keys):
|
|
661
|
+
for index_qubits, qubits in enumerate(qubits_array):
|
|
662
|
+
# Index for colors
|
|
663
|
+
index = index_irb if identifier == "irb" else index_qubits
|
|
664
|
+
# Plot Averages
|
|
665
|
+
# Draw the averages (w or w/o error bars)
|
|
666
|
+
if bars:
|
|
667
|
+
y_err_f = stddevs_from_mean[key][str(qubits)]
|
|
668
|
+
else:
|
|
669
|
+
y_err_f = None
|
|
670
|
+
ax.errorbar(
|
|
671
|
+
depths[key][str(qubits)],
|
|
672
|
+
average_polarizations[key][str(qubits)].values(),
|
|
673
|
+
yerr=y_err_f,
|
|
674
|
+
# label=f"Avg with {num_circ_samples} samples",
|
|
675
|
+
capsize=4,
|
|
676
|
+
color=colors[index],
|
|
677
|
+
fmt="o",
|
|
678
|
+
mec="black",
|
|
679
|
+
alpha=1,
|
|
680
|
+
markersize=5,
|
|
681
|
+
)
|
|
682
|
+
if shade_stdev:
|
|
683
|
+
# Shade the standard deviation
|
|
684
|
+
ax.fill_between(
|
|
685
|
+
depths[key][str(qubits)],
|
|
686
|
+
[
|
|
687
|
+
a - np.sqrt(num_circuit_samples) * b
|
|
688
|
+
for a, b in zip(
|
|
689
|
+
average_polarizations[key][str(qubits)].values(),
|
|
690
|
+
stddevs_from_mean[key][str(qubits)].values(),
|
|
691
|
+
)
|
|
692
|
+
],
|
|
693
|
+
[
|
|
694
|
+
a + np.sqrt(num_circuit_samples) * b
|
|
695
|
+
for a, b in zip(
|
|
696
|
+
average_polarizations[key][str(qubits)].values(),
|
|
697
|
+
stddevs_from_mean[key][str(qubits)].values(),
|
|
698
|
+
)
|
|
699
|
+
],
|
|
700
|
+
color=colors[index],
|
|
701
|
+
alpha=0.2,
|
|
702
|
+
interpolate=True,
|
|
703
|
+
)
|
|
704
|
+
if shade_meanerror:
|
|
705
|
+
# Shade the error from the mean
|
|
706
|
+
ax.fill_between(
|
|
707
|
+
depths[key][str(qubits)],
|
|
708
|
+
[
|
|
709
|
+
a - b
|
|
710
|
+
for a, b in zip(
|
|
711
|
+
average_polarizations[key][str(qubits)].values(),
|
|
712
|
+
stddevs_from_mean[key][str(qubits)].values(),
|
|
713
|
+
)
|
|
714
|
+
],
|
|
715
|
+
[
|
|
716
|
+
a + b
|
|
717
|
+
for a, b in zip(
|
|
718
|
+
average_polarizations[key][str(qubits)].values(),
|
|
719
|
+
stddevs_from_mean[key][str(qubits)].values(),
|
|
720
|
+
)
|
|
721
|
+
],
|
|
722
|
+
color=colors[index],
|
|
723
|
+
alpha=0.1,
|
|
724
|
+
interpolate=True,
|
|
725
|
+
)
|
|
726
|
+
if violin:
|
|
727
|
+
widths = [0.5 * m for m in depths[key][str(qubits)]]
|
|
728
|
+
violin_parts_f = ax.violinplot(
|
|
729
|
+
polarizations[key][str(qubits)].values(),
|
|
730
|
+
positions=depths[key][str(qubits)],
|
|
731
|
+
showmeans=False,
|
|
732
|
+
showextrema=False,
|
|
733
|
+
widths=widths,
|
|
734
|
+
)
|
|
735
|
+
assert isinstance(violin_parts_f["bodies"], list)
|
|
736
|
+
for pc in violin_parts_f["bodies"]:
|
|
737
|
+
assert isinstance(pc, PolyCollection)
|
|
738
|
+
pc.set_facecolor(colors[index])
|
|
739
|
+
pc.set_edgecolor("g")
|
|
740
|
+
pc.set_alpha(0.1)
|
|
741
|
+
if scatter:
|
|
742
|
+
# Plot the individual outcomes
|
|
743
|
+
flat_fidelity_data = list(chain.from_iterable(polarizations[key][str(qubits)].values()))
|
|
744
|
+
scatter_x = [
|
|
745
|
+
[depth] * len(polarizations[key][str(qubits)][depth]) for depth in depths[key][str(qubits)]
|
|
746
|
+
]
|
|
747
|
+
flat_scatter_x = list(chain.from_iterable(scatter_x))
|
|
748
|
+
ax.scatter(
|
|
749
|
+
flat_scatter_x,
|
|
750
|
+
flat_fidelity_data,
|
|
751
|
+
s=3,
|
|
752
|
+
color=colors[index],
|
|
753
|
+
alpha=0.35,
|
|
754
|
+
)
|
|
755
|
+
# Define the points of the x-axis
|
|
756
|
+
x_linspace = np.linspace(np.min(depths[key][str(qubits)]), np.max(depths[key][str(qubits)]), 300)
|
|
757
|
+
# Calculate the fit points of the y-axis
|
|
758
|
+
y_fit = exponential_rb(
|
|
759
|
+
x_linspace,
|
|
760
|
+
1 - decay_rate[key][str(qubits)],
|
|
761
|
+
offset[key][str(qubits)],
|
|
762
|
+
amplitude[key][str(qubits)],
|
|
763
|
+
)
|
|
764
|
+
# Plot
|
|
765
|
+
# Handle None types in instances where fidelity or stderr were not set
|
|
766
|
+
if fidelity_value[key][str(qubits)] is None:
|
|
767
|
+
fidelity_value[key][str(qubits)] = np.nan
|
|
768
|
+
if fidelity_stderr[key][str(qubits)] is None:
|
|
769
|
+
fidelity_stderr[key][str(qubits)] = np.nan
|
|
770
|
+
|
|
771
|
+
if identifier == "mrb":
|
|
772
|
+
plot_label = fr"$\overline{{F}}_\text{{MRB}} (n={len(qubits)})$ = {100.0 * fidelity_value[key][str(qubits)]:.2f} +/- {100.0 * fidelity_stderr[key][str(qubits)]:.2f} (%)"
|
|
773
|
+
elif key == "interleaved":
|
|
774
|
+
plot_label = fr"$\overline{{F}}_\text{{{interleaved_gate}}} ({qubits})$ = {100.0 * fidelity_value[key][str(qubits)]:.2f} +/- {100.0 * fidelity_stderr[key][str(qubits)]:.2f} (%)"
|
|
775
|
+
else:
|
|
776
|
+
plot_label = fr"$\overline{{F}}_\text{{Clifford}} ({qubits})$ = {100.0 * fidelity_value[key][str(qubits)]:.2f} +/- {100.0 * fidelity_stderr[key][str(qubits)]:.2f} (%)"
|
|
777
|
+
|
|
778
|
+
ax.plot(
|
|
779
|
+
x_linspace,
|
|
780
|
+
y_fit,
|
|
781
|
+
color=colors[index],
|
|
782
|
+
alpha=0.5,
|
|
783
|
+
label=plot_label,
|
|
784
|
+
)
|
|
785
|
+
|
|
786
|
+
# Labels
|
|
787
|
+
on_qubits = "on all qubit layouts"
|
|
788
|
+
fig_name = "all_qubit_layouts"
|
|
789
|
+
if len(qubits_array) == 1:
|
|
790
|
+
on_qubits = f"on qubits {qubits_array[0]}"
|
|
791
|
+
fig_name = f"{str(qubits_array[0])}"
|
|
792
|
+
|
|
793
|
+
if identifier == "irb":
|
|
794
|
+
ax.set_title(
|
|
795
|
+
f"{identifier.upper()} experiment for {interleaved_gate} {on_qubits}\nbackend: {backend_name} --- {timestamp}"
|
|
796
|
+
)
|
|
797
|
+
elif identifier == "clifford":
|
|
798
|
+
ax.set_title(f"{identifier.capitalize()} experiment {on_qubits}\nbackend: {backend_name} --- {timestamp}")
|
|
799
|
+
else:
|
|
800
|
+
ax.set_title(
|
|
801
|
+
f"{identifier.upper()} experiment {on_qubits}\n"
|
|
802
|
+
f"2Q gate density: {mrb_2q_density}, ensemble {mrb_2q_ensemble}\n"
|
|
803
|
+
f"backend: {backend_name} --- {timestamp}"
|
|
804
|
+
)
|
|
805
|
+
if identifier == "mrb":
|
|
806
|
+
ax.set_ylabel("Polarization")
|
|
807
|
+
ax.set_xlabel("Layer Depth")
|
|
808
|
+
else:
|
|
809
|
+
ax.set_ylabel("Fidelity")
|
|
810
|
+
ax.set_xlabel("Sequence Length")
|
|
811
|
+
if logscale:
|
|
812
|
+
ax.set_xscale("log")
|
|
813
|
+
|
|
814
|
+
# Show the relevant depths!
|
|
815
|
+
# Gather and flatten all the possible depths and then pick unique elements
|
|
816
|
+
all_depths = [x for d in depths.values() for w in d.values() for x in w]
|
|
817
|
+
|
|
818
|
+
xticks = sorted(list(set(all_depths)))
|
|
819
|
+
ax.set_xticks(xticks, labels=xticks)
|
|
820
|
+
|
|
821
|
+
plt.legend(fontsize=8)
|
|
822
|
+
ax.grid()
|
|
823
|
+
|
|
824
|
+
plt.gcf().set_dpi(250)
|
|
825
|
+
|
|
826
|
+
plt.close()
|
|
827
|
+
|
|
828
|
+
return fig_name, fig
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
def validate_rb_qubits(qubits_array: List[List[int]], backend_arg: str | IQMBackendBase):
|
|
832
|
+
"""Validate qubit inputs for a Clifford RB experiment
|
|
833
|
+
Args:
|
|
834
|
+
qubits_array (List[List[int]]): the array of qubits
|
|
835
|
+
backend_arg (IQMBackendBase): the IQM backend
|
|
836
|
+
Raises:
|
|
837
|
+
ValueError if specified pairs of qubits are not connected
|
|
838
|
+
"""
|
|
839
|
+
if isinstance(backend_arg, str):
|
|
840
|
+
backend = get_iqm_backend(backend_arg)
|
|
841
|
+
else:
|
|
842
|
+
backend = backend_arg
|
|
843
|
+
# Identify total amount of qubits
|
|
844
|
+
qubit_counts = [len(x) for x in qubits_array]
|
|
845
|
+
|
|
846
|
+
# Validations
|
|
847
|
+
# Suggest MRB for n>2 qubits
|
|
848
|
+
if any(n > 2 for n in qubit_counts):
|
|
849
|
+
raise ValueError("Please specify qubit layouts with only n=1 or n=2 qubits. Run MRB for n>2 instead.")
|
|
850
|
+
pairs_qubits = [qubits_array[i] for i, n in enumerate(qubit_counts) if n == 2]
|
|
851
|
+
# Qubits should be connected
|
|
852
|
+
if any(tuple(x) not in backend.coupling_map for x in pairs_qubits):
|
|
853
|
+
raise ValueError("Some specified pairs of qubits are not connected")
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
def validate_irb_gate(
|
|
857
|
+
gate_id: str,
|
|
858
|
+
backend_arg: str | IQMBackendBase,
|
|
859
|
+
gate_params: Optional[List[float]] = None,
|
|
860
|
+
) -> QuantumCircuit:
|
|
861
|
+
"""Validate that an input gate is Clifford and transpiled to IQM's native basis
|
|
862
|
+
|
|
863
|
+
Args:
|
|
864
|
+
gate_id (str): the gate identifier as a Qiskit circuit library operator
|
|
865
|
+
backend_arg (IQMBackendBase): the IQM backend to verify transpilation
|
|
866
|
+
gate_params (Optional[List[float]]): the gate parameters
|
|
867
|
+
Returns:
|
|
868
|
+
Transpiled circuit
|
|
869
|
+
|
|
870
|
+
"""
|
|
871
|
+
if gate_params is not None:
|
|
872
|
+
gate = getattr(import_module("qiskit.circuit.library"), gate_id)(*gate_params)
|
|
873
|
+
else:
|
|
874
|
+
gate = getattr(import_module("qiskit.circuit.library"), gate_id)()
|
|
875
|
+
|
|
876
|
+
if isinstance(backend_arg, str):
|
|
877
|
+
backend = get_iqm_backend(backend_arg)
|
|
878
|
+
else:
|
|
879
|
+
backend = backend_arg
|
|
880
|
+
|
|
881
|
+
qc = QuantumCircuit(gate.num_qubits)
|
|
882
|
+
if gate in backend.operation_names:
|
|
883
|
+
qc.compose(gate, qubits=range(gate.num_qubits), inplace=True)
|
|
884
|
+
return qc
|
|
885
|
+
|
|
886
|
+
# else transpile
|
|
887
|
+
qc.compose(gate, qubits=range(gate.num_qubits), inplace=True)
|
|
888
|
+
# Use Optimize SQG but retain final r gate - want the unitary to be preserved!
|
|
889
|
+
qc_t = optimize_single_qubit_gates(
|
|
890
|
+
transpile(qc, basis_gates=backend.operation_names, optimization_level=3), drop_final_rz=False
|
|
891
|
+
)
|
|
892
|
+
return qc_t
|