use super::{probability, BitTable, DetectorErrorModel, Error, Result};
use crate::batch::{StreamedWindows, belief_shots, bposd_shots, match_shots, streamed_window_shots, syndrome_shots, union_find_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.flat()?)?.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 UnionFind {
graph: SparseGraph,
num_detectors: usize,
num_observables: usize,
}
impl UnionFind {
pub fn new(dem: &DetectorErrorModel) -> Result<UnionFind> {
let (graph, _) = DemDecoder::new(dem.flat()?)?.into_parts(false);
Ok(UnionFind { graph, 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 = self.graph.union_find_scratch();
let o = self
.graph
.decode_union_find(&mut scratch, &sorted)
.ok_or_else(|| Error::new("no correction explains the detection events (an odd component with no boundary)"))?;
Ok(Prediction { observables: o, weight: None, bp_converged: None })
}
pub fn decode_batch(&self, shots: &BitTable, threads: usize) -> Result<Vec<Prediction>> {
check_width(shots, self.num_detectors)?;
let out = union_find_shots(&self.graph, shots.as_bytes(), self.num_detectors, shots.num_rows(), threads);
out.into_iter()
.enumerate()
.map(|(s, o)| match o {
Some(observables) => Ok(Prediction { observables, weight: None, bp_converged: None }),
None => Err(Error::new(format!("shot {s}: no correction explains its detection events"))),
})
.collect()
}
}
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.flat()?, 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.flat()?;
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)]
pub struct LsdOptions {
method: OsdMethod,
bits_per_step: usize,
always_run: bool,
}
impl LsdOptions {
pub fn new(method: OsdMethod) -> LsdOptions {
LsdOptions { method, bits_per_step: 1, always_run: false }
}
pub fn with_bits_per_step(mut self, bits: usize) -> LsdOptions {
self.bits_per_step = bits;
self
}
pub fn with_always_run(mut self, yes: bool) -> LsdOptions {
self.always_run = yes;
self
}
fn engine(&self, num_checks: usize, columns: Vec<Vec<u32>>, priors: &[f64], bp: BpOptions) -> Result<crate::lsd::BpLsd> {
Ok(crate::lsd::BpLsd::new(num_checks, columns, priors, bp.method.engine(), bp.max_iter, self.method.engine(), self.bits_per_step, self.always_run)?)
}
}
pub struct BpLsd {
inner: crate::lsd::BpLsd,
observables: Vec<u64>,
num_detectors: usize,
}
impl BpLsd {
pub fn new(dem: &DetectorErrorModel, bp: BpOptions, lsd: LsdOptions) -> Result<BpLsd> {
let d = dem.flat()?;
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 = lsd.engine(d.num_detectors, columns, &priors, bp)?;
Ok(BpLsd { 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 = syndrome_shots(&self.inner, &self.observables, shots.as_bytes(), self.num_detectors, shots.num_rows(), threads);
if let Some(s) = out.iter().position(|x| x.1 == 2) {
return Err(Error::new(format!("shot {s}: no correction explains the detection events")));
}
Ok(out.into_iter().map(|(o, c)| Prediction { observables: o, weight: None, bp_converged: Some(c == 1) }).collect())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct RelayOptions {
config: crate::relay::RelayConfig,
}
impl Default for RelayOptions {
fn default() -> Self {
RelayOptions::new()
}
}
impl RelayOptions {
pub fn new() -> RelayOptions {
RelayOptions {
config: crate::relay::RelayConfig {
pre_iter: 60,
legs: 60,
leg_iter: 60,
solutions: Some(5),
gamma0: Some(0.1),
gamma_range: (-0.24, 0.66),
gammas: None,
alpha: None,
alpha_scaling: 1.0,
seed: 0,
..crate::relay::RelayConfig::default()
},
}
}
pub fn legs(mut self, legs: usize) -> Self {
self.config.legs = legs;
self
}
pub fn solutions(mut self, solutions: Option<usize>) -> Self {
self.config.solutions = solutions;
self
}
pub fn pre_iterations(mut self, iterations: usize) -> Self {
self.config.pre_iter = iterations;
self
}
pub fn iterations(mut self, iterations: usize) -> Self {
self.config.leg_iter = iterations;
self
}
pub fn gamma0(mut self, gamma0: Option<f64>) -> Self {
self.config.gamma0 = gamma0;
self
}
pub fn gamma_range(mut self, low: f64, high: f64) -> Self {
self.config.gamma_range = (low, high);
self
}
pub fn gammas(mut self, rows: Vec<Vec<f64>>) -> Self {
self.config.gammas = Some(rows);
self
}
pub fn alpha(mut self, alpha: Option<f64>) -> Self {
self.config.alpha = alpha;
self
}
pub fn seed(mut self, seed: u64) -> Self {
self.config.seed = seed;
self
}
}
pub struct RelayBp {
inner: crate::relay::Relay,
observables: Vec<u64>,
num_detectors: usize,
}
impl RelayBp {
pub fn new(dem: &DetectorErrorModel, options: RelayOptions) -> Result<RelayBp> {
let d = dem.flat()?;
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::relay::Relay::new(d.num_detectors, &columns, &priors, options.config)?;
Ok(RelayBp { 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 = crate::batch::relay_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())
}
}
pub struct ColorMatching {
inner: crate::color::ColorDecoder,
num_detectors: usize,
}
impl ColorMatching {
pub fn new(dem: &DetectorErrorModel) -> Result<ColorMatching> {
ColorMatching::build(dem, false)
}
pub fn ignoring_decomposition_failures(dem: &DetectorErrorModel) -> Result<ColorMatching> {
ColorMatching::build(dem, true)
}
fn build(dem: &DetectorErrorModel, ignore: bool) -> Result<ColorMatching> {
let d = dem.flat()?;
Ok(ColorMatching { inner: crate::color::ColorDecoder::from_dem(d, ignore)?, num_detectors: d.num_detectors })
}
pub fn mobius_model(&self) -> Result<DetectorErrorModel> {
DetectorErrorModel::parse(self.inner.mobius_text())
}
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 = crate::batch::color_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 lifting of the matching explains its detection events (are the colour annotations right?)")));
}
Ok(out.into_iter().map(|(o, w, _)| Prediction { observables: o, weight: Some(w), bp_converged: None }).collect())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum DetectorOrder {
Index,
Bfs,
Coordinate,
}
#[derive(Clone, Debug, PartialEq)]
pub struct SearchOptions {
beam: Option<usize>,
beam_climbing: bool,
no_revisit: bool,
queue_limit: Option<usize>,
orders: Option<Vec<Vec<u32>>>,
method: DetectorOrder,
num_orders: usize,
seed: u64,
penalty: f64,
merge: bool,
}
impl Default for SearchOptions {
fn default() -> Self {
SearchOptions::new()
}
}
impl SearchOptions {
pub fn new() -> SearchOptions {
SearchOptions { beam: Some(5), beam_climbing: false, no_revisit: true, queue_limit: Some(200_000), orders: None, method: DetectorOrder::Index, num_orders: 20, seed: 2384753, penalty: 0.0, merge: true }
}
pub fn beam(mut self, beam: Option<usize>) -> Self {
self.beam = beam;
self
}
pub fn beam_climbing(mut self, yes: bool) -> Self {
self.beam_climbing = yes;
self
}
pub fn no_revisit(mut self, yes: bool) -> Self {
self.no_revisit = yes;
self
}
pub fn queue_limit(mut self, limit: Option<usize>) -> Self {
self.queue_limit = limit;
self
}
pub fn detector_orders(mut self, orders: Vec<Vec<u32>>) -> Self {
self.orders = Some(orders);
self
}
pub fn generated_orders(mut self, method: DetectorOrder, count: usize, seed: u64) -> Self {
self.orders = None;
self.method = method;
self.num_orders = count;
self.seed = seed;
self
}
pub fn detector_penalty(mut self, penalty: f64) -> Self {
self.penalty = penalty;
self
}
pub fn merge_errors(mut self, yes: bool) -> Self {
self.merge = yes;
self
}
}
pub struct SearchDecoder {
inner: crate::search::Search,
num_detectors: usize,
}
impl SearchDecoder {
pub fn new(dem: &DetectorErrorModel, options: SearchOptions) -> Result<SearchDecoder> {
let d = dem.flat()?;
let flavour = crate::search::Flavour::native();
let orders = match options.orders {
Some(o) => o,
None => {
if options.num_orders == 0 {
return Err(Error::new("at least one detector order is needed"));
}
let method = match options.method {
DetectorOrder::Index => crate::search::OrderMethod::Index,
DetectorOrder::Bfs => crate::search::OrderMethod::Bfs,
DetectorOrder::Coordinate => crate::search::OrderMethod::Coordinate,
};
crate::search::generated_orders(d, options.num_orders, method, options.seed, flavour)
}
};
let config = crate::search::SearchConfig {
beam: options.beam,
beam_climbing: options.beam_climbing,
no_revisit: options.no_revisit,
queue_limit: options.queue_limit,
orders,
detector_penalty: options.penalty,
merge_errors: options.merge,
flavour,
sparsify: None,
visualize: false,
verbose: false,
};
Ok(SearchDecoder { inner: crate::search::Search::new(d, config)?, 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_to_faults(&self, defects: &[u32]) -> Result<Option<Vec<usize>>> {
check_defects(defects, self.num_detectors)?;
let mut sorted = defects.to_vec();
sorted.sort_unstable();
sorted.dedup();
let found = self.inner.decode(&sorted);
Ok((!found.low_confidence).then_some(found.faults))
}
pub fn decode_batch(&self, shots: &BitTable, threads: usize) -> Result<Vec<Prediction>> {
check_width(shots, self.num_detectors)?;
let out = crate::batch::search_shots(&self.inner, shots.as_bytes(), self.num_detectors, shots.num_rows(), threads);
Ok(out.into_iter().map(|(o, c, gave_up)| Prediction { observables: if gave_up { 0 } else { o }, weight: (!gave_up).then_some(c), bp_converged: None }).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,
template: Option<bool>,
}
impl Window {
pub(crate) fn from_info((a, b, c0, c1, phase, waits_for): crate::batch::WindowInfo) -> Window {
Window { first_layer: a, end_layer: b, commit_start: c0, commit_end: c1, phase, waits_for }
}
}
impl WindowOptions {
pub(crate) fn parts(self) -> (usize, usize, WindowMode, bool) {
(self.commit, self.buffer, self.mode, self.correlations)
}
pub fn new(commit: usize, buffer: usize, mode: WindowMode) -> WindowOptions {
WindowOptions { commit, buffer, mode, correlations: false, template: None }
}
pub fn with_template(mut self, template: Option<bool>) -> WindowOptions {
self.template = template;
self
}
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: Windows,
correlations: bool,
num_detectors: usize,
}
enum Windows {
Whole(Box<WindowDecoder>),
Streamed(Box<StreamedWindows>),
}
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 streamed = || StreamedWindows::new(dem.program(), options.commit, options.buffer, mode).map(Box::new);
let inner = match options.template {
Some(true) => Windows::Streamed(streamed()?),
None if dem.num_errors() > 1_000_000 => match streamed() {
Ok(s) => Windows::Streamed(s),
Err(_) => Windows::Whole(Box::new(WindowDecoder::new(Model::new(dem.flat()?)?, options.commit, options.buffer, mode)?)),
},
_ => Windows::Whole(Box::new(WindowDecoder::new(Model::new(dem.flat()?)?, options.commit, options.buffer, mode)?)),
};
Ok(WindowMatching { inner, correlations: options.correlations, num_detectors: dem.num_detectors() })
}
pub fn windows(&self) -> Vec<Window> {
let info = match &self.inner {
Windows::Whole(wd) => window_info(wd),
Windows::Streamed(sw) => sw.windows(),
};
info.into_iter().map(Window::from_info).collect()
}
pub fn decode_batch(&self, shots: &BitTable, threads: usize) -> Result<Vec<Prediction>> {
check_width(shots, self.num_detectors)?;
let out = match &self.inner {
Windows::Whole(wd) => window_shots(wd, shots.as_bytes(), self.num_detectors, shots.num_rows(), self.correlations, threads, false),
Windows::Streamed(sw) => streamed_window_shots(sw, 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 })
}
}
pub struct BpLsdDecoder {
inner: crate::lsd::BpLsd,
num_checks: usize,
}
impl BpLsdDecoder {
pub fn new(num_checks: usize, columns: &[Vec<u32>], priors: &[f64], bp: BpOptions, lsd: LsdOptions) -> Result<BpLsdDecoder> {
check_matrix(num_checks, columns, priors)?;
Ok(BpLsdDecoder { inner: lsd.engine(num_checks, columns.to_vec(), priors, bp)?, 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);
if !out.solved {
return Err(Error::new("no correction explains the syndrome"));
}
Ok(BpOsdOutcome { correction: w.correction.iter().map(|&b| b != 0).collect(), converged: out.converged, iterations: out.iterations })
}
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct RelayOutcome {
pub correction: Vec<bool>,
pub converged: bool,
pub iterations: usize,
pub legs: usize,
pub weight: f64,
}
pub struct RelayBpDecoder {
inner: crate::relay::Relay,
work: std::sync::Mutex<crate::relay::RelayWork>,
}
impl RelayBpDecoder {
pub fn new(num_checks: usize, columns: &[Vec<u32>], priors: &[f64], options: RelayOptions) -> Result<RelayBpDecoder> {
check_matrix(num_checks, columns, priors)?;
let inner = crate::relay::Relay::new(num_checks, columns, priors, options.config)?;
let work = std::sync::Mutex::new(inner.work());
Ok(RelayBpDecoder { inner, work })
}
pub fn decode(&self, syndrome: &[bool]) -> Result<RelayOutcome> {
let s = syndrome_bytes(syndrome, self.inner.num_checks)?;
let mut w = self.work.lock().unwrap_or_else(|e| e.into_inner());
let out = self.inner.decode(&s, &mut w);
Ok(RelayOutcome {
correction: w.correction.iter().map(|&b| b != 0).collect(),
converged: out.converged,
iterations: out.iterations,
legs: out.legs,
weight: if out.converged { out.weight } else { f64::INFINITY },
})
}
}