usearch 2.26.0 → 2.26.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/README.md +2 -2
  2. package/cpp/test.cpp +66 -3
  3. package/include/usearch/index.hpp +9 -1
  4. package/include/usearch/index_dense.hpp +16 -5
  5. package/numkong/CITATION.cff +13 -9
  6. package/numkong/CONTRIBUTING.md +29 -24
  7. package/numkong/Cargo.toml +1 -1
  8. package/numkong/README.md +21 -7
  9. package/numkong/binding.gyp +47 -31
  10. package/numkong/build.rs +15 -5
  11. package/numkong/c/dispatch.h +0 -1
  12. package/numkong/c/parallel.c +139 -0
  13. package/numkong/c/parallel.h +47 -0
  14. package/numkong/cmake/toolchain-wasi.cmake +39 -5
  15. package/numkong/include/README.md +24 -17
  16. package/numkong/include/numkong/attention/sapphireamx.h +6 -6
  17. package/numkong/include/numkong/attention/sme.h +2 -2
  18. package/numkong/include/numkong/capabilities.h +2 -2
  19. package/numkong/include/numkong/cast/README.md +9 -7
  20. package/numkong/include/numkong/cast/loongsonasx.h +48 -40
  21. package/numkong/include/numkong/cast/serial.h +89 -11
  22. package/numkong/include/numkong/cast.h +3 -3
  23. package/numkong/include/numkong/curved/README.md +10 -10
  24. package/numkong/include/numkong/curved/neon.h +1 -0
  25. package/numkong/include/numkong/curved/neonbfdot.h +1 -0
  26. package/numkong/include/numkong/curved/smef64.h +1 -0
  27. package/numkong/include/numkong/dot/README.md +18 -18
  28. package/numkong/include/numkong/dot/alder.h +8 -8
  29. package/numkong/include/numkong/dot/haswell.h +8 -8
  30. package/numkong/include/numkong/dot/icelake.h +12 -12
  31. package/numkong/include/numkong/dot/loongsonasx.h +6 -6
  32. package/numkong/include/numkong/dot/neon.h +2 -1
  33. package/numkong/include/numkong/dot/neonbfdot.h +1 -0
  34. package/numkong/include/numkong/dot/neonfhm.h +1 -0
  35. package/numkong/include/numkong/dot/neonsdot.h +1 -0
  36. package/numkong/include/numkong/dot/powervsx.h +3 -3
  37. package/numkong/include/numkong/dot/serial.h +19 -6
  38. package/numkong/include/numkong/dot/skylake.h +4 -4
  39. package/numkong/include/numkong/dot/svehalf.h +1 -1
  40. package/numkong/include/numkong/dot/v128relaxed.h +24 -23
  41. package/numkong/include/numkong/dots/README.md +26 -26
  42. package/numkong/include/numkong/dots/genoa.h +3 -1
  43. package/numkong/include/numkong/dots/icelake.h +1 -0
  44. package/numkong/include/numkong/dots/powervsx.h +1 -1
  45. package/numkong/include/numkong/dots/serial.h +32 -33
  46. package/numkong/include/numkong/dots/sierra.h +4 -3
  47. package/numkong/include/numkong/each/README.md +5 -3
  48. package/numkong/include/numkong/each/sapphire.h +2 -0
  49. package/numkong/include/numkong/each/skylake.h +1 -0
  50. package/numkong/include/numkong/geospatial/README.md +6 -6
  51. package/numkong/include/numkong/geospatial/v128relaxed.h +1 -0
  52. package/numkong/include/numkong/maxsim/README.md +5 -5
  53. package/numkong/include/numkong/maxsim/genoa.h +4 -0
  54. package/numkong/include/numkong/maxsim/icelake.h +1 -0
  55. package/numkong/include/numkong/maxsim/serial.h +22 -9
  56. package/numkong/include/numkong/maxsim/sme.h +3 -2
  57. package/numkong/include/numkong/mesh/README.md +7 -4
  58. package/numkong/include/numkong/mesh/genoa.h +2 -2
  59. package/numkong/include/numkong/mesh/haswell.h +8 -8
  60. package/numkong/include/numkong/mesh/neon.h +6 -6
  61. package/numkong/include/numkong/mesh/neonbfdot.h +3 -2
  62. package/numkong/include/numkong/mesh/neonfhm.h +3 -2
  63. package/numkong/include/numkong/mesh/rvv.h +4 -4
  64. package/numkong/include/numkong/mesh/serial.h +17 -17
  65. package/numkong/include/numkong/mesh/skylake.h +4 -4
  66. package/numkong/include/numkong/mesh/v128relaxed.h +4 -4
  67. package/numkong/include/numkong/mesh.hpp +2 -2
  68. package/numkong/include/numkong/numkong.h +1 -1
  69. package/numkong/include/numkong/numkong.hpp +1 -1
  70. package/numkong/include/numkong/probability/README.md +17 -15
  71. package/numkong/include/numkong/probability/skylake.h +1 -1
  72. package/numkong/include/numkong/reduce/README.md +27 -21
  73. package/numkong/include/numkong/reduce/genoa.h +2 -1
  74. package/numkong/include/numkong/reduce/icelake.h +1 -0
  75. package/numkong/include/numkong/reduce/neon.h +1 -1
  76. package/numkong/include/numkong/reduce/serial.h +9 -9
  77. package/numkong/include/numkong/reduce/skylake.h +4 -4
  78. package/numkong/include/numkong/reduce.hpp +38 -0
  79. package/numkong/include/numkong/scalar/README.md +29 -28
  80. package/numkong/include/numkong/set/README.md +17 -14
  81. package/numkong/include/numkong/set/haswell.h +15 -16
  82. package/numkong/include/numkong/set/icelake.h +15 -16
  83. package/numkong/include/numkong/set/loongsonasx.h +12 -10
  84. package/numkong/include/numkong/set/neon.h +15 -19
  85. package/numkong/include/numkong/set/powervsx.h +15 -21
  86. package/numkong/include/numkong/set/serial.h +19 -20
  87. package/numkong/include/numkong/set/v128relaxed.h +10 -8
  88. package/numkong/include/numkong/sets/icelake.h +1 -0
  89. package/numkong/include/numkong/sparse/README.md +4 -4
  90. package/numkong/include/numkong/sparse/icelake.h +1 -0
  91. package/numkong/include/numkong/sparse/neon.h +1 -0
  92. package/numkong/include/numkong/sparse/serial.h +10 -3
  93. package/numkong/include/numkong/sparse/turin.h +1 -0
  94. package/numkong/include/numkong/spatial/README.md +25 -24
  95. package/numkong/include/numkong/spatial/alder.h +2 -1
  96. package/numkong/include/numkong/spatial/haswell.h +48 -40
  97. package/numkong/include/numkong/spatial/icelake.h +2 -1
  98. package/numkong/include/numkong/spatial/neon.h +54 -46
  99. package/numkong/include/numkong/spatial/neonbfdot.h +1 -1
  100. package/numkong/include/numkong/spatial/powervsx.h +54 -46
  101. package/numkong/include/numkong/spatial/serial.h +56 -46
  102. package/numkong/include/numkong/spatial/sierra.h +4 -3
  103. package/numkong/include/numkong/spatial/skylake.h +27 -22
  104. package/numkong/include/numkong/spatial/sve.h +6 -6
  105. package/numkong/include/numkong/spatial/svebfdot.h +1 -1
  106. package/numkong/include/numkong/spatial/svehalf.h +1 -1
  107. package/numkong/include/numkong/spatial/v128relaxed.h +36 -30
  108. package/numkong/include/numkong/spatials/README.md +15 -15
  109. package/numkong/include/numkong/spatials/genoa.h +3 -0
  110. package/numkong/include/numkong/spatials/graniteamx.h +2 -0
  111. package/numkong/include/numkong/spatials/icelake.h +1 -0
  112. package/numkong/include/numkong/spatials/sierra.h +1 -0
  113. package/numkong/include/numkong/tensor.hpp +236 -123
  114. package/numkong/include/numkong/trigonometry/README.md +4 -4
  115. package/numkong/include/numkong/trigonometry/serial.h +6 -6
  116. package/numkong/include/numkong/trigonometry/v128relaxed.h +1 -0
  117. package/numkong/include/numkong/types.hpp +46 -16
  118. package/numkong/include/numkong/vector.hpp +116 -23
  119. package/numkong/javascript/@numkong-darwin-arm64/package.json +1 -1
  120. package/numkong/javascript/@numkong-darwin-x64/package.json +1 -1
  121. package/numkong/javascript/@numkong-linux-arm64/package.json +1 -1
  122. package/numkong/javascript/@numkong-linux-x64/package.json +1 -1
  123. package/numkong/javascript/@numkong-win32-arm64/package.json +1 -1
  124. package/numkong/javascript/@numkong-win32-x64/package.json +1 -1
  125. package/numkong/javascript/README.md +18 -16
  126. package/numkong/javascript/numkong.c +70 -40
  127. package/numkong/package.json +8 -8
  128. package/numkong/rust/README.md +19 -9
  129. package/numkong/rust/cast.rs +52 -1
  130. package/numkong/rust/each.rs +480 -7
  131. package/numkong/rust/matrix.rs +877 -216
  132. package/numkong/rust/numkong.rs +12 -8
  133. package/numkong/rust/reduce.rs +433 -5
  134. package/numkong/rust/tensor.rs +450 -842
  135. package/numkong/rust/vector.rs +520 -101
  136. package/numkong/swift/README.md +5 -3
  137. package/numkong/test/README.md +4 -4
  138. package/numkong/test/test-wasi.mjs +17 -0
  139. package/numkong/test/test_each.cpp +30 -1
  140. package/numkong/test/test_each.py +74 -1
  141. package/numkong/test/test_reduce.cpp +51 -0
  142. package/numkong/test/test_tensor.cpp +2 -63
  143. package/numkong/test/test_tensor.py +93 -0
  144. package/package.json +1 -1
  145. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  146. package/prebuilds/linux-arm64/usearch.node +0 -0
  147. package/prebuilds/linux-x64/usearch.node +0 -0
  148. package/prebuilds/win32-x64/usearch.node +0 -0
@@ -1,4 +1,4 @@
1
- //! Core N-dimensional tensor types with elementwise, trigonometric, reduction, and cast operations.
1
+ //! Core N-dimensional tensor types and the `TensorRef` / `TensorMut` structural traits.
2
2
  //!
3
3
  //! This module provides:
4
4
  //!
@@ -6,6 +6,15 @@
6
6
  //! - [`TensorView`]: Immutable view into a tensor
7
7
  //! - [`TensorSpan`]: Mutable view into a tensor
8
8
  //! - [`Matrix`]: Type alias for 2D tensors
9
+ //! - [`TensorRef`] / [`TensorMut`]: Structural traits implemented by all three above
10
+ //! - [`Fill`] / [`CopyFrom`]: Container-level traits for in-place fill and slice/view-to-span copies
11
+ //!
12
+ //! Operation extension traits live in their respective domain modules so each `<op>.rs`
13
+ //! mirrors the C++ `<op>.hpp` layout:
14
+ //! - [`crate::each`]: `ScaleOps` / `SumOps` / `BlendOps` / `FmaOps` / `TrigSinOps` /
15
+ //! `TrigCosOps` / `TrigAtanOps` / `AllCloseOps`
16
+ //! - [`crate::reduce`]: `MomentsOps` / `MinMaxOps` / `BitwiseReductions`
17
+ //! - [`crate::cast`]: `CastOps`
9
18
  //!
10
19
  //! Batch matrix operations (GEMM, packed spatial distances) live in [`crate::matrix`].
11
20
  //!
@@ -56,9 +65,9 @@ use core::ptr::NonNull;
56
65
 
57
66
  use crate::cast::{cast, CastDtype};
58
67
  use crate::each::{EachATan, EachBlend, EachCos, EachFMA, EachScale, EachSin, EachSum};
59
- use crate::reduce::{ReduceMinMax, ReduceMoments};
68
+ use crate::reduce::{MomentsOps, ReduceMinMax, ReduceMoments, SumSqToF64};
60
69
  use crate::spatial::{Dot, Roots};
61
- use crate::types::{DimMut, DimRef, FloatConvertible, NumberLike, StorageElement};
70
+ use crate::types::{DimMut, DimRef, FloatConvertible, StorageElement};
62
71
  use crate::vector::{Vector, VectorIndex};
63
72
 
64
73
  // region: Constants and Allocator
