carray 3.0.2 → 3.0.4

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 (247) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +568 -0
  3. data/README.md +6 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_descriptor.h +27 -24
  6. data/ext/ca_axis_dispatch.c +220 -0
  7. data/ext/ca_axis_group.c +201 -123
  8. data/ext/ca_binop_dispatch.c +46 -52
  9. data/ext/ca_binop_dispatch.h +1 -1
  10. data/ext/ca_categorical_iterator.c +249 -183
  11. data/ext/ca_compare.c +7 -12
  12. data/ext/ca_for_buffer.h +21 -68
  13. data/ext/ca_group_iter.c +38 -23
  14. data/ext/ca_kernel_iterator.c +1765 -2076
  15. data/ext/ca_kernel_iterator.h +584 -566
  16. data/ext/ca_monop_dispatch.c +86 -63
  17. data/ext/ca_monop_dispatch.h +5 -0
  18. data/ext/ca_obj_array.c +223 -56
  19. data/ext/ca_obj_bincmp.c +172 -138
  20. data/ext/ca_obj_binop.c +116 -230
  21. data/ext/ca_obj_bitarray.c +40 -26
  22. data/ext/ca_obj_bitfield.c +100 -68
  23. data/ext/ca_obj_block.c +19 -9
  24. data/ext/ca_obj_byte_swap.c +45 -46
  25. data/ext/ca_obj_const_string.c +269 -44
  26. data/ext/ca_obj_face.c +612 -89
  27. data/ext/ca_obj_face.h +111 -47
  28. data/ext/ca_obj_fake.c +36 -79
  29. data/ext/ca_obj_farray.c +7 -3
  30. data/ext/ca_obj_field.c +33 -8
  31. data/ext/ca_obj_fixlen_string.c +23 -24
  32. data/ext/ca_obj_grid.c +541 -210
  33. data/ext/ca_obj_meld.c +331 -111
  34. data/ext/ca_obj_moncmp.c +80 -65
  35. data/ext/ca_obj_monop.c +177 -149
  36. data/ext/ca_obj_object.c +664 -248
  37. data/ext/ca_obj_record.c +63 -23
  38. data/ext/ca_obj_reduce.c +18 -16
  39. data/ext/ca_obj_refer.c +140 -58
  40. data/ext/ca_obj_remap.c +85 -24
  41. data/ext/ca_obj_repeat.c +24 -9
  42. data/ext/ca_obj_roll.c +121 -16
  43. data/ext/ca_obj_select.c +61 -58
  44. data/ext/ca_obj_select_axis.c +73 -107
  45. data/ext/ca_obj_shift.c +32 -102
  46. data/ext/ca_obj_stack.c +372 -122
  47. data/ext/ca_obj_stride.c +348 -140
  48. data/ext/ca_obj_string.c +88 -22
  49. data/ext/ca_obj_tile.c +38 -21
  50. data/ext/ca_obj_time.c +45 -31
  51. data/ext/ca_obj_timedelta.c +43 -29
  52. data/ext/ca_obj_transpose.c +17 -6
  53. data/ext/ca_obj_triop.c +125 -141
  54. data/ext/ca_obj_window.c +330 -44
  55. data/ext/ca_op_ipower.c +24 -11
  56. data/ext/ca_op_powi.h +57 -30
  57. data/ext/ca_pow5_table.h +656 -0
  58. data/ext/ca_sort_kernels.h +70 -0
  59. data/ext/ca_sweep_engine.c +104 -81
  60. data/ext/ca_sweep_engine.h +7 -20
  61. data/ext/ca_triop_dispatch.c +4 -1
  62. data/ext/ca_triop_dispatch.h +5 -2
  63. data/ext/caframe_csv_reader.c +542 -0
  64. data/ext/carray.h +241 -92
  65. data/ext/carray_access.c +608 -276
  66. data/ext/carray_address_basis.c +494 -97
  67. data/ext/carray_attribute.c +129 -4
  68. data/ext/carray_bincount.c +39 -7
  69. data/ext/carray_broadcast.c +56 -19
  70. data/ext/carray_build_flags.h +1 -1
  71. data/ext/carray_call_cfunc.c +108 -28
  72. data/ext/carray_cast.c +1576 -223
  73. data/ext/carray_cast_func.rb +77 -24
  74. data/ext/carray_class.c +4 -3
  75. data/ext/carray_conversion.c +208 -75
  76. data/ext/carray_copy.c +82 -24
  77. data/ext/carray_core.c +646 -162
  78. data/ext/carray_count.c +171 -88
  79. data/ext/carray_element.c +94 -60
  80. data/ext/carray_factorize.c +670 -324
  81. data/ext/carray_generate.c +260 -83
  82. data/ext/carray_histogram.c +209 -106
  83. data/ext/carray_hold.c +14 -16
  84. data/ext/carray_index_classifier.c +422 -243
  85. data/ext/carray_internal.h +692 -3
  86. data/ext/carray_kernels_bincmp.c +7 -0
  87. data/ext/carray_kernels_binop.c +487 -120
  88. data/ext/carray_kernels_init.c +653 -50
  89. data/ext/carray_kernels_moncmp.c +15 -8
  90. data/ext/carray_kernels_monop.c +99 -43
  91. data/ext/carray_kernels_reduce_aggregate.c +3925 -6112
  92. data/ext/carray_kernels_reduce_boolean.c +112 -36
  93. data/ext/carray_kernels_reduce_cumulative.c +2127 -6903
  94. data/ext/carray_kernels_reduce_extreme.c +3032 -2892
  95. data/ext/carray_kernels_reduce_variance.c +439 -500
  96. data/ext/carray_kernels_scan.c +1097 -488
  97. data/ext/carray_kernels_search.c +3787 -2176
  98. data/ext/carray_kernels_sort.c +1200 -920
  99. data/ext/carray_kernels_triop.c +479 -7
  100. data/ext/carray_key_windows.c +216 -0
  101. data/ext/carray_lazy.c +967 -153
  102. data/ext/carray_loop.c +81 -65
  103. data/ext/carray_mask.c +910 -324
  104. data/ext/carray_math_kernel.h +1 -0
  105. data/ext/carray_median_percentile.c +299 -100
  106. data/ext/carray_memory_view.c +872 -758
  107. data/ext/carray_operator.c +1171 -364
  108. data/ext/carray_order.c +505 -133
  109. data/ext/carray_partition.c +112 -65
  110. data/ext/carray_random.c +327 -239
  111. data/ext/carray_round.c +273 -0
  112. data/ext/carray_scatter.c +346 -78
  113. data/ext/carray_segment.c +257 -0
  114. data/ext/carray_slab.c +249 -86
  115. data/ext/carray_sort.c +224 -227
  116. data/ext/carray_sort_kernel.c +8 -1
  117. data/ext/carray_struct.c +118 -39
  118. data/ext/carray_test.c +193 -69
  119. data/ext/carray_time_text.c +1182 -0
  120. data/ext/carray_undef.c +4 -0
  121. data/ext/carray_utils.c +579 -113
  122. data/ext/extconf.rb +27 -2
  123. data/ext/mk_call_cfunc.rb +19 -4
  124. data/ext/mk_pow5_table.rb +35 -0
  125. data/ext/mkkernel.rb +1202 -1014
  126. data/ext/ruby_carray.c +17 -5
  127. data/ext/version.h +4 -4
  128. data/ext/version.rb +4 -2
  129. data/lib/carray/attribute.rb +160 -17
  130. data/lib/carray/autoload_carray.rb +23 -27
  131. data/lib/carray/autoload_method_extension.rb +20 -5
  132. data/lib/carray/axis_group.rb +360 -63
  133. data/lib/carray/basics.rb +162 -28
  134. data/lib/carray/bincount_nd.rb +18 -2
  135. data/lib/carray/block_iterator.rb +231 -58
  136. data/lib/carray/boolean_reduce.rb +34 -42
  137. data/lib/carray/categorical.rb +242 -52
  138. data/lib/carray/categorical_iterator.rb +437 -827
  139. data/lib/carray/complex.rb +41 -34
  140. data/lib/carray/conditional.rb +150 -14
  141. data/lib/carray/const_string.rb +59 -4
  142. data/lib/carray/construct.rb +15 -11
  143. data/lib/carray/data_type_extension.rb +1 -0
  144. data/lib/carray/distance_limit.rb +77 -0
  145. data/lib/carray/fixlen_string.rb +7 -1
  146. data/lib/carray/frame/column_verbs.rb +70 -0
  147. data/lib/carray/frame/concat.rb +157 -6
  148. data/lib/carray/frame/convert.rb +82 -7
  149. data/lib/carray/frame/csv_parser.rb +688 -87
  150. data/lib/carray/frame/describe.rb +80 -0
  151. data/lib/carray/frame/frame.rb +369 -45
  152. data/lib/carray/frame/group.rb +631 -90
  153. data/lib/carray/frame/io.rb +506 -79
  154. data/lib/carray/frame/join.rb +411 -62
  155. data/lib/carray/frame/records.rb +116 -19
  156. data/lib/carray/frame/reshape.rb +455 -0
  157. data/lib/carray/frame/sort.rb +30 -18
  158. data/lib/carray/frame/verbs.rb +868 -61
  159. data/lib/carray/frame/windows.rb +117 -0
  160. data/lib/carray/frame.rb +4 -0
  161. data/lib/carray/fuse_source.rb +118 -2
  162. data/lib/carray/fusion.rb +257 -42
  163. data/lib/carray/histogram.rb +6 -1
  164. data/lib/carray/inspect.rb +54 -13
  165. data/lib/carray/iterator.rb +99 -9
  166. data/lib/carray/key_windows.rb +620 -0
  167. data/lib/carray/lazy.rb +216 -193
  168. data/lib/carray/mask_gap_fill.rb +112 -92
  169. data/lib/carray/math.rb +32 -19
  170. data/lib/carray/meld_reduce.rb +52 -21
  171. data/lib/carray/methods/align_addr.rb +8 -4
  172. data/lib/carray/methods/bin.rb +26 -18
  173. data/lib/carray/methods/bincount.rb +18 -2
  174. data/lib/carray/methods/bit_string.rb +15 -4
  175. data/lib/carray/methods/choose.rb +16 -3
  176. data/lib/carray/methods/composition.rb +18 -10
  177. data/lib/carray/methods/factorize.rb +2 -2
  178. data/lib/carray/methods/gather_nd.rb +25 -5
  179. data/lib/carray/methods/insert_block.rb +9 -14
  180. data/lib/carray/methods/is_between.rb +59 -0
  181. data/lib/carray/methods/is_in.rb +140 -10
  182. data/lib/carray/methods/join.rb +2 -7
  183. data/lib/carray/methods/lag.rb +49 -0
  184. data/lib/carray/methods/locate_addr.rb +182 -45
  185. data/lib/carray/methods/mode.rb +18 -1
  186. data/lib/carray/methods/nunique.rb +11 -6
  187. data/lib/carray/methods/repeat.rb +1 -20
  188. data/lib/carray/methods/resize.rb +21 -23
  189. data/lib/carray/methods/snap.rb +34 -12
  190. data/lib/carray/methods/unique.rb +11 -2
  191. data/lib/carray/methods/value_counts.rb +4 -11
  192. data/lib/carray/record_order.rb +208 -0
  193. data/lib/carray/rng.rb +3 -0
  194. data/lib/carray/runtime.rb +65 -17
  195. data/lib/carray/segment_iterator.rb +1221 -0
  196. data/lib/carray/serialize.rb +214 -30
  197. data/lib/carray/slab_iterator.rb +101 -27
  198. data/lib/carray/stack.rb +10 -4
  199. data/lib/carray/string.rb +105 -5
  200. data/lib/carray/string_operation_extension.rb +12 -4
  201. data/lib/carray/struct.rb +137 -58
  202. data/lib/carray/struct_builder.rb +124 -17
  203. data/lib/carray/time.rb +1222 -186
  204. data/lib/carray/window_iterator.rb +242 -81
  205. data/lib/carray.rb +9 -2
  206. data/yard-stubs/ca_obj_array.rb +4 -4
  207. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  208. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  209. data/yard-stubs/ca_obj_fake.rb +1 -1
  210. data/yard-stubs/ca_obj_field.rb +5 -1
  211. data/yard-stubs/ca_obj_grid.rb +21 -10
  212. data/yard-stubs/ca_obj_refer.rb +5 -4
  213. data/yard-stubs/ca_obj_roll.rb +2 -3
  214. data/yard-stubs/ca_obj_shift.rb +5 -1
  215. data/yard-stubs/ca_obj_stride.rb +10 -7
  216. data/yard-stubs/ca_obj_tile.rb +1 -1
  217. data/yard-stubs/ca_obj_transpose.rb +1 -1
  218. data/yard-stubs/ca_obj_window.rb +9 -5
  219. data/yard-stubs/carray_access.rb +11 -5
  220. data/yard-stubs/carray_attribute.rb +0 -5
  221. data/yard-stubs/carray_broadcast.rb +1 -1
  222. data/yard-stubs/carray_cast.rb +88 -43
  223. data/yard-stubs/carray_conversion.rb +15 -2
  224. data/yard-stubs/carray_copy.rb +12 -5
  225. data/yard-stubs/carray_core.rb +3 -3
  226. data/yard-stubs/carray_count.rb +9 -0
  227. data/yard-stubs/carray_element.rb +10 -4
  228. data/yard-stubs/carray_generate.rb +21 -1
  229. data/yard-stubs/carray_lazy.rb +5 -1
  230. data/yard-stubs/carray_loop.rb +4 -1
  231. data/yard-stubs/carray_mask.rb +60 -12
  232. data/yard-stubs/carray_math.rb +52 -14
  233. data/yard-stubs/carray_median_percentile.rb +15 -8
  234. data/yard-stubs/carray_memory_view.rb +40 -23
  235. data/yard-stubs/carray_order.rb +61 -29
  236. data/yard-stubs/carray_random.rb +13 -4
  237. data/yard-stubs/carray_scatter.rb +21 -9
  238. data/yard-stubs/carray_segment.rb +72 -0
  239. data/yard-stubs/carray_slab.rb +10 -2
  240. data/yard-stubs/carray_sort.rb +49 -8
  241. data/yard-stubs/carray_test.rb +15 -3
  242. data/yard-stubs/carray_utils.rb +0 -26
  243. metadata +19 -5
  244. data/ext/ca_for_each_element.h +0 -239
  245. data/ext/carray_kernels_map.c +0 -3467
  246. data/lib/carray/arrow_tensor.rb +0 -401
  247. data/lib/carray/table.rb +0 -108
