usearch 2.26.1 → 2.26.3

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
@@ -7,6 +7,12 @@
7
7
  //! - [`VectorSpan`]: Mutable, strided, non-owning view
8
8
  //! - [`VectorIndex`]: Signed indexing trait (negative indices wrap from end)
9
9
  //!
10
+ //! `Vector` and `VectorSpan` implement [`crate::tensor::Fill`] (`fill_zeros` / `fill`) and
11
+ //! [`crate::tensor::CopyFrom`] for the natural source type of each container.
12
+ //! `Vector<u1x8>`, `VectorView<u1x8>`, and `VectorSpan<u1x8>` expose `popcount` / `any_set` /
13
+ //! `none_set` / `all_set` directly so the bit-reduction spelling matches the
14
+ //! [`crate::reduce::BitwiseReductions`] trait used on tensor containers.
15
+ //!
10
16
  //! All types use [`StorageElement`] as their element bound, with sub-byte types
11
17
  //! (i4x2, u4x2, u1x8) supported via `try_get`/`try_set` and iterators.
12
18
  //!
@@ -46,7 +52,7 @@ extern crate alloc;
46
52
  use core::marker::PhantomData;
47
53
  use core::ptr::NonNull;
48
54
 
49
- use crate::tensor::{Allocator, Global, Tensor, TensorError, SIMD_ALIGNMENT};
55
+ use crate::tensor::{Allocator, CopyFrom, Fill, Global, Tensor, TensorError, SIMD_ALIGNMENT};
50
56
  use crate::types::{DimMut, DimRef, FloatConvertible, NumberLike, StorageElement};
51
57
 
52
58
  // region: VectorIndex — Signed Indexing
@@ -61,40 +67,126 @@ pub trait VectorIndex: private::Sealed + Copy {
61
67
  fn resolve(self, len: usize) -> Option<usize>;
62
68
  }
63
69
 
