carray 3.0.1 → 3.0.3

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 (229) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +824 -0
  3. data/README.md +5 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +258 -4
  6. data/ext/ca_axis_group.c +368 -184
  7. data/ext/ca_binop_dispatch.c +54 -32
  8. data/ext/ca_binop_dispatch.h +1 -0
  9. data/ext/ca_categorical_iterator.c +90 -41
  10. data/ext/ca_compare.c +8 -2
  11. data/ext/ca_for_buffer.h +21 -68
  12. data/ext/ca_group_iter.c +27 -11
  13. data/ext/ca_kernel_iterator.c +960 -245
  14. data/ext/ca_kernel_iterator.h +352 -181
  15. data/ext/ca_monop_dispatch.c +4 -4
  16. data/ext/ca_monop_dispatch.h +4 -0
  17. data/ext/ca_obj_array.c +194 -42
  18. data/ext/ca_obj_bincmp.c +152 -121
  19. data/ext/ca_obj_binop.c +107 -210
  20. data/ext/ca_obj_bitarray.c +37 -24
  21. data/ext/ca_obj_bitfield.c +95 -62
  22. data/ext/ca_obj_block.c +5 -4
  23. data/ext/ca_obj_byte_swap.c +40 -40
  24. data/ext/ca_obj_const_string.c +180 -50
  25. data/ext/ca_obj_face.c +428 -62
  26. data/ext/ca_obj_face.h +107 -47
  27. data/ext/ca_obj_fake.c +21 -50
  28. data/ext/ca_obj_farray.c +1 -0
  29. data/ext/ca_obj_field.c +16 -3
  30. data/ext/ca_obj_fixlen_string.c +39 -26
  31. data/ext/ca_obj_grid.c +181 -124
  32. data/ext/ca_obj_meld.c +235 -55
  33. data/ext/ca_obj_moncmp.c +61 -61
  34. data/ext/ca_obj_monop.c +125 -107
  35. data/ext/ca_obj_object.c +551 -233
  36. data/ext/ca_obj_record.c +51 -16
  37. data/ext/ca_obj_reduce.c +5 -8
  38. data/ext/ca_obj_refer.c +117 -33
  39. data/ext/ca_obj_remap.c +57 -17
  40. data/ext/ca_obj_repeat.c +21 -5
  41. data/ext/ca_obj_roll.c +115 -11
  42. data/ext/ca_obj_select.c +88 -75
  43. data/ext/ca_obj_select_axis.c +58 -60
  44. data/ext/ca_obj_shift.c +22 -102
  45. data/ext/ca_obj_stack.c +223 -123
  46. data/ext/ca_obj_stride.c +267 -35
  47. data/ext/ca_obj_string.c +25 -22
  48. data/ext/ca_obj_tile.c +32 -16
  49. data/ext/ca_obj_time.c +23 -25
  50. data/ext/ca_obj_timedelta.c +21 -23
  51. data/ext/ca_obj_transpose.c +12 -3
  52. data/ext/ca_obj_triop.c +110 -117
  53. data/ext/ca_obj_window.c +157 -29
  54. data/ext/ca_op_ipower.c +25 -13
  55. data/ext/ca_op_powi.h +57 -30
  56. data/ext/ca_pow5_table.h +656 -0
  57. data/ext/ca_rng_normal.h +42 -0
  58. data/ext/ca_rng_xoshiro256pp.h +105 -0
  59. data/ext/ca_sort_kernels.h +70 -0
  60. data/ext/ca_sweep_engine.c +385 -198
  61. data/ext/ca_sweep_engine.h +31 -23
  62. data/ext/ca_triop_dispatch.c +4 -1
  63. data/ext/ca_triop_dispatch.h +5 -2
  64. data/ext/caframe_csv_reader.c +463 -0
  65. data/ext/carray.h +90 -28
  66. data/ext/carray_access.c +487 -231
  67. data/ext/carray_address_basis.c +731 -0
  68. data/ext/carray_attribute.c +45 -3
  69. data/ext/carray_bincount.c +32 -2
  70. data/ext/carray_broadcast.c +49 -17
  71. data/ext/carray_call_cfunc.c +667 -511
  72. data/ext/carray_cast.c +1556 -247
  73. data/ext/carray_cast_func.rb +34 -21
  74. data/ext/carray_conversion.c +197 -73
  75. data/ext/carray_copy.c +84 -38
  76. data/ext/carray_core.c +591 -119
  77. data/ext/carray_count.c +148 -96
  78. data/ext/carray_element.c +18 -27
  79. data/ext/carray_factorize.c +604 -291
  80. data/ext/carray_generate.c +76 -40
  81. data/ext/carray_histogram.c +152 -94
  82. data/ext/carray_hold.c +2 -1
  83. data/ext/carray_index_classifier.c +63 -96
  84. data/ext/carray_internal.h +400 -1
  85. data/ext/carray_lazy.c +834 -79
  86. data/ext/carray_loop.c +88 -66
  87. data/ext/carray_mask.c +705 -324
  88. data/ext/carray_math_kernel.h +1 -0
  89. data/ext/carray_median_percentile.c +263 -63
  90. data/ext/carray_memory_view.c +254 -248
  91. data/ext/carray_operator.c +1093 -363
  92. data/ext/carray_order.c +133 -69
  93. data/ext/carray_partition.c +102 -56
  94. data/ext/carray_random.c +582 -204
  95. data/ext/carray_scatter.c +308 -65
  96. data/ext/carray_segment.c +257 -0
  97. data/ext/carray_slab.c +183 -70
  98. data/ext/carray_sort.c +235 -240
  99. data/ext/carray_sort_kernel.c +8 -1
  100. data/ext/carray_struct.c +99 -21
  101. data/ext/carray_test.c +167 -64
  102. data/ext/carray_time_text.c +1182 -0
  103. data/ext/carray_utils.c +363 -98
  104. data/ext/extconf.rb +27 -2
  105. data/ext/mk_call_cfunc.rb +103 -118
  106. data/ext/mk_pow5_table.rb +35 -0
  107. data/ext/mkkernel.rb +1349 -851
  108. data/ext/ruby_carray.c +17 -2
  109. data/ext/version.h +4 -4
  110. data/ext/version.rb +4 -2
  111. data/lib/carray/attribute.rb +39 -0
  112. data/lib/carray/autoload_carray.rb +24 -3
  113. data/lib/carray/autoload_method_extension.rb +18 -0
  114. data/lib/carray/axis_group.rb +245 -20
  115. data/lib/carray/basics.rb +95 -25
  116. data/lib/carray/bincount_nd.rb +6 -0
  117. data/lib/carray/block_iterator.rb +200 -54
  118. data/lib/carray/boolean_reduce.rb +21 -29
  119. data/lib/carray/categorical.rb +244 -59
  120. data/lib/carray/categorical_iterator.rb +391 -757
  121. data/lib/carray/complex.rb +36 -29
  122. data/lib/carray/conditional.rb +94 -7
  123. data/lib/carray/const_string.rb +177 -27
  124. data/lib/carray/construct.rb +40 -0
  125. data/lib/carray/data_type_extension.rb +3 -0
  126. data/lib/carray/data_type_limits.rb +91 -0
  127. data/lib/carray/fixlen_string.rb +1 -1
  128. data/lib/carray/frame/concat.rb +157 -6
  129. data/lib/carray/frame/convert.rb +13 -7
  130. data/lib/carray/frame/csv_parser.rb +590 -76
  131. data/lib/carray/frame/describe.rb +79 -0
  132. data/lib/carray/frame/frame.rb +205 -35
  133. data/lib/carray/frame/group.rb +199 -40
  134. data/lib/carray/frame/io.rb +452 -60
  135. data/lib/carray/frame/join.rb +134 -46
  136. data/lib/carray/frame/records.rb +54 -19
  137. data/lib/carray/frame/reshape.rb +448 -0
  138. data/lib/carray/frame/sort.rb +26 -2
  139. data/lib/carray/frame/verbs.rb +726 -56
  140. data/lib/carray/frame.rb +2 -0
  141. data/lib/carray/fuse_source.rb +111 -2
  142. data/lib/carray/fusion.rb +229 -38
  143. data/lib/carray/inspect.rb +71 -20
  144. data/lib/carray/iterator.rb +157 -2
  145. data/lib/carray/lazy.rb +54 -67
  146. data/lib/carray/mask_gap_fill.rb +66 -50
  147. data/lib/carray/math.rb +32 -19
  148. data/lib/carray/meld_reduce.rb +40 -7
  149. data/lib/carray/methods/align_addr.rb +8 -4
  150. data/lib/carray/methods/bincount.rb +2 -2
  151. data/lib/carray/methods/bit_string.rb +14 -3
  152. data/lib/carray/methods/composition.rb +16 -10
  153. data/lib/carray/methods/discovery_along.rb +74 -0
  154. data/lib/carray/methods/factorize.rb +50 -0
  155. data/lib/carray/methods/gather_nd.rb +13 -2
  156. data/lib/carray/methods/insert_block.rb +9 -14
  157. data/lib/carray/methods/is_in.rb +130 -6
  158. data/lib/carray/methods/join.rb +1 -1
  159. data/lib/carray/methods/locate_addr.rb +235 -21
  160. data/lib/carray/methods/mask_duplicates.rb +35 -1
  161. data/lib/carray/methods/mode.rb +18 -1
  162. data/lib/carray/methods/nunique.rb +22 -1
  163. data/lib/carray/methods/repeat.rb +91 -0
  164. data/lib/carray/methods/resize.rb +21 -23
  165. data/lib/carray/methods/snap.rb +34 -12
  166. data/lib/carray/methods/unique.rb +52 -3
  167. data/lib/carray/record_order.rb +208 -0
  168. data/lib/carray/rng.rb +86 -0
  169. data/lib/carray/runtime.rb +14 -11
  170. data/lib/carray/segment_iterator.rb +990 -0
  171. data/lib/carray/serialize.rb +197 -27
  172. data/lib/carray/slab_iterator.rb +109 -40
  173. data/lib/carray/stack.rb +10 -4
  174. data/lib/carray/string_operation_extension.rb +9 -4
  175. data/lib/carray/struct.rb +142 -51
  176. data/lib/carray/struct_builder.rb +110 -14
  177. data/lib/carray/table.rb +2 -2
  178. data/lib/carray/time.rb +535 -105
  179. data/lib/carray/window_iterator.rb +259 -82
  180. data/lib/carray.rb +4 -2
  181. data/yard-stubs/ca_obj_array.rb +4 -4
  182. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  183. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  184. data/yard-stubs/ca_obj_block.rb +2 -7
  185. data/yard-stubs/ca_obj_fake.rb +1 -1
  186. data/yard-stubs/ca_obj_field.rb +1 -1
  187. data/yard-stubs/ca_obj_refer.rb +3 -3
  188. data/yard-stubs/ca_obj_roll.rb +0 -1
  189. data/yard-stubs/ca_obj_stride.rb +10 -7
  190. data/yard-stubs/ca_obj_transpose.rb +1 -1
  191. data/yard-stubs/ca_obj_window.rb +11 -4
  192. data/yard-stubs/carray_access.rb +6 -5
  193. data/yard-stubs/carray_broadcast.rb +2 -2
  194. data/yard-stubs/carray_cast.rb +78 -43
  195. data/yard-stubs/carray_conversion.rb +13 -1
  196. data/yard-stubs/carray_copy.rb +8 -3
  197. data/yard-stubs/carray_core.rb +3 -83
  198. data/yard-stubs/carray_count.rb +16 -2
  199. data/yard-stubs/carray_lazy.rb +210 -1
  200. data/yard-stubs/carray_loop.rb +4 -1
  201. data/yard-stubs/carray_mask.rb +34 -10
  202. data/yard-stubs/carray_math.rb +1499 -4
  203. data/yard-stubs/carray_median_percentile.rb +26 -8
  204. data/yard-stubs/carray_order.rb +43 -82
  205. data/yard-stubs/carray_scatter.rb +10 -3
  206. data/yard-stubs/carray_segment.rb +72 -0
  207. data/yard-stubs/carray_slab.rb +11 -8
  208. data/yard-stubs/carray_sort.rb +50 -8
  209. data/yard-stubs/carray_test.rb +10 -0
  210. data/yard-stubs/carray_utils.rb +0 -26
  211. metadata +19 -19
  212. data/ext/ca_for_each_element.h +0 -239
  213. data/ext/carray_build_flags.h +0 -3
  214. data/ext/carray_config.h +0 -28
  215. data/ext/carray_kernels_bincmp.c +0 -4446
  216. data/ext/carray_kernels_binop.c +0 -11001
  217. data/ext/carray_kernels_init.c +0 -1131
  218. data/ext/carray_kernels_map.c +0 -3467
  219. data/ext/carray_kernels_moncmp.c +0 -2097
  220. data/ext/carray_kernels_monop.c +0 -18313
  221. data/ext/carray_kernels_reduce_aggregate.c +0 -25837
  222. data/ext/carray_kernels_reduce_boolean.c +0 -330
  223. data/ext/carray_kernels_reduce_cumulative.c +0 -14593
  224. data/ext/carray_kernels_reduce_extreme.c +0 -16948
  225. data/ext/carray_kernels_reduce_variance.c +0 -3910
  226. data/ext/carray_kernels_scan.c +0 -3693
  227. data/ext/carray_kernels_search.c +0 -32138
  228. data/ext/carray_kernels_sort.c +0 -10626
  229. data/ext/carray_kernels_triop.c +0 -1392
