1use super::{
4 BondChunk, ChunkDescriptor, ChunkId, DatasetDescriptor, DatasetId, FrameChunk, LogicalRow,
5 PayloadKind, PropertyChunk, ProviderError, StructureChunk,
6};
7use crate::chunk::TARGET_CHUNK_ATOMS;
8use crate::structure::Structure;
9use std::sync::Arc;
10
11pub const TARGET_CHUNK_BONDS: u32 = 65_536;
13
14#[derive(Clone, Debug)]
16pub struct BondChunkProvider {
17 structure: Structure,
18 dataset: DatasetDescriptor,
19 atom_dataset: DatasetId,
20 atom_logical_start: LogicalRow,
21}
22
23impl BondChunkProvider {
24 pub fn new(
30 dataset: DatasetId,
31 first_chunk: ChunkId,
32 atom_dataset: DatasetId,
33 atom_logical_start: LogicalRow,
34 structure: Structure,
35 ) -> Result<Self, ProviderError> {
36 Self::with_rows_per_chunk(
37 dataset,
38 first_chunk,
39 atom_dataset,
40 atom_logical_start,
41 structure,
42 TARGET_CHUNK_BONDS,
43 )
44 }
45
46 pub fn with_rows_per_chunk(
52 dataset: DatasetId,
53 first_chunk: ChunkId,
54 atom_dataset: DatasetId,
55 atom_logical_start: LogicalRow,
56 structure: Structure,
57 rows_per_chunk: u32,
58 ) -> Result<Self, ProviderError> {
59 if !structure.data().bonds.is_available() {
60 return Err(ProviderError::BondTopologyUnavailable);
61 }
62 validate_atom_endpoint_range(atom_dataset, atom_logical_start, structure.atom_count())?;
63 let logical_rows = u64::try_from(structure.data().bonds.len()).map_err(|_| {
64 ProviderError::IdentityOverflow {
65 dataset,
66 field: "bond table rows",
67 }
68 })?;
69 let descriptor = DatasetDescriptor::regular(
70 dataset,
71 PayloadKind::BondTopology,
72 logical_rows,
73 rows_per_chunk,
74 first_chunk,
75 )?;
76 Ok(Self {
77 structure,
78 dataset: descriptor,
79 atom_dataset,
80 atom_logical_start,
81 })
82 }
83
84 #[must_use]
86 pub const fn dataset(&self) -> DatasetDescriptor {
87 self.dataset
88 }
89
90 pub fn chunk(&self, chunk: ChunkId) -> Result<BondChunk, ProviderError> {
96 let descriptor = self.dataset.regular_chunk(chunk)?;
97 let start = u32::try_from(descriptor.logical_start().get()).map_err(|_| {
98 ProviderError::IdentityOverflow {
99 dataset: self.dataset.id(),
100 field: "bond source row",
101 }
102 })?;
103 let end = start
104 .checked_add(descriptor.rows())
105 .ok_or(ProviderError::IdentityOverflow {
106 dataset: self.dataset.id(),
107 field: "bond source range",
108 })?;
109 BondChunk::shared(
110 descriptor,
111 self.structure.clone(),
112 start..end,
113 self.atom_dataset,
114 self.atom_logical_start,
115 )
116 }
117}
118
119fn storage_descriptor(
120 dataset: &DatasetDescriptor,
121 structure: &Structure,
122 chunk: ChunkId,
123) -> Result<(usize, ChunkDescriptor), ProviderError> {
124 let ordinal = dataset.chunk_ordinal(chunk)?;
125 let index = usize::try_from(ordinal).map_err(|_| ProviderError::IdentityOverflow {
126 dataset: dataset.id(),
127 field: "storage chunk index",
128 })?;
129 let storage = structure
130 .data()
131 .chunks
132 .get(index)
133 .ok_or(ProviderError::StorageChunkUnavailable { index })?;
134 let descriptor = ChunkDescriptor::new(
135 dataset.id(),
136 chunk,
137 LogicalRow::new(u64::from(storage.atoms().start)),
138 storage.len(),
139 )?;
140 Ok((index, descriptor))
141}
142
143pub(super) fn validate_atom_endpoint_range(
144 dataset: DatasetId,
145 first: LogicalRow,
146 rows: u32,
147) -> Result<(), ProviderError> {
148 if rows == 0 {
149 return Ok(());
150 }
151 first
152 .get()
153 .checked_add(u64::from(rows - 1))
154 .map(|_| ())
155 .ok_or(ProviderError::IdentityOverflow {
156 dataset,
157 field: "atom endpoint range",
158 })
159}
160
161#[derive(Clone, Debug)]
163pub struct StructureChunkProvider {
164 structure: Structure,
165 dataset: DatasetDescriptor,
166}
167
168impl StructureChunkProvider {
169 pub fn new(
175 dataset: DatasetId,
176 first_chunk: ChunkId,
177 structure: Structure,
178 ) -> Result<Self, ProviderError> {
179 let chunk_count = u64::try_from(structure.data().chunks.len()).map_err(|_| {
180 ProviderError::IdentityOverflow {
181 dataset,
182 field: "structure chunk count",
183 }
184 })?;
185 let descriptor = DatasetDescriptor::source_defined(
186 dataset,
187 PayloadKind::Structure,
188 u64::from(structure.atom_count()),
189 chunk_count,
190 first_chunk,
191 TARGET_CHUNK_ATOMS,
192 )?;
193 Ok(Self {
194 structure,
195 dataset: descriptor,
196 })
197 }
198
199 #[must_use]
201 pub const fn dataset(&self) -> DatasetDescriptor {
202 self.dataset
203 }
204
205 pub fn chunk(&self, chunk: ChunkId) -> Result<StructureChunk, ProviderError> {
211 let (index, descriptor) = storage_descriptor(&self.dataset, &self.structure, chunk)?;
212 StructureChunk::shared(descriptor, self.structure.clone(), index)
213 }
214}
215
216#[derive(Clone, Debug)]
218pub struct PropertyChunkProvider {
219 structure: Structure,
220 name: Arc<str>,
221 dataset: DatasetDescriptor,
222}
223
224impl PropertyChunkProvider {
225 pub fn plddt(
231 dataset: DatasetId,
232 first_chunk: ChunkId,
233 structure: Structure,
234 ) -> Result<Self, ProviderError> {
235 Self::new(
236 dataset,
237 first_chunk,
238 structure,
239 crate::annotation::PLDDT_ANNOTATION,
240 )
241 }
242
243 pub fn new(
249 dataset: DatasetId,
250 first_chunk: ChunkId,
251 structure: Structure,
252 name: impl Into<Arc<str>>,
253 ) -> Result<Self, ProviderError> {
254 let name = name.into();
255 let property = structure.annotations().get(&name).ok_or_else(|| {
256 ProviderError::PropertyUnavailable {
257 name: Box::from(name.as_ref()),
258 }
259 })?;
260 if property.len() != structure.atom_count() {
261 return Err(ProviderError::PropertyLengthMismatch {
262 name: Box::from(name.as_ref()),
263 expected: structure.atom_count(),
264 actual: property.len(),
265 });
266 }
267 let chunk_count = u64::try_from(structure.data().chunks.len()).map_err(|_| {
268 ProviderError::IdentityOverflow {
269 dataset,
270 field: "property chunk count",
271 }
272 })?;
273 let descriptor = DatasetDescriptor::source_defined(
274 dataset,
275 PayloadKind::Property,
276 u64::from(structure.atom_count()),
277 chunk_count,
278 first_chunk,
279 TARGET_CHUNK_ATOMS,
280 )?;
281 Ok(Self {
282 structure,
283 name,
284 dataset: descriptor,
285 })
286 }
287
288 #[must_use]
290 pub const fn dataset(&self) -> DatasetDescriptor {
291 self.dataset
292 }
293
294 pub fn chunk(&self, chunk: ChunkId) -> Result<PropertyChunk, ProviderError> {
300 let (index, descriptor) = storage_descriptor(&self.dataset, &self.structure, chunk)?;
301 PropertyChunk::shared(
302 descriptor,
303 self.structure.clone(),
304 Arc::clone(&self.name),
305 index,
306 )
307 }
308}
309
310#[derive(Clone, Debug)]
312pub struct FrameChunkProvider {
313 structure: Structure,
314 model: crate::ModelIndex,
315 storage_chunks: std::ops::Range<usize>,
316 dataset: DatasetDescriptor,
317}
318
319impl FrameChunkProvider {
320 pub fn new(
326 dataset: DatasetId,
327 first_chunk: ChunkId,
328 structure: Structure,
329 model: crate::ModelIndex,
330 ) -> Result<Self, ProviderError> {
331 let Some(coordinates) = structure.data().coords.block(model) else {
332 return Err(ProviderError::ModelUnavailable { model: model.get() });
333 };
334 let storage_chunks = model_chunk_range(&structure, model);
335 let chunk_count =
336 u64::try_from(storage_chunks.len()).map_err(|_| ProviderError::IdentityOverflow {
337 dataset,
338 field: "frame chunk count",
339 })?;
340 let descriptor = DatasetDescriptor::source_defined(
341 dataset,
342 PayloadKind::Frame,
343 u64::from(coordinates.len()),
344 chunk_count,
345 first_chunk,
346 TARGET_CHUNK_ATOMS,
347 )?;
348 Ok(Self {
349 structure,
350 model,
351 storage_chunks,
352 dataset: descriptor,
353 })
354 }
355
356 #[must_use]
358 pub const fn dataset(&self) -> DatasetDescriptor {
359 self.dataset
360 }
361
362 pub fn chunk(&self, chunk: ChunkId) -> Result<FrameChunk, ProviderError> {
368 let ordinal = self.dataset.chunk_ordinal(chunk)?;
369 let local = usize::try_from(ordinal).map_err(|_| ProviderError::IdentityOverflow {
370 dataset: self.dataset.id(),
371 field: "storage chunk index",
372 })?;
373 let index = self.storage_chunks.start.checked_add(local).ok_or(
374 ProviderError::IdentityOverflow {
375 dataset: self.dataset.id(),
376 field: "storage chunk index",
377 },
378 )?;
379 if index >= self.storage_chunks.end {
380 return Err(ProviderError::UnknownChunk {
381 dataset: self.dataset.id(),
382 chunk,
383 });
384 }
385 let storage = &self.structure.data().chunks[index];
386 let range = storage.atoms();
387 let descriptor = ChunkDescriptor::new(
388 self.dataset.id(),
389 chunk,
390 super::LogicalRow::new(u64::from(range.start)),
391 storage.len(),
392 )?;
393 let coordinates = self
394 .structure
395 .data()
396 .coords
397 .block(self.model)
398 .cloned()
399 .ok_or(ProviderError::ModelUnavailable {
400 model: self.model.get(),
401 })?;
402 FrameChunk::shared(descriptor, coordinates, range)
403 }
404}
405
406fn model_chunk_range(structure: &Structure, model: crate::ModelIndex) -> std::ops::Range<usize> {
407 let chunks = structure.data().chunks.as_slice();
408 let start = chunks.partition_point(|chunk| chunk.model() < model.get());
409 let end = chunks.partition_point(|chunk| chunk.model() <= model.get());
410 start..end
411}
412
413#[cfg(test)]
414#[path = "source_tests.rs"]
415mod tests;
416
417#[cfg(test)]
418#[path = "bond_source_tests.rs"]
419mod bond_tests;