github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/consensus/hotstuff/cruisectl/aggregators.go (about) 1 package cruisectl 2 3 import ( 4 "fmt" 5 ) 6 7 // Ewma implements the exponentially weighted moving average with smoothing factor α. 8 // The Ewma is a filter commonly applied to time-discrete signals. Mathematically, 9 // it is represented by the recursive update formula 10 // 11 // value ← α·v + (1-α)·value 12 // 13 // where `v` the next observation. Intuitively, the loss factor `α` relates to the 14 // time window of N observations that we average over. For example, let 15 // α ≡ 1/N and consider an input that suddenly changes from x to y as a step 16 // function. Then N is _roughly_ the number of samples required to move the output 17 // average about 2/3 of the way from x to y. 18 // For numeric stability, we require α to satisfy 0 < a < 1. 19 // Not concurrency safe. 20 type Ewma struct { 21 alpha float64 22 value float64 23 } 24 25 // NewEwma instantiates a new exponentially weighted moving average. 26 // The smoothing factor `alpha` relates to the averaging time window. Let `alpha` ≡ 1/N and 27 // consider an input that suddenly changes from x to y as a step function. Then N is roughly 28 // the number of samples required to move the output average about 2/3 of the way from x to y. 29 // For numeric stability, we require `alpha` to satisfy 0 < `alpha` < 1. 30 func NewEwma(alpha, initialValue float64) (Ewma, error) { 31 if (alpha <= 0) || (1 <= alpha) { 32 return Ewma{}, fmt.Errorf("for numeric stability, we require the smoothing factor to satisfy 0 < alpha < 1") 33 } 34 return Ewma{ 35 alpha: alpha, 36 value: initialValue, 37 }, nil 38 } 39 40 // AddRepeatedObservation adds k consecutive observations with the same value v. Returns the updated value. 41 func (e *Ewma) AddRepeatedObservation(v float64, k int) float64 { 42 // closed from for k consecutive updates with the same observation v: 43 // value ← r·value + v·(1-r) with r := (1-α)^k 44 r := powWithIntegerExponent(1.0-e.alpha, k) 45 e.value = r*e.value + v*(1.0-r) 46 return e.value 47 } 48 49 // AddObservation adds the value `v` to the EWMA. Returns the updated value. 50 func (e *Ewma) AddObservation(v float64) float64 { 51 // Update formula: value ← α·v + (1-α)·value = value + α·(v - value) 52 e.value = e.value + e.alpha*(v-e.value) 53 return e.value 54 } 55 56 func (e *Ewma) Value() float64 { 57 return e.value 58 } 59 60 // LeakyIntegrator is a filter commonly applied to time-discrete signals. 61 // Intuitively, it sums values over a limited time window. This implementation is 62 // parameterized by the loss factor `ß`: 63 // 64 // value ← v + (1-ß)·value 65 // 66 // where `v` the next observation. Intuitively, the loss factor `ß` relates to the 67 // time window of N observations that we integrate over. For example, let ß ≡ 1/N 68 // and consider a constant input x: 69 // - the integrator value will saturate at x·N 70 // - an integrator initialized at 0 reaches 2/3 of the saturation value after N samples 71 // 72 // For numeric stability, we require ß to satisfy 0 < ß < 1. 73 // Further details on Leaky Integrator: https://www.music.mcgill.ca/~gary/307/week2/node4.html 74 // Not concurrency safe. 75 type LeakyIntegrator struct { 76 feedbackCoef float64 // feedback coefficient := (1-ß) 77 value float64 78 } 79 80 // NewLeakyIntegrator instantiates a new leaky integrator with loss factor `beta`, where 81 // `beta relates to window of N observations that we integrate over. For example, let 82 // `beta` ≡ 1/N and consider a constant input x. The integrator value will saturate at x·N. 83 // An integrator initialized at 0 reaches 2/3 of the saturation value after N samples. 84 // For numeric stability, we require `beta` to satisfy 0 < `beta` < 1. 85 func NewLeakyIntegrator(beta, initialValue float64) (LeakyIntegrator, error) { 86 if (beta <= 0) || (1 <= beta) { 87 return LeakyIntegrator{}, fmt.Errorf("for numeric stability, we require the loss factor to satisfy 0 < beta < 1") 88 } 89 return LeakyIntegrator{ 90 feedbackCoef: 1.0 - beta, 91 value: initialValue, 92 }, nil 93 } 94 95 // AddRepeatedObservation adds k consecutive observations with the same value v. Returns the updated value. 96 func (e *LeakyIntegrator) AddRepeatedObservation(v float64, k int) float64 { 97 // closed from for k consecutive updates with the same observation v: 98 // value ← r·value + v·(1-r) with r := α^k 99 r := powWithIntegerExponent(e.feedbackCoef, k) 100 e.value = r*e.value + v*(1.0-r)/(1.0-e.feedbackCoef) 101 return e.value 102 } 103 104 // AddObservation adds the value `v` to the LeakyIntegrator. Returns the updated value. 105 func (e *LeakyIntegrator) AddObservation(v float64) float64 { 106 // Update formula: value ← v + feedbackCoef·value 107 // where feedbackCoef = (1-beta) 108 e.value = v + e.feedbackCoef*e.value 109 return e.value 110 } 111 112 func (e *LeakyIntegrator) Value() float64 { 113 return e.value 114 } 115 116 // powWithIntegerExponent implements exponentiation b^k optimized for integer k >=1 117 func powWithIntegerExponent(b float64, k int) float64 { 118 r := 1.0 119 for { 120 if k&1 == 1 { 121 r *= b 122 } 123 k >>= 1 124 if k == 0 { 125 break 126 } 127 b *= b 128 } 129 return r 130 }