Wrapping methods used by multiple structs

JeffreySarnoff
Wed, 26 Oct 2022 17:04:05 +0000
General Usage

For use running incremental statistics over windowed data, I have some structs that share struct-function-like methods. Here is an example that keeps track of the maximum value encountered as it runs over a windowed data stream.

abstract type Accumulator{T} end

mutable struct AccumMax{T} <: Accumulator{T}
    max::T
    AccumMax(::Type{T}=Float64) where {T} =
        (T <: Integer) ? new{T}(typemin(T)) : new{T}(floatmin(T))
end

(accum::AccumMax{T})() where {T} = (accum.max)
(accum::AccumMax{T})(x) where {T} =
    (accum.max = ifelse(accum.max < x, T(x), accum.max))

julia> accum = AccumMax(Float32)
AccumMax{Float32}(1.1754944f-38)
julia> accum(17.0f0) # the one-arg form accumulates the value
17.0f0
jula> accum(5.0f0);
julia> accum()  # the no-args form retrieves the accumulated value
17.0f0

I want to offer AccumMaxAbs that does what AccumMax does using abs(x) in place of x. This is not too hard to do in a way that keeps interoperability with e.g. AccumMin so AccumMinAbs can be realized the same way.

(accum::AccumMax{T})(x, fx::F=identity) where {T,F<:Function} =
    (x = fx(x); accum.max = ifelse(accum.max < x, T(x), accum.max))

[currently I recode each explicitly … there are more than two]
It would be great if there were a way to auto-generate the Abs versions; I am not concerned so much about making Abs versions by hand – I am opaque as to how one approaches modifying the internal calculation in the third statement in a more detailed accumulator. I would like to turn the following into an exponentially weighted version of itself knowing how the modification should be, without entirely recoding it (I would like to offer an exponentially weighted variant of most Accumulators).

mutable struct AccumMeanVariance{T} <: Accumulator{T}
    n::Int
    mean::T
    svar::T
    AccumMeanVariance(::Type{T}=Float64) where {T} = new{T}(0, zero(T), zero(T))
end

function (accum::AccumMeanVariance{T})() where {T}
    unbiased_var = accum.svar / (accum.n - 1)
    (a.mean, unbiased_var)
end

function (accum::AccumMeanVariance{T})(x) where {T}
    if !iszero(accum.n)
        oldmean = accum.mean
        accum.mean = oldmean + (x - oldmean) / accum.n
        accum.svar = accum.svar + (x - oldmean) * (x - accum.mean)
    else
        acccum.mean = x  # svar is zero already
    end
end

the exponentially weighted version of this is

mutable struct AccumExpWMeanVariance{T} <: Accumulator{T}
    n::Int
    alpha::T
    mean::T
    svar::T
    AccumExpWMeanVariance(::Type{T}=Float64; alpha::T=0.5) where {T} =
       new{T}(0, alpha, zero(T), zero(T))
end

function (accum::AccumExpWMeanVariance{T})() where {T}
    unbiased_var = accum.svar / (accum.n = 1)
    (accum.mean, unbiased_var)
end

function (accum::AccumExpWMeanVariance{T})(x) where {T}
    accum.n += 1
    diff = x - accum.mean
    incr = accum.alpha * diff
    accum.mean += accum.alpha * (x - a.mean)
    accum.svar = (one(T) - accum.alpha) * (accum.svar + diff * incr)
end

1 post - 1 participant

Read full topic