molframe_core/structure/
handle.rs1use super::data::StructureData;
12use super::handle_lookup::{find_named, range_or_empty, recorded_value};
13use crate::chunk::AtomChunk;
14use crate::element::Element;
15use crate::index::{AtomIndex, ChainIndex, EntityIndex, ModelIndex, ResidueIndex};
16use crate::symbol::{AltId, SymbolId};
17use crate::topology::PolymerKind;
18
19#[derive(Clone, Copy, Debug)]
21pub struct ModelRef<'a> {
22 data: &'a StructureData,
23 index: ModelIndex,
24}
25
26#[derive(Clone, Copy, Debug)]
28pub struct ChainRef<'a> {
29 pub(crate) data: &'a StructureData,
30 pub(crate) index: ChainIndex,
31}
32
33#[derive(Clone, Copy, Debug)]
35pub struct ResidueRef<'a> {
36 data: &'a StructureData,
37 index: ResidueIndex,
38}
39
40#[derive(Clone, Copy, Debug)]
42pub struct AtomRef<'a> {
43 data: &'a StructureData,
44 index: AtomIndex,
45}
46
47impl<'a> ModelRef<'a> {
48 const fn new(data: &'a StructureData, index: ModelIndex) -> Self {
49 Self { data, index }
50 }
51
52 #[must_use]
54 pub const fn index(self) -> ModelIndex {
55 self.index
56 }
57
58 #[must_use]
60 pub fn number(self) -> Option<i32> {
61 self.data.topology.models.model_num(self.index)
62 }
63
64 pub fn chains(self) -> impl Iterator<Item = ChainRef<'a>> {
66 let data = self.data;
67 let range = range_or_empty(self.data.topology.models.chains(self.index));
68
69 range.map(move |position| ChainRef::new(data, ChainIndex::new(position)))
70 }
71
72 #[must_use]
74 pub fn chain(self, label: &str) -> Option<ChainRef<'a>> {
75 let wanted = self.data.dictionary.get(label)?;
76
77 self.chains().find(|chain| chain.has_label(wanted))
78 }
79
80 #[must_use]
82 pub fn chain_at(self, position: usize) -> Option<ChainRef<'a>> {
83 self.chains().nth(position)
84 }
85}
86
87impl<'a> ChainRef<'a> {
88 const fn new(data: &'a StructureData, index: ChainIndex) -> Self {
89 Self { data, index }
90 }
91
92 #[must_use]
94 pub const fn index(self) -> ChainIndex {
95 self.index
96 }
97
98 #[must_use]
100 pub fn label_asym_id(self) -> Option<SymbolId> {
101 self.data.topology.chains.label_asym_id(self.index)
102 }
103
104 #[must_use]
106 pub fn label(self) -> Option<&'a str> {
107 self.data.dictionary.resolve(self.label_asym_id()?)
108 }
109
110 #[must_use]
112 pub fn auth_asym_id(self) -> Option<SymbolId> {
113 self.data.topology.chains.auth_asym_id(self.index)
114 }
115
116 #[must_use]
118 pub fn auth_label(self) -> Option<&'a str> {
119 self.data.dictionary.resolve(self.auth_asym_id()?)
120 }
121
122 #[must_use]
124 pub fn entity(self) -> Option<EntityIndex> {
125 self.data.topology.chains.entity(self.index)
126 }
127
128 #[must_use]
130 pub fn polymer_kind(self) -> PolymerKind {
131 match self.data.topology.chains.polymer_kind(self.index) {
132 Some(kind) => kind,
133 None => PolymerKind::None,
134 }
135 }
136
137 pub fn residues(self) -> impl Iterator<Item = ResidueRef<'a>> {
139 let data = self.data;
140 let range = range_or_empty(self.data.topology.chains.residues(self.index));
141
142 range.map(move |position| ResidueRef::new(data, ResidueIndex::new(position)))
143 }
144
145 #[must_use]
147 pub fn residue(self, number: i32) -> Option<ResidueRef<'a>> {
148 self.residues().find(|residue| residue.has_number(number))
149 }
150
151 fn has_label(self, wanted: SymbolId) -> bool {
152 self.label_asym_id() == Some(wanted) || self.auth_asym_id() == Some(wanted)
153 }
154
155 #[must_use]
157 pub fn residue_at(self, position: usize) -> Option<ResidueRef<'a>> {
158 self.residues().nth(position)
159 }
160}
161
162impl<'a> ResidueRef<'a> {
163 const fn new(data: &'a StructureData, index: ResidueIndex) -> Self {
164 Self { data, index }
165 }
166
167 #[must_use]
169 pub const fn index(self) -> ResidueIndex {
170 self.index
171 }
172
173 #[must_use]
175 pub fn label_comp_id(self) -> Option<SymbolId> {
176 self.data.topology.residues.label_comp_id(self.index)
177 }
178
179 #[must_use]
181 pub fn name(self) -> Option<&'a str> {
182 self.label_comp_id()
183 .and_then(|symbol| self.data.dictionary.resolve(symbol))
184 }
185
186 #[must_use]
188 pub fn auth_comp_id(self) -> Option<SymbolId> {
189 self.data.topology.residues.auth_comp_id(self.index)
190 }
191
192 #[must_use]
194 pub fn auth_name(self) -> Option<&'a str> {
195 self.data.dictionary.resolve(self.auth_comp_id()?)
196 }
197
198 #[must_use]
200 pub fn label_seq_id(self) -> Option<i32> {
201 self.data.topology.residues.label_seq_id(self.index)
202 }
203
204 #[must_use]
206 pub fn auth_seq_id(self) -> Option<i32> {
207 self.data.topology.residues.auth_seq_id(self.index)
208 }
209
210 #[must_use]
212 pub fn ins_code(self) -> Option<&'a str> {
213 self.data
214 .topology
215 .residues
216 .ins_code(self.index)
217 .and_then(|symbol| self.data.dictionary.resolve(symbol))
218 }
219
220 #[must_use]
222 pub fn is_het(self) -> bool {
223 self.data.topology.residues.is_het(self.index)
224 }
225
226 pub fn atoms(self) -> impl Iterator<Item = AtomRef<'a>> {
228 let data = self.data;
229 let range = range_or_empty(self.data.topology.residues.atoms(self.index));
230
231 range.map(move |position| AtomRef::new(data, AtomIndex::new(position)))
232 }
233
234 #[must_use]
241 pub fn atom(self, name: &str) -> Option<AtomRef<'a>> {
242 let wanted = self.data.dictionary.get(name)?;
243 let range = range_or_empty(self.data.topology.residues.atoms(self.index));
244 let position = find_named(&self.data.chunks, range, wanted)?;
245
246 Some(AtomRef::new(self.data, AtomIndex::new(position)))
247 }
248
249 fn has_number(self, number: i32) -> bool {
250 self.auth_seq_id() == Some(number) || self.label_seq_id() == Some(number)
251 }
252
253 #[must_use]
255 pub fn atom_at(self, position: usize) -> Option<AtomRef<'a>> {
256 self.atoms().nth(position)
257 }
258}
259
260impl<'a> AtomRef<'a> {
261 const fn new(data: &'a StructureData, index: AtomIndex) -> Self {
262 Self { data, index }
263 }
264
265 #[must_use]
267 pub const fn index(self) -> AtomIndex {
268 self.index
269 }
270
271 fn located(self) -> Option<(&'a AtomChunk, u32)> {
278 let position = self.index.get();
279 let candidate = self
280 .data
281 .chunks
282 .partition_point(|chunk| chunk.atoms().end <= position);
283
284 let chunk = self.data.chunks.get(candidate)?;
285 let atoms = chunk.atoms();
286
287 if !atoms.contains(&position) {
288 return None;
289 }
290
291 Some((chunk, position - atoms.start))
292 }
293
294 #[must_use]
296 pub fn name_symbol(self) -> Option<SymbolId> {
297 let (chunk, local) = self.located()?;
298 chunk.atom_name(local)
299 }
300
301 #[must_use]
303 pub fn name(self) -> Option<&'a str> {
304 self.name_symbol()
305 .and_then(|symbol| self.data.dictionary.resolve(symbol))
306 }
307
308 #[must_use]
310 pub fn auth_name_symbol(self) -> Option<SymbolId> {
311 let (chunk, local) = self.located()?;
312 chunk.auth_atom_name(local)
313 }
314
315 #[must_use]
317 pub fn auth_name(self) -> Option<&'a str> {
318 self.data.dictionary.resolve(self.auth_name_symbol()?)
319 }
320
321 #[must_use]
323 pub fn element(self) -> Option<Element> {
324 let (chunk, local) = self.located()?;
325 chunk.element(local)
326 }
327
328 #[must_use]
330 pub fn alt_id(self) -> Option<AltId> {
331 let (chunk, local) = self.located()?;
332 chunk.alt_id(local)
333 }
334
335 #[must_use]
337 pub fn alt_label(self) -> Option<&'a str> {
338 self.data.dictionary.resolve(self.alt_id()?.symbol()?)
339 }
340
341 #[must_use]
346 pub fn component_id(self) -> Option<SymbolId> {
347 let (chunk, local) = self.located()?;
348 if let Some(component) = chunk.alternate_component_id(local) {
349 return Some(component);
350 }
351 self.residue()?.label_comp_id()
352 }
353
354 #[must_use]
356 pub fn component_name(self) -> Option<&'a str> {
357 self.data.dictionary.resolve(self.component_id()?)
358 }
359
360 #[must_use]
362 pub fn b_factor(self) -> Option<f32> {
363 let (chunk, local) = self.located()?;
364
365 chunk.b_factor(local).and_then(recorded_value)
366 }
367
368 #[must_use]
370 pub fn occupancy(self) -> Option<f32> {
371 let (chunk, local) = self.located()?;
372
373 chunk.occupancy(local).and_then(recorded_value)
374 }
375
376 #[must_use]
378 pub fn formal_charge(self) -> Option<i8> {
379 let (chunk, local) = self.located()?;
380 let (value, presence) = chunk.formal_charge(local)?;
381 presence.is_present().then_some(value)
382 }
383
384 #[must_use]
386 pub fn atom_site_id(self) -> Option<u32> {
387 let (chunk, local) = self.located()?;
388 chunk.atom_site_id(local)
389 }
390
391 #[must_use]
393 pub fn position(self) -> Option<[f32; 3]> {
394 let (chunk, local) = self.located()?;
395
396 if !chunk.has_position(local) {
397 return None;
398 }
399
400 self.data
401 .coords
402 .block(ModelIndex::new(0))?
403 .as_slice()
404 .get(self.index.as_usize())
405 .copied()
406 }
407
408 #[must_use]
410 pub fn residue(self) -> Option<ResidueRef<'a>> {
411 let (chunk, local) = self.located()?;
412 let index = chunk.residue(local, &self.data.topology.residues)?;
413
414 Some(ResidueRef::new(self.data, index))
415 }
416}
417
418impl StructureData {
419 #[must_use]
421 pub fn model(&self, model: ModelIndex) -> Option<ModelRef<'_>> {
422 (model.as_usize() < self.topology.models.len()).then_some(ModelRef::new(self, model))
423 }
424
425 pub fn models(&self) -> impl Iterator<Item = ModelRef<'_>> {
427 self.topology
428 .models
429 .iter()
430 .map(move |index| ModelRef::new(self, index))
431 }
432
433 #[must_use]
435 pub fn chain(&self, chain: ChainIndex) -> Option<ChainRef<'_>> {
436 (chain.as_usize() < self.topology.chains.len()).then_some(ChainRef {
437 data: self,
438 index: chain,
439 })
440 }
441
442 #[must_use]
444 pub fn residue(&self, residue: ResidueIndex) -> Option<ResidueRef<'_>> {
445 (residue.as_usize() < self.topology.residues.len()).then_some(ResidueRef {
446 data: self,
447 index: residue,
448 })
449 }
450
451 pub fn chains(&self) -> impl Iterator<Item = ChainRef<'_>> {
453 self.topology
454 .chains
455 .iter()
456 .map(move |index| ChainRef::new(self, index))
457 }
458
459 #[must_use]
461 pub fn chain_named(&self, label: &str) -> Option<ChainRef<'_>> {
462 let wanted = self.dictionary.get(label)?;
463 self.chains().find(|chain| {
464 chain.label_asym_id() == Some(wanted) || chain.auth_asym_id() == Some(wanted)
465 })
466 }
467
468 pub fn residues(&self) -> impl Iterator<Item = ResidueRef<'_>> {
470 (0..self.topology.residues.len()).filter_map(move |position| {
471 u32::try_from(position)
472 .ok()
473 .map(|position| ResidueRef::new(self, ResidueIndex::new(position)))
474 })
475 }
476
477 pub fn atoms(&self) -> impl Iterator<Item = AtomRef<'_>> {
479 (0..self.atom_count()).map(move |position| AtomRef::new(self, AtomIndex::new(position)))
480 }
481
482 #[must_use]
484 pub fn atom(&self, atom: AtomIndex) -> Option<AtomRef<'_>> {
485 let count = self.chunks.last().map_or(0, |chunk| chunk.atoms().end);
486 (atom.get() < count).then_some(AtomRef {
487 data: self,
488 index: atom,
489 })
490 }
491}
492
493#[cfg(test)]
494#[path = "handle_tests.rs"]
495mod tests;