mitigation

package
v1.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 17, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package mitigation provides quantum error mitigation techniques for NISQ devices.

Error mitigation improves expectation value estimates from noisy quantum hardware without the overhead of full quantum error correction.

Zero-Noise Extrapolation (ZNE)

ZNE intentionally amplifies circuit noise at several scale factors, then extrapolates back to the zero-noise limit. The workflow is:

  1. Fold the circuit at each scale factor (FoldCircuit)
  2. Execute each folded circuit via an Executor
  3. Extrapolate the results to zero noise (Extrapolate)

RunZNE orchestrates these steps:

result, err := mitigation.RunZNE(ctx, mitigation.ZNEConfig{
    Circuit:      circ,
    Executor:     mitigation.DensityMatrixExecutor(hamiltonian, noiseModel),
    ScaleFactors: []float64{1, 3, 5},
    Extrapolator: mitigation.LinearExtrapolator,
})

Measurement Error Mitigation

Measurement (readout) error mitigation calibrates the classical confusion matrix of the measurement apparatus and inverts it to correct raw counts or probabilities:

cal, err := mitigation.CalibrateReadout(ctx, numQubits, shots, basisExec)
corrected := cal.CorrectCounts(rawCounts)

Pauli Twirling

Pauli twirling converts coherent errors into stochastic Pauli errors by inserting random Pauli gates around 2-qubit gates (CNOT and CZ). This is a prerequisite for PEC and improves ZNE accuracy:

result, err := mitigation.RunTwirl(ctx, mitigation.TwirlConfig{
    Circuit:  circ,
    Executor: noisyExec,
    Samples:  100,
})

Digital Dynamical Decoupling (DD)

DD inserts identity-equivalent pulse sequences (XX or XY4) into idle qubit periods. This is a pure circuit transform — no executor or noise model needed:

ddCirc, err := mitigation.InsertDD(mitigation.DDConfig{
    Circuit:  circ,
    Sequence: mitigation.DDXX,
})

Probabilistic Error Cancellation (PEC)

PEC provides unbiased estimation via quasi-probability sampling over Pauli corrections. Requires a depolarizing noise model:

result, err := mitigation.RunPEC(ctx, mitigation.PECConfig{
    Circuit:    circ,
    Executor:   noisyExec,
    NoiseModel: nm,
    Samples:    1000,
})

Clifford Data Regression (CDR)

CDR generates near-Clifford training circuits, runs noisy and ideal simulations, fits an affine correction model, and applies it to the original noisy result. No noise model is needed:

result, err := mitigation.RunCDR(ctx, mitigation.CDRConfig{
    Circuit:     circ,
    Executor:    noisyExec,
    Hamiltonian: hamiltonian,
    NumTraining: 20,
})

Twirled Readout Error eXtinction (TREX)

TREX mitigates readout errors with O(n) overhead by inserting random X gates before measurements and classically undoing the bit flips:

result, err := mitigation.RunTREX(ctx, mitigation.TREXConfig{
    Circuit: circ,
    Runner:  shotRunner,
    Shots:   1000,
    Samples: 10,
})

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Extrapolate

func Extrapolate(scaleFactors, values []float64, method Extrapolator) (float64, error)

Extrapolate estimates the zero-noise value from (scaleFactors, values) pairs. Both slices must have the same length (≥2).

func FoldCircuit

func FoldCircuit(circuit *ir.Circuit, scaleFactor int, method ScaleMethod) (*ir.Circuit, error)

FoldCircuit returns a new circuit with noise scaled by scaleFactor. scaleFactor must be a positive odd integer (1, 3, 5, ...). A scaleFactor of 1 returns an unmodified copy.

func InsertDD

func InsertDD(cfg DDConfig) (*ir.Circuit, error)

InsertDD returns a new circuit with dynamical decoupling sequences inserted into idle qubit periods. This is a pure circuit transform — no executor or noise model is needed.

Example
package main

import (
	"fmt"

	"github.com/splch/goqu/algorithm/mitigation"
	"github.com/splch/goqu/circuit/builder"
)

