use std::collections::HashMap;
use crate::bp::{Bp, BpWork, Method};
use crate::dem::Dem;
use crate::dem_decoder::{int_weight, merged_edges, DecodeError};
use crate::sparse::{Scratch, SparseGraph};
const EPS: f64 = 1e-14;
pub struct BeliefMatching {
pub bp: Bp,
pub graph: SparseGraph,
hyper_obs: Vec<u64>,
hyper_start: Vec<u32>,
hyper_edges: Vec<u32>,
pub method: Method,
pub max_iter: usize,
}
pub struct BeliefWork {
pub bp: BpWork,
scratch: Scratch,
syndrome: Vec<u8>,
edge_p: Vec<f64>,
edge_w: Vec<i64>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct BeliefOutcome {
pub observables: u64,
pub weight: f64,
pub converged: bool,
pub iterations: usize,
}
impl BeliefMatching {
pub fn from_dem(dem: &Dem, method: Method, max_iter: usize) -> Result<BeliefMatching, String> {
let (edges, _) = merged_edges(dem)?;
let graph = SparseGraph::from_edges(dem.num_detectors, &edges);
let boundary = dem.num_detectors as u32;
let mut ids: HashMap<Vec<u32>, usize> = HashMap::new();
let (mut columns, mut priors, mut obs) = (Vec::new(), Vec::new(), Vec::new());
let mut decomposition: Vec<Vec<u32>> = Vec::new();
for m in &dem.mechanisms {
let id = *ids.entry(m.detectors.clone()).or_insert_with(|| {
columns.push(m.detectors.clone());
priors.push(0.0);
obs.push(0);
let mut edges: Vec<u32> = m
.pieces
.iter()
.filter_map(|piece| match piece.detectors.as_slice() {
[a] => graph.edge_id(*a, boundary),
[a, b] => graph.edge_id(*a, *b),
_ => None,
})
.collect();
edges.sort_unstable();
edges.dedup();
decomposition.push(edges);
columns.len() - 1
});
let q: f64 = priors[id];
priors[id] = q * (1.0 - m.p) + m.p * (1.0 - q);
obs[id] = m.observables;
}
let bp = Bp::new(dem.num_detectors, &columns, &priors)?;
let mut hyper_start = vec![0u32];
let mut hyper_edges = Vec::new();
for d in decomposition {
hyper_edges.extend(d);
hyper_start.push(hyper_edges.len() as u32);
}
Ok(BeliefMatching { bp, graph, hyper_obs: obs, hyper_start, hyper_edges, method, max_iter })
}
pub fn work(&self) -> BeliefWork {
BeliefWork {
bp: self.bp.work(),
scratch: Scratch::new(&self.graph),
syndrome: vec![0; self.bp.num_checks],
edge_p: vec![0.0; self.graph.num_edges()],
edge_w: vec![0; self.graph.num_edges()],
}
}
pub fn decode(&self, defects: &[u32], w: &mut BeliefWork) -> Result<BeliefOutcome, DecodeError> {
w.syndrome.fill(0);
for &d in defects {
w.syndrome[d as usize] = 1;
}
let out = self.bp.decode(&w.syndrome, self.method, self.max_iter, &mut w.bp);
if out.converged {
let observables = (0..self.bp.num_vars).filter(|&v| w.bp.hard[v] == 1).fold(0u64, |o, v| o ^ self.hyper_obs[v]);
return Ok(BeliefOutcome { observables, weight: f64::NAN, converged: true, iterations: out.iterations });
}
w.edge_p.fill(0.0);
for h in 0..self.bp.num_vars {
let p = 1.0 / (1.0 + w.bp.llr[h].exp());
if p.is_nan() {
return Err(DecodeError::MatcherDeclined);
}
for &e in &self.hyper_edges[self.hyper_start[h] as usize..self.hyper_start[h + 1] as usize] {
w.edge_p[e as usize] += p;
}
}
for (wt, &p) in w.edge_w.iter_mut().zip(&w.edge_p) {
*wt = int_weight(-p.clamp(EPS, 1.0 - EPS).ln());
}
let prediction = self.graph.decode_with_weights(&mut w.scratch, defects, &w.edge_w)?;
Ok(BeliefOutcome { observables: prediction.observables, weight: prediction.weight, converged: false, iterations: out.iterations })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::circuit::Basis;
use crate::dem_decoder::DemDecoder;
use crate::frame_sampler::FrameSampler;
use crate::memory::{generate, CodeKind, NoiseModel};
use crate::surface_code::Xorshift;
fn defects_of(dets: &[bool]) -> Vec<u32> {
dets.iter().enumerate().filter(|x| *x.1).map(|x| x.0 as u32).collect()
}
fn model(kind: CodeKind, d: usize, p: f64, basis: Basis) -> (crate::circuit::Circuit, Dem) {
let c = generate(kind, d, d, NoiseModel::Sd6 { p }, basis).unwrap();
let dem = Dem::from_circuit(&c).unwrap();
(c, dem)
}
#[test]
fn every_single_fault_is_corrected() {
for kind in [CodeKind::Rotated, CodeKind::Xzzx] {
for basis in [Basis::Z, Basis::X] {
let (_, dem) = model(kind, 3, 0.004, basis);
let bm = BeliefMatching::from_dem(&dem, Method::ProductSum, 20).unwrap();
let mut w = bm.work();
let mut failed = 0;
for m in &dem.mechanisms {
let got = bm.decode(&m.detectors, &mut w).unwrap();
failed += usize::from(got.observables != m.observables);
}
assert_eq!(failed, 0, "{kind:?} {basis:?}: {failed} of {} faults", dem.mechanisms.len());
}
}
}
#[test]
fn decoding_with_the_models_weights_is_plain_decoding() {
let (c, dem) = model(CodeKind::Rotated, 5, 0.006, Basis::Z);
let dec = DemDecoder::new(&dem).unwrap();
let g = dec.graph();
let own: Vec<i64> = (0..g.num_edges() as u32).map(|e| g.weight_of(e)).collect();
let sampler = FrameSampler::new(&c).unwrap();
let mut rng = Xorshift::new(3);
let mut s = Scratch::new(g);
for _ in 0..300 {
let defects = defects_of(&sampler.sample(&mut rng).detectors);
let a = g.decode(&mut s, &defects).unwrap();
let b = g.decode_with_weights(&mut s, &defects, &own).unwrap();
assert_eq!((a.observables, a.iweight), (b.observables, b.iweight));
}
}
#[test]
fn belief_matching_does_not_lose_to_matching() {
let (c, dem) = model(CodeKind::Rotated, 5, 0.006, Basis::Z);
let dec = DemDecoder::new(&dem).unwrap();
let bm = BeliefMatching::from_dem(&dem, Method::ProductSum, 20).unwrap();
let mut w = bm.work();
let sampler = FrameSampler::new(&c).unwrap();
let mut rng = Xorshift::new(4);
let (mut plain, mut belief, mut converged) = (0i64, 0i64, 0);
let shots = 3000;
for _ in 0..shots {
let shot = sampler.sample(&mut rng);
let defects = defects_of(&shot.detectors);
plain += ((dec.decode(&defects).unwrap().observables ^ shot.observables) & 1) as i64;
let b = bm.decode(&defects, &mut w).unwrap();
converged += usize::from(b.converged);
belief += ((b.observables ^ shot.observables) & 1) as i64;
}
println!("plain {plain}, belief-matching {belief}, BP converged on {converged} of {shots}");
assert!(belief <= plain + 4 * ((plain + belief) as f64).sqrt() as i64, "{belief} vs {plain}");
}
}