diff options
| -rw-r--r-- | ms/ms.go | 127 | ||||
| -rw-r--r-- | ms/ms_test.go | 117 |
2 files changed, 219 insertions, 25 deletions
@@ -34,6 +34,114 @@ type MathematicalSeries[T constraints.Float | constraints.Integer] interface { Percentile(int) T } +func calculateAverage[T constraints.Integer | constraints.Float](elements []T) float64 { + total := T(0) + for i := 0; i < len(elements); i++ { + total += elements[i] + } + return float64(total) / float64(len(elements)) +} + +func calculatePercentile[T constraints.Integer | constraints.Float](elements []T, p int) (result T) { + result = T(0) + if p < 0 || p > 100 { + return + } + + sort.Slice(elements, func(l int, r int) bool { return elements[l] < elements[r] }) + pindex := int64((float64(p) / float64(100)) * float64(len(elements))) + result = elements[pindex] + return +} +func NewInfiniteMathematicalSeries[T constraints.Float | constraints.Integer]() MathematicalSeries[T] { + return &InfiniteMathematicalSeries[T]{} +} + +type InfiniteMathematicalSeries[T constraints.Float | constraints.Integer] struct { + elements []T +} + +func (ims *InfiniteMathematicalSeries[T]) AddElement(element T) { + ims.elements = append(ims.elements, element) +} + +func (ims *InfiniteMathematicalSeries[T]) CalculateAverage() float64 { + return calculateAverage(ims.elements) +} + +func (ims *InfiniteMathematicalSeries[T]) AllSequentialIncreasesLessThan(limit float64) (bool, float64) { + if len(ims.elements) < 2 { + return false, 0.0 + } + + maximumSequentialIncrease := float64(0) + for i := 1; i < len(ims.elements); i++ { + current := ims.elements[i] + previous := ims.elements[i-1] + percentChange := utilities.SignedPercentDifference(current, previous) + if percentChange > limit { + return false, percentChange + } + if percentChange > float64(maximumSequentialIncrease) { + maximumSequentialIncrease = percentChange + } + } + return true, maximumSequentialIncrease +} + +/* + * N.B.: Overflow is possible -- use at your discretion! + */ +func (ims *InfiniteMathematicalSeries[T]) StandardDeviation() (bool, T) { + + // From https://www.mathsisfun.com/data/standard-deviation-calculator.html + // Yes, for real! + + // Calculate the average of the numbers ... + average := ims.CalculateAverage() + + // Calculate the square of each of the elements' differences from the mean. + differences_squared := make([]float64, len(ims.elements)) + for index, value := range ims.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(len(ims.elements)) + + // Finally, the standard deviation is the square root + // of the variance. + sd := T(math.Sqrt(variance)) + //sd := T(variance) + + return true, sd +} + +func (ims *InfiniteMathematicalSeries[T]) IsNormallyDistributed() bool { + return false +} + +func (ims *InfiniteMathematicalSeries[T]) Size() int { + return len(ims.elements) +} + +func (ims *InfiniteMathematicalSeries[T]) Values() []T { + return ims.elements +} + +func (ims *InfiniteMathematicalSeries[T]) Percentile(p int) T { + return calculatePercentile(ims.elements, p) +} + type CappedMathematicalSeries[T constraints.Float | constraints.Integer] struct { elements_count int elements []T @@ -58,11 +166,10 @@ func (ma *CappedMathematicalSeries[T]) AddElement(measurement T) { } func (ma *CappedMathematicalSeries[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()) + // If we do not yet have all the values, then we know that the values + // exist between 0 and ma.divisor.Value(). If we do have all the values, + // we know that they, too, exist between 0 and ma.divisor.Value(). + return calculateAverage(ma.elements[0:ma.divisor.Value()]) } func (ma *CappedMathematicalSeries[T]) AllSequentialIncreasesLessThan(limit float64) (_ bool, maximumSequentialIncrease float64) { @@ -160,10 +267,9 @@ func (ma *CappedMathematicalSeries[T]) Size() int { return len(ma.elements) } -func (ma *CappedMathematicalSeries[T]) Percentile(p int) (result T) { - result = T(0) +func (ma *CappedMathematicalSeries[T]) Percentile(p int) T { if p < 0 || p > 100 { - return + return 0 } // Because we need to sort the list to perform the percentile calculation, @@ -172,8 +278,5 @@ func (ma *CappedMathematicalSeries[T]) Percentile(p int) (result T) { kopy := make([]T, len(ma.elements)) copy(kopy, ma.elements) - sort.Slice(kopy, func(l int, r int) bool { return kopy[l] < kopy[r] }) - pindex := int64((float64(p) / float64(100)) * float64(ma.elements_count)) - result = kopy[pindex] - return + return calculatePercentile(kopy, p) } diff --git a/ms/ms_test.go b/ms/ms_test.go index 988b647..d93102f 100644 --- a/ms/ms_test.go +++ b/ms/ms_test.go @@ -7,15 +7,106 @@ import ( "github.com/network-quality/goresponsiveness/utilities" ) -func Test_TooFewInstantsSequentialIncreasesLessThanAlwaysFalse(test *testing.T) { +func Test_InfiniteValues(test *testing.T) { + series := NewInfiniteMathematicalSeries[float64]() + shouldMatch := make([]float64, 0) + previous := float64(1.0) + for _ = range utilities.Iota(1, 80) { + previous *= 1.059 + series.AddElement(float64(previous)) + shouldMatch = append(shouldMatch, previous) + } + + if !reflect.DeepEqual(shouldMatch, series.Values()) { + test.Fatalf("Values() on infinite mathematical series does not work.") + } +} +func Test_InfiniteSequentialIncreasesAlwaysLessThan(test *testing.T) { + series := NewInfiniteMathematicalSeries[float64]() + previous := float64(1.0) + for _ = range utilities.Iota(1, 80) { + previous *= 1.059 + series.AddElement(float64(previous)) + } + if islt, maxSeqIncrease := series.AllSequentialIncreasesLessThan(6.0); !islt { + test.Fatalf("(infinite) Sequential increases are not always less than 6.0 (%f).", maxSeqIncrease) + } +} +func Test_CappedTooFewInstantsSequentialIncreasesLessThanAlwaysFalse(test *testing.T) { series := NewCappedMathematicalSeries[float64](500) series.AddElement(0.0) if islt, _ := series.AllSequentialIncreasesLessThan(6.0); islt { - test.Fatalf("Too few instants should always yield false when asking if sequential increases are less than a value.") + test.Fatalf("(infinite) 0 elements in a series should always yield false when asking if sequential increases are less than a value.") + } +} + +func Test_Infinite_degenerate_percentile_too_high(test *testing.T) { + series := NewInfiniteMathematicalSeries[int]() + if series.Percentile(101) != 0 { + test.Fatalf("(infinite) Series percentile of 101 failed.") + } +} +func Test_Infinite_degenerate_percentile_too_low(test *testing.T) { + series := NewInfiniteMathematicalSeries[int]() + if series.Percentile(-1) != 0 { + test.Fatalf("(infinite) Series percentile of -1 failed.") + } +} +func Test_Infinite90_percentile(test *testing.T) { + series := NewInfiniteMathematicalSeries[int]() + series.AddElement(10) + series.AddElement(9) + series.AddElement(8) + series.AddElement(7) + series.AddElement(6) + series.AddElement(5) + series.AddElement(4) + series.AddElement(3) + series.AddElement(2) + series.AddElement(1) + + if series.Percentile(90) != 10 { + test.Fatalf("(infinite) Series 90th percentile of 0 ... 10 failed: Expected 10 got %v.", series.Percentile(90)) + } +} + +func Test_Infinite90_percentile_reversed(test *testing.T) { + series := NewInfiniteMathematicalSeries[int64]() + series.AddElement(1) + series.AddElement(2) + series.AddElement(3) + series.AddElement(4) + series.AddElement(5) + series.AddElement(6) + series.AddElement(7) + series.AddElement(8) + series.AddElement(9) + series.AddElement(10) + + if series.Percentile(90) != 10 { + test.Fatalf("(infinite) Series 90th percentile of 0 ... 10 failed: Expected 10 got %v.", series.Percentile(90)) + } +} + +func Test_Infinite50_percentile_jumbled(test *testing.T) { + series := NewInfiniteMathematicalSeries[int64]() + series.AddElement(7) + series.AddElement(2) + series.AddElement(15) + series.AddElement(27) + series.AddElement(5) + series.AddElement(52) + series.AddElement(18) + series.AddElement(23) + series.AddElement(11) + series.AddElement(12) + + if series.Percentile(50) != 15 { + test.Fatalf("(infinite) Series 50 percentile of a jumble of numbers failed: Expected 15 got %v.", series.Percentile(50)) } } -func Test_SequentialIncreasesAlwaysLessThan(test *testing.T) { +func Test_CappedSequentialIncreasesAlwaysLessThan(test *testing.T) { series := NewCappedMathematicalSeries[float64](40) previous := float64(1.0) for _ = range utilities.Iota(1, 80) { @@ -27,7 +118,7 @@ func Test_SequentialIncreasesAlwaysLessThan(test *testing.T) { } } -func Test_SequentialIncreasesAlwaysLessThanWithWraparound(test *testing.T) { +func Test_CappedSequentialIncreasesAlwaysLessThanWithWraparound(test *testing.T) { series := NewCappedMathematicalSeries[float64](20) previous := float64(1.0) for range utilities.Iota(1, 20) { @@ -47,7 +138,7 @@ func Test_SequentialIncreasesAlwaysLessThanWithWraparound(test *testing.T) { } } -func Test_SequentialIncreasesAlwaysLessThanWithWraparoundInverse(test *testing.T) { +func Test_CappedSequentialIncreasesAlwaysLessThanWithWraparoundInverse(test *testing.T) { series := NewCappedMathematicalSeries[float64](20) previous := float64(1.0) for range utilities.Iota(1, 20) { @@ -67,7 +158,7 @@ func Test_SequentialIncreasesAlwaysLessThanWithWraparoundInverse(test *testing.T } } -func Test_StandardDeviationCalculation(test *testing.T) { +func Test_CappedStandardDeviationCalculation(test *testing.T) { series := NewCappedMathematicalSeries[float64](5) // 5.7, 1.0, 8.6, 7.4, 2.2 series.AddElement(5.7) @@ -83,7 +174,7 @@ func Test_StandardDeviationCalculation(test *testing.T) { } } -func Test_RotatingValues(test *testing.T) { +func Test_CappedRotatingValues(test *testing.T) { series := NewCappedMathematicalSeries[int](5) series.AddElement(1) @@ -99,7 +190,7 @@ func Test_RotatingValues(test *testing.T) { test.Fatalf("Adding values does not properly erase earlier values.") } } -func Test_Size(test *testing.T) { +func Test_CappedSize(test *testing.T) { series := NewCappedMathematicalSeries[int](5) series.AddElement(1) @@ -116,19 +207,19 @@ func Test_Size(test *testing.T) { } } -func Test_degenerate_percentile_too_high(test *testing.T) { +func Test_Capped_degenerate_percentile_too_high(test *testing.T) { series := NewCappedMathematicalSeries[int](21) if series.Percentile(101) != 0 { test.Fatalf("Series percentile of 101 failed.") } } -func Test_degenerate_percentile_too_low(test *testing.T) { +func Test_Capped_degenerate_percentile_too_low(test *testing.T) { series := NewCappedMathematicalSeries[int](21) if series.Percentile(-1) != 0 { test.Fatalf("Series percentile of -1 failed.") } } -func Test_90_percentile(test *testing.T) { +func Test_Capped90_percentile(test *testing.T) { series := NewCappedMathematicalSeries[int](10) series.AddElement(10) series.AddElement(9) @@ -146,7 +237,7 @@ func Test_90_percentile(test *testing.T) { } } -func Test_90_percentile_reversed(test *testing.T) { +func Test_Capped90_percentile_reversed(test *testing.T) { series := NewCappedMathematicalSeries[int64](10) series.AddElement(1) series.AddElement(2) @@ -164,7 +255,7 @@ func Test_90_percentile_reversed(test *testing.T) { } } -func Test_50_percentile_jumbled(test *testing.T) { +func Test_Capped50_percentile_jumbled(test *testing.T) { series := NewCappedMathematicalSeries[int64](10) series.AddElement(7) series.AddElement(2) |