func main() {
	// Build a circuit with idle qubit periods.
	circ, err := builder.New("dd_example", 3).
		H(0).
		X(0).
		H(0).
		X(0).
		H(0).
		CNOT(0, 1).
		CNOT(0, 2).
		Build()
	if err != nil {
		panic(err)
	}

	// Insert XX dynamical decoupling.
	ddCirc, err := mitigation.InsertDD(mitigation.DDConfig{
		Circuit:  circ,
		Sequence: mitigation.DDXX,
	})
	if err != nil {
		panic(err)
	}

	fmt.Printf("original gates: %d\n", len(circ.Ops()))
	fmt.Printf("DD gates: %d\n", len(ddCirc.Ops()))
	fmt.Printf("DD added %d pulses\n", len(ddCirc.Ops())-len(circ.Ops()))
}

func TwirlCircuit

func TwirlCircuit(circuit *ir.Circuit, rng *rand.Rand) (*ir.Circuit, error)

TwirlCircuit returns a new circuit with random Pauli gates inserted around each CNOT and CZ gate. Returns an error if unsupported 2-qubit gates are encountered.

Types

type BasisExecutor

type BasisExecutor func(ctx context.Context, basisState int, shots int) (map[string]int, error)

BasisExecutor prepares a computational basis state and measures it. basisState is the integer index of the basis state to prepare (0 to 2^n - 1). shots is the number of measurement repetitions. Returns a map from bitstring to count.

type CDRConfig

type CDRConfig struct {
	// Circuit is the quantum circuit to mitigate.
	Circuit *ir.Circuit
	// Executor is the noisy executor.
	Executor Executor
	// Hamiltonian is the observable for ideal expectation computation.
	Hamiltonian pauli.PauliSum
	// NumTraining is the number of near-Clifford training circuits. Default: 20.
	NumTraining int
	// Fraction is the fraction of non-Clifford gates to replace. Default: 0.75.
	Fraction float64
}

CDRConfig specifies parameters for Clifford Data Regression.

type CDRResult

type CDRResult struct {
	// MitigatedValue is the corrected expectation value.
	MitigatedValue float64
	// TrainingNoisy are the noisy expectation values for training circuits.
	TrainingNoisy []float64
	// TrainingIdeal are the ideal expectation values for training circuits.
	TrainingIdeal []float64
	// FitA is the slope of the affine fit y = a·x + b.
	FitA float64
	// FitB is the intercept of the affine fit.
	FitB float64
}

CDRResult holds the output of Clifford Data Regression.

func RunCDR

func RunCDR(ctx context.Context, cfg CDRConfig) (*CDRResult, error)

RunCDR performs Clifford Data Regression.

It generates near-Clifford training circuits by replacing a fraction of non-Clifford gates with their nearest Clifford equivalents, runs both noisy and ideal simulations on these training circuits, fits an affine correction model, and applies it to the original noisy result.

Example
package main

import (
	"context"
	"fmt"

	"github.com/splch/goqu/algorithm/mitigation"
	"github.com/splch/goqu/circuit/gate"
	"github.com/splch/goqu/circuit/ir"
	"github.com/splch/goqu/sim/noise"
	"github.com/splch/goqu/sim/pauli"
)

func main() {
	// Circuit with non-Clifford T gates.
	ops := []ir.Operation{
		{Gate: gate.H, Qubits: []int{0}},
		{Gate: gate.T, Qubits: []int{0}},
		{Gate: gate.CNOT, Qubits: []int{0, 1}},
		{Gate: gate.T, Qubits: []int{1}},
	}
	circ := ir.New("t_circuit", 2, 0, ops, nil)

	// Observable: ZZ.
	zz := pauli.NewPauliString(1, map[int]pauli.Pauli{0: pauli.Z, 1: pauli.Z}, 2)
	ham, err := pauli.NewPauliSum([]pauli.PauliString{zz})
	if err != nil {
		panic(err)
	}

	// Noisy executor.
	nm := noise.New()
	nm.AddDefaultError(1, noise.Depolarizing1Q(0.02))
	nm.AddDefaultError(2, noise.Depolarizing2Q(0.04))
	exec := mitigation.DensityMatrixExecutor(ham, nm)

	result, err := mitigation.RunCDR(context.Background(), mitigation.CDRConfig{
		Circuit:     circ,
		Executor:    exec,
		Hamiltonian: ham,
		NumTraining: 20,
		Fraction:    0.75,
	})
	if err != nil {
		panic(err)
	}

	fmt.Printf("CDR mitigated: %.4f (fit: y = %.4f·x + %.4f)\n",
		result.MitigatedValue, result.FitA, result.FitB)
}

