diff --git a/docs/tutorials/readout_mitigation.rst b/docs/tutorials/readout_mitigation.rst index b61284fc60..e844d039b5 100644 --- a/docs/tutorials/readout_mitigation.rst +++ b/docs/tutorials/readout_mitigation.rst @@ -1,15 +1,36 @@ Readout Mitigation ================== -Readout mitigation is part of ``qiskit-terra``. The readout mitigators -can be initalized based on existing backend data, or via a readout -mitigation experiment. - -In a readout mitigation experiment, simple circuits are generated for -various combinations of “0” and “1” readout values. The results give us -a matrix describing the probability to obtain a wrong measurement. This -matrix is used to initialize the readout mitigation object, which is -given as the result of the expriment. +Readout errors affect quantum computation during the measurement of the +qubits in a quantum device. By characterizing the readout errors, it is +possible to construct a *readout error mitigator* that is used both to +obtain a more accurate distribution of the outputs, and more accurate +measurements of expectation value for measurables. + +The readout mitigator is generated from an *assignment matrix*: a +:math:`2^n \times 2^n` matrix :math:`A` such that :math:`A_{y,x}` is the +probability to observe :math:`y` given the true outcome should be +:math:`x`. The assignment matrix is used to compute the *mitigation +matrix* used in the readout error mitigation process itself. + +A *Local readout mitigator* works under the assumption that readout +errors are mostly *local*, meaning readout errors for different qubits +are independent of each other. In this case, the assignment matrix is +the tensor product of :math:`n` :math:`2 \times 2` matrices, one for +each qubit, making it practical to store the assignment matrix in +implicit form, by storing the individual :math:`2 \times 2` assignment +matrices. The corresponding class in Qiskit is the `Local readout +mitigator `__ +in ``qiskit-terra``. + +A *Correlated readout mitigator* uses the full :math:`2^n \times 2^n` +assignment matrix, meaning it can only be used for small values of +:math:`n`. The corresponding class in Qiskit is the `Correlated readout +mitigator `__ +in ``qiskit-terra``. + +This notebook demonstrates the usage of both the local and correlated +experiments to generate the corresponding mitigators. .. jupyter-execute:: @@ -17,17 +38,17 @@ given as the result of the expriment. import matplotlib.pyplot as plt from qiskit import QuantumCircuit from qiskit.visualization import plot_histogram - from qiskit_experiments.library import ReadoutMitigationExperiment + from qiskit_experiments.library import LocalReadoutError, CorrelatedReadoutError # For simulation from qiskit.providers.aer import AerSimulator from qiskit.test.mock import FakeParis - + from qiskit.result.mitigation.utils import ( expval_with_stddev, str2diag, counts_probability_vector ) - + backend = AerSimulator.from_backend(FakeParis()) .. jupyter-execute:: @@ -45,14 +66,15 @@ circuits, one for all “0” and one for all “1” results. .. jupyter-execute:: - exp = ReadoutMitigationExperiment(qubits) + exp = LocalReadoutError(qubits) for c in exp.circuits(): print(c) .. jupyter-execute:: - result = exp.run(backend).block_for_results() + exp.analysis.set_options(plot=True) + result = exp.run(backend) mitigator = result.analysis_results(0).value The resulting measurement matrix can be illustrated by comparing it to @@ -62,6 +84,7 @@ the identity. result.figure(0) + Mitigation matrices ------------------- @@ -145,10 +168,11 @@ a few qubits. qubits = [0,3] num_qubits = len(qubits) - exp = ReadoutMitigationExperiment(qubits, method="correlated") + exp = CorrelatedReadoutError(qubits) for c in exp.circuits(): print(c) + .. jupyter-execute:: import qiskit.tools.jupyter diff --git a/qiskit_experiments/framework/json.py b/qiskit_experiments/framework/json.py index 89ed9be863..fecd19408b 100644 --- a/qiskit_experiments/framework/json.py +++ b/qiskit_experiments/framework/json.py @@ -34,6 +34,7 @@ from qiskit.circuit.library import BlueprintCircuit from qiskit.quantum_info import DensityMatrix from qiskit.quantum_info.operators.channel.quantum_channel import QuantumChannel +from qiskit.result import LocalReadoutMitigator, CorrelatedReadoutMitigator from qiskit_experiments.version import __version__ @@ -500,6 +501,14 @@ def default(self, obj: Any) -> Any: # pylint: disable=arguments-differ "output_dims": obj.output_dims(), } return _serialize_object(obj, settings=settings) + if isinstance(obj, LocalReadoutMitigator): + # Temporary handling until serialization is added to terra stable release + settings = {"assignment_matrices": obj._assignment_mats, "qubits": obj.qubits} + return _serialize_object(obj, settings=settings) + if isinstance(obj, CorrelatedReadoutMitigator): + # Temporary handling until serialization is added to terra stable release + settings = {"assignment_matrix": obj._assignment_mat, "qubits": obj.qubits} + return _serialize_object(obj, settings=settings) if isinstance(obj, DensityMatrix): # Temporary fix for incorrect settings in qiskit-terra # See https://github.com/Qiskit/qiskit-terra/pull/7194 diff --git a/qiskit_experiments/library/__init__.py b/qiskit_experiments/library/__init__.py index 8704430d25..24b07d1978 100644 --- a/qiskit_experiments/library/__init__.py +++ b/qiskit_experiments/library/__init__.py @@ -72,6 +72,8 @@ ~characterization.RamseyXY ~characterization.FineFrequency ~characterization.ReadoutAngle + ~characterization.LocalReadoutError + ~characterization.CorrelatedReadoutError ~characterization.ResonatorSpectroscopy @@ -105,7 +107,21 @@ class instance to manage parameters and pulse schedules. ~calibration.EFRoughXSXAmplitudeCal """ - +from .calibration import ( + RoughDragCal, + FineDragCal, + FineXDragCal, + FineSXDragCal, + RoughAmplitudeCal, + RoughXSXAmplitudeCal, + EFRoughXSXAmplitudeCal, + FineAmplitudeCal, + FineXAmplitudeCal, + FineSXAmplitudeCal, + RoughFrequencyCal, + FrequencyCal, + FineFrequencyCal, +) from .characterization import ( T1, T2Ramsey, @@ -128,28 +144,12 @@ class instance to manage parameters and pulse schedules. FineFrequency, ReadoutAngle, ResonatorSpectroscopy, + LocalReadoutError, + CorrelatedReadoutError, ) - -from .calibration import ( - RoughDragCal, - FineDragCal, - FineXDragCal, - FineSXDragCal, - RoughAmplitudeCal, - RoughXSXAmplitudeCal, - EFRoughXSXAmplitudeCal, - FineAmplitudeCal, - FineXAmplitudeCal, - FineSXAmplitudeCal, - RoughFrequencyCal, - FrequencyCal, - FineFrequencyCal, -) - from .randomized_benchmarking import StandardRB, InterleavedRB from .tomography import StateTomography, ProcessTomography from .quantum_volume import QuantumVolume -from .mitigation import ReadoutMitigationExperiment # Experiment Sub-modules from . import calibration diff --git a/qiskit_experiments/library/characterization/__init__.py b/qiskit_experiments/library/characterization/__init__.py index 147d1355fb..d3b568d3aa 100644 --- a/qiskit_experiments/library/characterization/__init__.py +++ b/qiskit_experiments/library/characterization/__init__.py @@ -43,6 +43,8 @@ FineDrag FineXDrag FineSXDrag + LocalReadoutError + CorrelatedReadoutError ResonatorSpectroscopy @@ -65,6 +67,9 @@ RamseyXYAnalysis ReadoutAngleAnalysis ResonatorSpectroscopyAnalysis + LocalReadoutErrorAnalysis + CorrelatedReadoutErrorAnalysis + """ from .analysis import ( @@ -80,6 +85,8 @@ CrossResonanceHamiltonianAnalysis, ReadoutAngleAnalysis, ResonatorSpectroscopyAnalysis, + LocalReadoutErrorAnalysis, + CorrelatedReadoutErrorAnalysis, ) from .t1 import T1 @@ -97,4 +104,6 @@ from .drag import RoughDrag from .readout_angle import ReadoutAngle from .fine_drag import FineDrag, FineXDrag, FineSXDrag +from .local_readout_error import LocalReadoutError +from .correlated_readout_error import CorrelatedReadoutError from .resonator_spectroscopy import ResonatorSpectroscopy diff --git a/qiskit_experiments/library/characterization/analysis/__init__.py b/qiskit_experiments/library/characterization/analysis/__init__.py index 0057ac8997..90406da161 100644 --- a/qiskit_experiments/library/characterization/analysis/__init__.py +++ b/qiskit_experiments/library/characterization/analysis/__init__.py @@ -24,4 +24,6 @@ from .tphi_analysis import TphiAnalysis from .cr_hamiltonian_analysis import CrossResonanceHamiltonianAnalysis from .readout_angle_analysis import ReadoutAngleAnalysis +from .local_readout_error_analysis import LocalReadoutErrorAnalysis +from .correlated_readout_error_analysis import CorrelatedReadoutErrorAnalysis from .resonator_spectroscopy_analysis import ResonatorSpectroscopyAnalysis diff --git a/qiskit_experiments/library/characterization/analysis/correlated_readout_error_analysis.py b/qiskit_experiments/library/characterization/analysis/correlated_readout_error_analysis.py new file mode 100644 index 0000000000..6d33e57031 --- /dev/null +++ b/qiskit_experiments/library/characterization/analysis/correlated_readout_error_analysis.py @@ -0,0 +1,124 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021, 2022. +# +# 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. +""" +Analysis class to characterize correlated readout error +""" +from typing import List, Tuple +import numpy as np +import matplotlib.pyplot as plt +from qiskit.result import CorrelatedReadoutMitigator +from qiskit_experiments.framework import ExperimentData +from qiskit_experiments.framework.matplotlib import get_non_gui_ax +from qiskit_experiments.framework import BaseAnalysis, AnalysisResultData, Options + + +class CorrelatedReadoutErrorAnalysis(BaseAnalysis): + r""" + Correlated readout error characterization analysis + + # section: overview + + This class generates the full assignment matrix :math:`A` characterizing the + readout error for the given qubits from the experiment results + and returns the resulting :class:`~qiskit.result.CorrelatedReadoutMitigator` + + :math:`A` is a :math:`2^n\times 2^n` matrix :math:`A` such that :math:`A_{y,x}` + is the probability to observe :math:`y` given the true outcome should be :math:`x`. + + In the experiment, for each :math:`x`a circuit is constructed whose expected + outcome is :math:`x`. From the observed results on the circuit, the probability for + each :math:`y` is determined, and :math:`A_{y,x}` is set accordingly. + + Analysis Results: + * "Local Readout Mitigator": The :class:`~qiskit.result.LocalReadoutMitigator`. + + Analysis Figures: + * (Optional) A figure of the assignment matrix. + + # section: reference + .. ref_arxiv:: 1 2006.14044 + """ + + @classmethod + def _default_options(cls) -> Options: + """Return default analysis options. + + Analysis Options: + plot (bool): Set ``True`` to create figure for fit result. + ax (AxesSubplot): Optional. A matplotlib axis object to draw. + """ + options = super()._default_options() + options.plot = True + options.ax = None + return options + + def _run_analysis( + self, experiment_data: ExperimentData, **options + ) -> Tuple[List[AnalysisResultData], List["matplotlib.figure.Figure"]]: + data = experiment_data.data() + qubits = experiment_data.metadata["physical_qubits"] + labels = [datum["metadata"]["state_label"] for datum in data] + matrix = self._generate_matrix(data, labels) + result_mitigator = CorrelatedReadoutMitigator(matrix, qubits=qubits) + analysis_results = [AnalysisResultData("Correlated Readout Mitigator", result_mitigator)] + if self.options.plot: + ax = options.get("ax", None) + figures = [self._assignment_matrix_visualization(matrix, labels, ax)] + else: + figures = [] + return analysis_results, figures + + def _generate_matrix(self, data, labels) -> np.array: + list_size = len(labels) + matrix = np.zeros([list_size, list_size], dtype=float) + # matrix[i][j] is the probability of counting i for expected j + for datum in data: + expected_outcome = datum["metadata"]["state_label"] + j = labels.index(expected_outcome) + total_counts = sum(datum["counts"].values()) + for measured_outcome, count in datum["counts"].items(): + i = labels.index(measured_outcome) + matrix[i][j] = count / total_counts + return matrix + + def _assignment_matrix_visualization( + self, matrix, labels, ax=None + ) -> "matplotlib.figure.Figure": + """ + Plot the assignment matrix (2D color grid plot). + + Args: + matrix: assignment matrix to plot + ax (matplotlib.axes): settings for the graph + + Returns: + The generated plot of the assignment matrix + + Raises: + QiskitError: if _cal_matrices was not set. + + ImportError: if matplotlib was not installed. + + """ + + if ax is None: + ax = get_non_gui_ax() + figure = ax.get_figure() + ax.matshow(matrix, cmap=plt.cm.binary, clim=[0, 1]) + ax.set_xlabel("Prepared State") + ax.xaxis.set_label_position("top") + ax.set_ylabel("Measured State") + ax.set_xticks(np.arange(len(labels))) + ax.set_yticks(np.arange(len(labels))) + ax.set_xticklabels(labels) + ax.set_yticklabels(labels) + return figure diff --git a/qiskit_experiments/library/mitigation/mitigation_analysis.py b/qiskit_experiments/library/characterization/analysis/local_readout_error_analysis.py similarity index 54% rename from qiskit_experiments/library/mitigation/mitigation_analysis.py rename to qiskit_experiments/library/characterization/analysis/local_readout_error_analysis.py index 8ec1049044..7282e19378 100644 --- a/qiskit_experiments/library/mitigation/mitigation_analysis.py +++ b/qiskit_experiments/library/characterization/analysis/local_readout_error_analysis.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2021. +# (C) Copyright IBM 2021, 2022. # # 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 @@ -10,12 +10,11 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. """ -Measurement calibration analysis classes +Analysis class to characterize local readout error """ from typing import List, Tuple import numpy as np import matplotlib.pyplot as plt -from qiskit.result import CorrelatedReadoutMitigator from qiskit.result import LocalReadoutMitigator from qiskit.result import marginal_counts from qiskit_experiments.framework import ExperimentData @@ -23,72 +22,32 @@ from qiskit_experiments.framework import BaseAnalysis, AnalysisResultData, Options -class CorrelatedMitigationAnalysis(BaseAnalysis): - """ - Measurement correction analysis for a full calibration - """ +class LocalReadoutErrorAnalysis(BaseAnalysis): + r""" + Local readout error characterization analysis + # section: overview - def _run_analysis( - self, experiment_data: ExperimentData, **options - ) -> Tuple[List[AnalysisResultData], List["matplotlib.figure.Figure"]]: - data = experiment_data.data() - qubits = experiment_data.metadata["physical_qubits"] - labels = [datum["metadata"]["label"] for datum in data] - matrix = self._generate_matrix(data, labels) - result_mitigator = CorrelatedReadoutMitigator(matrix, qubits=qubits) - analysis_results = [AnalysisResultData("Correlated Readout Mitigator", result_mitigator)] - ax = options.get("ax", None) - figures = [self._plot_calibration(matrix, labels, ax)] - return analysis_results, figures + This class generates the assignment matrices characterizing the + readout error for each of the given qubits from the experiment result, + and returns the resulting :class:`~qiskit.result.LocalReadoutMitigator` - def _generate_matrix(self, data, labels) -> np.array: - list_size = len(labels) - matrix = np.zeros([list_size, list_size], dtype=float) - # matrix[i][j] is the probability of counting i for expected j - for datum in data: - expected_outcome = datum["metadata"]["label"] - j = labels.index(expected_outcome) - total_counts = sum(datum["counts"].values()) - for measured_outcome, count in datum["counts"].items(): - i = labels.index(measured_outcome) - matrix[i][j] = count / total_counts - return matrix + Each such matrix is a :math:`2\times 2` matrix :math:`A`. Such that :math:`A_{y,x}` + is the probability to observe :math:`y` given the true outcome should be :math:`x`, + where :math:`x,y \in \left\{0,1\right\}` can be 0 and 1. - def _plot_calibration(self, matrix, labels, ax=None) -> "matplotlib.figure.Figure": - """ - Plot the calibration matrix (2D color grid plot). + In the experiment, two circuits are constructed - one for 0 outcome for all + qubits and one for 1 outcome. From the observed results on the circuit, the + probability for each :math:`x,y` is determined, and :math:`A_{y,x}` is set accordingly. - Args: - matrix: calibration matrix to plot - ax (matplotlib.axes): settings for the graph + Analysis Results: + * "Local Readout Mitigator": The :class:`~qiskit.result.LocalReadoutMitigator`. - Returns: - The generated plot of the calibration matrix + Analysis Figures: + * (Optional) A figure of the assignment matrix. + Note: producing this figure scales exponentially with the number of qubits. - Raises: - QiskitError: if _cal_matrices was not set. - - ImportError: if matplotlib was not installed. - - """ - - if ax is None: - ax = get_non_gui_ax() - figure = ax.get_figure() - ax.matshow(matrix, cmap=plt.cm.binary, clim=[0, 1]) - ax.set_xlabel("Prepared State") - ax.xaxis.set_label_position("top") - ax.set_ylabel("Measured State") - ax.set_xticks(np.arange(len(labels))) - ax.set_yticks(np.arange(len(labels))) - ax.set_xticklabels(labels) - ax.set_yticklabels(labels) - return figure - - -class LocalMitigationAnalysis(BaseAnalysis): - """ - Measurement correction analysis for a full calibration + # section: reference + .. ref_arxiv:: 1 2006.14044 """ @classmethod @@ -100,7 +59,8 @@ def _default_options(cls) -> Options: ax(AxesSubplot): Optional. A matplotlib axis object to draw. """ options = super()._default_options() - options.plot = True + # since the plot size grows exponentially with the number of qubits, plotting is off by default + options.plot = False options.ax = None return options @@ -122,11 +82,11 @@ def _run_analysis( return analysis_results, figures def _generate_matrices(self, data) -> List[np.array]: - num_qubits = len(data[0]["metadata"]["label"]) + num_qubits = len(data[0]["metadata"]["state_label"]) counts = [None, None] for result in data: for i in range(2): - if result["metadata"]["label"] == str(i) * num_qubits: + if result["metadata"]["state_label"] == str(i) * num_qubits: counts[i] = result["counts"] matrices = [] for k in range(num_qubits): @@ -156,6 +116,9 @@ def assignment_matrix_visualization(assignment_matrix, ax=None): ax.set_xticks(np.arange(n)) ax.set_yticklabels(n * [""]) ax.set_xticklabels(n * [""]) - ax.set_xlabel(r"$|A - I|$", fontsize=16) + ax.set_title(r"$|A - I |$", fontsize=16) + ax.set_xlabel("Prepared State") + ax.xaxis.set_label_position("top") + ax.set_ylabel("Measured State") figure.colorbar(im2, ax=ax) return figure diff --git a/qiskit_experiments/library/characterization/correlated_readout_error.py b/qiskit_experiments/library/characterization/correlated_readout_error.py new file mode 100644 index 0000000000..79bf7c52a6 --- /dev/null +++ b/qiskit_experiments/library/characterization/correlated_readout_error.py @@ -0,0 +1,89 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021, 2022. +# +# 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. +""" +Correlated readout error calibration experiment class. +""" +from typing import Iterable, List +from qiskit import QuantumCircuit +from qiskit_experiments.framework import BaseExperiment +from qiskit_experiments.library.characterization.analysis.correlated_readout_error_analysis import ( + CorrelatedReadoutErrorAnalysis, +) + + +def calibration_circuit(num_qubits: int, state_label: str) -> QuantumCircuit: + """Return a calibration circuit. + + This is an N-qubit circuit where N is the length of the label. + The circuit consists of X-gates on qubits with label bits equal to 1, + and measurements of all qubits. + """ + circ = QuantumCircuit(num_qubits, name="meas_mit_cal_" + state_label) + for i, val in enumerate(reversed(state_label)): + if val == "1": + circ.x(i) + circ.measure_all() + circ.metadata = {"state_label": state_label} + return circ + + +class CorrelatedReadoutError(BaseExperiment): + r"""Class for correlated readout error characterization experiment + # section: overview + This class constructs the a :class:`~qiskit.result.CorrelatedReadoutMitigator` + containing the full assignment matrix :math:`A` characterizing the readout error + for the given qubits from the experiment results accessible via the + :meth:`~qiskit.result.CorrelatedReadoutMitigator.assignment_matrix` method. + + Readout errors affect quantum computation during the measurement + of the qubits in a quantum device. By characterizing the readout errors, + it is possible to construct a *readout error mitigator* that is used both + to obtain a more accurate distribution of the outputs, and more accurate + measurements of expectation value for measurables. + + The readout mitigator is generated from an *assignment matrix*: + a :math:`2^n\times 2^n` matrix :math:`A` such that :math:`A_{y,x}` is the probability + to observe :math:`y` given the true outcome should be :math:`x`. The assignment matrix is used + to compute the *mitigation matrix* used in the readout error mitigation process itself. + + A *Correlated readout mitigator* uses the full :math:`2^n \times 2^n` assignment matrix, meaning + it can only be used for small values of :math:`n`. + The corresponding class in Qiskit is the + :class:`~qiskit.result.CorrelatedReadoutMitigator` in :mod:`qiskit.result`. + + The experiment generates :math:`2^n` circuits, for every possible + :math:`n`-qubit quantum state and constructs + the assignment matrix and correlated mitigator from the results. + + See :class:`CorrelatedReadoutErrorAnalysis` + documentation for additional information on correlated readout error experiment analysis. + + # section: analysis_ref + :py:class:`CorrelatedReadoutErrorAnalysis` + + # section: reference + .. ref_arxiv:: 1 2006.14044 + """ + + def __init__(self, qubits: Iterable[int]): + """Initialize a correlated readout error characterization experiment. + + Args: + qubits: The qubits being characterized for readout error + """ + super().__init__(qubits) + self.analysis = CorrelatedReadoutErrorAnalysis() + + def circuits(self) -> List[QuantumCircuit]: + """Returns the experiment's circuits""" + labels = [bin(j)[2:].zfill(self.num_qubits) for j in range(2**self.num_qubits)] + return [calibration_circuit(self.num_qubits, label) for label in labels] diff --git a/qiskit_experiments/library/characterization/local_readout_error.py b/qiskit_experiments/library/characterization/local_readout_error.py new file mode 100644 index 0000000000..77efdfb5b1 --- /dev/null +++ b/qiskit_experiments/library/characterization/local_readout_error.py @@ -0,0 +1,78 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021, 2022. +# +# 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. +""" +Local readout error calibration experiment class. +""" +from typing import Iterable, List +from qiskit import QuantumCircuit +from qiskit_experiments.framework import BaseExperiment +from qiskit_experiments.library.characterization.analysis.local_readout_error_analysis import ( + LocalReadoutErrorAnalysis, +) +from .correlated_readout_error import calibration_circuit + + +class LocalReadoutError(BaseExperiment): + r"""Class for local readout error characterization experiment + # section: overview + + This class constructs the a :class:`~qiskit.result.LocalReadoutMitigator` containing sequence + of assignment matrices :math:`A` characterizing the readout error for the given qubits + from the experiment results. The full assignment matrix is accessible via the + :meth:`~qiskit.result.LocalReadoutMitigator.assignment_matrix` method. + + Readout errors affect quantum computation during the measurement + of the qubits in a quantum device. By characterizing the readout errors, + it is possible to construct a *readout error mitigator* that is used both + to obtain a more accurate distribution of the outputs, and more accurate + measurements of expectation value for measurables. + + The readout mitigator is generated from an *assignment matrix*: + a :math:`2^n \times 2^n` matrix :math:`A` such that :math:`A_{y,x}` is the probability + to observe :math:`y` given the true outcome should be :math:`x`. The assignment matrix is used + to compute the *mitigation matrix* used in the readout error mitigation process itself. + + A *Local readout mitigator* works under the assumption that readout errors are mostly + *local*, meaning readout errors for different qubits are independent of each other. + In this case, the assignment matrix is the tensor product of :math:`n` :math:`2 \times 2` + matrices, one for each qubit, making it practical to store the assignment matrix in implicit + form, by storing the individual :math:`2 \times 2` assignment matrices. + The corresponding class in Qiskit is the :class:`~qiskit.result.LocalReadoutMitigator` + in :mod:`qiskit.result`. + + The experiment generates 2 circuits, corresponding to the states + :math:`|0^n\rangle` and :math:`|1^n\rangle`, measuring the error in all + the qubits at once, and constructs the assignment matrix and local mitigator from the results. + + See :class:`LocalReadoutErrorAnalysis` + documentation for additional information on local readout error experiment analysis. + + # section: analysis_ref + :py:class:`LocalReadoutErrorAnalysis` + + # section: reference + .. ref_arxiv:: 1 2006.14044 + """ + + def __init__(self, qubits: Iterable[int]): + """Initialize a local readout error characterization experiment. + + Args: + qubits: The qubits being characterized for readout error + """ + super().__init__(qubits) + self.analysis = LocalReadoutErrorAnalysis() + + def circuits(self) -> List[QuantumCircuit]: + """Returns the experiment's circuits""" + labels = ["0" * self.num_qubits, "1" * self.num_qubits] + return [calibration_circuit(self.num_qubits, label) for label in labels] diff --git a/qiskit_experiments/library/mitigation/__init__.py b/qiskit_experiments/library/mitigation/__init__.py deleted file mode 100644 index 25d6a23a35..0000000000 --- a/qiskit_experiments/library/mitigation/__init__.py +++ /dev/null @@ -1,41 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# 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. - -""" -=============================================================================================== -Readout Mitigation Experiments (:mod:`qiskit_experiments.library.mitigation`) -=============================================================================================== - -.. currentmodule:: qiskit_experiments.library.mitigation - -Experiment -=========== -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/experiment.rst - - ReadoutMitigationExperiment - - -Analysis -======== - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/analysis.rst - - CorrelatedMitigationAnalysis - LocalMitigationAnalysis -""" -from .mitigation_experiment import ReadoutMitigationExperiment -from .mitigation_analysis import CorrelatedMitigationAnalysis -from .mitigation_analysis import LocalMitigationAnalysis diff --git a/qiskit_experiments/library/mitigation/mitigation_experiment.py b/qiskit_experiments/library/mitigation/mitigation_experiment.py deleted file mode 100644 index 3bafa7fc9d..0000000000 --- a/qiskit_experiments/library/mitigation/mitigation_experiment.py +++ /dev/null @@ -1,114 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# 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. -""" -Measurement calibration experiment classes. -""" -from typing import Iterable, List - -from qiskit import QuantumCircuit -from qiskit.exceptions import QiskitError -from qiskit_experiments.framework import BaseExperiment -from .mitigation_analysis import CorrelatedMitigationAnalysis, LocalMitigationAnalysis - - -class ReadoutMitigationExperiment(BaseExperiment): - """Class for readout mitigation experiments""" - - METHOD_LOCAL = "local" - METHOD_CORRELATED = "correlated" - ALL_METHODS = [METHOD_LOCAL, METHOD_CORRELATED] - - def __init__(self, qubits: Iterable[int], method=METHOD_LOCAL): - """Initialize a mitigation experiment. - - Args: - qubits: The qubits being mitigated - method: A string denoting mitigation method - - Raises: - QiskitError: if the given mitigation method is not recoginzed - - Additional info: - The currently supported mitigation methods are: - * "local": each qubit is mitigated by itself; this is the default method, - and assumes readout errors are independent for each qubits - * "correlated": All the qubits are mitigated together; this results in an exponentially - large mitigation matrix and so is useable only for a small number of qubits, - but might be more accurate than local mitigation. - """ - super().__init__(qubits) - if method not in self.ALL_METHODS: - raise QiskitError("Method {} not recognized".format(method)) - if method == self.METHOD_LOCAL: - self.helper = LocalMitigationHelper(self.num_qubits) - if method == self.METHOD_CORRELATED: - self.helper = CorrelatedMitigationHelper(self.num_qubits) - - self.analysis = self.helper.analysis() - - def circuits(self) -> List[QuantumCircuit]: - """Returns the experiment's circuits""" - return [self._calibration_circuit(self.num_qubits, label) for label in self.helper.labels()] - - @staticmethod - def _calibration_circuit(num_qubits: int, label: str) -> QuantumCircuit: - """Return a calibration circuit. - - This is an N-qubit circuit where N is the length of the label. - The circuit consists of X-gates on qubits with label bits equal to 1, - and measurements of all qubits. - """ - circ = QuantumCircuit(num_qubits, name="meas_mit_cal_" + label) - for i, val in enumerate(reversed(label)): - if val == "1": - circ.x(i) - circ.measure_all() - circ.metadata = {"label": label} - return circ - - -class CorrelatedMitigationHelper: - """Helper class for correlated mitigation experiment data""" - - def __init__(self, num_qubits: int): - """Creates the helper class - Args: - num_qubits: The number of qubits being mitigated - """ - self.num_qubits = num_qubits - - def analysis(self): - """Returns the analysis class for the mitigation""" - return CorrelatedMitigationAnalysis() - - def labels(self) -> List[str]: - """Returns the labels dictating the generation of the mitigation circuits""" - return [bin(j)[2:].zfill(self.num_qubits) for j in range(2**self.num_qubits)] - - -class LocalMitigationHelper: - """Helper class for local mitigation experiment data""" - - def __init__(self, num_qubits: int): - """Creates the helper class - Args: - num_qubits: The number of qubits being mitigated - """ - self.num_qubits = num_qubits - - def analysis(self): - """Returns the analysis class for the mitigation""" - return LocalMitigationAnalysis() - - def labels(self) -> List[str]: - """Returns the labels dictating the generation of the mitigation circuits""" - return ["0" * self.num_qubits, "1" * self.num_qubits] diff --git a/releasenotes/notes/readout-mitigation-experiment-4ea5392ee955a54c.yaml b/releasenotes/notes/readout-mitigation-experiment-4ea5392ee955a54c.yaml index d630cd3637..16ab34188f 100644 --- a/releasenotes/notes/readout-mitigation-experiment-4ea5392ee955a54c.yaml +++ b/releasenotes/notes/readout-mitigation-experiment-4ea5392ee955a54c.yaml @@ -1,5 +1,5 @@ --- features: - | - Added a new experiment (`qiskit_experiments.library import ReadoutMitigationExperiment`) - for calibrating readout mitigators. + Added two new experiments (:class:`qiskit_experiments.library.LocalReadoutError` and :class:`qiskit_experiments.library.CorrelatedReadoutError`) + for characterizing the readout error of devices. diff --git a/test/test_mitigation.py b/test/test_readout_error.py similarity index 56% rename from test/test_mitigation.py rename to test/test_readout_error.py index 92acaa5a7b..73f6f00017 100644 --- a/test/test_mitigation.py +++ b/test/test_readout_error.py @@ -11,20 +11,26 @@ # that they have been altered from the originals. """ -A Tester for the Readout Mitigation experiment +A Tester for the Readout error experiment """ import unittest +import json from test.base import QiskitExperimentsTestCase import numpy as np from qiskit.quantum_info.operators.predicates import matrix_equal -from qiskit_experiments.library import ReadoutMitigationExperiment +from qiskit.providers.aer import AerSimulator +from qiskit.test.mock import FakeParis +from qiskit_experiments.library.characterization import LocalReadoutError, CorrelatedReadoutError from qiskit_experiments.framework import ExperimentData +from qiskit_experiments.framework import ParallelExperiment +from qiskit_experiments.test.fake_service import FakeService +from qiskit_experiments.framework.json import ExperimentEncoder, ExperimentDecoder -class TestMitigation(QiskitExperimentsTestCase): - """Test ReadoutMitigationExperiment""" +class TestRedoutError(QiskitExperimentsTestCase): + """Test Readout Error experiments""" def test_local_analysis(self): """Tests local mitigator generation from experimental data""" @@ -32,12 +38,12 @@ def test_local_analysis(self): run_data = [ { "counts": {"000": 986, "010": 10, "100": 16, "001": 12}, - "metadata": {"label": "000"}, + "metadata": {"state_label": "000"}, "shots": 1024, }, { "counts": {"111": 930, "110": 39, "011": 24, "101": 29, "010": 1, "100": 1}, - "metadata": {"label": "111"}, + "metadata": {"state_label": "111"}, "shots": 1024, }, ] @@ -50,7 +56,7 @@ def test_local_analysis(self): expdata = ExperimentData() expdata.add_data(run_data) expdata._metadata = run_meta - exp = ReadoutMitigationExperiment(qubits) + exp = LocalReadoutError(qubits) result = exp.analysis.run(expdata) mitigator = result.analysis_results(0).value @@ -64,42 +70,42 @@ def test_correlated_analysis(self): run_data = [ { "counts": {"000": 989, "010": 12, "100": 7, "001": 15, "101": 1}, - "metadata": {"label": "000"}, + "metadata": {"state_label": "000"}, "shots": 1024, }, { "counts": {"001": 971, "101": 15, "000": 36, "011": 2}, - "metadata": {"label": "001"}, + "metadata": {"state_label": "001"}, "shots": 1024, }, { "counts": {"000": 30, "010": 965, "110": 15, "011": 11, "001": 2, "100": 1}, - "metadata": {"label": "010"}, + "metadata": {"state_label": "010"}, "shots": 1024, }, { "counts": {"011": 955, "111": 15, "010": 26, "001": 27, "110": 1}, - "metadata": {"label": "011"}, + "metadata": {"state_label": "011"}, "shots": 1024, }, { "counts": {"100": 983, "101": 8, "110": 13, "000": 20}, - "metadata": {"label": "100"}, + "metadata": {"state_label": "100"}, "shots": 1024, }, { "counts": {"101": 947, "001": 34, "100": 32, "111": 11}, - "metadata": {"label": "101"}, + "metadata": {"state_label": "101"}, "shots": 1024, }, { "counts": {"100": 26, "110": 965, "010": 21, "111": 11, "000": 1}, - "metadata": {"label": "110"}, + "metadata": {"state_label": "110"}, "shots": 1024, }, { "counts": {"111": 938, "011": 23, "110": 35, "101": 27, "100": 1}, - "metadata": {"label": "111"}, + "metadata": {"state_label": "111"}, "shots": 1024, }, ] @@ -120,7 +126,7 @@ def test_correlated_analysis(self): expdata = ExperimentData() expdata.add_data(run_data) expdata._metadata = run_meta - exp = ReadoutMitigationExperiment(qubits, method="correlated") + exp = CorrelatedReadoutError(qubits) result = exp.analysis.run(expdata) mitigator = result.analysis_results(0).value @@ -128,6 +134,54 @@ def test_correlated_analysis(self): self.assertEqual(qubits, mitigator._qubits) self.assertTrue(matrix_equal(expected_assignment_matrix, mitigator.assignment_matrix())) + def test_circuit_generation(self): + """Verifies circuits are generated by the experiments""" + qubits = [1, 2, 3] + exp = CorrelatedReadoutError(qubits) + self.assertEqual(len(exp.circuits()), 8) + + exp = LocalReadoutError(qubits) + self.assertEqual(len(exp.circuits()), 2) + + def test_parallel_running(self): + """Test that parallel experiments work for this experiment""" + backend = AerSimulator.from_backend(FakeParis()) + exp1 = CorrelatedReadoutError([0, 2]) + exp2 = CorrelatedReadoutError([1, 3]) + exp = ParallelExperiment([exp1, exp2]) + expdata = exp.run(backend=backend).block_for_results() + mit1 = expdata.child_data(0).analysis_results(0).value + mit2 = expdata.child_data(1).analysis_results(0).value + assignment_matrix1 = mit1.assignment_matrix() + assignment_matrix2 = mit2.assignment_matrix() + self.assertFalse(matrix_equal(assignment_matrix1, assignment_matrix2)) + + def test_database_save_and_load(self): + """Tests saving and loading the mitigator from the DB""" + qubits = [0, 1] + backend = AerSimulator.from_backend(FakeParis()) + exp = LocalReadoutError(qubits) + exp_data = exp.run(backend).block_for_results() + exp_data.service = FakeService() + exp_data.save() + loaded_data = ExperimentData.load(exp_data.experiment_id, exp_data.service) + exp_res = exp_data.analysis_results() + load_res = loaded_data.analysis_results() + exp_matrix = exp_res[0].value.assignment_matrix() + load_matrix = load_res[0].value.assignment_matrix() + self.assertTrue(matrix_equal(exp_matrix, load_matrix)) + + def test_json_serialization(self): + """Verifies that mitigators can be serialized for DB storage""" + qubits = [0, 1] + backend = AerSimulator.from_backend(FakeParis()) + exp = LocalReadoutError(qubits) + exp_data = exp.run(backend).block_for_results() + mitigator = exp_data.analysis_results(0).value + serialized = json.dumps(mitigator, cls=ExperimentEncoder) + loaded = json.loads(serialized, cls=ExperimentDecoder) + self.assertTrue(matrix_equal(mitigator.assignment_matrix(), loaded.assignment_matrix())) + if __name__ == "__main__": unittest.main()