use std::fmt;
use std::ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign};
use std::str::FromStr;
use crate::error::{Error, Result};
use crate::ffi::bridge as sys;
use crate::unimod::UnimodTable;
use crate::{Element, Masses};
#[derive(Clone, Debug, Default)]
pub struct Composition {
elements: Vec<Element>,
counts: Vec<i32>,
}
#[derive(Clone, Debug, Default, PartialEq)]
#[non_exhaustive]
pub struct ExpandedComposition {
pub isotope_numbers: Vec<i32>,
pub atom_counts: Vec<i32>,
pub masses: Vec<f64>,
pub probabilities: Vec<f64>,
}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub struct PeptideOptions<'a> {
pub add_water: bool,
pub mods: &'a UnimodTable,
pub masses: Masses,
}
impl Default for PeptideOptions<'static> {
fn default() -> Self {
PeptideOptions {
add_water: true,
mods: UnimodTable::embedded(),
masses: Masses::Exact,
}
}
}
impl<'a> PeptideOptions<'a> {
pub fn with_add_water(mut self, add_water: bool) -> Self {
self.add_water = add_water;
self
}
pub fn with_mods<'b>(self, mods: &'b UnimodTable) -> PeptideOptions<'b> {
PeptideOptions {
add_water: self.add_water,
mods,
masses: self.masses,
}
}
pub fn with_masses(mut self, masses: Masses) -> Self {
self.masses = masses;
self
}
}
impl Composition {
pub fn new() -> Self {
Composition::default()
}
pub fn from_formula(formula: &str) -> Result<Self> {
let mut first = Vec::new();
let mut counts = Vec::new();
sys::composition_from_formula(formula, &mut first, &mut counts)?;
Ok(Composition::from_raw(&first, counts))
}
pub fn from_peptide(sequence: &str, options: &PeptideOptions<'_>) -> Result<Self> {
let mut composition = Composition::new();
composition.parse_peptide_into(sequence, options)?;
Ok(composition)
}
pub fn parse_peptide_into(
&mut self,
sequence: &str,
options: &PeptideOptions<'_>,
) -> Result<()> {
let mut first = Vec::new();
let mut counts = Vec::new();
sys::composition_from_peptide(
sequence,
options.mods.raw(),
options.add_water,
&mut first,
&mut counts,
)?;
self.elements.clear();
self.elements.extend(first.iter().map(|&f| element_of(f)));
self.counts = counts;
Ok(())
}
pub fn from_fasta(sequence: &str, add_water: bool) -> Self {
let mut chnosse = [0i32; 6];
sys::composition_from_fasta(sequence, add_water, &mut chnosse);
let mut composition = Composition::new();
for (symbol, count) in ["C", "H", "N", "O", "S", "Se"].iter().zip(chnosse) {
if count != 0 {
composition
.add_element(Element::from_symbol(symbol).expect("element table"), count);
}
}
composition
}
fn from_raw(first: &[i32], counts: Vec<i32>) -> Self {
Composition {
elements: first.iter().map(|&f| element_of(f)).collect(),
counts,
}
}
pub(crate) fn raw_first_indexes(&self) -> Vec<i32> {
self.elements.iter().map(|e| e.first_index()).collect()
}
pub(crate) fn raw_counts(&self) -> &[i32] {
&self.counts
}
pub fn add_element(&mut self, element: Element, count: i32) {
match self.elements.iter().position(|&e| e == element) {
Some(ii) => self.counts[ii] += count,
None => {
self.elements.push(element);
self.counts.push(count);
}
}
}
pub fn count(&self, element: Element) -> i32 {
self.elements
.iter()
.position(|&e| e == element)
.map_or(0, |ii| self.counts[ii])
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = (Element, i32)> + '_ {
self.elements
.iter()
.copied()
.zip(self.counts.iter().copied())
}
pub fn len(&self) -> usize {
self.elements.len()
}
pub fn is_empty(&self) -> bool {
self.elements.is_empty()
}
pub fn has_negative_counts(&self) -> bool {
self.counts.iter().any(|&c| c < 0)
}
pub fn expand(&self, masses: Masses) -> Result<ExpandedComposition> {
let mut out = ExpandedComposition::default();
sys::composition_expand(
&self.raw_first_indexes(),
&self.counts,
masses == Masses::Nominal,
&mut out.isotope_numbers,
&mut out.atom_counts,
&mut out.masses,
&mut out.probabilities,
)?;
Ok(out)
}
fn combine(&self, other: &Composition, sign: i32) -> Composition {
let mut result = self.clone();
for (element, count) in other.iter() {
result.add_element(element, sign * count);
}
result
}
}
impl Composition {
fn sorted(&self) -> Vec<(Element, i32)> {
let mut pairs: Vec<_> = self.iter().filter(|&(_, c)| c != 0).collect();
pairs.sort_unstable();
pairs
}
}
impl PartialEq for Composition {
fn eq(&self, other: &Composition) -> bool {
self.sorted() == other.sorted()
}
}
impl Eq for Composition {}
impl std::hash::Hash for Composition {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.sorted().hash(state);
}
}
fn element_of(first: i32) -> Element {
Element::from_first_index(first).expect("the library returned an invalid element index")
}
impl FromStr for Composition {
type Err = Error;
fn from_str(formula: &str) -> Result<Self> {
Composition::from_formula(formula)
}
}
impl fmt::Display for Composition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (element, count) in self.iter() {
write!(f, "{element}{count}")?;
}
Ok(())
}
}
impl Add for &Composition {
type Output = Composition;
fn add(self, other: &Composition) -> Composition {
self.combine(other, 1)
}
}
impl Sub for &Composition {
type Output = Composition;
fn sub(self, other: &Composition) -> Composition {
self.combine(other, -1)
}
}
impl AddAssign<&Composition> for Composition {
fn add_assign(&mut self, other: &Composition) {
for (element, count) in other.iter() {
self.add_element(element, count);
}
}
}
impl SubAssign<&Composition> for Composition {
fn sub_assign(&mut self, other: &Composition) {
for (element, count) in other.iter() {
self.add_element(element, -count);
}
}
}
impl Mul<i32> for &Composition {
type Output = Composition;
fn mul(self, factor: i32) -> Composition {
Composition {
elements: self.elements.clone(),
counts: self.counts.iter().map(|&c| c * factor).collect(),
}
}
}
impl Neg for &Composition {
type Output = Composition;
fn neg(self) -> Composition {
self * -1
}
}
impl FromIterator<(Element, i32)> for Composition {
fn from_iter<I: IntoIterator<Item = (Element, i32)>>(iter: I) -> Self {
let mut composition = Composition::new();
for (element, count) in iter {
composition.add_element(element, count);
}
composition
}
}