usearch 2.21.4 → 2.25.1

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 (604) hide show
  1. package/README.md +65 -69
  2. package/binding.gyp +15 -37
  3. package/c/CMakeLists.txt +4 -5
  4. package/c/README.md +8 -8
  5. package/c/lib.cpp +32 -14
  6. package/c/test.c +124 -1
  7. package/c/usearch.h +24 -3
  8. package/cpp/CMakeLists.txt +4 -0
  9. package/cpp/README.md +5 -5
  10. package/cpp/bench.cpp +97 -116
  11. package/cpp/test.cpp +117 -20
  12. package/include/usearch/index.hpp +224 -48
  13. package/include/usearch/index_dense.hpp +105 -40
  14. package/include/usearch/index_plugins.hpp +1387 -204
  15. package/javascript/README.md +1 -1
  16. package/javascript/dist/cjs/usearch.d.ts +8 -3
  17. package/javascript/dist/cjs/usearch.js +12 -4
  18. package/javascript/dist/esm/usearch.d.ts +8 -3
  19. package/javascript/dist/esm/usearch.js +12 -4
  20. package/javascript/lib.cpp +17 -0
  21. package/javascript/usearch.test.js +117 -94
  22. package/javascript/usearch.ts +14 -6
  23. package/numkong/.clang-tidy +30 -0
  24. package/{simsimd → numkong}/.cmake-format.py +18 -0
  25. package/numkong/.editorconfig +12 -0
  26. package/numkong/.git-blame-ignore-revs +123 -0
  27. package/numkong/.mailmap +15 -0
  28. package/numkong/.spi.yml +11 -0
  29. package/numkong/.swift-format +14 -0
  30. package/numkong/CITATION.cff +81 -0
  31. package/numkong/CONTRIBUTING.md +500 -0
  32. package/numkong/Cargo.toml +54 -0
  33. package/numkong/MANIFEST.in +8 -0
  34. package/numkong/Package.swift +84 -0
  35. package/numkong/README.md +569 -0
  36. package/numkong/bench/README.md +217 -0
  37. package/numkong/bench/bench-browser.html +323 -0
  38. package/numkong/bench/bench.cpp +344 -0
  39. package/numkong/bench/bench.hpp +816 -0
  40. package/numkong/bench/bench.js +148 -0
  41. package/numkong/bench/bench.mjs +616 -0
  42. package/numkong/bench/bench_attention.cpp +113 -0
  43. package/numkong/bench/bench_cast.cpp +264 -0
  44. package/numkong/bench/bench_cross_amx.cpp +62 -0
  45. package/numkong/bench/bench_cross_arm.cpp +245 -0
  46. package/numkong/bench/bench_cross_blas.cpp +309 -0
  47. package/numkong/bench/bench_cross_loongarch.cpp +106 -0
  48. package/numkong/bench/bench_cross_power.cpp +84 -0
  49. package/numkong/bench/bench_cross_rvv.cpp +121 -0
  50. package/numkong/bench/bench_cross_serial.cpp +151 -0
  51. package/numkong/bench/bench_cross_sme.cpp +163 -0
  52. package/numkong/bench/bench_cross_wasm.cpp +142 -0
  53. package/numkong/bench/bench_cross_x86.cpp +455 -0
  54. package/numkong/bench/bench_curved.cpp +210 -0
  55. package/numkong/bench/bench_dot.cpp +315 -0
  56. package/numkong/bench/bench_each.cpp +503 -0
  57. package/numkong/bench/bench_geospatial.cpp +112 -0
  58. package/numkong/bench/bench_maxsim.cpp +154 -0
  59. package/numkong/bench/bench_mesh.cpp +148 -0
  60. package/numkong/bench/bench_probability.cpp +60 -0
  61. package/numkong/bench/bench_reduce.cpp +371 -0
  62. package/numkong/bench/bench_set.cpp +73 -0
  63. package/numkong/bench/bench_sparse.cpp +198 -0
  64. package/numkong/bench/bench_spatial.cpp +347 -0
  65. package/numkong/bench/bench_trigonometry.cpp +137 -0
  66. package/numkong/bench/swift/Bench.swift +506 -0
  67. package/numkong/binding.gyp +169 -0
  68. package/numkong/build.rs +484 -0
  69. package/numkong/c/dispatch.h +512 -0
  70. package/numkong/c/dispatch_bf16.c +447 -0
  71. package/numkong/c/dispatch_bf16c.c +52 -0
  72. package/numkong/c/dispatch_e2m3.c +296 -0
  73. package/numkong/c/dispatch_e3m2.c +284 -0
  74. package/numkong/c/dispatch_e4m3.c +318 -0
  75. package/numkong/c/dispatch_e5m2.c +316 -0
  76. package/numkong/c/dispatch_f16.c +452 -0
  77. package/numkong/c/dispatch_f16c.c +58 -0
  78. package/numkong/c/dispatch_f32.c +434 -0
  79. package/numkong/c/dispatch_f32c.c +99 -0
  80. package/numkong/c/dispatch_f64.c +348 -0
  81. package/numkong/c/dispatch_f64c.c +98 -0
  82. package/numkong/c/dispatch_i16.c +96 -0
  83. package/numkong/c/dispatch_i32.c +89 -0
  84. package/numkong/c/dispatch_i4.c +153 -0
  85. package/numkong/c/dispatch_i64.c +86 -0
  86. package/numkong/c/dispatch_i8.c +348 -0
  87. package/numkong/c/dispatch_other.c +348 -0
  88. package/numkong/c/dispatch_u1.c +193 -0
  89. package/numkong/c/dispatch_u16.c +124 -0
  90. package/numkong/c/dispatch_u32.c +118 -0
  91. package/numkong/c/dispatch_u4.c +153 -0
  92. package/numkong/c/dispatch_u64.c +102 -0
  93. package/numkong/c/dispatch_u8.c +364 -0
  94. package/numkong/c/numkong.c +940 -0
  95. package/numkong/cmake/nk_arm_isa_probes.cmake +61 -0
  96. package/numkong/cmake/nk_isa_probe.cmake +90 -0
  97. package/numkong/cmake/nk_loongarch_isa_probes.cmake +15 -0
  98. package/numkong/cmake/nk_math_lib.cmake +46 -0
  99. package/numkong/cmake/nk_power_isa_probes.cmake +15 -0
  100. package/numkong/cmake/nk_riscv_isa_probes.cmake +19 -0
  101. package/numkong/cmake/nk_x86_isa_probes.cmake +35 -0
  102. package/numkong/cmake/toolchain-aarch64-gnu.cmake +86 -0
  103. package/numkong/cmake/toolchain-android-arm64.cmake +25 -0
  104. package/numkong/cmake/toolchain-android-armv7.cmake +25 -0
  105. package/numkong/cmake/toolchain-loongarch64-gnu.cmake +68 -0
  106. package/numkong/cmake/toolchain-ppc64le-gnu.cmake +74 -0
  107. package/numkong/cmake/toolchain-riscv64-gnu.cmake +107 -0
  108. package/numkong/cmake/toolchain-riscv64-llvm.cmake +114 -0
  109. package/numkong/cmake/toolchain-wasi.cmake +77 -0
  110. package/numkong/cmake/toolchain-wasm.cmake +70 -0
  111. package/numkong/cmake/toolchain-wasm64.cmake +62 -0
  112. package/numkong/cmake/toolchain-x86_64-llvm.cmake +47 -0
  113. package/numkong/include/README.md +643 -0
  114. package/numkong/include/module.modulemap +129 -0
  115. package/numkong/include/numkong/attention/sapphireamx.h +1363 -0
  116. package/numkong/include/numkong/attention/sme.h +2075 -0
  117. package/numkong/include/numkong/attention.h +49 -0
  118. package/numkong/include/numkong/capabilities.h +930 -0
  119. package/numkong/include/numkong/cast/README.md +262 -0
  120. package/numkong/include/numkong/cast/diamond.h +64 -0
  121. package/numkong/include/numkong/cast/haswell.h +823 -0
  122. package/numkong/include/numkong/cast/icelake.h +475 -0
  123. package/numkong/include/numkong/cast/loongsonasx.h +252 -0
  124. package/numkong/include/numkong/cast/neon.h +1174 -0
  125. package/numkong/include/numkong/cast/powervsx.h +449 -0
  126. package/numkong/include/numkong/cast/rvv.h +970 -0
  127. package/numkong/include/numkong/cast/sapphire.h +262 -0
  128. package/numkong/include/numkong/cast/serial.h +2336 -0
  129. package/numkong/include/numkong/cast/skylake.h +915 -0
  130. package/numkong/include/numkong/cast/v128relaxed.h +537 -0
  131. package/numkong/include/numkong/cast.h +254 -0
  132. package/numkong/include/numkong/cast.hpp +44 -0
  133. package/numkong/include/numkong/curved/README.md +223 -0
  134. package/numkong/include/numkong/curved/genoa.h +182 -0
  135. package/numkong/include/numkong/curved/haswell.h +276 -0
  136. package/numkong/include/numkong/curved/neon.h +329 -0
  137. package/numkong/include/numkong/curved/neonbfdot.h +211 -0
  138. package/numkong/include/numkong/curved/rvv.h +305 -0
  139. package/numkong/include/numkong/curved/serial.h +207 -0
  140. package/numkong/include/numkong/curved/skylake.h +457 -0
  141. package/numkong/include/numkong/curved/smef64.h +530 -0
  142. package/numkong/include/numkong/curved.h +513 -0
  143. package/numkong/include/numkong/curved.hpp +144 -0
  144. package/numkong/include/numkong/dot/README.md +442 -0
  145. package/numkong/include/numkong/dot/alder.h +563 -0
  146. package/numkong/include/numkong/dot/diamond.h +158 -0
  147. package/numkong/include/numkong/dot/genoa.h +289 -0
  148. package/numkong/include/numkong/dot/haswell.h +1723 -0
  149. package/numkong/include/numkong/dot/icelake.h +997 -0
  150. package/numkong/include/numkong/dot/loongsonasx.h +671 -0
  151. package/numkong/include/numkong/dot/neon.h +869 -0
  152. package/numkong/include/numkong/dot/neonbfdot.h +243 -0
  153. package/numkong/include/numkong/dot/neonfhm.h +358 -0
  154. package/numkong/include/numkong/dot/neonfp8.h +323 -0
  155. package/numkong/include/numkong/dot/neonsdot.h +622 -0
  156. package/numkong/include/numkong/dot/powervsx.h +752 -0
  157. package/numkong/include/numkong/dot/rvv.h +722 -0
  158. package/numkong/include/numkong/dot/rvvbb.h +72 -0
  159. package/numkong/include/numkong/dot/rvvbf16.h +123 -0
  160. package/numkong/include/numkong/dot/rvvhalf.h +129 -0
  161. package/numkong/include/numkong/dot/sapphire.h +141 -0
  162. package/numkong/include/numkong/dot/serial.h +874 -0
  163. package/numkong/include/numkong/dot/sierra.h +405 -0
  164. package/numkong/include/numkong/dot/skylake.h +1116 -0
  165. package/numkong/include/numkong/dot/sve.h +420 -0
  166. package/numkong/include/numkong/dot/svebfdot.h +75 -0
  167. package/numkong/include/numkong/dot/svehalf.h +168 -0
  168. package/numkong/include/numkong/dot/svesdot.h +90 -0
  169. package/numkong/include/numkong/dot/v128relaxed.h +1293 -0
  170. package/numkong/include/numkong/dot.h +1136 -0
  171. package/numkong/include/numkong/dot.hpp +94 -0
  172. package/numkong/include/numkong/dots/README.md +496 -0
  173. package/numkong/include/numkong/dots/alder.h +117 -0
  174. package/numkong/include/numkong/dots/diamond.h +86 -0
  175. package/numkong/include/numkong/dots/genoa.h +100 -0
  176. package/numkong/include/numkong/dots/graniteamx.h +733 -0
  177. package/numkong/include/numkong/dots/haswell.h +310 -0
  178. package/numkong/include/numkong/dots/icelake.h +180 -0
  179. package/numkong/include/numkong/dots/loongsonasx.h +176 -0
  180. package/numkong/include/numkong/dots/neon.h +123 -0
  181. package/numkong/include/numkong/dots/neonbfdot.h +59 -0
  182. package/numkong/include/numkong/dots/neonfhm.h +96 -0
  183. package/numkong/include/numkong/dots/neonfp8.h +99 -0
  184. package/numkong/include/numkong/dots/neonsdot.h +144 -0
  185. package/numkong/include/numkong/dots/powervsx.h +194 -0
  186. package/numkong/include/numkong/dots/rvv.h +2593 -0
  187. package/numkong/include/numkong/dots/sapphireamx.h +4017 -0
  188. package/numkong/include/numkong/dots/serial.h +2867 -0
  189. package/numkong/include/numkong/dots/sierra.h +100 -0
  190. package/numkong/include/numkong/dots/skylake.h +207 -0
  191. package/numkong/include/numkong/dots/sme.h +5041 -0
  192. package/numkong/include/numkong/dots/smebi32.h +477 -0
  193. package/numkong/include/numkong/dots/smef64.h +1328 -0
  194. package/numkong/include/numkong/dots/smehalf.h +47 -0
  195. package/numkong/include/numkong/dots/v128relaxed.h +317 -0
  196. package/numkong/include/numkong/dots.h +3110 -0
  197. package/numkong/include/numkong/dots.hpp +641 -0
  198. package/numkong/include/numkong/each/README.md +465 -0
  199. package/numkong/include/numkong/each/haswell.h +1658 -0
  200. package/numkong/include/numkong/each/icelake.h +272 -0
  201. package/numkong/include/numkong/each/neon.h +1138 -0
  202. package/numkong/include/numkong/each/neonbfdot.h +211 -0
  203. package/numkong/include/numkong/each/neonhalf.h +318 -0
  204. package/numkong/include/numkong/each/rvv.h +1121 -0
  205. package/numkong/include/numkong/each/sapphire.h +343 -0
  206. package/numkong/include/numkong/each/serial.h +282 -0
  207. package/numkong/include/numkong/each/skylake.h +1562 -0
  208. package/numkong/include/numkong/each/v128relaxed.h +562 -0
  209. package/numkong/include/numkong/each.h +2232 -0
  210. package/numkong/include/numkong/each.hpp +434 -0
  211. package/numkong/include/numkong/geospatial/README.md +147 -0
  212. package/numkong/include/numkong/geospatial/haswell.h +633 -0
  213. package/numkong/include/numkong/geospatial/neon.h +615 -0
  214. package/numkong/include/numkong/geospatial/rvv.h +701 -0
  215. package/numkong/include/numkong/geospatial/serial.h +309 -0
  216. package/numkong/include/numkong/geospatial/skylake.h +613 -0
  217. package/numkong/include/numkong/geospatial/v128relaxed.h +652 -0
  218. package/numkong/include/numkong/geospatial.h +453 -0
  219. package/numkong/include/numkong/geospatial.hpp +235 -0
  220. package/numkong/include/numkong/matrix.hpp +336 -0
  221. package/numkong/include/numkong/maxsim/README.md +187 -0
  222. package/numkong/include/numkong/maxsim/alder.h +511 -0
  223. package/numkong/include/numkong/maxsim/genoa.h +115 -0
  224. package/numkong/include/numkong/maxsim/haswell.h +553 -0
  225. package/numkong/include/numkong/maxsim/icelake.h +480 -0
  226. package/numkong/include/numkong/maxsim/neonsdot.h +394 -0
  227. package/numkong/include/numkong/maxsim/sapphireamx.h +877 -0
  228. package/numkong/include/numkong/maxsim/serial.h +490 -0
  229. package/numkong/include/numkong/maxsim/sme.h +993 -0
  230. package/numkong/include/numkong/maxsim/v128relaxed.h +280 -0
  231. package/numkong/include/numkong/maxsim.h +571 -0
  232. package/numkong/include/numkong/maxsim.hpp +133 -0
  233. package/numkong/include/numkong/mesh/README.md +219 -0
  234. package/numkong/include/numkong/mesh/haswell.h +2013 -0
  235. package/numkong/include/numkong/mesh/neon.h +1821 -0
  236. package/numkong/include/numkong/mesh/neonbfdot.h +771 -0
  237. package/numkong/include/numkong/mesh/rvv.h +936 -0
  238. package/numkong/include/numkong/mesh/serial.h +721 -0
  239. package/numkong/include/numkong/mesh/skylake.h +2344 -0
  240. package/numkong/include/numkong/mesh/v128relaxed.h +1081 -0
  241. package/numkong/include/numkong/mesh.h +666 -0
  242. package/numkong/include/numkong/mesh.hpp +701 -0
  243. package/numkong/include/numkong/numkong.h +78 -0
  244. package/numkong/include/numkong/numkong.hpp +58 -0
  245. package/numkong/include/numkong/probability/README.md +177 -0
  246. package/numkong/include/numkong/probability/haswell.h +267 -0
  247. package/numkong/include/numkong/probability/neon.h +255 -0
  248. package/numkong/include/numkong/probability/rvv.h +414 -0
  249. package/numkong/include/numkong/probability/serial.h +181 -0
  250. package/numkong/include/numkong/probability/skylake.h +324 -0
  251. package/numkong/include/numkong/probability.h +380 -0
  252. package/numkong/include/numkong/probability.hpp +120 -0
  253. package/numkong/include/numkong/random.h +50 -0
  254. package/numkong/include/numkong/random.hpp +285 -0
  255. package/numkong/include/numkong/reduce/README.md +552 -0
  256. package/numkong/include/numkong/reduce/alder.h +636 -0
  257. package/numkong/include/numkong/reduce/genoa.h +201 -0
  258. package/numkong/include/numkong/reduce/haswell.h +3785 -0
  259. package/numkong/include/numkong/reduce/icelake.h +549 -0
  260. package/numkong/include/numkong/reduce/neon.h +3980 -0
  261. package/numkong/include/numkong/reduce/neonbfdot.h +142 -0
  262. package/numkong/include/numkong/reduce/neonfhm.h +293 -0
  263. package/numkong/include/numkong/reduce/neonsdot.h +357 -0
  264. package/numkong/include/numkong/reduce/rvv.h +3533 -0
  265. package/numkong/include/numkong/reduce/serial.h +753 -0
  266. package/numkong/include/numkong/reduce/sierra.h +338 -0
  267. package/numkong/include/numkong/reduce/skylake.h +3794 -0
  268. package/numkong/include/numkong/reduce/sve.h +52 -0
  269. package/numkong/include/numkong/reduce/v128relaxed.h +2302 -0
  270. package/numkong/include/numkong/reduce.h +1582 -0
  271. package/numkong/include/numkong/reduce.hpp +778 -0
  272. package/numkong/include/numkong/scalar/README.md +89 -0
  273. package/numkong/include/numkong/scalar/haswell.h +122 -0
  274. package/numkong/include/numkong/scalar/loongsonasx.h +74 -0
  275. package/numkong/include/numkong/scalar/neon.h +122 -0
  276. package/numkong/include/numkong/scalar/neonhalf.h +70 -0
  277. package/numkong/include/numkong/scalar/powervsx.h +96 -0
  278. package/numkong/include/numkong/scalar/rvv.h +211 -0
  279. package/numkong/include/numkong/scalar/sapphire.h +74 -0
  280. package/numkong/include/numkong/scalar/serial.h +332 -0
  281. package/numkong/include/numkong/scalar/v128relaxed.h +56 -0
  282. package/numkong/include/numkong/scalar.h +696 -0
  283. package/numkong/include/numkong/set/README.md +179 -0
  284. package/numkong/include/numkong/set/haswell.h +334 -0
  285. package/numkong/include/numkong/set/icelake.h +485 -0
  286. package/numkong/include/numkong/set/loongsonasx.h +181 -0
  287. package/numkong/include/numkong/set/neon.h +363 -0
  288. package/numkong/include/numkong/set/powervsx.h +326 -0
  289. package/numkong/include/numkong/set/rvv.h +226 -0
  290. package/numkong/include/numkong/set/rvvbb.h +117 -0
  291. package/numkong/include/numkong/set/serial.h +174 -0
  292. package/numkong/include/numkong/set/sve.h +187 -0
  293. package/numkong/include/numkong/set/v128relaxed.h +240 -0
  294. package/numkong/include/numkong/set.h +471 -0
  295. package/numkong/include/numkong/set.hpp +114 -0
  296. package/numkong/include/numkong/sets/README.md +149 -0
  297. package/numkong/include/numkong/sets/haswell.h +63 -0
  298. package/numkong/include/numkong/sets/icelake.h +66 -0
  299. package/numkong/include/numkong/sets/loongsonasx.h +52 -0
  300. package/numkong/include/numkong/sets/neon.h +61 -0
  301. package/numkong/include/numkong/sets/powervsx.h +65 -0
  302. package/numkong/include/numkong/sets/serial.h +43 -0
  303. package/numkong/include/numkong/sets/smebi32.h +1135 -0
  304. package/numkong/include/numkong/sets/v128relaxed.h +58 -0
  305. package/numkong/include/numkong/sets.h +382 -0
  306. package/numkong/include/numkong/sparse/README.md +156 -0
  307. package/numkong/include/numkong/sparse/icelake.h +463 -0
  308. package/numkong/include/numkong/sparse/neon.h +288 -0
  309. package/numkong/include/numkong/sparse/serial.h +117 -0
  310. package/numkong/include/numkong/sparse/sve2.h +504 -0
  311. package/numkong/include/numkong/sparse/turin.h +322 -0
  312. package/numkong/include/numkong/sparse.h +363 -0
  313. package/numkong/include/numkong/sparse.hpp +113 -0
  314. package/numkong/include/numkong/spatial/README.md +439 -0
  315. package/numkong/include/numkong/spatial/alder.h +604 -0
  316. package/numkong/include/numkong/spatial/diamond.h +240 -0
  317. package/numkong/include/numkong/spatial/genoa.h +222 -0
  318. package/numkong/include/numkong/spatial/haswell.h +979 -0
  319. package/numkong/include/numkong/spatial/icelake.h +1067 -0
  320. package/numkong/include/numkong/spatial/loongsonasx.h +483 -0
  321. package/numkong/include/numkong/spatial/neon.h +846 -0
  322. package/numkong/include/numkong/spatial/neonbfdot.h +164 -0
  323. package/numkong/include/numkong/spatial/neonfp8.h +258 -0
  324. package/numkong/include/numkong/spatial/neonsdot.h +429 -0
  325. package/numkong/include/numkong/spatial/powervsx.h +738 -0
  326. package/numkong/include/numkong/spatial/rvv.h +991 -0
  327. package/numkong/include/numkong/spatial/rvvbf16.h +128 -0
  328. package/numkong/include/numkong/spatial/rvvhalf.h +120 -0
  329. package/numkong/include/numkong/spatial/serial.h +347 -0
  330. package/numkong/include/numkong/spatial/sierra.h +516 -0
  331. package/numkong/include/numkong/spatial/skylake.h +605 -0
  332. package/numkong/include/numkong/spatial/sve.h +246 -0
  333. package/numkong/include/numkong/spatial/svebfdot.h +125 -0
  334. package/numkong/include/numkong/spatial/svehalf.h +136 -0
  335. package/numkong/include/numkong/spatial/svesdot.h +143 -0
  336. package/numkong/include/numkong/spatial/v128relaxed.h +969 -0
  337. package/numkong/include/numkong/spatial.h +1681 -0
  338. package/numkong/include/numkong/spatial.hpp +183 -0
  339. package/numkong/include/numkong/spatials/README.md +580 -0
  340. package/numkong/include/numkong/spatials/alder.h +94 -0
  341. package/numkong/include/numkong/spatials/diamond.h +82 -0
  342. package/numkong/include/numkong/spatials/genoa.h +94 -0
  343. package/numkong/include/numkong/spatials/graniteamx.h +173 -0
  344. package/numkong/include/numkong/spatials/haswell.h +219 -0
  345. package/numkong/include/numkong/spatials/icelake.h +113 -0
  346. package/numkong/include/numkong/spatials/loongsonasx.h +153 -0
  347. package/numkong/include/numkong/spatials/neon.h +109 -0
  348. package/numkong/include/numkong/spatials/neonbfdot.h +60 -0
  349. package/numkong/include/numkong/spatials/neonfhm.h +92 -0
  350. package/numkong/include/numkong/spatials/neonfp8.h +111 -0
  351. package/numkong/include/numkong/spatials/neonsdot.h +143 -0
  352. package/numkong/include/numkong/spatials/powervsx.h +153 -0
  353. package/numkong/include/numkong/spatials/rvv.h +1976 -0
  354. package/numkong/include/numkong/spatials/sapphireamx.h +1149 -0
  355. package/numkong/include/numkong/spatials/serial.h +248 -0
  356. package/numkong/include/numkong/spatials/sierra.h +96 -0
  357. package/numkong/include/numkong/spatials/skylake.h +184 -0
  358. package/numkong/include/numkong/spatials/sme.h +1927 -0
  359. package/numkong/include/numkong/spatials/smef64.h +483 -0
  360. package/numkong/include/numkong/spatials/v128relaxed.h +240 -0
  361. package/numkong/include/numkong/spatials.h +3374 -0
  362. package/numkong/include/numkong/spatials.hpp +509 -0
  363. package/numkong/include/numkong/tensor.hpp +1981 -0
  364. package/numkong/include/numkong/trigonometry/README.md +184 -0
  365. package/numkong/include/numkong/trigonometry/haswell.h +653 -0
  366. package/numkong/include/numkong/trigonometry/neon.h +638 -0
  367. package/numkong/include/numkong/trigonometry/rvv.h +700 -0
  368. package/numkong/include/numkong/trigonometry/serial.h +703 -0
  369. package/numkong/include/numkong/trigonometry/skylake.h +725 -0
  370. package/numkong/include/numkong/trigonometry/v128relaxed.h +667 -0
  371. package/numkong/include/numkong/trigonometry.h +467 -0
  372. package/numkong/include/numkong/trigonometry.hpp +166 -0
  373. package/numkong/include/numkong/types.h +1695 -0
  374. package/numkong/include/numkong/types.hpp +6027 -0
  375. package/numkong/include/numkong/vector.hpp +766 -0
  376. package/numkong/javascript/@numkong-darwin-arm64/package.json +26 -0
  377. package/numkong/javascript/@numkong-darwin-x64/package.json +26 -0
  378. package/numkong/javascript/@numkong-linux-arm64/package.json +25 -0
  379. package/numkong/javascript/@numkong-linux-x64/package.json +25 -0
  380. package/numkong/javascript/@numkong-win32-arm64/package.json +25 -0
  381. package/numkong/javascript/@numkong-win32-x64/package.json +25 -0
  382. package/numkong/javascript/README.md +254 -0
  383. package/numkong/javascript/node-gyp-build.d.ts +4 -0
  384. package/numkong/javascript/numkong-browser.ts +40 -0
  385. package/numkong/javascript/numkong-wasm.ts +750 -0
  386. package/numkong/javascript/numkong.c +712 -0
  387. package/numkong/javascript/numkong.ts +613 -0
  388. package/{simsimd → numkong}/javascript/tsconfig-base.json +6 -3
  389. package/numkong/javascript/tsconfig-cjs.json +13 -0
  390. package/{simsimd → numkong}/javascript/tsconfig-esm.json +1 -1
  391. package/numkong/javascript/types.ts +784 -0
  392. package/numkong/numkong.gypi +104 -0
  393. package/numkong/package.json +108 -0
  394. package/numkong/probes/arm_neon.c +8 -0
  395. package/numkong/probes/arm_neon_bfdot.c +9 -0
  396. package/numkong/probes/arm_neon_fhm.c +9 -0
  397. package/numkong/probes/arm_neon_half.c +8 -0
  398. package/numkong/probes/arm_neon_sdot.c +9 -0
  399. package/numkong/probes/arm_neonfp8.c +9 -0
  400. package/numkong/probes/arm_sme.c +16 -0
  401. package/numkong/probes/arm_sme2.c +16 -0
  402. package/numkong/probes/arm_sme2p1.c +16 -0
  403. package/numkong/probes/arm_sme_bf16.c +16 -0
  404. package/numkong/probes/arm_sme_bi32.c +16 -0
  405. package/numkong/probes/arm_sme_f64.c +16 -0
  406. package/numkong/probes/arm_sme_fa64.c +14 -0
  407. package/numkong/probes/arm_sme_half.c +16 -0
  408. package/numkong/probes/arm_sme_lut2.c +15 -0
  409. package/numkong/probes/arm_sve.c +18 -0
  410. package/numkong/probes/arm_sve2.c +20 -0
  411. package/numkong/probes/arm_sve2p1.c +18 -0
  412. package/numkong/probes/arm_sve_bfdot.c +20 -0
  413. package/numkong/probes/arm_sve_half.c +18 -0
  414. package/numkong/probes/arm_sve_sdot.c +21 -0
  415. package/numkong/probes/loongarch_lasx.c +12 -0
  416. package/numkong/probes/power_vsx.c +12 -0
  417. package/numkong/probes/probe.js +127 -0
  418. package/numkong/probes/riscv_rvv.c +14 -0
  419. package/numkong/probes/riscv_rvv_bb.c +15 -0
  420. package/numkong/probes/riscv_rvv_bf16.c +17 -0
  421. package/numkong/probes/riscv_rvv_half.c +14 -0
  422. package/numkong/probes/wasm_v128relaxed.c +11 -0
  423. package/numkong/probes/x86_alder.c +17 -0
  424. package/numkong/probes/x86_diamond.c +17 -0
  425. package/numkong/probes/x86_genoa.c +17 -0
  426. package/numkong/probes/x86_graniteamx.c +19 -0
  427. package/numkong/probes/x86_haswell.c +11 -0
  428. package/numkong/probes/x86_icelake.c +17 -0
  429. package/numkong/probes/x86_sapphire.c +16 -0
  430. package/numkong/probes/x86_sapphireamx.c +18 -0
  431. package/numkong/probes/x86_sierra.c +17 -0
  432. package/numkong/probes/x86_skylake.c +15 -0
  433. package/numkong/probes/x86_turin.c +17 -0
  434. package/numkong/rust/README.md +698 -0
  435. package/numkong/rust/capabilities.rs +96 -0
  436. package/numkong/rust/cast.rs +314 -0
  437. package/numkong/rust/curved.rs +391 -0
  438. package/numkong/rust/each.rs +2721 -0
  439. package/numkong/rust/geospatial.rs +343 -0
  440. package/numkong/rust/matrix.rs +5424 -0
  441. package/numkong/rust/maxsim.rs +540 -0
  442. package/numkong/rust/mesh.rs +837 -0
  443. package/numkong/rust/numkong.rs +409 -0
  444. package/numkong/rust/probability.rs +272 -0
  445. package/numkong/rust/reduce.rs +1249 -0
  446. package/numkong/rust/set.rs +194 -0
  447. package/numkong/rust/sparse.rs +513 -0
  448. package/numkong/rust/spatial.rs +1815 -0
  449. package/numkong/rust/tensor.rs +7565 -0
  450. package/numkong/rust/types.rs +3666 -0
  451. package/numkong/rust/vector.rs +1899 -0
  452. package/numkong/rustfmt.toml +2 -0
  453. package/numkong/swift/Matrix.swift +1388 -0
  454. package/numkong/swift/MaxSim.swift +151 -0
  455. package/numkong/swift/NumKong.swift +283 -0
  456. package/numkong/swift/Numerics.swift +329 -0
  457. package/numkong/swift/README.md +433 -0
  458. package/numkong/swift/Scalar.swift +741 -0
  459. package/numkong/swift/Tensor.swift +209 -0
  460. package/numkong/test/README.md +315 -0
  461. package/numkong/test/playwright.config.ts +16 -0
  462. package/numkong/test/swift/Test.swift +534 -0
  463. package/numkong/test/test-browser-bundle-runner.mjs +36 -0
  464. package/numkong/test/test-browser-bundle.html +188 -0
  465. package/numkong/test/test-browser-runner.mjs +43 -0
  466. package/numkong/test/test-browser.html +305 -0
  467. package/numkong/test/test-dtypes.mjs +453 -0
  468. package/numkong/test/test-wasm.mjs +267 -0
  469. package/numkong/test/test.cpp +442 -0
  470. package/numkong/test/test.hpp +580 -0
  471. package/numkong/test/test.mjs +363 -0
  472. package/numkong/test/test_base.py +1041 -0
  473. package/numkong/test/test_cast.cpp +242 -0
  474. package/numkong/test/test_cross.hpp +551 -0
  475. package/numkong/test/test_cross.py +467 -0
  476. package/numkong/test/test_cross_amx.cpp +50 -0
  477. package/numkong/test/test_cross_arm.cpp +225 -0
  478. package/numkong/test/test_cross_blas.cpp +354 -0
  479. package/numkong/test/test_cross_loongarch.cpp +90 -0
  480. package/numkong/test/test_cross_power.cpp +73 -0
  481. package/numkong/test/test_cross_rvv.cpp +109 -0
  482. package/numkong/test/test_cross_serial.cpp +181 -0
  483. package/numkong/test/test_cross_sme.cpp +148 -0
  484. package/numkong/test/test_cross_wasm.cpp +141 -0
  485. package/numkong/test/test_cross_x86.cpp +442 -0
  486. package/numkong/test/test_curved.cpp +182 -0
  487. package/numkong/test/test_curved.py +207 -0
  488. package/numkong/test/test_dot.cpp +353 -0
  489. package/numkong/test/test_dot.py +296 -0
  490. package/numkong/test/test_each.cpp +437 -0
  491. package/numkong/test/test_each.py +756 -0
  492. package/numkong/test/test_geospatial.cpp +155 -0
  493. package/numkong/test/test_geospatial.py +241 -0
  494. package/numkong/test/test_maxsim.cpp +138 -0
  495. package/numkong/test/test_maxsim.py +209 -0
  496. package/numkong/test/test_mesh.cpp +203 -0
  497. package/numkong/test/test_mesh.py +394 -0
  498. package/numkong/test/test_probability.cpp +136 -0
  499. package/numkong/test/test_probability.py +166 -0
  500. package/numkong/test/test_reduce.cpp +563 -0
  501. package/numkong/test/test_reduce.py +781 -0
  502. package/numkong/test/test_set.cpp +151 -0
  503. package/numkong/test/test_set.py +120 -0
  504. package/numkong/test/test_similarities.py +579 -0
  505. package/numkong/test/test_sparse.cpp +121 -0
  506. package/numkong/test/test_sparse.py +118 -0
  507. package/numkong/test/test_spatial.cpp +518 -0
  508. package/numkong/test/test_spatial.py +269 -0
  509. package/numkong/test/test_tensor.cpp +884 -0
  510. package/numkong/test/test_tensor.py +1417 -0
  511. package/numkong/test/test_trigonometry.cpp +156 -0
  512. package/numkong/test/test_trigonometry.py +180 -0
  513. package/package.json +7 -8
  514. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  515. package/prebuilds/linux-arm64/usearch.node +0 -0
  516. package/prebuilds/linux-x64/usearch.node +0 -0
  517. package/fp16/CMakeLists.txt +0 -176
  518. package/fp16/LICENSE +0 -11
  519. package/fp16/README.md +0 -20
  520. package/fp16/bench/alt-element.cc +0 -89
  521. package/fp16/bench/from-alt-array.cc +0 -175
  522. package/fp16/bench/from-ieee-array.cc +0 -399
  523. package/fp16/bench/ieee-element.cc +0 -272
  524. package/fp16/bench/to-alt-array.cc +0 -91
  525. package/fp16/bench/to-ieee-array.cc +0 -303
  526. package/fp16/cmake/DownloadGoogleBenchmark.cmake +0 -15
  527. package/fp16/cmake/DownloadGoogleTest.cmake +0 -15
  528. package/fp16/cmake/DownloadPSimd.cmake +0 -15
  529. package/fp16/configure.py +0 -65
  530. package/fp16/confu.yaml +0 -8
  531. package/fp16/include/fp16/__init__.py +0 -0
  532. package/fp16/include/fp16/avx.py +0 -50
  533. package/fp16/include/fp16/avx2.py +0 -53
  534. package/fp16/include/fp16/bitcasts.h +0 -92
  535. package/fp16/include/fp16/fp16.h +0 -451
  536. package/fp16/include/fp16/psimd.h +0 -131
  537. package/fp16/include/fp16.h +0 -11
  538. package/fp16/jni/Android.mk +0 -6
  539. package/fp16/jni/Application.mk +0 -4
  540. package/fp16/test/alt-from-fp32-value.cc +0 -498
  541. package/fp16/test/alt-to-fp32-bits.cc +0 -263
  542. package/fp16/test/alt-to-fp32-psimd.cc +0 -145
  543. package/fp16/test/alt-to-fp32-value.cc +0 -348
  544. package/fp16/test/alt-to-fp32x2-psimd.cc +0 -245
  545. package/fp16/test/bitcasts.cc +0 -56
  546. package/fp16/test/ieee-from-fp32-value.cc +0 -523
  547. package/fp16/test/ieee-to-fp32-bits.cc +0 -333
  548. package/fp16/test/ieee-to-fp32-psimd.cc +0 -312
  549. package/fp16/test/ieee-to-fp32-value.cc +0 -420
  550. package/fp16/test/ieee-to-fp32x2-psimd.cc +0 -541
  551. package/fp16/test/peachpy/alt-xmm-to-fp32-xmm-avx.cc +0 -96
  552. package/fp16/test/peachpy/alt-xmm-to-fp32-ymm-avx2.cc +0 -96
  553. package/fp16/test/peachpy/stubs.py +0 -38
  554. package/fp16/test/tables.cc +0 -530
  555. package/fp16/test/tables.h +0 -13
  556. package/fp16/third-party/THHalf.h +0 -92
  557. package/fp16/third-party/eigen-half.h +0 -170
  558. package/fp16/third-party/float16-compressor.h +0 -80
  559. package/fp16/third-party/half.hpp +0 -2916
  560. package/fp16/third-party/npy-halffloat.h +0 -328
  561. package/simsimd/.git-blame-ignore-revs +0 -2
  562. package/simsimd/CONTRIBUTING.md +0 -321
  563. package/simsimd/Cargo.toml +0 -49
  564. package/simsimd/MANIFEST.in +0 -4
  565. package/simsimd/Package.swift +0 -28
  566. package/simsimd/README.md +0 -1493
  567. package/simsimd/binding.gyp +0 -35
  568. package/simsimd/build.rs +0 -70
  569. package/simsimd/c/lib.c +0 -374
  570. package/simsimd/include/module.modulemap +0 -4
  571. package/simsimd/include/simsimd/binary.h +0 -486
  572. package/simsimd/include/simsimd/curved.h +0 -1541
  573. package/simsimd/include/simsimd/dot.h +0 -1853
  574. package/simsimd/include/simsimd/elementwise.h +0 -2465
  575. package/simsimd/include/simsimd/geospatial.h +0 -43
  576. package/simsimd/include/simsimd/mesh.h +0 -69
  577. package/simsimd/include/simsimd/probability.h +0 -606
  578. package/simsimd/include/simsimd/simsimd.h +0 -2567
  579. package/simsimd/include/simsimd/sparse.h +0 -1384
  580. package/simsimd/include/simsimd/spatial.h +0 -2335
  581. package/simsimd/include/simsimd/types.h +0 -666
  582. package/simsimd/javascript/fallback.ts +0 -233
  583. package/simsimd/javascript/lib.c +0 -125
  584. package/simsimd/javascript/node-gyp-build.d.ts +0 -1
  585. package/simsimd/javascript/simsimd.ts +0 -179
  586. package/simsimd/javascript/tsconfig-cjs.json +0 -8
  587. package/simsimd/package-lock.json +0 -1761
  588. package/simsimd/package.json +0 -61
  589. package/simsimd/rust/lib.rs +0 -1821
  590. package/simsimd/scripts/bench.cxx +0 -1170
  591. package/simsimd/scripts/bench.ipynb +0 -187
  592. package/simsimd/scripts/bench.js +0 -127
  593. package/simsimd/scripts/bench.rs +0 -876
  594. package/simsimd/scripts/bench_vectors.py +0 -734
  595. package/simsimd/scripts/bench_vectors_live.py +0 -213
  596. package/simsimd/scripts/test.c +0 -187
  597. package/simsimd/scripts/test.mjs +0 -220
  598. package/simsimd/scripts/test.py +0 -1505
  599. package/simsimd/swift/SimSIMD.swift +0 -149
  600. package/simsimd/swift/Test.swift +0 -128
  601. /package/{simsimd → numkong}/CODE_OF_CONDUCT.md +0 -0
  602. /package/{simsimd → numkong}/LICENSE +0 -0
  603. /package/{simsimd → numkong}/javascript/dist-package-cjs.json +0 -0
  604. /package/{simsimd → numkong}/javascript/dist-package-esm.json +0 -0