data/ext/carray_random.c CHANGED
@@ -18,139 +18,159 @@
18
18
  ---------------------------------------------------------------------------- */
19
19
 
20
20
  #include "carray.h"
21
+ #include "carray_internal.h" /* ca_attach_window */
21
22
  #include <math.h>
22
23
  #include <string.h>
24
+ #include <stdint.h>
25
+
26
+ /* The generator itself, shared verbatim with carray-jit. See the file's
27
+ own comment for why it carries no include guard. */
28
+ #include "ca_rng_normal.h"
29
+ #include "ca_rng_xoshiro256pp.h"
30
+
31
+ VALUE rb_cCARng;
32
+
33
+ /* Where a fill's numbers come from. Resolved once per call rather than
34
+ per cell: the `rng:` argument is one object for the whole array, and
35
+ asking what it is inside the loop would put a Ruby type test between
36
+ every pair of draws.
37
+
38
+ CA_RNG_OWN holds the state's cells directly. That array belongs to
39
+ the CArray::Rng the caller passed, so the draws advance it and the
40
+ next call -- here or in a kernel -- carries on from where this one
41
+ stopped. */
42
+ enum {
43
+ CA_RNG_DEFAULT = 0, /* no rng: given -- the per-ractor MT */
44
+ CA_RNG_RUBY = 1, /* a ::Random instance -- also MT */
45
+ CA_RNG_OWN = 2 /* a CArray::Rng -- the generator above */
46
+ };
47
+
48
+ typedef struct {
49
+ int kind;
50
+ VALUE rng;
51
+ CArray *state; /* the CArray::Rng state for CA_RNG_OWN, else NULL */
52
+ int64_t *cells; /* state->ptr, once the window has attached it */
53
+ } ca_rng_t;
23
54
 
