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:
- Fold the circuit at each scale factor (FoldCircuit)
- Execute each folded circuit via an Executor
- 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 ¶
- func Extrapolate(scaleFactors, values []float64, method Extrapolator) (float64, error)
- func FoldCircuit(circuit *ir.Circuit, scaleFactor int, method ScaleMethod) (*ir.Circuit, error)
- func InsertDD(cfg DDConfig) (*ir.Circuit, error)
- func TwirlCircuit(circuit *ir.Circuit, rng *rand.Rand) (*ir.Circuit, error)
- type BasisExecutor
- type CDRConfig
- type CDRResult
- type DDConfig
- type DDSequence
- type Executor
- type Extrapolator
- type PECConfig
- type PECResult
- type ReadoutCalibration
- type ScaleMethod
- type ShotRunner
- type TREXCalibration
- type TREXConfig
- type TREXResult
- type TwirlConfig
- type TwirlResult
- type ZNEConfig
- type ZNEResult
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 ¶
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 ¶
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()))
}
Types ¶
type BasisExecutor ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
}