use crate::{
ChunkBounds, ChunkData, ChunkDescriptor, ChunkFootprint, ChunkPayload, ChunkSpan,
DatasetCatalog, DatasetError, DatasetId, PayloadKind,
};
use ::molframe::Aabb as MolframeAabb;
use molframe::engine::core as molframe;
use thiserror::Error;
#[derive(Clone, Debug, PartialEq, Eq, Error)]
#[non_exhaustive]
pub enum ProviderBridgeError {
#[error("provider chunk belongs to dataset {actual}, expected {expected}")]
DatasetMismatch {
expected: u64,
actual: u64,
},
#[error("provider dataset expects {expected:?}, received {actual:?}")]
PayloadKindMismatch {
expected: molframe::PayloadKind,
actual: molframe::PayloadKind,
},
#[error("provider chunk {chunk} lies outside dataset {dataset}")]
ChunkMismatch {
dataset: u64,
chunk: u64,
},
#[error(
"provider chunk rows [{first}, {end}) do not fit dataset {dataset} with {logical_rows} rows"
)]
RowRangeMismatch {
dataset: u64,
first: u64,
end: u64,
logical_rows: u64,
},
#[error("provider chunk {chunk} does not match its regular row layout")]
RegularLayoutMismatch {
chunk: u64,
},
#[error("provider chunk {chunk} has no finite spatial bounds")]
MissingBounds {
chunk: u64,
},
#[error(transparent)]
Dataset(#[from] DatasetError),
#[error(transparent)]
Provider(#[from] molframe::ProviderError),
}
#[derive(Clone, Debug)]
pub struct ProviderDatasetBridge {
catalog: DatasetCatalog,
provider: molframe::DatasetDescriptor,
}
impl ProviderDatasetBridge {
#[must_use]
pub fn new(descriptor: molframe::DatasetDescriptor) -> Self {
Self {
catalog: DatasetCatalog::from_provider(descriptor),
provider: descriptor,
}
}
#[must_use]
pub const fn catalog(&self) -> &DatasetCatalog {
&self.catalog
}
pub fn chunk_descriptor(
&self,
source: molframe::ChunkDescriptor,
bounds: ChunkBounds,
footprint: ChunkFootprint,
) -> Result<ChunkDescriptor, ProviderBridgeError> {
let dataset = self.provider;
validate_source(dataset, source)?;
Ok(ChunkDescriptor {
id: crate::ChunkId::new(source.chunk().get()),
parent: None,
level: 0,
rows: ChunkSpan::new(
crate::LogicalRow::new(source.logical_start().get()),
source.rows(),
)?,
bounds,
payload_kind: adapt_kind(dataset.payload()),
footprint,
})
}
pub fn structure_chunk(
&self,
chunk: molframe::StructureChunk,
footprint: ChunkFootprint,
) -> Result<ChunkData, ProviderBridgeError> {
self.require_kind(molframe::PayloadKind::Structure)?;
let bounds = adapt_bounds(chunk.descriptor().chunk(), chunk.atoms().stats().bounds)?;
self.finish(
chunk.descriptor(),
bounds,
footprint,
ChunkPayload::ProviderStructure(chunk),
)
}
pub fn bond_chunk(
&self,
chunk: molframe::BondChunk,
bounds: ChunkBounds,
footprint: ChunkFootprint,
) -> Result<ChunkData, ProviderBridgeError> {
self.require_kind(molframe::PayloadKind::BondTopology)?;
self.finish(
chunk.descriptor(),
bounds,
footprint,
ChunkPayload::ProviderBond(chunk),
)
}
pub fn property_chunk(
&self,
chunk: molframe::PropertyChunk,
footprint: ChunkFootprint,
) -> Result<ChunkData, ProviderBridgeError> {
self.require_kind(molframe::PayloadKind::Property)?;
let bounds = adapt_bounds(chunk.descriptor().chunk(), chunk.bounds())?;
self.finish(
chunk.descriptor(),
bounds,
footprint,
ChunkPayload::ProviderProperty(chunk),
)
}
pub fn frame_chunk(
&self,
chunk: molframe::FrameChunk,
footprint: ChunkFootprint,
) -> Result<ChunkData, ProviderBridgeError> {
self.require_kind(molframe::PayloadKind::Frame)?;
let bounds = adapt_bounds(chunk.descriptor().chunk(), chunk.bounds())?;
self.finish(
chunk.descriptor(),
bounds,
footprint,
ChunkPayload::ProviderFrame(chunk),
)
}
fn finish(
&self,
source: molframe::ChunkDescriptor,
bounds: ChunkBounds,
footprint: ChunkFootprint,
payload: ChunkPayload,
) -> Result<ChunkData, ProviderBridgeError> {
let descriptor = self.chunk_descriptor(source, bounds, footprint)?;
Ok(ChunkData::from_descriptor(
DatasetId::new(source.dataset().get()),
&descriptor,
payload,
)?)
}
fn require_kind(&self, actual: molframe::PayloadKind) -> Result<(), ProviderBridgeError> {
let expected = self.provider.payload();
if expected != actual {
return Err(ProviderBridgeError::PayloadKindMismatch { expected, actual });
}
Ok(())
}
}
const fn adapt_kind(kind: molframe::PayloadKind) -> PayloadKind {
match kind {
molframe::PayloadKind::Structure => PayloadKind::Structure,
molframe::PayloadKind::BondTopology => PayloadKind::BondTopology,
molframe::PayloadKind::Property => PayloadKind::ScalarProperty,
molframe::PayloadKind::Frame => PayloadKind::Trajectory,
}
}
fn validate_source(
dataset: molframe::DatasetDescriptor,
source: molframe::ChunkDescriptor,
) -> Result<(), ProviderBridgeError> {
if source.dataset() != dataset.id() {
return Err(ProviderBridgeError::DatasetMismatch {
expected: dataset.id().get(),
actual: source.dataset().get(),
});
}
validate_chunk_identity(dataset, source.chunk())?;
let first = source.logical_start().get();
let end = first.checked_add(u64::from(source.rows())).ok_or(
ProviderBridgeError::RowRangeMismatch {
dataset: dataset.id().get(),
first,
end: u64::MAX,
logical_rows: dataset.logical_rows(),
},
)?;
if source.rows() == 0 || end > dataset.logical_rows() {
return Err(ProviderBridgeError::RowRangeMismatch {
dataset: dataset.id().get(),
first,
end,
logical_rows: dataset.logical_rows(),
});
}
if matches!(dataset.layout(), molframe::ChunkLayout::Regular { .. })
&& dataset.regular_chunk(source.chunk())? != source
{
return Err(ProviderBridgeError::RegularLayoutMismatch {
chunk: source.chunk().get(),
});
}
Ok(())
}
fn validate_chunk_identity(
dataset: molframe::DatasetDescriptor,
chunk: molframe::ChunkId,
) -> Result<(), ProviderBridgeError> {
let Some(ordinal) = chunk.get().checked_sub(dataset.first_chunk().get()) else {
return Err(chunk_mismatch(dataset, chunk));
};
if ordinal >= dataset.chunk_count() {
return Err(chunk_mismatch(dataset, chunk));
}
Ok(())
}
const fn chunk_mismatch(
dataset: molframe::DatasetDescriptor,
chunk: molframe::ChunkId,
) -> ProviderBridgeError {
ProviderBridgeError::ChunkMismatch {
dataset: dataset.id().get(),
chunk: chunk.get(),
}
}
fn adapt_bounds(
chunk: molframe::ChunkId,
bounds: MolframeAabb,
) -> Result<ChunkBounds, ProviderBridgeError> {
if bounds.is_empty() {
return Err(ProviderBridgeError::MissingBounds { chunk: chunk.get() });
}
ChunkBounds::new(bounds.min, bounds.max).map_err(ProviderBridgeError::from)
}