type DDConfig

type DDConfig struct {
	// Circuit is the quantum circuit to protect.
	Circuit *ir.Circuit
	// Sequence selects the DD pulse sequence. Default: DDXX.
	Sequence DDSequence
}

DDConfig specifies parameters for dynamical decoupling insertion.

type DDSequence

type DDSequence int

DDSequence selects the dynamical decoupling pulse sequence.

const (
	// DDXX inserts an X-X sequence into idle periods.
	DDXX DDSequence = iota
	// DDXY4 inserts an X-Y-X-Y sequence into idle periods.
	DDXY4
)

type Executor

type Executor func(ctx context.Context, circuit *ir.Circuit) (float64, error)

Executor evaluates a circuit and returns an expectation value. Implementations must be safe for concurrent use.

func DensityMatrixExecutor

func DensityMatrixExecutor(hamiltonian pauli.PauliSum, nm *noise.NoiseModel) Executor

DensityMatrixExecutor returns an Executor that computes Tr(ρH) using density matrix simulation with a noise model. Each call creates a fresh simulator for goroutine safety.

func StatevectorExecutor

func StatevectorExecutor(hamiltonian pauli.PauliSum) Executor

StatevectorExecutor returns an Executor that computes ⟨ψ|H|ψ⟩ using ideal (noiseless) statevector simulation. Each call creates a fresh simulator for goroutine safety.

type Extrapolator

type Extrapolator int

Extrapolator selects the extrapolation method for ZNE.

const (
	// LinearExtrapolator fits y = a + bx and returns a (the y-intercept at x=0).
	LinearExtrapolator Extrapolator = iota
	// PolynomialExtrapolator fits a polynomial of degree len(points)-1 and evaluates at x=0.
	PolynomialExtrapolator
	// ExponentialExtrapolator fits y = a + b·exp(cx) and returns a.
	ExponentialExtrapolator
)

type PECConfig

type PECConfig struct {
	// Circuit is the quantum circuit to mitigate.
	Circuit *ir.Circuit
	// Executor evaluates a circuit and returns an expectation value.
	Executor Executor
	// NoiseModel describes the depolarizing noise on each gate.
	NoiseModel *noise.NoiseModel
	// Samples is the number of quasi-probability samples. Default: 1000.
	Samples int
}

PECConfig specifies parameters for probabilistic error cancellation.

type PECResult

type PECResult struct {
	// MitigatedValue is the unbiased expectation estimate.
	MitigatedValue float64
	// Overhead is the sampling overhead γ^L.
	Overhead float64
	// RawValues are the sign-weighted values per sample.
	RawValues []float64
}

PECResult holds the output of probabilistic error cancellation.

func RunPEC

func RunPEC(ctx context.Context, cfg PECConfig) (*PECResult, error)

RunPEC performs probabilistic error cancellation.

For each gate in the circuit, it decomposes the ideal inverse noise channel into a quasi-probability distribution over Pauli corrections. It then samples corrected circuits and averages the sign-weighted results.

Returns an error if any noise channel is non-depolarizing.

Example
package main

import (
	"context"
	"fmt"

	"github.com/splch/goqu/algorithm/mitigation"
	"github.com/splch/goqu/circuit/builder"
	"github.com/splch/goqu/sim/noise"
	"github.com/splch/goqu/sim/pauli"
)

