1use super::ExtensionStore;
13use crate::annotation::AtomAnnotations;
14use crate::chunk::AtomChunk;
15use crate::coords::{CoordinateBlock, CoordinateGeneration};
16use crate::index::ModelIndex;
17use crate::symbol::{Interner, SymbolId};
18use crate::topology::Topology;
19use std::fmt;
20use std::sync::Arc;
21
22const PLACEHOLDER_TOLERANCE: f64 = 1e-6;
23
24#[derive(Clone, Debug, Default)]
30pub struct EntryMetadata {
31 pub id: Option<Box<str>>,
33 pub title: Option<Box<str>>,
35 pub method: Option<Box<str>>,
37 pub resolution: Option<f32>,
39}
40
41#[derive(Clone, Copy, PartialEq, Debug)]
43pub struct UnitCell {
44 pub lengths: [f64; 3],
46 pub angles: [f64; 3],
48}
49
50impl UnitCell {
51 #[must_use]
58 pub fn is_placeholder(&self) -> bool {
59 values_are_near(&self.lengths, 1.0) && values_are_near(&self.angles, 90.0)
60 }
61}
62
63#[derive(Clone, Debug)]
65pub enum CoordinateStore {
66 Single(CoordinateBlock),
68 Dense {
73 frames: Vec<CoordinateBlock>,
75 },
76 Ragged {
82 models: Vec<Structure>,
84 },
85}
86
87impl CoordinateStore {
88 pub(crate) fn make_unique_in(
89 &mut self,
90 context: &crate::ExecutionContext,
91 ) -> Result<(), crate::MemoryBudgetError> {
92 match self {
93 Self::Single(block) => block.make_unique_in(context),
94 Self::Dense { frames } => {
95 for frame in frames {
96 frame.make_unique_in(context)?;
97 }
98 Ok(())
99 }
100 Self::Ragged { .. } => Ok(()),
101 }
102 }
103
104 #[must_use]
106 pub fn model_count(&self) -> usize {
107 match self {
108 Self::Single(_) => 1,
109 Self::Dense { frames } => frames.len(),
110 Self::Ragged { models } => models.len(),
111 }
112 }
113
114 #[must_use]
119 pub fn block(&self, model: ModelIndex) -> Option<&CoordinateBlock> {
120 match self {
121 Self::Single(block) => (model.get() == 0).then_some(block),
122 Self::Dense { frames } => frames.get(model.as_usize()),
123 Self::Ragged { .. } => None,
126 }
127 }
128
129 #[must_use]
131 pub const fn is_dense(&self) -> bool {
132 matches!(self, Self::Single(_) | Self::Dense { .. })
133 }
134
135 #[must_use]
137 pub fn ragged_models(&self) -> Option<&[Structure]> {
138 match self {
139 Self::Ragged { models } => Some(models),
140 Self::Single(_) | Self::Dense { .. } => None,
141 }
142 }
143}
144
145#[derive(Clone, Debug)]
147pub struct StructureData {
148 pub entry: EntryMetadata,
150 pub topology: Topology,
152 pub chunks: Arc<Vec<AtomChunk>>,
154 pub bonds: crate::bond::BondTable,
156 pub annotations: AtomAnnotations,
158 pub extensions: ExtensionStore,
160 pub coords: CoordinateStore,
162 pub dictionary: Interner,
164 pub cell: Option<UnitCell>,
166 pub generation: CoordinateGeneration,
168 pub(crate) reservation: Option<Arc<crate::MemoryReservation>>,
169}
170
171#[repr(C)]
186#[derive(Clone, Debug)]
187pub struct Structure(Arc<StructureData>);
188
189impl StructureData {
190 #[must_use]
192 pub fn empty() -> Self {
193 Self {
194 reservation: None,
195 entry: EntryMetadata::default(),
196 topology: Topology::default(),
197 chunks: Arc::new(Vec::new()),
198 bonds: crate::bond::BondTable::default(),
199 annotations: AtomAnnotations::default(),
200 extensions: ExtensionStore::new(),
201 coords: CoordinateStore::Single(CoordinateBlock::new()),
202 dictionary: Interner::new(),
203 cell: None,
204 generation: CoordinateGeneration::INITIAL,
205 }
206 }
207
208 pub(crate) fn atom_count(&self) -> u32 {
209 match self.chunks.last() {
210 Some(chunk) => chunk.atoms().end,
211 None => 0,
212 }
213 }
214}
215
216impl Structure {
217 #[must_use]
219 pub fn new(data: StructureData) -> Self {
220 Self(Arc::new(data))
221 }
222
223 pub(crate) fn retain_reservation(mut self, reservation: Arc<crate::MemoryReservation>) -> Self {
224 let data = Arc::make_mut(&mut self.0);
225 if let CoordinateStore::Ragged { models } = &mut data.coords {
226 for model in models {
227 *model = model.clone().retain_reservation(Arc::clone(&reservation));
228 }
229 }
230 data.reservation = Some(reservation);
231 self
232 }
233
234 #[must_use]
236 pub fn data(&self) -> &StructureData {
237 &self.0
238 }
239
240 #[must_use]
242 pub fn shared(&self) -> Arc<StructureData> {
243 Arc::clone(&self.0)
244 }
245
246 #[must_use]
248 pub fn atom_count(&self) -> u32 {
249 self.0.atom_count()
250 }
251
252 #[must_use]
254 pub fn atom(&self, atom: crate::index::AtomIndex) -> Option<super::AtomRef<'_>> {
255 self.0.atom(atom)
256 }
257
258 #[must_use]
260 pub fn model(&self, model: ModelIndex) -> Option<super::ModelRef<'_>> {
261 self.0.model(model)
262 }
263
264 #[must_use]
270 pub fn model_snapshot(&self, model: ModelIndex) -> Option<(Self, ModelIndex)> {
271 match &self.0.coords {
272 CoordinateStore::Ragged { models } => models
273 .get(model.as_usize())
274 .cloned()
275 .map(|structure| (structure, ModelIndex::new(0))),
276 CoordinateStore::Single(_) | CoordinateStore::Dense { .. } => self
277 .model(model)
278 .map(|_| (self.clone(), model))
279 .or_else(|| {
280 (model.get() == 0 && self.model_count() == 1).then(|| (self.clone(), model))
281 }),
282 }
283 }
284
285 #[must_use]
287 pub fn chain(&self, chain: crate::index::ChainIndex) -> Option<super::ChainRef<'_>> {
288 self.0.chain(chain)
289 }
290
291 #[must_use]
293 pub fn residue(&self, residue: crate::index::ResidueIndex) -> Option<super::ResidueRef<'_>> {
294 self.0.residue(residue)
295 }
296
297 #[must_use]
299 pub fn model_count(&self) -> usize {
300 self.0.coords.model_count()
301 }
302
303 #[must_use]
305 pub fn ragged_models(&self) -> Option<&[Self]> {
306 self.0.coords.ragged_models()
307 }
308
309 #[must_use]
311 pub fn chain_count(&self) -> usize {
312 self.0.topology.chains.len()
313 }
314
315 #[must_use]
317 pub fn residue_count(&self) -> usize {
318 self.0.topology.residues.len()
319 }
320
321 #[must_use]
323 pub fn entity_count(&self) -> usize {
324 self.0.topology.entities.len()
325 }
326
327 #[must_use]
332 pub fn positions(&self) -> &[[f32; 3]] {
333 match self.model_positions(ModelIndex::new(0)) {
334 Some(positions) => positions,
335 None => &[],
336 }
337 }
338
339 #[must_use]
341 pub fn model_positions(&self, model: ModelIndex) -> Option<&[[f32; 3]]> {
342 self.0.coords.block(model).map(CoordinateBlock::as_slice)
343 }
344
345 #[must_use]
347 pub fn generation(&self) -> CoordinateGeneration {
348 self.0.generation
349 }
350
351 #[must_use]
353 pub fn annotations(&self) -> &AtomAnnotations {
354 &self.0.annotations
355 }
356
357 #[must_use]
359 pub fn extensions(&self) -> &ExtensionStore {
360 &self.0.extensions
361 }
362
363 #[must_use]
365 pub fn with_extension<T>(&self, key: &'static str, value: T) -> Self
366 where
367 T: std::any::Any + Send + Sync + std::panic::RefUnwindSafe,
368 {
369 let mut data = self.data().clone();
370 data.extensions.insert(key, value);
371 Self::new(data)
372 }
373
374 #[must_use]
376 pub fn without_extensions(&self) -> Self {
377 if self.0.extensions.is_empty() {
378 return self.clone();
379 }
380 let mut data = self.data().clone();
381 data.extensions.clear();
382 Self::new(data)
383 }
384
385 #[must_use]
387 pub fn resolve(&self, symbol: SymbolId) -> Option<&str> {
388 self.0.dictionary.resolve(symbol)
389 }
390}
391
392impl From<StructureData> for Structure {
393 fn from(data: StructureData) -> Self {
394 Self::new(data)
395 }
396}
397
398fn values_are_near(values: &[f64], expected: f64) -> bool {
399 values
400 .iter()
401 .all(|value| (value - expected).abs() < PLACEHOLDER_TOLERANCE)
402}
403
404impl fmt::Display for Structure {
405 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
406 write!(
407 f,
408 "Structure(models={}, chains={}, residues={}, atoms={})",
409 self.model_count(),
410 self.chain_count(),
411 self.residue_count(),
412 self.atom_count(),
413 )
414 }
415}
416
417#[cfg(test)]
418#[path = "data_tests.rs"]
419mod tests;