24
-
25
- static inline double
26
- ca_random_real(VALUE rng)
55
+ static void
56
+ ca_rng_resolve (VALUE rng, ca_rng_t *source)
27
57
  {
28
- if (NIL_P(rng))
29
- return rb_genrand_real();
30
- else
31
- return rb_random_real(rng);
58
+ source->rng = rng;
59
+ source->state = NULL;
60
+ source->cells = NULL;
61
+ if (NIL_P(rng)) {
62
+ source->kind = CA_RNG_DEFAULT;
63
+ }
64
+ else if (rb_obj_is_kind_of(rng, rb_cCARng)) {
65
+ VALUE state = rb_ivar_get(rng, rb_intern("@state"));
66
+ CArray *ca;
67
+ TypedData_Get_Struct(state, CArray, &carray_data_type, ca);
68
+ source->kind = CA_RNG_OWN;
69
+ source->state = ca;
70
+ }
71
+ else {
72
+ source->kind = CA_RNG_RUBY;
73
+ }
32
74
  }
33
75
 
34
- static inline unsigned long
35
- ca_random_ulong_limited(VALUE rng, unsigned long limit)
76
+ /* The fills below draw inside a window over the array and, for a
77
+ CArray::Rng, the generator's state. A Ruby generator (rng: a
78
+ ::Random or anything with #rand) is called per draw and can raise part
79
+ way; the window then pushes back the cells drawn so far, and the state
80
+ they advanced, as it does when the fill completes. */
81
+
82
+ typedef struct {
83
+ CArray *ca;
84
+ ca_rng_t source;
85
+ ca_size_t n;
86
+ int is_default;
87
+ double low_dbl, high_dbl;
88
+ long low_long;
89
+ unsigned long limit;
90
+ int axis; /* shuffle!: -1 = all cells */
91
+ } ca_random_ctx_t;
92
+
93
+ static void
94
+ ca_random_run (ca_random_ctx_t *c, VALUE rng, VALUE (*fill)(VALUE))
36
95
  {
37
- if (NIL_P(rng))
38
- return rb_genrand_ulong_limited(limit);
39
- else
40
- return rb_random_ulong_limited(rng, limit);
96
+ CArray *list[2];
97
+ ca_rng_resolve(rng, &c->source);
98
+ list[0] = c->ca;
99
+ list[1] = c->source.state;
100
+ ca_attach_window(2, list, "ww", fill, (VALUE) c);
41
101
  }
42
102
 
43
- /* ---- random! ----------------------------------------------------------- */
44
-
45
- /* CArray#random!([low,] [high], rng:) — fill self with uniform random
46
- * numbers in-place, returning self. Range surface mirrors Numo/NumPy
47
- * (half-open [low, high) as the default) plus Ruby idiom (`..` closed,
48
- * `...` half-open):
49
- *
50
- * random! float [0.0, 1.0); integer -> ArgumentError
51
- * random!(high) [0, high) (Ruby rand shorthand)
52
- * random!(low, high) [low, high) (Numo positional)
53
- * random!(a..b) [a, b] closed (integer: b included)
54
- * random!(a...b) [a, b) half-open
55
- * random!(..., rng: r) any of the above with a custom Random source
56
- *
57
- * For float, `..` and `...` return the same distribution (endpoint
58
- * probability ~2^-53); the closed form is honored syntactically but not
59
- * enforced at the mantissa level, matching NumPy/SciPy convention.
60
- * Boolean fills 0/1 with 50% probability each and ignores the range
61
- * argument. Rejects CA_OBJECT / CA_FIXLEN. */
62
- static VALUE
63
- rb_ca_random_bang(int argc, VALUE *argv, VALUE self)
103
+ /* The state is attached once the window is open. */
104
+ static void
105
+ ca_rng_bind (ca_rng_t *source)
64
106
  {
65
- CArray *ca;
66
- VALUE arg1 = Qnil, arg2 = Qnil, opts = Qnil;
67
- VALUE low_val = Qnil, high_val = Qnil;
68
- int is_default = 1;
69
- int high_is_closed = 0;
70
- double low_dbl = 0.0, high_dbl = 0.0;
71
- long low_long = 0, high_long = 0;
72
- unsigned long limit = 0;
73
- ca_size_t i, n;
74
- VALUE rng = Qnil;
75
-
76
- rb_scan_args(argc, argv, "02:", &arg1, &arg2, &opts);
77
- rb_scan_options(opts, "rng", &rng);
107
+ source->cells = source->state ? (int64_t *) source->state->ptr : NULL;
108
+ }
78
109
 
