Circuit
A Circuit is a stim-like instruction list — gates, noise, measurements, detectors, and observables — recorded once and reused. Build it with the same uppercase methods as the Simulator, then run it, sample it, or hand it to a noise or QEC sampler.
from qliff import Circuit
c = Circuit(2)
c.H(0).CX(0, 1).M(0, 1)Circuit(num_qubits=0) grows its width automatically as you reference higher qubit indices, so the argument is only a hint.
Building blocks
Every instruction is a triple (name, targets, arg). The fluent methods are thin wrappers over append; use whichever reads better.
Gates and measurement
These mirror the simulator and take one or more targets (two-qubit gates take flattened (control, target) pairs).
| Method | Effect |
|---|---|
H, S, S_DAG, X, Y, Z | single-qubit Clifford gates |
CX (CNOT), CZ, SWAP | two-qubit Clifford gates |
M(*q) | measure in the |
MR(*q) | measure, then reset to |
R(*q) | reset to |
Noise
Noise methods take the target(s) first, then the channel parameter. See Noise for the channels themselves.
| Method | Parameter | Channel |
|---|---|---|
DEPOLARIZE1(q, p) | single-qubit depolarizing | |
DEPOLARIZE2(pair, p) | two-qubit depolarizing | |
X_ERROR(q, p), Z_ERROR(q, p) | bit-flip / phase-flip | |
PAULI_CHANNEL_1(q, (px,py,pz)) | tuple | arbitrary 1-qubit Pauli channel |
RZ(q, theta), RX(q, theta) | coherent rotation | |
AMPLITUDE_DAMP(q, p) | amplitude damping (non-unitary) |
Detectors and observables
A detector is a set of measurement records whose parity is deterministic in the noiseless circuit. An observable is a logical degree of freedom tracked across the run. Record indices use stim's rec[-1] convention: negative indices count back from the measurements declared so far.
| Method | Description |
|---|---|
detector(*recs) | declare a detector over the given measurement records |
observable(index, *recs) | declare logical observable index over records |
c = Circuit(1)
c.M(0)
c.X_ERROR(0, 0.1)
c.M(0)
c.detector(-1, -2)
c.observable(0, -1)Running and sampling
| Method | Returns | Description |
|---|---|---|
run(seed=None) | Simulator | apply a noiseless circuit and return the final state; raises if it contains noise |
sample(shots, seed=None) | ndarray[uint8] | measurement records over shots Pauli-noise trajectories, shape (shots, measurements) |
estimate(observable, shots=10000, stratify=None, seed=None) | float | reweighted estimate of |
detector_sampler() | DetectorSampler | a sampler of detection events (see QEC) |
dem() | DetectorErrorModel | the detector error model (see QEC) |
estimate reweights trajectories and is unbiased for any noise, including coherent and non-unitary channels. stratify chooses the variance strategy:
stratify | Behaviour | Use when |
|---|---|---|
None | auto: flat for Pauli, stratified otherwise | default |
False | flat importance sampling | all channels are Pauli |
True | stratified by fault count | general noise, lowest variance |
c = Circuit(1)
c.H(0).RZ(0, 0.3)
c.estimate("X", 20000) # ~ cos(0.3)
c.estimate("X", 20000, stratify=False)Properties
| Property | Description |
|---|---|
num_qubits | register width (grows as instructions are added) |
num_measurements | count of M/MR outcomes recorded so far |
instructions | the underlying (name, targets, arg) list |
detectors, observables | the declared detectors and observables |
is_pauli | True if every noise instruction is a Pauli channel |
Low-level and custom channels
append(name, targets, arg=None) adds any instruction directly, and noise(channel, *targets) drops in a custom Channel instance:
from qliff.noise import BitFlip
c = Circuit(1)
c.append("H", 0)
c.noise(BitFlip(0.1), 0)