carray 2.0.0 → 3.0.0

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