usearch 2.21.4 → 2.25.0

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 (602) hide show
  1. package/README.md +24 -24
  2. package/binding.gyp +15 -37
  3. package/c/CMakeLists.txt +4 -5
  4. package/c/README.md +7 -7
  5. package/c/lib.cpp +32 -14
  6. package/c/test.c +83 -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/dist/cjs/usearch.d.ts +8 -3
  16. package/javascript/dist/cjs/usearch.js +12 -4
  17. package/javascript/dist/esm/usearch.d.ts +8 -3
  18. package/javascript/dist/esm/usearch.js +12 -4
  19. package/javascript/lib.cpp +17 -0
  20. package/javascript/usearch.ts +14 -6
  21. package/numkong/.clang-tidy +30 -0
  22. package/{simsimd → numkong}/.cmake-format.py +18 -0
  23. package/numkong/.editorconfig +12 -0
  24. package/numkong/.git-blame-ignore-revs +123 -0
  25. package/numkong/.mailmap +15 -0
  26. package/numkong/.spi.yml +11 -0
  27. package/numkong/.swift-format +14 -0
  28. package/numkong/CITATION.cff +81 -0
  29. package/numkong/CONTRIBUTING.md +500 -0
  30. package/numkong/Cargo.toml +54 -0
  31. package/numkong/MANIFEST.in +8 -0
  32. package/numkong/Package.swift +84 -0
  33. package/numkong/README.md +569 -0
  34. package/numkong/bench/README.md +217 -0
  35. package/numkong/bench/bench-browser.html +323 -0
  36. package/numkong/bench/bench.cpp +344 -0
  37. package/numkong/bench/bench.hpp +816 -0
  38. package/numkong/bench/bench.js +148 -0
  39. package/numkong/bench/bench.mjs +616 -0
  40. package/numkong/bench/bench_attention.cpp +113 -0
  41. package/numkong/bench/bench_cast.cpp +264 -0
  42. package/numkong/bench/bench_cross_amx.cpp +62 -0
  43. package/numkong/bench/bench_cross_arm.cpp +245 -0
  44. package/numkong/bench/bench_cross_blas.cpp +309 -0
  45. package/numkong/bench/bench_cross_loongarch.cpp +106 -0
  46. package/numkong/bench/bench_cross_power.cpp +84 -0
  47. package/numkong/bench/bench_cross_rvv.cpp +121 -0
  48. package/numkong/bench/bench_cross_serial.cpp +151 -0
  49. package/numkong/bench/bench_cross_sme.cpp +163 -0
  50. package/numkong/bench/bench_cross_wasm.cpp +142 -0
  51. package/numkong/bench/bench_cross_x86.cpp +455 -0
  52. package/numkong/bench/bench_curved.cpp +210 -0
  53. package/numkong/bench/bench_dot.cpp +315 -0
  54. package/numkong/bench/bench_each.cpp +503 -0
  55. package/numkong/bench/bench_geospatial.cpp +112 -0
  56. package/numkong/bench/bench_maxsim.cpp +154 -0
  57. package/numkong/bench/bench_mesh.cpp +148 -0
  58. package/numkong/bench/bench_probability.cpp +60 -0
  59. package/numkong/bench/bench_reduce.cpp +371 -0
  60. package/numkong/bench/bench_set.cpp +73 -0
  61. package/numkong/bench/bench_sparse.cpp +198 -0
  62. package/numkong/bench/bench_spatial.cpp +347 -0
  63. package/numkong/bench/bench_trigonometry.cpp +137 -0
  64. package/numkong/bench/swift/Bench.swift +506 -0
  65. package/numkong/binding.gyp +169 -0
  66. package/numkong/build.rs +484 -0
  67. package/numkong/c/dispatch.h +512 -0
  68. package/numkong/c/dispatch_bf16.c +447 -0
  69. package/numkong/c/dispatch_bf16c.c +52 -0
  70. package/numkong/c/dispatch_e2m3.c +296 -0
  71. package/numkong/c/dispatch_e3m2.c +284 -0
  72. package/numkong/c/dispatch_e4m3.c +318 -0
  73. package/numkong/c/dispatch_e5m2.c +316 -0
  74. package/numkong/c/dispatch_f16.c +452 -0
  75. package/numkong/c/dispatch_f16c.c +58 -0
  76. package/numkong/c/dispatch_f32.c +434 -0
  77. package/numkong/c/dispatch_f32c.c +99 -0
  78. package/numkong/c/dispatch_f64.c +348 -0
  79. package/numkong/c/dispatch_f64c.c +98 -0
  80. package/numkong/c/dispatch_i16.c +96 -0
  81. package/numkong/c/dispatch_i32.c +89 -0
  82. package/numkong/c/dispatch_i4.c +153 -0
  83. package/numkong/c/dispatch_i64.c +86 -0
  84. package/numkong/c/dispatch_i8.c +348 -0
  85. package/numkong/c/dispatch_other.c +348 -0
  86. package/numkong/c/dispatch_u1.c +193 -0
  87. package/numkong/c/dispatch_u16.c +124 -0
  88. package/numkong/c/dispatch_u32.c +118 -0
  89. package/numkong/c/dispatch_u4.c +153 -0
  90. package/numkong/c/dispatch_u64.c +102 -0
  91. package/numkong/c/dispatch_u8.c +364 -0
  92. package/numkong/c/numkong.c +940 -0
  93. package/numkong/cmake/nk_arm_isa_probes.cmake +61 -0
  94. package/numkong/cmake/nk_isa_probe.cmake +90 -0
  95. package/numkong/cmake/nk_loongarch_isa_probes.cmake +15 -0
  96. package/numkong/cmake/nk_math_lib.cmake +46 -0
  97. package/numkong/cmake/nk_power_isa_probes.cmake +15 -0
  98. package/numkong/cmake/nk_riscv_isa_probes.cmake +19 -0
  99. package/numkong/cmake/nk_x86_isa_probes.cmake +35 -0
  100. package/numkong/cmake/toolchain-aarch64-gnu.cmake +86 -0
  101. package/numkong/cmake/toolchain-android-arm64.cmake +25 -0
  102. package/numkong/cmake/toolchain-android-armv7.cmake +25 -0
  103. package/numkong/cmake/toolchain-loongarch64-gnu.cmake +68 -0
  104. package/numkong/cmake/toolchain-ppc64le-gnu.cmake +74 -0
  105. package/numkong/cmake/toolchain-riscv64-gnu.cmake +107 -0
  106. package/numkong/cmake/toolchain-riscv64-llvm.cmake +114 -0
  107. package/numkong/cmake/toolchain-wasi.cmake +77 -0
  108. package/numkong/cmake/toolchain-wasm.cmake +70 -0
  109. package/numkong/cmake/toolchain-wasm64.cmake +62 -0
  110. package/numkong/cmake/toolchain-x86_64-llvm.cmake +47 -0
  111. package/numkong/include/README.md +643 -0
  112. package/numkong/include/module.modulemap +129 -0
  113. package/numkong/include/numkong/attention/sapphireamx.h +1363 -0
  114. package/numkong/include/numkong/attention/sme.h +2075 -0
  115. package/numkong/include/numkong/attention.h +49 -0
  116. package/numkong/include/numkong/capabilities.h +930 -0
  117. package/numkong/include/numkong/cast/README.md +262 -0
  118. package/numkong/include/numkong/cast/diamond.h +64 -0
  119. package/numkong/include/numkong/cast/haswell.h +823 -0
  120. package/numkong/include/numkong/cast/icelake.h +475 -0
  121. package/numkong/include/numkong/cast/loongsonasx.h +252 -0
  122. package/numkong/include/numkong/cast/neon.h +1174 -0
  123. package/numkong/include/numkong/cast/powervsx.h +449 -0
  124. package/numkong/include/numkong/cast/rvv.h +970 -0
  125. package/numkong/include/numkong/cast/sapphire.h +262 -0
  126. package/numkong/include/numkong/cast/serial.h +2336 -0
  127. package/numkong/include/numkong/cast/skylake.h +915 -0
  128. package/numkong/include/numkong/cast/v128relaxed.h +537 -0
  129. package/numkong/include/numkong/cast.h +254 -0
  130. package/numkong/include/numkong/cast.hpp +44 -0
  131. package/numkong/include/numkong/curved/README.md +223 -0
  132. package/numkong/include/numkong/curved/genoa.h +182 -0
  133. package/numkong/include/numkong/curved/haswell.h +276 -0
  134. package/numkong/include/numkong/curved/neon.h +329 -0
  135. package/numkong/include/numkong/curved/neonbfdot.h +211 -0
  136. package/numkong/include/numkong/curved/rvv.h +305 -0
  137. package/numkong/include/numkong/curved/serial.h +207 -0
  138. package/numkong/include/numkong/curved/skylake.h +457 -0
  139. package/numkong/include/numkong/curved/smef64.h +530 -0
  140. package/numkong/include/numkong/curved.h +513 -0
  141. package/numkong/include/numkong/curved.hpp +144 -0
  142. package/numkong/include/numkong/dot/README.md +442 -0
  143. package/numkong/include/numkong/dot/alder.h +563 -0
  144. package/numkong/include/numkong/dot/diamond.h +158 -0
  145. package/numkong/include/numkong/dot/genoa.h +289 -0
  146. package/numkong/include/numkong/dot/haswell.h +1723 -0
  147. package/numkong/include/numkong/dot/icelake.h +997 -0
  148. package/numkong/include/numkong/dot/loongsonasx.h +671 -0
  149. package/numkong/include/numkong/dot/neon.h +869 -0
  150. package/numkong/include/numkong/dot/neonbfdot.h +243 -0
  151. package/numkong/include/numkong/dot/neonfhm.h +358 -0
  152. package/numkong/include/numkong/dot/neonfp8.h +323 -0
  153. package/numkong/include/numkong/dot/neonsdot.h +622 -0
  154. package/numkong/include/numkong/dot/powervsx.h +752 -0
  155. package/numkong/include/numkong/dot/rvv.h +722 -0
  156. package/numkong/include/numkong/dot/rvvbb.h +72 -0
  157. package/numkong/include/numkong/dot/rvvbf16.h +123 -0
  158. package/numkong/include/numkong/dot/rvvhalf.h +129 -0
  159. package/numkong/include/numkong/dot/sapphire.h +141 -0
  160. package/numkong/include/numkong/dot/serial.h +874 -0
  161. package/numkong/include/numkong/dot/sierra.h +405 -0
  162. package/numkong/include/numkong/dot/skylake.h +1116 -0
  163. package/numkong/include/numkong/dot/sve.h +420 -0
  164. package/numkong/include/numkong/dot/svebfdot.h +75 -0
  165. package/numkong/include/numkong/dot/svehalf.h +168 -0
  166. package/numkong/include/numkong/dot/svesdot.h +90 -0
  167. package/numkong/include/numkong/dot/v128relaxed.h +1293 -0
  168. package/numkong/include/numkong/dot.h +1136 -0
  169. package/numkong/include/numkong/dot.hpp +94 -0
  170. package/numkong/include/numkong/dots/README.md +496 -0
  171. package/numkong/include/numkong/dots/alder.h +117 -0
  172. package/numkong/include/numkong/dots/diamond.h +86 -0
  173. package/numkong/include/numkong/dots/genoa.h +100 -0
  174. package/numkong/include/numkong/dots/graniteamx.h +733 -0
  175. package/numkong/include/numkong/dots/haswell.h +310 -0
  176. package/numkong/include/numkong/dots/icelake.h +180 -0
  177. package/numkong/include/numkong/dots/loongsonasx.h +176 -0
  178. package/numkong/include/numkong/dots/neon.h +123 -0
  179. package/numkong/include/numkong/dots/neonbfdot.h +59 -0
  180. package/numkong/include/numkong/dots/neonfhm.h +96 -0
  181. package/numkong/include/numkong/dots/neonfp8.h +99 -0
  182. package/numkong/include/numkong/dots/neonsdot.h +144 -0
  183. package/numkong/include/numkong/dots/powervsx.h +194 -0
  184. package/numkong/include/numkong/dots/rvv.h +2593 -0
  185. package/numkong/include/numkong/dots/sapphireamx.h +4017 -0
  186. package/numkong/include/numkong/dots/serial.h +2867 -0
  187. package/numkong/include/numkong/dots/sierra.h +100 -0
  188. package/numkong/include/numkong/dots/skylake.h +207 -0
  189. package/numkong/include/numkong/dots/sme.h +5041 -0
  190. package/numkong/include/numkong/dots/smebi32.h +477 -0
  191. package/numkong/include/numkong/dots/smef64.h +1328 -0
  192. package/numkong/include/numkong/dots/smehalf.h +47 -0
  193. package/numkong/include/numkong/dots/v128relaxed.h +317 -0
  194. package/numkong/include/numkong/dots.h +3110 -0
  195. package/numkong/include/numkong/dots.hpp +641 -0
  196. package/numkong/include/numkong/each/README.md +465 -0
  197. package/numkong/include/numkong/each/haswell.h +1658 -0
  198. package/numkong/include/numkong/each/icelake.h +272 -0
  199. package/numkong/include/numkong/each/neon.h +1138 -0
  200. package/numkong/include/numkong/each/neonbfdot.h +211 -0
  201. package/numkong/include/numkong/each/neonhalf.h +318 -0
  202. package/numkong/include/numkong/each/rvv.h +1121 -0
  203. package/numkong/include/numkong/each/sapphire.h +343 -0
  204. package/numkong/include/numkong/each/serial.h +282 -0
  205. package/numkong/include/numkong/each/skylake.h +1562 -0
  206. package/numkong/include/numkong/each/v128relaxed.h +562 -0
  207. package/numkong/include/numkong/each.h +2232 -0
  208. package/numkong/include/numkong/each.hpp +434 -0
  209. package/numkong/include/numkong/geospatial/README.md +147 -0
  210. package/numkong/include/numkong/geospatial/haswell.h +633 -0
  211. package/numkong/include/numkong/geospatial/neon.h +615 -0
  212. package/numkong/include/numkong/geospatial/rvv.h +701 -0
  213. package/numkong/include/numkong/geospatial/serial.h +309 -0
  214. package/numkong/include/numkong/geospatial/skylake.h +613 -0
  215. package/numkong/include/numkong/geospatial/v128relaxed.h +652 -0
  216. package/numkong/include/numkong/geospatial.h +453 -0
  217. package/numkong/include/numkong/geospatial.hpp +235 -0
  218. package/numkong/include/numkong/matrix.hpp +336 -0
  219. package/numkong/include/numkong/maxsim/README.md +187 -0
  220. package/numkong/include/numkong/maxsim/alder.h +511 -0
  221. package/numkong/include/numkong/maxsim/genoa.h +115 -0
  222. package/numkong/include/numkong/maxsim/haswell.h +553 -0
  223. package/numkong/include/numkong/maxsim/icelake.h +480 -0
  224. package/numkong/include/numkong/maxsim/neonsdot.h +394 -0
  225. package/numkong/include/numkong/maxsim/sapphireamx.h +877 -0
  226. package/numkong/include/numkong/maxsim/serial.h +490 -0
  227. package/numkong/include/numkong/maxsim/sme.h +993 -0
  228. package/numkong/include/numkong/maxsim/v128relaxed.h +280 -0
  229. package/numkong/include/numkong/maxsim.h +571 -0
  230. package/numkong/include/numkong/maxsim.hpp +133 -0
  231. package/numkong/include/numkong/mesh/README.md +219 -0
  232. package/numkong/include/numkong/mesh/haswell.h +2013 -0
  233. package/numkong/include/numkong/mesh/neon.h +1821 -0
  234. package/numkong/include/numkong/mesh/neonbfdot.h +771 -0
  235. package/numkong/include/numkong/mesh/rvv.h +936 -0
  236. package/numkong/include/numkong/mesh/serial.h +721 -0
  237. package/numkong/include/numkong/mesh/skylake.h +2344 -0
  238. package/numkong/include/numkong/mesh/v128relaxed.h +1081 -0
  239. package/numkong/include/numkong/mesh.h +666 -0
  240. package/numkong/include/numkong/mesh.hpp +701 -0
  241. package/numkong/include/numkong/numkong.h +78 -0
  242. package/numkong/include/numkong/numkong.hpp +58 -0
  243. package/numkong/include/numkong/probability/README.md +177 -0
  244. package/numkong/include/numkong/probability/haswell.h +267 -0
  245. package/numkong/include/numkong/probability/neon.h +255 -0
  246. package/numkong/include/numkong/probability/rvv.h +414 -0
  247. package/numkong/include/numkong/probability/serial.h +181 -0
  248. package/numkong/include/numkong/probability/skylake.h +324 -0
  249. package/numkong/include/numkong/probability.h +380 -0
  250. package/numkong/include/numkong/probability.hpp +120 -0
  251. package/numkong/include/numkong/random.h +50 -0
  252. package/numkong/include/numkong/random.hpp +285 -0
  253. package/numkong/include/numkong/reduce/README.md +552 -0
  254. package/numkong/include/numkong/reduce/alder.h +636 -0
  255. package/numkong/include/numkong/reduce/genoa.h +201 -0
  256. package/numkong/include/numkong/reduce/haswell.h +3785 -0
  257. package/numkong/include/numkong/reduce/icelake.h +549 -0
  258. package/numkong/include/numkong/reduce/neon.h +3980 -0
  259. package/numkong/include/numkong/reduce/neonbfdot.h +142 -0
  260. package/numkong/include/numkong/reduce/neonfhm.h +293 -0
  261. package/numkong/include/numkong/reduce/neonsdot.h +357 -0
  262. package/numkong/include/numkong/reduce/rvv.h +3533 -0
  263. package/numkong/include/numkong/reduce/serial.h +753 -0
  264. package/numkong/include/numkong/reduce/sierra.h +338 -0
  265. package/numkong/include/numkong/reduce/skylake.h +3794 -0
  266. package/numkong/include/numkong/reduce/sve.h +52 -0
  267. package/numkong/include/numkong/reduce/v128relaxed.h +2302 -0
  268. package/numkong/include/numkong/reduce.h +1582 -0
  269. package/numkong/include/numkong/reduce.hpp +778 -0
  270. package/numkong/include/numkong/scalar/README.md +89 -0
  271. package/numkong/include/numkong/scalar/haswell.h +122 -0
  272. package/numkong/include/numkong/scalar/loongsonasx.h +74 -0
  273. package/numkong/include/numkong/scalar/neon.h +122 -0
  274. package/numkong/include/numkong/scalar/neonhalf.h +70 -0
  275. package/numkong/include/numkong/scalar/powervsx.h +96 -0
  276. package/numkong/include/numkong/scalar/rvv.h +211 -0
  277. package/numkong/include/numkong/scalar/sapphire.h +74 -0
  278. package/numkong/include/numkong/scalar/serial.h +332 -0
  279. package/numkong/include/numkong/scalar/v128relaxed.h +56 -0
  280. package/numkong/include/numkong/scalar.h +696 -0
  281. package/numkong/include/numkong/set/README.md +179 -0
  282. package/numkong/include/numkong/set/haswell.h +334 -0
  283. package/numkong/include/numkong/set/icelake.h +485 -0
  284. package/numkong/include/numkong/set/loongsonasx.h +181 -0
  285. package/numkong/include/numkong/set/neon.h +363 -0
  286. package/numkong/include/numkong/set/powervsx.h +326 -0
  287. package/numkong/include/numkong/set/rvv.h +226 -0
  288. package/numkong/include/numkong/set/rvvbb.h +117 -0
  289. package/numkong/include/numkong/set/serial.h +174 -0
  290. package/numkong/include/numkong/set/sve.h +187 -0
  291. package/numkong/include/numkong/set/v128relaxed.h +240 -0
  292. package/numkong/include/numkong/set.h +471 -0
  293. package/numkong/include/numkong/set.hpp +114 -0
  294. package/numkong/include/numkong/sets/README.md +149 -0
  295. package/numkong/include/numkong/sets/haswell.h +63 -0
  296. package/numkong/include/numkong/sets/icelake.h +66 -0
  297. package/numkong/include/numkong/sets/loongsonasx.h +52 -0
  298. package/numkong/include/numkong/sets/neon.h +61 -0
  299. package/numkong/include/numkong/sets/powervsx.h +65 -0
  300. package/numkong/include/numkong/sets/serial.h +43 -0
  301. package/numkong/include/numkong/sets/smebi32.h +1135 -0
  302. package/numkong/include/numkong/sets/v128relaxed.h +58 -0
  303. package/numkong/include/numkong/sets.h +382 -0
  304. package/numkong/include/numkong/sparse/README.md +156 -0
  305. package/numkong/include/numkong/sparse/icelake.h +463 -0
  306. package/numkong/include/numkong/sparse/neon.h +288 -0
  307. package/numkong/include/numkong/sparse/serial.h +117 -0
  308. package/numkong/include/numkong/sparse/sve2.h +504 -0
  309. package/numkong/include/numkong/sparse/turin.h +322 -0
  310. package/numkong/include/numkong/sparse.h +363 -0
  311. package/numkong/include/numkong/sparse.hpp +113 -0
  312. package/numkong/include/numkong/spatial/README.md +439 -0
  313. package/numkong/include/numkong/spatial/alder.h +604 -0
  314. package/numkong/include/numkong/spatial/diamond.h +240 -0
  315. package/numkong/include/numkong/spatial/genoa.h +222 -0
  316. package/numkong/include/numkong/spatial/haswell.h +979 -0
  317. package/numkong/include/numkong/spatial/icelake.h +1067 -0
  318. package/numkong/include/numkong/spatial/loongsonasx.h +483 -0
  319. package/numkong/include/numkong/spatial/neon.h +846 -0
  320. package/numkong/include/numkong/spatial/neonbfdot.h +164 -0
  321. package/numkong/include/numkong/spatial/neonfp8.h +258 -0
  322. package/numkong/include/numkong/spatial/neonsdot.h +429 -0
  323. package/numkong/include/numkong/spatial/powervsx.h +738 -0
  324. package/numkong/include/numkong/spatial/rvv.h +991 -0
  325. package/numkong/include/numkong/spatial/rvvbf16.h +128 -0
  326. package/numkong/include/numkong/spatial/rvvhalf.h +120 -0
  327. package/numkong/include/numkong/spatial/serial.h +347 -0
  328. package/numkong/include/numkong/spatial/sierra.h +516 -0
  329. package/numkong/include/numkong/spatial/skylake.h +605 -0
  330. package/numkong/include/numkong/spatial/sve.h +246 -0
  331. package/numkong/include/numkong/spatial/svebfdot.h +125 -0
  332. package/numkong/include/numkong/spatial/svehalf.h +136 -0
  333. package/numkong/include/numkong/spatial/svesdot.h +143 -0
  334. package/numkong/include/numkong/spatial/v128relaxed.h +969 -0
  335. package/numkong/include/numkong/spatial.h +1681 -0
  336. package/numkong/include/numkong/spatial.hpp +183 -0
  337. package/numkong/include/numkong/spatials/README.md +580 -0
  338. package/numkong/include/numkong/spatials/alder.h +94 -0
  339. package/numkong/include/numkong/spatials/diamond.h +82 -0
  340. package/numkong/include/numkong/spatials/genoa.h +94 -0
  341. package/numkong/include/numkong/spatials/graniteamx.h +173 -0
  342. package/numkong/include/numkong/spatials/haswell.h +219 -0
  343. package/numkong/include/numkong/spatials/icelake.h +113 -0
  344. package/numkong/include/numkong/spatials/loongsonasx.h +153 -0
  345. package/numkong/include/numkong/spatials/neon.h +109 -0
  346. package/numkong/include/numkong/spatials/neonbfdot.h +60 -0
  347. package/numkong/include/numkong/spatials/neonfhm.h +92 -0
  348. package/numkong/include/numkong/spatials/neonfp8.h +111 -0
  349. package/numkong/include/numkong/spatials/neonsdot.h +143 -0
  350. package/numkong/include/numkong/spatials/powervsx.h +153 -0
  351. package/numkong/include/numkong/spatials/rvv.h +1976 -0
  352. package/numkong/include/numkong/spatials/sapphireamx.h +1149 -0
  353. package/numkong/include/numkong/spatials/serial.h +248 -0
  354. package/numkong/include/numkong/spatials/sierra.h +96 -0
  355. package/numkong/include/numkong/spatials/skylake.h +184 -0
  356. package/numkong/include/numkong/spatials/sme.h +1927 -0
  357. package/numkong/include/numkong/spatials/smef64.h +483 -0
  358. package/numkong/include/numkong/spatials/v128relaxed.h +240 -0
  359. package/numkong/include/numkong/spatials.h +3374 -0
  360. package/numkong/include/numkong/spatials.hpp +509 -0
  361. package/numkong/include/numkong/tensor.hpp +1981 -0
  362. package/numkong/include/numkong/trigonometry/README.md +184 -0
  363. package/numkong/include/numkong/trigonometry/haswell.h +653 -0
  364. package/numkong/include/numkong/trigonometry/neon.h +638 -0
  365. package/numkong/include/numkong/trigonometry/rvv.h +700 -0
  366. package/numkong/include/numkong/trigonometry/serial.h +703 -0
  367. package/numkong/include/numkong/trigonometry/skylake.h +725 -0
  368. package/numkong/include/numkong/trigonometry/v128relaxed.h +667 -0
  369. package/numkong/include/numkong/trigonometry.h +467 -0
  370. package/numkong/include/numkong/trigonometry.hpp +166 -0
  371. package/numkong/include/numkong/types.h +1695 -0
  372. package/numkong/include/numkong/types.hpp +6027 -0
  373. package/numkong/include/numkong/vector.hpp +766 -0
  374. package/numkong/javascript/@numkong-darwin-arm64/package.json +26 -0
  375. package/numkong/javascript/@numkong-darwin-x64/package.json +26 -0
  376. package/numkong/javascript/@numkong-linux-arm64/package.json +25 -0
  377. package/numkong/javascript/@numkong-linux-x64/package.json +25 -0
  378. package/numkong/javascript/@numkong-win32-arm64/package.json +25 -0
  379. package/numkong/javascript/@numkong-win32-x64/package.json +25 -0
  380. package/numkong/javascript/README.md +254 -0
  381. package/numkong/javascript/node-gyp-build.d.ts +4 -0
  382. package/numkong/javascript/numkong-browser.ts +40 -0
  383. package/numkong/javascript/numkong-wasm.ts +750 -0
  384. package/numkong/javascript/numkong.c +712 -0
  385. package/numkong/javascript/numkong.ts +613 -0
  386. package/{simsimd → numkong}/javascript/tsconfig-base.json +6 -3
  387. package/numkong/javascript/tsconfig-cjs.json +13 -0
  388. package/{simsimd → numkong}/javascript/tsconfig-esm.json +1 -1
  389. package/numkong/javascript/types.ts +784 -0
  390. package/numkong/numkong.gypi +104 -0
  391. package/numkong/package.json +108 -0
  392. package/numkong/probes/arm_neon.c +8 -0
  393. package/numkong/probes/arm_neon_bfdot.c +9 -0
  394. package/numkong/probes/arm_neon_fhm.c +9 -0
  395. package/numkong/probes/arm_neon_half.c +8 -0
  396. package/numkong/probes/arm_neon_sdot.c +9 -0
  397. package/numkong/probes/arm_neonfp8.c +9 -0
  398. package/numkong/probes/arm_sme.c +16 -0
  399. package/numkong/probes/arm_sme2.c +16 -0
  400. package/numkong/probes/arm_sme2p1.c +16 -0
  401. package/numkong/probes/arm_sme_bf16.c +16 -0
  402. package/numkong/probes/arm_sme_bi32.c +16 -0
  403. package/numkong/probes/arm_sme_f64.c +16 -0
  404. package/numkong/probes/arm_sme_fa64.c +14 -0
  405. package/numkong/probes/arm_sme_half.c +16 -0
  406. package/numkong/probes/arm_sme_lut2.c +15 -0
  407. package/numkong/probes/arm_sve.c +18 -0
  408. package/numkong/probes/arm_sve2.c +20 -0
  409. package/numkong/probes/arm_sve2p1.c +18 -0
  410. package/numkong/probes/arm_sve_bfdot.c +20 -0
  411. package/numkong/probes/arm_sve_half.c +18 -0
  412. package/numkong/probes/arm_sve_sdot.c +21 -0
  413. package/numkong/probes/loongarch_lasx.c +12 -0
  414. package/numkong/probes/power_vsx.c +12 -0
  415. package/numkong/probes/probe.js +127 -0
  416. package/numkong/probes/riscv_rvv.c +14 -0
  417. package/numkong/probes/riscv_rvv_bb.c +15 -0
  418. package/numkong/probes/riscv_rvv_bf16.c +17 -0
  419. package/numkong/probes/riscv_rvv_half.c +14 -0
  420. package/numkong/probes/wasm_v128relaxed.c +11 -0
  421. package/numkong/probes/x86_alder.c +17 -0
  422. package/numkong/probes/x86_diamond.c +17 -0
  423. package/numkong/probes/x86_genoa.c +17 -0
  424. package/numkong/probes/x86_graniteamx.c +19 -0
  425. package/numkong/probes/x86_haswell.c +11 -0
  426. package/numkong/probes/x86_icelake.c +17 -0
  427. package/numkong/probes/x86_sapphire.c +16 -0
  428. package/numkong/probes/x86_sapphireamx.c +18 -0
  429. package/numkong/probes/x86_sierra.c +17 -0
  430. package/numkong/probes/x86_skylake.c +15 -0
  431. package/numkong/probes/x86_turin.c +17 -0
  432. package/numkong/rust/README.md +698 -0
  433. package/numkong/rust/capabilities.rs +96 -0
  434. package/numkong/rust/cast.rs +314 -0
  435. package/numkong/rust/curved.rs +391 -0
  436. package/numkong/rust/each.rs +2721 -0
  437. package/numkong/rust/geospatial.rs +343 -0
  438. package/numkong/rust/matrix.rs +5424 -0
  439. package/numkong/rust/maxsim.rs +540 -0
  440. package/numkong/rust/mesh.rs +837 -0
  441. package/numkong/rust/numkong.rs +409 -0
  442. package/numkong/rust/probability.rs +272 -0
  443. package/numkong/rust/reduce.rs +1249 -0
  444. package/numkong/rust/set.rs +194 -0
  445. package/numkong/rust/sparse.rs +513 -0
  446. package/numkong/rust/spatial.rs +1815 -0
  447. package/numkong/rust/tensor.rs +7565 -0
  448. package/numkong/rust/types.rs +3666 -0
  449. package/numkong/rust/vector.rs +1899 -0
  450. package/numkong/rustfmt.toml +2 -0
  451. package/numkong/swift/Matrix.swift +1388 -0
  452. package/numkong/swift/MaxSim.swift +151 -0
  453. package/numkong/swift/NumKong.swift +283 -0
  454. package/numkong/swift/Numerics.swift +329 -0
  455. package/numkong/swift/README.md +433 -0
  456. package/numkong/swift/Scalar.swift +741 -0
  457. package/numkong/swift/Tensor.swift +209 -0
  458. package/numkong/test/README.md +315 -0
  459. package/numkong/test/playwright.config.ts +16 -0
  460. package/numkong/test/swift/Test.swift +534 -0
  461. package/numkong/test/test-browser-bundle-runner.mjs +36 -0
  462. package/numkong/test/test-browser-bundle.html +188 -0
  463. package/numkong/test/test-browser-runner.mjs +43 -0
  464. package/numkong/test/test-browser.html +305 -0
  465. package/numkong/test/test-dtypes.mjs +453 -0
  466. package/numkong/test/test-wasm.mjs +267 -0
  467. package/numkong/test/test.cpp +442 -0
  468. package/numkong/test/test.hpp +580 -0
  469. package/numkong/test/test.mjs +363 -0
  470. package/numkong/test/test_base.py +1041 -0
  471. package/numkong/test/test_cast.cpp +242 -0
  472. package/numkong/test/test_cross.hpp +551 -0
  473. package/numkong/test/test_cross.py +467 -0
  474. package/numkong/test/test_cross_amx.cpp +50 -0
  475. package/numkong/test/test_cross_arm.cpp +225 -0
  476. package/numkong/test/test_cross_blas.cpp +354 -0
  477. package/numkong/test/test_cross_loongarch.cpp +90 -0
  478. package/numkong/test/test_cross_power.cpp +73 -0
  479. package/numkong/test/test_cross_rvv.cpp +109 -0
  480. package/numkong/test/test_cross_serial.cpp +181 -0
  481. package/numkong/test/test_cross_sme.cpp +148 -0
  482. package/numkong/test/test_cross_wasm.cpp +141 -0
  483. package/numkong/test/test_cross_x86.cpp +442 -0
  484. package/numkong/test/test_curved.cpp +182 -0
  485. package/numkong/test/test_curved.py +207 -0
  486. package/numkong/test/test_dot.cpp +353 -0
  487. package/numkong/test/test_dot.py +296 -0
  488. package/numkong/test/test_each.cpp +437 -0
  489. package/numkong/test/test_each.py +756 -0
  490. package/numkong/test/test_geospatial.cpp +155 -0
  491. package/numkong/test/test_geospatial.py +241 -0
  492. package/numkong/test/test_maxsim.cpp +138 -0
  493. package/numkong/test/test_maxsim.py +209 -0
  494. package/numkong/test/test_mesh.cpp +203 -0
  495. package/numkong/test/test_mesh.py +394 -0
  496. package/numkong/test/test_probability.cpp +136 -0
  497. package/numkong/test/test_probability.py +166 -0
  498. package/numkong/test/test_reduce.cpp +563 -0
  499. package/numkong/test/test_reduce.py +781 -0
  500. package/numkong/test/test_set.cpp +151 -0
  501. package/numkong/test/test_set.py +120 -0
  502. package/numkong/test/test_similarities.py +579 -0
  503. package/numkong/test/test_sparse.cpp +121 -0
  504. package/numkong/test/test_sparse.py +118 -0
  505. package/numkong/test/test_spatial.cpp +518 -0
  506. package/numkong/test/test_spatial.py +269 -0
  507. package/numkong/test/test_tensor.cpp +884 -0
  508. package/numkong/test/test_tensor.py +1417 -0
  509. package/numkong/test/test_trigonometry.cpp +156 -0
  510. package/numkong/test/test_trigonometry.py +180 -0
  511. package/package.json +7 -8
  512. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  513. package/prebuilds/linux-arm64/usearch.node +0 -0
  514. package/prebuilds/linux-x64/usearch.node +0 -0
  515. package/fp16/CMakeLists.txt +0 -176
  516. package/fp16/LICENSE +0 -11
  517. package/fp16/README.md +0 -20
  518. package/fp16/bench/alt-element.cc +0 -89
  519. package/fp16/bench/from-alt-array.cc +0 -175
  520. package/fp16/bench/from-ieee-array.cc +0 -399
  521. package/fp16/bench/ieee-element.cc +0 -272
  522. package/fp16/bench/to-alt-array.cc +0 -91
  523. package/fp16/bench/to-ieee-array.cc +0 -303
  524. package/fp16/cmake/DownloadGoogleBenchmark.cmake +0 -15
  525. package/fp16/cmake/DownloadGoogleTest.cmake +0 -15
  526. package/fp16/cmake/DownloadPSimd.cmake +0 -15
  527. package/fp16/configure.py +0 -65
  528. package/fp16/confu.yaml +0 -8
  529. package/fp16/include/fp16/__init__.py +0 -0
  530. package/fp16/include/fp16/avx.py +0 -50
  531. package/fp16/include/fp16/avx2.py +0 -53
  532. package/fp16/include/fp16/bitcasts.h +0 -92
  533. package/fp16/include/fp16/fp16.h +0 -451
  534. package/fp16/include/fp16/psimd.h +0 -131
  535. package/fp16/include/fp16.h +0 -11
  536. package/fp16/jni/Android.mk +0 -6
  537. package/fp16/jni/Application.mk +0 -4
  538. package/fp16/test/alt-from-fp32-value.cc +0 -498
  539. package/fp16/test/alt-to-fp32-bits.cc +0 -263
  540. package/fp16/test/alt-to-fp32-psimd.cc +0 -145
  541. package/fp16/test/alt-to-fp32-value.cc +0 -348
  542. package/fp16/test/alt-to-fp32x2-psimd.cc +0 -245
  543. package/fp16/test/bitcasts.cc +0 -56
  544. package/fp16/test/ieee-from-fp32-value.cc +0 -523
  545. package/fp16/test/ieee-to-fp32-bits.cc +0 -333
  546. package/fp16/test/ieee-to-fp32-psimd.cc +0 -312
  547. package/fp16/test/ieee-to-fp32-value.cc +0 -420
  548. package/fp16/test/ieee-to-fp32x2-psimd.cc +0 -541
  549. package/fp16/test/peachpy/alt-xmm-to-fp32-xmm-avx.cc +0 -96
  550. package/fp16/test/peachpy/alt-xmm-to-fp32-ymm-avx2.cc +0 -96
  551. package/fp16/test/peachpy/stubs.py +0 -38
  552. package/fp16/test/tables.cc +0 -530
  553. package/fp16/test/tables.h +0 -13
  554. package/fp16/third-party/THHalf.h +0 -92
  555. package/fp16/third-party/eigen-half.h +0 -170
  556. package/fp16/third-party/float16-compressor.h +0 -80
  557. package/fp16/third-party/half.hpp +0 -2916
  558. package/fp16/third-party/npy-halffloat.h +0 -328
  559. package/simsimd/.git-blame-ignore-revs +0 -2
  560. package/simsimd/CONTRIBUTING.md +0 -321
  561. package/simsimd/Cargo.toml +0 -49
  562. package/simsimd/MANIFEST.in +0 -4
  563. package/simsimd/Package.swift +0 -28
  564. package/simsimd/README.md +0 -1493
  565. package/simsimd/binding.gyp +0 -35
  566. package/simsimd/build.rs +0 -70
  567. package/simsimd/c/lib.c +0 -374
  568. package/simsimd/include/module.modulemap +0 -4
  569. package/simsimd/include/simsimd/binary.h +0 -486
  570. package/simsimd/include/simsimd/curved.h +0 -1541
  571. package/simsimd/include/simsimd/dot.h +0 -1853
  572. package/simsimd/include/simsimd/elementwise.h +0 -2465
  573. package/simsimd/include/simsimd/geospatial.h +0 -43
  574. package/simsimd/include/simsimd/mesh.h +0 -69
  575. package/simsimd/include/simsimd/probability.h +0 -606
  576. package/simsimd/include/simsimd/simsimd.h +0 -2567
  577. package/simsimd/include/simsimd/sparse.h +0 -1384
  578. package/simsimd/include/simsimd/spatial.h +0 -2335
  579. package/simsimd/include/simsimd/types.h +0 -666
  580. package/simsimd/javascript/fallback.ts +0 -233
  581. package/simsimd/javascript/lib.c +0 -125
  582. package/simsimd/javascript/node-gyp-build.d.ts +0 -1
  583. package/simsimd/javascript/simsimd.ts +0 -179
  584. package/simsimd/javascript/tsconfig-cjs.json +0 -8
  585. package/simsimd/package-lock.json +0 -1761
  586. package/simsimd/package.json +0 -61
  587. package/simsimd/rust/lib.rs +0 -1821
  588. package/simsimd/scripts/bench.cxx +0 -1170
  589. package/simsimd/scripts/bench.ipynb +0 -187
  590. package/simsimd/scripts/bench.js +0 -127
  591. package/simsimd/scripts/bench.rs +0 -876
  592. package/simsimd/scripts/bench_vectors.py +0 -734
  593. package/simsimd/scripts/bench_vectors_live.py +0 -213
  594. package/simsimd/scripts/test.c +0 -187
  595. package/simsimd/scripts/test.mjs +0 -220
  596. package/simsimd/scripts/test.py +0 -1505
  597. package/simsimd/swift/SimSIMD.swift +0 -149
  598. package/simsimd/swift/Test.swift +0 -128
  599. /package/{simsimd → numkong}/CODE_OF_CONDUCT.md +0 -0
  600. /package/{simsimd → numkong}/LICENSE +0 -0
  601. /package/{simsimd → numkong}/javascript/dist-package-cjs.json +0 -0
  602. /package/{simsimd → numkong}/javascript/dist-package-esm.json +0 -0
