carray-jit 0.1.2 → 0.1.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +771 -3
  3. data/README.md +7 -6
  4. data/carray-jit.gemspec +1 -3
  5. data/docs/00_Introduction.md +4 -3
  6. data/docs/01_GettingStarted.md +1 -1
  7. data/docs/02_KernelShapes.md +93 -14
  8. data/docs/03_SupportedFeatures.md +582 -26
  9. data/docs/04_Compiling.md +33 -6
  10. data/docs/05_DesignNotes.md +3 -3
  11. data/docs/06_Cheatsheet.md +198 -5
  12. data/docs/07_StepByStep.ja.md +534 -0
  13. data/docs/07_StepByStep.md +535 -0
  14. data/examples/README.md +12 -0
  15. data/examples/applications/alarm.rb +121 -0
  16. data/examples/applications/collatz.rb +105 -0
  17. data/examples/applications/cubic_spline.rb +331 -0
  18. data/examples/applications/dithering.rb +144 -0
  19. data/examples/applications/group_stats.rb +115 -0
  20. data/examples/applications/lookup.rb +126 -0
  21. data/examples/applications/median_filter.rb +153 -0
  22. data/examples/applications/parcel_ascent.rb +220 -0
  23. data/examples/applications/point_in_polygon.rb +111 -0
  24. data/examples/applications/random_walk.rb +98 -0
  25. data/examples/applications/van_der_pol.rb +186 -0
  26. data/examples/applications/wet_bulb.rb +140 -0
  27. data/examples/features/10_complex.rb +14 -4
  28. data/examples/features/15_loops.rb +7 -1
  29. data/lib/carray/jit/access.rb +14 -0
  30. data/lib/carray/jit/analyzer.rb +2077 -136
  31. data/lib/carray/jit/block_reader.rb +37 -6
  32. data/lib/carray/jit/c_function.rb +613 -76
  33. data/lib/carray/jit/c_generator.rb +1595 -156
  34. data/lib/carray/jit/call.rb +68 -0
  35. data/lib/carray/jit/compiler.rb +75 -11
  36. data/lib/carray/jit/kernel.rb +369 -32
  37. data/lib/carray/jit/node.rb +359 -9
  38. data/lib/carray/jit/sorting_networks.rb +182 -0
  39. data/lib/carray/jit/type_assignment.rb +371 -34
  40. data/lib/carray/jit/version.rb +1 -1
  41. data/lib/carray/jit.rb +560 -64
  42. metadata +22 -8
  43. data/ext/carray_jit_access/carray_jit_access.c +0 -460
  44. data/ext/carray_jit_access/extconf.rb +0 -8
