usearch 2.26.1 → 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 (146) hide show
  1. package/README.md +1 -1
  2. package/include/usearch/index.hpp +5 -1
  3. package/numkong/CITATION.cff +13 -9
  4. package/numkong/CONTRIBUTING.md +29 -24
  5. package/numkong/Cargo.toml +1 -1
  6. package/numkong/README.md +21 -7
  7. package/numkong/binding.gyp +47 -31
  8. package/numkong/build.rs +15 -5
  9. package/numkong/c/dispatch.h +0 -1
  10. package/numkong/c/parallel.c +139 -0
  11. package/numkong/c/parallel.h +47 -0
  12. package/numkong/cmake/toolchain-wasi.cmake +39 -5
  13. package/numkong/include/README.md +24 -17
  14. package/numkong/include/numkong/attention/sapphireamx.h +6 -6
  15. package/numkong/include/numkong/attention/sme.h +2 -2
  16. package/numkong/include/numkong/capabilities.h +2 -2
  17. package/numkong/include/numkong/cast/README.md +9 -7
  18. package/numkong/include/numkong/cast/loongsonasx.h +48 -40
  19. package/numkong/include/numkong/cast/serial.h +89 -11
  20. package/numkong/include/numkong/cast.h +3 -3
  21. package/numkong/include/numkong/curved/README.md +10 -10
  22. package/numkong/include/numkong/curved/neon.h +1 -0
  23. package/numkong/include/numkong/curved/neonbfdot.h +1 -0
  24. package/numkong/include/numkong/curved/smef64.h +1 -0
  25. package/numkong/include/numkong/dot/README.md +18 -18
  26. package/numkong/include/numkong/dot/alder.h +8 -8
  27. package/numkong/include/numkong/dot/haswell.h +8 -8
  28. package/numkong/include/numkong/dot/icelake.h +12 -12
  29. package/numkong/include/numkong/dot/loongsonasx.h +6 -6
  30. package/numkong/include/numkong/dot/neon.h +2 -1
  31. package/numkong/include/numkong/dot/neonbfdot.h +1 -0
  32. package/numkong/include/numkong/dot/neonfhm.h +1 -0
  33. package/numkong/include/numkong/dot/neonsdot.h +1 -0
  34. package/numkong/include/numkong/dot/powervsx.h +3 -3
  35. package/numkong/include/numkong/dot/serial.h +19 -6
  36. package/numkong/include/numkong/dot/skylake.h +4 -4
  37. package/numkong/include/numkong/dot/svehalf.h +1 -1
  38. package/numkong/include/numkong/dot/v128relaxed.h +24 -23
  39. package/numkong/include/numkong/dots/README.md +26 -26
  40. package/numkong/include/numkong/dots/genoa.h +3 -1
  41. package/numkong/include/numkong/dots/icelake.h +1 -0
  42. package/numkong/include/numkong/dots/powervsx.h +1 -1
  43. package/numkong/include/numkong/dots/serial.h +32 -33
  44. package/numkong/include/numkong/dots/sierra.h +4 -3
  45. package/numkong/include/numkong/each/README.md +5 -3
  46. package/numkong/include/numkong/each/sapphire.h +2 -0
  47. package/numkong/include/numkong/each/skylake.h +1 -0
  48. package/numkong/include/numkong/geospatial/README.md +6 -6
  49. package/numkong/include/numkong/geospatial/v128relaxed.h +1 -0
  50. package/numkong/include/numkong/maxsim/README.md +5 -5
  51. package/numkong/include/numkong/maxsim/genoa.h +4 -0
  52. package/numkong/include/numkong/maxsim/icelake.h +1 -0
  53. package/numkong/include/numkong/maxsim/serial.h +22 -9
  54. package/numkong/include/numkong/maxsim/sme.h +3 -2
  55. package/numkong/include/numkong/mesh/README.md +7 -4
  56. package/numkong/include/numkong/mesh/genoa.h +2 -2
  57. package/numkong/include/numkong/mesh/haswell.h +8 -8
  58. package/numkong/include/numkong/mesh/neon.h +6 -6
  59. package/numkong/include/numkong/mesh/neonbfdot.h +3 -2
  60. package/numkong/include/numkong/mesh/neonfhm.h +3 -2
  61. package/numkong/include/numkong/mesh/rvv.h +4 -4
  62. package/numkong/include/numkong/mesh/serial.h +17 -17
  63. package/numkong/include/numkong/mesh/skylake.h +4 -4
  64. package/numkong/include/numkong/mesh/v128relaxed.h +4 -4
  65. package/numkong/include/numkong/mesh.hpp +2 -2
  66. package/numkong/include/numkong/numkong.h +1 -1
  67. package/numkong/include/numkong/numkong.hpp +1 -1
  68. package/numkong/include/numkong/probability/README.md +17 -15
  69. package/numkong/include/numkong/probability/skylake.h +1 -1
  70. package/numkong/include/numkong/reduce/README.md +27 -21
  71. package/numkong/include/numkong/reduce/genoa.h +2 -1
  72. package/numkong/include/numkong/reduce/icelake.h +1 -0
  73. package/numkong/include/numkong/reduce/neon.h +1 -1
  74. package/numkong/include/numkong/reduce/serial.h +9 -9
  75. package/numkong/include/numkong/reduce/skylake.h +4 -4
  76. package/numkong/include/numkong/reduce.hpp +38 -0
  77. package/numkong/include/numkong/scalar/README.md +29 -28
  78. package/numkong/include/numkong/set/README.md +17 -14
  79. package/numkong/include/numkong/set/haswell.h +15 -16
  80. package/numkong/include/numkong/set/icelake.h +15 -16
  81. package/numkong/include/numkong/set/loongsonasx.h +12 -10
  82. package/numkong/include/numkong/set/neon.h +15 -19
  83. package/numkong/include/numkong/set/powervsx.h +15 -21
  84. package/numkong/include/numkong/set/serial.h +19 -20
  85. package/numkong/include/numkong/set/v128relaxed.h +10 -8
  86. package/numkong/include/numkong/sets/icelake.h +1 -0
  87. package/numkong/include/numkong/sparse/README.md +4 -4
  88. package/numkong/include/numkong/sparse/icelake.h +1 -0
  89. package/numkong/include/numkong/sparse/neon.h +1 -0
  90. package/numkong/include/numkong/sparse/serial.h +10 -3
  91. package/numkong/include/numkong/sparse/turin.h +1 -0
  92. package/numkong/include/numkong/spatial/README.md +25 -24
  93. package/numkong/include/numkong/spatial/alder.h +2 -1
  94. package/numkong/include/numkong/spatial/haswell.h +48 -40
  95. package/numkong/include/numkong/spatial/icelake.h +2 -1
  96. package/numkong/include/numkong/spatial/neon.h +54 -46
  97. package/numkong/include/numkong/spatial/neonbfdot.h +1 -1
  98. package/numkong/include/numkong/spatial/powervsx.h +54 -46
  99. package/numkong/include/numkong/spatial/serial.h +56 -46
  100. package/numkong/include/numkong/spatial/sierra.h +4 -3
  101. package/numkong/include/numkong/spatial/skylake.h +27 -22
  102. package/numkong/include/numkong/spatial/sve.h +6 -6
  103. package/numkong/include/numkong/spatial/svebfdot.h +1 -1
  104. package/numkong/include/numkong/spatial/svehalf.h +1 -1
  105. package/numkong/include/numkong/spatial/v128relaxed.h +36 -30
  106. package/numkong/include/numkong/spatials/README.md +15 -15
  107. package/numkong/include/numkong/spatials/genoa.h +3 -0
  108. package/numkong/include/numkong/spatials/graniteamx.h +2 -0
  109. package/numkong/include/numkong/spatials/icelake.h +1 -0
  110. package/numkong/include/numkong/spatials/sierra.h +1 -0
  111. package/numkong/include/numkong/tensor.hpp +236 -123
  112. package/numkong/include/numkong/trigonometry/README.md +4 -4
  113. package/numkong/include/numkong/trigonometry/serial.h +6 -6
  114. package/numkong/include/numkong/trigonometry/v128relaxed.h +1 -0
  115. package/numkong/include/numkong/types.hpp +46 -16
  116. package/numkong/include/numkong/vector.hpp +116 -23
  117. package/numkong/javascript/@numkong-darwin-arm64/package.json +1 -1
  118. package/numkong/javascript/@numkong-darwin-x64/package.json +1 -1
  119. package/numkong/javascript/@numkong-linux-arm64/package.json +1 -1
  120. package/numkong/javascript/@numkong-linux-x64/package.json +1 -1
  121. package/numkong/javascript/@numkong-win32-arm64/package.json +1 -1
  122. package/numkong/javascript/@numkong-win32-x64/package.json +1 -1
  123. package/numkong/javascript/README.md +18 -16
  124. package/numkong/javascript/numkong.c +70 -40
  125. package/numkong/package.json +8 -8
  126. package/numkong/rust/README.md +19 -9
  127. package/numkong/rust/cast.rs +52 -1
  128. package/numkong/rust/each.rs +480 -7
  129. package/numkong/rust/matrix.rs +877 -216
  130. package/numkong/rust/numkong.rs +12 -8
  131. package/numkong/rust/reduce.rs +433 -5
  132. package/numkong/rust/tensor.rs +450 -842
  133. package/numkong/rust/vector.rs +520 -101
  134. package/numkong/swift/README.md +5 -3
  135. package/numkong/test/README.md +4 -4
  136. package/numkong/test/test-wasi.mjs +17 -0
  137. package/numkong/test/test_each.cpp +30 -1
  138. package/numkong/test/test_each.py +74 -1
  139. package/numkong/test/test_reduce.cpp +51 -0
  140. package/numkong/test/test_tensor.cpp +2 -63
  141. package/numkong/test/test_tensor.py +93 -0
  142. package/package.json +1 -1
  143. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  144. package/prebuilds/linux-arm64/usearch.node +0 -0
  145. package/prebuilds/linux-x64/usearch.node +0 -0
  146. package/prebuilds/win32-x64/usearch.node +0 -0
