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
37crate::contract::closed_vocabulary!(
38 PolymerKind,
39 "polymer_kind",
40 [
41 (PolymerKind::None, "none"),
42 (PolymerKind::Protein, "protein"),
43 (PolymerKind::Dna, "dna"),
44 (PolymerKind::Rna, "rna"),
45 (PolymerKind::NucleicHybrid, "nucleic-hybrid"),
46 (PolymerKind::Saccharide, "saccharide"),
47 (PolymerKind::Other, "other"),
48 ]
49);
50
51impl PolymerKind {
52 #[must_use]
54 pub const fn is_polymer(self) -> bool {
55 !matches!(self, Self::None)
56 }
57
58 #[must_use]
60 pub const fn is_nucleic(self) -> bool {
61 matches!(self, Self::Dna | Self::Rna | Self::NucleicHybrid)
62 }
63}
64
65#[derive(Clone, Copy, Debug)]
67pub struct ChainRecord {
68 pub label_asym_id: SymbolId,
70 pub auth_asym_id: OptionalSymbol,
72 pub entity: EntityIndex,
74 pub polymer_kind: PolymerKind,
76}
77
78#[derive(Clone, Debug, Default)]
80pub struct ChainTable {
81 first_residue: Arc<Vec<u32>>,
82 residue_count: Arc<Vec<u32>>,
83 label_asym_id: Arc<Vec<SymbolId>>,
84 auth_asym_id: Arc<Vec<OptionalSymbol>>,
85 entity: Arc<Vec<EntityIndex>>,
86 polymer_kind: Arc<Vec<PolymerKind>>,
87}
88
89impl ChainTable {
90 #[must_use]
92 pub fn len(&self) -> usize {
93 self.label_asym_id.len()
94 }
95
96 #[must_use]
98 pub fn is_empty(&self) -> bool {
99 self.label_asym_id.is_empty()
100 }
101
102 pub fn push(
108 &mut self,
109 record: ChainRecord,
110 residues: Range<u32>,
111 ) -> Result<ChainIndex, TableError> {
112 let position = u32::try_from(self.label_asym_id.len())
113 .map_err(|_| TableError::Capacity(CapacityError::new("chain table")))?;
114 let residue_count = residues
115 .end
116 .checked_sub(residues.start)
117 .ok_or(TableError::ReversedRange { table: "chain" })?;
118 Arc::make_mut(&mut self.first_residue).push(residues.start);
119 Arc::make_mut(&mut self.residue_count).push(residue_count);
120 Arc::make_mut(&mut self.label_asym_id).push(record.label_asym_id);
121 Arc::make_mut(&mut self.auth_asym_id).push(record.auth_asym_id);
122 Arc::make_mut(&mut self.entity).push(record.entity);
123 Arc::make_mut(&mut self.polymer_kind).push(record.polymer_kind);
124 Ok(ChainIndex::new(position))
125 }
126
127 #[must_use]
129 pub fn residues(&self, chain: ChainIndex) -> Option<Range<u32>> {
130 stored_range(&self.first_residue, &self.residue_count, chain.as_usize())
131 }
132
133 #[must_use]
140 pub fn containing(&self, residue: u32) -> Option<ChainIndex> {
141 let candidate = self
142 .first_residue
143 .partition_point(|first| *first <= residue)
144 .checked_sub(1)?;
145 let chain = ChainIndex::new(u32::try_from(candidate).ok()?);
146 self.residues(chain)?.contains(&residue).then_some(chain)
147 }
148
149 #[must_use]
151 pub fn label_asym_id(&self, chain: ChainIndex) -> Option<SymbolId> {
152 self.label_asym_id.get(chain.as_usize()).copied()
153 }
154
155 #[must_use]
157 pub fn auth_asym_id(&self, chain: ChainIndex) -> Option<SymbolId> {
158 self.auth_asym_id
159 .get(chain.as_usize())
160 .copied()
161 .and_then(OptionalSymbol::get)
162 }
163
164 pub fn rename(&mut self, chain: ChainIndex, label: SymbolId) -> Result<(), TableError> {
173 let position = chain.as_usize();
174 if position >= self.label_asym_id.len() || position >= self.auth_asym_id.len() {
175 return Err(TableError::MissingIndex { table: "chain" });
176 }
177 let Some(normalised) = Arc::make_mut(&mut self.label_asym_id).get_mut(position) else {
178 return Err(TableError::MissingIndex { table: "chain" });
179 };
180 *normalised = label;
181 let Some(depositor) = Arc::make_mut(&mut self.auth_asym_id).get_mut(position) else {
182 return Err(TableError::MissingIndex { table: "chain" });
183 };
184 *depositor = OptionalSymbol::some(label);
185 Ok(())
186 }
187
188 #[must_use]
190 pub fn entity(&self, chain: ChainIndex) -> Option<EntityIndex> {
191 self.entity.get(chain.as_usize()).copied()
192 }
193
194 #[must_use]
196 pub fn polymer_kind(&self, chain: ChainIndex) -> Option<PolymerKind> {
197 self.polymer_kind.get(chain.as_usize()).copied()
198 }
199
200 pub fn set_polymer_kind(
206 &mut self,
207 chain: ChainIndex,
208 kind: PolymerKind,
209 ) -> Result<(), TableError> {
210 let Some(value) = Arc::make_mut(&mut self.polymer_kind).get_mut(chain.as_usize()) else {
211 return Err(TableError::MissingIndex { table: "chain" });
212 };
213 *value = kind;
214 Ok(())
215 }
216
217 pub fn instances_of(&self, entity: EntityIndex) -> impl Iterator<Item = ChainIndex> + '_ {
223 self.entity
224 .iter()
225 .enumerate()
226 .filter(move |(_, chain_entity)| **chain_entity == entity)
227 .filter_map(|(position, _)| u32::try_from(position).ok().map(ChainIndex::new))
228 }
229
230 pub fn iter(&self) -> impl Iterator<Item = ChainIndex> + '_ {
233 self.label_asym_id
234 .iter()
235 .enumerate()
236 .filter_map(|(position, _)| u32::try_from(position).ok().map(ChainIndex::new))
237 }
238}
239
240fn stored_range(starts: &[u32], counts: &[u32], index: usize) -> Option<Range<u32>> {
241 let start = *starts.get(index)?;
242 let count = *counts.get(index)?;
243
244 Some(start..start.checked_add(count)?)
245}