Detector Error Models¶
A detector error model (DEM) is a detector error matrix (capturing which detectors each error mechanism flips) together with a noise model that assigns a likelihood to each error mechanism. It is the input a decoder needs to infer which errors occurred from a circuit’s measurement record.
In CUDA-Q you declare the parity checks (detectors) and logical observables
directly inside a kernel, then extract the DEM with
cudaq::dem_from_kernel (C++) or cudaq.dem_from_kernel (Python)
as text in Stim’s standard
.dem file format,
which stim.DetectorErrorModel parses back into a decoder-ready model. The
measurements that feed the declarations are measurement handles
(Measuring Kernels).
Three kernel-side declarations are available in both C++ and Python :
detector(m0, m1, ...) declares one detector as a parity
constraint over the given measurements; detectors(prev, curr) declares N
detectors by pairing two equal-length handle vectors element-wise (the
standard form for cross-round detectors); and
logical_observable(m0, m1, ...) declares a logical observable.
The example below is a three-qubit bit-flip memory experiment: each round
measures the data qubits and pairs them with the previous round via
detectors, with a final logical_observable reading out the register.
In-kernel apply_noise seeds the error mechanisms. Each call applies a
single-qubit bit-flip channel (cudaq::x_error in C++, cudaq.XError in
Python) that applies a Pauli X with the given probability, so a flipped
data qubit shows up as a parity change in the next detectors pair.
See the C++ and
Python API references for apply_noise
and the other predefined noise channels.
# A 3-qubit bit-flip memory experiment. Each round measures the data qubits;
# cross-round detectors pair each measurement with its value in the previous
# round, and a final logical observable reads out the register. In-kernel
# `apply_noise` seeds the error mechanisms the detector error model reports.
@cudaq.kernel
def memory_experiment(rounds: int):
data = cudaq.qvector(3)
prev = mz(data)
for r in range(rounds):
cudaq.apply_noise(cudaq.XError, 0.01, data[0])
cudaq.apply_noise(cudaq.XError, 0.01, data[1])
cudaq.apply_noise(cudaq.XError, 0.01, data[2])
curr = mz(data)
# One detector per qubit, pairing this round with the previous one.
cudaq.detectors(prev, curr)
prev = curr
cudaq.logical_observable(prev[0], prev[1], prev[2])
// A 3-qubit bit-flip memory experiment. Each round measures the data qubits;
// cross-round detectors pair each measurement with its value in the previous
// round, and a final logical observable reads out the register. In-kernel
// `apply_noise` seeds the error mechanisms the detector error model reports.
__qpu__ void memory_experiment(int rounds) {
cudaq::qvector data(3);
auto prev = mz(data);
for (int r = 0; r < rounds; ++r) {
cudaq::apply_noise<cudaq::x_error>(0.01, data[0]);
cudaq::apply_noise<cudaq::x_error>(0.01, data[1]);
cudaq::apply_noise<cudaq::x_error>(0.01, data[2]);
auto curr = mz(data);
// One detector per qubit, pairing this round with the previous one.
cudaq::detectors(prev, curr);
prev = curr;
}
cudaq::logical_observable(prev[0], prev[1], prev[2]);
}
Pass the kernel (and a noise model) to dem_from_kernel to extract the DEM.
# Generate the detector error model as Stim `.dem` text. A noise model must be
# supplied for the in-kernel `apply_noise` mechanisms to take effect. Parse the
# text with `stim.DetectorErrorModel(dem)` to drive a decoder.
noise = cudaq.NoiseModel()
dem = cudaq.dem_from_kernel(memory_experiment, 2, noise_model=noise)
print(dem)
// Generate the detector error model as Stim `.dem` text. A noise model must
// be supplied for the in-kernel `apply_noise` mechanisms to take effect.
// Parse the text with Stim (`stim::DetectorErrorModel{dem}`) to drive a
// decoder.
cudaq::noise_model noise;
std::string dem =
cudaq::dem_from_kernel(memory_experiment, &noise, /*rounds=*/2);
std::printf("%s\n", dem.c_str());
The .dem text is a list of independent error mechanisms. Each
error(p) D... L... line gives one mechanism: its probability p, the
detectors it flips (its symptoms, D), and the logical observables it
flips (its frame changes, L).
Output DEM: With two rounds and three data qubits, there are six independent
error mechanisms: one bit-flip per qubit per round at the in-kernel
probability 0.01 (printed at full floating-point precision). Each error
flips one detector together with the logical observable L0:
error(0.01000000000000000021) D0 L0
error(0.01000000000000000021) D1 L0
error(0.01000000000000000021) D2 L0
error(0.01000000000000000021) D3 L0
error(0.01000000000000000021) D4 L0
error(0.01000000000000000021) D5 L0
Limitations¶
Stabilizer (Clifford) circuits only. The DEM formalism requires detectors to be deterministic under noise-free execution, which is only well defined for Clifford circuits; a non-Clifford gate raises a diagnostic.
No measurement-conditional control flow. Branching on a measurement result changes the measurement count shot-to-shot and breaks the detector matrix model; such kernels are rejected.
Independent Pauli noise. Each error mechanism is assumed independent..
Pre-decomposition. The DEM reflects the abstract kernel circuit, not the hardware-decomposed circuit.