carray 2.0.1 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (386) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -25
  3. data/CHANGELOG.md +338 -0
  4. data/{NEWS.md → CHANGELOG.v1.md} +3 -0
  5. data/LICENSE +1 -1
  6. data/README.md +120 -36
  7. data/carray.gemspec +32 -30
  8. data/ext/ca_array_pool.c +91 -0
  9. data/ext/ca_axis_descriptor.h +186 -0
  10. data/ext/ca_axis_dispatch.c +924 -0
  11. data/ext/ca_axis_group.c +1244 -0
  12. data/ext/ca_bincmp_dispatch.c +76 -0
  13. data/ext/ca_bincmp_dispatch.h +85 -0
  14. data/ext/ca_binop_dispatch.c +124 -0
  15. data/ext/ca_binop_dispatch.h +152 -0
  16. data/ext/ca_categorical_iterator.c +1375 -0
  17. data/ext/ca_compare.c +94 -0
  18. data/ext/ca_compare.h +26 -0
  19. data/ext/ca_composite_dispatch.c +414 -0
  20. data/ext/ca_composite_dispatch.h +116 -0
  21. data/ext/ca_for_buffer.h +96 -0
  22. data/ext/ca_for_each_element.h +239 -0
  23. data/ext/ca_group_iter.c +304 -0
  24. data/ext/ca_iter_substrate.h +325 -0
  25. data/ext/ca_kernel_iterator.c +4367 -0
  26. data/ext/ca_kernel_iterator.h +2596 -0
  27. data/ext/ca_moncmp_dispatch.c +37 -0
  28. data/ext/ca_moncmp_dispatch.h +62 -0
  29. data/ext/ca_monop_dispatch.c +200 -0
  30. data/ext/ca_monop_dispatch.h +235 -0
  31. data/ext/ca_obj_array.c +355 -359
  32. data/ext/ca_obj_bincmp.c +839 -0
  33. data/ext/ca_obj_binop.c +948 -0
  34. data/ext/ca_obj_bitarray.c +369 -164
  35. data/ext/ca_obj_bitfield.c +294 -234
  36. data/ext/ca_obj_block.c +189 -711
  37. data/ext/ca_obj_byte_swap.c +766 -0
  38. data/ext/ca_obj_const_string.c +967 -0
  39. data/ext/ca_obj_face.c +750 -0
  40. data/ext/ca_obj_face.h +279 -0
  41. data/ext/ca_obj_fake.c +239 -100
  42. data/ext/ca_obj_farray.c +54 -441
  43. data/ext/ca_obj_field.c +82 -529
  44. data/ext/ca_obj_fixlen_string.c +308 -0
  45. data/ext/ca_obj_grid.c +866 -440
  46. data/ext/ca_obj_meld.c +1039 -0
  47. data/ext/ca_obj_moncmp.c +588 -0
  48. data/ext/ca_obj_monop.c +1123 -0
  49. data/ext/ca_obj_object.c +866 -296
  50. data/ext/ca_obj_record.c +470 -0
  51. data/ext/ca_obj_reduce.c +97 -82
  52. data/ext/ca_obj_refer.c +593 -459
  53. data/ext/ca_obj_remap.c +475 -0
  54. data/ext/ca_obj_repeat.c +92 -477
  55. data/ext/ca_obj_roll.c +624 -0
  56. data/ext/ca_obj_select.c +344 -296
  57. data/ext/ca_obj_select_axis.c +1306 -0
  58. data/ext/ca_obj_shift.c +231 -793
  59. data/ext/ca_obj_source.c +78 -0
  60. data/ext/ca_obj_stack.c +1173 -0
  61. data/ext/ca_obj_stride.c +2584 -0
  62. data/ext/ca_obj_string.c +270 -0
  63. data/ext/ca_obj_tile.c +622 -0
  64. data/ext/ca_obj_time.c +548 -0
  65. data/ext/ca_obj_timedelta.c +437 -0
  66. data/ext/ca_obj_transpose.c +62 -516
  67. data/ext/ca_obj_triop.c +785 -0
  68. data/ext/ca_obj_window.c +1202 -565
  69. data/ext/ca_op_byte_swap.c +175 -0
  70. data/ext/ca_op_cmplx64.h +123 -0
  71. data/ext/ca_op_ipower.c +316 -0
  72. data/ext/ca_op_powi.h +88 -0
  73. data/ext/ca_sort_kernels.h +132 -0
  74. data/ext/ca_sweep_engine.c +473 -0
  75. data/ext/ca_sweep_engine.h +166 -0
  76. data/ext/ca_transform_common.c +235 -0
  77. data/ext/ca_triop_dispatch.c +55 -0
  78. data/ext/ca_triop_dispatch.h +62 -0
  79. data/ext/carray.h +810 -420
  80. data/ext/carray_access.c +873 -731
  81. data/ext/carray_attribute.c +98 -329
  82. data/ext/carray_bincount.c +255 -0
  83. data/ext/carray_broadcast.c +376 -0
  84. data/ext/carray_build_flags.h +3 -0
  85. data/ext/carray_call_cfunc.c +2897 -874
  86. data/ext/carray_call_cfunc.h +313 -0
  87. data/ext/carray_cast.c +1264 -315
  88. data/ext/carray_cast_func.rb +81 -40
  89. data/ext/carray_class.c +53 -63
  90. data/ext/carray_config.h +28 -0
  91. data/ext/carray_conversion.c +350 -346
  92. data/ext/carray_copy.c +168 -270
  93. data/ext/carray_core.c +1396 -206
  94. data/ext/carray_count.c +312 -0
  95. data/ext/carray_data_type.c +43 -19
  96. data/ext/carray_element.c +585 -213
  97. data/ext/carray_factorize.c +2542 -0
  98. data/ext/carray_generate.c +230 -559
  99. data/ext/carray_histogram.c +490 -0
  100. data/ext/carray_hold.c +228 -0
  101. data/ext/carray_index_classifier.c +1021 -0
  102. data/ext/carray_index_classifier.h +27 -0
  103. data/ext/carray_internal.h +136 -0
  104. data/ext/carray_kernels_bincmp.c +4446 -0
  105. data/ext/carray_kernels_binop.c +11001 -0
  106. data/ext/carray_kernels_init.c +1131 -0
  107. data/ext/carray_kernels_map.c +3467 -0
  108. data/ext/carray_kernels_moncmp.c +2097 -0
  109. data/ext/carray_kernels_monop.c +18313 -0
  110. data/ext/carray_kernels_reduce_aggregate.c +25837 -0
  111. data/ext/carray_kernels_reduce_boolean.c +330 -0
  112. data/ext/carray_kernels_reduce_cumulative.c +14593 -0
  113. data/ext/carray_kernels_reduce_extreme.c +16948 -0
  114. data/ext/carray_kernels_reduce_variance.c +3910 -0
  115. data/ext/carray_kernels_scan.c +3693 -0
  116. data/ext/carray_kernels_search.c +32138 -0
  117. data/ext/carray_kernels_sort.c +10626 -0
  118. data/ext/carray_kernels_triop.c +1392 -0
  119. data/ext/carray_lazy.c +737 -0
  120. data/ext/carray_loop.c +88 -200
  121. data/ext/carray_mask.c +853 -158
  122. data/ext/carray_math_kernel.h +120 -0
  123. data/ext/carray_mathfunc.c +10 -241
  124. data/ext/carray_median_percentile.c +1257 -0
  125. data/ext/carray_memory_view.c +1650 -0
  126. data/ext/carray_operator.c +1525 -320
  127. data/ext/carray_order.c +664 -1394
  128. data/ext/carray_partition.c +416 -0
  129. data/ext/carray_random.c +518 -0
  130. data/ext/carray_scatter.c +357 -0
  131. data/ext/carray_slab.c +1219 -0
  132. data/ext/carray_slab.h +84 -0
  133. data/ext/carray_sort.c +829 -0
  134. data/ext/carray_sort_kernel.c +620 -0
  135. data/ext/carray_struct.c +695 -0
  136. data/ext/carray_test.c +343 -229
  137. data/ext/carray_undef.c +34 -17
  138. data/ext/carray_utils.c +175 -74
  139. data/ext/extconf.rb +234 -55
  140. data/ext/mk_call_cfunc.rb +671 -0
  141. data/ext/mkkernel.rb +9096 -0
  142. data/ext/ruby_carray.c +211 -108
  143. data/ext/version.h +4 -14
  144. data/ext/version.rb +5 -13
  145. data/lib/carray/arrow_tensor.rb +401 -0
  146. data/lib/carray/attribute.rb +166 -0
  147. data/lib/carray/autoload_carray.rb +239 -0
  148. data/lib/carray/autoload_method_extension.rb +45 -0
  149. data/lib/carray/axis_group.rb +711 -0
  150. data/lib/carray/basics.rb +481 -0
  151. data/lib/carray/bincount_nd.rb +358 -0
  152. data/lib/carray/block_iterator.rb +614 -0
  153. data/lib/carray/boolean_reduce.rb +109 -0
  154. data/lib/carray/categorical.rb +561 -0
  155. data/lib/carray/categorical_iterator.rb +1084 -0
  156. data/lib/carray/complex.rb +150 -0
  157. data/lib/carray/conditional.rb +216 -0
  158. data/lib/carray/const_string.rb +228 -0
  159. data/lib/carray/construct.rb +160 -328
  160. data/lib/carray/core_extensions.rb +297 -0
  161. data/lib/carray/data_type_extension.rb +250 -0
  162. data/lib/carray/fixlen_string.rb +95 -0
  163. data/lib/carray/frame/concat.rb +132 -0
  164. data/lib/carray/frame/convert.rb +95 -0
  165. data/lib/carray/frame/csv_parser.rb +211 -0
  166. data/lib/carray/frame/frame.rb +642 -0
  167. data/lib/carray/frame/group.rb +186 -0
  168. data/lib/carray/frame/io.rb +321 -0
  169. data/lib/carray/frame/join.rb +248 -0
  170. data/lib/carray/frame/records.rb +99 -0
  171. data/lib/carray/frame/sort.rb +113 -0
  172. data/lib/carray/frame/verbs.rb +316 -0
  173. data/lib/carray/frame.rb +16 -0
  174. data/lib/carray/fuse_source.rb +123 -0
  175. data/lib/carray/fusion.rb +218 -0
  176. data/lib/carray/histogram.rb +512 -0
  177. data/lib/carray/inspect.rb +37 -26
  178. data/lib/carray/iterator.rb +58 -349
  179. data/lib/carray/lazy.rb +941 -0
  180. data/lib/carray/mask_gap_fill.rb +200 -0
  181. data/lib/carray/math.rb +78 -342
  182. data/lib/carray/meld_reduce.rb +289 -0
  183. data/lib/carray/methods/align_addr.rb +116 -0
  184. data/lib/carray/methods/bin.rb +128 -0
  185. data/lib/carray/methods/bincount.rb +87 -0
  186. data/lib/carray/methods/bit_string.rb +92 -0
  187. data/lib/carray/methods/broadcast.rb +63 -0
  188. data/lib/carray/methods/choose.rb +39 -0
  189. data/lib/carray/methods/composition.rb +280 -0
  190. data/lib/carray/methods/gather_nd.rb +206 -0
  191. data/lib/carray/methods/index.rb +39 -0
  192. data/lib/carray/methods/insert_block.rb +99 -0
  193. data/lib/carray/methods/is_in.rb +141 -0
  194. data/lib/carray/methods/join.rb +90 -0
  195. data/lib/carray/methods/locate_addr.rb +52 -0
  196. data/lib/carray/methods/mask_duplicates.rb +41 -0
  197. data/lib/carray/methods/meshgrid.rb +90 -0
  198. data/lib/carray/methods/mode.rb +126 -0
  199. data/lib/carray/methods/nunique.rb +46 -0
  200. data/lib/carray/methods/resize.rb +56 -0
  201. data/lib/carray/methods/snap.rb +161 -0
  202. data/lib/carray/methods/string_format.rb +57 -0
  203. data/lib/carray/methods/unique.rb +47 -0
  204. data/lib/carray/methods/value_counts.rb +71 -0
  205. data/lib/carray/mkmf.rb +124 -101
  206. data/lib/carray/runtime.rb +89 -0
  207. data/lib/carray/serialize.rb +478 -167
  208. data/lib/carray/slab_iterator.rb +305 -0
  209. data/lib/carray/stack.rb +291 -0
  210. data/lib/carray/string.rb +56 -180
  211. data/lib/carray/string_operation_extension.rb +289 -0
  212. data/lib/carray/struct.rb +335 -323
  213. data/lib/carray/struct_builder.rb +697 -0
  214. data/lib/carray/table.rb +41 -2
  215. data/lib/carray/time.rb +2654 -38
  216. data/lib/carray/window_iterator.rb +927 -0
  217. data/lib/carray.rb +55 -57
  218. data/yard-stubs/ca_obj_array.rb +385 -0
  219. data/yard-stubs/ca_obj_bitarray.rb +38 -0
  220. data/yard-stubs/ca_obj_bitfield.rb +43 -0
  221. data/yard-stubs/ca_obj_block.rb +73 -0
  222. data/yard-stubs/ca_obj_byte_swap.rb +56 -0
  223. data/yard-stubs/ca_obj_fake.rb +31 -0
  224. data/yard-stubs/ca_obj_farray.rb +32 -0
  225. data/yard-stubs/ca_obj_field.rb +45 -0
  226. data/yard-stubs/ca_obj_grid.rb +35 -0
  227. data/yard-stubs/ca_obj_refer.rb +72 -0
  228. data/yard-stubs/ca_obj_roll.rb +45 -0
  229. data/yard-stubs/ca_obj_shift.rb +43 -0
  230. data/yard-stubs/ca_obj_stride.rb +181 -0
  231. data/yard-stubs/ca_obj_tile.rb +29 -0
  232. data/yard-stubs/ca_obj_transpose.rb +40 -0
  233. data/yard-stubs/ca_obj_window.rb +49 -0
  234. data/yard-stubs/carray_access.rb +131 -0
  235. data/yard-stubs/carray_attribute.rb +246 -0
  236. data/yard-stubs/carray_broadcast.rb +37 -0
  237. data/yard-stubs/carray_cast.rb +489 -0
  238. data/yard-stubs/carray_class.rb +65 -0
  239. data/yard-stubs/carray_conversion.rb +76 -0
  240. data/yard-stubs/carray_copy.rb +79 -0
  241. data/yard-stubs/carray_core.rb +114 -0
  242. data/yard-stubs/carray_count.rb +79 -0
  243. data/yard-stubs/carray_element.rb +108 -0
  244. data/yard-stubs/carray_generate.rb +66 -0
  245. data/yard-stubs/carray_lazy.rb +23 -0
  246. data/yard-stubs/carray_loop.rb +140 -0
  247. data/yard-stubs/carray_mask.rb +259 -0
  248. data/yard-stubs/carray_math.rb +132 -0
  249. data/yard-stubs/carray_mathfunc.rb +45 -0
  250. data/yard-stubs/carray_median_percentile.rb +89 -0
  251. data/yard-stubs/carray_memory_view.rb +163 -0
  252. data/yard-stubs/carray_order.rb +312 -0
  253. data/yard-stubs/carray_random.rb +89 -0
  254. data/yard-stubs/carray_scatter.rb +106 -0
  255. data/yard-stubs/carray_slab.rb +57 -0
  256. data/yard-stubs/carray_sort.rb +163 -0
  257. data/yard-stubs/carray_test.rb +85 -0
  258. data/yard-stubs/carray_undef.rb +64 -0
  259. data/yard-stubs/carray_utils.rb +97 -0
  260. data/yard-stubs/ruby_carray.rb +193 -0
  261. metadata +220 -138
  262. data/Rakefile +0 -51
  263. data/TODO.md +0 -18
  264. data/ext/ca_iter_block.c +0 -257
  265. data/ext/ca_iter_dimension.c +0 -299
  266. data/ext/ca_iter_window.c +0 -214
  267. data/ext/ca_obj_mapping.c +0 -644
  268. data/ext/ca_obj_unbound_repeat.c +0 -529
  269. data/ext/carray_iterator.c +0 -641
  270. data/ext/carray_math.rb +0 -850
  271. data/ext/carray_numeric.c +0 -259
  272. data/ext/carray_sort_addr.c +0 -254
  273. data/ext/carray_stat.c +0 -2100
  274. data/ext/carray_stat_proc.rb +0 -1999
  275. data/ext/mkmath.rb +0 -741
  276. data/ext/ruby_ccomplex.c +0 -509
  277. data/ext/ruby_float_func.c +0 -86
  278. data/lib/carray/array.rb +0 -8
  279. data/lib/carray/autoload/autoload_base.rb +0 -19
  280. data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
  281. data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
  282. data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
  283. data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
  284. data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
  285. data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
  286. data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
  287. data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
  288. data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
  289. data/lib/carray/autoload/autoload_gem_random.rb +0 -8
  290. data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
  291. data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
  292. data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
  293. data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
  294. data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
  295. data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
  296. data/lib/carray/autoload/autoload_object_link.rb +0 -1
  297. data/lib/carray/autoload/autoload_object_pack.rb +0 -2
  298. data/lib/carray/autoload.rb +0 -141
  299. data/lib/carray/basic.rb +0 -191
  300. data/lib/carray/broadcast.rb +0 -101
  301. data/lib/carray/compose.rb +0 -315
  302. data/lib/carray/convert.rb +0 -115
  303. data/lib/carray/info.rb +0 -110
  304. data/lib/carray/io/imagemagick.rb +0 -235
  305. data/lib/carray/mask.rb +0 -102
  306. data/lib/carray/math/histogram.rb +0 -177
  307. data/lib/carray/math/recurrence.rb +0 -93
  308. data/lib/carray/object/ca_obj_iterator.rb +0 -50
  309. data/lib/carray/object/ca_obj_link.rb +0 -50
  310. data/lib/carray/object/ca_obj_pack.rb +0 -99
  311. data/lib/carray/obsolete.rb +0 -256
  312. data/lib/carray/ordering.rb +0 -181
  313. data/lib/carray/testing.rb +0 -51
  314. data/lib/carray/transform.rb +0 -109
  315. data/mailmap +0 -1
  316. data/misc/Methods.ja.md +0 -182
  317. data/misc/NOTE +0 -51
  318. data/spec/Classes/CABitfield_spec.rb +0 -58
  319. data/spec/Classes/CABlockIterator_spec.rb +0 -114
  320. data/spec/Classes/CABlock_spec.rb +0 -205
  321. data/spec/Classes/CAField_spec.rb +0 -39
  322. data/spec/Classes/CAGrid_spec.rb +0 -75
  323. data/spec/Classes/CAMap_spec.rb +0 -0
  324. data/spec/Classes/CAMapping_spec.rb +0 -105
  325. data/spec/Classes/CAObject_attribute_spec.rb +0 -33
  326. data/spec/Classes/CAObject_spec.rb +0 -33
  327. data/spec/Classes/CARefer_spec.rb +0 -93
  328. data/spec/Classes/CARepeat_spec.rb +0 -65
  329. data/spec/Classes/CASelect_spec.rb +0 -22
  330. data/spec/Classes/CAShift_spec.rb +0 -16
  331. data/spec/Classes/CAStruct_spec.rb +0 -71
  332. data/spec/Classes/CATranspose_spec.rb +0 -60
  333. data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
  334. data/spec/Classes/CAWindow_spec.rb +0 -54
  335. data/spec/Classes/CAWrap_spec.rb +0 -8
  336. data/spec/Classes/CArray_spec.rb +0 -184
  337. data/spec/Classes/CScalar_spec.rb +0 -55
  338. data/spec/Features/feature_130_spec.rb +0 -19
  339. data/spec/Features/feature_attributes_spec.rb +0 -280
  340. data/spec/Features/feature_boolean_spec.rb +0 -98
  341. data/spec/Features/feature_broadcast.rb +0 -116
  342. data/spec/Features/feature_cast_function.rb +0 -19
  343. data/spec/Features/feature_cast_spec.rb +0 -33
  344. data/spec/Features/feature_class_spec.rb +0 -84
  345. data/spec/Features/feature_complex_spec.rb +0 -42
  346. data/spec/Features/feature_composite_spec.rb +0 -124
  347. data/spec/Features/feature_convert_spec.rb +0 -46
  348. data/spec/Features/feature_copy_spec.rb +0 -123
  349. data/spec/Features/feature_creation_spec.rb +0 -84
  350. data/spec/Features/feature_element_spec.rb +0 -144
  351. data/spec/Features/feature_extream_spec.rb +0 -54
  352. data/spec/Features/feature_generate_spec.rb +0 -74
  353. data/spec/Features/feature_index_spec.rb +0 -69
  354. data/spec/Features/feature_mask_spec.rb +0 -580
  355. data/spec/Features/feature_math_spec.rb +0 -97
  356. data/spec/Features/feature_order_spec.rb +0 -146
  357. data/spec/Features/feature_ref_store_spec.rb +0 -209
  358. data/spec/Features/feature_serialization_spec.rb +0 -125
  359. data/spec/Features/feature_stat_spec.rb +0 -397
  360. data/spec/Features/feature_virtual_spec.rb +0 -48
  361. data/spec/Features/method_eq_spec.rb +0 -81
  362. data/spec/Features/method_is_nan_spec.rb +0 -12
  363. data/spec/Features/method_map_spec.rb +0 -54
  364. data/spec/Features/method_max_with.rb +0 -20
  365. data/spec/Features/method_min_with.rb +0 -19
  366. data/spec/Features/method_ne_spec.rb +0 -18
  367. data/spec/Features/method_project_spec.rb +0 -188
  368. data/spec/Features/method_ref_spec.rb +0 -27
  369. data/spec/Features/method_round_spec.rb +0 -11
  370. data/spec/Features/method_s_linspace_spec.rb +0 -48
  371. data/spec/Features/method_s_span_spec.rb +0 -14
  372. data/spec/Features/method_seq_spec.rb +0 -47
  373. data/spec/Features/method_sort_with.rb +0 -43
  374. data/spec/Features/method_sorted_with.rb +0 -29
  375. data/spec/Features/method_span_spec.rb +0 -42
  376. data/spec/Features/method_wrap_readonly_spec.rb +0 -43
  377. data/spec/UnitTest/test_CAVirtual.rb +0 -214
  378. data/spec/spec_all.rb +0 -10
  379. data/utils/ca_ase.rb +0 -21
  380. data/utils/ca_methods.rb +0 -15
  381. data/utils/cast_checker.rb +0 -30
  382. data/utils/convert_test.rb +0 -73
  383. data/utils/extract_yard.rb +0 -22
  384. data/utils/guess_shape.rb +0 -76
  385. data/utils/monkey_patch_methods.rb +0 -62
  386. data/utils/remove_resource_fork.sh +0 -5
