carray 3.0.1 → 3.0.2

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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +520 -0
  3. data/README.md +2 -2
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +33 -4
  6. data/ext/ca_axis_group.c +202 -96
  7. data/ext/ca_categorical_iterator.c +108 -54
  8. data/ext/ca_kernel_iterator.c +317 -51
  9. data/ext/ca_kernel_iterator.h +142 -35
  10. data/ext/ca_obj_array.c +62 -20
  11. data/ext/ca_obj_block.c +4 -4
  12. data/ext/ca_obj_const_string.c +85 -26
  13. data/ext/ca_obj_face.c +24 -0
  14. data/ext/ca_obj_face.h +15 -0
  15. data/ext/ca_obj_fixlen_string.c +18 -5
  16. data/ext/ca_obj_meld.c +123 -25
  17. data/ext/ca_obj_object.c +8 -0
  18. data/ext/ca_obj_select.c +49 -34
  19. data/ext/ca_obj_stack.c +3 -8
  20. data/ext/ca_obj_stride.c +72 -1
  21. data/ext/ca_obj_string.c +8 -4
  22. data/ext/ca_obj_window.c +8 -2
  23. data/ext/ca_op_ipower.c +1 -2
  24. data/ext/ca_rng_normal.h +42 -0
  25. data/ext/ca_rng_xoshiro256pp.h +105 -0
  26. data/ext/ca_sweep_engine.c +307 -143
  27. data/ext/ca_sweep_engine.h +26 -5
  28. data/ext/carray.h +21 -2
  29. data/ext/carray_access.c +32 -20
  30. data/ext/carray_address_basis.c +590 -0
  31. data/ext/carray_broadcast.c +3 -3
  32. data/ext/carray_call_cfunc.c +667 -483
  33. data/ext/carray_cast.c +115 -41
  34. data/ext/carray_copy.c +55 -30
  35. data/ext/carray_core.c +83 -3
  36. data/ext/carray_count.c +9 -10
  37. data/ext/carray_factorize.c +46 -25
  38. data/ext/carray_internal.h +17 -0
  39. data/ext/carray_kernels_reduce_aggregate.c +168 -0
  40. data/ext/carray_kernels_reduce_cumulative.c +270 -1
  41. data/ext/carray_kernels_reduce_extreme.c +554 -8
  42. data/ext/carray_kernels_scan.c +4 -4
  43. data/ext/carray_kernels_search.c +94 -14
  44. data/ext/carray_loop.c +7 -1
  45. data/ext/carray_mask.c +23 -8
  46. data/ext/carray_median_percentile.c +55 -0
  47. data/ext/carray_operator.c +4 -4
  48. data/ext/carray_order.c +1 -1
  49. data/ext/carray_random.c +384 -40
  50. data/ext/carray_slab.c +13 -0
  51. data/ext/carray_sort.c +20 -22
  52. data/ext/mk_call_cfunc.rb +103 -116
  53. data/ext/mkkernel.rb +297 -29
  54. data/ext/ruby_carray.c +10 -1
  55. data/ext/version.h +4 -4
  56. data/lib/carray/autoload_carray.rb +5 -3
  57. data/lib/carray/autoload_method_extension.rb +12 -0
  58. data/lib/carray/axis_group.rb +77 -0
  59. data/lib/carray/basics.rb +4 -0
  60. data/lib/carray/block_iterator.rb +92 -16
  61. data/lib/carray/categorical.rb +150 -33
  62. data/lib/carray/categorical_iterator.rb +207 -80
  63. data/lib/carray/const_string.rb +131 -27
  64. data/lib/carray/construct.rb +40 -0
  65. data/lib/carray/data_type_extension.rb +3 -0
  66. data/lib/carray/data_type_limits.rb +91 -0
  67. data/lib/carray/fixlen_string.rb +1 -1
  68. data/lib/carray/frame/csv_parser.rb +11 -4
  69. data/lib/carray/frame/frame.rb +81 -10
  70. data/lib/carray/frame/group.rb +36 -3
  71. data/lib/carray/frame/io.rb +67 -15
  72. data/lib/carray/frame/records.rb +18 -4
  73. data/lib/carray/frame/verbs.rb +14 -11
  74. data/lib/carray/inspect.rb +42 -9
  75. data/lib/carray/iterator.rb +143 -0
  76. data/lib/carray/lazy.rb +0 -37
  77. data/lib/carray/mask_gap_fill.rb +3 -1
  78. data/lib/carray/methods/discovery_along.rb +74 -0
  79. data/lib/carray/methods/factorize.rb +50 -0
  80. data/lib/carray/methods/is_in.rb +13 -2
  81. data/lib/carray/methods/locate_addr.rb +75 -2
  82. data/lib/carray/methods/mask_duplicates.rb +35 -1
  83. data/lib/carray/methods/nunique.rb +22 -1
  84. data/lib/carray/methods/repeat.rb +110 -0
  85. data/lib/carray/methods/unique.rb +41 -1
  86. data/lib/carray/rng.rb +86 -0
  87. data/lib/carray/slab_iterator.rb +58 -13
  88. data/lib/carray/string_operation_extension.rb +5 -1
  89. data/lib/carray/time.rb +18 -2
  90. data/lib/carray/window_iterator.rb +142 -20
  91. data/lib/carray.rb +2 -0
  92. data/yard-stubs/ca_obj_block.rb +2 -7
  93. data/yard-stubs/ca_obj_window.rb +10 -2
  94. data/yard-stubs/carray_access.rb +1 -1
  95. data/yard-stubs/carray_broadcast.rb +1 -1
  96. data/yard-stubs/carray_core.rb +0 -80
  97. data/yard-stubs/carray_count.rb +7 -2
  98. data/yard-stubs/carray_lazy.rb +205 -0
  99. data/yard-stubs/carray_math.rb +1486 -3
  100. data/yard-stubs/carray_median_percentile.rb +16 -2
  101. data/yard-stubs/carray_order.rb +9 -69
  102. data/yard-stubs/carray_slab.rb +9 -7
  103. data/yard-stubs/carray_sort.rb +7 -5
  104. metadata +9 -1
