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
@@ -21,7 +21,7 @@ extern "C" {
21
21
  /**
22
22
  * @brief Computes an approximate sine of the given angle in radians with @b 3-ULP error bound for [-2π, 2π].
23
23
  * @see Based on @b `xfastsinf_u3500` in SLEEF library.
24
- * @param[in] angle The input angle in radians.
24
+ * @param[in] angle_radians The input angle in radians.
25
25
  * @return The approximate sine of the input angle in [-1, 1] range.
26
26
  */
27
27
  NK_PUBLIC nk_f32_t nk_f32_sin(nk_f32_t const angle_radians) {
@@ -62,7 +62,7 @@ NK_PUBLIC nk_f32_t nk_f32_sin(nk_f32_t const angle_radians) {
62
62
  /**
63
63
  * @brief Computes an approximate cosine of the given angle in radians with @b 3-ULP error bound for [-2π, 2π].
64
64
  * @see Based on @b `xfastcosf_u3500` in SLEEF library.
65
- * @param[in] angle The input angle in radians.
65
+ * @param[in] angle_radians The input angle in radians.
66
66
  * @return The approximate cosine of the input angle in [-1, 1] range.
67
67
  */
68
68
  NK_PUBLIC nk_f32_t nk_f32_cos(nk_f32_t const angle_radians) {
@@ -255,8 +255,8 @@ NK_PUBLIC nk_f32_t nk_f32_atan2(nk_f32_t const y_input, nk_f32_t const x_input)
255
255
  /**
256
256
  * @brief Computes the sine of the given angle in radians with @b 0-ULP error bound in [-2π, 2π].
257
257
  * @see Based on @b `xsin` in SLEEF library.
258
- * @param[in] angle The input angle in radians.
259
- * @return The approximate cosine of the input angle.
258
+ * @param[in] angle_radians The input angle in radians.
259
+ * @return The approximate sine of the input angle.
260
260
  */
261
261
  NK_PUBLIC nk_f64_t nk_f64_sin(nk_f64_t const angle_radians) {
262
262
 
@@ -316,7 +316,7 @@ NK_PUBLIC nk_f64_t nk_f64_sin(nk_f64_t const angle_radians) {
316
316
  /**
317
317
  * @brief Computes the cosine of the given angle in radians with @b 0-ULP error bound in [-2π, 2π].
318
318
  * @see Based on @b `xcos` in SLEEF library.
319
- * @param[in] angle The input angle in radians.
319
+ * @param[in] angle_radians The input angle in radians.
320
320
  * @return The approximate cosine of the input angle in [-1, 1] range.
321
321
  */
322
322
  NK_PUBLIC nk_f64_t nk_f64_cos(nk_f64_t const angle_radians) {
@@ -439,7 +439,7 @@ NK_PUBLIC nk_f64_t nk_f64_atan(nk_f64_t const input) {
439
439
  * @see Based on @b `xatan2` in SLEEF library.
440
440
  * @param y_input The input sine value.
441
441
  * @param x_input The input cosine value.
442
- * @return The arc-tangent of (y_input/x_input) in [-π/2, π/2] radians range.
442
+ * @return The arc-tangent of (y_input/x_input) in [-π, π] radians range.
443
443
  */
444
444
  NK_PUBLIC nk_f64_t nk_f64_atan2(nk_f64_t const y_input, nk_f64_t const x_input) {
445
445
  // Polynomial coefficients for atan2 approximation
@@ -37,6 +37,7 @@
37
37
  #if NK_TARGET_V128RELAXED
38
38
 
39
39
  #include "numkong/types.h"
40
+ #include "numkong/cast/serial.h" // `nk_partial_load_b32x4_serial_`, `nk_partial_load_b64x2_serial_`, `nk_partial_store_b32x4_serial_`
40
41
 
41
42
  #if defined(__cplusplus)
42
43
  extern "C" {
@@ -481,9 +481,11 @@ struct f32_t {
481
481
  inline f32_t clamp(f32_t lo, f32_t hi) const noexcept { return max(lo).min(hi); }
482
482
 
483
483
  static constexpr f32_t clamped_to_finite_(float x) noexcept {
484
- if (x == std::numeric_limits<float>::infinity()) return finite_max();
485
- if (x == -std::numeric_limits<float>::infinity()) return finite_min();
486
- return f32_t {x};
484
+ f32_t const value {x};
485
+ // Classify by bit pattern: arithmetic comparisons against infinity are folded away
486
+ // under `-ffinite-math-only`, silently disabling the clamp.
487
+ if (value.is_infinite()) return value.is_sign_negative() ? finite_min() : finite_max();
488
+ return value;
487
489
  }
488
490
 
489
491
  /** @brief Saturating addition: clamps to finite range on overflow. */
@@ -751,9 +753,11 @@ struct f64_t {
751
753
  inline f64_t clamp(f64_t lo, f64_t hi) const noexcept { return max(lo).min(hi); }
752
754
 
753
755
  static constexpr f64_t clamped_to_finite_(double x) noexcept {
754
- if (x == std::numeric_limits<double>::infinity()) return finite_max();
755
- if (x == -std::numeric_limits<double>::infinity()) return finite_min();
756
- return f64_t {x};
756
+ f64_t const value {x};
757
+ // Classify by bit pattern: arithmetic comparisons against infinity are folded away
758
+ // under `-ffinite-math-only`, silently disabling the clamp.
759
+ if (value.is_infinite()) return value.is_sign_negative() ? finite_min() : finite_max();
760
+ return value;
757
761
  }
758
762
 
759
763
  /** @brief Saturating addition: clamps to finite range on overflow. */
@@ -3062,11 +3066,16 @@ struct f118_t {
3062
3066
 
3063
3067
  /** @brief Returns true if the value is positive or negative infinity. */
3064
3068
  constexpr bool is_infinite() const noexcept {
3065
- return high_ == std::numeric_limits<double>::infinity() || high_ == -std::numeric_limits<double>::infinity();
3069
+ // Classify by bit pattern: arithmetic comparisons against infinity are folded away
3070
+ // under `-ffinite-math-only`.
3071
+ return (std::bit_cast<std::uint64_t>(high_) & 0x7FFFFFFFFFFFFFFFull) == 0x7FF0000000000000ull;
3066
3072
  }
3067
3073
 
3068
3074
  /** @brief Returns true if the value is neither infinite nor NaN. */
3069
- inline bool is_finite() const noexcept { return std::isfinite(high_); }
3075
+ constexpr bool is_finite() const noexcept {
3076
+ // Classify by bit pattern: `std::isfinite` constant-folds to true under `-ffinite-math-only`.
3077
+ return (std::bit_cast<std::uint64_t>(high_) & 0x7FF0000000000000ull) != 0x7FF0000000000000ull;
3078
+ }
3070
3079
 
3071
3080
  /** @brief Returns true if the sign bit is positive (includes +0). */
3072
3081
  inline bool is_sign_positive() const noexcept { return high_ > 0.0 || (high_ == 0.0 && !std::signbit(high_)); }
@@ -4917,11 +4926,13 @@ struct sub_byte_ref<u1x8_t> {
4917
4926
  constexpr bool get() const noexcept { return (*raw_ptr_ & bit_mask_) != 0; }
4918
4927
  constexpr operator bool() const noexcept { return get(); }
4919
4928
 
4920
- constexpr sub_byte_ref &operator=(bool value) noexcept {
4921
- if (value) *raw_ptr_ |= bit_mask_;
4929
+ template <std::integral integral_type_>
4930
+ constexpr sub_byte_ref &operator=(integral_type_ value) noexcept {
4931
+ if (value != integral_type_ {0}) *raw_ptr_ |= bit_mask_;
4922
4932
  else *raw_ptr_ &= static_cast<nk_u1x8_t>(~bit_mask_);
4923
4933
  return *this;
4924
4934
  }
4935
+ constexpr sub_byte_ref &operator=(u8_t value) noexcept { return *this = value.raw(); }
4925
4936
 
4926
4937
  constexpr void flip() noexcept { *raw_ptr_ ^= bit_mask_; }
4927
4938
  };
@@ -4941,11 +4952,21 @@ struct sub_byte_ref<i4x2_t> {
4941
4952
  }
4942
4953
  constexpr operator std::int8_t() const noexcept { return get(); }
4943
4954
 
4944
- constexpr sub_byte_ref &operator=(std::int8_t value) noexcept {
4945
- if (high_nibble_) *raw_ptr_ = static_cast<nk_i4x2_t>((*raw_ptr_ & 0x0F) | ((value & 0x0F) << 4));
4946
- else *raw_ptr_ = static_cast<nk_i4x2_t>((*raw_ptr_ & 0xF0) | (value & 0x0F));
4955
+ template <std::integral integral_type_>
4956
+ constexpr sub_byte_ref &operator=(integral_type_ value) noexcept {
4957
+ std::int8_t clamped;
4958
+ if constexpr (std::is_signed_v<integral_type_>) {
4959
+ clamped = value < integral_type_(-8) ? std::int8_t(-8)
4960
+ : value > integral_type_(7) ? std::int8_t(7)
4961
+ : static_cast<std::int8_t>(value);
4962
+ }
4963
+ else { clamped = value > integral_type_(7) ? std::int8_t(7) : static_cast<std::int8_t>(value); }
4964
+ auto nibble = static_cast<nk_i4x2_t>(clamped & 0x0F);
4965
+ if (high_nibble_) *raw_ptr_ = static_cast<nk_i4x2_t>((*raw_ptr_ & 0x0F) | (nibble << 4));
4966
+ else *raw_ptr_ = static_cast<nk_i4x2_t>((*raw_ptr_ & 0xF0) | nibble);
4947
4967
  return *this;
4948
4968
  }
4969
+ constexpr sub_byte_ref &operator=(i8_t value) noexcept { return *this = value.raw(); }
4949
4970
  };
4950
4971
 
4951
4972
  /** @brief Unsigned 4-bit access for u4x2_t vectors (2 nibbles packed per byte). */
@@ -4960,11 +4981,20 @@ struct sub_byte_ref<u4x2_t> {
4960
4981
  constexpr std::uint8_t get() const noexcept { return high_nibble_ ? (*raw_ptr_ >> 4) : (*raw_ptr_ & 0x0F); }
4961
4982
  constexpr operator std::uint8_t() const noexcept { return get(); }
4962
4983
 
4963
- constexpr sub_byte_ref &operator=(std::uint8_t value) noexcept {
4964
- if (high_nibble_) *raw_ptr_ = static_cast<nk_u4x2_t>((*raw_ptr_ & 0x0F) | ((value & 0x0F) << 4));
4965
- else *raw_ptr_ = static_cast<nk_u4x2_t>((*raw_ptr_ & 0xF0) | (value & 0x0F));
4984
+ template <std::integral integral_type_>
4985
+ constexpr sub_byte_ref &operator=(integral_type_ value) noexcept {
4986
+ std::uint8_t clamped;
4987
+ if constexpr (std::is_signed_v<integral_type_>) {
4988
+ clamped = value < integral_type_(0) ? std::uint8_t(0)
4989
+ : value > integral_type_(15) ? std::uint8_t(15)
4990
+ : static_cast<std::uint8_t>(value);
4991
+ }
4992
+ else { clamped = value > integral_type_(15) ? std::uint8_t(15) : static_cast<std::uint8_t>(value); }
4993
+ if (high_nibble_) *raw_ptr_ = static_cast<nk_u4x2_t>((*raw_ptr_ & 0x0F) | (clamped << 4));
4994
+ else *raw_ptr_ = static_cast<nk_u4x2_t>((*raw_ptr_ & 0xF0) | clamped);
4966
4995
  return *this;
4967
4996
  }
4997
+ constexpr sub_byte_ref &operator=(u8_t value) noexcept { return *this = value.raw(); }
4968
4998
  };
4969
4999
 
4970
5000
  /**
@@ -411,6 +411,9 @@ struct vector_view {
411
411
  */
412
412
  template <typename value_type_>
413
413
  struct vector_span {
414
+ static_assert(!std::is_const_v<value_type_>,
415
+ "vector_span requires a non-const value_type_; use vector_view<value_type_> for read-only access");
416
+
414
417
  using value_type = value_type_;
415
418
  using size_type = std::size_t;
416
419
  using difference_type = std::ptrdiff_t;
@@ -451,12 +454,10 @@ struct vector_span {
451
454
  }
452
455
 
453
456
  /** @brief Raw byte pointer. */
454
- constexpr char *byte_data() noexcept { return data_; }
455
- constexpr char const *byte_data() const noexcept { return data_; }
457
+ constexpr char *byte_data() const noexcept { return data_; }
456
458
 
457
459
  /** @brief Typed pointer (only valid if contiguous). */
458
- constexpr value_type *data() noexcept { return reinterpret_cast<value_type *>(data_); }
459
- constexpr value_type const *data() const noexcept { return reinterpret_cast<value_type const *>(data_); }
460
+ constexpr value_type *data() const noexcept { return reinterpret_cast<value_type *>(data_); }
460
461
 
461
462
  /** @brief Implicit conversion to const view. */
462
463
  constexpr operator vector_view<value_type>() const noexcept {
@@ -465,7 +466,7 @@ struct vector_span {
465
466
 
466
467
  /** @brief Mutable integral indexing. Signed negatives wrap from end. */
467
468
  template <std::integral index_type_>
468
- decltype(auto) operator[](index_type_ idx) noexcept {
469
+ decltype(auto) operator[](index_type_ idx) const noexcept {
469
470
  auto i = resolve_index_(idx, dimensions_);
470
471
  if constexpr (dimensions_per_value<value_type>() > 1) {
471
472
  constexpr auto dims_per_value = dimensions_per_value<value_type>();
@@ -479,37 +480,120 @@ struct vector_span {
479
480
  else { return *reinterpret_cast<value_type *>(data_ + static_cast<difference_type>(i) * stride_bytes_); }
480
481
  }
481
482
 
482
- /** @brief Const integral indexing. Signed negatives wrap from end. */
483
- template <std::integral index_type_>
484
- decltype(auto) operator[](index_type_ idx) const noexcept {
485
- return static_cast<vector_view<value_type>>(*this)[idx];
486
- }
487
-
488
483
  /** @brief Sub-slice via range. */
489
- vector_span operator[](range r) noexcept {
484
+ vector_span operator[](range r) const noexcept {
490
485
  size_type start, stop;
491
486
  resolve_range_(r, dimensions_, start, stop);
492
487
  auto count = range_extent_(start, stop, r.step);
493
488
  return {data_ + static_cast<difference_type>(start) * stride_bytes_, count, stride_bytes_ * r.step};
494
489
  }
495
490
 
496
- /** @brief Const sub-slice via range. */
497
- vector_view<value_type> operator[](range r) const noexcept {
498
- return static_cast<vector_view<value_type>>(*this)[r];
499
- }
500
-
501
491
  /** @brief Select all elements. */
502
- vector_span operator[](all_t) noexcept { return *this; }
492
+ vector_span operator[](all_t) const noexcept { return *this; }
503
493
 
504
- /** @brief Dimension iterator to beginning. */
505
- iterator begin() noexcept { return {*this, 0}; }
506
- const_iterator begin() const noexcept { return {*this, 0}; }
494
+ /** @brief Dimension iterator to beginning. The handle is shallow-const, so iteration yields mutable refs. */
495
+ iterator begin() const noexcept { return {const_cast<vector_span &>(*this), 0}; }
507
496
  const_iterator cbegin() const noexcept { return {*this, 0}; }
508
497
 
509
498
  /** @brief Dimension iterator to end. */
510
- iterator end() noexcept { return {*this, dimensions_}; }
511
- const_iterator end() const noexcept { return {*this, dimensions_}; }
499
+ iterator end() const noexcept { return {const_cast<vector_span &>(*this), dimensions_}; }
512
500
  const_iterator cend() const noexcept { return {*this, dimensions_}; }
501
+
502
+ /** @brief Zero-fill every element. memset on the contiguous fast path,
503
+ * one memset per element on the strided slow path. */
504
+ bool fill_zeros() noexcept {
505
+ static_assert(is_memset_zero_safe_v<value_type>,
506
+ "fill_zeros requires a dtype whose binary-zero is the value-zero");
507
+ if (data_ == nullptr || dimensions_ == 0) return true;
508
+ if constexpr (dimensions_per_value<value_type>() > 1) {
509
+ if (!is_contiguous()) return false;
510
+ auto byte_count = divide_round_up(dimensions_, dimensions_per_value<value_type>()) * sizeof(value_type);
511
+ std::memset(static_cast<void *>(data_), 0, byte_count);
512
+ return true;
513
+ }
514
+ else {
515
+ if (is_contiguous()) {
516
+ std::memset(static_cast<void *>(data_), 0, dimensions_ * sizeof(value_type));
517
+ return true;
518
+ }
519
+ for (size_type element_index = 0; element_index < dimensions_; ++element_index)
520
+ std::memset(static_cast<void *>(data_ + static_cast<difference_type>(element_index) * stride_bytes_),
521
+ 0, sizeof(value_type));
522
+ return true;
523
+ }
524
+ }
525
+
526
+ /** @brief Fill every element with `value`. memset-then-overlay strategy mirroring the
527
+ * free `fill` on tensor_span: 1-byte storage uses a single byte_pattern memset, multi-byte
528
+ * storage uses a typed scalar broadcast loop. */
529
+ bool fill(value_type value) noexcept {
530
+ if (!fill_zeros()) return false;
531
+ value_type const default_value {};
532
+ if (std::memcmp(&value, &default_value, sizeof(value_type)) == 0) return true;
533
+ if constexpr (sizeof(value_type) == 1) {
534
+ unsigned char byte_pattern;
535
+ std::memcpy(&byte_pattern, &value, 1);
536
+ if constexpr (dimensions_per_value<value_type>() > 1) {
537
+ auto byte_count = divide_round_up(dimensions_, dimensions_per_value<value_type>()) * sizeof(value_type);
538
+ std::memset(static_cast<void *>(data_), byte_pattern, byte_count);
539
+ return true;
540
+ }
541
+ else {
542
+ if (is_contiguous()) {
543
+ std::memset(static_cast<void *>(data_), byte_pattern, dimensions_ * sizeof(value_type));
544
+ return true;
545
+ }
546
+ for (size_type element_index = 0; element_index < dimensions_; ++element_index)
547
+ std::memset(
548
+ static_cast<void *>(data_ + static_cast<difference_type>(element_index) * stride_bytes_),
549
+ byte_pattern, sizeof(value_type));
550
+ return true;
551
+ }
552
+ }
553
+ else {
554
+ if (is_contiguous()) {
555
+ auto *typed_data = reinterpret_cast<value_type *>(data_);
556
+ for (size_type element_index = 0; element_index < dimensions_; ++element_index)
557
+ typed_data[element_index] = value;
558
+ return true;
559
+ }
560
+ for (size_type element_index = 0; element_index < dimensions_; ++element_index) {
561
+ auto *target =
562
+ reinterpret_cast<value_type *>(data_ + static_cast<difference_type>(element_index) * stride_bytes_);
563
+ *target = value;
564
+ }
565
+ return true;
566
+ }
567
+ }
568
+
569
+ /** @brief Copy from a same-size view. memcpy on the contiguous fast path,
570
+ * per-element copy on the strided slow path. Returns false on size mismatch. */
571
+ bool copy_from(vector_view<value_type> input) noexcept {
572
+ if (input.size() != dimensions_) return false;
573
+ if (dimensions_ == 0) return true;
574
+ if constexpr (dimensions_per_value<value_type>() > 1) {
575
+ if (!is_contiguous() || !input.is_contiguous()) return false;
576
+ auto byte_count = divide_round_up(dimensions_, dimensions_per_value<value_type>()) * sizeof(value_type);
577
+ std::memcpy(static_cast<void *>(data_), static_cast<void const *>(input.byte_data()), byte_count);
578
+ return true;
579
+ }
580
+ else {
581
+ if (is_contiguous() && input.is_contiguous()) {
582
+ std::memcpy(static_cast<void *>(data_), static_cast<void const *>(input.byte_data()),
583
+ dimensions_ * sizeof(value_type));
584
+ return true;
585
+ }
586
+ auto input_stride_bytes = input.stride_bytes();
587
+ for (size_type element_index = 0; element_index < dimensions_; ++element_index) {
588
+ auto *target =
589
+ reinterpret_cast<value_type *>(data_ + static_cast<difference_type>(element_index) * stride_bytes_);
590
+ auto const *source = reinterpret_cast<value_type const *>(
591
+ input.byte_data() + static_cast<difference_type>(element_index) * input_stride_bytes);
592
+ *target = *source;
593
+ }
594
+ return true;
595
+ }
596
+ }
513
597
  };
514
598
 
515
599
  #pragma endregion Vector Span
@@ -751,6 +835,15 @@ struct vector {
751
835
  iterator end() noexcept { return {*this, dimensions_}; }
752
836
  const_iterator end() const noexcept { return {*this, dimensions_}; }
753
837
  const_iterator cend() const noexcept { return {*this, dimensions_}; }
838
+
839
+ /** @brief Zero-fill every element. */
840
+ bool fill_zeros() noexcept { return span().fill_zeros(); }
841
+
842
+ /** @brief Fill every element with `value`. */
843
+ bool fill(value_type value) noexcept { return span().fill(value); }
844
+
845
+ /** @brief Copy from a same-size view. */
846
+ bool copy_from(vector_view<value_type> input) noexcept { return span().copy_from(input); }
754
847
  };
755
848
 
756
849
  /** @brief Non-member swap. */
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@numkong/darwin-arm64",
3
- "version": "7.6.0",
3
+ "version": "7.8.1",
4
4
  "description": "macOS arm64 native binary for NumKong",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@numkong/darwin-x64",
3
- "version": "7.6.0",
3
+ "version": "7.8.1",
4
4
  "description": "macOS x64 native binary for NumKong",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@numkong/linux-arm64",
3
- "version": "7.6.0",
3
+ "version": "7.8.1",
4
4
  "description": "Linux arm64 native binary for NumKong",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@numkong/linux-x64",
3
- "version": "7.6.0",
3
+ "version": "7.8.1",
4
4
  "description": "Linux x64 native binary for NumKong",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@numkong/win32-arm64",
3
- "version": "7.6.0",
3
+ "version": "7.8.1",
4
4
  "description": "Windows arm64 native binary for NumKong",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@numkong/win32-x64",
3
- "version": "7.6.0",
3
+ "version": "7.8.1",
4
4
  "description": "Windows x64 native binary for NumKong",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,15 +31,18 @@ This binding stays centered on the vector families it actually exports.
31
31
 
32
32
  ## Ecosystem Comparison
33
33
 
34
- | Feature | NumKong | [mathjs](https://github.com/josdejong/mathjs) | [tensorflow.js](https://github.com/tensorflow/tfjs) |
35
- | ---------------------------- | -------------------------------------------------------------- | --------------------------------------------- | --------------------------------------------------- |
36
- | Operation families | dots, distances, binary, probability, cast, packed, symmetric | general arithmetic, matrix ops, statistics | matmul, elementwise, reductions |
37
- | Precision | BFloat16 through sub-byte; automatic widening; Kahan summation | Float64 only; standard accuracy | Float32 primarily; no sub-byte; standard accuracy |
38
- | Runtime SIMD dispatch | auto-selects best ISA per-thread across x86, ARM, RISC-V | none; pure JS | fixed at build time via WASM SIMD or WebGL |
39
- | Packed matrix, GEMM-like | `dotsPack` + `dotsPacked`; persistent packing; amortized | `math.multiply` — no persistent packing | `tf.matMul` — no persistent packing |
40
- | Symmetric kernels, SYRK-like | `dotsSymmetric`; upper triangle only; row-range partitioning | no duplicate-pair skipping | no duplicate-pair skipping |
41
- | WASM fallback | yes — portable, runs in browser without native addon | yes — pure JS, no native required | yes — also WebGL/WebGPU |
42
- | Bundle size | small | moderate | large |
34
+ | Feature | NumKong | [mathjs][mathjs] | [tensorflow.js][tensorflow-js] |
35
+ | ---------------------------- | -------------------------------------------------------------- | ------------------------------------------ | ------------------------------------------------- |
36
+ | Operation families | dots, distances, binary, probability, cast, packed, symmetric | general arithmetic, matrix ops, statistics | matmul, elementwise, reductions |
37
+ | Precision | BFloat16 through sub-byte; automatic widening; Kahan summation | Float64 only; standard accuracy | Float32 primarily; no sub-byte; standard accuracy |
38
+ | Runtime SIMD dispatch | auto-selects best ISA per-thread across x86, ARM, RISC-V | none; pure JS | fixed at build time via WASM SIMD or WebGL |
39
+ | Packed matrix, GEMM-like | `dotsPack` + `dotsPacked`; persistent packing; amortized | `math.multiply` — no persistent packing | `tf.matMul` — no persistent packing |
40
+ | Symmetric kernels, SYRK-like | `dotsSymmetric`; upper triangle only; row-range partitioning | no duplicate-pair skipping | no duplicate-pair skipping |
41
+ | WASM fallback | yes — portable, runs in browser without native addon | yes — pure JS, no native required | yes — also WebGL/WebGPU |
42
+ | Bundle size | small | moderate | large |
43
+
44
+ [mathjs]: https://github.com/josdejong/mathjs
45
+ [tensorflow-js]: https://github.com/tensorflow/tfjs
43
46
 
44
47
  ## Installation
45
48
 
@@ -74,11 +77,7 @@ For self-hosted WASM, download the binaries from a [GitHub Release](https://gith
74
77
 
75
78
  ```html
76
79
  <script type="module">
77
- import * as numkong from "./numkong-emscripten.js";
78
- import NumKongModule from "./numkong.js";
79
-
80
- const wasm = await NumKongModule();
81
- numkong.initWasm(wasm);
80
+ import * as numkong from "./numkong.js";
82
81
 
83
82
  const a = new Float32Array([1, 2, 3]);
84
83
  const b = new Float32Array([4, 5, 6]);
@@ -86,6 +85,9 @@ For self-hosted WASM, download the binaries from a [GitHub Release](https://gith
86
85
  </script>
87
86
  ```
88
87
 
88
+ The release archive ships `numkong.js`, `numkong-emscripten.js`, and `numkong.wasm`.
89
+ Only `numkong.js` is imported — it locates the other two next to itself and instantiates the module before any export is used.
90
+
89
91
  Or import the subpath from a bundler or Node.js (without the native addon):
90
92
 
91
93
  ```js
@@ -186,11 +188,11 @@ console.log(a8.byteLength);
186
188
 
187
189
  If the underlying storage is a raw `Uint16Array` or `Uint8Array`, JS itself cannot know whether those bytes mean integers, `f16`, `bf16`, mini-floats, or packed bits.
188
190
  That is exactly when the `DType` tag becomes mandatory.
189
- You can also pass it to `cast` to convert many values between all supported types:
191
+ `cast` converts many values between all supported types, naming both sides by their dtype string:
190
192
 
191
193
  ```ts
192
194
  numkong.cast(f32Source, "f32", bf16Dest, "bf16");
193
- numkong.cast(f32Source, "f32", bf16Dest, DType.E4M3);
195
+ numkong.cast(f32Source, "f32", e4m3Dest, "e4m3");
194
196
  ```
195
197
 
196
198
  ## Vector Views and Owned Buffers
@@ -9,16 +9,11 @@
9
9
 
10
10
  #include <string.h> // `strcmp` function
11
11
 
12
- #if defined(NK_USE_OPENMP)
13
- #include <omp.h>
14
- #endif
15
-
16
12
  #include <node_api.h> // `napi_*` functions — N-API v6+ for BigInt (Node ≥ 10.20)
17
13
 
18
14
  #include <numkong/numkong.h> // `nk_*` functions — must be first to bring `_GNU_SOURCE`
19
15
 
20
- #define NK_PARALLEL_PACKED_TILE 64
21
- #define NK_PARALLEL_SYMMETRIC_TILE 32
16
+ #include "parallel.h" // `nk_parallel_for_tiles`, tile sizes
22
17
 
23
18
  /** @brief Global variable that caches the CPU capabilities, and is computed just once, when the module is loaded. */
24
19
  nk_capability_t static_capabilities = nk_cap_serial_k;
@@ -484,6 +479,28 @@ static napi_value api_dots_pack(napi_env env, napi_callback_info info) {
484
479
  return result_obj;
485
480
  }
486
481
 
482
+ /** @brief One tile of rows of C = A × Bᵀ with B pre-packed. */
483
+ typedef struct packed_task_t {
484
+ nk_dots_packed_punned_t kernel;
485
+ char const *a;
486
+ void const *b_packed;
487
+ char *c;
488
+ nk_size_t rows;
489
+ nk_size_t columns;
490
+ nk_size_t depth;
491
+ nk_size_t a_stride_bytes;
492
+ nk_size_t c_stride_bytes;
493
+ } packed_task_t;
494
+
495
+ static void packed_tile_(nk_size_t tile_index, void *context) {
496
+ packed_task_t const *task = (packed_task_t const *)context;
497
+ nk_size_t const row = tile_index * NK_PARALLEL_PACKED_TILE;
498
+ nk_size_t const chunk = (row + NK_PARALLEL_PACKED_TILE <= task->rows) ? NK_PARALLEL_PACKED_TILE
499
+ : (task->rows - row);
500
+ task->kernel(task->a + row * task->a_stride_bytes, task->b_packed, task->c + row * task->c_stride_bytes, chunk,
501
+ task->columns, task->depth, task->a_stride_bytes, task->c_stride_bytes);
502
+ }
503
+
487
504
  /**
488
505
  * @brief Shared dispatcher for packed operations (dots, angulars, euclideans).
489
506
  * Args: TypedArray a, ArrayBuffer packed, TypedArray result, numbers height/width/depth/aStride/resultStride, string
@@ -544,23 +561,17 @@ static napi_value api_packed_common(napi_env env, napi_callback_info info, nk_ke
544
561
  uint32_t threads = 1;
545
562
  if (argc == 10) napi_get_value_uint32(env, args[9], &threads);
546
563
 
547
- #if defined(NK_USE_OPENMP)
548
- if (threads == 0) threads = (uint32_t)omp_get_max_threads();
549
- omp_set_num_threads((int)threads);
550
- #endif
551
-
552
- // `int` loop counter pre-declared: MSVC's OpenMP stays at 2.0 canonical
553
- // form, which forbids in-init declarations and rejects 64-bit iterators
554
- // — either would trip C3015.
555
- int const tile_count = (int)nk_size_divide_round_up_(height, NK_PARALLEL_PACKED_TILE);
556
- int tile_idx;
557
- #pragma omp parallel for schedule(dynamic, 1) if (threads > 1)
558
- for (tile_idx = 0; tile_idx < tile_count; tile_idx++) {
559
- nk_size_t row = (nk_size_t)tile_idx * NK_PARALLEL_PACKED_TILE;
560
- nk_size_t chunk = (row + NK_PARALLEL_PACKED_TILE <= height) ? NK_PARALLEL_PACKED_TILE : (height - row);
561
- kernel((char const *)a_data + row * a_stride, packed_data, (char *)result_data + row * result_stride, chunk,
562
- (nk_size_t)width, (nk_size_t)depth, (nk_size_t)a_stride, (nk_size_t)result_stride);
563
- }
564
+ packed_task_t task;
565
+ task.kernel = kernel;
566
+ task.a = a_data;
567
+ task.b_packed = packed_data;
568
+ task.c = result_data;
569
+ task.rows = height;
570
+ task.columns = width;
571
+ task.depth = depth;
572
+ task.a_stride_bytes = a_stride;
573
+ task.c_stride_bytes = result_stride;
574
+ nk_parallel_for_tiles(nk_size_divide_round_up_(height, NK_PARALLEL_PACKED_TILE), threads, packed_tile_, &task);
564
575
  return NULL;
565
576
  }
566
577
 
@@ -574,6 +585,29 @@ static napi_value api_euclideans_packed(napi_env env, napi_callback_info info) {
574
585
  return api_packed_common(env, info, nk_kernel_euclideans_packed_k);
575
586
  }
576
587
 
588
+ /** @brief One tile of rows of C = A × Aᵀ. */
589
+ typedef struct symmetric_task_t {
590
+ nk_dots_symmetric_punned_t kernel;
591
+ void const *vectors;
592
+ void *result;
593
+ nk_size_t vectors_count;
594
+ nk_size_t depth;
595
+ nk_size_t stride_bytes;
596
+ nk_size_t result_stride_bytes;
597
+ nk_size_t row_start;
598
+ nk_size_t row_end;
599
+ } symmetric_task_t;
600
+
601
+ static void symmetric_tile_(nk_size_t tile_index, void *context) {
602
+ symmetric_task_t const *task = (symmetric_task_t const *)context;
603
+ nk_size_t const tile_start = task->row_start + tile_index * NK_PARALLEL_SYMMETRIC_TILE;
604
+ nk_size_t const tile_rows = (tile_start + NK_PARALLEL_SYMMETRIC_TILE <= task->row_end)
605
+ ? NK_PARALLEL_SYMMETRIC_TILE
606
+ : (task->row_end - tile_start);
607
+ task->kernel(task->vectors, task->vectors_count, task->depth, task->stride_bytes, task->result,
608
+ task->result_stride_bytes, tile_start, tile_rows);
609
+ }
610
+
577
611
  /**
578
612
  * @brief Shared dispatcher for symmetric operations (dots, angulars, euclideans).
579
613
  * Args: TypedArray vectors, TypedArray result, numbers nVectors/depth/vectorsStride/resultStride/rowStart/rowCount,
@@ -630,23 +664,19 @@ static napi_value api_symmetric_common(napi_env env, napi_callback_info info, nk
630
664
  uint32_t threads = 1;
631
665
  if (argc == 10) napi_get_value_uint32(env, args[9], &threads);
632
666
 
633
- #if defined(NK_USE_OPENMP)
634
- if (threads == 0) threads = (uint32_t)omp_get_max_threads();
635
- omp_set_num_threads((int)threads);
636
- #endif
637
-
638
- // `int` loop counter pre-declared: see note at `api_packed_common`.
639
- int const tile_count = (int)nk_size_divide_round_up_(row_count, NK_PARALLEL_SYMMETRIC_TILE);
640
- int tile_idx;
641
- #pragma omp parallel for schedule(dynamic, 1) if (threads > 1)
642
- for (tile_idx = 0; tile_idx < tile_count; tile_idx++) {
643
- nk_size_t tile_start = (nk_size_t)row_start + (nk_size_t)tile_idx * NK_PARALLEL_SYMMETRIC_TILE;
644
- nk_size_t tile_rows = (tile_start + NK_PARALLEL_SYMMETRIC_TILE <= (nk_size_t)row_start + row_count)
645
- ? NK_PARALLEL_SYMMETRIC_TILE
646
- : ((nk_size_t)row_start + row_count - tile_start);
647
- kernel(vectors_data, (nk_size_t)n_vectors, (nk_size_t)depth, (nk_size_t)vectors_stride, result_data,
648
- (nk_size_t)result_stride, tile_start, tile_rows);
649
- }
667
+ symmetric_task_t task;
668
+ task.kernel = kernel;
669
+ task.vectors = vectors_data;
670
+ task.result = result_data;
671
+ task.vectors_count = n_vectors;
672
+ task.depth = depth;
673
+ task.stride_bytes = vectors_stride;
674
+ task.result_stride_bytes = result_stride;
675
+ task.row_start = row_start;
676
+ // Widen before the sum so two `uint32_t` row bounds cannot wrap.
677
+ task.row_end = (nk_size_t)row_start + row_count;
678
+ nk_parallel_for_tiles(nk_size_divide_round_up_(row_count, NK_PARALLEL_SYMMETRIC_TILE), threads, symmetric_tile_,
679
+ &task);
650
680
 
651
681
  return NULL;
652
682
  }