Skip to content

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.

python
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).

MethodEffect
H, S, S_DAG, X, Y, Zsingle-qubit Clifford gates
SX, SX_DAGX and its inverse, recorded as H S H / H S_DAG H
CX (CNOT), CZ, SWAPtwo-qubit Clifford gates
M(*q) (MZ)measure in the Z basis (appends to the record)
MX(*q), MY(*q)measure in the X / Y basis, recorded as a basis change around M
MR(*q)measure, then reset to |0⟩
R(*q)reset to |0⟩

Noise ​

Noise methods take the target(s) first, then the channel parameter. See Noise for the channels themselves.

MethodParameterChannel
DEPOLARIZE1(q, p)p, dict or vectorsingle-qubit depolarizing; a {"Z": ..} dict makes it anisotropic
DEPOLARIZE2(pair, p)p, dict or vectortwo-qubit depolarizing; a {"ZZ": ..} dict makes it anisotropic
PAULI_CHANNEL_1(q, weights)p, dict or vectorthe same channel as DEPOLARIZE1
PAULI_CHANNEL_2(pair, weights)p, dict or vectorthe same channel as DEPOLARIZE2
X_ERROR(q, p), Y_ERROR(q, p), Z_ERROR(q, p)p, scalar onlyX / Y / Z with probability p
RZ(q, theta), RX(q, theta), RY(q, theta)θcoherent rotation e−iθP/2
AMPLITUDE_DAMP(q, p)pamplitude damping (non-unitary)

A scalar rate on a Pauli-set channel is isotropic; a {label: rate} dict or the dense ordered vector makes it anisotropic. See Anisotropy for the label sets, the pair ordering, and the errors raised on a bad label.

A rotation at a multiple of π/2 is a Clifford gate, and append records the gate rather than a noise location: a noise location is skipped by every noiseless reference while the trajectories apply it, so the two would disagree about a deterministic operation.

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.

MethodDescription
detector(*recs)declare a detector over the given measurement records
observable(index, *recs)declare logical observable index over records
python
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 ​

MethodReturnsDescription
run(seed=None)Simulatorapply a noiseless circuit and return the final state; raises ValueError on any noise instruction
sample(shots, seed=None)ndarray[uint8]measurement records over shots Pauli-noise trajectories, shape (shots, measurements); raises ValueError on non-Pauli noise
estimate(observable, shots=10000, stratify=None, seed=None)floatreweighted estimate of ⟨O⟩
detector_sampler()DetectorSamplera sampler of detection events (see QEC)
dem()DetectorErrorModelthe detector error model (see QEC)

sample and estimate share one cached noise.Sampler, rebuilt when instructions are added, so repeated sample calls compile the circuit and run the noiseless reference pass once.

estimate reweights trajectories and is unbiased for any noise, including coherent and non-unitary channels. stratify chooses the variance strategy:

stratifyBehaviourUse when
Noneauto: flat for Pauli, stratified otherwisedefault
Falseflat importance samplingall channels are Pauli
Truestratified by fault count kgeneral noise, lowest variance
python
c = Circuit(1)
c.H(0).RZ(0, 0.3)
c.estimate("X", 20000)                  # ~ cos(0.3)
c.estimate("X", 20000, stratify=False)

Properties ​

PropertyDescription
num_qubitsregister width (grows as instructions are added)
num_measurementscount of M/MR outcomes recorded so far
instructionsthe underlying (name, targets, arg) list
detectors, observablesthe declared detectors and observables
detector_coordsper-detector (x, y, t), filled by builders that record geometry
MethodReturns
noise_locations()iterator of (instruction index, name, targets, channel) per noise location
nonpauli()name of the first non-Pauli noise location, or None if all noise is Pauli

Low-level and custom channels ​

append(name, targets, arg=None) adds any instruction directly, and noise(channel, *targets) drops in a custom Channel instance:

python
from qliff.noise import PauliChannel

c = Circuit(1)
c.append("H", 0)
c.noise(PauliChannel({"X": 0.1}), 0)