@@ -141,10 +141,15 @@ pub use set::{BinarySimilarity, Hamming, Jaccard};
141
141
  pub use probability::{JensenShannon, KullbackLeibler, ProbabilitySimilarity};
142
142
 
143
143
  // Re-export elementwise and trig traits
144
- pub use each::{EachATan, EachBlend, EachCos, EachFMA, EachScale, EachSin, EachSum, Trigonometry};
144
+ pub use each::{
145
+ AllCloseOps, BlendOps, EachATan, EachBlend, EachCos, EachFMA, EachScale, EachSin, EachSum,
146
+ FmaOps, ScaleOps, SumOps, TrigAtanOps, TrigCosOps, TrigSinOps, Trigonometry,
147
+ };
145
148
 
146
149
  // Re-export reduction traits
147
- pub use reduce::{ReduceMinMax, ReduceMoments, Reductions};
150
+ pub use reduce::{
151
+ BitwiseReductions, MinMaxOps, MomentsOps, ReduceMinMax, ReduceMoments, Reductions, SumSqToF64,
152
+ };
148
153
 
149
154
  // Re-export curved metric traits
150
155
  pub use curved::{Bilinear, Mahalanobis};
@@ -159,7 +164,7 @@ pub use geospatial::{Geospatial, Haversine, Vincenty};
159
164
  pub use sparse::{SparseDot, SparseIntersect};
