use serde::{Deserialize, Serialize};
use super::elements::{cache, K_PATTERN};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct IsotopeModel {
pub residue_mass: f64,
pub c: f64,
pub h: f64,
pub n: f64,
pub o: f64,
#[serde(default)]
pub s: f64,
}
pub const PEPTIDE: IsotopeModel = IsotopeModel {
residue_mass: 111.1254,
c: 4.9384,
h: 7.7583,
n: 1.3577,
o: 1.4773,
s: 0.0417,
};
pub const RNA: IsotopeModel = IsotopeModel {
residue_mass: 321.2916,
c: 9.5,
h: 11.75,
n: 3.75,
o: 7.0,
s: 0.0,
};
pub const DNA: IsotopeModel = IsotopeModel {
residue_mass: 308.8006,
c: 9.75,
h: 12.25,
n: 3.75,
o: 6.0,
s: 0.0,
};
impl Default for IsotopeModel {
fn default() -> Self {
PEPTIDE
}
}
impl IsotopeModel {
pub fn counts(&self, neutral_mass: f64) -> (u32, u32, u32, u32, u32) {
let scale = neutral_mass.max(0.0) / self.residue_mass;
let round = |x: f64| -> u32 { (x + 0.5).floor().max(0.0) as u32 };
(
round(self.c * scale),
round(self.h * scale),
round(self.n * scale),
round(self.o * scale),
round(self.s * scale),
)
}
pub fn distribution(&self, neutral_mass: f64) -> [f64; K_PATTERN] {
let (c, h, n, o, s) = self.counts(neutral_mass);
cache().distribution(c, h, n, o, s)
}
pub fn distribution_with_sulfur(&self, neutral_mass: f64, n_s: u32) -> [f64; K_PATTERN] {
let (c, h, n, o, _) = self.counts(neutral_mass);
cache().distribution(c, h, n, o, n_s)
}
pub fn sulfur_ceil(&self, neutral_mass: f64) -> u32 {
let scale = neutral_mass.max(0.0) / self.residue_mass;
(self.s * scale).ceil().max(0.0) as u32
}
pub fn has_sulfur(&self) -> bool {
self.s > 0.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum IsotopeClass {
Peptide,
Rna,
Dna,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum IsotopeModelSpec {
Class(IsotopeClass),
Custom(IsotopeModel),
}
impl Default for IsotopeModelSpec {
fn default() -> Self {
Self::Class(IsotopeClass::Peptide)
}
}
impl IsotopeModelSpec {
pub fn model(&self) -> IsotopeModel {
match self {
Self::Class(IsotopeClass::Peptide) => PEPTIDE,
Self::Class(IsotopeClass::Rna) => RNA,
Self::Class(IsotopeClass::Dna) => DNA,
Self::Custom(m) => *m,
}
}
}
#[cfg(test)]
#[path = "model_tests.rs"]
mod tests;