data/ext/carray_order.c CHANGED
@@ -1,5 +1,6 @@
1
1
  #include "ruby.h"
2
2
  #include "carray.h"
3
+ #include "carray_internal.h" /* ca_integer_arg */
3
4
  #include "ca_kernel_iterator.h"
4
5
  #include "ca_obj_face.h"
5
6
  #include <math.h>
@@ -115,21 +116,115 @@ ca_project_loop (CArray *co, CArray *ca, CArray *ci, char *lfill, char *ufill)
115
116
  xfree(ma);
116
117
  }
117
118
 
118
- CArray *
119
+ /* The result is wrapped before anything is copied into it: for an object
120
+ array the gathered cells are VALUEs that only the result's mark keeps
121
+ alive once the source is detached. */
122
+ static VALUE
119
123
  ca_project (CArray *ca, CArray *ci, char *lfill, char *ufill)
120
124
  {
125
+ volatile VALUE obj;
121
126
  CArray *co;
122
127
 
123
- ca_attach_n(2, ca, ci); /* ATTACH */
128
+ obj = rb_carray_new(ca->data_type, ci->ndim, ci->dim, ca->bytes, NULL);
129
+ GetCArray(obj, co);
124
130
 
125
- co = carray_new(ca->data_type, ci->ndim, ci->dim, ca->bytes, NULL);
131
+ ca_attach_n(2, ca, ci); /* window: nothing raises inside */
126
132
  ca_project_loop(co, ca, ci, lfill, ufill);
127
-
128
133
  ca_detach_n(2, ca, ci); /* DETACH */
129
134
 
130
- return co;
135
+ return obj;
131
136
  }
