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,1041 @@
1
+ #!/usr/bin/env python3
2
+ """Shared test infrastructure for the NumKong test suite.
3
+
4
+ Provides random data generators, capability detection, high-precision baselines
5
+ via ``precise_decimal()``, and assertion helpers. Mirrors ``test.hpp`` from the
6
+ C++ test suite.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import array
12
+ import collections
13
+ import contextlib
14
+ import decimal
15
+ import faulthandler
16
+ import math
17
+ import os
18
+ import platform
19
+ import random
20
+ import sys
21
+ import time
22
+ from collections.abc import Callable, Generator
23
+ from typing import TYPE_CHECKING, Any
24
+
25
+ import numkong as nk
26
+ import pytest
27
+
28
+ if TYPE_CHECKING:
29
+ import numpy as np # static-analysis-only; the runtime try/except below is authoritative
30
+
31
+ faulthandler.enable()
32
+
33
+ _nk_seed_base: int | None = int(s) if (s := os.environ.get("NK_SEED")) is not None else None
34
+ _nk_in_qemu: bool = "NK_IN_QEMU" in os.environ
35
+
36
+ _nk_possible_dimensions = (
37
+ [1, 4, 16, 33, 64]
38
+ if _nk_in_qemu
39
+ else [
40
+ # start with simplest cases
41
+ 4,
42
+ 8,
43
+ 16,
44
+ 64,
45
+ 128,
46
+ # cover tiny cases
47
+ 1,
48
+ 2,
49
+ 3,
50
+ 5,
51
+ 7,
52
+ 9,
53
+ # corner cases around common sizes
54
+ 15,
55
+ 16,
56
+ 17,
57
+ 31,
58
+ 32,
59
+ 33,
60
+ 63,
61
+ 64,
62
+ 65,
63
+ 97,
64
+ ]
65
+ )
66
+
67
+
68
+ randomized_repetitions_count: int = (
69
+ int(s) if (s := os.environ.get("NK_REPETITIONS")) is not None else (3 if _nk_in_qemu else 10)
70
+ )
71
+
72
+ dense_dimensions: list[int] = (
73
+ [int(d) for d in s.split(",")]
74
+ if (s := os.environ.get("NK_DENSE_DIMENSIONS")) is not None
75
+ else _nk_possible_dimensions
76
+ )
77
+
78
+ # Deterministic random subsamples for multi-dimensional parametrization (height × width × depth),
79
+ # matching the C API naming in dots.h. Override via NK_MATRIX_HEIGHT/WIDTH/DEPTH env vars.
80
+ _dim_sample_k = min(6, len(dense_dimensions))
81
+ test_height_dimensions: list[int] = (
82
+ [int(d) for d in s.split(",")]
83
+ if (s := os.environ.get("NK_MATRIX_HEIGHT")) is not None
84
+ else sorted(random.Random(42).sample(dense_dimensions, _dim_sample_k))
85
+ )
86
+ test_width_dimensions: list[int] = (
87
+ [int(d) for d in s.split(",")]
88
+ if (s := os.environ.get("NK_MATRIX_WIDTH")) is not None
89
+ else sorted(random.Random(43).sample(dense_dimensions, _dim_sample_k))
90
+ )
91
+ test_depth_dimensions: list[int] = (
92
+ [int(d) for d in s.split(",")]
93
+ if (s := os.environ.get("NK_MATRIX_DEPTH")) is not None
94
+ else sorted(random.Random(44).sample(dense_dimensions, _dim_sample_k))
95
+ )
96
+
97
+ reduced_repetitions_count: int = max(1, randomized_repetitions_count // 5)
98
+
99
+ test_curved_dimensions: list[int] = (
100
+ [int(d) for d in s.split(",")]
101
+ if (s := os.environ.get("NK_CURVED_DIMENSIONS")) is not None
102
+ else sorted(random.Random(45).sample(dense_dimensions, min(5, len(dense_dimensions))))
103
+ )
104
+
105
+ sparse_dimensions: list[int] = (
106
+ [int(d) for d in s.split(",")] if (s := os.environ.get("NK_SPARSE_DIMENSIONS")) is not None else [256]
107
+ )
108
+
109
+ mesh_points: int = int(s) if (s := os.environ.get("NK_MESH_POINTS")) is not None else 100
110
+
111
+ max_coord_angle: float = float(s) if (s := os.environ.get("NK_MAX_COORD_ANGLE")) is not None else 180.0
112
+
113
+ try:
114
+ import numpy as np
115
+
116
+ numpy_available = True
117
+ except Exception:
118
+ numpy_available = False
119
+
120
+ try:
121
+ import scipy.spatial.distance # noqa: F401
122
+
123
+ scipy_available = True
124
+ except ImportError:
125
+ scipy_available = False
126
+
127
+ try:
128
+ import ml_dtypes # noqa: F401
129
+
130
+ ml_dtypes_available = True
131
+ except ImportError:
132
+ ml_dtypes_available = False
133
+
134
+
135
+ NK_RTOL = 0.1
136
+ NK_ATOL = 0.1
137
+
138
+ # Map dtype → the NumPy dtype used for "native precision" baseline computation.
139
+ # f64 types compute at f64; everything else at f32 (for floats) or i64 (for ints).
140
+ NATIVE_COMPUTE_DTYPE: dict[str, type] = (
141
+ {
142
+ "float64": np.float64,
143
+ "float32": np.float32,
144
+ "float16": np.float32,
145
+ "bfloat16": np.float32,
146
+ "bf16": np.float32,
147
+ "e4m3": np.float32,
148
+ "e5m2": np.float32,
149
+ "e2m3": np.float32,
150
+ "e3m2": np.float32,
151
+ "int8": np.int64,
152
+ "uint8": np.int64,
153
+ "int16": np.int64,
154
+ "uint16": np.int64,
155
+ "int32": np.int64,
156
+ "uint32": np.int64,
157
+ "int64": np.int64,
158
+ "uint64": np.int64,
159
+ "int4": np.int64,
160
+ "uint4": np.int64,
161
+ "complex64": np.complex128,
162
+ "complex128": np.complex128,
163
+ }
164
+ if numpy_available
165
+ else {}
166
+ )
167
+
168
+
169
+ PACKING_GRANULARITY: dict[str, int] = {
170
+ "int4": 2,
171
+ "uint4": 2,
172
+ "uint1": 8,
173
+ }
174
+
175
+
176
+ def round_up_to(value: int, multiple: int) -> int:
177
+ """Round *value* up to the nearest multiple of *multiple*."""
178
+ return (value + multiple - 1) // multiple * multiple
179
+
180
+
181
+ _DTYPES_NEEDING_DECIMAL = {"float32", "float64", "complex64", "complex128"}
182
+
183
+
184
+ @contextlib.contextmanager
185
+ def precise_decimal(dtype: str | None = None) -> Generator[tuple[Callable, Callable, Callable], None, None]:
186
+ """Yield ``(upcast, sqrt, ln)`` helpers for high-precision baselines.
187
+
188
+ When *dtype* is a small type (float32, float16, int8, …) native ``float``
189
+ already exceeds its precision, so we skip the Decimal overhead. For
190
+ float64/complex128 we use 120-digit Decimal arithmetic.
191
+
192
+ Usage::
193
+
194
+ with precise_decimal("float32") as (upcast, sqrt, ln):
195
+ total = upcast(0)
196
+ for x, y in zip(a, b):
197
+ total += upcast(x) * upcast(y)
198
+ return float(sqrt(total))
199
+ """
200
+ if dtype is not None and dtype not in _DTYPES_NEEDING_DECIMAL:
201
+ yield float, math.sqrt, math.log
202
+ else:
203
+ ctx = decimal.Context(prec=120)
204
+ ctx.traps[decimal.InvalidOperation] = False
205
+ with decimal.localcontext(ctx):
206
+ yield decimal.Decimal.from_float, decimal.Decimal.sqrt, decimal.Decimal.ln
207
+
208
+
209
+ def is_running_under_qemu() -> bool:
210
+ return "NK_IN_QEMU" in os.environ
211
+
212
+
213
+ def profile(callable: Callable | None, *args: Any, **kwargs: Any) -> tuple[int, Any]:
214
+ if callable is None:
215
+ return 0, None
216
+ before = time.perf_counter_ns()
217
+ result = callable(*args, **kwargs)
218
+ after = time.perf_counter_ns()
219
+ return after - before, result
220
+
221
+
222
+ def scipy_metric_name(metric: str) -> str:
223
+ """Convert NumKong metric names to SciPy equivalents."""
224
+ if metric == "angular":
225
+ return "cosine"
226
+ return metric
227
+
228
+
229
+ def to_array(x: Any, dtype: str | None = None) -> np.ndarray:
230
+ if numpy_available:
231
+ y = np.array(x)
232
+ if dtype is not None:
233
+ y = y.astype(dtype)
234
+ return y
235
+
236
+
237
+ _DTYPE_TOLERANCES: dict[str, tuple[float, float]] = {
238
+ "float64": (1e-6, 1e-6),
239
+ "float32": (1e-4, 1e-4),
240
+ "float16": (NK_ATOL, NK_RTOL),
241
+ "bfloat16": (NK_ATOL, NK_RTOL),
242
+ "bf16": (NK_ATOL, NK_RTOL),
243
+ "e4m3": (NK_ATOL, NK_RTOL),
244
+ "e5m2": (NK_ATOL, NK_RTOL),
245
+ "e2m3": (NK_ATOL, NK_RTOL),
246
+ "e3m2": (NK_ATOL, NK_RTOL),
247
+ "complex128": (1e-6, 1e-6),
248
+ "complex64": (1e-4, 1e-4),
249
+ "int8": (1, 0),
250
+ "uint8": (1, 0),
251
+ "int16": (1, 0),
252
+ "uint16": (1, 0),
253
+ "int32": (1, 0),
254
+ "uint32": (1, 0),
255
+ "int64": (1, 0),
256
+ "uint64": (1, 0),
257
+ "int4": (1, 0),
258
+ "uint4": (1, 0),
259
+ }
260
+
261
+
262
+ def tolerances_for_dtype(dtype: str) -> tuple[float, float]:
263
+ """Returns ``(atol, rtol)`` appropriate for assertions on the given dtype."""
264
+ return _DTYPE_TOLERANCES.get(dtype, (NK_ATOL, NK_RTOL))
265
+
266
+
267
+ def random_of_dtype(dtype: str, shape: tuple[int, ...]) -> tuple[Any, Any]:
268
+ """Legacy helper — thin wrapper around :func:`make_random`."""
269
+ raw, _ = make_random(shape, dtype)
270
+ return raw
271
+
272
+
273
+ class LazyFormat:
274
+ """Deferred string formatting — only evaluated when str() is called (on assertion failure)."""
275
+
276
+ __slots__ = ("_fn",)
277
+
278
+ def __init__(self, fn):
279
+ self._fn = fn
280
+
281
+ def __str__(self):
282
+ return self._fn()
283
+
284
+
285
+ def f32_downcast_to_bf16(array: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
286
+ """Converts an array of 32-bit floats into 16-bit brain-floats.
287
+
288
+ Uses IEEE 754 round-to-nearest-even (banker's rounding) to match
289
+ ml_dtypes.bfloat16 behavior.
290
+ """
291
+ array = np.asarray(array, dtype=np.float32)
292
+ u32 = array.view(np.uint32)
293
+ lower = u32 & np.uint32(0xFFFF)
294
+ # For exact ties (lower 16 bits == 0x8000), round to even:
295
+ # only round up when the bf16 mantissa LSB (bit 16) is odd.
296
+ is_tie = lower == np.uint32(0x8000)
297
+ lsb = (u32 >> np.uint32(16)) & np.uint32(1)
298
+ adjustment = np.where(is_tie, lsb << np.uint32(15), np.uint32(0x8000))
299
+ rounded_u32 = (u32 + adjustment) & np.uint32(0xFFFF0000)
300
+ array_f32_rounded = rounded_u32.view(np.float32)
301
+ array_bf16 = np.right_shift(rounded_u32, 16).astype(np.uint16)
302
+ return array_f32_rounded, array_bf16
303
+
304
+
305
+ def _pack_nibbles(array):
306
+ """Pack pairs of nibbles along the last axis, preserving leading dimensions.
307
+
308
+ Each pair of consecutive elements is packed into one byte:
309
+ low nibble = even index, high nibble = odd index.
310
+ Odd-length rows are zero-padded. Returns a uint8 array.
311
+ """
312
+ shape = array.shape
313
+ if array.ndim >= 2:
314
+ rows = array.reshape(-1, shape[-1])
315
+ cols = shape[-1]
316
+ packed_cols = (cols + 1) // 2
317
+ if cols % 2:
318
+ rows = np.concatenate([rows, np.zeros((rows.shape[0], 1), dtype=np.uint8)], axis=1)
319
+ low = rows[:, 0::2].astype(np.uint8) & 0x0F
320
+ high = (rows[:, 1::2].astype(np.uint8) & 0x0F) << 4
321
+ packed = (low | high).astype(np.uint8)
322
+ return packed.reshape(*shape[:-1], packed_cols)
323
+ else:
324
+ flat = array.ravel()
325
+ if len(flat) % 2:
326
+ flat = np.append(flat, np.uint8(0))
327
+ low = flat[0::2].astype(np.uint8) & 0x0F
328
+ high = (flat[1::2].astype(np.uint8) & 0x0F) << 4
329
+ return (low | high).astype(np.uint8)
330
+
331
+
332
+ def i8_downcast_to_i4(array):
333
+ """Pack signed 8-bit integers into signed 4-bit pairs (2 per byte).
334
+
335
+ Layout matches C ``nk_i4x2_t``: low nibble = even index, high nibble = odd index.
336
+ Input values must be in [-8, 7]. Preserves leading dimensions for 2-D+ inputs.
337
+ """
338
+ array = np.asarray(array, dtype=np.int8)
339
+ assert np.all(array >= -8) and np.all(array <= 7), "values must be in [-8, 7]"
340
+ return _pack_nibbles(array)
341
+
342
+
343
+ def u8_downcast_to_u4(array):
344
+ """Pack unsigned 8-bit integers into unsigned 4-bit pairs (2 per byte).
345
+
346
+ Layout matches C ``nk_u4x2_t``: low nibble = even index, high nibble = odd index.
347
+ Input values must be in [0, 15]. Preserves leading dimensions for 2-D+ inputs.
348
+ """
349
+ array = np.asarray(array, dtype=np.uint8)
350
+ assert np.all(array <= 15), "values must be in [0, 15]"
351
+ return _pack_nibbles(array)
352
+
353
+
354
+ def hex_array(arr: Any) -> str:
355
+ """Converts numerical array into a string of comma-separated hexadecimal values for debugging."""
356
+ arr = np.asarray(arr)
357
+ if not np.issubdtype(arr.dtype, np.integer):
358
+ # View non-integer data as raw bytes for hex display
359
+ shape = arr.shape
360
+ arr = arr.view(np.uint8).reshape((*shape, -1))
361
+ printer = np.vectorize(hex)
362
+ strings = printer(arr)
363
+ if strings.ndim == 1:
364
+ return ", ".join(strings)
365
+ else:
366
+ return "\n".join(", ".join(row) for row in strings.reshape(-1, strings.shape[-1]))
367
+
368
+
369
+ # Lookup tables for sub-byte float types.
370
+ # Built once from the encoding rules in ``include/numkong/cast/serial.h``.
371
+ # Each table maps a raw byte value to its float64 representation.
372
+ # NaN entries are stored as ``float('nan')``.
373
+
374
+
375
+ def build_subbyte_float_lookup_table(
376
+ sign_bit, exp_bits, mant_bits, bias, total_bits, has_inf=False, nan_only_max_mant=False
377
+ ):
378
+ """Build a byte→float64 lookup table for a sub-byte float format.
379
+
380
+ Args:
381
+ sign_bit: position of the sign bit (counting from bit 0)
382
+ exp_bits: number of exponent bits
383
+ mant_bits: number of mantissa bits
384
+ bias: exponent bias
385
+ total_bits: number of significant bits (6 for float6, 8 for float8)
386
+ has_inf: if True, max exponent with zero mantissa = ±∞
387
+ nan_only_max_mant: if True, only max_exp + max_mant is NaN (e4m3 rule)
388
+ """
389
+ n = 1 << total_bits
390
+ exp_mask = (1 << exp_bits) - 1
391
+ mant_mask = (1 << mant_bits) - 1
392
+ max_exp = exp_mask
393
+ max_mant = mant_mask
394
+ lut = [0.0] * n
395
+ for i in range(n):
396
+ sign = (i >> sign_bit) & 1
397
+ exp = (i >> mant_bits) & exp_mask
398
+ mant = i & mant_mask
399
+ if exp == 0:
400
+ # Subnormal: value = (−1)^s × 2^(1−bias) × (mant / 2^mant_bits)
401
+ val = (mant / (1 << mant_bits)) * (2.0 ** (1 - bias))
402
+ elif exp == max_exp:
403
+ if has_inf and mant == 0:
404
+ lut[i] = -float("inf") if sign else float("inf")
405
+ continue
406
+ if has_inf and mant != 0:
407
+ lut[i] = float("nan")
408
+ continue
409
+ if nan_only_max_mant and mant == max_mant:
410
+ lut[i] = float("nan")
411
+ continue
412
+ # Finite max-exponent value
413
+ val = 2.0 ** (exp - bias) * (1.0 + mant / (1 << mant_bits))
414
+ else:
415
+ val = 2.0 ** (exp - bias) * (1.0 + mant / (1 << mant_bits))
416
+ lut[i] = -val if sign else val
417
+ return lut
418
+
419
+
420
+ LOOKUP_TABLE_E2M3 = build_subbyte_float_lookup_table(sign_bit=5, exp_bits=2, mant_bits=3, bias=1, total_bits=6)
421
+ LOOKUP_TABLE_E3M2 = build_subbyte_float_lookup_table(sign_bit=5, exp_bits=3, mant_bits=2, bias=3, total_bits=6)
422
+ LOOKUP_TABLE_E4M3 = build_subbyte_float_lookup_table(
423
+ sign_bit=7, exp_bits=4, mant_bits=3, bias=7, total_bits=8, nan_only_max_mant=True
424
+ )
425
+ LOOKUP_TABLE_E5M2 = build_subbyte_float_lookup_table(
426
+ sign_bit=7, exp_bits=5, mant_bits=2, bias=15, total_bits=8, has_inf=True
427
+ )
428
+
429
+ SUBBYTE_LOOKUP_TABLES = {
430
+ "e2m3": LOOKUP_TABLE_E2M3,
431
+ "e3m2": LOOKUP_TABLE_E3M2,
432
+ "e4m3": LOOKUP_TABLE_E4M3,
433
+ "e5m2": LOOKUP_TABLE_E5M2,
434
+ }
435
+
436
+
437
+ def _make_random_numpy(shape, dtype):
438
+ """NumPy-backed random-data factory (original implementation)."""
439
+ if dtype in ("float64", "float32", "float16"):
440
+ raw = np.random.randn(*shape).astype(dtype)
441
+ baseline = raw.astype(np.float64)
442
+ return raw, baseline
443
+
444
+ if dtype in ("bfloat16", "bf16"):
445
+ f32_arr = np.random.randn(*shape).astype(np.float32)
446
+ f32_rounded, bf16_raw = f32_downcast_to_bf16(f32_arr)
447
+ baseline = f32_rounded.astype(np.float64)
448
+ return bf16_raw, baseline
449
+
450
+ if dtype in ("int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", "uint64"):
451
+ info = np.iinfo(np.dtype(dtype))
452
+ raw = np.random.randint(info.min, info.max, size=shape, dtype=dtype)
453
+ baseline = raw.astype(np.float64)
454
+ return raw, baseline
455
+
456
+ if dtype in ("complex64", "complex128"):
457
+ raw = (np.random.randn(*shape) + 1j * np.random.randn(*shape)).astype(dtype)
458
+ baseline = raw.astype(np.complex128)
459
+ return raw, baseline
460
+
461
+ if dtype in ("e4m3", "e5m2", "e2m3", "e3m2"):
462
+ lut = np.array(SUBBYTE_LOOKUP_TABLES[dtype])
463
+ # Exclude NaN/±∞ entries from random generation
464
+ finite_mask = np.isfinite(lut)
465
+ valid_bytes = np.where(finite_mask)[0].astype(np.uint8)
466
+ # For types whose max representable value can cause FP32 accumulation
467
+ # errors exceeding NK_ATOL (catastrophic cancellation), restrict to
468
+ # values whose magnitude keeps products within FP32's reliable range.
469
+ # Threshold: T² * eps_f32 < NK_ATOL/4 => T = floor(sqrt(NK_ATOL / (4 * eps_f32))) ~ 458
470
+ # Only e5m2 (max 57344) is affected; e4m3/e3m2/e2m3 are within bounds.
471
+ eps32 = np.finfo(np.float32).eps
472
+ mag_threshold = np.floor(np.sqrt(NK_ATOL / (4 * eps32)))
473
+ decoded = lut[valid_bytes.astype(int)]
474
+ magnitude_ok = np.abs(decoded) <= mag_threshold
475
+ if not np.all(magnitude_ok):
476
+ valid_bytes = valid_bytes[magnitude_ok]
477
+ raw = valid_bytes[np.random.randint(0, len(valid_bytes), size=shape)]
478
+ baseline = lut[raw.astype(int)]
479
+ return raw, baseline
480
+
481
+ if dtype == "int4":
482
+ values = np.random.randint(-8, 8, size=shape, dtype=np.int8)
483
+ baseline = values.astype(np.float64)
484
+ raw = i8_downcast_to_i4(values)
485
+ return raw, baseline
486
+
487
+ if dtype == "uint4":
488
+ values = np.random.randint(0, 16, size=shape, dtype=np.uint8)
489
+ baseline = values.astype(np.float64)
490
+ raw = u8_downcast_to_u4(values)
491
+ return raw, baseline
492
+
493
+ raise ValueError(f"Unsupported dtype for make_random: {dtype}")
494
+
495
+
496
+ def _make_random_fallback(shape, dtype, seed):
497
+ """NumPy-free random-data factory using ``nk.iota``.
498
+
499
+ Returns ``(nk_tensor, list_of_floats)`` — the list serves as the
500
+ baseline for Decimal-based precise functions.
501
+
502
+ Values are centered around zero and scaled to approximately [-3, 3]
503
+ (matching ``np.random.randn`` magnitude) to avoid overflow in
504
+ low-precision types.
505
+ """
506
+ n = 1
507
+ for d in shape:
508
+ n *= d
509
+ # Build centered iota: values from -n//2 to +n//2
510
+ raw = nk.iota(shape, seed=seed - n // 2, dtype=dtype)
511
+ if n > 6:
512
+ # Scale down to [-3, 3] range to match np.random.randn magnitude.
513
+ # This prevents overflow in product ops for float16/bf16.
514
+ scale_factor = 6.0 / n
515
+ raw = nk.scale(raw, alpha=scale_factor, beta=0.0)
516
+ baseline = [float(x) for x in raw.flatten()]
517
+ return raw, baseline
518
+
519
+
520
+ def make_random(shape: int | tuple[int, ...], dtype: str, seed: int = 0) -> tuple[Any, Any]:
521
+ """Unified random-data factory.
522
+
523
+ Returns ``(raw, baseline)`` where:
524
+
525
+ - *raw*: data in the dtype's storage format, suitable for SIMD kernels.
526
+ - *baseline*: ``float64`` (or ``complex128``) array for reference comparison.
527
+ When NumPy is available this is a NumPy array; otherwise a plain ``list[float]``.
528
+
529
+ For exotic types the raw array uses a NumPy-native storage dtype
530
+ (``uint16`` for bf16, ``uint8`` for float8/float6).
531
+ """
532
+ if isinstance(shape, int):
533
+ shape = (shape,)
534
+
535
+ if numpy_available:
536
+ return _make_random_numpy(shape, dtype)
537
+
538
+ return _make_random_fallback(shape, dtype, seed)
539
+
540
+
541
+ def make_nk(np_arr: np.ndarray, dtype: str | None = None) -> nk.Tensor:
542
+ """Copy a NumPy array into a NumKong tensor."""
543
+ if dtype is None:
544
+ dtype = str(np_arr.dtype)
545
+ nk_arr = nk.zeros(np_arr.shape, dtype=dtype)
546
+ dst = np.asarray(nk_arr)
547
+ src = np.ascontiguousarray(np_arr)
548
+ if dst.dtype != src.dtype:
549
+ src = src.view(np.uint8).reshape(dst.shape)
550
+ np.copyto(dst, src)
551
+ return nk_arr
552
+
553
+
554
+ def downcast_f32_to_dtype(f32_arr: np.ndarray, dtype: str) -> tuple[np.ndarray, np.ndarray]:
555
+ """Downcast an f32 array to *dtype*, returning (raw, f64_baseline).
556
+
557
+ For native NumPy dtypes (float16/32/64, int*), casts directly.
558
+ For bfloat16, uses round-to-nearest bf16 truncation.
559
+ The baseline is always derived from the *actually stored* values
560
+ (post-quantization), not the original f32.
561
+ """
562
+ if dtype in ("bfloat16", "bf16"):
563
+ f32_rounded, raw = f32_downcast_to_bf16(f32_arr)
564
+ return raw, f32_rounded.astype(np.float64)
565
+ raw = f32_arr.astype(dtype)
566
+ return raw, raw.astype(np.float64)
567
+
568
+
569
+ available_capabilities: dict[str, str] = nk.get_capabilities()
570
+
571
+ # fmt: off
572
+ possible_x86_capabilities: list[str] = [
573
+ "haswell", "alder", "sierra",
574
+ "skylake", "icelake", "genoa", "turin", "sapphire",
575
+ "sapphireamx", "graniteamx", "diamond",
576
+ ]
577
+ possible_arm_capabilities: list[str] = [
578
+ "neon", "neonhalf", "neonfhm", "neonbfdot", "neonsdot", "neonfp8",
579
+ "sve", "svehalf", "svebfdot", "svesdot", "sve2", "sve2p1",
580
+ "sme", "sme2", "sme2p1", "smef64", "smehalf", "smebf16", "smebi32", "smelut2", "smefa64",
581
+ ]
582
+ possible_rvv_capabilities: list[str] = ["rvv", "rvvhalf", "rvvbf16", "rvvbb"]
583
+ possible_loongarch_capabilities: list[str] = ["loongsonasx"]
584
+ possible_power_capabilities: list[str] = ["powervsx"]
585
+ possible_wasm_capabilities: list[str] = ["v128relaxed"]
586
+ # fmt: on
587
+
588
+ possible_x86_capabilities = [c for c in possible_x86_capabilities if available_capabilities.get(c, False)]
589
+ possible_arm_capabilities = [c for c in possible_arm_capabilities if available_capabilities.get(c, False)]
590
+ possible_rvv_capabilities = [c for c in possible_rvv_capabilities if available_capabilities.get(c, False)]
591
+ possible_loongarch_capabilities = [c for c in possible_loongarch_capabilities if available_capabilities.get(c, False)]
592
+ possible_power_capabilities = [c for c in possible_power_capabilities if available_capabilities.get(c, False)]
593
+ possible_wasm_capabilities = [c for c in possible_wasm_capabilities if available_capabilities.get(c, False)]
594
+
595
+ hardware_capabilities: list[str] = []
596
+ machine_architecture = platform.machine()
597
+
598
+ if sys.platform == "linux":
599
+ if machine_architecture == "x86_64":
600
+ hardware_capabilities = possible_x86_capabilities
601
+ elif machine_architecture == "aarch64":
602
+ hardware_capabilities = possible_arm_capabilities
603
+ elif machine_architecture == "riscv64":
604
+ hardware_capabilities = possible_rvv_capabilities
605
+ elif machine_architecture == "loongarch64":
606
+ hardware_capabilities = possible_loongarch_capabilities
607
+ elif machine_architecture in ("ppc64le", "ppc64"):
608
+ hardware_capabilities = possible_power_capabilities
609
+ elif sys.platform == "darwin":
610
+ if machine_architecture == "x86_64":
611
+ hardware_capabilities = possible_x86_capabilities
612
+ elif machine_architecture == "arm64":
613
+ hardware_capabilities = possible_arm_capabilities
614
+ elif sys.platform == "win32":
615
+ if machine_architecture == "AMD64":
616
+ hardware_capabilities = possible_x86_capabilities
617
+ elif machine_architecture == "ARM64":
618
+ hardware_capabilities = possible_arm_capabilities
619
+ elif sys.platform.startswith("freebsd"):
620
+ if machine_architecture == "amd64":
621
+ hardware_capabilities = possible_x86_capabilities
622
+ elif machine_architecture == "arm64":
623
+ hardware_capabilities = possible_arm_capabilities
624
+ elif sys.platform in ("emscripten", "wasi"):
625
+ hardware_capabilities = possible_wasm_capabilities
626
+
627
+ possible_capabilities: list[str] = ["serial", *hardware_capabilities]
628
+
629
+ current_capability: str | None = None
630
+
631
+
632
+ def keep_one_capability(cap: str):
633
+ global current_capability
634
+ assert cap in possible_capabilities, f"Capability {cap} is not available on this platform."
635
+ if cap == current_capability:
636
+ return
637
+ for c in possible_capabilities:
638
+ if c != cap and c != "serial":
639
+ nk.disable_capability(c)
640
+ if cap != "serial":
641
+ nk.enable_capability(cap)
642
+ current_capability = cap
643
+
644
+
645
+ def create_stats() -> dict[str, list]:
646
+ """Create a fresh stats dict for error collection."""
647
+ return {
648
+ "metric": [],
649
+ "ndim": [],
650
+ "dtype": [],
651
+ "capability": [],
652
+ "absolute_baseline_error": [],
653
+ "relative_baseline_error": [],
654
+ "absolute_nk_error": [],
655
+ "relative_nk_error": [],
656
+ "accurate_duration": [],
657
+ "baseline_duration": [],
658
+ "nk_duration": [],
659
+ "warnings": [],
660
+ }
661
+
662
+
663
+ _SENTINEL = object()
664
+
665
+
666
+ def _infer_dtype_name(value) -> str:
667
+ """Extract a dtype name string from a NumPy array, nk.Tensor, or scalar."""
668
+ if hasattr(value, "dtype"):
669
+ dt = value.dtype
670
+ if hasattr(dt, "name"):
671
+ return dt.name
672
+ return str(dt)
673
+ if isinstance(value, int):
674
+ return "int64"
675
+ if isinstance(value, float):
676
+ return "float64"
677
+ if numpy_available:
678
+ arr = np.asarray(value)
679
+ return arr.dtype.name
680
+ return ""
681
+
682
+
683
+ def assert_allclose(
684
+ actual: Any, expected: Any, atol: object = _SENTINEL, rtol: object = _SENTINEL, err_msg: str = ""
685
+ ) -> None:
686
+ """Drop-in replacement for ``np.testing.assert_allclose`` with dtype-aware defaults.
687
+
688
+ When both *atol* and *rtol* are omitted the tolerances are inferred from
689
+ the dtype of *actual* via :func:`tolerances_for_dtype`.
690
+ """
691
+ if atol is _SENTINEL and rtol is _SENTINEL:
692
+ dtype_name = _infer_dtype_name(actual)
693
+ atol, rtol = tolerances_for_dtype(dtype_name)
694
+ else:
695
+ if atol is _SENTINEL:
696
+ atol = 0
697
+ if rtol is _SENTINEL:
698
+ rtol = 1e-7
699
+ if numpy_available:
700
+ a_arr = np.asarray(actual)
701
+ e_arr = np.asarray(expected)
702
+ if not np.issubdtype(a_arr.dtype, np.complexfloating):
703
+ a_arr = a_arr.astype(float)
704
+ if not np.issubdtype(e_arr.dtype, np.complexfloating):
705
+ e_arr = e_arr.astype(float)
706
+ np.testing.assert_allclose(
707
+ a_arr,
708
+ e_arr,
709
+ atol=atol,
710
+ rtol=rtol,
711
+ err_msg=err_msg,
712
+ )
713
+ return
714
+ # Scalar path
715
+ if not hasattr(actual, "__len__") and not hasattr(expected, "__len__"):
716
+ a, e = float(actual), float(expected)
717
+ if abs(a - e) > atol + rtol * abs(e):
718
+ raise AssertionError(f"Not close: {a} vs {e}. {err_msg}")
719
+ return
720
+ # Iterable path
721
+ if hasattr(actual, "flatten"):
722
+ actual = actual.flatten()
723
+ if hasattr(expected, "flatten"):
724
+ expected = expected.flatten()
725
+ for i, (ai, ei) in enumerate(zip(actual, expected)):
726
+ ai_f, ei_f = float(ai), float(ei)
727
+ if abs(ai_f - ei_f) > atol + rtol * abs(ei_f):
728
+ raise AssertionError(f"Element {i}: {ai_f} vs {ei_f}. {err_msg}")
729
+
730
+
731
+ def _compute_errors_numpy(accurate_result, baseline_result, nk_result):
732
+ """Compute error metrics using NumPy."""
733
+ accurate_result = np.asarray(accurate_result)
734
+ eps = np.finfo(accurate_result.dtype).resolution if np.issubdtype(accurate_result.dtype, np.inexact) else 1.0
735
+ if baseline_result is None:
736
+ abs_bl, rel_bl = float("nan"), float("nan")
737
+ else:
738
+ abs_bl = float(np.max(np.abs(baseline_result - accurate_result)))
739
+ rel_bl = float(np.max(np.abs(baseline_result - accurate_result) / (np.abs(accurate_result) + eps)))
740
+ abs_nk = float(np.max(np.abs(nk_result - accurate_result)))
741
+ rel_nk = float(np.max(np.abs(nk_result - accurate_result) / (np.abs(accurate_result) + eps)))
742
+ return abs_bl, rel_bl, abs_nk, rel_nk
743
+
744
+
745
+ def _compute_errors_fallback(accurate_result, baseline_result, nk_result):
746
+ """Compute error metrics using pure Python."""
747
+ eps = 1e-15
748
+ accurate_f = float(accurate_result) if not hasattr(accurate_result, "__len__") else None
749
+ if accurate_f is not None:
750
+ nk_f = float(nk_result)
751
+ abs_nk = abs(nk_f - accurate_f)
752
+ rel_nk = abs(nk_f - accurate_f) / (abs(accurate_f) + eps)
753
+ else:
754
+ acc_flat = list(accurate_result.flatten()) if hasattr(accurate_result, "flatten") else list(accurate_result)
755
+ nk_flat = list(nk_result.flatten()) if hasattr(nk_result, "flatten") else list(nk_result)
756
+ abs_errs = [abs(float(a) - float(b)) for a, b in zip(nk_flat, acc_flat)]
757
+ rel_errs = [abs(float(a) - float(b)) / (abs(float(b)) + eps) for a, b in zip(nk_flat, acc_flat)]
758
+ abs_nk = max(abs_errs)
759
+ rel_nk = max(rel_errs)
760
+ if baseline_result is None:
761
+ abs_bl, rel_bl = float("nan"), float("nan")
762
+ else:
763
+ bl_f = float(baseline_result) if not hasattr(baseline_result, "__len__") else None
764
+ if bl_f is not None:
765
+ abs_bl = abs(bl_f - float(accurate_result))
766
+ rel_bl = abs(bl_f - float(accurate_result)) / (abs(float(accurate_result)) + eps)
767
+ else:
768
+ acc_flat = list(accurate_result.flatten()) if hasattr(accurate_result, "flatten") else list(accurate_result)
769
+ bl_flat = list(baseline_result.flatten()) if hasattr(baseline_result, "flatten") else list(baseline_result)
770
+ abs_errs = [abs(float(a) - float(b)) for a, b in zip(bl_flat, acc_flat)]
771
+ rel_errs = [abs(float(a) - float(b)) / (abs(float(b)) + eps) for a, b in zip(bl_flat, acc_flat)]
772
+ abs_bl = max(abs_errs)
773
+ rel_bl = max(rel_errs)
774
+ return abs_bl, rel_bl, abs_nk, rel_nk
775
+
776
+
777
+ def collect_errors(
778
+ metric: str,
779
+ ndim: int,
780
+ dtype: str,
781
+ accurate_result: float,
782
+ accurate_duration: float,
783
+ baseline_result: float,
784
+ baseline_duration: float,
785
+ nk_result: float,
786
+ nk_duration: float,
787
+ stats: dict[str, list],
788
+ ) -> None:
789
+ """Calculates and aggregates errors for a given test."""
790
+ if numpy_available:
791
+ abs_bl, rel_bl, abs_nk, rel_nk = _compute_errors_numpy(accurate_result, baseline_result, nk_result)
792
+ else:
793
+ abs_bl, rel_bl, abs_nk, rel_nk = _compute_errors_fallback(accurate_result, baseline_result, nk_result)
794
+
795
+ stats["metric"].append(metric)
796
+ stats["ndim"].append(ndim)
797
+ stats["dtype"].append(dtype)
798
+ stats["capability"].append(current_capability or "unknown")
799
+ stats["absolute_baseline_error"].append(abs_bl)
800
+ stats["relative_baseline_error"].append(rel_bl)
801
+ stats["absolute_nk_error"].append(abs_nk)
802
+ stats["relative_nk_error"].append(rel_nk)
803
+ stats["accurate_duration"].append(accurate_duration)
804
+ stats["baseline_duration"].append(baseline_duration)
805
+ stats["nk_duration"].append(nk_duration)
806
+
807
+
808
+ def collect_warnings(message: str, stats: dict[str, list]) -> None:
809
+ """Collects warnings for the final report."""
810
+ full_name = os.environ.get("PYTEST_CURRENT_TEST", "unknown::unknown").split(" ")[0]
811
+ function_name = full_name.split("::")[-1].split("[")[0]
812
+ stats["warnings"].append((function_name, message))
813
+
814
+
815
+ def format_scientific(value):
816
+ """Format a float as compact scientific notation (e.g. 7.4e-5). Return '0' for exact zero."""
817
+ if value == 0:
818
+ return "0"
819
+ s = f"{value:.1e}"
820
+ if "e" not in s:
821
+ return s # inf, nan, etc.
822
+ mantissa, exp = s.split("e")
823
+ exp_sign = exp[0]
824
+ exp_digits = exp[1:].lstrip("0") or "0"
825
+ return f"{mantissa}e{exp_sign}{exp_digits}"
826
+
827
+
828
+ def pad_with_ansi_color(visible, width, code):
829
+ """Pad visible string to width first, then wrap in ANSI so escape codes don't break alignment."""
830
+ padded = f"{visible:<{width}}"
831
+ return f"\033[{code}m{padded}\033[0m"
832
+
833
+
834
+ CapabilityRecord = collections.namedtuple(
835
+ "CapabilityRecord",
836
+ ["metric", "ndim", "dtype", "capability", "baseline_error_mean", "nk_error_mean", "speedup_mean"],
837
+ )
838
+
839
+
840
+ def print_stats_report(stats: dict[str, list]) -> None:
841
+ """Print a condensed error/speedup report: two rows per (metric, dtype) showing min/max ndim."""
842
+ if not stats["metric"]:
843
+ return
844
+
845
+ # Stage 1: Group raw stats by (metric, ndim, dtype, capability) and compute per-group means.
846
+ grouped = collections.defaultdict(
847
+ lambda: {"relative_baseline_error": [], "relative_nk_error": [], "baseline_duration": [], "nk_duration": []}
848
+ )
849
+ for metric, ndim, dtype, capability, rel_base, rel_nk, base_dur, nk_dur in zip(
850
+ stats["metric"],
851
+ stats["ndim"],
852
+ stats["dtype"],
853
+ stats["capability"],
854
+ stats["relative_baseline_error"],
855
+ stats["relative_nk_error"],
856
+ stats["baseline_duration"],
857
+ stats["nk_duration"],
858
+ ):
859
+ key = (metric, ndim, dtype, capability)
860
+ grouped[key]["relative_baseline_error"].append(rel_base)
861
+ grouped[key]["relative_nk_error"].append(rel_nk)
862
+ grouped[key]["baseline_duration"].append(base_dur)
863
+ grouped[key]["nk_duration"].append(nk_dur)
864
+
865
+ cap_records = []
866
+ for (metric, ndim, dtype, capability), vals in grouped.items():
867
+ n = len(vals["nk_duration"])
868
+ base_err_mean = sum(vals["relative_baseline_error"]) / n
869
+ nk_err_mean = sum(vals["relative_nk_error"]) / n
870
+ speedups = [b / nk for b, nk in zip(vals["baseline_duration"], vals["nk_duration"]) if nk > 0]
871
+ speedup_mean = sum(speedups) / len(speedups) if speedups else 0.0
872
+ cap_records.append(CapabilityRecord(metric, ndim, dtype, capability, base_err_mean, nk_err_mean, speedup_mean))
873
+
874
+ # Stage 2: Re-aggregate by (metric, dtype). For each, find min/max ndim and cross-capability aggregates.
875
+ by_metric_dtype = collections.defaultdict(list)
876
+ for rec in cap_records:
877
+ by_metric_dtype[(rec.metric, rec.dtype)].append(rec)
878
+
879
+ # Each output row: (metric_str, dtype_str, ndim_str, base_err_str, worst_nk_err_str, best_speedup_str)
880
+ rows = []
881
+ for (metric, dtype), recs in sorted(by_metric_dtype.items()):
882
+ all_ndims = sorted(set(r.ndim for r in recs))
883
+ min_ndim, max_ndim = all_ndims[0], all_ndims[-1]
884
+ single_ndim = min_ndim == max_ndim
885
+ target_ndims = [min_ndim] if single_ndim else [min_ndim, max_ndim]
886
+
887
+ for i, target_ndim in enumerate(target_ndims):
888
+ subset = [r for r in recs if r.ndim == target_ndim]
889
+ if not subset:
890
+ continue
891
+
892
+ # Base error: average across capabilities
893
+ base_err = sum(r.baseline_error_mean for r in subset) / len(subset)
894
+ # Worst NK error: capability with highest mean NK error
895
+ worst_rec = max(subset, key=lambda r: r.nk_error_mean)
896
+ worst_nk_err = worst_rec.nk_error_mean
897
+ worst_cap = worst_rec.capability
898
+ # Best speedup: capability with highest mean speedup
899
+ best_rec = max(subset, key=lambda r: r.speedup_mean)
900
+ best_speedup = best_rec.speedup_mean
901
+ best_cap = best_rec.capability
902
+ best_cap_err = best_rec.nk_error_mean
903
+
904
+ # Format strings
905
+ if i == 0:
906
+ metric_str = metric
907
+ dtype_str = dtype
908
+ else:
909
+ metric_str = ""
910
+ dtype_str = ""
911
+
912
+ if single_ndim:
913
+ ndim_str = str(target_ndim)
914
+ elif i == 0:
915
+ ndim_str = f"\u230a{target_ndim:>4}\u230b"
916
+ else:
917
+ ndim_str = f"\u2308{target_ndim:>4}\u2309"
918
+
919
+ rows.append(
920
+ (
921
+ metric_str,
922
+ dtype_str,
923
+ ndim_str,
924
+ base_err,
925
+ worst_nk_err,
926
+ worst_cap,
927
+ best_speedup,
928
+ best_cap,
929
+ best_cap_err,
930
+ )
931
+ )
932
+
933
+ # Stage 3: Render
934
+ col_w = {"kernel": 17, "dtype": 12, "ndim": 8, "base_err": 14, "worst_nk": 30, "best_spd": 34}
935
+ header = (
936
+ f"{'Kernel':<{col_w['kernel']}}"
937
+ f"{'DType':<{col_w['dtype']}}"
938
+ f"{'NDim':<{col_w['ndim']}}"
939
+ f"{'Base Error':<{col_w['base_err']}}"
940
+ f"{'Worst NK Error':<{col_w['worst_nk']}}"
941
+ f"{'Best NK Speedup':<{col_w['best_spd']}}"
942
+ )
943
+ sep = "\u2500" * len(header)
944
+ print(f"\n\n{header}")
945
+ print(sep)
946
+
947
+ for (
948
+ metric_str,
949
+ dtype_str,
950
+ ndim_str,
951
+ base_err,
952
+ worst_nk_err,
953
+ worst_cap,
954
+ best_speedup,
955
+ best_cap,
956
+ best_cap_err,
957
+ ) in rows:
958
+ base_err_s = format_scientific(base_err)
959
+ worst_nk_s = f"{format_scientific(worst_nk_err)} \u2039{worst_cap}\u203a"
960
+ best_spd_s = f"{best_speedup:.1f}x \u2039{best_cap}, err {format_scientific(best_cap_err)}\u203a"
961
+
962
+ # Color for worst NK error: red if NK error > base error
963
+ nk_err_code = "31" if worst_nk_err > base_err else "0"
964
+ # Color for speedup: green >=2x, yellow 1-2x, red <1x
965
+ if best_speedup >= 2.0:
966
+ spd_code = "32"
967
+ elif best_speedup >= 1.0:
968
+ spd_code = "33"
969
+ else:
970
+ spd_code = "31"
971
+
972
+ line = (
973
+ f"{metric_str:<{col_w['kernel']}}"
974
+ f"{dtype_str:<{col_w['dtype']}}"
975
+ f"{ndim_str:<{col_w['ndim']}}"
976
+ f"{base_err_s:<{col_w['base_err']}}"
977
+ f"{pad_with_ansi_color(worst_nk_s, col_w['worst_nk'], nk_err_code)}"
978
+ f"{pad_with_ansi_color(best_spd_s, col_w['best_spd'], spd_code)}"
979
+ )
980
+ print(line)
981
+
982
+ warnings_list = stats.get("warnings", [])
983
+ warnings_list = sorted(warnings_list)
984
+ warnings_list = [f"{name}: {message}" for name, message in warnings_list]
985
+ if len(warnings_list) != 0:
986
+ print("\nWarnings:")
987
+ warning_counts = collections.Counter(warnings_list)
988
+ for warning, count in sorted(warning_counts.items()):
989
+ print(f"- {count}x times: {warning}")
990
+
991
+
992
+ @pytest.fixture(autouse=True)
993
+ def seed_rng(__pytest_repeat_step_number: int) -> int:
994
+ """Auto-seed NumPy RNG before every test and return the computed seed.
995
+
996
+ When NK_SEED is set, each @pytest.mark.repeat() step gets a unique
997
+ derived seed. Tests that use ``nk.hash`` can accept this fixture as a
998
+ parameter and pass the return value as the ``seed=`` argument so that
999
+ repeated runs exercise different data.
1000
+ """
1001
+ step = __pytest_repeat_step_number or 0
1002
+ seed = (_nk_seed_base or 0) + step
1003
+ if numpy_available and _nk_seed_base is not None:
1004
+ np.random.seed(seed)
1005
+ return seed
1006
+
1007
+
1008
+ @pytest.fixture
1009
+ def nk_seed(seed_rng: int) -> int:
1010
+ """Per-test seed that incorporates the repeat step number.
1011
+
1012
+ Wraps *seed_rng* under the name ``nk_seed`` so tests that build data
1013
+ with ``nk.hash`` / ``nk.iota`` can request it by name.
1014
+ """
1015
+ return seed_rng
1016
+
1017
+
1018
+ # Map nk dtype → (array.array typecode, low, high)
1019
+ ARRAY_TYPECODES = {
1020
+ "float32": ("f", -10.0, 10.0),
1021
+ "float64": ("d", -10.0, 10.0),
1022
+ "int8": ("b", -128, 127),
1023
+ "uint8": ("B", 0, 255),
1024
+ }
1025
+
1026
+
1027
+ def make_random_buffer(n: int, dtype: str = "float32") -> array.array:
1028
+ """Create a random array.array for the given dtype — no numpy needed."""
1029
+ tc, lo, hi = ARRAY_TYPECODES[dtype]
1030
+ if tc in ("f", "d"):
1031
+ return array.array(tc, [random.uniform(lo, hi) for _ in range(n)])
1032
+ else:
1033
+ return array.array(tc, [random.randint(int(lo), int(hi)) for _ in range(n)])
1034
+
1035
+
1036
+ def make_positive_buffer(n: int, dtype: str = "float32") -> array.array:
1037
+ """Create a random positive array.array — for probability distributions."""
1038
+ tc = "f" if dtype == "float32" else "d"
1039
+ vals = [random.uniform(0.01, 1.0) for _ in range(n)]
1040
+ total = sum(vals)
1041
+ return array.array(tc, [v / total for v in vals])