iqm.error_reduction_tools.rem.rem_processors.ReadoutErrorMitigation#

class iqm.error_reduction_tools.rem.rem_processors.ReadoutErrorMitigation(readout_errors, max_entropy=28.0)#

Bases: object

Apply readout error mitigation to measurement counts, with optional twirling support.

This class performs tensor-product inversion of readout confusion matrices to correct measurement errors. Supports both standard and symmetrized (twirled) characterization data.

Methods

_mitigate(counts, cl_index_to_qubit_name[, ...])

Apply readout error mitigation to the given counts.

_normalize_observables(observables, ...)

Normalize observables to the canonical qubit-name list format.

_symmetrize_copy(charact_data)

Return a symmetrized copy of characterization data without modifying the original.

_validate_charact_data()

Validate the characterization data format.

_validate_counts_and_mapping(counts, ...)

Validate the counts format and qubit mapping.

_warn_high_error_rates(charact_data)

Emit warnings for qubits whose readout error rate is >= 20%.

from_client(client[, qubits, ...])

Create ReadoutErrorMitigation by running characterization on a client.

get_calibration_data()

Get the calibration data for all qubits.

mitigate_counts(experiment_counts, ...[, ...])

Mitigate a list of measurement count dictionaries for one or more observables.

symmetrize_charact_data()

Symmetrize the characterization data by averaging error rates.

Parameters:
classmethod from_client(client, qubits=None, number_of_circuits=100, shots=10000, symmetrize=True, seed=None, equatorial_randomization=True, max_entropy=28.0)#

Create ReadoutErrorMitigation by running characterization on a client.

Runs the full characterization workflow on the quantum computer and then returns an initialized ReadoutErrorMitigation instance.

Parameters:
  • client (Pulla) – Client for connecting to an IQM quantum computer.

  • qubits (list[str] | None) – List of qubit names to characterize (e.g., [“QB1”, “QB2”]). If None, characterizes all qubits available on the client.

  • number_of_circuits (int) – Number of calibration circuits to generate.

  • shots (int) – Total shots for characterization, distributed across all circuits.

  • symmetrize (bool) – If True, generates complementary I/X preparation pairs.

  • seed (int | None) – Random seed for reproducible characterization.

  • equatorial_randomization (bool) – If True, randomizes X gate phases.

  • max_entropy (float) – Maximum allowed Shannon entropy (in bits) threshold.

Returns:

ReadoutErrorMitigation instance with characterization data.

Return type:

ReadoutErrorMitigation

Example

>>> from iqm.pulla import Pulla
>>> client = Pulla(url="https://example.iqm.fi")
>>> mitigator = ReadoutErrorMitigation.from_client(
...     client=client,
...     qubits=["QB1", "QB2"],
...     number_of_circuits=30,
...     shots=10000,
... )
>>> mitigated = mitigator.mitigate(counts, cl_index_to_qubit_name=["QB1", "QB2"])
get_calibration_data()#

Get the calibration data for all qubits.

Returns:

Dictionary mapping qubit names to their calibration matrices (2x2 numpy arrays).

Return type:

dict[str, ndarray]

symmetrize_charact_data()#

Symmetrize the characterization data by averaging error rates.

Converts matrices to symmetrized scalar error rates by averaging P(0|1) and P(1|0). If data is already scalar, this method has no effect.

Return type:

None

mitigate_counts(experiment_counts, qubit_to_bit_mapping, observables=None, twirled=False, force_mitigation=False, nearest_probability=True, client=None, auto_characterize_shots=10000, auto_characterize_circuits=20)#

Mitigate a list of measurement count dictionaries for one or more observables.

Bitstrings and bit indices follow little-endian (Qiskit) convention: bit index 0 is the rightmost character of the bitstring.

