use std::fmt::{Debug, Display};
use std::num::NonZeroU64;
use std::ops::Range;
use crate::iterators::{
ContiguousIndices, ContiguousLinearisedIndices, Indices, LinearisedIndices,
};
use thiserror::Error;
use crate::indexer::{Indexer, IndexerError, IndexerIterator};
use crate::{ArrayIndices, ArrayIndicesTinyVec, ArrayShape, ArraySubsetTraits, ChunkShape};
#[derive(Clone, Debug, Error)]
#[error("incompatible start {0:?} with end {1:?}")]
#[allow(missing_docs)]
pub enum ArraySubsetError {
#[error("incompatible dimensionality {got}, expected {expected}")]
IncompatibleDimensionality { got: usize, expected: usize },
#[error("incompatible start {start:?} with shape {shape:?}")]
IncompatibleStartShape {
start: ArrayIndices,
shape: ArrayShape,
},
#[error("incompatible start {start:?} with end {end:?} (inclusive: {inclusive})")]
IncompatibleStartEnd {
start: ArrayIndices,
end: ArrayIndices,
inclusive: bool,
},
#[error("incompatible offset {offset:?} for region with start {start:?}")]
IncompatibleOffset { start: Vec<u64>, offset: Vec<u64> },
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
pub struct ArraySubset {
pub(crate) start: ArrayIndices,
pub(crate) shape: ArrayShape,
}
impl Display for ArraySubset {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.to_ranges().fmt(f)
}
}
impl<T: IntoIterator<Item = Range<u64>>> From<T> for ArraySubset {
fn from(ranges: T) -> Self {
let (start, shape) = ranges
.into_iter()
.map(|range| (range.start, range.end.saturating_sub(range.start)))
.unzip();
Self { start, shape }
}
}
impl ArraySubset {
#[must_use]
pub fn new_empty(dimensionality: usize) -> Self {
Self {
start: vec![0; dimensionality],
shape: vec![0; dimensionality],
}
}
#[must_use]
pub fn new_with_ranges(ranges: &[Range<u64>]) -> Self {
let (start, shape) = ranges
.iter()
.map(|range| (range.start, range.end.saturating_sub(range.start)))
.unzip();
Self { start, shape }
}
#[must_use]
pub fn new_with_shape(shape: ArrayShape) -> Self {
Self {
start: vec![0; shape.len()],
shape,
}
}
pub fn new_with_start_shape(
start: ArrayIndices,
shape: ArrayShape,
) -> Result<Self, ArraySubsetError> {
if start.len() == shape.len() {
Ok(Self { start, shape })
} else {
Err(ArraySubsetError::IncompatibleStartShape { start, shape })
}
}
pub fn new_with_start_end_inc(
start: ArrayIndices,
end: ArrayIndices,
) -> Result<Self, ArraySubsetError> {
if start.len() != end.len() || std::iter::zip(&start, &end).any(|(start, end)| end < start)
{
Err(ArraySubsetError::IncompatibleStartEnd {
start,
end,
inclusive: true,
})
} else {
let shape = std::iter::zip(&start, end)
.map(|(&start, end)| end.saturating_sub(start) + 1)
.collect();
Ok(Self { start, shape })
}
}
pub fn new_with_start_end_exc(
start: ArrayIndices,
end: ArrayIndices,
) -> Result<Self, ArraySubsetError> {
if start.len() != end.len() || std::iter::zip(&start, &end).any(|(start, end)| end < start)
{
Err(ArraySubsetError::IncompatibleStartEnd {
start,
end,
inclusive: false,
})
} else {
let shape = std::iter::zip(&start, end)
.map(|(&start, end)| end.saturating_sub(start))
.collect();
Ok(Self { start, shape })
}
}
#[must_use]
pub fn to_ranges(&self) -> Vec<Range<u64>> {
ArraySubsetTraits::to_ranges(self)
}
pub fn bound(&self, end: &[u64]) -> Result<Self, ArraySubsetError> {
if end.len() == self.start.len() {
let start = std::iter::zip(&self.start, end)
.map(|(&a, &b)| std::cmp::min(a, b))
.collect();
let end_exc = std::iter::zip(&self.start, &self.shape).map(|(&s, &l)| s + l);
let end = std::iter::zip(end_exc, end)
.map(|(a, &b)| std::cmp::min(a, b))
.collect();
Ok(Self::new_with_start_end_exc(start, end)?)
} else {
Err(ArraySubsetError::IncompatibleStartEnd {
start: self.start.clone(),
end: end.to_vec(),
inclusive: false,
})
}
}
#[must_use]
pub fn start(&self) -> &[u64] {
&self.start
}
#[must_use]
pub fn shape(&self) -> &[u64] {
&self.shape
}
#[must_use]
pub fn chunk_shape(&self) -> Option<ChunkShape> {
self.shape.iter().map(|s| NonZeroU64::new(*s)).collect()
}
#[must_use]
pub fn shape_usize(&self) -> Vec<usize> {
self.shape
.iter()
.map(|d| usize::try_from(*d).unwrap())
.collect()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.shape.iter().any(|i| i == &0)
}
#[must_use]
pub fn dimensionality(&self) -> usize {
self.start.len()
}
#[must_use]
pub fn end_inc(&self) -> Option<ArrayIndices> {
ArraySubsetTraits::end_inc(self)
}
#[must_use]
pub fn end_exc(&self) -> ArrayIndices {
ArraySubsetTraits::end_exc(self)
}
#[must_use]
pub fn num_elements(&self) -> u64 {
ArraySubsetTraits::num_elements(self)
}
#[must_use]
pub fn num_elements_usize(&self) -> usize {
ArraySubsetTraits::num_elements_usize(self)
}
#[must_use]
pub fn contains(&self, indices: &[u64]) -> bool {
ArraySubsetTraits::contains(self, indices)
}
#[must_use]
pub fn indices(&self) -> Indices {
ArraySubsetTraits::indices(self)
}
pub fn linearised_indices(
&self,
array_shape: &[u64],
) -> Result<LinearisedIndices, IndexerError> {
ArraySubsetTraits::linearised_indices(self, array_shape)
}
pub fn contiguous_indices(
&self,
array_shape: &[u64],
) -> Result<ContiguousIndices, IndexerError> {
ArraySubsetTraits::contiguous_indices(self, array_shape)
}
pub fn contiguous_linearised_indices(
&self,
array_shape: &[u64],
) -> Result<ContiguousLinearisedIndices, IndexerError> {
ArraySubsetTraits::contiguous_linearised_indices(self, array_shape)
}
pub fn overlap(&self, subset_other: &dyn ArraySubsetTraits) -> Result<Self, ArraySubsetError> {
ArraySubsetTraits::overlap(self, subset_other)
}
pub fn relative_to(&self, offset: &[u64]) -> Result<Self, ArraySubsetError> {
ArraySubsetTraits::relative_to(self, offset)
}
pub fn offset(&self, offset: &[u64]) -> Result<Self, ArraySubsetError> {
ArraySubsetTraits::offset(self, offset)
}
#[must_use]
pub fn inbounds(&self, subset: &dyn ArraySubsetTraits) -> bool {
ArraySubsetTraits::inbounds(self, subset)
}
#[must_use]
pub fn inbounds_shape(&self, array_shape: &[u64]) -> bool {
ArraySubsetTraits::inbounds_shape(self, array_shape)
}
}
impl Indexer for ArraySubset {
fn dimensionality(&self) -> usize {
self.start.len()
}
fn len(&self) -> u64 {
self.shape.iter().product()
}
fn output_shape(&self) -> Vec<u64> {
self.shape.clone()
}
fn iter_indices(&self) -> Box<dyn IndexerIterator<Item = ArrayIndicesTinyVec>> {
Box::new(self.indices().into_iter())
}
fn iter_linearised_indices(
&self,
array_shape: &[u64],
) -> Result<Box<dyn IndexerIterator<Item = u64>>, IndexerError> {
Ok(Box::new(self.linearised_indices(array_shape)?.into_iter()))
}
fn iter_contiguous_linearised_indices(
&self,
array_shape: &[u64],
) -> Result<Box<dyn IndexerIterator<Item = (u64, u64)>>, IndexerError> {
Ok(Box::new(
self.contiguous_linearised_indices(array_shape)?.into_iter(),
))
}
fn as_array_subset(&self) -> Option<&dyn ArraySubsetTraits> {
Some(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(clippy::single_range_in_vec_init)]
#[test]
fn array_subset() {
assert!(ArraySubset::new_with_start_shape(vec![0, 0], vec![10, 10]).is_ok());
assert!(ArraySubset::new_with_start_shape(vec![0, 0], vec![10]).is_err());
assert!(ArraySubset::new_with_start_end_inc(vec![0, 0], vec![10, 10]).is_ok());
assert!(ArraySubset::new_with_start_end_inc(vec![0, 0], vec![10]).is_err());
assert!(ArraySubset::new_with_start_end_inc(vec![5, 5], vec![0, 0]).is_err());
assert!(ArraySubset::new_with_start_end_exc(vec![0, 0], vec![10, 10]).is_ok());
assert!(ArraySubset::new_with_start_end_exc(vec![0, 0], vec![10]).is_err());
assert!(ArraySubset::new_with_start_end_exc(vec![5, 5], vec![0, 0]).is_err());
let array_subset = ArraySubset::new_with_start_shape(vec![0, 0], vec![10, 10])
.unwrap()
.bound(&[5, 5])
.unwrap();
assert_eq!(array_subset.shape(), &[5, 5]);
assert!(
ArraySubset::new_with_start_shape(vec![0, 0], vec![10, 10])
.unwrap()
.bound(&[5, 5, 5])
.is_err()
);
let array_subset0 = ArraySubset::new_with_ranges(&[1..5, 2..6]);
let array_subset1 = ArraySubset::new_with_ranges(&[3..6, 4..7]);
assert_eq!(
array_subset0.overlap(&array_subset1).unwrap(),
ArraySubset::new_with_ranges(&[3..5, 4..6])
);
assert_eq!(
array_subset0.relative_to(&[1, 1]).unwrap(),
ArraySubset::new_with_ranges(&[0..4, 1..5])
);
assert_eq!(
array_subset0.offset(&[3, 5]).unwrap(),
ArraySubset::new_with_ranges(&[4..8, 7..11])
);
assert!(array_subset0.relative_to(&[1, 1, 1]).is_err());
assert!(array_subset0.inbounds_shape(&[10, 10]));
assert!(!array_subset0.inbounds_shape(&[2, 2]));
assert!(!array_subset0.inbounds_shape(&[10, 10, 10]));
assert!(array_subset0.inbounds(&[0..6, 1..7]));
assert!(array_subset0.inbounds(&[1..5, 2..6]));
assert!(!array_subset0.inbounds(&[2..5, 2..6]));
assert!(!array_subset0.inbounds(&[1..5, 2..5]));
assert!(!array_subset0.inbounds(&[2..5]));
assert_eq!(array_subset0.to_ranges(), vec![1..5, 2..6]);
let array_subset2 = ArraySubset::new_with_ranges(&[3..6, 4..7, 0..1]);
assert!(array_subset0.overlap(&array_subset2).is_err());
assert_eq!(
array_subset2
.linearised_indices(&[6, 7, 1])
.unwrap()
.into_iter()
.next(),
Some(4 + (3 * 7))
);
}
#[test]
fn array_subset_bytes() {
let array_subset = ArraySubset::new_with_ranges(&[1..3, 1..3]);
assert!(
array_subset
.iter_contiguous_byte_ranges(&[1, 1], 1)
.is_err()
);
let ranges = array_subset
.iter_contiguous_byte_ranges(&[4, 4], 1)
.unwrap()
.collect::<Vec<_>>();
assert_eq!(ranges, vec![5..7, 9..11]);
}
}