Noise
qliff.noise simulates noisy circuits inside the stabilizer formalism. Each channel is a quasiprobability mixture of stabilizer (Clifford) channels:
A sampler runs many pure-Clifford trajectories and reweights each by
You rarely touch a channel directly. Add noise to a Circuit, then call estimate or sample — the classes below are what those methods build.
Channel
Channel is an abstract base; subclasses expose a stabilizer-channel decomposition. A branch is a pair (weight, ops), where ops is a list of (gate, targets). The identity (no-fault) branch comes first.
| Property/Method | Description |
|---|---|
is_pauli | True if all weights are probabilities (records are sampleable) |
branches(targets) | the (weight, ops) decomposition for the given qubits |
sample(targets, rng) | draw one branch as (sign·gamma, ops) |
Channel catalog
Each factory returns a configured Channel. The first five are Pauli (positive weights); the last two are general (signed weights, is_pauli = False).
| Channel | Circuit method | Arguments | Description |
|---|---|---|---|
Depolarize(p) | DEPOLARIZE1(q, p) | ||
Depolarize(p, twoq=True) | DEPOLARIZE2(pair, p) | the 15 two-qubit Paulis, each | |
BitFlip(p) | X_ERROR(q, p) | ||
PhaseFlip(p) | Z_ERROR(q, p) | ||
PauliChannel(px, py, pz) | PAULI_CHANNEL_1(q, (px,py,pz)) | arbitrary 1-qubit Pauli channel (twoq=True → 2-qubit depolarizing) | |
Rotation(axis, theta) | RZ(q, theta) / RX(q, theta) | axis, | coherent rotation |
AmplitudeDamping(p) | AMPLITUDE_DAMP(q, p) | energy decay |
Amplitude damping
AmplitudeDamping(p) decomposes exactly over
The overhead expect (estimate reweights automatically).
Sampler
Sampler wraps a circuit and runs trajectories. It has two methods.
| Method | Handles | Notes |
|---|---|---|
expect(obs, shots, seed=None, stratify=False) | any channel | importance estimate, unbiased for any noise |
sample(shots, seed=None) | Pauli only | uint8 array (shots, measurements); raises on a general channel |
expect draws one branch per noise location and reweights each trajectory by stratify=True it rewrites the estimate as
from qliff import Circuit
from qliff.noise import Sampler
c = Circuit(1)
c.H(0).RZ(0, 0.3)
Sampler(c).expect("X", 20000)
Sampler(c).expect("X", 20000, stratify=True)sample is Pauli-only: a measured bitstring cannot be reweighted by a negative quasiprobability, so a non-Pauli channel raises.
TIP
Prefer c.estimate(observable, shots). It uses flat importance sampling for Pauli circuits and stratifies otherwise, so you rarely build a Sampler by hand.
Custom channels
Subclass Channel, set is_pauli, and return the branches: an identity branch first, then (weight, ops) faults (weights may be negative quasiprobabilities). No Rust or recompilation needed. Drop it into a circuit with c.noise(channel, q).
from qliff import Circuit
from qliff.noise import Channel
class Dephase(Channel):
is_pauli = True
def __init__(self, p):
self.p = p
def branches(self, targets):
q = targets[0]
return [(1.0 - self.p, []), (self.p, [("Z", (q,))])]
c = Circuit(1)
c.H(0).noise(Dephase(0.1), 0)