carray 3.0.0 → 3.0.1
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/.yardopts +2 -1
- data/CHANGELOG.md +325 -3
- data/{NEWS.md → CHANGELOG.v1.md} +1 -1
- data/README.md +32 -32
- data/carray.gemspec +12 -8
- data/ext/ca_axis_group.c +55 -19
- data/ext/ca_binop_dispatch.c +5 -6
- data/ext/ca_binop_dispatch.h +0 -7
- data/ext/ca_categorical_iterator.c +13 -13
- data/ext/ca_for_each_element.h +6 -8
- data/ext/ca_group_iter.c +1 -1
- data/ext/ca_kernel_iterator.c +69 -23
- data/ext/ca_kernel_iterator.h +3 -10
- data/ext/ca_obj_bincmp.c +57 -27
- data/ext/ca_obj_binop.c +86 -30
- data/ext/ca_obj_const_string.c +3 -1
- data/ext/ca_obj_face.c +80 -0
- data/ext/ca_obj_face.h +33 -1
- data/ext/ca_obj_fake.c +11 -0
- data/ext/ca_obj_fixlen_string.c +3 -1
- data/ext/ca_obj_grid.c +11 -3
- data/ext/ca_obj_meld.c +13 -8
- data/ext/ca_obj_moncmp.c +33 -14
- data/ext/ca_obj_monop.c +19 -7
- data/ext/ca_obj_object.c +99 -4
- data/ext/ca_obj_record.c +3 -1
- data/ext/ca_obj_refer.c +30 -6
- data/ext/ca_obj_roll.c +13 -5
- data/ext/ca_obj_select_axis.c +16 -6
- data/ext/ca_obj_shift.c +3 -3
- data/ext/ca_obj_stride.c +106 -23
- data/ext/ca_obj_string.c +3 -1
- data/ext/ca_obj_tile.c +13 -5
- data/ext/ca_obj_time.c +3 -1
- data/ext/ca_obj_timedelta.c +3 -1
- data/ext/ca_obj_transpose.c +1 -1
- data/ext/ca_obj_triop.c +62 -23
- data/ext/ca_obj_window.c +86 -17
- data/ext/ca_op_cmplx64.h +123 -0
- data/ext/ca_op_ipower.c +0 -3
- data/ext/ca_sort_kernels.h +5 -5
- data/ext/ca_sweep_engine.c +78 -35
- data/ext/ca_sweep_engine.h +13 -4
- data/ext/ca_transform_common.c +7 -0
- data/ext/carray.h +127 -130
- data/ext/carray_access.c +56 -34
- data/ext/carray_bincount.c +8 -8
- data/ext/carray_broadcast.c +99 -6
- data/ext/carray_build_flags.h +3 -0
- data/ext/carray_call_cfunc.c +1491 -0
- data/ext/carray_call_cfunc.h +153 -0
- data/ext/carray_cast.c +69 -31
- data/ext/carray_conversion.c +22 -22
- data/ext/carray_copy.c +12 -2
- data/ext/carray_core.c +57 -10
- data/ext/carray_count.c +1 -1
- data/ext/carray_element.c +7 -7
- data/ext/carray_factorize.c +28 -28
- data/ext/carray_hold.c +1 -1
- data/ext/carray_index_classifier.c +7 -21
- data/ext/carray_internal.h +19 -3
- data/ext/carray_kernels_bincmp.c +1 -0
- data/ext/carray_kernels_binop.c +320 -298
- data/ext/carray_kernels_init.c +1095 -0
- data/ext/carray_kernels_map.c +1 -0
- data/ext/carray_kernels_moncmp.c +1 -0
- data/ext/carray_kernels_monop.c +412 -411
- data/ext/carray_kernels_reduce_aggregate.c +88 -87
- data/ext/carray_kernels_reduce_boolean.c +1 -0
- data/ext/carray_kernels_reduce_cumulative.c +54 -53
- data/ext/carray_kernels_reduce_extreme.c +51 -50
- data/ext/carray_kernels_reduce_variance.c +1 -0
- data/ext/carray_kernels_scan.c +1 -0
- data/ext/carray_kernels_search.c +93 -92
- data/ext/carray_kernels_sort.c +1 -0
- data/ext/carray_kernels_triop.c +1 -0
- data/ext/carray_lazy.c +175 -5
- data/ext/carray_mask.c +6 -5
- data/ext/carray_median_percentile.c +1 -1
- data/ext/carray_memory_view.c +46 -21
- data/ext/carray_operator.c +46 -49
- data/ext/carray_partition.c +4 -4
- data/ext/carray_random.c +7 -7
- data/ext/carray_scatter.c +1 -1
- data/ext/carray_sort.c +3 -3
- data/ext/carray_sort_kernel.c +10 -10
- data/ext/carray_test.c +1 -1
- data/ext/extconf.rb +18 -0
- data/ext/mk_call_cfunc.rb +243 -52
- data/ext/mkkernel.rb +354 -100
- data/ext/ruby_carray.c +18 -16
- data/ext/version.h +4 -4
- data/lib/carray/autoload_carray.rb +19 -0
- data/lib/carray/autoload_method_extension.rb +2 -1
- data/lib/carray/axis_group.rb +7 -7
- data/lib/carray/basics.rb +59 -59
- data/lib/carray/bincount_nd.rb +12 -12
- data/lib/carray/block_iterator.rb +24 -14
- data/lib/carray/categorical.rb +4 -4
- data/lib/carray/categorical_iterator.rb +98 -76
- data/lib/carray/conditional.rb +14 -14
- data/lib/carray/construct.rb +21 -0
- data/lib/carray/core_extensions.rb +62 -5
- data/lib/carray/data_type_extension.rb +18 -1
- data/lib/carray/frame/concat.rb +3 -3
- data/lib/carray/frame/convert.rb +1 -1
- data/lib/carray/frame/frame.rb +4 -11
- data/lib/carray/frame/io.rb +159 -2
- data/lib/carray/frame/sort.rb +1 -1
- data/lib/carray/frame/verbs.rb +18 -1
- data/lib/carray/fuse_source.rb +123 -0
- data/lib/carray/fusion.rb +218 -0
- data/lib/carray/histogram.rb +16 -16
- data/lib/carray/inspect.rb +1 -7
- data/lib/carray/iterator.rb +4 -3
- data/lib/carray/lazy.rb +125 -73
- data/lib/carray/meld_reduce.rb +2 -2
- data/lib/carray/methods/align_addr.rb +1 -1
- data/lib/carray/methods/composition.rb +1 -1
- data/lib/carray/methods/is_in.rb +12 -12
- data/lib/carray/methods/locate_addr.rb +6 -1
- data/lib/carray/methods/mask_duplicates.rb +1 -1
- data/lib/carray/methods/meshgrid.rb +4 -5
- data/lib/carray/methods/mode.rb +2 -2
- data/lib/carray/methods/nunique.rb +1 -1
- data/lib/carray/methods/snap.rb +7 -2
- data/lib/carray/methods/unique.rb +3 -3
- data/lib/carray/methods/value_counts.rb +2 -2
- data/lib/carray/runtime.rb +0 -19
- data/lib/carray/slab_iterator.rb +20 -7
- data/lib/carray/string_operation_extension.rb +5 -5
- data/lib/carray/time.rb +890 -491
- data/lib/carray/window_iterator.rb +285 -13
- data/lib/carray.rb +5 -5
- data/yard-stubs/ca_obj_array.rb +385 -0
- data/yard-stubs/ca_obj_bitarray.rb +38 -0
- data/yard-stubs/ca_obj_bitfield.rb +43 -0
- data/yard-stubs/ca_obj_block.rb +73 -0
- data/yard-stubs/ca_obj_byte_swap.rb +56 -0
- data/yard-stubs/ca_obj_fake.rb +31 -0
- data/yard-stubs/ca_obj_farray.rb +32 -0
- data/yard-stubs/ca_obj_field.rb +45 -0
- data/yard-stubs/ca_obj_grid.rb +35 -0
- data/yard-stubs/ca_obj_refer.rb +72 -0
- data/yard-stubs/ca_obj_roll.rb +45 -0
- data/yard-stubs/ca_obj_shift.rb +43 -0
- data/yard-stubs/ca_obj_stride.rb +181 -0
- data/yard-stubs/ca_obj_tile.rb +29 -0
- data/yard-stubs/ca_obj_transpose.rb +40 -0
- data/yard-stubs/ca_obj_window.rb +49 -0
- data/yard-stubs/carray_access.rb +131 -0
- data/yard-stubs/carray_attribute.rb +246 -0
- data/yard-stubs/carray_broadcast.rb +37 -0
- data/yard-stubs/carray_cast.rb +489 -0
- data/yard-stubs/carray_class.rb +65 -0
- data/yard-stubs/carray_conversion.rb +76 -0
- data/yard-stubs/carray_copy.rb +79 -0
- data/yard-stubs/carray_core.rb +114 -0
- data/yard-stubs/carray_count.rb +79 -0
- data/yard-stubs/carray_element.rb +108 -0
- data/yard-stubs/carray_generate.rb +66 -0
- data/yard-stubs/carray_lazy.rb +23 -0
- data/yard-stubs/carray_loop.rb +140 -0
- data/yard-stubs/carray_mask.rb +259 -0
- data/yard-stubs/carray_math.rb +132 -0
- data/yard-stubs/carray_mathfunc.rb +45 -0
- data/yard-stubs/carray_median_percentile.rb +89 -0
- data/yard-stubs/carray_memory_view.rb +163 -0
- data/yard-stubs/carray_order.rb +312 -0
- data/yard-stubs/carray_random.rb +89 -0
- data/yard-stubs/carray_scatter.rb +106 -0
- data/yard-stubs/carray_slab.rb +57 -0
- data/yard-stubs/carray_sort.rb +163 -0
- data/yard-stubs/carray_test.rb +85 -0
- data/yard-stubs/carray_undef.rb +64 -0
- data/yard-stubs/carray_utils.rb +97 -0
- data/yard-stubs/ruby_carray.rb +193 -0
- metadata +59 -10
- data/ext/ca_obj_unbound_repeat.c +0 -496
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
# margins filled by the boundary policy), take its `sliding_windows` view
|
|
19
19
|
# (a pure strided view over the padded buffer), and run a core reduction over
|
|
20
20
|
# the trailing window axes. One vectorized pass; the named reductions
|
|
21
|
-
# delegate straight to the core reduction, so their
|
|
21
|
+
# delegate straight to the core reduction, so their data type / mask / empty
|
|
22
22
|
# (ERI) / epsilon contracts are the core's, unchanged. This replaces the 2.0
|
|
23
23
|
# per-anchor C engine (ext/ca_iter_window.c, retired).
|
|
24
24
|
#
|
|
@@ -116,12 +116,21 @@ class CAWindowIterator < CAIterator
|
|
|
116
116
|
# Trailing window axes of the sliding_windows view: [ndim .. 2*ndim-1].
|
|
117
117
|
@window_axes = (@sndim...(2 * @sndim)).to_a
|
|
118
118
|
|
|
119
|
-
# Output iteration space (reference-shaped, except :truncate which
|
|
119
|
+
# Output iteration space (reference-shaped, except :truncate, which keeps
|
|
120
|
+
# only the anchors whose window lies wholly inside the source), and where
|
|
121
|
+
# each axis's first window starts in the buffer the windows are read from.
|
|
122
|
+
# A window that does not cover its own anchor -- `windows(1..2)`, the two
|
|
123
|
+
# cells after this one -- starts further along than the margin allows for,
|
|
124
|
+
# which is what @origins carries.
|
|
120
125
|
rshape = @source.shape
|
|
121
126
|
if @bounds == :truncate
|
|
122
|
-
|
|
127
|
+
first = @ranges.map { |r| [0, -r.begin].max }
|
|
128
|
+
last = @sndim.times.map { |i| [rshape[i] - 1, rshape[i] - 1 - @ranges[i].end].min }
|
|
129
|
+
@shape = @sndim.times.map { |i| [last[i] - first[i] + 1, 0].max }
|
|
130
|
+
@origins = @ranges.map { |r| [r.begin, 0].max }
|
|
123
131
|
else
|
|
124
132
|
@shape = rshape.dup
|
|
133
|
+
@origins = @sndim.times.map { |i| @lefts[i] + @ranges[i].begin }
|
|
125
134
|
end
|
|
126
135
|
@ndim = @shape.size
|
|
127
136
|
self
|
|
@@ -149,11 +158,23 @@ class CAWindowIterator < CAIterator
|
|
|
149
158
|
# the padded entity.
|
|
150
159
|
# @return [CArray]
|
|
151
160
|
def sliding_view
|
|
152
|
-
@sliding_view ||=
|
|
161
|
+
@sliding_view ||= anchored_buffer.sliding_windows(*@widths)
|
|
153
162
|
end
|
|
154
163
|
|
|
155
164
|
private
|
|
156
165
|
|
|
166
|
+
# The stretch of the padded buffer the windows are taken from: it begins at
|
|
167
|
+
# the first window and holds one window per anchor. For a window that covers
|
|
168
|
+
# its own anchor and a boundary policy that pads, that is the whole buffer.
|
|
169
|
+
def anchored_buffer
|
|
170
|
+
buffer = padded_entity
|
|
171
|
+
wanted = @ndim.times.map { |k| @origins[k]...(@origins[k] + @shape[k] + @widths[k] - 1) }
|
|
172
|
+
return buffer if wanted.each_with_index.all? { |range, k|
|
|
173
|
+
range.begin.zero? && range.end == buffer.dim[k]
|
|
174
|
+
}
|
|
175
|
+
buffer[*wanted]
|
|
176
|
+
end
|
|
177
|
+
|
|
157
178
|
# Build the padded entity (or, for :truncate, the source itself). Memoised.
|
|
158
179
|
def padded_entity
|
|
159
180
|
@padded_entity ||=
|
|
@@ -237,6 +258,27 @@ class CAWindowIterator < CAIterator
|
|
|
237
258
|
end
|
|
238
259
|
end
|
|
239
260
|
|
|
261
|
+
# How many of one axis's window offsets fall inside the source, per anchor.
|
|
262
|
+
# A window away from the edges holds all of them, so the vector is the window
|
|
263
|
+
# width everywhere but the two ends, and only the ends -- at most one window
|
|
264
|
+
# width of cells each -- are counted out.
|
|
265
|
+
def axis_cell_counts (k)
|
|
266
|
+
length = @shape[k]
|
|
267
|
+
first = @ranges[k].begin
|
|
268
|
+
last = @ranges[k].end
|
|
269
|
+
counts = CArray.int64(length)
|
|
270
|
+
counts[] = @widths[k]
|
|
271
|
+
truncated_at_the_start = [[-first, 0].max, length].min
|
|
272
|
+
truncated_at_the_end = [[length - last, 0].max, 0].max
|
|
273
|
+
indices = (0...truncated_at_the_start).to_a | (truncated_at_the_end...length).to_a
|
|
274
|
+
indices.each do |i|
|
|
275
|
+
low = [first, -i].max
|
|
276
|
+
high = [last, length - 1 - i].min
|
|
277
|
+
counts[i] = [high - low + 1, 0].max
|
|
278
|
+
end
|
|
279
|
+
counts
|
|
280
|
+
end
|
|
281
|
+
|
|
240
282
|
# An index list of length `nd` that is `nil` (full range) on every axis
|
|
241
283
|
# except `ax`, which is pinned to `k`.
|
|
242
284
|
def axis_selector (nd, ax, k)
|
|
@@ -251,7 +293,7 @@ class CAWindowIterator < CAIterator
|
|
|
251
293
|
#
|
|
252
294
|
# A per-window fold to one value over the trailing window axes is exactly a
|
|
253
295
|
# core per-axis reduction over those axes, so every reduction delegates to
|
|
254
|
-
# `sliding_view.<op>(axis: window_axes, ...)`. This inherits the core
|
|
296
|
+
# `sliding_view.<op>(axis: window_axes, ...)`. This inherits the core data type,
|
|
255
297
|
# mask, empty / all-masked (identity vs UNDEF) and epsilon-close contracts
|
|
256
298
|
# unchanged. `min_count:` / `fill_value:` pass straight to the core (the
|
|
257
299
|
# boundary strictness + result fill knobs).
|
|
@@ -259,17 +301,28 @@ class CAWindowIterator < CAIterator
|
|
|
259
301
|
# @overload sum(min_count: nil, fill_value: nil)
|
|
260
302
|
# Rolling sum, delegating to `sliding_view.sum(axis: window_axes)`.
|
|
261
303
|
# @return [CArray] reference-shaped (or shrunk, for :truncate)
|
|
304
|
+
# @overload accumulate(min_count: nil, fill_value: nil)
|
|
305
|
+
# Rolling sum in the source's own data type, wrapping at its width, as the
|
|
306
|
+
# core `accumulate` does. `sum` answers in the type the core promotes to
|
|
307
|
+
# (float64 for integers), which for a window over bytes moves eight times
|
|
308
|
+
# the bytes; this is the spelling for staying in the type when the window
|
|
309
|
+
# cannot overflow it.
|
|
310
|
+
# @return [CArray] reference-shaped (or shrunk, for :truncate)
|
|
262
311
|
# The rest are analogous: prod / mean / min / max, sample and population
|
|
263
312
|
# variance / stddev, all / any, fused minmax, and the window-local position
|
|
264
313
|
# min_index / max_index (index within the window axes).
|
|
265
|
-
[:sum, :prod, :mean, :min, :max, :variance, :stddev, :all, :any,
|
|
314
|
+
[:sum, :accumulate, :prod, :mean, :min, :max, :variance, :stddev, :all, :any,
|
|
266
315
|
:variancep, :stddevp, :minmax, :min_index, :max_index].each do |op|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
316
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
317
|
+
def #{op} (min_count: nil, fill_value: nil)
|
|
318
|
+
folded = fold_by_offset(:#{op}, min_count, fill_value)
|
|
319
|
+
return folded unless folded.nil?
|
|
320
|
+
kw = {}
|
|
321
|
+
kw[:min_count] = min_count unless min_count.nil?
|
|
322
|
+
kw[:fill_value] = fill_value unless fill_value.nil?
|
|
323
|
+
sliding_view.#{op}(axis: @window_axes, **kw)
|
|
324
|
+
end
|
|
325
|
+
RUBY
|
|
273
326
|
end
|
|
274
327
|
|
|
275
328
|
# @overload min_addr
|
|
@@ -290,6 +343,225 @@ class CAWindowIterator < CAIterator
|
|
|
290
343
|
|
|
291
344
|
private
|
|
292
345
|
|
|
346
|
+
# ---- folding by offset instead of by anchor ---------------------------
|
|
347
|
+
#
|
|
348
|
+
# Delegating to `sliding_view.<op>(axis: window_axes)` folds the window axes,
|
|
349
|
+
# which are the innermost ones, so the core pays its per-fiber setup once per
|
|
350
|
+
# output cell -- about 10 ns, whatever the window holds. For a 3x3 window
|
|
351
|
+
# that setup is nine tenths of the time.
|
|
352
|
+
#
|
|
353
|
+
# The same fold can be run the other way round: walk the window offsets, and
|
|
354
|
+
# for each one add the whole shifted plane into an accumulator. Then the
|
|
355
|
+
# setup is paid once per offset rather than once per output cell, and every
|
|
356
|
+
# pass is a straight walk over contiguous memory. It costs one pass per
|
|
357
|
+
# offset, so it only pays while the window is small; the crossover moves with
|
|
358
|
+
# rank and data type, but stays above width 5 on every axis in every
|
|
359
|
+
# combination measured (see devel/PROPOSAL_WINDOW_OFFSET_ACCUMULATION.md).
|
|
360
|
+
#
|
|
361
|
+
# What is decided here is the order of the fold and which pad it reads; the
|
|
362
|
+
# arithmetic is the core's elementwise kernel, and the result data type is
|
|
363
|
+
# the core's answer for the same reduction (asked below, so it cannot drift).
|
|
364
|
+
|
|
365
|
+
# The in-place elementwise kernel that accumulates one offset, per operation.
|
|
366
|
+
# An operation absent here has no identity to accumulate from and always
|
|
367
|
+
# takes the delegating path.
|
|
368
|
+
OFFSET_FOLD = { :sum => :add!, :accumulate => :add!,
|
|
369
|
+
:prod => :mul!,
|
|
370
|
+
:min => :pmin!, :max => :pmax!,
|
|
371
|
+
:all => :and!, :any => :or! }.freeze
|
|
372
|
+
|
|
373
|
+
# Widest window this path takes on any one axis.
|
|
374
|
+
OFFSET_FOLD_MAX_WIDTH = 5
|
|
375
|
+
|
|
376
|
+
# Runs the fold by offset, or returns nil when this window is not one it can
|
|
377
|
+
# answer for -- in which case the caller delegates as before.
|
|
378
|
+
def fold_by_offset (op, min_count, fill_value = nil)
|
|
379
|
+
return nil unless op == :mean || OFFSET_FOLD.key?(op)
|
|
380
|
+
return nil if @widths.any? { |width| width > OFFSET_FOLD_MAX_WIDTH }
|
|
381
|
+
return nil if neutral_value(op).nil?
|
|
382
|
+
|
|
383
|
+
folded = op == :mean ? fold_mean : accumulate_offsets(op)
|
|
384
|
+
folded = mask_empty_windows(folded) if EMPTY_WINDOW_IS_UNDEFINED.include?(op)
|
|
385
|
+
folded = apply_min_count(folded, min_count)
|
|
386
|
+
# A `fill_value:` on the call replaces a result that came out undefined.
|
|
387
|
+
folded = folded.strip_mask(fill_value) if !fill_value.nil? && folded.has_mask?
|
|
388
|
+
folded
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
# The mean is the sum over the same offsets, divided by the number of cells
|
|
392
|
+
# each window folded. A window that folded nothing divides by one here and
|
|
393
|
+
# is masked out by {#mask_empty_windows} after.
|
|
394
|
+
def fold_mean
|
|
395
|
+
counts = window_cell_counts
|
|
396
|
+
divisor = counts
|
|
397
|
+
if counts.is_a?(CArray) && counts.min.zero?
|
|
398
|
+
divisor = counts.copy
|
|
399
|
+
divisor[counts.eq(0)] = 1
|
|
400
|
+
end
|
|
401
|
+
accumulate_offsets(:sum).to_type(offset_fold_data_type(:mean)) / divisor
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# Walks the window offsets, accumulating each shifted plane into the result.
|
|
405
|
+
def accumulate_offsets (op, base = nil, kernel = nil, data_type = nil)
|
|
406
|
+
base ||= offset_fold_base(op)
|
|
407
|
+
kernel ||= OFFSET_FOLD.fetch(op)
|
|
408
|
+
data_type ||= offset_fold_data_type(op)
|
|
409
|
+
accumulator = nil
|
|
410
|
+
offset_grid.each do |offset|
|
|
411
|
+
plane = base[*@ndim.times.map { |k|
|
|
412
|
+
start = @origins[k] + offset[k]
|
|
413
|
+
start...(start + @shape[k])
|
|
414
|
+
}]
|
|
415
|
+
if accumulator.nil?
|
|
416
|
+
accumulator = CArray.new(data_type, @shape)
|
|
417
|
+
accumulator[] = plane
|
|
418
|
+
else
|
|
419
|
+
accumulator.send(kernel, plane)
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
accumulator
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
# These have no value to give for a window that folded nothing; the ones not
|
|
426
|
+
# listed answer with their identity, which the accumulation already holds.
|
|
427
|
+
EMPTY_WINDOW_IS_UNDEFINED = [:min, :max, :mean].freeze
|
|
428
|
+
|
|
429
|
+
def mask_empty_windows (folded)
|
|
430
|
+
return folded unless windows_can_be_empty?
|
|
431
|
+
folded[window_cell_counts.eq(0)] = UNDEF
|
|
432
|
+
folded
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# Whether any window can come out holding nothing at all. A masked source
|
|
436
|
+
# can leave one empty anywhere. Without one, it takes a window that reaches
|
|
437
|
+
# past the array and does not cover its own anchor -- `windows(1..2)` at the
|
|
438
|
+
# far edge -- and the count on an axis falls away towards its ends, so the
|
|
439
|
+
# two ends are the only places to look.
|
|
440
|
+
def windows_can_be_empty?
|
|
441
|
+
return true if @source.has_mask?
|
|
442
|
+
return false if window_cell_counts.is_a?(Integer)
|
|
443
|
+
@ndim.times.any? do |k|
|
|
444
|
+
along = axis_cell_counts(k)
|
|
445
|
+
along[0].zero? || along[@shape[k] - 1].zero?
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
# `min_count` asks for a result only where the window held that many cells.
|
|
450
|
+
def apply_min_count (folded, min_count)
|
|
451
|
+
return folded if min_count.nil?
|
|
452
|
+
counts = window_cell_counts
|
|
453
|
+
if counts.is_a?(Integer)
|
|
454
|
+
folded[] = UNDEF if counts < min_count
|
|
455
|
+
else
|
|
456
|
+
folded[counts.lt(min_count)] = UNDEF
|
|
457
|
+
end
|
|
458
|
+
folded
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
# What stands in for a cell the fold must not see: a margin the boundary
|
|
462
|
+
# policy does not fill, or a masked cell of the source. For sum, prod, all
|
|
463
|
+
# and any that is the operation's identity. For min and max it is any value
|
|
464
|
+
# that cannot win, and the array's own extreme is one -- so no table of
|
|
465
|
+
# per-type limits is needed. Nil means there is none to be had (a source
|
|
466
|
+
# masked everywhere), and the caller delegates instead.
|
|
467
|
+
def neutral_value (op)
|
|
468
|
+
@neutral_value ||= {}
|
|
469
|
+
return @neutral_value[op] if @neutral_value.key?(op)
|
|
470
|
+
@neutral_value[op] =
|
|
471
|
+
case op
|
|
472
|
+
when :sum, :accumulate, :mean then 0
|
|
473
|
+
when :prod then 1
|
|
474
|
+
when :all then true
|
|
475
|
+
when :any then false
|
|
476
|
+
when :min then source_extreme(:max)
|
|
477
|
+
when :max then source_extreme(:min)
|
|
478
|
+
end
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
def source_extreme (op)
|
|
482
|
+
value = @source.send(op)
|
|
483
|
+
value.equal?(UNDEF) ? nil : value
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
# The buffer the offsets are read from: the source with its margins filled
|
|
487
|
+
# per the boundary policy, with any masked cell replaced by the neutral value
|
|
488
|
+
# -- an accumulation propagates a mask where the fold would skip it -- and in
|
|
489
|
+
# the type the result is accumulated in. Adding across two types runs a
|
|
490
|
+
# different kernel from adding within one, and how much slower that is
|
|
491
|
+
# depends on the pair, the working set and the compiler; converting once is
|
|
492
|
+
# one behaviour everywhere. It costs a buffer in the wider type, which for
|
|
493
|
+
# a `sum` over bytes is the one case where it does not pay.
|
|
494
|
+
def offset_fold_base (op)
|
|
495
|
+
@offset_fold_base ||= {}
|
|
496
|
+
@offset_fold_base[op] ||=
|
|
497
|
+
begin
|
|
498
|
+
padded =
|
|
499
|
+
if @source.has_mask?
|
|
500
|
+
pad_for(@source.strip_mask(neutral_value(op)), neutral_value(op))
|
|
501
|
+
elsif @bounds == :skip
|
|
502
|
+
pad_source(@source, @lefts, @rights, :constant, neutral_value(op))
|
|
503
|
+
else
|
|
504
|
+
padded_entity
|
|
505
|
+
end
|
|
506
|
+
wanted = offset_fold_data_type(op)
|
|
507
|
+
padded.data_type == wanted ? padded : padded.to_type(wanted)
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
# Pads `values` the way this window's boundary policy says, with `outside`
|
|
512
|
+
# standing in for the margin where the policy does not fill one.
|
|
513
|
+
def pad_for (values, outside)
|
|
514
|
+
case @bounds
|
|
515
|
+
when :truncate then values
|
|
516
|
+
when :nearest then pad_source(values, @lefts, @rights, :edge, nil)
|
|
517
|
+
when :constant then pad_source(values, @lefts, @rights, :constant, @fill_value)
|
|
518
|
+
else pad_source(values, @lefts, @rights, :constant, outside)
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
# How many cells each window holds. With an unmasked source this follows
|
|
523
|
+
# from the geometry: every boundary policy but `:skip` fills its margins with
|
|
524
|
+
# real values, so the count is the whole window and one Integer says it;
|
|
525
|
+
# `:skip` counts the in-bounds offsets, which is separable -- the count on
|
|
526
|
+
# one axis depends on that axis alone. A masked source has to be counted for
|
|
527
|
+
# real, by accumulating over the same offsets.
|
|
528
|
+
def window_cell_counts
|
|
529
|
+
return @window_cell_counts unless @window_cell_counts.nil?
|
|
530
|
+
@window_cell_counts =
|
|
531
|
+
if @source.has_mask?
|
|
532
|
+
accumulate_offsets(:sum, pad_for(@source.is_not_masked.int64, 0),
|
|
533
|
+
:add!, CA_INT64)
|
|
534
|
+
elsif @bounds != :skip
|
|
535
|
+
@widths.inject(:*)
|
|
536
|
+
else
|
|
537
|
+
counts = CArray.int64(*@shape)
|
|
538
|
+
counts[] = 1
|
|
539
|
+
@ndim.times do |k|
|
|
540
|
+
shape = Array.new(@ndim, 1)
|
|
541
|
+
shape[k] = @shape[k]
|
|
542
|
+
counts.mul!(axis_cell_counts(k).reshape(*shape))
|
|
543
|
+
end
|
|
544
|
+
counts
|
|
545
|
+
end
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
# The data type the delegating path would have produced, asked of the core
|
|
549
|
+
# itself so the two paths cannot disagree.
|
|
550
|
+
def offset_fold_data_type (op)
|
|
551
|
+
@offset_fold_data_type ||= {}
|
|
552
|
+
@offset_fold_data_type[op] ||=
|
|
553
|
+
CArray.new(@source.data_type, [1, 1]).send(op, axis: [1]).data_type
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
# Every offset within the window, as a list of per-axis positions into the
|
|
557
|
+
# padded buffer.
|
|
558
|
+
def offset_grid
|
|
559
|
+
@offset_grid ||=
|
|
560
|
+
@widths.map { |width| (0...width).to_a }
|
|
561
|
+
.inject { |grid, axis| grid.product(axis).map { |pair| Array(pair).flatten } }
|
|
562
|
+
.map { |offset| Array(offset) }
|
|
563
|
+
end
|
|
564
|
+
|
|
293
565
|
# Source address of the per-anchor winner. The window-local flat index
|
|
294
566
|
# (min_index / max_index) decomposes into per-axis window coordinates; the
|
|
295
567
|
# source coordinate on each axis is anchor + offset + window-coordinate (the
|
|
@@ -303,7 +575,7 @@ class CAWindowIterator < CAIterator
|
|
|
303
575
|
(@sndim - 1).downto(0) { |i| wstride[i] = acc; acc *= @widths[i] }
|
|
304
576
|
sstride = Array.new(@sndim); acc = 1
|
|
305
577
|
(@sndim - 1).downto(0) { |i| sstride[i] = acc; acc *= n[i] }
|
|
306
|
-
lo = (@bounds == :truncate) ?
|
|
578
|
+
lo = (@bounds == :truncate) ? @origins : @ranges.map(&:begin)
|
|
307
579
|
addr = CArray.int64(*@shape); addr[] = 0
|
|
308
580
|
oob = CArray.boolean(*@shape); oob[] = 0
|
|
309
581
|
(0...@sndim).each do |i|
|
data/lib/carray.rb
CHANGED
|
@@ -32,11 +32,11 @@ require 'carray/math'
|
|
|
32
32
|
# carray/clip_cast CIFY (2026-06-23): translated to ext/carray_cast.c
|
|
33
33
|
require 'carray/complex' # real / imag accessors; MUST precede carray/lazy
|
|
34
34
|
# (lazy aliases real/imag at load time)
|
|
35
|
-
require 'carray/lazy' #
|
|
35
|
+
require 'carray/lazy' # the lazy elementwise view layer
|
|
36
36
|
# carray/face.rb was deleted (the Phase 1 skeleton CArray::Face module
|
|
37
37
|
# became dead weight, replaced by C-layer macro deploy +
|
|
38
38
|
# copy_state/storage_to_scalar convention).
|
|
39
|
-
require 'carray/time' # CATime / CATimedelta
|
|
39
|
+
require 'carray/time' # CATime / CATimedelta
|
|
40
40
|
# carray/methods/* (bincount / broadcast / gather_nd+put_nd) are small
|
|
41
41
|
# single-feature method files, loaded lazily via autoload_carray.
|
|
42
42
|
require 'carray/iterator'
|
|
@@ -44,9 +44,9 @@ require 'carray/iterator'
|
|
|
44
44
|
# stubs). Nothing in the eager core references CAStruct/CAUnion, so programs
|
|
45
45
|
# that only use numeric arrays never pay its load cost.
|
|
46
46
|
require 'carray/string_operation_extension' # shared StringOperationMixin (must precede the Faces)
|
|
47
|
-
require 'carray/const_string' #
|
|
48
|
-
require 'carray/string' #
|
|
49
|
-
require 'carray/fixlen_string' #
|
|
47
|
+
require 'carray/const_string' # CAConstString construction surface
|
|
48
|
+
require 'carray/string' # CAString construction surface
|
|
49
|
+
require 'carray/fixlen_string' # CAFixlenString construction surface
|
|
50
50
|
# CArray.format lives in carray/methods/string_format.rb, autoloaded on first call
|
|
51
51
|
|
|
52
52
|
# carray/frame is loaded lazily via autoload_carray (CAFrame / GroupedFrame
|