use std::fmt;
use std::str::FromStr;
use std::sync::Arc;
use cxx::UniquePtr;
use crate::composition::PeptideOptions;
use crate::error::{Error, ErrorKind, Result};
use crate::ffi::bridge as sys;
use crate::{Composition, Conf};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Masses {
#[default]
Exact,
Nominal,
}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub struct ElementSpec<'a> {
pub atoms: u32,
pub masses: &'a [f64],
pub probabilities: &'a [f64],
}
pub struct Iso {
inner: UniquePtr<sys::Iso>,
}
impl<'a> ElementSpec<'a> {
pub fn new(atoms: u32, masses: &'a [f64], probabilities: &'a [f64]) -> Self {
ElementSpec {
atoms,
masses,
probabilities,
}
}
}
fn atoms(n: u32) -> Result<i32> {
i32::try_from(n).map_err(|_| Error::new(ErrorKind::OutOfRange, "atom count out of range"))
}
fn check_isotopes(spec: &ElementSpec<'_>) -> Result<i32> {
if spec.masses.len() != spec.probabilities.len() {
return Err(Error::invalid(format!(
"an element has {} isotope masses but {} probabilities",
spec.masses.len(),
spec.probabilities.len()
)));
}
i32::try_from(spec.masses.len()).map_err(|_| Error::new(ErrorKind::Length, "too many isotopes"))
}
impl Iso {
pub(crate) fn from_raw(inner: UniquePtr<sys::Iso>) -> Iso {
debug_assert!(!inner.is_null());
Iso { inner }
}
pub(crate) fn raw(&self) -> &sys::Iso {
self.inner.as_ref().expect("non-null")
}
pub(crate) fn into_raw(self) -> UniquePtr<sys::Iso> {
self.inner
}
pub fn from_formula(formula: &str) -> Result<Iso> {
Iso::from_formula_with(formula, Masses::Exact)
}
pub fn from_formula_with(formula: &str, masses: Masses) -> Result<Iso> {
Ok(Iso::from_raw(sys::iso_from_formula(
formula,
masses == Masses::Nominal,
)?))
}
pub fn from_fasta(sequence: &str, add_water: bool, masses: Masses) -> Result<Iso> {
Ok(Iso::from_raw(sys::iso_from_fasta(
sequence,
masses == Masses::Nominal,
add_water,
)?))
}
pub fn from_peptide(sequence: &str, options: &PeptideOptions<'_>) -> Result<Iso> {
Ok(Iso::from_raw(sys::iso_from_peptide(
sequence,
options.mods.raw(),
options.masses == Masses::Nominal,
options.add_water,
)?))
}
pub fn from_composition(composition: &Composition, masses: Masses) -> Result<Iso> {
Ok(Iso::from_raw(sys::iso_from_composition(
&composition.raw_first_indexes(),
composition.raw_counts(),
masses == Masses::Nominal,
)?))
}
pub fn from_isotopes(elements: &[ElementSpec<'_>]) -> Result<Iso> {
let mut isotope_numbers = Vec::with_capacity(elements.len());
let mut atom_counts = Vec::with_capacity(elements.len());
let mut masses = Vec::new();
let mut probs = Vec::new();
for spec in elements {
isotope_numbers.push(check_isotopes(spec)?);
atom_counts.push(atoms(spec.atoms)?);
masses.extend_from_slice(spec.masses);
probs.extend_from_slice(spec.probabilities);
}
if i32::try_from(elements.len()).is_err() || i32::try_from(masses.len()).is_err() {
return Err(Error::new(ErrorKind::Length, "too many isotopes in total"));
}
Ok(Iso::from_raw(sys::iso_from_isotopes(
&isotope_numbers,
&atom_counts,
&masses,
&probs,
)?))
}
pub fn add_element(&mut self, element: &ElementSpec<'_>) -> Result<()> {
check_isotopes(element)?;
let atom_count = atoms(element.atoms)?;
sys::iso_add_element(
self.inner.pin_mut(),
atom_count,
element.masses,
element.probabilities,
)?;
Ok(())
}
pub fn try_clone(&self) -> Result<Iso> {
Ok(Iso::from_raw(sys::iso_clone(self.raw())?))
}
pub fn dim_number(&self) -> usize {
self.raw().getDimNumber() as usize
}
pub fn all_dim(&self) -> usize {
self.raw().getAllDim() as usize
}
pub fn isotope_numbers(&self) -> &[i32] {
sys::iso_isotope_numbers(self.raw())
}
pub(crate) fn isotope_numbers_arc(&self) -> Arc<[i32]> {
Arc::from(self.isotope_numbers())
}
pub fn atom_counts(&self) -> &[i32] {
sys::iso_atom_counts(self.raw())
}
pub fn element_masses(&self, element: usize) -> &[f64] {
assert!(element < self.dim_number(), "element index out of range");
sys::iso_element_masses(self.raw(), element as i32)
}
pub fn element_lprobs(&self, element: usize) -> &[f64] {
assert!(element < self.dim_number(), "element index out of range");
sys::iso_element_lprobs(self.raw(), element as i32)
}
fn conf_with(&self, fill: fn(&sys::Iso, &mut [i32])) -> Conf {
let mut counts = vec![0; self.all_dim()];
fill(self.raw(), &mut counts);
Conf::from_parts(counts, self.isotope_numbers_arc())
}
pub fn lightest_peak_mass(&self) -> f64 {
self.raw().getLightestPeakMass()
}
pub fn lightest_peak_lprob(&self) -> f64 {
self.raw().getLightestPeakLProb()
}
pub fn lightest_peak_conf(&self) -> Conf {
self.conf_with(sys::iso_lightest_conf)
}
pub fn heaviest_peak_mass(&self) -> f64 {
self.raw().getHeaviestPeakMass()
}
pub fn heaviest_peak_lprob(&self) -> f64 {
self.raw().getHeaviestPeakLProb()
}
pub fn heaviest_peak_conf(&self) -> Conf {
self.conf_with(sys::iso_heaviest_conf)
}
pub fn monoisotopic_mass(&self) -> f64 {
self.raw().getMonoisotopicPeakMass()
}
pub fn monoisotopic_lprob(&self) -> f64 {
self.raw().getMonoisotopicPeakLProb()
}
pub fn monoisotopic_conf(&self) -> Conf {
self.conf_with(sys::iso_monoisotopic_conf)
}
pub fn mode_mass(&self) -> f64 {
self.raw().getModeMass()
}
pub fn mode_lprob(&self) -> f64 {
self.raw().getModeLProb()
}
pub fn unlikeliest_peak_lprob(&self) -> f64 {
self.raw().getUnlikeliestPeakLProb()
}
pub fn average_mass(&self) -> f64 {
self.raw().getTheoreticalAverageMass()
}
pub fn variance(&self) -> f64 {
self.raw().variance()
}
pub fn stddev(&self) -> f64 {
self.variance().sqrt()
}
pub fn mass_rounding_bound(&self) -> f64 {
self.raw().getMassRoundingBound()
}
pub fn max_atom_mass_span(&self) -> f64 {
self.raw().getMaxAtomMassSpan()
}
pub fn marginal_log_size_estimates(&self, target_total_prob: f64) -> Result<Vec<f64>> {
let mut out = vec![0.0; self.dim_number()];
sys::iso_marginal_log_size_estimates(self.raw(), &mut out, target_total_prob)?;
Ok(out)
}
}
impl Clone for Iso {
fn clone(&self) -> Iso {
self.try_clone().expect("cloning an Iso")
}
}
impl FromStr for Iso {
type Err = Error;
fn from_str(formula: &str) -> Result<Iso> {
Iso::from_formula(formula)
}
}
impl fmt::Debug for Iso {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Iso")
.field("isotope_numbers", &self.isotope_numbers())
.field("atom_counts", &self.atom_counts())
.field("monoisotopic_mass", &self.monoisotopic_mass())
.finish()
}
}