@@ -0,0 +1,1815 @@
1
+ //! Spatial similarity: dot products, angular (cosine), and Euclidean distances.
2
+ //!
3
+ //! This module provides:
4
+ //!
5
+ //! - [`Dot`]: Inner product of two vectors
6
+ //! - [`Angular`]: Cosine distance (1 - cosine similarity)
7
+ //! - [`Euclidean`]: Squared L2 distance
8
+ //! - [`VDot`]: Complex-valued dot product
9
+ //! - [`Roots`]: Scalar square root and reciprocal square root
10
+ //! - [`SpatialSimilarity`]: Blanket trait combining `Dot + Angular + Euclidean`
11
+ //!
12
+ //! # Accumulator Widening — The Core Value Proposition
13
+ //!
14
+ //! Every spatial kernel promotes its accumulator to a wider type than the inputs.
15
+ //! This is not cosmetic — on long vectors of low-precision data, naive same-width
16
+ //! accumulation overflows silently (integers) or loses huge amounts of precision
17
+ //! (half-floats). NumKong widens systematically so every kernel in this module
18
+ //! returns a result that matches a textbook-accurate reference in the wide type:
19
+ //!
20
+ //! - **`f32` → `f64`**: single-precision inputs accumulate in double precision. A
21
+ //! `dot` over 2048 `f32` values returns `f64` and uses Neumaier-compensated
22
+ //! summation internally.
23
+ //! - **`f16` → `f32`** and **`bf16` → `f32`**: half-precision dots, norms, and
24
+ //! distances accumulate in `f32` rather than clamping at `f16::MAX = 65 504`.
25
+ //! - **`i8` → `i32`** (unsigned `u8` → `u32`): byte-level quantised inputs widen
26
+ //! into 32-bit integer accumulators. Two `i8` vectors of all `100`s and length
27
+ //! 2048 have a true dot of `20 480 000`, which overflows `i8` (saturates at
28
+ //! `±127`) and `i16` (`±32 767`) but is exact in `i32`.
29
+ //! - **FP8 variants** (`e4m3` / `e5m2` / `e2m3` / `e3m2`) all accumulate in `f32`.
30
+ //! - **4-bit packed** `i4x2` / `u4x2` behave like `i8` / `u8` but compute over
31
+ //! double the element count because each byte holds two logical values.
32
+ //!
33
+ //! This widening is the reason quantised retrieval pipelines (e.g. BFloat16 or
34
+ //! INT8 embeddings in vector search) can rely on NumKong without post-hoc rescaling.
35
+ //!
36
+ //! # Example — INT8 Dot Without Overflow
37
+ //!
38
+ //! ```
39
+ //! use numkong::Dot;
40
+ //!
41
+ //! // 2048-dim i8 vectors full of 100s: true dot = 2048 * 100 * 100 = 20_480_000,
42
+ //! // which would overflow i16 but fits exactly in i32.
43
+ //! let left = vec![100_i8; 2048];
44
+ //! let right = vec![100_i8; 2048];
45
+ //! let exact: i32 = i8::dot(&left, &right).unwrap();
46
+ //! assert_eq!(exact, 20_480_000);
47
+ //! ```
48
+
49
+ use crate::types::{
50
+ bf16, bf16c, e2m3, e3m2, e4m3, e5m2, f16, f16c, f32c, f64c, i4x2, u4x2, StorageElement,
51
+ };
52
+
53
+ #[link(name = "numkong")]
54
+ extern "C" {
55
+ // Scalar roots
56
+ fn nk_f32_sqrt(x: f32) -> f32;
57
+ fn nk_f32_rsqrt(x: f32) -> f32;
58
+ fn nk_f64_sqrt(x: f64) -> f64;
59
+ fn nk_f64_rsqrt(x: f64) -> f64;
60
+ fn nk_f16_sqrt(x: u16) -> u16;
61
+ fn nk_f16_rsqrt(x: u16) -> u16;
62
+
63
+ // Vector dot products
64
+ fn nk_dot_i8(a: *const i8, b: *const i8, c: usize, d: *mut i32);
65
+ fn nk_dot_u8(a: *const u8, b: *const u8, c: usize, d: *mut u32);
66
+ fn nk_dot_f16(a: *const u16, b: *const u16, c: usize, d: *mut f32);
67
+ fn nk_dot_bf16(a: *const u16, b: *const u16, c: usize, d: *mut f32);
68
+ fn nk_dot_e4m3(a: *const u8, b: *const u8, c: usize, d: *mut f32);
69
+ fn nk_dot_e5m2(a: *const u8, b: *const u8, c: usize, d: *mut f32);
70
+ fn nk_dot_e2m3(a: *const u8, b: *const u8, c: usize, d: *mut f32);
71
+ fn nk_dot_e3m2(a: *const u8, b: *const u8, c: usize, d: *mut f32);
72
+ fn nk_dot_f32(a: *const f32, b: *const f32, c: usize, d: *mut f64);
73
+ fn nk_dot_f64(a: *const f64, b: *const f64, c: usize, d: *mut f64);
74
+
75
+ fn nk_dot_f16c(a: *const u16, b: *const u16, c: usize, d: *mut f32);
76
+ fn nk_dot_bf16c(a: *const u16, b: *const u16, c: usize, d: *mut f32);
77
+ fn nk_dot_f32c(a: *const f32, b: *const f32, c: usize, d: *mut f64);
78
+ fn nk_dot_f64c(a: *const f64, b: *const f64, c: usize, d: *mut f64);
79
+
80
+ fn nk_vdot_f16c(a: *const u16, b: *const u16, c: usize, d: *mut f32);
81
+ fn nk_vdot_bf16c(a: *const u16, b: *const u16, c: usize, d: *mut f32);
82
+ fn nk_vdot_f32c(a: *const f32, b: *const f32, c: usize, d: *mut f64);
83
+ fn nk_vdot_f64c(a: *const f64, b: *const f64, c: usize, d: *mut f64);
84
+
85
+ // Spatial similarity/distance functions
86
+ fn nk_angular_i8(a: *const i8, b: *const i8, c: usize, d: *mut f32);
87
+ fn nk_angular_u8(a: *const u8, b: *const u8, c: usize, d: *mut f32);
88
+ fn nk_angular_f16(a: *const u16, b: *const u16, c: usize, d: *mut f32);
89
+ fn nk_angular_bf16(a: *const u16, b: *const u16, c: usize, d: *mut f32);
90
+ fn nk_angular_e4m3(a: *const u8, b: *const u8, c: usize, d: *mut f32);
91
+ fn nk_angular_e5m2(a: *const u8, b: *const u8, c: usize, d: *mut f32);
92
+ fn nk_angular_e2m3(a: *const u8, b: *const u8, c: usize, d: *mut f32);
93
+ fn nk_angular_e3m2(a: *const u8, b: *const u8, c: usize, d: *mut f32);
94
+ fn nk_angular_f32(a: *const f32, b: *const f32, c: usize, d: *mut f64);
95
+ fn nk_angular_f64(a: *const f64, b: *const f64, c: usize, d: *mut f64);
96
+
97
+ fn nk_sqeuclidean_i8(a: *const i8, b: *const i8, c: usize, d: *mut u32);
98
+ fn nk_sqeuclidean_u8(a: *const u8, b: *const u8, c: usize, d: *mut u32);
99
+ fn nk_sqeuclidean_f16(a: *const u16, b: *const u16, c: usize, d: *mut f32);
100
+ fn nk_sqeuclidean_bf16(a: *const u16, b: *const u16, c: usize, d: *mut f32);
101
+ fn nk_sqeuclidean_e4m3(a: *const u8, b: *const u8, c: usize, d: *mut f32);
102
+ fn nk_sqeuclidean_e5m2(a: *const u8, b: *const u8, c: usize, d: *mut f32);
103
+ fn nk_sqeuclidean_e2m3(a: *const u8, b: *const u8, c: usize, d: *mut f32);
104
+ fn nk_sqeuclidean_e3m2(a: *const u8, b: *const u8, c: usize, d: *mut f32);
105
+ fn nk_sqeuclidean_f32(a: *const f32, b: *const f32, c: usize, d: *mut f64);
106
+ fn nk_sqeuclidean_f64(a: *const f64, b: *const f64, c: usize, d: *mut f64);
107
+
108
+ fn nk_euclidean_i8(a: *const i8, b: *const i8, c: usize, d: *mut f32);
109
+ fn nk_euclidean_u8(a: *const u8, b: *const u8, c: usize, d: *mut f32);
110
+ fn nk_euclidean_f16(a: *const u16, b: *const u16, c: usize, d: *mut f32);
111
+ fn nk_euclidean_bf16(a: *const u16, b: *const u16, c: usize, d: *mut f32);
112
+ fn nk_euclidean_e4m3(a: *const u8, b: *const u8, c: usize, d: *mut f32);
113
+ fn nk_euclidean_e5m2(a: *const u8, b: *const u8, c: usize, d: *mut f32);
114
+ fn nk_euclidean_e2m3(a: *const u8, b: *const u8, c: usize, d: *mut f32);
115
+ fn nk_euclidean_e3m2(a: *const u8, b: *const u8, c: usize, d: *mut f32);
116
+ fn nk_euclidean_f32(a: *const f32, b: *const f32, c: usize, d: *mut f64);
117
+ fn nk_euclidean_f64(a: *const f64, b: *const f64, c: usize, d: *mut f64);
118
+
119
+ // 4-bit integer kernels
120
+ fn nk_dot_i4(a: *const u8, b: *const u8, n: usize, result: *mut i32);
121
+ fn nk_dot_u4(a: *const u8, b: *const u8, n: usize, result: *mut u32);
122
+ fn nk_sqeuclidean_i4(a: *const u8, b: *const u8, n: usize, result: *mut u32);
123
+ fn nk_sqeuclidean_u4(a: *const u8, b: *const u8, n: usize, result: *mut u32);
124
+ fn nk_euclidean_i4(a: *const u8, b: *const u8, n: usize, result: *mut f32);
125
+ fn nk_euclidean_u4(a: *const u8, b: *const u8, n: usize, result: *mut f32);
126
+ fn nk_angular_i4(a: *const u8, b: *const u8, n: usize, result: *mut f32);
127
+ fn nk_angular_u4(a: *const u8, b: *const u8, n: usize, result: *mut f32);
128
+ }
129
+
130
+ // region: Scalar Roots
131
+
132
+ /// Scalar square-root and reciprocal-square-root operations backed by NumKong's
133
+ /// exported kernels.
134
+ ///
135
+ /// Unlike the standard library's `f32::sqrt` / `f64::sqrt`, this trait routes into
136
+ /// the hand-tuned NumKong C kernels. On platforms where the ISA offers dedicated
137
+ /// reciprocal-sqrt instructions (e.g. `vrsqrte` on Arm NEON, `vrsqrt14` on AVX-512)
138
+ /// `rsqrt` uses a single refined Newton step to hit ~1 ULP accuracy — roughly 2-4×
139
+ /// faster than computing `1.0 / sqrt(x)` explicitly.
140
+ ///
141
+ /// Implementations are provided for the three scalar float types: `f32`, `f64`, and
142
+ /// `f16`. Half-precision input is upcast to `f32`, computed, and downcast.
143
+ pub trait Roots: Sized {
144
+ /// Non-negative square root of `self`. Equivalent to the intrinsic `sqrt`
145
+ /// but routed through NumKong's kernel table — picks up any runtime-dispatched
146
+ /// fast path available on the host CPU.
147
+ fn sqrt(self) -> Self;
148
+
149
+ /// Reciprocal square root `1 / sqrt(self)` computed as a single primitive op
150
+ /// when the hardware supports it, or a Newton-refined fallback otherwise.
151
+ /// Useful inside normalization-heavy kernels (e.g. cosine similarity) where
152
+ /// a division plus a sqrt would otherwise dominate the cost.
153
+ fn rsqrt(self) -> Self;
154
+ }
155
+
156
+ impl Roots for f32 {
157
+ /// Single-precision square root. Dispatches to the SIMD-assisted kernel.
158
+ fn sqrt(self) -> Self {
159
+ unsafe { nk_f32_sqrt(self) }
160
+ }
161
+
162
+ /// Single-precision reciprocal square root with a Newton refinement step.
163
+ fn rsqrt(self) -> Self {
164
+ unsafe { nk_f32_rsqrt(self) }
165
+ }
166
+ }
167
+
168
+ impl Roots for f64 {
169
+ /// Double-precision square root — full IEEE 754 accuracy.
170
+ fn sqrt(self) -> Self {
171
+ unsafe { nk_f64_sqrt(self) }
172
+ }
173
+
174
+ /// Double-precision reciprocal square root.
175
+ fn rsqrt(self) -> Self {
176
+ unsafe { nk_f64_rsqrt(self) }
177
+ }
178
+ }
179
+
180
+ impl Roots for f16 {
181
+ /// Half-precision square root. Input is upcast to `f32` internally and the
182
+ /// result is rounded back to `f16`.
183
+ fn sqrt(self) -> Self {
184
+ f16(unsafe { nk_f16_sqrt(self.0) })
185
+ }
186
+
187
+ /// Half-precision reciprocal square root with `f32` intermediate precision.
188
+ fn rsqrt(self) -> Self {
189
+ f16(unsafe { nk_f16_rsqrt(self.0) })
190
+ }
191
+ }
192
+
193
+ // endregion: Scalar Roots
194
+
195
+ // region: Dot
196
+
197
+ /// Computes the **dot product** (inner product) between two vectors.
198
+ ///
199
+ /// d = ∑ᵢ aᵢ × bᵢ
200
+ ///
201
+ /// Range: unbounded. Returns `None` if lengths differ.
202
+ ///
203
+ /// Implemented for: `f64`, `f32`, `f16`, `bf16`, `i8`, `u8`,
204
+ /// `e4m3`, `e5m2`, `e2m3`, `e3m2`, `i4x2`, `u4x2`.
205
+ ///
206
+ /// # Example
207
+ /// ```
208
+ /// use numkong::Dot;
209
+ /// let a = vec![1.0_f32, 2.0, 3.0];
210
+ /// let b = vec![4.0_f32, 5.0, 6.0];
211
+ /// let result = f32::dot(&a, &b).unwrap();
212
+ /// assert!((result - 32.0).abs() < 1e-5);
213
+ /// ```
214
+ pub trait Dot: StorageElement {
215
+ type Output;
216
+ fn dot(a: &[Self], b: &[Self]) -> Option<Self::Output>;
217
+
218
+ /// Alias for `dot`.
219
+ fn inner(a: &[Self], b: &[Self]) -> Option<Self::Output> {
220
+ Self::dot(a, b)
221
+ }
222
+ }
223
+
224
+ impl Dot for f64 {
225
+ type Output = f64;
226
+ fn dot(a: &[Self], b: &[Self]) -> Option<Self::Output> {
227
+ if a.len() != b.len() {
228
+ return None;
229
+ }
230
+ let mut result: Self::Output = 0.0;
231
+ unsafe { nk_dot_f64(a.as_ptr(), b.as_ptr(), a.len(), &mut result) };
232
+ Some(result)
233
+ }
234
+ }
235
+
236
+ impl Dot for f32 {
237
+ type Output = f64;
238
+ fn dot(a: &[Self], b: &[Self]) -> Option<Self::Output> {
239
+ if a.len() != b.len() {
240
+ return None;
241
+ }
242
+ let mut result: Self::Output = 0.0;
243
+ unsafe { nk_dot_f32(a.as_ptr(), b.as_ptr(), a.len(), &mut result) };
244
+ Some(result)
245
+ }
246
+ }
247
+
248
+ impl Dot for f16 {
249
+ type Output = f32;
250
+ fn dot(a: &[Self], b: &[Self]) -> Option<Self::Output> {
251
+ if a.len() != b.len() {
252
+ return None;
253
+ }
254
+ let mut result: Self::Output = 0.0;
255
+ unsafe {
256
+ nk_dot_f16(
257
+ a.as_ptr() as *const u16,
258
+ b.as_ptr() as *const u16,
259
+ a.len(),
260
+ &mut result,
261
+ )
262
+ };
263
+ Some(result)
264
+ }
265
+ }
266
+
267
+ impl Dot for bf16 {
268
+ type Output = f32;
269
+ fn dot(a: &[Self], b: &[Self]) -> Option<Self::Output> {
270
+ if a.len() != b.len() {
271
+ return None;
272
+ }
273
+ let mut result: Self::Output = 0.0;
274
+ unsafe {
275
+ nk_dot_bf16(
276
+ a.as_ptr() as *const u16,
277
+ b.as_ptr() as *const u16,
278
+ a.len(),
279
+ &mut result,
280
+ )
281
+ };
282
+ Some(result)
283
+ }
284
+ }
285
+
286
+ impl Dot for i8 {
287
+ type Output = i32;
288
+ fn dot(a: &[Self], b: &[Self]) -> Option<Self::Output> {
289
+ if a.len() != b.len() {
290
+ return None;
291
+ }
292
+ let mut result: Self::Output = 0;
293
+ unsafe { nk_dot_i8(a.as_ptr(), b.as_ptr(), a.len(), &mut result) };
294
+ Some(result)
295
+ }
296
+ }
297
+
298
+ impl Dot for u8 {
299
+ type Output = u32;
300
+ fn dot(a: &[Self], b: &[Self]) -> Option<Self::Output> {
301
+ if a.len() != b.len() {
302
+ return None;
303
+ }
304
+ let mut result: Self::Output = 0;
305
+ unsafe { nk_dot_u8(a.as_ptr(), b.as_ptr(), a.len(), &mut result) };
306
+ Some(result)
307
+ }
308
+ }
309
+
310
+ impl Dot for e4m3 {
311
+ type Output = f32;
312
+ fn dot(a: &[Self], b: &[Self]) -> Option<Self::Output> {
313
+ if a.len() != b.len() {
314
+ return None;
315
+ }
316
+ let mut result: Self::Output = 0.0;
317
+ unsafe {
318
+ nk_dot_e4m3(
319
+ a.as_ptr() as *const u8,
320
+ b.as_ptr() as *const u8,
321
+ a.len(),
322
+ &mut result,
323
+ )
324
+ };
325
+ Some(result)
326
+ }
327
+ }
328
+
329
+ impl Dot for e5m2 {
330
+ type Output = f32;
331
+ fn dot(a: &[Self], b: &[Self]) -> Option<Self::Output> {
332
+ if a.len() != b.len() {
333
+ return None;
334
+ }
335
+ let mut result: Self::Output = 0.0;
336
+ unsafe {
337
+ nk_dot_e5m2(
338
+ a.as_ptr() as *const u8,
339
+ b.as_ptr() as *const u8,
340
+ a.len(),
341
+ &mut result,
342
+ )
343
+ };
344
+ Some(result)
345
+ }
346
+ }
347
+
348
+ impl Dot for e2m3 {
349
+ type Output = f32;
350
+ fn dot(a: &[Self], b: &[Self]) -> Option<Self::Output> {
351
+ if a.len() != b.len() {
352
+ return None;
353
+ }
354
+ let mut result: Self::Output = 0.0;
355
+ unsafe {
356
+ nk_dot_e2m3(
357
+ a.as_ptr() as *const u8,
358
+ b.as_ptr() as *const u8,
359
+ a.len(),
360
+ &mut result,
361
+ )
362
+ };
363
+ Some(result)
364
+ }
365
+ }
366
+
367
+ impl Dot for e3m2 {
368
+ type Output = f32;
369
+ fn dot(a: &[Self], b: &[Self]) -> Option<Self::Output> {
370
+ if a.len() != b.len() {
371
+ return None;
372
+ }
373
+ let mut result: Self::Output = 0.0;
374
+ unsafe {
375
+ nk_dot_e3m2(
376
+ a.as_ptr() as *const u8,
377
+ b.as_ptr() as *const u8,
378
+ a.len(),
379
+ &mut result,
380
+ )
381
+ };
382
+ Some(result)
383
+ }
384
+ }
385
+
386
+ impl Dot for i4x2 {
387
+ type Output = i32;
388
+ fn dot(a: &[Self], b: &[Self]) -> Option<Self::Output> {
389
+ if a.len() != b.len() {
390
+ return None;
391
+ }
392
+ let mut result: Self::Output = 0;
393
+ let element_count = a.len() * 2; // Each i4x2 contains 2 elements
394
+ unsafe {
395
+ nk_dot_i4(
396
+ a.as_ptr() as *const u8,
397
+ b.as_ptr() as *const u8,
398
+ element_count,
399
+ &mut result,
400
+ )
401
+ };
402
+ Some(result)
403
+ }
404
+ }
405
+
406
+ impl Dot for u4x2 {
407
+ type Output = u32;
408
+ fn dot(a: &[Self], b: &[Self]) -> Option<Self::Output> {
409
+ if a.len() != b.len() {
410
+ return None;
411
+ }
412
+ let mut result: Self::Output = 0;
413
+ let element_count = a.len() * 2; // Each u4x2 contains 2 elements
414
+ unsafe {
415
+ nk_dot_u4(
416
+ a.as_ptr() as *const u8,
417
+ b.as_ptr() as *const u8,
418
+ element_count,
419
+ &mut result,
420
+ )
421
+ };
422
+ Some(result)
423
+ }
424
+ }
425
+
426
+ impl Dot for f16c {
427
+ type Output = f32c;
428
+ fn dot(a: &[Self], b: &[Self]) -> Option<Self::Output> {
429
+ if a.len() != b.len() {
430
+ return None;
431
+ }
432
+ let mut result = [0.0f32; 2];
433
+ unsafe {
434
+ nk_dot_f16c(
435
+ a.as_ptr() as *const u16,
436
+ b.as_ptr() as *const u16,
437
+ a.len(),
438
+ result.as_mut_ptr(),
439
+ )
440
+ };
441
+ Some(f32c {
442
+ re: result[0],
443
+ im: result[1],
444
+ })
445
+ }
446
+ }
447
+
448
+ impl Dot for bf16c {
449
+ type Output = f32c;
450
+ fn dot(a: &[Self], b: &[Self]) -> Option<Self::Output> {
451
+ if a.len() != b.len() {
452
+ return None;
453
+ }
454
+ let mut result = [0.0f32; 2];
455
+ unsafe {
456
+ nk_dot_bf16c(
457
+ a.as_ptr() as *const u16,
458
+ b.as_ptr() as *const u16,
459
+ a.len(),
460
+ result.as_mut_ptr(),
461
+ )
462
+ };
463
+ Some(f32c {
464
+ re: result[0],
465
+ im: result[1],
466
+ })
467
+ }
468
+ }
469
+
470
+ impl Dot for f32c {
471
+ type Output = f64c;
472
+ fn dot(a: &[Self], b: &[Self]) -> Option<Self::Output> {
473
+ if a.len() != b.len() {
474
+ return None;
475
+ }
476
+ let mut result = [0.0f64; 2];
477
+ unsafe {
478
+ nk_dot_f32c(
479
+ a.as_ptr() as *const f32,
480
+ b.as_ptr() as *const f32,
481
+ a.len(),
482
+ result.as_mut_ptr(),
483
+ )
484
+ };
485
+ Some(f64c {
486
+ re: result[0],
487
+ im: result[1],
488
+ })
489
+ }
490
+ }
491
+
492
+ impl Dot for f64c {
493
+ type Output = f64c;
494
+ fn dot(a: &[Self], b: &[Self]) -> Option<Self::Output> {
495
+ if a.len() != b.len() {
496
+ return None;
497
+ }
498
+ let mut result = [0.0f64; 2];
499
+ unsafe {
500
+ nk_dot_f64c(
501
+ a.as_ptr() as *const f64,
502
+ b.as_ptr() as *const f64,
503
+ a.len(),
504
+ result.as_mut_ptr(),
505
+ )
506
+ };
507
+ Some(f64c {
508
+ re: result[0],
509
+ im: result[1],
510
+ })
511
+ }
512
+ }
513
+
514
+ // endregion: Dot
515
+
516
+ // region: Angular
517
+
518
+ /// Computes the **angular distance** (cosine distance) between two vectors.
519
+ ///
520
+ /// d = 1 − (a · b) / (‖a‖ × ‖b‖)
521
+ ///
522
+ /// Range: \[0, 2\]. Returns `None` if lengths differ.
523
+ ///
524
+ /// Implemented for: `f64`, `f32`, `f16`, `bf16`, `i8`, `u8`,
525
+ /// `e4m3`, `e5m2`, `e2m3`, `e3m2`, `i4x2`, `u4x2`.
526
+ pub trait Angular: StorageElement {
527
+ type Output;
528
+ fn angular(a: &[Self], b: &[Self]) -> Option<Self::Output>;
529
+
530
+ /// Alias for `angular`.
531
+ fn cosine(a: &[Self], b: &[Self]) -> Option<Self::Output> {
532
+ Self::angular(a, b)
533
+ }
534
+ }
535
+
536
+ impl Angular for f64 {
537
+ type Output = f64;
538
+ fn angular(a: &[Self], b: &[Self]) -> Option<Self::Output> {
539
+ if a.len() != b.len() {
540
+ return None;
541
+ }
542
+ let mut result: Self::Output = 0.0;
543
+ unsafe { nk_angular_f64(a.as_ptr(), b.as_ptr(), a.len(), &mut result) };
544
+ Some(result)
545
+ }
546
+ }
547
+
548
+ impl Angular for f32 {
549
+ type Output = f64;
550
+ fn angular(a: &[Self], b: &[Self]) -> Option<Self::Output> {
551
+ if a.len() != b.len() {
552
+ return None;
553
+ }
554
+ let mut result: Self::Output = 0.0;
555
+ unsafe { nk_angular_f32(a.as_ptr(), b.as_ptr(), a.len(), &mut result) };
556
+ Some(result)
557
+ }
558
+ }
559
+
560
+ impl Angular for f16 {
561
+ type Output = f32;
562
+ fn angular(a: &[Self], b: &[Self]) -> Option<Self::Output> {
563
+ if a.len() != b.len() {
564
+ return None;
565
+ }
566
+ let mut result: Self::Output = 0.0;
567
+ unsafe {
568
+ nk_angular_f16(
569
+ a.as_ptr() as *const u16,
570
+ b.as_ptr() as *const u16,
571
+ a.len(),
572
+ &mut result,
573
+ )
574
+ };
575
+ Some(result)
576
+ }
577
+ }
578
+
579
+ impl Angular for bf16 {
580
+ type Output = f32;
581
+ fn angular(a: &[Self], b: &[Self]) -> Option<Self::Output> {
582
+ if a.len() != b.len() {
583
+ return None;
584
+ }
585
+ let mut result: Self::Output = 0.0;
586
+ unsafe {
587
+ nk_angular_bf16(
588
+ a.as_ptr() as *const u16,
589
+ b.as_ptr() as *const u16,
590
+ a.len(),
591
+ &mut result,
592
+ )
593
+ };
594
+ Some(result)
595
+ }
596
+ }
597
+
598
+ impl Angular for i8 {
599
+ type Output = f32;
600
+ fn angular(a: &[Self], b: &[Self]) -> Option<Self::Output> {
601
+ if a.len() != b.len() {
602
+ return None;
603
+ }
604
+ let mut result: Self::Output = 0.0;
605
+ unsafe { nk_angular_i8(a.as_ptr(), b.as_ptr(), a.len(), &mut result) };
606
+ Some(result)
607
+ }
608
+ }
609
+
610
+ impl Angular for u8 {
611
+ type Output = f32;
612
+ fn angular(a: &[Self], b: &[Self]) -> Option<Self::Output> {
613
+ if a.len() != b.len() {
614
+ return None;
615
+ }
616
+ let mut result: Self::Output = 0.0;
617
+ unsafe { nk_angular_u8(a.as_ptr(), b.as_ptr(), a.len(), &mut result) };
618
+ Some(result)
619
+ }
620
+ }
621
+
622
+ impl Angular for e4m3 {
623
+ type Output = f32;
624
+ fn angular(a: &[Self], b: &[Self]) -> Option<Self::Output> {
625
+ if a.len() != b.len() {
626
+ return None;
627
+ }
628
+ let mut result: Self::Output = 0.0;
629
+ unsafe {
630
+ nk_angular_e4m3(
631
+ a.as_ptr() as *const u8,
632
+ b.as_ptr() as *const u8,
633
+ a.len(),
634
+ &mut result,
635
+ )
636
+ };
637
+ Some(result)
638
+ }
639
+ }
640
+
641
+ impl Angular for e5m2 {
642
+ type Output = f32;
643
+ fn angular(a: &[Self], b: &[Self]) -> Option<Self::Output> {
644
+ if a.len() != b.len() {
645
+ return None;
646
+ }
647
+ let mut result: Self::Output = 0.0;
648
+ unsafe {
649
+ nk_angular_e5m2(
650
+ a.as_ptr() as *const u8,
651
+ b.as_ptr() as *const u8,
652
+ a.len(),
653
+ &mut result,
654
+ )
655
+ };
656
+ Some(result)
657
+ }
658
+ }
659
+
660
+ impl Angular for e2m3 {
661
+ type Output = f32;
662
+ fn angular(a: &[Self], b: &[Self]) -> Option<Self::Output> {
663
+ if a.len() != b.len() {
664
+ return None;
665
+ }
666
+ let mut result: Self::Output = 0.0;
667
+ unsafe {
668
+ nk_angular_e2m3(
669
+ a.as_ptr() as *const u8,
670
+ b.as_ptr() as *const u8,
671
+ a.len(),
672
+ &mut result,
673
+ )
674
+ };
675
+ Some(result)
676
+ }
677
+ }
678
+
679
+ impl Angular for e3m2 {
680
+ type Output = f32;
681
+ fn angular(a: &[Self], b: &[Self]) -> Option<Self::Output> {
682
+ if a.len() != b.len() {
683
+ return None;
684
+ }
685
+ let mut result: Self::Output = 0.0;
686
+ unsafe {
687
+ nk_angular_e3m2(
688
+ a.as_ptr() as *const u8,
689
+ b.as_ptr() as *const u8,
690
+ a.len(),
691
+ &mut result,
692
+ )
693
+ };
694
+ Some(result)
695
+ }
696
+ }
697
+
698
+ impl Angular for i4x2 {
699
+ type Output = f32;
700
+ fn angular(a: &[Self], b: &[Self]) -> Option<Self::Output> {
701
+ if a.len() != b.len() {
702
+ return None;
703
+ }
704
+ let mut result: Self::Output = 0.0;
705
+ let element_count = a.len() * 2; // Each i4x2 contains 2 elements
706
+ unsafe {
707
+ nk_angular_i4(
708
+ a.as_ptr() as *const u8,
709
+ b.as_ptr() as *const u8,
710
+ element_count,
711
+ &mut result,
712
+ )
713
+ };
714
+ Some(result)
715
+ }
716
+ }
717
+
718
+ impl Angular for u4x2 {
719
+ type Output = f32;
720
+ fn angular(a: &[Self], b: &[Self]) -> Option<Self::Output> {
721
+ if a.len() != b.len() {
722
+ return None;
723
+ }
724
+ let mut result: Self::Output = 0.0;
725
+ let element_count = a.len() * 2; // Each u4x2 contains 2 elements
726
+ unsafe {
727
+ nk_angular_u4(
728
+ a.as_ptr() as *const u8,
729
+ b.as_ptr() as *const u8,
730
+ element_count,
731
+ &mut result,
732
+ )
733
+ };
734
+ Some(result)
735
+ }
736
+ }
737
+
738
+ // endregion: Angular
739
+
740
+ // region: Euclidean
741
+
742
+ /// Computes the **Euclidean distance** (L2) between two vectors.
743
+ ///
744
+ /// d = √(∑ᵢ (aᵢ − bᵢ)²)
745
+ ///
746
+ /// Range: \[0, ∞). Returns `None` if lengths differ.
747
+ ///
748
+ /// Implemented for: `f64`, `f32`, `f16`, `bf16`, `i8`, `u8`,
749
+ /// `e4m3`, `e5m2`, `e2m3`, `e3m2`, `i4x2`, `u4x2`.
750
+ pub trait Euclidean: StorageElement {
751
+ type SqEuclideanOutput;
752
+ type EuclideanOutput;
753
+
754
+ /// Squared Euclidean distance (L2²). Faster than `euclidean` for comparisons.
755
+ fn sqeuclidean(a: &[Self], b: &[Self]) -> Option<Self::SqEuclideanOutput>;
756
+
757
+ /// Euclidean distance (L2). True metric distance.
758
+ fn euclidean(a: &[Self], b: &[Self]) -> Option<Self::EuclideanOutput>;
759
+ }
760
+
761
+ impl Euclidean for f64 {
762
+ type SqEuclideanOutput = f64;
763
+ type EuclideanOutput = f64;
764
+
765
+ fn sqeuclidean(a: &[Self], b: &[Self]) -> Option<Self::SqEuclideanOutput> {
766
+ if a.len() != b.len() {
767
+ return None;
768
+ }
769
+ let mut result: Self::SqEuclideanOutput = 0.0;
770
+ unsafe { nk_sqeuclidean_f64(a.as_ptr(), b.as_ptr(), a.len(), &mut result) };
771
+ Some(result)
772
+ }
773
+
774
+ fn euclidean(a: &[Self], b: &[Self]) -> Option<Self::EuclideanOutput> {
775
+ if a.len() != b.len() {
776
+ return None;
777
+ }
778
+ let mut result: Self::EuclideanOutput = 0.0;
779
+ unsafe { nk_euclidean_f64(a.as_ptr(), b.as_ptr(), a.len(), &mut result) };
780
+ Some(result)
781
+ }
782
+ }
783
+
784
+ impl Euclidean for f32 {
785
+ type SqEuclideanOutput = f64;
786
+ type EuclideanOutput = f64;
787
+
788
+ fn sqeuclidean(a: &[Self], b: &[Self]) -> Option<Self::SqEuclideanOutput> {
789
+ if a.len() != b.len() {
790
+ return None;
791
+ }
792
+ let mut result: Self::SqEuclideanOutput = 0.0;
793
+ unsafe { nk_sqeuclidean_f32(a.as_ptr(), b.as_ptr(), a.len(), &mut result) };
794
+ Some(result)
795
+ }
796
+
797
+ fn euclidean(a: &[Self], b: &[Self]) -> Option<Self::EuclideanOutput> {
798
+ if a.len() != b.len() {
799
+ return None;
800
+ }
801
+ let mut result: Self::EuclideanOutput = 0.0;
802
+ unsafe { nk_euclidean_f32(a.as_ptr(), b.as_ptr(), a.len(), &mut result) };
803
+ Some(result)
804
+ }
805
+ }
806
+
807
+ impl Euclidean for f16 {
808
+ type SqEuclideanOutput = f32;
809
+ type EuclideanOutput = f32;
810
+
811
+ fn sqeuclidean(a: &[Self], b: &[Self]) -> Option<Self::SqEuclideanOutput> {
812
+ if a.len() != b.len() {
813
+ return None;
814
+ }
815
+ let mut result: Self::SqEuclideanOutput = 0.0;
816
+ unsafe {
817
+ nk_sqeuclidean_f16(
818
+ a.as_ptr() as *const u16,
819
+ b.as_ptr() as *const u16,
820
+ a.len(),
821
+ &mut result,
822
+ )
823
+ };
824
+ Some(result)
825
+ }
826
+
827
+ fn euclidean(a: &[Self], b: &[Self]) -> Option<Self::EuclideanOutput> {
828
+ if a.len() != b.len() {
829
+ return None;
830
+ }
831
+ let mut result: Self::EuclideanOutput = 0.0;
832
+ unsafe {
833
+ nk_euclidean_f16(
834
+ a.as_ptr() as *const u16,
835
+ b.as_ptr() as *const u16,
836
+ a.len(),
837
+ &mut result,
838
+ )
839
+ };
840
+ Some(result)
841
+ }
842
+ }
843
+
844
+ impl Euclidean for bf16 {
845
+ type SqEuclideanOutput = f32;
846
+ type EuclideanOutput = f32;
847
+
848
+ fn sqeuclidean(a: &[Self], b: &[Self]) -> Option<Self::SqEuclideanOutput> {
849
+ if a.len() != b.len() {
850
+ return None;
851
+ }
852
+ let mut result: Self::SqEuclideanOutput = 0.0;
853
+ unsafe {
854
+ nk_sqeuclidean_bf16(
855
+ a.as_ptr() as *const u16,
856
+ b.as_ptr() as *const u16,
857
+ a.len(),
858
+ &mut result,
859
+ )
860
+ };
861
+ Some(result)
862
+ }
863
+
864
+ fn euclidean(a: &[Self], b: &[Self]) -> Option<Self::EuclideanOutput> {
865
+ if a.len() != b.len() {
866
+ return None;
867
+ }
868
+ let mut result: Self::EuclideanOutput = 0.0;
869
+ unsafe {
870
+ nk_euclidean_bf16(
871
+ a.as_ptr() as *const u16,
872
+ b.as_ptr() as *const u16,
873
+ a.len(),
874
+ &mut result,
875
+ )
876
+ };
877
+ Some(result)
878
+ }
879
+ }
880
+
881
+ impl Euclidean for i8 {
882
+ type SqEuclideanOutput = u32;
883
+ type EuclideanOutput = f32;
884
+
885
+ fn sqeuclidean(a: &[Self], b: &[Self]) -> Option<Self::SqEuclideanOutput> {
886
+ if a.len() != b.len() {
887
+ return None;
888
+ }
889
+ let mut result: Self::SqEuclideanOutput = 0;
890
+ unsafe { nk_sqeuclidean_i8(a.as_ptr(), b.as_ptr(), a.len(), &mut result) };
891
+ Some(result)
892
+ }
893
+
894
+ fn euclidean(a: &[Self], b: &[Self]) -> Option<Self::EuclideanOutput> {
895
+ if a.len() != b.len() {
896
+ return None;
897
+ }
898
+ let mut result: Self::EuclideanOutput = 0.0;
899
+ unsafe { nk_euclidean_i8(a.as_ptr(), b.as_ptr(), a.len(), &mut result) };
900
+ Some(result)
901
+ }
902
+ }
903
+
904
+ impl Euclidean for u8 {
905
+ type SqEuclideanOutput = u32;
906
+ type EuclideanOutput = f32;
907
+
908
+ fn sqeuclidean(a: &[Self], b: &[Self]) -> Option<Self::SqEuclideanOutput> {
909
+ if a.len() != b.len() {
910
+ return None;
911
+ }
912
+ let mut result: Self::SqEuclideanOutput = 0;
913
+ unsafe { nk_sqeuclidean_u8(a.as_ptr(), b.as_ptr(), a.len(), &mut result) };
914
+ Some(result)
915
+ }
916
+
917
+ fn euclidean(a: &[Self], b: &[Self]) -> Option<Self::EuclideanOutput> {
918
+ if a.len() != b.len() {
919
+ return None;
920
+ }
921
+ let mut result: Self::EuclideanOutput = 0.0;
922
+ unsafe { nk_euclidean_u8(a.as_ptr(), b.as_ptr(), a.len(), &mut result) };
923
+ Some(result)
924
+ }
925
+ }
926
+
927
+ impl Euclidean for e4m3 {
928
+ type SqEuclideanOutput = f32;
929
+ type EuclideanOutput = f32;
930
+
931
+ fn sqeuclidean(a: &[Self], b: &[Self]) -> Option<Self::SqEuclideanOutput> {
932
+ if a.len() != b.len() {
933
+ return None;
934
+ }
935
+ let mut result: Self::SqEuclideanOutput = 0.0;
936
+ unsafe {
937
+ nk_sqeuclidean_e4m3(
938
+ a.as_ptr() as *const u8,
939
+ b.as_ptr() as *const u8,
940
+ a.len(),
941
+ &mut result,
942
+ )
943
+ };
944
+ Some(result)
945
+ }
946
+
947
+ fn euclidean(a: &[Self], b: &[Self]) -> Option<Self::EuclideanOutput> {
948
+ if a.len() != b.len() {
949
+ return None;
950
+ }
951
+ let mut result: Self::EuclideanOutput = 0.0;
952
+ unsafe {
953
+ nk_euclidean_e4m3(
954
+ a.as_ptr() as *const u8,
955
+ b.as_ptr() as *const u8,
956
+ a.len(),
957
+ &mut result,
958
+ )
959
+ };
960
+ Some(result)
961
+ }
962
+ }
963
+
964
+ impl Euclidean for e5m2 {
965
+ type SqEuclideanOutput = f32;
966
+ type EuclideanOutput = f32;
967
+
968
+ fn sqeuclidean(a: &[Self], b: &[Self]) -> Option<Self::SqEuclideanOutput> {
969
+ if a.len() != b.len() {
970
+ return None;
971
+ }
972
+ let mut result: Self::SqEuclideanOutput = 0.0;
973
+ unsafe {
974
+ nk_sqeuclidean_e5m2(
975
+ a.as_ptr() as *const u8,
976
+ b.as_ptr() as *const u8,
977
+ a.len(),
978
+ &mut result,
979
+ )
980
+ };
981
+ Some(result)
982
+ }
983
+
984
+ fn euclidean(a: &[Self], b: &[Self]) -> Option<Self::EuclideanOutput> {
985
+ if a.len() != b.len() {
986
+ return None;
987
+ }
988
+ let mut result: Self::EuclideanOutput = 0.0;
989
+ unsafe {
990
+ nk_euclidean_e5m2(
991
+ a.as_ptr() as *const u8,
992
+ b.as_ptr() as *const u8,
993
+ a.len(),
994
+ &mut result,
995
+ )
996
+ };
997
+ Some(result)
998
+ }
999
+ }
1000
+
1001
+ impl Euclidean for e2m3 {
1002
+ type SqEuclideanOutput = f32;
1003
+ type EuclideanOutput = f32;
1004
+
1005
+ fn sqeuclidean(a: &[Self], b: &[Self]) -> Option<Self::SqEuclideanOutput> {
1006
+ if a.len() != b.len() {
1007
+ return None;
1008
+ }
1009
+ let mut result: Self::SqEuclideanOutput = 0.0;
1010
+ unsafe {
1011
+ nk_sqeuclidean_e2m3(
1012
+ a.as_ptr() as *const u8,
1013
+ b.as_ptr() as *const u8,
1014
+ a.len(),
1015
+ &mut result,
1016
+ )
1017
+ };
1018
+ Some(result)
1019
+ }
1020
+
1021
+ fn euclidean(a: &[Self], b: &[Self]) -> Option<Self::EuclideanOutput> {
1022
+ if a.len() != b.len() {
1023
+ return None;
1024
+ }
1025
+ let mut result: Self::EuclideanOutput = 0.0;
1026
+ unsafe {
1027
+ nk_euclidean_e2m3(
1028
+ a.as_ptr() as *const u8,
1029
+ b.as_ptr() as *const u8,
1030
+ a.len(),
1031
+ &mut result,
1032
+ )
1033
+ };
1034
+ Some(result)
1035
+ }
1036
+ }
1037
+
1038
+ impl Euclidean for e3m2 {
1039
+ type SqEuclideanOutput = f32;
1040
+ type EuclideanOutput = f32;
1041
+
1042
+ fn sqeuclidean(a: &[Self], b: &[Self]) -> Option<Self::SqEuclideanOutput> {
1043
+ if a.len() != b.len() {
1044
+ return None;
1045
+ }
1046
+ let mut result: Self::SqEuclideanOutput = 0.0;
1047
+ unsafe {
1048
+ nk_sqeuclidean_e3m2(
1049
+ a.as_ptr() as *const u8,
1050
+ b.as_ptr() as *const u8,
1051
+ a.len(),
1052
+ &mut result,
1053
+ )
1054
+ };
1055
+ Some(result)
1056
+ }
1057
+
1058
+ fn euclidean(a: &[Self], b: &[Self]) -> Option<Self::EuclideanOutput> {
1059
+ if a.len() != b.len() {
1060
+ return None;
1061
+ }
1062
+ let mut result: Self::EuclideanOutput = 0.0;
1063
+ unsafe {
1064
+ nk_euclidean_e3m2(
1065
+ a.as_ptr() as *const u8,
1066
+ b.as_ptr() as *const u8,
1067
+ a.len(),
1068
+ &mut result,
1069
+ )
1070
+ };
1071
+ Some(result)
1072
+ }
1073
+ }
1074
+
1075
+ impl Euclidean for i4x2 {
1076
+ type SqEuclideanOutput = u32;
1077
+ type EuclideanOutput = f32;
1078
+
1079
+ fn sqeuclidean(a: &[Self], b: &[Self]) -> Option<Self::SqEuclideanOutput> {
1080
+ if a.len() != b.len() {
1081
+ return None;
1082
+ }
1083
+ let mut result: Self::SqEuclideanOutput = 0;
1084
+ let element_count = a.len() * 2; // Each i4x2 contains 2 elements
1085
+ unsafe {
1086
+ nk_sqeuclidean_i4(
1087
+ a.as_ptr() as *const u8,
1088
+ b.as_ptr() as *const u8,
1089
+ element_count,
1090
+ &mut result,
1091
+ )
1092
+ };
1093
+ Some(result)
1094
+ }
1095
+
1096
+ fn euclidean(a: &[Self], b: &[Self]) -> Option<Self::EuclideanOutput> {
1097
+ if a.len() != b.len() {
1098
+ return None;
1099
+ }
1100
+ let mut result: Self::EuclideanOutput = 0.0;
1101
+ let element_count = a.len() * 2; // Each i4x2 contains 2 elements
1102
+ unsafe {
1103
+ nk_euclidean_i4(
1104
+ a.as_ptr() as *const u8,
1105
+ b.as_ptr() as *const u8,
1106
+ element_count,
1107
+ &mut result,
1108
+ )
1109
+ };
1110
+ Some(result)
1111
+ }
1112
+ }
1113
+
1114
+ impl Euclidean for u4x2 {
1115
+ type SqEuclideanOutput = u32;
1116
+ type EuclideanOutput = f32;
1117
+
1118
+ fn sqeuclidean(a: &[Self], b: &[Self]) -> Option<Self::SqEuclideanOutput> {
1119
+ if a.len() != b.len() {
1120
+ return None;
1121
+ }
1122
+ let mut result: Self::SqEuclideanOutput = 0;
1123
+ let element_count = a.len() * 2; // Each u4x2 contains 2 elements
1124
+ unsafe {
1125
+ nk_sqeuclidean_u4(
1126
+ a.as_ptr() as *const u8,
1127
+ b.as_ptr() as *const u8,
1128
+ element_count,
1129
+ &mut result,
1130
+ )
1131
+ };
1132
+ Some(result)
1133
+ }
1134
+
1135
+ fn euclidean(a: &[Self], b: &[Self]) -> Option<Self::EuclideanOutput> {
1136
+ if a.len() != b.len() {
1137
+ return None;
1138
+ }
1139
+ let mut result: Self::EuclideanOutput = 0.0;
1140
+ let element_count = a.len() * 2; // Each u4x2 contains 2 elements
1141
+ unsafe {
1142
+ nk_euclidean_u4(
1143
+ a.as_ptr() as *const u8,
1144
+ b.as_ptr() as *const u8,
1145
+ element_count,
1146
+ &mut result,
1147
+ )
1148
+ };
1149
+ Some(result)
1150
+ }
1151
+ }
1152
+
1153
+ // endregion: Euclidean
1154
+
1155
+ // region: VDot
1156
+
1157
+ /// Computes the conjugating (Hermitian) dot product.
1158
+ ///
1159
+ /// For real-valued types this is identical to [`Dot::dot`]. For complex-valued
1160
+ /// types it computes `∑ᵢ conj(aᵢ) × bᵢ`, which is the convention used in quantum
1161
+ /// mechanics and most linear-algebra textbooks (BLAS `cdotc`, NumPy `vdot`).
1162
+ ///
1163
+ /// Unlike an ordinary complex dot product, the Hermitian form produces a proper
1164
+ /// inner product: `vdot(x, x)` is always a non-negative real number equal to
1165
+ /// `‖x‖²`, and `vdot(x, y) == conj(vdot(y, x))`. This matters in retrieval and
1166
+ /// signal-processing pipelines where cancellation between a vector and its
1167
+ /// conjugate should leave the norm intact.
1168
+ pub trait VDot: Dot {
1169
+ /// Hermitian inner product. On real-valued types this falls back to `Dot::dot`;
1170
+ /// on complex types it returns `∑ᵢ conj(aᵢ) × bᵢ` computed in the widened
1171
+ /// accumulator described by `Dot::Output`.
1172
+ fn vdot(a: &[Self], b: &[Self]) -> Option<Self::Output> {
1173
+ Self::dot(a, b)
1174
+ }
1175
+ }
1176
+
1177
+ impl VDot for f64 {}
1178
+ impl VDot for f32 {}
1179
+ impl VDot for f16 {}
1180
+ impl VDot for bf16 {}
1181
+ impl VDot for i8 {}
1182
+ impl VDot for u8 {}
1183
+ impl VDot for e4m3 {}
1184
+ impl VDot for e5m2 {}
1185
+ impl VDot for e2m3 {}
1186
+ impl VDot for e3m2 {}
1187
+ impl VDot for i4x2 {}
1188
+ impl VDot for u4x2 {}
1189
+
1190
+ impl VDot for f16c {
1191
+ fn vdot(a: &[Self], b: &[Self]) -> Option<Self::Output> {
1192
+ if a.len() != b.len() {
1193
+ return None;
1194
+ }
1195
+ let mut result = [0.0f32; 2];
1196
+ unsafe {
1197
+ nk_vdot_f16c(
1198
+ a.as_ptr() as *const u16,
1199
+ b.as_ptr() as *const u16,
1200
+ a.len(),
1201
+ result.as_mut_ptr(),
1202
+ )
1203
+ };
1204
+ Some(f32c {
1205
+ re: result[0],
1206
+ im: result[1],
1207
+ })
1208
+ }
1209
+ }
1210
+
1211
+ impl VDot for bf16c {
1212
+ fn vdot(a: &[Self], b: &[Self]) -> Option<Self::Output> {
1213
+ if a.len() != b.len() {
1214
+ return None;
1215
+ }
1216
+ let mut result = [0.0f32; 2];
1217
+ unsafe {
1218
+ nk_vdot_bf16c(
1219
+ a.as_ptr() as *const u16,
1220
+ b.as_ptr() as *const u16,
1221
+ a.len(),
1222
+ result.as_mut_ptr(),
1223
+ )
1224
+ };
1225
+ Some(f32c {
1226
+ re: result[0],
1227
+ im: result[1],
1228
+ })
1229
+ }
1230
+ }
1231
+
1232
+ impl VDot for f32c {
1233
+ fn vdot(a: &[Self], b: &[Self]) -> Option<Self::Output> {
1234
+ if a.len() != b.len() {
1235
+ return None;
1236
+ }
1237
+ let mut result = [0.0f64; 2];
1238
+ unsafe {
1239
+ nk_vdot_f32c(
1240
+ a.as_ptr() as *const f32,
1241
+ b.as_ptr() as *const f32,
1242
+ a.len(),
1243
+ result.as_mut_ptr(),
1244
+ )
1245
+ };
1246
+ Some(f64c {
1247
+ re: result[0],
1248
+ im: result[1],
1249
+ })
1250
+ }
1251
+ }
1252
+
1253
+ impl VDot for f64c {
1254
+ fn vdot(a: &[Self], b: &[Self]) -> Option<Self::Output> {
1255
+ if a.len() != b.len() {
1256
+ return None;
1257
+ }
1258
+ let mut result = [0.0f64; 2];
1259
+ unsafe {
1260
+ nk_vdot_f64c(
1261
+ a.as_ptr() as *const f64,
1262
+ b.as_ptr() as *const f64,
1263
+ a.len(),
1264
+ result.as_mut_ptr(),
1265
+ )
1266
+ };
1267
+ Some(f64c {
1268
+ re: result[0],
1269
+ im: result[1],
1270
+ })
1271
+ }
1272
+ }
1273
+
1274
+ // endregion: VDot
1275
+
1276
+ /// `SpatialSimilarity` bundles spatial distance metrics: Dot, Angular, and Euclidean.
1277
+ pub trait SpatialSimilarity: Dot + Angular + Euclidean {}
1278
+ impl<Scalar: Dot + Angular + Euclidean> SpatialSimilarity for Scalar {}
1279
+
1280
+ #[cfg(test)]
1281
+ mod tests {
1282
+ use super::*;
1283
+ use crate::curved::Bilinear;
1284
+ use crate::types::{
1285
+ assert_close, bf16, bf16c, e2m3, e3m2, e4m3, e5m2, f16, f16c, f32c, f64c, i4x2, u4x2,
1286
+ FloatLike, NumberLike, StorageElement, TestableType,
1287
+ };
1288
+
1289
+ /// Test a two-input metric: convert f32 inputs to Scalar, call `op`, compare to `expected`.
1290
+ pub(crate) fn check_binary<Scalar, R, F>(
1291
+ a_vals: &[f32],
1292
+ b_vals: &[f32],
1293
+ op: F,
1294
+ expected: f64,
1295
+ label: &str,
1296
+ ) where
1297
+ Scalar: FloatLike + TestableType,
1298
+ R: FloatLike,
1299
+ F: FnOnce(&[Scalar], &[Scalar]) -> Option<R>,
1300
+ {
1301
+ let a: Vec<Scalar> = a_vals.iter().map(|&v| Scalar::from_f32(v)).collect();
1302
+ let b: Vec<Scalar> = b_vals.iter().map(|&v| Scalar::from_f32(v)).collect();
1303
+ let result = op(&a, &b).unwrap().to_f64();
1304
+ assert_close(
1305
+ result,
1306
+ expected,
1307
+ Scalar::atol(),
1308
+ Scalar::rtol(),
1309
+ &format!("{}<{}>", label, core::any::type_name::<Scalar>()),
1310
+ );
1311
+ }
1312
+
1313
+ // region: Dot Products
1314
+
1315
+ fn check_dot<Scalar>(a_vals: &[f32], b_vals: &[f32], expected: f64)
1316
+ where
1317
+ Scalar: FloatLike + TestableType + Dot,
1318
+ Scalar::Output: FloatLike,
1319
+ {
1320
+ check_binary::<Scalar, Scalar::Output, _>(a_vals, b_vals, Scalar::dot, expected, "dot");
1321
+ }
1322
+
1323
+ #[test]
1324
+ fn dot() {
1325
+ check_dot::<f32>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], 32.0);
1326
+ check_dot::<f64>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], 32.0);
1327
+ check_dot::<f16>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], 32.0);
1328
+ check_dot::<bf16>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], 32.0);
1329
+ check_dot::<i8>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], 32.0);
1330
+ check_dot::<u8>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], 32.0);
1331
+ check_dot::<e4m3>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], 32.0);
1332
+ check_dot::<e5m2>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], 32.0);
1333
+ check_dot::<e2m3>(&[1.0, 2.0, 3.0], &[1.0, 1.0, 1.0], 6.0);
1334
+ check_dot::<e3m2>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], 32.0);
1335
+ check_dot::<i4x2>(&[1.0, 2.0, 3.0], &[1.0, 1.0, 1.0], 12.0);
1336
+ check_dot::<u4x2>(&[1.0, 2.0, 3.0], &[1.0, 1.0, 1.0], 12.0);
1337
+ }
1338
+
1339
+ // endregion
1340
+
1341
+ // region: Angular Distances
1342
+
1343
+ fn check_angular<Scalar>(a_vals: &[f32], b_vals: &[f32], expected: f64)
1344
+ where
1345
+ Scalar: FloatLike + TestableType + Angular,
1346
+ Scalar::Output: FloatLike,
1347
+ {
1348
+ check_binary::<Scalar, Scalar::Output, _>(
1349
+ a_vals,
1350
+ b_vals,
1351
+ Scalar::angular,
1352
+ expected,
1353
+ "angular",
1354
+ );
1355
+ }
1356
+
1357
+ #[test]
1358
+ fn angular() {
1359
+ // angular([1,2,3],[4,5,6]) = 1 - 32/sqrt(14*77) ≈ 0.025368
1360
+ let expected = 1.0 - 32.0 / (14.0_f64.sqrt() * 77.0_f64.sqrt());
1361
+ check_angular::<f32>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], expected);
1362
+ check_angular::<f64>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], expected);
1363
+ check_angular::<f16>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], expected);
1364
+ check_angular::<bf16>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], expected);
1365
+ check_angular::<i8>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], expected);
1366
+ check_angular::<u8>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], expected);
1367
+ check_angular::<e4m3>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], expected);
1368
+ check_angular::<e5m2>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], expected);
1369
+ // e2m3 max is 7.5, use values in range
1370
+ let expected_e2m3 = 1.0 - 6.0 / (14.0_f64.sqrt() * 3.0_f64.sqrt());
1371
+ check_angular::<e2m3>(&[1.0, 2.0, 3.0], &[1.0, 1.0, 1.0], expected_e2m3);
1372
+ check_angular::<e3m2>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], expected);
1373
+ check_angular::<i4x2>(&[1.0, 2.0, 3.0], &[1.0, 1.0, 1.0], expected_e2m3);
1374
+ check_angular::<u4x2>(&[1.0, 2.0, 3.0], &[1.0, 1.0, 1.0], expected_e2m3);
1375
+ }
1376
+
1377
+ // endregion
1378
+
1379
+ // region: Euclidean Distances
1380
+
1381
+ fn check_sqeuclidean<Scalar>(a_vals: &[f32], b_vals: &[f32], expected: f64)
1382
+ where
1383
+ Scalar: FloatLike + TestableType + Euclidean,
1384
+ Scalar::SqEuclideanOutput: FloatLike,
1385
+ {
1386
+ check_binary::<Scalar, Scalar::SqEuclideanOutput, _>(
1387
+ a_vals,
1388
+ b_vals,
1389
+ Scalar::sqeuclidean,
1390
+ expected,
1391
+ "sqeuclidean",
1392
+ );
1393
+ }
1394
+
1395
+ fn check_euclidean<Scalar>(a_vals: &[f32], b_vals: &[f32], expected: f64)
1396
+ where
1397
+ Scalar: FloatLike + TestableType + Euclidean,
1398
+ Scalar::EuclideanOutput: FloatLike,
1399
+ {
1400
+ check_binary::<Scalar, Scalar::EuclideanOutput, _>(
1401
+ a_vals,
1402
+ b_vals,
1403
+ Scalar::euclidean,
1404
+ expected,
1405
+ "euclidean",
1406
+ );
1407
+ }
1408
+
1409
+ #[test]
1410
+ fn sqeuclidean() {
1411
+ check_sqeuclidean::<f32>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], 27.0);
1412
+ check_sqeuclidean::<f64>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], 27.0);
1413
+ check_sqeuclidean::<f16>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], 27.0);
1414
+ check_sqeuclidean::<bf16>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], 27.0);
1415
+ check_sqeuclidean::<i8>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], 27.0);
1416
+ check_sqeuclidean::<u8>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], 27.0);
1417
+ check_sqeuclidean::<e4m3>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], 27.0);
1418
+ check_sqeuclidean::<e5m2>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], 27.0);
1419
+ check_sqeuclidean::<e2m3>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], 27.0);
1420
+ check_sqeuclidean::<e3m2>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], 27.0);
1421
+ }
1422
+
1423
+ #[test]
1424
+ fn euclidean() {
1425
+ let expected = 27.0_f64.sqrt();
1426
+ check_euclidean::<f32>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], expected);
1427
+ check_euclidean::<f64>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], expected);
1428
+ check_euclidean::<f16>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], expected);
1429
+ check_euclidean::<bf16>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], expected);
1430
+ check_euclidean::<i8>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], expected);
1431
+ check_euclidean::<u8>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], expected);
1432
+ check_euclidean::<e4m3>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], expected);
1433
+ check_euclidean::<e5m2>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], expected);
1434
+ check_euclidean::<e2m3>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], expected);
1435
+ check_euclidean::<e3m2>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], expected);
1436
+ let expected_packed = 54.0_f64.sqrt(); // i4x2 duplicates each value into both nibbles
1437
+ check_euclidean::<i4x2>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], expected_packed);
1438
+ check_euclidean::<u4x2>(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], expected_packed);
1439
+ }
1440
+
1441
+ // endregion
1442
+
1443
+ // region: Complex Products
1444
+
1445
+ trait ComplexValue {
1446
+ fn real(&self) -> f64;
1447
+ fn imag(&self) -> f64;
1448
+ }
1449
+ impl ComplexValue for f32c {
1450
+ fn real(&self) -> f64 {
1451
+ self.re as f64
1452
+ }
1453
+ fn imag(&self) -> f64 {
1454
+ self.im as f64
1455
+ }
1456
+ }
1457
+ impl ComplexValue for f64c {
1458
+ fn real(&self) -> f64 {
1459
+ self.re
1460
+ }
1461
+ fn imag(&self) -> f64 {
1462
+ self.im
1463
+ }
1464
+ }
1465
+
1466
+ trait ComplexSample: Copy + StorageElement + Dot + VDot + Bilinear {
1467
+ fn from_real_imag(re: f32, im: f32) -> Self;
1468
+ fn atol() -> f64;
1469
+ fn rtol() -> f64;
1470
+ }
1471
+
1472
+ impl ComplexSample for f16c {
1473
+ fn from_real_imag(re: f32, im: f32) -> Self {
1474
+ Self {
1475
+ re: f16::from_f32(re),
1476
+ im: f16::from_f32(im),
1477
+ }
1478
+ }
1479
+ fn atol() -> f64 {
1480
+ 5e-2
1481
+ }
1482
+ fn rtol() -> f64 {
1483
+ 5e-2
1484
+ }
1485
+ }
1486
+
1487
+ impl ComplexSample for bf16c {
1488
+ fn from_real_imag(re: f32, im: f32) -> Self {
1489
+ Self {
1490
+ re: bf16::from_f32(re),
1491
+ im: bf16::from_f32(im),
1492
+ }
1493
+ }
1494
+ fn atol() -> f64 {
1495
+ 5e-2
1496
+ }
1497
+ fn rtol() -> f64 {
1498
+ 5e-2
1499
+ }
1500
+ }
1501
+
1502
+ impl ComplexSample for f32c {
1503
+ fn from_real_imag(re: f32, im: f32) -> Self {
1504
+ Self { re, im }
1505
+ }
1506
+ fn atol() -> f64 {
1507
+ 1e-6
1508
+ }
1509
+ fn rtol() -> f64 {
1510
+ 1e-6
1511
+ }
1512
+ }
1513
+
1514
+ impl ComplexSample for f64c {
1515
+ fn from_real_imag(re: f32, im: f32) -> Self {
1516
+ Self {
1517
+ re: re as f64,
1518
+ im: im as f64,
1519
+ }
1520
+ }
1521
+ fn atol() -> f64 {
1522
+ 1e-12
1523
+ }
1524
+ fn rtol() -> f64 {
1525
+ 1e-12
1526
+ }
1527
+ }
1528
+
1529
+ /// Test a complex two-input operation with real + imaginary expected outputs.
1530
+ fn check_complex<Scalar, R, F>(
1531
+ a: &[(f32, f32)],
1532
+ b: &[(f32, f32)],
1533
+ op: F,
1534
+ expected_re: f64,
1535
+ expected_im: f64,
1536
+ label: &str,
1537
+ ) where
1538
+ Scalar: ComplexSample,
1539
+ R: ComplexValue,
1540
+ F: FnOnce(&[Scalar], &[Scalar]) -> Option<R>,
1541
+ {
1542
+ let a_t: Vec<Scalar> = a
1543
+ .iter()
1544
+ .map(|&(re, im)| Scalar::from_real_imag(re, im))
1545
+ .collect();
1546
+ let b_t: Vec<Scalar> = b
1547
+ .iter()
1548
+ .map(|&(re, im)| Scalar::from_real_imag(re, im))
1549
+ .collect();
1550
+ let result = op(&a_t, &b_t).unwrap();
1551
+ let tol = Scalar::atol() + Scalar::rtol() * expected_re.abs().max(expected_im.abs());
1552
+ assert_close(
1553
+ result.real(),
1554
+ expected_re,
1555
+ tol,
1556
+ 0.0,
1557
+ &format!("{}<{}> real", label, core::any::type_name::<Scalar>()),
1558
+ );
1559
+ assert_close(
1560
+ result.imag(),
1561
+ expected_im,
1562
+ tol,
1563
+ 0.0,
1564
+ &format!("{}<{}> imag", label, core::any::type_name::<Scalar>()),
1565
+ );
1566
+ }
1567
+
1568
+ fn check_complex_dot<Scalar>(a: &[f32], b: &[f32], expected_re: f64, expected_im: f64)
1569
+ where
1570
+ Scalar: ComplexSample,
1571
+ <Scalar as Dot>::Output: ComplexValue,
1572
+ {
1573
+ let a_pairs: Vec<(f32, f32)> = a
1574
+ .chunks_exact(2)
1575
+ .map(|chunk| (chunk[0], chunk[1]))
1576
+ .collect();
1577
+ let b_pairs: Vec<(f32, f32)> = b
1578
+ .chunks_exact(2)
1579
+ .map(|chunk| (chunk[0], chunk[1]))
1580
+ .collect();
1581
+ check_complex::<Scalar, <Scalar as Dot>::Output, _>(
1582
+ &a_pairs,
1583
+ &b_pairs,
1584
+ <Scalar as Dot>::dot,
1585
+ expected_re,
1586
+ expected_im,
1587
+ "complex_dot",
1588
+ );
1589
+ }
1590
+
1591
+ fn check_complex_vdot<Scalar>(a: &[f32], b: &[f32], expected_re: f64, expected_im: f64)
1592
+ where
1593
+ Scalar: ComplexSample,
1594
+ <Scalar as Dot>::Output: ComplexValue,
1595
+ {
1596
+ let a_pairs: Vec<(f32, f32)> = a
1597
+ .chunks_exact(2)
1598
+ .map(|chunk| (chunk[0], chunk[1]))
1599
+ .collect();
1600
+ let b_pairs: Vec<(f32, f32)> = b
1601
+ .chunks_exact(2)
1602
+ .map(|chunk| (chunk[0], chunk[1]))
1603
+ .collect();
1604
+ check_complex::<Scalar, <Scalar as Dot>::Output, _>(
1605
+ &a_pairs,
1606
+ &b_pairs,
1607
+ Scalar::vdot,
1608
+ expected_re,
1609
+ expected_im,
1610
+ "complex_vdot",
1611
+ );
1612
+ }
1613
+
1614
+ fn check_complex_bilinear_identity<Scalar>(element_count: usize)
1615
+ where
1616
+ Scalar: ComplexSample,
1617
+ <Scalar as Bilinear>::Output: ComplexValue,
1618
+ {
1619
+ let mut a = vec![Scalar::zero(); element_count];
1620
+ let mut b = vec![Scalar::zero(); element_count];
1621
+ a[0] = Scalar::one();
1622
+ b[0] = Scalar::one();
1623
+ let mut c = vec![Scalar::zero(); element_count * element_count];
1624
+ for i in 0..element_count {
1625
+ c[i * element_count + i] = Scalar::one();
1626
+ }
1627
+ let result = Scalar::bilinear(&a, &b, &c).unwrap();
1628
+ let tol = Scalar::atol() + Scalar::rtol();
1629
+ assert_close(
1630
+ result.real(),
1631
+ 1.0,
1632
+ tol,
1633
+ 0.0,
1634
+ &format!(
1635
+ "complex_bilinear<{}> real",
1636
+ core::any::type_name::<Scalar>()
1637
+ ),
1638
+ );
1639
+ assert_close(
1640
+ result.imag(),
1641
+ 0.0,
1642
+ tol,
1643
+ 0.0,
1644
+ &format!(
1645
+ "complex_bilinear<{}> imag",
1646
+ core::any::type_name::<Scalar>()
1647
+ ),
1648
+ );
1649
+ }
1650
+
1651
+ #[test]
1652
+ fn complex_dot_vdot_bilinear() {
1653
+ // [1+2i, 3+4i] · [5+6i, 7+8i]
1654
+ let a = &[1.0_f32, 2.0, 3.0, 4.0];
1655
+ let b = &[5.0_f32, 6.0, 7.0, 8.0];
1656
+
1657
+ // dot: (-18, 68)
1658
+ check_complex_dot::<f64c>(a, b, -18.0, 68.0);
1659
+ check_complex_dot::<f32c>(a, b, -18.0, 68.0);
1660
+ check_complex_dot::<f16c>(a, b, -18.0, 68.0);
1661
+ check_complex_dot::<bf16c>(a, b, -18.0, 68.0);
1662
+
1663
+ // vdot (conjugate): (70, -8)
1664
+ check_complex_vdot::<f64c>(a, b, 70.0, -8.0);
1665
+ check_complex_vdot::<f32c>(a, b, 70.0, -8.0);
1666
+ check_complex_vdot::<f16c>(a, b, 70.0, -8.0);
1667
+ check_complex_vdot::<bf16c>(a, b, 70.0, -8.0);
1668
+
1669
+ // bilinear: identity matrix, unit vector → (1, 0)
1670
+ check_complex_bilinear_identity::<f64c>(4);
1671
+ check_complex_bilinear_identity::<f32c>(4);
1672
+ check_complex_bilinear_identity::<f16c>(4);
1673
+ check_complex_bilinear_identity::<bf16c>(4);
1674
+ }
1675
+
1676
+ // endregion
1677
+
1678
+ // region: Denormal (subnormal) inputs
1679
+ //
1680
+ // Verify that distance kernels produce correct results when fed IEEE-754
1681
+ // denormal (subnormal) values. This guards against FTZ/DAZ silently
1682
+ // flushing tiny values to zero.
1683
+
1684
+ #[test]
1685
+ fn dot_f32_denormals() {
1686
+ // Largest f32 denormal: exp=0, mantissa=0x7FFFFF → ≈ 1.1754942e-38
1687
+ let d = f32::from_bits(0x007F_FFFF);
1688
+ // dot([d,d,d], [d,d,d]) = 3 * d * d
1689
+ // F32 dot accumulates in F64 internally, so the result is precise.
1690
+ let expected = 3.0 * (d as f64) * (d as f64);
1691
+ let a = [d, d, d];
1692
+ let b = [d, d, d];
1693
+ let result = f32::dot(&a, &b).unwrap();
1694
+ assert_close(result as f64, expected, 1e-50, 1e-6, "dot<f32> denormal");
1695
+ }
1696
+
1697
+ #[test]
1698
+ fn dot_f64_denormals() {
1699
+ // Largest f64 denormal: exp=0, mantissa all-ones → ≈ 2.225e-308
1700
+ let d = f64::from_bits(0x000F_FFFF_FFFF_FFFF);
1701
+ let expected = 3.0 * d * d;
1702
+ let a = [d, d, d];
1703
+ let b = [d, d, d];
1704
+ let result = f64::dot(&a, &b).unwrap();
1705
+ // F64 dot uses Neumaier compensation; the product d*d underflows to 0 in f64
1706
+ // (since d ≈ 2.2e-308, d*d ≈ 5e-616 which is below f64 min denormal ~5e-324).
1707
+ // So expected = 0.0 and result should also be 0.0 — the key test is that
1708
+ // the kernel doesn't crash or produce NaN/Inf.
1709
+ assert!(
1710
+ result.is_finite(),
1711
+ "dot<f64> denormal produced non-finite: {result}"
1712
+ );
1713
+ assert_close(result, expected, 1e-300, 1e-6, "dot<f64> denormal");
1714
+ }
1715
+
1716
+ #[test]
1717
+ fn dot_f16_denormals() {
1718
+ // F16 denormal: exp=0, mantissa=0x0100 → 2^-14 * 256/1024 = 2^-16 ≈ 1.526e-5
1719
+ // F16 denormals upcast to normal f32 values, so no penalty or flushing.
1720
+ let d = f16(0x0100);
1721
+ let d_f64 = d.to_f64();
1722
+ let expected = 3.0 * d_f64 * d_f64;
1723
+ check_dot::<f16>(
1724
+ &[d.to_f32(), d.to_f32(), d.to_f32()],
1725
+ &[d.to_f32(), d.to_f32(), d.to_f32()],
1726
+ expected,
1727
+ );
1728
+ }
1729
+
1730
+ #[test]
1731
+ fn dot_bf16_denormals() {
1732
+ // BF16 denormal: exp=0, mantissa=0x7F (largest BF16 denormal)
1733
+ // BF16 shares f32's exponent, so this IS a f32 denormal after upcast.
1734
+ let d = bf16(0x007F);
1735
+ let d_f64 = d.to_f64();
1736
+ let expected = 3.0 * d_f64 * d_f64;
1737
+ check_dot::<bf16>(
1738
+ &[d.to_f32(), d.to_f32(), d.to_f32()],
1739
+ &[d.to_f32(), d.to_f32(), d.to_f32()],
1740
+ expected,
1741
+ );
1742
+ }
1743
+
1744
+ #[test]
1745
+ fn sqeuclidean_f32_denormals() {
1746
+ // Two vectors of denormals: differences are also denormal.
1747
+ let a_val = f32::from_bits(0x007F_FFFF); // largest f32 denormal
1748
+ let b_val = f32::from_bits(0x003F_FFFF); // half-way f32 denormal
1749
+ let diff = (a_val as f64) - (b_val as f64);
1750
+ let expected = 3.0 * diff * diff;
1751
+ let a = [a_val; 3];
1752
+ let b = [b_val; 3];
1753
+ let result = f32::sqeuclidean(&a, &b).unwrap();
1754
+ assert!(
1755
+ result.is_finite(),
1756
+ "sqeuclidean<f32> denormal produced non-finite: {result}"
1757
+ );
1758
+ assert_close(
1759
+ result as f64,
1760
+ expected,
1761
+ 1e-50,
1762
+ 1e-6,
1763
+ "sqeuclidean<f32> denormal",
1764
+ );
1765
+ }
1766
+
1767
+ #[test]
1768
+ fn sqeuclidean_f64_denormals() {
1769
+ let a_val = f64::from_bits(0x000F_FFFF_FFFF_FFFF);
1770
+ let b_val = f64::from_bits(0x0007_FFFF_FFFF_FFFF);
1771
+ let diff = a_val - b_val;
1772
+ let expected = 3.0 * diff * diff;
1773
+ let a = [a_val; 3];
1774
+ let b = [b_val; 3];
1775
+ let result = f64::sqeuclidean(&a, &b).unwrap();
1776
+ assert!(
1777
+ result.is_finite(),
1778
+ "sqeuclidean<f64> denormal produced non-finite: {result}"
1779
+ );
1780
+ assert_close(result, expected, 1e-300, 1e-6, "sqeuclidean<f64> denormal");
1781
+ }
1782
+
1783
+ #[test]
1784
+ fn angular_f32_denormals() {
1785
+ // Two identical denormal vectors: angular distance should be 0 (cosine = 1).
1786
+ let d = f32::from_bits(0x007F_FFFF);
1787
+ let a = [d, d, d];
1788
+ let result = f32::angular(&a, &a).unwrap();
1789
+ assert!(
1790
+ result.is_finite(),
1791
+ "angular<f32> denormal produced non-finite: {result}"
1792
+ );
1793
+ assert_close(
1794
+ result as f64,
1795
+ 0.0,
1796
+ 1e-4,
1797
+ 0.0,
1798
+ "angular<f32> identical denormals",
1799
+ );
1800
+ }
1801
+
1802
+ #[test]
1803
+ fn angular_f64_denormals() {
1804
+ let d = f64::from_bits(0x000F_FFFF_FFFF_FFFF);
1805
+ let a = [d, d, d];
1806
+ let result = f64::angular(&a, &a).unwrap();
1807
+ assert!(
1808
+ result.is_finite(),
1809
+ "angular<f64> denormal produced non-finite: {result}"
1810
+ );
1811
+ assert_close(result, 0.0, 1e-9, 0.0, "angular<f64> identical denormals");
1812
+ }
1813
+
1814
+ // endregion
1815
+ }