use std::{
collections::HashMap,
hash::Hash,
ops::{Bound, RangeBounds},
};
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use crate::{
annotation::model::ChargeRange,
fragment::{FragmentKind, NeutralLoss},
glycan::MonoSaccharide,
sequence::AminoAcid,
};
use super::built_in::GLYCAN_LOSSES;
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct GlycanModel {
pub allow_structural: bool,
pub compositional_range: (Option<usize>, Option<usize>),
pub neutral_losses: Vec<NeutralLoss>,
pub specific_neutral_losses: Vec<(MonoSaccharide, bool, Vec<NeutralLoss>)>,
pub default_peptide_fragment: GlycanPeptideFragment,
pub peptide_fragment_rules: Vec<(Vec<AminoAcid>, Vec<FragmentKind>, GlycanPeptideFragment)>,
pub oxonium_charge_range: ChargeRange,
pub other_charge_range: ChargeRange,
}
impl GlycanModel {
#[must_use]
pub fn allow_structural(self, allow_structural: bool) -> Self {
Self {
allow_structural,
..self
}
}
#[must_use]
pub fn compositional_range(self, compositional_range: (Option<usize>, Option<usize>)) -> Self {
Self {
compositional_range,
..self
}
}
#[must_use]
pub fn compositional_range_generic(self, compositional_range: impl RangeBounds<usize>) -> Self {
Self {
compositional_range: (
match compositional_range.start_bound() {
Bound::Unbounded => None,
Bound::Included(n) => Some(*n),
Bound::Excluded(n) => Some(n + 1),
},
match compositional_range.end_bound() {
Bound::Unbounded => None,
Bound::Included(n) => Some(*n),
Bound::Excluded(n) => Some(n - 1),
},
),
..self
}
}
#[must_use]
pub fn neutral_losses(self, neutral_losses: Vec<NeutralLoss>) -> Self {
Self {
neutral_losses,
..self
}
}
#[must_use]
pub fn oxonium_charge_range(self, oxonium_charge_range: ChargeRange) -> Self {
Self {
oxonium_charge_range,
..self
}
}
#[must_use]
pub fn other_charge_range(self, other_charge_range: ChargeRange) -> Self {
Self {
other_charge_range,
..self
}
}
#[must_use]
pub fn default_peptide_fragment(self, default_peptide_fragment: GlycanPeptideFragment) -> Self {
Self {
default_peptide_fragment,
..self
}
}
#[must_use]
pub fn peptide_fragment_rules(
self,
peptide_fragment_rules: Vec<(Vec<AminoAcid>, Vec<FragmentKind>, GlycanPeptideFragment)>,
) -> Self {
Self {
peptide_fragment_rules,
..self
}
}
pub fn default_allow() -> Self {
Self {
allow_structural: true,
compositional_range: (None, None),
neutral_losses: Vec::new(),
specific_neutral_losses: GLYCAN_LOSSES.clone(),
default_peptide_fragment: GlycanPeptideFragment::CORE_AND_FREE,
peptide_fragment_rules: Vec::new(),
oxonium_charge_range: ChargeRange::ONE,
other_charge_range: ChargeRange::ONE_TO_PRECURSOR,
}
}
pub const DISALLOW: Self = Self {
allow_structural: false,
compositional_range: (None, Some(0)),
neutral_losses: Vec::new(),
specific_neutral_losses: Vec::new(),
default_peptide_fragment: GlycanPeptideFragment::FULL,
peptide_fragment_rules: Vec::new(),
oxonium_charge_range: ChargeRange::ONE,
other_charge_range: ChargeRange::ONE_TO_PRECURSOR,
};
pub fn get_peptide_fragments(
&self,
attachment: Option<AminoAcid>,
) -> (
GlycanPeptideFragment,
HashMap<FragmentKind, GlycanPeptideFragment>,
) {
let base = self
.peptide_fragment_rules
.iter()
.find(|rule| attachment.is_some_and(|a| rule.0.contains(&a)) && rule.1.is_empty())
.map_or(self.default_peptide_fragment, |rule| rule.2);
(
base,
self.peptide_fragment_rules
.iter()
.filter(|rule| attachment.is_some_and(|a| rule.0.contains(&a)))
.flat_map(|rule| rule.1.iter().map(|f| (f, rule.2)))
.into_group_map()
.into_iter()
.map(|(f, settings)| (*f, settings.iter().fold(settings[0], |acc, v| acc + v)))
.collect(),
)
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct GlycanPeptideFragment {
pub(super) full: bool,
pub(super) core: Option<(Option<usize>, Option<usize>)>,
}
impl std::ops::Add<&Self> for GlycanPeptideFragment {
type Output = Self;
fn add(self, rhs: &Self) -> Self::Output {
Self {
full: self.full || rhs.full,
core: self
.core
.and_then(|c| rhs.core.map(|r| (c, r)))
.map(|(c, r)| (c.0.min(r.0), c.1.max(r.1)))
.or(self.core)
.or(rhs.core),
}
}
}
impl GlycanPeptideFragment {
pub const FULL: Self = Self {
full: true,
core: None,
};
pub const CORE_AND_FREE: Self = Self {
full: false,
core: Some((None, Some(1))),
};
pub const CORE: Self = Self {
full: false,
core: Some((Some(1), Some(1))),
};
pub const FREE: Self = Self {
full: false,
core: Some((None, Some(0))),
};
pub const fn full(self) -> bool {
self.full
}
pub const fn core(self) -> Option<(Option<usize>, Option<usize>)> {
self.core
}
}