use super::{probability, BitTable, DetectorErrorModel, Error, Result};
use crate::batch::{belief_shots, bposd_shots, match_shots, window_info, window_shots};
use crate::dem_decoder::{DecodeError, DemDecoder};
use crate::sparse::{Correlations, Scratch, SparseGraph};
use crate::window::{Mode, Model, WindowDecoder};
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub struct Prediction {
pub observables: u64,
pub weight: Option<f64>,
pub bp_converged: Option<bool>,
}
impl Prediction {
pub fn flips(&self, k: usize) -> bool {
k < 64 && (self.observables >> k) & 1 == 1
}
}
fn decode_error(e: DecodeError) -> Error {
Error::new(match e {
DecodeError::Unmatchable => "no matching explains the detection events (a defect with no path to a partner or the boundary)".to_string(),
other => format!("the matcher declined the shot ({other:?})"),
})
}
fn check_width(shots: &BitTable, num_detectors: usize) -> Result<()> {
if shots.num_bits() != num_detectors {
return Err(Error::new(format!("{} bits per shot, not the model's {num_detectors} detectors", shots.num_bits())));
}
Ok(())
}
fn check_defects(defects: &[u32], num_detectors: usize) -> Result<()> {
match defects.iter().find(|&&d| d as usize >= num_detectors) {
Some(d) => Err(Error::new(format!("detector {d} is beyond the model's {num_detectors}"))),
None => Ok(()),
}
}
pub struct Matching {
graph: SparseGraph,
corr: Option<Correlations>,
num_detectors: usize,
num_observables: usize,
}
impl Matching {
pub fn new(dem: &DetectorErrorModel) -> Result<Matching> {
Matching::build(dem, false)
}
pub fn with_correlations(dem: &DetectorErrorModel) -> Result<Matching> {
Matching::build(dem, true)
}
fn build(dem: &DetectorErrorModel, correlated: bool) -> Result<Matching> {
let (graph, corr) = DemDecoder::new(&dem.inner)?.into_parts(correlated);
Ok(Matching { graph, corr, num_detectors: dem.num_detectors(), num_observables: dem.num_observables() })
}
pub fn num_detectors(&self) -> usize {
self.num_detectors
}
pub fn num_observables(&self) -> usize {
self.num_observables
}
pub fn decode(&self, defects: &[u32]) -> Result<Prediction> {
check_defects(defects, self.num_detectors)?;
let mut sorted = defects.to_vec();
sorted.sort_unstable();
sorted.dedup();
let mut scratch = Scratch::new(&self.graph);
let p = match &self.corr {
Some(corr) => self.graph.decode_correlated(corr, &mut scratch, &sorted),
None => self.graph.decode(&mut scratch, &sorted),
}
.map_err(decode_error)?;
Ok(Prediction { observables: p.observables, weight: Some(p.weight), bp_converged: None })
}
pub fn decode_batch(&self, shots: &BitTable, threads: usize) -> Result<Vec<Prediction>> {
check_width(shots, self.num_detectors)?;
let out = match_shots(&self.graph, self.corr.as_ref(), shots.as_bytes(), self.num_detectors, shots.num_rows(), threads);
if let Some(s) = out.iter().position(|(_, w)| w.is_nan()) {
return Err(Error::new(format!("shot {s}: no matching explains its detection events")));
}
Ok(out.into_iter().map(|(o, w)| Prediction { observables: o, weight: Some(w), bp_converged: None }).collect())
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub enum BpMethod {
ProductSum,
MinimumSum {
scaling_factor: f64,
},
}
impl BpMethod {
fn engine(self) -> crate::bp::Method {
match self {
BpMethod::ProductSum => crate::bp::Method::ProductSum,
BpMethod::MinimumSum { scaling_factor } => crate::bp::Method::MinSum { scale: scaling_factor },
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct BpOptions {
max_iter: usize,
method: BpMethod,
}
impl BpOptions {
pub fn new(max_iter: usize, method: BpMethod) -> BpOptions {
BpOptions { max_iter, method }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum OsdMethod {
Osd0,
Exhaustive(usize),
CombinationSweep(usize),
}
impl OsdMethod {
fn engine(self) -> crate::osd::OsdMethod {
match self {
OsdMethod::Osd0 => crate::osd::OsdMethod::Osd0,
OsdMethod::Exhaustive(k) => crate::osd::OsdMethod::Exhaustive(k),
OsdMethod::CombinationSweep(k) => crate::osd::OsdMethod::CombinationSweep(k),
}
}
}
pub struct BeliefMatching {
inner: crate::belief::BeliefMatching,
num_detectors: usize,
}
impl BeliefMatching {
pub fn new(dem: &DetectorErrorModel, bp: BpOptions) -> Result<BeliefMatching> {
let inner = crate::belief::BeliefMatching::from_dem(&dem.inner, bp.method.engine(), bp.max_iter)?;
Ok(BeliefMatching { inner, num_detectors: dem.num_detectors() })
}
pub fn decode(&self, defects: &[u32]) -> Result<Prediction> {
check_defects(defects, self.num_detectors)?;
let mut sorted = defects.to_vec();
sorted.sort_unstable();
sorted.dedup();
let o = self.inner.decode(&sorted, &mut self.inner.work()).map_err(decode_error)?;
Ok(Prediction { observables: o.observables, weight: (!o.converged).then_some(o.weight), bp_converged: Some(o.converged) })
}
pub fn decode_batch(&self, shots: &BitTable, threads: usize) -> Result<Vec<Prediction>> {
check_width(shots, self.num_detectors)?;
let out = belief_shots(&self.inner, shots.as_bytes(), self.num_detectors, shots.num_rows(), threads);
if let Some(s) = out.iter().position(|x| x.2 == 2) {
return Err(Error::new(format!("shot {s}: no correction explains its detection events")));
}
Ok(out
.into_iter()
.map(|(o, w, c)| Prediction { observables: o, weight: (c == 0).then_some(w), bp_converged: Some(c == 1) })
.collect())
}
}
pub struct BpOsd {
inner: crate::osd::BpOsd,
observables: Vec<u64>,
num_detectors: usize,
}
impl BpOsd {
pub fn new(dem: &DetectorErrorModel, bp: BpOptions, osd: OsdMethod) -> Result<BpOsd> {
let d = &dem.inner;
let columns: Vec<Vec<u32>> = d.mechanisms.iter().map(|m| m.detectors.clone()).collect();
let priors: Vec<f64> = d.mechanisms.iter().map(|m| m.p).collect();
let inner = crate::osd::BpOsd::new(d.num_detectors, columns, &priors, bp.method.engine(), bp.max_iter, osd.engine())?;
Ok(BpOsd { inner, observables: d.mechanisms.iter().map(|m| m.observables).collect(), num_detectors: d.num_detectors })
}
pub fn decode(&self, defects: &[u32]) -> Result<Prediction> {
check_defects(defects, self.num_detectors)?;
let mut row = BitTable::zeros(1, self.num_detectors);
for &d in defects {
row.set(0, d as usize, true);
}
Ok(self.decode_batch(&row, 1)?.remove(0))
}
pub fn decode_batch(&self, shots: &BitTable, threads: usize) -> Result<Vec<Prediction>> {
check_width(shots, self.num_detectors)?;
let out = bposd_shots(&self.inner, &self.observables, shots.as_bytes(), self.num_detectors, shots.num_rows(), threads);
Ok(out.into_iter().map(|(o, c)| Prediction { observables: o, weight: None, bp_converged: Some(c == 1) }).collect())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum WindowMode {
Sliding,
Parallel,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WindowOptions {
commit: usize,
buffer: usize,
mode: WindowMode,
correlations: bool,
}
impl WindowOptions {
pub fn new(commit: usize, buffer: usize, mode: WindowMode) -> WindowOptions {
WindowOptions { commit, buffer, mode, correlations: false }
}
pub fn with_correlations(mut self, yes: bool) -> WindowOptions {
self.correlations = yes;
self
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Window {
pub first_layer: u32,
pub end_layer: u32,
pub commit_start: u32,
pub commit_end: u32,
pub phase: usize,
pub waits_for: Vec<usize>,
}
pub struct WindowMatching {
inner: WindowDecoder,
correlations: bool,
num_detectors: usize,
}
impl WindowMatching {
pub fn new(dem: &DetectorErrorModel, options: WindowOptions) -> Result<WindowMatching> {
let mode = match options.mode {
WindowMode::Sliding => Mode::Sliding,
WindowMode::Parallel => Mode::Parallel,
};
let inner = WindowDecoder::new(Model::new(&dem.inner)?, options.commit, options.buffer, mode)?;
Ok(WindowMatching { inner, correlations: options.correlations, num_detectors: dem.num_detectors() })
}
pub fn windows(&self) -> Vec<Window> {
window_info(&self.inner)
.into_iter()
.map(|(a, b, c0, c1, phase, waits_for)| Window { first_layer: a, end_layer: b, commit_start: c0, commit_end: c1, phase, waits_for })
.collect()
}
pub fn decode_batch(&self, shots: &BitTable, threads: usize) -> Result<Vec<Prediction>> {
check_width(shots, self.num_detectors)?;
let out = window_shots(&self.inner, shots.as_bytes(), self.num_detectors, shots.num_rows(), self.correlations, threads, false);
if let Some(s) = out.iter().position(|x| x.3) {
return Err(Error::new(format!("shot {s}: a window found no matching")));
}
if out.iter().any(|x| x.1 > 0) {
return Err(Error::new("internal error: window commits left defects unexplained; please report it"));
}
Ok(out.into_iter().map(|x| Prediction { observables: x.0, weight: None, bp_converged: None }).collect())
}
}
fn check_matrix(num_checks: usize, columns: &[Vec<u32>], priors: &[f64]) -> Result<()> {
if columns.len() != priors.len() {
return Err(Error::new(format!("{} columns but {} priors", columns.len(), priors.len())));
}
for (j, &p) in priors.iter().enumerate() {
probability(p, &format!("prior {j}"))?;
}
if let Some((j, r)) = columns.iter().enumerate().find_map(|(j, c)| c.iter().find(|&&r| r as usize >= num_checks).map(|r| (j, r))) {
return Err(Error::new(format!("column {j} touches check {r}, beyond {num_checks}")));
}
Ok(())
}
fn syndrome_bytes(syndrome: &[bool], num_checks: usize) -> Result<Vec<u8>> {
if syndrome.len() != num_checks {
return Err(Error::new(format!("{} syndrome bits for {num_checks} checks", syndrome.len())));
}
Ok(syndrome.iter().map(|&b| u8::from(b)).collect())
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct BpOutcome {
pub correction: Vec<bool>,
pub log_prob_ratios: Vec<f64>,
pub converged: bool,
pub iterations: usize,
}
pub struct BpDecoder {
inner: crate::bp::Bp,
options: BpOptions,
num_checks: usize,
}
impl BpDecoder {
pub fn new(num_checks: usize, columns: &[Vec<u32>], priors: &[f64], options: BpOptions) -> Result<BpDecoder> {
check_matrix(num_checks, columns, priors)?;
Ok(BpDecoder { inner: crate::bp::Bp::new(num_checks, columns, priors)?, options, num_checks })
}
pub fn decode(&self, syndrome: &[bool]) -> Result<BpOutcome> {
let s = syndrome_bytes(syndrome, self.num_checks)?;
let mut w = self.inner.work();
let out = self.inner.decode(&s, self.options.method.engine(), self.options.max_iter, &mut w);
Ok(BpOutcome {
correction: w.hard.iter().map(|&b| b != 0).collect(),
log_prob_ratios: w.llr.clone(),
converged: out.converged,
iterations: out.iterations,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct BpOsdOutcome {
pub correction: Vec<bool>,
pub converged: bool,
pub iterations: usize,
}
pub struct BpOsdDecoder {
inner: crate::osd::BpOsd,
num_checks: usize,
}
impl BpOsdDecoder {
pub fn new(num_checks: usize, columns: &[Vec<u32>], priors: &[f64], bp: BpOptions, osd: OsdMethod) -> Result<BpOsdDecoder> {
check_matrix(num_checks, columns, priors)?;
let inner = crate::osd::BpOsd::new(num_checks, columns.to_vec(), priors, bp.method.engine(), bp.max_iter, osd.engine())?;
Ok(BpOsdDecoder { inner, num_checks })
}
pub fn decode(&self, syndrome: &[bool]) -> Result<BpOsdOutcome> {
let s = syndrome_bytes(syndrome, self.num_checks)?;
let mut w = self.inner.work();
let out = self.inner.decode(&s, &mut w);
Ok(BpOsdOutcome { correction: w.correction.iter().map(|&b| b != 0).collect(), converged: out.converged, iterations: out.iterations })
}
}