data/ext/carray_random.c CHANGED
@@ -20,24 +20,111 @@
20
20
  #include "carray.h"
21
21
  #include <math.h>
22
22
  #include <string.h>
23
+ #include <stdint.h>
24
+
25
+ /* The generator itself, shared verbatim with carray-jit. See the file's
26
+ own comment for why it carries no include guard. */
27
+ #include "ca_rng_normal.h"
28
+ #include "ca_rng_xoshiro256pp.h"
29
+
30
+ VALUE rb_cCARng;
31
+
32
+ /* Where a fill's numbers come from. Resolved once per call rather than
33
+ per cell: the `rng:` argument is one object for the whole array, and
34
+ asking what it is inside the loop would put a Ruby type test between
35
+ every pair of draws.
36
+
37
+ CA_RNG_OWN holds the state's cells directly. That array belongs to
38
+ the CArray::Rng the caller passed, so the draws advance it and the
39
+ next call -- here or in a kernel -- carries on from where this one
40
+ stopped. */
41
+ enum {
42
+ CA_RNG_DEFAULT = 0, /* no rng: given -- the per-ractor MT */
43
+ CA_RNG_RUBY = 1, /* a ::Random instance -- also MT */
44
+ CA_RNG_OWN = 2 /* a CArray::Rng -- the generator above */
45
+ };
46
+
47
+ typedef struct {
48
+ int kind;
49
+ VALUE rng;
50
+ CArray *state; /* attached for CA_RNG_OWN, else NULL */
51
+ int64_t *cells;
52
+ } ca_rng_t;
23
53
 
54
+ static void
55
+ ca_rng_open (VALUE rng, ca_rng_t *source)
56
+ {
57
+ source->rng = rng;
58
+ source->state = NULL;
59
+ source->cells = NULL;
60
+ if (NIL_P(rng)) {
61
+ source->kind = CA_RNG_DEFAULT;
62
+ }
63
+ else if (rb_obj_is_kind_of(rng, rb_cCARng)) {
64
+ VALUE state = rb_ivar_get(rng, rb_intern("@state"));
65
+ CArray *ca;
66
+ TypedData_Get_Struct(state, CArray, &carray_data_type, ca);
67
+ ca_attach(ca);
68
+ source->kind = CA_RNG_OWN;
69
+ source->state = ca;
70
+ source->cells = (int64_t *) ca->ptr;
71
+ }
72
+ else {
73
+ source->kind = CA_RNG_RUBY;
74
+ }
75
+ }
76
+
77
+ /* Writes the advanced state back where it came from. Safe to call twice,
78
+ which is what lets an error path close before it raises. */
79
+ static void
80
+ ca_rng_close (ca_rng_t *source)
81
+ {
82
+ if (source->state) {
83
+ ca_sync(source->state);
84
+ ca_detach(source->state);
85
+ source->state = NULL;
86
+ source->cells = NULL;
87
+ }
88
+ }
24
89
 
25
90
  static inline double
26
- ca_random_real(VALUE rng)
91
+ ca_random_real (ca_rng_t *source)
27
92
  {
28
- if (NIL_P(rng))
29
- return rb_genrand_real();
30
- else
31
- return rb_random_real(rng);
93
+ switch (source->kind) {
94
+ case CA_RNG_OWN: return ca_xoshiro256pp_next_real(source->cells);
95
+ case CA_RNG_RUBY: return rb_random_real(source->rng);
96
+ default: return rb_genrand_real();
97
+ }
98
+ }
99
+
100
+ /* A draw in [0, limit], inclusive, matching what Ruby's own bounded draw
101
+ promises. For the generator above that is rejection sampling: taking
102
+ the remainder alone would favour the low end of the range whenever the
103
+ range does not divide 2**64. */
104
+ static inline uint64_t
105
+ ca_xoshiro256pp_below (int64_t *state, uint64_t range)
106
+ {
107
+ uint64_t threshold, draw;
108
+ if (range == 0) return ca_xoshiro256pp_next(state); /* the whole word */
109
+ threshold = (0 - range) % range; /* 2**64 mod range */
110
+ do {
111
+ draw = ca_xoshiro256pp_next(state);
112
+ } while (draw < threshold);
113
+ return draw % range;
32
114
  }
