1use super::ExtensionStore;
13use crate::annotation::AtomAnnotations;
14use crate::chunk::AtomChunk;
15use crate::coords::{CoordinateBlock, CoordinateGeneration};
16use crate::index::ModelIndex;
17use crate::secondary::{SecondarySource, 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 pub input_name: Option<Box<str>>,
45 pub input_sha256: Option<crate::contract::ContentDigest>,
49}
50
51#[derive(Clone, Copy, PartialEq, Debug)]
53pub struct UnitCell {
54 pub lengths: [f64; 3],
56 pub angles: [f64; 3],
58}
59
60impl UnitCell {
61 #[must_use]
68 pub fn is_placeholder(&self) -> bool {
69 values_are_near(&self.lengths, 1.0) && values_are_near(&self.angles, 90.0)
70 }
71}
72
73#[derive(Clone, Debug)]
75pub enum CoordinateStore {
76 Single(CoordinateBlock),
78 Dense {
83 frames: Vec<CoordinateBlock>,
85 },
86 Ragged {
92 models: Vec<Structure>,
94 },
95}
96
97impl CoordinateStore {
98 pub(crate) fn make_unique_in(
99 &mut self,
100 context: &crate::ExecutionContext,
101 ) -> Result<(), crate::MemoryBudgetError> {
102 match self {
103 Self::Single(block) => block.make_unique_in(context),
104 Self::Dense { frames } => {
105 for frame in frames {
106 frame.make_unique_in(context)?;
107 }
108 Ok(())
109 }
110 Self::Ragged { .. } => Ok(()),
111 }
112 }
113
114 #[must_use]
116 pub fn model_count(&self) -> usize {
117 match self {
118 Self::Single(_) => 1,
119 Self::Dense { frames } => frames.len(),
120 Self::Ragged { models } => models.len(),
121 }
122 }
123
124 #[must_use]
129 pub fn block(&self, model: ModelIndex) -> Option<&CoordinateBlock> {
130 match self {
131 Self::Single(block) => (model.get() == 0).then_some(block),
132 Self::Dense { frames } => frames.get(model.as_usize()),
133 Self::Ragged { .. } => None,
136 }
137 }
138
139 #[must_use]
141 pub const fn is_dense(&self) -> bool {
142 matches!(self, Self::Single(_) | Self::Dense { .. })
143 }
144
145 #[must_use]
147 pub fn ragged_models(&self) -> Option<&[Structure]> {
148 match self {
149 Self::Ragged { models } => Some(models),
150 Self::Single(_) | Self::Dense { .. } => None,
151 }
152 }
153}
154
155#[derive(Clone, Debug)]
157pub struct StructureData {
158 pub entry: EntryMetadata,
160 pub topology: Topology,
162 pub chunks: Arc<Vec<AtomChunk>>,
164 pub bonds: crate::bond::BondTable,
166 pub anisotropy: crate::anisotropy::AnisotropyTable,
168 pub secondary_structure: Arc<Vec<SecondaryStructure>>,
171 pub secondary_source: Arc<Vec<SecondarySource>>,
174 pub annotations: AtomAnnotations,
176 pub extensions: ExtensionStore,
178 pub coords: CoordinateStore,
180 pub dictionary: Interner,
182 pub cell: Option<UnitCell>,
184 pub generation: CoordinateGeneration,
186 pub(crate) reservation: Option<Arc<crate::MemoryReservation>>,
187}
188
189#[repr(C)]
204#[derive(Clone, Debug)]
205pub struct Structure(Arc<StructureData>);
206
207impl StructureData {
208 #[must_use]
210 pub fn empty() -> Self {
211 Self {
212 reservation: None,
213 entry: EntryMetadata::default(),
214 topology: Topology::default(),
215 chunks: Arc::new(Vec::new()),
216 bonds: crate::bond::BondTable::default(),
217 anisotropy: crate::anisotropy::AnisotropyTable::default(),
218 secondary_structure: Arc::new(Vec::new()),
219 secondary_source: Arc::new(Vec::new()),
220 annotations: AtomAnnotations::default(),
221 extensions: ExtensionStore::new(),
222 coords: CoordinateStore::Single(CoordinateBlock::new()),
223 dictionary: Interner::new(),
224 cell: None,
225 generation: CoordinateGeneration::INITIAL,
226 }
227 }
228
229 pub(crate) fn atom_count(&self) -> u32 {
230 match self.chunks.last() {
231 Some(chunk) => chunk.atoms().end,
232 None => 0,
233 }
234 }
235}
236
237impl Structure {
238 #[must_use]
240 pub fn new(data: StructureData) -> Self {
241 Self(Arc::new(data))
242 }
243
244 pub(crate) fn retain_reservation(mut self, reservation: Arc<crate::MemoryReservation>) -> Self {
245 let data = Arc::make_mut(&mut self.0);
246 if let CoordinateStore::Ragged { models } = &mut data.coords {
247 for model in models {
248 *model = model.clone().retain_reservation(Arc::clone(&reservation));
249 }
250 }
251 data.reservation = Some(reservation);
252 self
253 }
254
255 #[must_use]
259 pub fn with_input(
260 mut self,
261 name: Option<Box<str>>,
262 digest: Option<crate::contract::ContentDigest>,
263 ) -> Self {
264 let data = Arc::make_mut(&mut self.0);
265 data.entry.input_name = name;
266 data.entry.input_sha256 = digest;
267 self
268 }
269
270 #[must_use]
272 pub fn data(&self) -> &StructureData {
273 &self.0
274 }
275
276 #[must_use]
278 pub fn shared(&self) -> Arc<StructureData> {
279 Arc::clone(&self.0)
280 }
281
282 #[must_use]
284 pub fn atom_count(&self) -> u32 {
285 self.0.atom_count()
286 }
287
288 #[must_use]
290 pub fn atom(&self, atom: crate::index::AtomIndex) -> Option<super::AtomRef<'_>> {
291 self.0.atom(atom)
292 }
293
294 #[must_use]
296 pub fn model(&self, model: ModelIndex) -> Option<super::ModelRef<'_>> {
297 self.0.model(model)
298 }
299
300 #[must_use]
306 pub fn model_snapshot(&self, model: ModelIndex) -> Option<(Self, ModelIndex)> {
307 match &self.0.coords {
308 CoordinateStore::Ragged { models } => models
309 .get(model.as_usize())
310 .cloned()
311 .map(|structure| (structure, ModelIndex::new(0))),
312 CoordinateStore::Single(_) | CoordinateStore::Dense { .. } => self
313 .model(model)
314 .map(|_| (self.clone(), model))
315 .or_else(|| {
316 (model.get() == 0 && self.model_count() == 1).then(|| (self.clone(), model))
317 }),
318 }
319 }
320
321 #[must_use]
323 pub fn chain(&self, chain: crate::index::ChainIndex) -> Option<super::ChainRef<'_>> {
324 self.0.chain(chain)
325 }
326
327 #[must_use]
329 pub fn residue(&self, residue: crate::index::ResidueIndex) -> Option<super::ResidueRef<'_>> {
330 self.0.residue(residue)
331 }
332
333 #[must_use]
335 pub fn model_count(&self) -> usize {
336 self.0.coords.model_count()
337 }
338
339 #[must_use]
341 pub fn ragged_models(&self) -> Option<&[Self]> {
342 self.0.coords.ragged_models()
343 }
344
345 #[must_use]
347 pub fn chain_count(&self) -> usize {
348 self.0.topology.chains.len()
349 }
350
351 #[must_use]
353 pub fn residue_count(&self) -> usize {
354 self.0.topology.residues.len()
355 }
356
357 #[must_use]
359 pub fn entity_count(&self) -> usize {
360 self.0.topology.entities.len()
361 }
362
363 #[must_use]
368 pub fn positions(&self) -> &[[f32; 3]] {
369 match self.model_positions(ModelIndex::new(0)) {
370 Some(positions) => positions,
371 None => &[],
372 }
373 }
374
375 #[must_use]
377 pub fn model_positions(&self, model: ModelIndex) -> Option<&[[f32; 3]]> {
378 self.0.coords.block(model).map(CoordinateBlock::as_slice)
379 }
380
381 #[must_use]
383 pub fn generation(&self) -> CoordinateGeneration {
384 self.0.generation
385 }
386
387 #[must_use]
389 pub fn annotations(&self) -> &AtomAnnotations {
390 &self.0.annotations
391 }
392
393 #[must_use]
395 pub fn secondary_structure(&self) -> &[SecondaryStructure] {
396 &self.0.secondary_structure
397 }
398
399 #[must_use]
402 pub fn secondary_source(&self) -> &[SecondarySource] {
403 &self.0.secondary_source
404 }
405
406 #[must_use]
408 pub fn extensions(&self) -> &ExtensionStore {
409 &self.0.extensions
410 }
411
412 #[must_use]
414 pub fn with_extension<T>(&self, key: &'static str, value: T) -> Self
415 where
416 T: std::any::Any + Send + Sync + std::panic::RefUnwindSafe,
417 {
418 let mut data = self.data().clone();
419 data.extensions.insert(key, value);
420 Self::new(data)
421 }
422
423 #[must_use]
425 pub fn without_extensions(&self) -> Self {
426 if self.0.extensions.is_empty() {
427 return self.clone();
428 }
429 let mut data = self.data().clone();
430 data.extensions.clear();
431 Self::new(data)
432 }
433
434 #[must_use]
436 pub fn resolve(&self, symbol: SymbolId) -> Option<&str> {
437 self.0.dictionary.resolve(symbol)
438 }
439}
440
441impl From<StructureData> for Structure {
442 fn from(data: StructureData) -> Self {
443 Self::new(data)
444 }
445}
446
447fn values_are_near(values: &[f64], expected: f64) -> bool {
448 values
449 .iter()
450 .all(|value| (value - expected).abs() < PLACEHOLDER_TOLERANCE)
451}
452
453impl fmt::Display for Structure {
454 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
455 write!(
456 f,
457 "Structure(models={}, chains={}, residues={}, atoms={})",
458 self.model_count(),
459 self.chain_count(),
460 self.residue_count(),
461 self.atom_count(),
462 )
463 }
464}
465
466#[cfg(test)]
467#[path = "data_tests.rs"]
468mod tests;