use std::sync::Arc;
use cxx::UniquePtr;
use crate::error::Result;
use crate::ffi::bridge as sys;
use crate::{Conf, ConfRef, Iso};
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub struct Peak {
pub mass: f64,
pub prob: f64,
pub lprob: f64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Threshold {
Absolute,
Relative,
}
mod sealed {
pub trait Batch {
fn fill(
&mut self,
masses: &mut [f64],
probs: &mut [f64],
lprobs: &mut [f64],
) -> crate::Result<usize>;
}
}
const BATCH: usize = 256;
pub struct Peaks<G: sealed::Batch> {
generator: G,
buf: Box<[f64]>,
pos: usize,
len: usize,
exhausted: bool,
}
impl<G: sealed::Batch> Peaks<G> {
fn new(generator: G) -> Self {
Peaks {
generator,
buf: vec![0.0; 3 * BATCH].into_boxed_slice(),
pos: 0,
len: 0,
exhausted: false,
}
}
pub fn into_inner(self) -> G {
self.generator
}
}
impl<G: sealed::Batch> Peaks<G> {
#[cold]
#[inline(never)]
fn refill(&mut self) -> bool {
if self.exhausted {
return false;
}
let (masses, rest) = self.buf.split_at_mut(BATCH);
let (probs, lprobs) = rest.split_at_mut(BATCH);
self.len = self
.generator
.fill(masses, probs, lprobs)
.expect("isotopologue generator");
self.pos = 0;
self.exhausted = self.len < BATCH;
self.len > 0
}
}
impl<G: sealed::Batch> Iterator for Peaks<G> {
type Item = Peak;
#[inline]
fn next(&mut self) -> Option<Peak> {
if self.pos == self.len && !self.refill() {
return None;
}
let ii = self.pos;
self.pos += 1;
Some(Peak {
mass: self.buf[ii],
prob: self.buf[BATCH + ii],
lprob: self.buf[2 * BATCH + ii],
})
}
}
impl<G: sealed::Batch> std::iter::FusedIterator for Peaks<G> {}
impl<G: sealed::Batch + std::fmt::Debug> std::fmt::Debug for Peaks<G> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Peaks")
.field("generator", &self.generator)
.finish_non_exhaustive()
}
}
macro_rules! generator_common {
($name:ident, $raw:ident, $next:ident, $fill:ident, $conf:ident, $make:literal) => {
impl $name {
fn wrap(inner: UniquePtr<sys::$raw>, isotope_numbers: Arc<[i32]>) -> Self {
let all_dim = isotope_numbers.iter().map(|&n| n as usize).sum();
$name {
inner,
isotope_numbers,
all_dim,
current: None,
done: false,
conf_buf: Vec::new(),
}
}
#[doc = concat!("use isospec::{Iso, Threshold, StochasticOptions, ", stringify!($name), "};")]
#[doc = concat!("let mut g = ", $make, ";")]
pub fn advance(&mut self) -> Result<Option<Peak>> {
let mut out = sys::PeakData {
mass: 0.0,
prob: 0.0,
lprob: 0.0,
};
self.current = None;
if self.done {
return Ok(None);
}
if !sys::$next(self.inner.pin_mut(), &mut out)? {
self.done = true;
return Ok(None);
}
let peak = Peak {
mass: out.mass,
prob: out.prob,
lprob: out.lprob,
};
self.current = Some(peak);
Ok(Some(peak))
}
pub fn current(&self) -> Option<Peak> {
self.current
}
#[doc = concat!("use isospec::{Iso, Threshold, StochasticOptions, ", stringify!($name), "};")]
#[doc = concat!("let mut g = ", $make, ";")]
pub fn conf(&mut self) -> Option<ConfRef<'_>> {
self.current?;
self.conf_buf.resize(self.all_dim, 0);
sys::$conf(self.inner.as_ref().expect("non-null"), &mut self.conf_buf);
Some(ConfRef::new(&self.conf_buf, &self.isotope_numbers))
}
pub fn conf_owned(&mut self) -> Option<Conf> {
let counts = self.conf()?.counts().to_vec();
Some(Conf::from_parts(counts, self.isotope_numbers.clone()))
}
pub fn write_conf(&self, out: &mut [i32]) -> bool {
assert_eq!(
out.len(),
self.all_dim,
"configuration buffer of the wrong length"
);
if self.current.is_none() {
return false;
}
sys::$conf(self.inner.as_ref().expect("non-null"), out);
true
}
pub fn isotope_numbers(&self) -> &[i32] {
&self.isotope_numbers
}
pub fn all_dim(&self) -> usize {
self.all_dim
}
}
impl sealed::Batch for $name {
fn fill(
&mut self,
masses: &mut [f64],
probs: &mut [f64],
lprobs: &mut [f64],
) -> Result<usize> {
self.current = None;
if self.done {
return Ok(0);
}
let n = sys::$fill(self.inner.pin_mut(), masses, probs, lprobs)?;
if n < masses.len() {
self.done = true;
}
Ok(n)
}
}
impl IntoIterator for $name {
type Item = Peak;
type IntoIter = Peaks<$name>;
fn into_iter(self) -> Peaks<$name> {
Peaks::new(self)
}
}
impl std::fmt::Debug for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(stringify!($name))
.field("isotope_numbers", &self.isotope_numbers)
.field("current", &self.current)
.finish_non_exhaustive()
}
}
};
}
pub struct ThresholdGenerator {
inner: UniquePtr<sys::ThresholdGenerator>,
isotope_numbers: Arc<[i32]>,
all_dim: usize,
current: Option<Peak>,
done: bool,
conf_buf: Vec<i32>,
}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub struct ThresholdOptions {
pub reorder_marginals: bool,
}
impl Default for ThresholdOptions {
fn default() -> Self {
ThresholdOptions {
reorder_marginals: true,
}
}
}
impl ThresholdOptions {
pub fn with_reorder_marginals(mut self, reorder: bool) -> Self {
self.reorder_marginals = reorder;
self
}
}
generator_common!(
ThresholdGenerator,
ThresholdGenerator,
threshold_generator_next,
threshold_generator_fill,
threshold_generator_conf,
"ThresholdGenerator::new(iso, 1e-9, Threshold::Absolute)?"
);
impl ThresholdGenerator {
pub fn new(iso: Iso, threshold: f64, mode: Threshold) -> Result<Self> {
ThresholdGenerator::with_options(iso, threshold, mode, ThresholdOptions::default())
}
pub fn with_options(
iso: Iso,
threshold: f64,
mode: Threshold,
options: ThresholdOptions,
) -> Result<Self> {
let isotope_numbers = iso.isotope_numbers_arc();
let inner = sys::threshold_generator_new(
iso.into_raw(),
threshold,
mode == Threshold::Absolute,
options.reorder_marginals,
)?;
Ok(ThresholdGenerator::wrap(inner, isotope_numbers))
}
pub fn count_confs(&mut self) -> Result<usize> {
self.current = None;
self.done = false;
let count = sys::threshold_generator_count_confs(self.inner.pin_mut())?;
sys::threshold_generator_reset(self.inner.pin_mut())?;
Ok(count)
}
pub fn reset(&mut self) -> Result<()> {
self.current = None;
self.done = false;
sys::threshold_generator_reset(self.inner.pin_mut())?;
Ok(())
}
}
pub struct LayeredGenerator {
inner: UniquePtr<sys::LayeredGenerator>,
isotope_numbers: Arc<[i32]>,
all_dim: usize,
current: Option<Peak>,
done: bool,
conf_buf: Vec<i32>,
}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub struct LayeredOptions {
pub reorder_marginals: bool,
pub prob_hint: f64,
}
impl Default for LayeredOptions {
fn default() -> Self {
LayeredOptions {
reorder_marginals: true,
prob_hint: 0.99,
}
}
}
impl LayeredOptions {
pub fn with_reorder_marginals(mut self, reorder: bool) -> Self {
self.reorder_marginals = reorder;
self
}
pub fn with_prob_hint(mut self, prob_hint: f64) -> Self {
self.prob_hint = prob_hint;
self
}
}
generator_common!(
LayeredGenerator,
LayeredGenerator,
layered_generator_next,
layered_generator_fill,
layered_generator_conf,
"LayeredGenerator::new(iso)?"
);
impl LayeredGenerator {
pub fn new(iso: Iso) -> Result<Self> {
LayeredGenerator::with_options(iso, LayeredOptions::default())
}
pub fn with_options(iso: Iso, options: LayeredOptions) -> Result<Self> {
let isotope_numbers = iso.isotope_numbers_arc();
let inner = sys::layered_generator_new(
iso.into_raw(),
options.reorder_marginals,
options.prob_hint,
)?;
Ok(LayeredGenerator::wrap(inner, isotope_numbers))
}
}
pub struct OrderedGenerator {
inner: UniquePtr<sys::OrderedGenerator>,
isotope_numbers: Arc<[i32]>,
all_dim: usize,
current: Option<Peak>,
done: bool,
conf_buf: Vec<i32>,
}
generator_common!(
OrderedGenerator,
OrderedGenerator,
ordered_generator_next,
ordered_generator_fill,
ordered_generator_conf,
"OrderedGenerator::new(iso)?"
);
impl OrderedGenerator {
pub fn new(iso: Iso) -> Result<Self> {
let isotope_numbers = iso.isotope_numbers_arc();
let inner = sys::ordered_generator_new(iso.into_raw())?;
Ok(OrderedGenerator::wrap(inner, isotope_numbers))
}
}
pub struct StochasticGenerator {
inner: UniquePtr<sys::StochasticGenerator>,
isotope_numbers: Arc<[i32]>,
all_dim: usize,
current: Option<Peak>,
done: bool,
conf_buf: Vec<i32>,
}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub struct StochasticOptions {
pub precision: f64,
pub beta_bias: f64,
pub seed: Option<u64>,
}
impl Default for StochasticOptions {
fn default() -> Self {
StochasticOptions {
precision: 0.9999,
beta_bias: 5.0,
seed: None,
}
}
}
impl StochasticOptions {
pub fn with_precision(mut self, precision: f64) -> Self {
self.precision = precision;
self
}
pub fn with_beta_bias(mut self, beta_bias: f64) -> Self {
self.beta_bias = beta_bias;
self
}
pub fn with_seed(mut self, seed: u64) -> Self {
self.seed = Some(seed);
self
}
}
generator_common!(
StochasticGenerator,
StochasticGenerator,
stochastic_generator_next,
stochastic_generator_fill,
stochastic_generator_conf,
"StochasticGenerator::with_options(iso, 1000, StochasticOptions::default().with_seed(1))?"
);
impl StochasticGenerator {
pub fn new(iso: Iso, molecules: usize) -> Result<Self> {
StochasticGenerator::with_options(iso, molecules, StochasticOptions::default())
}
pub fn with_options(iso: Iso, molecules: usize, options: StochasticOptions) -> Result<Self> {
let isotope_numbers = iso.isotope_numbers_arc();
let inner = sys::stochastic_generator_new(
iso.into_raw(),
molecules,
options.precision,
options.beta_bias,
options.seed.is_some(),
options.seed.unwrap_or(0),
)?;
Ok(StochasticGenerator::wrap(inner, isotope_numbers))
}
pub fn molecule_count(&self) -> Option<u64> {
self.current.map(|p| p.prob as u64)
}
}