molframe_core/topology/
residue.rs1use super::csr::stored_range;
9use crate::column::BitVec;
10use crate::index::ResidueIndex;
11use crate::limits::{CapacityError, TableError};
12use crate::optional::{OptionalI32, OptionalSymbol};
13use crate::symbol::SymbolId;
14use std::ops::Range;
15use std::sync::Arc;
16
17#[derive(Clone, Debug, Default)]
19pub struct ResidueTable {
20 first_atom: Arc<Vec<u32>>,
21 atom_count: Arc<Vec<u32>>,
22 label_comp_id: Arc<Vec<SymbolId>>,
23 auth_comp_id: Arc<Vec<OptionalSymbol>>,
24 label_seq_id: Arc<Vec<OptionalI32>>,
25 auth_seq_id: Arc<Vec<OptionalI32>>,
26 ins_code: Arc<Vec<OptionalSymbol>>,
27 het: BitVec,
28}
29
30#[derive(Clone, Copy, Debug)]
32pub struct ResidueRecord {
33 pub label_comp_id: SymbolId,
35 pub auth_comp_id: OptionalSymbol,
37 pub label_seq_id: OptionalI32,
39 pub auth_seq_id: OptionalI32,
41 pub ins_code: OptionalSymbol,
43 pub het: bool,
45}
46
47impl ResidueTable {
48 #[must_use]
50 pub fn len(&self) -> usize {
51 self.label_comp_id.len()
52 }
53
54 #[must_use]
56 pub fn is_empty(&self) -> bool {
57 self.label_comp_id.is_empty()
58 }
59
60 pub fn push(
66 &mut self,
67 record: ResidueRecord,
68 atoms: Range<u32>,
69 ) -> Result<ResidueIndex, TableError> {
70 let capacity = || TableError::Capacity(CapacityError::new("residue table"));
71 let position = u32::try_from(self.label_comp_id.len()).map_err(|_| capacity())?;
72 let atom_count = atoms
73 .end
74 .checked_sub(atoms.start)
75 .ok_or(TableError::ReversedRange { table: "residue" })?;
76 self.het.try_push(record.het).map_err(|_| capacity())?;
77 Arc::make_mut(&mut self.first_atom).push(atoms.start);
78 Arc::make_mut(&mut self.atom_count).push(atom_count);
79 Arc::make_mut(&mut self.label_comp_id).push(record.label_comp_id);
80 Arc::make_mut(&mut self.auth_comp_id).push(record.auth_comp_id);
81 Arc::make_mut(&mut self.label_seq_id).push(record.label_seq_id);
82 Arc::make_mut(&mut self.auth_seq_id).push(record.auth_seq_id);
83 Arc::make_mut(&mut self.ins_code).push(record.ins_code);
84 Ok(ResidueIndex::new(position))
85 }
86
87 pub fn set_atoms(
97 &mut self,
98 residue: ResidueIndex,
99 atoms: Range<u32>,
100 ) -> Result<(), TableError> {
101 let atom_count = atoms
102 .end
103 .checked_sub(atoms.start)
104 .ok_or(TableError::ReversedRange { table: "residue" })?;
105 let Some(first) = Arc::make_mut(&mut self.first_atom).get_mut(residue.as_usize()) else {
106 return Err(TableError::MissingIndex { table: "residue" });
107 };
108 *first = atoms.start;
109 if let Some(count) = Arc::make_mut(&mut self.atom_count).get_mut(residue.as_usize()) {
110 *count = atom_count;
111 return Ok(());
112 }
113 Err(TableError::MissingIndex { table: "residue" })
114 }
115
116 pub(crate) fn last_atom_end(&self) -> Option<u32> {
117 self.first_atom
118 .last()
119 .copied()?
120 .checked_add(self.atom_count.last().copied()?)
121 }
122
123 #[must_use]
125 pub fn atoms(&self, residue: ResidueIndex) -> Option<Range<u32>> {
126 stored_range(&self.first_atom, &self.atom_count, residue.as_usize())
127 }
128
129 #[must_use]
131 pub fn label_comp_id(&self, residue: ResidueIndex) -> Option<SymbolId> {
132 self.label_comp_id.get(residue.as_usize()).copied()
133 }
134
135 #[must_use]
137 pub fn auth_comp_id(&self, residue: ResidueIndex) -> Option<SymbolId> {
138 self.auth_comp_id
139 .get(residue.as_usize())
140 .copied()
141 .and_then(OptionalSymbol::get)
142 }
143
144 #[must_use]
146 pub fn label_seq_id(&self, residue: ResidueIndex) -> Option<i32> {
147 self.label_seq_id
148 .get(residue.as_usize())
149 .copied()
150 .and_then(OptionalI32::get)
151 }
152
153 pub fn set_label_seq_id(&mut self, residue: ResidueIndex, seq: i32) -> Result<(), TableError> {
159 let slot = Arc::make_mut(&mut self.label_seq_id)
160 .get_mut(residue.as_usize())
161 .ok_or(TableError::MissingIndex { table: "residue" })?;
162 *slot = OptionalI32::some(seq);
163 Ok(())
164 }
165
166 #[must_use]
168 pub fn auth_seq_id(&self, residue: ResidueIndex) -> Option<i32> {
169 self.auth_seq_id
170 .get(residue.as_usize())
171 .copied()
172 .and_then(OptionalI32::get)
173 }
174
175 #[must_use]
180 pub fn ins_code(&self, residue: ResidueIndex) -> Option<SymbolId> {
181 self.ins_code
182 .get(residue.as_usize())
183 .copied()
184 .and_then(OptionalSymbol::get)
185 }
186
187 #[must_use]
189 pub fn is_het(&self, residue: ResidueIndex) -> bool {
190 self.het.test(residue.get())
191 }
192
193 #[must_use]
198 pub fn containing(&self, atom: u32) -> Option<ResidueIndex> {
199 let insertion = self.first_atom.partition_point(|first| *first <= atom);
200
201 let candidate = insertion.checked_sub(1)?;
202 let first = *self.first_atom.get(candidate)?;
203 let count = *self.atom_count.get(candidate)?;
204 let end = first.checked_add(count)?;
205
206 if atom < first || atom >= end {
207 return None;
208 }
209
210 let position = u32::try_from(candidate).ok()?;
211
212 Some(ResidueIndex::new(position))
213 }
214}