Class: CAWindowIterator
- Inherits:
-
CAIterator
- Object
- CAIterator
- CAWindowIterator
- Defined in:
- lib/carray/window_iterator.rb
Overview
Rolling (sliding-window) reduction dispatcher — the Window member of the
iterator family (sibling of CASlabIterator / CABlockIterator /
CACategoricalIterator). It folds an overlapping window centred on every
anchor cell, so the result is shaped like the source rather than reduced.
Obtained from CArray#windows, not constructed directly.
Instance Attribute Summary collapse
-
#bounds ⇒ Symbol
readonly
Returns the boundary policy symbol.
-
#source ⇒ CArray
readonly
Returns the array being rolled over.
Attributes inherited from CAIterator
Instance Method Summary collapse
-
#convolve(kernel, min_count: nil, fill_value: nil) ⇒ CArray
Rolling convolution
out[i] = Σ_j a[i-j]·k[j](true convolution: the kernel is flipped on every window axis). -
#correlate(kernel, min_count: nil, fill_value: nil) ⇒ CArray
Rolling cross-correlation
out[i] = Σ_j a[i+j]·k[j](kernel not flipped). -
#count(v = <none>) ⇒ CArray
Rolling count over the window.
-
#count_masked ⇒ CArray
Rolling count of masked cells.
-
#count_not_masked ⇒ CArray
Rolling count of present (non-masked) cells -- the denominator of a renormalizing convolution.
-
#each({ |window| ... }) {|window| ... } ⇒ Enumerator, self
Yields each anchor's window as a CArray.
-
#elements ⇒ CArray
Window cell count (structural, mask-independent): the constant window size
Π w_i, shaped like the output. -
#initialize(source, ranges, bounds: :skip, fill_value: nil) ⇒ CAWindowIterator
constructor
Builds a window iterator over
sourcewith a per-axis offset range. -
#map ⇒ Object
Not supported for a window iterator: overlapping windows share cells, so an element-wise transform has no well-defined scatter-back.
-
#max_addr ⇒ CArray
Rolling flat source address of the window maximum.
-
#median ⇒ CArray
Rolling median.
-
#min_addr ⇒ CArray
Rolling flat SOURCE address of the window minimum — which source cell holds it, so
source.reshape(source.elements)[sw.min_addr]are the window minima. - #op ⇒ Object
-
#percentile(*pers) ⇒ CArray+
Rolling percentile(s).
-
#quantile ⇒ Array<CArray>
Rolling five-number summary
[min, Q1, median, Q3, max](five CArrays), asCArray#quantile. - #reduce(*args, data_type: nil, &blk) ⇒ Object
-
#sliding_view ⇒ CArray
Returns the sliding_windows view feeding the reductions.
-
#sort_addr ⇒ Object
Not supported for a window iterator: a window's boundary cells are padding with no source address, and overlapping windows share cells, so a per-window sort returning source flat addresses is ill-defined.
-
#wmean(weights) ⇒ CArray
Rolling weighted mean,
weightsshaped like a single window. -
#wsum(weights) ⇒ CArray
Rolling weighted sum,
weightsshaped like a single window.
Constructor Details
#initialize(source, ranges, bounds: :skip, fill_value: nil) ⇒ CAWindowIterator
Returns a new instance of CAWindowIterator.
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/carray/window_iterator.rb', line 83 def initialize (source, *ranges, bounds: :skip, fill_value: nil) if source.is_a?(CArray) && source.obj_type == CA_OBJ_WINDOW # Backward-compat: read geometry from a CAWindow view built by #window. # start[i] = lo, dim[i] (window width) = w, so hi = lo + w - 1. win = source @source = win.parent widths = win.count @ranges = win.start.each_with_index.map { |lo, i| lo..(lo + widths[i] - 1) } # The legacy #window default is FILL (constant), whose value is the # view's fill_value; map that to a :constant margin. @bounds = :constant @fill_value = win.fill_value else @source = source @ranges = ranges.flatten(0) @bounds = bounds @fill_value = fill_value end unless @ranges.size == @source.ndim raise ArgumentError, "windows: expected #{@source.ndim} ranges (one per axis), " \ "got #{@ranges.size}" end @sndim = @source.ndim @widths = @ranges.map { |r| r.end - r.begin + 1 } @lefts = @ranges.map { |r| [0, -r.begin].max } # left margin per axis @rights = @ranges.map { |r| [0, r.end ].max } # right margin per axis # A constant fill_value: overrides :skip (constant margin escape hatch). @bounds = :constant if @fill_value != nil && @bounds == :skip # Trailing window axes of the sliding_windows view: [ndim .. 2*ndim-1]. @window_axes = (@sndim...(2 * @sndim)).to_a # Output iteration space (reference-shaped, except :truncate which shrinks). rshape = @source.shape if @bounds == :truncate @shape = @sndim.times.map { |i| rshape[i] - @widths[i] + 1 } else @shape = rshape.dup end @ndim = @shape.size self end |
Instance Attribute Details
#bounds ⇒ Symbol (readonly)
138 139 140 |
# File 'lib/carray/window_iterator.rb', line 138 def bounds @bounds end |
#source ⇒ CArray (readonly)
133 134 135 |
# File 'lib/carray/window_iterator.rb', line 133 def source @source end |
Instance Method Details
#convolve(kernel, min_count: nil, fill_value: nil) ⇒ CArray
410 411 412 |
# File 'lib/carray/window_iterator.rb', line 410 def convolve (kernel, min_count: nil, fill_value: nil) correlate(reverse_all_axes(kernel), min_count: min_count, fill_value: fill_value) end |
#correlate(kernel, min_count: nil, fill_value: nil) ⇒ CArray
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 |
# File 'lib/carray/window_iterator.rb', line 383 def correlate (kernel, min_count: nil, fill_value: nil) unless kernel.shape == @widths raise ArgumentError, "correlate: kernel shape #{kernel.shape.inspect} != " \ "window shape #{@widths.inspect}" end sv = sliding_view # Explicit broadcast of the kernel over the anchor axes: reshape to # 1 on every anchor axis, kernel width on every window axis (CArray forbids # implicit cross-ndim broadcast, so the shape is made explicit). kshape = ([1] * @sndim) + @widths # The product routes operand promotion through the single-source binop # coercion (result_type), so a float kernel over an int source promotes to # float instead of truncating the weights. Do not coerce the kernel here. prod = sv * kernel.reshape(*kshape) kw = {} kw[:min_count] = min_count unless min_count.nil? kw[:fill_value] = fill_value unless fill_value.nil? prod.sum(axis: @window_axes, **kw) end |
#count(v = <none>) ⇒ CArray
334 335 336 337 338 339 |
# File 'lib/carray/window_iterator.rb', line 334 def count (*args) return count_not_masked if args.empty? # The sliding_windows view is a CAStride, so its #count is not shadowed; # dispatch CArray#count explicitly anyway, matching the family regularity. CArray.instance_method(:count).bind_call(sliding_view, *args, axis: @window_axes) end |
#count_masked ⇒ CArray
352 353 354 |
# File 'lib/carray/window_iterator.rb', line 352 def count_masked sliding_view.count_masked(axis: @window_axes) end |
#count_not_masked ⇒ CArray
345 346 347 |
# File 'lib/carray/window_iterator.rb', line 345 def count_not_masked sliding_view.count_not_masked(axis: @window_axes) end |
#each({ |window| ... }) {|window| ... } ⇒ Enumerator, self
540 541 542 543 544 545 546 |
# File 'lib/carray/window_iterator.rb', line 540 def each return to_enum(:each) unless block_given? sv = sliding_view nils = Array.new(@sndim, nil) # full window on the trailing axes each_anchor_index { |idx| yield sv[*idx, *nils] } self end |
#elements ⇒ CArray
360 361 362 363 364 365 366 367 |
# File 'lib/carray/window_iterator.rb', line 360 def elements sz = @widths.inject(1) { |p, w| p * w } # count_not_masked gives the correct output shape (and is not shadowed); # overwrite with the constant window size. out = sliding_view.count_not_masked(axis: @window_axes) out[] = sz out end |
#map ⇒ Object
583 584 585 586 587 588 |
# File 'lib/carray/window_iterator.rb', line 583 def map (*) raise NotImplementedError, "#{self.class} has no map: overlapping windows share cells, so an " \ "element-wise scatter-back is ill-defined; use reduce for a custom " \ "per-window fold." end |
#max_addr ⇒ CArray
289 |
# File 'lib/carray/window_iterator.rb', line 289 def max_addr; window_winner_addr(:max_index); end |
#median ⇒ CArray
443 444 445 |
# File 'lib/carray/window_iterator.rb', line 443 def median order_stat { |view, axis| view.median(axis: axis) } end |
#min_addr ⇒ CArray
284 |
# File 'lib/carray/window_iterator.rb', line 284 def min_addr; window_winner_addr(:min_index); end |
#cumsum ⇒ Object #cumprod ⇒ Object #cummax ⇒ Object #cummin ⇒ Object #cumcount ⇒ Object
265 266 267 268 269 270 271 272 273 |
# File 'lib/carray/window_iterator.rb', line 265 [:sum, :prod, :mean, :min, :max, :variance, :stddev, :all, :any, :variancep, :stddevp, :minmax, :min_index, :max_index].each do |op| define_method(op) do |min_count: nil, fill_value: nil| kw = {} kw[:min_count] = min_count unless min_count.nil? kw[:fill_value] = fill_value unless fill_value.nil? sliding_view.send(op, axis: @window_axes, **kw) end end |
#percentile(*pers) ⇒ CArray+
452 453 454 |
# File 'lib/carray/window_iterator.rb', line 452 def percentile (*pers) order_stat { |view, axis| view.percentile(*pers, axis: axis) } end |
#quantile ⇒ Array<CArray>
460 461 462 |
# File 'lib/carray/window_iterator.rb', line 460 def quantile order_stat { |view, axis| view.quantile(axis: axis) } end |
#reduce({ |window| ... }) {|window| ... } ⇒ CArray #reduce(init) ⇒ CArray
559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 |
# File 'lib/carray/window_iterator.rb', line 559 def reduce (*args, data_type: nil, &blk) raise LocalJumpError, "no block given (yield)" unless blk dt = data_type || CA_OBJECT out = CArray.new(dt, @shape) sv = sliding_view nils = Array.new(@sndim, nil) # full window on the trailing axes if args.empty? each_anchor_index { |idx| out[*idx] = blk.call(sv[*idx, *nils]) } else init = args[0] each_anchor_index do |idx| acc = init sv[*idx, *nils].each { |e| acc = blk.call(acc, e) } out[*idx] = acc end end out end |
#sliding_view ⇒ CArray
151 152 153 |
# File 'lib/carray/window_iterator.rb', line 151 def sliding_view @sliding_view ||= padded_entity.sliding_windows(*@widths) end |
#sort_addr ⇒ Object
597 598 599 600 601 602 |
# File 'lib/carray/window_iterator.rb', line 597 def sort_addr (*) raise NotImplementedError, "#{self.class} has no sort_addr: padded boundary cells have no " \ "source address and overlapping windows share cells, so a per-window " \ "sort of source addresses is ill-defined." end |
#wmean(weights) ⇒ CArray
502 503 504 |
# File 'lib/carray/window_iterator.rb', line 502 def wmean (weights) weighted(weights) { |sv, w, axis| sv.wmean(w, axis: axis) } end |
#wsum(weights) ⇒ CArray
495 496 497 |
# File 'lib/carray/window_iterator.rb', line 495 def wsum (weights) weighted(weights) { |sv, w, axis| sv.wsum(w, axis: axis) } end |