diff --git a/qiskit_experiments/library/__init__.py b/qiskit_experiments/library/__init__.py index f4cb58bd78..5f4b9cb000 100644 --- a/qiskit_experiments/library/__init__.py +++ b/qiskit_experiments/library/__init__.py @@ -38,6 +38,8 @@ ~randomized_benchmarking.InterleavedRB ~tomography.StateTomography ~tomography.ProcessTomography + ~tomography.MitigatedStateTomography + ~tomography.MitigatedProcessTomography ~quantum_volume.QuantumVolume .. _characterization: @@ -156,7 +158,12 @@ class instance to manage parameters and pulse schedules. MultiStateDiscrimination, ) from .randomized_benchmarking import StandardRB, InterleavedRB -from .tomography import StateTomography, ProcessTomography +from .tomography import ( + StateTomography, + ProcessTomography, + MitigatedStateTomography, + MitigatedProcessTomography, +) from .quantum_volume import QuantumVolume # Experiment Sub-modules diff --git a/qiskit_experiments/library/tomography/__init__.py b/qiskit_experiments/library/tomography/__init__.py index aafbe020e7..c9ebeb058b 100644 --- a/qiskit_experiments/library/tomography/__init__.py +++ b/qiskit_experiments/library/tomography/__init__.py @@ -26,6 +26,8 @@ StateTomography ProcessTomography + MitigatedStateTomography + MitigatedProcessTomography Analysis @@ -37,7 +39,7 @@ StateTomographyAnalysis ProcessTomographyAnalysis - + MitigatedTomographyAnalysis Tomography Fitters ================== @@ -90,6 +92,9 @@ # Experiment Classes from .qst_experiment import StateTomography, StateTomographyAnalysis from .qpt_experiment import ProcessTomography, ProcessTomographyAnalysis +from .mit_qst_experiment import MitigatedStateTomography +from .mit_qpt_experiment import MitigatedProcessTomography +from .mit_tomography_analysis import MitigatedTomographyAnalysis # Basis Classes from . import basis diff --git a/qiskit_experiments/library/tomography/mit_qpt_experiment.py b/qiskit_experiments/library/tomography/mit_qpt_experiment.py new file mode 100644 index 0000000000..cf75c44984 --- /dev/null +++ b/qiskit_experiments/library/tomography/mit_qpt_experiment.py @@ -0,0 +1,111 @@ +# This code is part of Qiskit. +# +# (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. +""" +Quantum Process Tomography experiment +""" + +from typing import Union, Optional, Iterable, List, Tuple, Sequence +from qiskit.providers.backend import Backend +from qiskit.circuit import QuantumCircuit, Instruction +from qiskit.quantum_info.operators.base_operator import BaseOperator +from qiskit_experiments.framework import BatchExperiment, BaseAnalysis +from qiskit_experiments.library.characterization.local_readout_error import LocalReadoutError +from .qpt_experiment import ProcessTomography +from .mit_tomography_analysis import MitigatedTomographyAnalysis +from . import basis + + +class MitigatedProcessTomography(BatchExperiment): + """Readout error mitigated quantum process tomography experiment. + + # section: overview + Readout error mitigated Quantum process tomography is a batch + experiment consisting of a :class:`~.LocalReadoutError` characterization + experiments, followed by a :class:`~.ProcessTomography` experiment. + + During analysis the assignment matrix local readout error model is + used to automatically construct a noisy Pauli measurement basis for + performing readout error mitigated process tomography fitting. + + # section: note + Performing readout error mitigation full process tomography on an + `N`-qubit circuit requires running 2 readout error characterization + circuits and :math:`4^N 3^N` measurement circuits using the Pauli + preparation and measurement bases. + + # section: analysis_ref + :py:class:`MitigatedTomographyAnalysis` + + # section: see_also + qiskit_experiments.library.ProcessTomography + qiskit_experiments.library.LocalReadoutError + + """ + + def __init__( + self, + circuit: Union[QuantumCircuit, Instruction, BaseOperator], + backend: Optional[Backend] = None, + physical_qubits: Optional[Sequence[int]] = None, + measurement_indices: Optional[Sequence[int]] = None, + preparation_indices: Optional[Sequence[int]] = None, + basis_indices: Optional[Iterable[Tuple[List[int], List[int]]]] = None, + analysis: Union[BaseAnalysis, None, str] = "default", + ): + """Initialize a quantum process tomography experiment. + + Args: + circuit: the quantum process circuit. If not a quantum circuit + it must be a class that can be appended to a quantum circuit. + backend: The backend to run the experiment on. + physical_qubits: Optional, the physical qubits for the initial state circuit. + If None this will be qubits [0, N) for an N-qubit circuit. + measurement_indices: Optional, the `physical_qubits` indices to be measured. + If None all circuit physical qubits will be measured. + preparation_indices: Optional, the `physical_qubits` indices to be prepared. + If None all circuit physical qubits will be prepared. + basis_indices: Optional, a list of basis indices for generating partial + tomography measurement data. Each item should be given as a pair of + lists of preparation and measurement basis configurations + ``([p[0], p[1], ..], m[0], m[1], ...])``, where ``p[i]`` is the + preparation basis index, and ``m[i]`` is the measurement basis index + for qubit-i. If not specified full tomography for all indices of the + preparation and measurement bases will be performed. + analysis: Optional, a custom tomography analysis instance to use. + If ``"default"`` :class:`~.ProcessTomographyAnalysis` will be + used. If None no analysis instance will be set. + """ + tomo_exp = ProcessTomography( + circuit, + backend=backend, + physical_qubits=physical_qubits, + measurement_basis=basis.PauliMeasurementBasis(), + measurement_indices=measurement_indices, + preparation_basis=basis.PauliPreparationBasis(), + preparation_indices=preparation_indices, + basis_indices=basis_indices, + analysis=analysis, + ) + + roerror_exp = LocalReadoutError( + tomo_exp.physical_qubits, + backend=backend, + ) + + if analysis is None: + mit_analysis = (None,) + else: + mit_analysis = MitigatedTomographyAnalysis(roerror_exp.analysis, tomo_exp.analysis) + + super().__init__( + [roerror_exp, tomo_exp], backend=backend, flatten_results=True, analysis=mit_analysis + ) diff --git a/qiskit_experiments/library/tomography/mit_qst_experiment.py b/qiskit_experiments/library/tomography/mit_qst_experiment.py new file mode 100644 index 0000000000..bec409eb34 --- /dev/null +++ b/qiskit_experiments/library/tomography/mit_qst_experiment.py @@ -0,0 +1,104 @@ +# This code is part of Qiskit. +# +# (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. +""" +Quantum State Tomography experiment +""" + +from typing import Union, Optional, List, Sequence +from qiskit.providers.backend import Backend +from qiskit.circuit import QuantumCircuit, Instruction +from qiskit.quantum_info.operators.base_operator import BaseOperator +from qiskit_experiments.framework import BatchExperiment, BaseAnalysis +from qiskit_experiments.library.characterization.local_readout_error import LocalReadoutError +from .qst_experiment import StateTomography +from .mit_tomography_analysis import MitigatedTomographyAnalysis +from . import basis + + +class MitigatedStateTomography(BatchExperiment): + """Readout error mitigated quantum state tomography experiment. + + # section: overview + Readout error mitigated quantum state tomography is a batch + experiment consisting of a :class:`~.LocalReadoutError` characterization + experiments, followed by a :class:`~.StateTomography` experiment. + + During analysis the assignment matrix local readout error model is + used to automatically construct a noisy Pauli measurement basis for + performing readout error mitigated state tomography fitting. + + # section: note + Performing readout error mitigation full state tomography on an + `N`-qubit circuit requires running 2 readout error characterization + circuits and :math:`3^N` measurement circuits using the Pauli + measurement basis. + + # section: analysis_ref + :py:class:`MitigatedTomographyAnalysis` + + # section: see_also + qiskit_experiments.library.StateTomography + qiskit_experiments.library.LocalReadoutError + + """ + + def __init__( + self, + circuit: Union[QuantumCircuit, Instruction, BaseOperator], + backend: Optional[Backend] = None, + physical_qubits: Optional[Sequence[int]] = None, + measurement_indices: Optional[Sequence[int]] = None, + basis_indices: Optional[Sequence[List[int]]] = None, + analysis: Union[BaseAnalysis, None, str] = "default", + ): + """Initialize a quantum process tomography experiment. + + Args: + circuit: the quantum process circuit. If not a quantum circuit + it must be a class that can be appended to a quantum circuit. + backend: The backend to run the experiment on. + physical_qubits: Optional, the physical qubits for the initial state circuit. + If None this will be qubits [0, N) for an N-qubit circuit. + measurement_indices: Optional, the `physical_qubits` indices to be measured. + If None all circuit physical qubits will be measured. + basis_indices: Optional, a list of basis indices for generating partial + tomography measurement data. Each item should be given as a list of + measurement basis configurations ``[m[0], m[1], ...]`` where ``m[i]`` + is the measurement basis index for qubit-i. If not specified full + tomography for all indices of the measurement basis will be performed. + analysis: Optional, a custom tomography analysis instance to use. + If ``"default"`` :class:`~.ProcessTomographyAnalysis` will be + used. If None no analysis instance will be set. + """ + tomo_exp = StateTomography( + circuit, + backend=backend, + physical_qubits=physical_qubits, + measurement_basis=basis.PauliMeasurementBasis(), + measurement_indices=measurement_indices, + basis_indices=basis_indices, + analysis=analysis, + ) + + roerror_exp = LocalReadoutError( + tomo_exp.physical_qubits, + backend=backend, + ) + + if analysis is None: + mit_analysis = (None,) + else: + mit_analysis = MitigatedTomographyAnalysis(roerror_exp.analysis, tomo_exp.analysis) + + super().__init__( + [roerror_exp, tomo_exp], backend=backend, flatten_results=True, analysis=mit_analysis + ) diff --git a/qiskit_experiments/library/tomography/mit_tomography_analysis.py b/qiskit_experiments/library/tomography/mit_tomography_analysis.py new file mode 100644 index 0000000000..e23e746d65 --- /dev/null +++ b/qiskit_experiments/library/tomography/mit_tomography_analysis.py @@ -0,0 +1,127 @@ +# This code is part of Qiskit. +# +# (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. +""" +Readout error mitigated tomography analysis +""" + +from qiskit_experiments.framework import CompositeAnalysis +from qiskit_experiments.library.characterization import LocalReadoutErrorAnalysis +from .tomography_analysis import TomographyAnalysis +from .basis.pauli_basis import PauliMeasurementBasis + + +class MitigatedTomographyAnalysis(CompositeAnalysis): + """Analysis for readout error mitigated tomography experiments. + + Analysis is performed as a :class:`.CompositeAnalysis` consisting + of :class:`.LocalReadoutErrorAnalysis` to determine the local + assigment matrices describing single qubit Z-basis readout errors, + and then these matrices are used to automatically construct a noisy + :class:`~.PauliMeasurementBasis` for use during tomographic + fitting with the tomography analysis. + """ + + def __init__(self, roerror_analysis="default", tomography_analysis="default"): + """Initialize mitigated tomography analysis""" + if roerror_analysis == "default": + roerror_analysis = LocalReadoutErrorAnalysis() + if tomography_analysis == "default": + tomography_analysis = TomographyAnalysis() + super().__init__([roerror_analysis, tomography_analysis], flatten_results=True) + + @classmethod + def _default_options(cls): + """Default analysis options + + Analysis Options: + fitter (str or Callable): The fitter function to use for reconstruction. + This can be a string to select one of the built-in fitters, or a callable to + supply a custom fitter function. See the `Fitter Functions` section for + additional information. + fitter_options (dict): Any addition kwarg options to be supplied to the fitter + function. For documentation of available kwargs refer to the fitter function + documentation. + rescale_positive (bool): If True rescale the state returned by the fitter + to be positive-semidefinite. See the `PSD Rescaling` section for + additional information (Default: True). + rescale_trace (bool): If True rescale the state returned by the fitter + have either trace 1 for :class:`~qiskit.quantum_info.DensityMatrix`, + or trace dim for :class:`~qiskit.quantum_info.Choi` matrices (Default: True). + measurement_qubits (Sequence[int]): Optional, the physical qubits with tomographic + measurements. If not specified will be set to ``[0, ..., N-1]`` for N-qubit + tomographic measurements. + preparation_qubits (Sequence[int]): Optional, the physical qubits with tomographic + preparations. If not specified will be set to ``[0, ..., N-1]`` for N-qubit + tomographic preparations. + unmitigated_fit (bool): If True also run tomography fit without readout error + mitigation and include both mitigated and unmitigated analysis results. If + False only compute mitigated results (Default: False) + target (Any): Optional, target object for fidelity comparison of the fit + (Default: None). + """ + # Override options to be tomography options minus bases + options = super()._default_options() + options.fitter = "linear_inversion" + options.fitter_options = {} + options.rescale_positive = True + options.rescale_trace = True + options.measurement_qubits = None + options.preparation_qubits = None + options.unmitigated_fit = False + options.target = None + return options + + def set_options(self, **fields): + # filter fields + self_fields = {key: val for key, val in fields.items() if hasattr(self.options, key)} + super().set_options(**self_fields) + tomo_fields = { + key: val for key, val in fields.items() if hasattr(self._analyses[1].options, key) + } + self._analyses[1].set_options(**tomo_fields) + + def _run_analysis(self, experiment_data): + # Return list of experiment data containers for each component experiment + # containing the marginalized data from the composite experiment + roerror_analysis, tomo_analysis = self._analyses + roerror_data, tomo_data = self._component_experiment_data(experiment_data) + + # Run readout error analysis + roerror_analysis.run(roerror_data, replace_results=True).block_for_results() + + # Construct noisy measurement basis + mitigator = roerror_data.analysis_results(0).value + + # Construct noisy measurement basis + measurement_basis = PauliMeasurementBasis(mitigator=mitigator) + tomo_analysis.set_options(measurement_basis=measurement_basis) + + # Run mitigated tomography analysis + tomo_analysis.run(tomo_data, replace_results=True).block_for_results() + for res in tomo_data.analysis_results(block=False): + res.extra["mitigated"] = True + + # Combine results so that tomography results are ordered first + combined_data = [tomo_data, roerror_data] + + # Run unmitigated tomography analysis + if self.options.unmitigated_fit: + tomo_analysis.set_options(measurement_basis=PauliMeasurementBasis()) + nomit_data = tomo_analysis.run(tomo_data, replace_results=False).block_for_results() + for res in nomit_data.analysis_results(block=False): + res.extra["mitigated"] = False + combined_data.append(nomit_data) + + if self._flatten_results: + return self._combine_results(combined_data) + + return [], [] diff --git a/qiskit_experiments/library/tomography/qpt_analysis.py b/qiskit_experiments/library/tomography/qpt_analysis.py index 06f1eafc6e..b51d27ad24 100644 --- a/qiskit_experiments/library/tomography/qpt_analysis.py +++ b/qiskit_experiments/library/tomography/qpt_analysis.py @@ -82,6 +82,12 @@ def _default_options(cls) -> Options: fitter function. rescale_trace (bool): If True rescale the state returned by the fitter have either trace 1 (Default: True). + measurement_qubits (Sequence[int]): Optional, the physical qubits with tomographic + measurements. If not specified will be set to ``[0, ..., N-1]`` for N-qubit + tomographic measurements. + preparation_qubits (Sequence[int]): Optional, the physical qubits with tomographic + preparations. If not specified will be set to ``[0, ..., N-1]`` for N-qubit + tomographic preparations. target (Union[str, :class:`~qiskit.quantum_info.operators.channel.quantum_channel`, :class:`~qiskit.quantum_info.Operator`]): Optional, Set a custom target quantum channel for computing the :func:~qiskit.quantum_info.process_fidelity` of the diff --git a/qiskit_experiments/library/tomography/qpt_experiment.py b/qiskit_experiments/library/tomography/qpt_experiment.py index 481c0c6d20..512c6a70bf 100644 --- a/qiskit_experiments/library/tomography/qpt_experiment.py +++ b/qiskit_experiments/library/tomography/qpt_experiment.py @@ -13,7 +13,7 @@ Quantum Process Tomography experiment """ -from typing import Union, Optional, Iterable, List, Tuple, Sequence +from typing import Union, Optional, List, Tuple, Sequence import numpy as np from qiskit.circuit import QuantumCircuit, Instruction from qiskit.providers.backend import Backend @@ -52,7 +52,14 @@ class ProcessTomography(TomographyExperiment): """ - @deprecate_arguments({"qubits": "physical_qubits"}, "0.5") + @deprecate_arguments( + { + "qubits": "physical_qubits", + "measurement_qubits": "measurement_indices", + "preparation_qubits": "preparation_indices", + }, + "0.5", + ) def __init__( self, circuit: Union[QuantumCircuit, Instruction, BaseOperator], @@ -60,11 +67,9 @@ def __init__( physical_qubits: Optional[Sequence[int]] = None, measurement_basis: basis.MeasurementBasis = basis.PauliMeasurementBasis(), measurement_indices: Optional[Sequence[int]] = None, - measurement_qubits: Optional[Sequence[int]] = None, preparation_basis: basis.PreparationBasis = basis.PauliPreparationBasis(), preparation_indices: Optional[Sequence[int]] = None, - preparation_qubits: Optional[Sequence[int]] = None, - basis_indices: Optional[Iterable[Tuple[List[int], List[int]]]] = None, + basis_indices: Optional[Sequence[Tuple[List[int], List[int]]]] = None, analysis: Union[BaseAnalysis, None, str] = "default", target: Union[Statevector, DensityMatrix, None, str] = "default", ): @@ -80,12 +85,10 @@ def __init__( default basis is the :class:`~basis.PauliMeasurementBasis`. measurement_indices: Optional, the `physical_qubits` indices to be measured. If None all circuit physical qubits will be measured. - measurement_qubits: DEPRECATED, equivalent to measurement_indices. preparation_basis: Tomography basis for measurements. If not specified the default basis is the :class:`~basis.PauliPreparationBasis`. preparation_indices: Optional, the `physical_qubits` indices to be prepared. If None all circuit physical qubits will be prepared. - preparation_qubits: DEPRECATED, equivalent to preparation_indices. basis_indices: Optional, a list of basis indices for generating partial tomography measurement data. Each item should be given as a pair of lists of preparation and measurement basis configurations @@ -110,10 +113,8 @@ def __init__( physical_qubits=physical_qubits, measurement_basis=measurement_basis, measurement_indices=measurement_indices, - measurement_qubits=measurement_qubits, preparation_basis=preparation_basis, preparation_indices=preparation_indices, - preparation_qubits=preparation_qubits, basis_indices=basis_indices, analysis=analysis, ) diff --git a/qiskit_experiments/library/tomography/qst_analysis.py b/qiskit_experiments/library/tomography/qst_analysis.py index 484913e158..b29ee17e1e 100644 --- a/qiskit_experiments/library/tomography/qst_analysis.py +++ b/qiskit_experiments/library/tomography/qst_analysis.py @@ -78,6 +78,9 @@ def _default_options(cls) -> Options: fitter function. rescale_trace (bool): If True rescale the state returned by the fitter have either trace 1 (Default: True). + measurement_qubits (Sequence[int]): Optional, the physical qubits with tomographic + measurements. If not specified will be set to ``[0, ..., N-1]`` for N-qubit + tomographic measurements. target (Union[str, :class:`~qiskit.quantum_info.DensityMatrix`, :class:`~qiskit.quantum_info.Statevector`]): Optional, et a custom target quantum state for computing the :func:~qiskit.quantum_info.state_fidelity` diff --git a/qiskit_experiments/library/tomography/qst_experiment.py b/qiskit_experiments/library/tomography/qst_experiment.py index 157d7785a6..2a5fbedfda 100644 --- a/qiskit_experiments/library/tomography/qst_experiment.py +++ b/qiskit_experiments/library/tomography/qst_experiment.py @@ -13,7 +13,7 @@ Quantum State Tomography experiment """ -from typing import Union, Optional, Iterable, List, Sequence +from typing import Union, Optional, List, Sequence from qiskit.providers.backend import Backend from qiskit.circuit import QuantumCircuit, Instruction from qiskit.quantum_info.operators.base_operator import BaseOperator @@ -50,7 +50,9 @@ class StateTomography(TomographyExperiment): """ - @deprecate_arguments({"qubits": "physical_qubits"}, "0.5") + @deprecate_arguments( + {"qubits": "physical_qubits", "measurement_qubits": "measurement_indices"}, "0.5" + ) def __init__( self, circuit: Union[QuantumCircuit, Instruction, BaseOperator, Statevector], @@ -58,8 +60,7 @@ def __init__( physical_qubits: Optional[Sequence[int]] = None, measurement_basis: basis.MeasurementBasis = basis.PauliMeasurementBasis(), measurement_indices: Optional[Sequence[int]] = None, - measurement_qubits: Optional[Sequence[int]] = None, - basis_indices: Optional[Iterable[List[int]]] = None, + basis_indices: Optional[Sequence[List[int]]] = None, analysis: Union[BaseAnalysis, None, str] = "default", target: Union[Statevector, DensityMatrix, None, str] = "default", ): @@ -75,7 +76,6 @@ def __init__( default basis is the :class:`~basis.PauliMeasurementBasis`. measurement_indices: Optional, the `physical_qubits` indices to be measured. If None all circuit physical qubits will be measured. - measurement_qubits: DEPRECATED, equivalent to measurement_indices. basis_indices: Optional, a list of basis indices for generating partial tomography measurement data. Each item should be given as a list of measurement basis configurations ``[m[0], m[1], ...]`` where ``m[i]`` @@ -108,7 +108,6 @@ def __init__( physical_qubits=physical_qubits, measurement_basis=measurement_basis, measurement_indices=measurement_indices, - measurement_qubits=measurement_qubits, basis_indices=basis_indices, analysis=analysis, ) diff --git a/qiskit_experiments/library/tomography/tomography_analysis.py b/qiskit_experiments/library/tomography/tomography_analysis.py index 8aa7f675f3..b598e5e6ca 100644 --- a/qiskit_experiments/library/tomography/tomography_analysis.py +++ b/qiskit_experiments/library/tomography/tomography_analysis.py @@ -80,6 +80,12 @@ def _default_options(cls) -> Options: rescale_trace (bool): If True rescale the state returned by the fitter have either trace 1 for :class:`~qiskit.quantum_info.DensityMatrix`, or trace dim for :class:`~qiskit.quantum_info.Choi` matrices (Default: True). + measurement_qubits (Sequence[int]): Optional, the physical qubits with tomographic + measurements. If not specified will be set to ``[0, ..., N-1]`` for N-qubit + tomographic measurements. + preparation_qubits (Sequence[int]): Optional, the physical qubits with tomographic + preparations. If not specified will be set to ``[0, ..., N-1]`` for N-qubit + tomographic preparations. target (Any): Optional, target object for fidelity comparison of the fit (Default: None). """ @@ -87,12 +93,12 @@ def _default_options(cls) -> Options: options.measurement_basis = None options.preparation_basis = None - options.measurement_qubits = None - options.preparation_qubits = None options.fitter = "linear_inversion" options.fitter_options = {} options.rescale_positive = True options.rescale_trace = True + options.measurement_qubits = None + options.preparation_qubits = None options.target = None return options diff --git a/qiskit_experiments/library/tomography/tomography_experiment.py b/qiskit_experiments/library/tomography/tomography_experiment.py index d9687000f6..c4ecade5cf 100644 --- a/qiskit_experiments/library/tomography/tomography_experiment.py +++ b/qiskit_experiments/library/tomography/tomography_experiment.py @@ -13,7 +13,6 @@ Quantum Tomography experiment """ -import warnings from typing import Union, Optional, Iterable, List, Tuple, Sequence from itertools import product from qiskit.circuit import QuantumCircuit, Instruction, ClassicalRegister @@ -53,7 +52,14 @@ def _default_experiment_options(cls) -> Options: return options - @deprecate_arguments({"qubits": "physical_qubits"}, "0.5") + @deprecate_arguments( + { + "qubits": "physical_qubits", + "measurement_qubits": "measurement_indices", + "preparation_qubits": "preparation_indices", + }, + "0.5", + ) def __init__( self, circuit: Union[QuantumCircuit, Instruction, BaseOperator], @@ -61,10 +67,8 @@ def __init__( physical_qubits: Optional[Sequence[int]] = None, measurement_basis: Optional[MeasurementBasis] = None, measurement_indices: Optional[Sequence[int]] = None, - measurement_qubits: Optional[Sequence[int]] = None, preparation_basis: Optional[PreparationBasis] = None, preparation_indices: Optional[Sequence[int]] = None, - preparation_qubits: Optional[Sequence[int]] = None, basis_indices: Optional[Iterable[Tuple[List[int], List[int]]]] = None, analysis: Union[BaseAnalysis, None, str] = "default", ): @@ -81,13 +85,11 @@ def __init__( measurement_indices: Optional, the `physical_qubits` indices to be measured as specified by the `measurement_basis`. If None all circuit physical qubits will be measured. - measurement_qubits: DEPRECATED, equivalent to measurement_indices. preparation_basis: Tomography basis for measurements. If set to None no tomography preparations will be performed. preparation_indices: Optional, the `physical_qubits` indices to be prepared as specified by the `preparation_basis`. If None all circuit physical qubits will be prepared. - preparation_qubits: DEPRECATED, equivalent to preparation_indices. basis_indices: Optional, the basis elements to be measured. If None All basis elements will be measured. analysis: Optional, a custom analysis instance to use. If ``"default"`` @@ -97,23 +99,6 @@ def __init__( Raises: QiskitError: if input params are invalid. """ - if measurement_qubits is not None: - measurement_indices = measurement_qubits - warnings.warn( - "The `measurement_qubits` kwarg has been renamed to `measurement_indices`." - " It will be removed in a future release.", - DeprecationWarning, - stacklevel=2, - ) - if preparation_qubits is not None: - preparation_indices = preparation_qubits - warnings.warn( - "The `preparation_qubits` kwarg has been renamed to `preparation_indices`." - " It will be removed in a future release.", - DeprecationWarning, - stacklevel=2, - ) - # Initialize BaseExperiment if physical_qubits is None: physical_qubits = tuple(range(circuit.num_qubits)) diff --git a/releasenotes/notes/tomography-b091ce13d6983bc1.yaml b/releasenotes/notes/tomography-b091ce13d6983bc1.yaml index a92af48c25..63d6280cda 100644 --- a/releasenotes/notes/tomography-b091ce13d6983bc1.yaml +++ b/releasenotes/notes/tomography-b091ce13d6983bc1.yaml @@ -28,6 +28,12 @@ features: The :class:`.LocalReadoutError` experiment can be run to obtain the :class:`.LocalReadoutMitigator` from its analysis results. + - | + Adds readout error mitigated tomography experiments + :class:`.MitigatedStateTomography` and :class:`.MitigatedProcessTomography`. + These are both implemented as a :class:`.BatchExperiment` consisting of a + :class:`~.LocalReadoutError` characterization experiment followed by either + a :class:`~.StateTomography` or class:`~.ProcessTomography` experiment. fixes: - | Fixes bug in :class:`~.StateTomography` and :class:`~.ProcessTomography` diff --git a/test/library/tomography/test_process_tomography.py b/test/library/tomography/test_process_tomography.py index a9f657dc3c..e1a21a5693 100644 --- a/test/library/tomography/test_process_tomography.py +++ b/test/library/tomography/test_process_tomography.py @@ -22,10 +22,16 @@ import qiskit.quantum_info as qi from qiskit_aer import AerSimulator from qiskit_aer.noise import NoiseModel -from qiskit_experiments.library import ProcessTomography -from qiskit_experiments.library.tomography import ProcessTomographyAnalysis, basis from qiskit_experiments.database_service import ExperimentEntryNotFound -from .tomo_utils import FITTERS, filter_results, teleport_circuit, teleport_bell_circuit +from qiskit_experiments.library import ProcessTomography, MitigatedProcessTomography +from qiskit_experiments.library.tomography import ProcessTomographyAnalysis, basis +from .tomo_utils import ( + FITTERS, + filter_results, + teleport_circuit, + teleport_bell_circuit, + readout_noise_model, +) @ddt.ddt @@ -441,3 +447,52 @@ def test_qpt_amat_pauli_basis(self): self.assertExperimentDone(expdata) fid = expdata.analysis_results("process_fidelity").value self.assertGreater(fid, 0.95) + + @ddt.data((0,), (1,), (2,), (3,), (0, 1), (2, 0), (0, 3)) + def test_mitigated_full_qpt_random_unitary(self, qubits): + """Test QPT experiment""" + seed = 1234 + shots = 5000 + f_threshold = 0.95 + + noise_model = readout_noise_model(4, seed=seed) + backend = AerSimulator(seed_simulator=seed, shots=shots, noise_model=noise_model) + target = qi.random_unitary(2 ** len(qubits), seed=seed) + exp = MitigatedProcessTomography(target, backend=backend) + exp.analysis.set_options(unmitigated_fit=True) + expdata = exp.run(analysis=None) + self.assertExperimentDone(expdata) + + for fitter in FITTERS: + with self.subTest(fitter=fitter, qubits=qubits): + if fitter: + exp.analysis.set_options(fitter=fitter) + fitdata = exp.analysis.run(expdata) + self.assertExperimentDone(fitdata) + # Should be 2 results, mitigated and unmitigated + states = expdata.analysis_results("state") + self.assertEqual(len(states), 2) + + # Check state is density matrix + for state in states: + self.assertTrue( + isinstance(state.value, qi.Choi), + msg=f"{fitter} fitted state is not density matrix for qubits {qubits}", + ) + + # Check fit state fidelity + fids = expdata.analysis_results("process_fidelity") + self.assertEqual(len(fids), 2) + mitfid, nomitfid = fids + # Check mitigation improves fidelity + self.assertTrue( + mitfid.value >= nomitfid.value, + msg="mitigated {} did not improve fidelity for qubits {} ({:.4f} < {:.4f})".format( + fitter, qubits, mitfid.value, nomitfid.value + ), + ) + self.assertGreater( + mitfid.value, + f_threshold, + msg=f"{fitter} fit fidelity is low for qubits {qubits}", + ) diff --git a/test/library/tomography/test_state_tomography.py b/test/library/tomography/test_state_tomography.py index 918462bbdc..782d632985 100644 --- a/test/library/tomography/test_state_tomography.py +++ b/test/library/tomography/test_state_tomography.py @@ -24,10 +24,16 @@ from qiskit_aer import AerSimulator from qiskit_aer.noise import NoiseModel -from qiskit_experiments.library import StateTomography -from qiskit_experiments.library.tomography import StateTomographyAnalysis, basis from qiskit_experiments.database_service import ExperimentEntryNotFound -from .tomo_utils import FITTERS, filter_results, teleport_circuit, teleport_bell_circuit +from qiskit_experiments.library import StateTomography, MitigatedStateTomography +from qiskit_experiments.library.tomography import StateTomographyAnalysis, basis +from .tomo_utils import ( + FITTERS, + filter_results, + teleport_circuit, + teleport_bell_circuit, + readout_noise_model, +) @ddt.ddt @@ -341,3 +347,50 @@ def test_qst_amat_pauli_basis(self): self.assertExperimentDone(expdata) fid = expdata.analysis_results("state_fidelity").value self.assertGreater(fid, 0.95) + + @ddt.data((0,), (1,), (2,), (3,), (0, 1), (2, 0), (0, 3), (0, 3, 1)) + def test_mitigated_full_qst(self, qubits): + """Test QST experiment""" + seed = 1234 + shots = 5000 + f_threshold = 0.95 + + noise_model = readout_noise_model(4, seed=seed) + backend = AerSimulator(seed_simulator=seed, shots=shots, noise_model=noise_model) + target = qi.random_statevector(2 ** len(qubits), seed=seed) + exp = MitigatedStateTomography(target, physical_qubits=qubits, backend=backend) + exp.analysis.set_options(unmitigated_fit=True) + expdata = exp.run(analysis=None) + self.assertExperimentDone(expdata) + + for fitter in FITTERS: + with self.subTest(fitter=fitter, qubits=qubits): + if fitter: + exp.analysis.set_options(fitter=fitter) + fitdata = exp.analysis.run(expdata) + self.assertExperimentDone(fitdata) + # Should be 2 results, mitigated and unmitigated + states = expdata.analysis_results("state") + self.assertEqual(len(states), 2) + for state in states: + self.assertTrue( + isinstance(state.value, qi.DensityMatrix), + msg=f"{fitter} fitted state is not density matrix for qubits {qubits}", + ) + + # Check fit state fidelity + fids = expdata.analysis_results("state_fidelity") + self.assertEqual(len(fids), 2) + mitfid, nomitfid = fids + # Check mitigation improves fidelity + self.assertTrue( + mitfid.value >= nomitfid.value, + msg="mitigated {} did not improve fidelity for qubits {} ({:.4f} < {:.4f})".format( + fitter, qubits, mitfid.value, nomitfid.value + ), + ) + self.assertGreater( + mitfid.value, + f_threshold, + msg=f"{fitter} fit fidelity is low for qubits {qubits}", + ) diff --git a/test/library/tomography/tomo_utils.py b/test/library/tomography/tomo_utils.py index 7cdfc5f892..2a1e75b9ec 100644 --- a/test/library/tomography/tomo_utils.py +++ b/test/library/tomography/tomo_utils.py @@ -13,7 +13,9 @@ """ Common methods for tomography tests """ +import numpy as np from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister +from qiskit_aer.noise import NoiseModel FITTERS = [ @@ -77,3 +79,15 @@ def teleport_bell_circuit(flatten_creg=True): teleport.z(2).c_if(creg[0], 1) teleport.x(2).c_if(creg[1], 1) return teleport + + +def readout_noise_model(num_qubits, seed=None): + """Generate noise model of random local readout errors""" + rng = np.random.default_rng(seed=seed) + p1g0s = 0.15 * rng.random(num_qubits) + p0g1s = 0.3 * rng.random(num_qubits) + amats = np.stack([[1 - p1g0s, p1g0s], [p0g1s, 1 - p0g1s]]).T + noise_model = NoiseModel() + for i, amat in enumerate(amats): + noise_model.add_readout_error(amat.T, [i]) + return noise_model