Parameters:
  • experiment_counts (list[dict[str, int]]) – Measurement count dictionaries. Each dict maps bitstrings (e.g., "0101") to shot counts. Bitstrings follow little-endian convention (rightmost character = bit index 0).

  • qubit_to_bit_mapping (dict[str, int] | TwirledCircuit | list[TwirledCircuit]) –

    A property of the transpiled circuit: it maps every measured qubit name to its classical bit index and is fixed once the circuit has been run.

    Accepts multiple formats (auto-detected):

    • dict (canonical): {"QB5": 0, "QB12": 1, "QB3": 2, "QB7": 3}

    • TwirledCircuit: extracts mapping from measured_qubits

    • list[TwirledCircuit]: extracts from the first circuit

  • observables (list[list[str] | str | list[int]] | None) –

    Observables to mitigate and compute ZZ…Z expectation values for. observables is a list of qubit-name subsets; for each subset the raw counts are first marginalized to the corresponding bit indices (looked up from qubit_to_bit_mapping), then readout-error-mitigated, and finally a ZZ…Z expectation value is computed.

    Supports three formats per element (auto-detected):

    • Qubit names (current): [["QB3", "QB5"], ["QB1"]]

    • Pauli strings (Z/I only): ["IIIZZIZI", "ZIIIIIIII"]

    • Bit indices: [[2, 4], [0]]

    If None, a single observable covering all qubits in qubit_to_bit_mapping is used. Formats can be mixed within a single call.

  • twirled (bool) – If True, symmetrize the characterization data before mitigation by averaging P(0|1) and P(1|0) into a single error rate per qubit. This is appropriate when the counts were obtained using readout twirling. The original charact_data on the instance is not modified; a temporary symmetrized copy is used. Default is False.

  • force_mitigation (bool) – If True, apply mitigation even if complexity exceeds threshold.

  • nearest_probability (bool) – If True, project each quasi-probability distribution onto the nearest valid probability distribution using mthree’s nearest_probability_distribution method. Eliminates negative values at the cost of a small bias. Default is True.

  • client (Pulla | None) – Optional client for automatic characterization of missing qubits on the quantum computer.

  • auto_characterize_shots (int) – Shots to use when auto-characterizing (default: 10000).

  • auto_characterize_circuits (int) – Number of circuits for auto-characterization (default: 20).

Returns:

  • "mitigated_counts": list[list[dict[str, float]]]result["mitigated_counts"][i][j] is the mitigated quasi-probability distribution for circuit i under observable j.

  • "expectation_values": list[list[float]]result["expectation_values"][i][j] is the ZZ…Z expectation value for circuit i under observable j.

  • "characterization_performed": bool — True if auto-characterization ran.

Return type:

Dictionary containing

Raises:

ValueError – If experiment_counts or qubit_to_bit_mapping are empty, if any observable qubit is not in qubit_to_bit_mapping, or characterization data is missing and no client is provided.

Example — full-distribution mitigation:

>>> mitigator = ReadoutErrorMitigation(charact_data)
>>> result = mitigator.mitigate_counts(
...     experiment_counts=[{"00": 450, "11": 450}],
...     qubit_to_bit_mapping={"QB1": 0, "QB2": 1},
... )
>>> result["mitigated_counts"][0][0]   # circuit 0, observable 0
>>> result["expectation_values"][0][0]  # <ZZ> for circuit 0

Example — multiple observables in one call:

>>> result = mitigator.mitigate_counts(
...     experiment_counts=[{"0000": 450, "1111": 450}],
...     qubit_to_bit_mapping={"QB1": 0, "QB2": 1, "QB3": 2, "QB4": 3},
...     observables=[
...         ["QB1", "QB2", "QB3", "QB4"],  # full ZZZZ
...         ["QB1", "QB2"],                 # ZZ on first two qubits
...         ["QB3", "QB4"],                 # ZZ on last two qubits
...     ],
... )
>>> result["expectation_values"][0]  # [<ZZZZ>, <Z0Z1>, <Z2Z3>]

Example — auto-characterization:

>>> mitigator = ReadoutErrorMitigation(charact_data={})
>>> result = mitigator.mitigate_counts(
...     experiment_counts=counts_list,
...     qubit_to_bit_mapping={"QB1": 0, "QB2": 1},
...     client=client,
... )

Example — twirled mitigation (same instance reusable for both):

>>> rem = ReadoutErrorMitigation(charact_data=probs["charact_data"])
>>> result_twirled = rem.mitigate_counts(
...     [twirled_counts], mapping, twirled=True,
... )
>>> result_standard = rem.mitigate_counts(
...     [standard_counts], mapping, twirled=False,
... )

Example — Pauli-string observables (natural for Qiskit users):

>>> result = rem.mitigate_counts(
...     experiment_counts=[counts],
...     qubit_to_bit_mapping={"QB1": 0, "QB2": 1, "QB3": 2, "QB4": 3},
...     observables=["IZIZ", "ZZII"],
...     twirled=True,
... )

Example — bit-index observables (framework-agnostic):

>>> result = rem.mitigate_counts(
...     experiment_counts=[counts],
...     qubit_to_bit_mapping={"QB1": 0, "QB2": 1, "QB3": 2, "QB4": 3},
...     observables=[[0, 2], [2, 3]],
... )

Example — TwirledCircuit as mapping:

>>> result = rem.mitigate_counts(
...     experiment_counts=[counts],
...     qubit_to_bit_mapping=twirled_circuit,
...     twirled=True,
... )