What happens when I use TREX?#

The workflow of TREX in Mitiq consists of the following steps:

  1. The user provides a QPROGRAM (quantum circuit), an executor returning MeasurementResult, and an Observable.

  2. For each commuting group in the observable, Mitiq creates measurement circuits with appropriate basis rotations.

  3. For each randomization pattern, Mitiq inserts random X gates before measurement (readout twirling) and creates corresponding calibration circuits.

  4. All twirled and calibration circuits are executed via the executor.

  5. The measurement results are classically post-processed: bits are XOR’d with the randomization pattern to undo the X flips.

  6. For each Pauli string, the noisy expectation is divided by the calibration factor to correct readout errors.

  7. The corrected expectation value is returned to the user.

As shown in How do I use TREX?, the function execute_with_trex() applies TREX behind the scenes and directly returns the error-mitigated expectation value. In the next sections, we show how one can apply TREX at a lower level using mitiq.experimental.trex.trex.construct_circuits() and mitiq.experimental.trex.trex.combine_results().

Constructing twirled circuits#

from functools import partial

import numpy as np
from cirq import LineQubit, Circuit, X
from cirq.experiments.single_qubit_readout_calibration_test import (
    NoisySingleQubitReadoutSampler,
)

from mitiq import MeasurementResult
from mitiq.observable.observable import Observable
from mitiq.observable.pauli import PauliString
from mitiq.experimental.trex import construct_circuits, combine_results

qreg = [LineQubit(i) for i in range(2)]
circuit = Circuit(X.on_each(*qreg))
observable = Observable(PauliString("ZI"), PauliString("IZ"))

twirled_circuits, calibration_circuits, randomization_strings = \
    construct_circuits(circuit, observable, num_randomizations=8, random_state=42)

print(f"Number of twirled circuits: {len(twirled_circuits)}")
print(f"Number of calibration circuits: {len(calibration_circuits)}")
print(f"Number of randomization strings: {len(randomization_strings)}")
Number of twirled circuits: 8
Number of calibration circuits: 8
Number of randomization strings: 8

Let’s look at an example twirled circuit:

print("Original circuit:")
print(circuit)
print("\nTwirled measurement circuit (first randomization):")
print(twirled_circuits[0])
print("\nCalibration circuit (first randomization):")
print(calibration_circuits[0])
Original circuit:
0: ───X───

1: ───X───

Twirled measurement circuit (first randomization):
0: ───X───────M───
              │
1: ───X───X───M───

Calibration circuit (first randomization):
0: ───────M───
          │
1: ───X───M───

Executing and combining results#

def noisy_readout_executor(circuit, p0, p1, shots=8192) -> MeasurementResult:
    simulator = NoisySingleQubitReadoutSampler(p0, p1)
    result = simulator.run(circuit, repetitions=shots)
    bitstrings = np.column_stack(list(result.measurements.values()))
    return MeasurementResult(bitstrings, qubit_indices=(0, 1))

noisy_executor = partial(noisy_readout_executor, p0=0.15, p1=0.15)

# Execute all circuits
all_circuits = twirled_circuits + calibration_circuits
all_results = [noisy_executor(c) for c in all_circuits]

n_twirled = len(twirled_circuits)
twirled_results = all_results[:n_twirled]
calibration_results = all_results[n_twirled:]

# Combine results with TREX correction
corrected_value = combine_results(
    twirled_results, calibration_results,
    randomization_strings, observable,
)
print(f"TREX-corrected expectation value: {corrected_value:.4f}")
print("Ideal value: -2.0")
TREX-corrected expectation value: -2.0043
Ideal value: -2.0

Using mitigate_executor and trex_decorator#

TREX can also be applied using the executor wrapper pattern:

from mitiq.experimental.trex import mitigate_executor

mitigated = mitigate_executor(
    noisy_executor, observable,
    num_randomizations=16, random_state=42,
)
result = mitigated(circuit)
print(f"Mitigated result: {result:.4f}")
Mitigated result: -2.0037

Or with the decorator pattern:

from mitiq.experimental.trex import trex_decorator

@trex_decorator(observable, num_randomizations=16, random_state=42)
def my_executor(circuit) -> MeasurementResult:
    return noisy_executor(circuit)

result = my_executor(circuit)
print(f"Decorated result: {result:.4f}")
Decorated result: -1.9984