#![allow(clippy::useless_conversion)]
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::types::PyBytes;
use crate::batch_sampler::BatchSampler;
use crate::belief::BeliefMatching;
use crate::circuit::Circuit;
use crate::dem::Dem;
use crate::dem_decoder::DemDecoder;
use crate::m2d::M2d;
use crate::osd::{BpOsd, OsdMethod};
use crate::batch::{belief_shots, bposd_shots, match_shots, window_info, window_shots, WindowInfo};
use crate::window::{Mode, Model, WindowDecoder};
fn err(e: String) -> PyErr {
PyValueError::new_err(e)
}
pub(crate) fn check_rows(len: usize, stride: usize, num_shots: usize, bits: usize, what: &str) -> PyResult<()> {
if stride.checked_mul(num_shots) != Some(len) {
return Err(err(format!("{len} bytes is not {num_shots} shots of {bits} {what}")));
}
Ok(())
}
pub(crate) fn bits(xs: &[u8]) -> Vec<u32> {
xs.iter().map(|&b| u32::from(b)).collect()
}
fn le_u64(xs: impl IntoIterator<Item = u64>) -> Vec<u8> {
xs.into_iter().flat_map(u64::to_le_bytes).collect()
}
fn le_f64(xs: impl IntoIterator<Item = f64>) -> Vec<u8> {
xs.into_iter().flat_map(f64::to_le_bytes).collect()
}
#[pyclass(name = "Circuit", module = "stabilizer_qec._core")]
pub struct PyCircuit {
circuit: crate::api::Circuit,
}
type PyTarget = (String, Vec<f64>, String);
type PyLocation = (u64, Vec<PyTarget>, Option<(u64, Vec<PyTarget>)>, (String, String, Vec<f64>), (u32, u32), Vec<PyTarget>, Vec<(u64, u64, u64)>, String, String);
type PyExplained = (String, Vec<PyTarget>, Vec<PyLocation>);
fn explained_tuple(e: crate::api::ExplainedError) -> PyExplained {
let tw = |v: Vec<crate::api::TargetWithCoords>| v.into_iter().map(|t| (t.to_string(), t)).map(|(s, t)| (t.target, t.coords, s)).collect::<Vec<_>>();
let locations = e
.circuit_error_locations()
.into_iter()
.map(|l| {
let (text, instruction) = (l.to_string(), l.instruction_text().to_string());
(l.tick_offset, tw(l.flipped_pauli_product), l.flipped_measurement.map(|(i, o)| (i, tw(o))), (l.gate, l.tag, l.args), l.target_range, tw(l.targets_in_range), l.stack_frames, text, instruction)
})
.collect();
(e.to_string(), tw(e.terms_with_coords()), locations)
}
fn api_err(e: crate::api::Error) -> PyErr {
PyValueError::new_err(e.to_string())
}
impl PyCircuit {
pub(crate) fn engine(&self) -> &Circuit {
&self.circuit.inner
}
}
#[pymethods]
impl PyCircuit {
#[new]
fn new(text: &str) -> PyResult<Self> {
Ok(PyCircuit { circuit: crate::api::Circuit::parse(text).map_err(api_err)? })
}
#[getter]
fn num_qubits(&self) -> usize {
self.circuit.num_qubits()
}
#[getter]
fn num_sweep_bits(&self) -> usize {
self.circuit.num_sweep_bits()
}
#[getter]
fn num_measurements(&self) -> usize {
self.circuit.num_measurements()
}
#[getter]
fn num_detectors(&self) -> usize {
self.circuit.num_detectors()
}
#[getter]
fn num_observables(&self) -> usize {
self.circuit.num_observables()
}
fn __str__(&self) -> String {
self.circuit.to_string()
}
fn __eq__(&self, other: &PyCircuit) -> bool {
self.circuit.inner == other.circuit.inner
}
fn append_instruction(&mut self, name: &str, tag: &str, args: Vec<f64>, targets: Vec<String>) -> PyResult<()> {
let piece = Circuit::instruction(name, tag, &args, &targets).map_err(err)?;
let piece = crate::api::Circuit::from_engine(piece).map_err(api_err)?;
self.circuit.append_circuit(&piece).map_err(api_err)
}
fn append_text(&mut self, text: &str) -> PyResult<()> {
self.circuit.append_text(text).map_err(api_err)
}
fn append_circuit(&mut self, other: &PyCircuit) -> PyResult<()> {
self.circuit.append_circuit(&other.circuit).map_err(api_err)
}
fn repeated(&self, count: u64) -> PyResult<PyCircuit> {
Ok(PyCircuit { circuit: self.circuit.repeated(count).map_err(api_err)? })
}
#[pyo3(signature = (filter=None, reduce=false))]
fn explain(&self, py: Python<'_>, filter: Option<PyRef<'_, PyDem>>, reduce: bool) -> PyResult<Vec<PyExplained>> {
let filter = filter.map(|f| f.dem.clone());
let circuit = &self.circuit;
let out = py.detach(move || circuit.explain_errors(filter.as_ref(), reduce)).map_err(api_err)?;
Ok(out.into_iter().map(explained_tuple).collect())
}
fn shortest_graphlike(&self, py: Python<'_>, ignore: bool, canonicalize: bool) -> PyResult<Vec<PyExplained>> {
let circuit = &self.circuit;
let out = py.detach(move || circuit.shortest_graphlike_error(ignore, canonicalize)).map_err(api_err)?;
Ok(out.into_iter().map(explained_tuple).collect())
}
fn search_undetectable(&self, py: Python<'_>, max_symptoms: usize, max_degree: usize, no_increase: bool, canonicalize: bool) -> PyResult<Vec<PyExplained>> {
let circuit = &self.circuit;
let out = py.detach(move || circuit.search_for_undetectable_logical_errors(max_symptoms, max_degree, no_increase, canonicalize)).map_err(api_err)?;
Ok(out.into_iter().map(explained_tuple).collect())
}
#[pyo3(signature = (kind, tick=None, tick_end=None, rows=None))]
fn diagram(&self, kind: &str, tick: Option<u64>, tick_end: Option<u64>, rows: Option<u32>) -> PyResult<String> {
use crate::api::DiagramKind as K;
let need_tick = || tick.ok_or_else(|| err(format!("a {kind} diagram needs tick=")));
let range = || -> PyResult<(u64, u64)> {
let t = need_tick()?;
Ok((t, tick_end.unwrap_or(t + 1)))
};
let kind = match kind {
"timeline-text" => K::TimelineText,
"timeline-svg" => K::TimelineSvg,
"detslice-text" => K::DetectorSliceText { tick: need_tick()? },
"detslice-svg" if tick_end.is_none() && rows.is_none() => K::DetectorSliceSvg { tick: need_tick()? },
"detslice-svg" => K::DetectorSlicesSvg { ticks: range()?, rows },
"timeslice-svg" => K::TimeSliceSvg { ticks: range()?, rows },
"detslice-with-ops-svg" => K::DetectorSliceWithOpsSvg { ticks: range()?, rows },
"matchgraph-svg" => K::MatchGraphSvg,
other => return Err(err(format!("unknown diagram '{other}': timeline-text, timeline-svg, detslice-text, detslice-svg, timeslice-svg, detslice-with-ops-svg or matchgraph-svg"))),
};
self.circuit.diagram(kind).map_err(api_err)
}
fn copy(&self) -> PyCircuit {
PyCircuit { circuit: self.circuit.clone() }
}
#[pyo3(signature = (decompose, approximate=None, flatten=false, ignore_failures=false))]
fn detector_error_model(&self, decompose: bool, approximate: Option<f64>, flatten: bool, ignore_failures: bool) -> PyResult<PyDem> {
let options = crate::api::DemOptions::new().decompose_errors(decompose).approximate_disjoint_errors(approximate).flatten_loops(flatten).ignore_decomposition_failures(ignore_failures);
Ok(PyDem { dem: self.circuit.detector_error_model(&options).map_err(api_err)? })
}
fn reference_sample<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
let inner = &self.circuit.inner;
let qubits = crate::batch_sampler::Counts::of(&inner.instrs).map_err(err)?.qubits;
let bits = py.detach(|| crate::m2d::run(inner, qubits, &[], 1));
let mut row = vec![0u8; bits.len().div_ceil(8)];
for (k, &b) in bits.iter().enumerate() {
if b {
row[k / 8] |= 1 << (k % 8);
}
}
Ok(PyBytes::new(py, &row))
}
fn measurement_sampler(&self, py: Python<'_>, seed: u64, skip_reference: bool) -> PyResult<PyMeasurementSampler> {
let inner = &self.circuit.inner;
let sampler = BatchSampler::new(inner).map_err(err)?;
let reference = if skip_reference {
Vec::new()
} else {
let qubits = crate::batch_sampler::Counts::of(&inner.instrs).map_err(err)?.qubits;
py.detach(|| crate::m2d::run(inner, qubits, &[], 1))
};
Ok(PyMeasurementSampler { sampler, reference, seed, next: 0.into() })
}
fn sampler(&self, seed: u64) -> PyResult<PySampler> {
Ok(PySampler { sampler: BatchSampler::new(&self.circuit.inner).map_err(err)?, seed, next: 0.into() })
}
fn m2d(&self) -> PyResult<PyM2d> {
Ok(PyM2d { m2d: M2d::new(&self.circuit.inner).map_err(err)? })
}
}
#[pyclass(name = "Dem", module = "stabilizer_qec._core")]
pub struct PyDem {
dem: crate::api::DetectorErrorModel,
}
impl PyDem {
fn flat(&self) -> PyResult<&Dem> {
self.dem.flat().map_err(api_err)
}
}
#[pymethods]
impl PyDem {
#[new]
fn new(text: &str) -> PyResult<Self> {
Ok(PyDem { dem: crate::api::DetectorErrorModel::parse(text).map_err(api_err)? })
}
#[getter]
fn num_detectors(&self) -> usize {
self.dem.num_detectors()
}
#[getter]
fn num_observables(&self) -> usize {
self.dem.num_observables()
}
#[getter]
fn num_errors(&self) -> usize {
self.dem.num_errors()
}
fn __str__(&self) -> String {
self.dem.to_string()
}
fn shortest_graphlike(&self, py: Python<'_>, ignore: bool) -> PyResult<PyDem> {
let dem = &self.dem;
Ok(PyDem { dem: py.detach(move || dem.shortest_graphlike_error(ignore)).map_err(api_err)? })
}
fn search_undetectable(&self, py: Python<'_>, max_symptoms: usize, max_degree: usize, no_increase: bool) -> PyResult<PyDem> {
let dem = &self.dem;
Ok(PyDem { dem: py.detach(move || dem.search_for_undetectable_logical_errors(max_symptoms, max_degree, no_increase)).map_err(api_err)? })
}
fn sampler(&self, seed: u64) -> PyResult<PyDemSampler> {
Ok(PyDemSampler { sampler: crate::dem_sampler::DemSampler::new(self.dem.flat().map_err(api_err)?), seed, next: 0.into() })
}
fn matchgraph_svg(&self) -> PyResult<String> {
self.dem.matchgraph_svg().map_err(api_err)
}
fn flattened(&self) -> PyResult<PyDem> {
Ok(PyDem { dem: self.dem.flattened().map_err(api_err)? })
}
}
#[pyclass(name = "Sampler", module = "stabilizer_qec._core")]
pub struct PySampler {
sampler: BatchSampler,
seed: u64,
next: std::sync::atomic::AtomicU64,
}
#[pymethods]
impl PySampler {
#[getter]
fn num_detectors(&self) -> usize {
self.sampler.num_detectors
}
#[getter]
fn num_observables(&self) -> usize {
self.sampler.num_observables
}
fn sample<'py>(&self, py: Python<'py>, shots: usize, threads: usize) -> (Bound<'py, PyBytes>, Bound<'py, PyBytes>) {
let first = self.next.fetch_add(shots.div_ceil(64) as u64, std::sync::atomic::Ordering::Relaxed);
let (sampler, seed) = (&self.sampler, self.seed);
let (d, o) = py.detach(|| sampler.sample_seeded(seed, first, shots, threads));
(PyBytes::new(py, &d), PyBytes::new(py, &o))
}
}
#[pyclass(name = "MeasurementSampler", module = "stabilizer_qec._core")]
pub struct PyMeasurementSampler {
sampler: BatchSampler,
reference: Vec<bool>,
seed: u64,
next: std::sync::atomic::AtomicU64,
}
#[pymethods]
impl PyMeasurementSampler {
#[getter]
fn num_measurements(&self) -> usize {
self.sampler.num_measurements
}
fn sample<'py>(&self, py: Python<'py>, shots: usize, threads: usize) -> Bound<'py, PyBytes> {
let first = self.next.fetch_add(shots.div_ceil(64) as u64, std::sync::atomic::Ordering::Relaxed);
let (sampler, seed, reference) = (&self.sampler, self.seed, &self.reference);
let rows = py.detach(|| sampler.sample_measurements_seeded(seed, first, shots, threads, reference));
PyBytes::new(py, &rows)
}
}
#[pyclass(name = "DemSampler", module = "stabilizer_qec._core")]
pub struct PyDemSampler {
sampler: crate::dem_sampler::DemSampler,
seed: u64,
next: std::sync::atomic::AtomicU64,
}
#[pymethods]
impl PyDemSampler {
#[getter]
fn num_detectors(&self) -> usize {
self.sampler.num_detectors
}
#[getter]
fn num_observables(&self) -> usize {
self.sampler.num_observables
}
#[getter]
fn num_errors(&self) -> usize {
self.sampler.num_errors()
}
fn sample<'py>(&self, py: Python<'py>, shots: usize, threads: usize, errors: bool) -> (Bound<'py, PyBytes>, Bound<'py, PyBytes>, Bound<'py, PyBytes>) {
let first = self.next.fetch_add(shots.div_ceil(64) as u64, std::sync::atomic::Ordering::Relaxed);
let (sampler, seed) = (&self.sampler, self.seed);
let (d, o, e) = py.detach(|| sampler.sample_seeded(seed, first, shots, threads, errors));
(PyBytes::new(py, &d), PyBytes::new(py, &o), PyBytes::new(py, &e))
}
fn replay<'py>(&self, py: Python<'py>, errors: &[u8], shots: usize) -> PyResult<(Bound<'py, PyBytes>, Bound<'py, PyBytes>)> {
let (d, o) = self.sampler.replay(errors, shots).map_err(err)?;
Ok((PyBytes::new(py, &d), PyBytes::new(py, &o)))
}
}
#[pyclass(name = "M2d", module = "stabilizer_qec._core")]
pub struct PyM2d {
m2d: M2d,
}
#[pymethods]
impl PyM2d {
#[getter]
fn num_measurements(&self) -> usize {
self.m2d.num_measurements
}
#[getter]
fn num_sweep_bits(&self) -> usize {
self.m2d.num_sweep_bits
}
#[getter]
fn num_detectors(&self) -> usize {
self.m2d.num_detectors
}
#[getter]
fn num_observables(&self) -> usize {
self.m2d.num_observables
}
fn convert<'py>(&self, py: Python<'py>, meas: &[u8], sweeps: &[u8], shots: usize) -> PyResult<(Bound<'py, PyBytes>, Bound<'py, PyBytes>)> {
let m2d = &self.m2d;
let (d, o) = py.detach(|| m2d.convert_b8(meas, sweeps, shots)).map_err(err)?;
Ok((PyBytes::new(py, &d), PyBytes::new(py, &o)))
}
}
#[pyclass(name = "Matcher", module = "stabilizer_qec._core")]
pub struct PyMatcher {
graph: crate::sparse::SparseGraph,
corr: Option<crate::sparse::Correlations>,
#[pyo3(get)]
num_detectors: usize,
#[pyo3(get)]
num_observables: usize,
}
#[pymethods]
impl PyMatcher {
#[new]
fn new(dem: &PyDem, correlated: bool) -> PyResult<Self> {
let d = dem.flat()?;
let (graph, corr) = DemDecoder::new(d).map_err(err)?.into_parts(correlated);
Ok(PyMatcher { graph, corr, num_detectors: d.num_detectors, num_observables: d.num_observables })
}
fn decode_batch<'py>(&self, py: Python<'py>, packed: &[u8], shots: usize, threads: usize) -> PyResult<(Bound<'py, PyBytes>, Bound<'py, PyBytes>, Vec<usize>)> {
let nd = self.num_detectors;
check_rows(packed.len(), nd.div_ceil(8), shots, nd, "detectors")?;
let out = py.detach(|| match_shots(&self.graph, self.corr.as_ref(), packed, nd, shots, threads));
let failed = out.iter().enumerate().filter(|(_, (_, w))| w.is_nan()).map(|(s, _)| s).collect();
let preds = le_u64(out.iter().map(|&(o, _)| o));
let weights = le_f64(out.iter().map(|&(_, w)| w));
Ok((PyBytes::new(py, &preds), PyBytes::new(py, &weights), failed))
}
}
#[pyclass(name = "UnionFinder", module = "stabilizer_qec._core")]
pub struct PyUnionFinder {
graph: crate::sparse::SparseGraph,
#[pyo3(get)]
num_detectors: usize,
#[pyo3(get)]
num_observables: usize,
}
#[pymethods]
impl PyUnionFinder {
#[new]
fn new(dem: &PyDem) -> PyResult<Self> {
let d = dem.flat()?;
let (graph, _) = DemDecoder::new(d).map_err(err)?.into_parts(false);
Ok(PyUnionFinder { graph, num_detectors: d.num_detectors, num_observables: d.num_observables })
}
fn decode_batch<'py>(&self, py: Python<'py>, packed: &[u8], shots: usize, threads: usize) -> PyResult<(Bound<'py, PyBytes>, Vec<usize>)> {
let nd = self.num_detectors;
check_rows(packed.len(), nd.div_ceil(8), shots, nd, "detectors")?;
let out = py.detach(|| crate::batch::union_find_shots(&self.graph, packed, nd, shots, threads));
let failed = out.iter().enumerate().filter(|(_, o)| o.is_none()).map(|(s, _)| s).collect();
Ok((PyBytes::new(py, &le_u64(out.iter().map(|o| o.unwrap_or(0)))), failed))
}
}
fn bp_method(method: &str, scale: f64) -> PyResult<crate::bp::Method> {
match method {
"product_sum" => Ok(crate::bp::Method::ProductSum),
"minimum_sum" => Ok(crate::bp::Method::MinSum { scale }),
other => Err(err(format!("BP method '{other}' is neither product_sum nor minimum_sum"))),
}
}
fn osd_method(name: &str, order: usize) -> PyResult<OsdMethod> {
match name {
"osd_0" | "osd0" => Ok(OsdMethod::Osd0),
"osd_e" => Ok(OsdMethod::Exhaustive(order)),
"osd_cs" => Ok(OsdMethod::CombinationSweep(order)),
other => Err(err(format!("OSD method '{other}' is not osd_0, osd_e or osd_cs"))),
}
}
#[pyclass(name = "BeliefMatcher", module = "stabilizer_qec._core")]
pub struct PyBeliefMatcher {
bm: BeliefMatching,
#[pyo3(get)]
num_detectors: usize,
#[pyo3(get)]
num_observables: usize,
}
#[pymethods]
impl PyBeliefMatcher {
#[new]
fn new(dem: &PyDem, max_iter: usize, method: &str, scale: f64) -> PyResult<Self> {
let d = dem.flat()?;
let bm = BeliefMatching::from_dem(d, bp_method(method, scale)?, max_iter).map_err(err)?;
Ok(PyBeliefMatcher { bm, num_detectors: d.num_detectors, num_observables: d.num_observables })
}
#[allow(clippy::type_complexity)]
fn decode_batch<'py>(
&self,
py: Python<'py>,
packed: &[u8],
shots: usize,
threads: usize,
) -> PyResult<(Bound<'py, PyBytes>, Bound<'py, PyBytes>, Bound<'py, PyBytes>, Vec<usize>)> {
let nd = self.num_detectors;
check_rows(packed.len(), nd.div_ceil(8), shots, nd, "detectors")?;
let bm = &self.bm;
let out = py.detach(|| belief_shots(bm, packed, nd, shots, threads));
let failed = out.iter().enumerate().filter(|(_, x)| x.2 == 2).map(|(s, _)| s).collect();
let conv: Vec<u8> = out.iter().map(|x| u8::from(x.2 == 1)).collect();
Ok((
PyBytes::new(py, &le_u64(out.iter().map(|x| x.0))),
PyBytes::new(py, &le_f64(out.iter().map(|x| x.1))),
PyBytes::new(py, &conv),
failed,
))
}
}
#[pyclass(name = "DemBpOsd", module = "stabilizer_qec._core")]
pub struct PyDemBpOsd {
dec: BpOsd,
obs: Vec<u64>,
#[pyo3(get)]
num_detectors: usize,
#[pyo3(get)]
num_observables: usize,
}
#[pymethods]
impl PyDemBpOsd {
#[new]
#[allow(clippy::too_many_arguments)]
fn new(dem: &PyDem, max_iter: usize, method: &str, scale: f64, osd: &str, order: usize) -> PyResult<Self> {
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 dec = BpOsd::new(d.num_detectors, columns, &priors, bp_method(method, scale)?, max_iter, osd_method(osd, order)?)
.map_err(err)?;
let obs = d.mechanisms.iter().map(|m| m.observables).collect();
Ok(PyDemBpOsd { dec, obs, num_detectors: d.num_detectors, num_observables: d.num_observables })
}
fn decode_batch<'py>(&self, py: Python<'py>, packed: &[u8], shots: usize, threads: usize) -> PyResult<(Bound<'py, PyBytes>, Bound<'py, PyBytes>)> {
let nd = self.num_detectors;
check_rows(packed.len(), nd.div_ceil(8), shots, nd, "detectors")?;
let (dec, obs) = (&self.dec, &self.obs);
let out = py.detach(|| bposd_shots(dec, obs, packed, nd, shots, threads));
let conv: Vec<u8> = out.iter().map(|x| x.1).collect();
Ok((PyBytes::new(py, &le_u64(out.iter().map(|x| x.0))), PyBytes::new(py, &conv)))
}
}
#[pyclass(name = "WindowMatcher", module = "stabilizer_qec._core")]
pub struct PyWindowMatcher {
wd: PyWindows,
correlated: bool,
#[pyo3(get)]
num_detectors: usize,
#[pyo3(get)]
num_observables: usize,
}
enum PyWindows {
Whole(Box<WindowDecoder>),
Streamed(Box<crate::batch::StreamedWindows>),
}
#[pymethods]
impl PyWindowMatcher {
#[new]
#[pyo3(signature = (dem, commit, buffer, mode, correlated, template=None))]
fn new(dem: &PyDem, commit: usize, buffer: usize, mode: &str, correlated: bool, template: Option<bool>) -> PyResult<Self> {
let mode = match mode {
"sliding" => Mode::Sliding,
"parallel" => Mode::Parallel,
other => return Err(err(format!("mode '{other}' is neither sliding nor parallel"))),
};
let (nd, no) = (dem.dem.num_detectors(), dem.dem.num_observables());
let streamed = || crate::batch::StreamedWindows::new(dem.dem.program(), commit, buffer, mode).map(Box::new);
let whole = || -> PyResult<PyWindows> {
Ok(PyWindows::Whole(Box::new(WindowDecoder::new(Model::new(dem.flat()?).map_err(err)?, commit, buffer, mode).map_err(err)?)))
};
let wd = match template {
Some(true) => PyWindows::Streamed(streamed().map_err(err)?),
None if dem.dem.num_errors() > 1_000_000 => match streamed() {
Ok(s) => PyWindows::Streamed(s),
Err(_) => whole()?,
},
_ => whole()?,
};
Ok(PyWindowMatcher { wd, correlated, num_detectors: nd, num_observables: no })
}
fn windows(&self) -> Vec<WindowInfo> {
match &self.wd {
PyWindows::Whole(wd) => window_info(wd),
PyWindows::Streamed(sw) => sw.windows(),
}
}
#[getter]
fn streamed(&self) -> bool {
matches!(self.wd, PyWindows::Streamed(_))
}
#[allow(clippy::type_complexity)]
fn decode_batch<'py>(
&self,
py: Python<'py>,
packed: &[u8],
shots: usize,
threads: usize,
timings: bool,
) -> PyResult<(Bound<'py, PyBytes>, usize, Bound<'py, PyBytes>, Vec<usize>)> {
let nd = self.num_detectors;
check_rows(packed.len(), nd.div_ceil(8), shots, nd, "detectors")?;
let (wd, correlated) = (&self.wd, self.correlated);
let out = py.detach(|| match wd {
PyWindows::Whole(wd) => window_shots(wd, packed, nd, shots, correlated, threads, timings),
PyWindows::Streamed(sw) => crate::batch::streamed_window_shots(sw, packed, nd, shots, correlated, threads, timings),
});
let unexplained = out.iter().map(|x| x.1).sum();
let times = le_f64(out.iter().flat_map(|x| x.2.iter().copied()));
let failed = out.iter().enumerate().filter(|(_, x)| x.3).map(|(s, _)| s).collect();
Ok((PyBytes::new(py, &le_u64(out.iter().map(|x| x.0))), unexplained, PyBytes::new(py, ×), failed))
}
}
#[pyclass(name = "Bp", module = "stabilizer_qec._core")]
pub struct PyBp {
bp: crate::bp::Bp,
method: crate::bp::Method,
max_iter: usize,
num_checks: usize,
}
#[pymethods]
impl PyBp {
#[new]
fn new(num_checks: usize, columns: Vec<Vec<u32>>, priors: Vec<f64>, max_iter: usize, method: &str, scale: f64) -> PyResult<Self> {
let bp = crate::bp::Bp::new(num_checks, &columns, &priors).map_err(err)?;
Ok(PyBp { bp, method: bp_method(method, scale)?, max_iter, num_checks })
}
fn decode(&self, syndrome: Vec<u8>) -> PyResult<(Vec<u32>, Vec<f64>, bool, usize)> {
if syndrome.len() != self.num_checks {
return Err(err(format!("{} syndrome bits for {} checks", syndrome.len(), self.num_checks)));
}
let mut w = self.bp.work();
let out = self.bp.decode(&syndrome, self.method, self.max_iter, &mut w);
Ok((bits(&w.hard), w.llr, out.converged, out.iterations))
}
}
#[pyclass(name = "BpOsd", module = "stabilizer_qec._core")]
pub struct PyBpOsd {
dec: BpOsd,
num_checks: usize,
}
#[pymethods]
impl PyBpOsd {
#[new]
#[allow(clippy::too_many_arguments)]
fn new(num_checks: usize, columns: Vec<Vec<u32>>, priors: Vec<f64>, max_iter: usize, method: &str, scale: f64, osd: &str, order: usize) -> PyResult<Self> {
let dec = BpOsd::new(num_checks, columns, &priors, bp_method(method, scale)?, max_iter, osd_method(osd, order)?).map_err(err)?;
Ok(PyBpOsd { dec, num_checks })
}
fn decode(&self, syndrome: Vec<u8>) -> PyResult<(Vec<u32>, bool, usize)> {
if syndrome.len() != self.num_checks {
return Err(err(format!("{} syndrome bits for {} checks", syndrome.len(), self.num_checks)));
}
let mut w = self.dec.work();
let out = self.dec.decode(&syndrome, &mut w);
Ok((bits(&w.correction), out.converged, out.iterations))
}
}
pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyCircuit>()?;
m.add_class::<PyDem>()?;
m.add_class::<PySampler>()?;
m.add_class::<PyDemSampler>()?;
m.add_class::<PyMeasurementSampler>()?;
m.add_class::<PyM2d>()?;
m.add_class::<PyMatcher>()?;
m.add_class::<PyUnionFinder>()?;
m.add_class::<PyBeliefMatcher>()?;
m.add_class::<PyDemBpOsd>()?;
m.add_class::<PyWindowMatcher>()?;
m.add_class::<PyBp>()?;
m.add_class::<PyBpOsd>()?;
Ok(())
}