numkong 7.0.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 (294) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +495 -0
  3. package/binding.gyp +540 -0
  4. package/c/dispatch.h +512 -0
  5. package/c/dispatch_bf16.c +389 -0
  6. package/c/dispatch_bf16c.c +52 -0
  7. package/c/dispatch_e2m3.c +263 -0
  8. package/c/dispatch_e3m2.c +243 -0
  9. package/c/dispatch_e4m3.c +276 -0
  10. package/c/dispatch_e5m2.c +272 -0
  11. package/c/dispatch_f16.c +376 -0
  12. package/c/dispatch_f16c.c +58 -0
  13. package/c/dispatch_f32.c +378 -0
  14. package/c/dispatch_f32c.c +99 -0
  15. package/c/dispatch_f64.c +296 -0
  16. package/c/dispatch_f64c.c +98 -0
  17. package/c/dispatch_i16.c +96 -0
  18. package/c/dispatch_i32.c +89 -0
  19. package/c/dispatch_i4.c +150 -0
  20. package/c/dispatch_i64.c +86 -0
  21. package/c/dispatch_i8.c +289 -0
  22. package/c/dispatch_other.c +330 -0
  23. package/c/dispatch_u1.c +148 -0
  24. package/c/dispatch_u16.c +124 -0
  25. package/c/dispatch_u32.c +118 -0
  26. package/c/dispatch_u4.c +150 -0
  27. package/c/dispatch_u64.c +102 -0
  28. package/c/dispatch_u8.c +303 -0
  29. package/c/numkong.c +950 -0
  30. package/include/README.md +573 -0
  31. package/include/module.modulemap +129 -0
  32. package/include/numkong/attention/sapphireamx.h +1361 -0
  33. package/include/numkong/attention/sme.h +2066 -0
  34. package/include/numkong/attention.h +49 -0
  35. package/include/numkong/capabilities.h +748 -0
  36. package/include/numkong/cast/README.md +262 -0
  37. package/include/numkong/cast/haswell.h +975 -0
  38. package/include/numkong/cast/icelake.h +470 -0
  39. package/include/numkong/cast/neon.h +1192 -0
  40. package/include/numkong/cast/rvv.h +1021 -0
  41. package/include/numkong/cast/sapphire.h +262 -0
  42. package/include/numkong/cast/serial.h +2262 -0
  43. package/include/numkong/cast/skylake.h +856 -0
  44. package/include/numkong/cast/v128relaxed.h +180 -0
  45. package/include/numkong/cast.h +230 -0
  46. package/include/numkong/curved/README.md +223 -0
  47. package/include/numkong/curved/genoa.h +182 -0
  48. package/include/numkong/curved/haswell.h +276 -0
  49. package/include/numkong/curved/neon.h +205 -0
  50. package/include/numkong/curved/neonbfdot.h +212 -0
  51. package/include/numkong/curved/neonhalf.h +212 -0
  52. package/include/numkong/curved/rvv.h +305 -0
  53. package/include/numkong/curved/serial.h +207 -0
  54. package/include/numkong/curved/skylake.h +457 -0
  55. package/include/numkong/curved/smef64.h +506 -0
  56. package/include/numkong/curved.h +517 -0
  57. package/include/numkong/curved.hpp +144 -0
  58. package/include/numkong/dot/README.md +425 -0
  59. package/include/numkong/dot/alder.h +563 -0
  60. package/include/numkong/dot/genoa.h +315 -0
  61. package/include/numkong/dot/haswell.h +1688 -0
  62. package/include/numkong/dot/icelake.h +883 -0
  63. package/include/numkong/dot/neon.h +818 -0
  64. package/include/numkong/dot/neonbfdot.h +244 -0
  65. package/include/numkong/dot/neonfhm.h +360 -0
  66. package/include/numkong/dot/neonhalf.h +198 -0
  67. package/include/numkong/dot/neonsdot.h +508 -0
  68. package/include/numkong/dot/rvv.h +714 -0
  69. package/include/numkong/dot/rvvbb.h +72 -0
  70. package/include/numkong/dot/rvvbf16.h +123 -0
  71. package/include/numkong/dot/rvvhalf.h +129 -0
  72. package/include/numkong/dot/sapphire.h +141 -0
  73. package/include/numkong/dot/serial.h +838 -0
  74. package/include/numkong/dot/sierra.h +405 -0
  75. package/include/numkong/dot/skylake.h +1084 -0
  76. package/include/numkong/dot/sve.h +379 -0
  77. package/include/numkong/dot/svebfdot.h +74 -0
  78. package/include/numkong/dot/svehalf.h +123 -0
  79. package/include/numkong/dot/v128relaxed.h +1258 -0
  80. package/include/numkong/dot.h +1070 -0
  81. package/include/numkong/dot.hpp +94 -0
  82. package/include/numkong/dots/README.md +496 -0
  83. package/include/numkong/dots/alder.h +114 -0
  84. package/include/numkong/dots/genoa.h +94 -0
  85. package/include/numkong/dots/haswell.h +295 -0
  86. package/include/numkong/dots/icelake.h +171 -0
  87. package/include/numkong/dots/neon.h +120 -0
  88. package/include/numkong/dots/neonbfdot.h +58 -0
  89. package/include/numkong/dots/neonfhm.h +94 -0
  90. package/include/numkong/dots/neonhalf.h +57 -0
  91. package/include/numkong/dots/neonsdot.h +108 -0
  92. package/include/numkong/dots/rvv.h +2486 -0
  93. package/include/numkong/dots/sapphireamx.h +3973 -0
  94. package/include/numkong/dots/serial.h +2844 -0
  95. package/include/numkong/dots/sierra.h +97 -0
  96. package/include/numkong/dots/skylake.h +196 -0
  97. package/include/numkong/dots/sme.h +5372 -0
  98. package/include/numkong/dots/smebi32.h +461 -0
  99. package/include/numkong/dots/smef64.h +1318 -0
  100. package/include/numkong/dots/smehalf.h +47 -0
  101. package/include/numkong/dots/v128relaxed.h +294 -0
  102. package/include/numkong/dots.h +2804 -0
  103. package/include/numkong/dots.hpp +639 -0
  104. package/include/numkong/each/README.md +469 -0
  105. package/include/numkong/each/haswell.h +1658 -0
  106. package/include/numkong/each/icelake.h +272 -0
  107. package/include/numkong/each/neon.h +1104 -0
  108. package/include/numkong/each/neonbfdot.h +212 -0
  109. package/include/numkong/each/neonhalf.h +410 -0
  110. package/include/numkong/each/rvv.h +1121 -0
  111. package/include/numkong/each/sapphire.h +477 -0
  112. package/include/numkong/each/serial.h +260 -0
  113. package/include/numkong/each/skylake.h +1562 -0
  114. package/include/numkong/each.h +2146 -0
  115. package/include/numkong/each.hpp +434 -0
  116. package/include/numkong/geospatial/README.md +147 -0
  117. package/include/numkong/geospatial/haswell.h +593 -0
  118. package/include/numkong/geospatial/neon.h +571 -0
  119. package/include/numkong/geospatial/rvv.h +701 -0
  120. package/include/numkong/geospatial/serial.h +309 -0
  121. package/include/numkong/geospatial/skylake.h +577 -0
  122. package/include/numkong/geospatial/v128relaxed.h +613 -0
  123. package/include/numkong/geospatial.h +453 -0
  124. package/include/numkong/geospatial.hpp +235 -0
  125. package/include/numkong/matrix.hpp +336 -0
  126. package/include/numkong/maxsim/README.md +187 -0
  127. package/include/numkong/maxsim/alder.h +511 -0
  128. package/include/numkong/maxsim/genoa.h +115 -0
  129. package/include/numkong/maxsim/haswell.h +553 -0
  130. package/include/numkong/maxsim/icelake.h +480 -0
  131. package/include/numkong/maxsim/neonsdot.h +394 -0
  132. package/include/numkong/maxsim/sapphireamx.h +877 -0
  133. package/include/numkong/maxsim/serial.h +490 -0
  134. package/include/numkong/maxsim/sme.h +929 -0
  135. package/include/numkong/maxsim/v128relaxed.h +280 -0
  136. package/include/numkong/maxsim.h +571 -0
  137. package/include/numkong/maxsim.hpp +133 -0
  138. package/include/numkong/mesh/README.md +227 -0
  139. package/include/numkong/mesh/haswell.h +2235 -0
  140. package/include/numkong/mesh/neon.h +1329 -0
  141. package/include/numkong/mesh/neonbfdot.h +842 -0
  142. package/include/numkong/mesh/neonhalf.h +616 -0
  143. package/include/numkong/mesh/rvv.h +916 -0
  144. package/include/numkong/mesh/serial.h +742 -0
  145. package/include/numkong/mesh/skylake.h +1135 -0
  146. package/include/numkong/mesh/v128relaxed.h +1052 -0
  147. package/include/numkong/mesh.h +652 -0
  148. package/include/numkong/mesh.hpp +762 -0
  149. package/include/numkong/numkong.h +78 -0
  150. package/include/numkong/numkong.hpp +57 -0
  151. package/include/numkong/probability/README.md +173 -0
  152. package/include/numkong/probability/haswell.h +267 -0
  153. package/include/numkong/probability/neon.h +225 -0
  154. package/include/numkong/probability/rvv.h +409 -0
  155. package/include/numkong/probability/serial.h +169 -0
  156. package/include/numkong/probability/skylake.h +324 -0
  157. package/include/numkong/probability.h +383 -0
  158. package/include/numkong/probability.hpp +120 -0
  159. package/include/numkong/random.h +50 -0
  160. package/include/numkong/random.hpp +285 -0
  161. package/include/numkong/reduce/README.md +547 -0
  162. package/include/numkong/reduce/alder.h +632 -0
  163. package/include/numkong/reduce/genoa.h +201 -0
  164. package/include/numkong/reduce/haswell.h +3783 -0
  165. package/include/numkong/reduce/icelake.h +549 -0
  166. package/include/numkong/reduce/neon.h +3841 -0
  167. package/include/numkong/reduce/neonbfdot.h +353 -0
  168. package/include/numkong/reduce/neonfhm.h +665 -0
  169. package/include/numkong/reduce/neonhalf.h +157 -0
  170. package/include/numkong/reduce/neonsdot.h +357 -0
  171. package/include/numkong/reduce/rvv.h +3407 -0
  172. package/include/numkong/reduce/serial.h +757 -0
  173. package/include/numkong/reduce/sierra.h +338 -0
  174. package/include/numkong/reduce/skylake.h +3792 -0
  175. package/include/numkong/reduce/v128relaxed.h +2302 -0
  176. package/include/numkong/reduce.h +1597 -0
  177. package/include/numkong/reduce.hpp +633 -0
  178. package/include/numkong/scalar/README.md +89 -0
  179. package/include/numkong/scalar/haswell.h +113 -0
  180. package/include/numkong/scalar/neon.h +122 -0
  181. package/include/numkong/scalar/neonhalf.h +70 -0
  182. package/include/numkong/scalar/rvv.h +211 -0
  183. package/include/numkong/scalar/sapphire.h +63 -0
  184. package/include/numkong/scalar/serial.h +332 -0
  185. package/include/numkong/scalar/v128relaxed.h +56 -0
  186. package/include/numkong/scalar.h +683 -0
  187. package/include/numkong/set/README.md +179 -0
  188. package/include/numkong/set/haswell.h +334 -0
  189. package/include/numkong/set/icelake.h +485 -0
  190. package/include/numkong/set/neon.h +364 -0
  191. package/include/numkong/set/rvv.h +226 -0
  192. package/include/numkong/set/rvvbb.h +117 -0
  193. package/include/numkong/set/serial.h +174 -0
  194. package/include/numkong/set/sve.h +185 -0
  195. package/include/numkong/set/v128relaxed.h +240 -0
  196. package/include/numkong/set.h +457 -0
  197. package/include/numkong/set.hpp +114 -0
  198. package/include/numkong/sets/README.md +149 -0
  199. package/include/numkong/sets/haswell.h +63 -0
  200. package/include/numkong/sets/icelake.h +66 -0
  201. package/include/numkong/sets/neon.h +61 -0
  202. package/include/numkong/sets/serial.h +43 -0
  203. package/include/numkong/sets/smebi32.h +1099 -0
  204. package/include/numkong/sets/v128relaxed.h +58 -0
  205. package/include/numkong/sets.h +339 -0
  206. package/include/numkong/sparse/README.md +156 -0
  207. package/include/numkong/sparse/icelake.h +463 -0
  208. package/include/numkong/sparse/neon.h +288 -0
  209. package/include/numkong/sparse/serial.h +117 -0
  210. package/include/numkong/sparse/sve2.h +507 -0
  211. package/include/numkong/sparse/turin.h +322 -0
  212. package/include/numkong/sparse.h +363 -0
  213. package/include/numkong/sparse.hpp +113 -0
  214. package/include/numkong/spatial/README.md +435 -0
  215. package/include/numkong/spatial/alder.h +607 -0
  216. package/include/numkong/spatial/genoa.h +290 -0
  217. package/include/numkong/spatial/haswell.h +960 -0
  218. package/include/numkong/spatial/icelake.h +586 -0
  219. package/include/numkong/spatial/neon.h +773 -0
  220. package/include/numkong/spatial/neonbfdot.h +165 -0
  221. package/include/numkong/spatial/neonhalf.h +118 -0
  222. package/include/numkong/spatial/neonsdot.h +261 -0
  223. package/include/numkong/spatial/rvv.h +984 -0
  224. package/include/numkong/spatial/rvvbf16.h +123 -0
  225. package/include/numkong/spatial/rvvhalf.h +117 -0
  226. package/include/numkong/spatial/sapphire.h +343 -0
  227. package/include/numkong/spatial/serial.h +346 -0
  228. package/include/numkong/spatial/sierra.h +323 -0
  229. package/include/numkong/spatial/skylake.h +606 -0
  230. package/include/numkong/spatial/sve.h +224 -0
  231. package/include/numkong/spatial/svebfdot.h +122 -0
  232. package/include/numkong/spatial/svehalf.h +109 -0
  233. package/include/numkong/spatial/v128relaxed.h +717 -0
  234. package/include/numkong/spatial.h +1425 -0
  235. package/include/numkong/spatial.hpp +183 -0
  236. package/include/numkong/spatials/README.md +580 -0
  237. package/include/numkong/spatials/alder.h +94 -0
  238. package/include/numkong/spatials/genoa.h +94 -0
  239. package/include/numkong/spatials/haswell.h +219 -0
  240. package/include/numkong/spatials/icelake.h +113 -0
  241. package/include/numkong/spatials/neon.h +109 -0
  242. package/include/numkong/spatials/neonbfdot.h +60 -0
  243. package/include/numkong/spatials/neonfhm.h +92 -0
  244. package/include/numkong/spatials/neonhalf.h +58 -0
  245. package/include/numkong/spatials/neonsdot.h +109 -0
  246. package/include/numkong/spatials/rvv.h +1960 -0
  247. package/include/numkong/spatials/sapphireamx.h +1149 -0
  248. package/include/numkong/spatials/serial.h +226 -0
  249. package/include/numkong/spatials/sierra.h +96 -0
  250. package/include/numkong/spatials/skylake.h +184 -0
  251. package/include/numkong/spatials/sme.h +1901 -0
  252. package/include/numkong/spatials/smef64.h +465 -0
  253. package/include/numkong/spatials/v128relaxed.h +240 -0
  254. package/include/numkong/spatials.h +3021 -0
  255. package/include/numkong/spatials.hpp +508 -0
  256. package/include/numkong/tensor.hpp +1592 -0
  257. package/include/numkong/trigonometry/README.md +184 -0
  258. package/include/numkong/trigonometry/haswell.h +652 -0
  259. package/include/numkong/trigonometry/neon.h +639 -0
  260. package/include/numkong/trigonometry/rvv.h +699 -0
  261. package/include/numkong/trigonometry/serial.h +703 -0
  262. package/include/numkong/trigonometry/skylake.h +721 -0
  263. package/include/numkong/trigonometry/v128relaxed.h +666 -0
  264. package/include/numkong/trigonometry.h +467 -0
  265. package/include/numkong/trigonometry.hpp +166 -0
  266. package/include/numkong/types.h +1384 -0
  267. package/include/numkong/types.hpp +5603 -0
  268. package/include/numkong/vector.hpp +698 -0
  269. package/javascript/README.md +246 -0
  270. package/javascript/dist/cjs/numkong-wasm.d.ts +166 -0
  271. package/javascript/dist/cjs/numkong-wasm.js +617 -0
  272. package/javascript/dist/cjs/numkong.d.ts +343 -0
  273. package/javascript/dist/cjs/numkong.js +523 -0
  274. package/javascript/dist/cjs/package.json +3 -0
  275. package/javascript/dist/cjs/types.d.ts +284 -0
  276. package/javascript/dist/cjs/types.js +653 -0
  277. package/javascript/dist/esm/numkong-wasm.d.ts +166 -0
  278. package/javascript/dist/esm/numkong-wasm.js +595 -0
  279. package/javascript/dist/esm/numkong.d.ts +343 -0
  280. package/javascript/dist/esm/numkong.js +452 -0
  281. package/javascript/dist/esm/package.json +3 -0
  282. package/javascript/dist/esm/types.d.ts +284 -0
  283. package/javascript/dist/esm/types.js +630 -0
  284. package/javascript/dist-package-cjs.json +3 -0
  285. package/javascript/dist-package-esm.json +3 -0
  286. package/javascript/node-gyp-build.d.ts +1 -0
  287. package/javascript/numkong-wasm.ts +756 -0
  288. package/javascript/numkong.c +689 -0
  289. package/javascript/numkong.ts +575 -0
  290. package/javascript/tsconfig-base.json +39 -0
  291. package/javascript/tsconfig-cjs.json +8 -0
  292. package/javascript/tsconfig-esm.json +8 -0
  293. package/javascript/types.ts +674 -0
  294. package/package.json +87 -0
