usearch 2.26.0 → 2.26.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/README.md +2 -2
  2. package/cpp/test.cpp +66 -3
  3. package/include/usearch/index.hpp +9 -1
  4. package/include/usearch/index_dense.hpp +16 -5
  5. package/numkong/CITATION.cff +13 -9
  6. package/numkong/CONTRIBUTING.md +29 -24
  7. package/numkong/Cargo.toml +1 -1
  8. package/numkong/README.md +21 -7
  9. package/numkong/binding.gyp +47 -31
  10. package/numkong/build.rs +15 -5
  11. package/numkong/c/dispatch.h +0 -1
  12. package/numkong/c/parallel.c +139 -0
  13. package/numkong/c/parallel.h +47 -0
  14. package/numkong/cmake/toolchain-wasi.cmake +39 -5
  15. package/numkong/include/README.md +24 -17
  16. package/numkong/include/numkong/attention/sapphireamx.h +6 -6
  17. package/numkong/include/numkong/attention/sme.h +2 -2
  18. package/numkong/include/numkong/capabilities.h +2 -2
  19. package/numkong/include/numkong/cast/README.md +9 -7
  20. package/numkong/include/numkong/cast/loongsonasx.h +48 -40
  21. package/numkong/include/numkong/cast/serial.h +89 -11
  22. package/numkong/include/numkong/cast.h +3 -3
  23. package/numkong/include/numkong/curved/README.md +10 -10
  24. package/numkong/include/numkong/curved/neon.h +1 -0
  25. package/numkong/include/numkong/curved/neonbfdot.h +1 -0
  26. package/numkong/include/numkong/curved/smef64.h +1 -0
  27. package/numkong/include/numkong/dot/README.md +18 -18
  28. package/numkong/include/numkong/dot/alder.h +8 -8
  29. package/numkong/include/numkong/dot/haswell.h +8 -8
  30. package/numkong/include/numkong/dot/icelake.h +12 -12
  31. package/numkong/include/numkong/dot/loongsonasx.h +6 -6
  32. package/numkong/include/numkong/dot/neon.h +2 -1
  33. package/numkong/include/numkong/dot/neonbfdot.h +1 -0
  34. package/numkong/include/numkong/dot/neonfhm.h +1 -0
  35. package/numkong/include/numkong/dot/neonsdot.h +1 -0
  36. package/numkong/include/numkong/dot/powervsx.h +3 -3
  37. package/numkong/include/numkong/dot/serial.h +19 -6
  38. package/numkong/include/numkong/dot/skylake.h +4 -4
  39. package/numkong/include/numkong/dot/svehalf.h +1 -1
  40. package/numkong/include/numkong/dot/v128relaxed.h +24 -23
  41. package/numkong/include/numkong/dots/README.md +26 -26
  42. package/numkong/include/numkong/dots/genoa.h +3 -1
  43. package/numkong/include/numkong/dots/icelake.h +1 -0
  44. package/numkong/include/numkong/dots/powervsx.h +1 -1
  45. package/numkong/include/numkong/dots/serial.h +32 -33
  46. package/numkong/include/numkong/dots/sierra.h +4 -3
  47. package/numkong/include/numkong/each/README.md +5 -3
  48. package/numkong/include/numkong/each/sapphire.h +2 -0
  49. package/numkong/include/numkong/each/skylake.h +1 -0
  50. package/numkong/include/numkong/geospatial/README.md +6 -6
  51. package/numkong/include/numkong/geospatial/v128relaxed.h +1 -0
  52. package/numkong/include/numkong/maxsim/README.md +5 -5
  53. package/numkong/include/numkong/maxsim/genoa.h +4 -0
  54. package/numkong/include/numkong/maxsim/icelake.h +1 -0
  55. package/numkong/include/numkong/maxsim/serial.h +22 -9
  56. package/numkong/include/numkong/maxsim/sme.h +3 -2
  57. package/numkong/include/numkong/mesh/README.md +7 -4
  58. package/numkong/include/numkong/mesh/genoa.h +2 -2
  59. package/numkong/include/numkong/mesh/haswell.h +8 -8
  60. package/numkong/include/numkong/mesh/neon.h +6 -6
  61. package/numkong/include/numkong/mesh/neonbfdot.h +3 -2
  62. package/numkong/include/numkong/mesh/neonfhm.h +3 -2
  63. package/numkong/include/numkong/mesh/rvv.h +4 -4
  64. package/numkong/include/numkong/mesh/serial.h +17 -17
  65. package/numkong/include/numkong/mesh/skylake.h +4 -4
  66. package/numkong/include/numkong/mesh/v128relaxed.h +4 -4
  67. package/numkong/include/numkong/mesh.hpp +2 -2
  68. package/numkong/include/numkong/numkong.h +1 -1
  69. package/numkong/include/numkong/numkong.hpp +1 -1
  70. package/numkong/include/numkong/probability/README.md +17 -15
  71. package/numkong/include/numkong/probability/skylake.h +1 -1
  72. package/numkong/include/numkong/reduce/README.md +27 -21
  73. package/numkong/include/numkong/reduce/genoa.h +2 -1
  74. package/numkong/include/numkong/reduce/icelake.h +1 -0
  75. package/numkong/include/numkong/reduce/neon.h +1 -1
  76. package/numkong/include/numkong/reduce/serial.h +9 -9
  77. package/numkong/include/numkong/reduce/skylake.h +4 -4
  78. package/numkong/include/numkong/reduce.hpp +38 -0
  79. package/numkong/include/numkong/scalar/README.md +29 -28
  80. package/numkong/include/numkong/set/README.md +17 -14
  81. package/numkong/include/numkong/set/haswell.h +15 -16
  82. package/numkong/include/numkong/set/icelake.h +15 -16
  83. package/numkong/include/numkong/set/loongsonasx.h +12 -10
  84. package/numkong/include/numkong/set/neon.h +15 -19
  85. package/numkong/include/numkong/set/powervsx.h +15 -21
  86. package/numkong/include/numkong/set/serial.h +19 -20
  87. package/numkong/include/numkong/set/v128relaxed.h +10 -8
  88. package/numkong/include/numkong/sets/icelake.h +1 -0
  89. package/numkong/include/numkong/sparse/README.md +4 -4
  90. package/numkong/include/numkong/sparse/icelake.h +1 -0
  91. package/numkong/include/numkong/sparse/neon.h +1 -0
  92. package/numkong/include/numkong/sparse/serial.h +10 -3
  93. package/numkong/include/numkong/sparse/turin.h +1 -0
  94. package/numkong/include/numkong/spatial/README.md +25 -24
  95. package/numkong/include/numkong/spatial/alder.h +2 -1
  96. package/numkong/include/numkong/spatial/haswell.h +48 -40
  97. package/numkong/include/numkong/spatial/icelake.h +2 -1
  98. package/numkong/include/numkong/spatial/neon.h +54 -46
  99. package/numkong/include/numkong/spatial/neonbfdot.h +1 -1
  100. package/numkong/include/numkong/spatial/powervsx.h +54 -46
  101. package/numkong/include/numkong/spatial/serial.h +56 -46
  102. package/numkong/include/numkong/spatial/sierra.h +4 -3
  103. package/numkong/include/numkong/spatial/skylake.h +27 -22
  104. package/numkong/include/numkong/spatial/sve.h +6 -6
  105. package/numkong/include/numkong/spatial/svebfdot.h +1 -1
  106. package/numkong/include/numkong/spatial/svehalf.h +1 -1
  107. package/numkong/include/numkong/spatial/v128relaxed.h +36 -30
  108. package/numkong/include/numkong/spatials/README.md +15 -15
  109. package/numkong/include/numkong/spatials/genoa.h +3 -0
  110. package/numkong/include/numkong/spatials/graniteamx.h +2 -0
  111. package/numkong/include/numkong/spatials/icelake.h +1 -0
  112. package/numkong/include/numkong/spatials/sierra.h +1 -0
  113. package/numkong/include/numkong/tensor.hpp +236 -123
  114. package/numkong/include/numkong/trigonometry/README.md +4 -4
  115. package/numkong/include/numkong/trigonometry/serial.h +6 -6
  116. package/numkong/include/numkong/trigonometry/v128relaxed.h +1 -0
  117. package/numkong/include/numkong/types.hpp +46 -16
  118. package/numkong/include/numkong/vector.hpp +116 -23
  119. package/numkong/javascript/@numkong-darwin-arm64/package.json +1 -1
  120. package/numkong/javascript/@numkong-darwin-x64/package.json +1 -1
  121. package/numkong/javascript/@numkong-linux-arm64/package.json +1 -1
  122. package/numkong/javascript/@numkong-linux-x64/package.json +1 -1
  123. package/numkong/javascript/@numkong-win32-arm64/package.json +1 -1
  124. package/numkong/javascript/@numkong-win32-x64/package.json +1 -1
  125. package/numkong/javascript/README.md +18 -16
  126. package/numkong/javascript/numkong.c +70 -40
  127. package/numkong/package.json +8 -8
  128. package/numkong/rust/README.md +19 -9
  129. package/numkong/rust/cast.rs +52 -1
  130. package/numkong/rust/each.rs +480 -7
  131. package/numkong/rust/matrix.rs +877 -216
  132. package/numkong/rust/numkong.rs +12 -8
  133. package/numkong/rust/reduce.rs +433 -5
  134. package/numkong/rust/tensor.rs +450 -842
  135. package/numkong/rust/vector.rs +520 -101
  136. package/numkong/swift/README.md +5 -3
  137. package/numkong/test/README.md +4 -4
  138. package/numkong/test/test-wasi.mjs +17 -0
  139. package/numkong/test/test_each.cpp +30 -1
  140. package/numkong/test/test_each.py +74 -1
  141. package/numkong/test/test_reduce.cpp +51 -0
  142. package/numkong/test/test_tensor.cpp +2 -63
  143. package/numkong/test/test_tensor.py +93 -0
  144. package/package.json +1 -1
  145. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  146. package/prebuilds/linux-arm64/usearch.node +0 -0
  147. package/prebuilds/linux-x64/usearch.node +0 -0
  148. package/prebuilds/win32-x64/usearch.node +0 -0