func main() {
	// Build a Bell circuit.
	circ, err := builder.New("bell", 2).
		H(0).
		CNOT(0, 1).
		Build()
	if err != nil {
		panic(err)
	}

	// Observable: ZZ.
	zz := pauli.NewPauliString(1, map[int]pauli.Pauli{0: pauli.Z, 1: pauli.Z}, 2)
	ham, err := pauli.NewPauliSum([]pauli.PauliString{zz})
	if err != nil {
		panic(err)
	}

	// Depolarizing noise model (required for PEC).
	nm := noise.New()
	nm.AddDefaultError(1, noise.Depolarizing1Q(0.02))
	nm.AddDefaultError(2, noise.Depolarizing2Q(0.04))
	exec := mitigation.DensityMatrixExecutor(ham, nm)

	result, err := mitigation.RunPEC(context.Background(), mitigation.PECConfig{
		Circuit:    circ,
		Executor:   exec,
		NoiseModel: nm,
		Samples:    500,
	})
	if err != nil {
		panic(err)
	}

	fmt.Printf("PEC mitigated: %.4f (overhead: %.2f)\n",
		result.MitigatedValue, result.Overhead)
}

type ReadoutCalibration

type ReadoutCalibration struct {
	// contains filtered or unexported fields
}

ReadoutCalibration holds the confusion matrix and its inverse for measurement error mitigation.

func CalibrateReadout

func CalibrateReadout(ctx context.Context, numQubits, shots int, exec BasisExecutor) (*ReadoutCalibration, error)

CalibrateReadout builds a full confusion matrix by preparing all 2^n computational basis states and measuring. Practical for n ≤ 10.

Example
numQubits := 2
shots := 10000

// Mock basis executor with known readout errors.
exec := mockBasisExecutor(numQubits, 0.05, 0.03)

cal, err := mitigation.CalibrateReadout(context.Background(), numQubits, shots, exec)
if err != nil {
	panic(err)
}

// Simulate noisy measurement of |00⟩.
noisyCounts, err := exec(context.Background(), 0, shots)
if err != nil {
	panic(err)
}

corrected := cal.CorrectCounts(noisyCounts)

fmt.Printf("corrected counts have %d entries\n", len(corrected))
// Verify |00⟩ dominates.
total := 0
for _, c := range corrected {
	total += c
}
if total > 0 {
	fmt.Printf("|00⟩ fraction: %.2f\n", float64(corrected["00"])/float64(total))
}

func CalibrateReadoutPerQubit

func CalibrateReadoutPerQubit(ctx context.Context, numQubits, shots int, exec BasisExecutor) (*ReadoutCalibration, error)

CalibrateReadoutPerQubit builds a confusion matrix from per-qubit calibrations. Only requires 2 basis state preparations (|0...0⟩ and |1...1⟩) and constructs the full confusion matrix as a tensor product of 2×2 matrices. Scales to large qubit counts.

func (*ReadoutCalibration) CorrectCounts

func (cal *ReadoutCalibration) CorrectCounts(counts map[string]int) map[string]int

CorrectCounts applies the inverse confusion matrix to raw measurement counts. Negative corrected values are clipped to zero and the result is renormalized.

func (*ReadoutCalibration) CorrectProbabilities

func (cal *ReadoutCalibration) CorrectProbabilities(probs map[string]float64) map[string]float64

CorrectProbabilities applies the inverse confusion matrix to a probability distribution. Negative values are clipped to zero and the result is renormalized.

type ScaleMethod

type ScaleMethod int

ScaleMethod selects the noise-scaling strategy for ZNE.

const (
	// UnitaryFolding scales noise by appending C†C pairs to the full circuit:
	// C → C (C† C)^((s-1)/2). Preserves the logical unitary.
	UnitaryFolding ScaleMethod = iota
	// LocalFolding scales noise by replacing each gate G with
	// G (G† G)^((s-1)/2). Preserves the logical unitary per gate.
	LocalFolding
)

type ShotRunner

type ShotRunner func(ctx context.Context, circuit *ir.Circuit, shots int) (map[string]int, error)

ShotRunner executes a circuit with a given number of shots and returns measurement counts. This is the callback type for shot-based mitigation.

type TREXCalibration

type TREXCalibration struct {
	// contains filtered or unexported fields
}

TREXCalibration holds per-qubit readout flip rates for TREX correction.

func CalibrateTREX

