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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +771 -3
- data/README.md +7 -6
- data/carray-jit.gemspec +1 -3
- data/docs/00_Introduction.md +4 -3
- data/docs/01_GettingStarted.md +1 -1
- data/docs/02_KernelShapes.md +93 -14
- data/docs/03_SupportedFeatures.md +582 -26
- data/docs/04_Compiling.md +33 -6
- data/docs/05_DesignNotes.md +3 -3
- data/docs/06_Cheatsheet.md +198 -5
- data/docs/07_StepByStep.ja.md +534 -0
- data/docs/07_StepByStep.md +535 -0
- data/examples/README.md +12 -0
- data/examples/applications/alarm.rb +121 -0
- data/examples/applications/collatz.rb +105 -0
- data/examples/applications/cubic_spline.rb +331 -0
- data/examples/applications/dithering.rb +144 -0
- data/examples/applications/group_stats.rb +115 -0
- data/examples/applications/lookup.rb +126 -0
- data/examples/applications/median_filter.rb +153 -0
- data/examples/applications/parcel_ascent.rb +220 -0
- data/examples/applications/point_in_polygon.rb +111 -0
- data/examples/applications/random_walk.rb +98 -0
- data/examples/applications/van_der_pol.rb +186 -0
- data/examples/applications/wet_bulb.rb +140 -0
- data/examples/features/10_complex.rb +14 -4
- data/examples/features/15_loops.rb +7 -1
- data/lib/carray/jit/access.rb +14 -0
- data/lib/carray/jit/analyzer.rb +2077 -136
- data/lib/carray/jit/block_reader.rb +37 -6
- data/lib/carray/jit/c_function.rb +613 -76
- data/lib/carray/jit/c_generator.rb +1595 -156
- data/lib/carray/jit/call.rb +68 -0
- data/lib/carray/jit/compiler.rb +75 -11
- data/lib/carray/jit/kernel.rb +369 -32
- data/lib/carray/jit/node.rb +359 -9
- data/lib/carray/jit/sorting_networks.rb +182 -0
- data/lib/carray/jit/type_assignment.rb +371 -34
- data/lib/carray/jit/version.rb +1 -1
- data/lib/carray/jit.rb +560 -64
- metadata +22 -8
- data/ext/carray_jit_access/carray_jit_access.c +0 -460
- 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)
|