Skip to main content

molframe_core/topology/
entity.rs

1//! The distinct chemical species a structure contains.
2//!
3//! An entity is not a level of the hierarchy. Several chains may be copies of
4//! one, and no chain contains one, so it lives in its own table with a reference
5//! from each chain. That is what makes "which chains are copies of the same
6//! molecule?" a lookup rather than a sequence comparison, and what lets sequence
7//! and source metadata survive a round trip instead of being repeated per chain
8//! or dropped.
9//!
10//! The canonical sequence held here is the sequence that *should* be present.
11//! The residues in a chain are what was actually modelled, and the difference
12//! between them is the unmodelled region — which is data, not an absence.
13
14use crate::index::EntityIndex;
15use crate::limits::CapacityError;
16use crate::optional::OptionalSymbol;
17use crate::symbol::SymbolId;
18use std::ops::Range;
19use std::sync::Arc;
20
21/// What kind of chemical species an entity is.
22#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
23#[non_exhaustive]
24pub enum EntityKind {
25    /// A polymer: protein, nucleic acid, or a hybrid of the two.
26    Polymer,
27    /// A single chemical component that is not part of a polymer.
28    NonPolymer,
29    /// Water.
30    Water,
31    /// A branched species, such as an oligosaccharide.
32    Branched,
33    /// The file did not say.
34    #[default]
35    Unknown,
36}
37
38/// The entity table.
39///
40/// Canonical sequences share one pool rather than owning a collection each, so
41/// a structure with three hundred entities holds one allocation for their
42/// sequences instead of three hundred.
43#[derive(Clone, Debug, Default)]
44pub struct EntityTable {
45    kind: Arc<Vec<EntityKind>>,
46    id: Arc<Vec<OptionalSymbol>>,
47    description: Arc<Vec<OptionalSymbol>>,
48    sequence_span: Arc<Vec<Range<u32>>>,
49    sequence_pool: Arc<Vec<SymbolId>>,
50}
51
52impl EntityTable {
53    /// The number of entities.
54    #[must_use]
55    pub fn len(&self) -> usize {
56        self.kind.len()
57    }
58
59    /// Returns true when the structure declares no entities.
60    #[must_use]
61    pub fn is_empty(&self) -> bool {
62        self.kind.is_empty()
63    }
64
65    /// Appends an entity, returning its position.
66    ///
67    /// # Errors
68    ///
69    /// Returns [`CapacityError`] when the entity table exceeds its index space.
70    pub fn push(
71        &mut self,
72        id: SymbolId,
73        kind: EntityKind,
74        description: OptionalSymbol,
75        canonical_sequence: &[SymbolId],
76    ) -> Result<EntityIndex, CapacityError> {
77        self.push_with_optional_id(
78            OptionalSymbol::some(id),
79            kind,
80            description,
81            canonical_sequence,
82        )
83    }
84
85    /// Appends an entity whose source did not declare an identifier.
86    ///
87    /// # Errors
88    ///
89    /// Returns [`CapacityError`] when the entity table exceeds its index space.
90    pub fn push_without_id(
91        &mut self,
92        kind: EntityKind,
93        description: OptionalSymbol,
94        canonical_sequence: &[SymbolId],
95    ) -> Result<EntityIndex, CapacityError> {
96        self.push_with_optional_id(OptionalSymbol::NONE, kind, description, canonical_sequence)
97    }
98
99    fn push_with_optional_id(
100        &mut self,
101        id: OptionalSymbol,
102        kind: EntityKind,
103        description: OptionalSymbol,
104        canonical_sequence: &[SymbolId],
105    ) -> Result<EntityIndex, CapacityError> {
106        let error = || CapacityError::new("entity table");
107        let start = u32::try_from(self.sequence_pool.len()).map_err(|_| error())?;
108        let added = u32::try_from(canonical_sequence.len()).map_err(|_| error())?;
109        let end = start.checked_add(added).ok_or_else(error)?;
110        let position = u32::try_from(self.kind.len()).map_err(|_| error())?;
111        Arc::make_mut(&mut self.sequence_pool).extend_from_slice(canonical_sequence);
112        Arc::make_mut(&mut self.kind).push(kind);
113        Arc::make_mut(&mut self.id).push(id);
114        Arc::make_mut(&mut self.description).push(description);
115        Arc::make_mut(&mut self.sequence_span).push(start..end);
116        Ok(EntityIndex::new(position))
117    }
118
119    /// What kind of species this entity is.
120    #[must_use]
121    pub fn kind(&self, entity: EntityIndex) -> Option<EntityKind> {
122        self.kind.get(entity.as_usize()).copied()
123    }
124
125    /// The identifier the file gave this entity.
126    #[must_use]
127    pub fn id(&self, entity: EntityIndex) -> Option<SymbolId> {
128        self.id
129            .get(entity.as_usize())
130            .copied()
131            .and_then(OptionalSymbol::get)
132    }
133
134    /// The entity carrying `id`, where the file declared one.
135    ///
136    /// Entity tables are normally small and this is used while building one
137    /// chain at a time, not in an atom loop. Keeping the lookup here avoids a
138    /// second index whose lifetime and invalidation would duplicate the table.
139    ///
140    #[must_use]
141    pub fn find_by_id(&self, id: SymbolId) -> Option<EntityIndex> {
142        self.id
143            .iter()
144            .position(|candidate| candidate.get() == Some(id))
145            .and_then(|position| u32::try_from(position).ok())
146            .map(EntityIndex::new)
147    }
148
149    /// The entity's description, if the file carried one.
150    #[must_use]
151    pub fn description(&self, entity: EntityIndex) -> Option<SymbolId> {
152        self.description
153            .get(entity.as_usize())
154            .copied()
155            .and_then(OptionalSymbol::get)
156    }
157
158    /// The sequence this entity should have, one component per position.
159    ///
160    /// Compare it against the residues a chain actually holds to find what was
161    /// not modelled.
162    #[must_use]
163    pub fn canonical_sequence(&self, entity: EntityIndex) -> &[SymbolId] {
164        let Some(span) = self.sequence_span.get(entity.as_usize()) else {
165            return &[];
166        };
167
168        sequence_slice(&self.sequence_pool, span)
169    }
170
171    /// Every entity position.
172    ///
173    pub fn iter(&self) -> impl Iterator<Item = EntityIndex> + '_ {
174        self.kind
175            .iter()
176            .enumerate()
177            .filter_map(|(position, _)| u32::try_from(position).ok().map(EntityIndex::new))
178    }
179}
180
181fn sequence_slice<'a>(pool: &'a [SymbolId], span: &Range<u32>) -> &'a [SymbolId] {
182    let Ok(start) = usize::try_from(span.start) else {
183        return &[];
184    };
185    let Ok(end) = usize::try_from(span.end) else {
186        return &[];
187    };
188
189    match pool.get(start..end) {
190        Some(sequence) => sequence,
191        None => &[],
192    }
193}