func CalibrateTREX(ctx context.Context, numQubits, calibShots int, runner ShotRunner) (*TREXCalibration, error)

CalibrateTREX estimates per-qubit readout flip rates by running a trivial circuit with randomized X insertions before measurement. Uses the all-zero state so any measured 1s (after classical correction) indicate readout errors.

func (*TREXCalibration) CorrectExpectation

func (cal *TREXCalibration) CorrectExpectation(rawValue float64, qubits []int) float64

CorrectExpectation corrects a raw expectation value of a Z-type observable on the given qubits using the calibrated flip rates. The correction divides by ∏(1 - 2·f_q) for each qubit in the observable.

type TREXConfig

type TREXConfig struct {
	// Circuit is the quantum circuit to run.
	Circuit *ir.Circuit
	// Runner executes a circuit with shots and returns counts.
	Runner ShotRunner
	// Shots is the number of measurement shots per sample.
	Shots int
	// Samples is the number of twirled copies. Default: 10.
	Samples int
	// Calibration is a pre-computed calibration. If nil, auto-calibrate.
	Calibration *TREXCalibration
	// CalibShots is the number of shots for auto-calibration. Default: 1000.
	CalibShots int
}

TREXConfig specifies parameters for TREX readout error mitigation.

type TREXResult

type TREXResult struct {
	// Counts are the merged, corrected measurement counts.
	Counts map[string]int
	// Calibration is the calibration data (computed or provided).
	Calibration *TREXCalibration
}

TREXResult holds the output of TREX readout error mitigation.

func RunTREX

func RunTREX(ctx context.Context, cfg TREXConfig) (*TREXResult, error)

RunTREX performs Twirled Readout Error eXtinction.

It inserts random X gates before measurements, classically undoes the bit flips, and merges results across multiple twirled copies. This mitigates readout errors with O(n) overhead instead of O(2^n) for confusion-matrix methods.

Example
// Build a Bell circuit with measurements.
circ, err := builder.New("bell", 2).
	H(0).
	CNOT(0, 1).
	MeasureAll().
	Build()
if err != nil {
	panic(err)
}

// Mock shot runner with readout errors.
runner := mockShotRunner(0.05, 0.03)

result, err := mitigation.RunTREX(context.Background(), mitigation.TREXConfig{
	Circuit:    circ,
	Runner:     runner,
	Shots:      1000,
	Samples:    5,
	CalibShots: 10000,
})
if err != nil {
	panic(err)
}

total := 0
for _, c := range result.Counts {
	total += c
}
fmt.Printf("TREX total counts: %d\n", total)

type TwirlConfig

type TwirlConfig struct {
	// Circuit is the quantum circuit to twirl.
	Circuit *ir.Circuit
	// Executor evaluates a circuit and returns an expectation value.
	Executor Executor
	// Samples is the number of twirled circuit copies to average. Default: 100.
	Samples int
}

TwirlConfig specifies parameters for Pauli twirling.

type TwirlResult

type TwirlResult struct {
	// MitigatedValue is the average expectation over all twirled circuits.
	MitigatedValue float64
	// RawValues are the individual expectation values per sample.
	RawValues []float64
}

TwirlResult holds the output of Pauli twirling.

func RunTwirl

func RunTwirl(ctx context.Context, cfg TwirlConfig) (*TwirlResult, error)

RunTwirl performs Pauli twirling on 2-qubit gates (CNOT and CZ).

It generates multiple randomly twirled copies of the circuit, executes each, and averages the results. This converts coherent errors into stochastic Pauli errors, improving the accuracy of other mitigation techniques.

Example
package main

import (
	"context"
	"fmt"

	"github.com/splch/goqu/algorithm/mitigation"
	"github.com/splch/goqu/circuit/builder"
	"github.com/splch/goqu/sim/noise"
	"github.com/splch/goqu/sim/pauli"
)