@@ -0,0 +1,535 @@
1
+ # 31 carray-jit exercises, with solutions
2
+
3
+ Thirty-one small tasks, in the order the documentation introduces things, with a
4
+ solution under each. Roughly in order of difficulty -- the stars say how much
5
+ of the subset a task leans on, not how much code it takes.
6
+
7
+ They run as written. Start with
8
+
9
+ ```ruby
10
+ require "carray"
11
+ require "carray/jit"
12
+ ```
13
+
14
+ and every `#=>` is what that line actually answers.
15
+
16
+ Read the task, try it, then look. Where the answer is that the compiler
17
+ refuses something, the refusal is the answer and is printed under the code.
18
+
19
+ ---
20
+
21
+ #### 1. Compute `a + b * c` into `out` in a single pass, with no array built in between (★☆☆)
22
+
23
+ `hint: the method for work that reaches no neighbour`
24
+
25
+ ```ruby
26
+ a = CA_DOUBLE([1, 2, 3])
27
+ b = CA_DOUBLE([2, 3, 4])
28
+ c = CA_DOUBLE([3, 4, 5])
29
+ out = CArray.double(3)
30
+
31
+ CArray.jit_each { out = a + b * c }
32
+ out.to_a #=> [7.0, 14.0, 23.0]
33
+ ```
34
+
35
+ #### 2. Compute `(a + b * c) / 2` over three int32 arrays, taking the result back as a new array. What is its data type? (★☆☆)
36
+
37
+ `hint: the other element-wise method`
38
+
39
+ ```ruby
40
+ a = CA_INT32([1, 2, 3])
41
+ b = CA_INT32([2, 3, 4])
42
+ c = CA_INT32([3, 4, 5])
43
+
44
+ half = CArray.jit_map { (a + b * c) / 2.0 }
45
+ half.to_a #=> [3.5, 7.0, 11.5]
46
+ half.data_type_name #=> "float64"
47
+ # typed from the block's last value, not from the arrays: `2.0` has no width
48
+ # of its own, so it takes the one it meets. Without it, int64.
49
+ ```
50
+
51
+ #### 3. Add a row of three to every row of a 2x3 table into `out`, without copying the row (★☆☆)
52
+
53
+ `hint: CArray will broadcast an axis, but not invent a rank`
54
+
55
+ ```ruby
56
+ table = CArray.double(2, 3).seq!(1.0)
57
+ row = CA_DOUBLE([10, 20, 30]).reshape(1, 3) # 1x3, not 3: else `ndim mismatch`
58
+ out = CArray.double(2, 3)
59
+
60
+ CArray.jit_each { out = table + row }
61
+ out.to_a #=> [[11.0, 22.0, 33.0], [14.0, 25.0, 36.0]]
62
+ ```
63
+
64
+ #### 4. Scale an array by a Float the block closes over, twice with different values and compiling only once. Collect both results (★☆☆)
65
+
66
+ `hint: nothing special is needed`
67
+
68
+ ```ruby
69
+ a = CA_DOUBLE([1, 2])
70
+ out = CArray.double(2)
71
+
72
+ [2.0, 5.0].map { |gain|
73
+ CArray.jit_each { out = a * gain } # a captured scalar is an argument,
74
+ out.to_a # not a constant compiled in
75
+ } #=> [[2.0, 4.0], [5.0, 10.0]]
76
+ ```
77
+
78
+ #### 5. Scale an array into `out` by a factor that carries a data type of its own (★☆☆)
79
+
80
+ `hint: CScalar`
81
+
82
+ ```ruby
83
+ a = CA_DOUBLE([1, 2])
84
+ gain = CScalar.double() { 2.5 }
85
+ out = CArray.double(2)
86
+
87
+ CArray.jit_each { out = a * gain }
88
+ out.to_a #=> [2.5, 5.0]
89
+ ```
90
+
91
+ #### 6. Write each value into `out`, with the negatives clipped to zero (★☆☆)
92
+
93
+ `hint: the branch is an expression`
94
+
95
+ ```ruby
96
+ a = CA_DOUBLE([-2, 0, 1.5, 3])
97
+ out = CArray.double(4)
98
+
99
+ CArray.jit_each { out = a < 0.0 ? 0.0 : a }
100
+ out.to_a #=> [0.0, 0.0, 1.5, 3.0]
101
+ ```
102
+
103
+ #### 7. Write a vector into `out` reversed (★☆☆)
104
+
105
+ `hint: the cell has to say which cell it reads`
106
+
107
+ ```ruby
108
+ a = CArray.double(5).seq!(1.0)
109
+ out = CArray.double(5)
110
+
111
+ CArray.jit_for(5) { |i| out[i] = a[4 - i] }
112
+ out.to_a #=> [5.0, 4.0, 3.0, 2.0, 1.0]
113
+ ```
114
+
115
+ #### 8. First differences: `out[i] = a[i] - a[i-1]`, leaving `out[0]` at zero (★☆☆)
116
+
117
+ `hint: where can the range start?`
118
+
119
+ ```ruby
120
+ a = CA_DOUBLE([1, 3, 6, 10])
121
+ out = CArray.double(4)
122
+
123
+ CArray.jit_for(1...4) { |i| out[i] = a[i] - a[i - 1] }
124
+ out.to_a #=> [0.0, 2.0, 3.0, 4.0]
125
+ # 1... and not 0...: bounds are checked against the subscript as written,
126
+ # before the loop runs, so a guard inside the body would not help.
127
+ ```
128
+
129
+ #### 9. `out[i] = 0.5 * a[i] + 0.5 * out[i-1]`, with `out[0] = a[0]` (★★☆)
130
+
131
+ `hint: read what this loop just wrote`
132
+
133
+ ```ruby
134
+ a = CA_DOUBLE([4, 8, 8, 8])
135
+ out = CArray.double(4)
136
+ out[0] = a[0]
137
+
138
+ CArray.jit_for(1...4) { |i| out[i] = 0.5 * a[i] + 0.5 * out[i - 1] }
139
+ out.to_a #=> [4.0, 6.0, 7.0, 7.5]
140
+ ```
141
+
142
+ #### 10. Write a 2x3 into `out` transposed, as a 3x2 (★☆☆)
143
+
144
+ `hint: two parameters, two extents`
145
+
146
+ ```ruby
147
+ a = CArray.double(2, 3).seq!(1.0)
148
+ out = CArray.double(3, 2)
149
+
150
+ CArray.jit_for(2, 3) { |i, j| out[j, i] = a[i, j] }
151
+ out.to_a #=> [[1.0, 4.0], [2.0, 5.0], [3.0, 6.0]]
152
+ ```
153
+
154
+ #### 11. Write the largest value of each row into `out` (★☆☆)
155
+
156
+ `hint: a loop inside the cell`
157
+
158
+ ```ruby
159
+ a = CA_DOUBLE([[1, 3, 2], [9, 4, 5]])
160
+ out = CArray.double(2)
161
+
162
+ CArray.jit_for(2) { |i|
163
+ best = a[i, 0]
164
+ (1...3).each { |j| best = a[i, j] if a[i, j] > best }
165
+ out[i] = best
166
+ }
167
+ out.to_a #=> [3.0, 9.0]
168
+ ```
169
+
170
+ #### 12. Total an int32 array into a single value the kernel writes (★☆☆)
171
+
172
+ `hint: a local does not survive the iteration`
173
+
174
+ ```ruby
175
+ a = CArray.int32(10).seq!(1)
176
+ total = CScalar.int32() { 0 }
177
+
178
+ CArray.jit_for(10) { |i| total[] += a[i] }
179
+ total[0] #=> 55
180
+ ```
181
+
182
+ #### 13. Given the bin number of each reading, count the readings of every bin into `counts` (★★☆)
183
+
184
+ `hint: the subscript is a value, not the index`
185
+
186
+ ```ruby
187
+ bin = CA_INT32([0, 2, 1, 1, 0, 1])
188
+ counts = CArray.int32(3)
189
+
190
+ CArray.jit_for(6) { |i| counts[bin[i]] += 1 }
191
+ counts.to_a #=> [2, 3, 1]
192
+ ```
193
+
194
+ #### 14. Write the distance from the origin of each (x, y) into `out` (★☆☆)
195
+
196
+ `hint: Math`
197
+
198
+ ```ruby
199
+ x = CA_DOUBLE([3, 5])
200
+ y = CA_DOUBLE([4, 12])
201
+ out = CArray.double(2)
202
+
203
+ CArray.jit_each { out = Math.sqrt(x * x + y * y) }
204
+ out.to_a #=> [5.0, 13.0]
205
+ ```
206
+
207
+ #### 15. Give `jit_each` a block that names an index, and `jit_for` one that names none. What comes back? (★★☆)
208
+
209
+ `hint: neither guesses`
210
+
211
+ ```ruby
212
+ a = CA_DOUBLE([1, 2, 3])
213
+ out = CArray.double(3)
214
+
215
+ CArray.jit_each { |i| out[i] = a[i] + 1.0 }
216
+ #=> CArray::JIT::Unsupported: this block names the arrays it reaches, so it
217
+ # takes no parameters; for a loop that names its indices, see `jit_for`
218
+
219
+ CArray.jit_for(3) { out = a + 1.0 }
220
+ #=> CArray::JIT::Unsupported: jit_for's block names the cells it is on, so it
221
+ # takes the loop indices as its parameters; a block that names none is
222
+ # element-wise and belongs to jit_each
223
+ ```
224
+
225
+ #### 16. Write how many decimal digits each number has into `digits` (★★☆)
226
+
227
+ `hint: divide by ten until there is nothing left -- but how many times is that?`
228
+
229
+ ```ruby
230
+ value = CA_INT32([7, 42, 1000, 65536])
231
+ digits = CArray.int32(4)
232
+
233
+ CArray.jit_for(4) { |i|
234
+ n = value[i]
235
+ count = 0
236
+ while n > 0 # one cell goes round once, another five
237
+ n = n / 10 # integer division, and Ruby's: it floors
238
+ count = count + 1
239
+ end
240
+ digits[i] = count
241
+ }
242
+ digits.to_a #=> [1, 2, 4, 5]
243
+ ```
244
+
245
+ #### 17. Write the first column past 4.0 in each row into `first`, or -1 where there is none (★★☆)
246
+
247
+ `hint: stop looking`
248
+
249
+ ```ruby
250
+ rows = CA_DOUBLE([[1, 2, 9, 3], [5, 1, 1, 8]])
251
+ first = CArray.int32(2)
252
+
253
+ CArray.jit_for(2) { |i|
254
+ found = -1
255
+ (0...4).each { |j|
256
+ if rows[i, j] > 4.0
257
+ found = j
258
+ break
259
+ end
260
+ }
261
+ first[i] = found
262
+ }
263
+ first.to_a #=> [2, 0]
264
+ ```
265
+
266
+ #### 18. Multiply a 2x3 by a 3x2, taking the result back (★★☆)
267
+
268
+ `hint: the index that repeats`
269
+
270
+ ```ruby
271
+ a = CArray.double(2, 3).seq!(1.0)
272
+ b = CArray.double(3, 2).seq!(1.0)
273
+
274
+ CArray.jit_contract { |i, j, k| a[i, k] * b[k, j] }.to_a
275
+ #=> [[22.0, 28.0], [49.0, 64.0]]
276
+ ```
277
+
278
+ #### 19. For each row `i` of two 2x3 arrays, take back the sum over `k` of `x[i, k] * y[i, k]` (★★★)
279
+
280
+ `hint: the row index repeats too, and must not be summed`
281
+
282
+ ```ruby
283
+ x = CA_DOUBLE([[1, 2, 3], [4, 5, 6]])
284
+ y = CA_DOUBLE([[1, 1, 1], [2, 2, 2]])
285
+
286
+ CArray.jit_contract(:b) { |k| x[b, k] * y[b, k] }.to_a
287
+ #=> [6.0, 30.0]
288
+ # `b` is named in the call, not taken as a parameter; both at once is refused
289
+ ```
290
+
291
+ #### 20. Take back each cell plus its two neighbours, with a cell off the end reading as zero (★★☆)
292
+
293
+ `hint: no index, no extent, no edge`
294
+
295
+ ```ruby
296
+ a = CA_DOUBLE([1, 2, 3, 4])
297
+
298
+ CArray.jit_stencil(a, border: :zero) { |w| w[-1] + w[0] + w[1] }.to_a
299
+ #=> [3.0, 6.0, 9.0, 7.0]
300
+ # border: :zero, :clamp, :wrap compute the frame; :mask and :skip answer
301
+ # about the frame cells themselves
302
+ ```
303
+
304
+ #### 21. Write every cell into `out`, with `UNDEF` replaced by zero (★★☆)
305
+
306
+ `hint: ask about one cell`
307
+
308
+ ```ruby
309
+ a = CA_DOUBLE([1, 2, 3])
310
+ a[1] = UNDEF
311
+ out = CArray.double(3)
312
+
313
+ CArray.jit_for(3) { |i| out[i] = a[i] == UNDEF ? 0.0 : a[i] }
314
+ out.to_a #=> [1.0, 0.0, 3.0]
315
+ # where "propagate" is the answer wanted, write nothing: masks are ORed
316
+ # across the inputs and carried to the outputs on their own
317
+ ```
318
+
319
+ #### 22. Write 1, 2, 3 down the middle column of a 3x3, leaving the rest (★★☆)
320
+
321
+ `hint: hand the kernel the column`
322
+
323
+ ```ruby
324
+ table = CArray.double(3, 3)
325
+ column = table[nil, 1]
326
+
327
+ CArray.jit_for(3) { |i| column[i] = i + 1.0 }
328
+ table.to_a #=> [[0.0, 1.0, 0.0], [0.0, 2.0, 0.0], [0.0, 3.0, 0.0]]
329
+ ```
330
+
331
+ #### 23. Total a uint64 array through a local accumulator. What stops it, and what gets past it? (★★★)
332
+
333
+ `hint: a literal has no width of its own`
334
+
335
+ ```ruby
336
+ source = CArray.uint64(2).seq!(1)
337
+ out = CArray.uint64(1)
338
+
339
+ CArray.jit_for(1) { |i|
340
+ total = 0
341
+ (0...2).each { |j| total = total + source[j] }
342
+ out[i] = total
343
+ }
344
+ #=> CArray::JIT::Unsupported: `total` enters this loop as an Integer and comes
345
+ # back round as a uint64; the value carried to the next pass would change
346
+ # type, and one C variable is one type -- give it one type before the loop
347
+ ```
348
+
349
+ ```ruby
350
+ seed = CScalar.uint64() { 0 } # a value with a data type
351
+
352
+ CArray.jit_for(1) { |i|
353
+ total = seed
354
+ (0...2).each { |j| total = total + source[j] }
355
+ out[i] = total
356
+ }
357
+ out.to_a #=> [3]
358
+ ```
359
+
360
+ #### 24. Suffix sums: `out[i] = a[i] + out[i+1]`, with `out[4]` already set (★★★)
361
+
362
+ `hint: which way does the loop have to run, and where do you say so?`
363
+
364
+ ```ruby
365
+ a = CA_DOUBLE([1, 2, 3, 4, 5])
366
+ out = CArray.double(5)
367
+ out[4] = a[4]
368
+
369
+ CArray.jit_for(3.step(0, -1)) { |i| out[i] = a[i] + out[i + 1] }
370
+ out.to_a #=> [15.0, 14.0, 12.0, 9.0, 5.0]
371
+ ```
372
+
373
+ ```ruby
374
+ out = CArray.double(5)
375
+ out[4] = a[4]
376
+
377
+ CArray.jit_for(0...4) { |i| out[i] = a[i] + out[i + 1] }
378
+ out.to_a #=> [1.0, 2.0, 3.0, 9.0, 5.0]
379
+ # the extent is the only place the direction is stated, and stating it
380
+ # wrongly is not refused: every cell read the zero that was still there
381
+ ```
382
+
383
+ #### 25. Write the `i`-th Fibonacci number into `out[i]`, advancing two locals rather than reading the array back (★★★)
384
+
385
+ `hint: both names change at once`
386
+
387
+ ```ruby
388
+ out = CArray.int64(12)
389
+
390
+ CArray.jit_for(12) { |i|
391
+ a = 0
392
+ b = 1
393
+ i.times { |k| a, b = b, a + b }
394
+ out[i] = a
395
+ }
396
+ out.to_a #=> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
397
+ # every value on the right is settled before anything on the left is
398
+ # written, so `a + b` reads the `a` this pass came in with. Written as two
399
+ # lines -- `a = b` and then `b = a + b` -- the second reads the `a` the
400
+ # first just wrote, and `a` merely doubles: [0, 1, 2, 4, 8, ...]. Not a
401
+ # refusal, and not this compiler's doing: those two lines mean the same in
402
+ # Ruby
403
+ ```
404
+
405
+ #### 26. The longest run of 1s in a series of 0s and 1s (★★★)
406
+
407
+ `hint: two things have to survive the iteration, and neither is a sum`
408
+
409
+ ```ruby
410
+ flag = CA_INT32([0, 1, 1, 1, 0, 1, 1, 0])
411
+ run = CScalar.int32() { 0 }
412
+ best = CScalar.int32() { 0 }
413
+
414
+ CArray.jit_for(8) { |i|
415
+ run[] = flag[i] == 1 ? run[] + 1 : 0
416
+ best[] = run[] if run[] > best[]
417
+ }
418
+ best[0] #=> 3
419
+ ```
420
+
421
+ #### 27. For each query, write the index of the last grid point not above it into `found` (★★★)
422
+
423
+ `hint: the grid is sorted`
424
+
425
+ ```ruby
426
+ grid = CA_DOUBLE([0, 1, 4, 9, 16])
427
+ query = CA_DOUBLE([0.5, 5.0, 9.0, 12.0])
428
+ found = CArray.int32(4)
429
+
430
+ CArray.jit_for(4) { |i|
431
+ t = query[i]
432
+ low = 0
433
+ high = 4
434
+ while high - low > 1
435
+ middle = (low + high) / 2
436
+ if grid[middle] > t
437
+ high = middle
438
+ else
439
+ low = middle
440
+ end
441
+ end
442
+ found[i] = low
443
+ }
444
+ found.to_a #=> [0, 2, 3, 3]
445
+ ```
446
+
447
+ #### 28. Count up to `bound[i]`, read from an array. What stops it, and what gets past it? (★★★)
448
+
449
+ `hint: an inner range is known before the loop runs`
450
+
451
+ ```ruby
452
+ bound = CA_INT32([2, 3])
453
+ out = CArray.int32(2)
454
+
455
+ CArray.jit_for(2) { |i|
456
+ count = 0
457
+ (0...bound[i]).each { |j| count = count + 1 }
458
+ out[i] = count
459
+ }
460
+ #=> CArray::JIT::Unsupported: an inner loop's range is an integer expression
461
+ # over literals, captured scalars and the indices around it
462
+ ```
463
+
464
+ ```ruby
465
+ CArray.jit_for(2) { |i|
466
+ count = 0
467
+ j = 0
468
+ while j < bound[i] # `while` carries a bound the data decides
469
+ count = count + 1
470
+ j = j + 1
471
+ end
472
+ out[i] = count
473
+ }
474
+ out.to_a #=> [2, 3]
475
+ ```
476
+
477
+ #### 29. Write the square of a complex array into `squared`, and the magnitude of the original into `size` (★★☆)
478
+
479
+ `hint: a data type like the others`
480
+
481
+ ```ruby
482
+ z = CA_CMPLX128([Complex(0, 1), Complex(1, 1)])
483
+ squared = CArray.cmplx128(2)
484
+ size = CArray.double(2)
485
+
486
+ CArray.jit_each { squared = z * z }
487
+ CArray.jit_each { size = z.abs }
488
+
489
+ squared.to_a #=> [(-1.0+0.0i), (0.0+2.0i)]
490
+ size.to_a #=> [1.0, 1.4142135623730951]
491
+ ```
492
+
493
+ #### 30. Fill an array with a hundred thousand random numbers in [0, 1), drawn inside the kernel (★★☆)
494
+
495
+ `hint: not Ruby's rand -- the loop runs without the GVL`
496
+
497
+ ```ruby
498
+ random = CArray::Rng.new(seed: 3)
499
+ draws = CArray.double(100_000)
500
+
501
+ CArray.jit_for(100_000) { |i| draws[i] = random.random }
502
+ draws.mean.round(4) #=> 0.5006
503
+ # which draw lands in which cell is the loop's order; where that matters,
504
+ # fill an array with CArray#random! first and read a cell of it
505
+ ```
506
+
507
+ #### 31. A local holds an int32 cell and is squared into an int64 array. What C type did it get? (★★★)
508
+
509
+ `hint: read what was compiled`
510
+
511
+ ```ruby
512
+ source = CArray.int32(4).seq!(1)
513
+ out = CArray.int64(4)
514
+
515
+ kernel = CArray.jit_for(4) { |i|
516
+ v = source[i]
517
+ out[i] = v * v
518
+ }
519
+
520
+ kernel.c_source.lines.grep(/int64_t v;/).first.strip
521
+ #=> "int64_t v;"
522
+ # and the assignment below it:
523
+ # v = (int64_t)*(int32_t *)(p_source + (i) * source_s0);
524
+ # nothing in `v = source[i]` asked for 64 bits: a local's type is settled
525
+ # from the whole body, and `v * v` lands in an int64 array
526
+ ```
527
+
528
+ ---
529
+
530
+ Where to go next: [06_Cheatsheet.md](06_Cheatsheet.md) to look things up,
531
+ [examples/features/](../examples/features) for a tour of one feature per file,
532
+ and [examples/applications/](../examples/applications) for programs that use
533
+ all of this to do something -- `mandelbrot.rb` is a loop like 16 whose
534
+ length no cell shares, `alarm.rb` is 25 with hysteresis, `lookup.rb` is 26
535
+ against half a million queries, `sieve.rb` is 27 from the other side.
data/examples/README.md CHANGED
@@ -18,6 +18,7 @@ Applications
18
18
  | [game_of_life.rb](applications/game_of_life.rb) | Conway's rules as a stencil, twice: with the indices named, and as a window on a torus |
