kumulant

stat.decay

Time-weighted moments. Each observation enters the accumulator with a weight that shrinks toward zero with age, so older observations contribute less. Two parallel sub-families: timestamp-based decay (the Decaying* stats) and step-based EWMA (the Ewma* stats).

The two flavours

Timestamp-based: Decaying*

DecayingSumStat, DecayingMeanStat, DecayingVarianceStat are the time-weighted counterparts of com.eignex.kumulant.stat.summary.SumStat / com.eignex.kumulant.stat.summary.MeanStat / com.eignex.kumulant.stat.summary.VarianceStat. The decay schedule is configured via DecayWeighting:

  • HalfLife(durationMillis): exponential decay specified by the half-life of an observation's weight.

  • TimeConstant(tau): exponential decay specified by the time constant (e-fold) of the weighting function.

  • Custom(weighting): caller-supplied decay function from (elapsedNanos) to a multiplicative weight.

Because weight is a function of the timestamp delta, these handle irregular update intervals correctly. A stream that fires once a second and then once an hour applies decay over the actual elapsed time, not over the update count.

Step-based: Ewma*

EwmaMeanStat and EwmaVarianceStat are the classical exponentially-weighted moving variants with a step-based decay: alpha * new + (1 - alpha) * old. Smoothing factor lives in DecayWeighting.Alpha.

These are cheaper and more familiar but assume roughly fixed intervals between observations. Mixed-interval streams should reach for the timestamp-based variants instead.

Picking by need

Result shapes

DecayingMeanStat / EwmaMeanStat expose WeightedMeanResult, the same shape as the non-decayed MeanStat. DecayingVarianceStat / EwmaVarianceStat expose WeightedVarianceResult. This is intentional: downstream traits like com.eignex.kumulant.core.HasCenterScale and com.eignex.kumulant.core.HasSampleVariance work identically on decayed and undecayed results, so a band / standardize / scoring step doesn't care which provenance the moment came from.

Merge

Decayed stats with the same schedule merge cleanly: the weight formulation makes the Chan-style parallel merge work the same way it does for the undecayed Welford family. Stats with different schedules (different half-lives, different time constants) cannot be merged meaningfully; the merge contract enforces matching configuration.

Concurrency

All variants are Welford-coupled (mean + variance + total weight cells that have to stay consistent). Locked under com.eignex.kumulant.core.Concurrency.Strict / com.eignex.kumulant.core.Concurrency.HighWrite. Under com.eignex.kumulant.core.Concurrency.Relaxed the cells race; the drift is the same small ULP-level magnitude as the undecayed Welford family.

Types

Link copied to clipboard
@Serializable
@SerialName(value = "DecayingMeanResult")
data class DecayingMeanResult(val mean: Double, val totalWeights: Double, val timestampNanos: Long) : Result

Snapshot of an exponentially time-decayed weighted mean at timestampNanos.

Link copied to clipboard
class DecayingMeanStat(val weighting: DecayWeighting.HalfLife, val concurrency: Concurrency = Concurrency.None) : SeriesStat<DecayingMeanResult>

Exponentially decaying weighted mean: Sum(v_i*w_i*decay) / Sum(w_i*decay).

Link copied to clipboard
@Serializable
@SerialName(value = "DecayingSumResult")
data class DecayingSumResult(val sum: Double, val timestampNanos: Long) : Result

Snapshot of an exponentially time-decayed sum at timestampNanos.

Link copied to clipboard
class DecayingSumStat(val weighting: DecayWeighting.HalfLife, val concurrency: Concurrency = Concurrency.None) : SeriesStat<DecayingSumResult>

Exponentially decaying sum driven by wall-clock elapsed time.

Link copied to clipboard
@Serializable
@SerialName(value = "DecayingVarianceResult")
data class DecayingVarianceResult(val mean: Double, val variance: Double, val totalWeights: Double, val timestampNanos: Long) : Result

Snapshot of an exponentially time-decayed weighted variance at timestampNanos.

Link copied to clipboard
class DecayingVarianceStat(val weighting: DecayWeighting.HalfLife, val concurrency: Concurrency = Concurrency.None) : SeriesStat<DecayingVarianceResult>

Exponentially decaying weighted variance over the recent time window.

Link copied to clipboard
sealed interface DecayWeighting

Shared decay strategy for exponentially weighted stats.

Link copied to clipboard
class EwmaMeanStat(val weighting: DecayWeighting.Alpha, val concurrency: Concurrency = Concurrency.None) : SeriesStat<WeightedMeanResult>

Exponentially weighted moving average driven by cumulative observation weight.

Link copied to clipboard
class EwmaVarianceStat(val weighting: DecayWeighting.Alpha, val concurrency: Concurrency = Concurrency.None) : SeriesStat<WeightedVarianceResult>

Exponentially weighted moving variance driven by cumulative observation weight.

Functions

Link copied to clipboard

Shorthand for DecayWeighting.Alpha - usable as a shared weighting across stats.

Link copied to clipboard

Shorthand for DecayWeighting.HalfLife - usable as a shared weighting across stats.