/* * This file is part of Go Responsiveness. * * Go Responsiveness is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software Foundation, * either version 2 of the License, or (at your option) any later version. * Go Responsiveness is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with Go Responsiveness. If not, see . */ package ms import ( "math" "github.com/network-quality/goresponsiveness/saturating" "github.com/network-quality/goresponsiveness/utilities" "golang.org/x/exp/constraints" ) type MathematicalSeries[T constraints.Float | constraints.Integer] struct { elements_count int elements []T index int divisor *saturating.SaturatingInt } func NewMathematicalSeries[T constraints.Float | constraints.Integer](instants_count int) *MathematicalSeries[T] { return &MathematicalSeries[T]{ elements: make([]T, instants_count), elements_count: instants_count, divisor: saturating.NewSaturatingInt(instants_count), } } func (ma *MathematicalSeries[T]) AddElement(measurement T) { ma.elements[ma.index] = measurement ma.divisor.Add(1) // Invariant: ma.index always points to the oldest measurement ma.index = (ma.index + 1) % ma.elements_count } func (ma *MathematicalSeries[T]) CalculateAverage() float64 { total := T(0) for i := 0; i < ma.elements_count; i++ { total += ma.elements[i] } return float64(total) / float64(ma.divisor.Value()) } func (ma *MathematicalSeries[T]) AllSequentialIncreasesLessThan(limit float64) (_ bool, maximumSequentialIncrease float64) { // If we have not yet accumulated a complete set of intervals, // this is false. if ma.divisor.Value() != ma.elements_count { return false, 0 } // Invariant: ma.index always points to the oldest (see AddMeasurement // above) oldestIndex := ma.index previous := ma.elements[oldestIndex] maximumSequentialIncrease = 0 for i := 1; i < ma.elements_count; i++ { currentIndex := (oldestIndex + i) % ma.elements_count current := ma.elements[currentIndex] percentChange := utilities.SignedPercentDifference(current, previous) previous = current if percentChange > limit { return false, percentChange } } return true, maximumSequentialIncrease } func (ma *MathematicalSeries[T]) StandardDeviationLessThan(limit T) (bool, T) { // If we have not yet accumulated a complete set of intervals, // we are always false. if ma.divisor.Value() != ma.elements_count { return false, T(0) } // From https://www.mathsisfun.com/data/standard-deviation-calculator.html // Yes, for real! // Calculate the average of the numbers ... average := ma.CalculateAverage() // Calculate the square of each of the elements' differences from the mean. differences_squared := make([]float64, ma.elements_count) for index, value := range ma.elements { differences_squared[index] = math.Pow(float64(value-T(average)), 2) } // The variance is the average of the squared differences. // So, we need to ... // Accumulate all those squared differences. sds := float64(0) for _, dss := range differences_squared { sds += dss } // And then divide that total by the number of elements variance := sds / float64(ma.divisor.Value()) // Finally, the standard deviation is the square root // of the variance. sd := T(math.Sqrt(variance)) return T(sd) < limit, sd }