1use super::ExtensionStore;
13use crate::annotation::AtomAnnotations;
14use crate::chunk::AtomChunk;
15use crate::coords::{CoordinateBlock, CoordinateGeneration};
16use crate::index::ModelIndex;
17use crate::secondary::SecondaryStructure;
18use crate::symbol::{Interner, SymbolId};
19use crate::topology::Topology;
20use std::fmt;
21use std::sync::Arc;
22
23const PLACEHOLDER_TOLERANCE: f64 = 1e-6;
24
25#[derive(Clone, Debug, Default)]
31pub struct EntryMetadata {
32 pub id: Option<Box<str>>,
34 pub title: Option<Box<str>>,
36 pub method: Option<Box<str>>,
38 pub resolution: Option<f32>,
40 pub space_group: Option<Box<str>>,
43}
44
45#[derive(Clone, Copy, PartialEq, Debug)]
47pub struct UnitCell {
48 pub lengths: [f64; 3],
50 pub angles: [f64; 3],
52}
53
54impl UnitCell {
55 #[must_use]
62 pub fn is_placeholder(&self) -> bool {
63 values_are_near(&self.lengths, 1.0) && values_are_near(&self.angles, 90.0)
64 }
65}
66
67#[derive(Clone, Debug)]
69pub enum CoordinateStore {
70 Single(CoordinateBlock),
72 Dense {
77 frames: Vec<CoordinateBlock>,
79 },
80 Ragged {
86 models: Vec<Structure>,
88 },
89}
90
91impl CoordinateStore {
92 pub(crate) fn make_unique_in(
93 &mut self,
94 context: &crate::ExecutionContext,
95 ) -> Result<(), crate::MemoryBudgetError> {
96 match self {
97 Self::Single(block) => block.make_unique_in(context),
98 Self::Dense { frames } => {
99 for frame in frames {
100 frame.make_unique_in(context)?;
101 }
102 Ok(())
103 }
104 Self::Ragged { .. } => Ok(()),
105 }
106 }
107
108 #[must_use]
110 pub fn model_count(&self) -> usize {
111 match self {
112 Self::Single(_) => 1,
113 Self::Dense { frames } => frames.len(),
114 Self::Ragged { models } => models.len(),
115 }
116 }
117
118 #[must_use]
123 pub fn block(&self, model: ModelIndex) -> Option<&CoordinateBlock> {
124 match self {
125 Self::Single(block) => (model.get() == 0).then_some(block),
126 Self::Dense { frames } => frames.get(model.as_usize()),
127 Self::Ragged { .. } => None,
130 }
131 }
132
133 #[must_use]
135 pub const fn is_dense(&self) -> bool {
136 matches!(self, Self::Single(_) | Self::Dense { .. })
137 }
138
139 #[must_use]
141 pub fn ragged_models(&self) -> Option<&[Structure]> {
142 match self {
143 Self::Ragged { models } => Some(models),
144 Self::Single(_) | Self::Dense { .. } => None,
145 }
146 }
147}
148
149#[derive(Clone, Debug)]
151pub struct StructureData {
152 pub entry: EntryMetadata,
154 pub topology: Topology,
156 pub chunks: Arc<Vec<AtomChunk>>,
158 pub bonds: crate::bond::BondTable,
160 pub anisotropy: crate::anisotropy::AnisotropyTable,
162 pub secondary_structure: Arc<Vec<SecondaryStructure>>,
164 pub annotations: AtomAnnotations,
166 pub extensions: ExtensionStore,
168 pub coords: CoordinateStore,
170 pub dictionary: Interner,
172 pub cell: Option<UnitCell>,
174 pub generation: CoordinateGeneration,
176 pub(crate) reservation: Option<Arc<crate::MemoryReservation>>,
177}
178
179#[repr(C)]
194#[derive(Clone, Debug)]
195pub struct Structure(Arc<StructureData>);
196
197impl StructureData {
198 #[must_use]
200 pub fn empty() -> Self {
201 Self {
202 reservation: None,
203 entry: EntryMetadata::default(),
204 topology: Topology::default(),
205 chunks: Arc::new(Vec::new()),
206 bonds: crate::bond::BondTable::default(),
207 anisotropy: crate::anisotropy::AnisotropyTable::default(),
208 secondary_structure: Arc::new(Vec::new()),
209 annotations: AtomAnnotations::default(),
210 extensions: ExtensionStore::new(),
211 coords: CoordinateStore::Single(CoordinateBlock::new()),
212 dictionary: Interner::new(),
213 cell: None,
214 generation: CoordinateGeneration::INITIAL,
215 }
216 }
217
218 pub(crate) fn atom_count(&self) -> u32 {
219 match self.chunks.last() {
220 Some(chunk) => chunk.atoms().end,
221 None => 0,
222 }
223 }
224}
225
226impl Structure {
227 #[must_use]
229 pub fn new(data: StructureData) -> Self {
230 Self(Arc::new(data))
231 }
232
233 pub(crate) fn retain_reservation(mut self, reservation: Arc<crate::MemoryReservation>) -> Self {
234 let data = Arc::make_mut(&mut self.0);
235 if let CoordinateStore::Ragged { models } = &mut data.coords {
236 for model in models {
237 *model = model.clone().retain_reservation(Arc::clone(&reservation));
238 }
239 }
240 data.reservation = Some(reservation);
241 self
242 }
243
244 #[must_use]
246 pub fn data(&self) -> &StructureData {
247 &self.0
248 }
249
250 #[must_use]
252 pub fn shared(&self) -> Arc<StructureData> {
253 Arc::clone(&self.0)
254 }
255
256 #[must_use]
258 pub fn atom_count(&self) -> u32 {
259 self.0.atom_count()
260 }
261
262 #[must_use]
264 pub fn atom(&self, atom: crate::index::AtomIndex) -> Option<super::AtomRef<'_>> {
265 self.0.atom(atom)
266 }
267
268 #[must_use]
270 pub fn model(&self, model: ModelIndex) -> Option<super::ModelRef<'_>> {
271 self.0.model(model)
272 }
273
274 #[must_use]
280 pub fn model_snapshot(&self, model: ModelIndex) -> Option<(Self, ModelIndex)> {
281 match &self.0.coords {
282 CoordinateStore::Ragged { models } => models
283 .get(model.as_usize())
284 .cloned()
285 .map(|structure| (structure, ModelIndex::new(0))),
286 CoordinateStore::Single(_) | CoordinateStore::Dense { .. } => self
287 .model(model)
288 .map(|_| (self.clone(), model))
289 .or_else(|| {
290 (model.get() == 0 && self.model_count() == 1).then(|| (self.clone(), model))
291 }),
292 }
293 }
294
295 #[must_use]
297 pub fn chain(&self, chain: crate::index::ChainIndex) -> Option<super::ChainRef<'_>> {
298 self.0.chain(chain)
299 }
300
301 #[must_use]
303 pub fn residue(&self, residue: crate::index::ResidueIndex) -> Option<super::ResidueRef<'_>> {
304 self.0.residue(residue)
305 }
306
307 #[must_use]
309 pub fn model_count(&self) -> usize {
310 self.0.coords.model_count()
311 }
312
313 #[must_use]
315 pub fn ragged_models(&self) -> Option<&[Self]> {
316 self.0.coords.ragged_models()
317 }
318
319 #[must_use]
321 pub fn chain_count(&self) -> usize {
322 self.0.topology.chains.len()
323 }
324
325 #[must_use]
327 pub fn residue_count(&self) -> usize {
328 self.0.topology.residues.len()
329 }
330
331 #[must_use]
333 pub fn entity_count(&self) -> usize {
334 self.0.topology.entities.len()
335 }
336
337 #[must_use]
342 pub fn positions(&self) -> &[[f32; 3]] {
343 match self.model_positions(ModelIndex::new(0)) {
344 Some(positions) => positions,
345 None => &[],
346 }
347 }
348
349 #[must_use]
351 pub fn model_positions(&self, model: ModelIndex) -> Option<&[[f32; 3]]> {
352 self.0.coords.block(model).map(CoordinateBlock::as_slice)
353 }
354
355 #[must_use]
357 pub fn generation(&self) -> CoordinateGeneration {
358 self.0.generation
359 }
360
361 #[must_use]
363 pub fn annotations(&self) -> &AtomAnnotations {
364 &self.0.annotations
365 }
366
367 #[must_use]
369 pub fn secondary_structure(&self) -> &[SecondaryStructure] {
370 &self.0.secondary_structure
371 }
372
373 #[must_use]
375 pub fn extensions(&self) -> &ExtensionStore {
376 &self.0.extensions
377 }
378
379 #[must_use]
381 pub fn with_extension<T>(&self, key: &'static str, value: T) -> Self
382 where
383 T: std::any::Any + Send + Sync + std::panic::RefUnwindSafe,
384 {
385 let mut data = self.data().clone();
386 data.extensions.insert(key, value);
387 Self::new(data)
388 }
389
390 #[must_use]
392 pub fn without_extensions(&self) -> Self {
393 if self.0.extensions.is_empty() {
394 return self.clone();
395 }
396 let mut data = self.data().clone();
397 data.extensions.clear();
398 Self::new(data)
399 }
400
401 #[must_use]
403 pub fn resolve(&self, symbol: SymbolId) -> Option<&str> {
404 self.0.dictionary.resolve(symbol)
405 }
406}
407
408impl From<StructureData> for Structure {
409 fn from(data: StructureData) -> Self {
410 Self::new(data)
411 }
412}
413
414fn values_are_near(values: &[f64], expected: f64) -> bool {
415 values
416 .iter()
417 .all(|value| (value - expected).abs() < PLACEHOLDER_TOLERANCE)
418}
419
420impl fmt::Display for Structure {
421 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
422 write!(
423 f,
424 "Structure(models={}, chains={}, residues={}, atoms={})",
425 self.model_count(),
426 self.chain_count(),
427 self.residue_count(),
428 self.atom_count(),
429 )
430 }
431}
432
433#[cfg(test)]
434#[path = "data_tests.rs"]
435mod tests;