Skip to main content

molframe_core/structure/
sequence.rs

1//! Observed, canonical and database-reference sequence views.
2
3use super::ChainRef;
4use crate::ResidueIndex;
5use crate::symbol::SymbolId;
6
7/// Stable extension key for database sequence references and alignments.
8pub const SEQUENCE_REFERENCES_EXTENSION: &str = "molframe.sequence.references.v1";
9
10/// One database sequence named by `_struct_ref`.
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct ReferenceSequence {
13    /// Source row identifier.
14    pub id: Box<str>,
15    /// Entity instantiated by this reference.
16    pub entity_id: Box<str>,
17    /// Database name, such as `UNP`.
18    pub database_name: Option<Box<str>>,
19    /// Database code/name.
20    pub database_code: Option<Box<str>>,
21    /// Stable database accession.
22    pub accession: Option<Box<str>>,
23    /// Deposited one-letter reference sequence, with CIF whitespace removed.
24    pub one_letter_code: Option<Box<str>>,
25}
26
27/// One canonical-to-reference interval from `_struct_ref_seq`.
28#[derive(Clone, Debug, PartialEq, Eq)]
29pub struct ReferenceAlignment {
30    /// Source alignment identifier.
31    pub id: Box<str>,
32    /// Referenced `_struct_ref.id`.
33    pub reference_id: Box<str>,
34    /// Author chain identifiers covered by this alignment.
35    pub chain_ids: Box<[Box<str>]>,
36    /// Inclusive canonical sequence interval.
37    pub canonical: [i32; 2],
38    /// Inclusive reference database interval.
39    pub reference: [i32; 2],
40}
41
42/// Typed reference sequence metadata attached to a structure.
43#[derive(Clone, Debug, Default, PartialEq, Eq)]
44pub struct SequenceReferences {
45    /// Database sequences in source order.
46    pub sequences: Vec<ReferenceSequence>,
47    /// Canonical-to-reference alignments in source order.
48    pub alignments: Vec<ReferenceAlignment>,
49}
50
51/// One observed residue's correspondence across sequence namespaces.
52#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub struct SequenceMapping<'a> {
54    /// Observed structure residue.
55    pub residue: ResidueIndex,
56    /// Canonical entity position from `label_seq_id`.
57    pub canonical_position: Option<i32>,
58    /// Reference database position when an alignment covers it.
59    pub reference_position: Option<i32>,
60    /// Reference identifier supplying that position.
61    pub reference_id: Option<&'a str>,
62}
63
64/// One canonical residue absent from the observed structure.
65#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66pub struct MissingResidue {
67    /// One-based canonical sequence position.
68    pub canonical_position: u32,
69    /// Expected component identifier.
70    pub component: SymbolId,
71}
72
73/// Sequence views carried by a hierarchy chain handle.
74pub trait ChainSequenceExt<'a> {
75    /// Component identifiers actually modelled, in topology order.
76    fn observed_sequence(self) -> Vec<SymbolId>;
77    /// Entity sequence that should have been present.
78    fn canonical_sequence(self) -> &'a [SymbolId];
79    /// Database sequences associated with the chain's entity.
80    fn reference_sequences(self) -> Vec<&'a ReferenceSequence>;
81    /// Observed-to-canonical-to-reference position mapping.
82    fn sequence_mapping(self) -> Vec<SequenceMapping<'a>>;
83    /// Canonical positions not represented by an observed residue.
84    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}