Skip to content

Commit

Permalink
Adopt qiskit.result.mitigation into `qiskit_experiments.data_proces…
Browse files Browse the repository at this point in the history
…sing` (backport #1484) (#1486)

The readout error mitigator classes `LocalReadoutMitigator` and
`CorrelatedReadoutMitigator` have been copied into
`qiskit_experiments.data_processing.mitigation` from
`qiskit.result.mitigation` in order to prepare for the deprecation of
these classes from Qiskit. The experiments that previously used the
Qiskit versions of these mitigator classes now use the Experiments
versions. The manual has also been updated to use the new class
locations.

Additionally, the readout mitigation manual was refactored to avoid
using private functions and methods. Some adjacent `jupyter-execute`
cells were merged because in the rendered docs they look like one cell
any way, but they still have individual "copy to clipboard" buttons
which is confusing. You think clicking the button will get the merged
cell contents but it only gets a section of it.<hr>This is an automatic
backport of pull request #1484 done by [Mergify](https://mergify.com).

Co-authored-by: Will Shanks <[email protected]>
  • Loading branch information
mergify[bot] and wshanks authored Nov 15, 2024
1 parent da8e0f7 commit d2b41d6
Show file tree
Hide file tree
Showing 14 changed files with 1,388 additions and 32 deletions.
47 changes: 20 additions & 27 deletions docs/manuals/measurement/readout_mitigation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,25 +47,16 @@ experiments to generate the corresponding mitigators.
import numpy as np
import matplotlib.pyplot as plt
from qiskit import QuantumCircuit
from qiskit.quantum_info import Operator
from qiskit.visualization import plot_distribution
from qiskit_experiments.data_processing import LocalReadoutMitigator
from qiskit_experiments.library import LocalReadoutError, CorrelatedReadoutError

from qiskit_aer import AerSimulator
from qiskit_ibm_runtime.fake_provider import FakePerth

from qiskit.result.mitigation.utils import (
expval_with_stddev,
str2diag,
counts_probability_vector
)

backend = AerSimulator.from_backend(FakePerth())

.. jupyter-execute::

shots = 1024
qubits = [0,1,2,3]
num_qubits = len(qubits)

Standard mitigation experiment
------------------------------
Expand All @@ -76,13 +67,14 @@ circuits, one for all “0” and one for all “1” results.

.. jupyter-execute::

shots = 1024
qubits = [0,1,2,3]
num_qubits = len(qubits)

exp = LocalReadoutError(qubits)
for c in exp.circuits():
print(c)


.. jupyter-execute::

exp.analysis.set_options(plot=True)
result = exp.run(backend)
mitigator = result.analysis_results("Local Readout Mitigator").value
Expand All @@ -102,9 +94,9 @@ The individual mitigation matrices can be read off the mitigator.

.. jupyter-execute::

for m in mitigator._mitigation_mats:
print(m)
print()
for qubit in mitigator.qubits:
print(f"Qubit: {qubit}")
print(mitigator.mitigation_matrix(qubits=qubit))


Mitigation example
Expand All @@ -118,13 +110,9 @@ Mitigation example
qc.cx(i - 1, i)
qc.measure_all()

.. jupyter-execute::

counts = backend.run(qc, shots=shots, seed_simulator=42, method="density_matrix").result().get_counts()
unmitigated_probs = {label: count / shots for label, count in counts.items()}

.. jupyter-execute::

mitigated_quasi_probs = mitigator.quasi_probabilities(counts)
mitigated_stddev = mitigated_quasi_probs._stddev_upper_bound
mitigated_probs = (mitigated_quasi_probs.nearest_probability_distribution().binary_probabilities())
Expand All @@ -144,15 +132,20 @@ Expectation value
.. jupyter-execute::

diagonal_labels = ["ZZZZ", "ZIZI", "IZII", "1ZZ0"]
ideal_expectation = []
diagonals = [str2diag(d) for d in diagonal_labels]
diagonals = [
np.diag(np.real(Operator.from_label(d).to_matrix()))
for d in diagonal_labels
]

# Create a mitigator with no mitigation so that we can use its
# expectation_values method to generate an unmitigated expectation value to
# compare to the mitigated one.
identity_mitigator = LocalReadoutMitigator([np.eye(2) for _ in range(4)])

qubit_index = {i: i for i in range(num_qubits)}
unmitigated_probs_vector, _ = counts_probability_vector(unmitigated_probs, qubit_index=qubit_index)
unmitigated_expectation = [expval_with_stddev(d, unmitigated_probs_vector, shots) for d in diagonals]
unmitigated_expectation = [identity_mitigator.expectation_value(counts, d) for d in diagonals]
mitigated_expectation = [mitigator.expectation_value(counts, d) for d in diagonals]

.. jupyter-execute::

mitigated_expectation_values, mitigated_stddev = zip(*mitigated_expectation)
unmitigated_expectation_values, unmitigated_stddev = zip(*unmitigated_expectation)
legend = ['Mitigated Expectation', 'Unmitigated Expectation']
Expand Down
12 changes: 12 additions & 0 deletions qiskit_experiments/data_processing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@
BaseDiscriminator
SkLDA
SkQDA
Mitigators
==========
.. autosummary::
:toctree: ../stubs/
BaseReadoutMitigator
LocalReadoutMitigator
CorrelatedReadoutMitigator
"""

from .data_action import DataAction, TrainableDataAction
Expand All @@ -104,4 +113,7 @@

from .data_processor import DataProcessor
from .discriminator import BaseDiscriminator
from .mitigation.base_readout_mitigator import BaseReadoutMitigator
from .mitigation.correlated_readout_mitigator import CorrelatedReadoutMitigator
from .mitigation.local_readout_mitigator import LocalReadoutMitigator
from .sklearn_discriminators import SkLDA, SkQDA
22 changes: 22 additions & 0 deletions qiskit_experiments/data_processing/mitigation/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 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 error mitigation."""
from .base_readout_mitigator import BaseReadoutMitigator
from .correlated_readout_mitigator import CorrelatedReadoutMitigator
from .local_readout_mitigator import LocalReadoutMitigator
from .utils import (
counts_probability_vector,
expval_with_stddev,
stddev,
str2diag,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# 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.
"""
Base class for readout error mitigation.
"""

from abc import ABC, abstractmethod
from typing import Optional, List, Iterable, Tuple, Union, Callable

import numpy as np

from qiskit.result.counts import Counts
from qiskit.result.distributions.quasi import QuasiDistribution


class BaseReadoutMitigator(ABC):
"""Base readout error mitigator class."""

@abstractmethod
def quasi_probabilities(
self,
data: Counts,
qubits: Iterable[int] = None,
clbits: Optional[List[int]] = None,
shots: Optional[int] = None,
) -> QuasiDistribution:
"""Convert counts to a dictionary of quasi-probabilities
Args:
data: Counts to be mitigated.
qubits: the physical qubits measured to obtain the counts clbits.
If None these are assumed to be qubits [0, ..., N-1]
for N-bit counts.
clbits: Optional, marginalize counts to just these bits.
shots: Optional, the total number of shots, if None shots will
be calculated as the sum of all counts.
Returns:
QuasiDistribution: A dictionary containing pairs of [output, mean] where "output"
is the key in the dictionaries,
which is the length-N bitstring of a measured standard basis state,
and "mean" is the mean of non-zero quasi-probability estimates.
"""

@abstractmethod
def expectation_value(
self,
data: Counts,
diagonal: Union[Callable, dict, str, np.ndarray],
qubits: Iterable[int] = None,
clbits: Optional[List[int]] = None,
shots: Optional[int] = None,
) -> Tuple[float, float]:
"""Calculate the expectation value of a diagonal Hermitian operator.
Args:
data: Counts object to be mitigated.
diagonal: the diagonal operator. This may either be specified
as a string containing I,Z,0,1 characters, or as a
real valued 1D array_like object supplying the full diagonal,
or as a dictionary, or as Callable.
qubits: the physical qubits measured to obtain the counts clbits.
If None these are assumed to be qubits [0, ..., N-1]
for N-bit counts.
clbits: Optional, marginalize counts to just these bits.
shots: Optional, the total number of shots, if None shots will
be calculated as the sum of all counts.
Returns:
The mean and an upper bound of the standard deviation of operator
expectation value calculated from the current counts.
"""
Loading

0 comments on commit d2b41d6

Please sign in to comment.