usearch 2.21.3 → 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 +235 -52
  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,781 @@
1
+ #!/usr/bin/env python3
2
+ """Test reductions: nk.moments, nk.sum, nk.min, nk.max, nk.argmin, nk.argmax, nk.norm.
3
+
4
+ Dtypes: float64, float32, float16, int32.
5
+ Baselines: high-precision Decimal summation, NumPy reductions.
6
+ Matches C++ suite: test_reduce.cpp.
7
+ """
8
+
9
+ import atexit
10
+ import math
11
+ from collections.abc import Callable
12
+ from typing import TYPE_CHECKING
13
+
14
+ import pytest
15
+
16
+ if TYPE_CHECKING:
17
+ import numpy as np # static-analysis-only; the runtime try/except below is authoritative
18
+
19
+ try:
20
+ import numpy as np
21
+
22
+ numpy_available = True
23
+ except Exception:
24
+ numpy_available = False
25
+
26
+ import numkong as nk
27
+ from test_base import (
28
+ NK_ATOL,
29
+ NK_RTOL,
30
+ assert_allclose,
31
+ create_stats,
32
+ dense_dimensions,
33
+ keep_one_capability,
34
+ make_nk,
35
+ make_random,
36
+ nk_seed, # noqa: F401 — pytest fixture
37
+ numpy_available,
38
+ possible_capabilities,
39
+ precise_decimal,
40
+ print_stats_report,
41
+ randomized_repetitions_count,
42
+ seed_rng, # noqa: F401 — pytest fixture (autouse)
43
+ tolerances_for_dtype,
44
+ )
45
+
46
+ algebraic_dtypes = ["float32", "float64"]
47
+ algebraic_ndims = [7, 97]
48
+ stats = create_stats()
49
+ atexit.register(print_stats_report, stats)
50
+
51
+
52
+ def baseline_moments(a, dtype=None):
53
+ """Reference moments: (sum, sum_of_squares) at f64 precision."""
54
+ arr = np.asarray(a).astype(np.float64)
55
+ return (np.sum(arr), np.sum(arr**2))
56
+
57
+
58
+ def baseline_sum(a, dtype=None):
59
+ """Reference sum."""
60
+ return np.sum(np.asarray(a))
61
+
62
+
63
+ def baseline_min(a, dtype=None):
64
+ """Reference min."""
65
+ return np.min(np.asarray(a))
66
+
67
+
68
+ def baseline_max(a, dtype=None):
69
+ """Reference max."""
70
+ return np.max(np.asarray(a))
71
+
72
+
73
+ def baseline_argmin(a, dtype=None):
74
+ """Reference argmin."""
75
+ return np.argmin(np.asarray(a))
76
+
77
+
78
+ def baseline_argmax(a, dtype=None):
79
+ """Reference argmax."""
80
+ return np.argmax(np.asarray(a))
81
+
82
+
83
+ def baseline_norm(a, dtype=None):
84
+ """Reference L2 norm at f64 precision."""
85
+ return np.linalg.norm(np.asarray(a).astype(np.float64))
86
+
87
+
88
+ def precise_sum(a, dtype=None):
89
+ """High-precision sum via Decimal."""
90
+ with precise_decimal(dtype) as (upcast, _sqrt, _ln):
91
+ return float(sum(upcast(x) for x in a))
92
+
93
+
94
+ def precise_min(a, dtype=None):
95
+ return float(min(float(x) for x in a))
96
+
97
+
98
+ def precise_max(a, dtype=None):
99
+ return float(max(float(x) for x in a))
100
+
101
+
102
+ def precise_argmin(a, dtype=None):
103
+ values = [float(x) for x in a]
104
+ return values.index(min(values))
105
+
106
+
107
+ def precise_argmax(a, dtype=None):
108
+ values = [float(x) for x in a]
109
+ return values.index(max(values))
110
+
111
+
112
+ def precise_norm(a, dtype=None):
113
+ """High-precision L2 norm via Decimal."""
114
+ with precise_decimal(dtype) as (upcast, sqrt, _ln):
115
+ return float(sqrt(sum(upcast(x) ** 2 for x in a)))
116
+
117
+
118
+ KERNELS_REDUCE: dict[str, tuple[Callable | None, Callable, Callable | None]] = {
119
+ "moments": (baseline_moments, nk.moments, None),
120
+ "minmax": (
121
+ lambda a: (np.asarray(a).min(), np.asarray(a).argmin(), np.asarray(a).max(), np.asarray(a).argmax()),
122
+ nk.minmax,
123
+ None,
124
+ ),
125
+ "sum": (baseline_sum, nk.sum, precise_sum),
126
+ "min": (baseline_min, nk.min, precise_min),
127
+ "max": (baseline_max, nk.max, precise_max),
128
+ "argmin": (baseline_argmin, nk.argmin, precise_argmin),
129
+ "argmax": (baseline_argmax, nk.argmax, precise_argmax),
130
+ "norm": (baseline_norm, nk.norm, precise_norm),
131
+ }
132
+
133
+
134
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
135
+ @pytest.mark.repeat(randomized_repetitions_count)
136
+ @pytest.mark.parametrize("ndim", dense_dimensions)
137
+ @pytest.mark.parametrize(
138
+ "dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32"), pytest.param("float16", id="f16")]
139
+ )
140
+ @pytest.mark.parametrize("capability", possible_capabilities)
141
+ def test_moments(ndim: int, dtype: str, capability: str):
142
+ """Test nk.moments() against NumPy sum and sum-of-squares."""
143
+ keep_one_capability(capability)
144
+ np_arr = np.random.randn(ndim).astype(dtype)
145
+ nk_arr = make_nk(np_arr, dtype)
146
+
147
+ baseline_kernel, simd_kernel, _ = KERNELS_REDUCE["moments"]
148
+ nk_sum, nk_sum_sq = simd_kernel(nk_arr)
149
+
150
+ expected_sum, expected_sum_sq = baseline_kernel(np_arr)
151
+ atol, rtol = tolerances_for_dtype(dtype)
152
+ assert_allclose(nk_sum, expected_sum, rtol=rtol, atol=atol)
153
+ assert_allclose(nk_sum_sq, expected_sum_sq, rtol=rtol, atol=atol)
154
+
155
+
156
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
157
+ @pytest.mark.repeat(randomized_repetitions_count)
158
+ @pytest.mark.parametrize("ndim", dense_dimensions)
159
+ @pytest.mark.parametrize(
160
+ "dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32"), pytest.param("float16", id="f16")]
161
+ )
162
+ @pytest.mark.parametrize("capability", possible_capabilities)
163
+ def test_minmax(ndim: int, dtype: str, capability: str):
164
+ """Test nk.minmax() against NumPy min/argmin/max/argmax."""
165
+ keep_one_capability(capability)
166
+ np_arr = np.random.randn(ndim).astype(dtype)
167
+ nk_arr = make_nk(np_arr, dtype)
168
+
169
+ baseline_kernel, simd_kernel, _ = KERNELS_REDUCE["minmax"]
170
+ nk_min, nk_argmin, nk_max, nk_argmax = simd_kernel(nk_arr)
171
+
172
+ ref_min, ref_argmin, ref_max, ref_argmax = baseline_kernel(np_arr)
173
+ atol, rtol = tolerances_for_dtype(dtype)
174
+ assert_allclose(nk_min, ref_min, rtol=rtol, atol=atol)
175
+ assert int(nk_argmin) == ref_argmin
176
+ assert_allclose(nk_max, ref_max, rtol=rtol, atol=atol)
177
+ assert int(nk_argmax) == ref_argmax
178
+
179
+
180
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
181
+ @pytest.mark.parametrize("ndim", [1, 7, 16, 97])
182
+ @pytest.mark.parametrize(
183
+ "dtype",
184
+ [
185
+ pytest.param("float64", id="f64"),
186
+ pytest.param("float32", id="f32"),
187
+ pytest.param("float16", id="f16"),
188
+ ],
189
+ )
190
+ @pytest.mark.parametrize("capability", possible_capabilities)
191
+ def test_minmax_all_nan(ndim: int, dtype: str, capability: str):
192
+ """All-NaN input returns None from minmax."""
193
+ keep_one_capability(capability)
194
+ np_arr = np.full(ndim, np.nan, dtype=dtype)
195
+ nk_arr = make_nk(np_arr, dtype)
196
+ assert nk.minmax(nk_arr) is None
197
+
198
+
199
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
200
+ @pytest.mark.parametrize(
201
+ "dtype",
202
+ [
203
+ pytest.param("float64", id="f64"),
204
+ pytest.param("float32", id="f32"),
205
+ pytest.param("float16", id="f16"),
206
+ ],
207
+ )
208
+ @pytest.mark.parametrize("capability", possible_capabilities)
209
+ def test_minmax_mixed_nan(dtype: str, capability: str):
210
+ """Mixed NaN + valid values returns correct min/max."""
211
+ keep_one_capability(capability)
212
+ np_arr = np.array([np.nan, 3.0, np.nan, 1.0, np.nan, 5.0, np.nan], dtype=dtype)
213
+ nk_arr = make_nk(np_arr, dtype)
214
+ result = nk.minmax(nk_arr)
215
+ assert result is not None
216
+ nk_min, nk_argmin, nk_max, nk_argmax = result
217
+ assert float(nk_min) == pytest.approx(1.0, abs=0.1)
218
+ assert int(nk_argmin) == 3
219
+ assert float(nk_max) == pytest.approx(5.0, abs=0.1)
220
+ assert int(nk_argmax) == 5
221
+
222
+
223
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
224
+ @pytest.mark.parametrize("ndim", [1, 7, 16, 97])
225
+ @pytest.mark.parametrize(
226
+ "dtype",
227
+ [
228
+ pytest.param("float64", id="f64"),
229
+ pytest.param("float32", id="f32"),
230
+ pytest.param("float16", id="f16"),
231
+ ],
232
+ )
233
+ @pytest.mark.parametrize("capability", possible_capabilities)
234
+ def test_individual_reductions_all_nan(ndim: int, dtype: str, capability: str):
235
+ """All-NaN input returns None from min, max, argmin, argmax."""
236
+ keep_one_capability(capability)
237
+ np_arr = np.full(ndim, np.nan, dtype=dtype)
238
+ nk_arr = make_nk(np_arr, dtype)
239
+ assert nk.min(nk_arr) is None
240
+ assert nk.max(nk_arr) is None
241
+ assert nk.argmin(nk_arr) is None
242
+ assert nk.argmax(nk_arr) is None
243
+
244
+
245
+ @pytest.mark.repeat(randomized_repetitions_count)
246
+ @pytest.mark.parametrize("ndim", dense_dimensions)
247
+ @pytest.mark.parametrize(
248
+ "dtype",
249
+ [
250
+ pytest.param("float64", id="f64"),
251
+ pytest.param("float32", id="f32"),
252
+ pytest.param("float16", id="f16"),
253
+ ],
254
+ )
255
+ @pytest.mark.parametrize("metric", ["sum", "min", "max", "norm", "argmin", "argmax"])
256
+ @pytest.mark.parametrize("capability", possible_capabilities)
257
+ def test_module_level_reductions(ndim: int, dtype: str, capability: str, metric: str, nk_seed: int):
258
+ """Test scalar reductions via KERNELS_REDUCE lookup."""
259
+ keep_one_capability(capability)
260
+ raw, baseline = make_random((ndim,), dtype, seed=nk_seed)
261
+ nk_arr = make_nk(raw, dtype) if numpy_available else raw
262
+
263
+ baseline_kernel, simd_kernel, precise_kernel = KERNELS_REDUCE[metric]
264
+ accurate = (precise_kernel or baseline_kernel)(baseline, dtype=dtype)
265
+ result = simd_kernel(nk_arr)
266
+
267
+ if metric in ("argmin", "argmax"):
268
+ assert result == accurate
269
+ else:
270
+ assert_allclose(result, accurate, atol=NK_ATOL, rtol=NK_RTOL)
271
+
272
+
273
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
274
+ @pytest.mark.repeat(randomized_repetitions_count)
275
+ @pytest.mark.parametrize("dtype", [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")])
276
+ @pytest.mark.parametrize("capability", possible_capabilities)
277
+ def test_sum_axis(dtype: str, capability: str):
278
+ """sum(axis=) on 2D and 3D tensors vs NumPy, including out= parameter."""
279
+ keep_one_capability(capability)
280
+ atol, rtol = tolerances_for_dtype(dtype)
281
+ # 2D
282
+ np_arr = np.random.randn(5, 7).astype(dtype)
283
+ nk_arr = make_nk(np_arr, dtype)
284
+ for axis in [0, 1]:
285
+ result = np.asarray(nk_arr.sum(axis=axis))
286
+ expected = np_arr.astype(np.float64).sum(axis=axis)
287
+ assert_allclose(result, expected, rtol=rtol, atol=atol)
288
+ # 3D
289
+ np_arr3 = np.random.randn(3, 4, 5).astype(dtype)
290
+ nk_arr3 = make_nk(np_arr3, dtype)
291
+ for axis in [0, 1, 2]:
292
+ result = np.asarray(nk_arr3.sum(axis=axis))
293
+ expected = np_arr3.astype(np.float64).sum(axis=axis)
294
+ assert_allclose(result, expected, rtol=rtol, atol=atol)
295
+ # out= parameter: writes to pre-allocated tensor, returns it
296
+ out = nk.zeros((7,), dtype="float64")
297
+ ret = nk_arr.sum(axis=0, out=out)
298
+ assert np.asarray(ret).ctypes.data == np.asarray(out).ctypes.data
299
+ assert_allclose(np.asarray(out), np_arr.astype(np.float64).sum(axis=0), rtol=rtol, atol=atol)
300
+ # out= shape mismatch raises
301
+ with pytest.raises(ValueError):
302
+ nk_arr.sum(axis=0, out=nk.zeros((999,), dtype="float64"))
303
+
304
+
305
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
306
+ @pytest.mark.repeat(randomized_repetitions_count)
307
+ @pytest.mark.parametrize(
308
+ "dtype",
309
+ [
310
+ pytest.param("float64", id="f64"),
311
+ pytest.param("float32", id="f32"),
312
+ ],
313
+ )
314
+ @pytest.mark.parametrize("capability", possible_capabilities)
315
+ def test_min_max_axis(dtype: str, capability: str):
316
+ """min/max(axis=) on 2D tensors vs NumPy, including out= path."""
317
+ keep_one_capability(capability)
318
+ np_arr = np.random.randn(6, 8).astype(dtype)
319
+ nk_arr = make_nk(np_arr, dtype)
320
+ atol, rtol = tolerances_for_dtype(dtype)
321
+ for axis in [0, 1]:
322
+ result_min = np.asarray(nk_arr.min(axis=axis))
323
+ result_max = np.asarray(nk_arr.max(axis=axis))
324
+ assert_allclose(result_min, np_arr.min(axis=axis), rtol=rtol, atol=atol)
325
+ assert_allclose(result_max, np_arr.max(axis=axis), rtol=rtol, atol=atol)
326
+ # out= must match the allocated result
327
+ out_min = nk.zeros(result_min.shape, dtype=nk_arr.dtype)
328
+ out_max = nk.zeros(result_max.shape, dtype=nk_arr.dtype)
329
+ nk_arr.min(axis=axis, out=out_min)
330
+ nk_arr.max(axis=axis, out=out_max)
331
+ assert_allclose(np.asarray(out_min), result_min, rtol=rtol, atol=atol)
332
+ assert_allclose(np.asarray(out_max), result_max, rtol=rtol, atol=atol)
333
+
334
+
335
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
336
+ @pytest.mark.repeat(randomized_repetitions_count)
337
+ @pytest.mark.parametrize(
338
+ "dtype",
339
+ [
340
+ pytest.param("float64", id="f64"),
341
+ pytest.param("float32", id="f32"),
342
+ ],
343
+ )
344
+ @pytest.mark.parametrize("capability", possible_capabilities)
345
+ def test_argmin_argmax_axis(dtype: str, capability: str):
346
+ """argmin/argmax(axis=) on 2D tensors vs NumPy."""
347
+ keep_one_capability(capability)
348
+ np_arr = np.random.randn(6, 8).astype(dtype)
349
+ nk_arr = make_nk(np_arr, dtype)
350
+ for axis in [0, 1]:
351
+ nk_argmin = np.asarray(nk_arr.argmin(axis=axis))
352
+ nk_argmax = np.asarray(nk_arr.argmax(axis=axis))
353
+ np.testing.assert_array_equal(nk_argmin, np_arr.argmin(axis=axis))
354
+ np.testing.assert_array_equal(nk_argmax, np_arr.argmax(axis=axis))
355
+
356
+
357
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
358
+ @pytest.mark.repeat(randomized_repetitions_count)
359
+ @pytest.mark.parametrize(
360
+ "dtype",
361
+ [
362
+ pytest.param("float64", id="f64"),
363
+ pytest.param("float32", id="f32"),
364
+ ],
365
+ )
366
+ @pytest.mark.parametrize("capability", possible_capabilities)
367
+ def test_norm_axis(dtype: str, capability: str):
368
+ """norm(axis=) vs np.linalg.norm(x, axis=), including out= path."""
369
+ keep_one_capability(capability)
370
+ atol, rtol = tolerances_for_dtype(dtype)
371
+ np_arr = np.random.randn(5, 7).astype(dtype)
372
+ nk_arr = make_nk(np_arr, dtype)
373
+ for axis in [0, 1]:
374
+ result = np.asarray(nk_arr.norm(axis=axis))
375
+ expected = np.linalg.norm(np_arr.astype(np.float64), axis=axis)
376
+ assert_allclose(result, expected, rtol=rtol, atol=atol)
377
+ # out= must match the allocated result
378
+ out = nk.zeros(result.shape, dtype="float64")
379
+ nk_arr.norm(axis=axis, out=out)
380
+ assert_allclose(np.asarray(out), result, rtol=rtol, atol=atol)
381
+
382
+
383
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
384
+ @pytest.mark.repeat(randomized_repetitions_count)
385
+ @pytest.mark.parametrize(
386
+ "dtype",
387
+ [
388
+ pytest.param("float64", id="f64"),
389
+ pytest.param("float32", id="f32"),
390
+ ],
391
+ )
392
+ @pytest.mark.parametrize("capability", possible_capabilities)
393
+ def test_keepdims(dtype: str, capability: str):
394
+ """keepdims=True preserves rank with size-1 at reduced axis."""
395
+ keep_one_capability(capability)
396
+ np_arr = np.random.randn(4, 5).astype(dtype)
397
+ nk_arr = make_nk(np_arr, dtype)
398
+ for axis in [0, 1]:
399
+ expected_shape = list(np_arr.shape)
400
+ expected_shape[axis] = 1
401
+ for op in ["sum", "min", "max", "norm"]:
402
+ result = getattr(nk_arr, op)(axis=axis, keepdims=True)
403
+ assert result.ndim == 2, f"{op} keepdims ndim"
404
+ assert list(result.shape) == expected_shape, f"{op} keepdims shape"
405
+
406
+
407
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
408
+ @pytest.mark.repeat(randomized_repetitions_count)
409
+ @pytest.mark.parametrize("capability", possible_capabilities)
410
+ def test_module_level_axis(capability: str):
411
+ """nk.sum(a, axis=0), nk.min(a, axis=1), etc."""
412
+ keep_one_capability(capability)
413
+ np_arr = np.random.randn(4, 5).astype("float64")
414
+ nk_arr = make_nk(np_arr, "float64")
415
+ assert_allclose(np.asarray(nk.sum(nk_arr, axis=0)), np_arr.sum(axis=0))
416
+ assert_allclose(np.asarray(nk.min(nk_arr, axis=1)), np_arr.min(axis=1))
417
+ assert_allclose(np.asarray(nk.max(nk_arr, axis=0)), np_arr.max(axis=0))
418
+ np.testing.assert_array_equal(np.asarray(nk.argmin(nk_arr, axis=1)), np_arr.argmin(axis=1))
419
+ np.testing.assert_array_equal(np.asarray(nk.argmax(nk_arr, axis=0)), np_arr.argmax(axis=0))
420
+
421
+
422
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
423
+ @pytest.mark.repeat(randomized_repetitions_count)
424
+ @pytest.mark.parametrize("capability", possible_capabilities)
425
+ def test_negative_axis(capability: str):
426
+ """axis=-1 on 2D and axis=-2 on 3D reduce the correct dimension."""
427
+ keep_one_capability(capability)
428
+ # 2D, axis=-1
429
+ np_arr = np.random.randn(3, 4).astype("float64")
430
+ nk_arr = make_nk(np_arr, "float64")
431
+ result = np.asarray(nk_arr.sum(axis=-1))
432
+ expected = np_arr.sum(axis=-1)
433
+ assert_allclose(result, expected)
434
+ # 3D, axis=-2
435
+ np_arr3 = np.random.randn(2, 3, 4).astype("float64")
436
+ nk_arr3 = make_nk(np_arr3, "float64")
437
+ result3 = np.asarray(nk_arr3.sum(axis=-2))
438
+ expected3 = np_arr3.sum(axis=-2)
439
+ assert_allclose(result3, expected3)
440
+
441
+
442
+ @pytest.mark.parametrize("capability", possible_capabilities)
443
+ def test_axis_error(capability: str):
444
+ """axis out of range raises ValueError."""
445
+ keep_one_capability(capability)
446
+ nk_arr = nk.zeros((3, 4), dtype="float64")
447
+ with pytest.raises(ValueError, match="axis.*out of range"):
448
+ nk_arr.sum(axis=2)
449
+ with pytest.raises(ValueError, match="axis.*out of range"):
450
+ nk_arr.sum(axis=-3)
451
+
452
+
453
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
454
+ @pytest.mark.parametrize("capability", possible_capabilities)
455
+ def test_integer_axis_reductions(capability: str):
456
+ """sum/min/max/argmin/argmax along axis on int32 tensors."""
457
+ keep_one_capability(capability)
458
+ np_arr = np.array([[10, 3, 7], [1, 8, 5], [4, 9, 2]], dtype=np.int32)
459
+ nk_arr = make_nk(np_arr, "int32")
460
+ for axis in [0, 1]:
461
+ assert_allclose(np.asarray(nk_arr.sum(axis=axis)), np_arr.astype(np.float64).sum(axis=axis))
462
+ np.testing.assert_array_equal(np.asarray(nk_arr.min(axis=axis)), np_arr.min(axis=axis))
463
+ np.testing.assert_array_equal(np.asarray(nk_arr.max(axis=axis)), np_arr.max(axis=axis))
464
+ np.testing.assert_array_equal(np.asarray(nk_arr.argmin(axis=axis)), np_arr.argmin(axis=axis))
465
+ np.testing.assert_array_equal(np.asarray(nk_arr.argmax(axis=axis)), np_arr.argmax(axis=axis))
466
+
467
+
468
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
469
+ @pytest.mark.parametrize("capability", possible_capabilities)
470
+ def test_norm_integer(capability: str):
471
+ """norm(axis=) on int32 must include sqrt (the fix norm_slice was made for)."""
472
+ keep_one_capability(capability)
473
+ np_arr = np.array([[3, 4], [5, 12]], dtype=np.int32)
474
+ nk_arr = make_nk(np_arr, "int32")
475
+ for axis in [0, 1]:
476
+ result = np.asarray(nk_arr.norm(axis=axis))
477
+ expected = np.linalg.norm(np_arr.astype(np.float64), axis=axis)
478
+ assert_allclose(result, expected)
479
+
480
+
481
+ @pytest.mark.parametrize("ndim", algebraic_ndims)
482
+ @pytest.mark.parametrize("dtype", algebraic_dtypes)
483
+ @pytest.mark.parametrize("capability", possible_capabilities)
484
+ def test_sum_known(ndim: int, dtype: str, capability: str):
485
+ """sum(ones(n)) ~ n."""
486
+ keep_one_capability(capability)
487
+ ones_tensor = nk.ones((ndim,), dtype=dtype)
488
+ result = ones_tensor.sum()
489
+ assert abs(result - ndim) < 0.1 + 0.1 * ndim
490
+
491
+
492
+ @pytest.mark.parametrize("ndim", algebraic_ndims)
493
+ @pytest.mark.parametrize("dtype", algebraic_dtypes)
494
+ @pytest.mark.parametrize("capability", possible_capabilities)
495
+ def test_norm_known(ndim: int, dtype: str, capability: str):
496
+ """norm(ones(n)) ~ sqrt(n)."""
497
+ keep_one_capability(capability)
498
+ ones_tensor = nk.ones((ndim,), dtype=dtype)
499
+ result = nk.norm(ones_tensor)
500
+ expected = math.sqrt(ndim)
501
+ assert abs(result - expected) < 0.1 + 0.1 * expected
502
+
503
+
504
+ @pytest.mark.parametrize("ndim", algebraic_ndims)
505
+ @pytest.mark.parametrize("dtype", algebraic_dtypes)
506
+ @pytest.mark.parametrize("capability", possible_capabilities)
507
+ def test_min_max_known(ndim: int, dtype: str, capability: str):
508
+ """min(full(c)) = max(full(c)) = c."""
509
+ keep_one_capability(capability)
510
+ fill_value = 3.14
511
+ constant_tensor = nk.full((ndim,), fill_value, dtype=dtype)
512
+ assert abs(constant_tensor.min() - fill_value) < 0.01
513
+ assert abs(constant_tensor.max() - fill_value) < 0.01
514
+
515
+
516
+ @pytest.mark.parametrize("ndim", algebraic_ndims)
517
+ @pytest.mark.parametrize("dtype", algebraic_dtypes)
518
+ @pytest.mark.parametrize("capability", possible_capabilities)
519
+ def test_argmin_argmax_constant(ndim: int, dtype: str, capability: str):
520
+ """For a constant tensor, argmin and argmax return valid indices in [0, n)."""
521
+ keep_one_capability(capability)
522
+ constant_tensor = nk.full((ndim,), 2.0, dtype=dtype)
523
+ assert 0 <= constant_tensor.argmin() < ndim
524
+ assert 0 <= constant_tensor.argmax() < ndim
525
+
526
+
527
+ # region Multi-axis reductions
528
+
529
+
530
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
531
+ @pytest.mark.repeat(randomized_repetitions_count)
532
+ @pytest.mark.parametrize(
533
+ "dtype",
534
+ [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")],
535
+ )
536
+ @pytest.mark.parametrize("capability", possible_capabilities)
537
+ def test_multi_axis_sum(dtype: str, capability: str):
538
+ """sum(axis=tuple) on 3D tensor vs NumPy."""
539
+ keep_one_capability(capability)
540
+ atol, rtol = tolerances_for_dtype(dtype)
541
+ np_arr = np.random.randn(3, 4, 5).astype(dtype)
542
+ nk_arr = make_nk(np_arr, dtype)
543
+ for axes in [(0, 1), (1, 2), (0, 2), (0, 1, 2)]:
544
+ result = np.asarray(nk_arr.sum(axis=axes))
545
+ expected = np_arr.astype(np.float64).sum(axis=axes)
546
+ assert_allclose(result, expected, rtol=rtol, atol=atol)
547
+
548
+
549
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
550
+ @pytest.mark.repeat(randomized_repetitions_count)
551
+ @pytest.mark.parametrize(
552
+ "dtype",
553
+ [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")],
554
+ )
555
+ @pytest.mark.parametrize("capability", possible_capabilities)
556
+ def test_multi_axis_min_max(dtype: str, capability: str):
557
+ """min/max(axis=tuple) on 3D tensor vs NumPy."""
558
+ keep_one_capability(capability)
559
+ atol, rtol = tolerances_for_dtype(dtype)
560
+ np_arr = np.random.randn(3, 4, 5).astype(dtype)
561
+ nk_arr = make_nk(np_arr, dtype)
562
+ for axes in [(0, 1), (1, 2), (0, 2)]:
563
+ assert_allclose(np.asarray(nk_arr.min(axis=axes)), np_arr.min(axis=axes), rtol=rtol, atol=atol)
564
+ assert_allclose(np.asarray(nk_arr.max(axis=axes)), np_arr.max(axis=axes), rtol=rtol, atol=atol)
565
+
566
+
567
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
568
+ @pytest.mark.repeat(randomized_repetitions_count)
569
+ @pytest.mark.parametrize(
570
+ "dtype",
571
+ [pytest.param("float64", id="f64"), pytest.param("float32", id="f32")],
572
+ )
573
+ @pytest.mark.parametrize("capability", possible_capabilities)
574
+ def test_multi_axis_norm(dtype: str, capability: str):
575
+ """norm(axis=tuple) vs manual sqrt(sum(x**2, axis=axes))."""
576
+ keep_one_capability(capability)
577
+ atol, rtol = tolerances_for_dtype(dtype)
578
+ np_arr = np.random.randn(3, 4, 5).astype(dtype)
579
+ nk_arr = make_nk(np_arr, dtype)
580
+ for axes in [(0, 1), (1, 2), (0, 2)]:
581
+ result = np.asarray(nk_arr.norm(axis=axes))
582
+ expected = np.sqrt(np.sum(np_arr.astype(np.float64) ** 2, axis=axes))
583
+ assert_allclose(result, expected, rtol=rtol, atol=atol)
584
+
585
+
586
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
587
+ @pytest.mark.parametrize("capability", possible_capabilities)
588
+ def test_multi_axis_keepdims(capability: str):
589
+ """keepdims=True preserves rank with size-1 at each reduced axis."""
590
+ keep_one_capability(capability)
591
+ np_arr = np.random.randn(3, 4, 5).astype(np.float64)
592
+ nk_arr = make_nk(np_arr, "float64")
593
+ for axes in [(0, 2), (0, 1), (1, 2), (0, 1, 2)]:
594
+ result = nk_arr.sum(axis=axes, keepdims=True)
595
+ expected = np_arr.sum(axis=axes, keepdims=True)
596
+ assert result.shape == expected.shape, f"axes={axes}: {result.shape} != {expected.shape}"
597
+ assert_allclose(np.asarray(result), expected)
598
+
599
+
600
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
601
+ @pytest.mark.parametrize("capability", possible_capabilities)
602
+ def test_multi_axis_negative(capability: str):
603
+ """Negative indices in axis tuple are normalized correctly."""
604
+ keep_one_capability(capability)
605
+ np_arr = np.random.randn(3, 4, 5).astype(np.float64)
606
+ nk_arr = make_nk(np_arr, "float64")
607
+ result = np.asarray(nk_arr.sum(axis=(-1, 0)))
608
+ expected = np_arr.sum(axis=(0, 2)) # -1 -> 2, sorted -> (0, 2)
609
+ assert_allclose(result, expected)
610
+
611
+
612
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
613
+ @pytest.mark.parametrize("capability", possible_capabilities)
614
+ def test_multi_axis_single_element_tuple(capability: str):
615
+ """axis=(1,) should behave identically to axis=1."""
616
+ keep_one_capability(capability)
617
+ np_arr = np.random.randn(3, 4, 5).astype(np.float64)
618
+ nk_arr = make_nk(np_arr, "float64")
619
+ result_tuple = np.asarray(nk_arr.sum(axis=(1,)))
620
+ result_int = np.asarray(nk_arr.sum(axis=1))
621
+ assert_allclose(result_tuple, result_int)
622
+
623
+
624
+ @pytest.mark.parametrize("capability", possible_capabilities)
625
+ def test_multi_axis_errors(capability: str):
626
+ """Error cases for multi-axis reductions."""
627
+ keep_one_capability(capability)
628
+ nk_arr = nk.zeros((3, 4, 5), dtype="float64")
629
+
630
+ # Duplicate axes
631
+ with pytest.raises(ValueError, match="duplicate"):
632
+ nk_arr.sum(axis=(1, 1))
633
+
634
+ # Out of range
635
+ with pytest.raises(ValueError, match="out of range"):
636
+ nk_arr.sum(axis=(0, 5))
637
+
638
+ # Empty tuple
639
+ with pytest.raises(ValueError, match="empty"):
640
+ nk_arr.sum(axis=())
641
+
642
+ # argmin/argmax reject tuple axis
643
+ with pytest.raises(TypeError):
644
+ nk_arr.argmin(axis=(0, 1))
645
+ with pytest.raises(TypeError):
646
+ nk_arr.argmax(axis=(0, 1))
647
+
648
+ # Non-int in tuple
649
+ with pytest.raises(TypeError):
650
+ nk_arr.sum(axis=(0, 1.5))
651
+
652
+
653
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
654
+ @pytest.mark.parametrize("capability", possible_capabilities)
655
+ def test_multi_axis_module_level(capability: str):
656
+ """Module-level nk.sum(arr, axis=tuple) works for buffer-protocol inputs."""
657
+ keep_one_capability(capability)
658
+ np_arr = np.random.randn(3, 4, 5).astype(np.float64)
659
+ result = np.asarray(nk.sum(np_arr, axis=(0, 2)))
660
+ expected = np_arr.sum(axis=(0, 2))
661
+ assert_allclose(result, expected)
662
+
663
+
664
+ # endregion Multi-axis reductions
665
+
666
+ # region N-D tensor contraction tests
667
+ #
668
+ # These test the stride-collapsing optimization paths: uniform-stride tail detection,
669
+ # recursive re-analysis, and axis-reduction fast paths on contiguous non-axis dims.
670
+ # Each case is validated against a flattened-then-reduced reference (for global reductions)
671
+ # or NumPy (for axis reductions) to isolate the contraction logic from SIMD correctness.
672
+
673
+
674
+ def _nd_global_case(description, np_arr):
675
+ """Verify global reduction on an N-D view matches a flat contiguous copy."""
676
+ flat = np.ascontiguousarray(np_arr).reshape(-1)
677
+ nk_sum, nk_sumsq = nk.moments(np_arr)
678
+ ref_sum, ref_sumsq = nk.moments(nk.Tensor(flat))
679
+ assert_allclose(nk_sum, ref_sum, err_msg=f"{description}: sum")
680
+ assert_allclose(nk_sumsq, ref_sumsq, err_msg=f"{description}: sumsq")
681
+ assert nk.min(np_arr) == nk.min(nk.Tensor(flat)), f"{description}: min mismatch"
682
+ assert nk.max(np_arr) == nk.max(nk.Tensor(flat)), f"{description}: max mismatch"
683
+
684
+
685
+ def _nd_axis_case(description, np_arr, axis):
686
+ """Verify axis reduction matches NumPy."""
687
+ atol, rtol = tolerances_for_dtype(str(np_arr.dtype))
688
+ nk_result = np.asarray(nk.sum(np_arr, axis=axis))
689
+ np_result = np_arr.astype(np.float64).sum(axis=axis)
690
+ assert_allclose(nk_result, np_result, atol=atol, rtol=rtol, err_msg=description)
691
+
692
+
693
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
694
+ @pytest.mark.parametrize("dtype", ["uint8", "float32"])
695
+ def test_nd_contiguous_global_reduction(dtype: str):
696
+ """Global reduction on contiguous N-D tensors: all dims should collapse into one kernel call."""
697
+ np_dtype = np.dtype(dtype)
698
+ rng = np.random.RandomState(42)
699
+ for shape in [(4, 64, 64, 3), (2, 3, 4, 5, 6), (8, 12, 16), (1, 1, 256)]:
700
+ arr = (
701
+ rng.randint(0, 100, shape).astype(np_dtype)
702
+ if np.issubdtype(np_dtype, np.integer)
703
+ else rng.randn(*shape).astype(np_dtype)
704
+ )
705
+ _nd_global_case(f"contiguous {shape} {dtype}", arr)
706
+
707
+
708
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
709
+ @pytest.mark.parametrize("dtype", ["uint8", "float32"])
710
+ def test_nd_strided_global_reduction(dtype: str):
711
+ """Global reduction on non-contiguous but uniformly-strided subviews."""
712
+ np_dtype = np.dtype(dtype)
713
+ rng = np.random.RandomState(42)
714
+
715
+ # Channel subview: (H, W, C)[:, :, k] → uniform stride = C * itemsize
716
+ base = (
717
+ rng.randint(0, 100, (64, 64, 3)).astype(np_dtype)
718
+ if np.issubdtype(np_dtype, np.integer)
719
+ else rng.randn(64, 64, 3).astype(np_dtype)
720
+ )
721
+ _nd_global_case(f"channel subview {dtype}", base[:, :, 1])
722
+
723
+ # Row skip: (H, W)[::2, :] → first dim breaks, second dim contiguous
724
+ base2d = (
725
+ rng.randint(0, 100, (128, 64)).astype(np_dtype)
726
+ if np.issubdtype(np_dtype, np.integer)
727
+ else rng.randn(128, 64).astype(np_dtype)
728
+ )
729
+ _nd_global_case(f"row skip {dtype}", base2d[::2, :])
730
+
731
+ # Batch skip on 4-D: (B, H, W, C)[::2, :, :, :]
732
+ base4d = (
733
+ rng.randint(0, 100, (4, 32, 32, 3)).astype(np_dtype)
734
+ if np.issubdtype(np_dtype, np.integer)
735
+ else rng.randn(4, 32, 32, 3).astype(np_dtype)
736
+ )
737
+ _nd_global_case(f"batch skip {dtype}", base4d[::2, :, :, :])
738
+
739
+
740
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
741
+ def test_nd_reversed_global_reduction():
742
+ """Global reduction on reversed (negative stride) views."""
743
+ rng = np.random.RandomState(42)
744
+ arr = rng.randint(0, 200, (512,), dtype=np.uint8)
745
+ _nd_global_case("reversed 1D", arr[::-1])
746
+ arr2d = rng.randn(64, 32).astype(np.float32)
747
+ _nd_global_case("reversed last dim", arr2d[:, ::-1])
748
+
749
+
750
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
751
+ @pytest.mark.parametrize("axis", [0, 1, 2, 3, -1])
752
+ def test_nd_axis_reduction_contiguous(axis):
753
+ """Axis reductions on contiguous 4-D image-like tensor."""
754
+ rng = np.random.RandomState(42)
755
+ arr = rng.randn(4, 32, 32, 3).astype(np.float32)
756
+ _nd_axis_case(f"axis={axis} on (4,32,32,3)", arr, axis)
757
+
758
+
759
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
760
+ def test_nd_axis_reduction_on_subview():
761
+ """Axis reductions on non-contiguous subviews."""
762
+ rng = np.random.RandomState(42)
763
+ base = rng.randn(8, 64, 64, 3).astype(np.float32)
764
+ # Skip every other batch → non-contiguous outer dim, contiguous inner dims
765
+ sub = base[::2, :, :, :]
766
+ _nd_axis_case("axis=0 on batch-skipped", sub, 0)
767
+ _nd_axis_case("axis=-1 on batch-skipped", sub, -1)
768
+ _nd_axis_case("axis=2 on batch-skipped", sub, 2)
769
+
770
+
771
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
772
+ def test_nd_singleton_dims():
773
+ """Reduction through singleton dimensions (extent=1) that should collapse freely."""
774
+ rng = np.random.RandomState(42)
775
+ arr = rng.randn(1, 64, 1, 64, 1).astype(np.float32)
776
+ _nd_global_case("singleton dims", arr)
777
+ _nd_axis_case("singleton axis=1", arr, 1)
778
+ _nd_axis_case("singleton axis=3", arr, 3)
779
+
780
+
781
+ # endregion N-D tensor contraction tests