molframe_core/topology/
chain.rs1use crate::index::{ChainIndex, EntityIndex};
10use crate::limits::{CapacityError, TableError};
11use crate::optional::OptionalSymbol;
12use crate::symbol::SymbolId;
13use std::ops::Range;
14use std::sync::Arc;
15
16#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
18#[non_exhaustive]
19pub enum PolymerKind {
20 #[default]
22 None,
23 Protein,
25 Dna,
27 Rna,
29 NucleicHybrid,
31 Saccharide,
33 Other,
35}
36
37impl PolymerKind {
38 #[must_use]
40 pub const fn is_polymer(self) -> bool {
41 !matches!(self, Self::None)
42 }
43
44 #[must_use]
46 pub const fn is_nucleic(self) -> bool {
47 matches!(self, Self::Dna | Self::Rna | Self::NucleicHybrid)
48 }
49}
50
51#[derive(Clone, Copy, Debug)]
53pub struct ChainRecord {
54 pub label_asym_id: SymbolId,
56 pub auth_asym_id: OptionalSymbol,
58 pub entity: EntityIndex,
60 pub polymer_kind: PolymerKind,
62}
63
64#[derive(Clone, Debug, Default)]
66pub struct ChainTable {
67 first_residue: Arc<Vec<u32>>,
68 residue_count: Arc<Vec<u32>>,
69 label_asym_id: Arc<Vec<SymbolId>>,
70 auth_asym_id: Arc<Vec<OptionalSymbol>>,
71 entity: Arc<Vec<EntityIndex>>,
72 polymer_kind: Arc<Vec<PolymerKind>>,
73}
74
75impl ChainTable {
76 #[must_use]
78 pub fn len(&self) -> usize {
79 self.label_asym_id.len()
80 }
81
82 #[must_use]
84 pub fn is_empty(&self) -> bool {
85 self.label_asym_id.is_empty()
86 }
87
88 pub fn push(
94 &mut self,
95 record: ChainRecord,
96 residues: Range<u32>,
97 ) -> Result<ChainIndex, TableError> {
98 let position = u32::try_from(self.label_asym_id.len())
99 .map_err(|_| TableError::Capacity(CapacityError::new("chain table")))?;
100 let residue_count = residues
101 .end
102 .checked_sub(residues.start)
103 .ok_or(TableError::ReversedRange { table: "chain" })?;
104 Arc::make_mut(&mut self.first_residue).push(residues.start);
105 Arc::make_mut(&mut self.residue_count).push(residue_count);
106 Arc::make_mut(&mut self.label_asym_id).push(record.label_asym_id);
107 Arc::make_mut(&mut self.auth_asym_id).push(record.auth_asym_id);
108 Arc::make_mut(&mut self.entity).push(record.entity);
109 Arc::make_mut(&mut self.polymer_kind).push(record.polymer_kind);
110 Ok(ChainIndex::new(position))
111 }
112
113 #[must_use]
115 pub fn residues(&self, chain: ChainIndex) -> Option<Range<u32>> {
116 stored_range(&self.first_residue, &self.residue_count, chain.as_usize())
117 }
118
119 #[must_use]
126 pub fn containing(&self, residue: u32) -> Option<ChainIndex> {
127 let candidate = self
128 .first_residue
129 .partition_point(|first| *first <= residue)
130 .checked_sub(1)?;
131 let chain = ChainIndex::new(u32::try_from(candidate).ok()?);
132 self.residues(chain)?.contains(&residue).then_some(chain)
133 }
134
135 #[must_use]
137 pub fn label_asym_id(&self, chain: ChainIndex) -> Option<SymbolId> {
138 self.label_asym_id.get(chain.as_usize()).copied()
139 }
140
141 #[must_use]
143 pub fn auth_asym_id(&self, chain: ChainIndex) -> Option<SymbolId> {
144 self.auth_asym_id
145 .get(chain.as_usize())
146 .copied()
147 .and_then(OptionalSymbol::get)
148 }
149
150 pub fn rename(&mut self, chain: ChainIndex, label: SymbolId) -> Result<(), TableError> {
159 let position = chain.as_usize();
160 if position >= self.label_asym_id.len() || position >= self.auth_asym_id.len() {
161 return Err(TableError::MissingIndex { table: "chain" });
162 }
163 let Some(normalised) = Arc::make_mut(&mut self.label_asym_id).get_mut(position) else {
164 return Err(TableError::MissingIndex { table: "chain" });
165 };
166 *normalised = label;
167 let Some(depositor) = Arc::make_mut(&mut self.auth_asym_id).get_mut(position) else {
168 return Err(TableError::MissingIndex { table: "chain" });
169 };
170 *depositor = OptionalSymbol::some(label);
171 Ok(())
172 }
173
174 #[must_use]
176 pub fn entity(&self, chain: ChainIndex) -> Option<EntityIndex> {
177 self.entity.get(chain.as_usize()).copied()
178 }
179
180 #[must_use]
182 pub fn polymer_kind(&self, chain: ChainIndex) -> Option<PolymerKind> {
183 self.polymer_kind.get(chain.as_usize()).copied()
184 }
185
186 pub fn set_polymer_kind(
192 &mut self,
193 chain: ChainIndex,
194 kind: PolymerKind,
195 ) -> Result<(), TableError> {
196 let Some(value) = Arc::make_mut(&mut self.polymer_kind).get_mut(chain.as_usize()) else {
197 return Err(TableError::MissingIndex { table: "chain" });
198 };
199 *value = kind;
200 Ok(())
201 }
202
203 pub fn instances_of(&self, entity: EntityIndex) -> impl Iterator<Item = ChainIndex> + '_ {
209 self.entity
210 .iter()
211 .enumerate()
212 .filter(move |(_, chain_entity)| **chain_entity == entity)
213 .filter_map(|(position, _)| u32::try_from(position).ok().map(ChainIndex::new))
214 }
215
216 pub fn iter(&self) -> impl Iterator<Item = ChainIndex> + '_ {
219 self.label_asym_id
220 .iter()
221 .enumerate()
222 .filter_map(|(position, _)| u32::try_from(position).ok().map(ChainIndex::new))
223 }
224}
225
226fn stored_range(starts: &[u32], counts: &[u32], index: usize) -> Option<Range<u32>> {
227 let start = *starts.get(index)?;
228 let count = *counts.get(index)?;
229
230 Some(start..start.checked_add(count)?)
231}