Skip to main content

slop_tensor/
dimensions.rs

1use core::fmt;
2
3use arrayvec::ArrayVec;
4use itertools::Itertools;
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use thiserror::Error;
7
8const MAX_DIMENSIONS: usize = 3;
9
10#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
11#[repr(C)]
12pub struct Dimensions {
13    sizes: ArrayVec<usize, MAX_DIMENSIONS>,
14    strides: ArrayVec<usize, MAX_DIMENSIONS>,
15}
16
17impl fmt::Display for Dimensions {
18    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19        write!(f, "Dimensions({})", self.sizes.iter().join(", "))
20    }
21}
22
23#[derive(Debug, Clone, Copy, Error)]
24pub enum DimensionsError {
25    #[error("Too many dimensions {0}, maximum number allowed is {MAX_DIMENSIONS}")]
26    TooManyDimensions(usize),
27    #[error("dimension product overflows usize")]
28    SizeOverflow,
29    #[error("total number of elements must match, expected {0}, got {1}")]
30    NumElementsMismatch(usize, usize),
31}
32
33impl Dimensions {
34    fn new(sizes: ArrayVec<usize, MAX_DIMENSIONS>) -> Self {
35        let mut strides = ArrayVec::new();
36        let mut stride = 1;
37        for size in sizes.iter().rev() {
38            strides.push(stride);
39            stride *= size;
40        }
41        strides.reverse();
42        Self { sizes, strides }
43    }
44
45    fn check_size_product(sizes: &[usize]) -> Result<(), DimensionsError> {
46        sizes
47            .iter()
48            .try_fold(1usize, |product, size| product.checked_mul(*size))
49            .ok_or(DimensionsError::SizeOverflow)?;
50        sizes
51            .iter()
52            .rev()
53            .try_fold(1usize, |product, size| product.checked_mul(*size))
54            .map(|_| ())
55            .ok_or(DimensionsError::SizeOverflow)
56    }
57
58    #[inline]
59    pub fn total_len(&self) -> usize {
60        self.sizes.iter().product()
61    }
62
63    #[inline]
64    pub(crate) fn compatible(&self, other: &Dimensions) -> Result<(), DimensionsError> {
65        if self.total_len() != other.total_len() {
66            return Err(DimensionsError::NumElementsMismatch(self.total_len(), other.total_len()));
67        }
68        Ok(())
69    }
70
71    #[inline]
72    pub fn sizes(&self) -> &[usize] {
73        &self.sizes
74    }
75
76    pub(crate) fn sizes_mut(&mut self) -> &mut ArrayVec<usize, MAX_DIMENSIONS> {
77        &mut self.sizes
78    }
79
80    pub(crate) fn strides_mut(&mut self) -> &mut ArrayVec<usize, MAX_DIMENSIONS> {
81        &mut self.strides
82    }
83
84    #[inline]
85    pub fn strides(&self) -> &[usize] {
86        &self.strides
87    }
88
89    /// Maps a multi-dimensional index to a single-dimensional buffer index.
90    ///
91    /// Panics if the index is out of bounds, or the length of the index does not match the number
92    /// of dimensions.
93    #[inline]
94    pub(crate) fn index_map(&self, index: impl AsRef<[usize]>) -> usize {
95        // The panic code path was put into a cold function to not bloat the
96        // call site.
97        #[inline(never)]
98        #[cold]
99        #[track_caller]
100        fn index_length_mismatch(buffer_index: &[usize], dimensions: &Dimensions) -> ! {
101            panic!(
102                "Index tuple {buffer_index:?} has length {} which is out of bounds for dimensions 
103                {dimensions} of length {}",
104                buffer_index.len(),
105                dimensions.sizes().len()
106            );
107        }
108
109        // The panic code path was put into a cold function to not bloat the
110        // call site.
111        #[inline(never)]
112        #[cold]
113        #[track_caller]
114        fn index_out_of_bounds_fail(buffer_index: &[usize], dimensions: &Dimensions) -> ! {
115            panic!("Index {buffer_index:?} is out of bounds for dimensions {dimensions}",);
116        }
117
118        if index.as_ref().len() != self.sizes.len() {
119            index_length_mismatch(index.as_ref(), self);
120        }
121
122        let mut buffer_index = 0;
123        for ((idx, stride), len) in
124            index.as_ref().iter().zip_eq(self.strides.iter()).zip_eq(self.sizes.iter())
125        {
126            if *idx >= *len {
127                index_out_of_bounds_fail(index.as_ref(), self);
128            }
129            buffer_index += idx * stride;
130        }
131
132        buffer_index
133    }
134}
135
136impl TryFrom<&[usize]> for Dimensions {
137    type Error = DimensionsError;
138
139    fn try_from(value: &[usize]) -> Result<Self, Self::Error> {
140        let sizes = ArrayVec::try_from(value)
141            .map_err(|_| DimensionsError::TooManyDimensions(value.len()))?;
142        Self::check_size_product(&sizes)?;
143        Ok(Self::new(sizes))
144    }
145}
146
147impl TryFrom<Vec<usize>> for Dimensions {
148    type Error = DimensionsError;
149
150    fn try_from(value: Vec<usize>) -> Result<Self, Self::Error> {
151        let sizes = ArrayVec::try_from(value.as_slice())
152            .map_err(|_| DimensionsError::TooManyDimensions(value.len()))?;
153        Self::check_size_product(&sizes)?;
154        Ok(Self::new(sizes))
155    }
156}
157
158impl<const N: usize> TryFrom<[usize; N]> for Dimensions {
159    type Error = DimensionsError;
160
161    fn try_from(value: [usize; N]) -> Result<Self, Self::Error> {
162        let sizes = ArrayVec::try_from(value.as_slice())
163            .map_err(|_| DimensionsError::TooManyDimensions(value.len()))?;
164        Self::check_size_product(&sizes)?;
165        Ok(Self::new(sizes))
166    }
167}
168
169impl FromIterator<usize> for Dimensions {
170    #[inline]
171    fn from_iter<T: IntoIterator<Item = usize>>(iter: T) -> Self {
172        let sizes = ArrayVec::from_iter(iter);
173        Self::check_size_product(&sizes).expect("dimension product overflows usize");
174        Self::new(sizes)
175    }
176}
177
178impl Serialize for Dimensions {
179    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
180        self.sizes.serialize(serializer)
181    }
182}
183
184impl<'de> Deserialize<'de> for Dimensions {
185    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
186        let sizes = Vec::deserialize(deserializer)?;
187        Self::try_from(sizes).map_err(serde::de::Error::custom)
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    #[test]
196    fn rejects_too_many_dimensions_during_deserialization() {
197        let result = serde_json::from_str::<Dimensions>("[1,1,1,1]");
198        assert!(result.is_err());
199    }
200
201    #[test]
202    fn rejects_overflowing_dimension_product() {
203        assert!(matches!(
204            Dimensions::try_from([usize::MAX, 2]),
205            Err(DimensionsError::SizeOverflow)
206        ));
207    }
208}