@@ -0,0 +1,3910 @@
1
+ /* ---------------------------------------------------------------------------
2
+
3
+ carray_kernels.c -- GENERATED by ext/mkkernel.rb -- DO NOT EDIT
4
+
5
+ Kernel definitions live in mkkernel.rb (search for `MkKernel.reduce`).
6
+ Regenerate by running:
7
+
8
+ cd ext && ruby mkkernel.rb > carray_kernels.c
9
+
10
+ extconf.rb regenerates automatically when mkkernel.rb is touched.
11
+
12
+ --------------------------------------------------------------------------- */
13
+
14
+ #include "carray.h"
15
+ #include "ca_kernel_iterator.h"
16
+ #include "ca_sort_kernels.h" /* sort kernels: internal, not via the carray.h umbrella */
17
+ #include "carray_internal.h" /* ca_lazy_arena_*, ca_is_lazy_view (streaming reduce) */
18
+ #include "ca_obj_face.h" /* rb_ca_strip_face_value (Face ordering gate) */
19
+ #include <math.h>
20
+ #include <stdint.h>
21
+ #include <stdlib.h> /* qsort, mergesort (HAVE_MERGESORT) -- for :sort kind */
22
+
23
+ /* MEMO_REDUCTION_FASTPATH_ENTITY_ONLY_GUARD Tier 2: CAStack identity is
24
+ probed by operation-table function pointer (obj_type is runtime-
25
+ assigned, so no CA_OBJ_STACK compile-time constant exists). */
26
+ extern ca_operation_function_t ca_stack_func;
27
+ /* BOOL2VAL: bool -> Ruby (Qtrue/Qfalse). Used as ruby_scalar wrapper
28
+ for the all/any flat-reduction Ruby surface so `a.all` / `a.any`
29
+ return real true/false, not Integer 0/1. (BOOL2OBJ already exists
30
+ but returns Integer per CArray's boolean-as-int convention.) */
31
+ #ifndef CARRAY_BOOL2VAL_DEFINED
32
+ #define CARRAY_BOOL2VAL_DEFINED
33
+ static inline VALUE BOOL2VAL (boolean8_t x) { return x ? Qtrue : Qfalse; }
34
+ #endif
35
+
36
+ #include "ca_op_powi.h"
37
+ #include "ca_op_cmplx64.h"
38
+
39
+ /* ===== variancep_ki ============================================ */
40
+
41
+ #define __variancep_i8_P1EXPR(__x) ((double)(__x))
42
+ #define __variancep_i8_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
43
+ static VALUE
44
+ variancep_ki_native_i8 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
45
+ {
46
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
47
+ CArray *co;
48
+ GetCArray(vout, co);
49
+ double *op = (double *) co->ptr;
50
+ ca_iter_state st;
51
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
52
+ slab_axes, naxes, 0);
53
+ if ( rc != CA_ITER_OK ) {
54
+ rb_raise(rb_eRuntimeError,
55
+ "variancep_ki: kernel_iterator init failed rc=%d", rc);
56
+ }
57
+ char *p;
58
+ boolean8_t *m;
59
+ ca_size_t out_i = 0;
60
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
61
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
62
+ /* Pass 1: sum = Σx (+ masked_cnt) */
63
+ double sum = 0.0;
64
+ ca_size_t masked_cnt = 0;
65
+ CA_SLAB_REDUCE_SUM8_EX(int8_t, double, st, p, m, sum, 0.0, __variancep_i8_P1EXPR, masked_cnt);
66
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
67
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
68
+ if ( __trigger ) {
69
+ if ( ! op_mask ) {
70
+ ca_create_mask(co);
71
+ op_mask = (boolean8_t *) co->mask->ptr;
72
+ }
73
+ op_mask[out_i] = 1;
74
+ op[out_i++] = (double) 0.0;
75
+ } else if ( n_valid < 1 ) {
76
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
77
+ cell): return zero rather than UNDEF, matching pre-migration
78
+ behaviour of `cnt > 1 ? formula : 0`. */
79
+ op[out_i++] = (double) 0.0;
80
+ } else {
81
+ double mean = sum / (double) n_valid;
82
+ /* Pass 2: M2 = Σ (x - mean)² */
83
+ double M2 = 0.0;
84
+ ca_size_t __unused_mc = 0;
85
+ CA_SLAB_REDUCE_SUM8_EX(int8_t, double, st, p, m, M2, 0.0, __variancep_i8_P2EXPR, __unused_mc);
86
+ op[out_i++] = (double)(M2 / (double)(n_valid));
87
+ }
88
+ }
89
+ ca_iter_state_finish(&st);
90
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
91
+ keep_axis means the output is a 1-element CArray; unwrap it to
92
+ the ruby scalar (matches the standard reduce emit path lines
93
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
94
+ if ( naxes == ca->ndim && !keep_axis ) {
95
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
96
+ return rb_float_new(op[0]);
97
+ }
98
+ return vout;
99
+ }
100
+ #undef __variancep_i8_P1EXPR
101
+ #undef __variancep_i8_P2EXPR
102
+
103
+ #define __variancep_u8_P1EXPR(__x) ((double)(__x))
104
+ #define __variancep_u8_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
105
+ static VALUE
106
+ variancep_ki_native_u8 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
107
+ {
108
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
109
+ CArray *co;
110
+ GetCArray(vout, co);
111
+ double *op = (double *) co->ptr;
112
+ ca_iter_state st;
113
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
114
+ slab_axes, naxes, 0);
115
+ if ( rc != CA_ITER_OK ) {
116
+ rb_raise(rb_eRuntimeError,
117
+ "variancep_ki: kernel_iterator init failed rc=%d", rc);
118
+ }
119
+ char *p;
120
+ boolean8_t *m;
121
+ ca_size_t out_i = 0;
122
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
123
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
124
+ /* Pass 1: sum = Σx (+ masked_cnt) */
125
+ double sum = 0.0;
126
+ ca_size_t masked_cnt = 0;
127
+ CA_SLAB_REDUCE_SUM8_EX(uint8_t, double, st, p, m, sum, 0.0, __variancep_u8_P1EXPR, masked_cnt);
128
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
129
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
130
+ if ( __trigger ) {
131
+ if ( ! op_mask ) {
132
+ ca_create_mask(co);
133
+ op_mask = (boolean8_t *) co->mask->ptr;
134
+ }
135
+ op_mask[out_i] = 1;
136
+ op[out_i++] = (double) 0.0;
137
+ } else if ( n_valid < 1 ) {
138
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
139
+ cell): return zero rather than UNDEF, matching pre-migration
140
+ behaviour of `cnt > 1 ? formula : 0`. */
141
+ op[out_i++] = (double) 0.0;
142
+ } else {
143
+ double mean = sum / (double) n_valid;
144
+ /* Pass 2: M2 = Σ (x - mean)² */
145
+ double M2 = 0.0;
146
+ ca_size_t __unused_mc = 0;
147
+ CA_SLAB_REDUCE_SUM8_EX(uint8_t, double, st, p, m, M2, 0.0, __variancep_u8_P2EXPR, __unused_mc);
148
+ op[out_i++] = (double)(M2 / (double)(n_valid));
149
+ }
150
+ }
151
+ ca_iter_state_finish(&st);
152
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
153
+ keep_axis means the output is a 1-element CArray; unwrap it to
154
+ the ruby scalar (matches the standard reduce emit path lines
155
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
156
+ if ( naxes == ca->ndim && !keep_axis ) {
157
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
158
+ return rb_float_new(op[0]);
159
+ }
160
+ return vout;
161
+ }
162
+ #undef __variancep_u8_P1EXPR
163
+ #undef __variancep_u8_P2EXPR
164
+
165
+ #define __variancep_i16_P1EXPR(__x) ((double)(__x))
166
+ #define __variancep_i16_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
167
+ static VALUE
168
+ variancep_ki_native_i16 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
169
+ {
170
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
171
+ CArray *co;
172
+ GetCArray(vout, co);
173
+ double *op = (double *) co->ptr;
174
+ ca_iter_state st;
175
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
176
+ slab_axes, naxes, 0);
177
+ if ( rc != CA_ITER_OK ) {
178
+ rb_raise(rb_eRuntimeError,
179
+ "variancep_ki: kernel_iterator init failed rc=%d", rc);
180
+ }
181
+ char *p;
182
+ boolean8_t *m;
183
+ ca_size_t out_i = 0;
184
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
185
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
186
+ /* Pass 1: sum = Σx (+ masked_cnt) */
187
+ double sum = 0.0;
188
+ ca_size_t masked_cnt = 0;
189
+ CA_SLAB_REDUCE_SUM8_EX(int16_t, double, st, p, m, sum, 0.0, __variancep_i16_P1EXPR, masked_cnt);
190
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
191
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
192
+ if ( __trigger ) {
193
+ if ( ! op_mask ) {
194
+ ca_create_mask(co);
195
+ op_mask = (boolean8_t *) co->mask->ptr;
196
+ }
197
+ op_mask[out_i] = 1;
198
+ op[out_i++] = (double) 0.0;
199
+ } else if ( n_valid < 1 ) {
200
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
201
+ cell): return zero rather than UNDEF, matching pre-migration
202
+ behaviour of `cnt > 1 ? formula : 0`. */
203
+ op[out_i++] = (double) 0.0;
204
+ } else {
205
+ double mean = sum / (double) n_valid;
206
+ /* Pass 2: M2 = Σ (x - mean)² */
207
+ double M2 = 0.0;
208
+ ca_size_t __unused_mc = 0;
209
+ CA_SLAB_REDUCE_SUM8_EX(int16_t, double, st, p, m, M2, 0.0, __variancep_i16_P2EXPR, __unused_mc);
210
+ op[out_i++] = (double)(M2 / (double)(n_valid));
211
+ }
212
+ }
213
+ ca_iter_state_finish(&st);
214
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
215
+ keep_axis means the output is a 1-element CArray; unwrap it to
216
+ the ruby scalar (matches the standard reduce emit path lines
217
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
218
+ if ( naxes == ca->ndim && !keep_axis ) {
219
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
220
+ return rb_float_new(op[0]);
221
+ }
222
+ return vout;
223
+ }
224
+ #undef __variancep_i16_P1EXPR
225
+ #undef __variancep_i16_P2EXPR
226
+
227
+ #define __variancep_u16_P1EXPR(__x) ((double)(__x))
228
+ #define __variancep_u16_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
229
+ static VALUE
230
+ variancep_ki_native_u16 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
231
+ {
232
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
233
+ CArray *co;
234
+ GetCArray(vout, co);
235
+ double *op = (double *) co->ptr;
236
+ ca_iter_state st;
237
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
238
+ slab_axes, naxes, 0);
239
+ if ( rc != CA_ITER_OK ) {
240
+ rb_raise(rb_eRuntimeError,
241
+ "variancep_ki: kernel_iterator init failed rc=%d", rc);
242
+ }
243
+ char *p;
244
+ boolean8_t *m;
245
+ ca_size_t out_i = 0;
246
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
247
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
248
+ /* Pass 1: sum = Σx (+ masked_cnt) */
249
+ double sum = 0.0;
250
+ ca_size_t masked_cnt = 0;
251
+ CA_SLAB_REDUCE_SUM8_EX(uint16_t, double, st, p, m, sum, 0.0, __variancep_u16_P1EXPR, masked_cnt);
252
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
253
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
254
+ if ( __trigger ) {
255
+ if ( ! op_mask ) {
256
+ ca_create_mask(co);
257
+ op_mask = (boolean8_t *) co->mask->ptr;
258
+ }
259
+ op_mask[out_i] = 1;
260
+ op[out_i++] = (double) 0.0;
261
+ } else if ( n_valid < 1 ) {
262
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
263
+ cell): return zero rather than UNDEF, matching pre-migration
264
+ behaviour of `cnt > 1 ? formula : 0`. */
265
+ op[out_i++] = (double) 0.0;
266
+ } else {
267
+ double mean = sum / (double) n_valid;
268
+ /* Pass 2: M2 = Σ (x - mean)² */
269
+ double M2 = 0.0;
270
+ ca_size_t __unused_mc = 0;
271
+ CA_SLAB_REDUCE_SUM8_EX(uint16_t, double, st, p, m, M2, 0.0, __variancep_u16_P2EXPR, __unused_mc);
272
+ op[out_i++] = (double)(M2 / (double)(n_valid));
273
+ }
274
+ }
275
+ ca_iter_state_finish(&st);
276
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
277
+ keep_axis means the output is a 1-element CArray; unwrap it to
278
+ the ruby scalar (matches the standard reduce emit path lines
279
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
280
+ if ( naxes == ca->ndim && !keep_axis ) {
281
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
282
+ return rb_float_new(op[0]);
283
+ }
284
+ return vout;
285
+ }
286
+ #undef __variancep_u16_P1EXPR
287
+ #undef __variancep_u16_P2EXPR
288
+
289
+ #define __variancep_i32_P1EXPR(__x) ((double)(__x))
290
+ #define __variancep_i32_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
291
+ static VALUE
292
+ variancep_ki_native_i32 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
293
+ {
294
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
295
+ CArray *co;
296
+ GetCArray(vout, co);
297
+ double *op = (double *) co->ptr;
298
+ ca_iter_state st;
299
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
300
+ slab_axes, naxes, 0);
301
+ if ( rc != CA_ITER_OK ) {
302
+ rb_raise(rb_eRuntimeError,
303
+ "variancep_ki: kernel_iterator init failed rc=%d", rc);
304
+ }
305
+ char *p;
306
+ boolean8_t *m;
307
+ ca_size_t out_i = 0;
308
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
309
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
310
+ /* Pass 1: sum = Σx (+ masked_cnt) */
311
+ double sum = 0.0;
312
+ ca_size_t masked_cnt = 0;
313
+ CA_SLAB_REDUCE_SUM8_EX(int32_t, double, st, p, m, sum, 0.0, __variancep_i32_P1EXPR, masked_cnt);
314
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
315
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
316
+ if ( __trigger ) {
317
+ if ( ! op_mask ) {
318
+ ca_create_mask(co);
319
+ op_mask = (boolean8_t *) co->mask->ptr;
320
+ }
321
+ op_mask[out_i] = 1;
322
+ op[out_i++] = (double) 0.0;
323
+ } else if ( n_valid < 1 ) {
324
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
325
+ cell): return zero rather than UNDEF, matching pre-migration
326
+ behaviour of `cnt > 1 ? formula : 0`. */
327
+ op[out_i++] = (double) 0.0;
328
+ } else {
329
+ double mean = sum / (double) n_valid;
330
+ /* Pass 2: M2 = Σ (x - mean)² */
331
+ double M2 = 0.0;
332
+ ca_size_t __unused_mc = 0;
333
+ CA_SLAB_REDUCE_SUM8_EX(int32_t, double, st, p, m, M2, 0.0, __variancep_i32_P2EXPR, __unused_mc);
334
+ op[out_i++] = (double)(M2 / (double)(n_valid));
335
+ }
336
+ }
337
+ ca_iter_state_finish(&st);
338
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
339
+ keep_axis means the output is a 1-element CArray; unwrap it to
340
+ the ruby scalar (matches the standard reduce emit path lines
341
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
342
+ if ( naxes == ca->ndim && !keep_axis ) {
343
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
344
+ return rb_float_new(op[0]);
345
+ }
346
+ return vout;
347
+ }
348
+ #undef __variancep_i32_P1EXPR
349
+ #undef __variancep_i32_P2EXPR
350
+
351
+ #define __variancep_u32_P1EXPR(__x) ((double)(__x))
352
+ #define __variancep_u32_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
353
+ static VALUE
354
+ variancep_ki_native_u32 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
355
+ {
356
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
357
+ CArray *co;
358
+ GetCArray(vout, co);
359
+ double *op = (double *) co->ptr;
360
+ ca_iter_state st;
361
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
362
+ slab_axes, naxes, 0);
363
+ if ( rc != CA_ITER_OK ) {
364
+ rb_raise(rb_eRuntimeError,
365
+ "variancep_ki: kernel_iterator init failed rc=%d", rc);
366
+ }
367
+ char *p;
368
+ boolean8_t *m;
369
+ ca_size_t out_i = 0;
370
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
371
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
372
+ /* Pass 1: sum = Σx (+ masked_cnt) */
373
+ double sum = 0.0;
374
+ ca_size_t masked_cnt = 0;
375
+ CA_SLAB_REDUCE_SUM8_EX(uint32_t, double, st, p, m, sum, 0.0, __variancep_u32_P1EXPR, masked_cnt);
376
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
377
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
378
+ if ( __trigger ) {
379
+ if ( ! op_mask ) {
380
+ ca_create_mask(co);
381
+ op_mask = (boolean8_t *) co->mask->ptr;
382
+ }
383
+ op_mask[out_i] = 1;
384
+ op[out_i++] = (double) 0.0;
385
+ } else if ( n_valid < 1 ) {
386
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
387
+ cell): return zero rather than UNDEF, matching pre-migration
388
+ behaviour of `cnt > 1 ? formula : 0`. */
389
+ op[out_i++] = (double) 0.0;
390
+ } else {
391
+ double mean = sum / (double) n_valid;
392
+ /* Pass 2: M2 = Σ (x - mean)² */
393
+ double M2 = 0.0;
394
+ ca_size_t __unused_mc = 0;
395
+ CA_SLAB_REDUCE_SUM8_EX(uint32_t, double, st, p, m, M2, 0.0, __variancep_u32_P2EXPR, __unused_mc);
396
+ op[out_i++] = (double)(M2 / (double)(n_valid));
397
+ }
398
+ }
399
+ ca_iter_state_finish(&st);
400
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
401
+ keep_axis means the output is a 1-element CArray; unwrap it to
402
+ the ruby scalar (matches the standard reduce emit path lines
403
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
404
+ if ( naxes == ca->ndim && !keep_axis ) {
405
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
406
+ return rb_float_new(op[0]);
407
+ }
408
+ return vout;
409
+ }
410
+ #undef __variancep_u32_P1EXPR
411
+ #undef __variancep_u32_P2EXPR
412
+
413
+ #define __variancep_i64_P1EXPR(__x) ((double)(__x))
414
+ #define __variancep_i64_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
415
+ static VALUE
416
+ variancep_ki_native_i64 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
417
+ {
418
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
419
+ CArray *co;
420
+ GetCArray(vout, co);
421
+ double *op = (double *) co->ptr;
422
+ ca_iter_state st;
423
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
424
+ slab_axes, naxes, 0);
425
+ if ( rc != CA_ITER_OK ) {
426
+ rb_raise(rb_eRuntimeError,
427
+ "variancep_ki: kernel_iterator init failed rc=%d", rc);
428
+ }
429
+ char *p;
430
+ boolean8_t *m;
431
+ ca_size_t out_i = 0;
432
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
433
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
434
+ /* Pass 1: sum = Σx (+ masked_cnt) */
435
+ double sum = 0.0;
436
+ ca_size_t masked_cnt = 0;
437
+ CA_SLAB_REDUCE_SUM8_EX(int64_t, double, st, p, m, sum, 0.0, __variancep_i64_P1EXPR, masked_cnt);
438
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
439
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
440
+ if ( __trigger ) {
441
+ if ( ! op_mask ) {
442
+ ca_create_mask(co);
443
+ op_mask = (boolean8_t *) co->mask->ptr;
444
+ }
445
+ op_mask[out_i] = 1;
446
+ op[out_i++] = (double) 0.0;
447
+ } else if ( n_valid < 1 ) {
448
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
449
+ cell): return zero rather than UNDEF, matching pre-migration
450
+ behaviour of `cnt > 1 ? formula : 0`. */
451
+ op[out_i++] = (double) 0.0;
452
+ } else {
453
+ double mean = sum / (double) n_valid;
454
+ /* Pass 2: M2 = Σ (x - mean)² */
455
+ double M2 = 0.0;
456
+ ca_size_t __unused_mc = 0;
457
+ CA_SLAB_REDUCE_SUM8_EX(int64_t, double, st, p, m, M2, 0.0, __variancep_i64_P2EXPR, __unused_mc);
458
+ op[out_i++] = (double)(M2 / (double)(n_valid));
459
+ }
460
+ }
461
+ ca_iter_state_finish(&st);
462
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
463
+ keep_axis means the output is a 1-element CArray; unwrap it to
464
+ the ruby scalar (matches the standard reduce emit path lines
465
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
466
+ if ( naxes == ca->ndim && !keep_axis ) {
467
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
468
+ return rb_float_new(op[0]);
469
+ }
470
+ return vout;
471
+ }
472
+ #undef __variancep_i64_P1EXPR
473
+ #undef __variancep_i64_P2EXPR
474
+
475
+ #define __variancep_u64_P1EXPR(__x) ((double)(__x))
476
+ #define __variancep_u64_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
477
+ static VALUE
478
+ variancep_ki_native_u64 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
479
+ {
480
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
481
+ CArray *co;
482
+ GetCArray(vout, co);
483
+ double *op = (double *) co->ptr;
484
+ ca_iter_state st;
485
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
486
+ slab_axes, naxes, 0);
487
+ if ( rc != CA_ITER_OK ) {
488
+ rb_raise(rb_eRuntimeError,
489
+ "variancep_ki: kernel_iterator init failed rc=%d", rc);
490
+ }
491
+ char *p;
492
+ boolean8_t *m;
493
+ ca_size_t out_i = 0;
494
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
495
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
496
+ /* Pass 1: sum = Σx (+ masked_cnt) */
497
+ double sum = 0.0;
498
+ ca_size_t masked_cnt = 0;
499
+ CA_SLAB_REDUCE_SUM8_EX(uint64_t, double, st, p, m, sum, 0.0, __variancep_u64_P1EXPR, masked_cnt);
500
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
501
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
502
+ if ( __trigger ) {
503
+ if ( ! op_mask ) {
504
+ ca_create_mask(co);
505
+ op_mask = (boolean8_t *) co->mask->ptr;
506
+ }
507
+ op_mask[out_i] = 1;
508
+ op[out_i++] = (double) 0.0;
509
+ } else if ( n_valid < 1 ) {
510
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
511
+ cell): return zero rather than UNDEF, matching pre-migration
512
+ behaviour of `cnt > 1 ? formula : 0`. */
513
+ op[out_i++] = (double) 0.0;
514
+ } else {
515
+ double mean = sum / (double) n_valid;
516
+ /* Pass 2: M2 = Σ (x - mean)² */
517
+ double M2 = 0.0;
518
+ ca_size_t __unused_mc = 0;
519
+ CA_SLAB_REDUCE_SUM8_EX(uint64_t, double, st, p, m, M2, 0.0, __variancep_u64_P2EXPR, __unused_mc);
520
+ op[out_i++] = (double)(M2 / (double)(n_valid));
521
+ }
522
+ }
523
+ ca_iter_state_finish(&st);
524
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
525
+ keep_axis means the output is a 1-element CArray; unwrap it to
526
+ the ruby scalar (matches the standard reduce emit path lines
527
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
528
+ if ( naxes == ca->ndim && !keep_axis ) {
529
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
530
+ return rb_float_new(op[0]);
531
+ }
532
+ return vout;
533
+ }
534
+ #undef __variancep_u64_P1EXPR
535
+ #undef __variancep_u64_P2EXPR
536
+
537
+ #define __variancep_f32_P1EXPR(__x) ((double)(__x))
538
+ #define __variancep_f32_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
539
+ static VALUE
540
+ variancep_ki_native_f32 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
541
+ {
542
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
543
+ CArray *co;
544
+ GetCArray(vout, co);
545
+ double *op = (double *) co->ptr;
546
+ ca_iter_state st;
547
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
548
+ slab_axes, naxes, 0);
549
+ if ( rc != CA_ITER_OK ) {
550
+ rb_raise(rb_eRuntimeError,
551
+ "variancep_ki: kernel_iterator init failed rc=%d", rc);
552
+ }
553
+ char *p;
554
+ boolean8_t *m;
555
+ ca_size_t out_i = 0;
556
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
557
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
558
+ /* Pass 1: sum = Σx (+ masked_cnt) */
559
+ double sum = 0.0;
560
+ ca_size_t masked_cnt = 0;
561
+ CA_SLAB_REDUCE_SUM8_EX(float, double, st, p, m, sum, 0.0, __variancep_f32_P1EXPR, masked_cnt);
562
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
563
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
564
+ if ( __trigger ) {
565
+ if ( ! op_mask ) {
566
+ ca_create_mask(co);
567
+ op_mask = (boolean8_t *) co->mask->ptr;
568
+ }
569
+ op_mask[out_i] = 1;
570
+ op[out_i++] = (double) 0.0;
571
+ } else if ( n_valid < 1 ) {
572
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
573
+ cell): return zero rather than UNDEF, matching pre-migration
574
+ behaviour of `cnt > 1 ? formula : 0`. */
575
+ op[out_i++] = (double) 0.0;
576
+ } else {
577
+ double mean = sum / (double) n_valid;
578
+ /* Pass 2: M2 = Σ (x - mean)² */
579
+ double M2 = 0.0;
580
+ ca_size_t __unused_mc = 0;
581
+ CA_SLAB_REDUCE_SUM8_EX(float, double, st, p, m, M2, 0.0, __variancep_f32_P2EXPR, __unused_mc);
582
+ op[out_i++] = (double)(M2 / (double)(n_valid));
583
+ }
584
+ }
585
+ ca_iter_state_finish(&st);
586
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
587
+ keep_axis means the output is a 1-element CArray; unwrap it to
588
+ the ruby scalar (matches the standard reduce emit path lines
589
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
590
+ if ( naxes == ca->ndim && !keep_axis ) {
591
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
592
+ return rb_float_new(op[0]);
593
+ }
594
+ return vout;
595
+ }
596
+ #undef __variancep_f32_P1EXPR
597
+ #undef __variancep_f32_P2EXPR
598
+
599
+ #define __variancep_f64_P1EXPR(__x) ((double)(__x))
600
+ #define __variancep_f64_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
601
+ static VALUE
602
+ variancep_ki_native_f64 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
603
+ {
604
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
605
+ CArray *co;
606
+ GetCArray(vout, co);
607
+ double *op = (double *) co->ptr;
608
+ ca_iter_state st;
609
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
610
+ slab_axes, naxes, 0);
611
+ if ( rc != CA_ITER_OK ) {
612
+ rb_raise(rb_eRuntimeError,
613
+ "variancep_ki: kernel_iterator init failed rc=%d", rc);
614
+ }
615
+ char *p;
616
+ boolean8_t *m;
617
+ ca_size_t out_i = 0;
618
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
619
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
620
+ /* Pass 1: sum = Σx (+ masked_cnt) */
621
+ double sum = 0.0;
622
+ ca_size_t masked_cnt = 0;
623
+ CA_SLAB_REDUCE_SUM8_EX(double, double, st, p, m, sum, 0.0, __variancep_f64_P1EXPR, masked_cnt);
624
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
625
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
626
+ if ( __trigger ) {
627
+ if ( ! op_mask ) {
628
+ ca_create_mask(co);
629
+ op_mask = (boolean8_t *) co->mask->ptr;
630
+ }
631
+ op_mask[out_i] = 1;
632
+ op[out_i++] = (double) 0.0;
633
+ } else if ( n_valid < 1 ) {
634
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
635
+ cell): return zero rather than UNDEF, matching pre-migration
636
+ behaviour of `cnt > 1 ? formula : 0`. */
637
+ op[out_i++] = (double) 0.0;
638
+ } else {
639
+ double mean = sum / (double) n_valid;
640
+ /* Pass 2: M2 = Σ (x - mean)² */
641
+ double M2 = 0.0;
642
+ ca_size_t __unused_mc = 0;
643
+ CA_SLAB_REDUCE_SUM8_EX(double, double, st, p, m, M2, 0.0, __variancep_f64_P2EXPR, __unused_mc);
644
+ op[out_i++] = (double)(M2 / (double)(n_valid));
645
+ }
646
+ }
647
+ ca_iter_state_finish(&st);
648
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
649
+ keep_axis means the output is a 1-element CArray; unwrap it to
650
+ the ruby scalar (matches the standard reduce emit path lines
651
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
652
+ if ( naxes == ca->ndim && !keep_axis ) {
653
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
654
+ return rb_float_new(op[0]);
655
+ }
656
+ return vout;
657
+ }
658
+ #undef __variancep_f64_P1EXPR
659
+ #undef __variancep_f64_P2EXPR
660
+
661
+ #define __variancep_cmplx64_P1EXPR(__x) (__x)
662
+ #define __variancep_cmplx64_P2EXPR(__x) (creal((__x) - mean) * creal((__x) - mean) + cimag((__x) - mean) * cimag((__x) - mean))
663
+ static VALUE
664
+ variancep_ki_native_cmplx64 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
665
+ {
666
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
667
+ CArray *co;
668
+ GetCArray(vout, co);
669
+ double *op = (double *) co->ptr;
670
+ ca_iter_state st;
671
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
672
+ slab_axes, naxes, 0);
673
+ if ( rc != CA_ITER_OK ) {
674
+ rb_raise(rb_eRuntimeError,
675
+ "variancep_ki: kernel_iterator init failed rc=%d", rc);
676
+ }
677
+ char *p;
678
+ boolean8_t *m;
679
+ ca_size_t out_i = 0;
680
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
681
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
682
+ /* Pass 1: sum = Σx (+ masked_cnt) */
683
+ cmplx64_t sum = 0;
684
+ ca_size_t masked_cnt = 0;
685
+ CA_SLAB_REDUCE_SUM8_EX(cmplx64_t, cmplx64_t, st, p, m, sum, 0, __variancep_cmplx64_P1EXPR, masked_cnt);
686
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
687
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
688
+ if ( __trigger ) {
689
+ if ( ! op_mask ) {
690
+ ca_create_mask(co);
691
+ op_mask = (boolean8_t *) co->mask->ptr;
692
+ }
693
+ op_mask[out_i] = 1;
694
+ op[out_i++] = (double) 0.0;
695
+ } else if ( n_valid < 1 ) {
696
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
697
+ cell): return zero rather than UNDEF, matching pre-migration
698
+ behaviour of `cnt > 1 ? formula : 0`. */
699
+ op[out_i++] = (double) 0.0;
700
+ } else {
701
+ cmplx64_t mean = sum / (cmplx64_t)(double) n_valid;
702
+ /* Pass 2: M2 = Σ (x - mean)² */
703
+ double M2 = 0.0;
704
+ ca_size_t __unused_mc = 0;
705
+ CA_SLAB_REDUCE_SUM8_EX(cmplx64_t, double, st, p, m, M2, 0.0, __variancep_cmplx64_P2EXPR, __unused_mc);
706
+ op[out_i++] = (double)(M2 / (double)(n_valid));
707
+ }
708
+ }
709
+ ca_iter_state_finish(&st);
710
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
711
+ keep_axis means the output is a 1-element CArray; unwrap it to
712
+ the ruby scalar (matches the standard reduce emit path lines
713
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
714
+ if ( naxes == ca->ndim && !keep_axis ) {
715
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
716
+ return rb_float_new(op[0]);
717
+ }
718
+ return vout;
719
+ }
720
+ #undef __variancep_cmplx64_P1EXPR
721
+ #undef __variancep_cmplx64_P2EXPR
722
+
723
+ #define __variancep_cmplx128_P1EXPR(__x) (__x)
724
+ #define __variancep_cmplx128_P2EXPR(__x) (creal((__x) - mean) * creal((__x) - mean) + cimag((__x) - mean) * cimag((__x) - mean))
725
+ static VALUE
726
+ variancep_ki_native_cmplx128 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
727
+ {
728
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
729
+ CArray *co;
730
+ GetCArray(vout, co);
731
+ double *op = (double *) co->ptr;
732
+ ca_iter_state st;
733
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
734
+ slab_axes, naxes, 0);
735
+ if ( rc != CA_ITER_OK ) {
736
+ rb_raise(rb_eRuntimeError,
737
+ "variancep_ki: kernel_iterator init failed rc=%d", rc);
738
+ }
739
+ char *p;
740
+ boolean8_t *m;
741
+ ca_size_t out_i = 0;
742
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
743
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
744
+ /* Pass 1: sum = Σx (+ masked_cnt) */
745
+ cmplx128_t sum = 0;
746
+ ca_size_t masked_cnt = 0;
747
+ CA_SLAB_REDUCE_SUM8_EX(cmplx128_t, cmplx128_t, st, p, m, sum, 0, __variancep_cmplx128_P1EXPR, masked_cnt);
748
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
749
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
750
+ if ( __trigger ) {
751
+ if ( ! op_mask ) {
752
+ ca_create_mask(co);
753
+ op_mask = (boolean8_t *) co->mask->ptr;
754
+ }
755
+ op_mask[out_i] = 1;
756
+ op[out_i++] = (double) 0.0;
757
+ } else if ( n_valid < 1 ) {
758
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
759
+ cell): return zero rather than UNDEF, matching pre-migration
760
+ behaviour of `cnt > 1 ? formula : 0`. */
761
+ op[out_i++] = (double) 0.0;
762
+ } else {
763
+ cmplx128_t mean = sum / (cmplx128_t)(double) n_valid;
764
+ /* Pass 2: M2 = Σ (x - mean)² */
765
+ double M2 = 0.0;
766
+ ca_size_t __unused_mc = 0;
767
+ CA_SLAB_REDUCE_SUM8_EX(cmplx128_t, double, st, p, m, M2, 0.0, __variancep_cmplx128_P2EXPR, __unused_mc);
768
+ op[out_i++] = (double)(M2 / (double)(n_valid));
769
+ }
770
+ }
771
+ ca_iter_state_finish(&st);
772
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
773
+ keep_axis means the output is a 1-element CArray; unwrap it to
774
+ the ruby scalar (matches the standard reduce emit path lines
775
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
776
+ if ( naxes == ca->ndim && !keep_axis ) {
777
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
778
+ return rb_float_new(op[0]);
779
+ }
780
+ return vout;
781
+ }
782
+ #undef __variancep_cmplx128_P1EXPR
783
+ #undef __variancep_cmplx128_P2EXPR
784
+
785
+ #define __variancep_bool_P1EXPR(__x) ((double)(__x))
786
+ #define __variancep_bool_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
787
+ static VALUE
788
+ variancep_ki_native_bool (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
789
+ {
790
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
791
+ CArray *co;
792
+ GetCArray(vout, co);
793
+ double *op = (double *) co->ptr;
794
+ ca_iter_state st;
795
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
796
+ slab_axes, naxes, 0);
797
+ if ( rc != CA_ITER_OK ) {
798
+ rb_raise(rb_eRuntimeError,
799
+ "variancep_ki: kernel_iterator init failed rc=%d", rc);
800
+ }
801
+ char *p;
802
+ boolean8_t *m;
803
+ ca_size_t out_i = 0;
804
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
805
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
806
+ /* Pass 1: sum = Σx (+ masked_cnt) */
807
+ double sum = 0.0;
808
+ ca_size_t masked_cnt = 0;
809
+ CA_SLAB_REDUCE_SUM8_EX(boolean8_t, double, st, p, m, sum, 0.0, __variancep_bool_P1EXPR, masked_cnt);
810
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
811
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
812
+ if ( __trigger ) {
813
+ if ( ! op_mask ) {
814
+ ca_create_mask(co);
815
+ op_mask = (boolean8_t *) co->mask->ptr;
816
+ }
817
+ op_mask[out_i] = 1;
818
+ op[out_i++] = (double) 0.0;
819
+ } else if ( n_valid < 1 ) {
820
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
821
+ cell): return zero rather than UNDEF, matching pre-migration
822
+ behaviour of `cnt > 1 ? formula : 0`. */
823
+ op[out_i++] = (double) 0.0;
824
+ } else {
825
+ double mean = sum / (double) n_valid;
826
+ /* Pass 2: M2 = Σ (x - mean)² */
827
+ double M2 = 0.0;
828
+ ca_size_t __unused_mc = 0;
829
+ CA_SLAB_REDUCE_SUM8_EX(boolean8_t, double, st, p, m, M2, 0.0, __variancep_bool_P2EXPR, __unused_mc);
830
+ op[out_i++] = (double)(M2 / (double)(n_valid));
831
+ }
832
+ }
833
+ ca_iter_state_finish(&st);
834
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
835
+ keep_axis means the output is a 1-element CArray; unwrap it to
836
+ the ruby scalar (matches the standard reduce emit path lines
837
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
838
+ if ( naxes == ca->ndim && !keep_axis ) {
839
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
840
+ return rb_float_new(op[0]);
841
+ }
842
+ return vout;
843
+ }
844
+ #undef __variancep_bool_P1EXPR
845
+ #undef __variancep_bool_P2EXPR
846
+
847
+ static VALUE
848
+ variancep_ki_native_object (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
849
+ {
850
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_OBJECT, keep_axis);
851
+ CArray *co;
852
+ GetCArray(vout, co);
853
+ VALUE *op = (VALUE *) co->ptr;
854
+ ca_iter_state st;
855
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
856
+ slab_axes, naxes, 0);
857
+ if ( rc != CA_ITER_OK ) {
858
+ rb_raise(rb_eRuntimeError,
859
+ "variancep_ki: kernel_iterator init failed rc=%d", rc);
860
+ }
861
+ char *p;
862
+ boolean8_t *m;
863
+ ca_size_t out_i = 0;
864
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
865
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
866
+ /* Pass 1: sum = Σx (+ masked_cnt) */
867
+ VALUE sum = INT2FIX(0);
868
+ ca_size_t masked_cnt = 0;
869
+ CA_SLAB_REDUCE_T_EX(VALUE, st, p, m, sum, INT2FIX(0), sum = rb_funcall(sum, rb_intern("+"), 1, v), masked_cnt);
870
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
871
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
872
+ if ( __trigger ) {
873
+ if ( ! op_mask ) {
874
+ ca_create_mask(co);
875
+ op_mask = (boolean8_t *) co->mask->ptr;
876
+ }
877
+ op_mask[out_i] = 1;
878
+ op[out_i++] = (VALUE) INT2FIX(0);
879
+ } else if ( n_valid < 1 ) {
880
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
881
+ cell): return zero rather than UNDEF, matching pre-migration
882
+ behaviour of `cnt > 1 ? formula : 0`. */
883
+ op[out_i++] = (VALUE) INT2FIX(0);
884
+ } else {
885
+ VALUE mean = rb_funcall(sum, rb_intern("/"), 1, LONG2NUM(n_valid));
886
+ /* Pass 2: M2 = Σ (x - mean)² */
887
+ VALUE M2 = INT2FIX(0);
888
+ ca_size_t __unused_mc = 0;
889
+ CA_SLAB_REDUCE_T_EX(VALUE, st, p, m, M2, INT2FIX(0), VALUE __d = rb_funcall(v, rb_intern("-"), 1, mean);
890
+ M2 = rb_funcall(M2, rb_intern("+"), 1, rb_funcall(__d, rb_intern("*"), 1, __d)), __unused_mc);
891
+ op[out_i++] = (VALUE)(rb_funcall(M2, rb_intern("/"), 1, LONG2NUM(n_valid)));
892
+ }
893
+ }
894
+ ca_iter_state_finish(&st);
895
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
896
+ keep_axis means the output is a 1-element CArray; unwrap it to
897
+ the ruby scalar (matches the standard reduce emit path lines
898
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
899
+ if ( naxes == ca->ndim && !keep_axis ) {
900
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
901
+ return (VALUE)(op[0]);
902
+ }
903
+ return vout;
904
+ }
905
+
906
+ VALUE
907
+ rb_ca_variancep_ki (int argc, VALUE *argv, VALUE self)
908
+ {
909
+ CArray *src;
910
+ GetCArray(self, src);
911
+ volatile VALUE ropt = rb_pop_options(&argc, &argv);
912
+ volatile VALUE raxis = Qnil, rkeep_axis = Qnil, rmin_count = Qnil, rfval = Qnil;
913
+ rb_scan_options(ropt, "axis,keep_axis,min_count,fill_value", &raxis, &rkeep_axis, &rmin_count, &rfval);
914
+ int keep_axis = RTEST(rkeep_axis);
915
+ int8_t slab_axes[CA_RANK_MAX];
916
+ int8_t naxes = rb_ca_parse_reduce_axes_kw(raxis, src, slab_axes);
917
+ ca_size_t min_count = -1;
918
+ if ( ! NIL_P(rmin_count) ) {
919
+ ca_size_t mc_user = NUM2SIZE(rmin_count);
920
+ if ( mc_user < 0 ) {
921
+ rb_raise(rb_eArgError, "variancep_ki: :min_count must be non-negative, got %lld", (long long) mc_user);
922
+ }
923
+ /* :min_count = 0 collapses to legacy default (any 1 valid). */
924
+ if ( mc_user > 0 ) min_count = mc_user;
925
+ }
926
+ VALUE result;
927
+ if ( argc > 0 ) {
928
+ rb_raise(rb_eArgError, "variancep_ki: positional axis arguments are no longer accepted (got %d); use axis: kwarg, e.g. a.variancep(axis: 0) or a.variancep(axis: [0, 1])", argc);
929
+ }
930
+ switch ( src->data_type ) {
931
+ case CA_INT8: {
932
+ result = variancep_ki_native_i8(self, src, slab_axes, naxes, keep_axis, min_count);
933
+ break;
934
+ }
935
+ case CA_UINT8: {
936
+ result = variancep_ki_native_u8(self, src, slab_axes, naxes, keep_axis, min_count);
937
+ break;
938
+ }
939
+ case CA_INT16: {
940
+ result = variancep_ki_native_i16(self, src, slab_axes, naxes, keep_axis, min_count);
941
+ break;
942
+ }
943
+ case CA_UINT16: {
944
+ result = variancep_ki_native_u16(self, src, slab_axes, naxes, keep_axis, min_count);
945
+ break;
946
+ }
947
+ case CA_INT32: {
948
+ result = variancep_ki_native_i32(self, src, slab_axes, naxes, keep_axis, min_count);
949
+ break;
950
+ }
951
+ case CA_UINT32: {
952
+ result = variancep_ki_native_u32(self, src, slab_axes, naxes, keep_axis, min_count);
953
+ break;
954
+ }
955
+ case CA_INT64: {
956
+ result = variancep_ki_native_i64(self, src, slab_axes, naxes, keep_axis, min_count);
957
+ break;
958
+ }
959
+ case CA_UINT64: {
960
+ result = variancep_ki_native_u64(self, src, slab_axes, naxes, keep_axis, min_count);
961
+ break;
962
+ }
963
+ case CA_FLOAT32: {
964
+ result = variancep_ki_native_f32(self, src, slab_axes, naxes, keep_axis, min_count);
965
+ break;
966
+ }
967
+ case CA_FLOAT64: {
968
+ result = variancep_ki_native_f64(self, src, slab_axes, naxes, keep_axis, min_count);
969
+ break;
970
+ }
971
+ case CA_CMPLX64: {
972
+ result = variancep_ki_native_cmplx64(self, src, slab_axes, naxes, keep_axis, min_count);
973
+ break;
974
+ }
975
+ case CA_CMPLX128: {
976
+ result = variancep_ki_native_cmplx128(self, src, slab_axes, naxes, keep_axis, min_count);
977
+ break;
978
+ }
979
+ case CA_BOOLEAN: {
980
+ result = variancep_ki_native_bool(self, src, slab_axes, naxes, keep_axis, min_count);
981
+ break;
982
+ }
983
+ case CA_OBJECT: {
984
+ result = variancep_ki_native_object(self, src, slab_axes, naxes, keep_axis, min_count);
985
+ break;
986
+ }
987
+ default:
988
+ rb_raise(rb_eCADataTypeError, "variancep_ki: source data_type :%s not supported (expected one of: i8, u8, i16, u16, i32, u32, i64, u64, f32, f64, cmplx64, cmplx128, bool, object)", ca_type_name[src->data_type]);
989
+ }
990
+ if ( ! NIL_P(rfval) ) {
991
+ if ( result == CA_UNDEF ) {
992
+ result = rfval;
993
+ } else if ( TYPE(result) != T_FLOAT && rb_obj_is_kind_of(result, rb_cCArray) ) {
994
+ CArray *cr;
995
+ GetCArray(result, cr);
996
+ if ( ca_has_mask(cr) ) {
997
+ result = rb_ca_mask_fill_copy(result, rfval);
998
+ }
999
+ }
1000
+ }
1001
+ return result;
1002
+ }
1003
+
1004
+ /* ===== variance_ki ============================================ */
1005
+
1006
+ #define __variance_i8_P1EXPR(__x) ((double)(__x))
1007
+ #define __variance_i8_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
1008
+ static VALUE
1009
+ variance_ki_native_i8 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
1010
+ {
1011
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
1012
+ CArray *co;
1013
+ GetCArray(vout, co);
1014
+ double *op = (double *) co->ptr;
1015
+ ca_iter_state st;
1016
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
1017
+ slab_axes, naxes, 0);
1018
+ if ( rc != CA_ITER_OK ) {
1019
+ rb_raise(rb_eRuntimeError,
1020
+ "variance_ki: kernel_iterator init failed rc=%d", rc);
1021
+ }
1022
+ char *p;
1023
+ boolean8_t *m;
1024
+ ca_size_t out_i = 0;
1025
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
1026
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
1027
+ /* Pass 1: sum = Σx (+ masked_cnt) */
1028
+ double sum = 0.0;
1029
+ ca_size_t masked_cnt = 0;
1030
+ CA_SLAB_REDUCE_SUM8_EX(int8_t, double, st, p, m, sum, 0.0, __variance_i8_P1EXPR, masked_cnt);
1031
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
1032
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
1033
+ if ( __trigger ) {
1034
+ if ( ! op_mask ) {
1035
+ ca_create_mask(co);
1036
+ op_mask = (boolean8_t *) co->mask->ptr;
1037
+ }
1038
+ op_mask[out_i] = 1;
1039
+ op[out_i++] = (double) 0.0;
1040
+ } else if ( n_valid < 2 ) {
1041
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
1042
+ cell): return zero rather than UNDEF, matching pre-migration
1043
+ behaviour of `cnt > 1 ? formula : 0`. */
1044
+ op[out_i++] = (double) 0.0;
1045
+ } else {
1046
+ double mean = sum / (double) n_valid;
1047
+ /* Pass 2: M2 = Σ (x - mean)² */
1048
+ double M2 = 0.0;
1049
+ ca_size_t __unused_mc = 0;
1050
+ CA_SLAB_REDUCE_SUM8_EX(int8_t, double, st, p, m, M2, 0.0, __variance_i8_P2EXPR, __unused_mc);
1051
+ op[out_i++] = (double)(M2 / (double)(n_valid - 1));
1052
+ }
1053
+ }
1054
+ ca_iter_state_finish(&st);
1055
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
1056
+ keep_axis means the output is a 1-element CArray; unwrap it to
1057
+ the ruby scalar (matches the standard reduce emit path lines
1058
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
1059
+ if ( naxes == ca->ndim && !keep_axis ) {
1060
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
1061
+ return rb_float_new(op[0]);
1062
+ }
1063
+ return vout;
1064
+ }
1065
+ #undef __variance_i8_P1EXPR
1066
+ #undef __variance_i8_P2EXPR
1067
+
1068
+ #define __variance_u8_P1EXPR(__x) ((double)(__x))
1069
+ #define __variance_u8_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
1070
+ static VALUE
1071
+ variance_ki_native_u8 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
1072
+ {
1073
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
1074
+ CArray *co;
1075
+ GetCArray(vout, co);
1076
+ double *op = (double *) co->ptr;
1077
+ ca_iter_state st;
1078
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
1079
+ slab_axes, naxes, 0);
1080
+ if ( rc != CA_ITER_OK ) {
1081
+ rb_raise(rb_eRuntimeError,
1082
+ "variance_ki: kernel_iterator init failed rc=%d", rc);
1083
+ }
1084
+ char *p;
1085
+ boolean8_t *m;
1086
+ ca_size_t out_i = 0;
1087
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
1088
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
1089
+ /* Pass 1: sum = Σx (+ masked_cnt) */
1090
+ double sum = 0.0;
1091
+ ca_size_t masked_cnt = 0;
1092
+ CA_SLAB_REDUCE_SUM8_EX(uint8_t, double, st, p, m, sum, 0.0, __variance_u8_P1EXPR, masked_cnt);
1093
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
1094
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
1095
+ if ( __trigger ) {
1096
+ if ( ! op_mask ) {
1097
+ ca_create_mask(co);
1098
+ op_mask = (boolean8_t *) co->mask->ptr;
1099
+ }
1100
+ op_mask[out_i] = 1;
1101
+ op[out_i++] = (double) 0.0;
1102
+ } else if ( n_valid < 2 ) {
1103
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
1104
+ cell): return zero rather than UNDEF, matching pre-migration
1105
+ behaviour of `cnt > 1 ? formula : 0`. */
1106
+ op[out_i++] = (double) 0.0;
1107
+ } else {
1108
+ double mean = sum / (double) n_valid;
1109
+ /* Pass 2: M2 = Σ (x - mean)² */
1110
+ double M2 = 0.0;
1111
+ ca_size_t __unused_mc = 0;
1112
+ CA_SLAB_REDUCE_SUM8_EX(uint8_t, double, st, p, m, M2, 0.0, __variance_u8_P2EXPR, __unused_mc);
1113
+ op[out_i++] = (double)(M2 / (double)(n_valid - 1));
1114
+ }
1115
+ }
1116
+ ca_iter_state_finish(&st);
1117
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
1118
+ keep_axis means the output is a 1-element CArray; unwrap it to
1119
+ the ruby scalar (matches the standard reduce emit path lines
1120
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
1121
+ if ( naxes == ca->ndim && !keep_axis ) {
1122
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
1123
+ return rb_float_new(op[0]);
1124
+ }
1125
+ return vout;
1126
+ }
1127
+ #undef __variance_u8_P1EXPR
1128
+ #undef __variance_u8_P2EXPR
1129
+
1130
+ #define __variance_i16_P1EXPR(__x) ((double)(__x))
1131
+ #define __variance_i16_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
1132
+ static VALUE
1133
+ variance_ki_native_i16 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
1134
+ {
1135
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
1136
+ CArray *co;
1137
+ GetCArray(vout, co);
1138
+ double *op = (double *) co->ptr;
1139
+ ca_iter_state st;
1140
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
1141
+ slab_axes, naxes, 0);
1142
+ if ( rc != CA_ITER_OK ) {
1143
+ rb_raise(rb_eRuntimeError,
1144
+ "variance_ki: kernel_iterator init failed rc=%d", rc);
1145
+ }
1146
+ char *p;
1147
+ boolean8_t *m;
1148
+ ca_size_t out_i = 0;
1149
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
1150
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
1151
+ /* Pass 1: sum = Σx (+ masked_cnt) */
1152
+ double sum = 0.0;
1153
+ ca_size_t masked_cnt = 0;
1154
+ CA_SLAB_REDUCE_SUM8_EX(int16_t, double, st, p, m, sum, 0.0, __variance_i16_P1EXPR, masked_cnt);
1155
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
1156
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
1157
+ if ( __trigger ) {
1158
+ if ( ! op_mask ) {
1159
+ ca_create_mask(co);
1160
+ op_mask = (boolean8_t *) co->mask->ptr;
1161
+ }
1162
+ op_mask[out_i] = 1;
1163
+ op[out_i++] = (double) 0.0;
1164
+ } else if ( n_valid < 2 ) {
1165
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
1166
+ cell): return zero rather than UNDEF, matching pre-migration
1167
+ behaviour of `cnt > 1 ? formula : 0`. */
1168
+ op[out_i++] = (double) 0.0;
1169
+ } else {
1170
+ double mean = sum / (double) n_valid;
1171
+ /* Pass 2: M2 = Σ (x - mean)² */
1172
+ double M2 = 0.0;
1173
+ ca_size_t __unused_mc = 0;
1174
+ CA_SLAB_REDUCE_SUM8_EX(int16_t, double, st, p, m, M2, 0.0, __variance_i16_P2EXPR, __unused_mc);
1175
+ op[out_i++] = (double)(M2 / (double)(n_valid - 1));
1176
+ }
1177
+ }
1178
+ ca_iter_state_finish(&st);
1179
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
1180
+ keep_axis means the output is a 1-element CArray; unwrap it to
1181
+ the ruby scalar (matches the standard reduce emit path lines
1182
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
1183
+ if ( naxes == ca->ndim && !keep_axis ) {
1184
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
1185
+ return rb_float_new(op[0]);
1186
+ }
1187
+ return vout;
1188
+ }
1189
+ #undef __variance_i16_P1EXPR
1190
+ #undef __variance_i16_P2EXPR
1191
+
1192
+ #define __variance_u16_P1EXPR(__x) ((double)(__x))
1193
+ #define __variance_u16_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
1194
+ static VALUE
1195
+ variance_ki_native_u16 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
1196
+ {
1197
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
1198
+ CArray *co;
1199
+ GetCArray(vout, co);
1200
+ double *op = (double *) co->ptr;
1201
+ ca_iter_state st;
1202
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
1203
+ slab_axes, naxes, 0);
1204
+ if ( rc != CA_ITER_OK ) {
1205
+ rb_raise(rb_eRuntimeError,
1206
+ "variance_ki: kernel_iterator init failed rc=%d", rc);
1207
+ }
1208
+ char *p;
1209
+ boolean8_t *m;
1210
+ ca_size_t out_i = 0;
1211
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
1212
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
1213
+ /* Pass 1: sum = Σx (+ masked_cnt) */
1214
+ double sum = 0.0;
1215
+ ca_size_t masked_cnt = 0;
1216
+ CA_SLAB_REDUCE_SUM8_EX(uint16_t, double, st, p, m, sum, 0.0, __variance_u16_P1EXPR, masked_cnt);
1217
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
1218
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
1219
+ if ( __trigger ) {
1220
+ if ( ! op_mask ) {
1221
+ ca_create_mask(co);
1222
+ op_mask = (boolean8_t *) co->mask->ptr;
1223
+ }
1224
+ op_mask[out_i] = 1;
1225
+ op[out_i++] = (double) 0.0;
1226
+ } else if ( n_valid < 2 ) {
1227
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
1228
+ cell): return zero rather than UNDEF, matching pre-migration
1229
+ behaviour of `cnt > 1 ? formula : 0`. */
1230
+ op[out_i++] = (double) 0.0;
1231
+ } else {
1232
+ double mean = sum / (double) n_valid;
1233
+ /* Pass 2: M2 = Σ (x - mean)² */
1234
+ double M2 = 0.0;
1235
+ ca_size_t __unused_mc = 0;
1236
+ CA_SLAB_REDUCE_SUM8_EX(uint16_t, double, st, p, m, M2, 0.0, __variance_u16_P2EXPR, __unused_mc);
1237
+ op[out_i++] = (double)(M2 / (double)(n_valid - 1));
1238
+ }
1239
+ }
1240
+ ca_iter_state_finish(&st);
1241
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
1242
+ keep_axis means the output is a 1-element CArray; unwrap it to
1243
+ the ruby scalar (matches the standard reduce emit path lines
1244
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
1245
+ if ( naxes == ca->ndim && !keep_axis ) {
1246
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
1247
+ return rb_float_new(op[0]);
1248
+ }
1249
+ return vout;
1250
+ }
1251
+ #undef __variance_u16_P1EXPR
1252
+ #undef __variance_u16_P2EXPR
1253
+
1254
+ #define __variance_i32_P1EXPR(__x) ((double)(__x))
1255
+ #define __variance_i32_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
1256
+ static VALUE
1257
+ variance_ki_native_i32 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
1258
+ {
1259
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
1260
+ CArray *co;
1261
+ GetCArray(vout, co);
1262
+ double *op = (double *) co->ptr;
1263
+ ca_iter_state st;
1264
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
1265
+ slab_axes, naxes, 0);
1266
+ if ( rc != CA_ITER_OK ) {
1267
+ rb_raise(rb_eRuntimeError,
1268
+ "variance_ki: kernel_iterator init failed rc=%d", rc);
1269
+ }
1270
+ char *p;
1271
+ boolean8_t *m;
1272
+ ca_size_t out_i = 0;
1273
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
1274
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
1275
+ /* Pass 1: sum = Σx (+ masked_cnt) */
1276
+ double sum = 0.0;
1277
+ ca_size_t masked_cnt = 0;
1278
+ CA_SLAB_REDUCE_SUM8_EX(int32_t, double, st, p, m, sum, 0.0, __variance_i32_P1EXPR, masked_cnt);
1279
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
1280
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
1281
+ if ( __trigger ) {
1282
+ if ( ! op_mask ) {
1283
+ ca_create_mask(co);
1284
+ op_mask = (boolean8_t *) co->mask->ptr;
1285
+ }
1286
+ op_mask[out_i] = 1;
1287
+ op[out_i++] = (double) 0.0;
1288
+ } else if ( n_valid < 2 ) {
1289
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
1290
+ cell): return zero rather than UNDEF, matching pre-migration
1291
+ behaviour of `cnt > 1 ? formula : 0`. */
1292
+ op[out_i++] = (double) 0.0;
1293
+ } else {
1294
+ double mean = sum / (double) n_valid;
1295
+ /* Pass 2: M2 = Σ (x - mean)² */
1296
+ double M2 = 0.0;
1297
+ ca_size_t __unused_mc = 0;
1298
+ CA_SLAB_REDUCE_SUM8_EX(int32_t, double, st, p, m, M2, 0.0, __variance_i32_P2EXPR, __unused_mc);
1299
+ op[out_i++] = (double)(M2 / (double)(n_valid - 1));
1300
+ }
1301
+ }
1302
+ ca_iter_state_finish(&st);
1303
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
1304
+ keep_axis means the output is a 1-element CArray; unwrap it to
1305
+ the ruby scalar (matches the standard reduce emit path lines
1306
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
1307
+ if ( naxes == ca->ndim && !keep_axis ) {
1308
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
1309
+ return rb_float_new(op[0]);
1310
+ }
1311
+ return vout;
1312
+ }
1313
+ #undef __variance_i32_P1EXPR
1314
+ #undef __variance_i32_P2EXPR
1315
+
1316
+ #define __variance_u32_P1EXPR(__x) ((double)(__x))
1317
+ #define __variance_u32_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
1318
+ static VALUE
1319
+ variance_ki_native_u32 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
1320
+ {
1321
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
1322
+ CArray *co;
1323
+ GetCArray(vout, co);
1324
+ double *op = (double *) co->ptr;
1325
+ ca_iter_state st;
1326
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
1327
+ slab_axes, naxes, 0);
1328
+ if ( rc != CA_ITER_OK ) {
1329
+ rb_raise(rb_eRuntimeError,
1330
+ "variance_ki: kernel_iterator init failed rc=%d", rc);
1331
+ }
1332
+ char *p;
1333
+ boolean8_t *m;
1334
+ ca_size_t out_i = 0;
1335
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
1336
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
1337
+ /* Pass 1: sum = Σx (+ masked_cnt) */
1338
+ double sum = 0.0;
1339
+ ca_size_t masked_cnt = 0;
1340
+ CA_SLAB_REDUCE_SUM8_EX(uint32_t, double, st, p, m, sum, 0.0, __variance_u32_P1EXPR, masked_cnt);
1341
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
1342
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
1343
+ if ( __trigger ) {
1344
+ if ( ! op_mask ) {
1345
+ ca_create_mask(co);
1346
+ op_mask = (boolean8_t *) co->mask->ptr;
1347
+ }
1348
+ op_mask[out_i] = 1;
1349
+ op[out_i++] = (double) 0.0;
1350
+ } else if ( n_valid < 2 ) {
1351
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
1352
+ cell): return zero rather than UNDEF, matching pre-migration
1353
+ behaviour of `cnt > 1 ? formula : 0`. */
1354
+ op[out_i++] = (double) 0.0;
1355
+ } else {
1356
+ double mean = sum / (double) n_valid;
1357
+ /* Pass 2: M2 = Σ (x - mean)² */
1358
+ double M2 = 0.0;
1359
+ ca_size_t __unused_mc = 0;
1360
+ CA_SLAB_REDUCE_SUM8_EX(uint32_t, double, st, p, m, M2, 0.0, __variance_u32_P2EXPR, __unused_mc);
1361
+ op[out_i++] = (double)(M2 / (double)(n_valid - 1));
1362
+ }
1363
+ }
1364
+ ca_iter_state_finish(&st);
1365
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
1366
+ keep_axis means the output is a 1-element CArray; unwrap it to
1367
+ the ruby scalar (matches the standard reduce emit path lines
1368
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
1369
+ if ( naxes == ca->ndim && !keep_axis ) {
1370
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
1371
+ return rb_float_new(op[0]);
1372
+ }
1373
+ return vout;
1374
+ }
1375
+ #undef __variance_u32_P1EXPR
1376
+ #undef __variance_u32_P2EXPR
1377
+
1378
+ #define __variance_i64_P1EXPR(__x) ((double)(__x))
1379
+ #define __variance_i64_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
1380
+ static VALUE
1381
+ variance_ki_native_i64 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
1382
+ {
1383
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
1384
+ CArray *co;
1385
+ GetCArray(vout, co);
1386
+ double *op = (double *) co->ptr;
1387
+ ca_iter_state st;
1388
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
1389
+ slab_axes, naxes, 0);
1390
+ if ( rc != CA_ITER_OK ) {
1391
+ rb_raise(rb_eRuntimeError,
1392
+ "variance_ki: kernel_iterator init failed rc=%d", rc);
1393
+ }
1394
+ char *p;
1395
+ boolean8_t *m;
1396
+ ca_size_t out_i = 0;
1397
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
1398
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
1399
+ /* Pass 1: sum = Σx (+ masked_cnt) */
1400
+ double sum = 0.0;
1401
+ ca_size_t masked_cnt = 0;
1402
+ CA_SLAB_REDUCE_SUM8_EX(int64_t, double, st, p, m, sum, 0.0, __variance_i64_P1EXPR, masked_cnt);
1403
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
1404
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
1405
+ if ( __trigger ) {
1406
+ if ( ! op_mask ) {
1407
+ ca_create_mask(co);
1408
+ op_mask = (boolean8_t *) co->mask->ptr;
1409
+ }
1410
+ op_mask[out_i] = 1;
1411
+ op[out_i++] = (double) 0.0;
1412
+ } else if ( n_valid < 2 ) {
1413
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
1414
+ cell): return zero rather than UNDEF, matching pre-migration
1415
+ behaviour of `cnt > 1 ? formula : 0`. */
1416
+ op[out_i++] = (double) 0.0;
1417
+ } else {
1418
+ double mean = sum / (double) n_valid;
1419
+ /* Pass 2: M2 = Σ (x - mean)² */
1420
+ double M2 = 0.0;
1421
+ ca_size_t __unused_mc = 0;
1422
+ CA_SLAB_REDUCE_SUM8_EX(int64_t, double, st, p, m, M2, 0.0, __variance_i64_P2EXPR, __unused_mc);
1423
+ op[out_i++] = (double)(M2 / (double)(n_valid - 1));
1424
+ }
1425
+ }
1426
+ ca_iter_state_finish(&st);
1427
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
1428
+ keep_axis means the output is a 1-element CArray; unwrap it to
1429
+ the ruby scalar (matches the standard reduce emit path lines
1430
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
1431
+ if ( naxes == ca->ndim && !keep_axis ) {
1432
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
1433
+ return rb_float_new(op[0]);
1434
+ }
1435
+ return vout;
1436
+ }
1437
+ #undef __variance_i64_P1EXPR
1438
+ #undef __variance_i64_P2EXPR
1439
+
1440
+ #define __variance_u64_P1EXPR(__x) ((double)(__x))
1441
+ #define __variance_u64_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
1442
+ static VALUE
1443
+ variance_ki_native_u64 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
1444
+ {
1445
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
1446
+ CArray *co;
1447
+ GetCArray(vout, co);
1448
+ double *op = (double *) co->ptr;
1449
+ ca_iter_state st;
1450
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
1451
+ slab_axes, naxes, 0);
1452
+ if ( rc != CA_ITER_OK ) {
1453
+ rb_raise(rb_eRuntimeError,
1454
+ "variance_ki: kernel_iterator init failed rc=%d", rc);
1455
+ }
1456
+ char *p;
1457
+ boolean8_t *m;
1458
+ ca_size_t out_i = 0;
1459
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
1460
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
1461
+ /* Pass 1: sum = Σx (+ masked_cnt) */
1462
+ double sum = 0.0;
1463
+ ca_size_t masked_cnt = 0;
1464
+ CA_SLAB_REDUCE_SUM8_EX(uint64_t, double, st, p, m, sum, 0.0, __variance_u64_P1EXPR, masked_cnt);
1465
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
1466
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
1467
+ if ( __trigger ) {
1468
+ if ( ! op_mask ) {
1469
+ ca_create_mask(co);
1470
+ op_mask = (boolean8_t *) co->mask->ptr;
1471
+ }
1472
+ op_mask[out_i] = 1;
1473
+ op[out_i++] = (double) 0.0;
1474
+ } else if ( n_valid < 2 ) {
1475
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
1476
+ cell): return zero rather than UNDEF, matching pre-migration
1477
+ behaviour of `cnt > 1 ? formula : 0`. */
1478
+ op[out_i++] = (double) 0.0;
1479
+ } else {
1480
+ double mean = sum / (double) n_valid;
1481
+ /* Pass 2: M2 = Σ (x - mean)² */
1482
+ double M2 = 0.0;
1483
+ ca_size_t __unused_mc = 0;
1484
+ CA_SLAB_REDUCE_SUM8_EX(uint64_t, double, st, p, m, M2, 0.0, __variance_u64_P2EXPR, __unused_mc);
1485
+ op[out_i++] = (double)(M2 / (double)(n_valid - 1));
1486
+ }
1487
+ }
1488
+ ca_iter_state_finish(&st);
1489
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
1490
+ keep_axis means the output is a 1-element CArray; unwrap it to
1491
+ the ruby scalar (matches the standard reduce emit path lines
1492
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
1493
+ if ( naxes == ca->ndim && !keep_axis ) {
1494
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
1495
+ return rb_float_new(op[0]);
1496
+ }
1497
+ return vout;
1498
+ }
1499
+ #undef __variance_u64_P1EXPR
1500
+ #undef __variance_u64_P2EXPR
1501
+
1502
+ #define __variance_f32_P1EXPR(__x) ((double)(__x))
1503
+ #define __variance_f32_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
1504
+ static VALUE
1505
+ variance_ki_native_f32 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
1506
+ {
1507
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
1508
+ CArray *co;
1509
+ GetCArray(vout, co);
1510
+ double *op = (double *) co->ptr;
1511
+ ca_iter_state st;
1512
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
1513
+ slab_axes, naxes, 0);
1514
+ if ( rc != CA_ITER_OK ) {
1515
+ rb_raise(rb_eRuntimeError,
1516
+ "variance_ki: kernel_iterator init failed rc=%d", rc);
1517
+ }
1518
+ char *p;
1519
+ boolean8_t *m;
1520
+ ca_size_t out_i = 0;
1521
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
1522
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
1523
+ /* Pass 1: sum = Σx (+ masked_cnt) */
1524
+ double sum = 0.0;
1525
+ ca_size_t masked_cnt = 0;
1526
+ CA_SLAB_REDUCE_SUM8_EX(float, double, st, p, m, sum, 0.0, __variance_f32_P1EXPR, masked_cnt);
1527
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
1528
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
1529
+ if ( __trigger ) {
1530
+ if ( ! op_mask ) {
1531
+ ca_create_mask(co);
1532
+ op_mask = (boolean8_t *) co->mask->ptr;
1533
+ }
1534
+ op_mask[out_i] = 1;
1535
+ op[out_i++] = (double) 0.0;
1536
+ } else if ( n_valid < 2 ) {
1537
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
1538
+ cell): return zero rather than UNDEF, matching pre-migration
1539
+ behaviour of `cnt > 1 ? formula : 0`. */
1540
+ op[out_i++] = (double) 0.0;
1541
+ } else {
1542
+ double mean = sum / (double) n_valid;
1543
+ /* Pass 2: M2 = Σ (x - mean)² */
1544
+ double M2 = 0.0;
1545
+ ca_size_t __unused_mc = 0;
1546
+ CA_SLAB_REDUCE_SUM8_EX(float, double, st, p, m, M2, 0.0, __variance_f32_P2EXPR, __unused_mc);
1547
+ op[out_i++] = (double)(M2 / (double)(n_valid - 1));
1548
+ }
1549
+ }
1550
+ ca_iter_state_finish(&st);
1551
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
1552
+ keep_axis means the output is a 1-element CArray; unwrap it to
1553
+ the ruby scalar (matches the standard reduce emit path lines
1554
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
1555
+ if ( naxes == ca->ndim && !keep_axis ) {
1556
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
1557
+ return rb_float_new(op[0]);
1558
+ }
1559
+ return vout;
1560
+ }
1561
+ #undef __variance_f32_P1EXPR
1562
+ #undef __variance_f32_P2EXPR
1563
+
1564
+ #define __variance_f64_P1EXPR(__x) ((double)(__x))
1565
+ #define __variance_f64_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
1566
+ static VALUE
1567
+ variance_ki_native_f64 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
1568
+ {
1569
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
1570
+ CArray *co;
1571
+ GetCArray(vout, co);
1572
+ double *op = (double *) co->ptr;
1573
+ ca_iter_state st;
1574
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
1575
+ slab_axes, naxes, 0);
1576
+ if ( rc != CA_ITER_OK ) {
1577
+ rb_raise(rb_eRuntimeError,
1578
+ "variance_ki: kernel_iterator init failed rc=%d", rc);
1579
+ }
1580
+ char *p;
1581
+ boolean8_t *m;
1582
+ ca_size_t out_i = 0;
1583
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
1584
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
1585
+ /* Pass 1: sum = Σx (+ masked_cnt) */
1586
+ double sum = 0.0;
1587
+ ca_size_t masked_cnt = 0;
1588
+ CA_SLAB_REDUCE_SUM8_EX(double, double, st, p, m, sum, 0.0, __variance_f64_P1EXPR, masked_cnt);
1589
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
1590
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
1591
+ if ( __trigger ) {
1592
+ if ( ! op_mask ) {
1593
+ ca_create_mask(co);
1594
+ op_mask = (boolean8_t *) co->mask->ptr;
1595
+ }
1596
+ op_mask[out_i] = 1;
1597
+ op[out_i++] = (double) 0.0;
1598
+ } else if ( n_valid < 2 ) {
1599
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
1600
+ cell): return zero rather than UNDEF, matching pre-migration
1601
+ behaviour of `cnt > 1 ? formula : 0`. */
1602
+ op[out_i++] = (double) 0.0;
1603
+ } else {
1604
+ double mean = sum / (double) n_valid;
1605
+ /* Pass 2: M2 = Σ (x - mean)² */
1606
+ double M2 = 0.0;
1607
+ ca_size_t __unused_mc = 0;
1608
+ CA_SLAB_REDUCE_SUM8_EX(double, double, st, p, m, M2, 0.0, __variance_f64_P2EXPR, __unused_mc);
1609
+ op[out_i++] = (double)(M2 / (double)(n_valid - 1));
1610
+ }
1611
+ }
1612
+ ca_iter_state_finish(&st);
1613
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
1614
+ keep_axis means the output is a 1-element CArray; unwrap it to
1615
+ the ruby scalar (matches the standard reduce emit path lines
1616
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
1617
+ if ( naxes == ca->ndim && !keep_axis ) {
1618
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
1619
+ return rb_float_new(op[0]);
1620
+ }
1621
+ return vout;
1622
+ }
1623
+ #undef __variance_f64_P1EXPR
1624
+ #undef __variance_f64_P2EXPR
1625
+
1626
+ #define __variance_cmplx64_P1EXPR(__x) (__x)
1627
+ #define __variance_cmplx64_P2EXPR(__x) (creal((__x) - mean) * creal((__x) - mean) + cimag((__x) - mean) * cimag((__x) - mean))
1628
+ static VALUE
1629
+ variance_ki_native_cmplx64 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
1630
+ {
1631
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
1632
+ CArray *co;
1633
+ GetCArray(vout, co);
1634
+ double *op = (double *) co->ptr;
1635
+ ca_iter_state st;
1636
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
1637
+ slab_axes, naxes, 0);
1638
+ if ( rc != CA_ITER_OK ) {
1639
+ rb_raise(rb_eRuntimeError,
1640
+ "variance_ki: kernel_iterator init failed rc=%d", rc);
1641
+ }
1642
+ char *p;
1643
+ boolean8_t *m;
1644
+ ca_size_t out_i = 0;
1645
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
1646
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
1647
+ /* Pass 1: sum = Σx (+ masked_cnt) */
1648
+ cmplx64_t sum = 0;
1649
+ ca_size_t masked_cnt = 0;
1650
+ CA_SLAB_REDUCE_SUM8_EX(cmplx64_t, cmplx64_t, st, p, m, sum, 0, __variance_cmplx64_P1EXPR, masked_cnt);
1651
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
1652
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
1653
+ if ( __trigger ) {
1654
+ if ( ! op_mask ) {
1655
+ ca_create_mask(co);
1656
+ op_mask = (boolean8_t *) co->mask->ptr;
1657
+ }
1658
+ op_mask[out_i] = 1;
1659
+ op[out_i++] = (double) 0.0;
1660
+ } else if ( n_valid < 2 ) {
1661
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
1662
+ cell): return zero rather than UNDEF, matching pre-migration
1663
+ behaviour of `cnt > 1 ? formula : 0`. */
1664
+ op[out_i++] = (double) 0.0;
1665
+ } else {
1666
+ cmplx64_t mean = sum / (cmplx64_t)(double) n_valid;
1667
+ /* Pass 2: M2 = Σ (x - mean)² */
1668
+ double M2 = 0.0;
1669
+ ca_size_t __unused_mc = 0;
1670
+ CA_SLAB_REDUCE_SUM8_EX(cmplx64_t, double, st, p, m, M2, 0.0, __variance_cmplx64_P2EXPR, __unused_mc);
1671
+ op[out_i++] = (double)(M2 / (double)(n_valid - 1));
1672
+ }
1673
+ }
1674
+ ca_iter_state_finish(&st);
1675
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
1676
+ keep_axis means the output is a 1-element CArray; unwrap it to
1677
+ the ruby scalar (matches the standard reduce emit path lines
1678
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
1679
+ if ( naxes == ca->ndim && !keep_axis ) {
1680
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
1681
+ return rb_float_new(op[0]);
1682
+ }
1683
+ return vout;
1684
+ }
1685
+ #undef __variance_cmplx64_P1EXPR
1686
+ #undef __variance_cmplx64_P2EXPR
1687
+
1688
+ #define __variance_cmplx128_P1EXPR(__x) (__x)
1689
+ #define __variance_cmplx128_P2EXPR(__x) (creal((__x) - mean) * creal((__x) - mean) + cimag((__x) - mean) * cimag((__x) - mean))
1690
+ static VALUE
1691
+ variance_ki_native_cmplx128 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
1692
+ {
1693
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
1694
+ CArray *co;
1695
+ GetCArray(vout, co);
1696
+ double *op = (double *) co->ptr;
1697
+ ca_iter_state st;
1698
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
1699
+ slab_axes, naxes, 0);
1700
+ if ( rc != CA_ITER_OK ) {
1701
+ rb_raise(rb_eRuntimeError,
1702
+ "variance_ki: kernel_iterator init failed rc=%d", rc);
1703
+ }
1704
+ char *p;
1705
+ boolean8_t *m;
1706
+ ca_size_t out_i = 0;
1707
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
1708
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
1709
+ /* Pass 1: sum = Σx (+ masked_cnt) */
1710
+ cmplx128_t sum = 0;
1711
+ ca_size_t masked_cnt = 0;
1712
+ CA_SLAB_REDUCE_SUM8_EX(cmplx128_t, cmplx128_t, st, p, m, sum, 0, __variance_cmplx128_P1EXPR, masked_cnt);
1713
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
1714
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
1715
+ if ( __trigger ) {
1716
+ if ( ! op_mask ) {
1717
+ ca_create_mask(co);
1718
+ op_mask = (boolean8_t *) co->mask->ptr;
1719
+ }
1720
+ op_mask[out_i] = 1;
1721
+ op[out_i++] = (double) 0.0;
1722
+ } else if ( n_valid < 2 ) {
1723
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
1724
+ cell): return zero rather than UNDEF, matching pre-migration
1725
+ behaviour of `cnt > 1 ? formula : 0`. */
1726
+ op[out_i++] = (double) 0.0;
1727
+ } else {
1728
+ cmplx128_t mean = sum / (cmplx128_t)(double) n_valid;
1729
+ /* Pass 2: M2 = Σ (x - mean)² */
1730
+ double M2 = 0.0;
1731
+ ca_size_t __unused_mc = 0;
1732
+ CA_SLAB_REDUCE_SUM8_EX(cmplx128_t, double, st, p, m, M2, 0.0, __variance_cmplx128_P2EXPR, __unused_mc);
1733
+ op[out_i++] = (double)(M2 / (double)(n_valid - 1));
1734
+ }
1735
+ }
1736
+ ca_iter_state_finish(&st);
1737
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
1738
+ keep_axis means the output is a 1-element CArray; unwrap it to
1739
+ the ruby scalar (matches the standard reduce emit path lines
1740
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
1741
+ if ( naxes == ca->ndim && !keep_axis ) {
1742
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
1743
+ return rb_float_new(op[0]);
1744
+ }
1745
+ return vout;
1746
+ }
1747
+ #undef __variance_cmplx128_P1EXPR
1748
+ #undef __variance_cmplx128_P2EXPR
1749
+
1750
+ #define __variance_bool_P1EXPR(__x) ((double)(__x))
1751
+ #define __variance_bool_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
1752
+ static VALUE
1753
+ variance_ki_native_bool (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
1754
+ {
1755
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
1756
+ CArray *co;
1757
+ GetCArray(vout, co);
1758
+ double *op = (double *) co->ptr;
1759
+ ca_iter_state st;
1760
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
1761
+ slab_axes, naxes, 0);
1762
+ if ( rc != CA_ITER_OK ) {
1763
+ rb_raise(rb_eRuntimeError,
1764
+ "variance_ki: kernel_iterator init failed rc=%d", rc);
1765
+ }
1766
+ char *p;
1767
+ boolean8_t *m;
1768
+ ca_size_t out_i = 0;
1769
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
1770
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
1771
+ /* Pass 1: sum = Σx (+ masked_cnt) */
1772
+ double sum = 0.0;
1773
+ ca_size_t masked_cnt = 0;
1774
+ CA_SLAB_REDUCE_SUM8_EX(boolean8_t, double, st, p, m, sum, 0.0, __variance_bool_P1EXPR, masked_cnt);
1775
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
1776
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
1777
+ if ( __trigger ) {
1778
+ if ( ! op_mask ) {
1779
+ ca_create_mask(co);
1780
+ op_mask = (boolean8_t *) co->mask->ptr;
1781
+ }
1782
+ op_mask[out_i] = 1;
1783
+ op[out_i++] = (double) 0.0;
1784
+ } else if ( n_valid < 2 ) {
1785
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
1786
+ cell): return zero rather than UNDEF, matching pre-migration
1787
+ behaviour of `cnt > 1 ? formula : 0`. */
1788
+ op[out_i++] = (double) 0.0;
1789
+ } else {
1790
+ double mean = sum / (double) n_valid;
1791
+ /* Pass 2: M2 = Σ (x - mean)² */
1792
+ double M2 = 0.0;
1793
+ ca_size_t __unused_mc = 0;
1794
+ CA_SLAB_REDUCE_SUM8_EX(boolean8_t, double, st, p, m, M2, 0.0, __variance_bool_P2EXPR, __unused_mc);
1795
+ op[out_i++] = (double)(M2 / (double)(n_valid - 1));
1796
+ }
1797
+ }
1798
+ ca_iter_state_finish(&st);
1799
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
1800
+ keep_axis means the output is a 1-element CArray; unwrap it to
1801
+ the ruby scalar (matches the standard reduce emit path lines
1802
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
1803
+ if ( naxes == ca->ndim && !keep_axis ) {
1804
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
1805
+ return rb_float_new(op[0]);
1806
+ }
1807
+ return vout;
1808
+ }
1809
+ #undef __variance_bool_P1EXPR
1810
+ #undef __variance_bool_P2EXPR
1811
+
1812
+ static VALUE
1813
+ variance_ki_native_object (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
1814
+ {
1815
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_OBJECT, keep_axis);
1816
+ CArray *co;
1817
+ GetCArray(vout, co);
1818
+ VALUE *op = (VALUE *) co->ptr;
1819
+ ca_iter_state st;
1820
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
1821
+ slab_axes, naxes, 0);
1822
+ if ( rc != CA_ITER_OK ) {
1823
+ rb_raise(rb_eRuntimeError,
1824
+ "variance_ki: kernel_iterator init failed rc=%d", rc);
1825
+ }
1826
+ char *p;
1827
+ boolean8_t *m;
1828
+ ca_size_t out_i = 0;
1829
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
1830
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
1831
+ /* Pass 1: sum = Σx (+ masked_cnt) */
1832
+ VALUE sum = INT2FIX(0);
1833
+ ca_size_t masked_cnt = 0;
1834
+ CA_SLAB_REDUCE_T_EX(VALUE, st, p, m, sum, INT2FIX(0), sum = rb_funcall(sum, rb_intern("+"), 1, v), masked_cnt);
1835
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
1836
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
1837
+ if ( __trigger ) {
1838
+ if ( ! op_mask ) {
1839
+ ca_create_mask(co);
1840
+ op_mask = (boolean8_t *) co->mask->ptr;
1841
+ }
1842
+ op_mask[out_i] = 1;
1843
+ op[out_i++] = (VALUE) INT2FIX(0);
1844
+ } else if ( n_valid < 2 ) {
1845
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
1846
+ cell): return zero rather than UNDEF, matching pre-migration
1847
+ behaviour of `cnt > 1 ? formula : 0`. */
1848
+ op[out_i++] = (VALUE) INT2FIX(0);
1849
+ } else {
1850
+ VALUE mean = rb_funcall(sum, rb_intern("/"), 1, LONG2NUM(n_valid));
1851
+ /* Pass 2: M2 = Σ (x - mean)² */
1852
+ VALUE M2 = INT2FIX(0);
1853
+ ca_size_t __unused_mc = 0;
1854
+ CA_SLAB_REDUCE_T_EX(VALUE, st, p, m, M2, INT2FIX(0), VALUE __d = rb_funcall(v, rb_intern("-"), 1, mean);
1855
+ M2 = rb_funcall(M2, rb_intern("+"), 1, rb_funcall(__d, rb_intern("*"), 1, __d)), __unused_mc);
1856
+ op[out_i++] = (VALUE)(rb_funcall(M2, rb_intern("/"), 1, LONG2NUM(n_valid - 1)));
1857
+ }
1858
+ }
1859
+ ca_iter_state_finish(&st);
1860
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
1861
+ keep_axis means the output is a 1-element CArray; unwrap it to
1862
+ the ruby scalar (matches the standard reduce emit path lines
1863
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
1864
+ if ( naxes == ca->ndim && !keep_axis ) {
1865
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
1866
+ return (VALUE)(op[0]);
1867
+ }
1868
+ return vout;
1869
+ }
1870
+
1871
+ VALUE
1872
+ rb_ca_variance_ki (int argc, VALUE *argv, VALUE self)
1873
+ {
1874
+ CArray *src;
1875
+ GetCArray(self, src);
1876
+ volatile VALUE ropt = rb_pop_options(&argc, &argv);
1877
+ volatile VALUE raxis = Qnil, rkeep_axis = Qnil, rmin_count = Qnil, rfval = Qnil;
1878
+ rb_scan_options(ropt, "axis,keep_axis,min_count,fill_value", &raxis, &rkeep_axis, &rmin_count, &rfval);
1879
+ int keep_axis = RTEST(rkeep_axis);
1880
+ int8_t slab_axes[CA_RANK_MAX];
1881
+ int8_t naxes = rb_ca_parse_reduce_axes_kw(raxis, src, slab_axes);
1882
+ ca_size_t min_count = -1;
1883
+ if ( ! NIL_P(rmin_count) ) {
1884
+ ca_size_t mc_user = NUM2SIZE(rmin_count);
1885
+ if ( mc_user < 0 ) {
1886
+ rb_raise(rb_eArgError, "variance_ki: :min_count must be non-negative, got %lld", (long long) mc_user);
1887
+ }
1888
+ /* :min_count = 0 collapses to legacy default (any 1 valid). */
1889
+ if ( mc_user > 0 ) min_count = mc_user;
1890
+ }
1891
+ VALUE result;
1892
+ if ( argc > 0 ) {
1893
+ rb_raise(rb_eArgError, "variance_ki: positional axis arguments are no longer accepted (got %d); use axis: kwarg, e.g. a.variance(axis: 0) or a.variance(axis: [0, 1])", argc);
1894
+ }
1895
+ switch ( src->data_type ) {
1896
+ case CA_INT8: {
1897
+ result = variance_ki_native_i8(self, src, slab_axes, naxes, keep_axis, min_count);
1898
+ break;
1899
+ }
1900
+ case CA_UINT8: {
1901
+ result = variance_ki_native_u8(self, src, slab_axes, naxes, keep_axis, min_count);
1902
+ break;
1903
+ }
1904
+ case CA_INT16: {
1905
+ result = variance_ki_native_i16(self, src, slab_axes, naxes, keep_axis, min_count);
1906
+ break;
1907
+ }
1908
+ case CA_UINT16: {
1909
+ result = variance_ki_native_u16(self, src, slab_axes, naxes, keep_axis, min_count);
1910
+ break;
1911
+ }
1912
+ case CA_INT32: {
1913
+ result = variance_ki_native_i32(self, src, slab_axes, naxes, keep_axis, min_count);
1914
+ break;
1915
+ }
1916
+ case CA_UINT32: {
1917
+ result = variance_ki_native_u32(self, src, slab_axes, naxes, keep_axis, min_count);
1918
+ break;
1919
+ }
1920
+ case CA_INT64: {
1921
+ result = variance_ki_native_i64(self, src, slab_axes, naxes, keep_axis, min_count);
1922
+ break;
1923
+ }
1924
+ case CA_UINT64: {
1925
+ result = variance_ki_native_u64(self, src, slab_axes, naxes, keep_axis, min_count);
1926
+ break;
1927
+ }
1928
+ case CA_FLOAT32: {
1929
+ result = variance_ki_native_f32(self, src, slab_axes, naxes, keep_axis, min_count);
1930
+ break;
1931
+ }
1932
+ case CA_FLOAT64: {
1933
+ result = variance_ki_native_f64(self, src, slab_axes, naxes, keep_axis, min_count);
1934
+ break;
1935
+ }
1936
+ case CA_CMPLX64: {
1937
+ result = variance_ki_native_cmplx64(self, src, slab_axes, naxes, keep_axis, min_count);
1938
+ break;
1939
+ }
1940
+ case CA_CMPLX128: {
1941
+ result = variance_ki_native_cmplx128(self, src, slab_axes, naxes, keep_axis, min_count);
1942
+ break;
1943
+ }
1944
+ case CA_BOOLEAN: {
1945
+ result = variance_ki_native_bool(self, src, slab_axes, naxes, keep_axis, min_count);
1946
+ break;
1947
+ }
1948
+ case CA_OBJECT: {
1949
+ result = variance_ki_native_object(self, src, slab_axes, naxes, keep_axis, min_count);
1950
+ break;
1951
+ }
1952
+ default:
1953
+ rb_raise(rb_eCADataTypeError, "variance_ki: source data_type :%s not supported (expected one of: i8, u8, i16, u16, i32, u32, i64, u64, f32, f64, cmplx64, cmplx128, bool, object)", ca_type_name[src->data_type]);
1954
+ }
1955
+ if ( ! NIL_P(rfval) ) {
1956
+ if ( result == CA_UNDEF ) {
1957
+ result = rfval;
1958
+ } else if ( TYPE(result) != T_FLOAT && rb_obj_is_kind_of(result, rb_cCArray) ) {
1959
+ CArray *cr;
1960
+ GetCArray(result, cr);
1961
+ if ( ca_has_mask(cr) ) {
1962
+ result = rb_ca_mask_fill_copy(result, rfval);
1963
+ }
1964
+ }
1965
+ }
1966
+ return result;
1967
+ }
1968
+
1969
+ /* ===== stddevp_ki ============================================ */
1970
+
1971
+ #define __stddevp_i8_P1EXPR(__x) ((double)(__x))
1972
+ #define __stddevp_i8_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
1973
+ static VALUE
1974
+ stddevp_ki_native_i8 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
1975
+ {
1976
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
1977
+ CArray *co;
1978
+ GetCArray(vout, co);
1979
+ double *op = (double *) co->ptr;
1980
+ ca_iter_state st;
1981
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
1982
+ slab_axes, naxes, 0);
1983
+ if ( rc != CA_ITER_OK ) {
1984
+ rb_raise(rb_eRuntimeError,
1985
+ "stddevp_ki: kernel_iterator init failed rc=%d", rc);
1986
+ }
1987
+ char *p;
1988
+ boolean8_t *m;
1989
+ ca_size_t out_i = 0;
1990
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
1991
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
1992
+ /* Pass 1: sum = Σx (+ masked_cnt) */
1993
+ double sum = 0.0;
1994
+ ca_size_t masked_cnt = 0;
1995
+ CA_SLAB_REDUCE_SUM8_EX(int8_t, double, st, p, m, sum, 0.0, __stddevp_i8_P1EXPR, masked_cnt);
1996
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
1997
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
1998
+ if ( __trigger ) {
1999
+ if ( ! op_mask ) {
2000
+ ca_create_mask(co);
2001
+ op_mask = (boolean8_t *) co->mask->ptr;
2002
+ }
2003
+ op_mask[out_i] = 1;
2004
+ op[out_i++] = (double) 0.0;
2005
+ } else if ( n_valid < 1 ) {
2006
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
2007
+ cell): return zero rather than UNDEF, matching pre-migration
2008
+ behaviour of `cnt > 1 ? formula : 0`. */
2009
+ op[out_i++] = (double) 0.0;
2010
+ } else {
2011
+ double mean = sum / (double) n_valid;
2012
+ /* Pass 2: M2 = Σ (x - mean)² */
2013
+ double M2 = 0.0;
2014
+ ca_size_t __unused_mc = 0;
2015
+ CA_SLAB_REDUCE_SUM8_EX(int8_t, double, st, p, m, M2, 0.0, __stddevp_i8_P2EXPR, __unused_mc);
2016
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid), 0.0)));
2017
+ }
2018
+ }
2019
+ ca_iter_state_finish(&st);
2020
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
2021
+ keep_axis means the output is a 1-element CArray; unwrap it to
2022
+ the ruby scalar (matches the standard reduce emit path lines
2023
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
2024
+ if ( naxes == ca->ndim && !keep_axis ) {
2025
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
2026
+ return rb_float_new(op[0]);
2027
+ }
2028
+ return vout;
2029
+ }
2030
+ #undef __stddevp_i8_P1EXPR
2031
+ #undef __stddevp_i8_P2EXPR
2032
+
2033
+ #define __stddevp_u8_P1EXPR(__x) ((double)(__x))
2034
+ #define __stddevp_u8_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
2035
+ static VALUE
2036
+ stddevp_ki_native_u8 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
2037
+ {
2038
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
2039
+ CArray *co;
2040
+ GetCArray(vout, co);
2041
+ double *op = (double *) co->ptr;
2042
+ ca_iter_state st;
2043
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
2044
+ slab_axes, naxes, 0);
2045
+ if ( rc != CA_ITER_OK ) {
2046
+ rb_raise(rb_eRuntimeError,
2047
+ "stddevp_ki: kernel_iterator init failed rc=%d", rc);
2048
+ }
2049
+ char *p;
2050
+ boolean8_t *m;
2051
+ ca_size_t out_i = 0;
2052
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
2053
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
2054
+ /* Pass 1: sum = Σx (+ masked_cnt) */
2055
+ double sum = 0.0;
2056
+ ca_size_t masked_cnt = 0;
2057
+ CA_SLAB_REDUCE_SUM8_EX(uint8_t, double, st, p, m, sum, 0.0, __stddevp_u8_P1EXPR, masked_cnt);
2058
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
2059
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
2060
+ if ( __trigger ) {
2061
+ if ( ! op_mask ) {
2062
+ ca_create_mask(co);
2063
+ op_mask = (boolean8_t *) co->mask->ptr;
2064
+ }
2065
+ op_mask[out_i] = 1;
2066
+ op[out_i++] = (double) 0.0;
2067
+ } else if ( n_valid < 1 ) {
2068
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
2069
+ cell): return zero rather than UNDEF, matching pre-migration
2070
+ behaviour of `cnt > 1 ? formula : 0`. */
2071
+ op[out_i++] = (double) 0.0;
2072
+ } else {
2073
+ double mean = sum / (double) n_valid;
2074
+ /* Pass 2: M2 = Σ (x - mean)² */
2075
+ double M2 = 0.0;
2076
+ ca_size_t __unused_mc = 0;
2077
+ CA_SLAB_REDUCE_SUM8_EX(uint8_t, double, st, p, m, M2, 0.0, __stddevp_u8_P2EXPR, __unused_mc);
2078
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid), 0.0)));
2079
+ }
2080
+ }
2081
+ ca_iter_state_finish(&st);
2082
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
2083
+ keep_axis means the output is a 1-element CArray; unwrap it to
2084
+ the ruby scalar (matches the standard reduce emit path lines
2085
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
2086
+ if ( naxes == ca->ndim && !keep_axis ) {
2087
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
2088
+ return rb_float_new(op[0]);
2089
+ }
2090
+ return vout;
2091
+ }
2092
+ #undef __stddevp_u8_P1EXPR
2093
+ #undef __stddevp_u8_P2EXPR
2094
+
2095
+ #define __stddevp_i16_P1EXPR(__x) ((double)(__x))
2096
+ #define __stddevp_i16_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
2097
+ static VALUE
2098
+ stddevp_ki_native_i16 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
2099
+ {
2100
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
2101
+ CArray *co;
2102
+ GetCArray(vout, co);
2103
+ double *op = (double *) co->ptr;
2104
+ ca_iter_state st;
2105
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
2106
+ slab_axes, naxes, 0);
2107
+ if ( rc != CA_ITER_OK ) {
2108
+ rb_raise(rb_eRuntimeError,
2109
+ "stddevp_ki: kernel_iterator init failed rc=%d", rc);
2110
+ }
2111
+ char *p;
2112
+ boolean8_t *m;
2113
+ ca_size_t out_i = 0;
2114
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
2115
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
2116
+ /* Pass 1: sum = Σx (+ masked_cnt) */
2117
+ double sum = 0.0;
2118
+ ca_size_t masked_cnt = 0;
2119
+ CA_SLAB_REDUCE_SUM8_EX(int16_t, double, st, p, m, sum, 0.0, __stddevp_i16_P1EXPR, masked_cnt);
2120
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
2121
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
2122
+ if ( __trigger ) {
2123
+ if ( ! op_mask ) {
2124
+ ca_create_mask(co);
2125
+ op_mask = (boolean8_t *) co->mask->ptr;
2126
+ }
2127
+ op_mask[out_i] = 1;
2128
+ op[out_i++] = (double) 0.0;
2129
+ } else if ( n_valid < 1 ) {
2130
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
2131
+ cell): return zero rather than UNDEF, matching pre-migration
2132
+ behaviour of `cnt > 1 ? formula : 0`. */
2133
+ op[out_i++] = (double) 0.0;
2134
+ } else {
2135
+ double mean = sum / (double) n_valid;
2136
+ /* Pass 2: M2 = Σ (x - mean)² */
2137
+ double M2 = 0.0;
2138
+ ca_size_t __unused_mc = 0;
2139
+ CA_SLAB_REDUCE_SUM8_EX(int16_t, double, st, p, m, M2, 0.0, __stddevp_i16_P2EXPR, __unused_mc);
2140
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid), 0.0)));
2141
+ }
2142
+ }
2143
+ ca_iter_state_finish(&st);
2144
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
2145
+ keep_axis means the output is a 1-element CArray; unwrap it to
2146
+ the ruby scalar (matches the standard reduce emit path lines
2147
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
2148
+ if ( naxes == ca->ndim && !keep_axis ) {
2149
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
2150
+ return rb_float_new(op[0]);
2151
+ }
2152
+ return vout;
2153
+ }
2154
+ #undef __stddevp_i16_P1EXPR
2155
+ #undef __stddevp_i16_P2EXPR
2156
+
2157
+ #define __stddevp_u16_P1EXPR(__x) ((double)(__x))
2158
+ #define __stddevp_u16_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
2159
+ static VALUE
2160
+ stddevp_ki_native_u16 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
2161
+ {
2162
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
2163
+ CArray *co;
2164
+ GetCArray(vout, co);
2165
+ double *op = (double *) co->ptr;
2166
+ ca_iter_state st;
2167
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
2168
+ slab_axes, naxes, 0);
2169
+ if ( rc != CA_ITER_OK ) {
2170
+ rb_raise(rb_eRuntimeError,
2171
+ "stddevp_ki: kernel_iterator init failed rc=%d", rc);
2172
+ }
2173
+ char *p;
2174
+ boolean8_t *m;
2175
+ ca_size_t out_i = 0;
2176
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
2177
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
2178
+ /* Pass 1: sum = Σx (+ masked_cnt) */
2179
+ double sum = 0.0;
2180
+ ca_size_t masked_cnt = 0;
2181
+ CA_SLAB_REDUCE_SUM8_EX(uint16_t, double, st, p, m, sum, 0.0, __stddevp_u16_P1EXPR, masked_cnt);
2182
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
2183
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
2184
+ if ( __trigger ) {
2185
+ if ( ! op_mask ) {
2186
+ ca_create_mask(co);
2187
+ op_mask = (boolean8_t *) co->mask->ptr;
2188
+ }
2189
+ op_mask[out_i] = 1;
2190
+ op[out_i++] = (double) 0.0;
2191
+ } else if ( n_valid < 1 ) {
2192
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
2193
+ cell): return zero rather than UNDEF, matching pre-migration
2194
+ behaviour of `cnt > 1 ? formula : 0`. */
2195
+ op[out_i++] = (double) 0.0;
2196
+ } else {
2197
+ double mean = sum / (double) n_valid;
2198
+ /* Pass 2: M2 = Σ (x - mean)² */
2199
+ double M2 = 0.0;
2200
+ ca_size_t __unused_mc = 0;
2201
+ CA_SLAB_REDUCE_SUM8_EX(uint16_t, double, st, p, m, M2, 0.0, __stddevp_u16_P2EXPR, __unused_mc);
2202
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid), 0.0)));
2203
+ }
2204
+ }
2205
+ ca_iter_state_finish(&st);
2206
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
2207
+ keep_axis means the output is a 1-element CArray; unwrap it to
2208
+ the ruby scalar (matches the standard reduce emit path lines
2209
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
2210
+ if ( naxes == ca->ndim && !keep_axis ) {
2211
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
2212
+ return rb_float_new(op[0]);
2213
+ }
2214
+ return vout;
2215
+ }
2216
+ #undef __stddevp_u16_P1EXPR
2217
+ #undef __stddevp_u16_P2EXPR
2218
+
2219
+ #define __stddevp_i32_P1EXPR(__x) ((double)(__x))
2220
+ #define __stddevp_i32_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
2221
+ static VALUE
2222
+ stddevp_ki_native_i32 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
2223
+ {
2224
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
2225
+ CArray *co;
2226
+ GetCArray(vout, co);
2227
+ double *op = (double *) co->ptr;
2228
+ ca_iter_state st;
2229
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
2230
+ slab_axes, naxes, 0);
2231
+ if ( rc != CA_ITER_OK ) {
2232
+ rb_raise(rb_eRuntimeError,
2233
+ "stddevp_ki: kernel_iterator init failed rc=%d", rc);
2234
+ }
2235
+ char *p;
2236
+ boolean8_t *m;
2237
+ ca_size_t out_i = 0;
2238
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
2239
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
2240
+ /* Pass 1: sum = Σx (+ masked_cnt) */
2241
+ double sum = 0.0;
2242
+ ca_size_t masked_cnt = 0;
2243
+ CA_SLAB_REDUCE_SUM8_EX(int32_t, double, st, p, m, sum, 0.0, __stddevp_i32_P1EXPR, masked_cnt);
2244
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
2245
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
2246
+ if ( __trigger ) {
2247
+ if ( ! op_mask ) {
2248
+ ca_create_mask(co);
2249
+ op_mask = (boolean8_t *) co->mask->ptr;
2250
+ }
2251
+ op_mask[out_i] = 1;
2252
+ op[out_i++] = (double) 0.0;
2253
+ } else if ( n_valid < 1 ) {
2254
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
2255
+ cell): return zero rather than UNDEF, matching pre-migration
2256
+ behaviour of `cnt > 1 ? formula : 0`. */
2257
+ op[out_i++] = (double) 0.0;
2258
+ } else {
2259
+ double mean = sum / (double) n_valid;
2260
+ /* Pass 2: M2 = Σ (x - mean)² */
2261
+ double M2 = 0.0;
2262
+ ca_size_t __unused_mc = 0;
2263
+ CA_SLAB_REDUCE_SUM8_EX(int32_t, double, st, p, m, M2, 0.0, __stddevp_i32_P2EXPR, __unused_mc);
2264
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid), 0.0)));
2265
+ }
2266
+ }
2267
+ ca_iter_state_finish(&st);
2268
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
2269
+ keep_axis means the output is a 1-element CArray; unwrap it to
2270
+ the ruby scalar (matches the standard reduce emit path lines
2271
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
2272
+ if ( naxes == ca->ndim && !keep_axis ) {
2273
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
2274
+ return rb_float_new(op[0]);
2275
+ }
2276
+ return vout;
2277
+ }
2278
+ #undef __stddevp_i32_P1EXPR
2279
+ #undef __stddevp_i32_P2EXPR
2280
+
2281
+ #define __stddevp_u32_P1EXPR(__x) ((double)(__x))
2282
+ #define __stddevp_u32_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
2283
+ static VALUE
2284
+ stddevp_ki_native_u32 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
2285
+ {
2286
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
2287
+ CArray *co;
2288
+ GetCArray(vout, co);
2289
+ double *op = (double *) co->ptr;
2290
+ ca_iter_state st;
2291
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
2292
+ slab_axes, naxes, 0);
2293
+ if ( rc != CA_ITER_OK ) {
2294
+ rb_raise(rb_eRuntimeError,
2295
+ "stddevp_ki: kernel_iterator init failed rc=%d", rc);
2296
+ }
2297
+ char *p;
2298
+ boolean8_t *m;
2299
+ ca_size_t out_i = 0;
2300
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
2301
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
2302
+ /* Pass 1: sum = Σx (+ masked_cnt) */
2303
+ double sum = 0.0;
2304
+ ca_size_t masked_cnt = 0;
2305
+ CA_SLAB_REDUCE_SUM8_EX(uint32_t, double, st, p, m, sum, 0.0, __stddevp_u32_P1EXPR, masked_cnt);
2306
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
2307
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
2308
+ if ( __trigger ) {
2309
+ if ( ! op_mask ) {
2310
+ ca_create_mask(co);
2311
+ op_mask = (boolean8_t *) co->mask->ptr;
2312
+ }
2313
+ op_mask[out_i] = 1;
2314
+ op[out_i++] = (double) 0.0;
2315
+ } else if ( n_valid < 1 ) {
2316
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
2317
+ cell): return zero rather than UNDEF, matching pre-migration
2318
+ behaviour of `cnt > 1 ? formula : 0`. */
2319
+ op[out_i++] = (double) 0.0;
2320
+ } else {
2321
+ double mean = sum / (double) n_valid;
2322
+ /* Pass 2: M2 = Σ (x - mean)² */
2323
+ double M2 = 0.0;
2324
+ ca_size_t __unused_mc = 0;
2325
+ CA_SLAB_REDUCE_SUM8_EX(uint32_t, double, st, p, m, M2, 0.0, __stddevp_u32_P2EXPR, __unused_mc);
2326
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid), 0.0)));
2327
+ }
2328
+ }
2329
+ ca_iter_state_finish(&st);
2330
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
2331
+ keep_axis means the output is a 1-element CArray; unwrap it to
2332
+ the ruby scalar (matches the standard reduce emit path lines
2333
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
2334
+ if ( naxes == ca->ndim && !keep_axis ) {
2335
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
2336
+ return rb_float_new(op[0]);
2337
+ }
2338
+ return vout;
2339
+ }
2340
+ #undef __stddevp_u32_P1EXPR
2341
+ #undef __stddevp_u32_P2EXPR
2342
+
2343
+ #define __stddevp_i64_P1EXPR(__x) ((double)(__x))
2344
+ #define __stddevp_i64_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
2345
+ static VALUE
2346
+ stddevp_ki_native_i64 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
2347
+ {
2348
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
2349
+ CArray *co;
2350
+ GetCArray(vout, co);
2351
+ double *op = (double *) co->ptr;
2352
+ ca_iter_state st;
2353
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
2354
+ slab_axes, naxes, 0);
2355
+ if ( rc != CA_ITER_OK ) {
2356
+ rb_raise(rb_eRuntimeError,
2357
+ "stddevp_ki: kernel_iterator init failed rc=%d", rc);
2358
+ }
2359
+ char *p;
2360
+ boolean8_t *m;
2361
+ ca_size_t out_i = 0;
2362
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
2363
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
2364
+ /* Pass 1: sum = Σx (+ masked_cnt) */
2365
+ double sum = 0.0;
2366
+ ca_size_t masked_cnt = 0;
2367
+ CA_SLAB_REDUCE_SUM8_EX(int64_t, double, st, p, m, sum, 0.0, __stddevp_i64_P1EXPR, masked_cnt);
2368
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
2369
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
2370
+ if ( __trigger ) {
2371
+ if ( ! op_mask ) {
2372
+ ca_create_mask(co);
2373
+ op_mask = (boolean8_t *) co->mask->ptr;
2374
+ }
2375
+ op_mask[out_i] = 1;
2376
+ op[out_i++] = (double) 0.0;
2377
+ } else if ( n_valid < 1 ) {
2378
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
2379
+ cell): return zero rather than UNDEF, matching pre-migration
2380
+ behaviour of `cnt > 1 ? formula : 0`. */
2381
+ op[out_i++] = (double) 0.0;
2382
+ } else {
2383
+ double mean = sum / (double) n_valid;
2384
+ /* Pass 2: M2 = Σ (x - mean)² */
2385
+ double M2 = 0.0;
2386
+ ca_size_t __unused_mc = 0;
2387
+ CA_SLAB_REDUCE_SUM8_EX(int64_t, double, st, p, m, M2, 0.0, __stddevp_i64_P2EXPR, __unused_mc);
2388
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid), 0.0)));
2389
+ }
2390
+ }
2391
+ ca_iter_state_finish(&st);
2392
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
2393
+ keep_axis means the output is a 1-element CArray; unwrap it to
2394
+ the ruby scalar (matches the standard reduce emit path lines
2395
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
2396
+ if ( naxes == ca->ndim && !keep_axis ) {
2397
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
2398
+ return rb_float_new(op[0]);
2399
+ }
2400
+ return vout;
2401
+ }
2402
+ #undef __stddevp_i64_P1EXPR
2403
+ #undef __stddevp_i64_P2EXPR
2404
+
2405
+ #define __stddevp_u64_P1EXPR(__x) ((double)(__x))
2406
+ #define __stddevp_u64_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
2407
+ static VALUE
2408
+ stddevp_ki_native_u64 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
2409
+ {
2410
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
2411
+ CArray *co;
2412
+ GetCArray(vout, co);
2413
+ double *op = (double *) co->ptr;
2414
+ ca_iter_state st;
2415
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
2416
+ slab_axes, naxes, 0);
2417
+ if ( rc != CA_ITER_OK ) {
2418
+ rb_raise(rb_eRuntimeError,
2419
+ "stddevp_ki: kernel_iterator init failed rc=%d", rc);
2420
+ }
2421
+ char *p;
2422
+ boolean8_t *m;
2423
+ ca_size_t out_i = 0;
2424
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
2425
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
2426
+ /* Pass 1: sum = Σx (+ masked_cnt) */
2427
+ double sum = 0.0;
2428
+ ca_size_t masked_cnt = 0;
2429
+ CA_SLAB_REDUCE_SUM8_EX(uint64_t, double, st, p, m, sum, 0.0, __stddevp_u64_P1EXPR, masked_cnt);
2430
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
2431
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
2432
+ if ( __trigger ) {
2433
+ if ( ! op_mask ) {
2434
+ ca_create_mask(co);
2435
+ op_mask = (boolean8_t *) co->mask->ptr;
2436
+ }
2437
+ op_mask[out_i] = 1;
2438
+ op[out_i++] = (double) 0.0;
2439
+ } else if ( n_valid < 1 ) {
2440
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
2441
+ cell): return zero rather than UNDEF, matching pre-migration
2442
+ behaviour of `cnt > 1 ? formula : 0`. */
2443
+ op[out_i++] = (double) 0.0;
2444
+ } else {
2445
+ double mean = sum / (double) n_valid;
2446
+ /* Pass 2: M2 = Σ (x - mean)² */
2447
+ double M2 = 0.0;
2448
+ ca_size_t __unused_mc = 0;
2449
+ CA_SLAB_REDUCE_SUM8_EX(uint64_t, double, st, p, m, M2, 0.0, __stddevp_u64_P2EXPR, __unused_mc);
2450
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid), 0.0)));
2451
+ }
2452
+ }
2453
+ ca_iter_state_finish(&st);
2454
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
2455
+ keep_axis means the output is a 1-element CArray; unwrap it to
2456
+ the ruby scalar (matches the standard reduce emit path lines
2457
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
2458
+ if ( naxes == ca->ndim && !keep_axis ) {
2459
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
2460
+ return rb_float_new(op[0]);
2461
+ }
2462
+ return vout;
2463
+ }
2464
+ #undef __stddevp_u64_P1EXPR
2465
+ #undef __stddevp_u64_P2EXPR
2466
+
2467
+ #define __stddevp_f32_P1EXPR(__x) ((double)(__x))
2468
+ #define __stddevp_f32_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
2469
+ static VALUE
2470
+ stddevp_ki_native_f32 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
2471
+ {
2472
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
2473
+ CArray *co;
2474
+ GetCArray(vout, co);
2475
+ double *op = (double *) co->ptr;
2476
+ ca_iter_state st;
2477
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
2478
+ slab_axes, naxes, 0);
2479
+ if ( rc != CA_ITER_OK ) {
2480
+ rb_raise(rb_eRuntimeError,
2481
+ "stddevp_ki: kernel_iterator init failed rc=%d", rc);
2482
+ }
2483
+ char *p;
2484
+ boolean8_t *m;
2485
+ ca_size_t out_i = 0;
2486
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
2487
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
2488
+ /* Pass 1: sum = Σx (+ masked_cnt) */
2489
+ double sum = 0.0;
2490
+ ca_size_t masked_cnt = 0;
2491
+ CA_SLAB_REDUCE_SUM8_EX(float, double, st, p, m, sum, 0.0, __stddevp_f32_P1EXPR, masked_cnt);
2492
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
2493
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
2494
+ if ( __trigger ) {
2495
+ if ( ! op_mask ) {
2496
+ ca_create_mask(co);
2497
+ op_mask = (boolean8_t *) co->mask->ptr;
2498
+ }
2499
+ op_mask[out_i] = 1;
2500
+ op[out_i++] = (double) 0.0;
2501
+ } else if ( n_valid < 1 ) {
2502
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
2503
+ cell): return zero rather than UNDEF, matching pre-migration
2504
+ behaviour of `cnt > 1 ? formula : 0`. */
2505
+ op[out_i++] = (double) 0.0;
2506
+ } else {
2507
+ double mean = sum / (double) n_valid;
2508
+ /* Pass 2: M2 = Σ (x - mean)² */
2509
+ double M2 = 0.0;
2510
+ ca_size_t __unused_mc = 0;
2511
+ CA_SLAB_REDUCE_SUM8_EX(float, double, st, p, m, M2, 0.0, __stddevp_f32_P2EXPR, __unused_mc);
2512
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid), 0.0)));
2513
+ }
2514
+ }
2515
+ ca_iter_state_finish(&st);
2516
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
2517
+ keep_axis means the output is a 1-element CArray; unwrap it to
2518
+ the ruby scalar (matches the standard reduce emit path lines
2519
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
2520
+ if ( naxes == ca->ndim && !keep_axis ) {
2521
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
2522
+ return rb_float_new(op[0]);
2523
+ }
2524
+ return vout;
2525
+ }
2526
+ #undef __stddevp_f32_P1EXPR
2527
+ #undef __stddevp_f32_P2EXPR
2528
+
2529
+ #define __stddevp_f64_P1EXPR(__x) ((double)(__x))
2530
+ #define __stddevp_f64_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
2531
+ static VALUE
2532
+ stddevp_ki_native_f64 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
2533
+ {
2534
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
2535
+ CArray *co;
2536
+ GetCArray(vout, co);
2537
+ double *op = (double *) co->ptr;
2538
+ ca_iter_state st;
2539
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
2540
+ slab_axes, naxes, 0);
2541
+ if ( rc != CA_ITER_OK ) {
2542
+ rb_raise(rb_eRuntimeError,
2543
+ "stddevp_ki: kernel_iterator init failed rc=%d", rc);
2544
+ }
2545
+ char *p;
2546
+ boolean8_t *m;
2547
+ ca_size_t out_i = 0;
2548
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
2549
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
2550
+ /* Pass 1: sum = Σx (+ masked_cnt) */
2551
+ double sum = 0.0;
2552
+ ca_size_t masked_cnt = 0;
2553
+ CA_SLAB_REDUCE_SUM8_EX(double, double, st, p, m, sum, 0.0, __stddevp_f64_P1EXPR, masked_cnt);
2554
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
2555
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
2556
+ if ( __trigger ) {
2557
+ if ( ! op_mask ) {
2558
+ ca_create_mask(co);
2559
+ op_mask = (boolean8_t *) co->mask->ptr;
2560
+ }
2561
+ op_mask[out_i] = 1;
2562
+ op[out_i++] = (double) 0.0;
2563
+ } else if ( n_valid < 1 ) {
2564
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
2565
+ cell): return zero rather than UNDEF, matching pre-migration
2566
+ behaviour of `cnt > 1 ? formula : 0`. */
2567
+ op[out_i++] = (double) 0.0;
2568
+ } else {
2569
+ double mean = sum / (double) n_valid;
2570
+ /* Pass 2: M2 = Σ (x - mean)² */
2571
+ double M2 = 0.0;
2572
+ ca_size_t __unused_mc = 0;
2573
+ CA_SLAB_REDUCE_SUM8_EX(double, double, st, p, m, M2, 0.0, __stddevp_f64_P2EXPR, __unused_mc);
2574
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid), 0.0)));
2575
+ }
2576
+ }
2577
+ ca_iter_state_finish(&st);
2578
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
2579
+ keep_axis means the output is a 1-element CArray; unwrap it to
2580
+ the ruby scalar (matches the standard reduce emit path lines
2581
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
2582
+ if ( naxes == ca->ndim && !keep_axis ) {
2583
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
2584
+ return rb_float_new(op[0]);
2585
+ }
2586
+ return vout;
2587
+ }
2588
+ #undef __stddevp_f64_P1EXPR
2589
+ #undef __stddevp_f64_P2EXPR
2590
+
2591
+ #define __stddevp_cmplx64_P1EXPR(__x) (__x)
2592
+ #define __stddevp_cmplx64_P2EXPR(__x) (creal((__x) - mean) * creal((__x) - mean) + cimag((__x) - mean) * cimag((__x) - mean))
2593
+ static VALUE
2594
+ stddevp_ki_native_cmplx64 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
2595
+ {
2596
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
2597
+ CArray *co;
2598
+ GetCArray(vout, co);
2599
+ double *op = (double *) co->ptr;
2600
+ ca_iter_state st;
2601
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
2602
+ slab_axes, naxes, 0);
2603
+ if ( rc != CA_ITER_OK ) {
2604
+ rb_raise(rb_eRuntimeError,
2605
+ "stddevp_ki: kernel_iterator init failed rc=%d", rc);
2606
+ }
2607
+ char *p;
2608
+ boolean8_t *m;
2609
+ ca_size_t out_i = 0;
2610
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
2611
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
2612
+ /* Pass 1: sum = Σx (+ masked_cnt) */
2613
+ cmplx64_t sum = 0;
2614
+ ca_size_t masked_cnt = 0;
2615
+ CA_SLAB_REDUCE_SUM8_EX(cmplx64_t, cmplx64_t, st, p, m, sum, 0, __stddevp_cmplx64_P1EXPR, masked_cnt);
2616
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
2617
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
2618
+ if ( __trigger ) {
2619
+ if ( ! op_mask ) {
2620
+ ca_create_mask(co);
2621
+ op_mask = (boolean8_t *) co->mask->ptr;
2622
+ }
2623
+ op_mask[out_i] = 1;
2624
+ op[out_i++] = (double) 0.0;
2625
+ } else if ( n_valid < 1 ) {
2626
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
2627
+ cell): return zero rather than UNDEF, matching pre-migration
2628
+ behaviour of `cnt > 1 ? formula : 0`. */
2629
+ op[out_i++] = (double) 0.0;
2630
+ } else {
2631
+ cmplx64_t mean = sum / (cmplx64_t)(double) n_valid;
2632
+ /* Pass 2: M2 = Σ (x - mean)² */
2633
+ double M2 = 0.0;
2634
+ ca_size_t __unused_mc = 0;
2635
+ CA_SLAB_REDUCE_SUM8_EX(cmplx64_t, double, st, p, m, M2, 0.0, __stddevp_cmplx64_P2EXPR, __unused_mc);
2636
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid), 0.0)));
2637
+ }
2638
+ }
2639
+ ca_iter_state_finish(&st);
2640
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
2641
+ keep_axis means the output is a 1-element CArray; unwrap it to
2642
+ the ruby scalar (matches the standard reduce emit path lines
2643
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
2644
+ if ( naxes == ca->ndim && !keep_axis ) {
2645
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
2646
+ return rb_float_new(op[0]);
2647
+ }
2648
+ return vout;
2649
+ }
2650
+ #undef __stddevp_cmplx64_P1EXPR
2651
+ #undef __stddevp_cmplx64_P2EXPR
2652
+
2653
+ #define __stddevp_cmplx128_P1EXPR(__x) (__x)
2654
+ #define __stddevp_cmplx128_P2EXPR(__x) (creal((__x) - mean) * creal((__x) - mean) + cimag((__x) - mean) * cimag((__x) - mean))
2655
+ static VALUE
2656
+ stddevp_ki_native_cmplx128 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
2657
+ {
2658
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
2659
+ CArray *co;
2660
+ GetCArray(vout, co);
2661
+ double *op = (double *) co->ptr;
2662
+ ca_iter_state st;
2663
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
2664
+ slab_axes, naxes, 0);
2665
+ if ( rc != CA_ITER_OK ) {
2666
+ rb_raise(rb_eRuntimeError,
2667
+ "stddevp_ki: kernel_iterator init failed rc=%d", rc);
2668
+ }
2669
+ char *p;
2670
+ boolean8_t *m;
2671
+ ca_size_t out_i = 0;
2672
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
2673
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
2674
+ /* Pass 1: sum = Σx (+ masked_cnt) */
2675
+ cmplx128_t sum = 0;
2676
+ ca_size_t masked_cnt = 0;
2677
+ CA_SLAB_REDUCE_SUM8_EX(cmplx128_t, cmplx128_t, st, p, m, sum, 0, __stddevp_cmplx128_P1EXPR, masked_cnt);
2678
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
2679
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
2680
+ if ( __trigger ) {
2681
+ if ( ! op_mask ) {
2682
+ ca_create_mask(co);
2683
+ op_mask = (boolean8_t *) co->mask->ptr;
2684
+ }
2685
+ op_mask[out_i] = 1;
2686
+ op[out_i++] = (double) 0.0;
2687
+ } else if ( n_valid < 1 ) {
2688
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
2689
+ cell): return zero rather than UNDEF, matching pre-migration
2690
+ behaviour of `cnt > 1 ? formula : 0`. */
2691
+ op[out_i++] = (double) 0.0;
2692
+ } else {
2693
+ cmplx128_t mean = sum / (cmplx128_t)(double) n_valid;
2694
+ /* Pass 2: M2 = Σ (x - mean)² */
2695
+ double M2 = 0.0;
2696
+ ca_size_t __unused_mc = 0;
2697
+ CA_SLAB_REDUCE_SUM8_EX(cmplx128_t, double, st, p, m, M2, 0.0, __stddevp_cmplx128_P2EXPR, __unused_mc);
2698
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid), 0.0)));
2699
+ }
2700
+ }
2701
+ ca_iter_state_finish(&st);
2702
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
2703
+ keep_axis means the output is a 1-element CArray; unwrap it to
2704
+ the ruby scalar (matches the standard reduce emit path lines
2705
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
2706
+ if ( naxes == ca->ndim && !keep_axis ) {
2707
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
2708
+ return rb_float_new(op[0]);
2709
+ }
2710
+ return vout;
2711
+ }
2712
+ #undef __stddevp_cmplx128_P1EXPR
2713
+ #undef __stddevp_cmplx128_P2EXPR
2714
+
2715
+ #define __stddevp_bool_P1EXPR(__x) ((double)(__x))
2716
+ #define __stddevp_bool_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
2717
+ static VALUE
2718
+ stddevp_ki_native_bool (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
2719
+ {
2720
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
2721
+ CArray *co;
2722
+ GetCArray(vout, co);
2723
+ double *op = (double *) co->ptr;
2724
+ ca_iter_state st;
2725
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
2726
+ slab_axes, naxes, 0);
2727
+ if ( rc != CA_ITER_OK ) {
2728
+ rb_raise(rb_eRuntimeError,
2729
+ "stddevp_ki: kernel_iterator init failed rc=%d", rc);
2730
+ }
2731
+ char *p;
2732
+ boolean8_t *m;
2733
+ ca_size_t out_i = 0;
2734
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
2735
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
2736
+ /* Pass 1: sum = Σx (+ masked_cnt) */
2737
+ double sum = 0.0;
2738
+ ca_size_t masked_cnt = 0;
2739
+ CA_SLAB_REDUCE_SUM8_EX(boolean8_t, double, st, p, m, sum, 0.0, __stddevp_bool_P1EXPR, masked_cnt);
2740
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
2741
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
2742
+ if ( __trigger ) {
2743
+ if ( ! op_mask ) {
2744
+ ca_create_mask(co);
2745
+ op_mask = (boolean8_t *) co->mask->ptr;
2746
+ }
2747
+ op_mask[out_i] = 1;
2748
+ op[out_i++] = (double) 0.0;
2749
+ } else if ( n_valid < 1 ) {
2750
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
2751
+ cell): return zero rather than UNDEF, matching pre-migration
2752
+ behaviour of `cnt > 1 ? formula : 0`. */
2753
+ op[out_i++] = (double) 0.0;
2754
+ } else {
2755
+ double mean = sum / (double) n_valid;
2756
+ /* Pass 2: M2 = Σ (x - mean)² */
2757
+ double M2 = 0.0;
2758
+ ca_size_t __unused_mc = 0;
2759
+ CA_SLAB_REDUCE_SUM8_EX(boolean8_t, double, st, p, m, M2, 0.0, __stddevp_bool_P2EXPR, __unused_mc);
2760
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid), 0.0)));
2761
+ }
2762
+ }
2763
+ ca_iter_state_finish(&st);
2764
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
2765
+ keep_axis means the output is a 1-element CArray; unwrap it to
2766
+ the ruby scalar (matches the standard reduce emit path lines
2767
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
2768
+ if ( naxes == ca->ndim && !keep_axis ) {
2769
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
2770
+ return rb_float_new(op[0]);
2771
+ }
2772
+ return vout;
2773
+ }
2774
+ #undef __stddevp_bool_P1EXPR
2775
+ #undef __stddevp_bool_P2EXPR
2776
+
2777
+ static VALUE
2778
+ stddevp_ki_native_object (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
2779
+ {
2780
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
2781
+ CArray *co;
2782
+ GetCArray(vout, co);
2783
+ double *op = (double *) co->ptr;
2784
+ ca_iter_state st;
2785
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
2786
+ slab_axes, naxes, 0);
2787
+ if ( rc != CA_ITER_OK ) {
2788
+ rb_raise(rb_eRuntimeError,
2789
+ "stddevp_ki: kernel_iterator init failed rc=%d", rc);
2790
+ }
2791
+ char *p;
2792
+ boolean8_t *m;
2793
+ ca_size_t out_i = 0;
2794
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
2795
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
2796
+ /* Pass 1: sum = Σx (+ masked_cnt) */
2797
+ VALUE sum = INT2FIX(0);
2798
+ ca_size_t masked_cnt = 0;
2799
+ CA_SLAB_REDUCE_T_EX(VALUE, st, p, m, sum, INT2FIX(0), sum = rb_funcall(sum, rb_intern("+"), 1, v), masked_cnt);
2800
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
2801
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
2802
+ if ( __trigger ) {
2803
+ if ( ! op_mask ) {
2804
+ ca_create_mask(co);
2805
+ op_mask = (boolean8_t *) co->mask->ptr;
2806
+ }
2807
+ op_mask[out_i] = 1;
2808
+ op[out_i++] = (double) 0.0;
2809
+ } else if ( n_valid < 1 ) {
2810
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
2811
+ cell): return zero rather than UNDEF, matching pre-migration
2812
+ behaviour of `cnt > 1 ? formula : 0`. */
2813
+ op[out_i++] = (double) 0.0;
2814
+ } else {
2815
+ VALUE mean = rb_funcall(sum, rb_intern("/"), 1, LONG2NUM(n_valid));
2816
+ /* Pass 2: M2 = Σ (x - mean)² */
2817
+ VALUE M2 = INT2FIX(0);
2818
+ ca_size_t __unused_mc = 0;
2819
+ CA_SLAB_REDUCE_T_EX(VALUE, st, p, m, M2, INT2FIX(0), VALUE __d = rb_funcall(v, rb_intern("-"), 1, mean);
2820
+ M2 = rb_funcall(M2, rb_intern("+"), 1, rb_funcall(__d, rb_intern("*"), 1, __d)), __unused_mc);
2821
+ op[out_i++] = (double)(sqrt(fmax(NUM2DBL(rb_funcall(M2, rb_intern("/"), 1, LONG2NUM(n_valid))), 0.0)));
2822
+ }
2823
+ }
2824
+ ca_iter_state_finish(&st);
2825
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
2826
+ keep_axis means the output is a 1-element CArray; unwrap it to
2827
+ the ruby scalar (matches the standard reduce emit path lines
2828
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
2829
+ if ( naxes == ca->ndim && !keep_axis ) {
2830
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
2831
+ return rb_float_new(op[0]);
2832
+ }
2833
+ return vout;
2834
+ }
2835
+
2836
+ VALUE
2837
+ rb_ca_stddevp_ki (int argc, VALUE *argv, VALUE self)
2838
+ {
2839
+ CArray *src;
2840
+ GetCArray(self, src);
2841
+ volatile VALUE ropt = rb_pop_options(&argc, &argv);
2842
+ volatile VALUE raxis = Qnil, rkeep_axis = Qnil, rmin_count = Qnil, rfval = Qnil;
2843
+ rb_scan_options(ropt, "axis,keep_axis,min_count,fill_value", &raxis, &rkeep_axis, &rmin_count, &rfval);
2844
+ int keep_axis = RTEST(rkeep_axis);
2845
+ int8_t slab_axes[CA_RANK_MAX];
2846
+ int8_t naxes = rb_ca_parse_reduce_axes_kw(raxis, src, slab_axes);
2847
+ ca_size_t min_count = -1;
2848
+ if ( ! NIL_P(rmin_count) ) {
2849
+ ca_size_t mc_user = NUM2SIZE(rmin_count);
2850
+ if ( mc_user < 0 ) {
2851
+ rb_raise(rb_eArgError, "stddevp_ki: :min_count must be non-negative, got %lld", (long long) mc_user);
2852
+ }
2853
+ /* :min_count = 0 collapses to legacy default (any 1 valid). */
2854
+ if ( mc_user > 0 ) min_count = mc_user;
2855
+ }
2856
+ VALUE result;
2857
+ if ( argc > 0 ) {
2858
+ rb_raise(rb_eArgError, "stddevp_ki: positional axis arguments are no longer accepted (got %d); use axis: kwarg, e.g. a.stddevp(axis: 0) or a.stddevp(axis: [0, 1])", argc);
2859
+ }
2860
+ switch ( src->data_type ) {
2861
+ case CA_INT8: {
2862
+ result = stddevp_ki_native_i8(self, src, slab_axes, naxes, keep_axis, min_count);
2863
+ break;
2864
+ }
2865
+ case CA_UINT8: {
2866
+ result = stddevp_ki_native_u8(self, src, slab_axes, naxes, keep_axis, min_count);
2867
+ break;
2868
+ }
2869
+ case CA_INT16: {
2870
+ result = stddevp_ki_native_i16(self, src, slab_axes, naxes, keep_axis, min_count);
2871
+ break;
2872
+ }
2873
+ case CA_UINT16: {
2874
+ result = stddevp_ki_native_u16(self, src, slab_axes, naxes, keep_axis, min_count);
2875
+ break;
2876
+ }
2877
+ case CA_INT32: {
2878
+ result = stddevp_ki_native_i32(self, src, slab_axes, naxes, keep_axis, min_count);
2879
+ break;
2880
+ }
2881
+ case CA_UINT32: {
2882
+ result = stddevp_ki_native_u32(self, src, slab_axes, naxes, keep_axis, min_count);
2883
+ break;
2884
+ }
2885
+ case CA_INT64: {
2886
+ result = stddevp_ki_native_i64(self, src, slab_axes, naxes, keep_axis, min_count);
2887
+ break;
2888
+ }
2889
+ case CA_UINT64: {
2890
+ result = stddevp_ki_native_u64(self, src, slab_axes, naxes, keep_axis, min_count);
2891
+ break;
2892
+ }
2893
+ case CA_FLOAT32: {
2894
+ result = stddevp_ki_native_f32(self, src, slab_axes, naxes, keep_axis, min_count);
2895
+ break;
2896
+ }
2897
+ case CA_FLOAT64: {
2898
+ result = stddevp_ki_native_f64(self, src, slab_axes, naxes, keep_axis, min_count);
2899
+ break;
2900
+ }
2901
+ case CA_CMPLX64: {
2902
+ result = stddevp_ki_native_cmplx64(self, src, slab_axes, naxes, keep_axis, min_count);
2903
+ break;
2904
+ }
2905
+ case CA_CMPLX128: {
2906
+ result = stddevp_ki_native_cmplx128(self, src, slab_axes, naxes, keep_axis, min_count);
2907
+ break;
2908
+ }
2909
+ case CA_BOOLEAN: {
2910
+ result = stddevp_ki_native_bool(self, src, slab_axes, naxes, keep_axis, min_count);
2911
+ break;
2912
+ }
2913
+ case CA_OBJECT: {
2914
+ result = stddevp_ki_native_object(self, src, slab_axes, naxes, keep_axis, min_count);
2915
+ break;
2916
+ }
2917
+ default:
2918
+ rb_raise(rb_eCADataTypeError, "stddevp_ki: source data_type :%s not supported (expected one of: i8, u8, i16, u16, i32, u32, i64, u64, f32, f64, cmplx64, cmplx128, bool, object)", ca_type_name[src->data_type]);
2919
+ }
2920
+ if ( ! NIL_P(rfval) ) {
2921
+ if ( result == CA_UNDEF ) {
2922
+ result = rfval;
2923
+ } else if ( TYPE(result) != T_FLOAT && rb_obj_is_kind_of(result, rb_cCArray) ) {
2924
+ CArray *cr;
2925
+ GetCArray(result, cr);
2926
+ if ( ca_has_mask(cr) ) {
2927
+ result = rb_ca_mask_fill_copy(result, rfval);
2928
+ }
2929
+ }
2930
+ }
2931
+ return result;
2932
+ }
2933
+
2934
+ /* ===== stddev_ki ============================================ */
2935
+
2936
+ #define __stddev_i8_P1EXPR(__x) ((double)(__x))
2937
+ #define __stddev_i8_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
2938
+ static VALUE
2939
+ stddev_ki_native_i8 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
2940
+ {
2941
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
2942
+ CArray *co;
2943
+ GetCArray(vout, co);
2944
+ double *op = (double *) co->ptr;
2945
+ ca_iter_state st;
2946
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
2947
+ slab_axes, naxes, 0);
2948
+ if ( rc != CA_ITER_OK ) {
2949
+ rb_raise(rb_eRuntimeError,
2950
+ "stddev_ki: kernel_iterator init failed rc=%d", rc);
2951
+ }
2952
+ char *p;
2953
+ boolean8_t *m;
2954
+ ca_size_t out_i = 0;
2955
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
2956
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
2957
+ /* Pass 1: sum = Σx (+ masked_cnt) */
2958
+ double sum = 0.0;
2959
+ ca_size_t masked_cnt = 0;
2960
+ CA_SLAB_REDUCE_SUM8_EX(int8_t, double, st, p, m, sum, 0.0, __stddev_i8_P1EXPR, masked_cnt);
2961
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
2962
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
2963
+ if ( __trigger ) {
2964
+ if ( ! op_mask ) {
2965
+ ca_create_mask(co);
2966
+ op_mask = (boolean8_t *) co->mask->ptr;
2967
+ }
2968
+ op_mask[out_i] = 1;
2969
+ op[out_i++] = (double) 0.0;
2970
+ } else if ( n_valid < 2 ) {
2971
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
2972
+ cell): return zero rather than UNDEF, matching pre-migration
2973
+ behaviour of `cnt > 1 ? formula : 0`. */
2974
+ op[out_i++] = (double) 0.0;
2975
+ } else {
2976
+ double mean = sum / (double) n_valid;
2977
+ /* Pass 2: M2 = Σ (x - mean)² */
2978
+ double M2 = 0.0;
2979
+ ca_size_t __unused_mc = 0;
2980
+ CA_SLAB_REDUCE_SUM8_EX(int8_t, double, st, p, m, M2, 0.0, __stddev_i8_P2EXPR, __unused_mc);
2981
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid - 1), 0.0)));
2982
+ }
2983
+ }
2984
+ ca_iter_state_finish(&st);
2985
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
2986
+ keep_axis means the output is a 1-element CArray; unwrap it to
2987
+ the ruby scalar (matches the standard reduce emit path lines
2988
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
2989
+ if ( naxes == ca->ndim && !keep_axis ) {
2990
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
2991
+ return rb_float_new(op[0]);
2992
+ }
2993
+ return vout;
2994
+ }
2995
+ #undef __stddev_i8_P1EXPR
2996
+ #undef __stddev_i8_P2EXPR
2997
+
2998
+ #define __stddev_u8_P1EXPR(__x) ((double)(__x))
2999
+ #define __stddev_u8_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
3000
+ static VALUE
3001
+ stddev_ki_native_u8 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
3002
+ {
3003
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
3004
+ CArray *co;
3005
+ GetCArray(vout, co);
3006
+ double *op = (double *) co->ptr;
3007
+ ca_iter_state st;
3008
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
3009
+ slab_axes, naxes, 0);
3010
+ if ( rc != CA_ITER_OK ) {
3011
+ rb_raise(rb_eRuntimeError,
3012
+ "stddev_ki: kernel_iterator init failed rc=%d", rc);
3013
+ }
3014
+ char *p;
3015
+ boolean8_t *m;
3016
+ ca_size_t out_i = 0;
3017
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
3018
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
3019
+ /* Pass 1: sum = Σx (+ masked_cnt) */
3020
+ double sum = 0.0;
3021
+ ca_size_t masked_cnt = 0;
3022
+ CA_SLAB_REDUCE_SUM8_EX(uint8_t, double, st, p, m, sum, 0.0, __stddev_u8_P1EXPR, masked_cnt);
3023
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
3024
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
3025
+ if ( __trigger ) {
3026
+ if ( ! op_mask ) {
3027
+ ca_create_mask(co);
3028
+ op_mask = (boolean8_t *) co->mask->ptr;
3029
+ }
3030
+ op_mask[out_i] = 1;
3031
+ op[out_i++] = (double) 0.0;
3032
+ } else if ( n_valid < 2 ) {
3033
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
3034
+ cell): return zero rather than UNDEF, matching pre-migration
3035
+ behaviour of `cnt > 1 ? formula : 0`. */
3036
+ op[out_i++] = (double) 0.0;
3037
+ } else {
3038
+ double mean = sum / (double) n_valid;
3039
+ /* Pass 2: M2 = Σ (x - mean)² */
3040
+ double M2 = 0.0;
3041
+ ca_size_t __unused_mc = 0;
3042
+ CA_SLAB_REDUCE_SUM8_EX(uint8_t, double, st, p, m, M2, 0.0, __stddev_u8_P2EXPR, __unused_mc);
3043
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid - 1), 0.0)));
3044
+ }
3045
+ }
3046
+ ca_iter_state_finish(&st);
3047
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
3048
+ keep_axis means the output is a 1-element CArray; unwrap it to
3049
+ the ruby scalar (matches the standard reduce emit path lines
3050
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
3051
+ if ( naxes == ca->ndim && !keep_axis ) {
3052
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
3053
+ return rb_float_new(op[0]);
3054
+ }
3055
+ return vout;
3056
+ }
3057
+ #undef __stddev_u8_P1EXPR
3058
+ #undef __stddev_u8_P2EXPR
3059
+
3060
+ #define __stddev_i16_P1EXPR(__x) ((double)(__x))
3061
+ #define __stddev_i16_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
3062
+ static VALUE
3063
+ stddev_ki_native_i16 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
3064
+ {
3065
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
3066
+ CArray *co;
3067
+ GetCArray(vout, co);
3068
+ double *op = (double *) co->ptr;
3069
+ ca_iter_state st;
3070
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
3071
+ slab_axes, naxes, 0);
3072
+ if ( rc != CA_ITER_OK ) {
3073
+ rb_raise(rb_eRuntimeError,
3074
+ "stddev_ki: kernel_iterator init failed rc=%d", rc);
3075
+ }
3076
+ char *p;
3077
+ boolean8_t *m;
3078
+ ca_size_t out_i = 0;
3079
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
3080
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
3081
+ /* Pass 1: sum = Σx (+ masked_cnt) */
3082
+ double sum = 0.0;
3083
+ ca_size_t masked_cnt = 0;
3084
+ CA_SLAB_REDUCE_SUM8_EX(int16_t, double, st, p, m, sum, 0.0, __stddev_i16_P1EXPR, masked_cnt);
3085
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
3086
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
3087
+ if ( __trigger ) {
3088
+ if ( ! op_mask ) {
3089
+ ca_create_mask(co);
3090
+ op_mask = (boolean8_t *) co->mask->ptr;
3091
+ }
3092
+ op_mask[out_i] = 1;
3093
+ op[out_i++] = (double) 0.0;
3094
+ } else if ( n_valid < 2 ) {
3095
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
3096
+ cell): return zero rather than UNDEF, matching pre-migration
3097
+ behaviour of `cnt > 1 ? formula : 0`. */
3098
+ op[out_i++] = (double) 0.0;
3099
+ } else {
3100
+ double mean = sum / (double) n_valid;
3101
+ /* Pass 2: M2 = Σ (x - mean)² */
3102
+ double M2 = 0.0;
3103
+ ca_size_t __unused_mc = 0;
3104
+ CA_SLAB_REDUCE_SUM8_EX(int16_t, double, st, p, m, M2, 0.0, __stddev_i16_P2EXPR, __unused_mc);
3105
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid - 1), 0.0)));
3106
+ }
3107
+ }
3108
+ ca_iter_state_finish(&st);
3109
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
3110
+ keep_axis means the output is a 1-element CArray; unwrap it to
3111
+ the ruby scalar (matches the standard reduce emit path lines
3112
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
3113
+ if ( naxes == ca->ndim && !keep_axis ) {
3114
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
3115
+ return rb_float_new(op[0]);
3116
+ }
3117
+ return vout;
3118
+ }
3119
+ #undef __stddev_i16_P1EXPR
3120
+ #undef __stddev_i16_P2EXPR
3121
+
3122
+ #define __stddev_u16_P1EXPR(__x) ((double)(__x))
3123
+ #define __stddev_u16_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
3124
+ static VALUE
3125
+ stddev_ki_native_u16 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
3126
+ {
3127
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
3128
+ CArray *co;
3129
+ GetCArray(vout, co);
3130
+ double *op = (double *) co->ptr;
3131
+ ca_iter_state st;
3132
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
3133
+ slab_axes, naxes, 0);
3134
+ if ( rc != CA_ITER_OK ) {
3135
+ rb_raise(rb_eRuntimeError,
3136
+ "stddev_ki: kernel_iterator init failed rc=%d", rc);
3137
+ }
3138
+ char *p;
3139
+ boolean8_t *m;
3140
+ ca_size_t out_i = 0;
3141
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
3142
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
3143
+ /* Pass 1: sum = Σx (+ masked_cnt) */
3144
+ double sum = 0.0;
3145
+ ca_size_t masked_cnt = 0;
3146
+ CA_SLAB_REDUCE_SUM8_EX(uint16_t, double, st, p, m, sum, 0.0, __stddev_u16_P1EXPR, masked_cnt);
3147
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
3148
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
3149
+ if ( __trigger ) {
3150
+ if ( ! op_mask ) {
3151
+ ca_create_mask(co);
3152
+ op_mask = (boolean8_t *) co->mask->ptr;
3153
+ }
3154
+ op_mask[out_i] = 1;
3155
+ op[out_i++] = (double) 0.0;
3156
+ } else if ( n_valid < 2 ) {
3157
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
3158
+ cell): return zero rather than UNDEF, matching pre-migration
3159
+ behaviour of `cnt > 1 ? formula : 0`. */
3160
+ op[out_i++] = (double) 0.0;
3161
+ } else {
3162
+ double mean = sum / (double) n_valid;
3163
+ /* Pass 2: M2 = Σ (x - mean)² */
3164
+ double M2 = 0.0;
3165
+ ca_size_t __unused_mc = 0;
3166
+ CA_SLAB_REDUCE_SUM8_EX(uint16_t, double, st, p, m, M2, 0.0, __stddev_u16_P2EXPR, __unused_mc);
3167
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid - 1), 0.0)));
3168
+ }
3169
+ }
3170
+ ca_iter_state_finish(&st);
3171
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
3172
+ keep_axis means the output is a 1-element CArray; unwrap it to
3173
+ the ruby scalar (matches the standard reduce emit path lines
3174
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
3175
+ if ( naxes == ca->ndim && !keep_axis ) {
3176
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
3177
+ return rb_float_new(op[0]);
3178
+ }
3179
+ return vout;
3180
+ }
3181
+ #undef __stddev_u16_P1EXPR
3182
+ #undef __stddev_u16_P2EXPR
3183
+
3184
+ #define __stddev_i32_P1EXPR(__x) ((double)(__x))
3185
+ #define __stddev_i32_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
3186
+ static VALUE
3187
+ stddev_ki_native_i32 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
3188
+ {
3189
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
3190
+ CArray *co;
3191
+ GetCArray(vout, co);
3192
+ double *op = (double *) co->ptr;
3193
+ ca_iter_state st;
3194
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
3195
+ slab_axes, naxes, 0);
3196
+ if ( rc != CA_ITER_OK ) {
3197
+ rb_raise(rb_eRuntimeError,
3198
+ "stddev_ki: kernel_iterator init failed rc=%d", rc);
3199
+ }
3200
+ char *p;
3201
+ boolean8_t *m;
3202
+ ca_size_t out_i = 0;
3203
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
3204
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
3205
+ /* Pass 1: sum = Σx (+ masked_cnt) */
3206
+ double sum = 0.0;
3207
+ ca_size_t masked_cnt = 0;
3208
+ CA_SLAB_REDUCE_SUM8_EX(int32_t, double, st, p, m, sum, 0.0, __stddev_i32_P1EXPR, masked_cnt);
3209
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
3210
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
3211
+ if ( __trigger ) {
3212
+ if ( ! op_mask ) {
3213
+ ca_create_mask(co);
3214
+ op_mask = (boolean8_t *) co->mask->ptr;
3215
+ }
3216
+ op_mask[out_i] = 1;
3217
+ op[out_i++] = (double) 0.0;
3218
+ } else if ( n_valid < 2 ) {
3219
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
3220
+ cell): return zero rather than UNDEF, matching pre-migration
3221
+ behaviour of `cnt > 1 ? formula : 0`. */
3222
+ op[out_i++] = (double) 0.0;
3223
+ } else {
3224
+ double mean = sum / (double) n_valid;
3225
+ /* Pass 2: M2 = Σ (x - mean)² */
3226
+ double M2 = 0.0;
3227
+ ca_size_t __unused_mc = 0;
3228
+ CA_SLAB_REDUCE_SUM8_EX(int32_t, double, st, p, m, M2, 0.0, __stddev_i32_P2EXPR, __unused_mc);
3229
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid - 1), 0.0)));
3230
+ }
3231
+ }
3232
+ ca_iter_state_finish(&st);
3233
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
3234
+ keep_axis means the output is a 1-element CArray; unwrap it to
3235
+ the ruby scalar (matches the standard reduce emit path lines
3236
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
3237
+ if ( naxes == ca->ndim && !keep_axis ) {
3238
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
3239
+ return rb_float_new(op[0]);
3240
+ }
3241
+ return vout;
3242
+ }
3243
+ #undef __stddev_i32_P1EXPR
3244
+ #undef __stddev_i32_P2EXPR
3245
+
3246
+ #define __stddev_u32_P1EXPR(__x) ((double)(__x))
3247
+ #define __stddev_u32_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
3248
+ static VALUE
3249
+ stddev_ki_native_u32 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
3250
+ {
3251
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
3252
+ CArray *co;
3253
+ GetCArray(vout, co);
3254
+ double *op = (double *) co->ptr;
3255
+ ca_iter_state st;
3256
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
3257
+ slab_axes, naxes, 0);
3258
+ if ( rc != CA_ITER_OK ) {
3259
+ rb_raise(rb_eRuntimeError,
3260
+ "stddev_ki: kernel_iterator init failed rc=%d", rc);
3261
+ }
3262
+ char *p;
3263
+ boolean8_t *m;
3264
+ ca_size_t out_i = 0;
3265
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
3266
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
3267
+ /* Pass 1: sum = Σx (+ masked_cnt) */
3268
+ double sum = 0.0;
3269
+ ca_size_t masked_cnt = 0;
3270
+ CA_SLAB_REDUCE_SUM8_EX(uint32_t, double, st, p, m, sum, 0.0, __stddev_u32_P1EXPR, masked_cnt);
3271
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
3272
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
3273
+ if ( __trigger ) {
3274
+ if ( ! op_mask ) {
3275
+ ca_create_mask(co);
3276
+ op_mask = (boolean8_t *) co->mask->ptr;
3277
+ }
3278
+ op_mask[out_i] = 1;
3279
+ op[out_i++] = (double) 0.0;
3280
+ } else if ( n_valid < 2 ) {
3281
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
3282
+ cell): return zero rather than UNDEF, matching pre-migration
3283
+ behaviour of `cnt > 1 ? formula : 0`. */
3284
+ op[out_i++] = (double) 0.0;
3285
+ } else {
3286
+ double mean = sum / (double) n_valid;
3287
+ /* Pass 2: M2 = Σ (x - mean)² */
3288
+ double M2 = 0.0;
3289
+ ca_size_t __unused_mc = 0;
3290
+ CA_SLAB_REDUCE_SUM8_EX(uint32_t, double, st, p, m, M2, 0.0, __stddev_u32_P2EXPR, __unused_mc);
3291
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid - 1), 0.0)));
3292
+ }
3293
+ }
3294
+ ca_iter_state_finish(&st);
3295
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
3296
+ keep_axis means the output is a 1-element CArray; unwrap it to
3297
+ the ruby scalar (matches the standard reduce emit path lines
3298
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
3299
+ if ( naxes == ca->ndim && !keep_axis ) {
3300
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
3301
+ return rb_float_new(op[0]);
3302
+ }
3303
+ return vout;
3304
+ }
3305
+ #undef __stddev_u32_P1EXPR
3306
+ #undef __stddev_u32_P2EXPR
3307
+
3308
+ #define __stddev_i64_P1EXPR(__x) ((double)(__x))
3309
+ #define __stddev_i64_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
3310
+ static VALUE
3311
+ stddev_ki_native_i64 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
3312
+ {
3313
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
3314
+ CArray *co;
3315
+ GetCArray(vout, co);
3316
+ double *op = (double *) co->ptr;
3317
+ ca_iter_state st;
3318
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
3319
+ slab_axes, naxes, 0);
3320
+ if ( rc != CA_ITER_OK ) {
3321
+ rb_raise(rb_eRuntimeError,
3322
+ "stddev_ki: kernel_iterator init failed rc=%d", rc);
3323
+ }
3324
+ char *p;
3325
+ boolean8_t *m;
3326
+ ca_size_t out_i = 0;
3327
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
3328
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
3329
+ /* Pass 1: sum = Σx (+ masked_cnt) */
3330
+ double sum = 0.0;
3331
+ ca_size_t masked_cnt = 0;
3332
+ CA_SLAB_REDUCE_SUM8_EX(int64_t, double, st, p, m, sum, 0.0, __stddev_i64_P1EXPR, masked_cnt);
3333
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
3334
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
3335
+ if ( __trigger ) {
3336
+ if ( ! op_mask ) {
3337
+ ca_create_mask(co);
3338
+ op_mask = (boolean8_t *) co->mask->ptr;
3339
+ }
3340
+ op_mask[out_i] = 1;
3341
+ op[out_i++] = (double) 0.0;
3342
+ } else if ( n_valid < 2 ) {
3343
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
3344
+ cell): return zero rather than UNDEF, matching pre-migration
3345
+ behaviour of `cnt > 1 ? formula : 0`. */
3346
+ op[out_i++] = (double) 0.0;
3347
+ } else {
3348
+ double mean = sum / (double) n_valid;
3349
+ /* Pass 2: M2 = Σ (x - mean)² */
3350
+ double M2 = 0.0;
3351
+ ca_size_t __unused_mc = 0;
3352
+ CA_SLAB_REDUCE_SUM8_EX(int64_t, double, st, p, m, M2, 0.0, __stddev_i64_P2EXPR, __unused_mc);
3353
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid - 1), 0.0)));
3354
+ }
3355
+ }
3356
+ ca_iter_state_finish(&st);
3357
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
3358
+ keep_axis means the output is a 1-element CArray; unwrap it to
3359
+ the ruby scalar (matches the standard reduce emit path lines
3360
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
3361
+ if ( naxes == ca->ndim && !keep_axis ) {
3362
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
3363
+ return rb_float_new(op[0]);
3364
+ }
3365
+ return vout;
3366
+ }
3367
+ #undef __stddev_i64_P1EXPR
3368
+ #undef __stddev_i64_P2EXPR
3369
+
3370
+ #define __stddev_u64_P1EXPR(__x) ((double)(__x))
3371
+ #define __stddev_u64_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
3372
+ static VALUE
3373
+ stddev_ki_native_u64 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
3374
+ {
3375
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
3376
+ CArray *co;
3377
+ GetCArray(vout, co);
3378
+ double *op = (double *) co->ptr;
3379
+ ca_iter_state st;
3380
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
3381
+ slab_axes, naxes, 0);
3382
+ if ( rc != CA_ITER_OK ) {
3383
+ rb_raise(rb_eRuntimeError,
3384
+ "stddev_ki: kernel_iterator init failed rc=%d", rc);
3385
+ }
3386
+ char *p;
3387
+ boolean8_t *m;
3388
+ ca_size_t out_i = 0;
3389
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
3390
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
3391
+ /* Pass 1: sum = Σx (+ masked_cnt) */
3392
+ double sum = 0.0;
3393
+ ca_size_t masked_cnt = 0;
3394
+ CA_SLAB_REDUCE_SUM8_EX(uint64_t, double, st, p, m, sum, 0.0, __stddev_u64_P1EXPR, masked_cnt);
3395
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
3396
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
3397
+ if ( __trigger ) {
3398
+ if ( ! op_mask ) {
3399
+ ca_create_mask(co);
3400
+ op_mask = (boolean8_t *) co->mask->ptr;
3401
+ }
3402
+ op_mask[out_i] = 1;
3403
+ op[out_i++] = (double) 0.0;
3404
+ } else if ( n_valid < 2 ) {
3405
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
3406
+ cell): return zero rather than UNDEF, matching pre-migration
3407
+ behaviour of `cnt > 1 ? formula : 0`. */
3408
+ op[out_i++] = (double) 0.0;
3409
+ } else {
3410
+ double mean = sum / (double) n_valid;
3411
+ /* Pass 2: M2 = Σ (x - mean)² */
3412
+ double M2 = 0.0;
3413
+ ca_size_t __unused_mc = 0;
3414
+ CA_SLAB_REDUCE_SUM8_EX(uint64_t, double, st, p, m, M2, 0.0, __stddev_u64_P2EXPR, __unused_mc);
3415
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid - 1), 0.0)));
3416
+ }
3417
+ }
3418
+ ca_iter_state_finish(&st);
3419
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
3420
+ keep_axis means the output is a 1-element CArray; unwrap it to
3421
+ the ruby scalar (matches the standard reduce emit path lines
3422
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
3423
+ if ( naxes == ca->ndim && !keep_axis ) {
3424
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
3425
+ return rb_float_new(op[0]);
3426
+ }
3427
+ return vout;
3428
+ }
3429
+ #undef __stddev_u64_P1EXPR
3430
+ #undef __stddev_u64_P2EXPR
3431
+
3432
+ #define __stddev_f32_P1EXPR(__x) ((double)(__x))
3433
+ #define __stddev_f32_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
3434
+ static VALUE
3435
+ stddev_ki_native_f32 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
3436
+ {
3437
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
3438
+ CArray *co;
3439
+ GetCArray(vout, co);
3440
+ double *op = (double *) co->ptr;
3441
+ ca_iter_state st;
3442
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
3443
+ slab_axes, naxes, 0);
3444
+ if ( rc != CA_ITER_OK ) {
3445
+ rb_raise(rb_eRuntimeError,
3446
+ "stddev_ki: kernel_iterator init failed rc=%d", rc);
3447
+ }
3448
+ char *p;
3449
+ boolean8_t *m;
3450
+ ca_size_t out_i = 0;
3451
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
3452
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
3453
+ /* Pass 1: sum = Σx (+ masked_cnt) */
3454
+ double sum = 0.0;
3455
+ ca_size_t masked_cnt = 0;
3456
+ CA_SLAB_REDUCE_SUM8_EX(float, double, st, p, m, sum, 0.0, __stddev_f32_P1EXPR, masked_cnt);
3457
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
3458
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
3459
+ if ( __trigger ) {
3460
+ if ( ! op_mask ) {
3461
+ ca_create_mask(co);
3462
+ op_mask = (boolean8_t *) co->mask->ptr;
3463
+ }
3464
+ op_mask[out_i] = 1;
3465
+ op[out_i++] = (double) 0.0;
3466
+ } else if ( n_valid < 2 ) {
3467
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
3468
+ cell): return zero rather than UNDEF, matching pre-migration
3469
+ behaviour of `cnt > 1 ? formula : 0`. */
3470
+ op[out_i++] = (double) 0.0;
3471
+ } else {
3472
+ double mean = sum / (double) n_valid;
3473
+ /* Pass 2: M2 = Σ (x - mean)² */
3474
+ double M2 = 0.0;
3475
+ ca_size_t __unused_mc = 0;
3476
+ CA_SLAB_REDUCE_SUM8_EX(float, double, st, p, m, M2, 0.0, __stddev_f32_P2EXPR, __unused_mc);
3477
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid - 1), 0.0)));
3478
+ }
3479
+ }
3480
+ ca_iter_state_finish(&st);
3481
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
3482
+ keep_axis means the output is a 1-element CArray; unwrap it to
3483
+ the ruby scalar (matches the standard reduce emit path lines
3484
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
3485
+ if ( naxes == ca->ndim && !keep_axis ) {
3486
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
3487
+ return rb_float_new(op[0]);
3488
+ }
3489
+ return vout;
3490
+ }
3491
+ #undef __stddev_f32_P1EXPR
3492
+ #undef __stddev_f32_P2EXPR
3493
+
3494
+ #define __stddev_f64_P1EXPR(__x) ((double)(__x))
3495
+ #define __stddev_f64_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
3496
+ static VALUE
3497
+ stddev_ki_native_f64 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
3498
+ {
3499
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
3500
+ CArray *co;
3501
+ GetCArray(vout, co);
3502
+ double *op = (double *) co->ptr;
3503
+ ca_iter_state st;
3504
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
3505
+ slab_axes, naxes, 0);
3506
+ if ( rc != CA_ITER_OK ) {
3507
+ rb_raise(rb_eRuntimeError,
3508
+ "stddev_ki: kernel_iterator init failed rc=%d", rc);
3509
+ }
3510
+ char *p;
3511
+ boolean8_t *m;
3512
+ ca_size_t out_i = 0;
3513
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
3514
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
3515
+ /* Pass 1: sum = Σx (+ masked_cnt) */
3516
+ double sum = 0.0;
3517
+ ca_size_t masked_cnt = 0;
3518
+ CA_SLAB_REDUCE_SUM8_EX(double, double, st, p, m, sum, 0.0, __stddev_f64_P1EXPR, masked_cnt);
3519
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
3520
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
3521
+ if ( __trigger ) {
3522
+ if ( ! op_mask ) {
3523
+ ca_create_mask(co);
3524
+ op_mask = (boolean8_t *) co->mask->ptr;
3525
+ }
3526
+ op_mask[out_i] = 1;
3527
+ op[out_i++] = (double) 0.0;
3528
+ } else if ( n_valid < 2 ) {
3529
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
3530
+ cell): return zero rather than UNDEF, matching pre-migration
3531
+ behaviour of `cnt > 1 ? formula : 0`. */
3532
+ op[out_i++] = (double) 0.0;
3533
+ } else {
3534
+ double mean = sum / (double) n_valid;
3535
+ /* Pass 2: M2 = Σ (x - mean)² */
3536
+ double M2 = 0.0;
3537
+ ca_size_t __unused_mc = 0;
3538
+ CA_SLAB_REDUCE_SUM8_EX(double, double, st, p, m, M2, 0.0, __stddev_f64_P2EXPR, __unused_mc);
3539
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid - 1), 0.0)));
3540
+ }
3541
+ }
3542
+ ca_iter_state_finish(&st);
3543
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
3544
+ keep_axis means the output is a 1-element CArray; unwrap it to
3545
+ the ruby scalar (matches the standard reduce emit path lines
3546
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
3547
+ if ( naxes == ca->ndim && !keep_axis ) {
3548
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
3549
+ return rb_float_new(op[0]);
3550
+ }
3551
+ return vout;
3552
+ }
3553
+ #undef __stddev_f64_P1EXPR
3554
+ #undef __stddev_f64_P2EXPR
3555
+
3556
+ #define __stddev_cmplx64_P1EXPR(__x) (__x)
3557
+ #define __stddev_cmplx64_P2EXPR(__x) (creal((__x) - mean) * creal((__x) - mean) + cimag((__x) - mean) * cimag((__x) - mean))
3558
+ static VALUE
3559
+ stddev_ki_native_cmplx64 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
3560
+ {
3561
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
3562
+ CArray *co;
3563
+ GetCArray(vout, co);
3564
+ double *op = (double *) co->ptr;
3565
+ ca_iter_state st;
3566
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
3567
+ slab_axes, naxes, 0);
3568
+ if ( rc != CA_ITER_OK ) {
3569
+ rb_raise(rb_eRuntimeError,
3570
+ "stddev_ki: kernel_iterator init failed rc=%d", rc);
3571
+ }
3572
+ char *p;
3573
+ boolean8_t *m;
3574
+ ca_size_t out_i = 0;
3575
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
3576
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
3577
+ /* Pass 1: sum = Σx (+ masked_cnt) */
3578
+ cmplx64_t sum = 0;
3579
+ ca_size_t masked_cnt = 0;
3580
+ CA_SLAB_REDUCE_SUM8_EX(cmplx64_t, cmplx64_t, st, p, m, sum, 0, __stddev_cmplx64_P1EXPR, masked_cnt);
3581
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
3582
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
3583
+ if ( __trigger ) {
3584
+ if ( ! op_mask ) {
3585
+ ca_create_mask(co);
3586
+ op_mask = (boolean8_t *) co->mask->ptr;
3587
+ }
3588
+ op_mask[out_i] = 1;
3589
+ op[out_i++] = (double) 0.0;
3590
+ } else if ( n_valid < 2 ) {
3591
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
3592
+ cell): return zero rather than UNDEF, matching pre-migration
3593
+ behaviour of `cnt > 1 ? formula : 0`. */
3594
+ op[out_i++] = (double) 0.0;
3595
+ } else {
3596
+ cmplx64_t mean = sum / (cmplx64_t)(double) n_valid;
3597
+ /* Pass 2: M2 = Σ (x - mean)² */
3598
+ double M2 = 0.0;
3599
+ ca_size_t __unused_mc = 0;
3600
+ CA_SLAB_REDUCE_SUM8_EX(cmplx64_t, double, st, p, m, M2, 0.0, __stddev_cmplx64_P2EXPR, __unused_mc);
3601
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid - 1), 0.0)));
3602
+ }
3603
+ }
3604
+ ca_iter_state_finish(&st);
3605
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
3606
+ keep_axis means the output is a 1-element CArray; unwrap it to
3607
+ the ruby scalar (matches the standard reduce emit path lines
3608
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
3609
+ if ( naxes == ca->ndim && !keep_axis ) {
3610
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
3611
+ return rb_float_new(op[0]);
3612
+ }
3613
+ return vout;
3614
+ }
3615
+ #undef __stddev_cmplx64_P1EXPR
3616
+ #undef __stddev_cmplx64_P2EXPR
3617
+
3618
+ #define __stddev_cmplx128_P1EXPR(__x) (__x)
3619
+ #define __stddev_cmplx128_P2EXPR(__x) (creal((__x) - mean) * creal((__x) - mean) + cimag((__x) - mean) * cimag((__x) - mean))
3620
+ static VALUE
3621
+ stddev_ki_native_cmplx128 (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
3622
+ {
3623
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
3624
+ CArray *co;
3625
+ GetCArray(vout, co);
3626
+ double *op = (double *) co->ptr;
3627
+ ca_iter_state st;
3628
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
3629
+ slab_axes, naxes, 0);
3630
+ if ( rc != CA_ITER_OK ) {
3631
+ rb_raise(rb_eRuntimeError,
3632
+ "stddev_ki: kernel_iterator init failed rc=%d", rc);
3633
+ }
3634
+ char *p;
3635
+ boolean8_t *m;
3636
+ ca_size_t out_i = 0;
3637
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
3638
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
3639
+ /* Pass 1: sum = Σx (+ masked_cnt) */
3640
+ cmplx128_t sum = 0;
3641
+ ca_size_t masked_cnt = 0;
3642
+ CA_SLAB_REDUCE_SUM8_EX(cmplx128_t, cmplx128_t, st, p, m, sum, 0, __stddev_cmplx128_P1EXPR, masked_cnt);
3643
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
3644
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
3645
+ if ( __trigger ) {
3646
+ if ( ! op_mask ) {
3647
+ ca_create_mask(co);
3648
+ op_mask = (boolean8_t *) co->mask->ptr;
3649
+ }
3650
+ op_mask[out_i] = 1;
3651
+ op[out_i++] = (double) 0.0;
3652
+ } else if ( n_valid < 2 ) {
3653
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
3654
+ cell): return zero rather than UNDEF, matching pre-migration
3655
+ behaviour of `cnt > 1 ? formula : 0`. */
3656
+ op[out_i++] = (double) 0.0;
3657
+ } else {
3658
+ cmplx128_t mean = sum / (cmplx128_t)(double) n_valid;
3659
+ /* Pass 2: M2 = Σ (x - mean)² */
3660
+ double M2 = 0.0;
3661
+ ca_size_t __unused_mc = 0;
3662
+ CA_SLAB_REDUCE_SUM8_EX(cmplx128_t, double, st, p, m, M2, 0.0, __stddev_cmplx128_P2EXPR, __unused_mc);
3663
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid - 1), 0.0)));
3664
+ }
3665
+ }
3666
+ ca_iter_state_finish(&st);
3667
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
3668
+ keep_axis means the output is a 1-element CArray; unwrap it to
3669
+ the ruby scalar (matches the standard reduce emit path lines
3670
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
3671
+ if ( naxes == ca->ndim && !keep_axis ) {
3672
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
3673
+ return rb_float_new(op[0]);
3674
+ }
3675
+ return vout;
3676
+ }
3677
+ #undef __stddev_cmplx128_P1EXPR
3678
+ #undef __stddev_cmplx128_P2EXPR
3679
+
3680
+ #define __stddev_bool_P1EXPR(__x) ((double)(__x))
3681
+ #define __stddev_bool_P2EXPR(__x) (((double)(__x) - mean) * ((double)(__x) - mean))
3682
+ static VALUE
3683
+ stddev_ki_native_bool (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
3684
+ {
3685
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
3686
+ CArray *co;
3687
+ GetCArray(vout, co);
3688
+ double *op = (double *) co->ptr;
3689
+ ca_iter_state st;
3690
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
3691
+ slab_axes, naxes, 0);
3692
+ if ( rc != CA_ITER_OK ) {
3693
+ rb_raise(rb_eRuntimeError,
3694
+ "stddev_ki: kernel_iterator init failed rc=%d", rc);
3695
+ }
3696
+ char *p;
3697
+ boolean8_t *m;
3698
+ ca_size_t out_i = 0;
3699
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
3700
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
3701
+ /* Pass 1: sum = Σx (+ masked_cnt) */
3702
+ double sum = 0.0;
3703
+ ca_size_t masked_cnt = 0;
3704
+ CA_SLAB_REDUCE_SUM8_EX(boolean8_t, double, st, p, m, sum, 0.0, __stddev_bool_P1EXPR, masked_cnt);
3705
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
3706
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
3707
+ if ( __trigger ) {
3708
+ if ( ! op_mask ) {
3709
+ ca_create_mask(co);
3710
+ op_mask = (boolean8_t *) co->mask->ptr;
3711
+ }
3712
+ op_mask[out_i] = 1;
3713
+ op[out_i++] = (double) 0.0;
3714
+ } else if ( n_valid < 2 ) {
3715
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
3716
+ cell): return zero rather than UNDEF, matching pre-migration
3717
+ behaviour of `cnt > 1 ? formula : 0`. */
3718
+ op[out_i++] = (double) 0.0;
3719
+ } else {
3720
+ double mean = sum / (double) n_valid;
3721
+ /* Pass 2: M2 = Σ (x - mean)² */
3722
+ double M2 = 0.0;
3723
+ ca_size_t __unused_mc = 0;
3724
+ CA_SLAB_REDUCE_SUM8_EX(boolean8_t, double, st, p, m, M2, 0.0, __stddev_bool_P2EXPR, __unused_mc);
3725
+ op[out_i++] = (double)(sqrt(fmax(M2 / (double)(n_valid - 1), 0.0)));
3726
+ }
3727
+ }
3728
+ ca_iter_state_finish(&st);
3729
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
3730
+ keep_axis means the output is a 1-element CArray; unwrap it to
3731
+ the ruby scalar (matches the standard reduce emit path lines
3732
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
3733
+ if ( naxes == ca->ndim && !keep_axis ) {
3734
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
3735
+ return rb_float_new(op[0]);
3736
+ }
3737
+ return vout;
3738
+ }
3739
+ #undef __stddev_bool_P1EXPR
3740
+ #undef __stddev_bool_P2EXPR
3741
+
3742
+ static VALUE
3743
+ stddev_ki_native_object (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)
3744
+ {
3745
+ VALUE vout = rb_ca_new_reduced(self, slab_axes, naxes, CA_FLOAT64, keep_axis);
3746
+ CArray *co;
3747
+ GetCArray(vout, co);
3748
+ double *op = (double *) co->ptr;
3749
+ ca_iter_state st;
3750
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES,
3751
+ slab_axes, naxes, 0);
3752
+ if ( rc != CA_ITER_OK ) {
3753
+ rb_raise(rb_eRuntimeError,
3754
+ "stddev_ki: kernel_iterator init failed rc=%d", rc);
3755
+ }
3756
+ char *p;
3757
+ boolean8_t *m;
3758
+ ca_size_t out_i = 0;
3759
+ boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */
3760
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
3761
+ /* Pass 1: sum = Σx (+ masked_cnt) */
3762
+ VALUE sum = INT2FIX(0);
3763
+ ca_size_t masked_cnt = 0;
3764
+ CA_SLAB_REDUCE_T_EX(VALUE, st, p, m, sum, INT2FIX(0), sum = rb_funcall(sum, rb_intern("+"), 1, v), masked_cnt);
3765
+ ca_size_t n_valid = st.slab_elements - masked_cnt;
3766
+ int __trigger = (min_count < 0 ? masked_cnt == st.slab_elements : n_valid < min_count);
3767
+ if ( __trigger ) {
3768
+ if ( ! op_mask ) {
3769
+ ca_create_mask(co);
3770
+ op_mask = (boolean8_t *) co->mask->ptr;
3771
+ }
3772
+ op_mask[out_i] = 1;
3773
+ op[out_i++] = (double) 0.0;
3774
+ } else if ( n_valid < 2 ) {
3775
+ /* Legacy degenerate case (e.g. sample variance with 1 unmasked
3776
+ cell): return zero rather than UNDEF, matching pre-migration
3777
+ behaviour of `cnt > 1 ? formula : 0`. */
3778
+ op[out_i++] = (double) 0.0;
3779
+ } else {
3780
+ VALUE mean = rb_funcall(sum, rb_intern("/"), 1, LONG2NUM(n_valid));
3781
+ /* Pass 2: M2 = Σ (x - mean)² */
3782
+ VALUE M2 = INT2FIX(0);
3783
+ ca_size_t __unused_mc = 0;
3784
+ CA_SLAB_REDUCE_T_EX(VALUE, st, p, m, M2, INT2FIX(0), VALUE __d = rb_funcall(v, rb_intern("-"), 1, mean);
3785
+ M2 = rb_funcall(M2, rb_intern("+"), 1, rb_funcall(__d, rb_intern("*"), 1, __d)), __unused_mc);
3786
+ op[out_i++] = (double)(sqrt(fmax(NUM2DBL(rb_funcall(M2, rb_intern("/"), 1, LONG2NUM(n_valid - 1))), 0.0)));
3787
+ }
3788
+ }
3789
+ ca_iter_state_finish(&st);
3790
+ /* Full-reduction scalar shortcut: naxes == ca->ndim and no
3791
+ keep_axis means the output is a 1-element CArray; unwrap it to
3792
+ the ruby scalar (matches the standard reduce emit path lines
3793
+ 1811-1817). Full reduction with all-masked returns CA_UNDEF. */
3794
+ if ( naxes == ca->ndim && !keep_axis ) {
3795
+ if ( op_mask && op_mask[0] ) return CA_UNDEF;
3796
+ return rb_float_new(op[0]);
3797
+ }
3798
+ return vout;
3799
+ }
3800
+
3801
+ VALUE
3802
+ rb_ca_stddev_ki (int argc, VALUE *argv, VALUE self)
3803
+ {
3804
+ CArray *src;
3805
+ GetCArray(self, src);
3806
+ volatile VALUE ropt = rb_pop_options(&argc, &argv);
3807
+ volatile VALUE raxis = Qnil, rkeep_axis = Qnil, rmin_count = Qnil, rfval = Qnil;
3808
+ rb_scan_options(ropt, "axis,keep_axis,min_count,fill_value", &raxis, &rkeep_axis, &rmin_count, &rfval);
3809
+ int keep_axis = RTEST(rkeep_axis);
3810
+ int8_t slab_axes[CA_RANK_MAX];
3811
+ int8_t naxes = rb_ca_parse_reduce_axes_kw(raxis, src, slab_axes);
3812
+ ca_size_t min_count = -1;
3813
+ if ( ! NIL_P(rmin_count) ) {
3814
+ ca_size_t mc_user = NUM2SIZE(rmin_count);
3815
+ if ( mc_user < 0 ) {
3816
+ rb_raise(rb_eArgError, "stddev_ki: :min_count must be non-negative, got %lld", (long long) mc_user);
3817
+ }
3818
+ /* :min_count = 0 collapses to legacy default (any 1 valid). */
3819
+ if ( mc_user > 0 ) min_count = mc_user;
3820
+ }
3821
+ VALUE result;
3822
+ if ( argc > 0 ) {
3823
+ rb_raise(rb_eArgError, "stddev_ki: positional axis arguments are no longer accepted (got %d); use axis: kwarg, e.g. a.stddev(axis: 0) or a.stddev(axis: [0, 1])", argc);
3824
+ }
3825
+ switch ( src->data_type ) {
3826
+ case CA_INT8: {
3827
+ result = stddev_ki_native_i8(self, src, slab_axes, naxes, keep_axis, min_count);
3828
+ break;
3829
+ }
3830
+ case CA_UINT8: {
3831
+ result = stddev_ki_native_u8(self, src, slab_axes, naxes, keep_axis, min_count);
3832
+ break;
3833
+ }
3834
+ case CA_INT16: {
3835
+ result = stddev_ki_native_i16(self, src, slab_axes, naxes, keep_axis, min_count);
3836
+ break;
3837
+ }
3838
+ case CA_UINT16: {
3839
+ result = stddev_ki_native_u16(self, src, slab_axes, naxes, keep_axis, min_count);
3840
+ break;
3841
+ }
3842
+ case CA_INT32: {
3843
+ result = stddev_ki_native_i32(self, src, slab_axes, naxes, keep_axis, min_count);
3844
+ break;
3845
+ }
3846
+ case CA_UINT32: {
3847
+ result = stddev_ki_native_u32(self, src, slab_axes, naxes, keep_axis, min_count);
3848
+ break;
3849
+ }
3850
+ case CA_INT64: {
3851
+ result = stddev_ki_native_i64(self, src, slab_axes, naxes, keep_axis, min_count);
3852
+ break;
3853
+ }
3854
+ case CA_UINT64: {
3855
+ result = stddev_ki_native_u64(self, src, slab_axes, naxes, keep_axis, min_count);
3856
+ break;
3857
+ }
3858
+ case CA_FLOAT32: {
3859
+ result = stddev_ki_native_f32(self, src, slab_axes, naxes, keep_axis, min_count);
3860
+ break;
3861
+ }
3862
+ case CA_FLOAT64: {
3863
+ result = stddev_ki_native_f64(self, src, slab_axes, naxes, keep_axis, min_count);
3864
+ break;
3865
+ }
3866
+ case CA_CMPLX64: {
3867
+ result = stddev_ki_native_cmplx64(self, src, slab_axes, naxes, keep_axis, min_count);
3868
+ break;
3869
+ }
3870
+ case CA_CMPLX128: {
3871
+ result = stddev_ki_native_cmplx128(self, src, slab_axes, naxes, keep_axis, min_count);
3872
+ break;
3873
+ }
3874
+ case CA_BOOLEAN: {
3875
+ result = stddev_ki_native_bool(self, src, slab_axes, naxes, keep_axis, min_count);
3876
+ break;
3877
+ }
3878
+ case CA_OBJECT: {
3879
+ result = stddev_ki_native_object(self, src, slab_axes, naxes, keep_axis, min_count);
3880
+ break;
3881
+ }
3882
+ default:
3883
+ rb_raise(rb_eCADataTypeError, "stddev_ki: source data_type :%s not supported (expected one of: i8, u8, i16, u16, i32, u32, i64, u64, f32, f64, cmplx64, cmplx128, bool, object)", ca_type_name[src->data_type]);
3884
+ }
3885
+ if ( ! NIL_P(rfval) ) {
3886
+ if ( result == CA_UNDEF ) {
3887
+ result = rfval;
3888
+ } else if ( TYPE(result) != T_FLOAT && rb_obj_is_kind_of(result, rb_cCArray) ) {
3889
+ CArray *cr;
3890
+ GetCArray(result, cr);
3891
+ if ( ca_has_mask(cr) ) {
3892
+ result = rb_ca_mask_fill_copy(result, rfval);
3893
+ }
3894
+ }
3895
+ }
3896
+ return result;
3897
+ }
3898
+
3899
+ void
3900
+ Init_carray_kernels_reduce_variance (void)
3901
+ {
3902
+ /* Phase E rewire: legacy 'variancep' now dispatches via variancep_ki */
3903
+ rb_define_method(rb_cCArray, "variancep", rb_ca_variancep_ki, -1);
3904
+ /* Phase E rewire: legacy 'variance' now dispatches via variance_ki */
3905
+ rb_define_method(rb_cCArray, "variance", rb_ca_variance_ki, -1);
3906
+ /* Phase E rewire: legacy 'stddevp' now dispatches via stddevp_ki */
3907
+ rb_define_method(rb_cCArray, "stddevp", rb_ca_stddevp_ki, -1);
3908
+ /* Phase E rewire: legacy 'stddev' now dispatches via stddev_ki */
3909
+ rb_define_method(rb_cCArray, "stddev", rb_ca_stddev_ki, -1);
3910
+ }