use std::ops::Bound;
use crate::{
prelude::Peptidoform,
sequence::{AtMax, HasPeptidoformImpl, Linear},
space::UsedSpace,
};
pub trait AnnotatedPeptidoform: HasPeptidoformImpl {
fn regions(&self) -> &[(Region, usize)];
fn annotations(&self) -> &[(Annotation, usize)];
fn get_region(&self, index: usize) -> Option<(&Region, bool)> {
let regions = self.regions();
let mut left = index;
let mut regions_index = 0;
let mut next = ®ions[regions_index];
while left > next.1 {
left -= next.1;
regions_index += 1;
if regions_index == regions.len() {
return None;
}
next = ®ions[regions_index];
}
Some((&next.0, left == 1))
}
fn get_annotations(&self, index: usize) -> impl Iterator<Item = &Annotation> + '_ {
self.annotations().iter().filter(move |a| a.1 == index).map(|a| &a.0)
}
fn sub_peptidoform(
&self,
range: impl std::ops::RangeBounds<usize> + Clone,
) -> Option<(
Peptidoform<Self::Complexity>,
Vec<(Region, usize)>,
Vec<(Annotation, usize)>,
)>
where
Self::Complexity: AtMax<Linear>,
{
self.peptidoform().sub_peptidoform(range.clone()).map(|peptidoform| {
let start = match range.start_bound() {
Bound::Excluded(e) => e.saturating_add(1),
Bound::Included(i) => *i,
Bound::Unbounded => 0,
};
let end = match range.end_bound() {
Bound::Excluded(e) => *e,
Bound::Included(i) => i.saturating_add(1),
Bound::Unbounded => peptidoform.len(),
};
let mut index = 0;
let mut regions = Vec::new();
for (r, len) in self.regions() {
if index + len <= start {
} else if index <= start && index + len > start {
if index + len >= end {
regions.push((r.clone(), end.saturating_sub(start)));
break;
}
regions.push((r.clone(), len - (start - index)));
} else if index + len < end {
regions.push((r.clone(), *len));
} else {
regions.push((r.clone(), end - index));
break;
}
index += len;
}
(
peptidoform,
regions,
self.annotations()
.iter()
.filter(|&(_, i)| range.contains(i))
.map(|(a, i)| (a.clone(), i - start))
.collect(),
)
})
}
}
impl<T: AnnotatedPeptidoform + HasPeptidoformImpl> AnnotatedPeptidoform for &T {
fn regions(&self) -> &[(Region, usize)] {
(*self).regions()
}
fn annotations(&self) -> &[(Annotation, usize)] {
(*self).annotations()
}
}
impl<T: AnnotatedPeptidoform + HasPeptidoformImpl> AnnotatedPeptidoform for std::sync::Arc<T> {
fn regions(&self) -> &[(Region, usize)] {
self.as_ref().regions()
}
fn annotations(&self) -> &[(Annotation, usize)] {
self.as_ref().annotations()
}
}
impl<T: AnnotatedPeptidoform + HasPeptidoformImpl> AnnotatedPeptidoform for std::rc::Rc<T> {
fn regions(&self) -> &[(Region, usize)] {
self.as_ref().regions()
}
fn annotations(&self) -> &[(Annotation, usize)] {
self.as_ref().annotations()
}
}
impl<T: AnnotatedPeptidoform + HasPeptidoformImpl> AnnotatedPeptidoform for Box<T> {
fn regions(&self) -> &[(Region, usize)] {
self.as_ref().regions()
}
fn annotations(&self) -> &[(Annotation, usize)] {
self.as_ref().annotations()
}
}
use bincode::{Decode, Encode};
use itertools::Itertools;
use serde::{Deserialize, Serialize};
#[expect(missing_docs)]
#[derive(Clone, Debug, Decode, Deserialize, Encode, Eq, Hash, PartialEq, Serialize)]
pub enum Region {
Framework(usize),
ComplementarityDetermining(usize),
Hinge(Option<usize>),
ConstantHeavy(usize),
ConstantLight,
SecratoryTail,
MembraneTail(Option<usize>),
Other(String),
Joined(Vec<Self>),
None,
}
impl crate::space::Space for Region {
fn space(&self) -> UsedSpace {
match self {
Self::Framework(d) | Self::ComplementarityDetermining(d) | Self::ConstantHeavy(d) => {
d.space()
}
Self::ConstantLight | Self::None | Self::SecratoryTail => UsedSpace::default(),
Self::Hinge(d) | Self::MembraneTail(d) => d.space(),
Self::Other(d) => d.space(),
Self::Joined(d) => d.space(),
}
.set_total::<Self>()
}
}
#[derive(Clone, Debug, Decode, Deserialize, Encode, Eq, Hash, PartialEq, Serialize)]
pub enum Annotation {
Conserved,
NGlycan,
Other(String),
}
impl crate::space::Space for Annotation {
fn space(&self) -> UsedSpace {
match self {
Self::Conserved | Self::NGlycan => UsedSpace::default(),
Self::Other(d) => d.space(),
}
.set_total::<Self>()
}
}
impl std::fmt::Display for Region {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Framework(n) => write!(f, "FR{n}"),
Self::ComplementarityDetermining(n) => write!(f, "CDR{n}"),
Self::Hinge(n) => write!(f, "H{}", n.map_or(String::new(), |n| n.to_string())),
Self::ConstantHeavy(n) => write!(f, "CH{n}"),
Self::ConstantLight => write!(f, "CL"),
Self::SecratoryTail => write!(f, "CHS"),
Self::MembraneTail(n) => write!(f, "M{}", n.map_or(String::new(), |n| n.to_string())),
Self::Other(o) => write!(f, "{o}"),
Self::Joined(o) => write!(f, "{}", o.iter().join("-")),
Self::None => Ok(()),
}
}
}
impl std::str::FromStr for Region {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"" => Self::None,
"CL" => Self::ConstantLight,
"CHS" => Self::SecratoryTail,
"H" => Self::Hinge(None),
"M" => Self::MembraneTail(None),
cdr if cdr.starts_with("CDR") => cdr[3..].parse::<usize>().map_or_else(
|_| Self::Other(cdr.to_string()),
Self::ComplementarityDetermining,
),
fr if fr.starts_with("FR") => fr[2..]
.parse::<usize>()
.map_or_else(|_| Self::Other(fr.to_string()), Self::Framework),
ch if ch.starts_with("CH") => ch[2..]
.parse::<usize>()
.map_or_else(|_| Self::Other(ch.to_string()), Self::ConstantHeavy),
h if h.starts_with('H') => h[1..]
.parse::<usize>()
.map_or_else(|_| Self::Other(h.to_string()), |c| Self::Hinge(Some(c))),
m if m.starts_with('M') => m[1..].parse::<usize>().map_or_else(
|_| Self::Other(m.to_string()),
|c| Self::MembraneTail(Some(c)),
),
o => Self::Other(o.to_string()),
})
}
}
impl std::str::FromStr for Annotation {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"C" | "Conserved" => Self::Conserved,
"N" | "NGlycan" => Self::NGlycan,
o => Self::Other(o.to_string()),
})
}
}
impl std::fmt::Display for Annotation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Conserved => write!(f, "Conserved"),
Self::NGlycan => write!(f, "NGlycan"),
Self::Other(o) => write!(f, "{o}"),
}
}
}
#[cfg(test)]
mod tests {
use crate::{
ontology::STATIC_ONTOLOGIES,
prelude::{HasPeptidoformImpl, Peptidoform},
sequence::{AnnotatedPeptidoform, Annotation, Linear, Region},
};
struct AP {
peptidoform: Peptidoform<Linear>,
regions: Vec<(Region, usize)>,
annotations: Vec<(Annotation, usize)>,
}
impl AnnotatedPeptidoform for AP {
fn regions(&self) -> &[(Region, usize)] {
&self.regions
}
fn annotations(&self) -> &[(Annotation, usize)] {
&self.annotations
}
}
impl HasPeptidoformImpl for AP {
type Complexity = Linear;
fn peptidoform(&self) -> &Peptidoform<Self::Complexity> {
&self.peptidoform
}
}
#[test]
#[allow(clippy::missing_panics_doc)]
fn annotated_sub_peptidoform() {
let seq = AP {
peptidoform: Peptidoform::pro_forma("PEPTIDE", &STATIC_ONTOLOGIES)
.unwrap()
.0
.into_linear()
.unwrap(),
regions: vec![
(Region::Framework(1), 4),
(Region::ComplementarityDetermining(1), 1),
(Region::Framework(2), 2),
],
annotations: vec![
(Annotation::Conserved, 4),
(Annotation::NGlycan, 4),
(Annotation::Conserved, 6),
],
};
let sub = seq.sub_peptidoform(..4).unwrap();
dbg!(&sub);
assert_eq!(sub.0.len(), 4);
assert_eq!(sub.1.len(), 1);
assert_eq!(sub.1.iter().map(|(_, l)| l).sum::<usize>(), 4);
assert_eq!(sub.2.len(), 0);
let sub = seq.sub_peptidoform(3..6).unwrap();
dbg!(&sub);
assert_eq!(sub.0.len(), 3);
assert_eq!(sub.1.len(), 3);
assert_eq!(sub.1.iter().map(|(_, l)| l).sum::<usize>(), 3);
assert_eq!(sub.2.len(), 2);
let sub = seq.sub_peptidoform(5..7).unwrap();
dbg!(&sub);
assert_eq!(sub.0.len(), 2);
assert_eq!(sub.1.len(), 1);
assert_eq!(sub.1.iter().map(|(_, l)| l).sum::<usize>(), 2);
assert_eq!(sub.2.len(), 1);
let sub = seq.sub_peptidoform(1..3).unwrap();
dbg!(&sub);
assert_eq!(sub.0.len(), 2);
assert_eq!(sub.1.len(), 1);
assert_eq!(sub.1.iter().map(|(_, l)| l).sum::<usize>(), 2);
assert_eq!(sub.2.len(), 0);
let sub = seq.sub_peptidoform(4..5).unwrap();
dbg!(&sub);
assert_eq!(sub.0.len(), 1);
assert_eq!(sub.1.len(), 1);
assert_eq!(sub.1.iter().map(|(_, l)| l).sum::<usize>(), 1);
assert_eq!(sub.2.len(), 2);
}
}