@@ -0,0 +1,613 @@
1
+ /**
2
+ * @brief SIMD-accelerated Geospatial Distances for WASM.
3
+ * @file include/numkong/geospatial/v128relaxed.h
4
+ * @author Ash Vardanian
5
+ * @date February 6, 2026
6
+ *
7
+ * @sa include/numkong/geospatial.h
8
+ *
9
+ * Implements Haversine and Vincenty great-circle distances for f32x4 and f64x2.
10
+ * Haversine uses sin/cos/atan2 with min/max clamping to keep the atan2 argument in [0,1].
11
+ * Vincenty iterates sin/cos/atan2 until convergence, using `i8x16_all_true` to test whether
12
+ * all SIMD lanes have converged without per-lane extraction.
13
+ *
14
+ * @section geospatial_wasm_instructions Key WASM SIMD Instructions (beyond trig)
15
+ *
16
+ * Intrinsic Operation
17
+ * wasm_f32x4_sqrt(a) Square root (4-way f32)
18
+ * wasm_f64x2_sqrt(a) Square root (2-way f64)
19
+ * wasm_f32x4_div(a, b) Division (4-way f32)
20
+ * wasm_f64x2_div(a, b) Division (2-way f64)
21
+ * wasm_f32x4_min/max(a, b) Clamping for Haversine
22
+ * wasm_f64x2_min/max(a, b) Clamping for Haversine
23
+ * wasm_f32x4_relaxed_min/max(a, b) Min/max without NaN fixup (1 vs 6-9 on x86)
24
+ * wasm_f64x2_relaxed_min/max(a, b) Min/max without NaN fixup (1 vs 6-9 on x86)
25
+ * wasm_i32x4_relaxed_laneselect(a, b, m) Lane select (1 instr vs 3 on x86)
26
+ * wasm_i64x2_relaxed_laneselect(a, b, m) Lane select for f64 masks
27
+ * wasm_i8x16_all_true(a) Vincenty convergence check (all lanes at once)
28
+ */
29
+ #ifndef NK_GEOSPATIAL_V128RELAXED_H
30
+ #define NK_GEOSPATIAL_V128RELAXED_H
31
+
32
+ #if NK_TARGET_V128RELAXED
33
+
34
+ #include "numkong/types.h"
35
+ #include "numkong/trigonometry/v128relaxed.h"
36
+
37
+ #if defined(__cplusplus)
38
+ extern "C" {
39
+ #endif
40
+
41
+ #if defined(__clang__)
42
+ #pragma clang attribute push(__attribute__((target("relaxed-simd"))), apply_to = function)
43
+ #endif
44
+
45
+ /* WASM Relaxed SIMD implementations using 2-wide f64 and 4-wide f32 SIMD.
46
+ * These require WASM trigonometric kernels from trigonometry/v128relaxed.h.
47
+ */
48
+
49
+ NK_INTERNAL v128_t nk_haversine_f64x2_v128relaxed_( //
50
+ v128_t first_latitudes, v128_t first_longitudes, //
51
+ v128_t second_latitudes, v128_t second_longitudes) {
52
+
53
+ v128_t const earth_radius = wasm_f64x2_splat(NK_EARTH_MEDIATORIAL_RADIUS);
54
+ v128_t const half = wasm_f64x2_splat(0.5);
55
+ v128_t const one = wasm_f64x2_splat(1.0);
56
+ v128_t const two = wasm_f64x2_splat(2.0);
57
+
58
+ v128_t latitude_delta = wasm_f64x2_sub(second_latitudes, first_latitudes);
59
+ v128_t longitude_delta = wasm_f64x2_sub(second_longitudes, first_longitudes);
60
+
61
+ // Haversine terms: sin^2(delta/2)
62
+ v128_t latitude_delta_half = wasm_f64x2_mul(latitude_delta, half);
63
+ v128_t longitude_delta_half = wasm_f64x2_mul(longitude_delta, half);
64
+ v128_t sin_latitude_delta_half = nk_f64x2_sin_v128relaxed_(latitude_delta_half);
65
+ v128_t sin_longitude_delta_half = nk_f64x2_sin_v128relaxed_(longitude_delta_half);
66
+ v128_t sin_squared_latitude_delta_half = wasm_f64x2_mul(sin_latitude_delta_half, sin_latitude_delta_half);
67
+ v128_t sin_squared_longitude_delta_half = wasm_f64x2_mul(sin_longitude_delta_half, sin_longitude_delta_half);
68
+
69
+ // Latitude cosine product
70
+ v128_t cos_first_latitude = nk_f64x2_cos_v128relaxed_(first_latitudes);
71
+ v128_t cos_second_latitude = nk_f64x2_cos_v128relaxed_(second_latitudes);
72
+ v128_t cos_latitude_product = wasm_f64x2_mul(cos_first_latitude, cos_second_latitude);
73
+
74
+ // a = sin^2(dlat/2) + cos(lat1) * cos(lat2) * sin^2(dlon/2)
75
+ v128_t haversine_term = wasm_f64x2_add(sin_squared_latitude_delta_half,
76
+ wasm_f64x2_mul(cos_latitude_product, sin_squared_longitude_delta_half));
77
+ // Clamp haversine_term to [0, 1] to prevent NaN from sqrt of negative values
78
+ // relaxed_min/max: 1 instruction (minpd/maxpd) vs 6-9 (with NaN/signed-zero fixup) on x86.
79
+ // Safe because haversine_term is a product of finite sin/cos values — NaN is impossible.
80
+ v128_t zero = wasm_f64x2_splat(0.0);
81
+ haversine_term = wasm_f64x2_relaxed_max(zero, wasm_f64x2_relaxed_min(one, haversine_term));
82
+
83
+ // Central angle: c = 2 * atan2(sqrt(a), sqrt(1-a))
84
+ v128_t sqrt_haversine = wasm_f64x2_sqrt(haversine_term);
85
+ v128_t sqrt_complement = wasm_f64x2_sqrt(wasm_f64x2_sub(one, haversine_term));
86
+ v128_t central_angle = wasm_f64x2_mul(two, nk_f64x2_atan2_v128relaxed_(sqrt_haversine, sqrt_complement));
87
+
88
+ return wasm_f64x2_mul(earth_radius, central_angle);
89
+ }
90
+
91
+ NK_PUBLIC void nk_haversine_f64_v128relaxed( //
92
+ nk_f64_t const *a_lats, nk_f64_t const *a_lons, //
93
+ nk_f64_t const *b_lats, nk_f64_t const *b_lons, //
94
+ nk_size_t n, nk_f64_t *results) {
95
+
96
+ while (n >= 2) {
97
+ v128_t first_latitudes = wasm_v128_load(a_lats);
98
+ v128_t first_longitudes = wasm_v128_load(a_lons);
99
+ v128_t second_latitudes = wasm_v128_load(b_lats);
100
+ v128_t second_longitudes = wasm_v128_load(b_lons);
101
+
102
+ v128_t distances = nk_haversine_f64x2_v128relaxed_(first_latitudes, first_longitudes, second_latitudes,
103
+ second_longitudes);
104
+ wasm_v128_store(results, distances);
105
+
106
+ a_lats += 2, a_lons += 2, b_lats += 2, b_lons += 2, results += 2, n -= 2;
107
+ }
108
+
109
+ // Handle tail with partial loads (n can only be 0 or 1 here)
110
+ if (n > 0) {
111
+ nk_b128_vec_t a_lat_vec, a_lon_vec, b_lat_vec, b_lon_vec, result_vec;
112
+ nk_partial_load_b64x2_serial_(a_lats, &a_lat_vec, n);
113
+ nk_partial_load_b64x2_serial_(a_lons, &a_lon_vec, n);
114
+ nk_partial_load_b64x2_serial_(b_lats, &b_lat_vec, n);
115
+ nk_partial_load_b64x2_serial_(b_lons, &b_lon_vec, n);
116
+ v128_t distances = nk_haversine_f64x2_v128relaxed_(a_lat_vec.v128, a_lon_vec.v128, b_lat_vec.v128,
117
+ b_lon_vec.v128);
118
+ result_vec.v128 = distances;
119
+ nk_partial_store_b64x2_serial_(&result_vec, results, n);
120
+ }
121
+ }
122
+
123
+ NK_INTERNAL v128_t nk_haversine_f32x4_v128relaxed_( //
124
+ v128_t first_latitudes, v128_t first_longitudes, //
125
+ v128_t second_latitudes, v128_t second_longitudes) {
126
+
127
+ v128_t const earth_radius = wasm_f32x4_splat((float)NK_EARTH_MEDIATORIAL_RADIUS);
128
+ v128_t const half = wasm_f32x4_splat(0.5f);
129
+ v128_t const one = wasm_f32x4_splat(1.0f);
130
+ v128_t const two = wasm_f32x4_splat(2.0f);
131
+
132
+ v128_t latitude_delta = wasm_f32x4_sub(second_latitudes, first_latitudes);
133
+ v128_t longitude_delta = wasm_f32x4_sub(second_longitudes, first_longitudes);
134
+
135
+ // Haversine terms: sin^2(delta/2)
136
+ v128_t latitude_delta_half = wasm_f32x4_mul(latitude_delta, half);
137
+ v128_t longitude_delta_half = wasm_f32x4_mul(longitude_delta, half);
138
+ v128_t sin_latitude_delta_half = nk_f32x4_sin_v128relaxed_(latitude_delta_half);
139
+ v128_t sin_longitude_delta_half = nk_f32x4_sin_v128relaxed_(longitude_delta_half);
140
+ v128_t sin_squared_latitude_delta_half = wasm_f32x4_mul(sin_latitude_delta_half, sin_latitude_delta_half);
141
+ v128_t sin_squared_longitude_delta_half = wasm_f32x4_mul(sin_longitude_delta_half, sin_longitude_delta_half);
142
+
143
+ // Latitude cosine product
144
+ v128_t cos_first_latitude = nk_f32x4_cos_v128relaxed_(first_latitudes);
145
+ v128_t cos_second_latitude = nk_f32x4_cos_v128relaxed_(second_latitudes);
146
+ v128_t cos_latitude_product = wasm_f32x4_mul(cos_first_latitude, cos_second_latitude);
147
+
148
+ // a = sin^2(dlat/2) + cos(lat1) * cos(lat2) * sin^2(dlon/2)
149
+ v128_t haversine_term = wasm_f32x4_add(sin_squared_latitude_delta_half,
150
+ wasm_f32x4_mul(cos_latitude_product, sin_squared_longitude_delta_half));
151
+
152
+ // Clamp to [0, 1] to avoid NaN from sqrt of negative numbers (due to floating point errors)
153
+ // relaxed_min/max: 1 instruction (minps/maxps) vs 6-9 (with NaN/signed-zero fixup) on x86.
154
+ // Safe because haversine_term is a product of finite sin/cos values — NaN is impossible.
155
+ v128_t zero = wasm_f32x4_splat(0.0f);
156
+ haversine_term = wasm_f32x4_relaxed_max(zero, wasm_f32x4_relaxed_min(one, haversine_term));
157
+
158
+ // Central angle: c = 2 * atan2(sqrt(a), sqrt(1-a))
159
+ v128_t sqrt_haversine = wasm_f32x4_sqrt(haversine_term);
160
+ v128_t sqrt_complement = wasm_f32x4_sqrt(wasm_f32x4_sub(one, haversine_term));
161
+ v128_t central_angle = wasm_f32x4_mul(two, nk_f32x4_atan2_v128relaxed_(sqrt_haversine, sqrt_complement));
162
+
163
+ return wasm_f32x4_mul(earth_radius, central_angle);
164
+ }
165
+
166
+ NK_PUBLIC void nk_haversine_f32_v128relaxed( //
167
+ nk_f32_t const *a_lats, nk_f32_t const *a_lons, //
168
+ nk_f32_t const *b_lats, nk_f32_t const *b_lons, //
169
+ nk_size_t n, nk_f32_t *results) {
170
+
171
+ while (n >= 4) {
172
+ v128_t first_latitudes = wasm_v128_load(a_lats);
173
+ v128_t first_longitudes = wasm_v128_load(a_lons);
174
+ v128_t second_latitudes = wasm_v128_load(b_lats);
175
+ v128_t second_longitudes = wasm_v128_load(b_lons);
176
+
177
+ v128_t distances = nk_haversine_f32x4_v128relaxed_(first_latitudes, first_longitudes, second_latitudes,
178
+ second_longitudes);
179
+ wasm_v128_store(results, distances);
180
+
181
+ a_lats += 4, a_lons += 4, b_lats += 4, b_lons += 4, results += 4, n -= 4;
182
+ }
183
+
184
+ // Handle tail with partial loads (n can be 0-3 here)
185
+ if (n > 0) {
186
+ nk_b128_vec_t a_lat_vec, a_lon_vec, b_lat_vec, b_lon_vec, result_vec;
187
+ nk_partial_load_b32x4_serial_(a_lats, &a_lat_vec, n);
188
+ nk_partial_load_b32x4_serial_(a_lons, &a_lon_vec, n);
189
+ nk_partial_load_b32x4_serial_(b_lats, &b_lat_vec, n);
190
+ nk_partial_load_b32x4_serial_(b_lons, &b_lon_vec, n);
191
+ v128_t distances = nk_haversine_f32x4_v128relaxed_(a_lat_vec.v128, a_lon_vec.v128, b_lat_vec.v128,
192
+ b_lon_vec.v128);
193
+ result_vec.v128 = distances;
194
+ nk_partial_store_b32x4_serial_(&result_vec, results, n);
195
+ }
196
+ }
197
+
198
+ /**
199
+ * @brief WASM Relaxed SIMD helper for Vincenty's geodesic distance on 2 f64 point pairs.
200
+ * @note This is a true SIMD implementation using masked convergence tracking via blending.
201
+ */
202
+ NK_INTERNAL v128_t nk_vincenty_f64x2_v128relaxed_( //
203
+ v128_t first_latitudes, v128_t first_longitudes, //
204
+ v128_t second_latitudes, v128_t second_longitudes) {
205
+
206
+ v128_t const equatorial_radius = wasm_f64x2_splat(NK_EARTH_ELLIPSOID_EQUATORIAL_RADIUS);
207
+ v128_t const polar_radius = wasm_f64x2_splat(NK_EARTH_ELLIPSOID_POLAR_RADIUS);
208
+ v128_t const flattening = wasm_f64x2_splat(1.0 / NK_EARTH_ELLIPSOID_INVERSE_FLATTENING);
209
+ v128_t const convergence_threshold = wasm_f64x2_splat(NK_VINCENTY_CONVERGENCE_THRESHOLD_F64);
210
+ v128_t const one = wasm_f64x2_splat(1.0);
211
+ v128_t const two = wasm_f64x2_splat(2.0);
212
+ v128_t const three = wasm_f64x2_splat(3.0);
213
+ v128_t const four = wasm_f64x2_splat(4.0);
214
+ v128_t const six = wasm_f64x2_splat(6.0);
215
+ v128_t const sixteen = wasm_f64x2_splat(16.0);
216
+ v128_t const epsilon = wasm_f64x2_splat(1e-15);
217
+
218
+ // Longitude difference
219
+ v128_t longitude_difference = wasm_f64x2_sub(second_longitudes, first_longitudes);
220
+
221
+ // Reduced latitudes: tan(U) = (1-f) * tan(lat)
222
+ v128_t one_minus_f = wasm_f64x2_sub(one, flattening);
223
+ v128_t tan_first = wasm_f64x2_div(nk_f64x2_sin_v128relaxed_(first_latitudes),
224
+ nk_f64x2_cos_v128relaxed_(first_latitudes));
225
+ v128_t tan_second = wasm_f64x2_div(nk_f64x2_sin_v128relaxed_(second_latitudes),
226
+ nk_f64x2_cos_v128relaxed_(second_latitudes));
227
+ v128_t tan_reduced_first = wasm_f64x2_mul(one_minus_f, tan_first);
228
+ v128_t tan_reduced_second = wasm_f64x2_mul(one_minus_f, tan_second);
229
+
230
+ // cos(U) = 1/sqrt(1 + tan^2(U)), sin(U) = tan(U) * cos(U)
231
+ v128_t cos_reduced_first = wasm_f64x2_div(
232
+ one, wasm_f64x2_sqrt(wasm_f64x2_relaxed_madd(tan_reduced_first, tan_reduced_first, one)));
233
+ v128_t sin_reduced_first = wasm_f64x2_mul(tan_reduced_first, cos_reduced_first);
234
+ v128_t cos_reduced_second = wasm_f64x2_div(
235
+ one, wasm_f64x2_sqrt(wasm_f64x2_relaxed_madd(tan_reduced_second, tan_reduced_second, one)));
236
+ v128_t sin_reduced_second = wasm_f64x2_mul(tan_reduced_second, cos_reduced_second);
237
+
238
+ // Initialize lambda and tracking variables
239
+ v128_t lambda = longitude_difference;
240
+ v128_t sin_angular_distance, cos_angular_distance, angular_distance;
241
+ v128_t sin_azimuth, cos_squared_azimuth, cos_double_angular_midpoint;
242
+
243
+ // Track convergence and coincident points using masks
244
+ v128_t converged_mask = wasm_i64x2_splat(0);
245
+ v128_t coincident_mask = wasm_i64x2_splat(0);
246
+
247
+ for (nk_u32_t iteration = 0; iteration < NK_VINCENTY_MAX_ITERATIONS; ++iteration) {
248
+ // Check if all lanes converged
249
+ if (wasm_i8x16_all_true(converged_mask)) break;
250
+
251
+ v128_t sin_lambda = nk_f64x2_sin_v128relaxed_(lambda);
252
+ v128_t cos_lambda = nk_f64x2_cos_v128relaxed_(lambda);
253
+
254
+ // sin^2(angular_distance) = (cos(U2) * sin(l))^2 + (cos(U1) * sin(U2) - sin(U1) * cos(U2) * cos(l))^2
255
+ v128_t cross_term = wasm_f64x2_mul(cos_reduced_second, sin_lambda);
256
+ v128_t mixed_term = wasm_f64x2_sub(
257
+ wasm_f64x2_mul(cos_reduced_first, sin_reduced_second),
258
+ wasm_f64x2_mul(wasm_f64x2_mul(sin_reduced_first, cos_reduced_second), cos_lambda));
259
+ v128_t sin_angular_dist_sq = wasm_f64x2_relaxed_madd(cross_term, cross_term,
260
+ wasm_f64x2_mul(mixed_term, mixed_term));
261
+ sin_angular_distance = wasm_f64x2_sqrt(sin_angular_dist_sq);
262
+
263
+ // Check for coincident points (sin_angular_distance ~ 0)
264
+ coincident_mask = wasm_f64x2_lt(sin_angular_distance, epsilon);
265
+
266
+ // cos(angular_distance) = sin(U1) * sin(U2) + cos(U1) * cos(U2) * cos(l)
267
+ cos_angular_distance = wasm_f64x2_relaxed_madd(wasm_f64x2_mul(cos_reduced_first, cos_reduced_second),
268
+ cos_lambda,
269
+ wasm_f64x2_mul(sin_reduced_first, sin_reduced_second));
270
+
271
+ // angular_distance = atan2(sin, cos)
272
+ angular_distance = nk_f64x2_atan2_v128relaxed_(sin_angular_distance, cos_angular_distance);
273
+
274
+ // sin(azimuth) = cos(U1) * cos(U2) * sin(l) / sin(angular_distance)
275
+ // Avoid division by zero by using blending
276
+ // relaxed_laneselect: 1 instruction (vblendvpd) vs 3 (vpand+vpandn+vpor) on x86.
277
+ // Safe because mask is from comparison (all-ones or all-zeros per lane).
278
+ v128_t safe_sin_angular = wasm_i64x2_relaxed_laneselect(one, sin_angular_distance, coincident_mask);
279
+ sin_azimuth = wasm_f64x2_div(wasm_f64x2_mul(wasm_f64x2_mul(cos_reduced_first, cos_reduced_second), sin_lambda),
280
+ safe_sin_angular);
281
+ cos_squared_azimuth = wasm_f64x2_relaxed_nmadd(sin_azimuth, sin_azimuth, one);
282
+
283
+ // Handle equatorial case: cos^2(a) ~ 0
284
+ v128_t equatorial_mask = wasm_f64x2_lt(cos_squared_azimuth, epsilon);
285
+ v128_t safe_cos_sq_azimuth = wasm_i64x2_relaxed_laneselect(one, cos_squared_azimuth, equatorial_mask);
286
+
287
+ // cos(2sm) = cos(s) - 2 * sin(U1) * sin(U2) / cos^2(a)
288
+ v128_t sin_product = wasm_f64x2_mul(sin_reduced_first, sin_reduced_second);
289
+ cos_double_angular_midpoint = wasm_f64x2_sub(
290
+ cos_angular_distance, wasm_f64x2_div(wasm_f64x2_mul(two, sin_product), safe_cos_sq_azimuth));
291
+ cos_double_angular_midpoint = wasm_i64x2_relaxed_laneselect(wasm_f64x2_splat(0.0), cos_double_angular_midpoint,
292
+ equatorial_mask);
293
+
294
+ // C = f/16 * cos^2(a) * (4 + f*(4 - 3*cos^2(a)))
295
+ v128_t correction_factor = wasm_f64x2_mul(
296
+ wasm_f64x2_div(flattening, sixteen),
297
+ wasm_f64x2_mul(
298
+ cos_squared_azimuth,
299
+ wasm_f64x2_relaxed_madd(flattening, wasm_f64x2_relaxed_nmadd(three, cos_squared_azimuth, four), four)));
300
+
301
+ // l' = L + (1-C) * f * sin(a) * (s + C * sin(s) * (cos(2sm) + C * cos(s) * (-1 + 2 * cos^2(2sm))))
302
+ v128_t cos_2sm_sq = wasm_f64x2_mul(cos_double_angular_midpoint, cos_double_angular_midpoint);
303
+ // innermost = -1 + 2 * cos^2(2sm)
304
+ v128_t innermost = wasm_f64x2_relaxed_madd(two, cos_2sm_sq, wasm_f64x2_splat(-1.0));
305
+ // middle = cos(2sm) + C * cos(s) * innermost
306
+ v128_t middle = wasm_f64x2_relaxed_madd(wasm_f64x2_mul(correction_factor, cos_angular_distance), innermost,
307
+ cos_double_angular_midpoint);
308
+ // inner = C * sin(s) * middle
309
+ v128_t inner = wasm_f64x2_mul(wasm_f64x2_mul(correction_factor, sin_angular_distance), middle);
310
+
311
+ // l' = L + (1-C) * f * sin_a * (s + inner)
312
+ v128_t lambda_new = wasm_f64x2_relaxed_madd(
313
+ wasm_f64x2_mul(wasm_f64x2_mul(wasm_f64x2_sub(one, correction_factor), flattening), sin_azimuth),
314
+ wasm_f64x2_add(angular_distance, inner), longitude_difference);
315
+
316
+ // Check convergence: |l - l'| < threshold
317
+ v128_t lambda_diff = wasm_f64x2_sub(lambda_new, lambda);
318
+ v128_t lambda_diff_abs = wasm_f64x2_abs(lambda_diff);
319
+ v128_t newly_converged = wasm_f64x2_lt(lambda_diff_abs, convergence_threshold);
320
+ converged_mask = wasm_v128_or(converged_mask, newly_converged);
321
+
322
+ // Only update lambda for non-converged lanes
323
+ // relaxed_laneselect: 1 instruction (vblendvpd) vs 3 (vpand+vpandn+vpor) on x86.
324
+ // Safe because mask is from comparison (all-ones or all-zeros per lane).
325
+ lambda = wasm_i64x2_relaxed_laneselect(lambda, lambda_new, converged_mask);
326
+ }
327
+
328
+ // Final distance calculation
329
+ // u^2 = cos^2(a) * (a^2 - b^2) / b^2
330
+ v128_t a_sq = wasm_f64x2_mul(equatorial_radius, equatorial_radius);
331
+ v128_t b_sq = wasm_f64x2_mul(polar_radius, polar_radius);
332
+ v128_t u_squared = wasm_f64x2_div(wasm_f64x2_mul(cos_squared_azimuth, wasm_f64x2_sub(a_sq, b_sq)), b_sq);
333
+
334
+ // A = 1 + u^2/16384 * (4096 + u^2*(-768 + u^2*(320 - 175*u^2)))
335
+ v128_t series_a = wasm_f64x2_relaxed_madd(u_squared, wasm_f64x2_splat(-175.0), wasm_f64x2_splat(320.0));
336
+ series_a = wasm_f64x2_relaxed_madd(u_squared, series_a, wasm_f64x2_splat(-768.0));
337
+ series_a = wasm_f64x2_relaxed_madd(u_squared, series_a, wasm_f64x2_splat(4096.0));
338
+ series_a = wasm_f64x2_relaxed_madd(wasm_f64x2_div(u_squared, wasm_f64x2_splat(16384.0)), series_a, one);
339
+
340
+ // B = u^2/1024 * (256 + u^2*(-128 + u^2*(74 - 47*u^2)))
341
+ v128_t series_b = wasm_f64x2_relaxed_madd(u_squared, wasm_f64x2_splat(-47.0), wasm_f64x2_splat(74.0));
342
+ series_b = wasm_f64x2_relaxed_madd(u_squared, series_b, wasm_f64x2_splat(-128.0));
343
+ series_b = wasm_f64x2_relaxed_madd(u_squared, series_b, wasm_f64x2_splat(256.0));
344
+ series_b = wasm_f64x2_mul(wasm_f64x2_div(u_squared, wasm_f64x2_splat(1024.0)), series_b);
345
+
346
+ // Delta-sigma calculation
347
+ v128_t cos_2sm_sq = wasm_f64x2_mul(cos_double_angular_midpoint, cos_double_angular_midpoint);
348
+ v128_t sin_sq = wasm_f64x2_mul(sin_angular_distance, sin_angular_distance);
349
+ v128_t term1 = wasm_f64x2_relaxed_madd(two, cos_2sm_sq, wasm_f64x2_splat(-1.0));
350
+ term1 = wasm_f64x2_mul(cos_angular_distance, term1);
351
+ v128_t term2 = wasm_f64x2_relaxed_madd(four, sin_sq, wasm_f64x2_splat(-3.0));
352
+ v128_t term3 = wasm_f64x2_relaxed_madd(four, cos_2sm_sq, wasm_f64x2_splat(-3.0));
353
+ term2 = wasm_f64x2_mul(wasm_f64x2_mul(wasm_f64x2_div(series_b, six), cos_double_angular_midpoint),
354
+ wasm_f64x2_mul(term2, term3));
355
+ v128_t delta_sigma = wasm_f64x2_mul(
356
+ series_b, wasm_f64x2_mul(sin_angular_distance, wasm_f64x2_add(cos_double_angular_midpoint,
357
+ wasm_f64x2_mul(wasm_f64x2_div(series_b, four),
358
+ wasm_f64x2_sub(term1, term2)))));
359
+
360
+ // s = b * A * (s - ds)
361
+ v128_t distances = wasm_f64x2_mul(wasm_f64x2_mul(polar_radius, series_a),
362
+ wasm_f64x2_sub(angular_distance, delta_sigma));
363
+
364
+ // Set coincident points to zero
365
+ // relaxed_laneselect: 1 instruction (vblendvpd) vs 3 (vpand+vpandn+vpor) on x86.
366
+ // Safe because mask is from comparison (all-ones or all-zeros per lane).
367
+ distances = wasm_i64x2_relaxed_laneselect(wasm_f64x2_splat(0.0), distances, coincident_mask);
368
+
369
+ return distances;
370
+ }
371
+
372
+ NK_PUBLIC void nk_vincenty_f64_v128relaxed( //
373
+ nk_f64_t const *a_lats, nk_f64_t const *a_lons, //
374
+ nk_f64_t const *b_lats, nk_f64_t const *b_lons, //
375
+ nk_size_t n, nk_f64_t *results) {
376
+
377
+ while (n >= 2) {
378
+ v128_t first_latitudes = wasm_v128_load(a_lats);
379
+ v128_t first_longitudes = wasm_v128_load(a_lons);
380
+ v128_t second_latitudes = wasm_v128_load(b_lats);
381
+ v128_t second_longitudes = wasm_v128_load(b_lons);
382
+
383
+ v128_t distances = nk_vincenty_f64x2_v128relaxed_(first_latitudes, first_longitudes, second_latitudes,
384
+ second_longitudes);
385
+ wasm_v128_store(results, distances);
386
+
387
+ a_lats += 2, a_lons += 2, b_lats += 2, b_lons += 2, results += 2, n -= 2;
388
+ }
389
+
390
+ // Handle remaining elements with partial loads (n can only be 0 or 1 here)
391
+ if (n > 0) {
392
+ nk_b128_vec_t a_lat_vec, a_lon_vec, b_lat_vec, b_lon_vec, result_vec;
393
+ nk_partial_load_b64x2_serial_(a_lats, &a_lat_vec, n);
394
+ nk_partial_load_b64x2_serial_(a_lons, &a_lon_vec, n);
395
+ nk_partial_load_b64x2_serial_(b_lats, &b_lat_vec, n);
396
+ nk_partial_load_b64x2_serial_(b_lons, &b_lon_vec, n);
397
+ v128_t distances = nk_vincenty_f64x2_v128relaxed_(a_lat_vec.v128, a_lon_vec.v128, b_lat_vec.v128,
398
+ b_lon_vec.v128);
399
+ result_vec.v128 = distances;
400
+ nk_partial_store_b64x2_serial_(&result_vec, results, n);
401
+ }
402
+ }
403
+
404
+ /**
405
+ * @brief WASM Relaxed SIMD helper for Vincenty's geodesic distance on 4 f32 point pairs.
406
+ * @note This is a true SIMD implementation using masked convergence tracking via blending.
407
+ */
408
+ NK_INTERNAL v128_t nk_vincenty_f32x4_v128relaxed_( //
409
+ v128_t first_latitudes, v128_t first_longitudes, //
410
+ v128_t second_latitudes, v128_t second_longitudes) {
411
+
412
+ v128_t const equatorial_radius = wasm_f32x4_splat((float)NK_EARTH_ELLIPSOID_EQUATORIAL_RADIUS);
413
+ v128_t const polar_radius = wasm_f32x4_splat((float)NK_EARTH_ELLIPSOID_POLAR_RADIUS);
414
+ v128_t const flattening = wasm_f32x4_splat(1.0f / (float)NK_EARTH_ELLIPSOID_INVERSE_FLATTENING);
415
+ v128_t const convergence_threshold = wasm_f32x4_splat(NK_VINCENTY_CONVERGENCE_THRESHOLD_F32);
416
+ v128_t const one = wasm_f32x4_splat(1.0f);
417
+ v128_t const two = wasm_f32x4_splat(2.0f);
418
+ v128_t const three = wasm_f32x4_splat(3.0f);
419
+ v128_t const four = wasm_f32x4_splat(4.0f);
420
+ v128_t const six = wasm_f32x4_splat(6.0f);
421
+ v128_t const sixteen = wasm_f32x4_splat(16.0f);
422
+ v128_t const epsilon = wasm_f32x4_splat(1e-7f);
423
+
424
+ // Longitude difference
425
+ v128_t longitude_difference = wasm_f32x4_sub(second_longitudes, first_longitudes);
426
+
427
+ // Reduced latitudes: tan(U) = (1-f) * tan(lat)
428
+ v128_t one_minus_f = wasm_f32x4_sub(one, flattening);
429
+ v128_t tan_first = wasm_f32x4_div(nk_f32x4_sin_v128relaxed_(first_latitudes),
430
+ nk_f32x4_cos_v128relaxed_(first_latitudes));
431
+ v128_t tan_second = wasm_f32x4_div(nk_f32x4_sin_v128relaxed_(second_latitudes),
432
+ nk_f32x4_cos_v128relaxed_(second_latitudes));
433
+ v128_t tan_reduced_first = wasm_f32x4_mul(one_minus_f, tan_first);
434
+ v128_t tan_reduced_second = wasm_f32x4_mul(one_minus_f, tan_second);
435
+
436
+ // cos(U) = 1/sqrt(1 + tan^2(U)), sin(U) = tan(U) * cos(U)
437
+ v128_t cos_reduced_first = wasm_f32x4_div(
438
+ one, wasm_f32x4_sqrt(wasm_f32x4_relaxed_madd(tan_reduced_first, tan_reduced_first, one)));
439
+ v128_t sin_reduced_first = wasm_f32x4_mul(tan_reduced_first, cos_reduced_first);
440
+ v128_t cos_reduced_second = wasm_f32x4_div(
441
+ one, wasm_f32x4_sqrt(wasm_f32x4_relaxed_madd(tan_reduced_second, tan_reduced_second, one)));
442
+ v128_t sin_reduced_second = wasm_f32x4_mul(tan_reduced_second, cos_reduced_second);
443
+
444
+ // Initialize lambda and tracking variables
445
+ v128_t lambda = longitude_difference;
446
+ v128_t sin_angular_distance, cos_angular_distance, angular_distance;
447
+ v128_t sin_azimuth, cos_squared_azimuth, cos_double_angular_midpoint;
448
+
449
+ // Track convergence and coincident points using masks
450
+ v128_t converged_mask = wasm_i32x4_splat(0);
451
+ v128_t coincident_mask = wasm_i32x4_splat(0);
452
+
453
+ for (nk_u32_t iteration = 0; iteration < NK_VINCENTY_MAX_ITERATIONS; ++iteration) {
454
+ // Check if all lanes converged
455
+ if (wasm_i8x16_all_true(converged_mask)) break;
456
+
457
+ v128_t sin_lambda = nk_f32x4_sin_v128relaxed_(lambda);
458
+ v128_t cos_lambda = nk_f32x4_cos_v128relaxed_(lambda);
459
+
460
+ // sin^2(angular_distance) = (cos(U2) * sin(l))^2 + (cos(U1) * sin(U2) - sin(U1) * cos(U2) * cos(l))^2
461
+ v128_t cross_term = wasm_f32x4_mul(cos_reduced_second, sin_lambda);
462
+ v128_t mixed_term = wasm_f32x4_sub(
463
+ wasm_f32x4_mul(cos_reduced_first, sin_reduced_second),
464
+ wasm_f32x4_mul(wasm_f32x4_mul(sin_reduced_first, cos_reduced_second), cos_lambda));
465
+ v128_t sin_angular_dist_sq = wasm_f32x4_relaxed_madd(cross_term, cross_term,
466
+ wasm_f32x4_mul(mixed_term, mixed_term));
467
+ sin_angular_distance = wasm_f32x4_sqrt(sin_angular_dist_sq);
468
+
469
+ // Check for coincident points (sin_angular_distance ~ 0)
470
+ coincident_mask = wasm_f32x4_lt(sin_angular_distance, epsilon);
471
+
472
+ // cos(angular_distance) = sin(U1) * sin(U2) + cos(U1) * cos(U2) * cos(l)
473
+ cos_angular_distance = wasm_f32x4_relaxed_madd(wasm_f32x4_mul(cos_reduced_first, cos_reduced_second),
474
+ cos_lambda,
475
+ wasm_f32x4_mul(sin_reduced_first, sin_reduced_second));
476
+
477
+ // angular_distance = atan2(sin, cos)
478
+ angular_distance = nk_f32x4_atan2_v128relaxed_(sin_angular_distance, cos_angular_distance);
479
+
480
+ // sin(azimuth) = cos(U1) * cos(U2) * sin(l) / sin(angular_distance)
481
+ // relaxed_laneselect: 1 instruction (vblendvps) vs 3 (vpand+vpandn+vpor) on x86.
482
+ // Safe because mask is from comparison (all-ones or all-zeros per lane).
483
+ v128_t safe_sin_angular = wasm_i32x4_relaxed_laneselect(one, sin_angular_distance, coincident_mask);
484
+ sin_azimuth = wasm_f32x4_div(wasm_f32x4_mul(wasm_f32x4_mul(cos_reduced_first, cos_reduced_second), sin_lambda),
485
+ safe_sin_angular);
486
+ cos_squared_azimuth = wasm_f32x4_relaxed_nmadd(sin_azimuth, sin_azimuth, one);
487
+
488
+ // Handle equatorial case: cos^2(a) ~ 0
489
+ v128_t equatorial_mask = wasm_f32x4_lt(cos_squared_azimuth, epsilon);
490
+ v128_t safe_cos_sq_azimuth = wasm_i32x4_relaxed_laneselect(one, cos_squared_azimuth, equatorial_mask);
491
+
492
+ // cos(2sm) = cos(s) - 2 * sin(U1) * sin(U2) / cos^2(a)
493
+ v128_t sin_product = wasm_f32x4_mul(sin_reduced_first, sin_reduced_second);
494
+ cos_double_angular_midpoint = wasm_f32x4_sub(
495
+ cos_angular_distance, wasm_f32x4_div(wasm_f32x4_mul(two, sin_product), safe_cos_sq_azimuth));
496
+ cos_double_angular_midpoint = wasm_i32x4_relaxed_laneselect(wasm_f32x4_splat(0.0f), cos_double_angular_midpoint,
497
+ equatorial_mask);
498
+
499
+ // C = f/16 * cos^2(a) * (4 + f*(4 - 3*cos^2(a)))
500
+ v128_t correction_factor = wasm_f32x4_mul(
501
+ wasm_f32x4_div(flattening, sixteen),
502
+ wasm_f32x4_mul(
503
+ cos_squared_azimuth,
504
+ wasm_f32x4_relaxed_madd(flattening, wasm_f32x4_relaxed_nmadd(three, cos_squared_azimuth, four), four)));
505
+
506
+ // l' = L + (1-C) * f * sin(a) * (s + C * sin(s) * (cos(2sm) + C * cos(s) * (-1 + 2 * cos^2(2sm))))
507
+ v128_t cos_2sm_sq = wasm_f32x4_mul(cos_double_angular_midpoint, cos_double_angular_midpoint);
508
+ v128_t innermost = wasm_f32x4_relaxed_madd(two, cos_2sm_sq, wasm_f32x4_splat(-1.0f));
509
+ v128_t middle = wasm_f32x4_relaxed_madd(wasm_f32x4_mul(correction_factor, cos_angular_distance), innermost,
510
+ cos_double_angular_midpoint);
511
+ v128_t inner = wasm_f32x4_mul(wasm_f32x4_mul(correction_factor, sin_angular_distance), middle);
512
+
513
+ v128_t lambda_new = wasm_f32x4_relaxed_madd(
514
+ wasm_f32x4_mul(wasm_f32x4_mul(wasm_f32x4_sub(one, correction_factor), flattening), sin_azimuth),
515
+ wasm_f32x4_add(angular_distance, inner), longitude_difference);
516
+
517
+ // Check convergence: |l - l'| < threshold
518
+ v128_t lambda_diff = wasm_f32x4_sub(lambda_new, lambda);
519
+ v128_t lambda_diff_abs = wasm_f32x4_abs(lambda_diff);
520
+ v128_t newly_converged = wasm_f32x4_lt(lambda_diff_abs, convergence_threshold);
521
+ converged_mask = wasm_v128_or(converged_mask, newly_converged);
522
+
523
+ // Only update lambda for non-converged lanes
524
+ // relaxed_laneselect: 1 instruction (vblendvps) vs 3 (vpand+vpandn+vpor) on x86.
525
+ // Safe because mask is from comparison (all-ones or all-zeros per lane).
526
+ lambda = wasm_i32x4_relaxed_laneselect(lambda, lambda_new, converged_mask);
527
+ }
528
+
529
+ // Final distance calculation
530
+ v128_t a_sq = wasm_f32x4_mul(equatorial_radius, equatorial_radius);
531
+ v128_t b_sq = wasm_f32x4_mul(polar_radius, polar_radius);
532
+ v128_t u_squared = wasm_f32x4_div(wasm_f32x4_mul(cos_squared_azimuth, wasm_f32x4_sub(a_sq, b_sq)), b_sq);
533
+
534
+ // A = 1 + u^2/16384 * (4096 + u^2*(-768 + u^2*(320 - 175*u^2)))
535
+ v128_t series_a = wasm_f32x4_relaxed_madd(u_squared, wasm_f32x4_splat(-175.0f), wasm_f32x4_splat(320.0f));
536
+ series_a = wasm_f32x4_relaxed_madd(u_squared, series_a, wasm_f32x4_splat(-768.0f));
537
+ series_a = wasm_f32x4_relaxed_madd(u_squared, series_a, wasm_f32x4_splat(4096.0f));
538
+ series_a = wasm_f32x4_relaxed_madd(wasm_f32x4_div(u_squared, wasm_f32x4_splat(16384.0f)), series_a, one);
539
+
540
+ // B = u^2/1024 * (256 + u^2*(-128 + u^2*(74 - 47*u^2)))
541
+ v128_t series_b = wasm_f32x4_relaxed_madd(u_squared, wasm_f32x4_splat(-47.0f), wasm_f32x4_splat(74.0f));
542
+ series_b = wasm_f32x4_relaxed_madd(u_squared, series_b, wasm_f32x4_splat(-128.0f));
543
+ series_b = wasm_f32x4_relaxed_madd(u_squared, series_b, wasm_f32x4_splat(256.0f));
544
+ series_b = wasm_f32x4_mul(wasm_f32x4_div(u_squared, wasm_f32x4_splat(1024.0f)), series_b);
545
+
546
+ // Delta-sigma calculation
547
+ v128_t cos_2sm_sq = wasm_f32x4_mul(cos_double_angular_midpoint, cos_double_angular_midpoint);
548
+ v128_t sin_sq = wasm_f32x4_mul(sin_angular_distance, sin_angular_distance);
549
+ v128_t term1 = wasm_f32x4_relaxed_madd(two, cos_2sm_sq, wasm_f32x4_splat(-1.0f));
550
+ term1 = wasm_f32x4_mul(cos_angular_distance, term1);
551
+ v128_t term2 = wasm_f32x4_relaxed_madd(four, sin_sq, wasm_f32x4_splat(-3.0f));
552
+ v128_t term3 = wasm_f32x4_relaxed_madd(four, cos_2sm_sq, wasm_f32x4_splat(-3.0f));
553
+ term2 = wasm_f32x4_mul(wasm_f32x4_mul(wasm_f32x4_div(series_b, six), cos_double_angular_midpoint),
554
+ wasm_f32x4_mul(term2, term3));
555
+ v128_t delta_sigma = wasm_f32x4_mul(
556
+ series_b, wasm_f32x4_mul(sin_angular_distance, wasm_f32x4_add(cos_double_angular_midpoint,
557
+ wasm_f32x4_mul(wasm_f32x4_div(series_b, four),
558
+ wasm_f32x4_sub(term1, term2)))));
559
+
560
+ // s = b * A * (s - ds)
561
+ v128_t distances = wasm_f32x4_mul(wasm_f32x4_mul(polar_radius, series_a),
562
+ wasm_f32x4_sub(angular_distance, delta_sigma));
563
+
564
+ // Set coincident points to zero
565
+ // relaxed_laneselect: 1 instruction (vblendvps) vs 3 (vpand+vpandn+vpor) on x86.
566
+ // Safe because mask is from comparison (all-ones or all-zeros per lane).
567
+ distances = wasm_i32x4_relaxed_laneselect(wasm_f32x4_splat(0.0f), distances, coincident_mask);
568
+
569
+ return distances;
570
+ }
571
+
572
+ NK_PUBLIC void nk_vincenty_f32_v128relaxed( //
573
+ nk_f32_t const *a_lats, nk_f32_t const *a_lons, //
574
+ nk_f32_t const *b_lats, nk_f32_t const *b_lons, //
575
+ nk_size_t n, nk_f32_t *results) {
576
+
577
+ while (n >= 4) {
578
+ v128_t first_latitudes = wasm_v128_load(a_lats);
579
+ v128_t first_longitudes = wasm_v128_load(a_lons);
580
+ v128_t second_latitudes = wasm_v128_load(b_lats);
581
+ v128_t second_longitudes = wasm_v128_load(b_lons);
582
+
583
+ v128_t distances = nk_vincenty_f32x4_v128relaxed_(first_latitudes, first_longitudes, second_latitudes,
584
+ second_longitudes);
585
+ wasm_v128_store(results, distances);
586
+
587
+ a_lats += 4, a_lons += 4, b_lats += 4, b_lons += 4, results += 4, n -= 4;
588
+ }
589
+
590
+ // Handle remaining elements with partial loads (n can be 1-3 here)
591
+ if (n > 0) {
592
+ nk_b128_vec_t a_lat_vec, a_lon_vec, b_lat_vec, b_lon_vec, result_vec;
593
+ nk_partial_load_b32x4_serial_(a_lats, &a_lat_vec, n);
594
+ nk_partial_load_b32x4_serial_(a_lons, &a_lon_vec, n);
595
+ nk_partial_load_b32x4_serial_(b_lats, &b_lat_vec, n);
596
+ nk_partial_load_b32x4_serial_(b_lons, &b_lon_vec, n);
597
+ v128_t distances = nk_vincenty_f32x4_v128relaxed_(a_lat_vec.v128, a_lon_vec.v128, b_lat_vec.v128,
598
+ b_lon_vec.v128);
599
+ result_vec.v128 = distances;
600
+ nk_partial_store_b32x4_serial_(&result_vec, results, n);
601
+ }
602
+ }
603
+
604
+ #if defined(__clang__)
605
+ #pragma clang attribute pop
606
+ #endif
607
+
608
+ #if defined(__cplusplus)
609
+ } // extern "C"
610
+ #endif
611
+
612
+ #endif // NK_TARGET_V128RELAXED
613
+ #endif // NK_GEOSPATIAL_V128RELAXED_H