func main() {
	// Build a Bell circuit.
	circ, err := builder.New("bell", 2).
		H(0).
		CNOT(0, 1).
		Build()
	if err != nil {
		panic(err)
	}

	// Observable: ZZ correlation.
	zz := pauli.NewPauliString(1, map[int]pauli.Pauli{0: pauli.Z, 1: pauli.Z}, 2)
	ham, err := pauli.NewPauliSum([]pauli.PauliString{zz})
	if err != nil {
		panic(err)
	}

	// Noisy executor.
	nm := noise.New()
	nm.AddDefaultError(1, noise.Depolarizing1Q(0.02))
	nm.AddDefaultError(2, noise.Depolarizing2Q(0.04))
	exec := mitigation.DensityMatrixExecutor(ham, nm)

	result, err := mitigation.RunTwirl(context.Background(), mitigation.TwirlConfig{
		Circuit:  circ,
		Executor: exec,
		Samples:  50,
	})
	if err != nil {
		panic(err)
	}

	fmt.Printf("twirled value: %.4f (averaged over %d samples)\n",
		result.MitigatedValue, len(result.RawValues))
}

type ZNEConfig

type ZNEConfig struct {
	// Circuit is the quantum circuit to mitigate.
	Circuit *ir.Circuit
	// Executor evaluates a circuit and returns an expectation value.
	Executor Executor
	// ScaleFactors are the noise scale factors. Must be positive odd integers.
	// Default: [1, 3, 5].
	ScaleFactors []float64
	// ScaleMethod selects the noise-scaling strategy. Default: UnitaryFolding.
	ScaleMethod ScaleMethod
	// Extrapolator selects the extrapolation method. Default: LinearExtrapolator.
	Extrapolator Extrapolator
}

ZNEConfig specifies the parameters for zero-noise extrapolation.

type ZNEResult

type ZNEResult struct {
	// MitigatedValue is the extrapolated zero-noise expectation value.
	MitigatedValue float64
	// NoisyValues are the raw expectation values at each scale factor.
	NoisyValues []float64
	// ScaleFactors are the scale factors that were used.
	ScaleFactors []float64
}

ZNEResult holds the output of zero-noise extrapolation.

func RunZNE

func RunZNE(ctx context.Context, cfg ZNEConfig) (*ZNEResult, error)

RunZNE performs zero-noise extrapolation.

It folds the circuit at each scale factor, executes each folded circuit, and extrapolates the results to the zero-noise limit.

Example
package main

import (
	"context"
	"fmt"

	"github.com/splch/goqu/algorithm/mitigation"
	"github.com/splch/goqu/circuit/builder"
	"github.com/splch/goqu/sim/noise"
	"github.com/splch/goqu/sim/pauli"
)

func main() {
	// Build a Bell circuit.
	circ, err := builder.New("bell", 2).
		H(0).
		CNOT(0, 1).
		Build()
	if err != nil {
		panic(err)
	}

	// Observable: Z on qubit 0.
	hamiltonian, err := pauli.NewPauliSum([]pauli.PauliString{
		pauli.ZOn([]int{0}, 2),
	})
	if err != nil {
		panic(err)
	}

	// Create a noisy executor.
	nm := noise.New()
	nm.AddDefaultError(1, noise.Depolarizing1Q(0.02))
	nm.AddDefaultError(2, noise.Depolarizing2Q(0.04))
	exec := mitigation.DensityMatrixExecutor(hamiltonian, nm)

	// Run ZNE with linear extrapolation.
	result, err := mitigation.RunZNE(context.Background(), mitigation.ZNEConfig{
		Circuit:      circ,
		Executor:     exec,
		ScaleFactors: []float64{1, 3, 5},
		Extrapolator: mitigation.LinearExtrapolator,
	})
	if err != nil {
		panic(err)
	}

	fmt.Printf("mitigated value: %.4f\n", result.MitigatedValue)
	fmt.Printf("scale factors: %v\n", result.ScaleFactors)
	fmt.Printf("noisy values: [")
	for i, v := range result.NoisyValues {
		if i > 0 {
			fmt.Print(", ")
		}
		fmt.Printf("%.4f", v)
	}
	fmt.Println("]")
	// Output will vary with noise parameters, but mitigated value
	// should be closer to the ideal (0.0 for Bell state Z0) than
	// the raw noisy value at scale factor 1.
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL