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 129 130 131 132 133 134 135 136 137 |
# 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 keeps # only the anchors whose window lies wholly inside the source), and where # each axis's first window starts in the buffer the windows are read from. # A window that does not cover its own anchor -- `windows(1..2)`, the two # cells after this one -- starts further along than the margin allows for, # which is what @origins carries. rshape = @source.shape if @bounds == :truncate first = @ranges.map { |r| [0, -r.begin].max } last = @sndim.times.map { |i| [rshape[i] - 1, rshape[i] - 1 - @ranges[i].end].min } @shape = @sndim.times.map { |i| [last[i] - first[i] + 1, 0].max } @origins = @ranges.map { |r| [r.begin, 0].max } else @shape = rshape.dup @origins = @sndim.times.map { |i| @lefts[i] + @ranges[i].begin } end @ndim = @shape.size self end |
Instance Attribute Details
#bounds ⇒ Symbol (readonly)
147 148 149 |
# File 'lib/carray/window_iterator.rb', line 147 def bounds @bounds end |
#source ⇒ CArray (readonly)
142 143 144 |
# File 'lib/carray/window_iterator.rb', line 142 def source @source end |
Instance Method Details
#convolve(kernel, min_count: nil, fill_value: nil) ⇒ CArray
682 683 684 |
# File 'lib/carray/window_iterator.rb', line 682 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
655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 |
# File 'lib/carray/window_iterator.rb', line 655 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
606 607 608 609 610 611 |
# File 'lib/carray/window_iterator.rb', line 606 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
624 625 626 |
# File 'lib/carray/window_iterator.rb', line 624 def count_masked sliding_view.count_masked(axis: @window_axes) end |
#count_not_masked ⇒ CArray
617 618 619 |
# File 'lib/carray/window_iterator.rb', line 617 def count_not_masked sliding_view.count_not_masked(axis: @window_axes) end |
#each({ |window| ... }) {|window| ... } ⇒ Enumerator, self
812 813 814 815 816 817 818 |
# File 'lib/carray/window_iterator.rb', line 812 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
632 633 634 635 636 637 638 639 |
# File 'lib/carray/window_iterator.rb', line 632 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
855 856 857 858 859 860 |
# File 'lib/carray/window_iterator.rb', line 855 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
342 |
# File 'lib/carray/window_iterator.rb', line 342 def max_addr; window_winner_addr(:max_index); end |
#median ⇒ CArray
715 716 717 |
# File 'lib/carray/window_iterator.rb', line 715 def median order_stat { |view, axis| view.median(axis: axis) } end |
#min_addr ⇒ CArray
337 |
# File 'lib/carray/window_iterator.rb', line 337 def min_addr; window_winner_addr(:min_index); end |
#cumsum ⇒ Object #cumprod ⇒ Object #cummax ⇒ Object #cummin ⇒ Object #cumcount ⇒ Object
314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/carray/window_iterator.rb', line 314 [:sum, :accumulate, :prod, :mean, :min, :max, :variance, :stddev, :all, :any, :variancep, :stddevp, :minmax, :min_index, :max_index].each do |op| class_eval <<~RUBY, __FILE__, __LINE__ + 1 def #{op} (min_count: nil, fill_value: nil) folded = fold_by_offset(:#{op}, min_count, fill_value) return folded unless folded.nil? kw = {} kw[:min_count] = min_count unless min_count.nil? kw[:fill_value] = fill_value unless fill_value.nil? sliding_view.#{op}(axis: @window_axes, **kw) end RUBY end |
#percentile(*pers) ⇒ CArray+
724 725 726 |
# File 'lib/carray/window_iterator.rb', line 724 def percentile (*pers) order_stat { |view, axis| view.percentile(*pers, axis: axis) } end |
#quantile ⇒ Array<CArray>
732 733 734 |
# File 'lib/carray/window_iterator.rb', line 732 def quantile order_stat { |view, axis| view.quantile(axis: axis) } end |
#reduce({ |window| ... }) {|window| ... } ⇒ CArray #reduce(init) ⇒ CArray
831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 |
# File 'lib/carray/window_iterator.rb', line 831 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
160 161 162 |
# File 'lib/carray/window_iterator.rb', line 160 def sliding_view @sliding_view ||= anchored_buffer.sliding_windows(*@widths) end |
#sort_addr ⇒ Object
869 870 871 872 873 874 |
# File 'lib/carray/window_iterator.rb', line 869 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
774 775 776 |
# File 'lib/carray/window_iterator.rb', line 774 def wmean (weights) weighted(weights) { |sv, w, axis| sv.wmean(w, axis: axis) } end |
#wsum(weights) ⇒ CArray
767 768 769 |
# File 'lib/carray/window_iterator.rb', line 767 def wsum (weights) weighted(weights) { |sv, w, axis| sv.wsum(w, axis: axis) } end |