1#![allow(clippy::elidable_lifetime_names)]
14#![allow(clippy::too_many_arguments)]
15
16use super::shape::{Shape, Strides};
17use smallvec::SmallVec;
18
19pub struct TensorView<'a> {
23 data: &'a [f64],
25 shape: Shape,
27 strides: Strides,
29 storage_offset: usize,
31}
32
33impl<'a> TensorView<'a> {
34 #[must_use]
40 pub fn new(data: &'a [f64], shape: Shape, storage_offset: usize) -> Self {
41 let strides = shape.row_major_strides();
42 let max_idx = if shape.numel() > 0 {
43 strides.max_flat_index(&shape)
44 } else {
45 0
46 };
47 let required_len = storage_offset + max_idx + usize::from(shape.numel() > 0);
48 assert!(
49 data.len() >= required_len,
50 "Buffer too small for contiguous shape: need {} elements, got {}",
51 required_len,
52 data.len()
53 );
54
55 Self {
56 data,
57 shape,
58 strides,
59 storage_offset,
60 }
61 }
62
63 #[must_use]
65 pub fn new_default(data: &'a [f64], shape: Shape) -> Self {
66 Self::new(data, shape, 0)
67 }
68
69 #[must_use]
75 pub fn with_strides(
76 data: &'a [f64],
77 shape: Shape,
78 strides: Strides,
79 storage_offset: usize,
80 ) -> Self {
81 assert_eq!(
82 shape.rank(),
83 strides.as_slice().len(),
84 "Rank mismatch between shape and strides"
85 );
86
87 let max_idx = if shape.numel() > 0 {
88 strides.max_flat_index(&shape)
89 } else {
90 0
91 };
92 let required_len = storage_offset + max_idx + usize::from(shape.numel() > 0);
93 assert!(
94 data.len() >= required_len,
95 "Buffer too small for strided layout: need space for index {} (required size {}), got buffer of size {}",
96 max_idx,
97 required_len,
98 data.len()
99 );
100
101 Self {
102 data,
103 shape,
104 strides,
105 storage_offset,
106 }
107 }
108
109 #[must_use]
111 pub fn with_strides_default(data: &'a [f64], shape: Shape, strides: Strides) -> Self {
112 Self::with_strides(data, shape, strides, 0)
113 }
114
115 #[must_use]
117 pub const fn shape(&self) -> &Shape {
118 &self.shape
119 }
120
121 #[must_use]
123 pub const fn strides(&self) -> &Strides {
124 &self.strides
125 }
126
127 #[must_use]
131 pub fn get(&self, idx: &[usize]) -> Option<f64> {
132 if idx.len() != self.shape.rank() {
133 return None;
134 }
135 for (&i, &d) in idx.iter().zip(self.shape.dims()) {
136 if i >= d {
137 return None;
138 }
139 }
140 let flat = self.strides.flat_index(idx);
141 self.data.get(self.storage_offset + flat).copied()
142 }
143
144 #[must_use]
150 pub unsafe fn get_unchecked(&self, idx: &[usize]) -> f64 {
151 let flat = self.strides.flat_index(idx);
152 unsafe { *self.data.get_unchecked(self.storage_offset + flat) }
154 }
155
156 #[must_use]
158 pub fn rank(&self) -> usize {
159 self.shape.rank()
160 }
161
162 #[must_use]
164 pub fn numel(&self) -> usize {
165 self.shape.numel()
166 }
167
168 #[must_use]
170 pub fn is_contiguous(&self) -> bool {
171 if self.shape.numel() <= 1 {
172 return true;
173 }
174 self.strides == self.shape.row_major_strides()
175 }
176
177 #[must_use]
180 pub const fn as_raw_slice(&self) -> &[f64] {
181 self.data
182 }
183
184 #[must_use]
189 pub fn as_slice(&self) -> &[f64] {
190 assert!(
191 self.is_contiguous(),
192 "View is not contiguous, cannot convert to slice."
193 );
194 &self.data[self.storage_offset..self.storage_offset + self.numel()]
195 }
196
197 #[must_use]
202 pub fn iter_elements(&self) -> ElementIter<'_> {
203 if self.is_contiguous() {
204 ElementIter::Contiguous(self.as_slice().iter())
205 } else {
206 let rank = self.shape.rank();
207 let idx = SmallVec::<[usize; 8]>::from_elem(0, rank);
208 let done = self.shape.dims().contains(&0);
209 ElementIter::Strided(StridedElementIter {
210 data: self.data,
211 strides: self.strides.clone(),
212 dims: self.shape.clone(),
213 idx,
214 done,
215 flat_offset: self.storage_offset,
216 elements_yielded: 0,
217 })
218 }
219 }
220}
221
222impl<'a, const N: usize> core::ops::Index<[usize; N]> for TensorView<'a> {
223 type Output = f64;
224
225 #[inline]
226 fn index(&self, index: [usize; N]) -> &Self::Output {
227 assert_eq!(N, self.shape.rank(), "Index rank mismatch");
228 for (&i, &d) in index.iter().zip(self.shape.dims()) {
229 assert!(i < d, "Index {i} out of bounds for dimension size {d}");
230 }
231 let flat = self.strides.flat_index(&index);
232 &self.data[self.storage_offset + flat]
233 }
234}
235
236impl<'a> core::ops::Index<&[usize]> for TensorView<'a> {
237 type Output = f64;
238
239 #[inline]
240 fn index(&self, index: &[usize]) -> &Self::Output {
241 assert_eq!(index.len(), self.shape.rank(), "Index rank mismatch");
242 for (&i, &d) in index.iter().zip(self.shape.dims()) {
243 assert!(i < d, "Index {i} out of bounds for dimension size {d}");
244 }
245 let flat = self.strides.flat_index(index);
246 &self.data[self.storage_offset + flat]
247 }
248}
249
250impl<'a> core::ops::Index<(usize,)> for TensorView<'a> {
251 type Output = f64;
252 #[inline]
253 fn index(&self, index: (usize,)) -> &Self::Output {
254 &self[[index.0]]
255 }
256}
257
258impl<'a> core::ops::Index<(usize, usize)> for TensorView<'a> {
259 type Output = f64;
260 #[inline]
261 fn index(&self, index: (usize, usize)) -> &Self::Output {
262 &self[[index.0, index.1]]
263 }
264}
265
266impl<'a> core::ops::Index<(usize, usize, usize)> for TensorView<'a> {
267 type Output = f64;
268 #[inline]
269 fn index(&self, index: (usize, usize, usize)) -> &Self::Output {
270 &self[[index.0, index.1, index.2]]
271 }
272}
273
274pub struct TensorViewMut<'a> {
279 data: &'a mut [f64],
280 shape: Shape,
281 strides: Strides,
282 storage_offset: usize,
283}
284
285impl<'a> TensorViewMut<'a> {
286 #[must_use]
291 pub fn new(data: &'a mut [f64], shape: Shape, storage_offset: usize) -> Self {
292 let strides = shape.row_major_strides();
293 let max_idx = if shape.numel() > 0 {
294 strides.max_flat_index(&shape)
295 } else {
296 0
297 };
298 let required_len = storage_offset + max_idx + usize::from(shape.numel() > 0);
299 assert!(
300 data.len() >= required_len,
301 "Buffer too small for shape and offset: need {} elements, got {}",
302 required_len,
303 data.len(),
304 );
305
306 Self {
307 data,
308 shape,
309 strides,
310 storage_offset,
311 }
312 }
313
314 #[must_use]
316 pub fn new_default(data: &'a mut [f64], shape: Shape) -> Self {
317 Self::new(data, shape, 0)
318 }
319
320 #[must_use]
322 pub const fn shape(&self) -> &Shape {
323 &self.shape
324 }
325
326 #[must_use]
328 pub const fn strides(&self) -> &Strides {
329 &self.strides
330 }
331
332 #[must_use]
334 pub fn is_contiguous(&self) -> bool {
335 if self.shape.numel() <= 1 {
336 return true;
337 }
338 self.strides == self.shape.row_major_strides()
339 }
340
341 pub fn get_mut(&mut self, idx: &[usize]) -> Option<&mut f64> {
345 if idx.len() != self.shape.rank() {
346 return None;
347 }
348 for (&i, &d) in idx.iter().zip(self.shape.dims()) {
349 if i >= d {
350 return None;
351 }
352 }
353 let flat = self.strides.flat_index(idx);
354 self.data.get_mut(self.storage_offset + flat)
355 }
356
357 #[must_use]
364 pub unsafe fn get_unchecked_mut(&mut self, idx: &[usize]) -> &mut f64 {
365 let flat = self.strides.flat_index(idx);
366 unsafe { self.data.get_unchecked_mut(self.storage_offset + flat) }
367 }
368
369 pub fn set(&mut self, idx: &[usize], val: f64) -> bool {
373 self.get_mut(idx).is_some_and(|slot| {
374 *slot = val;
375 true
376 })
377 }
378
379 #[must_use]
382 pub const fn as_raw_slice(&self) -> &[f64] {
383 self.data
384 }
385
386 pub const fn as_raw_slice_mut(&mut self) -> &mut [f64] {
389 self.data
390 }
391
392 #[must_use]
397 pub fn as_slice(&self) -> &[f64] {
398 assert!(
399 self.is_contiguous(),
400 "View is not contiguous, cannot convert to slice."
401 );
402 &self.data[self.storage_offset..self.storage_offset + self.shape.numel()]
403 }
404
405 pub fn as_slice_mut(&mut self) -> &mut [f64] {
410 assert!(
411 self.is_contiguous(),
412 "View is not contiguous, cannot convert to mutable slice."
413 );
414 let numel = self.shape.numel();
415 &mut self.data[self.storage_offset..self.storage_offset + numel]
416 }
417
418 #[must_use]
420 pub fn as_view(&self) -> TensorView<'_> {
421 TensorView {
422 data: self.data,
423 shape: self.shape.clone(),
424 strides: self.strides.clone(),
425 storage_offset: self.storage_offset,
426 }
427 }
428}
429
430impl<'a, const N: usize> core::ops::Index<[usize; N]> for TensorViewMut<'a> {
431 type Output = f64;
432
433 #[inline]
434 fn index(&self, index: [usize; N]) -> &Self::Output {
435 assert_eq!(N, self.shape.rank(), "Index rank mismatch");
436 for (&i, &d) in index.iter().zip(self.shape.dims()) {
437 assert!(i < d, "Index {i} out of bounds for dimension size {d}");
438 }
439 let flat = self.strides.flat_index(&index);
440 &self.data[self.storage_offset + flat]
441 }
442}
443
444impl<'a, const N: usize> core::ops::IndexMut<[usize; N]> for TensorViewMut<'a> {
445 #[inline]
446 fn index_mut(&mut self, index: [usize; N]) -> &mut Self::Output {
447 assert_eq!(N, self.shape.rank(), "Index rank mismatch");
448 for (&i, &d) in index.iter().zip(self.shape.dims()) {
449 assert!(i < d, "Index {i} out of bounds for dimension size {d}");
450 }
451 let flat = self.strides.flat_index(&index);
452 &mut self.data[self.storage_offset + flat]
453 }
454}
455
456impl<'a> core::ops::IndexMut<&[usize]> for TensorViewMut<'a> {
457 #[inline]
458 fn index_mut(&mut self, index: &[usize]) -> &mut Self::Output {
459 assert_eq!(index.len(), self.shape.rank(), "Index rank mismatch");
460 for (&i, &d) in index.iter().zip(self.shape.dims()) {
461 assert!(i < d, "Index {i} out of bounds for dimension size {d}");
462 }
463 let flat = self.strides.flat_index(index);
464 &mut self.data[self.storage_offset + flat]
465 }
466}
467
468impl<'a> core::ops::Index<&[usize]> for TensorViewMut<'a> {
469 type Output = f64;
470
471 #[inline]
472 fn index(&self, index: &[usize]) -> &Self::Output {
473 assert_eq!(index.len(), self.shape.rank(), "Index rank mismatch");
474 for (&i, &d) in index.iter().zip(self.shape.dims()) {
475 assert!(i < d, "Index {i} out of bounds for dimension size {d}");
476 }
477 let flat = self.strides.flat_index(index);
478 &self.data[self.storage_offset + flat]
479 }
480}
481
482impl<'a> core::ops::Index<(usize,)> for TensorViewMut<'a> {
483 type Output = f64;
484 #[inline]
485 fn index(&self, index: (usize,)) -> &Self::Output {
486 &self[[index.0]]
487 }
488}
489
490impl<'a> core::ops::Index<(usize, usize)> for TensorViewMut<'a> {
491 type Output = f64;
492 #[inline]
493 fn index(&self, index: (usize, usize)) -> &Self::Output {
494 &self[[index.0, index.1]]
495 }
496}
497
498impl<'a> core::ops::Index<(usize, usize, usize)> for TensorViewMut<'a> {
499 type Output = f64;
500 #[inline]
501 fn index(&self, index: (usize, usize, usize)) -> &Self::Output {
502 &self[[index.0, index.1, index.2]]
503 }
504}
505
506impl<'a> core::ops::IndexMut<(usize,)> for TensorViewMut<'a> {
507 #[inline]
508 fn index_mut(&mut self, index: (usize,)) -> &mut Self::Output {
509 &mut self[[index.0]]
510 }
511}
512
513impl<'a> core::ops::IndexMut<(usize, usize)> for TensorViewMut<'a> {
514 #[inline]
515 fn index_mut(&mut self, index: (usize, usize)) -> &mut Self::Output {
516 &mut self[[index.0, index.1]]
517 }
518}
519
520impl<'a> core::ops::IndexMut<(usize, usize, usize)> for TensorViewMut<'a> {
521 #[inline]
522 fn index_mut(&mut self, index: (usize, usize, usize)) -> &mut Self::Output {
523 &mut self[[index.0, index.1, index.2]]
524 }
525}
526
527pub fn broadcast_elementwise<F>(
542 a: &TensorView<'_>,
543 b: &TensorView<'_>,
544 out: &mut TensorViewMut<'_>,
545 op: F,
546) -> Result<(), String>
547where
548 F: Fn(f64, f64) -> f64 + Copy,
549{
550 let out_shape = a.shape().broadcast_output(b.shape()).ok_or_else(|| {
551 format!(
552 "Shapes {} and {} are not broadcast-compatible",
553 a.shape(),
554 b.shape(),
555 )
556 })?;
557
558 if out.shape() != &out_shape {
559 return Err(format!(
560 "Output shape {} doesn't match expected broadcast shape {}",
561 out.shape(),
562 out_shape,
563 ));
564 }
565
566 let is_contiguous_fast_path = a.shape() == b.shape()
567 && a.shape() == out.shape()
568 && a.is_contiguous()
569 && b.is_contiguous()
570 && out.is_contiguous();
571
572 let a_slice = a.as_raw_slice();
573 let b_slice = b.as_raw_slice();
574
575 if is_contiguous_fast_path {
576 let out_slice = out.as_slice_mut();
577 let len = a.numel();
578
579 let a_sub = &a_slice[a.storage_offset..a.storage_offset + len];
580 let b_sub = &b_slice[b.storage_offset..b.storage_offset + len];
581 let out_sub = &mut out_slice[..len];
582
583 for i in 0..len {
584 out_sub[i] = op(a_sub[i], b_sub[i]);
585 }
586 return Ok(());
587 }
588
589 let rank = out_shape.rank();
590 let a_offset = rank.saturating_sub(a.rank());
591 let b_offset = rank.saturating_sub(b.rank());
592
593 let a_strides = a.strides().as_slice();
594 let b_strides = b.strides().as_slice();
595
596 let mut a_strides_padded = SmallVec::<[usize; 8]>::from_elem(0, rank);
597 let mut b_strides_padded = SmallVec::<[usize; 8]>::from_elem(0, rank);
598
599 let a_dims = a.shape().dims();
601 for i in a_offset..rank {
602 if a_dims[i - a_offset] > 1 {
603 a_strides_padded[i] = a_strides[i - a_offset];
604 }
605 }
606
607 let b_dims = b.shape().dims();
608 for i in b_offset..rank {
609 if b_dims[i - b_offset] > 1 {
610 b_strides_padded[i] = b_strides[i - b_offset];
611 }
612 }
613
614 let out_strides_cloned = out.strides().clone();
615 let output_iter = OutputFlatIndexIterator::new(
616 out_shape,
617 out_strides_cloned.clone(),
618 a_strides_padded.clone(),
619 b_strides_padded.clone(),
620 );
621
622 let a_step = if rank > 0 {
623 a_strides_padded[rank - 1]
624 } else {
625 0
626 };
627 let b_step = if rank > 0 {
628 b_strides_padded[rank - 1]
629 } else {
630 0
631 };
632 let out_step = if rank > 0 {
633 out_strides_cloned.as_slice()[rank - 1]
634 } else {
635 0
636 };
637
638 let a_ptr = unsafe { a_slice.as_ptr().add(a.storage_offset) };
639 let b_ptr = unsafe { b_slice.as_ptr().add(b.storage_offset) };
640 let out_ptr = unsafe { out.as_raw_slice_mut().as_mut_ptr().add(out.storage_offset) };
641
642 debug_assert!(
644 a_ptr.cast::<()>() != out_ptr as *const (),
645 "Tensor A and Output alias!"
646 );
647 debug_assert!(
648 b_ptr.cast::<()>() != out_ptr as *const (),
649 "Tensor B and Output alias!"
650 );
651
652 unsafe {
653 broadcast_elementwise_kernel(
654 output_iter,
655 a_ptr,
656 b_ptr,
657 out_ptr,
658 a_step,
659 b_step,
660 out_step,
661 op,
662 );
663 }
664
665 Ok(())
666}
667
668#[inline(always)]
669unsafe fn process_row_contiguous<F>(
670 a_ptr: *const f64,
671 b_ptr: *const f64,
672 out_ptr: *mut f64,
673 mut current_a: usize,
674 mut current_b: usize,
675 mut current_out: usize,
676 row_len: usize,
677 op: F,
678) where
679 F: Fn(f64, f64) -> f64,
680{
681 for _ in 0..row_len {
682 let va = unsafe { *a_ptr.add(current_a) };
683 let vb = unsafe { *b_ptr.add(current_b) };
684 unsafe { *out_ptr.add(current_out) = op(va, vb) };
685 current_a += 1;
686 current_b += 1;
687 current_out += 1;
688 }
689}
690
691#[inline(always)]
692#[allow(clippy::similar_names)]
693unsafe fn process_row_broadcast_a<OpF>(
694 a_ptr: *const f64,
695 b_ptr: *const f64,
696 out_ptr: *mut f64,
697 current_a_fixed: usize,
698 mut current_b: usize,
699 mut current_out: usize,
700 b_step: usize,
701 out_step: usize,
702 row_len: usize,
703 op: OpF,
704) where
705 OpF: Fn(f64, f64) -> f64,
706{
707 let va = unsafe { *a_ptr.add(current_a_fixed) };
708 for _ in 0..row_len {
709 let vb = unsafe { *b_ptr.add(current_b) };
710 unsafe { *out_ptr.add(current_out) = op(va, vb) };
711 current_b += b_step;
712 current_out += out_step;
713 }
714}
715
716#[inline(always)]
717#[allow(clippy::similar_names)]
718unsafe fn process_row_broadcast_b<OpF>(
719 a_ptr: *const f64,
720 b_ptr: *const f64,
721 out_ptr: *mut f64,
722 mut current_a: usize,
723 current_b_fixed: usize,
724 mut current_out: usize,
725 a_step: usize,
726 out_step: usize,
727 row_len: usize,
728 op: OpF,
729) where
730 OpF: Fn(f64, f64) -> f64,
731{
732 let vb = unsafe { *b_ptr.add(current_b_fixed) };
733 for _ in 0..row_len {
734 let va = unsafe { *a_ptr.add(current_a) };
735 unsafe { *out_ptr.add(current_out) = op(va, vb) };
736 current_a += a_step;
737 current_out += out_step;
738 }
739}
740
741#[inline(always)]
742#[allow(clippy::similar_names)]
743unsafe fn process_row_broadcast_ab<OpF>(
744 a_ptr: *const f64,
745 b_ptr: *const f64,
746 out_ptr: *mut f64,
747 current_a_fixed: usize,
748 current_b_fixed: usize,
749 mut current_out: usize,
750 out_step: usize,
751 row_len: usize,
752 op: OpF,
753) where
754 OpF: Fn(f64, f64) -> f64,
755{
756 let va = unsafe { *a_ptr.add(current_a_fixed) };
757 let vb = unsafe { *b_ptr.add(current_b_fixed) };
758 let result = op(va, vb);
759 for _ in 0..row_len {
760 unsafe { *out_ptr.add(current_out) = result };
761 current_out += out_step;
762 }
763}
764
765#[inline(always)]
766unsafe fn process_row_general<F>(
767 a_ptr: *const f64,
768 b_ptr: *const f64,
769 out_ptr: *mut f64,
770 mut current_a: usize,
771 mut current_b: usize,
772 mut current_out: usize,
773 a_step: usize,
774 b_step: usize,
775 out_step: usize,
776 row_len: usize,
777 op: F,
778) where
779 F: Fn(f64, f64) -> f64,
780{
781 for _ in 0..row_len {
782 let va = unsafe { *a_ptr.add(current_a) };
783 let vb = unsafe { *b_ptr.add(current_b) };
784 unsafe { *out_ptr.add(current_out) = op(va, vb) };
785 current_a += a_step;
786 current_b += b_step;
787 current_out += out_step;
788 }
789}
790
791#[inline(always)]
792unsafe fn broadcast_elementwise_kernel<F>(
793 output_iter: OutputFlatIndexIterator,
794 a_ptr: *const f64,
795 b_ptr: *const f64,
796 out_ptr: *mut f64,
797 a_step: usize,
798 b_step: usize,
799 out_step: usize,
800 op: F,
801) where
802 F: Fn(f64, f64) -> f64 + Copy,
803{
804 for (a_flat, b_flat, out_flat, row_len) in output_iter {
805 if a_step == 1 && b_step == 1 && out_step == 1 {
806 unsafe {
807 process_row_contiguous(
808 a_ptr, b_ptr, out_ptr, a_flat, b_flat, out_flat, row_len, op,
809 );
810 }
811 } else if a_step == 0 && b_step == 0 {
812 unsafe {
813 process_row_broadcast_ab(
814 a_ptr, b_ptr, out_ptr, a_flat, b_flat, out_flat, out_step, row_len, op,
815 );
816 }
817 } else if a_step == 0 {
818 unsafe {
819 process_row_broadcast_a(
820 a_ptr, b_ptr, out_ptr, a_flat, b_flat, out_flat, b_step, out_step, row_len, op,
821 );
822 }
823 } else if b_step == 0 {
824 unsafe {
825 process_row_broadcast_b(
826 a_ptr, b_ptr, out_ptr, a_flat, b_flat, out_flat, a_step, out_step, row_len, op,
827 );
828 }
829 } else {
830 unsafe {
831 process_row_general(
832 a_ptr, b_ptr, out_ptr, a_flat, b_flat, out_flat, a_step, b_step, out_step,
833 row_len, op,
834 );
835 }
836 }
837 }
838}
839
840#[allow(clippy::large_enum_variant)]
842pub enum ElementIter<'a> {
843 Contiguous(std::slice::Iter<'a, f64>),
845 Strided(StridedElementIter<'a>),
847}
848
849impl<'a> Iterator for ElementIter<'a> {
850 type Item = f64;
851
852 #[inline]
853 fn next(&mut self) -> Option<Self::Item> {
854 match self {
855 Self::Contiguous(it) => it.next().copied(),
856 Self::Strided(it) => it.next(),
857 }
858 }
859
860 #[inline]
861 fn size_hint(&self) -> (usize, Option<usize>) {
862 match self {
863 Self::Contiguous(it) => it.size_hint(),
864 Self::Strided(it) => it.size_hint(),
865 }
866 }
867}
868
869pub struct StridedElementIter<'a> {
875 data: &'a [f64],
876 strides: Strides,
877 dims: Shape,
878 idx: SmallVec<[usize; 8]>,
879 done: bool,
880 flat_offset: usize,
881 elements_yielded: usize,
882}
883
884impl<'a> Iterator for StridedElementIter<'a> {
885 type Item = f64;
886
887 #[inline]
891 fn next(&mut self) -> Option<Self::Item> {
892 if self.done {
893 return None;
894 }
895
896 let val = unsafe { *self.data.get_unchecked(self.flat_offset) };
897 self.elements_yielded += 1;
898
899 let rank = self.dims.rank();
900 let dims = self.dims.dims();
901 let strides_slice = self.strides.as_slice();
902
903 let mut carry = true;
904 for i in (0..rank).rev() {
905 self.idx[i] += 1;
906 self.flat_offset += strides_slice[i];
907
908 if self.idx[i] >= dims[i] {
909 let steps_taken = self.idx[i];
911 self.flat_offset -= strides_slice[i] * steps_taken;
912 self.idx[i] = 0;
913 } else {
914 carry = false;
915 break;
916 }
917 }
918
919 if carry {
920 self.done = true;
921 }
922
923 Some(val)
924 }
925
926 #[inline]
927 fn size_hint(&self) -> (usize, Option<usize>) {
928 if self.done {
929 (0, Some(0))
930 } else {
931 let total_numel = self.dims.numel();
932 let remaining = total_numel.saturating_sub(self.elements_yielded);
933 (remaining, Some(remaining))
934 }
935 }
936}
937
938pub struct OutputFlatIndexIterator {
940 dims: Shape,
941 strides: Strides,
942 idx: SmallVec<[usize; 8]>,
943 done: bool,
944 a_strides_padded: SmallVec<[usize; 8]>,
945 b_strides_padded: SmallVec<[usize; 8]>,
946 current_a: usize,
947 current_b: usize,
948 current_out: usize,
949}
950
951impl OutputFlatIndexIterator {
952 #[must_use]
954 pub fn new(
955 output_shape: Shape,
956 output_strides: Strides,
957 a_strides_padded: SmallVec<[usize; 8]>,
958 b_strides_padded: SmallVec<[usize; 8]>,
959 ) -> Self {
960 let rank = output_shape.rank();
961 let idx = SmallVec::<[usize; 8]>::from_elem(0, rank);
962 let done = output_shape.numel() == 0;
963
964 let mut current_a = 0;
965 let mut current_b = 0;
966 let mut current_out = 0;
967
968 if rank > 0 && !done {
969 let out_strides_slice = output_strides.as_slice();
970 for i in 0..rank {
971 current_a += idx[i] * a_strides_padded[i];
972 current_b += idx[i] * b_strides_padded[i];
973 current_out += idx[i] * out_strides_slice[i];
974 }
975 }
976
977 Self {
978 dims: output_shape,
979 strides: output_strides,
980 idx,
981 done,
982 a_strides_padded,
983 b_strides_padded,
984 current_a,
985 current_b,
986 current_out,
987 }
988 }
989}
990
991impl Iterator for OutputFlatIndexIterator {
992 type Item = (usize, usize, usize, usize);
993
994 #[inline]
995 fn next(&mut self) -> Option<Self::Item> {
996 if self.done {
997 return None;
998 }
999
1000 let rank = self.dims.rank();
1001 let dims = self.dims.dims();
1002 let out_strides_slice = self.strides.as_slice();
1003
1004 if rank == 0 {
1005 self.done = true;
1006 return Some((0, 0, 0, 1));
1007 }
1008
1009 let innermost_dim_idx = rank - 1;
1010 let row_len = dims[innermost_dim_idx];
1011
1012 let res = (self.current_a, self.current_b, self.current_out, row_len);
1013
1014 let mut carry = true;
1015 let mut i = rank;
1016
1017 while carry && i > 0 {
1018 i -= 1;
1019
1020 if i == innermost_dim_idx {
1021 self.idx[i] += row_len;
1022 self.current_a += self.a_strides_padded[i] * row_len;
1023 self.current_b += self.b_strides_padded[i] * row_len;
1024 self.current_out += out_strides_slice[i] * row_len;
1025 } else {
1026 self.idx[i] += 1;
1027 self.current_a += self.a_strides_padded[i];
1028 self.current_b += self.b_strides_padded[i];
1029 self.current_out += out_strides_slice[i];
1030 }
1031
1032 if self.idx[i] >= dims[i] {
1033 let steps_taken = self.idx[i];
1034 self.current_a -= self.a_strides_padded[i] * steps_taken;
1036 self.current_b -= self.b_strides_padded[i] * steps_taken;
1037 self.current_out -= out_strides_slice[i] * steps_taken;
1038 self.idx[i] = 0;
1039 } else {
1040 carry = false;
1041 }
1042 }
1043
1044 if carry {
1045 self.done = true;
1046 }
1047
1048 Some(res)
1049 }
1050}
1051
1052#[cfg(test)]
1053mod tests {
1054 use super::*;
1055
1056 #[test]
1057 fn test_tensor_view_indexing() {
1058 let data = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0];
1059 let view = TensorView::new_default(&data, Shape::matrix(2, 3));
1060 assert_eq!(view.get(&[0, 0]), Some(0.0));
1061 assert_eq!(view.get(&[0, 2]), Some(2.0));
1062 assert_eq!(view.get(&[1, 1]), Some(4.0));
1063 assert_eq!(view.get(&[2, 0]), None); assert_eq!(view[[0, 0]], 0.0);
1066 assert_eq!(view[[0, 2]], 2.0);
1067 assert_eq!(view[[1, 1]], 4.0);
1068 }
1069
1070 #[test]
1071 fn test_tensor_view_mut_indexing() {
1072 let mut data = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0];
1073 let mut view_mut = TensorViewMut::new_default(&mut data, Shape::matrix(2, 3));
1074
1075 assert_eq!(view_mut[[1, 1]], 4.0);
1076 view_mut[[1, 1]] = 42.0;
1077 assert_eq!(view_mut[[1, 1]], 42.0);
1078
1079 assert!(view_mut.set(&[0, 1], 99.0));
1080 assert_eq!(view_mut[[0, 1]], 99.0);
1081 }
1082}