132
137
 
138
+ /* rk.__count_below__(q, inclusive) -- for each cell of q, how many values
139
+ * of the sorted array rk are below it (at or below it when inclusive).
140
+ * rk is 1-D, ascending, with no masked or NaN value; q has rk's data type
141
+ * (any shape, read in address order). The count is the insertion position
142
+ * of the cell among rk's values, found by binary search; a masked or NaN
143
+ * cell gets an arbitrary count, for the caller to mask. Returns an int64
144
+ * array of q's shape. Numeric and boolean types only. */
145
+
146
+ /* The search keeps a base and a length and moves the base by a comparison
147
+ rather than branching on it, which keeps the loop free of mispredicted
148
+ branches; the last comparison adds the cell at the base. */
149
+ #define CA_COUNT_BELOW_LOOP(T, CMP) \
150
+ { \
151
+ const T *r = (const T *) cr->ptr; \
152
+ const T *x = (const T *) cq->ptr; \
153
+ for ( i = 0; i < m; i++ ) { \
154
+ T v = x[i]; \
155
+ const T *base = r; \
156
+ ca_size_t len = n; \
157
+ if ( len == 0 ) { out[i] = 0; continue; } \
158
+ while ( len > 1 ) { \
159
+ ca_size_t half = len / 2; \
160
+ base = (base[half] CMP v) ? base + half : base; \
161
+ len -= half; \
162
+ } \
163
+ out[i] = (int64_t) (base - r) + ((*base CMP v) ? 1 : 0); \
164
+ } \
165
+ }
166
+
167
+ #define CA_COUNT_BELOW(T) \
168
+ if ( inclusive ) CA_COUNT_BELOW_LOOP(T, <=) \
169
+ else CA_COUNT_BELOW_LOOP(T, <)
170
+
171
+ static VALUE
172
+ rb_ca_count_below (VALUE self, VALUE vq, VALUE vinclusive)
173
+ {
174
+ volatile VALUE vout;
175
+ CArray *cr, *cq, *co;
176
+ ca_size_t n, m, i;
177
+ int64_t *out;
178
+ int inclusive = RTEST(vinclusive);
179
+
180
+ GetCArray(self, cr);
181
+ rb_check_carray_object(vq);
182
+ GetCArray(vq, cq);
183
+ if ( cr->ndim != 1 ) {
184
+ rb_raise(rb_eArgError, "__count_below__: the reference must be 1-D");
185
+ }
186
+ if ( cr->data_type != cq->data_type ) {
187
+ rb_raise(rb_eCADataTypeError,
188
+ "__count_below__: the reference and the query must have one data type");
189
+ }
190
+ switch ( cr->data_type ) {
191
+ case CA_BOOLEAN: case CA_INT8: case CA_UINT8: case CA_INT16: case CA_UINT16:
192
+ case CA_INT32: case CA_UINT32: case CA_INT64: case CA_UINT64:
193
+ case CA_FLOAT32: case CA_FLOAT64:
194
+ break;
195
+ default:
196
+ rb_raise(rb_eCADataTypeError, "__count_below__: no order for %s",
197
+ ca_type_name[cr->data_type]);
198
+ }
199
+
200
+ n = cr->elements;
201
+ m = cq->elements;
202
+ vout = rb_carray_new(CA_INT64, cq->ndim, cq->dim, 0, NULL);
203
+ GetCArray(vout, co);
204
+ out = (int64_t *) co->ptr;
205
+
206
+ ca_attach_n(2, cr, cq); /* window: nothing raises inside */
207
+ switch ( cr->data_type ) {
208
+ case CA_BOOLEAN:
209
+ case CA_UINT8: CA_COUNT_BELOW(uint8_t); break;
210
+ case CA_INT8: CA_COUNT_BELOW(int8_t); break;
211
+ case CA_INT16: CA_COUNT_BELOW(int16_t); break;
212
+ case CA_UINT16: CA_COUNT_BELOW(uint16_t); break;
213
+ case CA_INT32: CA_COUNT_BELOW(int32_t); break;
214
+ case CA_UINT32: CA_COUNT_BELOW(uint32_t); break;
215
+ case CA_INT64: CA_COUNT_BELOW(int64_t); break;
216
+ case CA_UINT64: CA_COUNT_BELOW(uint64_t); break;
217
+ case CA_FLOAT32: CA_COUNT_BELOW(float); break;
218
+ case CA_FLOAT64: CA_COUNT_BELOW(double); break;
219
+ }
220
+ ca_detach_n(2, cr, cq);
221
+
222
+ return vout;
223
+ }
224
+
225
+ #undef CA_COUNT_BELOW
226
+ #undef CA_COUNT_BELOW_LOOP
227
+
133
228
  /* project(idx, lval=nil, uval=nil) — gather: for each element of `idx`,
134
229
  * pick `self[idx[i]]`. Returns an entity CArray shaped like `idx`.
135
230
  *
@@ -142,6 +237,7 @@ VALUE
142
237
  rb_ca_project (int argc, VALUE *argv, VALUE self)
143
238
  {
144
239
  volatile VALUE obj, ridx, vlfval, vufval, vstorage;
240
+ volatile VALUE hlfval = 0, hufval = 0;
145
241
  CArray *ca, *ci, *co;
146
242
  char *lfval, *ufval;
147
243
  int self_is_face;
@@ -152,21 +248,22 @@ rb_ca_project (int argc, VALUE *argv, VALUE self)
152
248
  self_is_face = ca_is_face(ca);
153
249
 
154
250
  rb_check_carray_object(ridx);
251
+ ca_check_index_array(ridx);
155
252
  ci = ca_wrap_readonly(ridx, CA_SIZE);
156
253
 
157
254
  /* The fill args arrive as surface values. rb_ca_obj2ptr owns the
158
- surface->storage conversion: for a Face self it fires the scalar_to_storage
255
+ surface->storage conversion: for a Face self it fires the element_to_storage
159
256
  write hook (a datetime scalar / Time is reconciled to the Face's unit),
160
257
  for a plain array it writes the storage bytes directly. */
161
- lfval = xmalloc(ca->bytes);
162
- ufval = xmalloc(ca->bytes);
258
+ lfval = ALLOCV_N(char, hlfval, ca->bytes); /* converting a fill value */
259
+ ufval = ALLOCV_N(char, hufval, ca->bytes); /* can raise; the unwind frees */
163
260
 