160
165
 
161
166
  // Re-export cast operations
162
- pub use cast::{cast, CastDtype};
167
+ pub use cast::{cast, CastDtype, CastOps};
163
168
 
164
169
  // Re-export capabilities
165
170
  pub use capabilities::cap;
@@ -167,11 +172,10 @@ pub use capabilities::{available, configure_thread, uses_dynamic_dispatch};
167
172
 
168
173
  // Re-export tensor types
169
174
  pub use tensor::{
170
- AllCloseOps, Allocator, AxisIterator, AxisIteratorMut, BlendOps, CastOps, FmaOps, Global,
171
- Matrix, MatrixSpan, MatrixView, MinMaxOps, MinMaxResult, MomentsOps, RangeStep, ScaleOps,
172
- SliceArg, SliceRange, SliceSpec, SumOps, Tensor, TensorDims, TensorError, TensorIterator,
173
- TensorMut, TensorRef, TensorSpan, TensorSpanDims, TensorSpanIterator, TensorView,
174
- TensorViewDims, TensorViewIterator, TrigAtanOps, TrigCosOps, TrigSinOps, DEFAULT_MAX_RANK,
175
+ Allocator, AxisIterator, AxisIteratorMut, CopyFrom, Fill, Global, Matrix, MatrixSpan,
176
+ MatrixView, MinMaxResult, RangeStep, SliceArg, SliceRange, SliceSpec, Tensor, TensorDims,
177
+ TensorError, TensorIterator, TensorMut, TensorRef, TensorSpan, TensorSpanDims,
178
+ TensorSpanIterator, TensorView, TensorViewDims, TensorViewIterator, DEFAULT_MAX_RANK,
175
179
  SIMD_ALIGNMENT,
176
180
  };
177
181
 
@@ -1,12 +1,24 @@
1
- //! Statistical reductions: moments (sum/sum-of-squares) and min/max.
1
+ //! Statistical reductions: moments, min/max, and bit-population counts.
2
2
  //!
3
3
  //! This module provides:
4
4
  //!
