1#![allow(clippy::redundant_closure_call)]
7use crate::symbol::{SymEnv, SymId};
8use crate::{DimVec, Error, Result};
9use smallvec::smallvec;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub enum Extent {
25 Scalar(usize),
27 Range { min: usize, max: usize, sym: SymId },
30}
31
32impl Extent {
33 pub fn bound(&self) -> usize {
36 match self {
37 Extent::Scalar(v) => *v,
38 Extent::Range { max, .. } => *max,
39 }
40 }
41
42 pub fn min(&self) -> usize {
44 match self {
45 Extent::Scalar(v) => *v,
46 Extent::Range { min, .. } => *min,
47 }
48 }
49
50 pub fn is_dynamic(&self) -> bool {
52 matches!(self, Extent::Range { .. })
53 }
54
55 pub fn sym(&self) -> Option<SymId> {
57 match self {
58 Extent::Scalar(_) => None,
59 Extent::Range { sym, .. } => Some(*sym),
60 }
61 }
62
63 pub fn resolve(&self, env: &SymEnv) -> Option<usize> {
66 match self {
67 Extent::Scalar(v) => Some(*v),
68 Extent::Range { sym, .. } => env.get(*sym),
69 }
70 }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
77pub struct DynAxis {
78 pub axis: usize,
79 pub min: usize,
80 pub sym: SymId,
81}
82
83type DynAxisVec = smallvec::SmallVec<[DynAxis; 2]>;
86
87#[derive(Clone, PartialEq, Eq)]
102pub struct Shape {
103 dims: DimVec,
104 dynamic: Option<DynAxisVec>,
105}
106
107pub const SCALAR: Shape = Shape {
109 dims: smallvec::SmallVec::new_const(),
110 dynamic: None,
111};
112
113impl std::fmt::Debug for Shape {
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 write!(f, "{:?}", self.dims())?;
116 if let Some(dyn_axes) = &self.dynamic
117 && !dyn_axes.is_empty()
118 {
119 write!(f, "{{dyn: {dyn_axes:?}}}")?;
120 }
121 Ok(())
122 }
123}
124
125impl<const C: usize> From<&[usize; C]> for Shape {
126 fn from(dims: &[usize; C]) -> Self {
127 Self {
128 dims: DimVec::from_slice(dims),
129 dynamic: None,
130 }
131 }
132}
133
134impl From<&[usize]> for Shape {
135 fn from(dims: &[usize]) -> Self {
136 Self {
137 dims: DimVec::from_slice(dims),
138 dynamic: None,
139 }
140 }
141}
142
143impl From<&Shape> for Shape {
144 fn from(shape: &Shape) -> Self {
145 Self {
146 dims: shape.dims.clone(),
147 dynamic: shape.dynamic.clone(),
148 }
149 }
150}
151
152impl From<()> for Shape {
153 fn from(_: ()) -> Self {
154 Self {
155 dims: smallvec![],
156 dynamic: None,
157 }
158 }
159}
160
161impl From<usize> for Shape {
162 fn from(d1: usize) -> Self {
163 Self {
164 dims: smallvec![d1],
165 dynamic: None,
166 }
167 }
168}
169
170macro_rules! impl_from_tuple {
171 ($tuple:ty, $($index:tt),+) => {
172 impl From<$tuple> for Shape {
173 fn from(d: $tuple) -> Self {
174 Self { dims: smallvec![$(d.$index,)+], dynamic: None }
175 }
176 }
177 }
178}
179
180impl_from_tuple!((usize,), 0);
181impl_from_tuple!((usize, usize), 0, 1);
182impl_from_tuple!((usize, usize, usize), 0, 1, 2);
183impl_from_tuple!((usize, usize, usize, usize), 0, 1, 2, 3);
184impl_from_tuple!((usize, usize, usize, usize, usize), 0, 1, 2, 3, 4);
185impl_from_tuple!((usize, usize, usize, usize, usize, usize), 0, 1, 2, 3, 4, 5);
186
187impl From<Vec<usize>> for Shape {
188 fn from(dims: Vec<usize>) -> Self {
189 Self {
190 dims: DimVec::from(dims),
191 dynamic: None,
192 }
193 }
194}
195
196impl From<DimVec> for Shape {
197 fn from(dims: DimVec) -> Self {
198 Self {
199 dims,
200 dynamic: None,
201 }
202 }
203}
204
205#[macro_export]
212macro_rules! extract_dims {
213 ($(#[$fn_meta:meta])* $fn_name:ident, $cnt:tt, $dims:expr, $out_type:ty,
214 $(#[$shape_meta:meta])* shape) => {
215 $(#[$fn_meta])*
216 pub fn $fn_name(dims: &[usize]) -> $crate::Result<$out_type> {
217 if dims.len() != $cnt {
218 Err($crate::Error::UnexpectedNumberOfDims {
219 expected: $cnt,
220 got: dims.len(),
221 shape: Box::new($crate::Shape::from(dims)),
222 }
223 .bt())
224 } else {
225 Ok($dims(dims))
226 }
227 }
228
229 impl $crate::Shape {
230 $(#[$shape_meta])*
231 pub fn $fn_name(&self) -> $crate::Result<$out_type> {
232 $fn_name(self.dims())
233 }
234 }
235
236 impl std::convert::TryInto<$out_type> for $crate::Shape {
237 type Error = $crate::Error;
238 fn try_into(self) -> std::result::Result<$out_type, Self::Error> {
239 self.$fn_name()
240 }
241 }
242 };
243}
244
245impl Shape {
246 pub fn from_dims(dims: &[usize]) -> Self {
248 Self {
249 dims: DimVec::from_slice(dims),
250 dynamic: None,
251 }
252 }
253
254 pub fn rank(&self) -> usize {
256 self.dims.len()
257 }
258
259 pub fn into_dims(self) -> Vec<usize> {
261 self.dims.into_vec()
262 }
263
264 pub fn dims(&self) -> &[usize] {
270 &self.dims
271 }
272
273 pub fn extent(&self, axis: usize) -> Extent {
277 if let Some(dyn_axes) = &self.dynamic
278 && let Some(da) = dyn_axes.iter().find(|d| d.axis == axis)
279 {
280 return Extent::Range {
281 min: da.min,
282 max: self.dims[axis],
283 sym: da.sym,
284 };
285 }
286 Extent::Scalar(self.dims[axis])
287 }
288
289 pub fn extents(&self) -> impl Iterator<Item = Extent> + '_ {
291 (0..self.dims.len()).map(move |i| self.extent(i))
292 }
293
294 pub fn has_dynamic(&self) -> bool {
296 self.dynamic.as_ref().is_some_and(|v| !v.is_empty())
297 }
298
299 pub fn with_dynamic_axis(mut self, axis: usize, min: usize, sym: SymId) -> Self {
305 assert!(
306 axis < self.dims.len(),
307 "with_dynamic_axis: axis {axis} out of range for rank {}",
308 self.dims.len(),
309 );
310 let da = DynAxis { axis, min, sym };
311 let v = self.dynamic.get_or_insert_with(DynAxisVec::new);
312 match v.binary_search_by_key(&axis, |d| d.axis) {
314 Ok(i) => v[i] = da,
315 Err(i) => v.insert(i, da),
316 }
317 self
318 }
319
320 pub fn resolve(&self, env: &SymEnv) -> Result<Shape> {
325 let Some(dyn_axes) = &self.dynamic else {
326 return Ok(self.clone());
327 };
328 let mut dims = self.dims.clone();
329 for da in dyn_axes {
330 match env.get(da.sym) {
331 Some(v) => dims[da.axis] = v,
332 None => {
333 return Err(Error::Msg(format!(
334 "Shape::resolve: dynamic axis {} symbol {:?} is unbound",
335 da.axis, da.sym,
336 ))
337 .bt());
338 }
339 }
340 }
341 Ok(Shape {
342 dims,
343 dynamic: None,
344 })
345 }
346
347 pub fn dim<D: Dim>(&self, dim: D) -> Result<usize> {
351 let dim = dim.to_index(self, "dim")?;
352 Ok(self.dims()[dim])
353 }
354
355 pub fn elem_count(&self) -> usize {
359 self.dims.iter().product()
360 }
361
362 pub fn stride_contiguous(&self) -> crate::StrideVec {
368 let mut stride: crate::StrideVec = self
369 .dims
370 .iter()
371 .rev()
372 .scan(1_isize, |prod, &u| {
373 let prod_pre_mult = *prod;
374 *prod *= u as isize;
375 Some(prod_pre_mult)
376 })
377 .collect();
378 stride.reverse();
379 stride
380 }
381
382 pub fn is_contiguous(&self, stride: &[isize]) -> bool {
384 if self.dims.len() != stride.len() {
385 return false;
386 }
387 let mut acc: isize = 1;
388 for (&stride, &dim) in stride.iter().zip(self.dims.iter()).rev() {
389 if dim > 1 && stride != acc {
390 return false;
391 }
392 acc *= dim as isize;
393 }
394 true
395 }
396
397 pub fn is_fortran_contiguous(&self, stride: &[isize]) -> bool {
399 if self.dims.len() != stride.len() {
400 return false;
401 }
402 let mut acc: isize = 1;
403 for (&stride, &dim) in stride.iter().zip(self.dims.iter()) {
404 if dim > 1 && stride != acc {
405 return false;
406 }
407 acc *= dim as isize;
408 }
409 true
410 }
411
412 pub fn extend(mut self, additional_dims: &[usize]) -> Self {
415 self.dims.extend_from_slice(additional_dims);
418 self
419 }
420
421 pub fn broadcast_shape_binary_op(&self, rhs: &Self, op: &'static str) -> Result<Shape> {
424 let lhs = self;
425 let lhs_dims = lhs.dims();
426 let rhs_dims = rhs.dims();
427 let lhs_ndims = lhs_dims.len();
428 let rhs_ndims = rhs_dims.len();
429 let bcast_ndims = usize::max(lhs_ndims, rhs_ndims);
430 let mut bcast_dims = smallvec![0; bcast_ndims];
431 for (idx, bcast_value) in bcast_dims.iter_mut().enumerate() {
432 let rev_idx = bcast_ndims - idx;
433 let l_value = if lhs_ndims < rev_idx {
434 1
435 } else {
436 lhs_dims[lhs_ndims - rev_idx]
437 };
438 let r_value = if rhs_ndims < rev_idx {
439 1
440 } else {
441 rhs_dims[rhs_ndims - rev_idx]
442 };
443 *bcast_value = if l_value == r_value {
444 l_value
445 } else if l_value == 1 {
446 r_value
447 } else if r_value == 1 {
448 l_value
449 } else {
450 Err(Error::ShapeMismatchBinaryOp {
451 lhs: Box::new(lhs.clone()),
452 rhs: Box::new(rhs.clone()),
453 op,
454 }
455 .bt())?
456 }
457 }
458 Ok(Shape::from(bcast_dims))
459 }
460
461 pub fn broadcast_shape_matmul(&self, rhs: &Self) -> Result<(Shape, Shape)> {
463 let lhs = self;
464 let lhs_dims = lhs.dims();
465 let rhs_dims = rhs.dims();
466 if lhs_dims.len() < 2 || rhs_dims.len() < 2 {
467 crate::bail!("only 2d matrixes are supported {lhs:?} {rhs:?}")
468 }
469 let (m, lhs_k) = (lhs_dims[lhs_dims.len() - 2], lhs_dims[lhs_dims.len() - 1]);
470 let (rhs_k, n) = (rhs_dims[rhs_dims.len() - 2], rhs_dims[rhs_dims.len() - 1]);
471 if lhs_k != rhs_k {
472 crate::bail!("different inner dimensions in broadcast matmul {lhs:?} {rhs:?}")
473 }
474
475 let lhs_b = Self::from(&lhs_dims[..lhs_dims.len() - 2]);
476 let rhs_b = Self::from(&rhs_dims[..rhs_dims.len() - 2]);
477 let bcast = lhs_b.broadcast_shape_binary_op(&rhs_b, "broadcast_matmul")?;
478 let bcast_dims = bcast.dims();
479
480 let bcast_lhs = [bcast_dims, &[m, lhs_k]].concat();
481 let bcast_rhs = [bcast_dims, &[rhs_k, n]].concat();
482 Ok((Shape::from(bcast_lhs), Shape::from(bcast_rhs)))
483 }
484}
485
486pub trait Dim {
490 fn to_index(&self, shape: &Shape, op: &'static str) -> Result<usize>;
492 fn to_index_plus_one(&self, shape: &Shape, op: &'static str) -> Result<usize>;
494}
495
496impl Dim for usize {
497 fn to_index(&self, shape: &Shape, op: &'static str) -> Result<usize> {
498 let dim = *self;
499 if dim >= shape.dims().len() {
500 Err(Error::DimOutOfRange {
501 shape: Box::new(shape.clone()),
502 dim: dim as i32,
503 op,
504 }
505 .bt())?
506 } else {
507 Ok(dim)
508 }
509 }
510
511 fn to_index_plus_one(&self, shape: &Shape, op: &'static str) -> Result<usize> {
512 let dim = *self;
513 if dim > shape.dims().len() {
514 Err(Error::DimOutOfRange {
515 shape: Box::new(shape.clone()),
516 dim: dim as i32,
517 op,
518 }
519 .bt())?
520 } else {
521 Ok(dim)
522 }
523 }
524}
525
526#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
531pub enum D {
532 Minus1,
534 Minus2,
536 Minus(usize),
538}
539
540impl D {
541 fn out_of_range(&self, shape: &Shape, op: &'static str) -> Error {
542 let dim = match self {
543 Self::Minus1 => -1,
544 Self::Minus2 => -2,
545 Self::Minus(u) => -(*u as i32),
546 };
547 Error::DimOutOfRange {
548 shape: Box::new(shape.clone()),
549 dim,
550 op,
551 }
552 .bt()
553 }
554}
555
556impl Dim for D {
557 fn to_index(&self, shape: &Shape, op: &'static str) -> Result<usize> {
558 let rank = shape.rank();
559 match self {
560 Self::Minus1 if rank >= 1 => Ok(rank - 1),
561 Self::Minus2 if rank >= 2 => Ok(rank - 2),
562 Self::Minus(u) if *u > 0 && rank >= *u => Ok(rank - *u),
563 _ => Err(self.out_of_range(shape, op)),
564 }
565 }
566
567 fn to_index_plus_one(&self, shape: &Shape, op: &'static str) -> Result<usize> {
568 let rank = shape.rank();
569 match self {
570 Self::Minus1 => Ok(rank),
571 Self::Minus2 if rank >= 1 => Ok(rank - 1),
572 Self::Minus(u) if *u > 0 && rank + 1 >= *u => Ok(rank + 1 - *u),
573 _ => Err(self.out_of_range(shape, op)),
574 }
575 }
576}
577
578pub trait Dims: Sized {
582 fn to_indexes_internal(self, shape: &Shape, op: &'static str) -> Result<Vec<usize>>;
584
585 fn to_indexes(self, shape: &Shape, op: &'static str) -> Result<Vec<usize>> {
586 let dims = self.to_indexes_internal(shape, op)?;
587 for (i, &dim) in dims.iter().enumerate() {
588 if dims[..i].contains(&dim) {
589 Err(Error::DuplicateDimIndex {
590 shape: Box::new(shape.clone()),
591 dims: dims.clone(),
592 op,
593 }
594 .bt())?
595 }
596 if dim >= shape.rank() {
597 Err(Error::DimOutOfRange {
598 shape: Box::new(shape.clone()),
599 dim: dim as i32,
600 op,
601 }
602 .bt())?
603 }
604 }
605 Ok(dims)
606 }
607}
608
609impl Dims for Vec<usize> {
610 fn to_indexes_internal(self, _: &Shape, _: &'static str) -> Result<Vec<usize>> {
611 Ok(self)
612 }
613}
614
615impl<const N: usize> Dims for [usize; N] {
616 fn to_indexes_internal(self, _: &Shape, _: &'static str) -> Result<Vec<usize>> {
617 Ok(self.to_vec())
618 }
619}
620
621impl Dims for &[usize] {
622 fn to_indexes_internal(self, _: &Shape, _: &'static str) -> Result<Vec<usize>> {
623 Ok(self.to_vec())
624 }
625}
626
627impl Dims for () {
628 fn to_indexes_internal(self, _: &Shape, _: &'static str) -> Result<Vec<usize>> {
629 Ok(vec![])
630 }
631}
632
633impl<D: Dim + Sized> Dims for D {
634 fn to_indexes_internal(self, shape: &Shape, op: &'static str) -> Result<Vec<usize>> {
635 let dim = self.to_index(shape, op)?;
636 Ok(vec![dim])
637 }
638}
639
640impl<D: Dim> Dims for (D,) {
641 fn to_indexes_internal(self, shape: &Shape, op: &'static str) -> Result<Vec<usize>> {
642 let dim = self.0.to_index(shape, op)?;
643 Ok(vec![dim])
644 }
645}
646
647impl<D1: Dim, D2: Dim> Dims for (D1, D2) {
648 fn to_indexes_internal(self, shape: &Shape, op: &'static str) -> Result<Vec<usize>> {
649 let d0 = self.0.to_index(shape, op)?;
650 let d1 = self.1.to_index(shape, op)?;
651 Ok(vec![d0, d1])
652 }
653}
654
655impl<D1: Dim, D2: Dim, D3: Dim> Dims for (D1, D2, D3) {
656 fn to_indexes_internal(self, shape: &Shape, op: &'static str) -> Result<Vec<usize>> {
657 let d0 = self.0.to_index(shape, op)?;
658 let d1 = self.1.to_index(shape, op)?;
659 let d2 = self.2.to_index(shape, op)?;
660 Ok(vec![d0, d1, d2])
661 }
662}
663
664impl<D1: Dim, D2: Dim, D3: Dim, D4: Dim> Dims for (D1, D2, D3, D4) {
665 fn to_indexes_internal(self, shape: &Shape, op: &'static str) -> Result<Vec<usize>> {
666 let d0 = self.0.to_index(shape, op)?;
667 let d1 = self.1.to_index(shape, op)?;
668 let d2 = self.2.to_index(shape, op)?;
669 let d3 = self.3.to_index(shape, op)?;
670 Ok(vec![d0, d1, d2, d3])
671 }
672}
673
674impl<D1: Dim, D2: Dim, D3: Dim, D4: Dim, D5: Dim> Dims for (D1, D2, D3, D4, D5) {
675 fn to_indexes_internal(self, shape: &Shape, op: &'static str) -> Result<Vec<usize>> {
676 let d0 = self.0.to_index(shape, op)?;
677 let d1 = self.1.to_index(shape, op)?;
678 let d2 = self.2.to_index(shape, op)?;
679 let d3 = self.3.to_index(shape, op)?;
680 let d4 = self.4.to_index(shape, op)?;
681 Ok(vec![d0, d1, d2, d3, d4])
682 }
683}
684
685impl<D1: Dim, D2: Dim, D3: Dim, D4: Dim, D5: Dim, D6: Dim> Dims for (D1, D2, D3, D4, D5, D6) {
686 fn to_indexes_internal(self, shape: &Shape, op: &'static str) -> Result<Vec<usize>> {
687 let d0 = self.0.to_index(shape, op)?;
688 let d1 = self.1.to_index(shape, op)?;
689 let d2 = self.2.to_index(shape, op)?;
690 let d3 = self.3.to_index(shape, op)?;
691 let d4 = self.4.to_index(shape, op)?;
692 let d5 = self.5.to_index(shape, op)?;
693 Ok(vec![d0, d1, d2, d3, d4, d5])
694 }
695}
696
697extract_dims!(
698 dims0, 0, |_: &[usize]| (), (),
700 shape);
702
703extract_dims!(
704 dims1, 1, |d: &[usize]| d[0], usize,
706 shape);
708
709extract_dims!(
710 dims2, 2, |d: &[usize]| (d[0], d[1]), (usize, usize),
712 shape);
714
715extract_dims!(
716 dims3, 3, |d: &[usize]| (d[0], d[1], d[2]), (usize, usize, usize),
718 shape);
720
721extract_dims!(
722 dims4, 4, |d: &[usize]| (d[0], d[1], d[2], d[3]), (usize, usize, usize, usize),
724 shape);
726
727extract_dims!(
728 dims5, 5, |d: &[usize]| (d[0], d[1], d[2], d[3], d[4]), (usize, usize, usize, usize, usize),
730 shape);
732
733pub fn stride_dims2(stride: &[isize]) -> Result<(usize, usize)> {
745 if stride.len() != 2 {
746 return Err(Error::UnexpectedNumberOfDims {
747 expected: 2,
748 got: stride.len(),
749 shape: Box::new(Shape::from(&[][..])),
750 }
751 .bt());
752 }
753 debug_assert!(
754 stride.iter().all(|&s| s >= 0),
755 "stride_dims2: negative stride"
756 );
757 Ok((stride[0] as usize, stride[1] as usize))
758}
759
760pub fn stride_dims3(stride: &[isize]) -> Result<(usize, usize, usize)> {
762 if stride.len() != 3 {
763 return Err(Error::UnexpectedNumberOfDims {
764 expected: 3,
765 got: stride.len(),
766 shape: Box::new(Shape::from(&[][..])),
767 }
768 .bt());
769 }
770 debug_assert!(
771 stride.iter().all(|&s| s >= 0),
772 "stride_dims3: negative stride"
773 );
774 Ok((stride[0] as usize, stride[1] as usize, stride[2] as usize))
775}
776
777pub fn stride_dims4(stride: &[isize]) -> Result<(usize, usize, usize, usize)> {
779 if stride.len() != 4 {
780 return Err(Error::UnexpectedNumberOfDims {
781 expected: 4,
782 got: stride.len(),
783 shape: Box::new(Shape::from(&[][..])),
784 }
785 .bt());
786 }
787 debug_assert!(
788 stride.iter().all(|&s| s >= 0),
789 "stride_dims4: negative stride"
790 );
791 Ok((
792 stride[0] as usize,
793 stride[1] as usize,
794 stride[2] as usize,
795 stride[3] as usize,
796 ))
797}
798
799pub fn stride_dims5(stride: &[isize]) -> Result<(usize, usize, usize, usize, usize)> {
801 if stride.len() != 5 {
802 return Err(Error::UnexpectedNumberOfDims {
803 expected: 5,
804 got: stride.len(),
805 shape: Box::new(Shape::from(&[][..])),
806 }
807 .bt());
808 }
809 debug_assert!(
810 stride.iter().all(|&s| s >= 0),
811 "stride_dims5: negative stride"
812 );
813 Ok((
814 stride[0] as usize,
815 stride[1] as usize,
816 stride[2] as usize,
817 stride[3] as usize,
818 stride[4] as usize,
819 ))
820}
821
822pub trait ShapeWithOneHole {
827 fn into_shape(self, el_count: usize) -> Result<Shape>;
829}
830
831impl<S: Into<Shape>> ShapeWithOneHole for S {
832 fn into_shape(self, _el_count: usize) -> Result<Shape> {
833 Ok(self.into())
834 }
835}
836
837impl ShapeWithOneHole for ((),) {
838 fn into_shape(self, el_count: usize) -> Result<Shape> {
839 Ok(el_count.into())
840 }
841}
842
843fn hole_size(el_count: usize, prod_d: usize, s: &dyn std::fmt::Debug) -> Result<usize> {
844 if prod_d == 0 {
845 crate::bail!("cannot reshape tensor of {el_count} elements to {s:?}")
846 }
847 if !el_count.is_multiple_of(prod_d) {
848 crate::bail!("cannot reshape tensor with {el_count} elements to {s:?}")
849 }
850 Ok(el_count / prod_d)
851}
852
853impl ShapeWithOneHole for ((), usize) {
854 fn into_shape(self, el_count: usize) -> Result<Shape> {
855 let ((), d1) = self;
856 Ok((hole_size(el_count, d1, &self)?, d1).into())
857 }
858}
859
860impl ShapeWithOneHole for (usize, ()) {
861 fn into_shape(self, el_count: usize) -> Result<Shape> {
862 let (d1, ()) = self;
863 Ok((d1, hole_size(el_count, d1, &self)?).into())
864 }
865}
866
867impl ShapeWithOneHole for ((), usize, usize) {
868 fn into_shape(self, el_count: usize) -> Result<Shape> {
869 let ((), d1, d2) = self;
870 Ok((hole_size(el_count, d1 * d2, &self)?, d1, d2).into())
871 }
872}
873
874impl ShapeWithOneHole for (usize, (), usize) {
875 fn into_shape(self, el_count: usize) -> Result<Shape> {
876 let (d1, (), d2) = self;
877 Ok((d1, hole_size(el_count, d1 * d2, &self)?, d2).into())
878 }
879}
880
881impl ShapeWithOneHole for (usize, usize, ()) {
882 fn into_shape(self, el_count: usize) -> Result<Shape> {
883 let (d1, d2, ()) = self;
884 Ok((d1, d2, hole_size(el_count, d1 * d2, &self)?).into())
885 }
886}
887
888impl ShapeWithOneHole for ((), usize, usize, usize) {
889 fn into_shape(self, el_count: usize) -> Result<Shape> {
890 let ((), d1, d2, d3) = self;
891 let d = hole_size(el_count, d1 * d2 * d3, &self)?;
892 Ok((d, d1, d2, d3).into())
893 }
894}
895
896impl ShapeWithOneHole for (usize, (), usize, usize) {
897 fn into_shape(self, el_count: usize) -> Result<Shape> {
898 let (d1, (), d2, d3) = self;
899 let d = hole_size(el_count, d1 * d2 * d3, &self)?;
900 Ok((d1, d, d2, d3).into())
901 }
902}
903
904impl ShapeWithOneHole for (usize, usize, (), usize) {
905 fn into_shape(self, el_count: usize) -> Result<Shape> {
906 let (d1, d2, (), d3) = self;
907 let d = hole_size(el_count, d1 * d2 * d3, &self)?;
908 Ok((d1, d2, d, d3).into())
909 }
910}
911
912impl ShapeWithOneHole for (usize, usize, usize, ()) {
913 fn into_shape(self, el_count: usize) -> Result<Shape> {
914 let (d1, d2, d3, ()) = self;
915 let d = hole_size(el_count, d1 * d2 * d3, &self)?;
916 Ok((d1, d2, d3, d).into())
917 }
918}
919
920impl ShapeWithOneHole for ((), usize, usize, usize, usize) {
921 fn into_shape(self, el_count: usize) -> Result<Shape> {
922 let ((), d1, d2, d3, d4) = self;
923 let d = hole_size(el_count, d1 * d2 * d3 * d4, &self)?;
924 Ok((d, d1, d2, d3, d4).into())
925 }
926}
927
928impl ShapeWithOneHole for (usize, (), usize, usize, usize) {
929 fn into_shape(self, el_count: usize) -> Result<Shape> {
930 let (d1, (), d2, d3, d4) = self;
931 let d = hole_size(el_count, d1 * d2 * d3 * d4, &self)?;
932 Ok((d1, d, d2, d3, d4).into())
933 }
934}
935
936impl ShapeWithOneHole for (usize, usize, (), usize, usize) {
937 fn into_shape(self, el_count: usize) -> Result<Shape> {
938 let (d1, d2, (), d3, d4) = self;
939 let d = hole_size(el_count, d1 * d2 * d3 * d4, &self)?;
940 Ok((d1, d2, d, d3, d4).into())
941 }
942}
943
944impl ShapeWithOneHole for (usize, usize, usize, (), usize) {
945 fn into_shape(self, el_count: usize) -> Result<Shape> {
946 let (d1, d2, d3, (), d4) = self;
947 let d = hole_size(el_count, d1 * d2 * d3 * d4, &self)?;
948 Ok((d1, d2, d3, d, d4).into())
949 }
950}
951
952impl ShapeWithOneHole for (usize, usize, usize, usize, ()) {
953 fn into_shape(self, el_count: usize) -> Result<Shape> {
954 let (d1, d2, d3, d4, ()) = self;
955 let d = hole_size(el_count, d1 * d2 * d3 * d4, &self)?;
956 Ok((d1, d2, d3, d4, d).into())
957 }
958}
959
960#[cfg(test)]
961mod tests {
962 use super::*;
963
964 #[test]
965 fn stride() {
966 let shape = Shape::from(());
967 assert_eq!(shape.stride_contiguous().to_vec(), Vec::<isize>::new());
968 let shape = Shape::from(42);
969 assert_eq!(shape.stride_contiguous().to_vec(), [1_isize]);
970 let shape = Shape::from((42, 1337));
971 assert_eq!(shape.stride_contiguous().to_vec(), [1337_isize, 1]);
972 let shape = Shape::from((299, 792, 458));
973 assert_eq!(
974 shape.stride_contiguous().to_vec(),
975 [458_isize * 792, 458, 1]
976 );
977 }
978
979 #[test]
980 fn test_from_tuple() {
981 let shape = Shape::from((2,));
982 assert_eq!(shape.dims(), &[2]);
983 let shape = Shape::from((2, 3));
984 assert_eq!(shape.dims(), &[2, 3]);
985 let shape = Shape::from((2, 3, 4));
986 assert_eq!(shape.dims(), &[2, 3, 4]);
987 let shape = Shape::from((2, 3, 4, 5));
988 assert_eq!(shape.dims(), &[2, 3, 4, 5]);
989 let shape = Shape::from((2, 3, 4, 5, 6));
990 assert_eq!(shape.dims(), &[2, 3, 4, 5, 6]);
991 let shape = Shape::from((2, 3, 4, 5, 6, 7));
992 assert_eq!(shape.dims(), &[2, 3, 4, 5, 6, 7]);
993 }
994
995 #[test]
1002 fn symbolic_extent_foundation() {
1003 use crate::{Layout, SymEnv, SymId};
1004
1005 let sym = SymId(0);
1007 let shape = Shape::from_dims(&[1, 32, 4096, 128]).with_dynamic_axis(2, 0, sym);
1008
1009 assert_eq!(shape.dims(), &[1, 32, 4096, 128]);
1011 assert!(shape.has_dynamic());
1012
1013 assert_eq!(shape.extent(0), Extent::Scalar(1));
1015 assert_eq!(
1016 shape.extent(2),
1017 Extent::Range {
1018 min: 0,
1019 max: 4096,
1020 sym
1021 }
1022 );
1023 assert_eq!(shape.extent(2).bound(), 4096);
1024 assert!(shape.extent(2).is_dynamic());
1025 assert_eq!(shape.extent(2).sym(), Some(sym));
1026 assert_eq!(shape.extent(0).sym(), None);
1027 assert_eq!(shape.extents().count(), 4);
1028
1029 let mut env = SymEnv::new();
1031 assert!(shape.resolve(&env).is_err(), "unbound symbol must error");
1032 env.bind(sym, 53).unwrap();
1033 let concrete = shape.resolve(&env).unwrap();
1034 assert_eq!(concrete.dims(), &[1, 32, 53, 128]);
1035 assert!(!concrete.has_dynamic());
1036 assert_eq!(shape.extent(2).resolve(&env), Some(53));
1037
1038 let other = Shape::from_dims(&[1, 32, 4096, 128]).with_dynamic_axis(2, 0, sym);
1040 assert_eq!(other.resolve(&env).unwrap().dims(), &[1, 32, 53, 128]);
1041
1042 let concrete_caps = Shape::from_dims(&[1, 32, 4096, 128]);
1044 assert_ne!(shape, concrete_caps);
1045 assert_eq!(shape, other);
1046 assert!(!concrete_caps.has_dynamic());
1047 assert_eq!(concrete_caps.extent(2), Extent::Scalar(4096));
1049
1050 assert_eq!(shape.elem_count(), 32 * 4096 * 128);
1052
1053 let layout = Layout::contiguous_with_offset(shape.clone(), 0);
1055 assert!(layout.has_dynamic());
1056 let rlayout = layout.resolve(&env).unwrap();
1057 assert_eq!(rlayout.dims(), &[1, 32, 53, 128]);
1058 assert_eq!(
1059 rlayout.stride(),
1060 layout.stride(),
1061 "strides stay capacity-based"
1062 );
1063 }
1064
1065 #[test]
1066 fn broadcast_identical_shapes() {
1067 let a = Shape::from((2, 3));
1068 let b = Shape::from((2, 3));
1069 let result = a.broadcast_shape_binary_op(&b, "add").unwrap();
1070 assert_eq!(result.dims(), &[2, 3]);
1071 }
1072
1073 #[test]
1074 fn broadcast_broadcasts_one_on_lhs() {
1075 let a = Shape::from((1, 3));
1076 let b = Shape::from((2, 3));
1077 let result = a.broadcast_shape_binary_op(&b, "add").unwrap();
1078 assert_eq!(result.dims(), &[2, 3]);
1079 }
1080
1081 #[test]
1082 fn broadcast_broadcasts_one_on_rhs() {
1083 let a = Shape::from((2, 1));
1084 let b = Shape::from((2, 3));
1085 let result = a.broadcast_shape_binary_op(&b, "add").unwrap();
1086 assert_eq!(result.dims(), &[2, 3]);
1087 }
1088
1089 #[test]
1090 fn broadcast_scalar_with_matrix() {
1091 let a = Shape::from(());
1092 let b = Shape::from((2, 3));
1093 let result = a.broadcast_shape_binary_op(&b, "add").unwrap();
1094 assert_eq!(result.dims(), &[2, 3]);
1095 }
1096
1097 #[test]
1098 fn broadcast_different_ranks() {
1099 let a = Shape::from((1,));
1100 let b = Shape::from((3, 4, 5));
1101 let result = a.broadcast_shape_binary_op(&b, "add").unwrap();
1102 assert_eq!(result.dims(), &[3, 4, 5]);
1103 }
1104
1105 #[test]
1106 fn broadcast_incompatible_shapes() {
1107 let a = Shape::from((2, 3));
1108 let b = Shape::from((4, 3));
1109 assert!(a.broadcast_shape_binary_op(&b, "add").is_err());
1110 }
1111
1112 #[test]
1113 fn broadcast_both_ones_differ() {
1114 let a = Shape::from((1, 3));
1115 let b = Shape::from((2, 1));
1116 let result = a.broadcast_shape_binary_op(&b, "add").unwrap();
1117 assert_eq!(result.dims(), &[2, 3]);
1118 }
1119
1120 #[test]
1121 fn broadcast_3d_mixed() {
1122 let a = Shape::from((2, 1, 4));
1123 let b = Shape::from((1, 3, 1));
1124 let result = a.broadcast_shape_binary_op(&b, "mul").unwrap();
1125 assert_eq!(result.dims(), &[2, 3, 4]);
1126 }
1127}