1#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct SliceRange {
39 pub start: usize,
41 pub stop: usize,
43 pub step: usize,
45}
46
47impl Default for SliceRange {
48 fn default() -> Self {
49 Self {
50 start: 0,
51 stop: 0,
52 step: 1,
53 }
54 }
55}
56
57impl SliceRange {
58 #[must_use]
60 pub fn new(start: usize, stop: usize, step: usize) -> Self {
61 Self { start, stop, step }
62 }
63
64 #[must_use]
66 pub fn contiguous(start: usize, stop: usize) -> Self {
67 Self {
68 start,
69 stop,
70 step: 1,
71 }
72 }
73
74 #[must_use]
79 pub fn len(&self) -> usize {
80 if self.step == 0 || self.start >= self.stop {
81 return 0;
82 }
83 let span = self.stop - self.start;
84 span.div_ceil(self.step)
85 }
86
87 #[must_use]
89 pub fn is_empty(&self) -> bool {
90 self.len() == 0
91 }
92
93 #[must_use]
95 pub fn indices(&self) -> Vec<usize> {
96 if self.step == 0 || self.start >= self.stop {
97 return Vec::new();
98 }
99 let mut result = Vec::with_capacity(self.len());
100 let mut idx = self.start;
101 while idx < self.stop {
102 result.push(idx);
103 idx = match idx.checked_add(self.step) {
104 Some(v) => v,
105 None => break,
106 };
107 }
108 result
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct ViewDescriptor {
140 pub base_offset: usize,
142 pub shape: Vec<usize>,
144 pub strides: Vec<usize>,
146}
147
148impl ViewDescriptor {
149 #[must_use]
151 pub fn ndim(&self) -> usize {
152 self.shape.len()
153 }
154
155 #[must_use]
157 pub fn total_elements(&self) -> usize {
158 self.shape.iter().product()
159 }
160
161 #[must_use]
167 pub fn flat_index(&self, indices: &[usize]) -> Option<usize> {
168 if indices.len() != self.ndim() {
169 return None;
170 }
171 let mut offset = self.base_offset;
172 for (dim, (&idx, (&dim_size, &stride))) in indices
173 .iter()
174 .zip(self.shape.iter().zip(self.strides.iter()))
175 .enumerate()
176 {
177 let _ = dim; if idx >= dim_size {
179 return None;
180 }
181 offset = offset.checked_add(idx * stride)?;
182 }
183 Some(offset)
184 }
185
186 #[must_use]
191 pub fn is_contiguous(&self) -> bool {
192 let n = self.ndim();
193 if n == 0 {
194 return true;
195 }
196 if self.strides[n - 1] != 1 {
197 return false;
198 }
199 let mut expected = 1usize;
200 for i in (0..n).rev() {
201 if self.strides[i] != expected {
202 return false;
203 }
204 expected *= self.shape[i];
205 }
206 true
207 }
208}
209
210#[derive(Debug, Clone, PartialEq, Eq)]
228pub struct BroadcastShape {
229 pub shape: Vec<usize>,
231}
232
233impl BroadcastShape {
234 #[must_use]
239 pub fn compatible_with(&self, other: &[usize]) -> bool {
240 let self_ndim = self.shape.len();
241 let other_ndim = other.len();
242
243 for k in 0..self_ndim.max(other_ndim) {
244 let a = if k < self_ndim {
245 self.shape[self_ndim - 1 - k]
246 } else {
247 1
248 };
249 let b = if k < other_ndim {
250 other[other_ndim - 1 - k]
251 } else {
252 1
253 };
254 if a != b && a != 1 && b != 1 {
255 return false;
256 }
257 }
258 true
259 }
260
261 #[must_use]
269 pub fn broadcast_to(&self, target: &[usize]) -> Option<Vec<usize>> {
270 if !self.compatible_with(target) {
271 return None;
272 }
273
274 let target_ndim = target.len();
275 let src_ndim = self.shape.len();
276
277 let src_strides = row_major_strides(&self.shape);
279
280 let mut strides = vec![0usize; target_ndim];
281 for k in 0..target_ndim {
282 let src_k_rev = k; let src_k = if src_k_rev < src_ndim {
285 src_ndim - 1 - src_k_rev
286 } else {
287 let out_k = target_ndim - 1 - k;
289 strides[out_k] = 0;
290 continue;
291 };
292 let out_k = target_ndim - 1 - k;
293 let src_dim = self.shape[src_k];
294 let tgt_dim = target[out_k];
295
296 if src_dim == 1 && tgt_dim != 1 {
297 strides[out_k] = 0;
299 } else {
300 strides[out_k] = src_strides[src_k];
302 }
303 }
304
305 Some(strides)
306 }
307}
308
309#[derive(Debug, Clone, Default)]
315pub struct SliceViewStats {
316 pub total_views_created: u64,
318 pub total_slices: u64,
320 pub total_broadcasts: u64,
322}
323
324#[must_use]
333fn row_major_strides(shape: &[usize]) -> Vec<usize> {
334 let n = shape.len();
335 if n == 0 {
336 return Vec::new();
337 }
338 let mut strides = vec![1usize; n];
339 for i in (0..n - 1).rev() {
340 strides[i] = strides[i + 1] * shape[i + 1];
341 }
342 strides
343}
344
345#[derive(Debug)]
372pub struct TensorSliceView {
373 pub data: Vec<f64>,
375 pub descriptor: ViewDescriptor,
377 pub stats: SliceViewStats,
379}
380
381impl TensorSliceView {
382 #[must_use]
387 pub fn new(data: Vec<f64>, shape: Vec<usize>) -> Self {
388 let strides = row_major_strides(&shape);
389 let descriptor = ViewDescriptor {
390 base_offset: 0,
391 shape,
392 strides,
393 };
394 let stats = SliceViewStats {
395 total_views_created: 1,
396 ..Default::default()
397 };
398 Self {
399 data,
400 descriptor,
401 stats,
402 }
403 }
404
405 #[must_use]
410 pub fn get(&self, indices: &[usize]) -> Option<f64> {
411 let flat = self.descriptor.flat_index(indices)?;
412 self.data.get(flat).copied()
413 }
414
415 pub fn slice(&mut self, dim: usize, range: SliceRange) -> Option<ViewDescriptor> {
426 let ndim = self.descriptor.ndim();
427 if dim >= ndim {
428 return None;
429 }
430 if range.start >= self.descriptor.shape[dim] {
431 return None;
432 }
433
434 let mut new_shape = self.descriptor.shape.clone();
435 let mut new_strides = self.descriptor.strides.clone();
436
437 let new_base_offset = self
439 .descriptor
440 .base_offset
441 .checked_add(range.start * self.descriptor.strides[dim])?;
442
443 new_shape[dim] = range.len();
445
446 new_strides[dim] = self.descriptor.strides[dim].checked_mul(range.step.max(1))?;
448
449 let desc = ViewDescriptor {
450 base_offset: new_base_offset,
451 shape: new_shape,
452 strides: new_strides,
453 };
454
455 self.stats.total_slices += 1;
456 Some(desc)
457 }
458
459 pub fn broadcast_strides(&mut self, target_shape: &[usize]) -> Option<Vec<usize>> {
464 let bs = BroadcastShape {
465 shape: self.descriptor.shape.clone(),
466 };
467 let strides = bs.broadcast_to(target_shape)?;
468 self.stats.total_broadcasts += 1;
469 Some(strides)
470 }
471
472 #[must_use]
474 pub fn stats(&self) -> &SliceViewStats {
475 &self.stats
476 }
477}
478
479#[cfg(test)]
484mod tests {
485 use super::*;
486
487 #[test]
490 fn slice_range_len_step_zero_is_empty() {
491 let r = SliceRange {
492 start: 0,
493 stop: 10,
494 step: 0,
495 };
496 assert_eq!(r.len(), 0);
497 }
498
499 #[test]
500 fn slice_range_len_start_ge_stop_is_empty() {
501 let r = SliceRange {
502 start: 5,
503 stop: 5,
504 step: 1,
505 };
506 assert_eq!(r.len(), 0);
507 let r2 = SliceRange {
508 start: 6,
509 stop: 5,
510 step: 1,
511 };
512 assert_eq!(r2.len(), 0);
513 }
514
515 #[test]
516 fn slice_range_len_step_one() {
517 let r = SliceRange::contiguous(2, 7);
518 assert_eq!(r.len(), 5);
519 }
520
521 #[test]
522 fn slice_range_len_step_two_even() {
523 let r = SliceRange::new(0, 10, 2);
525 assert_eq!(r.len(), 5);
526 }
527
528 #[test]
529 fn slice_range_len_step_two_odd_span() {
530 let r = SliceRange::new(0, 5, 2);
532 assert_eq!(r.len(), 3);
533 }
534
535 #[test]
536 fn slice_range_len_step_larger_than_span() {
537 let r = SliceRange::new(0, 5, 10);
539 assert_eq!(r.len(), 1);
540 }
541
542 #[test]
543 fn slice_range_len_step_three() {
544 let r = SliceRange::new(1, 9, 3);
546 assert_eq!(r.len(), 3);
547 }
548
549 #[test]
552 fn slice_range_indices_step_one() {
553 let r = SliceRange::contiguous(3, 6);
554 assert_eq!(r.indices(), vec![3, 4, 5]);
555 }
556
557 #[test]
558 fn slice_range_indices_step_two() {
559 let r = SliceRange::new(0, 10, 2);
560 assert_eq!(r.indices(), vec![0, 2, 4, 6, 8]);
561 }
562
563 #[test]
564 fn slice_range_indices_empty_step_zero() {
565 let r = SliceRange {
566 start: 0,
567 stop: 10,
568 step: 0,
569 };
570 assert!(r.indices().is_empty());
571 }
572
573 #[test]
574 fn slice_range_indices_start_ge_stop() {
575 let r = SliceRange::new(5, 3, 1);
576 assert!(r.indices().is_empty());
577 }
578
579 #[test]
582 fn view_descriptor_flat_index_2d_correct() {
583 let desc = ViewDescriptor {
585 base_offset: 0,
586 shape: vec![3, 4],
587 strides: vec![4, 1],
588 };
589 assert_eq!(desc.flat_index(&[0, 0]), Some(0));
590 assert_eq!(desc.flat_index(&[1, 2]), Some(6));
591 assert_eq!(desc.flat_index(&[2, 3]), Some(11));
592 }
593
594 #[test]
595 fn view_descriptor_flat_index_with_base_offset() {
596 let desc = ViewDescriptor {
597 base_offset: 10,
598 shape: vec![2, 2],
599 strides: vec![2, 1],
600 };
601 assert_eq!(desc.flat_index(&[0, 0]), Some(10));
602 assert_eq!(desc.flat_index(&[1, 1]), Some(13));
603 }
604
605 #[test]
606 fn view_descriptor_flat_index_none_wrong_ndim() {
607 let desc = ViewDescriptor {
608 base_offset: 0,
609 shape: vec![3, 4],
610 strides: vec![4, 1],
611 };
612 assert_eq!(desc.flat_index(&[0]), None);
613 assert_eq!(desc.flat_index(&[0, 0, 0]), None);
614 }
615
616 #[test]
617 fn view_descriptor_flat_index_none_out_of_bounds() {
618 let desc = ViewDescriptor {
619 base_offset: 0,
620 shape: vec![3, 4],
621 strides: vec![4, 1],
622 };
623 assert_eq!(desc.flat_index(&[3, 0]), None); assert_eq!(desc.flat_index(&[0, 4]), None); }
626
627 #[test]
630 fn view_descriptor_is_contiguous_row_major() {
631 let desc = ViewDescriptor {
632 base_offset: 0,
633 shape: vec![2, 3, 4],
634 strides: vec![12, 4, 1],
635 };
636 assert!(desc.is_contiguous());
637 }
638
639 #[test]
640 fn view_descriptor_is_not_contiguous_non_unit_last_stride() {
641 let desc = ViewDescriptor {
642 base_offset: 0,
643 shape: vec![2, 3],
644 strides: vec![6, 2], };
646 assert!(!desc.is_contiguous());
647 }
648
649 #[test]
650 fn view_descriptor_is_not_contiguous_broadcast_zero_stride() {
651 let desc = ViewDescriptor {
652 base_offset: 0,
653 shape: vec![3, 4],
654 strides: vec![0, 1], };
656 assert!(!desc.is_contiguous());
657 }
658
659 #[test]
662 fn tensor_slice_view_new_correct_strides_1d() {
663 let data = vec![1.0, 2.0, 3.0];
664 let view = TensorSliceView::new(data, vec![3]);
665 assert_eq!(view.descriptor.strides, vec![1]);
666 assert_eq!(view.descriptor.base_offset, 0);
667 assert_eq!(view.stats.total_views_created, 1);
668 }
669
670 #[test]
671 fn tensor_slice_view_new_correct_strides_3d() {
672 let data = vec![0.0; 24];
673 let view = TensorSliceView::new(data, vec![2, 3, 4]);
674 assert_eq!(view.descriptor.strides, vec![12, 4, 1]);
676 }
677
678 #[test]
681 fn tensor_slice_view_get_correct_element() {
682 let data: Vec<f64> = (0..6).map(|x| x as f64).collect();
684 let view = TensorSliceView::new(data, vec![2, 3]);
685 assert_eq!(view.get(&[0, 0]), Some(0.0));
686 assert_eq!(view.get(&[0, 2]), Some(2.0));
687 assert_eq!(view.get(&[1, 0]), Some(3.0));
688 assert_eq!(view.get(&[1, 2]), Some(5.0));
689 }
690
691 #[test]
692 fn tensor_slice_view_get_none_out_of_bounds() {
693 let data: Vec<f64> = (0..6).map(|x| x as f64).collect();
694 let view = TensorSliceView::new(data, vec![2, 3]);
695 assert_eq!(view.get(&[2, 0]), None);
696 assert_eq!(view.get(&[0, 3]), None);
697 }
698
699 #[test]
702 fn slice_on_dim0_updates_base_offset() {
703 let data: Vec<f64> = (0..12).map(|x| x as f64).collect();
705 let mut view = TensorSliceView::new(data, vec![3, 4]);
706 let desc = view
707 .slice(0, SliceRange::contiguous(1, 3))
708 .expect("test: should succeed");
709 assert_eq!(desc.base_offset, 4);
710 assert_eq!(desc.shape, vec![2, 4]);
711 assert_eq!(desc.strides, vec![4, 1]);
712 assert_eq!(view.stats.total_slices, 1);
713 }
714
715 #[test]
716 fn slice_with_step_multiplies_stride() {
717 let data: Vec<f64> = (0..10).map(|x| x as f64).collect();
719 let mut view = TensorSliceView::new(data, vec![10]);
720 let desc = view
721 .slice(0, SliceRange::new(0, 10, 3))
722 .expect("test: should succeed");
723 assert_eq!(desc.strides[0], 3);
724 assert_eq!(desc.shape[0], 4); }
726
727 #[test]
728 fn slice_returns_none_for_invalid_dim() {
729 let data = vec![1.0, 2.0, 3.0];
730 let mut view = TensorSliceView::new(data, vec![3]);
731 assert!(view.slice(1, SliceRange::contiguous(0, 1)).is_none());
732 }
733
734 #[test]
735 fn slice_returns_none_when_start_ge_shape_dim() {
736 let data = vec![1.0, 2.0, 3.0];
737 let mut view = TensorSliceView::new(data, vec![3]);
738 assert!(view.slice(0, SliceRange::contiguous(3, 4)).is_none());
739 }
740
741 #[test]
742 fn slice_stat_incremented() {
743 let data: Vec<f64> = (0..6).map(|x| x as f64).collect();
744 let mut view = TensorSliceView::new(data, vec![2, 3]);
745 let _ = view.slice(1, SliceRange::contiguous(0, 2));
746 let _ = view.slice(0, SliceRange::contiguous(0, 1));
747 assert_eq!(view.stats.total_slices, 2);
748 }
749
750 #[test]
753 fn broadcast_compatible_same_shape() {
754 let bs = BroadcastShape { shape: vec![3, 4] };
755 assert!(bs.compatible_with(&[3, 4]));
756 }
757
758 #[test]
759 fn broadcast_compatible_scalar_broadcasts_everywhere() {
760 let bs = BroadcastShape { shape: vec![1] };
761 assert!(bs.compatible_with(&[5, 7]));
762 }
763
764 #[test]
765 fn broadcast_compatible_leading_ones() {
766 let bs = BroadcastShape { shape: vec![1, 3] };
768 assert!(bs.compatible_with(&[4, 3]));
769 }
770
771 #[test]
772 fn broadcast_incompatible_mismatched_dims() {
773 let bs = BroadcastShape { shape: vec![2, 3] };
774 assert!(!bs.compatible_with(&[2, 4]));
775 }
776
777 #[test]
780 fn broadcast_strides_compatible_returns_strides() {
781 let data = vec![1.0, 2.0, 3.0];
783 let mut view = TensorSliceView::new(data, vec![1, 3]);
784 let strides = view
785 .broadcast_strides(&[4, 3])
786 .expect("test: should succeed");
787 assert_eq!(strides[0], 0); assert_eq!(strides[1], 1); assert_eq!(view.stats.total_broadcasts, 1);
790 }
791
792 #[test]
793 fn broadcast_strides_incompatible_returns_none() {
794 let data = vec![1.0, 2.0];
795 let mut view = TensorSliceView::new(data, vec![2]);
796 assert!(view.broadcast_strides(&[3]).is_none());
797 assert_eq!(view.stats.total_broadcasts, 0);
798 }
799
800 #[test]
801 fn broadcast_strides_stat_incremented() {
802 let data = vec![0.0; 4];
803 let mut view = TensorSliceView::new(data, vec![1, 4]);
804 let _ = view.broadcast_strides(&[3, 4]);
805 let _ = view.broadcast_strides(&[5, 4]);
806 assert_eq!(view.stats.total_broadcasts, 2);
807 }
808
809 #[test]
812 fn stats_getter_returns_reference() {
813 let data = vec![0.0; 6];
814 let mut view = TensorSliceView::new(data, vec![2, 3]);
815 let _ = view.slice(0, SliceRange::contiguous(0, 1));
816 assert_eq!(view.stats().total_slices, 1);
817 assert_eq!(view.stats().total_views_created, 1);
818 }
819
820 #[test]
823 fn combined_slice_and_get() {
824 let data: Vec<f64> = (0..12).map(|x| x as f64).collect();
826 let mut view = TensorSliceView::new(data, vec![3, 4]);
827 let desc = view
829 .slice(0, SliceRange::contiguous(1, 3))
830 .expect("test: should succeed");
831 let flat = desc.flat_index(&[0, 2]).expect("test: should succeed");
833 assert_eq!(view.data[flat], 6.0);
834 }
835
836 #[test]
837 fn row_major_strides_scalar() {
838 assert_eq!(row_major_strides(&[]), Vec::<usize>::new());
839 }
840
841 #[test]
842 fn row_major_strides_1d() {
843 assert_eq!(row_major_strides(&[5]), vec![1]);
844 }
845
846 #[test]
847 fn row_major_strides_3d() {
848 assert_eq!(row_major_strides(&[2, 3, 4]), vec![12, 4, 1]);
849 }
850}