use super::ChainRef;
use crate::ResidueIndex;
use crate::symbol::SymbolId;
pub const SEQUENCE_REFERENCES_EXTENSION: &str = "molframe.sequence.references.v1";
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ReferenceSequence {
pub id: Box<str>,
pub entity_id: Box<str>,
pub database_name: Option<Box<str>>,
pub database_code: Option<Box<str>>,
pub accession: Option<Box<str>>,
pub one_letter_code: Option<Box<str>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ReferenceAlignment {
pub id: Box<str>,
pub reference_id: Box<str>,
pub chain_ids: Box<[Box<str>]>,
pub canonical: [i32; 2],
pub reference: [i32; 2],
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SequenceReferences {
pub sequences: Vec<ReferenceSequence>,
pub alignments: Vec<ReferenceAlignment>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SequenceMapping<'a> {
pub residue: ResidueIndex,
pub canonical_position: Option<i32>,
pub reference_position: Option<i32>,
pub reference_id: Option<&'a str>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MissingResidue {
pub canonical_position: u32,
pub component: SymbolId,
}
pub trait ChainSequenceExt<'a> {
fn observed_sequence(self) -> Vec<SymbolId>;
fn canonical_sequence(self) -> &'a [SymbolId];
fn reference_sequences(self) -> Vec<&'a ReferenceSequence>;
fn sequence_mapping(self) -> Vec<SequenceMapping<'a>>;
fn missing_residues(self) -> Vec<MissingResidue>;
}
impl<'a> ChainSequenceExt<'a> for ChainRef<'a> {
fn observed_sequence(self) -> Vec<SymbolId> {
self.residues()
.filter_map(super::ResidueRef::label_comp_id)
.collect()
}
fn canonical_sequence(self) -> &'a [SymbolId] {
let Some(entity) = self.entity() else {
return &[];
};
self.data.topology.entities.canonical_sequence(entity)
}
fn reference_sequences(self) -> Vec<&'a ReferenceSequence> {
let Some(entity) = self.entity() else {
return Vec::new();
};
let Some(entity_id) = self
.data
.topology
.entities
.id(entity)
.and_then(|id| self.data.dictionary.resolve(id))
else {
return Vec::new();
};
match self.references() {
Some(references) => references
.sequences
.iter()
.filter(|sequence| sequence.entity_id.as_ref() == entity_id)
.collect(),
None => Vec::new(),
}
}
fn sequence_mapping(self) -> Vec<SequenceMapping<'a>> {
let alignment = self.alignment();
self.residues()
.map(|residue| {
let canonical_position = residue.label_seq_id();
let (reference_position, reference_id) = alignment
.and_then(|alignment| {
let position = canonical_position?;
if position < alignment.canonical[0] || position > alignment.canonical[1] {
return None;
}
Some((
alignment.reference[0] + position - alignment.canonical[0],
alignment.reference_id.as_ref(),
))
})
.map_or((None, None), |(position, id)| (Some(position), Some(id)));
SequenceMapping {
residue: residue.index(),
canonical_position,
reference_position,
reference_id,
}
})
.collect()
}
fn missing_residues(self) -> Vec<MissingResidue> {
let observed = self
.residues()
.filter_map(super::ResidueRef::label_seq_id)
.collect::<std::collections::BTreeSet<_>>();
self.canonical_sequence()
.iter()
.enumerate()
.filter_map(|(position, component)| {
let canonical_position = u32::try_from(position).ok()?.checked_add(1)?;
let observed_position = i32::try_from(canonical_position).ok()?;
(!observed.contains(&observed_position)).then_some(MissingResidue {
canonical_position,
component: *component,
})
})
.collect()
}
}
impl<'a> ChainRef<'a> {
fn references(self) -> Option<&'a SequenceReferences> {
self.data.extensions.get(SEQUENCE_REFERENCES_EXTENSION)
}
fn alignment(self) -> Option<&'a ReferenceAlignment> {
let label = self.label();
let auth = self.auth_label();
self.references()?.alignments.iter().find(|alignment| {
alignment
.chain_ids
.iter()
.any(|chain| label == Some(chain.as_ref()) || auth == Some(chain.as_ref()))
})
}
}