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,802 @@
|
|
|
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
|
+
GHZ state benchmark
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from io import BytesIO
|
|
20
|
+
from itertools import chain
|
|
21
|
+
import json
|
|
22
|
+
from time import strftime, time
|
|
23
|
+
from typing import Dict, List, Optional, Tuple, Type, cast
|
|
24
|
+
|
|
25
|
+
from networkx import Graph, all_pairs_shortest_path, is_connected, minimum_spanning_tree
|
|
26
|
+
import numpy as np
|
|
27
|
+
import pycurl
|
|
28
|
+
from qiskit import QuantumCircuit, transpile
|
|
29
|
+
from qiskit.quantum_info import random_clifford
|
|
30
|
+
from qiskit_aer import Aer
|
|
31
|
+
from scipy.spatial.distance import hamming
|
|
32
|
+
import xarray as xr
|
|
33
|
+
|
|
34
|
+
from iqm.benchmarks import Benchmark
|
|
35
|
+
from iqm.benchmarks.benchmark import BenchmarkConfigurationBase
|
|
36
|
+
from iqm.benchmarks.benchmark_definition import AnalysisResult, RunResult, add_counts_to_dataset
|
|
37
|
+
from iqm.benchmarks.logging_config import qcvv_logger
|
|
38
|
+
from iqm.benchmarks.readout_mitigation import apply_readout_error_mitigation
|
|
39
|
+
from iqm.benchmarks.utils import (
|
|
40
|
+
perform_backend_transpilation,
|
|
41
|
+
reduce_to_active_qubits,
|
|
42
|
+
set_coupling_map,
|
|
43
|
+
timeit,
|
|
44
|
+
xrvariable_to_counts,
|
|
45
|
+
)
|
|
46
|
+
from iqm.qiskit_iqm.iqm_backend import IQMBackendBase
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@timeit
|
|
50
|
+
def append_rms(
|
|
51
|
+
circuit: QuantumCircuit,
|
|
52
|
+
num_rms: int,
|
|
53
|
+
backend: IQMBackendBase,
|
|
54
|
+
# optimize_sqg: bool = False,
|
|
55
|
+
) -> List[QuantumCircuit]:
|
|
56
|
+
"""
|
|
57
|
+
Appends 1Q Clifford gates sampled uniformly at random to all qubits in the given circuit.
|
|
58
|
+
Args:
|
|
59
|
+
circuit (QuantumCircuit):
|
|
60
|
+
num_rms (int):
|
|
61
|
+
backend (Optional[IQMBackendBase]): whether Cliffords are decomposed for the given backend
|
|
62
|
+
Returns:
|
|
63
|
+
List[QuantumCircuit] of the original circuit with 1Q Clifford gates appended to it
|
|
64
|
+
"""
|
|
65
|
+
rm_circuits = []
|
|
66
|
+
for _ in range(num_rms):
|
|
67
|
+
rm_circ = circuit.copy()
|
|
68
|
+
# It shouldn't matter if measurement bits get scrambled
|
|
69
|
+
rm_circ.remove_final_measurements()
|
|
70
|
+
|
|
71
|
+
active_qubits = set()
|
|
72
|
+
for instruction in rm_circ.data:
|
|
73
|
+
for qubit in instruction[1]:
|
|
74
|
+
active_qubits.add(qubit.index)
|
|
75
|
+
|
|
76
|
+
for q in active_qubits:
|
|
77
|
+
if backend is not None:
|
|
78
|
+
rand_clifford = random_clifford(1).to_circuit()
|
|
79
|
+
else:
|
|
80
|
+
rand_clifford = random_clifford(1).to_instruction()
|
|
81
|
+
rm_circ.compose(rand_clifford, qubits=[q], inplace=True)
|
|
82
|
+
|
|
83
|
+
rm_circ.measure_active()
|
|
84
|
+
# if backend is not None and backend.name != "IQMNdonisBackend" and optimize_sqg:
|
|
85
|
+
# rm_circuits.append(optimize_single_qubit_gates(rm_circ))
|
|
86
|
+
# else:
|
|
87
|
+
rm_circuits.append(transpile(rm_circ, basis_gates=backend.operation_names))
|
|
88
|
+
|
|
89
|
+
return rm_circuits
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def fidelity_ghz_randomized_measurements(
|
|
93
|
+
dataset: xr.Dataset, qubit_layout, ideal_probabilities: List[Dict[str, int]], num_qubits: int
|
|
94
|
+
) -> Dict[str, float]:
|
|
95
|
+
"""
|
|
96
|
+
Estimates GHZ state fidelity through cross-correlations of RMs.
|
|
97
|
+
Implementation of Eq. (34) in https://arxiv.org/abs/1812.02624
|
|
98
|
+
|
|
99
|
+
Arguments:
|
|
100
|
+
dataset (xr.Dataset):
|
|
101
|
+
qubit_layout: List[int]: The subset of system-qubits used in the protocol
|
|
102
|
+
ideal_probabilities (List[Dict[str, int]]):
|
|
103
|
+
num_qubits (int):
|
|
104
|
+
Returns:
|
|
105
|
+
Dict[str, float]
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
# List for each RM contribution to the fidelity
|
|
109
|
+
fid_rm = []
|
|
110
|
+
|
|
111
|
+
# Loop through RMs and add each contribution
|
|
112
|
+
num_rms = len(dataset.attrs["transpiled_circuits"][f"{str(qubit_layout)}"][tuple(qubit_layout)])
|
|
113
|
+
for u in range(num_rms):
|
|
114
|
+
# Probability estimates for noisy measurements
|
|
115
|
+
probabilities_sample = {}
|
|
116
|
+
c_keys = dataset[f"{str(qubit_layout)}_state_{u}"].data # measurements[u].keys()
|
|
117
|
+
num_shots_noisy = sum(dataset[f"{str(qubit_layout)}_counts_{u}"].data)
|
|
118
|
+
for k, key in enumerate(c_keys):
|
|
119
|
+
probabilities_sample[key] = dataset[f"{str(qubit_layout)}_counts_{u}"].data[k] / num_shots_noisy
|
|
120
|
+
# Keys for corresponding ideal probabilities
|
|
121
|
+
c_id_keys = ideal_probabilities[u].keys()
|
|
122
|
+
|
|
123
|
+
p_sum = []
|
|
124
|
+
for sb in c_id_keys:
|
|
125
|
+
for sa in c_keys:
|
|
126
|
+
exponent = hamming(list(sa), list(sb)) * num_qubits
|
|
127
|
+
p_sum.append(np.power(-2, -exponent) * probabilities_sample[sa] * ideal_probabilities[u][sb])
|
|
128
|
+
fid_rm.append((2**num_qubits) * sum(p_sum))
|
|
129
|
+
fidelities = {"mean": np.mean(fid_rm), "std": np.std(fid_rm) / np.sqrt(num_rms)}
|
|
130
|
+
|
|
131
|
+
if dataset.attrs["rem"]:
|
|
132
|
+
fid_rm_rem = []
|
|
133
|
+
for u in range(num_rms):
|
|
134
|
+
# Probability estimates for noisy measurements
|
|
135
|
+
probabilities_sample = {}
|
|
136
|
+
c_keys = dataset[f"{str(qubit_layout)}_rem_state_{u}"].data # measurements[u].keys()
|
|
137
|
+
num_shots_noisy = sum(dataset[f"{str(qubit_layout)}_rem_counts_{u}"].data)
|
|
138
|
+
for k, key in enumerate(c_keys):
|
|
139
|
+
probabilities_sample[key] = dataset[f"{str(qubit_layout)}_rem_counts_{u}"].data[k] / num_shots_noisy
|
|
140
|
+
# Keys for corresponding ideal probabilities
|
|
141
|
+
c_id_keys = ideal_probabilities[u].keys()
|
|
142
|
+
|
|
143
|
+
p_sum = []
|
|
144
|
+
for sb in c_id_keys:
|
|
145
|
+
for sa in c_keys:
|
|
146
|
+
exponent = hamming(list(sa), list(sb)) * num_qubits
|
|
147
|
+
p_sum.append(np.power(-2, -exponent) * probabilities_sample[sa] * ideal_probabilities[u][sb])
|
|
148
|
+
fid_rm_rem.append((2**num_qubits) * sum(p_sum))
|
|
149
|
+
fidelities = fidelities | {"mean_rem": np.mean(fid_rm_rem), "std_rem": np.std(fid_rm_rem) / np.sqrt(num_rms)}
|
|
150
|
+
return fidelities
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def fidelity_ghz_coherences(dataset: xr.Dataset, qubit_layout: List[int]) -> List[float]:
|
|
154
|
+
"""
|
|
155
|
+
Estimates the GHZ state fidelity based on the multiple quantum coherences method based on [Mooney, 2021]
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
dataset: xr.Dataset
|
|
159
|
+
An xarray dataset containing the measurement data
|
|
160
|
+
qubit_layout: List[int]
|
|
161
|
+
The subset of system-qubits used in the protocol
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
List[int]: The ghz fidelity or, if rem=True, fidelity and readout error mitigated fidelity
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
num_qubits = len(qubit_layout)
|
|
168
|
+
phases = [np.pi * i / (num_qubits + 1) for i in range(2 * num_qubits + 2)]
|
|
169
|
+
transpiled_circuits = dataset.attrs["transpiled_circuits"][f"{str(qubit_layout)}"][tuple(qubit_layout)]
|
|
170
|
+
num_shots = dataset.attrs["shots"]
|
|
171
|
+
num_circuits = len(transpiled_circuits)
|
|
172
|
+
|
|
173
|
+
# Computing the phase acquired by the |11...1> component for each interval
|
|
174
|
+
complex_coefficients = np.exp(1j * num_qubits * np.array(phases))
|
|
175
|
+
|
|
176
|
+
# Loading the counts from the dataset
|
|
177
|
+
counts = xrvariable_to_counts(dataset, str(qubit_layout), num_circuits)
|
|
178
|
+
# for u in range(num_circuits):
|
|
179
|
+
# counts.append(
|
|
180
|
+
# dict(
|
|
181
|
+
# zip(
|
|
182
|
+
# list(dataset[f"{str(qubit_layout)}_state_{u}"].data),
|
|
183
|
+
# dataset[f"{str(qubit_layout)}_counts_{u}"].data,
|
|
184
|
+
# )
|
|
185
|
+
# )
|
|
186
|
+
# )
|
|
187
|
+
all_zero_probability_list = [] # An ordered list for storing the probabilities of returning to the |00..0> state
|
|
188
|
+
for count in counts[1:]:
|
|
189
|
+
if "0" * num_qubits in count.keys():
|
|
190
|
+
probability = count["0" * num_qubits] / num_shots
|
|
191
|
+
else:
|
|
192
|
+
probability = 0
|
|
193
|
+
all_zero_probability_list.append(probability)
|
|
194
|
+
|
|
195
|
+
# Extracting coherence parameter i_n using the fourier transform
|
|
196
|
+
i_n = np.abs(np.dot(complex_coefficients, np.array(all_zero_probability_list))) / (len(phases))
|
|
197
|
+
|
|
198
|
+
# Extracting the probabilities of the 00...0 and 11...1 bit strings
|
|
199
|
+
probs_direct = {label: count / num_shots for label, count in counts[0].items()}
|
|
200
|
+
|
|
201
|
+
# Computing GHZ state fidelity from i_n and the probabilities according to the method in [Mooney, 2021]
|
|
202
|
+
p0 = probs_direct["0" * num_qubits] if "0" * num_qubits in probs_direct.keys() else 0
|
|
203
|
+
p1 = probs_direct["1" * num_qubits] if "1" * num_qubits in probs_direct.keys() else 0
|
|
204
|
+
fidelity = (p0 + p1) / 2 + np.sqrt(i_n)
|
|
205
|
+
|
|
206
|
+
# Same procedure for error mitigated data
|
|
207
|
+
if dataset.attrs["rem"]:
|
|
208
|
+
probs_mit = xrvariable_to_counts(dataset, f"{str(qubit_layout)}_rem", num_circuits)
|
|
209
|
+
# for u in range(num_circuits):
|
|
210
|
+
# probs_mit.append(
|
|
211
|
+
# dict(
|
|
212
|
+
# zip(
|
|
213
|
+
# list(dataset[f"{str(qubit_layout)}_rem_state_{u}"].data),
|
|
214
|
+
# dataset[f"{str(qubit_layout)}_rem_counts_{u}"].data,
|
|
215
|
+
# )
|
|
216
|
+
# )
|
|
217
|
+
# )
|
|
218
|
+
all_zero_probability_list_mit = []
|
|
219
|
+
for prob in probs_mit[1:]:
|
|
220
|
+
if "0" * num_qubits in prob.keys():
|
|
221
|
+
probability = prob["0" * num_qubits]
|
|
222
|
+
else:
|
|
223
|
+
probability = 0
|
|
224
|
+
all_zero_probability_list_mit.append(probability)
|
|
225
|
+
i_n_mit = np.abs(np.dot(complex_coefficients, np.array(all_zero_probability_list_mit))) / (len(phases))
|
|
226
|
+
probs_direct_mit = dict(probs_mit[0].items())
|
|
227
|
+
p0_mit = probs_direct_mit["0" * num_qubits] if "0" * num_qubits in probs_direct_mit.keys() else 0
|
|
228
|
+
p1_mit = probs_direct_mit["1" * num_qubits] if "1" * num_qubits in probs_direct_mit.keys() else 0
|
|
229
|
+
fidelity_mit = (p0_mit + p1_mit) / 2 + np.sqrt(i_n_mit)
|
|
230
|
+
return [fidelity, fidelity_mit]
|
|
231
|
+
return [fidelity]
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def fidelity_analysis(run: RunResult) -> AnalysisResult:
|
|
235
|
+
"""Analyze counts and compute the state fidelity
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
run: RunResult
|
|
239
|
+
The RunResult object containing a dataset with counts and benchmark parameters
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
AnalysisResult
|
|
243
|
+
An object containing the dataset, plots, and observations
|
|
244
|
+
"""
|
|
245
|
+
observations = {}
|
|
246
|
+
dataset = run.dataset
|
|
247
|
+
routine = dataset.attrs["fidelity_routine"]
|
|
248
|
+
qubit_layouts = dataset.attrs["custom_qubits_array"]
|
|
249
|
+
backend_name = dataset.attrs["backend_name"]
|
|
250
|
+
|
|
251
|
+
for qubit_layout in qubit_layouts:
|
|
252
|
+
if routine == "randomized_measurements":
|
|
253
|
+
ideal_simulator = Aer.get_backend("statevector_simulator")
|
|
254
|
+
for qubit_layout in qubit_layouts:
|
|
255
|
+
ideal_probabilities = []
|
|
256
|
+
all_circuits = run.dataset.attrs["transpiled_circuits"][str(qubit_layout)][tuple(qubit_layout)]
|
|
257
|
+
for qc in all_circuits:
|
|
258
|
+
qc_copy = qc.copy()
|
|
259
|
+
qc_copy.remove_final_measurements()
|
|
260
|
+
deflated_qc = reduce_to_active_qubits(qc_copy, backend_name)
|
|
261
|
+
ideal_probabilities.append(
|
|
262
|
+
dict(sorted(ideal_simulator.run(deflated_qc).result().get_counts().items()))
|
|
263
|
+
)
|
|
264
|
+
result_dict = fidelity_ghz_randomized_measurements(
|
|
265
|
+
dataset, qubit_layout, ideal_probabilities, len(qubit_layout)
|
|
266
|
+
)
|
|
267
|
+
else: # default routine == "coherences":
|
|
268
|
+
fidelity = fidelity_ghz_coherences(dataset, qubit_layout)
|
|
269
|
+
result_dict = {"fidelity": fidelity[0]}
|
|
270
|
+
if len(fidelity) > 1:
|
|
271
|
+
result_dict.update({"fidelity_rem": fidelity[1]})
|
|
272
|
+
observations[str(qubit_layout)] = result_dict
|
|
273
|
+
return AnalysisResult(dataset=dataset, observations=observations)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def generate_ghz_linear(num_qubits: int) -> QuantumCircuit:
|
|
277
|
+
"""
|
|
278
|
+
Generates a GHZ state by applying a Hadamard and a series of CX gates in a linear fashion.
|
|
279
|
+
The construction is symmetrized to halve the circuit depth.
|
|
280
|
+
Args:
|
|
281
|
+
num_qubits: the number of qubits of the GHZ state
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
A quantum circuit generating a GHZ state of n qubits
|
|
285
|
+
"""
|
|
286
|
+
s = int(num_qubits / 2)
|
|
287
|
+
qc = QuantumCircuit(num_qubits)
|
|
288
|
+
qc.h(s)
|
|
289
|
+
|
|
290
|
+
for m in range(s, 0, -1):
|
|
291
|
+
qc.cx(m, m - 1)
|
|
292
|
+
if num_qubits % 2 == 0 and m == s:
|
|
293
|
+
continue
|
|
294
|
+
qc.cx(num_qubits - m - 1, num_qubits - m)
|
|
295
|
+
qc.measure_all()
|
|
296
|
+
return qc
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def generate_ghz_log_cruz(num_qubits: int) -> QuantumCircuit:
|
|
300
|
+
"""
|
|
301
|
+
Generates a GHZ state in log-depth according to https://arxiv.org/abs/1807.05572
|
|
302
|
+
Args:
|
|
303
|
+
num_qubits: the number of qubits of the GHZ state
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
A quantum circuit generating a GHZ state of n qubits
|
|
307
|
+
"""
|
|
308
|
+
qc = QuantumCircuit(num_qubits)
|
|
309
|
+
qc.h(0)
|
|
310
|
+
|
|
311
|
+
for m in range(num_qubits):
|
|
312
|
+
for k in range(2**m):
|
|
313
|
+
if ((2**m) + k) >= num_qubits:
|
|
314
|
+
break
|
|
315
|
+
qc.cx(k, 2**m + k)
|
|
316
|
+
qc.measure_all()
|
|
317
|
+
return qc
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def generate_ghz_log_mooney(num_qubits: int) -> QuantumCircuit:
|
|
321
|
+
"""
|
|
322
|
+
Generates a GHZ state in log-depth according to https://arxiv.org/abs/2101.08946
|
|
323
|
+
Args:
|
|
324
|
+
num_qubits: the number of qubits of the GHZ state
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
A quantum circuit generating a GHZ state of n qubits
|
|
328
|
+
"""
|
|
329
|
+
qc = QuantumCircuit(num_qubits)
|
|
330
|
+
qc.h(0)
|
|
331
|
+
|
|
332
|
+
aux_n = int(np.ceil(np.log2(num_qubits)))
|
|
333
|
+
for m in range(aux_n, 0, -1):
|
|
334
|
+
for k in range(0, num_qubits, 2**m):
|
|
335
|
+
if k + 2 ** (m - 1) >= num_qubits:
|
|
336
|
+
continue
|
|
337
|
+
qc.cx(k, k + 2 ** (m - 1))
|
|
338
|
+
qc.measure_all()
|
|
339
|
+
return qc
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def generate_ghz_spanning_tree(
|
|
343
|
+
graph: Graph,
|
|
344
|
+
qubit_layout: List[int],
|
|
345
|
+
n_state: int | None = None,
|
|
346
|
+
) -> Tuple[QuantumCircuit, List[int]]:
|
|
347
|
+
"""
|
|
348
|
+
Generates a GHZ state in log-depth by computing a minimal spanning tree for a given coupling map
|
|
349
|
+
Args:
|
|
350
|
+
graph: networkx.Graph
|
|
351
|
+
A graph of the backend coupling map
|
|
352
|
+
qubit_layout: List[int]
|
|
353
|
+
The subset of system-qubits used in the protocol, indexed from 0
|
|
354
|
+
n_state: int
|
|
355
|
+
The number of qubits for which a GHZ state should be created. This values should be smaller or equal to
|
|
356
|
+
the number of qubits in qubit_layout
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
qc: QuantumCircuit
|
|
360
|
+
A quantum circuit generating a GHZ state of n qubits
|
|
361
|
+
participating_qubits: List[int]
|
|
362
|
+
The list of qubits on which the GHZ state is defined. This is a subset of qubit_layout with size n_state
|
|
363
|
+
"""
|
|
364
|
+
|
|
365
|
+
cx_map = get_cx_map(qubit_layout, graph)
|
|
366
|
+
if n_state is None:
|
|
367
|
+
n_state = len(cx_map) + 1
|
|
368
|
+
participating_qubits = set(qubit for pair in cx_map[: n_state - 1] for qubit in pair)
|
|
369
|
+
|
|
370
|
+
relabeling = {idx_old: idx_new for idx_new, idx_old in enumerate(participating_qubits)}
|
|
371
|
+
qc = QuantumCircuit(n_state, name="ghz")
|
|
372
|
+
qc.h([relabeling[cx_map[0][0]]])
|
|
373
|
+
for _, pair in zip(np.arange(n_state - 1), cx_map):
|
|
374
|
+
relabeled_pair = [relabeling[pair[0]], relabeling[pair[1]]]
|
|
375
|
+
qc.barrier(relabeled_pair)
|
|
376
|
+
qc.cx(*relabeled_pair)
|
|
377
|
+
qc.measure_active()
|
|
378
|
+
return qc, list(participating_qubits)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def extract_fidelities(cal_url: str, qubit_layout: List[int]) -> Tuple[List[List[int]], List[float]]:
|
|
382
|
+
"""Returns couplings and CZ-fidelities from calibration data URL
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
cal_url: str
|
|
386
|
+
The url under which the calibration data for the backend can be found
|
|
387
|
+
qubit_layout: List[int]
|
|
388
|
+
The subset of system-qubits used in the protocol, indexed from 0
|
|
389
|
+
Returns:
|
|
390
|
+
list_couplings: List[List[int]]
|
|
391
|
+
A list of pairs, each of which is a qubit coupling for which the calibration
|
|
392
|
+
data contains a fidelity.
|
|
393
|
+
list_fids: List[float]
|
|
394
|
+
A list of CZ fidelities from the calibration url, ordered in the same way as list_couplings
|
|
395
|
+
"""
|
|
396
|
+
|
|
397
|
+
byteobj = BytesIO() # buffer creation
|
|
398
|
+
curlobj = pycurl.Curl() # pylint: disable=c-extension-no-member
|
|
399
|
+
curlobj.setopt(curlobj.URL, f"{cal_url}") # type: ignore
|
|
400
|
+
curlobj.setopt(curlobj.WRITEDATA, byteobj) # type: ignore
|
|
401
|
+
curlobj.perform() # perform file transfer
|
|
402
|
+
curlobj.close() # end of session
|
|
403
|
+
body = byteobj.getvalue()
|
|
404
|
+
res = json.loads(body.decode())
|
|
405
|
+
|
|
406
|
+
qubit_mapping = {qubit: idx for idx, qubit in enumerate(qubit_layout)}
|
|
407
|
+
list_couplings = []
|
|
408
|
+
list_fids = []
|
|
409
|
+
for key in res["metrics"]:
|
|
410
|
+
if "irb.cz" in key:
|
|
411
|
+
idx_1 = key.index(".QB")
|
|
412
|
+
idx_2 = key.index("__QB")
|
|
413
|
+
idx_3 = key.index(".fidelity")
|
|
414
|
+
qb1 = int(key[idx_1 + 3 : idx_2]) - 1
|
|
415
|
+
qb2 = int(key[idx_2 + 4 : idx_3]) - 1
|
|
416
|
+
if all([qb1 in qubit_layout, qb2 in qubit_layout]):
|
|
417
|
+
list_couplings.append([qubit_mapping[qb1], qubit_mapping[qb2]])
|
|
418
|
+
list_fids.append(float(res["metrics"][key]["value"]))
|
|
419
|
+
return list_couplings, list_fids
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def get_edges(coupling_map, qubit_layout, edges_cal=None, fidelities_cal=None):
|
|
423
|
+
"""Produces a networkx.Graph from coupling map fidelity information, with edges given by couplings
|
|
424
|
+
and edge weights given by fidelities
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
coupling_map: List[int]
|
|
428
|
+
The list pairs on which 2-qubit gates are natively supported
|
|
429
|
+
qubit_layout: List[int]
|
|
430
|
+
The subset of system-qubits used in the protocol, indexed from 0
|
|
431
|
+
edges_cal: List[int]
|
|
432
|
+
Same as the coupling map, but only with connections that have CZ fidelities in the calibration data
|
|
433
|
+
fidelities_cal: List[float]
|
|
434
|
+
A list of CZ fidelities ordered in the same way as edges_cal
|
|
435
|
+
|
|
436
|
+
Returns:
|
|
437
|
+
graph: networkx.Graph
|
|
438
|
+
The final weighted graph for the given calibration or coupling map
|
|
439
|
+
"""
|
|
440
|
+
edges_coupling = list(coupling_map.get_edges())[::2]
|
|
441
|
+
edges_patch = []
|
|
442
|
+
for idx, edge in enumerate(edges_coupling):
|
|
443
|
+
if edge[0] in qubit_layout and edge[1] in qubit_layout:
|
|
444
|
+
edges_patch.append([edge[0], edge[1]])
|
|
445
|
+
|
|
446
|
+
if fidelities_cal is None:
|
|
447
|
+
weights = np.ones(len(edges_patch))
|
|
448
|
+
else:
|
|
449
|
+
fidelities_cal = np.minimum(np.array(fidelities_cal), np.ones(len(fidelities_cal))) # get rid of > 1 fidelities
|
|
450
|
+
fidelities_patch = []
|
|
451
|
+
for edge in edges_patch:
|
|
452
|
+
for idx, edge_2 in enumerate(edges_cal):
|
|
453
|
+
if edge == edge_2:
|
|
454
|
+
fidelities_patch.append(fidelities_cal[idx])
|
|
455
|
+
weights = -np.log(np.array(fidelities_patch))
|
|
456
|
+
graph = Graph()
|
|
457
|
+
for idx, edge in enumerate(edges_patch):
|
|
458
|
+
graph.add_edge(*edge, weight=weights[idx])
|
|
459
|
+
if not is_connected(graph):
|
|
460
|
+
print("Warning: The subgraph of selected qubit_layout is not connected.")
|
|
461
|
+
return graph
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def get_cx_map(qubit_layout, graph) -> list[list[int]]:
|
|
465
|
+
"""Calculate the cx_map based on participating qubits and the 2QB gate fidelities between them.
|
|
466
|
+
|
|
467
|
+
Uses networkx graph algorithms to calculate the minimal spanning tree of the subgraph defined by qubit_layout.
|
|
468
|
+
The weights are -log(CZ fidelity) for each edge. Then, finds the qubit in the most central position
|
|
469
|
+
by calculating the distances between all qubits. Next, adds CX applications to the list, starting
|
|
470
|
+
from the central qubit, such that the smallest number of layers is executed (most parallel).
|
|
471
|
+
|
|
472
|
+
Args:
|
|
473
|
+
qubit_layout: List[int]
|
|
474
|
+
The subset of system-qubits used in the protocol, indexed from 0
|
|
475
|
+
graph: networkx.Graph
|
|
476
|
+
The connectivity graph with edge weight given by CZ fidelities
|
|
477
|
+
Returns:
|
|
478
|
+
cx_map: List[List[int]]
|
|
479
|
+
A list of CX gates for the GHZ generation circuit, starting from the first gate to be applied
|
|
480
|
+
"""
|
|
481
|
+
|
|
482
|
+
rev_mapping = {component: component for i, component in enumerate(qubit_layout)}
|
|
483
|
+
span = minimum_spanning_tree(graph)
|
|
484
|
+
path = dict(all_pairs_shortest_path(span))
|
|
485
|
+
all_distances = [max(len(path[target_qubit][qubit]) for qubit in qubit_layout) for target_qubit in qubit_layout]
|
|
486
|
+
shortest_distance = min(all_distances)
|
|
487
|
+
central_qubit = qubit_layout[np.argmin(all_distances)]
|
|
488
|
+
all_paths = list(path[central_qubit].values())
|
|
489
|
+
all_paths.sort(key=lambda x: -len(x))
|
|
490
|
+
already_entangled = [central_qubit]
|
|
491
|
+
cx_map = []
|
|
492
|
+
for i in range(1, shortest_distance):
|
|
493
|
+
for qubit_path in all_paths:
|
|
494
|
+
if i < len(qubit_path):
|
|
495
|
+
new_qubit = qubit_path[i]
|
|
496
|
+
else:
|
|
497
|
+
continue
|
|
498
|
+
if new_qubit in already_entangled:
|
|
499
|
+
continue
|
|
500
|
+
cx_map.append([rev_mapping[qubit_path[i - 1]], rev_mapping[new_qubit]])
|
|
501
|
+
already_entangled.append(new_qubit)
|
|
502
|
+
return cx_map
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
class GHZBenchmark(Benchmark):
|
|
506
|
+
"""The GHZ Benchmark estimates the quality of generated Greenberger-Horne-Zeilinger states"""
|
|
507
|
+
|
|
508
|
+
analysis_function = staticmethod(fidelity_analysis)
|
|
509
|
+
|
|
510
|
+
def __init__(self, backend: IQMBackendBase, configuration: "GHZConfiguration"):
|
|
511
|
+
"""Construct the GHZBenchmark class.
|
|
512
|
+
|
|
513
|
+
Args:
|
|
514
|
+
backend (IQMBackendBase): the backend to execute the benchmark on
|
|
515
|
+
configuration (QuantumVolumeConfiguration): the configuration of the benchmark
|
|
516
|
+
"""
|
|
517
|
+
super().__init__(backend, configuration)
|
|
518
|
+
|
|
519
|
+
self.state_generation_routine = configuration.state_generation_routine
|
|
520
|
+
self.choose_qubits_routine = configuration.choose_qubits_routine
|
|
521
|
+
if configuration.custom_qubits_array:
|
|
522
|
+
self.custom_qubits_array = configuration.custom_qubits_array
|
|
523
|
+
else:
|
|
524
|
+
self.custom_qubits_array = list(set(chain(*backend.coupling_map)))
|
|
525
|
+
if not configuration.qubit_counts:
|
|
526
|
+
self.qubit_counts = [len(layout) for layout in self.custom_qubits_array]
|
|
527
|
+
else:
|
|
528
|
+
if any(np.max(configuration.qubit_counts) > [len(layout) for layout in self.custom_qubits_array]):
|
|
529
|
+
raise ValueError("The maximum given qubit count is larger than the size of the smallest qubit layout.")
|
|
530
|
+
self.qubit_counts = configuration.qubit_counts
|
|
531
|
+
|
|
532
|
+
self.qiskit_optim_level = configuration.qiskit_optim_level
|
|
533
|
+
self.optimize_sqg = configuration.optimize_sqg
|
|
534
|
+
|
|
535
|
+
self.fidelity_routine = configuration.fidelity_routine
|
|
536
|
+
self.num_RMs = configuration.num_RMs
|
|
537
|
+
|
|
538
|
+
self.rem = configuration.rem
|
|
539
|
+
self.mit_shots = configuration.mit_shots
|
|
540
|
+
self.cal_url = configuration.cal_url
|
|
541
|
+
|
|
542
|
+
self.timestamp = strftime("%Y%m%d-%H%M%S")
|
|
543
|
+
|
|
544
|
+
@staticmethod
|
|
545
|
+
def name() -> str:
|
|
546
|
+
return "ghz"
|
|
547
|
+
|
|
548
|
+
def generate_native_ghz(self, qubit_layout: List[int], qubit_count: int, routine: str) -> QuantumCircuit:
|
|
549
|
+
"""
|
|
550
|
+
Generate a circuit preparing a GHZ state,
|
|
551
|
+
according to a given routine and transpiled to the native gate set and topology
|
|
552
|
+
Args:
|
|
553
|
+
qubit_layout: List[int]
|
|
554
|
+
The subset of system-qubits used in the protocol, indexed from 0
|
|
555
|
+
qubit_count: int
|
|
556
|
+
The number of qubits for which a GHZ state should be created. This values should be smaller or equal to
|
|
557
|
+
the number of qubits in qubit_layout
|
|
558
|
+
routine: str
|
|
559
|
+
The routine to generate the GHZ circuit
|
|
560
|
+
|
|
561
|
+
Returns:
|
|
562
|
+
QuantumCircuit implementing GHZ native state
|
|
563
|
+
"""
|
|
564
|
+
# num_qubits = len(qubit_layout)
|
|
565
|
+
fixed_coupling_map = set_coupling_map(qubit_layout, self.backend, "fixed")
|
|
566
|
+
|
|
567
|
+
if routine == "naive":
|
|
568
|
+
ghz = generate_ghz_linear(qubit_count)
|
|
569
|
+
self.untranspiled_circuits[str(qubit_layout)].update({qubit_count: ghz})
|
|
570
|
+
ghz_native_transpiled, _ = perform_backend_transpilation(
|
|
571
|
+
[ghz],
|
|
572
|
+
self.backend,
|
|
573
|
+
qubit_layout,
|
|
574
|
+
fixed_coupling_map,
|
|
575
|
+
qiskit_optim_level=self.qiskit_optim_level,
|
|
576
|
+
optimize_sqg=self.optimize_sqg,
|
|
577
|
+
)
|
|
578
|
+
final_ghz = ghz_native_transpiled
|
|
579
|
+
elif routine == "tree":
|
|
580
|
+
if self.cal_url:
|
|
581
|
+
edges_cal, fidelities_cal = extract_fidelities(self.cal_url, qubit_layout)
|
|
582
|
+
graph = get_edges(self.backend.coupling_map, qubit_layout, edges_cal, fidelities_cal)
|
|
583
|
+
else:
|
|
584
|
+
graph = get_edges(self.backend.coupling_map, qubit_layout)
|
|
585
|
+
ghz, _ = generate_ghz_spanning_tree(graph, qubit_layout, qubit_count)
|
|
586
|
+
self.untranspiled_circuits[str(qubit_layout)].update({qubit_count: ghz})
|
|
587
|
+
ghz_native_transpiled, _ = perform_backend_transpilation(
|
|
588
|
+
[ghz],
|
|
589
|
+
self.backend,
|
|
590
|
+
qubit_layout,
|
|
591
|
+
fixed_coupling_map,
|
|
592
|
+
qiskit_optim_level=self.qiskit_optim_level,
|
|
593
|
+
optimize_sqg=self.optimize_sqg,
|
|
594
|
+
)
|
|
595
|
+
final_ghz = ghz_native_transpiled
|
|
596
|
+
else:
|
|
597
|
+
ghz_log = [generate_ghz_log_cruz(qubit_count), generate_ghz_log_mooney(qubit_count)]
|
|
598
|
+
ghz_native_transpiled, _ = perform_backend_transpilation(
|
|
599
|
+
ghz_log,
|
|
600
|
+
self.backend,
|
|
601
|
+
qubit_layout,
|
|
602
|
+
fixed_coupling_map,
|
|
603
|
+
qiskit_optim_level=self.qiskit_optim_level,
|
|
604
|
+
optimize_sqg=self.optimize_sqg,
|
|
605
|
+
)
|
|
606
|
+
# Use either the circuit with min depth after transpilation or min #2q gates
|
|
607
|
+
if ghz_native_transpiled[0].depth() == ghz_native_transpiled[1].depth():
|
|
608
|
+
index_min_2q = np.argmin([c.count_ops()["cz"] for c in ghz_native_transpiled])
|
|
609
|
+
final_ghz = ghz_native_transpiled[index_min_2q]
|
|
610
|
+
self.untranspiled_circuits[str(qubit_layout)].update({qubit_count: ghz_log[index_min_2q]})
|
|
611
|
+
else:
|
|
612
|
+
index_min_depth = np.argmin([c.depth() for c in ghz_native_transpiled])
|
|
613
|
+
final_ghz = ghz_native_transpiled[index_min_depth]
|
|
614
|
+
self.untranspiled_circuits[str(qubit_layout)].update({qubit_count: ghz_log[index_min_depth]})
|
|
615
|
+
return final_ghz[0]
|
|
616
|
+
|
|
617
|
+
def generate_coherence_meas_circuits(self, qubit_layout: List[int], qubit_count: int) -> List[QuantumCircuit]:
|
|
618
|
+
"""
|
|
619
|
+
Takes a given GHZ circuit and outputs circuits needed to measure fidelity via mult. q. coherences method
|
|
620
|
+
|
|
621
|
+
Args:
|
|
622
|
+
qubit_layout: List[int]
|
|
623
|
+
The subset of system-qubits used in the protocol, indexed from 0
|
|
624
|
+
qubit_count: int
|
|
625
|
+
The number of qubits for which a GHZ state should be created. This values should be smaller or equal to
|
|
626
|
+
the number of qubits in qubit_layout
|
|
627
|
+
Returns:
|
|
628
|
+
qc_list_transpiled: List[QuantumCircuit]
|
|
629
|
+
A list of transpiled quantum circuits to be measured
|
|
630
|
+
"""
|
|
631
|
+
|
|
632
|
+
qc = self.untranspiled_circuits[str(qubit_layout)][qubit_count]
|
|
633
|
+
qc_list = [qc.copy()]
|
|
634
|
+
|
|
635
|
+
qc.remove_final_measurements()
|
|
636
|
+
qc_inv = qc.inverse()
|
|
637
|
+
phases = [np.pi * i / (qubit_count + 1) for i in range(2 * qubit_count + 2)]
|
|
638
|
+
for phase in phases:
|
|
639
|
+
qc_phase = qc.copy()
|
|
640
|
+
qc_phase.barrier()
|
|
641
|
+
for qubit, _ in enumerate(qubit_layout):
|
|
642
|
+
qc_phase.p(phase, qubit)
|
|
643
|
+
qc_phase.barrier()
|
|
644
|
+
qc_phase.compose(qc_inv, inplace=True)
|
|
645
|
+
qc_phase.measure_active()
|
|
646
|
+
qc_list.append(qc_phase)
|
|
647
|
+
|
|
648
|
+
fixed_coupling_map = set_coupling_map(qubit_layout, self.backend, "fixed")
|
|
649
|
+
qc_list_transpiled, _ = perform_backend_transpilation(
|
|
650
|
+
qc_list,
|
|
651
|
+
self.backend,
|
|
652
|
+
qubit_layout,
|
|
653
|
+
fixed_coupling_map,
|
|
654
|
+
qiskit_optim_level=self.qiskit_optim_level,
|
|
655
|
+
optimize_sqg=self.optimize_sqg,
|
|
656
|
+
)
|
|
657
|
+
self.untranspiled_circuits[str(qubit_layout)].update({qubit_count: qc_list})
|
|
658
|
+
return qc_list_transpiled
|
|
659
|
+
|
|
660
|
+
def generate_readout_circuit(self, qubit_layout, qubit_count):
|
|
661
|
+
"""
|
|
662
|
+
A wrapper for the creation of different circuits to estimate the fidelity
|
|
663
|
+
|
|
664
|
+
Args:
|
|
665
|
+
qubit_layout: List[int]
|
|
666
|
+
The subset of system-qubits used in the protocol, indexed from 0
|
|
667
|
+
qubit_count: int
|
|
668
|
+
The number of qubits for which a GHZ state should be created. This values should be smaller or equal to
|
|
669
|
+
the number of qubits in qubit_layout
|
|
670
|
+
|
|
671
|
+
Returns:
|
|
672
|
+
all_circuits_list: List[QuantumCircuit]
|
|
673
|
+
A list of transpiled quantum circuits to be measured
|
|
674
|
+
"""
|
|
675
|
+
# Generate the list of circuits
|
|
676
|
+
self.untranspiled_circuits[str(qubit_layout)] = {}
|
|
677
|
+
self.transpiled_circuits[str(qubit_layout)] = {}
|
|
678
|
+
|
|
679
|
+
qcvv_logger.info(f"Now generating a {len(qubit_layout)}-qubit GHZ state on qubits {qubit_layout}")
|
|
680
|
+
transpiled_ghz = self.generate_native_ghz(qubit_layout, qubit_count, self.state_generation_routine)
|
|
681
|
+
|
|
682
|
+
if self.fidelity_routine == "randomized_measurements":
|
|
683
|
+
all_circuits_list, _ = append_rms(transpiled_ghz, cast(int, self.num_RMs), self.backend)
|
|
684
|
+
all_circuits_dict = {tuple(qubit_layout): all_circuits_list}
|
|
685
|
+
elif self.fidelity_routine == "coherences":
|
|
686
|
+
all_circuits_list = self.generate_coherence_meas_circuits(qubit_layout, qubit_count)
|
|
687
|
+
all_circuits_dict = {tuple(qubit_layout): all_circuits_list}
|
|
688
|
+
else:
|
|
689
|
+
all_circuits_list = transpiled_ghz
|
|
690
|
+
all_circuits_dict = {tuple(qubit_layout): all_circuits_list}
|
|
691
|
+
|
|
692
|
+
self.transpiled_circuits[str(qubit_layout)].update(all_circuits_dict)
|
|
693
|
+
return all_circuits_list
|
|
694
|
+
|
|
695
|
+
def add_configuration_to_dataset(self, dataset): # CHECK
|
|
696
|
+
"""
|
|
697
|
+
Creates an xarray.Dataset and adds the circuits and configuration metadata to it
|
|
698
|
+
|
|
699
|
+
Args:
|
|
700
|
+
self: Source class
|
|
701
|
+
Returns:
|
|
702
|
+
dataset: xarray.Dataset to be used for further data storage
|
|
703
|
+
"""
|
|
704
|
+
|
|
705
|
+
for key, value in self.configuration:
|
|
706
|
+
if key == "benchmark": # Avoid saving the class object
|
|
707
|
+
dataset.attrs[key] = value.name()
|
|
708
|
+
else:
|
|
709
|
+
dataset.attrs[key] = value
|
|
710
|
+
dataset.attrs[f"backend_name"] = self.backend.name
|
|
711
|
+
dataset.attrs[f"untranspiled_circuits"] = self.untranspiled_circuits
|
|
712
|
+
dataset.attrs[f"transpiled_circuits"] = self.transpiled_circuits
|
|
713
|
+
|
|
714
|
+
def execute(self, backend) -> xr.Dataset:
|
|
715
|
+
"""
|
|
716
|
+
Executes the benchmark.
|
|
717
|
+
"""
|
|
718
|
+
aux_custom_qubits_array = cast(List[List[int]], self.custom_qubits_array).copy()
|
|
719
|
+
dataset = xr.Dataset()
|
|
720
|
+
|
|
721
|
+
for qubit_layout in aux_custom_qubits_array:
|
|
722
|
+
qubit_count = len(qubit_layout)
|
|
723
|
+
circuits = self.generate_readout_circuit(qubit_layout, qubit_count)
|
|
724
|
+
|
|
725
|
+
qcvv_logger.info(f"Retrieving results")
|
|
726
|
+
t_start = time()
|
|
727
|
+
job = backend.run(circuits, shots=self.shots)
|
|
728
|
+
counts = job.result().get_counts()
|
|
729
|
+
print(f"\t Getting counts took {time()-t_start:.2f} sec")
|
|
730
|
+
|
|
731
|
+
# coordinates = [(f"qubit_layout", [str(qubit_layout)])]
|
|
732
|
+
identifier = str(qubit_layout)
|
|
733
|
+
qcvv_logger.info(f"Adding counts to dataset")
|
|
734
|
+
dataset, _ = add_counts_to_dataset(counts, identifier, dataset)
|
|
735
|
+
if self.rem:
|
|
736
|
+
qcvv_logger.info(f"Applying readout error mitigation")
|
|
737
|
+
rem_results, _ = apply_readout_error_mitigation(
|
|
738
|
+
backend, circuits, job.result().get_counts(), self.mit_shots
|
|
739
|
+
)
|
|
740
|
+
rem_results_dist = [counts_mit.nearest_probability_distribution() for counts_mit in rem_results]
|
|
741
|
+
qcvv_logger.info(f"Adding REM results to dataset")
|
|
742
|
+
dataset, _ = add_counts_to_dataset(rem_results_dist, f"{identifier}_rem", dataset)
|
|
743
|
+
self.add_configuration_to_dataset(dataset)
|
|
744
|
+
return dataset
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
class GHZConfiguration(BenchmarkConfigurationBase):
|
|
748
|
+
"""GHZ state configuration
|
|
749
|
+
|
|
750
|
+
Attributes:
|
|
751
|
+
benchmark (Type[Benchmark]): GHZBenchmark
|
|
752
|
+
state_generation_routine (str): The routine to construct circuits generating a GHZ state. Possible values:
|
|
753
|
+
- "tree" (default): Optimized GHZ state generation circuit in log depth that
|
|
754
|
+
takes the qubit coupling and CZ fidelities into account. The algorithm creates
|
|
755
|
+
a minimal spanning tree for the qubit layout and chooses an initial qubit
|
|
756
|
+
that minimizes largest weighted distance to all other qubits.
|
|
757
|
+
- "log": Optimized circuit with parallel application of CX gates such that the
|
|
758
|
+
number of CX gates scales logarithmically in the system size. This
|
|
759
|
+
implementation currently does not take connectivity on the backend into account.
|
|
760
|
+
- "naive": Applies the naive textbook circuit with #CX gates scaling linearly in
|
|
761
|
+
the system size.
|
|
762
|
+
* If other is specified, assumes "log".
|
|
763
|
+
custom_qubits_array (Optional[Sequence[Sequence[int]]]): A sequence (e.g., Tuple or List) of sequences of
|
|
764
|
+
physical qubit layouts, as specified by integer labels, where the benchmark is meant to be run.
|
|
765
|
+
* If None, takes all qubits specified in the backend coupling map.
|
|
766
|
+
qubit_counts (Optional[Sequence[int]]): A sequence (e.g., Tuple or List) of integers denoting number of qubits
|
|
767
|
+
for which the benchmark is meant to be run. The largest qubit count provided here has to be smaller than the
|
|
768
|
+
smallest given qubit layout.
|
|
769
|
+
qiskit_optim_level (int): The optimization level used for transpilation to backend architecture.
|
|
770
|
+
* Default: 3
|
|
771
|
+
optimize_sqg (bool): Whether consecutive single qubit gates are optimized for reduced gate count via
|
|
772
|
+
iqm.qiskit_iqm.iqm_transpilation.optimize_single_qubit_gates
|
|
773
|
+
* Default: True
|
|
774
|
+
fidelity_routine (str): The method with which the fidelity is estimated. Possible values:
|
|
775
|
+
- "coherences": The multiple quantum coherences method as in [Mooney, 2021]
|
|
776
|
+
- "randomized_measurements": Fidelity estimation via randomized measurements outlined in
|
|
777
|
+
https://arxiv.org/abs/1812.02624
|
|
778
|
+
* Default is "coherences"
|
|
779
|
+
num_RMs (Optional[int]): The number of randomized measurements used if the respective fidelity routine is chosen
|
|
780
|
+
* Default: 100
|
|
781
|
+
rem (bool): Boolean flag determining if readout error mitigation is used
|
|
782
|
+
* Default: True
|
|
783
|
+
mit_shots (int): Total number of shots for readout error mitigation
|
|
784
|
+
* Default: 1000
|
|
785
|
+
cal_url (Optional[str]): Optional URL where the calibration data for the selected backend can be retrieved from
|
|
786
|
+
The calibration data is used for the "tree" state generation routine to prioritize couplings with high
|
|
787
|
+
CZ fidelity.
|
|
788
|
+
* Default: None
|
|
789
|
+
"""
|
|
790
|
+
|
|
791
|
+
benchmark: Type[Benchmark] = GHZBenchmark
|
|
792
|
+
state_generation_routine: str = "tree"
|
|
793
|
+
choose_qubits_routine: str = "custom"
|
|
794
|
+
custom_qubits_array: Optional[List[List[int]]] = None
|
|
795
|
+
qubit_counts: Optional[List[int]] = None
|
|
796
|
+
qiskit_optim_level: int = 3
|
|
797
|
+
optimize_sqg: bool = True
|
|
798
|
+
fidelity_routine: str = "coherences"
|
|
799
|
+
num_RMs: Optional[int] = 24
|
|
800
|
+
rem: bool = True
|
|
801
|
+
mit_shots: int = 1_000
|
|
802
|
+
cal_url: Optional[str] = None
|