use std::ops::Range;
use thiserror::Error;
use crate::{
ArrayIndices, ArrayIndicesTinyVec, ArrayShape, ArraySubsetTraits,
IncompatibleDimensionalityError, MaybeSend, MaybeSync, ravel_indices,
};
#[derive(Clone, Debug, Error)]
pub enum IndexerError {
#[error("indexer is incompatible with array shape {_0:?}")]
IncompatibleDimensionality(#[from] IncompatibleDimensionalityError),
#[error(
"indexer references array indices {_0:?} which are out-of-bounds of array shape {_1:?}"
)]
OutOfBounds(ArrayIndices, ArrayShape),
#[error("indexer has an incompatible length {_0}, expected {_1}")]
IncompatibleLength(u64, u64),
}
impl IndexerError {
#[must_use]
pub fn new_incompatible_dimensionality(got: usize, expected: usize) -> Self {
IncompatibleDimensionalityError::new(got, expected).into()
}
#[must_use]
pub fn new_oob(indices: ArrayIndices, shape: ArrayShape) -> Self {
Self::OutOfBounds(indices, shape)
}
#[must_use]
pub fn new_incompatible_length(got: u64, expected: u64) -> Self {
Self::IncompatibleLength(got, expected)
}
}
pub trait IndexerIterator: Iterator + MaybeSend + MaybeSync {}
impl<T: Iterator + MaybeSend + MaybeSync> IndexerIterator for T {}
pub trait Indexer: MaybeSend + MaybeSync {
#[must_use]
fn dimensionality(&self) -> usize;
#[must_use]
fn len(&self) -> u64;
#[must_use]
fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
fn output_shape(&self) -> Vec<u64>;
fn iter_indices(&self) -> Box<dyn IndexerIterator<Item = ArrayIndicesTinyVec>>;
fn iter_linearised_indices(
&self,
array_shape: &[u64],
) -> Result<Box<dyn IndexerIterator<Item = u64>>, IndexerError>;
fn iter_contiguous_linearised_indices(
&self,
array_shape: &[u64],
) -> Result<Box<dyn IndexerIterator<Item = (u64, u64)>>, IndexerError>;
fn iter_contiguous_byte_ranges(
&self,
array_shape: &[u64],
element_size: usize,
) -> Result<Box<dyn IndexerIterator<Item = std::ops::Range<u64>>>, IndexerError> {
let element_size_u64 = element_size as u64;
let byte_ranges = self.iter_contiguous_linearised_indices(array_shape)?.map(
move |(array_index, contiguous_elements)| {
let byte_index = array_index * element_size_u64;
byte_index..byte_index + contiguous_elements * element_size_u64
},
);
Ok(Box::new(byte_ranges))
}
fn as_array_subset(&self) -> Option<&dyn ArraySubsetTraits> {
None
}
}
impl<T: Indexer> Indexer for &T {
fn dimensionality(&self) -> usize {
(**self).dimensionality()
}
fn len(&self) -> u64 {
(**self).len()
}
fn output_shape(&self) -> Vec<u64> {
(**self).output_shape()
}
fn iter_indices(&self) -> Box<dyn IndexerIterator<Item = ArrayIndicesTinyVec>> {
(**self).iter_indices()
}
fn iter_linearised_indices(
&self,
array_shape: &[u64],
) -> Result<Box<dyn IndexerIterator<Item = u64>>, IndexerError> {
(**self).iter_linearised_indices(array_shape)
}
fn iter_contiguous_linearised_indices(
&self,
array_shape: &[u64],
) -> Result<Box<dyn IndexerIterator<Item = (u64, u64)>>, IndexerError> {
(**self).iter_contiguous_linearised_indices(array_shape)
}
fn as_array_subset(&self) -> Option<&dyn ArraySubsetTraits> {
(**self).as_array_subset()
}
}
impl<T: Indexer> Indexer for &[T] {
fn dimensionality(&self) -> usize {
self.first().map_or(0, T::dimensionality)
}
fn len(&self) -> u64 {
self.iter().map(T::len).sum()
}
fn output_shape(&self) -> Vec<u64> {
vec![self.len()]
}
fn iter_indices(&self) -> Box<dyn IndexerIterator<Item = ArrayIndicesTinyVec>> {
let indices = self.iter().map(Indexer::iter_indices).collect::<Vec<_>>();
Box::new(indices.into_iter().flat_map(IntoIterator::into_iter))
}
fn iter_linearised_indices(
&self,
array_shape: &[u64],
) -> Result<Box<dyn IndexerIterator<Item = u64>>, IndexerError> {
let linearised_indices = self
.iter()
.map(|indexer| indexer.iter_linearised_indices(array_shape))
.collect::<Result<Vec<_>, _>>()?;
Ok(Box::new(
linearised_indices
.into_iter()
.flat_map(IntoIterator::into_iter),
))
}
fn iter_contiguous_linearised_indices(
&self,
array_shape: &[u64],
) -> Result<Box<dyn IndexerIterator<Item = (u64, u64)>>, IndexerError> {
let contiguous_linearised_indices = self
.iter()
.map(|indexer| indexer.iter_contiguous_linearised_indices(array_shape))
.collect::<Result<Vec<_>, _>>()?;
Ok(Box::new(
contiguous_linearised_indices
.into_iter()
.flat_map(IntoIterator::into_iter),
))
}
}
impl<T: Indexer> Indexer for Vec<T> {
fn dimensionality(&self) -> usize {
self.as_slice().dimensionality()
}
fn len(&self) -> u64 {
self.iter().map(T::len).sum()
}
fn is_empty(&self) -> bool {
self.is_empty()
}
fn output_shape(&self) -> Vec<u64> {
self.as_slice().output_shape()
}
fn iter_indices(&self) -> Box<dyn IndexerIterator<Item = ArrayIndicesTinyVec>> {
self.as_slice().iter_indices()
}
fn iter_linearised_indices(
&self,
array_shape: &[u64],
) -> Result<Box<dyn IndexerIterator<Item = u64>>, IndexerError> {
self.as_slice().iter_linearised_indices(array_shape)
}
fn iter_contiguous_linearised_indices(
&self,
array_shape: &[u64],
) -> Result<Box<dyn IndexerIterator<Item = (u64, u64)>>, IndexerError> {
self.as_slice()
.iter_contiguous_linearised_indices(array_shape)
}
}
impl<T: Indexer, const N: usize> Indexer for [T; N] {
fn dimensionality(&self) -> usize {
self.as_slice().dimensionality()
}
fn len(&self) -> u64 {
self.iter().map(T::len).sum()
}
fn is_empty(&self) -> bool {
self.as_slice().is_empty()
}
fn output_shape(&self) -> Vec<u64> {
self.as_slice().output_shape()
}
fn iter_indices(&self) -> Box<dyn IndexerIterator<Item = ArrayIndicesTinyVec>> {
self.as_slice().iter_indices()
}
fn iter_linearised_indices(
&self,
array_shape: &[u64],
) -> Result<Box<dyn IndexerIterator<Item = u64>>, IndexerError> {
self.as_slice().iter_linearised_indices(array_shape)
}
fn iter_contiguous_linearised_indices(
&self,
array_shape: &[u64],
) -> Result<Box<dyn IndexerIterator<Item = (u64, u64)>>, IndexerError> {
self.as_slice()
.iter_contiguous_linearised_indices(array_shape)
}
}
impl Indexer for &[ArrayIndices] {
fn dimensionality(&self) -> usize {
self.first().map_or(0, Vec::len)
}
fn len(&self) -> u64 {
<[ArrayIndices]>::len(self) as u64
}
fn is_empty(&self) -> bool {
<[ArrayIndices]>::is_empty(self)
}
fn output_shape(&self) -> Vec<u64> {
vec![self.len()]
}
fn iter_indices(&self) -> Box<dyn IndexerIterator<Item = ArrayIndicesTinyVec>> {
let indices: Vec<ArrayIndicesTinyVec> = self
.iter()
.map(|v| v.iter().copied().collect::<ArrayIndicesTinyVec>())
.collect();
Box::new(indices.into_iter())
}
fn iter_linearised_indices(
&self,
array_shape: &[u64],
) -> Result<Box<dyn IndexerIterator<Item = u64>>, IndexerError> {
let linearised_indices = self
.iter()
.map(|indices| {
if indices.len() == array_shape.len() {
ravel_indices(indices, array_shape)
.ok_or_else(|| IndexerError::new_oob(indices.clone(), array_shape.to_vec()))
} else {
Err(IncompatibleDimensionalityError::new(
self.dimensionality(),
array_shape.len(),
)
.into())
}
})
.collect::<Result<Vec<_>, _>>()?;
Ok(Box::new(linearised_indices.into_iter()))
}
fn iter_contiguous_linearised_indices(
&self,
array_shape: &[u64],
) -> Result<Box<dyn IndexerIterator<Item = (u64, u64)>>, IndexerError> {
let contiguous_linearised_indices = self
.iter()
.map(|indices| {
if indices.len() == array_shape.len() {
let index = ravel_indices(indices, array_shape).ok_or_else(|| {
IndexerError::new_oob(indices.clone(), array_shape.to_vec())
})?;
Ok((index, 1))
} else {
Err(IncompatibleDimensionalityError::new(
self.dimensionality(),
array_shape.len(),
)
.into())
}
})
.collect::<Result<Vec<_>, IndexerError>>()?;
Ok(Box::new(contiguous_linearised_indices.into_iter()))
}
}
impl Indexer for Vec<ArrayIndices> {
fn dimensionality(&self) -> usize {
self.as_slice().dimensionality()
}
fn len(&self) -> u64 {
self.as_slice().len() as u64
}
fn is_empty(&self) -> bool {
self.as_slice().is_empty()
}
fn output_shape(&self) -> Vec<u64> {
self.as_slice().output_shape()
}
fn iter_indices(&self) -> Box<dyn IndexerIterator<Item = ArrayIndicesTinyVec>> {
self.as_slice().iter_indices()
}
fn iter_linearised_indices(
&self,
array_shape: &[u64],
) -> Result<Box<dyn IndexerIterator<Item = u64>>, IndexerError> {
self.as_slice().iter_linearised_indices(array_shape)
}
fn iter_contiguous_linearised_indices(
&self,
array_shape: &[u64],
) -> Result<Box<dyn IndexerIterator<Item = (u64, u64)>>, IndexerError> {
self.as_slice()
.iter_contiguous_linearised_indices(array_shape)
}
}
impl<const N: usize> Indexer for [ArrayIndices; N] {
fn dimensionality(&self) -> usize {
self.as_slice().dimensionality()
}
fn len(&self) -> u64 {
self.as_slice().len() as u64
}
fn is_empty(&self) -> bool {
self.as_slice().is_empty()
}
fn output_shape(&self) -> Vec<u64> {
self.as_slice().output_shape()
}
fn iter_indices(&self) -> Box<dyn IndexerIterator<Item = ArrayIndicesTinyVec>> {
self.as_slice().iter_indices()
}
fn iter_linearised_indices(
&self,
array_shape: &[u64],
) -> Result<Box<dyn IndexerIterator<Item = u64>>, IndexerError> {
self.as_slice().iter_linearised_indices(array_shape)
}
fn iter_contiguous_linearised_indices(
&self,
array_shape: &[u64],
) -> Result<Box<dyn IndexerIterator<Item = (u64, u64)>>, IndexerError> {
self.as_slice()
.iter_contiguous_linearised_indices(array_shape)
}
}
macro_rules! impl_indexer_for_ranges {
($ty:ty) => {
impl Indexer for $ty {
fn dimensionality(&self) -> usize {
(*self).len()
}
fn len(&self) -> u64 {
ArraySubsetTraits::num_elements(self)
}
fn output_shape(&self) -> Vec<u64> {
self.iter().map(|r| r.end.saturating_sub(r.start)).collect()
}
fn iter_indices(&self) -> Box<dyn IndexerIterator<Item = ArrayIndicesTinyVec>> {
Box::new(self.to_array_subset().indices().into_iter())
}
fn iter_linearised_indices(
&self,
array_shape: &[u64],
) -> Result<Box<dyn IndexerIterator<Item = u64>>, IndexerError> {
Ok(Box::new(
self.to_array_subset()
.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.to_array_subset()
.contiguous_linearised_indices(array_shape)?
.into_iter(),
))
}
}
};
}
impl_indexer_for_ranges!(&[Range<u64>]);
impl_indexer_for_ranges!(Vec<Range<u64>>);
impl<const N: usize> Indexer for [Range<u64>; N] {
fn dimensionality(&self) -> usize {
N
}
fn len(&self) -> u64 {
ArraySubsetTraits::num_elements(self)
}
fn output_shape(&self) -> Vec<u64> {
self.iter().map(|r| r.end.saturating_sub(r.start)).collect()
}
fn iter_indices(&self) -> Box<dyn IndexerIterator<Item = ArrayIndicesTinyVec>> {
Box::new(self.to_array_subset().indices().into_iter())
}
fn iter_linearised_indices(
&self,
array_shape: &[u64],
) -> Result<Box<dyn IndexerIterator<Item = u64>>, IndexerError> {
Ok(Box::new(
self.to_array_subset()
.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.to_array_subset()
.contiguous_linearised_indices(array_shape)?
.into_iter(),
))
}
fn as_array_subset(&self) -> Option<&dyn ArraySubsetTraits> {
Some(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ArraySubset;
#[test]
fn test_as_array_subset() {
let subset = ArraySubset::new_with_shape(vec![10, 10]);
assert!(subset.as_array_subset().is_some());
let ref_subset: &ArraySubset = ⊂
assert!(ref_subset.as_array_subset().is_some());
let dyn_ref: &dyn ArraySubsetTraits = ⊂
assert!(dyn_ref.as_array_subset().is_some());
let indexer_ref: &dyn Indexer = ⊂
assert!(indexer_ref.as_array_subset().is_some());
let ranges: [Range<u64>; 2] = [0..10, 0..10];
assert!(ranges.as_array_subset().is_some());
let ranges_ref: &[Range<u64>; 2] = &ranges;
assert!(ranges_ref.as_array_subset().is_some());
}
}