33
115
 
34
116
  static inline unsigned long
35
- ca_random_ulong_limited(VALUE rng, unsigned long limit)
117
+ ca_random_ulong_limited (ca_rng_t *source, unsigned long limit)
36
118
  {
37
- if (NIL_P(rng))
119
+ switch (source->kind) {
120
+ case CA_RNG_OWN:
121
+ return (unsigned long)
122
+ ca_xoshiro256pp_below(source->cells, (uint64_t) limit + 1);
123
+ case CA_RNG_RUBY:
124
+ return rb_random_ulong_limited(source->rng, limit);
125
+ default:
38
126
  return rb_genrand_ulong_limited(limit);
39
- else
40
- return rb_random_ulong_limited(rng, limit);
127
+ }
41
128
  }
42
129
 
43
130
  /* ---- random! ----------------------------------------------------------- */
@@ -72,6 +159,7 @@ rb_ca_random_bang(int argc, VALUE *argv, VALUE self)
72
159
  unsigned long limit = 0;
73
160
  ca_size_t i, n;
74
161
  VALUE rng = Qnil;
162
+ ca_rng_t source;
75
163
 
76
164
  rb_scan_args(argc, argv, "02:", &arg1, &arg2, &opts);
77
165
  rb_scan_options(opts, "rng", &rng);
@@ -140,17 +228,18 @@ rb_ca_random_bang(int argc, VALUE *argv, VALUE self)
140
228
 
141
229
  n = ca->elements;
142
230
  ca_attach(ca);
231
+ ca_rng_open(rng, &source);
143
232
 
144
233
  switch (ca->data_type) {
145
234
  case CA_FLOAT64: {
146
235
  double *p = (double *)ca->ptr;
147
236
  if (is_default) {
148
237
  for (i = 0; i < n; i++)
149
- p[i] = ca_random_real(rng);
238
+ p[i] = ca_random_real(&source);
150
239
  } else {
151
240
  double range = high_dbl - low_dbl;
152
241
  for (i = 0; i < n; i++)
153
- p[i] = low_dbl + ca_random_real(rng) * range;
242
+ p[i] = low_dbl + ca_random_real(&source) * range;
154
243
  }
155
244
  break;
156
245
  }
@@ -158,11 +247,11 @@ rb_ca_random_bang(int argc, VALUE *argv, VALUE self)
158
247
  float *p = (float *)ca->ptr;
159
248
  if (is_default) {
160
249
  for (i = 0; i < n; i++)
161
- p[i] = (float)ca_random_real(rng);
250
+ p[i] = (float)ca_random_real(&source);
162
251
  } else {
163
252
  double range = high_dbl - low_dbl;
164
253
  for (i = 0; i < n; i++)
165
- p[i] = (float)(low_dbl + ca_random_real(rng) * range);
254
+ p[i] = (float)(low_dbl + ca_random_real(&source) * range);
166
255
  }
167
256
  break;
168
257
  }
@@ -170,12 +259,12 @@ rb_ca_random_bang(int argc, VALUE *argv, VALUE self)
170
259
  double complex *p = (double complex *)ca->ptr;
171
260
  if (is_default) {
172
261
  for (i = 0; i < n; i++)
173
- p[i] = ca_random_real(rng) + ca_random_real(rng) * I;
262
+ p[i] = ca_random_real(&source) + ca_random_real(&source) * I;
174
263
  } else {
175
264
  double range = high_dbl - low_dbl;
176
265
  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;
266
+ p[i] = (low_dbl + ca_random_real(&source) * range)
267
+ + (low_dbl + ca_random_real(&source) * range) * I;
179
268
  }
180
269
  break;
181
270
  }
@@ -183,24 +272,25 @@ rb_ca_random_bang(int argc, VALUE *argv, VALUE self)
183
272
  float complex *p = (float complex *)ca->ptr;
184
273
  if (is_default) {
185
274
  for (i = 0; i < n; i++)
186
- p[i] = (float)ca_random_real(rng) + (float)ca_random_real(rng) * I;
275
+ p[i] = (float)ca_random_real(&source) + (float)ca_random_real(&source) * I;
187
276
  } else {
188
277
  double range = high_dbl - low_dbl;
189
278
  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;
279
+ p[i] = (float)(low_dbl + ca_random_real(&source) * range)
280
+ + (float)(low_dbl + ca_random_real(&source) * range) * I;
192
281
  }
193
282
  break;
194
283
  }
195
284
  case CA_BOOLEAN: {
196
285
  boolean8_t *p = (boolean8_t *)ca->ptr;
197
286
  for (i = 0; i < n; i++)
198
- p[i] = (ca_random_real(rng) < 0.5) ? 1 : 0;
287
+ p[i] = (ca_random_real(&source) < 0.5) ? 1 : 0;
199
288
  break;
200
289
  }