@@ -248,11 +257,13 @@ pub struct MinMaxResult<Value, AnyIndex = usize> {
248
257
  /// let b_packed = PackedMatrix::try_pack(&b).unwrap();
249
258
  /// let c = a.dots_packed(&b_packed); // Returns (1024 × 256)
250
259
  /// ```
251
- pub struct Tensor<Scalar, Alloc: Allocator = Global, const MAX_RANK: usize = DEFAULT_MAX_RANK> {
260
+ pub struct Tensor<
261
+ Scalar: StorageElement,
262
+ Alloc: Allocator = Global,
263
+ const MAX_RANK: usize = DEFAULT_MAX_RANK,
264
+ > {
252
265
  /// Raw pointer to data buffer.
253
266
  data: NonNull<Scalar>,
254
- /// Total allocation size in bytes (used by Drop and as_slice).
255
- alloc_bytes: usize,
256
267
  /// Shape dimensions (always logical).
257
268
  shape: [usize; MAX_RANK],
258
269
  /// Strides in bytes.
@@ -264,28 +275,37 @@ pub struct Tensor<Scalar, Alloc: Allocator = Global, const MAX_RANK: usize = DEF
264
275
  }
265
276
 
266
277
  // Safety: Tensor owns its data and Scalar: Send implies the array is Send
267
- unsafe impl<Scalar: Send, Alloc: Allocator + Send, const MAX_RANK: usize> Send
278
+ unsafe impl<Scalar: StorageElement + Send, Alloc: Allocator + Send, const MAX_RANK: usize> Send
268
279
  for Tensor<Scalar, Alloc, MAX_RANK>
269
280
  {
270
281
  }
271
282
  // Safety: Tensor has no interior mutability, &Tensor<Scalar> is safe to share if Scalar: Sync
272
- unsafe impl<Scalar: Sync, Alloc: Allocator + Sync, const MAX_RANK: usize> Sync
283
+ unsafe impl<Scalar: StorageElement + Sync, Alloc: Allocator + Sync, const MAX_RANK: usize> Sync
273
284
  for Tensor<Scalar, Alloc, MAX_RANK>
274
285
  {
275
286
  }
276
287
 
277
- impl<Scalar, Alloc: Allocator, const MAX_RANK: usize> Drop for Tensor<Scalar, Alloc, MAX_RANK> {
288
+ impl<Scalar: StorageElement, Alloc: Allocator, const MAX_RANK: usize> Drop
289
+ for Tensor<Scalar, Alloc, MAX_RANK>
290
+ {
278
291
  fn drop(&mut self) {
279
- if self.alloc_bytes > 0 {
292
+ let storage_count = if self.ndim == 0 {
293
+ 0
294
+ } else {
295
+ let total: usize = self.shape[..self.ndim].iter().product();
296
+ total / Scalar::dimensions_per_value()
297
+ };
298
+ if storage_count > 0 {
280
299
  unsafe {
281
- let count = self.alloc_bytes / core::mem::size_of::<Scalar>();
282
300
  core::ptr::drop_in_place(core::ptr::slice_from_raw_parts_mut(
283
301
  self.data.as_ptr(),
284
- count,
302
+ storage_count,
285
303
  ));
286
- let layout =
287
- alloc::alloc::Layout::from_size_align(self.alloc_bytes, SIMD_ALIGNMENT)
288
- .unwrap();
304
+ let layout = alloc::alloc::Layout::from_size_align(
305
+ storage_count * core::mem::size_of::<Scalar>(),
306
+ SIMD_ALIGNMENT,
307
+ )
308
+ .unwrap();
289
309
  self.alloc.deallocate(
290
310
  NonNull::new_unchecked(self.data.as_ptr() as *mut u8),
291
311
  layout,
@@ -385,7 +405,6 @@ impl<Scalar: StorageElement, Alloc: Allocator, const MAX_RANK: usize>
385
405
 
386
406
  Ok(Self {
387
407
  data,
388
- alloc_bytes: storage_count * core::mem::size_of::<Scalar>(),
389
408
  shape: shape_arr,
390
409
  strides: strides_arr,
391
410
  ndim: shape.len(),
@@ -470,7 +489,6 @@ impl<Scalar: StorageElement, Alloc: Allocator, const MAX_RANK: usize>
470
489
 
471
490
  Ok(Self {
472
491
  data,
473
- alloc_bytes: storage_count * core::mem::size_of::<Scalar>(),
474
492
  shape: shape_arr,
475
493
  strides: strides_arr,
476
494
  ndim: shape.len(),
@@ -544,7 +562,6 @@ impl<Scalar: StorageElement, Alloc: Allocator, const MAX_RANK: usize>
544
562
 
545
563
  Ok(Self {
546
564
  data: ptr,
547
- alloc_bytes: expected_storage * core::mem::size_of::<Scalar>(),
548
565
  shape: shape_arr,
549
566
  strides: strides_arr,
550
567
  ndim: shape.len(),
@@ -667,27 +684,29 @@ impl<Scalar: StorageElement, Alloc: Allocator, const MAX_RANK: usize>
667
684
  return Err(TensorError::NonContiguousRows);
668
685
  }
669
686
  let dims = self.shape[0];
670
- let values = dims.div_ceil(Scalar::dimensions_per_value());
671
687
  let data = self.data;
672
688
  let alloc = unsafe { core::ptr::read(&self.alloc) };
673
689
  core::mem::forget(self);
674
- // SAFETY: data and alloc_bytes originate from a valid Tensor allocation
675
- Ok(unsafe { Vector::from_raw_parts(data, dims, values, alloc) })
690
+ // SAFETY: storage size derived from `dims` matches the original allocation.
691
+ Ok(unsafe { Vector::from_raw_parts(data, dims, alloc) })
676
692
  }
677
693
  }
678
694
 
679
695
  // Methods that don't require Clone
680
- impl<Scalar, Alloc: Allocator, const MAX_RANK: usize> Tensor<Scalar, Alloc, MAX_RANK> {
696
+ impl<Scalar: StorageElement, Alloc: Allocator, const MAX_RANK: usize>
697
+ Tensor<Scalar, Alloc, MAX_RANK>
698
+ {
681
699
  /// Construct a tensor from raw parts, taking ownership of the allocation.
682
700
  ///
683
701
  /// # Safety
684
- /// - `data` must point to a valid allocation of `alloc_bytes` bytes
685
- /// obtained from `alloc`, aligned to [`SIMD_ALIGNMENT`].
702
+ /// - `data` must point to an allocation obtained from `alloc`, aligned to
703
+ /// [`SIMD_ALIGNMENT`], whose backing buffer is sized for the storage count
704
+ /// implied by `shape` (`product(shape) / Scalar::dimensions_per_value()`
705
+ /// slots of `Scalar`).
686
706
  /// - `shape`, `strides`, and `ndim` must be consistent with the data layout.
687
707
  /// - The caller must not free the memory (this tensor takes ownership).
688
708
  pub unsafe fn from_raw_parts(
689
709
  data: NonNull<Scalar>,
690
- alloc_bytes: usize,
691
710
  shape: [usize; MAX_RANK],
692
711
  strides: [isize; MAX_RANK],
693
712
  ndim: usize,
@@ -695,7 +714,6 @@ impl<Scalar, Alloc: Allocator, const MAX_RANK: usize> Tensor<Scalar, Alloc, MAX_
695
714
  ) -> Self {
696
715
  Self {
697
716
  data,
698
- alloc_bytes,
699
717
  shape,
700
718
  strides,
701
719
  ndim,
@@ -728,7 +746,7 @@ impl<Scalar, Alloc: Allocator, const MAX_RANK: usize> Tensor<Scalar, Alloc, MAX_
728
746
 
729
747
  /// Returns true if the array has no elements.
730
748
  pub fn is_empty(&self) -> bool {
731
- self.alloc_bytes == 0
749
+ self.numel() == 0
732
750
  }
733
751
 
734
752
  /// Returns the stride in bytes for the given dimension.
@@ -751,7 +769,7 @@ impl<Scalar, Alloc: Allocator, const MAX_RANK: usize> Tensor<Scalar, Alloc, MAX_
751
769
  /// For sub-byte types, the slice contains packed storage values, not
752
770
  /// individual logical elements.
753
771
  pub fn as_slice(&self) -> &[Scalar] {
754
- let count = self.alloc_bytes / core::mem::size_of::<Scalar>();
772
+ let count = self.numel() / Scalar::dimensions_per_value();
755
773
  unsafe { core::slice::from_raw_parts(self.data.as_ptr(), count) }
756
774
  }
757
775
 
@@ -760,7 +778,7 @@ impl<Scalar, Alloc: Allocator, const MAX_RANK: usize> Tensor<Scalar, Alloc, MAX_
760
778
  /// For sub-byte types, the slice contains packed storage values, not
761
779
  /// individual logical elements.
762
780
  pub fn as_mut_slice(&mut self) -> &mut [Scalar] {
763
- let count = self.alloc_bytes / core::mem::size_of::<Scalar>();
781
+ let count = self.numel() / Scalar::dimensions_per_value();
764
782
  unsafe { core::slice::from_raw_parts_mut(self.data.as_ptr(), count) }
765
783
  }
766
784
 
@@ -2570,6 +2588,37 @@ pub trait TensorMut<Scalar: StorageElement, const MAX_RANK: usize>:
2570
2588
  fn as_mut_ptr(&mut self) -> *mut Scalar;
2571
2589
  }
2572
2590
 
2591
+ /// In-place fill operations shared by every mutable container of `Scalar`.
2592
+ ///
2593
+ /// Implemented for [`Tensor`], [`TensorSpan`], [`crate::vector::Vector`], and
2594
+ /// [`crate::vector::VectorSpan`]. Mirrors `tensor_span::fill_zeros / fill` and
2595
+ /// `vector_span::fill_zeros / fill` from the C++ side. Both methods are
2596
+ /// infallible and operate on every storage byte the container owns or borrows.
2597
+ pub trait Fill<Scalar: StorageElement> {
2598
+ /// Set every storage byte to zero. Works for every `StorageElement` since
2599
+ /// the binary-zero of all NumKong scalar dtypes is the value-zero.
2600
+ fn fill_zeros(&mut self);
2601
+
2602
+ /// Set every storage element to `value`. For 1-byte storage (including
2603
+ /// sub-byte packings) this becomes a single byte-pattern memset; for
2604
+ /// multi-byte storage it falls through to a typed broadcast loop on the
2605
+ /// pre-zeroed buffer (so float NaN/inf patterns from uninitialised memory
2606
+ /// cannot leak through).
2607
+ fn fill(&mut self, value: Scalar);
2608
+ }
2609
+
2610
+ /// Copy storage from a `Source` into this container in place.
2611
+ ///
2612
+ /// `Source` is the natural read-side counterpart for the implementor:
2613
+ /// `&[Scalar]` (raw storage slice) for owning `Tensor` / `Vector`, or the
2614
+ /// matching `View` type for span implementors. Mirrors `tensor_span::copy_from`
2615
+ /// and `vector_span::copy_from` on the C++ side.
2616
+ pub trait CopyFrom<Source> {
2617
+ /// Copy from `source` into self. Returns an error on a shape or storage
2618
+ /// mismatch and does not modify the destination on error.
2619
+ fn copy_from(&mut self, source: Source) -> Result<(), TensorError>;
2620
+ }
2621
+
2573
2622
  impl<Scalar: StorageElement, Alloc: Allocator, const R: usize> TensorRef<Scalar, R>
2574
2623
  for Tensor<Scalar, Alloc, R>
2575
2624
  {
@@ -2660,6 +2709,191 @@ impl<'a, Scalar: StorageElement, const R: usize> TensorMut<Scalar, R>
2660
2709
 
2661
2710
  // endregion: TensorRef / TensorMut Traits
2662
2711
 
2712
+ // region: Fill / CopyFrom Implementations
2713
+
2714
+ /// Compares the bit pattern of a fresh `Scalar::default()` to `value` to decide
2715
+ /// whether the post-`fill_zeros` overlay can be skipped. Treats `+0.0` and the
2716
+ /// default `Scalar::default()` representation as identical so a `fill(0.0)` on a
2717
+ /// freshly-defaulted buffer is a single memset.
2718
+ #[inline]
2719
+ fn value_is_default_bit_pattern<Scalar: StorageElement>(value: Scalar) -> bool {
2720
+ let default_value = Scalar::default();
2721
+ let value_bytes = unsafe {
2722
+ core::slice::from_raw_parts(
2723
+ (&value as *const Scalar) as *const u8,
2724
+ core::mem::size_of::<Scalar>(),
2725
+ )
2726
+ };
2727
+ let default_bytes = unsafe {
2728
+ core::slice::from_raw_parts(
2729
+ (&default_value as *const Scalar) as *const u8,
2730
+ core::mem::size_of::<Scalar>(),
2731
+ )
2732
+ };
2733
+ value_bytes == default_bytes
2734
+ }
2735
+
2736
+ /// Body shared by every `Fill::fill` impl: zero the buffer, then byte-pattern
2737
+ /// memset (1-byte storage) or typed broadcast (multi-byte storage).
2738
+ #[inline]
2739
+ unsafe fn overlay_value_into_storage<Scalar: StorageElement>(
2740
+ storage_ptr: *mut Scalar,
2741
+ storage_count: usize,
2742
+ value: Scalar,
2743
+ ) {
2744
+ if storage_count == 0 {
2745
+ return;
2746
+ }
2747
+ if value_is_default_bit_pattern(value) {
2748
+ return;
2749
+ }
2750
+ if core::mem::size_of::<Scalar>() == 1 {
2751
+ let value_byte = *((&value as *const Scalar) as *const u8);
2752
+ core::ptr::write_bytes(storage_ptr as *mut u8, value_byte, storage_count);
2753
+ } else {
2754
+ for storage_index in 0..storage_count {
2755
+ core::ptr::write(storage_ptr.add(storage_index), value);
2756
+ }
2757
+ }
2758
+ }
2759
+
2760
+ impl<Scalar: StorageElement, Alloc: Allocator, const MAX_RANK: usize> Fill<Scalar>
2761
+ for Tensor<Scalar, Alloc, MAX_RANK>
2762
+ {
2763
+ fn fill_zeros(&mut self) {
2764
+ let storage_count = self.numel() / Scalar::dimensions_per_value();
2765
+ if storage_count == 0 {
2766
+ return;
2767
+ }
2768
+ unsafe {
2769
+ core::ptr::write_bytes(self.data.as_ptr(), 0, storage_count);
2770
+ }
2771
+ }
2772
+
2773
+ fn fill(&mut self, value: Scalar) {
2774
+ self.fill_zeros();
2775
+ let storage_count = self.numel() / Scalar::dimensions_per_value();
2776
+ unsafe {
2777
+ overlay_value_into_storage(self.data.as_ptr(), storage_count, value);
2778
+ }
2779
+ }
2780
+ }
2781
+
2782
+ impl<Scalar: StorageElement, Alloc: Allocator, const MAX_RANK: usize> CopyFrom<&[Scalar]>
2783
+ for Tensor<Scalar, Alloc, MAX_RANK>
2784
+ {
2785
+ fn copy_from(&mut self, source: &[Scalar]) -> Result<(), TensorError> {
2786
+ let storage_count = self.storage_len();
2787
+ if source.len() != storage_count {
2788
+ return Err(TensorError::ShapeMismatch {
2789
+ axis: 0,
2790
+ expected: storage_count,
2791
+ got: source.len(),
2792
+ });
2793
+ }
2794
+ if storage_count == 0 {
2795
+ return Ok(());
2796
+ }
2797
+ unsafe {
2798
+ core::ptr::copy_nonoverlapping(source.as_ptr(), self.data.as_ptr(), storage_count);
2799
+ }
2800
+ Ok(())
2801
+ }
2802
+ }
2803
+
2804
+ impl<'a, Scalar: StorageElement, const MAX_RANK: usize> Fill<Scalar>
2805
+ for TensorSpan<'a, Scalar, MAX_RANK>
2806
+ {
2807
+ fn fill_zeros(&mut self) {
2808
+ // Contiguous fast path: one write_bytes for the whole span.
2809
+ if self.is_contiguous() {
2810
+ let storage_count = self.numel().div_ceil(Scalar::dimensions_per_value());
2811
+ if storage_count == 0 {
2812
+ return;
2813
+ }
2814
+ unsafe {
2815
+ core::ptr::write_bytes(self.data, 0, storage_count);
2816
+ }
2817
+ return;
2818
+ }
2819
+ // Strided path: walk the leading axis and recurse.
2820
+ if self.ndim == 0 {
2821
+ return;
2822
+ }
2823
+ if let Ok(axis_iter) = self.axis_spans(0usize) {
2824
+ for mut sub_span in axis_iter {
2825
+ sub_span.fill_zeros();
2826
+ }
2827
+ }
2828
+ }
2829
+
2830
+ fn fill(&mut self, value: Scalar) {
2831
+ if self.is_contiguous() {
2832
+ let storage_count = self.numel().div_ceil(Scalar::dimensions_per_value());
2833
+ if storage_count == 0 {
2834
+ return;
2835
+ }
2836
+ unsafe {
2837
+ core::ptr::write_bytes(self.data, 0, storage_count);
2838
+ overlay_value_into_storage(self.data, storage_count, value);
2839
+ }
2840
+ return;
2841
+ }
2842
+ if self.ndim == 0 {
2843
+ return;
2844
+ }
2845
+ if let Ok(axis_iter) = self.axis_spans(0usize) {
2846
+ for mut sub_span in axis_iter {
2847
+ sub_span.fill(value);
2848
+ }
2849
+ }
2850
+ }
2851
+ }
2852
+
2853
+ impl<'a, 'b, Scalar: StorageElement, const MAX_RANK: usize>
2854
+ CopyFrom<&'b TensorView<'_, Scalar, MAX_RANK>> for TensorSpan<'a, Scalar, MAX_RANK>
2855
+ {
2856
+ fn copy_from(
2857
+ &mut self,
2858
+ source: &'b TensorView<'_, Scalar, MAX_RANK>,
2859
+ ) -> Result<(), TensorError> {
2860
+ if self.shape() != source.shape() {
2861
+ return Err(TensorError::ShapeMismatch {
2862
+ axis: 0,
2863
+ expected: source.numel(),
2864
+ got: self.numel(),
2865
+ });
2866
+ }
2867
+ if self.is_empty() {
2868
+ return Ok(());
2869
+ }
2870
+ // Contiguous fast path: memcpy the whole storage at once.
2871
+ if self.is_contiguous() && source.is_contiguous() {
2872
+ let storage_count = self.numel().div_ceil(Scalar::dimensions_per_value());
2873
+ unsafe {
2874
+ core::ptr::copy_nonoverlapping(source.as_ptr(), self.data, storage_count);
2875
+ }
2876
+ return Ok(());
2877
+ }
2878
+ // Strided: walk the leading axis on both source and destination, recurse.
2879
+ if self.ndim == 0 {
2880
+ return Ok(());
2881
+ }
2882
+ let source_axis = source.axis_views(0usize)?;
2883
+ let mut destination_axis = self.axis_spans(0usize)?;
2884
+ for source_sub in source_axis {
2885
+ let mut destination_sub = match destination_axis.next() {
2886
+ Some(span) => span,
2887
+ None => break,
2888
+ };
2889
+ destination_sub.copy_from(&source_sub)?;
2890
+ }
2891
+ Ok(())
2892
+ }
2893
+ }
2894
+
2895
+ // endregion: Fill / CopyFrom Implementations
2896
+
2663
2897
  // region: AxisIterator
2664
2898
 
2665
2899
  /// Iterator over sub-tensor views along a given axis.
@@ -2993,7 +3227,7 @@ impl<'a, Scalar, const MAX_RANK: usize> TensorSpan<'a, Scalar, MAX_RANK> {
2993
3227
  }
2994
3228
  }
2995
3229
 
2996
- impl<Scalar, Alloc: Allocator, const MAX_RANK: usize> Tensor<Scalar, Alloc, MAX_RANK> {
3230
+ impl<Scalar: StorageElement, Alloc: Allocator, const MAX_RANK: usize> Tensor<Scalar, Alloc, MAX_RANK> {
2997
3231
  /// Iterate along the given axis, yielding sub-tensor views with rank-1.
2998
3232
  pub fn axis_views<AnyIndex: VectorIndex>(
2999
3233
  &self,
@@ -3032,7 +3266,7 @@ impl<Scalar, Alloc: Allocator, const MAX_RANK: usize> Tensor<Scalar, Alloc, MAX_
3032
3266
 
3033
3267
  // region: Debug
3034
3268
 
3035
- impl<Scalar: core::fmt::Debug, Alloc: Allocator, const MAX_RANK: usize> core::fmt::Debug
3269
+ impl<Scalar: StorageElement + core::fmt::Debug, Alloc: Allocator, const MAX_RANK: usize> core::fmt::Debug
3036
3270
  for Tensor<Scalar, Alloc, MAX_RANK>
3037
3271
  {
3038
3272
  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
@@ -3469,7 +3703,7 @@ impl<'a, Scalar: FloatConvertible, const MAX_RANK: usize> IntoIterator
3469
3703
 
3470
3704
  // region: PartialEq
3471
3705
 
3472
- impl<Scalar: PartialEq, Alloc: Allocator, const MAX_RANK: usize> PartialEq
3706
+ impl<Scalar: StorageElement + PartialEq, Alloc: Allocator, const MAX_RANK: usize> PartialEq
3473
3707
  for Tensor<Scalar, Alloc, MAX_RANK>
3474
3708
  {
3475
3709
  fn eq(&self, other: &Self) -> bool {
@@ -3513,46 +3747,11 @@ where
3513
3747
 
3514
3748
  // endregion: PartialEq
3515
3749
 
3516
- // region: Tolerance Equality
3517
-
3518
- /// Extension trait: tolerance-based equality for any [`TensorRef`] implementor.
3519
- ///
3520
- /// Uses the formula `|a - b| <= atol + rtol * |b|` per element.
3521
- /// Returns `false` if shapes differ.
3522
- pub trait AllCloseOps<Scalar: FloatConvertible, const MAX_RANK: usize>:
3523
- TensorRef<Scalar, MAX_RANK>
3524
- where
3525
- Scalar::DimScalar: NumberLike,
3526
- {
3527
- fn allclose(
3528
- &self,
3529
- other: &(impl TensorRef<Scalar, MAX_RANK> + ?Sized),
3530
- atol: f64,
3531
- rtol: f64,
3532
- ) -> bool {
3533
- let a = self.view();
3534
- let b = other.view();
3535
- a.ndim() == b.ndim()
3536
- && a.shape() == b.shape()
3537
- && a.iter()
3538
- .dims()
3539
- .zip(b.iter().dims())
3540
- .all(|(x, y)| crate::types::is_close((*x).to_f64(), (*y).to_f64(), atol, rtol))
3541
- }
3542
- }
3543
-
3544
- impl<C, Scalar: FloatConvertible, const R: usize> AllCloseOps<Scalar, R> for C
3545
- where
3546
- C: TensorRef<Scalar, R>,
3547
- Scalar::DimScalar: NumberLike,
3548
- {
3549
- }
3550
-
3551
- // endregion: Tolerance Equality
3750
+ // endregion: Tolerance Equality (AllCloseOps moved to crate::each)
3552
3751
 
3553
3752
  // region: AsRef
3554
3753
 
3555
- impl<Scalar, Alloc: Allocator, const MAX_RANK: usize> AsRef<[Scalar]>
3754
+ impl<Scalar: StorageElement, Alloc: Allocator, const MAX_RANK: usize> AsRef<[Scalar]>
3556
3755
  for Tensor<Scalar, Alloc, MAX_RANK>
3557
3756
  {
3558
3757
  fn as_ref(&self) -> &[Scalar] {
@@ -3564,7 +3763,7 @@ impl<Scalar, Alloc: Allocator, const MAX_RANK: usize> AsRef<[Scalar]>
3564
3763
 
3565
3764
  // region: Tensor View and Slice Methods
3566
3765
 
3567
- impl<Scalar, Alloc: Allocator, const MAX_RANK: usize> Tensor<Scalar, Alloc, MAX_RANK> {
3766
+ impl<Scalar: StorageElement, Alloc: Allocator, const MAX_RANK: usize> Tensor<Scalar, Alloc, MAX_RANK> {
3568
3767
  /// Create a view of the entire array.
3569
3768
  pub fn view(&self) -> TensorView<'_, Scalar, MAX_RANK> {
3570
3769
  TensorView {
@@ -3819,7 +4018,7 @@ impl<
3819
4018
  I1: VectorIndex,
3820
4019
  I2: VectorIndex,
3821
4020
  I3: VectorIndex,
3822
- Scalar,
4021
+ Scalar: StorageElement,
3823
4022
  const MAX_RANK: usize,
3824
4023
  > Index<(I0, I1, I2, I3)> for TensorView<'a, Scalar, MAX_RANK>
3825
4024
  {
@@ -3838,7 +4037,7 @@ impl<
3838
4037
  I2: VectorIndex,
3839
4038
  I3: VectorIndex,
3840
4039
  I4: VectorIndex,
3841
- Scalar,
4040
+ Scalar: StorageElement,
3842
4041
  const MAX_RANK: usize,
3843
4042
  > Index<(I0, I1, I2, I3, I4)> for TensorView<'a, Scalar, MAX_RANK>
3844
4043
  {
@@ -3858,7 +4057,7 @@ impl<
3858
4057
  I3: VectorIndex,
3859
4058
  I4: VectorIndex,
3860
4059
  I5: VectorIndex,
3861
- Scalar,
4060
+ Scalar: StorageElement,
3862
4061
  const MAX_RANK: usize,
3863
4062
  > Index<(I0, I1, I2, I3, I4, I5)> for TensorView<'a, Scalar, MAX_RANK>
3864
4063
  {
@@ -3879,7 +4078,7 @@ impl<
3879
4078
  I4: VectorIndex,
3880
4079
  I5: VectorIndex,
3881
4080
  I6: VectorIndex,
3882
- Scalar,
4081
+ Scalar: StorageElement,
3883
4082
  const MAX_RANK: usize,
3884
4083
  > Index<(I0, I1, I2, I3, I4, I5, I6)> for TensorView<'a, Scalar, MAX_RANK>
3885
4084
  {
@@ -3901,7 +4100,7 @@ impl<
3901
4100
  I5: VectorIndex,
3902
4101
  I6: VectorIndex,
3903
4102
  I7: VectorIndex,
3904
- Scalar,
4103
+ Scalar: StorageElement,
3905
4104
  const MAX_RANK: usize,
3906
4105
  > Index<(I0, I1, I2, I3, I4, I5, I6, I7)> for TensorView<'a, Scalar, MAX_RANK>
3907
4106
  {
@@ -3977,7 +4176,7 @@ impl<
3977
4176
  I1: VectorIndex,
3978
4177
  I2: VectorIndex,
3979
4178
  I3: VectorIndex,
3980
- Scalar,
4179
+ Scalar: StorageElement,
3981
4180
  const MAX_RANK: usize,
3982
4181
  > Index<(I0, I1, I2, I3)> for TensorSpan<'a, Scalar, MAX_RANK>
3983
4182
  {
@@ -3995,7 +4194,7 @@ impl<
3995
4194
  I1: VectorIndex,
3996
4195
  I2: VectorIndex,
3997
4196
  I3: VectorIndex,
3998
- Scalar,
4197
+ Scalar: StorageElement,
3999
4198
  const MAX_RANK: usize,
4000
4199
  > IndexMut<(I0, I1, I2, I3)> for TensorSpan<'a, Scalar, MAX_RANK>
4001
4200
  {
@@ -4012,7 +4211,7 @@ impl<
4012
4211
  I2: VectorIndex,
4013
4212
  I3: VectorIndex,
4014
4213
  I4: VectorIndex,
4015
- Scalar,
4214
+ Scalar: StorageElement,
4016
4215
  const MAX_RANK: usize,
4017
4216
  > Index<(I0, I1, I2, I3, I4)> for TensorSpan<'a, Scalar, MAX_RANK>
4018
4217
  {
@@ -4031,7 +4230,7 @@ impl<
4031
4230
  I2: VectorIndex,
4032
4231
  I3: VectorIndex,
4033
4232
  I4: VectorIndex,
4034
- Scalar,
4233
+ Scalar: StorageElement,
4035
4234
  const MAX_RANK: usize,
4036
4235
  > IndexMut<(I0, I1, I2, I3, I4)> for TensorSpan<'a, Scalar, MAX_RANK>
4037
4236
  {
@@ -4049,7 +4248,7 @@ impl<
4049
4248
  I3: VectorIndex,
4050
4249
  I4: VectorIndex,
4051
4250
  I5: VectorIndex,
4052
- Scalar,
4251
+ Scalar: StorageElement,
4053
4252
  const MAX_RANK: usize,
4054
4253
  > Index<(I0, I1, I2, I3, I4, I5)> for TensorSpan<'a, Scalar, MAX_RANK>
4055
4254
  {
@@ -4069,7 +4268,7 @@ impl<
4069
4268
  I3: VectorIndex,
4070
4269
  I4: VectorIndex,
4071
4270
  I5: VectorIndex,
4072
- Scalar,
4271
+ Scalar: StorageElement,
4073
4272
  const MAX_RANK: usize,
4074
4273
  > IndexMut<(I0, I1, I2, I3, I4, I5)> for TensorSpan<'a, Scalar, MAX_RANK>
4075
4274
  {
@@ -4088,7 +4287,7 @@ impl<
4088
4287
  I4: VectorIndex,
4089
4288
  I5: VectorIndex,
4090
4289
  I6: VectorIndex,
4091
- Scalar,
4290
+ Scalar: StorageElement,
4092
4291
  const MAX_RANK: usize,
4093
4292
  > Index<(I0, I1, I2, I3, I4, I5, I6)> for TensorSpan<'a, Scalar, MAX_RANK>
4094
4293
  {
@@ -4109,7 +4308,7 @@ impl<
4109
4308
  I4: VectorIndex,
4110
4309
  I5: VectorIndex,
4111
4310
  I6: VectorIndex,
4112
- Scalar,
4311
+ Scalar: StorageElement,
4113
4312
  const MAX_RANK: usize,
4114
4313
  > IndexMut<(I0, I1, I2, I3, I4, I5, I6)> for TensorSpan<'a, Scalar, MAX_RANK>
4115
4314
  {
@@ -4129,7 +4328,7 @@ impl<
4129
4328
  I5: VectorIndex,
4130
4329
  I6: VectorIndex,
4131
4330
  I7: VectorIndex,
4132
- Scalar,
4331
+ Scalar: StorageElement,
4133
4332
  const MAX_RANK: usize,
4134
4333
  > Index<(I0, I1, I2, I3, I4, I5, I6, I7)> for TensorSpan<'a, Scalar, MAX_RANK>
4135
4334
  {
@@ -4151,7 +4350,7 @@ impl<
4151
4350
  I5: VectorIndex,
4152
4351
  I6: VectorIndex,
4153
4352
  I7: VectorIndex,
4154
- Scalar,
4353
+ Scalar: StorageElement,
4155
4354
  const MAX_RANK: usize,
4156
4355
  > IndexMut<(I0, I1, I2, I3, I4, I5, I6, I7)> for TensorSpan<'a, Scalar, MAX_RANK>
4157
4356
  {
@@ -4161,7 +4360,7 @@ impl<
4161
4360
  }
4162
4361
  }
4163
4362
 
4164
- impl<AnyIndex: VectorIndex, Scalar, Alloc: Allocator, const MAX_RANK: usize> Index<AnyIndex>
4363
+ impl<AnyIndex: VectorIndex, Scalar: StorageElement, Alloc: Allocator, const MAX_RANK: usize> Index<AnyIndex>
4165
4364
  for Tensor<Scalar, Alloc, MAX_RANK>
4166
4365
  {
4167
4366
  type Output = Scalar;
@@ -4171,7 +4370,7 @@ impl<AnyIndex: VectorIndex, Scalar, Alloc: Allocator, const MAX_RANK: usize> Ind
4171
4370
  }
4172
4371
  }
4173
4372
 
4174
- impl<AnyIndex: VectorIndex, Scalar, Alloc: Allocator, const MAX_RANK: usize> IndexMut<AnyIndex>
4373
+ impl<AnyIndex: VectorIndex, Scalar: StorageElement, Alloc: Allocator, const MAX_RANK: usize> IndexMut<AnyIndex>
4175
4374
  for Tensor<Scalar, Alloc, MAX_RANK>
4176
4375
  {
4177
4376
  fn index_mut(&mut self, index: AnyIndex) -> &mut Self::Output {
@@ -4180,7 +4379,7 @@ impl<AnyIndex: VectorIndex, Scalar, Alloc: Allocator, const MAX_RANK: usize> Ind
4180
4379
  }
4181
4380
  }
4182
4381
 
4183
- impl<I0: VectorIndex, I1: VectorIndex, Scalar, Alloc: Allocator, const MAX_RANK: usize>
4382
+ impl<I0: VectorIndex, I1: VectorIndex, Scalar: StorageElement, Alloc: Allocator, const MAX_RANK: usize>
4184
4383
  Index<(I0, I1)> for Tensor<Scalar, Alloc, MAX_RANK>
4185
4384
  {
4186
4385
  type Output = Scalar;
@@ -4191,7 +4390,7 @@ impl<I0: VectorIndex, I1: VectorIndex, Scalar, Alloc: Allocator, const MAX_RANK:
4191
4390
  }
4192
4391
  }
4193
4392
 
4194
- impl<I0: VectorIndex, I1: VectorIndex, Scalar, Alloc: Allocator, const MAX_RANK: usize>
4393
+ impl<I0: VectorIndex, I1: VectorIndex, Scalar: StorageElement, Alloc: Allocator, const MAX_RANK: usize>
4195
4394
  IndexMut<(I0, I1)> for Tensor<Scalar, Alloc, MAX_RANK>
4196
4395
  {
4197
4396
  fn index_mut(&mut self, index: (I0, I1)) -> &mut Self::Output {
@@ -4204,7 +4403,7 @@ impl<
4204
4403
  I0: VectorIndex,
4205
4404
  I1: VectorIndex,
4206
4405
  I2: VectorIndex,
4207
- Scalar,
4406
+ Scalar: StorageElement,
4208
4407
  Alloc: Allocator,
4209
4408
  const MAX_RANK: usize,
4210
4409
  > Index<(I0, I1, I2)> for Tensor<Scalar, Alloc, MAX_RANK>
@@ -4221,7 +4420,7 @@ impl<
4221
4420
  I0: VectorIndex,
4222
4421
  I1: VectorIndex,
4223
4422
  I2: VectorIndex,
4224
- Scalar,
4423
+ Scalar: StorageElement,
4225
4424
  Alloc: Allocator,
4226
4425
  const MAX_RANK: usize,
4227
4426
  > IndexMut<(I0, I1, I2)> for Tensor<Scalar, Alloc, MAX_RANK>
@@ -4237,7 +4436,7 @@ impl<
4237
4436
  I1: VectorIndex,
4238
4437
  I2: VectorIndex,
4239
4438
  I3: VectorIndex,
4240
- Scalar,
4439
+ Scalar: StorageElement,
4241
4440
  Alloc: Allocator,
4242
4441
  const MAX_RANK: usize,
4243
4442
  > Index<(I0, I1, I2, I3)> for Tensor<Scalar, Alloc, MAX_RANK>
@@ -4255,7 +4454,7 @@ impl<
4255
4454
  I1: VectorIndex,
4256
4455
  I2: VectorIndex,
4257
4456
  I3: VectorIndex,
4258
- Scalar,
4457
+ Scalar: StorageElement,
4259
4458
  Alloc: Allocator,
4260
4459
  const MAX_RANK: usize,
4261
4460
  > IndexMut<(I0, I1, I2, I3)> for Tensor<Scalar, Alloc, MAX_RANK>
@@ -4272,7 +4471,7 @@ impl<
4272
4471
  I2: VectorIndex,
4273
4472
  I3: VectorIndex,
4274
4473
  I4: VectorIndex,
4275
- Scalar,
4474
+ Scalar: StorageElement,
4276
4475
  Alloc: Allocator,
4277
4476
  const MAX_RANK: usize,
4278
4477
  > Index<(I0, I1, I2, I3, I4)> for Tensor<Scalar, Alloc, MAX_RANK>
@@ -4291,7 +4490,7 @@ impl<
4291
4490
  I2: VectorIndex,
4292
4491
  I3: VectorIndex,
4293
4492
  I4: VectorIndex,
4294
- Scalar,
4493
+ Scalar: StorageElement,
4295
4494
  Alloc: Allocator,
4296
4495
  const MAX_RANK: usize,
4297
4496
  > IndexMut<(I0, I1, I2, I3, I4)> for Tensor<Scalar, Alloc, MAX_RANK>
@@ -4309,7 +4508,7 @@ impl<
4309
4508
  I3: VectorIndex,
4310
4509
  I4: VectorIndex,
4311
4510
  I5: VectorIndex,
4312
- Scalar,
4511
+ Scalar: StorageElement,
4313
4512
  Alloc: Allocator,
4314
4513
  const MAX_RANK: usize,
4315
4514
  > Index<(I0, I1, I2, I3, I4, I5)> for Tensor<Scalar, Alloc, MAX_RANK>
@@ -4329,7 +4528,7 @@ impl<
4329
4528
  I3: VectorIndex,
4330
4529
  I4: VectorIndex,
4331
4530
  I5: VectorIndex,
4332
- Scalar,
4531
+ Scalar: StorageElement,
4333
4532
  Alloc: Allocator,
4334
4533
  const MAX_RANK: usize,
4335
4534
  > IndexMut<(I0, I1, I2, I3, I4, I5)> for Tensor<Scalar, Alloc, MAX_RANK>
@@ -4348,7 +4547,7 @@ impl<
4348
4547
  I4: VectorIndex,
4349
4548
  I5: VectorIndex,
4350
4549
  I6: VectorIndex,
4351
- Scalar,
4550
+ Scalar: StorageElement,
4352
4551
  Alloc: Allocator,
4353
4552
  const MAX_RANK: usize,
4354
4553
  > Index<(I0, I1, I2, I3, I4, I5, I6)> for Tensor<Scalar, Alloc, MAX_RANK>
@@ -4369,7 +4568,7 @@ impl<
4369
4568
  I4: VectorIndex,
4370
4569
  I5: VectorIndex,
4371
4570
  I6: VectorIndex,
4372
- Scalar,
4571
+ Scalar: StorageElement,
4373
4572
  Alloc: Allocator,
4374
4573
  const MAX_RANK: usize,
4375
4574
  > IndexMut<(I0, I1, I2, I3, I4, I5, I6)> for Tensor<Scalar, Alloc, MAX_RANK>
@@ -4389,7 +4588,7 @@ impl<
4389
4588
  I5: VectorIndex,
4390
4589
  I6: VectorIndex,
4391
4590
  I7: VectorIndex,
4392
- Scalar,
4591
+ Scalar: StorageElement,
4393
4592
  Alloc: Allocator,
4394
4593
  const MAX_RANK: usize,
4395
4594
  > Index<(I0, I1, I2, I3, I4, I5, I6, I7)> for Tensor<Scalar, Alloc, MAX_RANK>
@@ -4411,7 +4610,7 @@ impl<
4411
4610
  I5: VectorIndex,
4412
4611
  I6: VectorIndex,
4413
4612
  I7: VectorIndex,
4414
- Scalar,
4613
+ Scalar: StorageElement,
4415
4614
  Alloc: Allocator,
4416
4615
  const MAX_RANK: usize,
4417
4616
  > IndexMut<(I0, I1, I2, I3, I4, I5, I6, I7)> for Tensor<Scalar, Alloc, MAX_RANK>
@@ -5400,31 +5599,7 @@ where
5400
5599
  }
5401
5600
  }
5402
5601
 
5403
- #[doc(hidden)]
5404
- pub trait SumSqToF64 {
5405
- fn to_f64(self) -> f64;
5406
- }
5407
-
5408
- impl SumSqToF64 for f32 {
5409
- fn to_f64(self) -> f64 {
5410
- self as f64
5411
- }
5412
- }
5413
- impl SumSqToF64 for f64 {
5414
- fn to_f64(self) -> f64 {
5415
- self
5416
- }
5417
- }
5418
- impl SumSqToF64 for u64 {
5419
- fn to_f64(self) -> f64 {
5420
- self as f64
5421
- }
5422
- }
5423
- impl SumSqToF64 for i64 {
5424
- fn to_f64(self) -> f64 {
5425
- self as f64
5426
- }
5427
- }
5602
+ // SumSqToF64 trait moved to crate::reduce
5428
5603
 
5429
5604
  fn try_alloc_output_like<Destination: Clone + StorageElement, Kernel, const MAX_RANK: usize>(
5430
5605
  shape: &[usize],
@@ -5441,7 +5616,12 @@ where
5441
5616
  Ok(result)
5442
5617
  }
5443
5618
 
5444
- fn try_reborrow_tensor_into<Scalar: Clone, Destination: Clone, Kernel, const MAX_RANK: usize>(
5619
+ pub(crate) fn try_reborrow_tensor_into<
5620
+ Scalar: StorageElement + Clone,
5621
+ Destination: StorageElement + Clone,
5622
+ Kernel,
5623
+ const MAX_RANK: usize,
5624
+ >(
5445
5625
  source: &Tensor<Scalar, Global, MAX_RANK>,
5446
5626
  out: &mut Tensor<Destination, Global, MAX_RANK>,
5447
5627
  apply: Kernel,
@@ -5457,7 +5637,11 @@ where
5457
5637
  apply(&view, &mut span)
5458
5638
  }
5459
5639
 
5460
- fn try_reborrow_tensor_inplace<Scalar: Clone, Kernel, const MAX_RANK: usize>(
5640
+ pub(crate) fn try_reborrow_tensor_inplace<
5641
+ Scalar: StorageElement + Clone,
5642
+ Kernel,
5643
+ const MAX_RANK: usize,
5644
+ >(
5461
5645
  tensor: &mut Tensor<Scalar, Global, MAX_RANK>,
5462
5646
  apply: Kernel,
5463
5647
  ) -> Result<(), TensorError>
@@ -5910,470 +6094,106 @@ impl<'a, Source: Clone + CastDtype, const MAX_RANK: usize> TensorView<'a, Source
5910
6094
  }
5911
6095
  }
5912
6096
 
5913
- /// Extension trait: scalar arithmetic for any [`TensorRef`] implementor.
5914
- pub trait ScaleOps<Scalar: Clone + EachScale, const MAX_RANK: usize>:
5915
- TensorRef<Scalar, MAX_RANK>
5916
- where
5917
- Scalar::Scalar: From<f32> + core::ops::Mul<Output = Scalar::Scalar> + Copy,
5918
- {
5919
- fn try_add_scalar(
5920
- &self,
5921
- scalar: Scalar::Scalar,
5922
- ) -> Result<Tensor<Scalar, Global, MAX_RANK>, TensorError> {
5923
- self.view().try_add_scalar(scalar)
5924
- }
6097
+ // endregion: Tensor Explicit Elementwise + Cast (ScaleOps/SumOps/BlendOps/FmaOps/CastOps moved to crate::each / crate::cast)
5925
6098
 
5926
- fn try_sub_scalar(
5927
- &self,
5928
- scalar: Scalar::Scalar,
5929
- ) -> Result<Tensor<Scalar, Global, MAX_RANK>, TensorError> {
5930
- self.view().try_sub_scalar(scalar)
5931
- }
6099
+ // region: Tensor Trigonometry
5932
6100
 
5933
- fn try_mul_scalar(
5934
- &self,
5935
- scalar: Scalar::Scalar,
5936
- ) -> Result<Tensor<Scalar, Global, MAX_RANK>, TensorError> {
5937
- self.view().try_mul_scalar(scalar)
6101
+ impl<'a, Scalar: Clone + EachSin, const MAX_RANK: usize> TensorView<'a, Scalar, MAX_RANK> {
6102
+ pub fn try_sin(&self) -> Result<Tensor<Scalar, Global, MAX_RANK>, TensorError> {
6103
+ try_alloc_output_like(self.shape(), |span| self.try_sin_into(span))
5938
6104
  }
5939
- }
5940
-
5941
- impl<Scalar: Clone + EachScale, const R: usize, C: TensorRef<Scalar, R>> ScaleOps<Scalar, R> for C where
5942
- Scalar::Scalar: From<f32> + core::ops::Mul<Output = Scalar::Scalar> + Copy
5943
- {
5944
- }
5945
6105
 
5946
- impl<Scalar: Clone + EachScale, const MAX_RANK: usize> Tensor<Scalar, Global, MAX_RANK>
5947
- where
5948
- Scalar::Scalar: From<f32> + core::ops::Mul<Output = Scalar::Scalar> + Copy,
5949
- {
5950
- pub fn try_add_scalar_into(
6106
+ pub fn try_sin_into(
5951
6107
  &self,
5952
- scalar: Scalar::Scalar,
5953
- out: &mut Tensor<Scalar, Global, MAX_RANK>,
6108
+ out: &mut TensorSpan<'_, Scalar, MAX_RANK>,
5954
6109
  ) -> Result<(), TensorError> {
5955
- try_reborrow_tensor_into(self, out, |view, span| {
5956
- view.try_add_scalar_into(scalar, span)
6110
+ try_unary_kernel_into(self, out, |source, target| {
6111
+ Scalar::sin(source, target);
5957
6112
  })
5958
6113
  }
6114
+ }
5959
6115
 
5960
- pub fn try_sub_scalar_into(
5961
- &self,
5962
- scalar: Scalar::Scalar,
5963
- out: &mut Tensor<Scalar, Global, MAX_RANK>,
5964
- ) -> Result<(), TensorError> {
5965
- try_reborrow_tensor_into(self, out, |view, span| {
5966
- view.try_sub_scalar_into(scalar, span)
5967
- })
6116
+ impl<'a, Scalar: Clone + EachCos, const MAX_RANK: usize> TensorView<'a, Scalar, MAX_RANK> {
6117
+ pub fn try_cos(&self) -> Result<Tensor<Scalar, Global, MAX_RANK>, TensorError> {
6118
+ try_alloc_output_like(self.shape(), |span| self.try_cos_into(span))
5968
6119
  }
5969
6120
 
5970
- pub fn try_mul_scalar_into(
6121
+ pub fn try_cos_into(
5971
6122
  &self,
5972
- scalar: Scalar::Scalar,
5973
- out: &mut Tensor<Scalar, Global, MAX_RANK>,
6123
+ out: &mut TensorSpan<'_, Scalar, MAX_RANK>,
5974
6124
  ) -> Result<(), TensorError> {
5975
- try_reborrow_tensor_into(self, out, |view, span| {
5976
- view.try_mul_scalar_into(scalar, span)
6125
+ try_unary_kernel_into(self, out, |source, target| {
6126
+ Scalar::cos(source, target);
5977
6127
  })
5978
6128
  }
6129
+ }
5979
6130
 
5980
- pub fn try_add_scalar_inplace(&mut self, scalar: Scalar::Scalar) -> Result<(), TensorError> {
5981
- self.add_scalar_inplace(scalar);
5982
- Ok(())
5983
- }
5984
-
5985
- pub fn try_sub_scalar_inplace(&mut self, scalar: Scalar::Scalar) -> Result<(), TensorError> {
5986
- self.sub_scalar_inplace(scalar);
5987
- Ok(())
5988
- }
5989
-
5990
- pub fn try_mul_scalar_inplace(&mut self, scalar: Scalar::Scalar) -> Result<(), TensorError> {
5991
- self.mul_scalar_inplace(scalar);
5992
- Ok(())
6131
+ impl<'a, Scalar: Clone + EachATan, const MAX_RANK: usize> TensorView<'a, Scalar, MAX_RANK> {
6132
+ pub fn try_atan(&self) -> Result<Tensor<Scalar, Global, MAX_RANK>, TensorError> {
6133
+ try_alloc_output_like(self.shape(), |span| self.try_atan_into(span))
5993
6134
  }
5994
6135
 
5995
- /// Element-wise add scalar in-place (infallible — self vs self always matches).
5996
- pub fn add_scalar_inplace(&mut self, scalar: Scalar::Scalar) {
5997
- try_reborrow_tensor_inplace(self, |view, span| view.try_add_scalar_into(scalar, span))
5998
- .expect("inplace scalar op on self cannot fail")
6136
+ pub fn try_atan_into(
6137
+ &self,
6138
+ out: &mut TensorSpan<'_, Scalar, MAX_RANK>,
6139
+ ) -> Result<(), TensorError> {
6140
+ try_unary_kernel_into(self, out, |source, target| {
6141
+ Scalar::atan(source, target);
6142
+ })
5999
6143
  }
6144
+ }
6000
6145
 
6001
- /// Element-wise subtract scalar in-place (infallible — self vs self always matches).
6002
- pub fn sub_scalar_inplace(&mut self, scalar: Scalar::Scalar) {
6003
- try_reborrow_tensor_inplace(self, |view, span| view.try_sub_scalar_into(scalar, span))
6004
- .expect("inplace scalar op on self cannot fail")
6005
- }
6146
+ // endregion: Tensor Trigonometry (moved to crate::each)
6006
6147
 
6007
- /// Element-wise multiply scalar in-place (infallible — self vs self always matches).
6008
- pub fn mul_scalar_inplace(&mut self, scalar: Scalar::Scalar) {
6009
- try_reborrow_tensor_inplace(self, |view, span| view.try_mul_scalar_into(scalar, span))
6010
- .expect("inplace scalar op on self cannot fail")
6011
- }
6012
- }
6148
+ // region: Tensor Reductions
6013
6149
 
6014
- impl<Scalar: Clone + EachScale, const MAX_RANK: usize> core::ops::AddAssign<Scalar::Scalar>
6015
- for Tensor<Scalar, Global, MAX_RANK>
6016
- where
6017
- Scalar::Scalar: From<f32> + core::ops::Mul<Output = Scalar::Scalar> + Copy,
6018
- {
6019
- fn add_assign(&mut self, scalar: Scalar::Scalar) {
6020
- self.add_scalar_inplace(scalar);
6150
+ impl<Scalar: Clone + Dot, const MAX_RANK: usize> Tensor<Scalar, Global, MAX_RANK> {
6151
+ /// Compute the dot product of this array with another.
6152
+ ///
6153
+ /// Both arrays must be 1D with the same length.
6154
+ pub fn dot_product<const OTHER_MAX_RANK: usize>(
6155
+ &self,
6156
+ other: &Tensor<Scalar, Global, OTHER_MAX_RANK>,
6157
+ ) -> Result<Scalar::Output, TensorError> {
6158
+ if self.ndim != 1 || other.ndim != 1 {
6159
+ return Err(TensorError::DimensionMismatch {
6160
+ expected: 1,
6161
+ got: if self.ndim != 1 {
6162
+ self.ndim
6163
+ } else {
6164
+ other.ndim
6165
+ },
6166
+ });
6167
+ }
6168
+ if self.numel() != other.numel() {
6169
+ return Err(TensorError::ShapeMismatch {
6170
+ axis: 0,
6171
+ expected: self.numel(),
6172
+ got: other.numel(),
6173
+ });
6174
+ }
6175
+ // Dot::dot returns Option, unwrap since we verified lengths match
6176
+ Ok(Scalar::dot(self.as_slice(), other.as_slice()).expect("dot product failed"))
6021
6177
  }
6022
6178
  }
6023
6179
 
6024
- impl<Scalar: Clone + EachScale, const MAX_RANK: usize> core::ops::SubAssign<Scalar::Scalar>
6025
- for Tensor<Scalar, Global, MAX_RANK>
6026
- where
6027
- Scalar::Scalar: From<f32> + core::ops::Mul<Output = Scalar::Scalar> + Copy,
6028
- {
6029
- fn sub_assign(&mut self, scalar: Scalar::Scalar) {
6030
- self.sub_scalar_inplace(scalar);
6031
- }
6032
- }
6180
+ pub(crate) type MomentsAxisResult<Scalar, const MAX_RANK: usize> = Result<
6181
+ (
6182
+ Tensor<<Scalar as ReduceMoments>::SumOutput, Global, MAX_RANK>,
6183
+ Tensor<<Scalar as ReduceMoments>::SumSqOutput, Global, MAX_RANK>,
6184
+ ),
6185
+ TensorError,
6186
+ >;
6033
6187
 
6034
- impl<Scalar: Clone + EachScale, const MAX_RANK: usize> core::ops::MulAssign<Scalar::Scalar>
6035
- for Tensor<Scalar, Global, MAX_RANK>
6188
+ impl<'a, Scalar: Clone + ReduceMoments, const MAX_RANK: usize> TensorView<'a, Scalar, MAX_RANK>
6036
6189
  where
6037
- Scalar::Scalar: From<f32> + core::ops::Mul<Output = Scalar::Scalar> + Copy,
6190
+ Scalar::SumOutput: Clone + Default + core::ops::AddAssign,
6191
+ Scalar::SumSqOutput: Clone + Default + core::ops::AddAssign + SumSqToF64,
6038
6192
  {
6039
- fn mul_assign(&mut self, scalar: Scalar::Scalar) {
6040
- self.mul_scalar_inplace(scalar);
6041
- }
6042
- }
6043
-
6044
- /// Extension trait: element-wise addition for any [`TensorRef`] implementor.
6045
- pub trait SumOps<Scalar: Clone + EachSum, const MAX_RANK: usize>:
6046
- TensorRef<Scalar, MAX_RANK>
6047
- {
6048
- fn try_add_tensor(
6049
- &self,
6050
- other: &(impl TensorRef<Scalar, MAX_RANK> + ?Sized),
6051
- ) -> Result<Tensor<Scalar, Global, MAX_RANK>, TensorError> {
6052
- self.view().try_add_tensor(&other.view())
6053
- }
6054
- }
6055
-
6056
- impl<Scalar: Clone + EachSum, const R: usize, C: TensorRef<Scalar, R>> SumOps<Scalar, R> for C {}
6057
-
6058
- impl<Scalar: Clone + EachSum, const MAX_RANK: usize> Tensor<Scalar, Global, MAX_RANK> {
6059
- pub fn try_add_tensor_into(
6060
- &self,
6061
- other: &Tensor<Scalar, Global, MAX_RANK>,
6062
- out: &mut Tensor<Scalar, Global, MAX_RANK>,
6063
- ) -> Result<(), TensorError> {
6064
- try_reborrow_tensor_into(self, out, |view, span| {
6065
- view.try_add_tensor_into(&other.view(), span)
6066
- })
6067
- }
6068
-
6069
- pub fn try_add_tensor_inplace(
6070
- &mut self,
6071
- other: &Tensor<Scalar, Global, MAX_RANK>,
6072
- ) -> Result<(), TensorError> {
6073
- try_reborrow_tensor_inplace(self, |view, span| {
6074
- view.try_add_tensor_into(&other.view(), span)
6075
- })
6076
- }
6077
- }
6078
-
6079
- /// Extension trait: element-wise subtraction for any [`TensorRef`] implementor.
6080
- pub trait BlendOps<Scalar: Clone + EachBlend, const MAX_RANK: usize>:
6081
- TensorRef<Scalar, MAX_RANK>
6082
- where
6083
- Scalar::Scalar: From<f32> + Copy,
6084
- {
6085
- fn try_sub_tensor(
6086
- &self,
6087
- other: &(impl TensorRef<Scalar, MAX_RANK> + ?Sized),
6088
- ) -> Result<Tensor<Scalar, Global, MAX_RANK>, TensorError> {
6089
- self.view().try_sub_tensor(&other.view())
6090
- }
6091
- }
6092
-
6093
- impl<Scalar: Clone + EachBlend, const R: usize, C: TensorRef<Scalar, R>> BlendOps<Scalar, R> for C where
6094
- Scalar::Scalar: From<f32> + Copy
6095
- {
6096
- }
6097
-
6098
- impl<Scalar: Clone + EachBlend, const MAX_RANK: usize> Tensor<Scalar, Global, MAX_RANK>
6099
- where
6100
- Scalar::Scalar: From<f32> + Copy,
6101
- {
6102
- pub fn try_sub_tensor_into(
6103
- &self,
6104
- other: &Tensor<Scalar, Global, MAX_RANK>,
6105
- out: &mut Tensor<Scalar, Global, MAX_RANK>,
6106
- ) -> Result<(), TensorError> {
6107
- try_reborrow_tensor_into(self, out, |view, span| {
6108
- view.try_sub_tensor_into(&other.view(), span)
6109
- })
6110
- }
6111
-
6112
- pub fn try_sub_tensor_inplace(
6113
- &mut self,
6114
- other: &Tensor<Scalar, Global, MAX_RANK>,
6115
- ) -> Result<(), TensorError> {
6116
- try_reborrow_tensor_inplace(self, |view, span| {
6117
- view.try_sub_tensor_into(&other.view(), span)
6118
- })
6119
- }
6120
- }
6121
-
6122
- /// Extension trait: element-wise multiplication for any [`TensorRef`] implementor.
6123
- pub trait FmaOps<Scalar: Clone + EachFMA, const MAX_RANK: usize>:
6124
- TensorRef<Scalar, MAX_RANK>
6125
- where
6126
- Scalar::Scalar: From<f32> + Copy,
6127
- {
6128
- fn try_mul_tensor(
6129
- &self,
6130
- other: &(impl TensorRef<Scalar, MAX_RANK> + ?Sized),
6131
- ) -> Result<Tensor<Scalar, Global, MAX_RANK>, TensorError> {
6132
- self.view().try_mul_tensor(&other.view())
6133
- }
6134
- }
6135
-
6136
- impl<Scalar: Clone + EachFMA, const R: usize, C: TensorRef<Scalar, R>> FmaOps<Scalar, R> for C where
6137
- Scalar::Scalar: From<f32> + Copy
6138
- {
6139
- }
6140
-
6141
- impl<Scalar: Clone + EachFMA, const MAX_RANK: usize> Tensor<Scalar, Global, MAX_RANK>
6142
- where
6143
- Scalar::Scalar: From<f32> + Copy,
6144
- {
6145
- pub fn try_mul_tensor_into(
6146
- &self,
6147
- other: &Tensor<Scalar, Global, MAX_RANK>,
6148
- out: &mut Tensor<Scalar, Global, MAX_RANK>,
6149
- ) -> Result<(), TensorError> {
6150
- try_reborrow_tensor_into(self, out, |view, span| {
6151
- view.try_mul_tensor_into(&other.view(), span)
6152
- })
6153
- }
6154
-
6155
- pub fn try_mul_tensor_inplace(
6156
- &mut self,
6157
- other: &Tensor<Scalar, Global, MAX_RANK>,
6158
- ) -> Result<(), TensorError> {
6159
- try_reborrow_tensor_inplace(self, |view, span| {
6160
- view.try_mul_tensor_into(&other.view(), span)
6161
- })
6162
- }
6163
- }
6164
-
6165
- /// Extension trait: type casting for any [`TensorRef`] implementor.
6166
- pub trait CastOps<Source: Clone + CastDtype, const MAX_RANK: usize>:
6167
- TensorRef<Source, MAX_RANK>
6168
- {
6169
- fn try_cast_dtype<Destination: Clone + CastDtype>(
6170
- &self,
6171
- ) -> Result<Tensor<Destination, Global, MAX_RANK>, TensorError> {
6172
- self.view().try_cast_dtype()
6173
- }
6174
- }
6175
-
6176
- impl<Source: Clone + CastDtype, const R: usize, C: TensorRef<Source, R>> CastOps<Source, R> for C {}
6177
-
6178
- impl<Source: Clone + CastDtype, const MAX_RANK: usize> Tensor<Source, Global, MAX_RANK> {
6179
- pub fn try_cast_dtype_into<Destination: Clone + CastDtype>(
6180
- &self,
6181
- out: &mut Tensor<Destination, Global, MAX_RANK>,
6182
- ) -> Result<(), TensorError> {
6183
- try_reborrow_tensor_into(self, out, |view, span| view.try_cast_dtype_into(span))
6184
- }
6185
- }
6186
-
6187
- // endregion: Tensor Explicit Elementwise + Cast
6188
-
6189
- // region: Tensor Trigonometry
6190
-
6191
- impl<'a, Scalar: Clone + EachSin, const MAX_RANK: usize> TensorView<'a, Scalar, MAX_RANK> {
6192
- pub fn try_sin(&self) -> Result<Tensor<Scalar, Global, MAX_RANK>, TensorError> {
6193
- try_alloc_output_like(self.shape(), |span| self.try_sin_into(span))
6194
- }
6195
-
6196
- pub fn try_sin_into(
6197
- &self,
6198
- out: &mut TensorSpan<'_, Scalar, MAX_RANK>,
6199
- ) -> Result<(), TensorError> {
6200
- try_unary_kernel_into(self, out, |source, target| {
6201
- Scalar::sin(source, target);
6202
- })
6203
- }
6204
- }
6205
-
6206
- impl<'a, Scalar: Clone + EachCos, const MAX_RANK: usize> TensorView<'a, Scalar, MAX_RANK> {
6207
- pub fn try_cos(&self) -> Result<Tensor<Scalar, Global, MAX_RANK>, TensorError> {
6208
- try_alloc_output_like(self.shape(), |span| self.try_cos_into(span))
6209
- }
6210
-
6211
- pub fn try_cos_into(
6212
- &self,
6213
- out: &mut TensorSpan<'_, Scalar, MAX_RANK>,
6214
- ) -> Result<(), TensorError> {
6215
- try_unary_kernel_into(self, out, |source, target| {
6216
- Scalar::cos(source, target);
6217
- })
6218
- }
6219
- }
6220
-
6221
- impl<'a, Scalar: Clone + EachATan, const MAX_RANK: usize> TensorView<'a, Scalar, MAX_RANK> {
6222
- pub fn try_atan(&self) -> Result<Tensor<Scalar, Global, MAX_RANK>, TensorError> {
6223
- try_alloc_output_like(self.shape(), |span| self.try_atan_into(span))
6224
- }
6225
-
6226
- pub fn try_atan_into(
6227
- &self,
6228
- out: &mut TensorSpan<'_, Scalar, MAX_RANK>,
6229
- ) -> Result<(), TensorError> {
6230
- try_unary_kernel_into(self, out, |source, target| {
6231
- Scalar::atan(source, target);
6232
- })
6233
- }
6234
- }
6235
-
6236
- /// Extension trait: element-wise sine for any [`TensorRef`] implementor.
6237
- pub trait TrigSinOps<Scalar: Clone + EachSin, const MAX_RANK: usize>:
6238
- TensorRef<Scalar, MAX_RANK>
6239
- {
6240
- fn try_sin(&self) -> Result<Tensor<Scalar, Global, MAX_RANK>, TensorError> {
6241
- self.view().try_sin()
6242
- }
6243
- }
6244
-
6245
- impl<Scalar: Clone + EachSin, const R: usize, C: TensorRef<Scalar, R>> TrigSinOps<Scalar, R> for C {}
6246
-
6247
- /// Extension trait: element-wise cosine for any [`TensorRef`] implementor.
6248
- pub trait TrigCosOps<Scalar: Clone + EachCos, const MAX_RANK: usize>:
6249
- TensorRef<Scalar, MAX_RANK>
6250
- {
6251
- fn try_cos(&self) -> Result<Tensor<Scalar, Global, MAX_RANK>, TensorError> {
6252
- self.view().try_cos()
6253
- }
6254
- }
6255
-
6256
- impl<Scalar: Clone + EachCos, const R: usize, C: TensorRef<Scalar, R>> TrigCosOps<Scalar, R> for C {}
6257
-
6258
- /// Extension trait: element-wise arctangent for any [`TensorRef`] implementor.
6259
- pub trait TrigAtanOps<Scalar: Clone + EachATan, const MAX_RANK: usize>:
6260
- TensorRef<Scalar, MAX_RANK>
6261
- {
6262
- fn try_atan(&self) -> Result<Tensor<Scalar, Global, MAX_RANK>, TensorError> {
6263
- self.view().try_atan()
6264
- }
6265
- }
6266
-
6267
- impl<Scalar: Clone + EachATan, const R: usize, C: TensorRef<Scalar, R>> TrigAtanOps<Scalar, R>
6268
- for C
6269
- {
6270
- }
6271
-
6272
- impl<Scalar: Clone + EachSin, const MAX_RANK: usize> Tensor<Scalar, Global, MAX_RANK> {
6273
- /// Element-wise sine: result\[i\] = sin(self\[i\])
6274
- pub fn sin(&self) -> Result<Tensor<Scalar, Global, MAX_RANK>, TensorError> {
6275
- self.view().try_sin()
6276
- }
6277
-
6278
- /// Element-wise sine in-place (infallible — self vs self always matches).
6279
- pub fn sin_inplace(&mut self) {
6280
- try_reborrow_tensor_inplace(self, |view, span| view.try_sin_into(span))
6281
- .expect("inplace trig op on self cannot fail")
6282
- }
6283
-
6284
- pub fn try_sin_inplace(&mut self) -> Result<(), TensorError> {
6285
- self.sin_inplace();
6286
- Ok(())
6287
- }
6288
- }
6289
-
6290
- impl<Scalar: Clone + EachCos, const MAX_RANK: usize> Tensor<Scalar, Global, MAX_RANK> {
6291
- /// Element-wise cosine: result\[i\] = cos(self\[i\])
6292
- pub fn cos(&self) -> Result<Tensor<Scalar, Global, MAX_RANK>, TensorError> {
6293
- self.view().try_cos()
6294
- }
6295
-
6296
- /// Element-wise cosine in-place (infallible — self vs self always matches).
6297
- pub fn cos_inplace(&mut self) {
6298
- try_reborrow_tensor_inplace(self, |view, span| view.try_cos_into(span))
6299
- .expect("inplace trig op on self cannot fail")
6300
- }
6301
-
6302
- pub fn try_cos_inplace(&mut self) -> Result<(), TensorError> {
6303
- self.cos_inplace();
6304
- Ok(())
6305
- }
6306
- }
6307
-
6308
- impl<Scalar: Clone + EachATan, const MAX_RANK: usize> Tensor<Scalar, Global, MAX_RANK> {
6309
- /// Element-wise arctangent: result\[i\] = atan(self\[i\])
6310
- pub fn atan(&self) -> Result<Tensor<Scalar, Global, MAX_RANK>, TensorError> {
6311
- self.view().try_atan()
6312
- }
6313
-
6314
- /// Element-wise arctangent in-place (infallible — self vs self always matches).
6315
- pub fn atan_inplace(&mut self) {
6316
- try_reborrow_tensor_inplace(self, |view, span| view.try_atan_into(span))
6317
- .expect("inplace trig op on self cannot fail")
6318
- }
6319
-
6320
- pub fn try_atan_inplace(&mut self) -> Result<(), TensorError> {
6321
- self.atan_inplace();
6322
- Ok(())
6323
- }
6324
- }
6325
-
6326
- // endregion: Tensor Trigonometry
6327
-
6328
- // region: Tensor Reductions
6329
-
6330
- impl<Scalar: Clone + Dot, const MAX_RANK: usize> Tensor<Scalar, Global, MAX_RANK> {
6331
- /// Compute the dot product of this array with another.
6332
- ///
6333
- /// Both arrays must be 1D with the same length.
6334
- pub fn dot_product<const OTHER_MAX_RANK: usize>(
6335
- &self,
6336
- other: &Tensor<Scalar, Global, OTHER_MAX_RANK>,
6337
- ) -> Result<Scalar::Output, TensorError> {
6338
- if self.ndim != 1 || other.ndim != 1 {
6339
- return Err(TensorError::DimensionMismatch {
6340
- expected: 1,
6341
- got: if self.ndim != 1 {
6342
- self.ndim
6343
- } else {
6344
- other.ndim
6345
- },
6346
- });
6347
- }
6348
- if self.numel() != other.numel() {
6349
- return Err(TensorError::ShapeMismatch {
6350
- axis: 0,
6351
- expected: self.numel(),
6352
- got: other.numel(),
6353
- });
6354
- }
6355
- // Dot::dot returns Option, unwrap since we verified lengths match
6356
- Ok(Scalar::dot(self.as_slice(), other.as_slice()).expect("dot product failed"))
6357
- }
6358
- }
6359
-
6360
- type MomentsAxisResult<Scalar, const MAX_RANK: usize> = Result<
6361
- (
6362
- Tensor<<Scalar as ReduceMoments>::SumOutput, Global, MAX_RANK>,
6363
- Tensor<<Scalar as ReduceMoments>::SumSqOutput, Global, MAX_RANK>,
6364
- ),
6365
- TensorError,
6366
- >;
6367
-
6368
- impl<'a, Scalar: Clone + ReduceMoments, const MAX_RANK: usize> TensorView<'a, Scalar, MAX_RANK>
6369
- where
6370
- Scalar::SumOutput: Clone + Default + core::ops::AddAssign,
6371
- Scalar::SumSqOutput: Clone + Default + core::ops::AddAssign + SumSqToF64,
6372
- {
6373
- pub fn try_moments_all(&self) -> Result<(Scalar::SumOutput, Scalar::SumSqOutput), TensorError> {
6374
- Ok(unsafe {
6375
- reduce_moments_recursive::<Scalar>(self.data, self.shape(), &self.strides[..self.ndim])
6376
- })
6193
+ pub fn try_moments_all(&self) -> Result<(Scalar::SumOutput, Scalar::SumSqOutput), TensorError> {
6194
+ Ok(unsafe {
6195
+ reduce_moments_recursive::<Scalar>(self.data, self.shape(), &self.strides[..self.ndim])
6196
+ })
6377
6197
  }
6378
6198
 
6379
6199
  pub fn try_moments_axis<AnyIndex: VectorIndex>(
@@ -6502,7 +6322,7 @@ where
6502
6322
  }
6503
6323
  }
6504
6324
 
6505
- type MinMaxAxisResult<Scalar, const MAX_RANK: usize> = Result<
6325
+ pub(crate) type MinMaxAxisResult<Scalar, const MAX_RANK: usize> = Result<
6506
6326
  MinMaxResult<
6507
6327
  Tensor<<Scalar as ReduceMinMax>::Output, Global, MAX_RANK>,
6508
6328
  Tensor<usize, Global, MAX_RANK>,
@@ -6669,173 +6489,7 @@ where
6669
6489
  }
6670
6490
  }
6671
6491
 
6672
- /// Extension trait: statistical reductions for any [`TensorRef`] implementor.
6673
- pub trait MomentsOps<Scalar: Clone + ReduceMoments, const MAX_RANK: usize>:
6674
- TensorRef<Scalar, MAX_RANK>
6675
- where
6676
- Scalar::SumOutput: Clone + Default + core::ops::AddAssign,
6677
- Scalar::SumSqOutput: Clone + Default + core::ops::AddAssign + SumSqToF64,
6678
- {
6679
- fn try_moments_all(&self) -> Result<(Scalar::SumOutput, Scalar::SumSqOutput), TensorError> {
6680
- self.view().try_moments_all()
6681
- }
6682
-
6683
- fn try_moments_axis<AnyIndex: VectorIndex>(
6684
- &self,
6685
- axis: AnyIndex,
6686
- keep_dims: bool,
6687
- ) -> MomentsAxisResult<Scalar, MAX_RANK> {
6688
- self.view().try_moments_axis(axis, keep_dims)
6689
- }
6690
-
6691
- fn try_moments_axis_into<AnyIndex: VectorIndex>(
6692
- &self,
6693
- axis: AnyIndex,
6694
- keep_dims: bool,
6695
- sum_out: &mut Tensor<Scalar::SumOutput, Global, MAX_RANK>,
6696
- sumsq_out: &mut Tensor<Scalar::SumSqOutput, Global, MAX_RANK>,
6697
- ) -> Result<(), TensorError> {
6698
- self.view()
6699
- .try_moments_axis_into(axis, keep_dims, sum_out, sumsq_out)
6700
- }
6701
-
6702
- fn try_sum_all(&self) -> Result<Scalar::SumOutput, TensorError> {
6703
- self.view().try_sum_all()
6704
- }
6705
-
6706
- fn try_sum_axis<AnyIndex: VectorIndex>(
6707
- &self,
6708
- axis: AnyIndex,
6709
- keep_dims: bool,
6710
- ) -> Result<Tensor<Scalar::SumOutput, Global, MAX_RANK>, TensorError> {
6711
- self.view().try_sum_axis(axis, keep_dims)
6712
- }
6713
-
6714
- fn try_sum_axis_into<AnyIndex: VectorIndex>(
6715
- &self,
6716
- axis: AnyIndex,
6717
- keep_dims: bool,
6718
- out: &mut Tensor<Scalar::SumOutput, Global, MAX_RANK>,
6719
- ) -> Result<(), TensorError> {
6720
- self.view().try_sum_axis_into(axis, keep_dims, out)
6721
- }
6722
-
6723
- fn try_norm_all(&self) -> Result<f64, TensorError> {
6724
- self.view().try_norm_all()
6725
- }
6726
-
6727
- fn try_norm_axis<AnyIndex: VectorIndex>(
6728
- &self,
6729
- axis: AnyIndex,
6730
- keep_dims: bool,
6731
- ) -> Result<Tensor<f64, Global, MAX_RANK>, TensorError> {
6732
- self.view().try_norm_axis(axis, keep_dims)
6733
- }
6734
-
6735
- fn try_norm_axis_into<AnyIndex: VectorIndex>(
6736
- &self,
6737
- axis: AnyIndex,
6738
- keep_dims: bool,
6739
- out: &mut Tensor<f64, Global, MAX_RANK>,
6740
- ) -> Result<(), TensorError> {
6741
- self.view().try_norm_axis_into(axis, keep_dims, out)
6742
- }
6743
- }
6744
-
6745
- impl<Scalar: Clone + ReduceMoments, const R: usize, C: TensorRef<Scalar, R>> MomentsOps<Scalar, R>
6746
- for C
6747
- where
6748
- Scalar::SumOutput: Clone + Default + core::ops::AddAssign,
6749
- Scalar::SumSqOutput: Clone + Default + core::ops::AddAssign + SumSqToF64,
6750
- {
6751
- }
6752
-
6753
- /// Extension trait: min/max reductions for any [`TensorRef`] implementor.
6754
- pub trait MinMaxOps<Scalar: Clone + ReduceMinMax, const MAX_RANK: usize>:
6755
- TensorRef<Scalar, MAX_RANK>
6756
- where
6757
- Scalar::Output: Clone + Default + PartialOrd,
6758
- {
6759
- fn try_minmax_all(&self) -> Result<MinMaxResult<Scalar::Output>, TensorError> {
6760
- self.view().try_minmax_all()
6761
- }
6762
-
6763
- fn try_minmax_axis<AnyIndex: VectorIndex>(
6764
- &self,
6765
- axis: AnyIndex,
6766
- keep_dims: bool,
6767
- ) -> MinMaxAxisResult<Scalar, MAX_RANK> {
6768
- self.view().try_minmax_axis(axis, keep_dims)
6769
- }
6770
-
6771
- fn try_minmax_axis_into<AnyIndex: VectorIndex>(
6772
- &self,
6773
- axis: AnyIndex,
6774
- keep_dims: bool,
6775
- min_out: &mut Tensor<Scalar::Output, Global, MAX_RANK>,
6776
- argmin_out: &mut Tensor<usize, Global, MAX_RANK>,
6777
- max_out: &mut Tensor<Scalar::Output, Global, MAX_RANK>,
6778
- argmax_out: &mut Tensor<usize, Global, MAX_RANK>,
6779
- ) -> Result<(), TensorError> {
6780
- self.view()
6781
- .try_minmax_axis_into(axis, keep_dims, min_out, argmin_out, max_out, argmax_out)
6782
- }
6783
-
6784
- fn try_min_all(&self) -> Result<Scalar::Output, TensorError> {
6785
- self.view().try_min_all()
6786
- }
6787
-
6788
- fn try_argmin_all(&self) -> Result<usize, TensorError> {
6789
- self.view().try_argmin_all()
6790
- }
6791
-
6792
- fn try_max_all(&self) -> Result<Scalar::Output, TensorError> {
6793
- self.view().try_max_all()
6794
- }
6795
-
6796
- fn try_argmax_all(&self) -> Result<usize, TensorError> {
6797
- self.view().try_argmax_all()
6798
- }
6799
-
6800
- fn try_min_axis<AnyIndex: VectorIndex>(
6801
- &self,
6802
- axis: AnyIndex,
6803
- keep_dims: bool,
6804
- ) -> Result<Tensor<Scalar::Output, Global, MAX_RANK>, TensorError> {
6805
- self.view().try_min_axis(axis, keep_dims)
6806
- }
6807
-
6808
- fn try_argmin_axis<AnyIndex: VectorIndex>(
6809
- &self,
6810
- axis: AnyIndex,
6811
- keep_dims: bool,
6812
- ) -> Result<Tensor<usize, Global, MAX_RANK>, TensorError> {
6813
- self.view().try_argmin_axis(axis, keep_dims)
6814
- }
6815
-
6816
- fn try_max_axis<AnyIndex: VectorIndex>(
6817
- &self,
6818
- axis: AnyIndex,
6819
- keep_dims: bool,
6820
- ) -> Result<Tensor<Scalar::Output, Global, MAX_RANK>, TensorError> {
6821
- self.view().try_max_axis(axis, keep_dims)
6822
- }
6823
-
6824
- fn try_argmax_axis<AnyIndex: VectorIndex>(
6825
- &self,
6826
- axis: AnyIndex,
6827
- keep_dims: bool,
6828
- ) -> Result<Tensor<usize, Global, MAX_RANK>, TensorError> {
6829
- self.view().try_argmax_axis(axis, keep_dims)
6830
- }
6831
- }
6832
-
6833
- impl<Scalar: Clone + ReduceMinMax, const R: usize, C: TensorRef<Scalar, R>> MinMaxOps<Scalar, R>
6834
- for C
6835
- where
6836
- Scalar::Output: Clone + Default + PartialOrd,
6837
- {
6838
- }
6492
+ // MomentsOps and MinMaxOps moved to crate::reduce
6839
6493
 
6840
6494
  impl<const MAX_RANK: usize> Tensor<f32, Global, MAX_RANK> {
6841
6495
  /// Sum all elements of the tensor.
@@ -6858,30 +6512,38 @@ impl<const MAX_RANK: usize> Tensor<f64, Global, MAX_RANK> {
6858
6512
  #[cfg(test)]
6859
6513
  mod tests {
6860
6514
  use super::*;
6515
+ use crate::cast::CastOps;
6516
+ use crate::each::{AllCloseOps, ScaleOps, SumOps, TrigSinOps};
6517
+ use crate::reduce::MomentsOps;
6861
6518
  use crate::types::{bf16c, f16, f16c, f32c};
6862
6519
 
6863
6520
  #[test]
6864
- fn tensor_construction() {
6865
- // Creation
6521
+ fn tensor_creation_from_factories() {
6866
6522
  let arr = Tensor::<f32>::try_full(&[3, 4], 1.0f32).unwrap();
6867
6523
  assert_eq!(arr.shape(), &[3, 4]);
6868
6524
  assert_eq!(arr.ndim(), 2);
6869
6525
  assert_eq!(arr.numel(), 12);
6870
6526
  assert!(!arr.is_empty());
6527
+ }
6871
6528
 
6872
- // From slice
6529
+ #[test]
6530
+ fn tensor_from_slice() {
6873
6531
  let data: Vec<f32> = (0..12).map(|i| i as f32).collect();
6874
6532
  let arr = Tensor::<f32>::try_from_slice(&data, &[3, 4]).unwrap();
6875
6533
  assert_eq!(arr.shape(), &[3, 4]);
6876
6534
  assert_eq!(arr.as_slice(), &data[..]);
6535
+ }
6877
6536
 
6878
- // Clone
6537
+ #[test]
6538
+ fn tensor_clone() {
6879
6539
  let arr = Tensor::<f32>::try_full(&[3, 4], 2.5f32).unwrap();
6880
6540
  let cloned = arr.clone();
6881
6541
  assert_eq!(cloned.shape(), arr.shape());
6882
6542
  assert_eq!(cloned.as_slice(), arr.as_slice());
6543
+ }
6883
6544
 
6884
- // Error display
6545
+ #[test]
6546
+ fn tensor_error_display() {
6885
6547
  let err = TensorError::AllocationFailed;
6886
6548
  assert_eq!(format!("{}", err), "memory allocation failed");
6887
6549
  let err = TensorError::TooManyRanks { got: 10 };
@@ -6889,16 +6551,17 @@ mod tests {
6889
6551
  }
6890
6552
 
6891
6553
  #[test]
6892
- fn tensor_views() {
6893
- // Row access
6554
+ fn tensor_row_access() {
6894
6555
  let data: Vec<f32> = (0..12).map(|i| i as f32).collect();
6895
6556
  let arr = Tensor::<f32>::try_from_slice(&data, &[3, 4]).unwrap();
6896
6557
  assert_eq!(arr.row(0), Some(&[0.0, 1.0, 2.0, 3.0][..]));
6897
6558
  assert_eq!(arr.row(1), Some(&[4.0, 5.0, 6.0, 7.0][..]));
6898
6559
  assert_eq!(arr.row(2), Some(&[8.0, 9.0, 10.0, 11.0][..]));
6899
6560
  assert_eq!(arr.row(3), None);
6561
+ }
6900
6562
 
6901
- // Slicing
6563
+ #[test]
6564
+ fn tensor_slicing() {
6902
6565
  let arr = Tensor::<f32>::try_full(&[4, 5], 1.0f32).unwrap();
6903
6566
  let view = arr
6904
6567
  .slice(&[SliceRange::full(), SliceRange::full()])
@@ -6913,19 +6576,25 @@ mod tests {
6913
6576
  .unwrap();
6914
6577
  assert_eq!(view.shape(), &[5]);
6915
6578
  assert_eq!(view.ndim(), 1);
6579
+ }
6916
6580
 
6917
- // Transpose
6581
+ #[test]
6582
+ fn tensor_transpose_2d() {
6918
6583
  let arr = Tensor::<f32>::try_full(&[3, 4], 1.0f32).unwrap();
6919
6584
  let transposed = arr.transpose().unwrap();
6920
6585
  assert_eq!(transposed.shape(), &[4, 3]);
6586
+ }
6921
6587
 
6922
- // Contiguous check
6588
+ #[test]
6589
+ fn tensor_is_contiguous() {
6923
6590
  let arr = Tensor::<f32>::try_full(&[3, 4], 1.0f32).unwrap();
6924
6591
  let view = arr.view();
6925
6592
  assert!(view.is_contiguous());
6926
6593
  assert!(arr.has_contiguous_rows());
6594
+ }
6927
6595
 
6928
- // Matrix alias
6596
+ #[test]
6597
+ fn matrix_alias_round_trip() {
6929
6598
  let mat: Matrix<f32> = Matrix::try_full(&[3, 4], 1.0f32).unwrap();
6930
6599
  assert_eq!(mat.shape(), &[3, 4]);
6931
6600
  }
@@ -7002,105 +6671,6 @@ mod tests {
7002
6671
  assert!((sum - 100.0).abs() < 1e-9);
7003
6672
  }
7004
6673
 
7005
- #[test]
7006
- fn elementwise_and_cast() {
7007
- let data: Vec<f32> = (0..12).map(|i| i as f32).collect();
7008
- let left = Tensor::<f32>::try_from_slice(&data, &[3, 4]).unwrap();
7009
- let right = Tensor::<f32>::try_full(&[3, 4], 2.0).unwrap();
7010
-
7011
- let left_even = left
7012
- .slice(&[SliceRange::full(), SliceRange::range_step(0, 4, 2)])
7013
- .unwrap();
7014
- let right_even = right
7015
- .slice(&[SliceRange::full(), SliceRange::range_step(0, 4, 2)])
7016
- .unwrap();
7017
-
7018
- let added = left_even.try_add_tensor(&right_even).unwrap();
7019
- assert_eq!(added.shape(), &[3, 2]);
7020
- assert_eq!(added.as_slice(), &[2.0, 4.0, 6.0, 8.0, 10.0, 12.0]);
7021
-
7022
- let scaled = left_even.try_mul_scalar(0.5).unwrap();
7023
- assert_eq!(scaled.as_slice(), &[0.0, 1.0, 2.0, 3.0, 4.0, 5.0]);
7024
-
7025
- let casted = left_even.try_cast_dtype::<f64>().unwrap();
7026
- assert_eq!(casted.shape(), &[3, 2]);
7027
- assert_eq!(casted.as_slice(), &[0.0, 2.0, 4.0, 6.0, 8.0, 10.0]);
7028
-
7029
- let complex = left_even.try_cast_dtype::<f32c>().unwrap();
7030
- assert_eq!(complex.shape(), &[3, 2]);
7031
- assert_eq!(complex.as_slice()[0], f32c::from_real_imag(0.0, 0.0));
7032
- assert_eq!(complex.as_slice()[5], f32c::from_real_imag(10.0, 0.0));
7033
-
7034
- let mut out = Tensor::<f32>::try_full(&[3, 4], 0.0).unwrap();
7035
- left.try_add_tensor_into(&right, &mut out).unwrap();
7036
- assert_eq!(out.as_slice()[0], 2.0);
7037
- assert_eq!(out.as_slice()[11], 13.0);
7038
-
7039
- let mut inplace = Tensor::<f32>::try_from_slice(&data, &[3, 4]).unwrap();
7040
- inplace.try_add_scalar_inplace(1.0).unwrap();
7041
- assert_eq!(inplace.as_slice()[0], 1.0);
7042
- assert_eq!(inplace.as_slice()[11], 12.0);
7043
-
7044
- let mut trig_out = Tensor::<f32>::try_full(&[3, 2], 0.0).unwrap();
7045
- {
7046
- let mut span = trig_out.span();
7047
- left_even.try_sin_into(&mut span).unwrap();
7048
- }
7049
- assert_eq!(trig_out.shape(), &[3, 2]);
7050
- assert!((trig_out.as_slice()[0] - 0.0).abs() < 1e-6);
7051
- }
7052
-
7053
- #[test]
7054
- fn reductions_axis_and_strided_views() {
7055
- let data: Vec<f32> = (0..12).map(|i| i as f32).collect();
7056
- let a = Tensor::<f32>::try_from_slice(&data, &[3, 4]).unwrap();
7057
- let a_even = a
7058
- .slice(&[SliceRange::full(), SliceRange::range_step(0, 4, 2)])
7059
- .unwrap();
7060
-
7061
- let sum_all = a_even.try_sum_all().unwrap();
7062
- assert!((sum_all - 30.0).abs() < 1e-6);
7063
-
7064
- let norm_all = a_even.try_norm_all().unwrap();
7065
- assert!((norm_all - 14.832396974191326).abs() < 1e-9);
7066
-
7067
- let (sum_axis0, sumsq_axis0) = a_even.try_moments_axis(0, false).unwrap();
7068
- assert_eq!(sum_axis0.shape(), &[2]);
7069
- assert!((sum_axis0.as_slice()[0] - 12.0).abs() < 1e-6);
7070
- assert!((sum_axis0.as_slice()[1] - 18.0).abs() < 1e-6);
7071
- assert!((sumsq_axis0.as_slice()[0] - 80.0).abs() < 1e-6);
7072
- assert!((sumsq_axis0.as_slice()[1] - 140.0).abs() < 1e-6);
7073
-
7074
- let sum_axis1_keep = a_even.try_sum_axis(-1_i32, true).unwrap();
7075
- assert_eq!(sum_axis1_keep.shape(), &[3, 1]);
7076
- assert!((sum_axis1_keep.as_slice()[0] - 2.0).abs() < 1e-6);
7077
- assert!((sum_axis1_keep.as_slice()[1] - 10.0).abs() < 1e-6);
7078
- assert!((sum_axis1_keep.as_slice()[2] - 18.0).abs() < 1e-6);
7079
-
7080
- let MinMaxResult {
7081
- min_value: min_axis0,
7082
- min_index: argmin_axis0,
7083
- max_value: max_axis0,
7084
- max_index: argmax_axis0,
7085
- } = a_even.try_minmax_axis(0, false).unwrap();
7086
- assert_eq!(min_axis0.as_slice(), &[0.0, 2.0]);
7087
- assert_eq!(max_axis0.as_slice(), &[8.0, 10.0]);
7088
- assert_eq!(argmin_axis0.as_slice(), &[0, 0]);
7089
- assert_eq!(argmax_axis0.as_slice(), &[2, 2]);
7090
-
7091
- let reversed = a
7092
- .slice(&[SliceRange::full(), SliceRange::range_step(3, 0, -1)])
7093
- .unwrap();
7094
- let reversed_sum = reversed.try_sum_axis(-1_i32, false).unwrap();
7095
- assert_eq!(reversed_sum.shape(), &[3]);
7096
- assert_eq!(reversed_sum.as_slice(), &[6.0, 18.0, 30.0]);
7097
-
7098
- let reversed_argmin = reversed.try_argmin_axis(-1_i32, false).unwrap();
7099
- let reversed_argmax = reversed.try_argmax_axis(-1_i32, false).unwrap();
7100
- assert_eq!(reversed_argmin.as_slice(), &[2, 2, 2]);
7101
- assert_eq!(reversed_argmax.as_slice(), &[0, 0, 0]);
7102
- }
7103
-
7104
6674
  #[test]
7105
6675
  fn nd_contraction_global_reductions() {
7106
6676
  // Verify that N-D contiguous tensors produce the same moments/minmax as a flat copy.
@@ -7560,6 +7130,44 @@ mod tests {
7560
7130
  assert_eq!(tensor.shape(), &[2, 4]);
7561
7131
  assert_eq!(tensor.storage_len(), 4);
7562
7132
  }
7133
+
7134
+ #[test]
7135
+ fn fill_zeros_and_fill_on_tensor_and_span() {
7136
+ // Tensor: fill with zero, then fill with 7.0, verify storage matches.
7137
+ let mut tensor = unsafe { Tensor::<f32>::try_empty(&[3, 4]).unwrap() };
7138
+ tensor.fill_zeros();
7139
+ assert!(tensor.as_slice().iter().all(|&value| value == 0.0));
7140
+ tensor.fill(7.0);
7141
+ assert!(tensor.as_slice().iter().all(|&value| value == 7.0));
7142
+
7143
+ // TensorSpan via tensor.span() also exposes Fill methods.
7144
+ let mut other = unsafe { Tensor::<f32>::try_empty(&[3, 4]).unwrap() };
7145
+ {
7146
+ let mut span = other.span();
7147
+ span.fill_zeros();
7148
+ span.fill(2.5);
7149
+ }
7150
+ assert!(other.as_slice().iter().all(|&value| value == 2.5));
7151
+ }
7152
+
7153
+ #[test]
7154
+ fn copy_from_via_trait_round_trips_through_span() {
7155
+ let source_tensor = Tensor::<f32>::try_from_slice(&[1.0, 2.0, 3.0, 4.0], &[2, 2]).unwrap();
7156
+ let mut destination_tensor = unsafe { Tensor::<f32>::try_empty(&[2, 2]).unwrap() };
7157
+ // Tensor: CopyFrom<&[Scalar]>.
7158
+ destination_tensor.copy_from(source_tensor.as_slice()).unwrap();
7159
+ assert_eq!(destination_tensor.as_slice(), source_tensor.as_slice());
7160
+
7161
+ // TensorSpan: CopyFrom<&TensorView>.
7162
+ let mut span_destination = unsafe { Tensor::<f32>::try_empty(&[2, 2]).unwrap() };
7163
+ {
7164
+ let mut span = span_destination.span();
7165
+ let view = source_tensor.view();
7166
+ span.copy_from(&view).unwrap();
7167
+ }
7168
+ assert_eq!(span_destination.as_slice(), source_tensor.as_slice());
7169
+ }
7170
+
7563
7171
  }
7564
7172
 
7565
7173
  // endregion: Tests