slop_tensor/
dimensions.rs1use 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 #[inline]
94 pub(crate) fn index_map(&self, index: impl AsRef<[usize]>) -> usize {
95 #[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 #[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}