Skip to content

Noise ​

Add channels to a Circuit, then sample trajectories with Sampler. sample returns Pauli-noise measurement records; expect reweights each trajectory by the signed quasiprobability weights, staying unbiased for coherent and non-unitary noise too.

Pauli noise records ​

Apply a bit-flip X error with probability p and sample the noisy measurement records.

python
from qliff import Circuit
from qliff.noise import Sampler

c = Circuit(1)
c.X_ERROR(0, 0.25)
c.M(0)

records = Sampler(c).sample(5000, seed=0)
ones = sum(r[0] for r in records) / 5000    # ~ 0.25

About a quarter of the shots flip to 1, matching the error rate p=0.25.

Estimate an observable under depolarizing noise ​

DEPOLARIZE1 shrinks every expectation; estimate ⟨X⟩ over many trajectories.

python
from qliff import Circuit
from qliff.noise import Sampler

c = Circuit(1)
c.H(0)
c.DEPOLARIZE1(0, 0.1)

est = Sampler(c).expect("X", 5000, seed=0)   # ~ 1 - 4p/3 = 0.867

Depolarizing with p=0.1 damps ⟨X⟩ from 1 toward 1−43p.

Anisotropic and biased Pauli noise ​

A bare scalar rate is isotropic. A {label: rate} dict makes the same channel anisotropic, with every unlisted Pauli at zero.

python
from qliff import Circuit
from qliff.noise import Sampler

c = Circuit(1)
c.H(0)
c.DEPOLARIZE1(0, {"Z": 0.05})    # pure dephasing; p_x = p_y = 0

est = Sampler(c).expect("X", 5000, seed=0)   # 0.90

A Z flip at p damps ⟨X⟩ by 1−2p, so pz=0.05 gives 0.90 exactly. The two-qubit form takes pair labels: c.DEPOLARIZE2([0, 1], {"ZZ": 0.02}).

For a single-axis split of a total rate, the code builders take bias (an η) and bias_axis rather than a dict:

python
from qliff.noise.channel import bias_split

bias_split(0.03, 100.0, "X")   # (0.0297..., 0.000148..., 0.000148...)

Coherent rotation with importance sampling ​

A coherent RZ rotation is not a Pauli channel, so expect reweights each trajectory by its signed quasiprobability weight.

python
from qliff import Circuit
from qliff.noise import Sampler

c = Circuit(1)
c.H(0).RZ(0, 0.3)

est = Sampler(c).expect("X", 20000, seed=0)   # ~ cos(0.3) = 0.955

The estimate tracks the exact ⟨X⟩=cos⁡θ.

Lower variance with stratification ​

stratify=True gives the same unbiased answer but strata by fault count for much lower variance.

python
from qliff import Circuit
from qliff.noise import Sampler

c = Circuit(1)
c.H(0).RZ(0, 0.3)

est = Sampler(c).expect("X", 20000, seed=0, stratify=True)   # ~ cos(0.3)

Same cos⁡θ target, smaller error bars at equal shots. (Or just call c.estimate("X", 20000) and let it pick the sampler.)

A custom channel ​

Subclass Channel: return an identity branch first, then (weight, ops) faults. This is a pure Z dephasing channel.

python
from qliff import Circuit
from qliff.noise import Channel, Sampler

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)

est = Sampler(c).expect("X", 5000, seed=0)   # ~ 1 - 2p = 0.8

A Z flip with probability p damps ⟨X⟩ by 1−2p. No Rust or recompilation -- any Channel subclass drops straight into a circuit.