@@ -21,6 +21,10 @@
21
21
  #include "numkong/types.h"
22
22
  #include "numkong/maxsim/icelake.h" // `nk_maxsim_coarse_argmax_icelake_`
23
23
  #include "numkong/dot.h" // `nk_dot_bf16`
24
+ #include "numkong/cast/icelake.h" // `nk_e5m2x32_to_bf16x32_icelake_`
25
+ #include "numkong/dot/skylake.h" // `nk_dot_through_f32_finalize_skylake_`
26
+ #include "numkong/maxsim/serial.h" // `nk_maxsim_packed_header_setup_`, `nk_maxsim_packed_size_`
27
+ #include "numkong/reduce/skylake.h" // `nk_reduce_add_f32x16_skylake_`
24
28
 
25
29
  #if defined(__cplusplus)
26
30
  extern "C" {
@@ -30,6 +30,7 @@
30
30
  #include "numkong/dot.h" // `nk_dot_f32`, `nk_dot_f16`
31
31
  #include "numkong/cast/haswell.h" // `nk_f16_to_f32_haswell`
32
32
  #include "numkong/spatial/haswell.h" // `nk_f32_sqrt_haswell`
33
+ #include "numkong/reduce/skylake.h" // `nk_reduce_add_f32x16_skylake_`
33
34
 
34
35
  #if defined(__cplusplus)
35
36
  extern "C" {
@@ -71,15 +71,6 @@ NK_STATIC_ASSERT(sizeof(nk_maxsim_vector_metadata_t) == 12, nk_maxsim_vector_met
71
71
  */
72
72
  typedef void (*nk_maxsim_to_f32_t)(void const *source, nk_f32_t *destination);
73
73
 
74
- /* Keep the serial instantiations below actually scalar, regardless of build type.
75
- * See dots/serial.h for rationale. */
76
- #if defined(__clang__)
77
- #pragma clang attribute push(__attribute__((noinline)), apply_to = function)
78
- #elif defined(__GNUC__)
79
- #pragma GCC push_options
80
- #pragma GCC optimize("no-tree-vectorize", "no-tree-slp-vectorize", "no-ipa-cp-clone", "no-inline")
81
- #endif
82
-
83
74
  /** @brief Identity conversion for f32 sources — just a typed memcpy. */
84
75
  NK_INTERNAL void nk_f32_to_f32_(void const *source, nk_f32_t *destination) { *destination = *(nk_f32_t const *)source; }
85
76
 
@@ -234,6 +225,15 @@ NK_INTERNAL nk_size_t nk_maxsim_packed_size_( //
234
225
  return header_size + i8_region_size + metadata_region_size + originals_region_size;
235
226
  }
236
227
 
228
+ /* Keep the serial instantiations below actually scalar, regardless of build type.
229
+ * See dots/serial.h for rationale. */
230
+ #if defined(__clang__)
231
+ #pragma clang attribute push(__attribute__((noinline)), apply_to = function)
232
+ #elif defined(__GNUC__)
233
+ #pragma GCC push_options
234
+ #pragma GCC optimize("no-tree-vectorize", "no-tree-slp-vectorize", "no-ipa-cp-clone", "no-inline")
235
+ #endif
236
+
237
237
  NK_PUBLIC nk_size_t nk_maxsim_packed_size_bf16_serial(nk_size_t vector_count, nk_size_t depth) {
238
238
  return nk_maxsim_packed_size_(vector_count, depth, sizeof(nk_bf16_t), 1);
239
239
  }
@@ -323,6 +323,12 @@ NK_PUBLIC void nk_maxsim_pack_f16_serial( //
323
323
  }
324
324
  }
325
325
 
326
+ #if defined(__clang__)
327
+ #pragma clang attribute pop
328
+ #elif defined(__GNUC__)
329
+ #pragma GCC pop_options
330
+ #endif
331
+
326
332
  /**
327
333
  * @brief Dtype-agnostic coarse i8 argmax kernel for the serial backend.
328
334
  * Produces per-query best document indices using signed i8×i8 dot products.
@@ -389,6 +395,13 @@ NK_INTERNAL void nk_maxsim_coarse_argmax_serial_( //
389
395
  }
390
396
  }
391
397
 
398
+ #if defined(__clang__)
399
+ #pragma clang attribute push(__attribute__((noinline)), apply_to = function)
400
+ #elif defined(__GNUC__)
401
+ #pragma GCC push_options
402
+ #pragma GCC optimize("no-tree-vectorize", "no-tree-slp-vectorize", "no-ipa-cp-clone", "no-inline")
403
+ #endif
404
+
392
405
  NK_PUBLIC void nk_maxsim_packed_bf16_serial( //
393
406
  void const *query_packed, void const *document_packed, nk_size_t query_count, nk_size_t document_count,
394
407
  nk_size_t depth, nk_f32_t *result) {
@@ -46,8 +46,9 @@
46
46
  #if NK_TARGET_ARM64_
47
47
  #if NK_TARGET_SME
48
48
 
49
- #include "numkong/dots/sme.h" // `nk_dots_sme_packed_header_t`
50
- #include "numkong/reduce/sve.h" // `nk_svaddv_f64_`
49
+ #include "numkong/dots/sme.h" // `nk_dots_sme_packed_header_t`
50
+ #include "numkong/reduce/sve.h" // `nk_svaddv_f64_`
51
+ #include "numkong/scalar/neon.h" // `nk_f64_rsqrt_neon`
51
52
 
52
53
  #if defined(__cplusplus)
53
54
  extern "C" {
@@ -51,10 +51,13 @@ def rmsd(a: np.ndarray, b: np.ndarray) -> float:
51
51
  | Input Type | Output Type | Description |
52
52
  | ---------- | ----------- | ---------------------------------------------- |
53
53
  | `f64` | `f64` | 64-bit IEEE 754 double precision |
54
- | `f32` | `f32` | 32-bit IEEE 754 single precision |
54
+ | `f32` | `f64` | 32-bit IEEE 754 single precision, widened RMSD |
55
55
  | `f16` | `f32` | 16-bit IEEE 754 half precision, widened output |
56
56
  | `bf16` | `f32` | 16-bit brain float, widened output |
57
57
 
58
+ The output type above is the RMSD result.
59
+ The centroid, rotation, and scale outputs use the transform type instead: `f64` for `f64` inputs, `f32` for `f32`, `f16`, and `bf16` inputs.
60
+
58
61
  ## Optimizations
59
62
 
60
63
  ### McAdams Branching-Free 3×3 SVD
@@ -199,9 +202,9 @@ Measured with Wasmtime v43 (Cranelift backend), WASI-SDK 24, `-msimd128 -mrelaxe
199
202
  | `nk_kabsch_bf16_neonbfdot` | 180 mp/s, 0.7 ulp | 448 mp/s, 0.9 ulp | 726 mp/s, 1.3 ulp |
200
203
  | `nk_umeyama_bf16_neonbfdot` | 176 mp/s, 0.2 ulp | 433 mp/s, 0.4 ulp | 705 mp/s, 0.8 ulp |
201
204
  | __f16__ | ░░░░░░░░░░░░░░░░░░░░░░░░ | ░░░░░░░░░░░░░░░░░░░░░░░░ | ░░░░░░░░░░░░░░░░░░░░░░░░ |
202
- | `nk_rmsd_f16_neonhalf` | 2,998 mp/s, 0.4 ulp | 3,215 mp/s, 1.7 ulp | 3,216 mp/s, 4.6 ulp |
203
- | `nk_kabsch_f16_neonhalf` | 178 mp/s, 0.9 ulp | 443 mp/s, 1.3 ulp | 711 mp/s, 2.4 ulp |
204
- | `nk_umeyama_f16_neonhalf` | 175 mp/s, 0.4 ulp | 408 mp/s, 0.8 ulp | 620 mp/s, 1.5 ulp |
205
+ | `nk_rmsd_f16_neon` | 2,998 mp/s, 0.4 ulp | 3,215 mp/s, 1.7 ulp | 3,216 mp/s, 4.6 ulp |
206
+ | `nk_kabsch_f16_neon` | 178 mp/s, 0.9 ulp | 443 mp/s, 1.3 ulp | 711 mp/s, 2.4 ulp |
207
+ | `nk_umeyama_f16_neon` | 175 mp/s, 0.4 ulp | 408 mp/s, 0.8 ulp | 620 mp/s, 1.5 ulp |
205
208
 
206
209
  #### WASM
207
210
 
@@ -410,7 +410,7 @@ NK_PUBLIC void nk_umeyama_bf16_genoa(nk_bf16_t const *a, nk_bf16_t const *b, nk_
410
410
  optimal_rotation[3] = 0.0f, optimal_rotation[4] = 1.0f, optimal_rotation[5] = 0.0f;
411
411
  optimal_rotation[6] = 0.0f, optimal_rotation[7] = 0.0f, optimal_rotation[8] = 1.0f;
412
412
  trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
413
- c = centered_norm_squared_a > 0.0f ? trace_rotation_covariance / centered_norm_squared_a : 0.0f;
413
+ c = trace_rotation_covariance / centered_norm_squared_a;
414
414
  }
415
415
  else {
416
416
  nk_f32_t svd_left[9], svd_diagonal[9], svd_right[9];
@@ -422,7 +422,7 @@ NK_PUBLIC void nk_umeyama_bf16_genoa(nk_bf16_t const *a, nk_bf16_t const *b, nk_
422
422
  nk_f32_t d3 = det < 0.0f ? -1.0f : 1.0f;
423
423
  nk_f32_t trace_ds = nk_sum_three_products_f32_(svd_diagonal[0], 1.0f, svd_diagonal[4], 1.0f, svd_diagonal[8],
424
424
  d3);
425
- c = centered_norm_squared_a > 0.0f ? trace_ds / centered_norm_squared_a : 0.0f;
425
+ c = trace_ds / centered_norm_squared_a;
426
426
 
427
427
  if (det < 0.0f) {
428
428
  svd_right[2] = -svd_right[2], svd_right[5] = -svd_right[5], svd_right[8] = -svd_right[8];
@@ -763,7 +763,7 @@ NK_PUBLIC void nk_umeyama_f32_haswell(nk_f32_t const *a, nk_f32_t const *b, nk_s
763
763
  optimal_rotation[3] = 0.0, optimal_rotation[4] = 1.0, optimal_rotation[5] = 0.0;
764
764
  optimal_rotation[6] = 0.0, optimal_rotation[7] = 0.0, optimal_rotation[8] = 1.0;
765
765
  trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
766
- applied_scale = centered_norm_squared_a > 0.0 ? trace_rotation_covariance / centered_norm_squared_a : 0.0;
766
+ applied_scale = trace_rotation_covariance / centered_norm_squared_a;
767
767
  }
768
768
  else {
769
769
  nk_f64_t svd_left[9], svd_diagonal[9], svd_right[9];
@@ -792,7 +792,7 @@ NK_PUBLIC void nk_umeyama_f32_haswell(nk_f32_t const *a, nk_f32_t const *b, nk_s
792
792
  optimal_rotation[8] = svd_right[6] * svd_left[6] + svd_right[7] * svd_left[7] + svd_right[8] * svd_left[8];
793
793
  }
794
794
  nk_f64_t trace_ds = svd_diagonal[0] + svd_diagonal[4] + sign_correction * svd_diagonal[8];
795
- applied_scale = centered_norm_squared_a > 0.0 ? trace_ds / centered_norm_squared_a : 0.0;
795
+ applied_scale = trace_ds / centered_norm_squared_a;
796
796
  trace_rotation_covariance =
797
797
  optimal_rotation[0] * cross_covariance[0] + optimal_rotation[1] * cross_covariance[3] +
798
798
  optimal_rotation[2] * cross_covariance[6] + optimal_rotation[3] * cross_covariance[1] +
@@ -962,7 +962,7 @@ NK_PUBLIC void nk_umeyama_f64_haswell(nk_f64_t const *a, nk_f64_t const *b, nk_s
962
962
  optimal_rotation[3] = 0.0, optimal_rotation[4] = 1.0, optimal_rotation[5] = 0.0;
963
963
  optimal_rotation[6] = 0.0, optimal_rotation[7] = 0.0, optimal_rotation[8] = 1.0;
964
964
  trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
965
- c = centered_norm_squared_a > 0.0 ? trace_rotation_covariance / centered_norm_squared_a : 0.0;
965
+ c = trace_rotation_covariance / centered_norm_squared_a;
966
966
  }
967
967
  else {
968
968
  nk_f64_t svd_left[9], svd_diagonal[9], svd_right[9];
@@ -972,7 +972,7 @@ NK_PUBLIC void nk_umeyama_f64_haswell(nk_f64_t const *a, nk_f64_t const *b, nk_s
972
972
  nk_f64_t det = nk_det3x3_f64_(optimal_rotation);
973
973
  nk_f64_t d3 = det < 0 ? -1.0 : 1.0;
974
974
  nk_f64_t trace_ds = nk_sum_three_products_f64_(svd_diagonal[0], 1.0, svd_diagonal[4], 1.0, svd_diagonal[8], d3);
975
- c = centered_norm_squared_a > 0.0 ? trace_ds / centered_norm_squared_a : 0.0;
975
+ c = trace_ds / centered_norm_squared_a;
976
976
 
977
977
  if (det < 0) {
978
978
  svd_right[2] = -svd_right[2], svd_right[5] = -svd_right[5], svd_right[8] = -svd_right[8];
@@ -1633,7 +1633,7 @@ NK_PUBLIC void nk_umeyama_f16_haswell(nk_f16_t const *a, nk_f16_t const *b, nk_s
1633
1633
  optimal_rotation[3] = 0.0f, optimal_rotation[4] = 1.0f, optimal_rotation[5] = 0.0f;
1634
1634
  optimal_rotation[6] = 0.0f, optimal_rotation[7] = 0.0f, optimal_rotation[8] = 1.0f;
1635
1635
  trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
1636
- applied_scale = centered_norm_squared_a > 0.0f ? trace_rotation_covariance / centered_norm_squared_a : 0.0f;
1636
+ applied_scale = trace_rotation_covariance / centered_norm_squared_a;
1637
1637
  }
1638
1638
  else {
1639
1639
  nk_f32_t svd_left[9], svd_diagonal[9], svd_right[9];
@@ -1664,7 +1664,7 @@ NK_PUBLIC void nk_umeyama_f16_haswell(nk_f16_t const *a, nk_f16_t const *b, nk_s
1664
1664
  optimal_rotation[8] = svd_right[6] * svd_left[6] + svd_right[7] * svd_left[7] + svd_right[8] * svd_left[8];
1665
1665
  }
1666
1666
  nk_f32_t trace_ds = svd_diagonal[0] + svd_diagonal[4] + sign_correction * svd_diagonal[8];
1667
- applied_scale = centered_norm_squared_a > 0.0f ? trace_ds / centered_norm_squared_a : 0.0f;
1667
+ applied_scale = trace_ds / centered_norm_squared_a;
1668
1668
  trace_rotation_covariance =
1669
1669
  optimal_rotation[0] * cross_covariance[0] + optimal_rotation[1] * cross_covariance[3] +
1670
1670
  optimal_rotation[2] * cross_covariance[6] + optimal_rotation[3] * cross_covariance[1] +
@@ -1819,7 +1819,7 @@ NK_PUBLIC void nk_umeyama_bf16_haswell(nk_bf16_t const *a, nk_bf16_t const *b, n
1819
1819
  optimal_rotation[3] = 0.0f, optimal_rotation[4] = 1.0f, optimal_rotation[5] = 0.0f;
1820
1820
  optimal_rotation[6] = 0.0f, optimal_rotation[7] = 0.0f, optimal_rotation[8] = 1.0f;
1821
1821
  trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
1822
- applied_scale = centered_norm_squared_a > 0.0f ? trace_rotation_covariance / centered_norm_squared_a : 0.0f;
1822
+ applied_scale = trace_rotation_covariance / centered_norm_squared_a;
1823
1823
  }
1824
1824
  else {
1825
1825
  nk_f32_t svd_left[9], svd_diagonal[9], svd_right[9];
@@ -1850,7 +1850,7 @@ NK_PUBLIC void nk_umeyama_bf16_haswell(nk_bf16_t const *a, nk_bf16_t const *b, n
1850
1850
  optimal_rotation[8] = svd_right[6] * svd_left[6] + svd_right[7] * svd_left[7] + svd_right[8] * svd_left[8];
1851
1851
  }
1852
1852
  nk_f32_t trace_ds = svd_diagonal[0] + svd_diagonal[4] + sign_correction * svd_diagonal[8];
1853
- applied_scale = centered_norm_squared_a > 0.0f ? trace_ds / centered_norm_squared_a : 0.0f;
1853
+ applied_scale = trace_ds / centered_norm_squared_a;
1854
1854
  trace_rotation_covariance =
1855
1855
  optimal_rotation[0] * cross_covariance[0] + optimal_rotation[1] * cross_covariance[3] +
1856
1856
  optimal_rotation[2] * cross_covariance[6] + optimal_rotation[3] * cross_covariance[1] +
@@ -853,7 +853,7 @@ NK_PUBLIC void nk_umeyama_f32_neon(nk_f32_t const *a, nk_f32_t const *b, nk_size
853
853
  optimal_rotation[4] = 1, optimal_rotation[5] = 0, optimal_rotation[6] = 0, optimal_rotation[7] = 0,
854
854
  optimal_rotation[8] = 1;
855
855
  trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
856
- applied_scale = centered_norm_squared_a > 0.0 ? trace_rotation_covariance / centered_norm_squared_a : 0.0;
856
+ applied_scale = trace_rotation_covariance / centered_norm_squared_a;
857
857
  }
858
858
  else {
859
859
  nk_f64_t svd_left[9], svd_diagonal[9], svd_right[9];
@@ -884,7 +884,7 @@ NK_PUBLIC void nk_umeyama_f32_neon(nk_f32_t const *a, nk_f32_t const *b, nk_size
884
884
  }
885
885
 
886
886
  nk_f64_t trace_ds = svd_diagonal[0] + svd_diagonal[4] + sign_correction * svd_diagonal[8];
887
- applied_scale = centered_norm_squared_a > 0.0 ? trace_ds / centered_norm_squared_a : 0.0;
887
+ applied_scale = trace_ds / centered_norm_squared_a;
888
888
 
889
889
  trace_rotation_covariance =
890
890
  optimal_rotation[0] * cross_covariance[0] + optimal_rotation[1] * cross_covariance[3] +
@@ -1139,7 +1139,7 @@ NK_PUBLIC void nk_umeyama_f64_neon(nk_f64_t const *a, nk_f64_t const *b, nk_size
1139
1139
  optimal_rotation[4] = 1, optimal_rotation[5] = 0, optimal_rotation[6] = 0, optimal_rotation[7] = 0,
1140
1140
  optimal_rotation[8] = 1;
1141
1141
  trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
1142
- computed_scale = centered_norm_squared_a > 0.0 ? trace_rotation_covariance / centered_norm_squared_a : 0.0;
1142
+ computed_scale = trace_rotation_covariance / centered_norm_squared_a;
1143
1143
  }
1144
1144
  else {
1145
1145
  nk_f64_t svd_left[9], svd_diagonal[9], svd_right[9];
@@ -1149,7 +1149,7 @@ NK_PUBLIC void nk_umeyama_f64_neon(nk_f64_t const *a, nk_f64_t const *b, nk_size
1149
1149
  // Handle reflection and compute scale
1150
1150
  nk_f64_t det = nk_det3x3_f64_(optimal_rotation);
1151
1151
  nk_f64_t trace_d_s = svd_diagonal[0] + svd_diagonal[4] + (det < 0 ? -svd_diagonal[8] : svd_diagonal[8]);
1152
- computed_scale = centered_norm_squared_a > 0.0 ? trace_d_s / centered_norm_squared_a : 0.0;
1152
+ computed_scale = trace_d_s / centered_norm_squared_a;
1153
1153
 
1154
1154
  if (det < 0) {
1155
1155
  svd_right[2] = -svd_right[2], svd_right[5] = -svd_right[5], svd_right[8] = -svd_right[8];
@@ -1679,7 +1679,7 @@ NK_PUBLIC void nk_umeyama_f16_neon(nk_f16_t const *a, nk_f16_t const *b, nk_size
1679
1679
  optimal_rotation[4] = 1, optimal_rotation[5] = 0, optimal_rotation[6] = 0, optimal_rotation[7] = 0,
1680
1680
  optimal_rotation[8] = 1;
1681
1681
  trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
1682
- scale_factor = centered_norm_squared_a > 0.0f ? trace_rotation_covariance / centered_norm_squared_a : 0.0f;
1682
+ scale_factor = trace_rotation_covariance / centered_norm_squared_a;
1683
1683
  }
1684
1684
  else {
1685
1685
  // SVD of H = U * S * Vᵀ
@@ -1701,7 +1701,7 @@ NK_PUBLIC void nk_umeyama_f16_neon(nk_f16_t const *a, nk_f16_t const *b, nk_size
1701
1701
  nk_f32_t rotation_determinant = nk_det3x3_f32_(optimal_rotation);
1702
1702
  nk_f32_t sign_det = rotation_determinant < 0 ? -1.0f : 1.0f;
1703
1703
  nk_f32_t trace_scaled_s = svd_diagonal[0] + svd_diagonal[4] + sign_det * svd_diagonal[8];
1704
- scale_factor = centered_norm_squared_a > 0.0f ? trace_scaled_s / centered_norm_squared_a : 0.0f;
1704
+ scale_factor = trace_scaled_s / centered_norm_squared_a;
1705
1705
 
1706
1706
  if (rotation_determinant < 0) {
1707
1707
  svd_right[2] = -svd_right[2], svd_right[5] = -svd_right[5], svd_right[8] = -svd_right[8];
@@ -40,6 +40,7 @@
40
40
  #include "numkong/types.h"
41
41
  #include "numkong/cast/neon.h" // `nk_u16x8_splat_`
42
42
  #include "numkong/spatial/neon.h" // `nk_f32_sqrt_neon`
43
+ #include "numkong/mesh/serial.h" // `nk_det3x3_f32_`, `nk_svd3x3_f32_`
43
44
 
44
45
  #if defined(__cplusplus)
45
46
  extern "C" {
@@ -559,7 +560,7 @@ NK_PUBLIC void nk_umeyama_bf16_neonbfdot(nk_bf16_t const *a, nk_bf16_t const *b,
559
560
  optimal_rotation[4] = 1, optimal_rotation[5] = 0, optimal_rotation[6] = 0, optimal_rotation[7] = 0,
560
561
  optimal_rotation[8] = 1;
561
562
  trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
562
- c = centered_norm_squared_a > 0.0f ? trace_rotation_covariance / centered_norm_squared_a : 0.0f;
563
+ c = trace_rotation_covariance / centered_norm_squared_a;
563
564
  }
564
565
  else {
565
566
  nk_f32_t svd_left[9], svd_diagonal[9], svd_right[9];
@@ -581,7 +582,7 @@ NK_PUBLIC void nk_umeyama_bf16_neonbfdot(nk_bf16_t const *a, nk_bf16_t const *b,
581
582
  nk_f32_t rotation_det = nk_det3x3_f32_(optimal_rotation);
582
583
  nk_f32_t sign_det = rotation_det < 0 ? -1.0f : 1.0f;
583
584
  nk_f32_t trace_scaled_s = svd_diagonal[0] + svd_diagonal[4] + sign_det * svd_diagonal[8];
584
- c = centered_norm_squared_a > 0.0f ? trace_scaled_s / centered_norm_squared_a : 0.0f;
585
+ c = trace_scaled_s / centered_norm_squared_a;
585
586
 
586
587
  if (rotation_det < 0) {
587
588
  svd_right[2] = -svd_right[2], svd_right[5] = -svd_right[5], svd_right[8] = -svd_right[8];
@@ -38,6 +38,7 @@
38
38
 
39
39
  #include "numkong/types.h"
40
40
  #include "numkong/spatial/neon.h" // `nk_f32_sqrt_neon`
41
+ #include "numkong/mesh/serial.h" // `nk_det3x3_f32_`, `nk_svd3x3_f32_`
41
42
 
42
43
  #if defined(__cplusplus)
43
44
  extern "C" {
@@ -604,7 +605,7 @@ NK_PUBLIC void nk_umeyama_f16_neonfhm(nk_f16_t const *a, nk_f16_t const *b, nk_s
604
605
  optimal_rotation[4] = 1, optimal_rotation[5] = 0, optimal_rotation[6] = 0, optimal_rotation[7] = 0,
605
606
  optimal_rotation[8] = 1;
606
607
  trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
607
- scale_factor = centered_norm_squared_a > 0.0f ? trace_rotation_covariance / centered_norm_squared_a : 0.0f;
608
+ scale_factor = trace_rotation_covariance / centered_norm_squared_a;
608
609
  }
609
610
  else {
610
611
  // SVD of H = U · S · Vᵀ
@@ -626,7 +627,7 @@ NK_PUBLIC void nk_umeyama_f16_neonfhm(nk_f16_t const *a, nk_f16_t const *b, nk_s
626
627
  nk_f32_t rotation_determinant = nk_det3x3_f32_(optimal_rotation);
627
628
  nk_f32_t sign_det = rotation_determinant < 0 ? -1.0f : 1.0f;
628
629
  nk_f32_t trace_scaled_s = svd_diagonal[0] + svd_diagonal[4] + sign_det * svd_diagonal[8];
629
- scale_factor = centered_norm_squared_a > 0.0f ? trace_scaled_s / centered_norm_squared_a : 0.0f;
630
+ scale_factor = trace_scaled_s / centered_norm_squared_a;
630
631
 
631
632
  if (rotation_determinant < 0) {
632
633
  svd_right[2] = -svd_right[2], svd_right[5] = -svd_right[5], svd_right[8] = -svd_right[8];
@@ -884,7 +884,7 @@ NK_PUBLIC void nk_umeyama_f32_rvv(nk_f32_t const *a, nk_f32_t const *b, nk_size_
884
884
  optimal_rotation[4] = 1, optimal_rotation[5] = 0, optimal_rotation[6] = 0, optimal_rotation[7] = 0,
885
885
  optimal_rotation[8] = 1;
886
886
  trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
887
- scale_factor = centered_norm_squared_a > 0.0 ? trace_rotation_covariance / centered_norm_squared_a : 0.0;
887
+ scale_factor = trace_rotation_covariance / centered_norm_squared_a;
888
888
  }
889
889
  else {
890
890
  nk_f64_t svd_left[9], svd_diagonal[9], svd_right[9];
@@ -894,7 +894,7 @@ NK_PUBLIC void nk_umeyama_f32_rvv(nk_f32_t const *a, nk_f32_t const *b, nk_size_
894
894
  nk_f64_t sign_det = det < 0 ? -1.0 : 1.0;
895
895
  nk_f64_t trace_ds = nk_sum_three_products_f64_(svd_diagonal[0], 1.0, svd_diagonal[4], 1.0, svd_diagonal[8],
896
896
  sign_det);
897
- scale_factor = centered_norm_squared_a > 0.0 ? trace_ds / centered_norm_squared_a : 0.0;
897
+ scale_factor = trace_ds / centered_norm_squared_a;
898
898
  if (det < 0) {
899
899
  svd_right[2] = -svd_right[2], svd_right[5] = -svd_right[5], svd_right[8] = -svd_right[8];
900
900
  nk_rotation_from_svd_f64_serial_(svd_left, svd_right, optimal_rotation);
@@ -944,7 +944,7 @@ NK_PUBLIC void nk_umeyama_f64_rvv(nk_f64_t const *a, nk_f64_t const *b, nk_size_
944
944
  optimal_rotation[4] = 1, optimal_rotation[5] = 0, optimal_rotation[6] = 0, optimal_rotation[7] = 0,
945
945
  optimal_rotation[8] = 1;
946
946
  trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
947
- scale_factor = centered_norm_squared_a > 0.0 ? trace_rotation_covariance / centered_norm_squared_a : 0.0;
947
+ scale_factor = trace_rotation_covariance / centered_norm_squared_a;
948
948
  }
949
949
  else {
950
950
  nk_f64_t svd_left[9], svd_diagonal[9], svd_right[9];
@@ -954,7 +954,7 @@ NK_PUBLIC void nk_umeyama_f64_rvv(nk_f64_t const *a, nk_f64_t const *b, nk_size_
954
954
  nk_f64_t sign_det = det < 0 ? -1.0 : 1.0;
955
955
  nk_f64_t trace_ds = nk_sum_three_products_f64_(svd_diagonal[0], 1.0, svd_diagonal[4], 1.0, svd_diagonal[8],
956
956
  sign_det);
957
- scale_factor = centered_norm_squared_a > 0.0 ? trace_ds / centered_norm_squared_a : 0.0;
957
+ scale_factor = trace_ds / centered_norm_squared_a;
958
958
  if (det < 0) {
959
959
  svd_right[2] = -svd_right[2], svd_right[5] = -svd_right[5], svd_right[8] = -svd_right[8];
960
960
  nk_rotation_from_svd_f64_serial_(svd_left, svd_right, optimal_rotation);
@@ -272,17 +272,6 @@ extern "C" {
272
272
  m[2] * (m[3] * m[7] - m[4] * m[6]); \
273
273
  }
274
274
 
275
- /* Keep the serial instantiations below actually scalar, regardless of build type.
276
- * Without this, -O3 + LTO can vectorize or clone the serial kernels under AVX-512
277
- * callers in dispatch_*.c, which wastes binary and breaks the nk_*_serial-as-scalar-oracle
278
- * contract that tests and numerical-stability docs rely on. See dots/serial.h. */
279
- #if defined(__clang__)
280
- #pragma clang attribute push(__attribute__((noinline)), apply_to = function)
281
- #elif defined(__GNUC__)
282
- #pragma GCC push_options
283
- #pragma GCC optimize("no-tree-vectorize", "no-tree-slp-vectorize", "no-ipa-cp-clone", "no-inline")
284
- #endif
285
-
286
275
  /* Size bias for release. Gated on NDEBUG so Debug builds keep -O0 for stepping. */
287
276
  #if defined(NDEBUG)
288
277
  #if defined(_MSC_VER)
@@ -701,6 +690,17 @@ nk_define_det3x3_(f64)
701
690
  *result = (nk_##result_type##_t)compute_sqrt((sum_squared + sum_squared_compensation) * inv_n); \
702
691
  }
703
692
 
693
+ /* Keep the serial instantiations below actually scalar, regardless of build type.
694
+ * Without this, -O3 + LTO can vectorize or clone the serial kernels under AVX-512
695
+ * callers in dispatch_*.c, which wastes binary and breaks the nk_*_serial-as-scalar-oracle
696
+ * contract that tests and numerical-stability docs rely on. See dots/serial.h. */
697
+ #if defined(__clang__)
698
+ #pragma clang attribute push(__attribute__((noinline)), apply_to = function)
699
+ #elif defined(__GNUC__)
700
+ #pragma GCC push_options
701
+ #pragma GCC optimize("no-tree-vectorize", "no-tree-slp-vectorize", "no-ipa-cp-clone", "no-inline")
702
+ #endif
703
+
704
704
  nk_define_rmsd_(f64, f64, f64, f64, nk_assign_from_to_, nk_f64_sqrt_serial) // nk_rmsd_f64_serial
705
705
  nk_define_kabsch_(f64, f64, f64, f64, f64, nk_assign_from_to_, nk_f64_sqrt_serial) // nk_kabsch_f64_serial
706
706
  nk_define_umeyama_(f64, f64, f64, f64, f64, nk_assign_from_to_, nk_f64_sqrt_serial) // nk_umeyama_f64_serial
@@ -740,21 +740,21 @@ nk_define_umeyama_(bf16, f32, f32, f32, f32, nk_bf16_to_f32_serial, nk_f32_sqrt_
740
740
  #undef nk_define_kabsch_
741
741
  #undef nk_define_umeyama_
742
742
 
743
- #if defined(NDEBUG)
744
- #if defined(_MSC_VER)
745
- #pragma optimize("", on)
746
- #elif defined(__clang__)
743
+ #if defined(__clang__)
747
744
  #pragma clang attribute pop
748
745
  #elif defined(__GNUC__)
749
746
  #pragma GCC pop_options
750
747
  #endif
751
- #endif
752
748
 
753
- #if defined(__clang__)
749
+ #if defined(NDEBUG)
750
+ #if defined(_MSC_VER)
751
+ #pragma optimize("", on)
752
+ #elif defined(__clang__)
754
753
  #pragma clang attribute pop
755
754
  #elif defined(__GNUC__)
756
755
  #pragma GCC pop_options
757
756
  #endif
757
+ #endif
758
758
 
759
759
  #if defined(__cplusplus)
760
760
  } // extern "C"
@@ -1157,7 +1157,7 @@ NK_PUBLIC void nk_umeyama_f64_skylake(nk_f64_t const *a, nk_f64_t const *b, nk_s
1157
1157
  optimal_rotation[4] = 1, optimal_rotation[5] = 0, optimal_rotation[6] = 0, optimal_rotation[7] = 0,
1158
1158
  optimal_rotation[8] = 1;
1159
1159
  trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
1160
- c = centered_norm_squared_a > 0.0 ? trace_rotation_covariance / centered_norm_squared_a : 0.0;
1160
+ c = trace_rotation_covariance / centered_norm_squared_a;
1161
1161
  }
1162
1162
  else {
1163
1163
  nk_f64_t svd_left[9], svd_diagonal[9], svd_right[9];
@@ -1168,7 +1168,7 @@ NK_PUBLIC void nk_umeyama_f64_skylake(nk_f64_t const *a, nk_f64_t const *b, nk_s
1168
1168
  nk_f64_t det = nk_det3x3_f64_(optimal_rotation);
1169
1169
  nk_f64_t d3 = det < 0 ? -1.0 : 1.0;
1170
1170
  nk_f64_t trace_ds = nk_sum_three_products_f64_(svd_diagonal[0], 1.0, svd_diagonal[4], 1.0, svd_diagonal[8], d3);
1171
- c = centered_norm_squared_a > 0.0 ? trace_ds / centered_norm_squared_a : 0.0;
1171
+ c = trace_ds / centered_norm_squared_a;
1172
1172
 
1173
1173
  if (det < 0) {
1174
1174
  svd_right[2] = -svd_right[2], svd_right[5] = -svd_right[5], svd_right[8] = -svd_right[8];
@@ -1620,7 +1620,7 @@ NK_PUBLIC void nk_umeyama_f16_skylake(nk_f16_t const *a, nk_f16_t const *b, nk_s
1620
1620
  nk_f32_t det = nk_det3x3_f32_(optimal_rotation);
1621
1621
  nk_f32_t d3 = det < 0.0f ? -1.0f : 1.0f;
1622
1622
  nk_f32_t trace_ds = nk_sum_three_products_f32_(svd_diagonal[0], 1.0f, svd_diagonal[4], 1.0f, svd_diagonal[8], d3);
1623
- nk_f32_t c = centered_norm_squared_a > 0.0f ? trace_ds / centered_norm_squared_a : 0.0f;
1623
+ nk_f32_t c = trace_ds / centered_norm_squared_a;
1624
1624
  if (scale) *scale = c;
1625
1625
 
1626
1626
  if (det < 0.0f) {
@@ -1749,7 +1749,7 @@ NK_PUBLIC void nk_umeyama_bf16_skylake(nk_bf16_t const *a, nk_bf16_t const *b, n
1749
1749
  nk_f32_t det = nk_det3x3_f32_(optimal_rotation);
1750
1750
  nk_f32_t d3 = det < 0.0f ? -1.0f : 1.0f;
1751
1751
  nk_f32_t trace_ds = nk_sum_three_products_f32_(svd_diagonal[0], 1.0f, svd_diagonal[4], 1.0f, svd_diagonal[8], d3);
1752
- nk_f32_t c = centered_norm_squared_a > 0.0f ? trace_ds / centered_norm_squared_a : 0.0f;
1752
+ nk_f32_t c = trace_ds / centered_norm_squared_a;
1753
1753
  if (scale) *scale = c;
1754
1754
 
1755
1755
  if (det < 0.0f) {
@@ -842,7 +842,7 @@ NK_PUBLIC void nk_umeyama_f32_v128relaxed(nk_f32_t const *a, nk_f32_t const *b,
842
842
  optimal_rotation[4] = 1, optimal_rotation[5] = 0, optimal_rotation[6] = 0, optimal_rotation[7] = 0,
843
843
  optimal_rotation[8] = 1;
844
844
  trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
845
- computed_scale = centered_norm_squared_a > 0.0 ? trace_rotation_covariance / centered_norm_squared_a : 0.0;
845
+ computed_scale = trace_rotation_covariance / centered_norm_squared_a;
846
846
  }
847
847
  else {
848
848
  nk_f64_t svd_left[9], svd_diagonal[9], svd_right[9];
@@ -874,7 +874,7 @@ NK_PUBLIC void nk_umeyama_f32_v128relaxed(nk_f32_t const *a, nk_f32_t const *b,
874
874
 
875
875
  nk_f64_t trace_signed_singular_values = svd_diagonal[0] + svd_diagonal[4] +
876
876
  (det < 0 ? -svd_diagonal[8] : svd_diagonal[8]);
877
- computed_scale = centered_norm_squared_a > 0.0 ? trace_signed_singular_values / centered_norm_squared_a : 0.0;
877
+ computed_scale = trace_signed_singular_values / centered_norm_squared_a;
878
878
 
879
879
  trace_rotation_covariance =
880
880
  optimal_rotation[0] * cross_covariance[0] + optimal_rotation[1] * cross_covariance[3] +
@@ -1059,7 +1059,7 @@ NK_PUBLIC void nk_umeyama_f64_v128relaxed(nk_f64_t const *a, nk_f64_t const *b,
1059
1059
  optimal_rotation[4] = 1, optimal_rotation[5] = 0, optimal_rotation[6] = 0, optimal_rotation[7] = 0,
1060
1060
  optimal_rotation[8] = 1;
1061
1061
  trace_rotation_covariance = cross_covariance[0] + cross_covariance[4] + cross_covariance[8];
1062
- computed_scale = centered_norm_squared_a > 0.0 ? trace_rotation_covariance / centered_norm_squared_a : 0.0;
1062
+ computed_scale = trace_rotation_covariance / centered_norm_squared_a;
1063
1063
  }
1064
1064
  else {
1065
1065
  nk_f64_t svd_left[9], svd_diagonal[9], svd_right[9];
@@ -1069,7 +1069,7 @@ NK_PUBLIC void nk_umeyama_f64_v128relaxed(nk_f64_t const *a, nk_f64_t const *b,
1069
1069
  // Handle reflection and compute scale
1070
1070
  nk_f64_t det = nk_det3x3_f64_(optimal_rotation);
1071
1071
  nk_f64_t trace_d_s = svd_diagonal[0] + svd_diagonal[4] + (det < 0 ? -svd_diagonal[8] : svd_diagonal[8]);
1072
- computed_scale = centered_norm_squared_a > 0.0 ? trace_d_s / centered_norm_squared_a : 0.0;
1072
+ computed_scale = trace_d_s / centered_norm_squared_a;
1073
1073
 
1074
1074
  if (det < 0) {
1075
1075
  svd_right[2] = -svd_right[2], svd_right[5] = -svd_right[5], svd_right[8] = -svd_right[8];
@@ -319,8 +319,8 @@ void svd3x3_(scalar_type_ const *a, scalar_type_ *svd_u, scalar_type_ *svd_s, sc
319
319
 
320
320
  /**
321
321
  * @brief Root Mean Square Deviation between two 3D point clouds (no alignment)
322
- * @param[in] a,b Point clouds [d x 3] interleaved (x0,y0,z0, x1,y1,z1, ...)
323
- * @param[in] d Number of 3D points
322
+ * @param[in] a,b Point clouds [n x 3] interleaved (x0,y0,z0, x1,y1,z1, ...)
323
+ * @param[in] n Number of 3D points
324
324
  * @param[out] a_centroid,b_centroid Centroids (3 values each), can be nullptr
325
325
  * @param[out] rotation 3x3 rotation matrix (9 values), always identity, can be nullptr
326
326
  * @param[out] scale Scale factor, always 1.0, can be nullptr
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @brief SIMD-accelerated Similarity Measures and Distance Functions.
3
- * @file include/numkong.h
3
+ * @file include/numkong/numkong.h
4
4
  * @author Ash Vardanian
5
5
  * @date March 14, 2023
6
6
  *
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @brief NumKong SDK for C++23 and newer.
3
- * @file include/numkong.hpp
3
+ * @file include/numkong/numkong.hpp
4
4
  * @author Ash Vardanian
5
5
  * @date January 7, 2026
6
6
  *
@@ -6,7 +6,7 @@ These are used in variational inference, topic modeling, and distribution compar
6
6
  Kullback-Leibler divergence from $P$ to $Q$:
7
7
 
8
8
  $$
9
- \text{KLD}(P \| Q) = \sum_{i=0}^{n-1} P(i) \log_2 \frac{P(i)}{Q(i)}
9
+ \text{KLD}(P \| Q) = \sum_{i=0}^{n-1} P(i) \ln \frac{P(i)}{Q(i)}
10
10
  $$
11
11
 
12
12
  Jensen-Shannon distance is the square root of the symmetrized KLD through a mixture:
@@ -28,9 +28,8 @@ Reformulating as Python pseudocode:
28
28
  ```python
29
29
  import numpy as np
30
30
 
31
- def kld(p: np.ndarray, q: np.ndarray) -> float:
32
- mask = p > 0
33
- return np.sum(p[mask] * np.log2(p[mask] / q[mask]))
31
+ def kld(p: np.ndarray, q: np.ndarray, eps: float = 1e-7) -> float:
32
+ return np.sum(p * np.log((p + eps) / (q + eps)))
34
33
 
35
34
  def jsd(p: np.ndarray, q: np.ndarray) -> float:
36
35
  m = (p + q) / 2
@@ -45,12 +44,12 @@ __Jensen-Shannon distance__ is commonly used in microbiome community comparison
45
44
 
46
45
  ## Input & Output Types
47
46
 
48
- | Input Type | Output Type | Description |
49
- | ---------- | ----------- | ---------------------------------------------- |
50
- | `f64` | `f64` | 64-bit IEEE 754 double precision |
51
- | `f32` | `f32` | 32-bit IEEE 754 single precision |
52
- | `f16` | `f32` | 16-bit IEEE 754 half precision, widened output |
53
- | `bf16` | `f32` | 16-bit brain float, widened output |
47
+ | Input Type | Output Type | Description |
48
+ | ---------- | ----------- | ------------------------------------------------ |
49
+ | `f64` | `f64` | 64-bit IEEE 754 double precision |
50
+ | `f32` | `f64` | 32-bit IEEE 754 single precision, widened output |
51
+ | `f16` | `f32` | 16-bit IEEE 754 half precision, widened output |
52
+ | `bf16` | `f32` | 16-bit brain float, widened output |
54
53
 
55
54
  ## Optimizations
56
55
 
@@ -61,24 +60,27 @@ The pipeline on Skylake is:
61
60
 
62
61
  ```
63
62
  exponent = VGETEXPPS(x)
64
- mantissa = VGETMANTPS(x, normalize_to_[1,2)) - 1
65
- log2(x) ≈ exponent + polynomial(mantissa)
63
+ mantissa = VGETMANTPS(x, normalize_to_[1,2))
64
+ s = (mantissa - 1) / (mantissa + 1)
65
+ log2(x) ≈ exponent + (2/ln2) · s · (1 + s²/3 + s⁴/5 + s⁶/7 + s⁸/9)
66
66
  ```
67
67
 
68
68
  `VGETEXP` extracts the unbiased exponent as a float, while `VGETMANT` normalizes the mantissa to $[1, 2)$.
69
- A degree-4 minimax polynomial over the normalized mantissa completes the approximation.
69
+ A five-term odd series in $s$ — the $\operatorname{atanh}$ expansion, which converges quickly because $s \in [0, 1/3]$ — completes the approximation.
70
70
  These instructions handle subnormals correctly without extra integer bit manipulation.
71
71
 
72
72
  `nk_kld_f32_neon`, `nk_jsd_f32_neon`, `nk_kld_f16_haswell`, `nk_jsd_f16_haswell` use integer bit extraction instead:
73
73
 
74
74
  ```
75
75
  exponent = (reinterpret_as_int(x) >> 23) - 127
76
- mantissa = reinterpret_as_float((reinterpret_as_int(x) & 0x7FFFFF) | 0x3F800000) - 1
77
- log2(x) ≈ exponent + c₁·m + c₂·m² + c₃·m³ + c₄·m⁴ + c₅·m⁵
76
+ mantissa = reinterpret_as_float((reinterpret_as_int(x) & 0x7FFFFF) | 0x3F800000)
77
+ log2(x) ≈ exponent + series_or_polynomial(mantissa)
78
78
  ```
79
79
 
80
80
  This approach reinterprets the float as an integer, shifts out the mantissa bits to obtain the exponent, then masks and recombines to produce a normalized mantissa in $[1, 2)$.
81
+ The Haswell kernels then reuse the same $s$-series as Skylake, while the NEON kernels evaluate a degree-5 polynomial in the mantissa scaled by $m - 1$.
81
82
  It works on any ISA with integer-float reinterpretation and avoids the need for specialized exponent/mantissa instructions.
83
+ The SIMD kernels scale the accumulated $\log_2$ sum by $\ln 2$ before returning, while the serial ones call a natural-log helper directly — either way the reported divergence is in nats, not bits.
82
84
 
83
85
  ### Kahan Compensated Summation for Float64
84
86
 
@@ -14,7 +14,7 @@
14
14
 
15
15
  #include "numkong/types.h"
16
16
  #include "numkong/spatial/haswell.h" // `nk_f32_sqrt_haswell`, `nk_f64_sqrt_haswell`
17
- #include "numkong/spatial/skylake.h" // `nk_f32_sqrt_skylake`, `nk_f64_sqrt_skylake`
17
+ #include "numkong/spatial/skylake.h"
18
18
 
19
19
  #if defined(__cplusplus)
20
20
  extern "C" {