164
- if ( ! NIL_P(vlfval) ) {
261
+ if ( ! NIL_P(vlfval) && vlfval != CA_UNDEF ) {
165
262
  rb_ca_obj2ptr(self, vlfval, lfval);
166
263
  rb_ca_obj2ptr(self, vlfval, ufval);
167
264
  }
168
265
 
169
- if ( ! NIL_P(vufval) ) {
266
+ if ( ! NIL_P(vufval) && vufval != CA_UNDEF ) {
170
267
  rb_ca_obj2ptr(self, vufval, ufval);
171
268
  }
172
269
 
@@ -181,14 +278,18 @@ rb_ca_project (int argc, VALUE *argv, VALUE self)
181
278
  TypedData_Get_Struct(vstorage, CArray, &carray_data_type, ca);
182
279
  }
183
280
 
184
- co = ca_project(ca, ci,
185
- ( ! NIL_P(vlfval) ) ? lfval : NULL,
186
- ( ( ! NIL_P(vufval) ) || ( ! NIL_P(vlfval) ) ) ? ufval : NULL);
187
-
188
- xfree(lfval);
189
- xfree(ufval);
281
+ /* A fill of UNDEF is no fill: the cell is masked, as it is with none. */
282
+ {
283
+ VALUE vlower = vlfval;
284
+ VALUE vupper = NIL_P(vufval) ? vlfval : vufval;
285
+ obj = ca_project(ca, ci,
286
+ ( NIL_P(vlower) || vlower == CA_UNDEF ) ? NULL : lfval,
287
+ ( NIL_P(vupper) || vupper == CA_UNDEF ) ? NULL : ufval);
288
+ }
289
+ GetCArray(obj, co);
190
290
 
191
- obj = ca_wrap_struct(co);
291
+ ALLOCV_END(hlfval);
292
+ ALLOCV_END(hufval);
192
293
 
193
294
  if ( ! ca_is_any_masked(co) ) {
194
295
  obj = rb_ca_unmask_copy(obj);
@@ -198,6 +299,7 @@ rb_ca_project (int argc, VALUE *argv, VALUE self)
198
299
  obj = ca_face_lift(obj, self);
199
300
  }
200
301
 
302
+ rb_ca_inherit_attr(obj, self);
201
303
  return obj;
202
304
  }
203
305
 
@@ -255,7 +357,7 @@ extern VALUE rb_ca_reshape (int argc, VALUE *argv, VALUE self)
255
357
  * through rb_funcall + kwarg hash construction. */
256
358
  extern VALUE rb_ca_sort_index_ki_quick (VALUE self, VALUE vaxis);
257
359
  extern VALUE rb_ca_partition_index_ki (VALUE self, VALUE vaxis, VALUE vkth);
258
- extern VALUE rb_ca_rank_index_ki_quick_dense (VALUE self, VALUE vaxis, int dense);
360
+ extern VALUE rb_ca_rank_index_ki_kind_dense (VALUE self, VALUE vaxis, int stable, int dense);
259
361
 
260
362
  /* ---- no-axis (flat) search helper -----------------------------------
261
363
  *
@@ -439,6 +541,41 @@ rb_ca_search_nearest_addr_kw (int argc, VALUE *argv, VALUE self)
439
541
 
440
542
  /* ---- sort_by_key family ---------------------------------------------- */
441
543
 
544
+ /* The key orders self's cells one for one, so it has self's shape. */
545
+ static void
546
+ ca_by_key_check_shape (VALUE self, VALUE rkey, const char *name)
547
+ {
548
+ CArray *ca, *ck;
549
+ int8_t i;
550
+ rb_check_carray_object(rkey);
551
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
552
+ TypedData_Get_Struct(rkey, CArray, &carray_data_type, ck);
553
+ if ( ca->ndim == ck->ndim ) {
554
+ for (i = 0; i < ca->ndim; i++) {
555
+ if ( ca->dim[i] != ck->dim[i] ) break;
556
+ }
557
+ if ( i == ca->ndim ) return;
558
+ }
559
+ rb_raise(rb_eArgError, "%s: the key's shape %"PRIsVALUE" differs from "
560
+ "the array's %"PRIsVALUE, name,
561
+ rb_funcall(rkey, rb_intern("shape"), 0),
562
+ rb_funcall(self, rb_intern("shape"), 0));
563
+ }
564
+
565
+ /* self at the addresses an extremum by key found. A fiber with no cell to
566
+ compare has a masked (or UNDEF) address and answers UNDEF. */
567
+ static VALUE
568
+ ca_by_key_fetch (VALUE self, VALUE addrs)
569
+ {
570
+ if ( addrs == CA_UNDEF ) {
571
+ return CA_UNDEF;
572
+ }
573
+ if ( rb_obj_is_carray(addrs) && RTEST(rb_ca_has_mask(addrs)) ) {
574
+ return rb_funcall(self, rb_intern("project"), 1, addrs);
575
+ }
576
+ return rb_ca_fetch(self, addrs);
577
+ }
578
+
442
579
  /* sort_by_key(key, axis: 0) -- gather self at key.sort_addr(axis:). */
443
580
  static VALUE
444
581
  rb_ca_sort_by_key (int argc, VALUE *argv, VALUE self)
@@ -449,6 +586,7 @@ rb_ca_sort_by_key (int argc, VALUE *argv, VALUE self)
449
586
  if ( NIL_P(raxis) ) {
450
587
  raxis = INT2FIX(0);
451
588
  }
589
+ ca_by_key_check_shape(self, rkey, "sort_by_key");
452
590
  VALUE addrs = rb_ca_sort_addr_c(rkey, raxis, 0, 1); /* masked_last=1 (:last), unchanged default */
453
591
  return rb_ca_fetch(self, addrs);
454
592
  }
@@ -460,9 +598,11 @@ rb_ca_max_by_key (int argc, VALUE *argv, VALUE self)
460
598
  VALUE rkey, rkw = Qnil, raxis = Qnil;
461
599
  rb_scan_args(argc, argv, "1:", &rkey, &rkw);
462
600
  rb_scan_options(rkw, "axis", &raxis);
463
- if ( RTEST(rb_ca_is_empty(self)) ) {
464
- return rb_const_get(rb_cObject, rb_intern("UNDEF"));
601
+ ca_by_key_check_shape(self, rkey, "max_by_key");
602
+ if ( NIL_P(raxis) && RTEST(rb_ca_is_empty(self)) ) {
603
+ return CA_UNDEF;
465
604
  }
605
+ rkey = ca_order_key(rkey, "max_by_key");
466
606
 
467
607
  VALUE addrs;
468
608
 
@@ -473,9 +613,9 @@ rb_ca_max_by_key (int argc, VALUE *argv, VALUE self)
473
613
  else {
474
614
  VALUE kw = rb_hash_new();
475
615
  rb_hash_aset(kw, ID2SYM(id_axis), raxis);
476
- addrs = rb_ca_argmax_addr_ki(1, &kw, rkey);
616
+ addrs = rb_funcallv_kw(rkey, rb_intern("max_addr"), 1, &kw, RB_PASS_KEYWORDS);
477
617
  }
478
- return rb_ca_fetch(self, addrs);
618
+ return ca_by_key_fetch(self, addrs);
479
619
  }
480
620
 
481
621
  /* min_by_key(key, axis: nil) -- self[key.min_addr(axis:)] (or UNDEF if empty). */
@@ -485,9 +625,11 @@ rb_ca_min_by_key (int argc, VALUE *argv, VALUE self)
485
625
  VALUE rkey, rkw = Qnil, raxis = Qnil;
486
626
  rb_scan_args(argc, argv, "1:", &rkey, &rkw);
487
627
  rb_scan_options(rkw, "axis", &raxis);
488
- if ( RTEST(rb_ca_is_empty(self)) ) {
489
- return rb_const_get(rb_cObject, rb_intern("UNDEF"));
628
+ ca_by_key_check_shape(self, rkey, "min_by_key");
629
+ if ( NIL_P(raxis) && RTEST(rb_ca_is_empty(self)) ) {
630
+ return CA_UNDEF;
490
631
  }
632
+ rkey = ca_order_key(rkey, "min_by_key");
491
633
 
492
634
  VALUE addrs;
493
635
 
@@ -498,9 +640,9 @@ rb_ca_min_by_key (int argc, VALUE *argv, VALUE self)
498
640
  else {
499
641
  VALUE kw = rb_hash_new();
500
642
  rb_hash_aset(kw, ID2SYM(id_axis), raxis);
501
- addrs = rb_ca_argmin_addr_ki(1, &kw, rkey);
643
+ addrs = rb_funcallv_kw(rkey, rb_intern("min_addr"), 1, &kw, RB_PASS_KEYWORDS);
502
644
  }
503
- return rb_ca_fetch(self, addrs);
645
+ return ca_by_key_fetch(self, addrs);
504
646
  }
505
647
 
506
648
  /* ---- take_along_axis / put_along_axis -------------------------------- */