79
- /* Parse positional args into (low_val, high_val) + high_is_closed. */
80
- if (rb_obj_is_kind_of(arg1, rb_cRange)) {
81
- if (!NIL_P(arg2)) {
82
- rb_raise(rb_eArgError,
83
- "random: cannot combine a Range with a second positional arg");
84
- }
85
- low_val = rb_funcall(arg1, rb_intern("begin"), 0);
86
- high_val = rb_funcall(arg1, rb_intern("end"), 0);
87
- if (NIL_P(low_val) || NIL_P(high_val)) {
88
- rb_raise(rb_eArgError,
89
- "random: Range must have finite begin and end");
90
- }
91
- high_is_closed = ! RTEST(rb_funcall(arg1, rb_intern("exclude_end?"), 0));
92
- is_default = 0;
93
- } else if (!NIL_P(arg1) && !NIL_P(arg2)) {
94
- low_val = arg1;
95
- high_val = arg2;
96
- is_default = 0;
97
- } else if (!NIL_P(arg1)) {
98
- low_val = INT2FIX(0);
99
- high_val = arg1;
100
- is_default = 0;
110
+ static inline double
111
+ ca_random_real (ca_rng_t *source)
112
+ {
113
+ switch (source->kind) {
114
+ case CA_RNG_OWN: return ca_xoshiro256pp_next_real(source->cells);
115
+ case CA_RNG_RUBY: return rb_random_real(source->rng);
116
+ default: return rb_genrand_real();
101
117
  }
118
+ }
102
119
 
103
- rb_ca_modify(self);
104
- TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
105
-
106
- if (ca->data_type == CA_OBJECT || ca->data_type == CA_FIXLEN) {
107
- rb_raise(rb_eCADataTypeError,
108
- "random! is not supported for object/fixlen arrays");
109
- }
120
+ /* A draw in [0, limit], inclusive, matching what Ruby's own bounded draw
121
+ promises. For the generator above that is rejection sampling: taking
122
+ the remainder alone would favour the low end of the range whenever the
123
+ range does not divide 2**64. */
124
+ static inline uint64_t
125
+ ca_xoshiro256pp_below (int64_t *state, uint64_t range)
126
+ {
127
+ uint64_t threshold, draw;
128
+ if (range == 0) return ca_xoshiro256pp_next(state); /* the whole word */
129
+ threshold = (0 - range) % range; /* 2**64 mod range */
130
+ do {
131
+ draw = ca_xoshiro256pp_next(state);
132
+ } while (draw < threshold);
133
+ return draw % range;
134
+ }
110
135
 
111
- /* Materialize (low, high) as the correct scalar type and validate.
112
- * For integer data types, `..` closed adds 1 to high (turns into half-open
113
- * for the sampler); for float data types, closed and half-open are
114
- * equivalent so no adjustment. */
115
- if (!is_default) {
116
- int is_integer_type = (ca->data_type >= CA_INT8
117
- && ca->data_type <= CA_UINT64);
118
- if (is_integer_type) {
119
- low_long = NUM2LONG(low_val);
120
- high_long = NUM2LONG(high_val);
121
- if (high_is_closed) high_long += 1;
122
- if (low_long >= high_long) {
123
- rb_raise(rb_eArgError,
124
- "random: low must be less than high "
125
- "(got low=%ld, high=%ld)",
126
- low_long, high_long);
127
- }
128
- limit = (unsigned long)(high_long - low_long) - 1;
129
- } else {
130
- low_dbl = NUM2DBL(low_val);
131
- high_dbl = NUM2DBL(high_val);
132
- if (low_dbl >= high_dbl) {
133
- rb_raise(rb_eArgError,
134
- "random: low must be less than high "
135
- "(got low=%g, high=%g)",
136
- low_dbl, high_dbl);
137
- }
138
- }
136
+ static inline unsigned long
137
+ ca_random_ulong_limited (ca_rng_t *source, unsigned long limit)
138
+ {
139
+ switch (source->kind) {
140
+ case CA_RNG_OWN:
141
+ return (unsigned long)
142
+ ca_xoshiro256pp_below(source->cells, (uint64_t) limit + 1);
143
+ case CA_RNG_RUBY:
144
+ return rb_random_ulong_limited(source->rng, limit);
145
+ default:
146
+ return rb_genrand_ulong_limited(limit);
139
147
  }
148
+ }
140
149
 
141
- n = ca->elements;
142
- ca_attach(ca);
150
+ /* ---- random! ----------------------------------------------------------- */
143
151
 
152
+ static VALUE
153
+ ca_random_bang_fill (VALUE arg)
154
+ {
155
+ ca_random_ctx_t *c = (ca_random_ctx_t *) arg;
156
+ CArray *ca = c->ca;
157
+ ca_size_t i, n = c->n;
158
+ int is_default = c->is_default;
159
+ double low_dbl = c->low_dbl, high_dbl = c->high_dbl;
160
+ long low_long = c->low_long;
161
+ unsigned long limit = c->limit;
162
+
163
+ ca_rng_bind(&c->source);
144
164
  switch (ca->data_type) {
145
165
  case CA_FLOAT64: {
146
166
  double *p = (double *)ca->ptr;
147
167
  if (is_default) {
148
168
  for (i = 0; i < n; i++)
149
- p[i] = ca_random_real(rng);
169
+ p[i] = ca_random_real(&c->source);
150
170
  } else {
151
171
  double range = high_dbl - low_dbl;
152
172
  for (i = 0; i < n; i++)
153
- p[i] = low_dbl + ca_random_real(rng) * range;
173
+ p[i] = low_dbl + ca_random_real(&c->source) * range;
154
174
  }
155
175
  break;
156
176
  }
@@ -158,11 +178,11 @@ rb_ca_random_bang(int argc, VALUE *argv, VALUE self)
158
178
  float *p = (float *)ca->ptr;
159
179
  if (is_default) {
160
180
  for (i = 0; i < n; i++)
161
- p[i] = (float)ca_random_real(rng);
181
+ p[i] = (float)ca_random_real(&c->source);
162
182
  } else {
163
183
  double range = high_dbl - low_dbl;
164
184
  for (i = 0; i < n; i++)
165
- p[i] = (float)(low_dbl + ca_random_real(rng) * range);
185
+ p[i] = (float)(low_dbl + ca_random_real(&c->source) * range);
166
186
  }
167
187
  break;
168
188
  }
@@ -170,12 +190,12 @@ rb_ca_random_bang(int argc, VALUE *argv, VALUE self)
170
190
  double complex *p = (double complex *)ca->ptr;
171
191
  if (is_default) {
172
192
  for (i = 0; i < n; i++)
173
- p[i] = ca_random_real(rng) + ca_random_real(rng) * I;
193
+ p[i] = ca_random_real(&c->source) + ca_random_real(&c->source) * I;
174
194
  } else {
175
195
  double range = high_dbl - low_dbl;
176
196
  for (i = 0; i < n; i++)
177
- p[i] = (low_dbl + ca_random_real(rng) * range)
178
- + (low_dbl + ca_random_real(rng) * range) * I;
197
+ p[i] = (low_dbl + ca_random_real(&c->source) * range)
198
+ + (low_dbl + ca_random_real(&c->source) * range) * I;
179
199
  }
180
200
  break;
181
201
  }
@@ -183,26 +203,24 @@ rb_ca_random_bang(int argc, VALUE *argv, VALUE self)
183
203
  float complex *p = (float complex *)ca->ptr;
184
204
  if (is_default) {
185
205
  for (i = 0; i < n; i++)
186
- p[i] = (float)ca_random_real(rng) + (float)ca_random_real(rng) * I;
206
+ p[i] = (float)ca_random_real(&c->source) + (float)ca_random_real(&c->source) * I;
187
207
  } else {
188
208
  double range = high_dbl - low_dbl;
189
209
  for (i = 0; i < n; i++)
190
- p[i] = (float)(low_dbl + ca_random_real(rng) * range)
191
- + (float)(low_dbl + ca_random_real(rng) * range) * I;
210
+ p[i] = (float)(low_dbl + ca_random_real(&c->source) * range)
211
+ + (float)(low_dbl + ca_random_real(&c->source) * range) * I;
192
212
  }
193
213
  break;
194
214
  }
195
215
  case CA_BOOLEAN: {
196
216
  boolean8_t *p = (boolean8_t *)ca->ptr;
197
217
  for (i = 0; i < n; i++)
198
- p[i] = (ca_random_real(rng) < 0.5) ? 1 : 0;
218
+ p[i] = (ca_random_real(&c->source) < 0.5) ? 1 : 0;
199
219
  break;
200
220
  }
201
221
  default: {
202
222
  /* integer types: CA_INT8..CA_UINT64 */
203
223
  if (is_default) {
204
- ca_sync(ca);
205
- ca_detach(ca);
206
224
  rb_raise(rb_eArgError,
207
225
  "random! on an integer array requires a range: "
208
226
  "a.random!(high), a.random!(low, high), or "
@@ -212,54 +230,52 @@ rb_ca_random_bang(int argc, VALUE *argv, VALUE self)
212
230
  case CA_INT8: {
213
231
  int8_t *p = (int8_t *)ca->ptr;
214
232
  for (i = 0; i < n; i++)
215
- p[i] = (int8_t)(low_long + ca_random_ulong_limited(rng, limit));
233
+ p[i] = (int8_t)(low_long + ca_random_ulong_limited(&c->source, limit));
216
234
  break;
217
235
  }
218
236
  case CA_UINT8: {
219
237
  uint8_t *p = (uint8_t *)ca->ptr;
220
238
  for (i = 0; i < n; i++)
221
- p[i] = (uint8_t)(low_long + ca_random_ulong_limited(rng, limit));
239
+ p[i] = (uint8_t)(low_long + ca_random_ulong_limited(&c->source, limit));
222
240
  break;
223
241
  }
224
242
  case CA_INT16: {
225
243
  int16_t *p = (int16_t *)ca->ptr;
226
244
  for (i = 0; i < n; i++)
227
- p[i] = (int16_t)(low_long + ca_random_ulong_limited(rng, limit));
245
+ p[i] = (int16_t)(low_long + ca_random_ulong_limited(&c->source, limit));
228
246
  break;
229
247
  }
230
248
  case CA_UINT16: {
231
249
  uint16_t *p = (uint16_t *)ca->ptr;
232
250
  for (i = 0; i < n; i++)
233
- p[i] = (uint16_t)(low_long + ca_random_ulong_limited(rng, limit));
251
+ p[i] = (uint16_t)(low_long + ca_random_ulong_limited(&c->source, limit));
234
252
  break;
235
253
  }
236
254
  case CA_INT32: {
237
255
  int32_t *p = (int32_t *)ca->ptr;
238
256
  for (i = 0; i < n; i++)
239
- p[i] = (int32_t)(low_long + ca_random_ulong_limited(rng, limit));
257
+ p[i] = (int32_t)(low_long + ca_random_ulong_limited(&c->source, limit));
240
258
  break;
241
259
  }
242
260
  case CA_UINT32: {
243
261
  uint32_t *p = (uint32_t *)ca->ptr;
244
262
  for (i = 0; i < n; i++)
245
- p[i] = (uint32_t)(low_long + ca_random_ulong_limited(rng, limit));
263
+ p[i] = (uint32_t)(low_long + ca_random_ulong_limited(&c->source, limit));
246
264
  break;
247
265
  }
248
266
  case CA_INT64: {
249
267
  int64_t *p = (int64_t *)ca->ptr;
250
268
  for (i = 0; i < n; i++)
251
- p[i] = (int64_t)(low_long + ca_random_ulong_limited(rng, limit));
269
+ p[i] = (int64_t)(low_long + ca_random_ulong_limited(&c->source, limit));
252
270
  break;
253
271
  }
254
272
  case CA_UINT64: {
255
273
  uint64_t *p = (uint64_t *)ca->ptr;
256
274
  for (i = 0; i < n; i++)
257
- p[i] = (uint64_t)(low_long + ca_random_ulong_limited(rng, limit));
275
+ p[i] = (uint64_t)(low_long + ca_random_ulong_limited(&c->source, limit));
258
276
  break;
259
277
  }
260
278
  default:
261
- ca_sync(ca);
262
- ca_detach(ca);
263
279
  rb_raise(rb_eCADataTypeError,
264
280
  "random! is not supported for this data type");
265
281
  }
@@ -267,64 +283,213 @@ rb_ca_random_bang(int argc, VALUE *argv, VALUE self)
267
283
  }
268
284
  }
269
285
 
270
- ca_sync(ca);
271
- ca_detach(ca);
286
+ return Qnil;
287
+ }
288
+
289
+ /* CArray#random!([low,] [high], rng:) — fill self with uniform random
290
+ * numbers in-place, returning self. Range surface mirrors Numo/NumPy
291
+ * (half-open [low, high) as the default) plus Ruby idiom (`..` closed,
292
+ * `...` half-open):
293
+ *
294
+ * random! float [0.0, 1.0); integer -> ArgumentError
295
+ * random!(high) [0, high) (Ruby rand shorthand)
296
+ * random!(low, high) [low, high) (Numo positional)
297
+ * random!(a..b) [a, b] closed (integer: b included)
298
+ * random!(a...b) [a, b) half-open
299
+ * random!(..., rng: r) any of the above with a custom Random source
300
+ *
301
+ * For float, `..` and `...` return the same distribution (endpoint
302
+ * probability ~2^-53); the closed form is honored syntactically but not
303
+ * enforced at the mantissa level, matching NumPy/SciPy convention.
304
+ * Boolean fills 0/1 with 50% probability each and ignores the range
305
+ * argument. Rejects CA_OBJECT / CA_FIXLEN. */
306
+ static VALUE
307
+ rb_ca_random_bang(int argc, VALUE *argv, VALUE self)
308
+ {
309
+ CArray *ca;
310
+ VALUE arg1 = Qnil, arg2 = Qnil, opts = Qnil;
311
+ VALUE low_val = Qnil, high_val = Qnil;
312
+ int is_default = 1;
313
+ int high_is_closed = 0;
314
+ double low_dbl = 0.0, high_dbl = 0.0;
315
+ long low_long = 0, high_long = 0;
316
+ unsigned long limit = 0;
317
+ VALUE rng = Qnil;
318
+
319
+ rb_scan_args(argc, argv, "02:", &arg1, &arg2, &opts);
320
+ rb_scan_options(opts, "rng", &rng);
321
+
322
+ /* Parse positional args into (low_val, high_val) + high_is_closed. */
323
+ if (rb_obj_is_kind_of(arg1, rb_cRange)) {
324
+ if (!NIL_P(arg2)) {
325
+ rb_raise(rb_eArgError,
326
+ "random: cannot combine a Range with a second positional arg");
327
+ }
328
+ low_val = rb_funcall(arg1, rb_intern("begin"), 0);
329
+ high_val = rb_funcall(arg1, rb_intern("end"), 0);
330
+ if (NIL_P(low_val) || NIL_P(high_val)) {
331
+ rb_raise(rb_eArgError,
332
+ "random: Range must have finite begin and end");
333
+ }
334
+ high_is_closed = ! RTEST(rb_funcall(arg1, rb_intern("exclude_end?"), 0));
335
+ is_default = 0;
336
+ } else if (!NIL_P(arg1) && !NIL_P(arg2)) {
337
+ low_val = arg1;
338
+ high_val = arg2;
339
+ is_default = 0;
340
+ } else if (!NIL_P(arg1)) {
341
+ low_val = INT2FIX(0);
342
+ high_val = arg1;
343
+ is_default = 0;
344
+ }
345
+
346
+ rb_ca_modify(self);
347
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
348
+
349
+ if (ca->data_type == CA_OBJECT || ca->data_type == CA_FIXLEN) {
350
+ rb_raise(rb_eCADataTypeError,
351
+ "random! is not supported for object/fixlen arrays");
352
+ }
353
+
354
+ /* Materialize (low, high) as the correct scalar type and validate.
355
+ * For integer data types, `..` closed adds 1 to high (turns into half-open
356
+ * for the sampler); for float data types, closed and half-open are
357
+ * equivalent so no adjustment. */
358
+ if (!is_default) {
359
+ int is_integer_type = (ca->data_type >= CA_INT8
360
+ && ca->data_type <= CA_UINT64);
361
+ if (is_integer_type) {
362
+ low_long = NUM2LONG(low_val);
363
+ high_long = NUM2LONG(high_val);
364
+ if (high_is_closed) high_long += 1;
365
+ if (low_long >= high_long) {
366
+ rb_raise(rb_eArgError,
367
+ "random: low must be less than high "
368
+ "(got low=%ld, high=%ld)",
369
+ low_long, high_long);
370
+ }
371
+ limit = (unsigned long)(high_long - low_long) - 1;
372
+ } else {
373
+ low_dbl = NUM2DBL(low_val);
374
+ high_dbl = NUM2DBL(high_val);
375
+ if (low_dbl >= high_dbl) {
376
+ rb_raise(rb_eArgError,
377
+ "random: low must be less than high "
378
+ "(got low=%g, high=%g)",
379
+ low_dbl, high_dbl);
380
+ }
381
+ }
382
+ }
383
+
384
+ {
385
+ ca_random_ctx_t c;
386
+ c.ca = ca;
387
+ c.n = ca->elements;
388
+ c.is_default = is_default;
389
+ c.low_dbl = low_dbl;
390
+ c.high_dbl = high_dbl;
391
+ c.low_long = low_long;
392
+ c.limit = limit;
393
+ ca_random_run(&c, rng, ca_random_bang_fill);
394
+ }
272
395
  return self;
273
396
  }
