1use std::fmt;
2
3use crate::Error;
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
17pub struct Shape(Vec<usize>);
18
19impl Shape {
20 pub fn new(dims: impl Into<Vec<usize>>) -> Self {
21 Self(dims.into())
22 }
23
24 pub fn scalar() -> Self {
26 Self(Vec::new())
27 }
28
29 pub fn dims(&self) -> &[usize] {
30 &self.0
31 }
32
33 pub fn rank(&self) -> usize {
35 self.0.len()
36 }
37
38 pub fn elem_count(&self) -> usize {
43 self.0.iter().product()
44 }
45
46 pub fn strides(&self) -> Vec<usize> {
53 let mut strides = vec![1; self.rank()];
54 for i in (0..self.rank().saturating_sub(1)).rev() {
55 strides[i] = strides[i + 1] * self.0[i + 1];
56 }
57 strides
58 }
59
60 pub fn reshape(&self, dims: impl Into<Vec<usize>>) -> Result<Self, Error> {
67 let candidate = Self::new(dims);
68 if candidate.elem_count() != self.elem_count() {
69 return Err(Error::ShapeMismatch {
70 expected: self.clone(),
71 actual: candidate,
72 });
73 }
74 Ok(candidate)
75 }
76
77 pub fn broadcast(&self, other: &Shape) -> Result<Shape, Error> {
85 let rank = self.rank().max(other.rank());
86 let mut dims = vec![0usize; rank];
87
88 for i in 0..rank {
89 let a = self.dim_from_end(i).unwrap_or(1);
91 let b = other.dim_from_end(i).unwrap_or(1);
92
93 dims[rank - 1 - i] = match (a, b) {
94 (a, b) if a == b => a,
95 (1, b) => b,
96 (a, 1) => a,
97 _ => {
98 return Err(Error::NotBroadcastable {
99 left: self.clone(),
100 right: other.clone(),
101 });
102 }
103 };
104 }
105
106 Ok(Shape::new(dims))
107 }
108
109 fn dim_from_end(&self, i: usize) -> Option<usize> {
111 (i < self.rank()).then(|| self.0[self.rank() - 1 - i])
112 }
113}
114
115impl From<Vec<usize>> for Shape {
116 fn from(dims: Vec<usize>) -> Self {
117 Self(dims)
118 }
119}
120
121impl<const N: usize> From<[usize; N]> for Shape {
122 fn from(dims: [usize; N]) -> Self {
123 Self(dims.to_vec())
124 }
125}
126
127impl From<&[usize]> for Shape {
128 fn from(dims: &[usize]) -> Self {
129 Self(dims.to_vec())
130 }
131}
132
133impl fmt::Display for Shape {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 f.write_str("[")?;
136 for (i, dim) in self.0.iter().enumerate() {
137 if i > 0 {
138 f.write_str(", ")?;
139 }
140 write!(f, "{dim}")?;
141 }
142 f.write_str("]")
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn a_scalar_has_rank_zero_and_exactly_one_element() {
152 let s = Shape::scalar();
153 assert_eq!(s.rank(), 0);
154 assert_eq!(s.elem_count(), 1);
155 }
156
157 #[test]
158 fn elem_count_is_the_product_of_dims() {
159 assert_eq!(Shape::from([2, 3, 4]).elem_count(), 24);
160 assert_eq!(Shape::from([5]).elem_count(), 5);
161 }
162
163 #[test]
164 fn a_zero_dimension_means_zero_elements() {
165 assert_eq!(Shape::from([3, 0, 4]).elem_count(), 0);
166 }
167
168 #[test]
169 fn strides_are_row_major_with_the_last_dim_contiguous() {
170 assert_eq!(Shape::from([2, 3, 4]).strides(), vec![12, 4, 1]);
171 assert_eq!(Shape::from([5]).strides(), vec![1]);
172 assert!(Shape::scalar().strides().is_empty());
173 }
174
175 #[test]
176 fn reshape_preserves_element_count_or_errors() {
177 let s = Shape::from([2, 6]);
178 assert_eq!(s.reshape([3, 4]).unwrap(), Shape::from([3, 4]));
179 assert_eq!(s.reshape([12]).unwrap(), Shape::from([12]));
180 assert!(s.reshape([5, 5]).is_err());
181 }
182
183 #[test]
184 fn broadcast_follows_the_numpy_right_aligned_rule() {
185 assert_eq!(
187 Shape::from([3, 1]).broadcast(&Shape::from([3, 4])).unwrap(),
188 Shape::from([3, 4])
189 );
190 assert_eq!(
192 Shape::from([4]).broadcast(&Shape::from([3, 4])).unwrap(),
193 Shape::from([3, 4])
194 );
195 assert_eq!(
197 Shape::from([3, 1]).broadcast(&Shape::from([1, 4])).unwrap(),
198 Shape::from([3, 4])
199 );
200 }
201
202 #[test]
203 fn broadcast_rejects_incompatible_dims() {
204 assert!(Shape::from([3, 2]).broadcast(&Shape::from([3, 4])).is_err());
205 }
206
207 #[test]
208 fn broadcasting_against_a_scalar_is_the_identity() {
209 let s = Shape::from([2, 3]);
210 assert_eq!(s.broadcast(&Shape::scalar()).unwrap(), s);
211 assert_eq!(Shape::scalar().broadcast(&s).unwrap(), s);
212 }
213}