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.
- package/README.md +2 -2
- package/cpp/test.cpp +66 -3
- package/include/usearch/index.hpp +9 -1
- package/include/usearch/index_dense.hpp +16 -5
- package/numkong/CITATION.cff +13 -9
- package/numkong/CONTRIBUTING.md +29 -24
- package/numkong/Cargo.toml +1 -1
- package/numkong/README.md +21 -7
- package/numkong/binding.gyp +47 -31
- package/numkong/build.rs +15 -5
- package/numkong/c/dispatch.h +0 -1
- package/numkong/c/parallel.c +139 -0
- package/numkong/c/parallel.h +47 -0
- package/numkong/cmake/toolchain-wasi.cmake +39 -5
- package/numkong/include/README.md +24 -17
- package/numkong/include/numkong/attention/sapphireamx.h +6 -6
- package/numkong/include/numkong/attention/sme.h +2 -2
- package/numkong/include/numkong/capabilities.h +2 -2
- package/numkong/include/numkong/cast/README.md +9 -7
- package/numkong/include/numkong/cast/loongsonasx.h +48 -40
- package/numkong/include/numkong/cast/serial.h +89 -11
- package/numkong/include/numkong/cast.h +3 -3
- package/numkong/include/numkong/curved/README.md +10 -10
- package/numkong/include/numkong/curved/neon.h +1 -0
- package/numkong/include/numkong/curved/neonbfdot.h +1 -0
- package/numkong/include/numkong/curved/smef64.h +1 -0
- package/numkong/include/numkong/dot/README.md +18 -18
- package/numkong/include/numkong/dot/alder.h +8 -8
- package/numkong/include/numkong/dot/haswell.h +8 -8
- package/numkong/include/numkong/dot/icelake.h +12 -12
- package/numkong/include/numkong/dot/loongsonasx.h +6 -6
- package/numkong/include/numkong/dot/neon.h +2 -1
- package/numkong/include/numkong/dot/neonbfdot.h +1 -0
- package/numkong/include/numkong/dot/neonfhm.h +1 -0
- package/numkong/include/numkong/dot/neonsdot.h +1 -0
- package/numkong/include/numkong/dot/powervsx.h +3 -3
- package/numkong/include/numkong/dot/serial.h +19 -6
- package/numkong/include/numkong/dot/skylake.h +4 -4
- package/numkong/include/numkong/dot/svehalf.h +1 -1
- package/numkong/include/numkong/dot/v128relaxed.h +24 -23
- package/numkong/include/numkong/dots/README.md +26 -26
- package/numkong/include/numkong/dots/genoa.h +3 -1
- package/numkong/include/numkong/dots/icelake.h +1 -0
- package/numkong/include/numkong/dots/powervsx.h +1 -1
- package/numkong/include/numkong/dots/serial.h +32 -33
- package/numkong/include/numkong/dots/sierra.h +4 -3
- package/numkong/include/numkong/each/README.md +5 -3
- package/numkong/include/numkong/each/sapphire.h +2 -0
- package/numkong/include/numkong/each/skylake.h +1 -0
- package/numkong/include/numkong/geospatial/README.md +6 -6
- package/numkong/include/numkong/geospatial/v128relaxed.h +1 -0
- package/numkong/include/numkong/maxsim/README.md +5 -5
- package/numkong/include/numkong/maxsim/genoa.h +4 -0
- package/numkong/include/numkong/maxsim/icelake.h +1 -0
- package/numkong/include/numkong/maxsim/serial.h +22 -9
- package/numkong/include/numkong/maxsim/sme.h +3 -2
- package/numkong/include/numkong/mesh/README.md +7 -4
- package/numkong/include/numkong/mesh/genoa.h +2 -2
- package/numkong/include/numkong/mesh/haswell.h +8 -8
- package/numkong/include/numkong/mesh/neon.h +6 -6
- package/numkong/include/numkong/mesh/neonbfdot.h +3 -2
- package/numkong/include/numkong/mesh/neonfhm.h +3 -2
- package/numkong/include/numkong/mesh/rvv.h +4 -4
- package/numkong/include/numkong/mesh/serial.h +17 -17
- package/numkong/include/numkong/mesh/skylake.h +4 -4
- package/numkong/include/numkong/mesh/v128relaxed.h +4 -4
- package/numkong/include/numkong/mesh.hpp +2 -2
- package/numkong/include/numkong/numkong.h +1 -1
- package/numkong/include/numkong/numkong.hpp +1 -1
- package/numkong/include/numkong/probability/README.md +17 -15
- package/numkong/include/numkong/probability/skylake.h +1 -1
- package/numkong/include/numkong/reduce/README.md +27 -21
- package/numkong/include/numkong/reduce/genoa.h +2 -1
- package/numkong/include/numkong/reduce/icelake.h +1 -0
- package/numkong/include/numkong/reduce/neon.h +1 -1
- package/numkong/include/numkong/reduce/serial.h +9 -9
- package/numkong/include/numkong/reduce/skylake.h +4 -4
- package/numkong/include/numkong/reduce.hpp +38 -0
- package/numkong/include/numkong/scalar/README.md +29 -28
- package/numkong/include/numkong/set/README.md +17 -14
- package/numkong/include/numkong/set/haswell.h +15 -16
- package/numkong/include/numkong/set/icelake.h +15 -16
- package/numkong/include/numkong/set/loongsonasx.h +12 -10
- package/numkong/include/numkong/set/neon.h +15 -19
- package/numkong/include/numkong/set/powervsx.h +15 -21
- package/numkong/include/numkong/set/serial.h +19 -20
- package/numkong/include/numkong/set/v128relaxed.h +10 -8
- package/numkong/include/numkong/sets/icelake.h +1 -0
- package/numkong/include/numkong/sparse/README.md +4 -4
- package/numkong/include/numkong/sparse/icelake.h +1 -0
- package/numkong/include/numkong/sparse/neon.h +1 -0
- package/numkong/include/numkong/sparse/serial.h +10 -3
- package/numkong/include/numkong/sparse/turin.h +1 -0
- package/numkong/include/numkong/spatial/README.md +25 -24
- package/numkong/include/numkong/spatial/alder.h +2 -1
- package/numkong/include/numkong/spatial/haswell.h +48 -40
- package/numkong/include/numkong/spatial/icelake.h +2 -1
- package/numkong/include/numkong/spatial/neon.h +54 -46
- package/numkong/include/numkong/spatial/neonbfdot.h +1 -1
- package/numkong/include/numkong/spatial/powervsx.h +54 -46
- package/numkong/include/numkong/spatial/serial.h +56 -46
- package/numkong/include/numkong/spatial/sierra.h +4 -3
- package/numkong/include/numkong/spatial/skylake.h +27 -22
- package/numkong/include/numkong/spatial/sve.h +6 -6
- package/numkong/include/numkong/spatial/svebfdot.h +1 -1
- package/numkong/include/numkong/spatial/svehalf.h +1 -1
- package/numkong/include/numkong/spatial/v128relaxed.h +36 -30
- package/numkong/include/numkong/spatials/README.md +15 -15
- package/numkong/include/numkong/spatials/genoa.h +3 -0
- package/numkong/include/numkong/spatials/graniteamx.h +2 -0
- package/numkong/include/numkong/spatials/icelake.h +1 -0
- package/numkong/include/numkong/spatials/sierra.h +1 -0
- package/numkong/include/numkong/tensor.hpp +236 -123
- package/numkong/include/numkong/trigonometry/README.md +4 -4
- package/numkong/include/numkong/trigonometry/serial.h +6 -6
- package/numkong/include/numkong/trigonometry/v128relaxed.h +1 -0
- package/numkong/include/numkong/types.hpp +46 -16
- package/numkong/include/numkong/vector.hpp +116 -23
- package/numkong/javascript/@numkong-darwin-arm64/package.json +1 -1
- package/numkong/javascript/@numkong-darwin-x64/package.json +1 -1
- package/numkong/javascript/@numkong-linux-arm64/package.json +1 -1
- package/numkong/javascript/@numkong-linux-x64/package.json +1 -1
- package/numkong/javascript/@numkong-win32-arm64/package.json +1 -1
- package/numkong/javascript/@numkong-win32-x64/package.json +1 -1
- package/numkong/javascript/README.md +18 -16
- package/numkong/javascript/numkong.c +70 -40
- package/numkong/package.json +8 -8
- package/numkong/rust/README.md +19 -9
- package/numkong/rust/cast.rs +52 -1
- package/numkong/rust/each.rs +480 -7
- package/numkong/rust/matrix.rs +877 -216
- package/numkong/rust/numkong.rs +12 -8
- package/numkong/rust/reduce.rs +433 -5
- package/numkong/rust/tensor.rs +450 -842
- package/numkong/rust/vector.rs +520 -101
- package/numkong/swift/README.md +5 -3
- package/numkong/test/README.md +4 -4
- package/numkong/test/test-wasi.mjs +17 -0
- package/numkong/test/test_each.cpp +30 -1
- package/numkong/test/test_each.py +74 -1
- package/numkong/test/test_reduce.cpp +51 -0
- package/numkong/test/test_tensor.cpp +2 -63
- package/numkong/test/test_tensor.py +93 -0
- package/package.json +1 -1
- package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
- package/prebuilds/linux-arm64/usearch.node +0 -0
- package/prebuilds/linux-x64/usearch.node +0 -0
- package/prebuilds/win32-x64/usearch.node +0 -0
package/numkong/rust/numkong.rs
CHANGED
|
@@ -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::{
|
|
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::{
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
|
package/numkong/rust/reduce.rs
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
|
-
//! Statistical reductions: moments
|
|
1
|
+
//! Statistical reductions: moments, min/max, and bit-population counts.
|
|
2
2
|
//!
|
|
3
3
|
//! This module provides:
|
|
4
4
|
//!
|
|
5
|
-
//! -
|
|
6
|
-
//!
|
|
7
|
-
//!
|
|
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
|
}
|