274
397
 
275
398
  /* ---- randomn! ---------------------------------------------------------- */
276
399
 
400
+ /* One standard normal from the resolved source.
401
+ *
402
+ * For CArray::Rng this is two draws and no spare, which is what
403
+ * ca_rng_normal is and what lets a kernel's `randomn` continue a
404
+ * `randomn!` -- both are this function, called once per cell.
405
+ *
406
+ * For Ruby's MT the paired form below is kept: nothing there draws one
407
+ * at a time, so there is no second caller to agree with, and changing it
408
+ * would change the numbers an existing call gets. */
409
+ static inline double
410
+ ca_random_normal (ca_rng_t *source)
411
+ {
412
+ double u1 = ca_random_real(source);
413
+ double u2 = ca_random_real(source);
414
+ return ca_rng_normal(u1, u2);
415
+ }
416
+
277
417
  static inline void
278
- box_muller_pair(VALUE rng, double *r1, double *r2)
418
+ box_muller_pair(ca_rng_t *source, double *r1, double *r2)
279
419
  {
280
- double u1 = ca_random_real(rng);
281
- double u2 = ca_random_real(rng);
420
+ double u1 = ca_random_real(source);
421
+ double u2 = ca_random_real(source);
282
422
  while (u1 == 0.0)
283
- u1 = ca_random_real(rng);
423
+ u1 = ca_random_real(source);
284
424
  double r = sqrt(-2.0 * log(u1));
285
425
  double theta = 2.0 * M_PI * u2;
286
426
  *r1 = r * cos(theta);
287
427
  *r2 = r * sin(theta);
288
428
  }