19
19
  | [moving_average.rb](applications/moving_average.rb) | a price series: exponential smoothing, running peak, drawdown |
20
20
  | [heat_equation.rb](applications/heat_equation.rb) | implicit diffusion in a rod -- a tridiagonal solve every step |
21
+ | [cubic_spline.rb](applications/cubic_spline.rb) | a C2 curve through uneven samples: the moments by Thomas, natural ends against clamped |
21
22
  | [sobel_edges.rb](applications/sobel_edges.rb) | edge detection on an image, printed as ASCII |
22
23
  | [sensor_gaps.rb](applications/sensor_gaps.rb) | quality control on a record with holes in it |
23
24
  | [point_cloud.rb](applications/point_cloud.rb) | rotating points, their covariance, projecting onto a basis |
@@ -25,6 +26,17 @@ Applications
25
26
  | [relaxation.rb](applications/relaxation.rb) | steady heat on a plate: a stencil sweep with the boundary held |
26
27
  | [partial_sums.rb](applications/partial_sums.rb) | nine series in one pass, and where the order of a sum is a choice |
27
28
  | [sieve.rb](applications/sieve.rb) | Eratosthenes: an inner loop the data decides the length of |
29
+ | [collatz.rb](applications/collatz.rb) | how long each number takes to fall to 1 -- a `while` only the cell can end, and a local typed from the far end of the body |
30
+ | [dithering.rb](applications/dithering.rb) | Floyd-Steinberg: a pixel writes the cells the walk is about to read, so the order is the algorithm |
31
+ | [lookup.rb](applications/lookup.rb) | half a million queries against an uneven grid: a binary search per cell, against the same answer as index arrays |
32
+ | [random_walk.rb](applications/random_walk.rb) | gambler's ruin from `CArray::Rng`, drawn inside the kernel, against the exact answer |
33
+ | [group_stats.rb](applications/group_stats.rb) | per-label aggregates that are not sums: one walk against a pass per label |
34
+ | [wet_bulb.rb](applications/wet_bulb.rb) | a derived field on a weather grid, where every cell solves its own equation |
35
+ | [alarm.rb](applications/alarm.rb) | an alarm with hysteresis: a state machine carried in `CScalar`s, which no array expression composes |
36
+ | [parcel_ascent.rb](applications/parcel_ascent.rb) | lifting a parcel up a sounding for CAPE and CIN: the equation changes at a level each column picks for itself |
37
+ | [van_der_pol.rb](applications/van_der_pol.rb) | an adaptive integrator where the step, the retry and the count belong to the trajectory |
38
+ | [median_filter.rb](applications/median_filter.rb) | despiking with a median of nine, three ways, and where a cell's scratch belongs |
39
+ | [point_in_polygon.rb](applications/point_in_polygon.rb) | a million points against 200 edges, with the crossing count in a register instead of 1.5 GB |
28
40
  | [recursion.rb](applications/recursion.rb) | fib, tak, tarai and ackermann as `jit_function`s that call themselves |