64
- macro_rules! impl_vec_index_unsigned {
65
- ($($t:ty),*) => {$(
66
- impl private::Sealed for $t {}
67
- impl VectorIndex for $t {
68
- #[inline]
69
- fn resolve(self, len: usize) -> Option<usize> {
70
- let index = self as usize;
71
- if index < len { Some(index) } else { None }
72
- }
73
- }
74
- )*};
75
- }
76
-
77
- macro_rules! impl_vec_index_signed {
78
- ($($t:ty),*) => {$(
79
- impl private::Sealed for $t {}
80
- impl VectorIndex for $t {
81
- #[inline]
82
- fn resolve(self, len: usize) -> Option<usize> {
83
- let index = if self >= 0 {
84
- self as usize
85
- } else {
86
- let neg = (-(self as isize)) as usize;
87
- if neg > len { return None; }
88
- len - neg
89
- };
90
- if index < len { Some(index) } else { None }
91
- }
92
- }
93
- )*};
70
+ // Unsigned indexing: direct widen-to-usize, then bounds-check.
71
+ impl private::Sealed for usize {}
72
+ impl VectorIndex for usize {
73
+ #[inline]
74
+ fn resolve(self, len: usize) -> Option<usize> {
75
+ if self < len { Some(self) } else { None }
76
+ }
77
+ }
78
+
79
+ impl private::Sealed for u8 {}
80
+ impl VectorIndex for u8 {
81
+ #[inline]
82
+ fn resolve(self, len: usize) -> Option<usize> {
83
+ let index = self as usize;
84
+ if index < len { Some(index) } else { None }
85
+ }
86
+ }
87
+
88
+ impl private::Sealed for u16 {}
89
+ impl VectorIndex for u16 {
90
+ #[inline]
91
+ fn resolve(self, len: usize) -> Option<usize> {
92
+ let index = self as usize;
93
+ if index < len { Some(index) } else { None }
94
+ }
95
+ }
96
+
97
+ impl private::Sealed for u32 {}
98
+ impl VectorIndex for u32 {
99
+ #[inline]
100
+ fn resolve(self, len: usize) -> Option<usize> {
101
+ let index = self as usize;
102
+ if index < len { Some(index) } else { None }
103
+ }
104
+ }
105
+
106
+ impl private::Sealed for u64 {}
107
+ impl VectorIndex for u64 {
108
+ #[inline]
109
+ fn resolve(self, len: usize) -> Option<usize> {
110
+ let index = self as usize;
111
+ if index < len { Some(index) } else { None }
112
+ }
113
+ }
114
+
115
+ // Signed indexing: negative values wrap from the end (-1 is the last element).
116
+ impl private::Sealed for isize {}
117
+ impl VectorIndex for isize {
118
+ #[inline]
119
+ fn resolve(self, len: usize) -> Option<usize> {
120
+ let index = if self >= 0 {
121
+ self as usize
122
+ } else {
123
+ let negation = (-self) as usize;
124
+ if negation > len { return None; }
125
+ len - negation
126
+ };
127
+ if index < len { Some(index) } else { None }
128
+ }
129
+ }
130
+
131
+ impl private::Sealed for i8 {}
132
+ impl VectorIndex for i8 {
133
+ #[inline]
134
+ fn resolve(self, len: usize) -> Option<usize> {
135
+ let index = if self >= 0 {
136
+ self as usize
137
+ } else {
138
+ let negation = (-(self as isize)) as usize;
139
+ if negation > len { return None; }
140
+ len - negation
141
+ };
142
+ if index < len { Some(index) } else { None }
143
+ }
144
+ }
145
+
146
+ impl private::Sealed for i16 {}
147
+ impl VectorIndex for i16 {
148
+ #[inline]
149
+ fn resolve(self, len: usize) -> Option<usize> {
150
+ let index = if self >= 0 {
151
+ self as usize
152
+ } else {
153
+ let negation = (-(self as isize)) as usize;
154
+ if negation > len { return None; }
155
+ len - negation
156
+ };
157
+ if index < len { Some(index) } else { None }
158
+ }
159
+ }
160
+
161
+ impl private::Sealed for i32 {}
162
+ impl VectorIndex for i32 {
163
+ #[inline]
164
+ fn resolve(self, len: usize) -> Option<usize> {
165
+ let index = if self >= 0 {
166
+ self as usize
167
+ } else {
168
+ let negation = (-(self as isize)) as usize;
169
+ if negation > len { return None; }
170
+ len - negation
171
+ };
172
+ if index < len { Some(index) } else { None }
173
+ }
94
174
  }
95
175
 
96
- impl_vec_index_unsigned!(usize, u8, u16, u32, u64);
97
- impl_vec_index_signed!(isize, i8, i16, i32, i64);
176
+ impl private::Sealed for i64 {}
177
+ impl VectorIndex for i64 {
178
+ #[inline]
179
+ fn resolve(self, len: usize) -> Option<usize> {
180
+ let index = if self >= 0 {
181
+ self as usize
182
+ } else {
183
+ let negation = (-(self as isize)) as usize;
184
+ if negation > len { return None; }
185
+ len - negation
186
+ };
187
+ if index < len { Some(index) } else { None }
188
+ }
189
+ }
98
190
 
99
191
  // endregion: VectorIndex
100
192
 
@@ -309,10 +401,9 @@ impl<'a> BitRefMut<'a> {
309
401
  pub struct Vector<Scalar: StorageElement, Alloc: Allocator = Global> {
310
402
  /// Pointer to the allocated buffer (typed as `Scalar` for alignment).
311
403
  data: NonNull<Scalar>,
312
- /// Number of logical dimensions.
404
+ /// Number of logical dimensions. Storage size is derived as
405
+ /// `dims_to_values::<Scalar>(self.dims)`.
313
406
  dims: usize,
314
- /// Number of storage values (dims / dimensions_per_value, rounded up).
315
- values: usize,
316
407
  /// Allocator instance.
317
408
  alloc: Alloc,
318
409
  }
@@ -322,14 +413,15 @@ unsafe impl<Scalar: StorageElement + Sync, Alloc: Allocator + Sync> Sync for Vec
322
413
 
323
414
  impl<Scalar: StorageElement, Alloc: Allocator> Drop for Vector<Scalar, Alloc> {
324
415
  fn drop(&mut self) {
325
- if self.values > 0 {
416
+ let storage_count = dims_to_values::<Scalar>(self.dims);
417
+ if storage_count > 0 {
326
418
  let layout = alloc::alloc::Layout::from_size_align(
327
- self.values * core::mem::size_of::<Scalar>(),
419
+ storage_count * core::mem::size_of::<Scalar>(),
328
420
  SIMD_ALIGNMENT,
329
421
  )
330
422
  .unwrap();
331
423
  // SAFETY: data was allocated with this layout in try_zeros_in,
332
- // and values > 0 guarantees the pointer is non-dangling.
424
+ // and storage_count > 0 guarantees the pointer is non-dangling.
333
425
  unsafe {
334
426
  self.alloc.deallocate(
335
427
  NonNull::new_unchecked(self.data.as_ptr() as *mut u8),
@@ -353,36 +445,25 @@ impl<Scalar: StorageElement, Alloc: Allocator> Vector<Scalar, Alloc> {
353
445
  /// Construct a vector from raw parts, taking ownership of the allocation.
354
446
  ///
355
447
  /// # Safety
356
- /// - `data` must point to a valid allocation of `values * size_of::<Scalar>()` bytes
357
- /// obtained from `alloc`, aligned to [`SIMD_ALIGNMENT`].
358
- /// - `dims` and `values` must be consistent (`values == ceil(dims / dimensions_per_value())`).
448
+ /// - `data` must point to an allocation obtained from `alloc`, aligned to
449
+ /// [`SIMD_ALIGNMENT`], whose backing buffer is sized for the storage count
450
+ /// implied by `dims` (`dims_to_values::<Scalar>(dims)` slots of `Scalar`).
359
451
  /// - The caller must not free the memory (this vector takes ownership).
360
- pub unsafe fn from_raw_parts(
361
- data: NonNull<Scalar>,
362
- dims: usize,
363
- values: usize,
364
- alloc: Alloc,
365
- ) -> Self {
366
- Self {
367
- data,
368
- dims,
369
- values,
370
- alloc,
371
- }
452
+ pub unsafe fn from_raw_parts(data: NonNull<Scalar>, dims: usize, alloc: Alloc) -> Self {
453
+ Self { data, dims, alloc }
372
454
  }
373
455
 
374
456
  /// Try to create a zero-initialized vector with the given number of dimensions.
375
457
  pub fn try_zeros_in(dims: usize, alloc: Alloc) -> Result<Self, TensorError> {
376
- let values = dims_to_values::<Scalar>(dims);
377
- if values == 0 {
458
+ let storage_count = dims_to_values::<Scalar>(dims);
459
+ if storage_count == 0 {
378
460
  return Ok(Self {
379
461
  data: NonNull::dangling(),
380
462
  dims: 0,
381
- values: 0,
382
463
  alloc,
383
464
  });
384
465
  }
385
- let size = values * core::mem::size_of::<Scalar>();
466
+ let size = storage_count * core::mem::size_of::<Scalar>();
386
467
  let layout = alloc::alloc::Layout::from_size_align(size, SIMD_ALIGNMENT)
387
468
  .map_err(|_| TensorError::AllocationFailed)?;
388
469
  let ptr = alloc
@@ -392,7 +473,6 @@ impl<Scalar: StorageElement, Alloc: Allocator> Vector<Scalar, Alloc> {
392
473
  Ok(Self {
393
474
  data: unsafe { NonNull::new_unchecked(ptr.as_ptr() as *mut Scalar) },
394
475
  dims,
395
- values,
396
476
  alloc,
397
477
  })
398
478
  }
@@ -400,9 +480,10 @@ impl<Scalar: StorageElement, Alloc: Allocator> Vector<Scalar, Alloc> {
400
480
  /// Try to create a vector filled with `value`.
401
481
  pub fn try_full_in(dims: usize, value: Scalar, alloc: Alloc) -> Result<Self, TensorError> {
402
482
  let v = Self::try_zeros_in(dims, alloc)?;
403
- if v.values > 0 {
483
+ let storage_count = dims_to_values::<Scalar>(v.dims);
484
+ if storage_count > 0 {
404
485
  let ptr = v.data.as_ptr();
405
- for i in 0..v.values {
486
+ for i in 0..storage_count {
406
487
  unsafe { ptr.add(i).write(value) };
407
488
  }
408
489
  }
@@ -423,16 +504,15 @@ impl<Scalar: StorageElement, Alloc: Allocator> Vector<Scalar, Alloc> {
423
504
  /// The returned vector's contents are uninitialized. Reading from it before
424
505
  /// writing is undefined behavior.
425
506
  pub unsafe fn try_empty_in(dims: usize, alloc: Alloc) -> Result<Self, TensorError> {
426
- let values = dims_to_values::<Scalar>(dims);
427
- if values == 0 {
507
+ let storage_count = dims_to_values::<Scalar>(dims);
508
+ if storage_count == 0 {
428
509
  return Ok(Self {
429
510
  data: NonNull::dangling(),
430
511
  dims: 0,
431
- values: 0,
432
512
  alloc,
433
513
  });
434
514
  }
435
- let size = values * core::mem::size_of::<Scalar>();
515
+ let size = storage_count * core::mem::size_of::<Scalar>();
436
516
  let layout = alloc::alloc::Layout::from_size_align(size, SIMD_ALIGNMENT)
437
517
  .map_err(|_| TensorError::AllocationFailed)?;
438
518
  let ptr = alloc
@@ -441,7 +521,6 @@ impl<Scalar: StorageElement, Alloc: Allocator> Vector<Scalar, Alloc> {
441
521
  Ok(Self {
442
522
  data: unsafe { NonNull::new_unchecked(ptr.as_ptr() as *mut Scalar) },
443
523
  dims,
444
- values,
445
524
  alloc,
446
525
  })
447
526
  }
@@ -494,7 +573,7 @@ impl<Scalar: StorageElement, Alloc: Allocator> Vector<Scalar, Alloc> {
494
573
  /// Number of underlying storage values (`Scalar` instances).
495
574
  #[inline]
496
575
  pub fn size_values(&self) -> usize {
497
- self.values
576
+ dims_to_values::<Scalar>(self.dims)
498
577
  }
499
578
 
500
579
  /// Returns true if the vector has zero dimensions.
@@ -518,7 +597,7 @@ impl<Scalar: StorageElement, Alloc: Allocator> Vector<Scalar, Alloc> {
518
597
  /// Size in bytes.
519
598
  #[inline]
520
599
  pub fn size_bytes(&self) -> usize {
521
- self.values * core::mem::size_of::<Scalar>()
600
+ dims_to_values::<Scalar>(self.dims) * core::mem::size_of::<Scalar>()
522
601
  }
523
602
 
524
603
  /// Create an immutable view of this vector.
@@ -579,7 +658,7 @@ impl<Scalar: StorageElement, Alloc: Allocator> Vector<Scalar, Alloc> {
579
658
  let dims_per_value = Scalar::dimensions_per_value();
580
659
  let value_index = i / dims_per_value;
581
660
  let sub_index = i % dims_per_value;
582
- // SAFETY: value_index < self.values, guaranteed by dims/values invariant
661
+ // SAFETY: value_index < dims_to_values(self.dims), implied by the bounds check above
583
662
  let packed = unsafe { *self.data.as_ptr().add(value_index) };
584
663
  Ok(packed.unpack().as_ref()[sub_index])
585
664
  }
@@ -618,7 +697,7 @@ impl<Scalar: StorageElement, Alloc: Allocator> Vector<Scalar, Alloc> {
618
697
  let dims_per_value = Scalar::dimensions_per_value();
619
698
  let value_index = i / dims_per_value;
620
699
  let sub_index = i % dims_per_value;
621
- // SAFETY: value_index < self.values, guaranteed by dims/values invariant
700
+ // SAFETY: value_index < dims_to_values(self.dims), implied by the bounds check above
622
701
  let ptr = unsafe { self.data.as_ptr().add(value_index) };
623
702
  let mut unpacked = unsafe { *ptr }.unpack();
624
703
  unpacked.as_mut()[sub_index] = value;
@@ -626,21 +705,19 @@ impl<Scalar: StorageElement, Alloc: Allocator> Vector<Scalar, Alloc> {
626
705
  Ok(())
627
706
  }
628
707
 
629
- /// Get a slice of the underlying storage values (only for normal types).
708
+ /// Get a slice of the underlying storage values. For sub-byte types this returns
709
+ /// the packed storage values, not the logical dimensions.
630
710
  #[inline]
631
711
  pub fn as_slice(&self) -> &[Scalar] {
632
- if Scalar::dimensions_per_value() == 1 {
633
- unsafe { core::slice::from_raw_parts(self.data.as_ptr(), self.values) }
634
- } else {
635
- // For sub-byte types, return the packed values
636
- unsafe { core::slice::from_raw_parts(self.data.as_ptr(), self.values) }
637
- }
712
+ let storage_count = dims_to_values::<Scalar>(self.dims);
713
+ unsafe { core::slice::from_raw_parts(self.data.as_ptr(), storage_count) }
638
714
  }
639
715
 
640
716
  /// Get a mutable slice of the underlying storage values.
641
717
  #[inline]
642
718
  pub fn as_mut_slice(&mut self) -> &mut [Scalar] {
643
- unsafe { core::slice::from_raw_parts_mut(self.data.as_ptr(), self.values) }
719
+ let storage_count = dims_to_values::<Scalar>(self.dims);
720
+ unsafe { core::slice::from_raw_parts_mut(self.data.as_ptr(), storage_count) }
644
721
  }
645
722
 
646
723
  /// Returns an iterator over the logical dimension values, yielding [`DimRef`] proxies.
@@ -678,13 +755,12 @@ impl<Scalar: StorageElement, Alloc: Allocator> Vector<Scalar, Alloc> {
678
755
  shape[0] = self.dims;
679
756
  let mut strides = [0isize; MAX_RANK];
680
757
  strides[0] = core::mem::size_of::<Scalar>() as isize;
681
- let alloc_bytes = self.values * core::mem::size_of::<Scalar>();
682
758
  let data = self.data;
683
759
  // SAFETY: we read the allocator out before forget, transferring ownership
684
760
  let alloc = unsafe { core::ptr::read(&self.alloc) };
685
761
  core::mem::forget(self);
686
- // SAFETY: data/alloc_bytes match the original allocation
687
- let tensor = unsafe { Tensor::from_raw_parts(data, alloc_bytes, shape, strides, 1, alloc) };
762
+ // SAFETY: storage size derived from `shape[0]` matches the original allocation.
763
+ let tensor = unsafe { Tensor::from_raw_parts(data, shape, strides, 1, alloc) };
688
764
  Ok(tensor)
689
765
  }
690
766
  }
@@ -774,15 +850,15 @@ impl<AnyIndex: VectorIndex, Scalar: StorageElement, Alloc: Allocator> core::ops:
774
850
  impl<Scalar: StorageElement + Clone, Alloc: Allocator + Clone> Vector<Scalar, Alloc> {
775
851
  /// Try to clone this vector, returning an error on allocation failure.
776
852
  pub fn try_clone(&self) -> Result<Self, TensorError> {
777
- if self.values == 0 {
853
+ let storage_count = dims_to_values::<Scalar>(self.dims);
854
+ if storage_count == 0 {
778
855
  return Ok(Self {
779
856
  data: NonNull::dangling(),
780
857
  dims: 0,
781
- values: 0,
782
858
  alloc: self.alloc.clone(),
783
859
  });
784
860
  }
785
- let size = self.values * core::mem::size_of::<Scalar>();
861
+ let size = storage_count * core::mem::size_of::<Scalar>();
786
862
  let layout = alloc::alloc::Layout::from_size_align(size, SIMD_ALIGNMENT)
787
863
  .map_err(|_| TensorError::AllocationFailed)?;
788
864
  let ptr = self
@@ -795,7 +871,6 @@ impl<Scalar: StorageElement + Clone, Alloc: Allocator + Clone> Vector<Scalar, Al
795
871
  Ok(Self {
796
872
  data: unsafe { NonNull::new_unchecked(ptr.as_ptr() as *mut Scalar) },
797
873
  dims: self.dims,
798
- values: self.values,
799
874
  alloc: self.alloc.clone(),
800
875
  })
801
876
  }
@@ -812,7 +887,6 @@ impl<Scalar: StorageElement> Default for Vector<Scalar, Global> {
812
887
  Self {
813
888
  data: NonNull::dangling(),
814
889
  dims: 0,
815
- values: 0,
816
890
  alloc: Global,
817
891
  }
818
892
  }
@@ -1173,21 +1247,6 @@ impl<'a, Scalar: StorageElement> VectorSpan<'a, Scalar> {
1173
1247
  Ok(())
1174
1248
  }
1175
1249
 
1176
- /// Fill all elements with a value, respecting the stride between elements.
1177
- ///
1178
- /// Iterates over storage values (not logical dimensions) to avoid buffer
1179
- /// overwrite for sub-byte types.
1180
- pub fn fill(&mut self, value: Scalar) {
1181
- let values = self.dims.div_ceil(Scalar::dimensions_per_value());
1182
- for i in 0..values {
1183
- // SAFETY: i < values; stride * i stays within the allocation
1184
- let ptr = unsafe {
1185
- (self.data as *mut u8).offset(self.stride_bytes * i as isize) as *mut Scalar
1186
- };
1187
- unsafe { ptr.write(value) };
1188
- }
1189
- }
1190
-
1191
1250
  /// Get a contiguous slice, if this span is contiguous.
1192
1251
  #[inline]
1193
1252
  pub fn as_contiguous_slice(&self) -> Option<&[Scalar]> {
@@ -1275,6 +1334,280 @@ impl<'a, AnyIndex: VectorIndex, Scalar: StorageElement> core::ops::IndexMut<AnyI
1275
1334
 
1276
1335
  // endregion: VectorSpan
1277
1336
 
1337
+ // region: Bit Reductions on u1x8 vectors
1338
+
1339
+ use crate::reduce::ReduceMoments;
1340
+ use crate::types::u1x8;
1341
+
1342
+ /// Population count of a contiguous packed-bit storage slice.
1343
+ ///
1344
+ /// Shared body for the inherent `popcount` methods on `Vector<u1x8>`,
1345
+ /// `VectorView<u1x8>`, and `VectorSpan<u1x8>`. Sub-byte vector views always
1346
+ /// have a one-byte stride (you cannot stride by less than a byte), so
1347
+ /// contiguous storage is implied — the slice form is always valid.
1348
+ fn popcount_u1x8_storage(storage: &[u1x8]) -> u64 {
1349
+ if storage.is_empty() {
1350
+ return 0;
1351
+ }
1352
+ let (sum, _sum_of_squares) = u1x8::reduce_moments(storage, core::mem::size_of::<u1x8>());
1353
+ sum
1354
+ }
1355
+
1356
+ impl<Alloc: Allocator> Vector<u1x8, Alloc> {
1357
+ /// Number of set bits across the entire vector.
1358
+ pub fn popcount(&self) -> u64 {
1359
+ let storage_count = self.dims.div_ceil(u1x8::dimensions_per_value());
1360
+ let storage =
1361
+ unsafe { core::slice::from_raw_parts(self.data.as_ptr(), storage_count) };
1362
+ popcount_u1x8_storage(storage)
1363
+ }
1364
+
1365
+ /// `true` if at least one bit in the vector is set.
1366
+ pub fn any_set(&self) -> bool {
1367
+ self.popcount() != 0
1368
+ }
1369
+
1370
+ /// `true` if no bit in the vector is set.
1371
+ pub fn none_set(&self) -> bool {
1372
+ !self.any_set()
1373
+ }
1374
+
1375
+ /// `true` if every bit in the vector is set.
1376
+ pub fn all_set(&self) -> bool {
1377
+ self.popcount() == self.dims as u64
1378
+ }
1379
+ }
1380
+
1381
+ impl<'a> VectorView<'a, u1x8> {
1382
+ /// Number of set bits across the entire vector view.
1383
+ pub fn popcount(&self) -> u64 {
1384
+ let storage_count = self.dims.div_ceil(u1x8::dimensions_per_value());
1385
+ let storage = unsafe { core::slice::from_raw_parts(self.data, storage_count) };
1386
+ popcount_u1x8_storage(storage)
1387
+ }
1388
+
1389
+ /// `true` if at least one bit in the vector view is set.
1390
+ pub fn any_set(&self) -> bool {
1391
+ self.popcount() != 0
1392
+ }
1393
+
1394
+ /// `true` if no bit in the vector view is set.
1395
+ pub fn none_set(&self) -> bool {
1396
+ !self.any_set()
1397
+ }
1398
+
1399
+ /// `true` if every bit in the vector view is set.
1400
+ pub fn all_set(&self) -> bool {
1401
+ self.popcount() == self.dims as u64
1402
+ }
1403
+ }
1404
+
1405
+ impl<'a> VectorSpan<'a, u1x8> {
1406
+ /// Number of set bits across the entire vector span.
1407
+ pub fn popcount(&self) -> u64 {
1408
+ let storage_count = self.dims.div_ceil(u1x8::dimensions_per_value());
1409
+ let storage = unsafe { core::slice::from_raw_parts(self.data as *const u1x8, storage_count) };
1410
+ popcount_u1x8_storage(storage)
1411
+ }
1412
+
1413
+ /// `true` if at least one bit in the vector span is set.
1414
+ pub fn any_set(&self) -> bool {
1415
+ self.popcount() != 0
1416
+ }
1417
+
1418
+ /// `true` if no bit in the vector span is set.
1419
+ pub fn none_set(&self) -> bool {
1420
+ !self.any_set()
1421
+ }
1422
+
1423
+ /// `true` if every bit in the vector span is set.
1424
+ pub fn all_set(&self) -> bool {
1425
+ self.popcount() == self.dims as u64
1426
+ }
1427
+ }
1428
+
1429
+ // endregion: Bit Reductions on u1x8 vectors
1430
+
1431
+ // region: Fill / CopyFrom Implementations
1432
+
1433
+ impl<Scalar: StorageElement, Alloc: Allocator> Fill<Scalar> for Vector<Scalar, Alloc> {
1434
+ fn fill_zeros(&mut self) {
1435
+ let storage_count = dims_to_values::<Scalar>(self.dims);
1436
+ if storage_count == 0 {
1437
+ return;
1438
+ }
1439
+ unsafe {
1440
+ core::ptr::write_bytes(self.data.as_ptr(), 0, storage_count);
1441
+ }
1442
+ }
1443
+
1444
+ fn fill(&mut self, value: Scalar) {
1445
+ self.fill_zeros();
1446
+ let storage_count = dims_to_values::<Scalar>(self.dims);
1447
+ if storage_count == 0 {
1448
+ return;
1449
+ }
1450
+ // Skip overlay when `value` matches the freshly-zeroed bit pattern.
1451
+ let default_value = Scalar::default();
1452
+ let value_bytes = unsafe {
1453
+ core::slice::from_raw_parts(
1454
+ (&value as *const Scalar) as *const u8,
1455
+ core::mem::size_of::<Scalar>(),
1456
+ )
1457
+ };
1458
+ let default_bytes = unsafe {
1459
+ core::slice::from_raw_parts(
1460
+ (&default_value as *const Scalar) as *const u8,
1461
+ core::mem::size_of::<Scalar>(),
1462
+ )
1463
+ };
1464
+ if value_bytes == default_bytes {
1465
+ return;
1466
+ }
1467
+ if core::mem::size_of::<Scalar>() == 1 {
1468
+ unsafe {
1469
+ core::ptr::write_bytes(self.data.as_ptr() as *mut u8, value_bytes[0], storage_count);
1470
+ }
1471
+ } else {
1472
+ for storage_index in 0..storage_count {
1473
+ unsafe { core::ptr::write(self.data.as_ptr().add(storage_index), value) };
1474
+ }
1475
+ }
1476
+ }
1477
+ }
1478
+
1479
+ impl<Scalar: StorageElement, Alloc: Allocator> CopyFrom<&[Scalar]> for Vector<Scalar, Alloc> {
1480
+ fn copy_from(&mut self, source: &[Scalar]) -> Result<(), TensorError> {
1481
+ let storage_count = dims_to_values::<Scalar>(self.dims);
1482
+ if source.len() != storage_count {
1483
+ return Err(TensorError::ShapeMismatch {
1484
+ axis: 0,
1485
+ expected: storage_count,
1486
+ got: source.len(),
1487
+ });
1488
+ }
1489
+ if storage_count == 0 {
1490
+ return Ok(());
1491
+ }
1492
+ unsafe {
1493
+ core::ptr::copy_nonoverlapping(source.as_ptr(), self.data.as_ptr(), storage_count);
1494
+ }
1495
+ Ok(())
1496
+ }
1497
+ }
1498
+
1499
+ impl<'a, Scalar: StorageElement> Fill<Scalar> for VectorSpan<'a, Scalar> {
1500
+ fn fill_zeros(&mut self) {
1501
+ let storage_count = self.dims.div_ceil(Scalar::dimensions_per_value());
1502
+ if storage_count == 0 {
1503
+ return;
1504
+ }
1505
+ let element_size = core::mem::size_of::<Scalar>() as isize;
1506
+ if self.stride_bytes == element_size {
1507
+ // Contiguous fast path.
1508
+ unsafe {
1509
+ core::ptr::write_bytes(self.data, 0, storage_count);
1510
+ }
1511
+ return;
1512
+ }
1513
+ // Strided: zero one element at a time.
1514
+ for storage_index in 0..storage_count {
1515
+ unsafe {
1516
+ let ptr = (self.data as *mut u8)
1517
+ .offset(self.stride_bytes * storage_index as isize)
1518
+ as *mut Scalar;
1519
+ core::ptr::write_bytes(ptr, 0, 1);
1520
+ }
1521
+ }
1522
+ }
1523
+
1524
+ fn fill(&mut self, value: Scalar) {
1525
+ self.fill_zeros();
1526
+ let storage_count = self.dims.div_ceil(Scalar::dimensions_per_value());
1527
+ if storage_count == 0 {
1528
+ return;
1529
+ }
1530
+ let default_value = Scalar::default();
1531
+ let value_bytes = unsafe {
1532
+ core::slice::from_raw_parts(
1533
+ (&value as *const Scalar) as *const u8,
1534
+ core::mem::size_of::<Scalar>(),
1535
+ )
1536
+ };
1537
+ let default_bytes = unsafe {
1538
+ core::slice::from_raw_parts(
1539
+ (&default_value as *const Scalar) as *const u8,
1540
+ core::mem::size_of::<Scalar>(),
1541
+ )
1542
+ };
1543
+ if value_bytes == default_bytes {
1544
+ return;
1545
+ }
1546
+ let element_size = core::mem::size_of::<Scalar>() as isize;
1547
+ if self.stride_bytes == element_size {
1548
+ // Contiguous fast path.
1549
+ if core::mem::size_of::<Scalar>() == 1 {
1550
+ unsafe {
1551
+ core::ptr::write_bytes(self.data as *mut u8, value_bytes[0], storage_count);
1552
+ }
1553
+ } else {
1554
+ for storage_index in 0..storage_count {
1555
+ unsafe { core::ptr::write(self.data.add(storage_index), value) };
1556
+ }
1557
+ }
1558
+ return;
1559
+ }
1560
+ // Strided typed broadcast.
1561
+ for storage_index in 0..storage_count {
1562
+ unsafe {
1563
+ let ptr = (self.data as *mut u8)
1564
+ .offset(self.stride_bytes * storage_index as isize)
1565
+ as *mut Scalar;
1566
+ core::ptr::write(ptr, value);
1567
+ }
1568
+ }
1569
+ }
1570
+ }
1571
+
1572
+ impl<'a, 'b, Scalar: StorageElement> CopyFrom<&'b VectorView<'_, Scalar>>
1573
+ for VectorSpan<'a, Scalar>
1574
+ {
1575
+ fn copy_from(&mut self, source: &'b VectorView<'_, Scalar>) -> Result<(), TensorError> {
1576
+ if source.size() != self.dims {
1577
+ return Err(TensorError::ShapeMismatch {
1578
+ axis: 0,
1579
+ expected: self.dims,
1580
+ got: source.size(),
1581
+ });
1582
+ }
1583
+ if self.dims == 0 {
1584
+ return Ok(());
1585
+ }
1586
+ let storage_count = self.dims.div_ceil(Scalar::dimensions_per_value());
1587
+ let element_size = core::mem::size_of::<Scalar>() as isize;
1588
+ if self.stride_bytes == element_size && source.stride_bytes() == element_size {
1589
+ unsafe {
1590
+ core::ptr::copy_nonoverlapping(source.as_ptr(), self.data, storage_count);
1591
+ }
1592
+ return Ok(());
1593
+ }
1594
+ for storage_index in 0..storage_count {
1595
+ unsafe {
1596
+ let destination_ptr = (self.data as *mut u8)
1597
+ .offset(self.stride_bytes * storage_index as isize)
1598
+ as *mut Scalar;
1599
+ let source_ptr = (source.as_ptr() as *const u8)
1600
+ .offset(source.stride_bytes() * storage_index as isize)
1601
+ as *const Scalar;
1602
+ core::ptr::write(destination_ptr, *source_ptr);
1603
+ }
1604
+ }
1605
+ Ok(())
1606
+ }
1607
+ }
1608
+
1609
+ // endregion: Fill / CopyFrom Implementations
1610
+
1278
1611
  // region: Iterators
1279
1612
 
1280
1613
  /// Stride-aware immutable iterator over logical dimensions, yielding [`DimRef`] proxies.
@@ -1894,6 +2227,92 @@ mod tests {
1894
2227
  let v = Vector::<f32>::try_from_scalars(&[1.0]).unwrap();
1895
2228
  let _it: VectorIterator<'_, f32> = v.iter();
1896
2229
  }
2230
+
2231
+ #[test]
2232
+ fn vector_index_signed_negative_resolves_from_end() {
2233
+ // After macro removal: i32 negative indices wrap from end.
2234
+ assert_eq!(VectorIndex::resolve(-1i32, 5), Some(4));
2235
+ assert_eq!(VectorIndex::resolve(-5i32, 5), Some(0));
2236
+ assert_eq!(VectorIndex::resolve(-6i32, 5), None); // out of bounds
2237
+ // Same logic across all signed widths.
2238
+ assert_eq!(VectorIndex::resolve(-1i8, 5), Some(4));
2239
+ assert_eq!(VectorIndex::resolve(-1i16, 5), Some(4));
2240
+ assert_eq!(VectorIndex::resolve(-1i64, 5), Some(4));
2241
+ assert_eq!(VectorIndex::resolve(-1isize, 5), Some(4));
2242
+ // Unsigned: bounds check only.
2243
+ assert_eq!(VectorIndex::resolve(3u32, 5), Some(3));
2244
+ assert_eq!(VectorIndex::resolve(5u32, 5), None);
2245
+ assert_eq!(VectorIndex::resolve(0u8, 5), Some(0));
2246
+ assert_eq!(VectorIndex::resolve(0usize, 5), Some(0));
2247
+ }
2248
+
2249
+ #[test]
2250
+ fn fill_zeros_and_fill_on_vector_and_span() {
2251
+ // Vector: Fill trait gives fill_zeros + fill methods.
2252
+ use crate::tensor::Fill;
2253
+ let mut v = Vector::<f32>::try_full(8, 3.5).unwrap();
2254
+ v.fill_zeros();
2255
+ assert!(v.as_slice().iter().all(|&value| value == 0.0));
2256
+ v.fill(2.5);
2257
+ assert!(v.as_slice().iter().all(|&value| value == 2.5));
2258
+
2259
+ // VectorSpan: same trait methods.
2260
+ let mut data = [0.0f32; 8];
2261
+ let mut span = unsafe {
2262
+ VectorSpan::<f32>::from_raw_parts(
2263
+ data.as_mut_ptr(),
2264
+ 8,
2265
+ core::mem::size_of::<f32>() as isize,
2266
+ )
2267
+ };
2268
+ span.fill(1.25);
2269
+ assert!(data.iter().all(|&value| value == 1.25));
2270
+ }
2271
+
2272
+ #[test]
2273
+ fn copy_from_round_trips_vector_and_span() {
2274
+ use crate::tensor::CopyFrom;
2275
+ // Vector: CopyFrom<&[Scalar]>.
2276
+ let mut destination = Vector::<f32>::try_zeros(4).unwrap();
2277
+ let source = [1.0f32, 2.0, 3.0, 4.0];
2278
+ destination.copy_from(source.as_slice()).unwrap();
2279
+ assert_eq!(destination.as_slice(), &source[..]);
2280
+
2281
+ // VectorSpan: CopyFrom<&VectorView>.
2282
+ let source_vec = Vector::<f32>::try_from_scalars(&[5.0, 6.0, 7.0, 8.0]).unwrap();
2283
+ let mut destination_buffer = [0.0f32; 4];
2284
+ {
2285
+ let mut destination_span = unsafe {
2286
+ VectorSpan::<f32>::from_raw_parts(
2287
+ destination_buffer.as_mut_ptr(),
2288
+ 4,
2289
+ core::mem::size_of::<f32>() as isize,
2290
+ )
2291
+ };
2292
+ let source_view = source_vec.view();
2293
+ destination_span.copy_from(&source_view).unwrap();
2294
+ }
2295
+ assert_eq!(destination_buffer, [5.0f32, 6.0, 7.0, 8.0]);
2296
+ }
2297
+
2298
+ #[test]
2299
+ fn bit_reductions_on_u1x8_vector_match_method_names() {
2300
+ use crate::types::u1x8;
2301
+ let mut bits_vector = Vector::<u1x8>::try_zeros(32).unwrap();
2302
+ // Set all 32 bits.
2303
+ for slot in bits_vector.as_mut_slice().iter_mut() {
2304
+ *slot = u1x8(0xFFu8);
2305
+ }
2306
+ assert_eq!(bits_vector.popcount(), 32);
2307
+ assert!(bits_vector.any_set());
2308
+ assert!(!bits_vector.none_set());
2309
+ assert!(bits_vector.all_set());
2310
+
2311
+ bits_vector.fill_zeros();
2312
+ assert_eq!(bits_vector.popcount(), 0);
2313
+ assert!(!bits_vector.any_set());
2314
+ assert!(bits_vector.none_set());
2315
+ }
1897
2316
  }
1898
2317
 
1899
2318
  // endregion: Tests