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}
41
42#[derive(Clone, Copy, PartialEq, Debug)]
44pub struct UnitCell {
45 pub lengths: [f64; 3],
47 pub angles: [f64; 3],
49}
50
51impl UnitCell {
52 #[must_use]
59 pub fn is_placeholder(&self) -> bool {
60 values_are_near(&self.lengths, 1.0) && values_are_near(&self.angles, 90.0)
61 }
62}
63
64#[derive(Clone, Debug)]
66pub enum CoordinateStore {
67 Single(CoordinateBlock),
69 Dense {
74 frames: Vec<CoordinateBlock>,
76 },
77 Ragged {
83 models: Vec<Structure>,
85 },
86}
87
88impl CoordinateStore {
89 pub(crate) fn make_unique_in(
90 &mut self,
91 context: &crate::ExecutionContext,
92 ) -> Result<(), crate::MemoryBudgetError> {
93 match self {
94 Self::Single(block) => block.make_unique_in(context),
95 Self::Dense { frames } => {
96 for frame in frames {
97 frame.make_unique_in(context)?;
98 }
99 Ok(())
100 }
101 Self::Ragged { .. } => Ok(()),
102 }
103 }
104
105 #[must_use]
107 pub fn model_count(&self) -> usize {
108 match self {
109 Self::Single(_) => 1,
110 Self::Dense { frames } => frames.len(),
111 Self::Ragged { models } => models.len(),
112 }
113 }
114
115 #[must_use]
120 pub fn block(&self, model: ModelIndex) -> Option<&CoordinateBlock> {
121 match self {
122 Self::Single(block) => (model.get() == 0).then_some(block),
123 Self::Dense { frames } => frames.get(model.as_usize()),
124 Self::Ragged { .. } => None,
127 }
128 }
129
130 #[must_use]
132 pub const fn is_dense(&self) -> bool {
133 matches!(self, Self::Single(_) | Self::Dense { .. })
134 }
135
136 #[must_use]
138 pub fn ragged_models(&self) -> Option<&[Structure]> {
139 match self {
140 Self::Ragged { models } => Some(models),
141 Self::Single(_) | Self::Dense { .. } => None,
142 }
143 }
144}
145
146#[derive(Clone, Debug)]
148pub struct StructureData {
149 pub entry: EntryMetadata,
151 pub topology: Topology,
153 pub chunks: Arc<Vec<AtomChunk>>,
155 pub bonds: crate::bond::BondTable,
157 pub anisotropy: crate::anisotropy::AnisotropyTable,
159 pub secondary_structure: Arc<Vec<SecondaryStructure>>,
161 pub annotations: AtomAnnotations,
163 pub extensions: ExtensionStore,
165 pub coords: CoordinateStore,
167 pub dictionary: Interner,
169 pub cell: Option<UnitCell>,
171 pub generation: CoordinateGeneration,
173 pub(crate) reservation: Option<Arc<crate::MemoryReservation>>,
174}
175
176#[repr(C)]
191#[derive(Clone, Debug)]
192pub struct Structure(Arc<StructureData>);
193
194impl StructureData {
195 #[must_use]
197 pub fn empty() -> Self {
198 Self {
199 reservation: None,
200 entry: EntryMetadata::default(),
201 topology: Topology::default(),
202 chunks: Arc::new(Vec::new()),
203 bonds: crate::bond::BondTable::default(),
204 anisotropy: crate::anisotropy::AnisotropyTable::default(),
205 secondary_structure: Arc::new(Vec::new()),
206 annotations: AtomAnnotations::default(),
207 extensions: ExtensionStore::new(),
208 coords: CoordinateStore::Single(CoordinateBlock::new()),
209 dictionary: Interner::new(),
210 cell: None,
211 generation: CoordinateGeneration::INITIAL,
212 }
213 }
214
215 pub(crate) fn atom_count(&self) -> u32 {
216 match self.chunks.last() {
217 Some(chunk) => chunk.atoms().end,
218 None => 0,
219 }
220 }
221}
222
223impl Structure {
224 #[must_use]
226 pub fn new(data: StructureData) -> Self {
227 Self(Arc::new(data))
228 }
229
230 pub(crate) fn retain_reservation(mut self, reservation: Arc<crate::MemoryReservation>) -> Self {
231 let data = Arc::make_mut(&mut self.0);
232 if let CoordinateStore::Ragged { models } = &mut data.coords {
233 for model in models {
234 *model = model.clone().retain_reservation(Arc::clone(&reservation));
235 }
236 }
237 data.reservation = Some(reservation);
238 self
239 }
240
241 #[must_use]
243 pub fn data(&self) -> &StructureData {
244 &self.0
245 }
246
247 #[must_use]
249 pub fn shared(&self) -> Arc<StructureData> {
250 Arc::clone(&self.0)
251 }
252
253 #[must_use]
255 pub fn atom_count(&self) -> u32 {
256 self.0.atom_count()
257 }
258
259 #[must_use]
261 pub fn atom(&self, atom: crate::index::AtomIndex) -> Option<super::AtomRef<'_>> {
262 self.0.atom(atom)
263 }
264
265 #[must_use]
267 pub fn model(&self, model: ModelIndex) -> Option<super::ModelRef<'_>> {
268 self.0.model(model)
269 }
270
271 #[must_use]
277 pub fn model_snapshot(&self, model: ModelIndex) -> Option<(Self, ModelIndex)> {
278 match &self.0.coords {
279 CoordinateStore::Ragged { models } => models
280 .get(model.as_usize())
281 .cloned()
282 .map(|structure| (structure, ModelIndex::new(0))),
283 CoordinateStore::Single(_) | CoordinateStore::Dense { .. } => self
284 .model(model)
285 .map(|_| (self.clone(), model))
286 .or_else(|| {
287 (model.get() == 0 && self.model_count() == 1).then(|| (self.clone(), model))
288 }),
289 }
290 }
291
292 #[must_use]
294 pub fn chain(&self, chain: crate::index::ChainIndex) -> Option<super::ChainRef<'_>> {
295 self.0.chain(chain)
296 }
297
298 #[must_use]
300 pub fn residue(&self, residue: crate::index::ResidueIndex) -> Option<super::ResidueRef<'_>> {
301 self.0.residue(residue)
302 }
303
304 #[must_use]
306 pub fn model_count(&self) -> usize {
307 self.0.coords.model_count()
308 }
309
310 #[must_use]
312 pub fn ragged_models(&self) -> Option<&[Self]> {
313 self.0.coords.ragged_models()
314 }
315
316 #[must_use]
318 pub fn chain_count(&self) -> usize {
319 self.0.topology.chains.len()
320 }
321
322 #[must_use]
324 pub fn residue_count(&self) -> usize {
325 self.0.topology.residues.len()
326 }
327
328 #[must_use]
330 pub fn entity_count(&self) -> usize {
331 self.0.topology.entities.len()
332 }
333
334 #[must_use]
339 pub fn positions(&self) -> &[[f32; 3]] {
340 match self.model_positions(ModelIndex::new(0)) {
341 Some(positions) => positions,
342 None => &[],
343 }
344 }
345
346 #[must_use]
348 pub fn model_positions(&self, model: ModelIndex) -> Option<&[[f32; 3]]> {
349 self.0.coords.block(model).map(CoordinateBlock::as_slice)
350 }
351
352 #[must_use]
354 pub fn generation(&self) -> CoordinateGeneration {
355 self.0.generation
356 }
357
358 #[must_use]
360 pub fn annotations(&self) -> &AtomAnnotations {
361 &self.0.annotations
362 }
363
364 #[must_use]
366 pub fn secondary_structure(&self) -> &[SecondaryStructure] {
367 &self.0.secondary_structure
368 }
369
370 #[must_use]
372 pub fn extensions(&self) -> &ExtensionStore {
373 &self.0.extensions
374 }
375
376 #[must_use]
378 pub fn with_extension<T>(&self, key: &'static str, value: T) -> Self
379 where
380 T: std::any::Any + Send + Sync + std::panic::RefUnwindSafe,
381 {
382 let mut data = self.data().clone();
383 data.extensions.insert(key, value);
384 Self::new(data)
385 }
386
387 #[must_use]
389 pub fn without_extensions(&self) -> Self {
390 if self.0.extensions.is_empty() {
391 return self.clone();
392 }
393 let mut data = self.data().clone();
394 data.extensions.clear();
395 Self::new(data)
396 }
397
398 #[must_use]
400 pub fn resolve(&self, symbol: SymbolId) -> Option<&str> {
401 self.0.dictionary.resolve(symbol)
402 }
403}
404
405impl From<StructureData> for Structure {
406 fn from(data: StructureData) -> Self {
407 Self::new(data)
408 }
409}
410
411fn values_are_near(values: &[f64], expected: f64) -> bool {
412 values
413 .iter()
414 .all(|value| (value - expected).abs() < PLACEHOLDER_TOLERANCE)
415}
416
417impl fmt::Display for Structure {
418 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
419 write!(
420 f,
421 "Structure(models={}, chains={}, residues={}, atoms={})",
422 self.model_count(),
423 self.chain_count(),
424 self.residue_count(),
425 self.atom_count(),
426 )
427 }
428}
429
430#[cfg(test)]
431#[path = "data_tests.rs"]
432mod tests;