29
41
  | [quicksort.rb](applications/quicksort.rb) | the textbook partition as a compiled C function, recursing through a pointer |
30
42
  | [kepler.rb](applications/kepler.rb) | Newton's method where the passes are the cell's business, not the program's |
@@ -0,0 +1,121 @@
1
+ # An alarm with hysteresis, which is a state machine over a series.
2
+ #
3
+ # A threshold on its own chatters: a reading sitting on the line flips the
4
+ # alarm on and off every sample. So an alarm is written with two thresholds
5
+ # and a dwell -- it raises only after the reading has been above the high
6
+ # line for a while, and clears only after it has been below the low line for
7
+ # a while -- and what it does at any sample depends on what it was doing at
8
+ # the sample before.
9
+ #
10
+ # That is the part arrays have no answer for. `a.gt(HIGH)` is a pass, and so
11
+ # is a running sum, but neither can say "raise only if we were not already
12
+ # raised, and only if the run of exceedances reaching this sample is long
13
+ # enough, where that run was reset by the last reading that was not one".
14
+ # The state is carried, the carry is not a sum, and CArray has no operation
15
+ # that composes it. So this is ordinarily a Ruby loop over every sample, and
16
+ # the samples are usually many.
17
+ #
18
+ # ruby examples/applications/alarm.rb
19
+
20
+ $LOAD_PATH.unshift(File.expand_path("../../lib", __dir__))
21
+ require "carray/jit"
22
+
23
+ SAMPLES = 2_000_000
24
+ HIGH = 70.0 # raise above this
25
+ LOW = 60.0 # and clear below this
26
+ DWELL = 5 # after this many in a row
27
+
28
+ # A series that wanders across both lines, with noise sitting on them.
29
+ random = CArray::Rng.new(seed: 20260913)
30
+ noise = CArray.double(SAMPLES)
31
+ noise.random!(rng: random)
32
+ reading = CArray.double(SAMPLES)
33
+ CArray.jit_for(SAMPLES) { |i|
34
+ slow = 64.0 + 12.0 * Math.sin(i / 90000.0) + 4.0 * Math.sin(i / 700.0)
35
+ reading[i] = slow + 6.0 * (noise[i] - 0.5)
36
+ }
37
+
38
+ state = CArray.int8(SAMPLES)
39
+
40
+ # The machine's state, as the four things it remembers. A `CScalar` is a
41
+ # value with a home, so it is still there on the next iteration -- which is
42
+ # what makes it the carry a state machine needs, and what a local cannot be.
43
+ raised = CScalar.int8()
44
+ above = CScalar.int32()
45
+ below = CScalar.int32()
46
+ episodes = CScalar.int64()
47
+
48
+ def alarm (reading, state, raised, above, below, episodes)
49
+ raised[] = 0
50
+ above[] = 0
51
+ below[] = 0
52
+ episodes[] = 0
53
+ CArray.jit_for(reading.elements) { |i|
54
+ value = reading[i]
55
+ above[] = value > HIGH ? above[] + 1 : 0
56
+ below[] = value < LOW ? below[] + 1 : 0
57
+ if raised[] == 0 && above[] >= DWELL
58
+ raised[] = 1
59
+ episodes[] += 1
60
+ elsif raised[] == 1 && below[] >= DWELL
61
+ raised[] = 0
62
+ end
63
+ state[i] = raised[]
64
+ }
65
+ end
66
+
67
+ alarm(reading, state, raised, above, below, episodes)
68
+
69
+ # What it found.
70
+ up = state.eq(1).count(1)
71
+ puts format("%d samples, %.1f%% of them above %.0f", SAMPLES,
72
+ 100.0 * reading.gt(HIGH).count(1) / SAMPLES, HIGH)
73
+ puts format(" alarm raised %d times, and was up for %.1f%% of the record",
74
+ episodes[0], 100.0 * up / SAMPLES)
75
+
76
+ # A plain threshold, for the contrast: the same record, no hysteresis and no
77
+ # dwell, which is what an array expression can say.
78
+ plain = reading.gt(HIGH).int8
79
+ plain_flips = plain[1..-1].ne(plain[0..-2]).count(1)
80
+ state_flips = state[1..-1].ne(state[0..-2]).count(1)
81
+ puts format(" a bare threshold changes its mind %d times; this alarm, %d",
82
+ plain_flips, state_flips)
83
+
84
+ # The same loop in Ruby.
85
+ def alarm_in_ruby (reading)
86
+ values = reading.to_a
87
+ state = Array.new(values.size, 0)
88
+ raised = 0
89
+ above = 0
90
+ below = 0
91
+ episodes = 0
92
+ values.each_with_index do |value, i|
93
+ above = value > HIGH ? above + 1 : 0
94
+ below = value < LOW ? below + 1 : 0
95
+ if raised == 0 && above >= DWELL
96
+ raised = 1
97
+ episodes += 1
98
+ elsif raised == 1 && below >= DWELL
99
+ raised = 0
100
+ end
101
+ state[i] = raised
102
+ end
103
+ [state, episodes]
104
+ end
105
+
106
+ in_ruby, ruby_episodes = alarm_in_ruby(reading)
107
+ puts format(" agrees with the same loop in Ruby %s",
108
+ state.to_a == in_ruby && episodes[0] == ruby_episodes)
109
+
110
+ def timed (repeats = 3)
111
+ started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
112
+ repeats.times { yield }
113
+ (Process.clock_gettime(Process::CLOCK_MONOTONIC) - started) / repeats
114
+ end
115
+
116
+ compiled = timed { alarm(reading, state, raised, above, below, episodes) }
117
+ interpreted = timed(1) { alarm_in_ruby(reading) }
118
+
119
+ puts
120
+ puts format(" %6.1f ms compiled, %6.0f ms in Ruby (%.0fx)",
121
+ compiled * 1e3, interpreted * 1e3, interpreted / compiled)