289
429
 
290
- /* CArray#randomn!(rng:) — fill self with standard normal N(0, 1)
291
- * samples in-place via Box-Muller, returning self.
292
- *
293
- * Restricted to float / complex data types. Complex fills real + imag as
294
- * two independent normals per cell. */
295
430
  static VALUE
296
- rb_ca_randomn_bang(int argc, VALUE *argv, VALUE self)
431
+ ca_randomn_bang_fill (VALUE arg)
297
432
  {
298
- CArray *ca;
299
- VALUE opts = Qnil;
300
- ca_size_t i, n;
301
-
302
- rb_scan_args(argc, argv, "0:", &opts);
303
- VALUE rng = Qnil;
304
- rb_scan_options(opts, "rng", &rng);
305
-
306
- rb_ca_modify(self);
307
- TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
308
-
309
- if (ca->data_type != CA_FLOAT64 && ca->data_type != CA_FLOAT32 &&
310
- ca->data_type != CA_CMPLX128 && ca->data_type != CA_CMPLX64) {
311
- rb_raise(rb_eCADataTypeError,
312
- "randomn! requires float or complex array");
433
+ ca_random_ctx_t *c = (ca_random_ctx_t *) arg;
434
+ CArray *ca = c->ca;
435
+ ca_size_t i, n = c->n;
436
+
437
+ ca_rng_bind(&c->source);
438
+ /* A CArray::Rng fills one cell per call rather than two, so that this
439
+ and a kernel drawing afterwards are the one sequence: both are
440
+ `ca_random_normal` run once per cell. It costs a draw a cell over
441
+ the paired form, and what it buys is that where the generator
442
+ stands can be worked out rather than run. */
443
+ if (c->source.kind == CA_RNG_OWN) {
444
+ switch (ca->data_type) {
445
+ case CA_FLOAT64: {
446
+ double *p = (double *)ca->ptr;
447
+ for (i = 0; i < n; i++)
448
+ p[i] = ca_random_normal(&c->source);
449
+ break;
450
+ }
451
+ case CA_FLOAT32: {
452
+ float *p = (float *)ca->ptr;
453
+ for (i = 0; i < n; i++)
454
+ p[i] = (float) ca_random_normal(&c->source);
455
+ break;
456
+ }
457
+ case CA_CMPLX128: {
458
+ double complex *p = (double complex *)ca->ptr;
459
+ for (i = 0; i < n; i++) {
460
+ /* Into locals first: C does not say which order a call's
461
+ arguments are evaluated in, and these two advance a state. */
462
+ double re = ca_random_normal(&c->source);
463
+ double im = ca_random_normal(&c->source);
464
+ p[i] = re + im * I;
465
+ }
466
+ break;
467
+ }
468
+ case CA_CMPLX64: {
469
+ float complex *p = (float complex *)ca->ptr;
470
+ for (i = 0; i < n; i++) {
471
+ double re = ca_random_normal(&c->source);
472
+ double im = ca_random_normal(&c->source);
473
+ p[i] = (float) re + (float) im * I;
474
+ }
475
+ break;
476
+ }
477
+ default:
478
+ break;
479
+ }
480
+ return Qnil;
313
481
  }
314
482
 
315
- n = ca->elements;
316
- ca_attach(ca);
317
-
318
483
  switch (ca->data_type) {
319
484
  case CA_FLOAT64: {
320
485
  double *p = (double *)ca->ptr;
321
486
  ca_size_t pairs = n / 2;
322
487
  for (i = 0; i < pairs; i++) {
323
- box_muller_pair(rng, &p[2*i], &p[2*i+1]);
488
+ box_muller_pair(&c->source, &p[2*i], &p[2*i+1]);
324
489
  }
325
490
  if (n % 2 == 1) {
326
491
  double r1, r2;
327
- box_muller_pair(rng, &r1, &r2);
492
+ box_muller_pair(&c->source, &r1, &r2);
328
493
  p[n-1] = r1;
329
494
  }
330
495
  break;
@@ -334,13 +499,13 @@ rb_ca_randomn_bang(int argc, VALUE *argv, VALUE self)
334
499
  ca_size_t pairs = n / 2;
335
500
  for (i = 0; i < pairs; i++) {
336
501
  double r1, r2;
337
- box_muller_pair(rng, &r1, &r2);
502
+ box_muller_pair(&c->source, &r1, &r2);
338
503
  p[2*i] = (float)r1;
339
504
  p[2*i+1] = (float)r2;
340
505
  }
341
506
  if (n % 2 == 1) {
342
507
  double r1, r2;
343
- box_muller_pair(rng, &r1, &r2);
508
+ box_muller_pair(&c->source, &r1, &r2);
344
509
  p[n-1] = (float)r1;
345
510
  }
346
511
  break;
@@ -349,7 +514,7 @@ rb_ca_randomn_bang(int argc, VALUE *argv, VALUE self)
349
514
  double complex *p = (double complex *)ca->ptr;
350
515
  for (i = 0; i < n; i++) {
351
516
  double r1, r2;
352
- box_muller_pair(rng, &r1, &r2);
517
+ box_muller_pair(&c->source, &r1, &r2);
353
518
  p[i] = r1 + r2 * I;
354
519
  }
355
520
  break;
@@ -358,7 +523,7 @@ rb_ca_randomn_bang(int argc, VALUE *argv, VALUE self)
358
523
  float complex *p = (float complex *)ca->ptr;
359
524
  for (i = 0; i < n; i++) {
360
525
  double r1, r2;
361
- box_muller_pair(rng, &r1, &r2);
526
+ box_muller_pair(&c->source, &r1, &r2);
362
527
  p[i] = (float)r1 + (float)r2 * I;
363
528
  }
364
529
  break;
@@ -367,8 +532,39 @@ rb_ca_randomn_bang(int argc, VALUE *argv, VALUE self)
367
532
  break;
368
533
  }
369
534
 
370
- ca_sync(ca);
371
- ca_detach(ca);
535
+ return Qnil;
536
+ }
537
+
538
+ /* CArray#randomn!(rng:) — fill self with standard normal N(0, 1)
539
+ * samples in-place via Box-Muller, returning self.
540
+ *
541
+ * Restricted to float / complex data types. Complex fills real + imag as
542
+ * two independent normals per cell. */
543
+ static VALUE
544
+ rb_ca_randomn_bang(int argc, VALUE *argv, VALUE self)
545
+ {
546
+ CArray *ca;
547
+ VALUE opts = Qnil;
548
+
549
+ rb_scan_args(argc, argv, "0:", &opts);
550
+ VALUE rng = Qnil;
551
+ rb_scan_options(opts, "rng", &rng);
552
+
553
+ rb_ca_modify(self);
554
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
555
+
556
+ if (ca->data_type != CA_FLOAT64 && ca->data_type != CA_FLOAT32 &&
557
+ ca->data_type != CA_CMPLX128 && ca->data_type != CA_CMPLX64) {
558
+ rb_raise(rb_eCADataTypeError,
559
+ "randomn! requires float or complex array");
560
+ }
561
+
562
+ {
563
+ ca_random_ctx_t c;
564
+ c.ca = ca;
565
+ c.n = ca->elements;
566
+ ca_random_run(&c, rng, ca_randomn_bang_fill);
567
+ }
372
568
  return self;
373
569
  }
374
570
 
@@ -390,55 +586,35 @@ swap_chunks(char *a, char *b, ca_size_t chunk_bytes, char *tmp)
390
586
  * as a byte chunk and swapped whole, so multi-dim slices move
391
587
  * together). */
392
588
  static VALUE
393
- rb_ca_shuffle_bang(int argc, VALUE *argv, VALUE self)
589
+ ca_shuffle_bang_fill (VALUE arg)
394
590
  {
395
- CArray *ca;
396
- VALUE opts = Qnil, v_axis = Qnil;
591
+ ca_random_ctx_t *c = (ca_random_ctx_t *) arg;
592
+ CArray *ca = c->ca;
397
593
  ca_size_t n;
398
- int axis = -1;
399
-
400
- rb_scan_args(argc, argv, "0:", &opts);
401
- VALUE rng = Qnil;
402
- rb_scan_options(opts, "rng,axis", &rng, &v_axis);
403
594
 
404
- rb_ca_modify(self);
405
- TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
406
-
407
- if (ca->elements <= 1) return self;
408
-
409
- ca_attach(ca);
595
+ ca_rng_bind(&c->source);
410
596
 
411
- if (NIL_P(v_axis)) {
597
+ if (c->axis < 0) {
412
598
  /* shuffle all elements */
413
- n = ca->elements;
599
+ volatile VALUE tmp_holder;
414
600
  ca_size_t elem_bytes = ca->bytes;
415
- char *tmp = (char *)xmalloc(elem_bytes);
601
+ char *tmp = ALLOCV_N(char, tmp_holder, elem_bytes);
416
602
  char *p = ca->ptr;
417
603
 
604
+ n = ca->elements;
418
605
  for (ca_size_t i = n - 1; i > 0; i--) {
419
- unsigned long j = ca_random_ulong_limited(rng, (unsigned long)i);
606
+ unsigned long j = ca_random_ulong_limited(&c->source, (unsigned long)i);
420
607
  if ((ca_size_t)j != i) {
421
608
  swap_chunks(p + i * elem_bytes, p + j * elem_bytes, elem_bytes, tmp);
422
609
  }
423
610
  }
424
- xfree(tmp);
611
+ ALLOCV_END(tmp_holder);
425
612
  }
426
613
  else {
427
- axis = NUM2INT(v_axis);
428
- if (axis < 0) axis += ca->ndim;
429
- if (axis < 0 || axis >= ca->ndim) {
430
- ca_sync(ca);
431
- ca_detach(ca);
432
- rb_raise(rb_eArgError,
433
- "axis %d is out of range for ndim %d", axis, ca->ndim);
434
- }
614
+ int axis = c->axis;
615
+ volatile VALUE tmp_holder;
435
616
 
436
617
  n = ca->dim[axis];
437
- if (n <= 1) {
438
- ca_sync(ca);
439
- ca_detach(ca);
440
- return self;
441
- }
442
618
 
443
619
  ca_size_t outer = 1;
444
620
  for (int d = 0; d < axis; d++)
@@ -450,12 +626,12 @@ rb_ca_shuffle_bang(int argc, VALUE *argv, VALUE self)
450
626
 
451
627
  ca_size_t chunk_bytes = inner * ca->bytes;
452
628
  ca_size_t stride = n * chunk_bytes;
453
- char *tmp = (char *)xmalloc(chunk_bytes);
629
+ char *tmp = ALLOCV_N(char, tmp_holder, chunk_bytes);
454
630
 
455
631
  for (ca_size_t o = 0; o < outer; o++) {
456
632
  char *base = ca->ptr + o * stride;
457
633
  for (ca_size_t i = n - 1; i > 0; i--) {
458
- unsigned long j = ca_random_ulong_limited(rng, (unsigned long)i);
634
+ unsigned long j = ca_random_ulong_limited(&c->source, (unsigned long)i);
459
635
  if ((ca_size_t)j != i) {
460
636
  swap_chunks(base + i * chunk_bytes,
461
637
  base + j * chunk_bytes,
@@ -463,11 +639,38 @@ rb_ca_shuffle_bang(int argc, VALUE *argv, VALUE self)
463
639
  }
464
640
  }
465
641
  }
466
- xfree(tmp);
642
+ ALLOCV_END(tmp_holder);
643
+ }
644
+ return Qnil;
645
+ }
646
+
647
+ static VALUE
648
+ rb_ca_shuffle_bang(int argc, VALUE *argv, VALUE self)
649
+ {
650
+ CArray *ca;
651
+ VALUE opts = Qnil, v_axis = Qnil;
652
+ int axis = -1;
653
+ ca_random_ctx_t c;
654
+
655
+ rb_scan_args(argc, argv, "0:", &opts);
656
+ VALUE rng = Qnil;
657
+ rb_scan_options(opts, "rng,axis", &rng, &v_axis);
658
+
659
+ rb_ca_modify(self);
660
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
661
+
662
+ if (ca->elements <= 1) return self;
663
+
664
+ if (!NIL_P(v_axis)) {
665
+ axis = rb_ca_normalize_axis_for_ndim(ca_axis_integer(v_axis, NULL), ca->ndim, NULL);
666
+ if (ca->dim[axis] <= 1) {
667
+ return self;
668
+ }
467
669
  }
468
670
 
469
- ca_sync(ca);
470
- ca_detach(ca);
671
+ c.ca = ca;
672
+ c.axis = axis;
673
+ ca_random_run(&c, rng, ca_shuffle_bang_fill);
471
674
  return self;
472
675
  }
473
676
 
@@ -503,11 +706,186 @@ rb_ca_randomn(int argc, VALUE *argv, VALUE self)
503
706
  return rb_ca_randomn_bang(argc, argv, copy);
504
707
  }
505
708
 
709
+ /* ---- CArray::Rng ---------------------------------------------------- */
710
+
711
+ /* A generator with its own state, so that a sequence belongs to an object
712
+ * rather than to the process:
713
+ *
714
+ * r = CArray::Rng.new(seed: 4)
715
+ * a.random!(rng: r) # fills, advancing r
716
+ * CArray.jit_for(n) { |i| b[i] = r.call } # carries on from there
717
+ *
718
+ * The state is an ordinary CA_INT64 array of four cells, which is what
719
+ * lets the second line work: carray-jit hands that array's address to a
720
+ * kernel that pasted the same generator, and the kernel advances the
721
+ * same cells this file does. Nothing about the generator is hidden
722
+ * behind a struct only this extension can read.
723
+ *
724
+ * The seed is not part of the state. It is remembered so that #reset
725
+ * with no argument can repeat a run, and so `inspect` can say what a
726
+ * generator was started from. */
727
+
728
+ static VALUE rb_ca_rng_reset (int argc, VALUE *argv, VALUE self);
729
+
730
+ /* The state's cells, attached. Every entry point here goes through this
731
+ * rather than reaching into the ivar, so that "what is the state" has one
732
+ * answer even after another generator is added. */
733
+ static CArray *
734
+ ca_rng_cells (VALUE self, int64_t **cells)
735
+ {
736
+ CArray *ca;
737
+ VALUE state = rb_ivar_get(self, rb_intern("@state"));
738
+ TypedData_Get_Struct(state, CArray, &carray_data_type, ca);
739
+ ca_attach(ca); /* window: the state is an entity, whose attach holds nothing */
740
+ *cells = (int64_t *) ca->ptr;
741
+ return ca;
742
+ }
743
+
744
+ /* CArray::Rng.new(generator = :xoshiro256pp, seed: nil) */
745
+ static VALUE
746
+ rb_ca_rng_initialize (int argc, VALUE *argv, VALUE self)
747
+ {
748
+ VALUE gen = Qnil, opts = Qnil, seed = Qnil, state;
749
+
750
+ rb_scan_args(argc, argv, "01:", &gen, &opts);
751
+ rb_scan_options(opts, "seed", &seed);
752
+
753
+ if (NIL_P(gen)) gen = ID2SYM(rb_intern("xoshiro256pp"));
754
+ if (!SYMBOL_P(gen) || SYM2ID(gen) != rb_intern("xoshiro256pp")) {
755
+ rb_raise(rb_eArgError,
756
+ "unknown generator %"PRIsVALUE"; carray has :xoshiro256pp",
757
+ rb_inspect(gen));
758
+ }
759
+
760
+ state = rb_funcall(rb_cCArray, rb_intern("int64"), 1, INT2FIX(4));
761
+ rb_ivar_set(self, rb_intern("@generator"), gen);
762
+ rb_ivar_set(self, rb_intern("@state"), state);
763
+
764
+ return rb_ca_rng_reset(NIL_P(seed) ? 0 : 1, &seed, self);
765
+ }
766
+
767
+ /* CArray::Rng#reset(seed = nil) — start the sequence over.
768
+ *
769
+ * With no argument, from the seed this generator already carries, which
770
+ * repeats the run exactly. A generator made without a seed is given one
771
+ * from Random.new_seed, so two of them differ; that drawn seed is kept,
772
+ * so even an unseeded run can be repeated once it has begun. */
773
+ static VALUE
774
+ rb_ca_rng_reset (int argc, VALUE *argv, VALUE self)
775
+ {
776
+ VALUE seed = Qnil, masked;
777
+ CArray *ca;
778
+ int64_t *cells;
779
+
780
+ rb_scan_args(argc, argv, "01", &seed);
781
+ if (NIL_P(seed)) seed = rb_ivar_get(self, rb_intern("@seed"));
782
+ if (NIL_P(seed)) {
783
+ seed = rb_funcall(rb_path2class("Random"), rb_intern("new_seed"), 0);
784
+ }
785
+
786
+ /* Any Integer is a seed: a negative one and one wider than a word are
787
+ folded into 64 bits rather than refused, which is what `&` does. */
788
+ masked = rb_funcall(rb_to_int(seed), rb_intern("&"),
789
+ 1, ULL2NUM(0xFFFFFFFFFFFFFFFFULL));
790
+
791
+ ca = ca_rng_cells(self, &cells);
792
+ ca_xoshiro256pp_seed(cells, (uint64_t) NUM2ULL(masked));
793
+ ca_sync_detach(ca);
794
+
795
+ rb_ivar_set(self, rb_intern("@seed"), seed);
796
+ return self;
797
+ }
798
+
799
+ /* CArray::Rng#random — one draw in [0.0, 1.0), the state advanced.
800
+ *
801
+ * Named for `CArray#random!`, which it is the one-cell form of, and to
802
+ * pair with `#randomn` the way those two pair. Not `#rand`: Ruby's
803
+ * `random:` keyword calls `rand(n)` on what it is given, and a `#rand`
804
+ * here that takes no argument would make this look usable there and
805
+ * then fail with an arity error rather than say what it is.
806
+ *
807
+ * It is the same draw `random!` takes for one cell, and the same one a
808
+ * kernel's `random(rng:)` takes, because all three run the code in
809
+ * ca_rng_xoshiro256pp.h. */
810
+ static VALUE
811
+ rb_ca_rng_random (VALUE self)
812
+ {
813
+ CArray *ca;
814
+ int64_t *cells;
815
+ double value;
816
+
817
+ ca = ca_rng_cells(self, &cells);
818
+ value = ca_xoshiro256pp_next_real(cells);
819
+ ca_sync_detach(ca);
820
+ return rb_float_new(value);
821
+ }
822
+
823
+ /* CArray::Rng#bits — the same draw as the generator's raw 64 bits.
824
+ *
825
+ * Not a bounded draw and not Ruby's `rand(n)`: it is the word the
826
+ * generator produced, before it was turned into a double. What that is
827
+ * for is checking this generator against the sequence its authors
828
+ * published, which is the one question `#rand` cannot answer. */
829
+ static VALUE
830
+ rb_ca_rng_bits (VALUE self)
831
+ {
832
+ CArray *ca;
833
+ int64_t *cells;
834
+ uint64_t value;
835
+
836
+ ca = ca_rng_cells(self, &cells);
837
+ value = ca_xoshiro256pp_next(cells);
838
+ ca_sync_detach(ca);
839
+ return ULL2NUM(value);
840
+ }
841
+
842
+ /* CArray::Rng#randomn — one standard normal, which is two draws.
843
+ *
844
+ * The same one `randomn!(rng: self)` writes into a cell and the same
845
+ * one a kernel's `randomn(rng: self)` takes, because all three are
846
+ * ca_random_normal. */
847
+ static VALUE
848
+ rb_ca_rng_randomn (VALUE self)
849
+ {
850
+ CArray *ca;
851
+ int64_t *cells;
852
+ ca_rng_t source;
853
+ double value;
854
+
855
+ ca = ca_rng_cells(self, &cells);
856
+ source.kind = CA_RNG_OWN;
857
+ source.rng = self;
858
+ source.state = NULL;
859
+ source.cells = cells;
860
+ value = ca_random_normal(&source);
861
+ ca_sync_detach(ca);
862
+ return rb_float_new(value);
863
+ }
864
+
865
+ static VALUE
866
+ rb_ca_rng_inspect (VALUE self)
867
+ {
868
+ return rb_sprintf("#<CArray::Rng %"PRIsVALUE" seed=%"PRIsVALUE">",
869
+ rb_ivar_get(self, rb_intern("@generator")),
870
+ rb_ivar_get(self, rb_intern("@seed")));
871
+ }
872
+
506
873
  /* ---- Init -------------------------------------------------------------- */
507
874
 
508
875
  void
509
876
  Init_carray_random (void)
510
877
  {
878
+ rb_cCARng = rb_define_class_under(rb_cCArray, "Rng", rb_cObject);
879
+ rb_define_method(rb_cCARng, "initialize", rb_ca_rng_initialize, -1);
880
+ rb_define_method(rb_cCARng, "reset", rb_ca_rng_reset, -1);
881
+ rb_define_method(rb_cCARng, "random", rb_ca_rng_random, 0);
882
+ rb_define_method(rb_cCARng, "randomn", rb_ca_rng_randomn, 0);
883
+ rb_define_method(rb_cCARng, "bits", rb_ca_rng_bits, 0);
884
+ rb_define_method(rb_cCARng, "inspect", rb_ca_rng_inspect, 0);
885
+ rb_define_attr(rb_cCARng, "generator", 1, 0);
886
+ rb_define_attr(rb_cCARng, "state", 1, 0);
887
+ rb_define_attr(rb_cCARng, "seed", 1, 0);
888
+
511
889
  rb_define_method(rb_cCArray, "random!", rb_ca_random_bang, -1);
512
890
  rb_define_method(rb_cCArray, "randomn!", rb_ca_randomn_bang, -1);
513
891
  rb_define_method(rb_cCArray, "shuffle!", rb_ca_shuffle_bang, -1);