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 |
SX, SX_DAG | H S H / H S_DAG H |
CX (CNOT), CZ, SWAP | two-qubit Clifford gates |
M(*q) (MZ) | measure in the |
MX(*q), MY(*q) | measure in the M |
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; a {"Z": ..} dict makes it anisotropic | |
DEPOLARIZE2(pair, p) | two-qubit depolarizing; a {"ZZ": ..} dict makes it anisotropic | |
PAULI_CHANNEL_1(q, weights) | the same channel as DEPOLARIZE1 | |
PAULI_CHANNEL_2(pair, weights) | the same channel as DEPOLARIZE2 | |
X_ERROR(q, p), Y_ERROR(q, p), Z_ERROR(q, p) | ||
RZ(q, theta), RX(q, theta), RY(q, theta) | coherent rotation | |
AMPLITUDE_DAMP(q, p) | amplitude 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 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.
| 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 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) | float | reweighted estimate of |
detector_sampler() | DetectorSampler | a sampler of detection events (see QEC) |
dem() | DetectorErrorModel | the 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:
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 |
detector_coords | per-detector (x, y, t), filled by builders that record geometry |
| Method | Returns |
|---|---|
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:
from qliff.noise import PauliChannel
c = Circuit(1)
c.append("H", 0)
c.noise(PauliChannel({"X": 0.1}), 0)