molframe_core/structure/
sequence.rs1use super::ChainRef;
4use crate::ResidueIndex;
5use crate::symbol::SymbolId;
6
7pub const SEQUENCE_REFERENCES_EXTENSION: &str = "molframe.sequence.references.v1";
9
10#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct ReferenceSequence {
13 pub id: Box<str>,
15 pub entity_id: Box<str>,
17 pub database_name: Option<Box<str>>,
19 pub database_code: Option<Box<str>>,
21 pub accession: Option<Box<str>>,
23 pub one_letter_code: Option<Box<str>>,
25}
26
27#[derive(Clone, Debug, PartialEq, Eq)]
29pub struct ReferenceAlignment {
30 pub id: Box<str>,
32 pub reference_id: Box<str>,
34 pub chain_ids: Box<[Box<str>]>,
36 pub canonical: [i32; 2],
38 pub reference: [i32; 2],
40}
41
42#[derive(Clone, Debug, Default, PartialEq, Eq)]
44pub struct SequenceReferences {
45 pub sequences: Vec<ReferenceSequence>,
47 pub alignments: Vec<ReferenceAlignment>,
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub struct SequenceMapping<'a> {
54 pub residue: ResidueIndex,
56 pub canonical_position: Option<i32>,
58 pub reference_position: Option<i32>,
60 pub reference_id: Option<&'a str>,
62}
63
64#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66pub struct MissingResidue {
67 pub canonical_position: u32,
69 pub component: SymbolId,
71}
72
73pub trait ChainSequenceExt<'a> {
75 fn observed_sequence(self) -> Vec<SymbolId>;
77 fn canonical_sequence(self) -> &'a [SymbolId];
79 fn reference_sequences(self) -> Vec<&'a ReferenceSequence>;
81 fn sequence_mapping(self) -> Vec<SequenceMapping<'a>>;
83 fn missing_residues(self) -> Vec<MissingResidue>;
85}
86
87impl<'a> ChainSequenceExt<'a> for ChainRef<'a> {
88 fn observed_sequence(self) -> Vec<SymbolId> {
89 self.residues()
90 .filter_map(super::ResidueRef::label_comp_id)
91 .collect()
92 }
93
94 fn canonical_sequence(self) -> &'a [SymbolId] {
95 let Some(entity) = self.entity() else {
96 return &[];
97 };
98 self.data.topology.entities.canonical_sequence(entity)
99 }
100
101 fn reference_sequences(self) -> Vec<&'a ReferenceSequence> {
102 let Some(entity) = self.entity() else {
103 return Vec::new();
104 };
105 let Some(entity_id) = self
106 .data
107 .topology
108 .entities
109 .id(entity)
110 .and_then(|id| self.data.dictionary.resolve(id))
111 else {
112 return Vec::new();
113 };
114 match self.references() {
115 Some(references) => references
116 .sequences
117 .iter()
118 .filter(|sequence| sequence.entity_id.as_ref() == entity_id)
119 .collect(),
120 None => Vec::new(),
121 }
122 }
123
124 fn sequence_mapping(self) -> Vec<SequenceMapping<'a>> {
125 let alignment = self.alignment();
126 self.residues()
127 .map(|residue| {
128 let canonical_position = residue.label_seq_id();
129 let (reference_position, reference_id) = alignment
130 .and_then(|alignment| {
131 let position = canonical_position?;
132 if position < alignment.canonical[0] || position > alignment.canonical[1] {
133 return None;
134 }
135 Some((
136 alignment.reference[0] + position - alignment.canonical[0],
137 alignment.reference_id.as_ref(),
138 ))
139 })
140 .map_or((None, None), |(position, id)| (Some(position), Some(id)));
141 SequenceMapping {
142 residue: residue.index(),
143 canonical_position,
144 reference_position,
145 reference_id,
146 }
147 })
148 .collect()
149 }
150
151 fn missing_residues(self) -> Vec<MissingResidue> {
152 let observed = self
153 .residues()
154 .filter_map(super::ResidueRef::label_seq_id)
155 .collect::<std::collections::BTreeSet<_>>();
156 self.canonical_sequence()
157 .iter()
158 .enumerate()
159 .filter_map(|(position, component)| {
160 let canonical_position = u32::try_from(position).ok()?.checked_add(1)?;
161 let observed_position = i32::try_from(canonical_position).ok()?;
162 (!observed.contains(&observed_position)).then_some(MissingResidue {
163 canonical_position,
164 component: *component,
165 })
166 })
167 .collect()
168 }
169}
170
171impl<'a> ChainRef<'a> {
172 fn references(self) -> Option<&'a SequenceReferences> {
173 self.data.extensions.get(SEQUENCE_REFERENCES_EXTENSION)
174 }
175
176 fn alignment(self) -> Option<&'a ReferenceAlignment> {
177 let label = self.label();
178 let auth = self.auth_label();
179 self.references()?.alignments.iter().find(|alignment| {
180 alignment
181 .chain_ids
182 .iter()
183 .any(|chain| label == Some(chain.as_ref()) || auth == Some(chain.as_ref()))
184 })
185 }
186}