@@ -0,0 +1,1417 @@
1
+ #!/usr/bin/env python3
2
+ """Test tensor API: constructors, slicing, reductions, scalar types, NumPy interop.
3
+
4
+ Dtypes: float64, float32, int8, int32, bfloat16, float8_e4m3, float8_e5m2, float6_e2m3, float6_e3m2.
5
+ Matches C++ suite: test_tensor.cpp.
6
+ """
7
+
8
+ import concurrent.futures
9
+ import multiprocessing
10
+ import sys
11
+ import sysconfig
12
+ import time
13
+ import warnings
14
+ from collections.abc import Callable
15
+ from typing import TYPE_CHECKING
16
+
17
+ import pytest
18
+
19
+ if TYPE_CHECKING:
20
+ import numpy as np # static-analysis-only; the runtime try/except below is authoritative
21
+
22
+ try:
23
+ import numpy as np
24
+
25
+ numpy_available = True
26
+ except Exception:
27
+ numpy_available = False
28
+
29
+ import numkong as nk
30
+ from test_base import (
31
+ NK_ATOL,
32
+ NK_RTOL,
33
+ assert_allclose,
34
+ dense_dimensions,
35
+ f32_downcast_to_bf16,
36
+ make_nk,
37
+ ml_dtypes_available,
38
+ nk_seed, # noqa: F401 — pytest fixture
39
+ numpy_available,
40
+ precise_decimal,
41
+ randomized_repetitions_count,
42
+ seed_rng, # noqa: F401 — pytest fixture (autouse)
43
+ to_array,
44
+ tolerances_for_dtype,
45
+ )
46
+
47
+ try:
48
+ import ml_dtypes
49
+ except ImportError:
50
+ ml_dtypes = None # type: ignore[assignment]
51
+
52
+
53
+ def precise_sum(a, dtype=None):
54
+ """High-precision sum via Python Decimal."""
55
+ with precise_decimal(dtype) as (upcast, _sqrt, _ln):
56
+ return float(sum(upcast(x) for x in np.asarray(a).flat))
57
+
58
+
59
+ def precise_min(a, dtype=None):
60
+ """Exact min — comparison is exact on IEEE floats."""
61
+ return float(min(float(x) for x in np.asarray(a).flat))
62
+
63
+
64
+ def precise_max(a, dtype=None):
65
+ """Exact max — comparison is exact on IEEE floats."""
66
+ return float(max(float(x) for x in np.asarray(a).flat))
67
+
68
+
69
+ def precise_add(a, b, dtype=None):
70
+ """High-precision element-wise add via Python Decimal."""
71
+ with precise_decimal(dtype) as (upcast, _sqrt, _ln):
72
+ return [float(upcast(x) + upcast(y)) for x, y in zip(np.asarray(a).flat, np.asarray(b).flat)]
73
+
74
+
75
+ def precise_subtract(a, b, dtype=None):
76
+ """High-precision element-wise subtract via Python Decimal."""
77
+ with precise_decimal(dtype) as (upcast, _sqrt, _ln):
78
+ return [float(upcast(x) - upcast(y)) for x, y in zip(np.asarray(a).flat, np.asarray(b).flat)]
79
+
80
+
81
+ def precise_multiply(a, b, dtype=None):
82
+ """High-precision element-wise multiply via Python Decimal."""
83
+ with precise_decimal(dtype) as (upcast, _sqrt, _ln):
84
+ return [float(upcast(x) * upcast(y)) for x, y in zip(np.asarray(a).flat, np.asarray(b).flat)]
85
+
86
+
87
+ def precise_negate(a, dtype=None):
88
+ """Exact negate — sign flip has no precision loss."""
89
+ return [-float(x) for x in np.asarray(a).flat]
90
+
91
+
92
+ KERNELS_TENSOR: dict[str, tuple[Callable, Callable, Callable | None]] = {
93
+ "sum": (lambda a: np.sum(np.asarray(a)), lambda a: a.sum(), precise_sum),
94
+ "min": (lambda a: np.min(np.asarray(a)), lambda a: a.min(), precise_min),
95
+ "max": (lambda a: np.max(np.asarray(a)), lambda a: a.max(), precise_max),
96
+ "argmin": (lambda a: np.argmin(np.asarray(a)), lambda a: a.argmin(), None),
97
+ "argmax": (lambda a: np.argmax(np.asarray(a)), lambda a: a.argmax(), None),
98
+ "add": (lambda a, b: np.add(np.asarray(a), np.asarray(b)), lambda a, b: a + b, precise_add),
99
+ "subtract": (lambda a, b: np.subtract(np.asarray(a), np.asarray(b)), lambda a, b: a - b, precise_subtract),
100
+ "multiply": (lambda a, b: np.multiply(np.asarray(a), np.asarray(b)), lambda a, b: a * b, precise_multiply),
101
+ "negate": (lambda a: -np.asarray(a), lambda a: -a, precise_negate),
102
+ }
103
+
104
+
105
+ def test_pointers_availability():
106
+ """Tests the availability of pre-compiled functions for compatibility with USearch."""
107
+ assert nk.pointer_to_sqeuclidean("float64") != 0
108
+ assert nk.pointer_to_angular("float64") != 0
109
+ assert nk.pointer_to_inner("float64") != 0
110
+
111
+ assert nk.pointer_to_sqeuclidean("float32") != 0
112
+ assert nk.pointer_to_angular("float32") != 0
113
+ assert nk.pointer_to_inner("float32") != 0
114
+
115
+ assert nk.pointer_to_sqeuclidean("float16") != 0
116
+ assert nk.pointer_to_angular("float16") != 0
117
+ assert nk.pointer_to_inner("float16") != 0
118
+
119
+ assert nk.pointer_to_sqeuclidean("int8") != 0
120
+ assert nk.pointer_to_angular("int8") != 0
121
+ assert nk.pointer_to_inner("int8") != 0
122
+
123
+ assert nk.pointer_to_sqeuclidean("uint8") != 0
124
+ assert nk.pointer_to_angular("uint8") != 0
125
+ assert nk.pointer_to_inner("uint8") != 0
126
+
127
+
128
+ def test_capabilities_list():
129
+ """Tests the visibility of hardware capabilities."""
130
+ caps = nk.get_capabilities()
131
+ avx2_caps = ["haswell", "alder", "sierra"]
132
+ avx512_caps = ["skylake", "icelake", "genoa", "sapphire", "turin", "diamond"]
133
+ amx_caps = ["sapphireamx", "graniteamx"]
134
+ neon_caps = ["neon", "neonhalf", "neonfhm", "neonbfdot", "neonsdot", "neonfp8"]
135
+ sve_caps = ["sve", "svehalf", "svebfdot", "svesdot", "sve2", "sve2p1"]
136
+ sme_caps = ["sme", "sme2", "sme2p1", "smef64", "smehalf", "smebf16", "smebi32", "smelut2", "smefa64"]
137
+ rvv_caps = ["rvv", "rvvhalf", "rvvbf16", "rvvbb"]
138
+ power_caps = ["powervsx"]
139
+ loongarch_caps = ["loongsonasx"]
140
+ wasm_caps = ["v128relaxed"]
141
+
142
+ expected_capabilities = [
143
+ "serial",
144
+ *avx2_caps,
145
+ *avx512_caps,
146
+ *amx_caps,
147
+ *neon_caps,
148
+ *sve_caps,
149
+ *sme_caps,
150
+ *rvv_caps,
151
+ *power_caps,
152
+ *loongarch_caps,
153
+ *wasm_caps,
154
+ ]
155
+ for cap in expected_capabilities:
156
+ assert cap in caps, f"Capability '{cap}' missing from get_capabilities()"
157
+ assert caps.get("serial") == 1
158
+
159
+ previous_value = nk.get_capabilities().get("neon")
160
+ nk.enable_capability("neon")
161
+ assert nk.get_capabilities().get("neon") == 1
162
+ if not previous_value:
163
+ nk.disable_capability("neon")
164
+
165
+
166
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
167
+ @pytest.mark.parametrize(
168
+ "function, expected_error, args, kwargs",
169
+ [
170
+ (nk.sqeuclidean, TypeError, (), {}),
171
+ (nk.sqeuclidean, TypeError, (to_array([1.0]),), {}),
172
+ (nk.sqeuclidean, ValueError, (to_array([1.0]), to_array([1.0]), "missing_dtype"), {}),
173
+ (nk.sqeuclidean, TypeError, (to_array([1.0]), "invalid"), {}),
174
+ (nk.sqeuclidean, TypeError, (to_array([1.0]), to_array([1.0])), {"invalid_kwarg": "value"}),
175
+ (nk.enable_capability, TypeError, (123,), {}),
176
+ (nk.disable_capability, TypeError, ([],), {}),
177
+ (nk.mahalanobis, TypeError, (to_array([1.0]), to_array([1.0])), {}),
178
+ (nk.bilinear, TypeError, (to_array([1.0]),), {}),
179
+ (nk.angular, TypeError, (to_array([1.0]), to_array([1.0]), to_array([1.0])), {}),
180
+ (nk.cdist, TypeError, (to_array([[1.0]]), to_array([[1.0]]), "l2", "dos"), {}),
181
+ (nk.cdist, TypeError, (to_array([[1.0]]), to_array([[1.0]]), "l2"), {"metric": "l2"}),
182
+ (nk.angular, LookupError, (to_array([1 + 2j]), to_array([1 + 2j])), {}),
183
+ (nk.angular, ValueError, (to_array([1.0]), to_array([1.0, 2.0])), {}),
184
+ (nk.angular, TypeError, (to_array([1.0]), to_array([1], "int8")), {}),
185
+ (nk.angular, TypeError, (to_array([1], "float32"), to_array([1], "float16")), {}),
186
+ ],
187
+ )
188
+ def test_invalid_argument_handling(function, expected_error, args, kwargs):
189
+ """Test that functions raise appropriate errors with invalid arguments."""
190
+ with pytest.raises(expected_error):
191
+ function(*args, **kwargs)
192
+
193
+
194
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
195
+ @pytest.mark.skipif(not ml_dtypes_available, reason="ml_dtypes is not installed")
196
+ @pytest.mark.repeat(randomized_repetitions_count)
197
+ @pytest.mark.parametrize("ndim", dense_dimensions)
198
+ def test_bf16_conversion_vs_ml_dtypes(ndim: int):
199
+ """Compare NumKong bfloat16 conversion with ml_dtypes reference implementation."""
200
+ a_f32 = np.random.randn(ndim).astype(np.float32)
201
+ _, a_nk_bf16 = f32_downcast_to_bf16(a_f32)
202
+ a_ml_bf16 = a_f32.astype(ml_dtypes.bfloat16)
203
+ ml_bits = np.asarray(a_ml_bf16.view(np.uint16), dtype=np.uint16)
204
+ assert np.array_equal(a_nk_bf16, ml_bits), (
205
+ f"BFloat16 conversion mismatch with ml_dtypes:\n"
206
+ f" NumKong bits: {a_nk_bf16[:5]}...\n"
207
+ f" ml_dtypes bits: {ml_bits[:5]}..."
208
+ )
209
+
210
+
211
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
212
+ @pytest.mark.skipif(not ml_dtypes_available, reason="ml_dtypes is not installed")
213
+ @pytest.mark.repeat(randomized_repetitions_count)
214
+ @pytest.mark.parametrize("ndim", dense_dimensions)
215
+ def test_float8_e4m3_conversion_vs_ml_dtypes(ndim: int):
216
+ """Compare NumKong float8_e4m3 conversion with ml_dtypes reference implementation."""
217
+ a_f32 = (np.random.randn(ndim) * 10).astype(np.float32)
218
+ a_f32 = np.clip(a_f32, -448, 448)
219
+ a_ml_e4m3 = a_f32.astype(ml_dtypes.float8_e4m3fn)
220
+ a_nk = nk.zeros((ndim,), dtype="e4m3")
221
+ assert a_ml_e4m3.dtype == ml_dtypes.float8_e4m3fn
222
+ assert a_nk.dtype == "e4m3"
223
+
224
+
225
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
226
+ @pytest.mark.skipif(not ml_dtypes_available, reason="ml_dtypes is not installed")
227
+ @pytest.mark.repeat(randomized_repetitions_count)
228
+ @pytest.mark.parametrize("ndim", dense_dimensions)
229
+ def test_float8_e5m2_conversion_vs_ml_dtypes(ndim: int):
230
+ """Compare NumKong float8_e5m2 conversion with ml_dtypes reference implementation."""
231
+ a_f32 = (np.random.randn(ndim) * 100).astype(np.float32)
232
+ a_f32 = np.clip(a_f32, -57344, 57344)
233
+ a_ml_e5m2 = a_f32.astype(ml_dtypes.float8_e5m2)
234
+ a_nk = nk.zeros((ndim,), dtype="e5m2")
235
+ assert a_ml_e5m2.dtype == ml_dtypes.float8_e5m2
236
+ assert a_nk.dtype == "e5m2"
237
+
238
+
239
+ @pytest.mark.parametrize("ndim", dense_dimensions)
240
+ def test_float6_e2m3_construction(ndim: int):
241
+ """Verify that e2m3 tensors can be constructed and have the correct dtype."""
242
+ a_nk = nk.zeros((ndim,), dtype="e2m3")
243
+ assert a_nk.dtype == "e2m3"
244
+ assert a_nk.shape == (ndim,)
245
+ assert a_nk.ndim == 1
246
+
247
+
248
+ @pytest.mark.parametrize("ndim", dense_dimensions)
249
+ def test_float6_e3m2_construction(ndim: int):
250
+ """Verify that e3m2 tensors can be constructed and have the correct dtype."""
251
+ a_nk = nk.zeros((ndim,), dtype="e3m2")
252
+ assert a_nk.dtype == "e3m2"
253
+ assert a_nk.shape == (ndim,)
254
+ assert a_nk.ndim == 1
255
+
256
+
257
+ def test_distances_tensor_properties(nk_seed: int):
258
+ a = nk.iota((5, 128), nk_seed, dtype="float64")
259
+ b = nk.iota((7, 128), nk_seed + 1, dtype="float64")
260
+ result = nk.cdist(a, b, metric="sqeuclidean")
261
+
262
+ assert hasattr(result, "shape")
263
+ assert hasattr(result, "dtype")
264
+ assert hasattr(result, "ndim")
265
+ assert hasattr(result, "size")
266
+ assert hasattr(result, "nbytes")
267
+ assert hasattr(result, "strides")
268
+ assert hasattr(result, "itemsize")
269
+
270
+ assert result.shape == (5, 7)
271
+ assert result.dtype == "float64"
272
+ assert result.ndim == 2
273
+ assert result.size == 35
274
+ assert result.nbytes == 35 * 8
275
+ assert result.itemsize == 8
276
+ assert isinstance(result.strides, tuple)
277
+ assert len(result.strides) == 2
278
+
279
+
280
+ def test_distances_tensor_properties_1d(nk_seed: int):
281
+ a = nk.iota((10, 128), nk_seed, dtype="float64")
282
+ b = nk.iota((10, 128), nk_seed + 1, dtype="float64")
283
+ result = nk.sqeuclidean(a, b)
284
+
285
+ assert result.shape == (10,)
286
+ assert result.ndim == 1
287
+ assert result.size == 10
288
+ assert len(result.strides) == 1
289
+
290
+
291
+ def test_distances_tensor_len(nk_seed: int):
292
+ a = nk.iota((5, 128), nk_seed, dtype="float64")
293
+ b = nk.iota((7, 128), nk_seed + 1, dtype="float64")
294
+ result_2d = nk.cdist(a, b, metric="sqeuclidean")
295
+ assert len(result_2d) == 5
296
+
297
+ a = nk.iota((10, 128), nk_seed, dtype="float64")
298
+ b = nk.iota((10, 128), nk_seed + 1, dtype="float64")
299
+ result_1d = nk.sqeuclidean(a, b)
300
+ assert len(result_1d) == 10
301
+
302
+
303
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
304
+ def test_distances_tensor_indexing():
305
+ a = np.random.rand(5, 128).astype(np.float64)
306
+ b = np.random.rand(7, 128).astype(np.float64)
307
+ result = nk.cdist(a, b, metric="sqeuclidean")
308
+ assert repr(result) # smoke-test repr doesn't crash
309
+ expected = np.asarray(result)
310
+
311
+ row0 = result[0]
312
+ assert hasattr(row0, "shape")
313
+ assert row0.shape == (7,)
314
+
315
+ row_last = result[-1]
316
+ assert row_last.shape == (7,)
317
+ assert_allclose(np.asarray(row_last), expected[-1])
318
+
319
+ val = result[2, 3]
320
+ assert isinstance(val, float)
321
+ assert_allclose(val, expected[2, 3])
322
+
323
+ val_neg = result[-1, -1]
324
+ assert isinstance(val_neg, float)
325
+ assert_allclose(val_neg, expected[-1, -1])
326
+
327
+ with pytest.raises(IndexError):
328
+ _ = result[10]
329
+ with pytest.raises(IndexError):
330
+ _ = result[0, 100]
331
+
332
+
333
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
334
+ def test_distances_tensor_iteration():
335
+ a = np.random.rand(5, 128).astype(np.float64)
336
+ b = np.random.rand(7, 128).astype(np.float64)
337
+ result = nk.cdist(a, b, metric="sqeuclidean")
338
+ expected = np.asarray(result)
339
+
340
+ count = 0
341
+ for i, row in enumerate(result):
342
+ count += 1
343
+ assert row.shape == (7,)
344
+ assert_allclose(np.asarray(row), expected[i])
345
+ assert count == 5
346
+
347
+ a = np.random.rand(3, 128).astype(np.float64)
348
+ b = np.random.rand(3, 128).astype(np.float64)
349
+ result_1d = nk.sqeuclidean(a, b)
350
+ expected_1d = np.asarray(result_1d)
351
+
352
+ items = list(result_1d)
353
+ assert len(items) == 3
354
+ for i, item in enumerate(items):
355
+ assert isinstance(item, float)
356
+ assert_allclose(item, expected_1d[i])
357
+
358
+
359
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
360
+ def test_distances_tensor_numpy_interop():
361
+ a = np.random.rand(5, 128).astype(np.float64)
362
+ b = np.random.rand(7, 128).astype(np.float64)
363
+ result = nk.cdist(a, b, metric="sqeuclidean")
364
+
365
+ arr = np.asarray(result)
366
+ assert isinstance(arr, np.ndarray)
367
+ assert arr.shape == (5, 7)
368
+ assert arr.dtype == np.float64
369
+ assert np.all(arr >= 0)
370
+
371
+ mv = memoryview(result)
372
+ assert mv.ndim == 2
373
+ assert mv.shape == (5, 7)
374
+
375
+ arr_from_mv = np.asarray(mv)
376
+ np.testing.assert_array_equal(arr, arr_from_mv)
377
+
378
+
379
+ def test_distances_tensor_scalar_conversion(nk_seed: int):
380
+ a = nk.iota((1, 128), nk_seed, dtype="float64").flatten()
381
+ b = nk.iota((1, 128), nk_seed + 1, dtype="float64").flatten()
382
+ result = nk.sqeuclidean(a, b)
383
+ assert isinstance(result, float)
384
+ assert result >= 0
385
+
386
+ a2 = nk.iota((3, 128), nk_seed, dtype="float64")
387
+ b2 = nk.iota((5, 128), nk_seed + 1, dtype="float64")
388
+ result2 = nk.cdist(a2, b2, metric="sqeuclidean")
389
+ with pytest.raises(TypeError):
390
+ float(result2)
391
+
392
+
393
+ @pytest.mark.parametrize("input_dtype", ["float32", "float64"])
394
+ def test_distances_tensor_dtype_consistency(input_dtype, nk_seed: int):
395
+ a = nk.iota((5, 128), nk_seed, dtype=input_dtype)
396
+ b = nk.iota((7, 128), nk_seed + 1, dtype=input_dtype)
397
+ result = nk.cdist(a, b, metric="sqeuclidean")
398
+ assert result.dtype == "float64"
399
+
400
+
401
+ def test_distances_tensor_strides(nk_seed: int):
402
+ a = nk.iota((5, 128), nk_seed, dtype="float64")
403
+ b = nk.iota((7, 128), nk_seed + 1, dtype="float64")
404
+ result = nk.cdist(a, b, metric="sqeuclidean")
405
+
406
+ strides = result.strides
407
+ assert isinstance(strides, tuple)
408
+ assert len(strides) == 2
409
+ assert strides == (7 * 8, 8)
410
+
411
+
412
+ def test_distances_tensor_array_interface(nk_seed: int):
413
+ a = nk.iota((5, 128), nk_seed, dtype="float64")
414
+ b = nk.iota((7, 128), nk_seed + 1, dtype="float64")
415
+ result = nk.cdist(a, b, metric="sqeuclidean")
416
+
417
+ ai = result.__array_interface__
418
+ assert isinstance(ai, dict)
419
+ assert "shape" in ai
420
+ assert "typestr" in ai
421
+ assert "data" in ai
422
+ assert "strides" in ai
423
+ assert "version" in ai
424
+ assert ai["shape"] == (5, 7)
425
+ assert ai["typestr"] == "<f8"
426
+ assert ai["version"] == 3
427
+ assert isinstance(ai["data"], tuple)
428
+ assert len(ai["data"]) == 2
429
+
430
+
431
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
432
+ def test_distances_tensor_transpose():
433
+ a = np.random.rand(5, 128).astype(np.float64)
434
+ b = np.random.rand(7, 128).astype(np.float64)
435
+ result = nk.cdist(a, b, metric="sqeuclidean")
436
+
437
+ t = result.T
438
+ assert t.shape == (7, 5)
439
+ result_arr = np.asarray(result)
440
+ t_arr = np.asarray(t)
441
+ assert_allclose(result_arr.T, t_arr)
442
+
443
+ a1d = np.random.rand(10, 128).astype(np.float64)
444
+ b1d = np.random.rand(10, 128).astype(np.float64)
445
+ result1d = nk.sqeuclidean(a1d, b1d)
446
+ t1d = result1d.T
447
+ assert t1d.shape == result1d.shape
448
+
449
+
450
+ def test_distances_tensor_str(nk_seed: int):
451
+ a = nk.iota((3, 128), nk_seed, dtype="float64")
452
+ b = nk.iota((4, 128), nk_seed + 1, dtype="float64")
453
+ result = nk.cdist(a, b, metric="sqeuclidean")
454
+
455
+ s = str(result)
456
+ assert len(s) > 0
457
+ assert any(c.isdigit() for c in s)
458
+
459
+
460
+ def test_distances_tensor_equality(nk_seed: int):
461
+ a = nk.iota((5, 4), nk_seed, dtype="float32")
462
+ b = nk.iota((7, 4), nk_seed + 20, dtype="float32")
463
+ result = nk.cdist(a, b, metric="sqeuclidean")
464
+
465
+ copy = result.copy()
466
+ assert result == copy
467
+ assert not (result != copy) # noqa: SIM202 — intentionally tests __ne__
468
+
469
+ a2 = nk.iota((5, 4), nk_seed + 100, dtype="float32")
470
+ b2 = nk.iota((7, 4), nk_seed + 200, dtype="float32")
471
+ result2 = nk.cdist(a2, b2, metric="sqeuclidean")
472
+ assert result != result2
473
+
474
+ a3 = nk.iota((3, 4), nk_seed, dtype="float32")
475
+ b3 = nk.iota((4, 4), nk_seed + 20, dtype="float32")
476
+ result3 = nk.cdist(a3, b3, metric="sqeuclidean")
477
+ assert result != result3
478
+
479
+
480
+ def test_distances_tensor_inf_nan_propagation(nk_seed: int):
481
+ """Verify that inf/nan inputs propagate through cdist without crashing."""
482
+ normal = nk.iota((3, 4), nk_seed, dtype="float64")
483
+
484
+ inf_tensor = nk.full((2, 4), float("inf"), dtype="float64")
485
+ result_inf = nk.cdist(inf_tensor, normal, metric="sqeuclidean")
486
+ assert result_inf.shape == (2, 3)
487
+ assert result_inf.dtype == "float64"
488
+ flat = result_inf.flatten()
489
+ for i in range(result_inf.size):
490
+ v = float(flat[i])
491
+ assert v == float("inf") or v != v # inf or nan
492
+
493
+ nan_tensor = nk.full((2, 4), float("nan"), dtype="float64")
494
+ result_nan = nk.cdist(nan_tensor, normal, metric="sqeuclidean")
495
+ assert result_nan.shape == (2, 3)
496
+ assert result_nan.dtype == "float64"
497
+ flat_nan = result_nan.flatten()
498
+ for i in range(result_nan.size):
499
+ assert float(flat_nan[i]) != float(flat_nan[i]) # all nan
500
+
501
+ s = str(result_inf)
502
+ assert len(s) > 0
503
+ assert any(c.isdigit() or c in "inaINAf" for c in s)
504
+
505
+
506
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
507
+ def test_distances_tensor_copy():
508
+ a = np.random.rand(5, 128).astype(np.float64)
509
+ b = np.random.rand(7, 128).astype(np.float64)
510
+ result = nk.cdist(a, b, metric="sqeuclidean")
511
+
512
+ copy = result.copy()
513
+ assert copy.shape == result.shape
514
+ assert copy.dtype == result.dtype
515
+ assert result == copy
516
+
517
+ result_arr = np.asarray(result)
518
+ copy_arr = np.asarray(copy)
519
+ np.testing.assert_array_equal(result_arr, copy_arr)
520
+
521
+
522
+ def test_distances_tensor_reshape(nk_seed: int):
523
+ a = nk.iota((5, 128), nk_seed, dtype="float64")
524
+ b = nk.iota((7, 128), nk_seed + 1, dtype="float64")
525
+ result = nk.cdist(a, b, metric="sqeuclidean")
526
+
527
+ flat = result.reshape(35)
528
+ assert flat.shape == (35,)
529
+ assert flat.size == 35
530
+
531
+ back = flat.reshape(5, 7)
532
+ assert back.shape == (5, 7)
533
+
534
+ reshaped = result.reshape((7, 5))
535
+ assert reshaped.shape == (7, 5)
536
+
537
+ with pytest.raises(ValueError):
538
+ result.reshape(10, 10)
539
+
540
+
541
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
542
+ def test_distances_tensor_slicing():
543
+ a = np.random.rand(10, 128).astype(np.float64)
544
+ b = np.random.rand(8, 128).astype(np.float64)
545
+ result = nk.cdist(a, b, metric="sqeuclidean")
546
+ expected = np.asarray(result)
547
+
548
+ sliced = result[2:5]
549
+ assert sliced.shape == (3, 8)
550
+ assert_allclose(np.asarray(sliced), expected[2:5])
551
+
552
+ step_sliced = result[::2]
553
+ assert step_sliced.shape == (5, 8)
554
+ assert_allclose(np.asarray(step_sliced), expected[::2])
555
+
556
+ rev_sliced = result[::-1]
557
+ assert rev_sliced.shape == (10, 8)
558
+ assert_allclose(np.asarray(rev_sliced), expected[::-1])
559
+
560
+ end_sliced = result[-3:]
561
+ assert end_sliced.shape == (3, 8)
562
+ assert_allclose(np.asarray(end_sliced), expected[-3:])
563
+
564
+
565
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
566
+ def test_distances_tensor_zero_copy_views():
567
+ a = np.random.rand(5, 64).astype(np.float64)
568
+ b = np.random.rand(4, 64).astype(np.float64)
569
+ result = nk.cdist(a, b, metric="sqeuclidean")
570
+ orig_np = np.asarray(result)
571
+
572
+ sliced = result[1:4]
573
+ sliced_np = np.asarray(sliced)
574
+ assert np.shares_memory(orig_np, sliced_np)
575
+
576
+ step_sliced = result[::2]
577
+ step_np = np.asarray(step_sliced)
578
+ assert np.shares_memory(orig_np, step_np)
579
+
580
+ rev_sliced = result[::-1]
581
+ rev_np = np.asarray(rev_sliced)
582
+ assert np.shares_memory(orig_np, rev_np)
583
+
584
+ row = result[0]
585
+ row_np = np.asarray(row)
586
+ assert np.shares_memory(orig_np, row_np)
587
+
588
+ transposed = result.T
589
+ trans_np = np.asarray(transposed)
590
+ assert np.shares_memory(orig_np, trans_np)
591
+
592
+ flat = result.reshape(20)
593
+ flat_np = np.asarray(flat)
594
+ assert np.shares_memory(orig_np, flat_np)
595
+
596
+ trans_reshaped = transposed.reshape(20)
597
+ trans_reshaped_np = np.asarray(trans_reshaped)
598
+ assert not np.shares_memory(orig_np, trans_reshaped_np)
599
+
600
+ chained = result[1:4][1:]
601
+ chained_np = np.asarray(chained)
602
+ assert np.shares_memory(orig_np, chained_np)
603
+
604
+ for _i, row in enumerate(result):
605
+ row_np = np.asarray(row)
606
+ assert np.shares_memory(orig_np, row_np)
607
+
608
+ orig_val = orig_np[2, 0].copy()
609
+ view = result[2]
610
+ view_np = np.asarray(view)
611
+ view_np[0] = 999.0
612
+ assert orig_np[2, 0] == 999.0
613
+ orig_np[2, 0] = orig_val
614
+
615
+ copied = result.copy()
616
+ copied_np = np.asarray(copied)
617
+ assert not np.shares_memory(orig_np, copied_np)
618
+
619
+
620
+ @pytest.mark.parametrize(
621
+ "dtype",
622
+ [
623
+ pytest.param("float64", id="f64"),
624
+ pytest.param("float32", id="f32"),
625
+ pytest.param("int8", id="i8"),
626
+ pytest.param("int32", id="i32"),
627
+ ],
628
+ )
629
+ @pytest.mark.parametrize(
630
+ "shape",
631
+ [pytest.param((10,), id="1d-10"), pytest.param((5, 4), id="2d-5x4"), pytest.param((2, 3, 4), id="3d-2x3x4")],
632
+ )
633
+ def test_ndarray_zeros(dtype: str, shape):
634
+ arr = nk.zeros(shape, dtype=dtype)
635
+ assert arr.shape == shape
636
+ assert arr.dtype == dtype
637
+ flat = arr.flatten()
638
+ for i in range(arr.size):
639
+ assert float(flat[i]) == 0
640
+
641
+
642
+ @pytest.mark.parametrize(
643
+ "dtype",
644
+ [
645
+ pytest.param("float64", id="f64"),
646
+ pytest.param("float32", id="f32"),
647
+ pytest.param("int8", id="i8"),
648
+ pytest.param("int32", id="i32"),
649
+ ],
650
+ )
651
+ @pytest.mark.parametrize(
652
+ "shape",
653
+ [pytest.param((10,), id="1d-10"), pytest.param((5, 4), id="2d-5x4"), pytest.param((2, 3, 4), id="3d-2x3x4")],
654
+ )
655
+ def test_ndarray_ones(dtype: str, shape):
656
+ arr = nk.ones(shape, dtype=dtype)
657
+ assert arr.shape == shape
658
+ assert arr.dtype == dtype
659
+ flat = arr.flatten()
660
+ for i in range(arr.size):
661
+ assert float(flat[i]) == 1
662
+
663
+
664
+ @pytest.mark.parametrize(
665
+ "dtype,fill_value",
666
+ [
667
+ pytest.param("float64", 3.14, id="f64-pi"),
668
+ pytest.param("float32", -2.5, id="f32-neg"),
669
+ pytest.param("int8", 42, id="i8-42"),
670
+ pytest.param("int32", -100, id="i32-neg"),
671
+ ],
672
+ )
673
+ @pytest.mark.parametrize("shape", [pytest.param((10,), id="1d-10"), pytest.param((5, 4), id="2d-5x4")])
674
+ def test_ndarray_full(dtype: str, fill_value, shape):
675
+ arr = nk.full(shape, fill_value, dtype=dtype)
676
+ assert arr.shape == shape
677
+ assert arr.dtype == dtype
678
+ flat = arr.flatten()
679
+ for i in range(arr.size):
680
+ val = float(flat[i])
681
+ if dtype.startswith("float"):
682
+ assert abs(val - fill_value) < 1e-5
683
+ else:
684
+ assert int(val) == fill_value
685
+
686
+
687
+ @pytest.mark.parametrize("dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")])
688
+ @pytest.mark.parametrize("shape", [pytest.param((10,), id="1d-10"), pytest.param((5, 4), id="2d-5x4")])
689
+ def test_ndarray_empty(dtype: str, shape):
690
+ arr = nk.empty(shape, dtype=dtype)
691
+ assert arr.shape == shape
692
+ assert arr.dtype == dtype
693
+
694
+
695
+ @pytest.mark.parametrize(
696
+ "dtype",
697
+ [
698
+ pytest.param("float32", id="f32"),
699
+ pytest.param("float64", id="f64"),
700
+ pytest.param("int8", id="i8"),
701
+ pytest.param("int32", id="i32"),
702
+ ],
703
+ )
704
+ @pytest.mark.parametrize("shape", [pytest.param((10,), id="1d-10"), pytest.param((3, 4), id="2d-3x4")])
705
+ def test_ndarray_iota(dtype: str, shape):
706
+ seed = 5
707
+ arr = nk.iota(shape, seed, dtype=dtype)
708
+ assert arr.shape == shape
709
+ assert arr.dtype == dtype
710
+ flat = arr.flatten()
711
+ assert float(flat[0]) == seed
712
+ assert float(flat[1]) == seed + 1
713
+ assert float(flat[2]) == seed + 2
714
+
715
+
716
+ @pytest.mark.parametrize(
717
+ "dtype",
718
+ [
719
+ pytest.param("float32", id="f32"),
720
+ pytest.param("float64", id="f64"),
721
+ pytest.param("int8", id="i8"),
722
+ pytest.param("int32", id="i32"),
723
+ ],
724
+ )
725
+ @pytest.mark.parametrize("n", [pytest.param(4, id="4x4"), pytest.param(8, id="8x8")])
726
+ def test_ndarray_diagonal(dtype: str, n):
727
+ seed = 3
728
+ arr = nk.diagonal(n, seed, dtype=dtype)
729
+ assert arr.shape == (n, n)
730
+ assert arr.dtype == dtype
731
+ for i in range(n):
732
+ assert float(arr[i, i]) == seed
733
+ if i + 1 < n:
734
+ assert float(arr[i, i + 1]) == 0
735
+
736
+
737
+ @pytest.mark.parametrize(
738
+ "dtype",
739
+ [
740
+ pytest.param("float32", id="f32"),
741
+ pytest.param("float64", id="f64"),
742
+ pytest.param("int8", id="i8"),
743
+ pytest.param("int32", id="i32"),
744
+ ],
745
+ )
746
+ @pytest.mark.parametrize("shape", [pytest.param((10,), id="1d-10"), pytest.param((3, 4), id="2d-3x4")])
747
+ def test_ndarray_hash(dtype: str, shape):
748
+ a = nk.hash(shape, seed=42, dtype=dtype)
749
+ b = nk.hash(shape, seed=42, dtype=dtype)
750
+ assert a.shape == shape
751
+ assert a.dtype == dtype
752
+ # Determinism: same seed → same data
753
+ flat_a = a.flatten()
754
+ flat_b = b.flatten()
755
+ total = 1
756
+ for d in shape:
757
+ total *= d
758
+ for i in range(total):
759
+ assert float(flat_a[i]) == float(flat_b[i])
760
+ # Different seed → different data
761
+ c = nk.hash(shape, seed=99, dtype=dtype)
762
+ flat_c = c.flatten()
763
+ differs = any(float(flat_a[i]) != float(flat_c[i]) for i in range(total))
764
+ assert differs, "Different seeds should produce different data"
765
+
766
+
767
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
768
+ @pytest.mark.parametrize("dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")])
769
+ @pytest.mark.parametrize(
770
+ "shape",
771
+ [pytest.param((100,), id="1d-100"), pytest.param((10, 10), id="2d-10x10"), pytest.param((4, 5, 5), id="3d-4x5x5")],
772
+ )
773
+ def test_ndarray_sum_float(dtype: str, shape):
774
+ np_arr = np.random.randn(*shape).astype(dtype)
775
+ nk_arr = make_nk(np_arr, dtype)
776
+ baseline_kernel, simd_kernel, _ = KERNELS_TENSOR["sum"]
777
+ expected = baseline_kernel(np_arr)
778
+ result = simd_kernel(nk_arr)
779
+ atol, rtol = tolerances_for_dtype(dtype)
780
+ assert_allclose(result, expected, rtol=rtol, atol=atol)
781
+
782
+
783
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
784
+ @pytest.mark.parametrize("dtype", [pytest.param("int8", id="i8"), pytest.param("int32", id="i32")])
785
+ @pytest.mark.parametrize(
786
+ "shape",
787
+ [pytest.param((100,), id="1d-100"), pytest.param((10, 10), id="2d-10x10"), pytest.param((4, 5, 5), id="3d-4x5x5")],
788
+ )
789
+ def test_ndarray_sum_integer(dtype: str, shape):
790
+ nk_dtype_conversion_info = np.iinfo(np.dtype(dtype))
791
+ np_arr = np.random.randint(
792
+ nk_dtype_conversion_info.min // 2, nk_dtype_conversion_info.max // 2, size=shape, dtype=dtype
793
+ )
794
+ nk_arr = make_nk(np_arr, dtype)
795
+ assert nk_arr.sum() == np_arr.sum()
796
+
797
+
798
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
799
+ @pytest.mark.parametrize("dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")])
800
+ @pytest.mark.parametrize("shape", [pytest.param((100,), id="1d-100"), pytest.param((10, 10), id="2d-10x10")])
801
+ def test_ndarray_min_max_float(dtype: str, shape):
802
+ np_arr = np.random.randn(*shape).astype(dtype)
803
+ nk_arr = make_nk(np_arr, dtype)
804
+ atol, rtol = tolerances_for_dtype(dtype)
805
+ for op in ("min", "max"):
806
+ baseline_kernel, simd_kernel, _ = KERNELS_TENSOR[op]
807
+ assert_allclose(simd_kernel(nk_arr), baseline_kernel(np_arr), rtol=rtol, atol=atol)
808
+
809
+
810
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
811
+ @pytest.mark.parametrize("dtype", [pytest.param("int8", id="i8"), pytest.param("int32", id="i32")])
812
+ @pytest.mark.parametrize("shape", [pytest.param((100,), id="1d-100"), pytest.param((10, 10), id="2d-10x10")])
813
+ def test_ndarray_min_max_integer(dtype: str, shape):
814
+ nk_dtype_conversion_info = np.iinfo(np.dtype(dtype))
815
+ np_arr = np.random.randint(
816
+ nk_dtype_conversion_info.min // 2, nk_dtype_conversion_info.max // 2, size=shape, dtype=dtype
817
+ )
818
+ nk_arr = make_nk(np_arr, dtype)
819
+ assert nk_arr.min() == np_arr.min()
820
+ assert nk_arr.max() == np_arr.max()
821
+
822
+
823
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
824
+ @pytest.mark.parametrize(
825
+ "dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32"), pytest.param("int32", id="i32")]
826
+ )
827
+ @pytest.mark.parametrize("shape", [pytest.param((100,), id="1d-100"), pytest.param((10, 10), id="2d-10x10")])
828
+ def test_ndarray_argmin_argmax_methods(dtype: str, shape):
829
+ if dtype.startswith("float"):
830
+ np_arr = np.random.randn(*shape).astype(dtype)
831
+ else:
832
+ nk_dtype_conversion_info = np.iinfo(np.dtype(dtype))
833
+ np_arr = np.random.randint(
834
+ nk_dtype_conversion_info.min // 2, nk_dtype_conversion_info.max // 2, size=shape, dtype=dtype
835
+ )
836
+ nk_arr = make_nk(np_arr, dtype)
837
+
838
+ for op in ("argmin", "argmax"):
839
+ baseline_kernel, simd_kernel, _ = KERNELS_TENSOR[op]
840
+ assert simd_kernel(nk_arr) == baseline_kernel(np_arr)
841
+
842
+
843
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
844
+ @pytest.mark.parametrize("dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")])
845
+ def test_ndarray_add_float(dtype: str):
846
+ shape = (20,)
847
+ np_a = np.random.randn(*shape).astype(dtype)
848
+ np_b = np.random.randn(*shape).astype(dtype)
849
+ nk_a = make_nk(np_a, dtype)
850
+ nk_b = make_nk(np_b, dtype)
851
+ baseline_kernel, simd_kernel, _ = KERNELS_TENSOR["add"]
852
+ result_np = np.asarray(simd_kernel(nk_a, nk_b))
853
+ assert_allclose(result_np, baseline_kernel(np_a, np_b))
854
+
855
+
856
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
857
+ def test_ndarray_add_integer():
858
+ shape = (20,)
859
+ np_a = np.random.randint(-50, 50, size=shape, dtype="int32")
860
+ np_b = np.random.randint(-50, 50, size=shape, dtype="int32")
861
+ nk_a = make_nk(np_a, "int32")
862
+ nk_b = make_nk(np_b, "int32")
863
+ np.testing.assert_array_equal(np.asarray(nk_a + nk_b), np_a + np_b)
864
+
865
+
866
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
867
+ @pytest.mark.parametrize("dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")])
868
+ def test_ndarray_subtract_float(dtype: str):
869
+ shape = (20,)
870
+ np_a = np.random.randn(*shape).astype(dtype)
871
+ np_b = np.random.randn(*shape).astype(dtype)
872
+ nk_a = make_nk(np_a, dtype)
873
+ nk_b = make_nk(np_b, dtype)
874
+ baseline_kernel, simd_kernel, _ = KERNELS_TENSOR["subtract"]
875
+ result_np = np.asarray(simd_kernel(nk_a, nk_b))
876
+ assert_allclose(result_np, baseline_kernel(np_a, np_b))
877
+
878
+
879
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
880
+ def test_ndarray_subtract_integer():
881
+ shape = (20,)
882
+ np_a = np.random.randint(-50, 50, size=shape, dtype="int32")
883
+ np_b = np.random.randint(-50, 50, size=shape, dtype="int32")
884
+ nk_a = make_nk(np_a, "int32")
885
+ nk_b = make_nk(np_b, "int32")
886
+ np.testing.assert_array_equal(np.asarray(nk_a - nk_b), np_a - np_b)
887
+
888
+
889
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
890
+ @pytest.mark.parametrize("dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")])
891
+ def test_ndarray_multiply_float(dtype: str):
892
+ shape = (20,)
893
+ np_a = np.random.randn(*shape).astype(dtype)
894
+ np_b = np.random.randn(*shape).astype(dtype)
895
+ nk_a = make_nk(np_a, dtype)
896
+ nk_b = make_nk(np_b, dtype)
897
+ baseline_kernel, simd_kernel, _ = KERNELS_TENSOR["multiply"]
898
+ result_np = np.asarray(simd_kernel(nk_a, nk_b))
899
+ assert_allclose(result_np, baseline_kernel(np_a, np_b))
900
+
901
+
902
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
903
+ def test_ndarray_multiply_integer():
904
+ shape = (20,)
905
+ np_a = np.random.randint(-10, 10, size=shape, dtype="int32")
906
+ np_b = np.random.randint(-10, 10, size=shape, dtype="int32")
907
+ nk_a = make_nk(np_a, "int32")
908
+ nk_b = make_nk(np_b, "int32")
909
+ np.testing.assert_array_equal(np.asarray(nk_a * nk_b), np_a * np_b)
910
+
911
+
912
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
913
+ @pytest.mark.parametrize("dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")])
914
+ def test_ndarray_unary_float(dtype: str):
915
+ shape = (20,)
916
+ np_a = np.random.randn(*shape).astype(dtype)
917
+ nk_a = make_nk(np_a, dtype)
918
+ baseline_kernel, simd_kernel, _ = KERNELS_TENSOR["negate"]
919
+ result_neg_np = np.asarray(simd_kernel(nk_a))
920
+ assert_allclose(result_neg_np, baseline_kernel(np_a))
921
+ np.testing.assert_array_equal(np.asarray(+nk_a), np_a)
922
+
923
+
924
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
925
+ def test_ndarray_unary_integer():
926
+ shape = (20,)
927
+ np_a = np.random.randint(-50, 50, size=shape, dtype="int32")
928
+ nk_a = make_nk(np_a, "int32")
929
+ np.testing.assert_array_equal(np.asarray(-nk_a), -np_a)
930
+ np.testing.assert_array_equal(np.asarray(+nk_a), np_a)
931
+
932
+
933
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
934
+ @pytest.mark.parametrize("dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")])
935
+ def test_reduction_on_strided_array(dtype: str):
936
+ np_arr = np.random.randn(10, 10).astype(dtype)
937
+ nk_arr = make_nk(np_arr, dtype)
938
+
939
+ np_strided = np_arr[::2]
940
+ nk_strided = nk_arr[::2]
941
+
942
+ atol, rtol = tolerances_for_dtype(dtype)
943
+ for op in ("sum", "min", "max"):
944
+ baseline_kernel, simd_kernel, _ = KERNELS_TENSOR[op]
945
+ assert_allclose(simd_kernel(nk_strided), baseline_kernel(np_strided), rtol=rtol, atol=atol)
946
+
947
+
948
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
949
+ @pytest.mark.parametrize("dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")])
950
+ def test_reduction_on_transposed_array(dtype: str):
951
+ np_arr = np.random.randn(5, 8).astype(dtype)
952
+ nk_arr = make_nk(np_arr, dtype)
953
+
954
+ np_t = np_arr.T
955
+ nk_t = nk_arr.T
956
+
957
+ atol, rtol = tolerances_for_dtype(dtype)
958
+ for op in ("sum", "min", "max"):
959
+ baseline_kernel, simd_kernel, _ = KERNELS_TENSOR[op]
960
+ assert_allclose(simd_kernel(nk_t), baseline_kernel(np_t), rtol=rtol, atol=atol)
961
+
962
+
963
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
964
+ @pytest.mark.parametrize("dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")])
965
+ def test_reduction_on_subview(dtype: str):
966
+ np_arr = np.random.randn(20, 20).astype(dtype)
967
+ nk_arr = make_nk(np_arr, dtype)
968
+
969
+ np_sub = np_arr[5:15, 5:15]
970
+ nk_sub = nk_arr[5:15, 5:15]
971
+
972
+ atol, rtol = tolerances_for_dtype(dtype)
973
+ for op in ("sum", "min", "max"):
974
+ baseline_kernel, simd_kernel, _ = KERNELS_TENSOR[op]
975
+ assert_allclose(simd_kernel(nk_sub), baseline_kernel(np_sub), rtol=rtol, atol=atol)
976
+ for op in ("argmin", "argmax"):
977
+ baseline_kernel, simd_kernel, _ = KERNELS_TENSOR[op]
978
+ assert simd_kernel(nk_sub) == baseline_kernel(np_sub)
979
+
980
+
981
+ def test_bfloat16_scalar_creation():
982
+ bf = nk.bfloat16(3.14159)
983
+ assert isinstance(bf, nk.bfloat16)
984
+ assert abs(float(bf) - 3.14159) < 0.01
985
+ assert int(bf) == 3
986
+ assert repr(bf) # smoke-test repr doesn't crash
987
+
988
+
989
+ def test_bfloat16_scalar_arithmetic():
990
+ a = nk.bfloat16(1.5)
991
+ b = nk.bfloat16(2.5)
992
+
993
+ result = a + b
994
+ assert isinstance(result, nk.bfloat16)
995
+ assert float(result) == 4.0
996
+
997
+ result = b - a
998
+ assert isinstance(result, nk.bfloat16)
999
+ assert float(result) == 1.0
1000
+
1001
+ result = a * b
1002
+ assert isinstance(result, nk.bfloat16)
1003
+ assert float(result) == 3.75
1004
+
1005
+ result = b / a
1006
+ assert isinstance(result, nk.bfloat16)
1007
+ assert abs(float(result) - 1.6666) < 0.01
1008
+
1009
+
1010
+ def test_bfloat16_scalar_unary():
1011
+ a = nk.bfloat16(1.5)
1012
+ assert float(-a) == -1.5
1013
+ assert float(+a) == 1.5
1014
+ assert float(abs(nk.bfloat16(-1.5))) == 1.5
1015
+ assert bool(a)
1016
+ assert not bool(nk.bfloat16(0.0))
1017
+
1018
+
1019
+ def test_bfloat16_scalar_comparison():
1020
+ a = nk.bfloat16(1.5)
1021
+ b = nk.bfloat16(2.5)
1022
+
1023
+ assert a < b
1024
+ assert a <= b
1025
+ assert a <= a
1026
+ assert b > a
1027
+ assert b >= a
1028
+ assert a == a
1029
+ assert a != b
1030
+
1031
+ assert a == 1.5
1032
+ assert a < 2.0
1033
+ assert a > 1.0
1034
+
1035
+
1036
+ def test_bfloat16_scalar_hash():
1037
+ a = nk.bfloat16(1.5)
1038
+ b = nk.bfloat16(1.5)
1039
+ s = {a, b}
1040
+ assert len(s) == 1
1041
+ d = {a: "value"}
1042
+ assert d[b] == "value"
1043
+
1044
+
1045
+ def test_float8_e4m3_scalar():
1046
+ f8 = nk.float8_e4m3(1.5)
1047
+ assert isinstance(f8, nk.float8_e4m3)
1048
+ assert float(f8) == 1.5
1049
+ assert int(f8) == 1
1050
+ assert "float8_e4m3" in repr(f8)
1051
+ assert float(-f8) == -1.5
1052
+ assert bool(f8)
1053
+ assert not bool(nk.float8_e4m3(0.0))
1054
+
1055
+
1056
+ def test_float8_e4m3_scalar_arithmetic():
1057
+ a = nk.float8_e4m3(1.5)
1058
+ b = nk.float8_e4m3(2.0)
1059
+
1060
+ result = a + b
1061
+ assert isinstance(result, nk.float8_e4m3)
1062
+ assert abs(float(result) - 3.5) < 0.5
1063
+
1064
+ result = b - a
1065
+ assert isinstance(result, nk.float8_e4m3)
1066
+ assert abs(float(result) - 0.5) < 0.5
1067
+
1068
+ result = a * b
1069
+ assert isinstance(result, nk.float8_e4m3)
1070
+ assert abs(float(result) - 3.0) < 0.5
1071
+
1072
+ result = b / a
1073
+ assert isinstance(result, nk.float8_e4m3)
1074
+ assert abs(float(result) - 1.33) < 0.5
1075
+
1076
+ assert float(+a) == float(a)
1077
+ assert float(abs(nk.float8_e4m3(-1.5))) == 1.5
1078
+
1079
+
1080
+ def test_float8_e5m2_scalar():
1081
+ f8 = nk.float8_e5m2(1.5)
1082
+ assert isinstance(f8, nk.float8_e5m2)
1083
+ assert float(f8) == 1.5
1084
+ assert int(f8) == 1
1085
+ assert "float8_e5m2" in repr(f8)
1086
+ assert float(-f8) == -1.5
1087
+ assert bool(f8)
1088
+ assert not bool(nk.float8_e5m2(0.0))
1089
+
1090
+
1091
+ def test_float8_e5m2_scalar_arithmetic():
1092
+ a = nk.float8_e5m2(1.5)
1093
+ b = nk.float8_e5m2(2.0)
1094
+
1095
+ result = a + b
1096
+ assert isinstance(result, nk.float8_e5m2)
1097
+ assert abs(float(result) - 3.5) < 0.5
1098
+
1099
+ result = b - a
1100
+ assert isinstance(result, nk.float8_e5m2)
1101
+ assert abs(float(result) - 0.5) < 0.5
1102
+
1103
+ result = a * b
1104
+ assert isinstance(result, nk.float8_e5m2)
1105
+ assert abs(float(result) - 3.0) < 0.5
1106
+
1107
+ result = b / a
1108
+ assert isinstance(result, nk.float8_e5m2)
1109
+ assert abs(float(result) - 1.33) < 0.5
1110
+
1111
+ assert float(+a) == float(a)
1112
+ assert float(abs(nk.float8_e5m2(-1.5))) == 1.5
1113
+
1114
+
1115
+ @pytest.mark.skipif(not ml_dtypes_available, reason="ml_dtypes not installed")
1116
+ def test_bfloat16_vs_ml_dtypes():
1117
+ test_values = [0.0, 1.0, -1.0, 3.14159, 100.0, 0.001, -65504.0]
1118
+ for val in test_values:
1119
+ nk_bf16 = nk.bfloat16(val)
1120
+ ml_bf16 = ml_dtypes.bfloat16(val)
1121
+ assert float(nk_bf16) == float(ml_bf16), f"Mismatch for {val}: nk={float(nk_bf16)}, ml={float(ml_bf16)}"
1122
+
1123
+
1124
+ @pytest.mark.skipif(not ml_dtypes_available, reason="ml_dtypes not installed")
1125
+ def test_float8_e4m3_vs_ml_dtypes():
1126
+ test_values = [0.0, 1.0, -1.0, 1.5, 2.0, 0.5]
1127
+ for val in test_values:
1128
+ nk_f8 = nk.float8_e4m3(val)
1129
+ ml_f8 = ml_dtypes.float8_e4m3fn(val)
1130
+ assert float(nk_f8) == float(ml_f8), f"Mismatch for {val}: nk={float(nk_f8)}, ml={float(ml_f8)}"
1131
+
1132
+
1133
+ @pytest.mark.skipif(not ml_dtypes_available, reason="ml_dtypes not installed")
1134
+ def test_float8_e5m2_vs_ml_dtypes():
1135
+ test_values = [0.0, 1.0, -1.0, 1.5, 2.0, 0.5]
1136
+ for val in test_values:
1137
+ nk_f8 = nk.float8_e5m2(val)
1138
+ ml_f8 = ml_dtypes.float8_e5m2(val)
1139
+ assert float(nk_f8) == float(ml_f8), f"Mismatch for {val}: nk={float(nk_f8)}, ml={float(ml_f8)}"
1140
+
1141
+
1142
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
1143
+ @pytest.mark.skipif(not ml_dtypes_available, reason="ml_dtypes not installed")
1144
+ @pytest.mark.parametrize(
1145
+ "ml_dtype,nk_name",
1146
+ [
1147
+ ("bfloat16", "bfloat16"),
1148
+ ("float8_e4m3fn", "e4m3"),
1149
+ ("float8_e5m2", "e5m2"),
1150
+ ("float6_e2m3fn", "e2m3"),
1151
+ ("float6_e3m2fn", "e3m2"),
1152
+ ],
1153
+ )
1154
+ def test_ml_dtypes_array_to_tensor(ml_dtype, nk_name):
1155
+ """Verify that ml_dtypes arrays can be consumed as nk.Tensor via __array_interface__."""
1156
+ dt = getattr(ml_dtypes, ml_dtype)
1157
+ a_f32 = np.random.randn(16).astype(np.float32).clip(-1, 1)
1158
+ a_ml = a_f32.astype(dt)
1159
+ # 1D
1160
+ t = nk.Tensor(a_ml)
1161
+ assert t.dtype == nk_name
1162
+ assert t.shape == (16,)
1163
+ # 2D
1164
+ a_2d = np.random.randn(4, 8).astype(np.float32).clip(-1, 1).astype(dt)
1165
+ t2 = nk.Tensor(a_2d)
1166
+ assert t2.dtype == nk_name
1167
+ assert t2.shape == (4, 8)
1168
+
1169
+
1170
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
1171
+ @pytest.mark.skipif(not ml_dtypes_available, reason="ml_dtypes not installed")
1172
+ @pytest.mark.parametrize(
1173
+ "ml_dtype",
1174
+ ["float8_e4m3fnuz", "float8_e5m2fnuz", "float8_e4m3b11fnuz", "float8_e8m0fnu"],
1175
+ )
1176
+ def test_ml_dtypes_incompatible_rejected(ml_dtype):
1177
+ """Verify that fnuz/b11fnuz/e8m0fnu types are rejected, not silently misinterpreted."""
1178
+ dt = getattr(ml_dtypes, ml_dtype, None)
1179
+ if dt is None:
1180
+ pytest.skip(f"ml_dtypes.{ml_dtype} not available in this version")
1181
+ a = np.array([0.5], dtype=dt)
1182
+ with pytest.raises((TypeError, ValueError)):
1183
+ nk.Tensor(a)
1184
+
1185
+
1186
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
1187
+ @pytest.mark.parametrize(
1188
+ "scalar_type,name",
1189
+ [
1190
+ (nk.bfloat16, "bfloat16"),
1191
+ (nk.float8_e4m3, "float8_e4m3"),
1192
+ (nk.float8_e5m2, "float8_e5m2"),
1193
+ ],
1194
+ )
1195
+ def test_numpy_array_with_nk_dtype(scalar_type, name):
1196
+ """Verify that nk scalar types can be used as NumPy dtype specifiers."""
1197
+ if not hasattr(scalar_type, "dtype"):
1198
+ pytest.skip("NumPy dtype registration not available")
1199
+ arr = np.array([1.0, 2.0, 3.0], dtype=scalar_type)
1200
+ assert abs(float(arr[0]) - 1.0) < 0.1
1201
+ assert abs(float(arr[1]) - 2.0) < 0.1
1202
+
1203
+
1204
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
1205
+ def test_nk_dtype_numpy_roundtrip():
1206
+ """Verify roundtrip: np.array(dtype=nk.bfloat16) → float32 → bfloat16 preserves values."""
1207
+ if not hasattr(nk.bfloat16, "dtype"):
1208
+ pytest.skip("NumPy dtype registration not available")
1209
+ vals = [0.0, 1.0, -1.0, 0.5, 3.14]
1210
+ arr = np.array(vals, dtype=nk.bfloat16)
1211
+ # Cast to float32 and back to verify the registered cast functions work.
1212
+ f32 = arr.astype(np.float32)
1213
+ back = f32.astype(nk.bfloat16)
1214
+ for i in range(len(vals)):
1215
+ assert float(arr[i]) == float(back[i])
1216
+
1217
+
1218
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
1219
+ def test_dots_packed_row_range():
1220
+ """Test dots_packed with start_row/end_row splits produce the same result."""
1221
+ height, depth, width = 100, 64, 50
1222
+ left_matrix = np.random.randn(height, depth).astype(np.float32)
1223
+ right_matrix = np.ascontiguousarray(np.random.randn(width, depth).astype(np.float32))
1224
+ right_packed = nk.dots_pack(right_matrix, dtype="float32")
1225
+
1226
+ reference = np.array(nk.dots_packed(left_matrix, right_packed))
1227
+
1228
+ output = nk.zeros((height, width), dtype="float64")
1229
+ nk.dots_packed(left_matrix, right_packed, out=output, start_row=0, end_row=50)
1230
+ nk.dots_packed(left_matrix, right_packed, out=output, start_row=50, end_row=100)
1231
+
1232
+ assert_allclose(np.array(output), reference, err_msg="Row-range split differs from full computation")
1233
+
1234
+
1235
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
1236
+ def test_dots_symmetric_row_range():
1237
+ """Test dots_symmetric with start_row/end_row.
1238
+
1239
+ Only the upper triangle of the output is guaranteed to be initialized,
1240
+ so we compare only the upper-triangle entries that fall within each row range.
1241
+ """
1242
+ count, depth = 64, 32
1243
+ vectors = np.random.randn(count, depth).astype(np.float32)
1244
+
1245
+ reference = np.array(nk.dots_symmetric(vectors))
1246
+ mask = np.triu(np.ones((count, count), dtype=bool))
1247
+
1248
+ output = nk.zeros((count, count), dtype="float64")
1249
+ nk.dots_symmetric(vectors, out=output, start_row=0, end_row=count)
1250
+
1251
+ assert_allclose(np.array(output)[mask], reference[mask], err_msg="Full-range dots_symmetric differs from default")
1252
+
1253
+
1254
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
1255
+ @pytest.mark.parametrize("height", [63, 64, 129])
1256
+ def test_dots_packed_threads(height):
1257
+ """Verify OpenMP-parallel dots_packed matches the serial path across tile boundaries.
1258
+
1259
+ The packed row tile is 64, so sizes straddling one and two tiles exercise
1260
+ both the whole-tile and tail-chunk branches of the parallel loop.
1261
+ """
1262
+ depth, width = 64, 32
1263
+ left_matrix = np.random.randn(height, depth).astype(np.float32)
1264
+ right_matrix = np.ascontiguousarray(np.random.randn(width, depth).astype(np.float32))
1265
+ right_packed = nk.dots_pack(right_matrix, dtype="float32")
1266
+
1267
+ serial = np.array(nk.dots_packed(left_matrix, right_packed, threads=1))
1268
+ parallel = np.array(nk.dots_packed(left_matrix, right_packed, threads=4))
1269
+ assert_allclose(parallel, serial, err_msg=f"threads=4 diverges from threads=1 at height={height}")
1270
+
1271
+
1272
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
1273
+ @pytest.mark.parametrize("count", [31, 32, 65])
1274
+ def test_dots_symmetric_threads(count):
1275
+ """Verify OpenMP-parallel dots_symmetric matches the serial path across tile boundaries.
1276
+
1277
+ The symmetric row tile is 32; only the upper triangle is guaranteed written.
1278
+ """
1279
+ depth = 32
1280
+ vectors = np.random.randn(count, depth).astype(np.float32)
1281
+ mask = np.triu(np.ones((count, count), dtype=bool))
1282
+
1283
+ serial = np.array(nk.dots_symmetric(vectors, threads=1))
1284
+ parallel = np.array(nk.dots_symmetric(vectors, threads=4))
1285
+ assert_allclose(parallel[mask], serial[mask], err_msg=f"threads=4 diverges from threads=1 at count={count}")
1286
+
1287
+
1288
+ def _skip_unless_free_threaded():
1289
+ version = sys.version_info
1290
+ if version.major == 3 and version.minor >= 13:
1291
+ is_free_threaded = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
1292
+ if not is_free_threaded:
1293
+ pytest.skip("Uses non-free-threaded Python, skipping GIL-related tests")
1294
+ if sys._is_gil_enabled():
1295
+ pytest.skip("GIL is enabled, skipping GIL-related tests")
1296
+ else:
1297
+ pytest.skip("Python < 3.13t, skipping GIL-related tests")
1298
+
1299
+
1300
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
1301
+ def test_gil_free_threading():
1302
+ """Test NumKong in Python 3.13t free-threaded mode if available."""
1303
+ _skip_unless_free_threaded()
1304
+
1305
+ num_threads = multiprocessing.cpu_count()
1306
+ vectors_a = np.random.rand(32 * 1024 * num_threads, 1024).astype(np.float32)
1307
+ vectors_b = np.random.rand(32 * 1024 * num_threads, 1024).astype(np.float32)
1308
+ distances = np.zeros(vectors_a.shape[0], dtype=np.float32)
1309
+
1310
+ def compute_batch(start_idx, end_idx) -> float:
1311
+ slice_a = vectors_a[start_idx:end_idx]
1312
+ slice_b = vectors_b[start_idx:end_idx]
1313
+ slice_distances = distances[start_idx:end_idx]
1314
+ nk.angular(slice_a, slice_b, out=slice_distances)
1315
+ return sum(slice_distances)
1316
+
1317
+ def compute_with_threads(threads: int) -> float:
1318
+ chunk_size = len(vectors_a) // threads
1319
+ futures = []
1320
+ with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as executor:
1321
+ for i in range(threads):
1322
+ start_idx = i * chunk_size
1323
+ end_idx = (i + 1) * chunk_size if i < threads - 1 else len(vectors_a)
1324
+ futures.append(executor.submit(compute_batch, start_idx, end_idx))
1325
+ total_sum = 0.0
1326
+ for future in concurrent.futures.as_completed(futures):
1327
+ total_sum += future.result()
1328
+ return total_sum
1329
+
1330
+ start_time = time.time()
1331
+ baseline_sum = compute_with_threads(2)
1332
+ end_time = time.time()
1333
+ baseline_duration = end_time - start_time
1334
+
1335
+ start_time = time.time()
1336
+ multi_sum = compute_with_threads(num_threads)
1337
+ end_time = time.time()
1338
+ multi_duration = end_time - start_time
1339
+
1340
+ assert np.allclose(
1341
+ baseline_sum, multi_sum, atol=NK_ATOL, rtol=NK_RTOL
1342
+ ), f"Results differ: baseline {baseline_sum} vs multi-threaded {multi_sum}"
1343
+
1344
+ if baseline_duration < multi_duration:
1345
+ warnings.warn(
1346
+ f"{num_threads}-threaded execution took longer than 2-threaded baseline: {multi_duration:.2f}s vs {baseline_duration:.2f}s",
1347
+ UserWarning,
1348
+ )
1349
+
1350
+
1351
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
1352
+ def test_gil_free_dots_packed_threading():
1353
+ """Test multi-threaded dots_packed with start_row/end_row."""
1354
+ _skip_unless_free_threaded()
1355
+
1356
+ height, depth, width = 200, 64, 50
1357
+ num_threads = min(multiprocessing.cpu_count(), 4)
1358
+ left_matrix = np.random.randn(height, depth).astype(np.float32)
1359
+ right_matrix = np.ascontiguousarray(np.random.randn(width, depth).astype(np.float32))
1360
+ right_packed = nk.dots_pack(right_matrix, dtype="float32")
1361
+
1362
+ # Single-threaded reference
1363
+ reference = nk.dots_packed(left_matrix, right_packed)
1364
+
1365
+ # Multi-threaded with row slicing into shared output
1366
+ output = nk.zeros((height, width), dtype="float64")
1367
+ rows_per_thread = height // num_threads
1368
+
1369
+ def compute_slice(thread_index):
1370
+ start_row = thread_index * rows_per_thread
1371
+ end_row = start_row + rows_per_thread if thread_index < num_threads - 1 else height
1372
+ nk.dots_packed(left_matrix, right_packed, out=output, start_row=start_row, end_row=end_row)
1373
+
1374
+ with concurrent.futures.ThreadPoolExecutor(max_workers=num_threads) as pool:
1375
+ list(pool.map(compute_slice, range(num_threads)))
1376
+
1377
+ assert_allclose(
1378
+ np.array(output), np.array(reference), err_msg="Multi-threaded dots_packed result differs from single-threaded"
1379
+ )
1380
+
1381
+
1382
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
1383
+ def test_gil_free_dots_symmetric_threading():
1384
+ """Test multi-threaded dots_symmetric with start_row/end_row.
1385
+
1386
+ The symmetric kernel only fills the upper triangle of each row's output.
1387
+ Each thread computes a disjoint row range into the same shared output tensor.
1388
+ """
1389
+ _skip_unless_free_threaded()
1390
+
1391
+ count, depth = 100, 64
1392
+ num_threads = min(multiprocessing.cpu_count(), 4)
1393
+ vectors = np.random.randn(count, depth).astype(np.float32)
1394
+
1395
+ # Single-threaded reference (upper triangle only is meaningful)
1396
+ reference = np.array(nk.dots_symmetric(vectors))
1397
+
1398
+ # Multi-threaded with row slicing into shared output
1399
+ output = nk.zeros((count, count), dtype="float64")
1400
+ rows_per_thread = count // num_threads
1401
+
1402
+ def compute_slice(thread_index):
1403
+ start_row = thread_index * rows_per_thread
1404
+ end_row = start_row + rows_per_thread if thread_index < num_threads - 1 else count
1405
+ nk.dots_symmetric(vectors, out=output, start_row=start_row, end_row=end_row)
1406
+
1407
+ with concurrent.futures.ThreadPoolExecutor(max_workers=num_threads) as pool:
1408
+ list(pool.map(compute_slice, range(num_threads)))
1409
+
1410
+ # Only the upper triangle is guaranteed initialized by the symmetric kernel
1411
+ result = np.array(output)
1412
+ mask = np.triu(np.ones((count, count), dtype=bool))
1413
+ assert_allclose(
1414
+ result[mask],
1415
+ reference[mask],
1416
+ err_msg="Multi-threaded dots_symmetric upper triangle differs from single-threaded",
1417
+ )