@@ -515,6 +657,11 @@ rb_ca_take_along_axis_c (VALUE self, VALUE indices, VALUE raxis)
515
657
  raxis = INT2FIX(0);
516
658
  }
517
659
  VALUE addrs = rb_ca_axis2addr_c(self, indices, raxis);
660
+ /* A masked index reads as UNDEF: project carries the mask of its
661
+ addresses into the result. */
662
+ if ( RTEST(rb_ca_has_mask(addrs)) ) {
663
+ return rb_funcall(self, rb_intern("project"), 1, addrs);
664
+ }
518
665
  return rb_ca_fetch(self, addrs);
519
666
  }
520
667
 
@@ -539,6 +686,18 @@ rb_ca_put_along_axis (int argc, VALUE *argv, VALUE self)
539
686
  raxis = INT2FIX(0);
540
687
  }
541
688
  VALUE addrs = rb_ca_axis2addr_c(self, argv[0], raxis);
689
+ /* A masked index writes nothing. The values are first laid out over the
690
+ indices by the store itself (an array of the indices' shape, so a
691
+ broadcast, UNDEF or a Face value means what it means unmasked), and
692
+ only the cells at unmasked indices are written. */
693
+ if ( RTEST(rb_ca_has_mask(addrs)) ) {
694
+ VALUE sel = rb_funcall(addrs, rb_intern("is_not_masked"), 0);
695
+ VALUE vals = rb_funcall(self, rb_intern("project"), 1, addrs);
696
+ rb_funcall(vals, rb_intern("[]="), 1, argv[1]);
697
+ rb_ca_store(self, rb_funcall(addrs, rb_intern("[]"), 1, sel),
698
+ rb_funcall(vals, rb_intern("[]"), 1, sel));
699
+ return self;
700
+ }
542
701
  rb_ca_store(self, addrs, argv[1]);
543
702
  return self;
544
703
  }
@@ -549,7 +708,12 @@ static VALUE
549
708
  rb_ca_range_method (VALUE self)
