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
data/ext/carray_sort.c ADDED
@@ -0,0 +1,829 @@
1
+ /* ---------------------------------------------------------------------------
2
+
3
+ Sort surface: sort / sort_copy (value & view), sort_addr / axis2addr
4
+ (address sort). The typed textbook sort kernels live in
5
+ carray_sort_kernel.c; partition / partition_copy in carray_partition.c.
6
+
7
+ ---------------------------------------------------------------------------- */
8
+
9
+ #include "ruby.h"
10
+ #include "carray.h"
11
+ #include "ca_for_buffer.h" /* CA_WITH_BUFFER (scoped attach/detach) */
12
+ #include "ca_kernel_iterator.h" /* CA_FOR_EACH_FIBER_INOUT (sort_copy) */
13
+ #include "ca_obj_face.h" /* CA_FACE_LIFT_IF_FACE */
14
+ #include "ca_sort_kernels.h" /* ca_sort_quick_* / ca_sort_merge_* / ca_partition_nan_* */
15
+ #include "ca_compare.h" /* ca_elem_cmp[] -- shared element comparators */
16
+ #include <math.h>
17
+ #include <float.h>
18
+
19
+ /* ----------------------------------------------------------------- */
20
+
21
+ /* CA_FIXLEN comparator: direct memcmp over the packed byte width.
22
+ * The shared ca_elem_cmp table (ca_compare.h) leaves FIXLEN unsupported
23
+ * because a 2-arg comparator cannot carry the width; numeric / object
24
+ * data_types go through ca_elem_cmp[data_type].
25
+ *
26
+ * Called by sort_addr_cmp only. */
27
+ static int
28
+ cmp_fixlen_bytes (char *a, char *b, ca_size_t bytes)
29
+ {
30
+ return memcmp(a, b, (size_t) bytes);
31
+ }
32
+
33
+ /* ----------------------------------------------------------------- */
34
+
35
+ struct cmp_base {
36
+ int n;
37
+ CArray **ca;
38
+ int masked_last; /* 1 (default, :last) or 0 (:first) */
39
+ };
40
+
41
+ struct sort_addr_key {
42
+ ca_size_t i;
43
+ struct cmp_base *base;
44
+ };
45
+
46
+ /* Multi-key comparator for CArray.sort_addr: compares keys in priority
47
+ * order, original index breaks ties (stable). Masked cells are an
48
+ * incomparable sentinel clustered at base->masked_last's end (same
49
+ * role NaN plays for float dtypes, and the same masked_position:
50
+ * contract as the sort/partition family's :sentinel kernel mode --
51
+ * see MASKED_POSITION rev1 in mkkernel.rb's MkKernel.sort doc).
52
+ *
53
+ * Called as a qsort/mergesort callback in rb_ca_s_sort_addr. */
54
+ static int
55
+ sort_addr_cmp (struct sort_addr_key *a, struct sort_addr_key *b)
56
+ {
57
+ struct cmp_base *base = a->base;
58
+ int n = base->n;
59
+ CArray **ca = base->ca;
60
+ ca_size_t ia = a->i;
61
+ ca_size_t ib = b->i;
62
+ int result;
63
+ int i;
64
+ for (i=0; i<n; i++) {
65
+ int8_t data_type = ca[i]->data_type;
66
+ char *ptr = ca[i]->ptr;
67
+ boolean8_t *m = ( ca[i]->mask ) ? (boolean8_t *) ca[i]->mask->ptr : NULL;
68
+ ca_size_t bytes = ca[i]->bytes;
69
+ if ( ( ! m ) ||
70
+ ( ( ! m[ia] ) && ( ! m[ib] ) ) ) {
71
+ if ( data_type == CA_FIXLEN ) {
72
+ result = cmp_fixlen_bytes(ptr + ia*bytes,
73
+ ptr + ib*bytes, bytes);
74
+ }
75
+ else {
76
+ result = ca_elem_cmp[data_type](ptr + ia*bytes,
77
+ ptr + ib*bytes);
78
+ }
79
+ }
80
+ else if ( ( ! m[ia] ) && ( m[ib] ) ) {
81
+ result = base->masked_last ? -1 : 1;
82
+ }
83
+ else if ( ( m[ia] ) && ( ! m[ib] ) ) {
84
+ result = base->masked_last ? 1 : -1;
85
+ }
86
+ else {
87
+ result = 0;
88
+ }
89
+ if ( result ) {
90
+ return result;
91
+ }
92
+ }
93
+ return ( ia > ib ) ? 1 : -1; /* for stable sort */
94
+ }
95
+
96
+ /* CArray.sort_addr(*args, masked_position: :last) — multi-key lex
97
+ * sort. Returns a 1-D CA_SIZE array of indices that sorts the
98
+ * arguments in priority order (a > b > c):
99
+ *
100
+ * idx = CArray.sort_addr(a, b, c)
101
+ * a[idx]; b[idx]; c[idx]
102
+ *
103
+ * All arguments must have the same element count. Masked cells are
104
+ * an incomparable sentinel clustered at masked_position: (:last
105
+ * default, or :first); ties are broken by original index (stable). */
106
+ static VALUE
107
+ rb_ca_s_sort_addr (int argc, VALUE *argv, VALUE self)
108
+ {
109
+ volatile VALUE out;
110
+ CArray *co;
111
+ struct cmp_base *base;
112
+ struct sort_addr_key *data;
113
+ ca_size_t elements;
114
+ ca_size_t *q;
115
+ int j;
116
+ ca_size_t i;
117
+
118
+ VALUE ropt = rb_pop_options(&argc, &argv);
119
+ VALUE vmasked_position = Qnil;
120
+ rb_scan_options(ropt, "masked_position", &vmasked_position);
121
+ int masked_last = 1;
122
+ if ( !NIL_P(vmasked_position) ) {
123
+ static ID sym_first = 0, sym_last = 0;
124
+ if ( !sym_first ) sym_first = rb_intern("first");
125
+ if ( !sym_last ) sym_last = rb_intern("last");
126
+ ID mp_id = SYM2ID(vmasked_position);
127
+ if ( mp_id == sym_last ) masked_last = 1;
128
+ else if ( mp_id == sym_first ) masked_last = 0;
129
+ else {
130
+ rb_raise(rb_eArgError,
131
+ "sort_addr: unknown masked_position %s (expected :first or :last)",
132
+ rb_id2name(mp_id));
133
+ }
134
+ }
135
+
136
+ if ( argc <= 0 ) {
137
+ rb_raise(rb_eArgError, "no arg given");
138
+ }
139
+
140
+ rb_check_carray_object(argv[0]);
141
+ elements = NUM2SIZE(rb_ca_elements(argv[0]));
142
+
143
+ for (j=0; j<argc; j++) {
144
+ CArray *ca_j;
145
+ rb_check_carray_object(argv[j]);
146
+ if ( elements != NUM2SIZE(rb_ca_elements(argv[j])) ) {
147
+ rb_raise(rb_eArgError, "elements mismatch");
148
+ }
149
+ /* Face gate: descend a Face to its storage and build attach / comparator
150
+ / template output on that plain storage (this also removes the SEGV
151
+ from a face-lifted NULL-ptr index output). A fixlen storage sorts by
152
+ memcmp (the default order for fixlen, as for a plain fixlen array); a
153
+ numeric storage requires ORDERABLE so the numeric order equals the
154
+ surface order, else it raises. */
155
+ TypedData_Get_Struct(argv[j], CArray, &carray_data_type, ca_j);
156
+ if ( ca_is_face(ca_j) ) {
157
+ int orderable = ca_test_flag(ca_j, CA_FLAG_FACE_ORDERABLE_STORAGE);
158
+ VALUE stripped = rb_ca_strip_face_value(argv[j]);
159
+ CArray *sc;
160
+ TypedData_Get_Struct(stripped, CArray, &carray_data_type, sc);
161
+ if ( sc->data_type != CA_FIXLEN && ! orderable ) {
162
+ rb_raise(rb_eArgError,
163
+ "sort_addr: Face-typed input (%s) is not orderable by "
164
+ "storage; use ca.parent to descend to storage",
165
+ rb_obj_classname(argv[j]));
166
+ }
167
+ argv[j] = stripped;
168
+ }
169
+ }
170
+
171
+ base = xmalloc(sizeof(struct cmp_base));
172
+ base->n = argc;
173
+ base->masked_last = masked_last;
174
+ base->ca = xmalloc(sizeof(CArray *)*base->n);
175
+
176
+ for (j=0; j<argc; j++) {
177
+ CArray *ca;
178
+ TypedData_Get_Struct(argv[j], CArray, &carray_data_type, ca);
179
+ base->ca[j] = ca;
180
+ ca_attach(ca);
181
+ }
182
+
183
+ data = xmalloc(sizeof(struct sort_addr_key)*elements);
184
+ for (i=0; i<elements; i++) {
185
+ data[i].i = i;
186
+ data[i].base = base;
187
+ }
188
+
189
+ #ifdef HAVE_MERGESORT
190
+ mergesort(data, elements, sizeof(struct sort_addr_key),
191
+ (int (*)(const void*,const void*)) sort_addr_cmp);
192
+ #else
193
+ qsort(data, elements, sizeof(struct sort_addr_key),
194
+ (int (*)(const void*,const void*)) sort_addr_cmp);
195
+ #endif
196
+
197
+ out = rb_ca_template_with_type(argv[0], INT2NUM(CA_SIZE), INT2NUM(0));
198
+ TypedData_Get_Struct(out, CArray, &carray_data_type, co);
199
+ q = (ca_size_t *) co->ptr;
200
+
201
+ for (i=0; i<elements; i++) {
202
+ *q = data[i].i;
203
+ q++;
204
+ }
205
+
206
+ for (j=0; j<argc; j++) {
207
+ ca_detach(base->ca[j]);
208
+ }
209
+
210
+ xfree(data);
211
+ xfree(base->ca);
212
+ xfree(base);
213
+
214
+ return out;
215
+ }
216
+
217
+ /* Internal sort_addr_ki kernel entries (C-level only, no Ruby binding;
218
+ * declared in the generated carray_kernels.c). The _quick and _stable
219
+ * variants implement the kind: dispatch; _ki itself is a 2-arg alias of
220
+ * _quick retained for older callers. The _mp ("masked position") twins
221
+ * take an explicit masked_last so `sort` / `sort_copy` can pass the
222
+ * masked_position: kwarg through (see MASKED_POSITION rev1 in
223
+ * mkkernel.rb's MkKernel.sort doc). */
224
+ extern VALUE rb_ca_sort_addr_ki (VALUE self, VALUE vaxis);
225
+ extern VALUE rb_ca_sort_addr_ki_quick (VALUE self, VALUE vaxis);
226
+ extern VALUE rb_ca_sort_addr_ki_stable (VALUE self, VALUE vaxis);
227
+ extern VALUE rb_ca_sort_addr_ki_quick_mp (VALUE self, VALUE vaxis, int masked_last);
228
+ extern VALUE rb_ca_sort_addr_ki_stable_mp (VALUE self, VALUE vaxis, int masked_last);
229
+ extern VALUE rb_ca_remap_new (VALUE cary, VALUE rmapper); /* ca_obj_remap.c (sort view) */
230
+
231
+ /* sort_addr(axis: nil, kind: :quick, masked_position: :last) — returns
232
+ * view-flat addresses that index a sort. Two modes:
233
+ *
234
+ * axis: nil (no kwarg) — flat lex form, equivalent to
235
+ * CArray.sort_addr(self, masked_position:).
236
+ * Shape is preserved (NOT flattened -- this is
237
+ * the legacy "1 key" case of the class method's
238
+ * multi-key lex sort, pinned by
239
+ * test_sort_addr_no_arg_2d_preserves_shape).
240
+ * kind: has no effect (the flat lex path uses
241
+ * qsort/mergesort with its own comparator).
242
+ * axis: k — per-fiber view-flat addresses along axis k.
243
+ * Output shape == self.shape. Dispatches to
244
+ * sort_addr_ki (mkkernel `:sort` kind).
245
+ *
246
+ * kind: selects the sort algorithm for the axis: path:
247
+ * :quick (default) — introsort with mergesort escape
248
+ * :stable — bottom-up mergesort
249
+ *
250
+ * Both kinds are algorithmically stable (pair sort with index tie-
251
+ * break); kind: chooses the performance characteristic only.
252
+ *
253
+ * masked_position: (:last default, or :first) picks which end masked
254
+ * cells cluster to. Effective on BOTH modes: the axis: path forwards
255
+ * to sort_addr_ki's masked_last-aware _mp entries; the no-axis path
256
+ * forwards to CArray.sort_addr's own masked_last-aware comparator
257
+ * (sort_addr_cmp). See {sort}'s doc for the underlying incomparable-
258
+ * sentinel contract.
259
+ */
260
+
261
+ /* C-callable entry: skip rb_scan_args (which depends on call-frame
262
+ * keyword-splat state, only set by full Ruby method dispatch).
263
+ * axis = Qnil -> flat (= CArray.sort_addr(self, masked_position:))
264
+ * axis = Integer -> per-fiber axis path
265
+ * stable: 0 (quick, default) / non-zero (stable)
266
+ * masked_last: 1 (default, :last) / 0 (:first)
267
+ *
268
+ * Called by the Ruby binding rb_ca_sort_addr below, and externally
269
+ * by rb_ca_sort_by_key in carray_order.c (always with axis given, so
270
+ * the flat branch below is unreached from that caller). */
271
+ VALUE
272
+ rb_ca_sort_addr_c (VALUE self, VALUE axis, int stable, int masked_last)
273
+ {
274
+ if ( NIL_P(axis) ) {
275
+ /* Legacy flat: equivalent to CArray.sort_addr(self, masked_position:).
276
+ kind: ignored for now (the flat path uses qsort/mergesort
277
+ internally). rb_ca_s_sort_addr's rb_pop_options handles a trailing
278
+ Hash the same way regardless of call-frame keyword-splat state (it
279
+ type-checks the last positional arg, not rb_keyword_given_p), so
280
+ this raw C call is safe unlike the rb_scan_args "1:" pattern used
281
+ elsewhere in this file. */
282
+ VALUE kw = rb_hash_new();
283
+ rb_hash_aset(kw, ID2SYM(rb_intern("masked_position")),
284
+ masked_last ? ID2SYM(rb_intern("last")) : ID2SYM(rb_intern("first")));
285
+ VALUE flat_argv[2] = { self, kw };
286
+ return rb_ca_s_sort_addr(2, flat_argv, rb_cCArray);
287
+ }
288
+ return stable ? rb_ca_sort_addr_ki_stable_mp(self, axis, masked_last)
289
+ : rb_ca_sort_addr_ki_quick_mp (self, axis, masked_last);
290
+ }
291
+
292
+ /* Ruby binding entry: parses (axis: / kind: / masked_position:) kwargs,
293
+ then forwards. */
294
+ static VALUE
295
+ rb_ca_sort_addr (int argc, VALUE *argv, VALUE self)
296
+ {
297
+ VALUE opts = Qnil;
298
+ VALUE axis = Qnil;
299
+ VALUE kind = Qnil;
300
+ VALUE vmasked_position = Qnil;
301
+
302
+ rb_scan_args(argc, argv, "0:", &opts);
303
+ rb_scan_options(opts, "axis,kind,masked_position", &axis, &kind, &vmasked_position);
304
+
305
+ int do_stable = 0;
306
+ if ( ! NIL_P(kind) ) {
307
+ static ID sym_quick = 0, sym_stable = 0;
308
+ if ( ! sym_quick ) sym_quick = rb_intern("quick");
309
+ if ( ! sym_stable ) sym_stable = rb_intern("stable");
310
+ ID kind_id = SYM2ID(kind);
311
+ if ( kind_id == sym_quick ) do_stable = 0;
312
+ else if ( kind_id == sym_stable ) do_stable = 1;
313
+ else {
314
+ rb_raise(rb_eArgError,
315
+ "sort_addr: unknown kind %s (expected :quick or :stable)",
316
+ rb_id2name(kind_id));
317
+ }
318
+ }
319
+
320
+ int masked_last = 1;
321
+ if ( ! NIL_P(vmasked_position) ) {
322
+ static ID sym_first = 0, sym_last = 0;
323
+ if ( !sym_first ) sym_first = rb_intern("first");
324
+ if ( !sym_last ) sym_last = rb_intern("last");
325
+ ID mp_id = SYM2ID(vmasked_position);
326
+ if ( mp_id == sym_last ) masked_last = 1;
327
+ else if ( mp_id == sym_first ) masked_last = 0;
328
+ else {
329
+ rb_raise(rb_eArgError,
330
+ "sort_addr: unknown masked_position %s (expected :first or :last)",
331
+ rb_id2name(mp_id));
332
+ }
333
+ }
334
+
335
+ return rb_ca_sort_addr_c(self, axis, do_stable, masked_last);
336
+ }
337
+
338
+ /* axis2addr(indices, axis: 0) — converts per-fiber axis-local indices
339
+ * into row-major view-flat addresses into self. For each cell at coord
340
+ * c = (c_0, ..., c_(n-1)) in `indices`:
341
+ *
342
+ * addr[c] = sum_{j != axis} c_j * stride_j + indices[c] * stride_axis
343
+ *
344
+ * where strides are row-major over self.shape
345
+ * (stride_j = product of self.dim[j+1..n-1]).
346
+ *
347
+ * This is the canonical converter between the two axis-position
348
+ * representations the *_index / *_addr kernel families produce:
349
+ *
350
+ * a.min_index(axis: k) — axis-local scalar per fiber
351
+ * a.min_addr(axis: k) — view-flat address per fiber
352
+ * flat_addrs = key.axis2addr(key.min_index(axis: k), axis: k)
353
+ * # == key.min_addr(axis: k)
354
+ *
355
+ * Sits underneath `take_along_axis`: the heavy "axis-local -> view-
356
+ * flat" arithmetic lives here, and `take_along_axis` is a one-liner
357
+ * on top of `flatten[axis2addr(...)]`.
358
+ *
359
+ * Shape rule: indices.ndim == self.ndim, indices.dim[j] == self.dim[j]
360
+ * for all j != axis; indices.dim[axis] is free (the output along axis
361
+ * can be any length).
362
+ *
363
+ * indices data_type: any integer kind (zero-copy if already CA_SIZE).
364
+ * Negative indices: Python-style (-1 = last), normalized internally.
365
+ * OOB indices: raises RangeError. Default axis: 0; negative axis
366
+ * Python-style.
367
+ *
368
+ * Returns: CArray of CA_SIZE, same shape as indices.
369
+ */
370
+
371
+ /* C-callable entry: skip rb_scan_args (call-frame state dependency).
372
+ * vaxis = Qnil treated as axis 0; Integer is taken as-is (negative
373
+ * axis normalized internally).
374
+ *
375
+ * Called by the Ruby binding rb_ca_axis2addr below, and externally
376
+ * by rb_ca_take_along_axis_c / rb_ca_put_along_axis in
377
+ * carray_order.c. */
378
+ VALUE
379
+ rb_ca_axis2addr_c (VALUE self, VALUE vindices, VALUE vaxis)
380
+ {
381
+ rb_check_carray_object(vindices);
382
+
383
+ CArray *ca, *idx_ca, *out_ca;
384
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
385
+ TypedData_Get_Struct(vindices, CArray, &carray_data_type, idx_ca);
386
+
387
+ /* axis normalization + range check. */
388
+ int axis_raw = NIL_P(vaxis) ? 0 : NUM2INT(vaxis);
389
+ int axis = (axis_raw < 0) ? ((int) ca->ndim + axis_raw) : axis_raw;
390
+ if ( axis < 0 || axis >= ca->ndim ) {
391
+ rb_raise(rb_eIndexError,
392
+ "axis2addr: axis %d out of range for ndim %d",
393
+ axis_raw, (int) ca->ndim);
394
+ }
395
+
396
+ /* Shape rule: indices.ndim == self.ndim, dims match except at axis. */
397
+ if ( idx_ca->ndim != ca->ndim ) {
398
+ rb_raise(rb_eArgError,
399
+ "axis2addr: indices.ndim (%d) must equal self.ndim (%d)",
400
+ (int) idx_ca->ndim, (int) ca->ndim);
401
+ }
402
+ for ( int8_t j = 0; j < ca->ndim; j++ ) {
403
+ if ( j == axis ) continue;
404
+ if ( idx_ca->dim[j] != ca->dim[j] ) {
405
+ rb_raise(rb_eArgError,
406
+ "axis2addr: indices.dim[%d] (%lld) must equal "
407
+ "self.dim[%d] (%lld)",
408
+ (int) j, (long long) idx_ca->dim[j],
409
+ (int) j, (long long) ca->dim[j]);
410
+ }
411
+ }
412
+
413
+ /* indices data_type: integer kind accepted; cast to CA_SIZE if needed
414
+ (zero-copy when already CA_SIZE via to_type identity). */
415
+ if ( ! ca_is_integer_type(idx_ca) ) {
416
+ rb_raise(rb_eArgError,
417
+ "axis2addr: indices data_type must be integer kind (got %d)",
418
+ (int) idx_ca->data_type);
419
+ }
420
+ volatile VALUE vidx_cast = vindices;
421
+ CArray *idx_cast = idx_ca;
422
+ if ( idx_ca->data_type != CA_SIZE ) {
423
+ vidx_cast = rb_funcall(vindices, rb_intern("to_type"), 1,
424
+ INT2NUM(CA_SIZE));
425
+ TypedData_Get_Struct(vidx_cast, CArray, &carray_data_type, idx_cast);
426
+ }
427
+
428
+ /* Row-major view-flat strides for self (in cells). */
429
+ ca_size_t strides[CA_RANK_MAX];
430
+ ca_size_t s = 1;
431
+ for ( int8_t j = (int8_t)(ca->ndim - 1); j >= 0; j-- ) {
432
+ strides[j] = s;
433
+ s *= ca->dim[j];
434
+ }
435
+ ca_size_t axis_size = ca->dim[axis];
436
+
437
+ /* Allocate output: same shape as indices, data_type CA_SIZE. */
438
+ volatile VALUE vout =
439
+ rb_ca_template_with_type(vidx_cast, INT2NUM(CA_SIZE), INT2NUM(0));
440
+ TypedData_Get_Struct(vout, CArray, &carray_data_type, out_ca);
441
+
442
+ /* out_ca is a freshly allocated entity (ptr already valid), so it needs
443
+ no attach/sync/detach. idx_cast may still be a view (CA_SIZE identity
444
+ branch above), so its contig buffer is delivered via CA_WITH_BUFFER,
445
+ which aliases when contig / materialises a view into scratch and scopes
446
+ the attach/detach to the block. */
447
+ ca_size_t *out_ptr = (ca_size_t *) out_ca->ptr;
448
+
449
+ ca_size_t coord[CA_RANK_MAX];
450
+ int ndim = idx_cast->ndim;
451
+
452
+ /* OOB is reported after the block: rb_raise from inside CA_WITH_BUFFER
453
+ would longjmp past the scoped ca_detach and leak the attach (= doc
454
+ constraint "restructure to break"). Record the offender, break, raise
455
+ once the buffer lifecycle has closed. */
456
+ ca_size_t bad_k = -1;
457
+ ca_size_t bad_raw = 0;
458
+
459
+ ca_size_t *idx_ptr;
460
+ ca_size_t n;
461
+ CA_WITH_BUFFER(idx_cast, ca_size_t, idx_ptr, n) {
462
+ for ( int8_t j = 0; j < ndim; j++ ) coord[j] = 0;
463
+ for ( ca_size_t k = 0; k < n; k++ ) {
464
+ /* Negative normalize + OOB check (raises on out-of-bounds). */
465
+ ca_size_t raw = idx_ptr[k];
466
+ ca_size_t norm = (raw < 0) ? (raw + axis_size) : raw;
467
+ if ( norm < 0 || norm >= axis_size ) {
468
+ bad_k = k; bad_raw = raw;
469
+ break;
470
+ }
471
+ /* Compute flat addr: sum c_j * stride_j (with c_axis = norm). */
472
+ ca_size_t addr = 0;
473
+ for ( int8_t j = 0; j < ndim; j++ ) {
474
+ if ( j == axis ) {
475
+ addr += norm * strides[j];
476
+ } else {
477
+ addr += coord[j] * strides[j];
478
+ }
479
+ }
480
+ out_ptr[k] = addr;
481
+ /* Advance coord row-major (last axis ticks fastest). */
482
+ for ( int8_t j = (int8_t)(ndim - 1); j >= 0; j-- ) {
483
+ if ( ++coord[j] < idx_cast->dim[j] ) break;
484
+ coord[j] = 0;
485
+ }
486
+ }
487
+ }
488
+
489
+ if ( bad_k >= 0 ) {
490
+ rb_raise(rb_eRangeError,
491
+ "axis2addr: indices[%lld] = %lld out of range [0, %lld) "
492
+ "(after negative normalize)",
493
+ (long long) bad_k, (long long) bad_raw, (long long) axis_size);
494
+ }
495
+
496
+ return vout;
497
+ }
498
+
499
+ /* Ruby binding entry: parses (indices, axis:) -> forwards to _c. */
500
+ static VALUE
501
+ rb_ca_axis2addr (int argc, VALUE *argv, VALUE self)
502
+ {
503
+ VALUE vindices;
504
+ VALUE opts = Qnil;
505
+ VALUE vaxis = Qnil;
506
+
507
+ rb_scan_args(argc, argv, "1:", &vindices, &opts);
508
+ rb_scan_options(opts, "axis", &vaxis);
509
+ return rb_ca_axis2addr_c(self, vindices, vaxis);
510
+ }
511
+
512
+
513
+ /* ===== sort / sort_copy (value & view surface, sibling of the
514
+ sort_addr / axis2addr block above). =============== */
515
+
516
+ /* sort(axis: nil, kind: :quick, masked_position: :last) — returns a
517
+ * CARemap view of self whose elements are sorted along the given axis.
518
+ * When axis: is omitted, self is first flattened to 1-D and the entire
519
+ * array is sorted (so the result is a 1-D view regardless of self.ndim).
520
+ *
521
+ * kind: selects the sort algorithm. Both kinds are algorithmically
522
+ * stable (pair sort with fiber-local index tie-break), so the order
523
+ * is identical for equal values; the choice is a performance
524
+ * characteristic:
525
+ *
526
+ * :quick (default) — portable textbook introsort with mergesort
527
+ * escape. Faster on random data.
528
+ * :stable — portable textbook bottom-up mergesort with
529
+ * insertion pre-pass and sorted-skip merge.
530
+ * Slightly slower on random data but predictable
531
+ * worst case.
532
+ *
533
+ * Mask handling: masked cells are an incomparable sentinel, the same
534
+ * role NaN plays for float dtypes. They are excluded from the value
535
+ * comparison and clustered at one end of each fiber; masked_position:
536
+ * picks which end (:last, default, or :first). Relative order within
537
+ * the masked cluster is unspecified (same contract as the < / > regions
538
+ * of partition). Since `sort` gathers through CARemap, each masked
539
+ * cell's mask bit shows through at its new (clustered) position -- no
540
+ * separate output mask handling is needed here.
541
+ *
542
+ * Dispatch:
543
+ * CA_FIXLEN flows through sort_addr_ki's fixlen dialect (memcmp
544
+ * lexicographic order) + ca_remap_new, same view path as
545
+ * numeric. kind: has no effect.
546
+ * CA_OBJECT flows through sort_addr_ki's object dialect (Ruby `<=>`
547
+ * via rb_funcall) + ca_remap_new, same view path as
548
+ * numeric, both no-axis and axis: forms.
549
+ */
550
+ static VALUE
551
+ rb_ca_sorted_view (int argc, VALUE *argv, VALUE self)
552
+ {
553
+ VALUE rkw = Qnil;
554
+ VALUE vaxis = Qnil;
555
+ VALUE vkind = Qnil;
556
+ VALUE vmasked_position = Qnil;
557
+
558
+ /* Parse kwargs: `sort` accepts `axis:`, `kind:`, `masked_position:`. */
559
+ rb_scan_args(argc, argv, "0:", &rkw);
560
+ rb_scan_options(rkw, "axis,kind,masked_position", &vaxis, &vkind, &vmasked_position);
561
+
562
+ /* Resolve kind: -> do_stable. :quick (default) = introsort; :stable
563
+ = bottom-up mergesort. Both share the same pair layout and produce
564
+ identical orderings; only the algorithm differs. */
565
+ int do_stable = 0;
566
+ if ( !NIL_P(vkind) ) {
567
+ static ID sym_quick = 0, sym_stable = 0;
568
+ if ( !sym_quick ) sym_quick = rb_intern("quick");
569
+ if ( !sym_stable ) sym_stable = rb_intern("stable");
570
+ ID kind_id = SYM2ID(vkind);
571
+ if ( kind_id == sym_quick ) do_stable = 0;
572
+ else if ( kind_id == sym_stable ) do_stable = 1;
573
+ else {
574
+ rb_raise(rb_eArgError,
575
+ "sort: unknown kind %s (expected :quick or :stable)",
576
+ rb_id2name(kind_id));
577
+ }
578
+ }
579
+
580
+ /* Resolve masked_position: -> masked_last. :last (default) or :first. */
581
+ int masked_last = 1;
582
+ if ( !NIL_P(vmasked_position) ) {
583
+ static ID sym_first = 0, sym_last = 0;
584
+ if ( !sym_first ) sym_first = rb_intern("first");
585
+ if ( !sym_last ) sym_last = rb_intern("last");
586
+ ID mp_id = SYM2ID(vmasked_position);
587
+ if ( mp_id == sym_last ) masked_last = 1;
588
+ else if ( mp_id == sym_first ) masked_last = 0;
589
+ else {
590
+ rb_raise(rb_eArgError,
591
+ "sort: unknown masked_position %s (expected :first or :last)",
592
+ rb_id2name(mp_id));
593
+ }
594
+ }
595
+
596
+ CArray *ca;
597
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
598
+
599
+ /* Build target view (flatten for no-arg, identity for axis: kwarg)
600
+ before mask handling: rb_ca_flatten re-propagates the mask field
601
+ from the parent, so any mask stripping must happen on the post-
602
+ flatten target. */
603
+ VALUE target;
604
+ VALUE vaxis_use;
605
+ if ( NIL_P(vaxis) ) {
606
+ target = rb_ca_flatten(self);
607
+ vaxis_use = INT2NUM(0);
608
+ } else {
609
+ target = self;
610
+ vaxis_use = vaxis;
611
+ }
612
+
613
+ /* sort_addr_ki_{quick,stable}_mp returns a CA_SIZE same-shape array of
614
+ view-flat addresses into target, with masked cells clustered at
615
+ masked_position: (mask field present but nothing actually masked is
616
+ handled gracefully too -- the split degenerates to a no-op). Feed
617
+ directly to ca_remap_new to produce the sorted view; the remap gather
618
+ carries target's mask bits through, so masked cells land at their
619
+ clustered position still marked masked -- no extra output-mask step
620
+ needed. */
621
+ volatile VALUE sigma_addr = do_stable
622
+ ? rb_ca_sort_addr_ki_stable_mp(target, vaxis_use, masked_last)
623
+ : rb_ca_sort_addr_ki_quick_mp (target, vaxis_use, masked_last);
624
+ {
625
+ VALUE obj = rb_ca_remap_new(target, sigma_addr);
626
+ CA_FACE_LIFT_IF_FACE(obj, self, ca);
627
+ return obj;
628
+ }
629
+ }
630
+
631
+ /* sort_copy(axis: nil, kind: :quick, masked_position: :last) — eager
632
+ * counterpart to {sort}. Returns a fresh entity CArray of the same
633
+ * shape and data_type as self, with elements sorted along the given
634
+ * axis (no-arg flattens to 1-D, same convention as sort).
635
+ *
636
+ * Implementation: per-fiber gather + sort + scatter via
637
+ * CA_FOR_EACH_FIBER_INOUT. Bypasses the sort_addr_ki + ca_remap_new
638
+ * view chain that sort uses: a single gather + sort + scatter per
639
+ * fiber, no pair struct, no view layer. This fast path is numeric-only
640
+ * and mask-free (CA_KERNEL_NO_MASK below).
641
+ *
642
+ * CA_FIXLEN and masked input both delegate to {sort} + copy instead of
643
+ * duplicating the fixlen dialect / mask-position split in this per-
644
+ * fiber loop: masked_position: is forwarded unchanged. Masked cells
645
+ * keep their masked-ness (the view's remap gather carries the mask bit
646
+ * through, and .copy materializes it), clustered at masked_position:
647
+ * within each fiber -- same contract as {sort}.
648
+ */
649
+ static VALUE
650
+ rb_ca_sort_copy (int argc, VALUE *argv, VALUE self)
651
+ {
652
+ VALUE rkw = Qnil;
653
+ VALUE vaxis = Qnil;
654
+ VALUE vkind = Qnil;
655
+ VALUE vmasked_position = Qnil;
656
+
657
+ rb_scan_args(argc, argv, "0:", &rkw);
658
+ rb_scan_options(rkw, "axis,kind,masked_position", &vaxis, &vkind, &vmasked_position);
659
+
660
+ /* Resolve kind:: :quick (default) = portable textbook quicksort,
661
+ :stable = portable textbook bottom-up mergesort. */
662
+ int do_stable = 0;
663
+ if ( !NIL_P(vkind) ) {
664
+ static ID sym_quick = 0, sym_stable = 0;
665
+ if ( !sym_quick ) sym_quick = rb_intern("quick");
666
+ if ( !sym_stable ) sym_stable = rb_intern("stable");
667
+ ID kind_id = SYM2ID(vkind);
668
+ if ( kind_id == sym_quick ) do_stable = 0;
669
+ else if ( kind_id == sym_stable ) do_stable = 1;
670
+ else {
671
+ rb_raise(rb_eArgError,
672
+ "sort_copy: unknown kind %s (expected :quick or :stable)",
673
+ rb_id2name(kind_id));
674
+ }
675
+ }
676
+
677
+ CArray *ca;
678
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
679
+
680
+ /* CA_FIXLEN and masked input: the per-fiber path below covers
681
+ unmasked numeric dtypes only. Delegate to {sort} (which handles
682
+ both the fixlen dialect and the masked_position split) + copy to
683
+ get the same shape/class contract as the fast path. */
684
+ if ( ca_is_fixlen_type(ca) || ca_has_mask(ca) ) {
685
+ VALUE sv_kw = rb_hash_new();
686
+ if ( !NIL_P(vaxis) ) rb_hash_aset(sv_kw, ID2SYM(rb_intern("axis")), vaxis);
687
+ if ( !NIL_P(vkind) ) rb_hash_aset(sv_kw, ID2SYM(rb_intern("kind")), vkind);
688
+ if ( !NIL_P(vmasked_position) ) rb_hash_aset(sv_kw, ID2SYM(rb_intern("masked_position")), vmasked_position);
689
+ VALUE sv_argv[1] = { sv_kw };
690
+ int sv_argc = ( NIL_P(vaxis) && NIL_P(vkind) && NIL_P(vmasked_position) ) ? 0 : 1;
691
+ VALUE obj = rb_ca_copy(rb_ca_sorted_view(sv_argc, sv_argv, self));
692
+ CA_FACE_LIFT_IF_FACE(obj, self, ca);
693
+ return obj;
694
+ }
695
+
696
+ /* Build target view (flatten for no-arg, identity for axis: kwarg).
697
+ Unmasked past this point (guarded above), so no mask handling is
698
+ needed here -- see the analogous block in rb_ca_sorted_view. */
699
+ VALUE target;
700
+ VALUE vaxis_use;
701
+ if ( NIL_P(vaxis) ) {
702
+ target = rb_ca_flatten(self);
703
+ vaxis_use = INT2NUM(0);
704
+ } else {
705
+ target = self;
706
+ vaxis_use = vaxis;
707
+ }
708
+
709
+ CArray *cat;
710
+ TypedData_Get_Struct(target, CArray, &carray_data_type, cat);
711
+
712
+ /* Normalize axis (negative -> +ndim, range check). */
713
+ int axis = NUM2INT(vaxis_use);
714
+ if ( axis < 0 ) axis += cat->ndim;
715
+ if ( axis < 0 || axis >= cat->ndim ) {
716
+ rb_raise(rb_eArgError, "sort_copy: axis %d out of range for ndim %d",
717
+ NUM2INT(vaxis_use), cat->ndim);
718
+ }
719
+
720
+ /* data_type check: ALL_NUMERIC only (CA_INT8..CA_FLOAT64).
721
+ Complex / object are rejected here; CA_OBJECT goes through the
722
+ axis: lift in rb_ca_sorted_view, and complex sort semantics
723
+ differ enough that we do not pick a default. */
724
+ if ( cat->data_type < CA_INT8 || cat->data_type > CA_FLOAT64 ) {
725
+ rb_raise(rb_eCADataTypeError,
726
+ "sort_copy: data_type %d not supported "
727
+ "(expected one of: i8, u8, i16, u16, i32, u32, i64, u64, f32, f64)",
728
+ cat->data_type);
729
+ }
730
+
731
+ /* Allocate output: same shape and data_type as target
732
+ (bytes=0 = preserve native bytes). */
733
+ volatile VALUE vout = rb_ca_template_with_type(target,
734
+ INT2NUM(cat->data_type),
735
+ INT2NUM(0));
736
+ CArray *cao;
737
+ TypedData_Get_Struct(vout, CArray, &carray_data_type, cao);
738
+
739
+ /* Per-fiber sort via the CA_FOR_EACH_FIBER_INOUT catalog macro.
740
+ The kernel_iterator engine guarantees pi/po contig delivery
741
+ (aliasing the parent when possible, materialising into per-fiber
742
+ or whole-view scratch otherwise), so the author body is stride-
743
+ free: copy contig input -> contig output, sort in place. */
744
+ ca_size_t fiber_n_for_check = (ca_size_t) cat->dim[axis];
745
+ ca_size_t bytes = (ca_size_t) cat->bytes;
746
+ ca_iter_state st_in, st_out;
747
+ char *pi, *po;
748
+ ca_size_t n;
749
+
750
+ /* Per-fiber aux buffer for the stable path: allocated once outside
751
+ the fiber loop and reused across fibers (all fibers share the
752
+ same axis length and data_type). */
753
+ void *aux = NULL;
754
+ if ( do_stable ) {
755
+ aux = xmalloc((size_t) cat->dim[axis] * (size_t) bytes);
756
+ }
757
+ CA_FOR_EACH_FIBER_INOUT(st_in, st_out, cat, cao, (int8_t) axis,
758
+ CA_KERNEL_NO_MASK, pi, po, n) {
759
+ (void) fiber_n_for_check;
760
+ memcpy(po, pi, (size_t) n * (size_t) bytes);
761
+ if ( do_stable ) {
762
+ switch ( cat->data_type ) {
763
+ case CA_INT8: ca_sort_merge_i8 ((int8_t *) po, (int8_t *) aux, n); break;
764
+ case CA_UINT8: ca_sort_merge_u8 ((uint8_t *) po, (uint8_t *) aux, n); break;
765
+ case CA_INT16: ca_sort_merge_i16((int16_t *) po, (int16_t *) aux, n); break;
766
+ case CA_UINT16: ca_sort_merge_u16((uint16_t *) po, (uint16_t *) aux, n); break;
767
+ case CA_INT32: ca_sort_merge_i32((int32_t *) po, (int32_t *) aux, n); break;
768
+ case CA_UINT32: ca_sort_merge_u32((uint32_t *) po, (uint32_t *) aux, n); break;
769
+ case CA_INT64: ca_sort_merge_i64((int64_t *) po, (int64_t *) aux, n); break;
770
+ case CA_UINT64: ca_sort_merge_u64((uint64_t *) po, (uint64_t *) aux, n); break;
771
+ case CA_FLOAT32: {
772
+ ca_size_t fin = ca_partition_nan_f32((float32_t *) po, n);
773
+ ca_sort_merge_f32((float32_t *) po, (float32_t *) aux, fin);
774
+ break;
775
+ }
776
+ case CA_FLOAT64: {
777
+ ca_size_t fin = ca_partition_nan_f64((double *) po, n);
778
+ ca_sort_merge_f64((double *) po, (double *) aux, fin);
779
+ break;
780
+ }
781
+ default:
782
+ rb_raise(rb_eCADataTypeError,
783
+ "sort_copy: BUG: unexpected data_type %d", cat->data_type);
784
+ }
785
+ } else {
786
+ switch ( cat->data_type ) {
787
+ case CA_INT8: ca_sort_quick_i8 ((int8_t *) po, n); break;
788
+ case CA_UINT8: ca_sort_quick_u8 ((uint8_t *) po, n); break;
789
+ case CA_INT16: ca_sort_quick_i16((int16_t *) po, n); break;
790
+ case CA_UINT16: ca_sort_quick_u16((uint16_t *) po, n); break;
791
+ case CA_INT32: ca_sort_quick_i32((int32_t *) po, n); break;
792
+ case CA_UINT32: ca_sort_quick_u32((uint32_t *) po, n); break;
793
+ case CA_INT64: ca_sort_quick_i64((int64_t *) po, n); break;
794
+ case CA_UINT64: ca_sort_quick_u64((uint64_t *) po, n); break;
795
+ case CA_FLOAT32: {
796
+ ca_size_t fin = ca_partition_nan_f32((float32_t *) po, n);
797
+ ca_sort_quick_f32((float32_t *) po, fin);
798
+ break;
799
+ }
800
+ case CA_FLOAT64: {
801
+ ca_size_t fin = ca_partition_nan_f64((double *) po, n);
802
+ ca_sort_quick_f64((double *) po, fin);
803
+ break;
804
+ }
805
+ default:
806
+ rb_raise(rb_eCADataTypeError,
807
+ "sort_copy: BUG: unexpected data_type %d", cat->data_type);
808
+ }
809
+ }
810
+ }
811
+ if ( aux ) xfree(aux);
812
+
813
+ return vout;
814
+ }
815
+
816
+
817
+ void
818
+ Init_carray_sort (void)
819
+ {
820
+ rb_define_singleton_method(rb_cCArray, "sort_addr", rb_ca_s_sort_addr, -1);
821
+ rb_define_method(rb_cCArray, "sort_addr", rb_ca_sort_addr, -1);
822
+ rb_define_method(rb_cCArray, "axis2addr", rb_ca_axis2addr, -1);
823
+
824
+ /* `sort` returns a CARemap view (no-axis flattens to 1-D);
825
+ `sort_copy` is the eager entity counterpart. Both accept axis:
826
+ and kind: kwargs -- see the function comments above. */
827
+ rb_define_method(rb_cCArray, "sort", rb_ca_sorted_view, -1);
828
+ rb_define_method(rb_cCArray, "sort_copy", rb_ca_sort_copy, -1);
829
+ }