Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tensored fitter #8345

Merged
merged 15 commits into from
Jul 15, 2022
51 changes: 50 additions & 1 deletion qiskit/utils/mitigation/fitters.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def add_data(self, new_results, rebuild_cal_matrix=True):

self._tens_fitt.add_data(new_results, rebuild_cal_matrix)

def subset_fitter(self, qubit_sublist=None):
def subset_fitter(self, qubit_sublist):
"""
Return a fitter object that is a subset of the qubits in the original
list.
Expand Down Expand Up @@ -431,3 +431,52 @@ def _build_calibration_matrices(self):
out=np.zeros_like(self._cal_matrices[mat_index]),
where=sums_of_columns != 0,
)

def subset_fitter(self, qubit_sublist):
"""Return a fitter object that is a subset of the qubits in the original list.

This is only a partial implementation of the ``subset_fitter`` method since only
mitigation patterns of length 1 are supported. This corresponds to patterns of the
form ``[[0], [1], [2], ...]``. Note however, that such patterns are a good first
approximation to mitigate readout errors on large quantum circuits.

Args:
qubit_sublist (list): must be a subset of qubit_list

Returns:
TensoredMeasFitter: A new fitter that has the calibration for a
subset of qubits

Raises:
QiskitError: If the calibration matrix is not initialized
QiskitError: If the mit pattern is not a tensor of single-qubit
measurement error mitigation.
QiskitError: If a qubit in the given ``qubit_sublist`` is not in the list of
qubits in the mit. pattern.
"""
if self._cal_matrices is None:
raise QiskitError("Calibration matrices are not initialized.")

if qubit_sublist is None:
raise QiskitError("Qubit sublist must be specified.")

if not all(len(tensor) == 1 for tensor in self._mit_pattern):
raise QiskitError(
f"Each element in the mit pattern should have length 1. Found {self._mit_pattern}."
)

supported_qubits = set(tensor[0] for tensor in self._mit_pattern)
for qubit in qubit_sublist:
if qubit not in supported_qubits:
raise QiskitError(f"Qubit {qubit} is not in the mit pattern {self._mit_pattern}.")

new_mit_pattern = [[idx] for idx in qubit_sublist]
new_substate_labels_list = [self._substate_labels_list[idx] for idx in qubit_sublist]

new_fitter = TensoredMeasFitter(
results=None, mit_pattern=new_mit_pattern, substate_labels_list=new_substate_labels_list
)

new_fitter.cal_matrices = [self._cal_matrices[idx] for idx in qubit_sublist]

return new_fitter
4 changes: 4 additions & 0 deletions qiskit/utils/quantum_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,10 @@ def _find_save_state(data):
tmp_result.results = [result.results[i] for i in c_idx]
if curr_qubit_index == qubit_index:
tmp_fitter = meas_error_mitigation_fitter
elif isinstance(meas_error_mitigation_fitter, TensoredMeasFitter):
# Different from the complete meas. fitter as only the Terra fitter
# implements the ``subset_fitter`` method.
tmp_fitter = meas_error_mitigation_fitter.subset_fitter(curr_qubit_index)
elif _MeasFitterType.COMPLETE_MEAS_FITTER == _MeasFitterType.type_from_instance(
meas_error_mitigation_fitter
):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
features:
- |
The ``subset_fitter`` method is added to the :class:`.TensoredMeasFitter`
class. The implementation is restricted to mitigation patterns in which each
qubit is mitigated individually, e.g. ``[[0], [1], [2]]``. This is, however,
the most widely used case. It allows the :class:`.TensoredMeasFitter` to
be used in cases where the numberical order of the physical qubits does not
match the index of the classical bit.
87 changes: 80 additions & 7 deletions test/python/algorithms/test_measure_error_mitigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
import unittest

from test.python.algorithms import QiskitAlgorithmsTestCase
from ddt import ddt, data
from ddt import ddt, data, unpack
import numpy as np
import retworkx as rx
from qiskit import QuantumCircuit
from qiskit import QuantumCircuit, execute
from qiskit.quantum_info import Pauli
from qiskit.exceptions import QiskitError
from qiskit.utils import QuantumInstance, algorithm_globals
Expand All @@ -27,6 +27,7 @@
from qiskit.algorithms.optimizers import SPSA, COBYLA
from qiskit.circuit.library import EfficientSU2
from qiskit.utils.mitigation import CompleteMeasFitter, TensoredMeasFitter
from qiskit.utils.measurement_error_mitigation import build_measurement_error_mitigation_circuits
from qiskit.utils import optionals

if optionals.HAS_AER:
Expand All @@ -46,8 +47,19 @@ class TestMeasurementErrorMitigation(QiskitAlgorithmsTestCase):
"""Test measurement error mitigation."""

@unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required for this test")
@data("CompleteMeasFitter", "TensoredMeasFitter")
def test_measurement_error_mitigation_with_diff_qubit_order(self, fitter_str):
@data(
("CompleteMeasFitter", None, False),
("TensoredMeasFitter", None, False),
("TensoredMeasFitter", [[0, 1]], True),
("TensoredMeasFitter", [[1], [0]], False),
)
@unpack
def test_measurement_error_mitigation_with_diff_qubit_order(
self,
fitter_str,
mit_pattern,
fails,
):
"""measurement error mitigation with different qubit order"""
algorithm_globals.random_seed = 0

Expand All @@ -68,6 +80,7 @@ def test_measurement_error_mitigation_with_diff_qubit_order(self, fitter_str):
noise_model=noise_model,
measurement_error_mitigation_cls=fitter_cls,
cals_matrix_refresh_period=0,
mit_pattern=mit_pattern,
)
# circuit
qc1 = QuantumCircuit(2, 2)
Expand All @@ -81,15 +94,14 @@ def test_measurement_error_mitigation_with_diff_qubit_order(self, fitter_str):
qc2.measure(1, 0)
qc2.measure(0, 1)

if fitter_cls == TensoredMeasFitter:
if fails:
self.assertRaisesRegex(
QiskitError,
"TensoredMeasFitter doesn't support subset_fitter.",
"Each element in the mit pattern should have length 1.",
quantum_instance.execute,
[qc1, qc2],
)
else:
# this should run smoothly
quantum_instance.execute([qc1, qc2])

self.assertGreater(quantum_instance.time_taken, 0.0)
Expand Down Expand Up @@ -387,6 +399,67 @@ def test_circuit_modified(self):
_ = qi.execute(circuits_input, had_transpiled=True)
self.assertEqual(circuits_ref, circuits_input, msg="Transpiled circuit array modified.")

@unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required for this test")
def test_tensor_subset_fitter(self):
"""Test the subset fitter method of the tensor fitter."""

# Construct a noise model where readout has errors of different strengths.
noise_model = noise.NoiseModel()
# big error
read_err0 = noise.errors.readout_error.ReadoutError([[0.90, 0.10], [0.25, 0.75]])
# ideal
read_err1 = noise.errors.readout_error.ReadoutError([[1.00, 0.00], [0.00, 1.00]])
# small error
read_err2 = noise.errors.readout_error.ReadoutError([[0.98, 0.02], [0.03, 0.97]])
noise_model.add_readout_error(read_err0, (0,))
noise_model.add_readout_error(read_err1, (1,))
noise_model.add_readout_error(read_err2, (2,))

mit_pattern = [[idx] for idx in range(3)]
backend = Aer.get_backend("aer_simulator")
backend.set_options(seed_simulator=123)
mit_circuits = build_measurement_error_mitigation_circuits(
[0, 1, 2],
TensoredMeasFitter,
backend,
backend_config={},
compile_config={},
mit_pattern=mit_pattern,
)
result = execute(mit_circuits[0], backend, noise_model=noise_model).result()
fitter = TensoredMeasFitter(result, mit_pattern=mit_pattern)
cal_matrices = fitter.cal_matrices

# Check that permutations and permuted subsets match.
for subset in [[1, 0], [1, 2], [0, 2], [2, 0, 1]]:
with self.subTest(subset=subset):
new_fitter = fitter.subset_fitter(subset)
for idx, qubit in enumerate(subset):
self.assertTrue(np.allclose(new_fitter.cal_matrices[idx], cal_matrices[qubit]))

self.assertRaisesRegex(
QiskitError,
"Qubit 3 is not in the mit pattern",
fitter.subset_fitter,
[0, 2, 3],
)

# Test that we properly correct a circuit with permuted measurements.
circuit = QuantumCircuit(3, 3)
circuit.x(range(3))
circuit.measure(1, 0)
circuit.measure(2, 1)
circuit.measure(0, 2)

result = execute(
circuit, backend, noise_model=noise_model, shots=1000, seed_simulator=0
).result()
new_result = fitter.subset_fitter([1, 2, 0]).filter.apply(result)

# The noisy result should have a poor 111 state, the mit. result should be good.
self.assertTrue(result.get_counts()["111"] < 800)
self.assertTrue(new_result.get_counts()["111"] > 990)


if __name__ == "__main__":
unittest.main()