5
- //! - [`ReduceMoments`]: Sum and sum-of-squares over a strided slice
6
- //! - [`ReduceMinMax`]: Minimum and maximum over a strided slice
7
- //! - [`Reductions`]: Blanket trait combining `ReduceMoments + ReduceMinMax`
8
-
5
+ //! - Slice-level traits:
6
+ //! - [`ReduceMoments`]: Sum and sum-of-squares over a strided slice
7
+ //! - [`ReduceMinMax`]: Minimum and maximum over a strided slice
8
+ //! - [`Reductions`]: Blanket trait combining `ReduceMoments + ReduceMinMax`
9
+ //! - Tensor-shaped extension traits (auto-implemented on every [`crate::tensor::TensorRef`]):
10
+ //! - [`MomentsOps`]: Per-axis and full-tensor moments / sums / norms
11
+ //! - [`MinMaxOps`]: Per-axis and full-tensor min / max / argmin / argmax
12
+ //! - [`BitwiseReductions`]: `popcount` / `any_set` / `none_set` / `all_set` for `u1x8` tensors;
13
+ //! `Vector<u1x8>` / `VectorView<u1x8>` / `VectorSpan<u1x8>` expose the same method names as
14
+ //! concrete impls (defined in [`crate::vector`]) until a `VectorRef` trait exists
15
+ //! - [`SumSqToF64`]: Helper bound used by `MomentsOps::try_norm_*` to convert sum-of-squares to f64
16
+
17
+ use crate::tensor::{
18
+ Global, MinMaxAxisResult, MinMaxResult, MomentsAxisResult, Tensor, TensorError, TensorRef,
19
+ };
9
20
  use crate::types::{bf16, e2m3, e3m2, e4m3, e5m2, f16, i4x2, u1x8, u4x2, StorageElement};
21
+ use crate::vector::VectorIndex;
10
22
 
11
23
  #[link(name = "numkong")]
