mod array_subset_traits;
pub use array_subset_traits::ArraySubsetTraits;
mod array_subset;
pub use array_subset::{ArraySubset, ArraySubsetError};
mod indexer;
pub use indexer::{Indexer, IndexerError, IndexerIterator};
mod chunk_shape_traits;
pub use chunk_shape_traits::ChunkShapeTraits;
pub mod iterators;
use std::sync::{Arc, LazyLock};
use derive_more::{Deref, From};
pub use zarrs_metadata::{ArrayShape, ChunkShape};
pub type ArrayIndices = Vec<u64>;
pub type ArrayIndicesTinyVec = tinyvec::TinyVec<[u64; 4]>;
use iterators::{IndicesIntoIterator, ParIndicesIntoIterator};
use zarrs_metadata::Configuration;
use zarrs_metadata::v3::MetadataV3;
use zarrs_plugin::{
ExtensionAliases, ExtensionName, MaybeSend, MaybeSync, Plugin2, PluginCreateError,
PluginUnsupportedError, RuntimePlugin2, RuntimeRegistry, ZarrVersion, ZarrVersion3,
};
#[derive(Copy, Clone, Debug, thiserror::Error)]
#[error("incompatible dimensionality {0}, expected {1}")]
pub struct IncompatibleDimensionalityError(usize, usize);
impl IncompatibleDimensionalityError {
#[must_use]
pub const fn new(got: usize, expected: usize) -> Self {
Self(got, expected)
}
}
#[derive(Debug, Clone, Deref, From)]
pub struct ChunkGrid(Arc<dyn ChunkGridTraits>);
impl<T: ChunkGridTraits + 'static> From<T> for ChunkGrid {
fn from(chunk_grid: T) -> Self {
let chunk_grid: Arc<dyn ChunkGridTraits> = Arc::new(chunk_grid);
Self(chunk_grid)
}
}
impl<T: ChunkGridTraits + 'static> From<Arc<T>> for ChunkGrid {
fn from(chunk_grid: Arc<T>) -> Self {
Self(chunk_grid)
}
}
impl ExtensionName for ChunkGrid {
fn name(&self, version: ZarrVersion) -> Option<std::borrow::Cow<'static, str>> {
self.0.name(version)
}
}
#[derive(derive_more::Deref)]
pub struct ChunkGridPlugin(Plugin2<ChunkGrid, MetadataV3, ArrayShape>);
inventory::collect!(ChunkGridPlugin);
impl ChunkGridPlugin {
pub const fn new<T: ExtensionAliases<ZarrVersion3> + ChunkGridTraits>() -> Self {
Self(Plugin2::new(T::matches_name, T::create))
}
}
pub type ChunkGridRuntimePlugin = RuntimePlugin2<ChunkGrid, MetadataV3, ArrayShape>;
pub type ChunkGridRuntimeRegistryHandle = Arc<ChunkGridRuntimePlugin>;
pub static CHUNK_GRID_RUNTIME_REGISTRY: LazyLock<RuntimeRegistry<ChunkGridRuntimePlugin>> =
LazyLock::new(RuntimeRegistry::new);
pub fn register_chunk_grid(plugin: ChunkGridRuntimePlugin) -> ChunkGridRuntimeRegistryHandle {
CHUNK_GRID_RUNTIME_REGISTRY.register(plugin)
}
pub fn unregister_chunk_grid(handle: &ChunkGridRuntimeRegistryHandle) -> bool {
CHUNK_GRID_RUNTIME_REGISTRY.unregister(handle)
}
impl ChunkGrid {
pub fn new<T: ChunkGridTraits + 'static>(chunk_grid: T) -> Self {
let chunk_grid: Arc<dyn ChunkGridTraits> = Arc::new(chunk_grid);
chunk_grid.into()
}
#[must_use]
pub fn metadata(&self) -> MetadataV3 {
let name = self.name_v3().expect("chunk grid must have a V3 name");
let configuration = self.configuration();
if configuration.is_empty() {
MetadataV3::new(name.into_owned())
} else {
MetadataV3::new_with_configuration(name.into_owned(), configuration)
}
}
pub fn from_metadata(
metadata: &MetadataV3,
array_shape: &[u64],
) -> Result<Self, PluginCreateError> {
let name = metadata.name();
{
let result = CHUNK_GRID_RUNTIME_REGISTRY.with_plugins(|plugins| {
for plugin in plugins {
if plugin.match_name(name) {
return Some(plugin.create(metadata, &array_shape.to_vec()));
}
}
None
});
if let Some(result) = result {
return result;
}
}
for plugin in inventory::iter::<ChunkGridPlugin> {
if plugin.match_name(name) {
return plugin.create(metadata, &array_shape.to_vec());
}
}
Err(
PluginUnsupportedError::new(metadata.name().to_string(), "chunk grid".to_string())
.into(),
)
}
}
pub unsafe trait ChunkGridTraits:
ExtensionName + core::fmt::Debug + MaybeSend + MaybeSync
{
fn create(
metadata: &MetadataV3,
array_shape: &ArrayShape,
) -> Result<ChunkGrid, PluginCreateError>
where
Self: Sized;
fn configuration(&self) -> Configuration;
fn dimensionality(&self) -> usize;
fn array_shape(&self) -> &[u64];
fn grid_shape(&self) -> &[u64];
fn chunk_shape(
&self,
chunk_indices: &[u64],
) -> Result<Option<ChunkShape>, IncompatibleDimensionalityError>;
fn chunk_shape_u64(
&self,
chunk_indices: &[u64],
) -> Result<Option<ArrayShape>, IncompatibleDimensionalityError>;
fn chunk_origin(
&self,
chunk_indices: &[u64],
) -> Result<Option<ArrayIndices>, IncompatibleDimensionalityError>;
fn subset(
&self,
chunk_indices: &[u64],
) -> Result<Option<ArraySubset>, IncompatibleDimensionalityError> {
let chunk_origin = self.chunk_origin(chunk_indices)?;
let chunk_shape = self.chunk_shape(chunk_indices)?;
if let (Some(chunk_origin), Some(chunk_shape)) = (chunk_origin, chunk_shape) {
let ranges = chunk_origin
.into_iter()
.zip(chunk_shape)
.map(|(o, s)| o..(o + s.get()));
Ok(Some(ArraySubset::from(ranges)))
} else {
Ok(None)
}
}
fn chunks_subset(
&self,
chunks: &dyn ArraySubsetTraits,
) -> Result<Option<ArraySubset>, IncompatibleDimensionalityError> {
if chunks.dimensionality() != self.dimensionality() {
Err(IncompatibleDimensionalityError::new(
chunks.dimensionality(),
self.dimensionality(),
))
} else if let Some(end) = chunks.end_inc() {
let chunk0 = self.subset(&chunks.start())?;
let chunk1 = self.subset(&end)?;
if let (Some(chunk0), Some(chunk1)) = (chunk0, chunk1) {
let ranges = std::iter::zip(chunk0.start(), chunk1.end_exc())
.map(|(&s, e)| s..e)
.collect::<Vec<_>>();
Ok(Some(ArraySubset::new_with_ranges(&ranges)))
} else {
Ok(None)
}
} else {
Ok(Some(ArraySubset::new_empty(chunks.dimensionality())))
}
}
fn chunk_indices(
&self,
array_indices: &[u64],
) -> Result<Option<ArrayIndices>, IncompatibleDimensionalityError>;
fn chunk_element_indices(
&self,
array_indices: &[u64],
) -> Result<Option<ArrayIndices>, IncompatibleDimensionalityError>;
#[must_use]
fn array_indices_inbounds(&self, array_indices: &[u64]) -> bool {
array_indices.len() == self.dimensionality()
&& std::iter::zip(array_indices, self.array_shape())
.all(|(&index, &shape)| shape == 0 || index < shape)
}
#[must_use]
fn chunk_indices_inbounds(&self, chunk_indices: &[u64]) -> bool {
chunk_indices.len() == self.dimensionality()
&& std::iter::zip(chunk_indices, self.grid_shape())
.all(|(&index, &shape)| shape == 0 || index < shape)
}
fn chunks_in_array_subset(
&self,
region: &dyn ArraySubsetTraits,
) -> Result<Option<ArraySubset>, IncompatibleDimensionalityError> {
match region.end_inc() {
Some(end) => {
let chunks_start = self.chunk_indices(®ion.start())?;
let chunks_end = self.chunk_indices(&end)?;
Ok(
if let (Some(chunks_start), Some(chunks_end)) = (chunks_start, chunks_end) {
let ranges = std::iter::zip(&chunks_start, chunks_end)
.map(|(&s, e)| s..e + 1)
.collect::<Vec<_>>();
Some(ArraySubset::new_with_ranges(&ranges))
} else {
None
},
)
}
None => Ok(Some(ArraySubset::new_empty(self.dimensionality()))),
}
}
fn iter_chunk_indices(&self) -> IndicesIntoIterator {
let shape = self.grid_shape().to_vec();
let n_chunks = shape.iter().product::<u64>();
let n_chunks = usize::try_from(n_chunks).unwrap();
IndicesIntoIterator {
subset: ArraySubset::new_with_shape(shape),
range: 0..n_chunks,
}
}
fn par_iter_chunk_indices(&self) -> ParIndicesIntoIterator {
let shape = self.grid_shape().to_vec();
let n_chunks = shape.iter().product::<u64>();
let n_chunks = usize::try_from(n_chunks).unwrap();
ParIndicesIntoIterator {
subset: ArraySubset::new_with_shape(shape),
range: 0..n_chunks,
}
}
}
pub trait ChunkGridTraitsIterators: ChunkGridTraits {
fn iter_chunk_subsets(&self) -> Box<dyn Iterator<Item = ArraySubset> + '_> {
Box::new(self.iter_chunk_indices().map(|chunk_indices| {
self.subset(&chunk_indices)
.expect("matching dimensionality")
.expect("inbounds chunk")
}))
}
fn iter_chunk_indices_and_subsets(
&self,
) -> Box<dyn Iterator<Item = (ArrayIndicesTinyVec, ArraySubset)> + '_> {
Box::new(self.iter_chunk_indices().map(|chunk_indices| {
let chunk_subset = self
.subset(&chunk_indices)
.expect("matching dimensionality")
.expect("inbounds chunk");
(chunk_indices, chunk_subset)
}))
}
}
impl<T> ChunkGridTraitsIterators for T where T: ChunkGridTraits {}
#[must_use]
pub fn ravel_indices(indices: &[u64], shape: &[u64]) -> Option<u64> {
let mut index: u64 = 0;
let mut count = 1;
for (i, s) in std::iter::zip(indices, shape).rev() {
if i >= s {
return None;
}
index += i * count;
count *= s;
}
Some(index)
}
#[must_use]
fn unravel_index(mut index: u64, shape: &[u64]) -> Option<ArrayIndicesTinyVec> {
let total_size: u64 = shape
.iter()
.try_fold(1u64, |acc, &dim| acc.checked_mul(dim))?;
if index >= total_size {
return None;
}
match shape.len() {
0 => Some(ArrayIndicesTinyVec::new()),
1 => Some(tinyvec::tiny_vec!([u64; 4] => index % shape[0])),
2 => {
let i1 = index % shape[1];
index /= shape[1];
let i0 = index % shape[0];
Some(tinyvec::tiny_vec!([u64; 4] => i0, i1))
}
3 => {
let i2 = index % shape[2];
index /= shape[2];
let i1 = index % shape[1];
index /= shape[1];
let i0 = index % shape[0];
Some(tinyvec::tiny_vec!([u64; 4] => i0, i1, i2))
}
4 => {
let i3 = index % shape[3];
index /= shape[3];
let i2 = index % shape[2];
index /= shape[2];
let i1 = index % shape[1];
index /= shape[1];
let i0 = index % shape[0];
Some(tinyvec::tiny_vec!([u64; 4] => i0, i1, i2, i3))
}
len => {
let mut vec = Vec::with_capacity(len);
{
let indices = unsafe { vec_spare_capacity_to_mut_slice(&mut vec) };
for i in (0..len).rev() {
indices[i] = index % shape[i];
index /= shape[i];
}
}
unsafe { vec.set_len(len) };
Some(ArrayIndicesTinyVec::Heap(vec))
}
}
}
unsafe fn vec_spare_capacity_to_mut_slice<T>(vec: &mut Vec<T>) -> &mut [T] {
let spare_capacity = vec.spare_capacity_mut();
unsafe {
std::slice::from_raw_parts_mut(
spare_capacity.as_mut_ptr().cast::<T>(),
spare_capacity.len(),
)
}
}