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,290 @@
|
|
|
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
|
+
"""M3 modification for readout mitigation at IQM QPU's."""
|
|
15
|
+
import logging
|
|
16
|
+
from math import ceil
|
|
17
|
+
import sys
|
|
18
|
+
import threading
|
|
19
|
+
from typing import Any, Dict, Iterable, List
|
|
20
|
+
import warnings
|
|
21
|
+
|
|
22
|
+
import mthree
|
|
23
|
+
from mthree.circuits import _marg_meas_states, _tensor_meas_states, balanced_cal_circuits, balanced_cal_strings
|
|
24
|
+
from mthree.classes import QuasiCollection
|
|
25
|
+
from mthree.exceptions import M3Error
|
|
26
|
+
from mthree.mitigation import _job_thread
|
|
27
|
+
from mthree.utils import final_measurement_mapping
|
|
28
|
+
from qiskit import QuantumCircuit, execute # pylint: disable = no-name-in-module
|
|
29
|
+
from qiskit.providers import Backend, BackendV1, BackendV2
|
|
30
|
+
|
|
31
|
+
from iqm.benchmarks.utils import get_iqm_backend, timeit
|
|
32
|
+
from iqm.qiskit_iqm.iqm_backend import IQMBackendBase
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# The code here is close to the original M3 code licenced under Apache 2 as well (https://github.com/Qiskit/qiskit-addon-mthree/blob/main/LICENSE.txt).
|
|
36
|
+
# We deactivate this to not break functionality.
|
|
37
|
+
# pylint: disable=too-many-instance-attributes, too-many-arguments, too-many-locals, too-many-branches, too-many-statements,
|
|
38
|
+
# pylint: disable=attribute-defined-outside-init, too-few-public-methods, access-member-before-definition
|
|
39
|
+
class M3IQM(mthree.M3Mitigation):
|
|
40
|
+
"""M3 readout mitigation class modified to work with IQM devices."""
|
|
41
|
+
|
|
42
|
+
def cals_from_system(
|
|
43
|
+
self,
|
|
44
|
+
qubits=None,
|
|
45
|
+
shots=None,
|
|
46
|
+
method=None,
|
|
47
|
+
initial_reset=False,
|
|
48
|
+
rep_delay=None,
|
|
49
|
+
cals_file=None,
|
|
50
|
+
async_cal=False,
|
|
51
|
+
cal_id=None,
|
|
52
|
+
):
|
|
53
|
+
"""Grab calibration data from system.
|
|
54
|
+
|
|
55
|
+
Parameters:
|
|
56
|
+
qubits (array_like): Qubits over which to correct calibration data. Default is all.
|
|
57
|
+
shots (int): Number of shots per circuit. min(1e4, max_shots).
|
|
58
|
+
method (str): Type of calibration, 'balanced' (default for hardware),
|
|
59
|
+
'independent' (default for simulators), or 'marginal'.
|
|
60
|
+
initial_reset (bool): Use resets at beginning of calibration circuits, default=False.
|
|
61
|
+
rep_delay (float): Delay between circuits on IBM Quantum backends.
|
|
62
|
+
cals_file (str): Output path to write JSON calibration data to.
|
|
63
|
+
async_cal (bool): Do calibration async in a separate thread, default is False.
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
M3Error: Called while a calibration currently in progress.
|
|
67
|
+
"""
|
|
68
|
+
if self._thread:
|
|
69
|
+
raise M3Error("Calibration currently in progress.")
|
|
70
|
+
if qubits is None:
|
|
71
|
+
qubits = range(self.num_qubits)
|
|
72
|
+
# Remove faulty qubits if any
|
|
73
|
+
if any(self.system_info["inoperable_qubits"]):
|
|
74
|
+
qubits = list(
|
|
75
|
+
filter(
|
|
76
|
+
lambda item: item not in self.system_info["inoperable_qubits"],
|
|
77
|
+
list(range(self.num_qubits)),
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
warnings.warn(
|
|
81
|
+
f"Backend reporting inoperable qubits. Skipping calibrations for: %s",
|
|
82
|
+
self.system_info["inoperable_qubits"],
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
if method is None:
|
|
86
|
+
method = "balanced"
|
|
87
|
+
# if self.system_info["simulator"]:
|
|
88
|
+
# method = "independent"
|
|
89
|
+
self.cal_method = method
|
|
90
|
+
self.rep_delay = rep_delay
|
|
91
|
+
self.cals_file = cals_file
|
|
92
|
+
self.cal_timestamp = None
|
|
93
|
+
self._grab_additional_cals(
|
|
94
|
+
qubits,
|
|
95
|
+
shots=shots,
|
|
96
|
+
method=method,
|
|
97
|
+
rep_delay=rep_delay,
|
|
98
|
+
initial_reset=initial_reset,
|
|
99
|
+
async_cal=async_cal,
|
|
100
|
+
cal_id=cal_id,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def _grab_additional_cals(
|
|
104
|
+
self,
|
|
105
|
+
qubits,
|
|
106
|
+
shots=None,
|
|
107
|
+
method="balanced",
|
|
108
|
+
rep_delay=None,
|
|
109
|
+
initial_reset=False,
|
|
110
|
+
async_cal=False,
|
|
111
|
+
cal_id=None,
|
|
112
|
+
):
|
|
113
|
+
"""Grab missing calibration data from backend.
|
|
114
|
+
|
|
115
|
+
Parameters:
|
|
116
|
+
qubits (array_like): List of measured qubits.
|
|
117
|
+
shots (int): Number of shots to take, min(1e4, max_shots).
|
|
118
|
+
method (str): Type of calibration, 'balanced' (default), 'independent', or 'marginal'.
|
|
119
|
+
rep_delay (float): Delay between circuits on IBM Quantum backends.
|
|
120
|
+
initial_reset (bool): Use resets at beginning of calibration circuits, default=False.
|
|
121
|
+
async_cal (bool): Do calibration async in a separate thread, default is False.
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
M3Error: Backend not set.
|
|
125
|
+
M3Error: Faulty qubits found.
|
|
126
|
+
"""
|
|
127
|
+
if self.system is None:
|
|
128
|
+
raise M3Error("System is not set. Use 'cals_from_file'.")
|
|
129
|
+
if self.single_qubit_cals is None:
|
|
130
|
+
self.single_qubit_cals = [None] * self.num_qubits
|
|
131
|
+
if self.cal_shots is None:
|
|
132
|
+
if shots is None:
|
|
133
|
+
shots = min(self.system_info["max_shots"], 10000)
|
|
134
|
+
self.cal_shots = shots
|
|
135
|
+
if self.rep_delay is None:
|
|
136
|
+
self.rep_delay = rep_delay
|
|
137
|
+
|
|
138
|
+
if method not in ["independent", "balanced", "marginal"]:
|
|
139
|
+
raise M3Error(
|
|
140
|
+
f"Invalid calibration method: {method}. Valid methods are 'independent', 'balanced', or 'marginal'."
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
if isinstance(qubits, dict):
|
|
144
|
+
# Assuming passed a mapping
|
|
145
|
+
qubits = list(set(qubits.values()))
|
|
146
|
+
elif isinstance(qubits, list):
|
|
147
|
+
# Check if passed a list of mappings
|
|
148
|
+
if isinstance(qubits[0], dict):
|
|
149
|
+
# Assuming list of mappings, need to get unique elements
|
|
150
|
+
_qubits = []
|
|
151
|
+
for item in qubits:
|
|
152
|
+
_qubits.extend(list(set(item.values())))
|
|
153
|
+
qubits = list(set(_qubits))
|
|
154
|
+
|
|
155
|
+
# Do check for inoperable qubits here
|
|
156
|
+
inoperable_overlap = list(set(qubits) & set(self.system_info["inoperable_qubits"]))
|
|
157
|
+
if any(inoperable_overlap):
|
|
158
|
+
raise M3Error(f"Attempting to calibrate inoperable qubits: {inoperable_overlap}")
|
|
159
|
+
|
|
160
|
+
num_cal_qubits = len(qubits)
|
|
161
|
+
cal_strings = []
|
|
162
|
+
# shots is needed here because balanced cals will use a value
|
|
163
|
+
# different from cal_shots
|
|
164
|
+
shots = self.cal_shots
|
|
165
|
+
if method == "marginal":
|
|
166
|
+
trans_qcs = _marg_meas_states(qubits, self.num_qubits, initial_reset=initial_reset)
|
|
167
|
+
elif method == "balanced":
|
|
168
|
+
cal_strings = balanced_cal_strings(num_cal_qubits)
|
|
169
|
+
trans_qcs = balanced_cal_circuits(cal_strings, qubits, self.num_qubits, initial_reset=initial_reset)
|
|
170
|
+
shots = self.cal_shots // num_cal_qubits
|
|
171
|
+
if self.cal_shots / num_cal_qubits != shots:
|
|
172
|
+
shots += 1
|
|
173
|
+
self._balanced_shots = shots * num_cal_qubits
|
|
174
|
+
# Independent
|
|
175
|
+
else:
|
|
176
|
+
trans_qcs = []
|
|
177
|
+
for qubit in qubits:
|
|
178
|
+
trans_qcs.extend(_tensor_meas_states(qubit, self.num_qubits, initial_reset=initial_reset))
|
|
179
|
+
|
|
180
|
+
num_circs = len(trans_qcs)
|
|
181
|
+
# check for max number of circuits per job
|
|
182
|
+
if isinstance(self.system, BackendV1):
|
|
183
|
+
# Aer simulator has no 'max_experiments'
|
|
184
|
+
max_circuits = getattr(self.system.configuration(), "max_experiments", 300)
|
|
185
|
+
elif isinstance(self.system, BackendV2):
|
|
186
|
+
max_circuits = self.system.max_circuits
|
|
187
|
+
# Needed for https://github.com/Qiskit/qiskit-terra/issues/9947
|
|
188
|
+
if max_circuits is None:
|
|
189
|
+
max_circuits = 300
|
|
190
|
+
else:
|
|
191
|
+
raise M3Error("Unknown backend type")
|
|
192
|
+
# Determine the number of jobs required
|
|
193
|
+
num_jobs = ceil(num_circs / max_circuits)
|
|
194
|
+
# Get the slice length
|
|
195
|
+
circ_slice = ceil(num_circs / num_jobs)
|
|
196
|
+
circs_list = [trans_qcs[kk * circ_slice : (kk + 1) * circ_slice] for kk in range(num_jobs - 1)] + [
|
|
197
|
+
trans_qcs[(num_jobs - 1) * circ_slice :]
|
|
198
|
+
]
|
|
199
|
+
# Do job submission here
|
|
200
|
+
# This Backend check is here for Qiskit direct access. Should be removed later.
|
|
201
|
+
jobs = []
|
|
202
|
+
if not isinstance(self.system, Backend):
|
|
203
|
+
for circs in circs_list:
|
|
204
|
+
_job = execute(
|
|
205
|
+
circs,
|
|
206
|
+
self.system,
|
|
207
|
+
optimization_level=0,
|
|
208
|
+
shots=shots,
|
|
209
|
+
rep_delay=self.rep_delay,
|
|
210
|
+
)
|
|
211
|
+
jobs.append(_job)
|
|
212
|
+
|
|
213
|
+
# *****************************************
|
|
214
|
+
else: # That's what IQM backend do!
|
|
215
|
+
# *****************************************
|
|
216
|
+
for circs in circs_list:
|
|
217
|
+
if cal_id is None:
|
|
218
|
+
_job = self.system.run(circs, shots=shots, rep_delay=self.rep_delay)
|
|
219
|
+
else:
|
|
220
|
+
_job = self.system.run(
|
|
221
|
+
circs,
|
|
222
|
+
shots=shots,
|
|
223
|
+
rep_delay=self.rep_delay,
|
|
224
|
+
calibration_set_id=cal_id,
|
|
225
|
+
)
|
|
226
|
+
jobs.append(_job)
|
|
227
|
+
print(f"{len(circs)} calibration circuits ready to be executed!")
|
|
228
|
+
|
|
229
|
+
# Execute job and cal building in new thread.
|
|
230
|
+
self._job_error = None
|
|
231
|
+
if async_cal:
|
|
232
|
+
thread = threading.Thread(
|
|
233
|
+
target=_job_thread,
|
|
234
|
+
args=(jobs, self, qubits, num_cal_qubits, cal_strings),
|
|
235
|
+
)
|
|
236
|
+
self._thread = thread
|
|
237
|
+
self._thread.start()
|
|
238
|
+
else:
|
|
239
|
+
_job_thread(jobs, self, qubits, num_cal_qubits, cal_strings)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def readout_error_m3(counts: Dict[str, float], mit: M3IQM, qubits: Iterable) -> Dict[str, float]:
|
|
243
|
+
"""Counts processor using M3IQM for readout error mitigation.
|
|
244
|
+
|
|
245
|
+
The qubits argument can be either a dictionary coming from `mthree.utils.final_measurement_mapping`
|
|
246
|
+
or an array like the initial layout coming from [backend.qubit_name_to_index(name)]
|
|
247
|
+
|
|
248
|
+
returns a dictionary of quasiprobabilties.
|
|
249
|
+
|
|
250
|
+
NOTE: we could also pass a list of input counts and then this would return a list of quasiprobabilities.
|
|
251
|
+
This would not work out of the box for us since we need the annotations of either Dict or List (not Union).
|
|
252
|
+
"""
|
|
253
|
+
return mit.apply_correction(counts, qubits)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@timeit
|
|
257
|
+
def apply_readout_error_mitigation(
|
|
258
|
+
backend_arg: str | IQMBackendBase,
|
|
259
|
+
transpiled_circuits: List[QuantumCircuit],
|
|
260
|
+
counts: List[Dict[str, int]],
|
|
261
|
+
mit_shots: int = 1000,
|
|
262
|
+
) -> List[tuple[Any, Any]] | List[tuple[QuasiCollection, list]] | List[QuasiCollection]:
|
|
263
|
+
"""
|
|
264
|
+
Args:
|
|
265
|
+
backend_arg (str | IQMBackendBase): the backend to calibrate an M3 mitigator against.
|
|
266
|
+
transpiled_circuits (List[QuantumCircuit]): the list of transpiled quantum circuits.
|
|
267
|
+
counts (List[Dict[str, int]]): the measurement counts corresponding to the circuits.
|
|
268
|
+
mit_shots (int): number of shots per circuit.
|
|
269
|
+
Returns:
|
|
270
|
+
tuple[Any, Any] | tuple[QuasiCollection, list] | QuasiCollection: a list of dictionaries with REM-corrected quasiprobabilities for each outcome.
|
|
271
|
+
"""
|
|
272
|
+
# M3IQM uses mthree.mitigation, which for some reason displays many INFO messages
|
|
273
|
+
# Not sure if this is the best way to suppress them; MODIFY IF NECESSARY !
|
|
274
|
+
warnings.warn("Suppressing INFO messages from M3IQM with logging.disable(sys.maxsize) - update if problematic!")
|
|
275
|
+
logging.disable(sys.maxsize)
|
|
276
|
+
|
|
277
|
+
if isinstance(backend_arg, str):
|
|
278
|
+
backend = get_iqm_backend(backend_arg)
|
|
279
|
+
else:
|
|
280
|
+
backend = backend_arg
|
|
281
|
+
|
|
282
|
+
# Initialize with the given system and get calibration data
|
|
283
|
+
qubits_rem = [final_measurement_mapping(c) for c in transpiled_circuits]
|
|
284
|
+
|
|
285
|
+
mit = M3IQM(backend)
|
|
286
|
+
mit.cals_from_system(qubits_rem, shots=mit_shots)
|
|
287
|
+
# Apply the REM correction to the given measured counts
|
|
288
|
+
rem_quasidistro = [mit.apply_correction(c, q) for c, q in zip(counts, qubits_rem)]
|
|
289
|
+
|
|
290
|
+
return rem_quasidistro
|