diff --git a/.gitignore b/.gitignore index eb279fcf..0bc83f63 100644 --- a/.gitignore +++ b/.gitignore @@ -133,3 +133,4 @@ mthree/version.py docs/*.json docs/stubs/* docs/tutorials/ +bad_cals.json diff --git a/mthree/mitigation.py b/mthree/mitigation.py index fe1260ae..b0613280 100644 --- a/mthree/mitigation.py +++ b/mthree/mitigation.py @@ -67,6 +67,8 @@ def __init__(self, system=None, iter_threshold=4096): self._job_error = None # Holds the cals file self.cals_file = None + # faulty qubits + self.faulty_qubits = [] def __getattribute__(self, attr): """This allows for checking the status of the threaded cals call @@ -197,6 +199,7 @@ def cals_from_file(self, cals_file): self.cal_shots = None self.single_qubit_cals = [np.asarray(cal) if cal else None for cal in loaded_data] + self.faulty_qubits = _faulty_qubit_checker(self.single_qubit_cals) def cals_to_file(self, cals_file=None): """Save calibration data to JSON file. @@ -213,7 +216,7 @@ def cals_to_file(self, cals_file=None): if not self.single_qubit_cals: raise M3Error('Mitigator is not calibrated.') save_dict = {'timestamp': self.cal_timestamp, - 'backend': self.system_info["name"], + 'backend': self.system_info.get("name", None), 'shots': self.cal_shots, 'cals': self.single_qubit_cals} with open(cals_file, 'wb') as fd: @@ -247,6 +250,7 @@ def cals_from_matrices(self, matrices): raise M3Error('Input list length not equal to' ' number of qubits {} != {}'.format(len(matrices), self.num_qubits)) self.single_qubit_cals = matrices + self.faulty_qubits = _faulty_qubit_checker(self.single_qubit_cals) def cals_to_matrices(self): """Return single qubit cals as list of NumPy arrays @@ -383,6 +387,15 @@ def apply_correction(self, counts, qubits, distance=None, if len(qubits) != len(counts): raise M3Error('Length of counts does not match length of qubits.') + # Check if using faulty qubits + bad_qubits = set() + for item in qubits: + for qu in item: + if qu in self.faulty_qubits: + bad_qubits.add(qu) + if any(bad_qubits): + raise M3Error('Using faulty qubits: {}'.format(bad_qubits)) + quasi_out = [] for idx, cnts in enumerate(counts): @@ -743,19 +756,32 @@ def _job_thread(job, mit, method, qubits, num_cal_qubits, cal_strings): else: cal[1, 1] += good_prep[kk] / denom - for jj, cal in enumerate(cals): + for cal in cals: cal[1, 0] = 1.0 - cal[0, 0] cal[0, 1] = 1.0 - cal[1, 1] - if cal[0, 1] >= cal[0, 0]: - bad_list.append(qubits[jj]) - for idx, cal in enumerate(cals): mit.single_qubit_cals[qubits[idx]] = cal # save cals to file, if requested if mit.cals_file: mit.cals_to_file(mit.cals_file) - # Return list of faulty qubits, if any - if any(bad_list): - mit._job_error = M3Error('Faulty qubits detected: {}'.format(bad_list)) + # faulty qubits, if any + mit.faulty_qubits = _faulty_qubit_checker(mit.single_qubit_cals) + + +def _faulty_qubit_checker(cals): + """Find faulty qubits in cals + + Parameters: + cals (list): Input list of calibrations + + Returns: + list: Faulty qubits + """ + faulty_qubits = [] + for idx, cal in enumerate(cals): + if cal is not None: + if cal[0, 1] >= cal[0, 0]: + faulty_qubits.append(idx) + return faulty_qubits diff --git a/mthree/test/test_faulty.py b/mthree/test/test_faulty.py new file mode 100644 index 00000000..34094313 --- /dev/null +++ b/mthree/test/test_faulty.py @@ -0,0 +1,53 @@ +# This code is part of Mthree. +# +# (C) Copyright IBM 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +# pylint: disable=no-name-in-module + +"""Test faulty qubits handling""" +import numpy as np +import pytest + +import mthree + + +def test_faulty_logic(): + """Test faulty qubits block correction""" + + mit = mthree.M3Mitigation(None) + mit.single_qubit_cals = [np.array([[0.9819, 0.043], + [0.0181, 0.957]]), + np.array([[0.4849, 0.5233], + [0.5151, 0.4767]]), + np.array([[0.9092, 0.4021], + [0.0908, 0.5979]]), + np.array([[0.4117, 0.8101], + [0.5883, 0.1899]])] + mit.faulty_qubits = [1, 3] + counts = {"00": 0.4, "01": 0.1, "11": 0.5} + with pytest.raises(mthree.exceptions.M3Error) as _: + _ = mit.apply_correction(counts, qubits=[3, 2]) + + +def test_faulty_io(): + """Check round-tripping IO still has faulty qubits""" + mit = mthree.M3Mitigation(None) + mit.single_qubit_cals = [np.array([[0.9819, 0.043], + [0.0181, 0.957]]), + np.array([[0.4849, 0.5233], + [0.5151, 0.4767]]), + np.array([[0.9092, 0.4021], + [0.0908, 0.5979]]), + np.array([[0.4117, 0.8101], + [0.5883, 0.1899]])] + mit.cals_to_file('bad_cals.json') + mit2 = mthree.M3Mitigation(None) + mit2.cals_from_file('bad_cals.json') + assert mit2.faulty_qubits == [1, 3] diff --git a/setup.py b/setup.py index 57e7c774..5ef060f7 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ from Cython.Build import cythonize MAJOR = 2 -MINOR = 2 +MINOR = 3 MICRO = 0 ISRELEASED = False