550
709
  {
551
710
  VALUE mm_argv[1] = { Qnil };
552
- VALUE pair = rb_ca_minmax_ki(0, mm_argv, self);
711
+ CArray *ca;
712
+ VALUE pair;
713
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
714
+ /* A Face answers its own minmax (in its order, with its elements). */
715
+ pair = ca_is_face(ca) ? rb_funcall(self, rb_intern("minmax"), 0)
716
+ : rb_ca_minmax_ki(0, mm_argv, self);
553
717
  VALUE lo = rb_ary_entry(pair, 0);
554
718
  VALUE hi = rb_ary_entry(pair, 1);
555
719
  return rb_range_new(lo, hi, 0);
@@ -557,6 +721,221 @@ rb_ca_range_method (VALUE self)
557
721
 
558
722
  /* ---- nlargest / nsmallest family ------------------------------------- */
559
723
 
724
+ /* The partition picks the right values but, among cells that tie with the
725
+ last one picked, any of them. The answer is the cells first in the
726
+ order (value, then NaN, then masked; the values descending for the
727
+ largest) with ties broken by position, as `order` breaks them. So the
728
+ tied candidates are replaced by the earliest cells that tie, and the
729
+ candidates are sorted by (order, position): O(n) per fiber besides the
730
+ sort of the candidates. */
731
+
732
+ typedef struct {
733
+ const char *v; /* the fiber's values, contiguous */
734
+ const boolean8_t *m; /* its mask, or NULL */
735
+ int8_t type;
736
+ ca_size_t bytes;
737
+ int desc;
738
+ } ca_topk_fiber_t;
739
+
740
+ /* 0 a value, 1 NaN, 2 masked: the classes come in that order. */
741
+ static int
742
+ ca_topk_class (const ca_topk_fiber_t *f, ca_size_t i)
743
+ {
744
+ if ( f->m && f->m[i] ) return 2;
745
+ if ( f->type == CA_FLOAT64 && isnan(((const double *) f->v)[i]) ) return 1;
746
+ if ( f->type == CA_FLOAT32 && isnan(((const float *) f->v)[i]) ) return 1;
747
+ return 0;
748
+ }
749
+
750
+ #define CA_TOPK_CMP(T) \
751
+ { T a_ = ((const T *) f->v)[i], b_ = ((const T *) f->v)[j]; \
752
+ r = (a_ < b_) ? -1 : (a_ > b_) ? 1 : 0; } \
753
+ break
754
+
755
+ /* The order of cells i and j, ties not broken. */
756
+ static int
757
+ ca_topk_cmp (const ca_topk_fiber_t *f, ca_size_t i, ca_size_t j)
758
+ {
759
+ int ci = ca_topk_class(f, i), cj = ca_topk_class(f, j), r = 0;
760
+ if ( ci != cj ) return ( ci < cj ) ? -1 : 1;
761
+ if ( ci != 0 ) return 0;
762
+ switch ( f->type ) {
763
+ case CA_BOOLEAN:
764
+ case CA_UINT8: CA_TOPK_CMP(uint8_t);
765
+ case CA_INT8: CA_TOPK_CMP(int8_t);
766
+ case CA_INT16: CA_TOPK_CMP(int16_t);
767
+ case CA_UINT16: CA_TOPK_CMP(uint16_t);
768
+ case CA_INT32: CA_TOPK_CMP(int32_t);
769
+ case CA_UINT32: CA_TOPK_CMP(uint32_t);
770
+ case CA_INT64: CA_TOPK_CMP(int64_t);
771
+ case CA_UINT64: CA_TOPK_CMP(uint64_t);
772
+ case CA_FLOAT32: CA_TOPK_CMP(float);
773
+ case CA_FLOAT64: CA_TOPK_CMP(double);
774
+ case CA_FIXLEN:
775
+ r = memcmp(f->v + i * f->bytes, f->v + j * f->bytes, (size_t) f->bytes);
776
+ r = ( r > 0 ) - ( r < 0 );
777
+ break;
778
+ default:
779
+ r = 0;
780
+ }
781
+ return f->desc ? -r : r;
782
+ }
783
+
784
+ #undef CA_TOPK_CMP
785
+
786
+ /* Merge sort of positions by (order, position). */
787
+ static void
788
+ ca_topk_sort (const ca_topk_fiber_t *f, ca_size_t *p, ca_size_t *tmp, ca_size_t n)
789
+ {
790
+ ca_size_t h, i, j, k;
791
+ if ( n < 2 ) return;
792
+ h = n / 2;
793
+ ca_topk_sort(f, p, tmp, h);
794
+ ca_topk_sort(f, p + h, tmp, n - h);
795
+ i = 0; j = h; k = 0;
796
+ while ( i < h && j < n ) {
797
+ int r = ca_topk_cmp(f, p[i], p[j]);
798
+ if ( r < 0 || ( r == 0 && p[i] < p[j] ) ) tmp[k++] = p[i++];
799
+ else tmp[k++] = p[j++];
800
+ }
801
+ while ( i < h ) tmp[k++] = p[i++];
802
+ while ( j < n ) tmp[k++] = p[j++];
803
+ memcpy(p, tmp, (size_t) n * sizeof(ca_size_t));
804
+ }
805
+
806
+ #define CA_TOPK_SCAN_VALUE(T) \
807
+ { const T *v_ = (const T *) f->v; T w_ = v_[worst]; \
808
+ for (j = 0; j <= end && ties > 0; j++) { \
809
+ if ( v_[j] == w_ && ! ( f->m && f->m[j] ) ) { c[n++] = j; ties--; } \
810
+ } } \
811
+ break
812
+
813
+ /* The earliest cells, up to `end`, that tie with cell `worst`, appended to
814
+ c[n..] until `ties` are found. A value compares by ==, which leaves out
815
+ NaN and the masked cells; those classes tie within themselves. */
816
+ static ca_size_t
817
+ ca_topk_scan_ties (const ca_topk_fiber_t *f, ca_size_t worst, ca_size_t end,
818
+ ca_size_t *c, ca_size_t n, ca_size_t ties)
819
+ {
820
+ ca_size_t j;
821
+ int cls = ca_topk_class(f, worst);
822
+ if ( cls != 0 || f->type == CA_FIXLEN ) {
823
+ for (j = 0; j <= end && ties > 0; j++) {
824
+ if ( ca_topk_cmp(f, j, worst) == 0 ) { c[n++] = j; ties--; }
825
+ }
826
+ return n;
827
+ }
828
+ switch ( f->type ) {
829
+ case CA_BOOLEAN:
830
+ case CA_UINT8: CA_TOPK_SCAN_VALUE(uint8_t);
831
+ case CA_INT8: CA_TOPK_SCAN_VALUE(int8_t);
832
+ case CA_INT16: CA_TOPK_SCAN_VALUE(int16_t);
833
+ case CA_UINT16: CA_TOPK_SCAN_VALUE(uint16_t);
834
+ case CA_INT32: CA_TOPK_SCAN_VALUE(int32_t);
835
+ case CA_UINT32: CA_TOPK_SCAN_VALUE(uint32_t);
836
+ case CA_INT64: CA_TOPK_SCAN_VALUE(int64_t);
837
+ case CA_UINT64: CA_TOPK_SCAN_VALUE(uint64_t);
838
+ case CA_FLOAT32: CA_TOPK_SCAN_VALUE(float);
839
+ case CA_FLOAT64: CA_TOPK_SCAN_VALUE(double);
840
+ }
841
+ return n;
842
+ }
843
+
844
+ #undef CA_TOPK_SCAN_VALUE
845
+
846
+ static void
847
+ ca_topk_fix_fiber (const ca_topk_fiber_t *f, ca_size_t len,
848
+ ca_size_t *c, ca_size_t cap, ca_size_t *tmp)
849
+ {
850
+ ca_size_t worst = c[0], ties = 0, n = 0, end = 0, k;
851
+ for (k = 1; k < cap; k++) {
852
+ if ( ca_topk_cmp(f, c[k], worst) > 0 ) worst = c[k];
853
+ }
854
+ /* Keep the candidates before the worst; the ones tied with it are
855
+ replaced by the earliest cells that tie, which lie no later than the
856
+ last tied candidate. */
857
+ for (k = 0; k < cap; k++) {
858
+ if ( ca_topk_cmp(f, c[k], worst) < 0 ) {
859
+ c[n++] = c[k];
860
+ }
861
+ else {
862
+ ties++;
863
+ if ( c[k] > end ) end = c[k];
864
+ }
865
+ }
866
+ (void) len;
867
+ n = ca_topk_scan_ties(f, worst, end, c, n, ties);
868
+ ca_topk_sort(f, c, tmp, cap);
869
+ }
870
+
871
+ /* `picked` holds cap candidate positions per fiber along axis; returns the
872
+ stable answer of the same shape. The fibers are read with the axis
873
+ moved last, from a contiguous copy. */
874
+ static VALUE
875
+ ca_topk_stable (VALUE self, VALUE picked, long axis, int desc)
876
+ {
877
+ volatile VALUE key = ca_order_key(self, desc ? "nlargest" : "nsmallest");
878
+ volatile VALUE vals, pk, holder = 0;
879
+ VALUE perm[CA_RANK_MAX], inv[CA_RANK_MAX];
880
+ CArray *cv, *cp;
881
+ ca_topk_fiber_t f;
882
+ ca_size_t len, cap, outer, o, *tmp;
883
+ int ndim, k, last;
884
+
885
+ GetCArray(key, cv);
886
+ ndim = cv->ndim;
887
+ last = ( axis == ndim - 1 );
888
+ if ( ! last ) {
889
+ int n = 0;
890
+ for (k = 0; k < ndim; k++) if ( k != axis ) perm[n++] = INT2FIX(k);
891
+ perm[n] = LONG2NUM(axis);
892
+ for (k = 0; k < ndim; k++) inv[NUM2INT(perm[k])] = INT2FIX(k);
893
+ }
894
+ /* An entity whose fibers already lie along its last axis is read in
895
+ place; anything else is copied into that layout. */
896
+ if ( last && ca_is_entity(cv) ) {
897
+ vals = key;
898
+ ca_update_mask(cv);
899
+ }
900
+ else {
901
+ vals = rb_ca_copy(last ? key : rb_funcallv(key, rb_intern("transpose"), ndim, perm));
902
+ }
903
+ pk = rb_ca_copy(last ? picked : rb_funcallv(picked, rb_intern("transpose"), ndim, perm));
904
+ GetCArray(vals, cv);
905
+ GetCArray(pk, cp);
906
+
907
+ len = cv->dim[ndim - 1];
908
+ cap = cp->dim[ndim - 1];
909
+ if ( len == 0 || cap == 0 ) return picked;
910
+ outer = cv->elements / len;
911
+ f.type = cv->data_type;
912
+ f.bytes = cv->bytes;
913
+ f.desc = desc;
914
+ tmp = ALLOCV_N(ca_size_t, holder, cap);
915
+ for (o = 0; o < outer; o++) {
916
+ f.v = cv->ptr + o * len * cv->bytes;
917
+ f.m = cv->mask ? (boolean8_t *) cv->mask->ptr + o * len : NULL;
918
+ ca_topk_fix_fiber(&f, len, (ca_size_t *) cp->ptr + o * cap, cap, tmp);
919
+ }
920
+ ALLOCV_END(holder);
921
+ return last ? pk : rb_ca_copy(rb_funcallv(pk, rb_intern("transpose"), ndim, inv));
922
+ }
923
+
924
+ /* Whether self holds a cell the order puts after the numbers: a masked
925
+ cell, or a NaN. */
926
+ static int
927
+ ca_topk_has_cells_after_numbers (VALUE self, CArray *ca)
928
+ {
929
+ if ( ca_has_mask(ca) && ca_count_masked(ca) > 0 ) {
930
+ return 1;
931
+ }
932
+ if ( ca_is_float_type(ca) ) {
933
+ VALUE nan = rb_funcall(self, rb_intern("is_nan"), 0);
934
+ return RTEST(rb_funcall(nan, rb_intern("any"), 0));
935
+ }
936
+ return 0;
937
+ }
938
+
560
939
  /* Per-axis top-k positions (cap clamped, axis normalized).
561
940
  * Called by rb_ca_topk_index below. */
562
941
  static VALUE
@@ -567,12 +946,32 @@ rb_ca_topk_positions (VALUE self, long cap, long axis_norm, int desc)
567
946
  long dim_axis = (long) ca->dim[axis_norm];
568
947
  VALUE raxis_norm = LONG2NUM(axis_norm);
569
948
 
949
+ /* The largest: reversing the ascending order would put the NaN and the
950
+ masked cells first, so rank them descending (NaN, then the masked
951
+ cells, last) when there are any. An object array is ranked by
952
+ `order` both ways (its cells compare in Ruby). */
953
+ if ( ca->data_type == CA_OBJECT ||
954
+ ( desc && ca_topk_has_cells_after_numbers(self, ca) ) ) {
955
+ VALUE kw = rb_hash_new();
956
+ rb_hash_aset(kw, ID2SYM(rb_intern("axis")), raxis_norm);
957
+ rb_hash_aset(kw, ID2SYM(rb_intern("descending")), desc ? Qtrue : Qfalse);
958
+ VALUE rank = rb_funcallv_kw(self, rb_intern("order"), 1, &kw, RB_PASS_KEYWORDS);
959
+ VALUE full = rb_ca_sort_index_ki_quick(rank, raxis_norm);
960
+ if ( cap >= dim_axis ) {
961
+ return rb_ca_copy(full);
962
+ }
963
+ int ndim = (int) ca->ndim;
964
+ VALUE spec[CA_RANK_MAX];
965
+ for ( int k = 0; k < ndim; k++ ) {
966
+ spec[k] = Qnil;
967
+ }
968
+ spec[axis_norm] = rb_range_new(INT2FIX(0), LONG2NUM(cap), 1);
969
+ return rb_ca_copy(rb_ca_fetch2(full, ndim, spec));
970
+ }
971
+
570
972
  if ( cap >= dim_axis ) {
571
973
  VALUE full = rb_ca_sort_index_ki_quick(self, raxis_norm);
572
- if ( desc ) {
573
- full = rb_ca_flip_axis(full, NUM2LONG(raxis_norm));
574
- }
575
- return rb_ca_copy(full);
974
+ return ca_topk_stable(self, full, axis_norm, desc);
576
975
  }
577
976
 
578
977
  long pivot = desc ? (dim_axis - cap) : (cap - 1);
@@ -599,7 +998,7 @@ rb_ca_topk_positions (VALUE self, long cap, long axis_norm, int desc)
599
998
  order = rb_ca_flip_axis(order, NUM2LONG(raxis_norm));
600
999
  }
