kumulant

ExponentialSmoothingStat

class ExponentialSmoothingStat(val alphaWeighting: DecayWeighting.Alpha, val betaWeighting: DecayWeighting.Alpha = alphaWeighting, val phi: Double = StatDefaults.SMOOTHING_PHI, val period: Int = StatDefaults.SMOOTHING_PERIOD, val gammaWeighting: DecayWeighting.Alpha = alphaWeighting, val mode: SeasonalMode = StatDefaults.seasonalMode, val concurrency: Concurrency = Concurrency.None) : SeriesStat<ExponentialSmoothingResult> (source)

Exponential smoothing with a damped trend and optional seasonality: Holt's method at period 1, Holt-Winters with a seasonal cycle of period updates otherwise.

Additive recurrence (with a = correction(alpha, weight) and analogous b, g, and k the current seasonal-slot index):

sOld   = seasons[k]
prev   = level
level  = a * (value - sOld) + (1 - a) * (prev + phi * trend)
trend  = b * (level - prev) + (1 - b) * phi * trend
seasons[k] = g * (value - level) + (1 - g) * sOld
k      = (k + 1) mod period

Multiplicative is the same shape with subtraction replaced by division and addition by multiplication; a zero season falls back to 1.0 and a zero level leaves the season as it was, so neither path divides by zero. At period 1 the single season stays at its identity and the recurrence reduces to Holt's level and trend.

With phi == 1.0 the trend is undamped; with phi < 1.0 it is geometrically damped. At beta == 0 the level is an exponentially weighted mean seeded by the first value; it is not com.eignex.kumulant.stat.decay.EwmaMeanStat, which bias-corrects a zero seed instead, so the two differ from the second update on.

The first update seeds the level, a zero trend, and leaves the seasonal vector at its identity (0 additive, 1 multiplicative); every later update runs the recurrence above. Nothing seeds the seasonal vector separately, so the first cycle carries little seasonal signal.

Merge adopts a remote state while empty and otherwise averages level, trend, and seasonal factors, using the remote seasonal phase. This is an approximate roll-up, not replay of the combined ordered observations.

Use cases: smoothing a noisy series and producing short-horizon forecasts when a local trend, a recurring cycle, or both are expected.

Memory: O(period); three scalar cells plus a season array plus a lock.

Update: O(1); single seasonal slot touched per update.

Concurrency: Order-dependent recurrence, same model as com.eignex.kumulant.stat.decay.EwmaMeanStat. Concurrency.Strict and Concurrency.HighWrite lock the body so each update is atomic but the lock serialises arrival, not order; Concurrency.Relaxed drops the lock and the level/trend/seasonal cells race independently with bounded drift; never throws.

Constructors

ExponentialSmoothingStat

constructor(alphaWeighting: DecayWeighting.Alpha, betaWeighting: DecayWeighting.Alpha = alphaWeighting, phi: Double = StatDefaults.SMOOTHING_PHI, period: Int = StatDefaults.SMOOTHING_PERIOD, gammaWeighting: DecayWeighting.Alpha = alphaWeighting, mode: SeasonalMode = StatDefaults.seasonalMode, concurrency: Concurrency = Concurrency.None)(source)
constructor(alpha: Double, beta: Double = alpha, phi: Double = StatDefaults.SMOOTHING_PHI, period: Int = StatDefaults.SMOOTHING_PERIOD, gamma: Double = alpha, mode: SeasonalMode = StatDefaults.seasonalMode, concurrency: Concurrency = Concurrency.None)(source)

Properties

alpha

Level decay rate; each update steps the level by 1 - exp(-alpha * weight).

alphaWeighting

Per-observation smoothing schedule for the level.

beta

Trend decay rate; each update steps the trend by 1 - exp(-beta * weight).

betaWeighting

Per-observation smoothing schedule for the trend. Defaults to the level's alphaWeighting.

concurrency

open override val concurrency: Concurrency(source)

Thread-safety mode fixed at construction. See Concurrency for the shared contract and the implementation's KDoc for its cell and lock strategy.

gamma

Seasonal decay rate; each update steps the current season by 1 - exp(-gamma * weight).

gammaWeighting

Per-observation smoothing schedule for the seasonal vector. Defaults to the level's alphaWeighting.

mode

Seasonal coupling; defaults to SeasonalMode.Additive.

period

Length of the seasonal cycle in updates; 1 disables seasonality.

phi

Trend damping factor in (0, 1]; 1.0 is an undamped trend.

Functions

create

open override fun create(concurrency: Concurrency? = null): ExponentialSmoothingStat(source)

Create an independent accumulator at the configured baseline. A null concurrency preserves this accumulator's mode; a non-null value overrides it. Modality subtypes narrow the return type to preserve the input shape.

merge

open override fun merge(values: ExponentialSmoothingResult, timestampNanos: Long = currentTimeNanos())(source)

Fold a compatible snapshot values into this accumulator at timestampNanos. Implementations document whether the combine is exact or approximate and which state the snapshot omits. The source accumulator is unaffected.

Time-dependent statistics, such as windows, place the snapshot at timestampNanos, which must share the clock the caller uses for updates and reads. Time-independent statistics ignore it.

read

open override fun read(timestampNanos: Long = currentTimeNanos()): ExponentialSmoothingResult(source)

Return the current result evaluated at timestampNanos. Time-independent statistics ignore the timestamp. Reading does not add an observation.

Snapshot consistency depends on the implementation's synchronisation. Locks protect coupled state under Concurrency.Strict and Concurrency.HighWrite; independently updated cells and JVM striped adders can still yield a best-effort snapshot. Concurrency.Relaxed permits drift across coupled cells.

reset

open override fun reset()(source)

Restore the configured baseline, including any prior seed. Preserves the configuration and concurrency.

validateMerge

open override fun validateMerge(values: ExponentialSmoothingResult)(source)

Validate values for merge without changing accumulator state. Containers validate every child before merging any of them. Successful validation checks compatibility; it does not make a concurrent merge transactional.

Link copied to clipboard
open fun update(value: Double, weight: Double = 1.0)

Record value with weight at the current monotonic time.

open override fun update(value: Double, timestampNanos: Long, weight: Double = 1.0)

Record value with weight at timestampNanos.

update

open override fun update(value: Double, timestampNanos: Long, weight: Double = 1.0)(source)

Record value with weight at timestampNanos.