use std::collections::BTreeMap;
use crate::decoder::{Edge, SyndromeGraph};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum StabKind {
X,
Z,
}
#[derive(Clone, Copy, Debug)]
pub enum Op {
Reset(usize),
H(usize),
Cnot(usize, usize),
Cz(usize, usize),
Measure(usize, StabKind, usize),
Noise(usize),
}
pub type Pauli = u8;
#[derive(Clone, Copy, Debug)]
pub enum Fault {
Gate(usize, Pauli),
Readout(usize),
}
pub struct DetectorGraph {
pub graph: SyndromeGraph,
pub correction: Vec<u128>,
pub site_edges: Vec<Vec<usize>>,
}
pub struct CircuitModel {
pub for_x_errors: DetectorGraph,
pub for_z_errors: DetectorGraph,
pub num_rounds: usize,
pub noise_slots: usize,
pub erasure_sites: usize,
}
pub(crate) struct Frame {
pub(crate) x: Vec<bool>,
pub(crate) z: Vec<bool>,
}
impl Frame {
pub(crate) fn new(n: usize) -> Self {
Frame { x: vec![false; n], z: vec![false; n] }
}
pub(crate) fn apply(&mut self, q: usize, pauli: Pauli) {
if pauli & 1 != 0 { self.x[q] ^= true; }
if pauli & 2 != 0 { self.z[q] ^= true; }
}
pub(crate) fn step(&mut self, op: Op) -> Option<(StabKind, usize, bool)> {
match op {
Op::Reset(q) => {
self.x[q] = false;
self.z[q] = false;
None
}
Op::H(q) => {
std::mem::swap(&mut self.x[q], &mut self.z[q]);
None
}
Op::Cnot(c, t) => {
if self.x[c] { self.x[t] ^= true; }
if self.z[t] { self.z[c] ^= true; }
None
}
Op::Cz(a, b) => {
if self.x[a] { self.z[b] ^= true; }
if self.x[b] { self.z[a] ^= true; }
None
}
Op::Measure(q, kind, idx) => {
let flipped = self.x[q];
self.x[q] = false;
self.z[q] = false;
Some((kind, idx, flipped))
}
Op::Noise(_) => None,
}
}
}
pub(crate) struct FaultEffect {
pub(crate) flips_x_stab: Vec<bool>,
pub(crate) flips_z_stab: Vec<bool>,
pub(crate) residual_x: u128,
pub(crate) residual_z: u128,
}
pub struct CircuitLayout {
pub program: Vec<Op>,
pub num_qubits: usize,
pub num_data: usize,
pub num_x_stabs: usize,
pub num_z_stabs: usize,
}
impl CircuitLayout {
pub(crate) fn propagate(&self, fault: Fault, round: usize, rounds_total: usize) -> FaultEffect {
let mut frame = Frame::new(self.num_qubits);
let mut flips_x = vec![false; self.num_x_stabs * rounds_total];
let mut flips_z = vec![false; self.num_z_stabs * rounds_total];
for r in 0..rounds_total {
for (i, &op) in self.program.iter().enumerate() {
if r == round {
if let Fault::Gate(at, pauli) = fault {
if at == i {
if let Op::Noise(q) = op {
frame.apply(q, pauli);
}
}
}
}
if let Some((kind, idx, mut flipped)) = frame.step(op) {
if r == round {
if let Fault::Readout(at) = fault {
if at == i {
flipped ^= true;
}
}
}
match kind {
StabKind::X => flips_x[r * self.num_x_stabs + idx] = flipped,
StabKind::Z => flips_z[r * self.num_z_stabs + idx] = flipped,
}
}
}
}
let mut residual_x = 0u128;
let mut residual_z = 0u128;
for q in 0..self.num_data {
if frame.x[q] { residual_x |= 1u128 << q; }
if frame.z[q] { residual_z |= 1u128 << q; }
}
FaultEffect { flips_x_stab: flips_x, flips_z_stab: flips_z, residual_x, residual_z }
}
pub(crate) fn fault_locations(&self) -> Vec<(Fault, Option<usize>)> {
let mut out = Vec::new();
let mut slot = 0usize;
for (i, op) in self.program.iter().enumerate() {
match op {
Op::Noise(_) => {
for pauli in 1..=3u8 {
out.push((Fault::Gate(i, pauli), Some(slot)));
}
slot += 1;
}
Op::Measure(..) => out.push((Fault::Readout(i), None)),
_ => {}
}
}
out
}
pub fn noise_slots(&self) -> usize {
self.program.iter().filter(|op| matches!(op, Op::Noise(_))).count()
}
}
pub fn rounds_executed(num_rounds: usize) -> usize {
num_rounds + 2
}
pub fn detector_layers(num_rounds: usize) -> usize {
num_rounds + 1
}
fn detectors(flips: &[bool], num_stabs: usize, rounds_total: usize) -> Vec<usize> {
let mut fired = Vec::new();
for t in 1..rounds_total {
for s in 0..num_stabs {
let here = flips[t * num_stabs + s];
let before = flips[(t - 1) * num_stabs + s];
if here != before {
fired.push(s + (t - 1) * num_stabs);
}
}
}
fired
}
fn assemble(
candidates: Vec<(Vec<usize>, u128, usize)>,
num_nodes: usize,
num_sites: usize,
) -> DetectorGraph {
let mut tally: BTreeMap<(usize, usize), BTreeMap<u128, usize>> = BTreeMap::new();
let endpoints = |nodes: &Vec<usize>| -> Option<(usize, usize)> {
match nodes.len() {
1 => Some((nodes[0], num_nodes)), 2 => Some((nodes[0], nodes[1])),
_ => None,
}
};
for (nodes, corr, _) in &candidates {
if let Some(pair) = endpoints(nodes) {
*tally.entry(pair).or_default().entry(*corr).or_insert(0) += 1;
}
}
let mut edges = Vec::new();
let mut edge_to_qubit = Vec::new();
let mut correction = Vec::new();
let mut edge_of_pair: BTreeMap<(usize, usize), usize> = BTreeMap::new();
for (&(u, v), corrs) in &tally {
let (&best, _) = corrs
.iter()
.max_by_key(|(mask, count)| (**count, std::cmp::Reverse(**mask)))
.expect("a tallied pair has at least one correction");
let representative = if best == 0 { None } else { Some(best.trailing_zeros() as usize) };
edge_of_pair.insert((u, v), edges.len());
edges.push(Edge { u, v, id: edges.len() });
edge_to_qubit.push(representative);
correction.push(best);
}
let mut site_edges = vec![Vec::new(); num_sites];
for (nodes, _, site) in &candidates {
if let Some(pair) = endpoints(nodes) {
if let Some(&id) = edge_of_pair.get(&pair) {
if !site_edges[*site].contains(&id) {
site_edges[*site].push(id);
}
}
}
}
DetectorGraph {
graph: SyndromeGraph { num_nodes, edges, edge_to_qubit },
correction,
site_edges,
}
}
pub fn single_fault_failures(
layout: &CircuitLayout,
model: &CircuitModel,
num_rounds: usize,
decoder_type: usize,
logical_x_support: u128,
logical_z_support: u128,
) -> (usize, usize) {
let rounds_total = rounds_executed(num_rounds);
let mut tested = 0usize;
let mut failures = 0usize;
for round in 1..rounds_total - 1 {
for (fault, _) in layout.fault_locations() {
let effect = layout.propagate(fault, round, rounds_total);
tested += 1;
let dz = detectors(&effect.flips_z_stab, layout.num_z_stabs, rounds_total);
let dx = detectors(&effect.flips_x_stab, layout.num_x_stabs, rounds_total);
let mut residual_x = effect.residual_x;
let mut residual_z = effect.residual_z;
let mut defects = vec![false; model.for_x_errors.graph.num_nodes];
for &n in &dz { defects[n] = true; }
let none_x = vec![false; model.for_x_errors.graph.edges.len()];
residual_x ^= correction_for(&model.for_x_errors, &defects, decoder_type, &none_x);
let mut defects = vec![false; model.for_z_errors.graph.num_nodes];
for &n in &dx { defects[n] = true; }
let none_z = vec![false; model.for_z_errors.graph.edges.len()];
residual_z ^= correction_for(&model.for_z_errors, &defects, decoder_type, &none_z);
let logical_x = (residual_x & logical_z_support).count_ones() % 2 == 1;
let logical_z = (residual_z & logical_x_support).count_ones() % 2 == 1;
if logical_x || logical_z {
failures += 1;
}
}
}
(tested, failures)
}
pub fn correction_for(
dg: &DetectorGraph,
defects: &[bool],
decoder_type: usize,
erased: &[bool],
) -> u128 {
let chosen = match decoder_type {
1 => crate::decoder::decode_greedy(&dg.graph, defects, erased),
2 => crate::decoder::decode_mwpm(&dg.graph, defects, erased),
_ => crate::decoder::decode_union_find(&dg.graph, defects, erased),
};
let mut mask = 0u128;
for edge_idx in chosen {
mask ^= dg.correction[edge_idx];
}
mask
}
pub struct ModelStats {
pub x_buckets: [usize; 5],
pub z_buckets: [usize; 5],
pub x_edges: usize,
pub z_edges: usize,
}
pub fn stats(layout: &CircuitLayout, num_rounds: usize) -> ModelStats {
let rounds_total = rounds_executed(num_rounds);
let mut st = ModelStats { x_buckets: [0; 5], z_buckets: [0; 5], x_edges: 0, z_edges: 0 };
for round in 1..rounds_total - 1 {
for (fault, _) in layout.fault_locations() {
let effect = layout.propagate(fault, round, rounds_total);
let dz = detectors(&effect.flips_z_stab, layout.num_z_stabs, rounds_total);
let dx = detectors(&effect.flips_x_stab, layout.num_x_stabs, rounds_total);
st.x_buckets[dz.len().min(4)] += 1;
st.z_buckets[dx.len().min(4)] += 1;
}
}
let model = build(layout, num_rounds);
st.x_edges = model.for_x_errors.graph.edges.len();
st.z_edges = model.for_z_errors.graph.edges.len();
st
}
pub fn build(layout: &CircuitLayout, num_rounds: usize) -> CircuitModel {
let rounds_total = rounds_executed(num_rounds);
let layers = detector_layers(num_rounds);
let num_x_nodes = layout.num_x_stabs * layers;
let num_z_nodes = layout.num_z_stabs * layers;
let noise_slots = layout.noise_slots();
let erasure_sites = noise_slots * num_rounds;
let mut for_x: Vec<(Vec<usize>, u128, usize)> = Vec::new();
let mut for_z: Vec<(Vec<usize>, u128, usize)> = Vec::new();
for round in 1..rounds_total - 1 {
for (fault, slot) in layout.fault_locations() {
let effect = layout.propagate(fault, round, rounds_total);
let site = slot
.map(|k| (round - 1) * noise_slots + k)
.unwrap_or(erasure_sites);
let dz = detectors(&effect.flips_z_stab, layout.num_z_stabs, rounds_total);
if !dz.is_empty() && dz.len() <= 2 {
for_x.push((dz, effect.residual_x, site));
}
let dx = detectors(&effect.flips_x_stab, layout.num_x_stabs, rounds_total);
if !dx.is_empty() && dx.len() <= 2 {
for_z.push((dx, effect.residual_z, site));
}
}
}
CircuitModel {
for_x_errors: assemble(for_x, num_z_nodes, erasure_sites + 1),
for_z_errors: assemble(for_z, num_x_nodes, erasure_sites + 1),
num_rounds,
noise_slots,
erasure_sites,
}
}
pub struct CombinedGraph {
pub graph: SyndromeGraph,
pub correction_x: Vec<u128>,
pub correction_z: Vec<u128>,
pub site_edges: Vec<Vec<usize>>,
}
pub struct CombinedModel {
pub graph: CombinedGraph,
pub num_rounds: usize,
pub noise_slots: usize,
pub erasure_sites: usize,
}
fn assemble_combined(
candidates: Vec<(Vec<usize>, u128, u128, usize)>,
num_nodes: usize,
num_sites: usize,
) -> CombinedGraph {
let endpoints = |nodes: &Vec<usize>| -> Option<(usize, usize)> {
match nodes.len() {
1 => Some((nodes[0], num_nodes)), 2 => Some((nodes[0], nodes[1])),
_ => None,
}
};
let mut tally: BTreeMap<(usize, usize), BTreeMap<(u128, u128), usize>> = BTreeMap::new();
for (nodes, cx, cz, _) in &candidates {
if let Some(pair) = endpoints(nodes) {
*tally.entry(pair).or_default().entry((*cx, *cz)).or_insert(0) += 1;
}
}
let mut edges = Vec::new();
let mut edge_to_qubit = Vec::new();
let mut correction_x = Vec::new();
let mut correction_z = Vec::new();
let mut edge_of_pair: BTreeMap<(usize, usize), usize> = BTreeMap::new();
for (&(u, v), corrs) in &tally {
let (&(cx, cz), _) = corrs
.iter()
.max_by_key(|(masks, count)| (**count, std::cmp::Reverse(**masks)))
.expect("a tallied pair has at least one correction");
let representative = if cx != 0 {
Some(cx.trailing_zeros() as usize)
} else if cz != 0 {
Some(cz.trailing_zeros() as usize)
} else {
None
};
edge_of_pair.insert((u, v), edges.len());
edges.push(Edge { u, v, id: edges.len() });
edge_to_qubit.push(representative);
correction_x.push(cx);
correction_z.push(cz);
}
let mut site_edges = vec![Vec::new(); num_sites];
for (nodes, _, _, site) in &candidates {
if let Some(pair) = endpoints(nodes) {
if let Some(&id) = edge_of_pair.get(&pair) {
if !site_edges[*site].contains(&id) {
site_edges[*site].push(id);
}
}
}
}
CombinedGraph {
graph: SyndromeGraph { num_nodes, edges, edge_to_qubit },
correction_x,
correction_z,
site_edges,
}
}
pub fn build_combined(layout: &CircuitLayout, num_rounds: usize) -> CombinedModel {
let rounds_total = rounds_executed(num_rounds);
let layers = detector_layers(num_rounds);
let num_nodes = layout.num_z_stabs * layers;
let noise_slots = layout.noise_slots();
let erasure_sites = noise_slots * num_rounds;
let mut candidates: Vec<(Vec<usize>, u128, u128, usize)> = Vec::new();
for round in 1..rounds_total - 1 {
for (fault, slot) in layout.fault_locations() {
if let Fault::Gate(_, 3) = fault {
continue; }
let effect = layout.propagate(fault, round, rounds_total);
let site = slot
.map(|k| (round - 1) * noise_slots + k)
.unwrap_or(erasure_sites);
let d = detectors(&effect.flips_z_stab, layout.num_z_stabs, rounds_total);
if !d.is_empty() && d.len() <= 2 {
candidates.push((d, effect.residual_x, effect.residual_z, site));
}
}
}
CombinedModel {
graph: assemble_combined(candidates, num_nodes, erasure_sites + 1),
num_rounds,
noise_slots,
erasure_sites,
}
}
pub fn correction_for_combined(
cg: &CombinedGraph,
defects: &[bool],
decoder_type: usize,
erased: &[bool],
) -> (u128, u128) {
let chosen = match decoder_type {
1 => crate::decoder::decode_greedy(&cg.graph, defects, erased),
2 => crate::decoder::decode_mwpm(&cg.graph, defects, erased),
_ => crate::decoder::decode_union_find(&cg.graph, defects, erased),
};
let (mut mx, mut mz) = (0u128, 0u128);
for edge_idx in chosen {
mx ^= cg.correction_x[edge_idx];
mz ^= cg.correction_z[edge_idx];
}
(mx, mz)
}
pub fn stats_combined(layout: &CircuitLayout, num_rounds: usize) -> ([usize; 5], usize) {
let rounds_total = rounds_executed(num_rounds);
let mut buckets = [0usize; 5];
for round in 1..rounds_total - 1 {
for (fault, _) in layout.fault_locations() {
if let Fault::Gate(_, 3) = fault {
continue;
}
let effect = layout.propagate(fault, round, rounds_total);
let d = detectors(&effect.flips_z_stab, layout.num_z_stabs, rounds_total);
buckets[d.len().min(4)] += 1;
}
}
let edges = build_combined(layout, num_rounds).graph.graph.edges.len();
(buckets, edges)
}
pub fn single_fault_failures_combined(
layout: &CircuitLayout,
model: &CombinedModel,
num_rounds: usize,
decoder_type: usize,
is_logical: &dyn Fn(u128, u128) -> bool,
) -> (usize, usize) {
let rounds_total = rounds_executed(num_rounds);
let mut tested = 0usize;
let mut failures = 0usize;
let none = vec![false; model.graph.graph.edges.len()];
for round in 1..rounds_total - 1 {
for (fault, _) in layout.fault_locations() {
let effect = layout.propagate(fault, round, rounds_total);
tested += 1;
let d = detectors(&effect.flips_z_stab, layout.num_z_stabs, rounds_total);
let mut defects = vec![false; model.graph.graph.num_nodes];
for &n in &d { defects[n] = true; }
let (cx, cz) = correction_for_combined(&model.graph, &defects, decoder_type, &none);
if is_logical(effect.residual_x ^ cx, effect.residual_z ^ cz) {
failures += 1;
}
}
}
(tested, failures)
}