github.com/MetalBlockchain/metalgo@v1.11.9/utils/math/continuous_averager.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package math 5 6 import ( 7 "math" 8 "time" 9 ) 10 11 var convertEToBase2 = math.Log(2) 12 13 type continuousAverager struct { 14 halflife float64 15 weightedSum float64 16 normalizer float64 17 lastUpdated time.Time 18 } 19 20 // NewUninitializedAverager creates a new averager with the given halflife. If 21 // [Read] is called before [Observe], the zero value will be returned. When 22 // [Observe] is called the first time, the averager will be initialized with 23 // [value] at that time. 24 func NewUninitializedAverager(halfLife time.Duration) Averager { 25 // Use 0 as the initialPrediction and 0 as the currentTime, so that when the 26 // first observation occurs (at a non-zero time) the initial prediction's 27 // weight will become negligible. 28 return NewAverager(0, halfLife, time.Time{}) 29 } 30 31 func NewAverager( 32 initialPrediction float64, 33 halflife time.Duration, 34 currentTime time.Time, 35 ) Averager { 36 return &continuousAverager{ 37 halflife: float64(halflife) / convertEToBase2, 38 weightedSum: initialPrediction, 39 normalizer: 1, 40 lastUpdated: currentTime, 41 } 42 } 43 44 func (a *continuousAverager) Observe(value float64, currentTime time.Time) { 45 delta := a.lastUpdated.Sub(currentTime) 46 switch { 47 case delta < 0: 48 // If the times are called in order, scale the previous values to keep the 49 // sizes manageable 50 newWeight := math.Exp(float64(delta) / a.halflife) 51 52 a.weightedSum = value + newWeight*a.weightedSum 53 a.normalizer = 1 + newWeight*a.normalizer 54 55 a.lastUpdated = currentTime 56 case delta == 0: 57 // If this is called multiple times at the same wall clock time, no 58 // scaling needs to occur 59 a.weightedSum += value 60 a.normalizer++ 61 default: 62 // If the times are called out of order, don't scale the previous values 63 newWeight := math.Exp(float64(-delta) / a.halflife) 64 65 a.weightedSum += newWeight * value 66 a.normalizer += newWeight 67 } 68 } 69 70 func (a *continuousAverager) Read() float64 { 71 return a.weightedSum / a.normalizer 72 }