12
24
  extern "C" {
@@ -1046,6 +1058,284 @@ impl ReduceMinMax for u1x8 {
1046
1058
  pub trait Reductions: ReduceMoments + ReduceMinMax {}
1047
1059
  impl<Scalar: ReduceMoments + ReduceMinMax> Reductions for Scalar {}
1048
1060
 
1061
+ /// Walks a `u1x8` tensor view, accumulating set-bit counts via
1062
+ /// [`u1x8::reduce_moments`] on each rank-1 contiguous lane.
1063
+ ///
1064
+ /// At rank ≤ 1 the view's logical bit count divides cleanly into a contiguous
1065
+ /// storage slice (sub-byte stride is always one byte per `u1x8`, so rank-1 is
1066
+ /// trivially contiguous). At higher ranks we recurse on the leading axis via
1067
+ /// the `TensorRef`-supplied structural accessors so that matrices and higher-rank
1068
+ /// tensors of bits work without flattening first. Non-contiguous outer strides
1069
+ /// fall out of the recursion naturally — only the leaf storage slice has to be
1070
+ /// dense, and sub-byte storage always is.
1071
+ fn popcount_via_tensor_ref<View, const MAX_RANK: usize>(view: &View) -> u64
1072
+ where
1073
+ View: TensorRef<u1x8, MAX_RANK> + ?Sized,
1074
+ {
1075
+ let dims_per_value = u1x8::dimensions_per_value();
1076
+ let logical_count: usize = view.shape().iter().product();
1077
+ if logical_count == 0 {
1078
+ return 0;
1079
+ }
1080
+ if view.ndim() <= 1 {
1081
+ let storage_count = logical_count / dims_per_value;
1082
+ if storage_count == 0 {
1083
+ return 0;
1084
+ }
1085
+ let storage_slice = unsafe { core::slice::from_raw_parts(view.as_ptr(), storage_count) };
1086
+ let (sum, _sum_of_squares) =
1087
+ u1x8::reduce_moments(storage_slice, core::mem::size_of::<u1x8>());
1088
+ return sum;
1089
+ }
1090
+ let mut total: u64 = 0;
1091
+ let inner_view = view.view();
1092
+ if let Ok(axis_iter) = inner_view.axis_views(0usize) {
1093
+ for sub_view in axis_iter {
1094
+ total += popcount_via_tensor_ref::<_, MAX_RANK>(&sub_view);
1095
+ }
1096
+ }
1097
+ total
1098
+ }
1099
+
1100
+ /// Population count, any/none/all reductions over packed-bit (`u1x8`) tensors.
1101
+ ///
1102
+ /// Blanket-impl'd on every [`TensorRef<u1x8, R>`], so `Tensor<u1x8>`,
1103
+ /// `TensorView<u1x8>`, and `TensorSpan<u1x8>` all expose `.popcount()` /
1104
+ /// `.any_set()` / `.none_set()` / `.all_set()` with identical spellings.
1105
+ /// Mirrors `numkong::popcount(tensor_view)` in `include/numkong/reduce.hpp`.
1106
+ ///
1107
+ /// Method names use the `_set` suffix to leave room for a future generic
1108
+ /// `any` / `none` / `all` over arbitrary scalar predicates and to avoid
1109
+ /// colliding with the `all` slice marker exported from [`crate::vector`].
1110
+ pub trait BitwiseReductions<const MAX_RANK: usize>: TensorRef<u1x8, MAX_RANK> {
1111
+ /// Number of set bits across the entire tensor.
1112
+ fn popcount(&self) -> u64 {
1113
+ popcount_via_tensor_ref::<_, MAX_RANK>(self)
1114
+ }
1115
+
1116
+ /// `true` if at least one bit in the tensor is set.
1117
+ fn any_set(&self) -> bool {
1118
+ self.popcount() != 0
1119
+ }
1120
+
1121
+ /// `true` if no bit in the tensor is set.
1122
+ fn none_set(&self) -> bool {
1123
+ !self.any_set()
1124
+ }
1125
+
1126
+ /// `true` if every bit in the tensor is set.
1127
+ fn all_set(&self) -> bool {
1128
+ self.popcount() == self.numel() as u64
1129
+ }
1130
+ }
1131
+
1132
+ impl<Container, const MAX_RANK: usize> BitwiseReductions<MAX_RANK> for Container where
1133
+ Container: TensorRef<u1x8, MAX_RANK> + ?Sized
1134
+ {
1135
+ }
1136
+
1137
+ // region: SumSqToF64 helper trait (moved from crate::tensor)
1138
+
1139
+ #[doc(hidden)]
1140
+ pub trait SumSqToF64 {
1141
+ fn to_f64(self) -> f64;
1142
+ }
1143
+
1144
+ impl SumSqToF64 for f32 {
1145
+ fn to_f64(self) -> f64 {
1146
+ self as f64
1147
+ }
1148
+ }
1149
+ impl SumSqToF64 for f64 {
1150
+ fn to_f64(self) -> f64 {
1151
+ self
1152
+ }
1153
+ }
1154
+ impl SumSqToF64 for u64 {
1155
+ fn to_f64(self) -> f64 {
1156
+ self as f64
1157
+ }
1158
+ }
1159
+ impl SumSqToF64 for i64 {
1160
+ fn to_f64(self) -> f64 {
1161
+ self as f64
1162
+ }
1163
+ }
1164
+
1165
+ // endregion: SumSqToF64
1166
+
1167
+ // region: MomentsOps / MinMaxOps (moved from crate::tensor)
1168
+
1169
+ /// Extension trait: statistical reductions for any [`TensorRef`] implementor.
1170
+ pub trait MomentsOps<Scalar: Clone + ReduceMoments, const MAX_RANK: usize>:
1171
+ TensorRef<Scalar, MAX_RANK>
1172
+ where
1173
+ Scalar::SumOutput: Clone + Default + core::ops::AddAssign,
1174
+ Scalar::SumSqOutput: Clone + Default + core::ops::AddAssign + SumSqToF64,
1175
+ {
1176
+ fn try_moments_all(&self) -> Result<(Scalar::SumOutput, Scalar::SumSqOutput), TensorError> {
1177
+ self.view().try_moments_all()
1178
+ }
1179
+
1180
+ fn try_moments_axis<AnyIndex: VectorIndex>(
1181
+ &self,
1182
+ axis: AnyIndex,
1183
+ keep_dims: bool,
1184
+ ) -> MomentsAxisResult<Scalar, MAX_RANK> {
1185
+ self.view().try_moments_axis(axis, keep_dims)
1186
+ }
1187
+
1188
+ fn try_moments_axis_into<AnyIndex: VectorIndex>(
1189
+ &self,
1190
+ axis: AnyIndex,
1191
+ keep_dims: bool,
1192
+ sum_out: &mut Tensor<Scalar::SumOutput, Global, MAX_RANK>,
1193
+ sumsq_out: &mut Tensor<Scalar::SumSqOutput, Global, MAX_RANK>,
1194
+ ) -> Result<(), TensorError> {
1195
+ self.view()
1196
+ .try_moments_axis_into(axis, keep_dims, sum_out, sumsq_out)
1197
+ }
1198
+
1199
+ fn try_sum_all(&self) -> Result<Scalar::SumOutput, TensorError> {
1200
+ self.view().try_sum_all()
1201
+ }
1202
+
1203
+ fn try_sum_axis<AnyIndex: VectorIndex>(
1204
+ &self,
1205
+ axis: AnyIndex,
1206
+ keep_dims: bool,
1207
+ ) -> Result<Tensor<Scalar::SumOutput, Global, MAX_RANK>, TensorError> {
1208
+ self.view().try_sum_axis(axis, keep_dims)
1209
+ }
1210
+
1211
+ fn try_sum_axis_into<AnyIndex: VectorIndex>(
1212
+ &self,
1213
+ axis: AnyIndex,
1214
+ keep_dims: bool,
1215
+ out: &mut Tensor<Scalar::SumOutput, Global, MAX_RANK>,
1216
+ ) -> Result<(), TensorError> {
1217
+ self.view().try_sum_axis_into(axis, keep_dims, out)
1218
+ }
1219
+
1220
+ fn try_norm_all(&self) -> Result<f64, TensorError> {
1221
+ self.view().try_norm_all()
1222
+ }
1223
+
1224
+ fn try_norm_axis<AnyIndex: VectorIndex>(
1225
+ &self,
1226
+ axis: AnyIndex,
1227
+ keep_dims: bool,
1228
+ ) -> Result<Tensor<f64, Global, MAX_RANK>, TensorError> {
1229
+ self.view().try_norm_axis(axis, keep_dims)
1230
+ }
1231
+
1232
+ fn try_norm_axis_into<AnyIndex: VectorIndex>(
1233
+ &self,
1234
+ axis: AnyIndex,
1235
+ keep_dims: bool,
1236
+ out: &mut Tensor<f64, Global, MAX_RANK>,
1237
+ ) -> Result<(), TensorError> {
1238
+ self.view().try_norm_axis_into(axis, keep_dims, out)
1239
+ }
1240
+ }
1241
+
1242
+ impl<Scalar: Clone + ReduceMoments, const R: usize, C: TensorRef<Scalar, R>> MomentsOps<Scalar, R>
1243
+ for C
1244
+ where
1245
+ Scalar::SumOutput: Clone + Default + core::ops::AddAssign,
1246
+ Scalar::SumSqOutput: Clone + Default + core::ops::AddAssign + SumSqToF64,
1247
+ {
1248
+ }
1249
+
1250
+ /// Extension trait: min/max reductions for any [`TensorRef`] implementor.
1251
+ pub trait MinMaxOps<Scalar: Clone + ReduceMinMax, const MAX_RANK: usize>:
1252
+ TensorRef<Scalar, MAX_RANK>
1253
+ where
1254
+ Scalar::Output: Clone + Default + PartialOrd,
1255
+ {
1256
+ fn try_minmax_all(&self) -> Result<MinMaxResult<Scalar::Output>, TensorError> {
1257
+ self.view().try_minmax_all()
1258
+ }
1259
+
1260
+ fn try_minmax_axis<AnyIndex: VectorIndex>(
1261
+ &self,
1262
+ axis: AnyIndex,
1263
+ keep_dims: bool,
1264
+ ) -> MinMaxAxisResult<Scalar, MAX_RANK> {
1265
+ self.view().try_minmax_axis(axis, keep_dims)
1266
+ }
1267
+
1268
+ fn try_minmax_axis_into<AnyIndex: VectorIndex>(
1269
+ &self,
1270
+ axis: AnyIndex,
1271
+ keep_dims: bool,
1272
+ min_out: &mut Tensor<Scalar::Output, Global, MAX_RANK>,
1273
+ argmin_out: &mut Tensor<usize, Global, MAX_RANK>,
1274
+ max_out: &mut Tensor<Scalar::Output, Global, MAX_RANK>,
1275
+ argmax_out: &mut Tensor<usize, Global, MAX_RANK>,
1276
+ ) -> Result<(), TensorError> {
1277
+ self.view()
1278
+ .try_minmax_axis_into(axis, keep_dims, min_out, argmin_out, max_out, argmax_out)
1279
+ }
1280
+
1281
+ fn try_min_all(&self) -> Result<Scalar::Output, TensorError> {
1282
+ self.view().try_min_all()
1283
+ }
1284
+
1285
+ fn try_argmin_all(&self) -> Result<usize, TensorError> {
1286
+ self.view().try_argmin_all()
1287
+ }
1288
+
1289
+ fn try_max_all(&self) -> Result<Scalar::Output, TensorError> {
1290
+ self.view().try_max_all()
1291
+ }
1292
+
1293
+ fn try_argmax_all(&self) -> Result<usize, TensorError> {
1294
+ self.view().try_argmax_all()
1295
+ }
1296
+
1297
+ fn try_min_axis<AnyIndex: VectorIndex>(
1298
+ &self,
1299
+ axis: AnyIndex,
1300
+ keep_dims: bool,
1301
+ ) -> Result<Tensor<Scalar::Output, Global, MAX_RANK>, TensorError> {
1302
+ self.view().try_min_axis(axis, keep_dims)
1303
+ }
1304
+
1305
+ fn try_argmin_axis<AnyIndex: VectorIndex>(
1306
+ &self,
1307
+ axis: AnyIndex,
1308
+ keep_dims: bool,
1309
+ ) -> Result<Tensor<usize, Global, MAX_RANK>, TensorError> {
1310
+ self.view().try_argmin_axis(axis, keep_dims)
1311
+ }
1312
+
1313
+ fn try_max_axis<AnyIndex: VectorIndex>(
1314
+ &self,
1315
+ axis: AnyIndex,
1316
+ keep_dims: bool,
1317
+ ) -> Result<Tensor<Scalar::Output, Global, MAX_RANK>, TensorError> {
1318
+ self.view().try_max_axis(axis, keep_dims)
1319
+ }
1320
+
1321
+ fn try_argmax_axis<AnyIndex: VectorIndex>(
1322
+ &self,
1323
+ axis: AnyIndex,
1324
+ keep_dims: bool,
1325
+ ) -> Result<Tensor<usize, Global, MAX_RANK>, TensorError> {
1326
+ self.view().try_argmax_axis(axis, keep_dims)
1327
+ }
1328
+ }
1329
+
1330
+ impl<Scalar: Clone + ReduceMinMax, const R: usize, C: TensorRef<Scalar, R>> MinMaxOps<Scalar, R>
1331
+ for C
1332
+ where
1333
+ Scalar::Output: Clone + Default + PartialOrd,
1334
+ {
1335
+ }
1336
+
1337
+ // endregion: MomentsOps / MinMaxOps
1338
+
1049
1339
  #[cfg(test)]
1050
1340
  mod tests {
1051
1341
  use super::*;
@@ -1246,4 +1536,142 @@ mod tests {
1246
1536
  }
1247
1537
 
1248
1538
  // endregion
1539
+
1540
+ // region: BitwiseReductions across containers
1541
+
1542
+ #[test]
1543
+ fn bitwise_popcount_across_tensor_shapes() {
1544
+ use crate::tensor::Tensor;
1545
+ // 16 bytes × 8 bits = 128 bits total. Alternating fully-set/zero bytes → popcount = 64.
1546
+ let bits_storage: Vec<u1x8> = (0..16u8)
1547
+ .map(|byte_index| if byte_index % 2 == 0 { u1x8(0xFFu8) } else { u1x8(0x00u8) })
1548
+ .collect();
1549
+ let mut tensor = Tensor::<u1x8>::try_zeros(&[4, 32]).unwrap();
1550
+ for (slot, value) in tensor.as_mut_slice().iter_mut().zip(bits_storage.iter()) {
1551
+ *slot = *value;
1552
+ }
1553
+
1554
+ // Same `.popcount()` spelling on Tensor and TensorView.
1555
+ assert_eq!(tensor.popcount(), 64);
1556
+ assert_eq!(tensor.view().popcount(), 64);
1557
+
1558
+ assert!(tensor.any_set());
1559
+ assert!(tensor.view().any_set());
1560
+
1561
+ assert!(!tensor.none_set());
1562
+ assert!(!tensor.view().none_set());
1563
+
1564
+ assert!(!tensor.all_set());
1565
+ assert!(!tensor.view().all_set());
1566
+
1567
+ // All-zeros tensor → none_set, !any_set, !all_set.
1568
+ let zero_tensor = Tensor::<u1x8>::try_zeros(&[4, 32]).unwrap();
1569
+ assert_eq!(zero_tensor.popcount(), 0);
1570
+ assert!(!zero_tensor.any_set());
1571
+ assert!(zero_tensor.none_set());
1572
+ assert!(!zero_tensor.all_set());
1573
+
1574
+ // All-ones tensor → all_set.
1575
+ let mut ones_tensor = Tensor::<u1x8>::try_zeros(&[4, 32]).unwrap();
1576
+ for slot in ones_tensor.as_mut_slice().iter_mut() {
1577
+ *slot = u1x8(0xFFu8);
1578
+ }
1579
+ assert_eq!(ones_tensor.popcount(), 128);
1580
+ assert!(ones_tensor.any_set());
1581
+ assert!(!ones_tensor.none_set());
1582
+ assert!(ones_tensor.all_set());
1583
+ }
1584
+
1585
+ #[test]
1586
+ fn bitwise_reductions_on_vector_containers() {
1587
+ use crate::vector::{Vector, VectorSpan, VectorView};
1588
+ // Three set bytes (24 bits), one zero byte → popcount = 24.
1589
+ let mut storage = [u1x8(0xFFu8), u1x8(0xFFu8), u1x8(0x00u8), u1x8(0xFFu8)];
1590
+ let mut vector = Vector::<u1x8>::try_zeros(32).unwrap();
1591
+ for (slot, value) in vector.as_mut_slice().iter_mut().zip(storage.iter()) {
1592
+ *slot = *value;
1593
+ }
1594
+ assert_eq!(vector.popcount(), 24);
1595
+ assert!(vector.any_set());
1596
+ assert!(!vector.none_set());
1597
+ assert!(!vector.all_set());
1598
+
1599
+ let view = unsafe {
1600
+ VectorView::<u1x8>::from_raw_parts(
1601
+ storage.as_ptr(),
1602
+ 32,
1603
+ core::mem::size_of::<u1x8>() as isize,
1604
+ )
1605
+ };
1606
+ assert_eq!(view.popcount(), 24);
1607
+ assert!(view.any_set());
1608
+
1609
+ let span = unsafe {
1610
+ VectorSpan::<u1x8>::from_raw_parts(
1611
+ storage.as_mut_ptr(),
1612
+ 32,
1613
+ core::mem::size_of::<u1x8>() as isize,
1614
+ )
1615
+ };
1616
+ assert_eq!(span.popcount(), 24);
1617
+ assert!(span.any_set());
1618
+ }
1619
+
1620
+ // endregion: BitwiseReductions across containers
1621
+
1622
+ // region: tensor-shaped MomentsOps / MinMaxOps wrappers
1623
+
1624
+ #[test]
1625
+ fn reductions_axis_and_strided_views() {
1626
+ use crate::tensor::{MinMaxResult, SliceRange, Tensor};
1627
+ let data: Vec<f32> = (0..12).map(|i| i as f32).collect();
1628
+ let a = Tensor::<f32>::try_from_slice(&data, &[3, 4]).unwrap();
1629
+ let a_even = a
1630
+ .slice(&[SliceRange::full(), SliceRange::range_step(0, 4, 2)])
1631
+ .unwrap();
1632
+
1633
+ let sum_all = a_even.try_sum_all().unwrap();
1634
+ assert!((sum_all - 30.0).abs() < 1e-6);
1635
+
1636
+ let norm_all = a_even.try_norm_all().unwrap();
1637
+ assert!((norm_all - 14.832396974191326).abs() < 1e-9);
1638
+
1639
+ let (sum_axis0, sumsq_axis0) = a_even.try_moments_axis(0, false).unwrap();
1640
+ assert_eq!(sum_axis0.shape(), &[2]);
1641
+ assert!((sum_axis0.as_slice()[0] - 12.0).abs() < 1e-6);
1642
+ assert!((sum_axis0.as_slice()[1] - 18.0).abs() < 1e-6);
1643
+ assert!((sumsq_axis0.as_slice()[0] - 80.0).abs() < 1e-6);
1644
+ assert!((sumsq_axis0.as_slice()[1] - 140.0).abs() < 1e-6);
1645
+
1646
+ let sum_axis1_keep = a_even.try_sum_axis(-1_i32, true).unwrap();
1647
+ assert_eq!(sum_axis1_keep.shape(), &[3, 1]);
1648
+ assert!((sum_axis1_keep.as_slice()[0] - 2.0).abs() < 1e-6);
1649
+ assert!((sum_axis1_keep.as_slice()[1] - 10.0).abs() < 1e-6);
1650
+ assert!((sum_axis1_keep.as_slice()[2] - 18.0).abs() < 1e-6);
1651
+
1652
+ let MinMaxResult {
1653
+ min_value: min_axis0,
1654
+ min_index: argmin_axis0,
1655
+ max_value: max_axis0,
1656
+ max_index: argmax_axis0,
1657
+ } = a_even.try_minmax_axis(0, false).unwrap();
1658
+ assert_eq!(min_axis0.as_slice(), &[0.0, 2.0]);
1659
+ assert_eq!(max_axis0.as_slice(), &[8.0, 10.0]);
1660
+ assert_eq!(argmin_axis0.as_slice(), &[0, 0]);
1661
+ assert_eq!(argmax_axis0.as_slice(), &[2, 2]);
1662
+
1663
+ let reversed = a
1664
+ .slice(&[SliceRange::full(), SliceRange::range_step(3, 0, -1)])
1665
+ .unwrap();
1666
+ let reversed_sum = reversed.try_sum_axis(-1_i32, false).unwrap();
1667
+ assert_eq!(reversed_sum.shape(), &[3]);
1668
+ assert_eq!(reversed_sum.as_slice(), &[6.0, 18.0, 30.0]);
1669
+
1670
+ let reversed_argmin = reversed.try_argmin_axis(-1_i32, false).unwrap();
1671
+ let reversed_argmax = reversed.try_argmax_axis(-1_i32, false).unwrap();
1672
+ assert_eq!(reversed_argmin.as_slice(), &[2, 2, 2]);
1673
+ assert_eq!(reversed_argmax.as_slice(), &[0, 0, 0]);
1674
+ }
1675
+
1676
+ // endregion: tensor-shaped MomentsOps / MinMaxOps wrappers
1249
1677
  }