use crate::io::{IoError, IoResult};
pub(crate) fn index_grid(
dims: &[u64],
max_dims: Option<&[u64]>,
chunk_dims: &[u64],
) -> IoResult<Vec<u64>> {
let ndims = dims.len();
if chunk_dims.len() != ndims {
return Err(IoError::InvalidState(format!(
"dataset chunk shape has {} dimensions but the dataspace has {}",
chunk_dims.len(),
ndims
)));
}
if let Some(max) = max_dims {
if max.len() != ndims {
return Err(IoError::InvalidState(format!(
"dataset maximum shape has {} dimensions but the dataspace has {}",
max.len(),
ndims
)));
}
}
let mut grid = Vec::with_capacity(ndims);
for d in 0..ndims {
if chunk_dims[d] == 0 {
return Err(IoError::InvalidState(format!(
"chunk dimension {d} is zero"
)));
}
let extent = match max_dims {
Some(max) if max[d] != u64::MAX => max[d],
_ => dims[d],
};
grid.push(extent.div_ceil(chunk_dims[d]));
}
Ok(grid)
}
fn is_unlimited(max_dims: Option<&[u64]>, d: usize) -> bool {
max_dims.is_some_and(|m| m[d] == u64::MAX)
}
fn single_unlimited_dim(max_dims: Option<&[u64]>, ndims: usize) -> IoResult<Option<usize>> {
let mut found = None;
for d in 0..ndims {
if is_unlimited(max_dims, d) {
if let Some(prev) = found {
return Err(IoError::InvalidState(format!(
"dimensions {prev} and {d} are both unlimited: chunks have no \
finite linear index with more than one unlimited dimension \
(that needs a v2 B-tree index instead)"
)));
}
found = Some(d);
}
}
Ok(found)
}
pub(crate) fn linear_index(
dims: &[u64],
max_dims: Option<&[u64]>,
chunk_dims: &[u64],
coords: &[u64],
) -> IoResult<u64> {
let ndims = dims.len();
if coords.len() != ndims {
return Err(IoError::InvalidState(format!(
"chunk_coords has {} entries but the dataset has {} dimensions",
coords.len(),
ndims
)));
}
let grid = index_grid(dims, max_dims, chunk_dims)?;
let seed_dim = single_unlimited_dim(max_dims, ndims)?.unwrap_or(0);
for d in 0..ndims {
if !is_unlimited(max_dims, d) && coords[d] >= grid[d] {
return Err(IoError::InvalidState(format!(
"chunk coordinate {} in dimension {} is outside the chunk grid (0..{})",
coords[d], d, grid[d]
)));
}
}
let mut linear = coords[seed_dim];
for d in 0..ndims {
if d == seed_dim {
continue;
}
linear = linear
.checked_mul(grid[d])
.and_then(|l| l.checked_add(coords[d]))
.ok_or_else(|| {
IoError::InvalidState("chunk coordinates overflow the array index".into())
})?;
}
Ok(linear)
}
pub(crate) fn coords_of(
dims: &[u64],
max_dims: Option<&[u64]>,
chunk_dims: &[u64],
linear: u64,
) -> IoResult<Vec<u64>> {
let ndims = dims.len();
let grid = index_grid(dims, max_dims, chunk_dims)?;
let unlim_dim = single_unlimited_dim(max_dims, ndims)?;
let mut coords = vec![0u64; ndims];
coords_into(&grid, unlim_dim, linear, &mut coords)?;
Ok(coords)
}
pub(crate) fn coords_table(
dims: &[u64],
max_dims: Option<&[u64]>,
chunk_dims: &[u64],
count: usize,
) -> IoResult<Vec<u64>> {
let ndims = dims.len();
let grid = index_grid(dims, max_dims, chunk_dims)?;
let unlim_dim = single_unlimited_dim(max_dims, ndims)?;
let mut table = vec![0u64; count.saturating_mul(ndims)];
for (linear, coords) in table.chunks_mut(ndims.max(1)).enumerate() {
coords_into(&grid, unlim_dim, linear as u64, &mut coords[..ndims])?;
}
Ok(table)
}
fn coords_into(
grid: &[u64],
unlim_dim: Option<usize>,
linear: u64,
coords: &mut [u64],
) -> IoResult<()> {
let ndims = coords.len();
let seed_dim = unlim_dim.unwrap_or(0);
let mut rem = linear;
for d in (0..ndims).rev() {
if d == seed_dim {
continue;
}
if grid[d] == 0 {
return Err(IoError::InvalidState(format!(
"chunk grid is empty in dimension {d}"
)));
}
coords[d] = rem % grid[d];
rem /= grid[d];
}
if ndims > 0 {
if unlim_dim.is_none() && rem >= grid[seed_dim] {
return Err(IoError::InvalidState(format!(
"chunk index {linear} is outside the chunk grid"
)));
}
coords[seed_dim] = rem;
} else if rem != 0 {
return Err(IoError::InvalidState(format!(
"chunk index {linear} is outside the chunk grid"
)));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn indices_come_from_the_maximum_grid() {
let dims = [4, 3];
let max = [10, 9];
let chunks = [2, 3];
let li = |c: &[u64]| linear_index(&dims, Some(&max), &chunks, c).unwrap();
assert_eq!(li(&[0, 0]), 0);
assert_eq!(li(&[0, 2]), 2);
assert_eq!(li(&[1, 0]), 3); assert_eq!(li(&[4, 2]), 14);
assert_eq!(
coords_of(&dims, Some(&max), &chunks, 14).unwrap(),
vec![4, 2]
);
}
#[test]
fn absent_maximum_means_the_current_extent() {
let dims = [4, 4];
let chunks = [2, 2];
assert_eq!(linear_index(&dims, None, &chunks, &[1, 1]).unwrap(), 3);
assert_eq!(coords_of(&dims, None, &chunks, 3).unwrap(), vec![1, 1]);
let err = linear_index(&dims, None, &chunks, &[2, 0]).unwrap_err();
assert!(err.to_string().contains("outside the chunk grid"));
}
#[test]
fn unlimited_dimension_zero_is_unbounded() {
let dims = [4, 8];
let max = [u64::MAX, 8];
let chunks = [2, 4];
assert_eq!(
linear_index(&dims, Some(&max), &chunks, &[100, 1]).unwrap(),
201
);
assert_eq!(
coords_of(&dims, Some(&max), &chunks, 201).unwrap(),
vec![100, 1]
);
}
#[test]
fn unlimited_inner_dimension_is_indexable() {
let dims = [4, 5, 6];
let max = [4, u64::MAX, 6];
let chunks = [2, 3, 2];
assert_eq!(
linear_index(&dims, Some(&max), &chunks, &[1, 2, 1]).unwrap(),
16
);
assert_eq!(
coords_of(&dims, Some(&max), &chunks, 16).unwrap(),
vec![1, 2, 1]
);
assert_eq!(
linear_index(&dims, Some(&max), &chunks, &[0, 100, 0]).unwrap(),
600
);
assert_eq!(
coords_of(&dims, Some(&max), &chunks, 600).unwrap(),
vec![0, 100, 0]
);
}
#[test]
fn two_unlimited_dimensions_are_rejected() {
let dims = [4, 5];
let max = [u64::MAX, u64::MAX];
let chunks = [2, 3];
let err = linear_index(&dims, Some(&max), &chunks, &[0, 0]).unwrap_err();
assert!(err.to_string().contains("both unlimited"), "{err}");
let err = coords_of(&dims, Some(&max), &chunks, 0).unwrap_err();
assert!(err.to_string().contains("both unlimited"), "{err}");
}
}