201
290
  default: {
202
291
  /* integer types: CA_INT8..CA_UINT64 */
203
292
  if (is_default) {
293
+ ca_rng_close(&source);
204
294
  ca_sync(ca);
205
295
  ca_detach(ca);
206
296
  rb_raise(rb_eArgError,
@@ -212,52 +302,53 @@ rb_ca_random_bang(int argc, VALUE *argv, VALUE self)
212
302
  case CA_INT8: {
213
303
  int8_t *p = (int8_t *)ca->ptr;
214
304
  for (i = 0; i < n; i++)
215
- p[i] = (int8_t)(low_long + ca_random_ulong_limited(rng, limit));
305
+ p[i] = (int8_t)(low_long + ca_random_ulong_limited(&source, limit));
216
306
  break;
217
307
  }
218
308
  case CA_UINT8: {
219
309
  uint8_t *p = (uint8_t *)ca->ptr;
220
310
  for (i = 0; i < n; i++)
221
- p[i] = (uint8_t)(low_long + ca_random_ulong_limited(rng, limit));
311
+ p[i] = (uint8_t)(low_long + ca_random_ulong_limited(&source, limit));
222
312
  break;
223
313
  }
224
314
  case CA_INT16: {
225
315
  int16_t *p = (int16_t *)ca->ptr;
226
316
  for (i = 0; i < n; i++)
227
- p[i] = (int16_t)(low_long + ca_random_ulong_limited(rng, limit));
317
+ p[i] = (int16_t)(low_long + ca_random_ulong_limited(&source, limit));
228
318
  break;
229
319
  }
230
320
  case CA_UINT16: {
231
321
  uint16_t *p = (uint16_t *)ca->ptr;
232
322
  for (i = 0; i < n; i++)
233
- p[i] = (uint16_t)(low_long + ca_random_ulong_limited(rng, limit));
323
+ p[i] = (uint16_t)(low_long + ca_random_ulong_limited(&source, limit));
234
324
  break;
235
325
  }
236
326
  case CA_INT32: {
237
327
  int32_t *p = (int32_t *)ca->ptr;
238
328
  for (i = 0; i < n; i++)
239
- p[i] = (int32_t)(low_long + ca_random_ulong_limited(rng, limit));
329
+ p[i] = (int32_t)(low_long + ca_random_ulong_limited(&source, limit));
240
330
  break;
241
331
  }
242
332
  case CA_UINT32: {
243
333
  uint32_t *p = (uint32_t *)ca->ptr;
244
334
  for (i = 0; i < n; i++)
245
- p[i] = (uint32_t)(low_long + ca_random_ulong_limited(rng, limit));
335
+ p[i] = (uint32_t)(low_long + ca_random_ulong_limited(&source, limit));
246
336
  break;
247
337
  }
248
338
  case CA_INT64: {
249
339
  int64_t *p = (int64_t *)ca->ptr;
250
340
  for (i = 0; i < n; i++)
251
- p[i] = (int64_t)(low_long + ca_random_ulong_limited(rng, limit));
341
+ p[i] = (int64_t)(low_long + ca_random_ulong_limited(&source, limit));
252
342
  break;
253
343
  }
254
344
  case CA_UINT64: {
255
345
  uint64_t *p = (uint64_t *)ca->ptr;
256
346
  for (i = 0; i < n; i++)
257
- p[i] = (uint64_t)(low_long + ca_random_ulong_limited(rng, limit));
347
+ p[i] = (uint64_t)(low_long + ca_random_ulong_limited(&source, limit));
258
348
  break;
259
349
  }
260
350
  default:
351
+ ca_rng_close(&source);
261
352
  ca_sync(ca);
262
353
  ca_detach(ca);
263
354
  rb_raise(rb_eCADataTypeError,
@@ -267,6 +358,7 @@ rb_ca_random_bang(int argc, VALUE *argv, VALUE self)
267
358
  }
268
359
  }
269
360
 
361
+ ca_rng_close(&source);
270
362
  ca_sync(ca);
271
363
  ca_detach(ca);
272
364
  return self;
@@ -274,13 +366,30 @@ rb_ca_random_bang(int argc, VALUE *argv, VALUE self)
274
366
 
275
367
  /* ---- randomn! ---------------------------------------------------------- */
276
368
 
369
+ /* One standard normal from the resolved source.
370
+ *
371
+ * For CArray::Rng this is two draws and no spare, which is what
372
+ * ca_rng_normal is and what lets a kernel's `randomn` continue a
373
+ * `randomn!` -- both are this function, called once per cell.
374
+ *
375
+ * For Ruby's MT the paired form below is kept: nothing there draws one
376
+ * at a time, so there is no second caller to agree with, and changing it
377
+ * would change the numbers an existing call gets. */
378
+ static inline double
379
+ ca_random_normal (ca_rng_t *source)
380
+ {
381
+ double u1 = ca_random_real(source);
382
+ double u2 = ca_random_real(source);
383
+ return ca_rng_normal(u1, u2);
384
+ }
385
+
277
386
  static inline void
278
- box_muller_pair(VALUE rng, double *r1, double *r2)
387
+ box_muller_pair(ca_rng_t *source, double *r1, double *r2)
279
388
  {
280
- double u1 = ca_random_real(rng);
281
- double u2 = ca_random_real(rng);
389
+ double u1 = ca_random_real(source);
390
+ double u2 = ca_random_real(source);
282
391
  while (u1 == 0.0)
283
- u1 = ca_random_real(rng);
392
+ u1 = ca_random_real(source);
284
393
  double r = sqrt(-2.0 * log(u1));
285
394
  double theta = 2.0 * M_PI * u2;
286
395
  *r1 = r * cos(theta);
@@ -301,6 +410,7 @@ rb_ca_randomn_bang(int argc, VALUE *argv, VALUE self)
301
410
 
302
411
  rb_scan_args(argc, argv, "0:", &opts);
303
412
  VALUE rng = Qnil;
413
+ ca_rng_t source;
304
414
  rb_scan_options(opts, "rng", &rng);
305
415
 
306
416
  rb_ca_modify(self);
@@ -314,17 +424,66 @@ rb_ca_randomn_bang(int argc, VALUE *argv, VALUE self)
314
424
 
315
425
  n = ca->elements;
316
426
  ca_attach(ca);
427
+ ca_rng_open(rng, &source);
428
+
429
+ /* A CArray::Rng fills one cell per call rather than two, so that this
430
+ and a kernel drawing afterwards are the one sequence: both are
431
+ `ca_random_normal` run once per cell. It costs a draw a cell over
432
+ the paired form, and what it buys is that where the generator
433
+ stands can be worked out rather than run. */
434
+ if (source.kind == CA_RNG_OWN) {
435
+ switch (ca->data_type) {
436
+ case CA_FLOAT64: {
437
+ double *p = (double *)ca->ptr;
438
+ for (i = 0; i < n; i++)
439
+ p[i] = ca_random_normal(&source);
440
+ break;
441
+ }
442
+ case CA_FLOAT32: {
443
+ float *p = (float *)ca->ptr;
444
+ for (i = 0; i < n; i++)
445
+ p[i] = (float) ca_random_normal(&source);
446
+ break;
447
+ }
448
+ case CA_CMPLX128: {
449
+ double complex *p = (double complex *)ca->ptr;
450
+ for (i = 0; i < n; i++) {
451
+ /* Into locals first: C does not say which order a call's
452
+ arguments are evaluated in, and these two advance a state. */
453
+ double re = ca_random_normal(&source);
454
+ double im = ca_random_normal(&source);
455
+ p[i] = re + im * I;
456
+ }
457
+ break;
458
+ }
459
+ case CA_CMPLX64: {
460
+ float complex *p = (float complex *)ca->ptr;
461
+ for (i = 0; i < n; i++) {
462
+ double re = ca_random_normal(&source);
463
+ double im = ca_random_normal(&source);
464
+ p[i] = (float) re + (float) im * I;
465
+ }
466
+ break;
467
+ }
468
+ default:
469
+ break;
470
+ }
471
+ ca_rng_close(&source);
472
+ ca_sync(ca);
473
+ ca_detach(ca);
474
+ return self;
475
+ }
317
476
 
318
477
  switch (ca->data_type) {
319
478
  case CA_FLOAT64: {
320
479
  double *p = (double *)ca->ptr;
321
480
  ca_size_t pairs = n / 2;
322
481
  for (i = 0; i < pairs; i++) {
323
- box_muller_pair(rng, &p[2*i], &p[2*i+1]);
482
+ box_muller_pair(&source, &p[2*i], &p[2*i+1]);
324
483
  }
325
484
  if (n % 2 == 1) {
326
485
  double r1, r2;
327
- box_muller_pair(rng, &r1, &r2);
486
+ box_muller_pair(&source, &r1, &r2);
328
487
  p[n-1] = r1;
329
488
  }
330
489
  break;
@@ -334,13 +493,13 @@ rb_ca_randomn_bang(int argc, VALUE *argv, VALUE self)
334
493
  ca_size_t pairs = n / 2;
335
494
  for (i = 0; i < pairs; i++) {
336
495
  double r1, r2;
337
- box_muller_pair(rng, &r1, &r2);
496
+ box_muller_pair(&source, &r1, &r2);
338
497
  p[2*i] = (float)r1;
339
498
  p[2*i+1] = (float)r2;
340
499
  }
341
500
  if (n % 2 == 1) {
342
501
  double r1, r2;
343
- box_muller_pair(rng, &r1, &r2);
502
+ box_muller_pair(&source, &r1, &r2);
344
503
  p[n-1] = (float)r1;
345
504
  }
346
505
  break;
@@ -349,7 +508,7 @@ rb_ca_randomn_bang(int argc, VALUE *argv, VALUE self)
349
508
  double complex *p = (double complex *)ca->ptr;
350
509
  for (i = 0; i < n; i++) {
351
510
  double r1, r2;
352
- box_muller_pair(rng, &r1, &r2);
511
+ box_muller_pair(&source, &r1, &r2);
353
512
  p[i] = r1 + r2 * I;
354
513
  }
355
514
  break;
@@ -358,7 +517,7 @@ rb_ca_randomn_bang(int argc, VALUE *argv, VALUE self)
358
517
  float complex *p = (float complex *)ca->ptr;
359
518
  for (i = 0; i < n; i++) {
360
519
  double r1, r2;
361
- box_muller_pair(rng, &r1, &r2);
520
+ box_muller_pair(&source, &r1, &r2);
362
521
  p[i] = (float)r1 + (float)r2 * I;
363
522
  }
364
523
  break;
@@ -367,6 +526,7 @@ rb_ca_randomn_bang(int argc, VALUE *argv, VALUE self)
367
526
  break;
368
527
  }
369
528
 
529
+ ca_rng_close(&source);
370
530
  ca_sync(ca);
371
531
  ca_detach(ca);
372
532
  return self;
@@ -399,6 +559,7 @@ rb_ca_shuffle_bang(int argc, VALUE *argv, VALUE self)
399
559
 
400
560
  rb_scan_args(argc, argv, "0:", &opts);
401
561
  VALUE rng = Qnil;
562
+ ca_rng_t source;
402
563
  rb_scan_options(opts, "rng,axis", &rng, &v_axis);
403
564
 
404
565
  rb_ca_modify(self);
@@ -407,6 +568,7 @@ rb_ca_shuffle_bang(int argc, VALUE *argv, VALUE self)
407
568
  if (ca->elements <= 1) return self;
408
569
 
409
570
  ca_attach(ca);
571
+ ca_rng_open(rng, &source);
410
572
 
411
573
  if (NIL_P(v_axis)) {
412
574
  /* shuffle all elements */
@@ -416,7 +578,7 @@ rb_ca_shuffle_bang(int argc, VALUE *argv, VALUE self)
416
578
  char *p = ca->ptr;
417
579
 
418
580
  for (ca_size_t i = n - 1; i > 0; i--) {
419
- unsigned long j = ca_random_ulong_limited(rng, (unsigned long)i);
581
+ unsigned long j = ca_random_ulong_limited(&source, (unsigned long)i);
420
582
  if ((ca_size_t)j != i) {
421
583
  swap_chunks(p + i * elem_bytes, p + j * elem_bytes, elem_bytes, tmp);
422
584
  }
@@ -427,6 +589,7 @@ rb_ca_shuffle_bang(int argc, VALUE *argv, VALUE self)
427
589
  axis = NUM2INT(v_axis);
428
590
  if (axis < 0) axis += ca->ndim;
429
591
  if (axis < 0 || axis >= ca->ndim) {
592
+ ca_rng_close(&source);
430
593
  ca_sync(ca);
431
594
  ca_detach(ca);
432
595
  rb_raise(rb_eArgError,
@@ -435,6 +598,7 @@ rb_ca_shuffle_bang(int argc, VALUE *argv, VALUE self)
435
598
 
436
599
  n = ca->dim[axis];
437
600
  if (n <= 1) {
601
+ ca_rng_close(&source);
438
602
  ca_sync(ca);
439
603
  ca_detach(ca);
440
604
  return self;
@@ -455,7 +619,7 @@ rb_ca_shuffle_bang(int argc, VALUE *argv, VALUE self)
455
619
  for (ca_size_t o = 0; o < outer; o++) {
456
620
  char *base = ca->ptr + o * stride;
457
621
  for (ca_size_t i = n - 1; i > 0; i--) {
458
- unsigned long j = ca_random_ulong_limited(rng, (unsigned long)i);
622
+ unsigned long j = ca_random_ulong_limited(&source, (unsigned long)i);
459
623
  if ((ca_size_t)j != i) {
460
624
  swap_chunks(base + i * chunk_bytes,
461
625
  base + j * chunk_bytes,
@@ -466,6 +630,7 @@ rb_ca_shuffle_bang(int argc, VALUE *argv, VALUE self)
466
630
  xfree(tmp);
467
631
  }
468
632
 
633
+ ca_rng_close(&source);
469
634
  ca_sync(ca);
470
635
  ca_detach(ca);
471
636
  return self;
@@ -503,11 +668,190 @@ rb_ca_randomn(int argc, VALUE *argv, VALUE self)
503
668
  return rb_ca_randomn_bang(argc, argv, copy);
504
669
  }
505
670
 
671
+ /* ---- CArray::Rng ---------------------------------------------------- */
672
+
673
+ /* A generator with its own state, so that a sequence belongs to an object
674
+ * rather than to the process:
675
+ *
676
+ * r = CArray::Rng.new(seed: 4)
677
+ * a.random!(rng: r) # fills, advancing r
678
+ * CArray.jit_for(n) { |i| b[i] = r.call } # carries on from there
679
+ *
680
+ * The state is an ordinary CA_INT64 array of four cells, which is what
681
+ * lets the second line work: carray-jit hands that array's address to a
682
+ * kernel that pasted the same generator, and the kernel advances the
683
+ * same cells this file does. Nothing about the generator is hidden
684
+ * behind a struct only this extension can read.
685
+ *
686
+ * The seed is not part of the state. It is remembered so that #reset
687
+ * with no argument can repeat a run, and so `inspect` can say what a
688
+ * generator was started from. */
689
+
690
+ static VALUE rb_ca_rng_reset (int argc, VALUE *argv, VALUE self);
691
+
692
+ /* The state's cells, attached. Every entry point here goes through this
693
+ * rather than reaching into the ivar, so that "what is the state" has one
694
+ * answer even after another generator is added. */
695
+ static CArray *
696
+ ca_rng_cells (VALUE self, int64_t **cells)
697
+ {
698
+ CArray *ca;
699
+ VALUE state = rb_ivar_get(self, rb_intern("@state"));
700
+ TypedData_Get_Struct(state, CArray, &carray_data_type, ca);
701
+ ca_attach(ca);
702
+ *cells = (int64_t *) ca->ptr;
703
+ return ca;
704
+ }
705
+
706
+ /* CArray::Rng.new(generator = :xoshiro256pp, seed: nil) */
707
+ static VALUE
708
+ rb_ca_rng_initialize (int argc, VALUE *argv, VALUE self)
709
+ {
710
+ VALUE gen = Qnil, opts = Qnil, seed = Qnil, state;
711
+
712
+ rb_scan_args(argc, argv, "01:", &gen, &opts);
713
+ rb_scan_options(opts, "seed", &seed);
714
+
715
+ if (NIL_P(gen)) gen = ID2SYM(rb_intern("xoshiro256pp"));
716
+ if (!SYMBOL_P(gen) || SYM2ID(gen) != rb_intern("xoshiro256pp")) {
717
+ rb_raise(rb_eArgError,
718
+ "unknown generator %"PRIsVALUE"; carray has :xoshiro256pp",
719
+ rb_inspect(gen));
720
+ }
721
+
722
+ state = rb_funcall(rb_cCArray, rb_intern("int64"), 1, INT2FIX(4));
723
+ rb_ivar_set(self, rb_intern("@generator"), gen);
724
+ rb_ivar_set(self, rb_intern("@state"), state);
725
+
726
+ return rb_ca_rng_reset(NIL_P(seed) ? 0 : 1, &seed, self);
727
+ }
728
+
729
+ /* CArray::Rng#reset(seed = nil) — start the sequence over.
730
+ *
731
+ * With no argument, from the seed this generator already carries, which
732
+ * repeats the run exactly. A generator made without a seed is given one
733
+ * from Random.new_seed, so two of them differ; that drawn seed is kept,
734
+ * so even an unseeded run can be repeated once it has begun. */
735
+ static VALUE
736
+ rb_ca_rng_reset (int argc, VALUE *argv, VALUE self)
737
+ {
738
+ VALUE seed = Qnil, masked;
739
+ CArray *ca;
740
+ int64_t *cells;
741
+
742
+ rb_scan_args(argc, argv, "01", &seed);
743
+ if (NIL_P(seed)) seed = rb_ivar_get(self, rb_intern("@seed"));
744
+ if (NIL_P(seed)) {
745
+ seed = rb_funcall(rb_path2class("Random"), rb_intern("new_seed"), 0);
746
+ }
747
+
748
+ /* Any Integer is a seed: a negative one and one wider than a word are
749
+ folded into 64 bits rather than refused, which is what `&` does. */
750
+ masked = rb_funcall(rb_to_int(seed), rb_intern("&"),
751
+ 1, ULL2NUM(0xFFFFFFFFFFFFFFFFULL));
752
+
753
+ ca = ca_rng_cells(self, &cells);
754
+ ca_xoshiro256pp_seed(cells, (uint64_t) NUM2ULL(masked));
755
+ ca_sync(ca);
756
+ ca_detach(ca);
757
+
758
+ rb_ivar_set(self, rb_intern("@seed"), seed);
759
+ return self;
760
+ }
761
+
762
+ /* CArray::Rng#random — one draw in [0.0, 1.0), the state advanced.
763
+ *
764
+ * Named for `CArray#random!`, which it is the one-cell form of, and to
765
+ * pair with `#randomn` the way those two pair. Not `#rand`: Ruby's
766
+ * `random:` keyword calls `rand(n)` on what it is given, and a `#rand`
767
+ * here that takes no argument would make this look usable there and
768
+ * then fail with an arity error rather than say what it is.
769
+ *
770
+ * It is the same draw `random!` takes for one cell, and the same one a
771
+ * kernel's `random(rng:)` takes, because all three run the code in
772
+ * ca_rng_xoshiro256pp.h. */
773
+ static VALUE
774
+ rb_ca_rng_random (VALUE self)
775
+ {
776
+ CArray *ca;
777
+ int64_t *cells;
778
+ double value;
779
+
780
+ ca = ca_rng_cells(self, &cells);
781
+ value = ca_xoshiro256pp_next_real(cells);
782
+ ca_sync(ca);
783
+ ca_detach(ca);
784
+ return rb_float_new(value);
785
+ }
786
+
787
+ /* CArray::Rng#bits — the same draw as the generator's raw 64 bits.
788
+ *
789
+ * Not a bounded draw and not Ruby's `rand(n)`: it is the word the
790
+ * generator produced, before it was turned into a double. What that is
791
+ * for is checking this generator against the sequence its authors
792
+ * published, which is the one question `#rand` cannot answer. */
793
+ static VALUE
794
+ rb_ca_rng_bits (VALUE self)
795
+ {
796
+ CArray *ca;
797
+ int64_t *cells;
798
+ uint64_t value;
799
+
800
+ ca = ca_rng_cells(self, &cells);
801
+ value = ca_xoshiro256pp_next(cells);
802
+ ca_sync(ca);
803
+ ca_detach(ca);
804
+ return ULL2NUM(value);
805
+ }
806
+
807
+ /* CArray::Rng#randomn — one standard normal, which is two draws.
808
+ *
809
+ * The same one `randomn!(rng: self)` writes into a cell and the same
810
+ * one a kernel's `randomn(rng: self)` takes, because all three are
811
+ * ca_random_normal. */
812
+ static VALUE
813
+ rb_ca_rng_randomn (VALUE self)
814
+ {
815
+ CArray *ca;
816
+ int64_t *cells;
817
+ ca_rng_t source;
818
+ double value;
819
+
820
+ ca = ca_rng_cells(self, &cells);
821
+ source.kind = CA_RNG_OWN;
822
+ source.rng = self;
823
+ source.state = NULL;
824
+ source.cells = cells;
825
+ value = ca_random_normal(&source);
826
+ ca_sync(ca);
827
+ ca_detach(ca);
828
+ return rb_float_new(value);
829
+ }
830
+
831
+ static VALUE
832
+ rb_ca_rng_inspect (VALUE self)
833
+ {
834
+ return rb_sprintf("#<CArray::Rng %"PRIsVALUE" seed=%"PRIsVALUE">",
835
+ rb_ivar_get(self, rb_intern("@generator")),
836
+ rb_ivar_get(self, rb_intern("@seed")));
837
+ }
838
+
506
839
  /* ---- Init -------------------------------------------------------------- */
507
840
 
508
841
  void
509
842
  Init_carray_random (void)
510
843
  {
844
+ rb_cCARng = rb_define_class_under(rb_cCArray, "Rng", rb_cObject);
845
+ rb_define_method(rb_cCARng, "initialize", rb_ca_rng_initialize, -1);
846
+ rb_define_method(rb_cCARng, "reset", rb_ca_rng_reset, -1);
847
+ rb_define_method(rb_cCARng, "random", rb_ca_rng_random, 0);
848
+ rb_define_method(rb_cCARng, "randomn", rb_ca_rng_randomn, 0);
849
+ rb_define_method(rb_cCARng, "bits", rb_ca_rng_bits, 0);
850
+ rb_define_method(rb_cCARng, "inspect", rb_ca_rng_inspect, 0);
851
+ rb_define_attr(rb_cCARng, "generator", 1, 0);
852
+ rb_define_attr(rb_cCARng, "state", 1, 0);
853
+ rb_define_attr(rb_cCARng, "seed", 1, 0);
854
+
511
855
  rb_define_method(rb_cCArray, "random!", rb_ca_random_bang, -1);
512
856
  rb_define_method(rb_cCArray, "randomn!", rb_ca_randomn_bang, -1);
513
857
  rb_define_method(rb_cCArray, "shuffle!", rb_ca_shuffle_bang, -1);
data/ext/carray_slab.c CHANGED
@@ -460,6 +460,19 @@ ca_slab_setup_input_slab_view (ca_slab_iter_state_t *st, CArray *src,
460
460
  }
461
461
  }
462
462
 
463
+ /* The slab is a window onto the source when the fiber is contiguous and
464
+ a copy in the iterator's scratch when it is not, so writing through it
465
+ landed on one axis and vanished on the next. Nothing in the surface
466
+ said which you would get. Refuse the write on both instead: the block
467
+ forms that produce values (map_slab, reduce_slab) hand their result
468
+ back rather than writing into the slab, and an in-place walk belongs
469
+ to `ca[] = ...` or a C kernel with CA_KERNEL_WRITE. */
470
+ {
471
+ CArray *slab;
472
+ TypedData_Get_Struct(st->slab_view, CArray, &carray_data_type, slab);
473
+ ca_set_flag(slab, CA_FLAG_READ_ONLY);
474
+ }
475
+
463
476
  return in_alias;
464
477
  }
465
478