601
1000
  VALUE picked = rb_ca_take_along_axis_c(partidx_top, order, raxis_norm);
602
- return rb_ca_copy(picked);
1001
+ return ca_topk_stable(self, picked, axis_norm, desc);
603
1002
  }
604
1003
 
605
1004
  /* Dispatcher: normalize axis, clamp cap, empty-cap corner.
@@ -611,12 +1010,14 @@ rb_ca_topk_index (VALUE self, VALUE rn, VALUE raxis, int desc, const char *name)
611
1010
  CArray *ca;
612
1011
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
613
1012
  long a = rb_ca_normalize_axis_value(self, raxis, name);
614
- long n_val = NUM2LONG(rn);
1013
+ long n_val = ca_integer_arg(rn, "n", name);
615
1014
  long dim_a = (long) ca->dim[a];
616
- long cap = (n_val < dim_a) ? n_val : dim_a;
617
- if ( cap < 0 ) {
618
- cap = 0;
1015
+ /* As Array#max(n): a negative count is an error, one past the length
1016
+ gives the whole fiber. */
1017
+ if ( n_val < 0 ) {
1018
+ rb_raise(rb_eArgError, "%s: n must be non-negative (got %ld)", name, n_val);
619
1019
  }
1020
+ long cap = (n_val < dim_a) ? n_val : dim_a;
620
1021
  if ( cap == 0 ) {
621
1022
  /* Return zero-along-axis int64 array via the C carray constructor. */
622
1023
  ca_size_t out_dim[CA_RANK_MAX];
@@ -701,39 +1102,42 @@ rb_ca_nsmallest (int argc, VALUE *argv, VALUE self)
701
1102
  * ever compare equal), so a later sort_addr key never gets consulted;
702
1103
  * dense ranks preserve ties, so it does.
703
1104
  *
704
- * descending: still works under method: :dense via the same
705
- * (n-1)-ascending transform below: n is fixed per-fiber, so within a
706
- * tied group ascending values are equal and (n-1)-ascending stays
707
- * equal too -- ties survive the transform, and the group-to-group
708
- * order still reverses correctly. */
1105
+ * descending: ranks the largest value 0, with NaN after the numbers as
1106
+ * ascending. The kernel gives the descending dense rank; the ordinal rank
1107
+ * is the ascending ordinal rank of it, so tied cells keep their original
1108
+ * order in both directions. */
709
1109
 
710
1110
  static VALUE
711
1111
  rb_ca_order (int argc, VALUE *argv, VALUE self)
712
1112
  {
713
1113
  VALUE ropt = rb_pop_options(&argc, &argv);
714
- VALUE raxis = Qnil, rdesc = Qfalse, rmethod = Qnil;
715
- rb_scan_options(ropt, "axis,descending,method", &raxis, &rdesc, &rmethod);
1114
+ VALUE raxis = Qnil, rdesc = Qfalse, rmethod = Qnil, rkind = Qnil;
1115
+ rb_scan_options(ropt, "axis,descending,method,kind", &raxis, &rdesc, &rmethod, &rkind);
716
1116
  rb_check_arity(argc, 0, 0);
717
1117
 
718
- int dense = 0; /* default method: :ordinal */
719
- if ( ! NIL_P(rmethod) ) {
720
- static ID sym_ordinal = 0, sym_dense = 0;
721
- if ( ! sym_ordinal ) sym_ordinal = rb_intern("ordinal");
722
- if ( ! sym_dense ) sym_dense = rb_intern("dense");
723
- ID method_id = SYM2ID(rmethod);
724
- if ( method_id == sym_ordinal ) dense = 0;
725
- else if ( method_id == sym_dense ) dense = 1;
726
- else {
727
- rb_raise(rb_eArgError, "order: unknown method %s (expected :ordinal or :dense)",
728
- rb_id2name(method_id));
729
- }
1118
+ int dense = NIL_P(rmethod) ? 0
1119
+ : ca_symbol_choice(rmethod, "method", "ordinal", "dense", NULL);
1120
+ int do_stable = NIL_P(rkind) ? 0
1121
+ : ca_symbol_choice(rkind, "kind", "quick", "stable", NULL);
1122
+
1123
+ if ( RTEST(rdesc) && ! dense ) {
1124
+ VALUE kw = rb_hash_new();
1125
+ rb_hash_aset(kw, ID2SYM(rb_intern("axis")), raxis);
1126
+ rb_hash_aset(kw, ID2SYM(rb_intern("descending")), Qtrue);
1127
+ rb_hash_aset(kw, ID2SYM(rb_intern("method")), ID2SYM(rb_intern("dense")));
1128
+ rb_hash_aset(kw, ID2SYM(rb_intern("kind")), do_stable ? ID2SYM(rb_intern("stable")) : ID2SYM(rb_intern("quick")));
1129
+ VALUE ranked = rb_funcallv_kw(self, rb_intern("order"), 1, &kw, RB_PASS_KEYWORDS);
1130
+ rb_hash_delete(kw, ID2SYM(rb_intern("descending")));
1131
+ rb_hash_delete(kw, ID2SYM(rb_intern("method")));
1132
+ return rb_funcallv_kw(ranked, rb_intern("order"), 1, &kw, RB_PASS_KEYWORDS);
730
1133
  }
731
1134
 
732
- VALUE asc, n;
1135
+ /* The kernel's dense == 2 is the descending dense rank. */
1136
+ int kmode = ( dense && RTEST(rdesc) ) ? 2 : dense;
733
1137
  if ( NIL_P(raxis) ) {
734
1138
  VALUE flat = rb_ca_flatten(self);
735
1139
  /* Direct C-level dispatch via c_callable: true extern (mkkernel). */
736
- VALUE ranked = rb_ca_rank_index_ki_quick_dense(flat, INT2FIX(0), dense);
1140
+ VALUE ranked = rb_ca_rank_index_ki_kind_dense(flat, INT2FIX(0), do_stable, kmode);
737
1141
  /* reshape(*shape) */
738
1142
  CArray *ca;
739
1143
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
@@ -741,43 +1145,12 @@ rb_ca_order (int argc, VALUE *argv, VALUE self)
741
1145
  for ( long k = 0; k < (long) ca->ndim; k++ ) {
742
1146
  shape_argv[k] = LONG2NUM((long) ca->dim[k]);
743
1147
  }
744
- asc = rb_ca_reshape((int) ca->ndim, shape_argv, ranked);
745
- n = RTEST(rb_ca_has_mask(self))
746
- ? SIZE2NUM(ca_count_not_masked(ca)) /* = elements - count_masked */
747
- : rb_ca_elements(self);
748
- } else {
749
- long axis_norm = rb_ca_normalize_axis_value(self, raxis, "order");
750
- VALUE raxis_norm = LONG2NUM(axis_norm);
751
- asc = rb_ca_rank_index_ki_quick_dense(self, raxis_norm, dense);
752
- if ( RTEST(rb_ca_has_mask(self)) ) {
753
- n = rb_ca_count_not_masked_c(self, raxis_norm);
754
- } else {
755
- CArray *ca;
756
- TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
757
- n = LONG2NUM((long) ca->dim[axis_norm]);
758
- }
759
- if ( RTEST(rb_obj_is_kind_of(n, rb_cCArray)) ) {
760
- VALUE ia_argv[1] = { raxis_norm };
761
- n = rb_ca_insert_axis(1, ia_argv, n);
762
- }
763
- }
764
-
765
- VALUE diff;
766
- if ( RTEST(rdesc) ) {
767
- /* `-` via rb_funcall handles both Integer-Integer and
768
- CArray/Integer mixed receivers uniformly via Ruby's operator
769
- dispatch. No single C entry covers both. */
770
- VALUE n_minus_1 = rb_funcall(n, id_sub, 1, INT2FIX(1));
771
- diff = rb_funcall(n_minus_1, id_sub, 1, asc);
772
- } else {
773
- diff = asc;
1148
+ return rb_ca_reshape((int) ca->ndim, shape_argv, ranked);
774
1149
  }
775
- /* Output data_type: CA_SIZE (= ca_size_t, int64 on 64-bit builds),
776
- matching the rest of the *_index family (sort_index / partition_index
777
- / rank_index) that order is built on. No explicit cast needed here:
778
- asc (rank_index_ki's output) is already CA_SIZE, and Integer - CArray
779
- promotion in the descending branch preserves it. */
780
- return diff;
1150
+ long axis_norm = rb_ca_normalize_axis_value(self, raxis, "order");
1151
+ /* Output data_type: CA_SIZE (int64 on 64-bit builds), as the rest of the
1152
+ *_index family. */
1153
+ return rb_ca_rank_index_ki_kind_dense(self, LONG2NUM(axis_norm), do_stable, kmode);
781
1154
  }
782
1155
 
783
1156
  /* ---- linear_section / linear_fetch ----------------------------------- */
@@ -799,12 +1172,7 @@ ca_linear_prep (VALUE self, VALUE raxis, const char *name, long *axis_out)
799
1172
  Face) is caught by the rb_ca_to_float64 cast below, so no separate
800
1173
  float-numeric flag is needed. */
801
1174
  if ( ca_is_face(ca) ) {
802
- if ( ! ca_test_flag(ca, CA_FLAG_FACE_ORDERABLE_STORAGE) ) {
803
- rb_raise(rb_eArgError,
804
- "%s: Face-typed input (%s) is not orderable by storage; "
805
- "use ca.parent to descend to storage",
806
- name, rb_obj_classname(self));
807
- }
1175
+ ca_face_require_orderable(self, name, "order");
808
1176
  self = rb_ca_strip_face_value(self);
809
1177
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
810
1178
  }
@@ -831,44 +1199,26 @@ rb_ca_linear_section_m (int argc, VALUE *argv, VALUE self)
831
1199
  rb_check_arity(argc, 1, 1);
832
1200
  VALUE val = argv[0];
833
1201
 
834
- /* Query gate (generic, mirror search + reference flip
835
- PROPOSAL_TO_COMPARABLE_RECEIVER_FLIP): the reference axis Face reconciles
836
- the query into its own space. A COMPARABLE axis is directly comparable
837
- -> strip a Face query, take a plain query as-is. A non-COMPARABLE Face
838
- axis calls its own to_comparable(query) for ANY query type (Face CArray,
839
- our Scalar, a Ruby Time / DateTime, ...), then strips; it raises if it
840
- cannot reconcile the query. `self` is still the pre-strip axis Face
841
- here, so it carries the unit for to_comparable and its COMPARABLE flag. */
842
- {
843
- int val_is_face = 0, self_is_face = 0, self_comparable = 0;
1202
+ /* Query gate: the reference axis, still a Face here, and the query are
1203
+ brought into one space (ca_face_reconcile_pair). */
1204
+ if ( rb_obj_is_carray(self) ) {
844
1205
  CArray *sca;
845
- if ( rb_obj_is_kind_of(val, rb_cCArray) ) {
846
- CArray *vca;
847
- TypedData_Get_Struct(val, CArray, &carray_data_type, vca);
848
- val_is_face = ca_is_face(vca);
849
- }
850
1206
  TypedData_Get_Struct(self, CArray, &carray_data_type, sca);
851
1207
  if ( ca_is_face(sca) ) {
852
- self_is_face = 1;
853
- self_comparable = ca_test_flag(sca, CA_FLAG_FACE_COMPARABLE_STORAGE);
1208
+ volatile VALUE ref = self, query = val;
1209
+ ca_face_reconcile_pair(&ref, &query, "linear_section");
1210
+ self = ref;
1211
+ val = query;
854
1212
  }
855
- if ( self_comparable ) {
856
- if ( val_is_face ) {
857
- val = ca_face_operand_descend(val, "linear_section");
858
- }
859
- }
860
- else if ( self_is_face ) {
861
- if ( rb_respond_to(self, rb_intern("to_comparable")) ) {
862
- val = rb_funcall(self, rb_intern("to_comparable"), 1, val);
863
- val = rb_ca_strip_face_value(val);
864
- }
865
- else {
866
- rb_raise(rb_eArgError,
867
- "linear_section: non-comparable Face axis (%s) has no "
868
- "to_comparable to reconcile the query; use ca.parent to "
869
- "search the hidden storage explicitly",
870
- rb_obj_classname(self));
871
- }
1213
+ }
1214
+
1215
+ {
1216
+ CArray *ca;
1217
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
1218
+ if ( ca_is_complex_type(ca) ) {
1219
+ rb_raise(rb_eCADataTypeError,
1220
+ "linear_section: a %s array has no order (complex numbers are "
1221
+ "not ordered)", ca_type_name[ca->data_type]);
872
1222
  }
873
1223
  }
874
1224
 
@@ -897,6 +1247,27 @@ rb_ca_linear_fetch_m (int argc, VALUE *argv, VALUE self)
897
1247
  rb_check_arity(argc, 1, 1);
898
1248
  VALUE addr = argv[0];
899
1249
 
1250
+ /* A complex array interpolates its real and imaginary parts: the
1251
+ weights are real, so each part is a real interpolation. */
1252
+ {
1253
+ CArray *ca;
1254
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
1255
+ if ( ca_is_complex_type(ca) ) {
1256
+ VALUE args[2], re, im;
1257
+ int n = 1;
1258
+ args[0] = addr;
1259
+ if ( ! NIL_P(ropt) ) args[n++] = ropt;
1260
+ re = rb_funcallv_kw(rb_funcall(self, rb_intern("real"), 0),
1261
+ rb_intern("linear_fetch"), n, args,
1262
+ NIL_P(ropt) ? RB_NO_KEYWORDS : RB_PASS_KEYWORDS);
1263
+ im = rb_funcallv_kw(rb_funcall(self, rb_intern("imag"), 0),
1264
+ rb_intern("linear_fetch"), n, args,
1265
+ NIL_P(ropt) ? RB_NO_KEYWORDS : RB_PASS_KEYWORDS);
1266
+ return rb_funcall(re, '+', 1,
1267
+ rb_funcall(im, '*', 1, rb_Complex(INT2FIX(0), INT2FIX(1))));
1268
+ }
1269
+ }
1270
+
900
1271
  long axis_norm = 0;
901
1272
  VALUE sc = ca_linear_prep(self, raxis, "linear_fetch", &axis_norm);
902
1273
  return rb_ca_linear_fetch_ki(sc, addr, LONG2NUM(axis_norm));
@@ -925,6 +1296,7 @@ Init_carray_order (void)
925
1296
  id_sym_linear = rb_intern("linear");
926
1297
 
927
1298
  rb_define_method(rb_cCArray, "project", rb_ca_project, -1);
1299
+ rb_define_private_method(rb_cCArray, "__count_below__", rb_ca_count_below, 2);
928
1300
 
929
1301
  /* Search family (dual API: index / addr) -- see the trampoline
930
1302
  comment block above for the axis: kwarg dispatch contract. */