dkforest

A forum and chat platform (onion)
git clone https://git.dasho.dev/n0tr1v/dkforest.git
Log | Files | Refs | LICENSE

commit 1b75ddb4a0f09d5cf3a9b1a34105658719c186d0
parent 8463439870ce528d18a9890541345d667cd3bee9
Author: n0tr1v <n0tr1v@protonmail.com>
Date:   Wed, 14 Jun 2023 11:24:13 -0700

fix standard deviation

Diffstat:
Mpkg/web/handlers/interceptors/chess.go | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mpkg/web/handlers/interceptors/chess_test.go | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 110 insertions(+), 28 deletions(-)

diff --git a/pkg/web/handlers/interceptors/chess.go b/pkg/web/handlers/interceptors/chess.go @@ -910,42 +910,59 @@ func AnalyseGame(pgn string, t int64) (out AnalyseResult, err error) { }, nil } -func standardDeviation(num []WinPercent) float64 { - nb := len(num) - var sum, mean, sd float64 - for i := 0; i < nb; i++ { - sum += float64(num[i]) +func mean(arr []float64) float64 { + var sum float64 + for _, n := range arr { + sum += n } - mean = sum / float64(nb) - for j := 0; j < nb; j++ { - sd += math.Pow(float64(num[j])-mean, 2) + return sum / float64(len(arr)) +} + +func standardDeviation(arr []float64) float64 { + nb := float64(len(arr)) + m := mean(arr) + var acc float64 + for _, n := range arr { + acc += (n - m) * (n - m) } - sd = math.Sqrt(sd) / float64(nb) - return sd + return math.Sqrt(acc / nb) } // https://www.scribbr.com/statistics/standard-deviation/ -//// using population variance -//def standardDeviation(a: Iterable[Double]): Option[Double] = -//mean(a) map { mean => -//Math.sqrt: -//a.foldLeft(0d) { (sum, x) => -//sum + Math.pow(x - mean, 2) -//} / a.size -//} +/** + def mean[T](a: Iterable[T])(using n: Numeric[T]): Option[Double] = + a.nonEmpty option (n.toDouble(a.sum) / a.size) + + def standardDeviation(a: Iterable[Double]): Option[Double] = + mean(a) map { mean => + Math.sqrt: + a.foldLeft(0d) { (sum, x) => + sum + Math.pow(x - mean, 2) + } / a.size + } +*/ type WinPercent float64 type Cp int -func fromCentiPawns(cp Cp) WinPercent { - return 50 + 50*winningChances(cp) +func (c Cp) ceiled() Cp { + if c > 1000 { + return 1000 + } else if c < -1000 { + return -1000 + } + return c } -func winningChances(cp Cp) WinPercent { +func fromCentiPawns(cp Cp) float64 { + return 50 + 50*winningChances(cp.ceiled()) +} + +func winningChances(cp Cp) float64 { const MULTIPLIER = -0.00368208 // https://github.com/lichess-org/lila/pull/11148 res := 2/(1+math.Exp(MULTIPLIER*float64(cp))) - 1 - out := WinPercent(math.Max(math.Min(res, 1), -1)) + out := math.Max(math.Min(res, 1), -1) return out } @@ -959,7 +976,7 @@ func fromWinPercents(before, after float64) (accuracy float64) { return math.Min(math.Max(raw, 0), 100) } -func calcWindows(allWinPercents []WinPercent, windowSize int) (out [][]WinPercent) { +func calcWindows(allWinPercents []float64, windowSize int) (out [][]float64) { start := allWinPercents[:windowSize] tmp := windowSize if tmp > len(allWinPercents) { @@ -970,7 +987,7 @@ func calcWindows(allWinPercents []WinPercent, windowSize int) (out [][]WinPercen } for i := 0; i < len(allWinPercents)-(windowSize-1); i++ { - curr := make([]WinPercent, 0) + curr := make([]float64, 0) for j := 0; j < windowSize; j++ { curr = append(curr, allWinPercents[i+j]) } @@ -979,14 +996,14 @@ func calcWindows(allWinPercents []WinPercent, windowSize int) (out [][]WinPercen return } -func calcWeights(windows [][]WinPercent) (out []float64) { +func calcWeights(windows [][]float64) (out []float64) { for _, w := range windows { out = append(out, math.Min(math.Max(standardDeviation(w), 0.5), 12)) } return } -func calcWeightedAccuracies(allWinPercents []WinPercent, weights []float64) (float64, float64) { +func calcWeightedAccuracies(allWinPercents []float64, weights []float64) (float64, float64) { sw := calcWindows(allWinPercents, 2) whites := make([][2]float64, 0) blacks := make([][2]float64, 0) @@ -1025,6 +1042,19 @@ func harmonicMean(arr [][2]float64) float64 { return float64(len(vs)) / sm } +/** + def harmonicMean(a: Iterable[Double]): Option[Double] = + a.nonEmpty option { + a.size / a.foldLeft(0d) { (acc, v) => acc + 1 / Math.max(1, v) } + } + + def weightedMean(a: Iterable[(Double, Double)]): Option[Double] = + a.nonEmpty so { + a.foldLeft(0d -> 0d) { case ((av, aw), (v, w)) => (av + v * w, aw + w) } match + case (v, w) => w != 0 option v / w + } +*/ + func weightedMean(a [][2]float64) float64 { vs := make([]float64, 0) ws := make([]float64, 0) @@ -1040,7 +1070,7 @@ func weightedMean(a [][2]float64) float64 { continue } sumWeight += ws[i] - avg += float64(v) * ws[i] + avg += v * ws[i] } avg /= sumWeight return avg @@ -1048,7 +1078,7 @@ func weightedMean(a [][2]float64) float64 { func gameAccuracy(cps []int) (float64, float64) { cps = append([]int{15}, cps...) - var allWinPercents []WinPercent + var allWinPercents []float64 for _, cp := range cps { allWinPercents = append(allWinPercents, fromCentiPawns(Cp(cp))) } diff --git a/pkg/web/handlers/interceptors/chess_test.go b/pkg/web/handlers/interceptors/chess_test.go @@ -82,3 +82,55 @@ func Test_gameAccuracy(t *testing.T) { assert.True(t, isCloseTo(w, 20, 8)) assert.True(t, isCloseTo(b, 20, 8)) } + +func Test_gameAccuracy1(t *testing.T) { + type args struct { + cps []int + } + tests := []struct { + name string + args args + want float64 + want1 float64 + }{ + { + name: "test1", + args: args{ + cps: []int{48, 38, 36, 36, 25, 61, 42, 131, 82, 135, 109, 186, 152, 145, 134, 280, 287, 264, 278, 271, 271, 306, 246, 335, 311, 500, 87, 276, 284, 398, 196, 514, 520, 641, -144, -74, -156, -107, -94, 60, 47, 146, -33, 98, -483, 31, -174, 385, -152, 467, 515, 846, 874, 884, 874, 938, 885, 889, 935, 946, 831, 998, 1018, 1026, 1031, 1142, 935, 1062, 1096, 1123, 1112, 1123}, + }, + want: 61.61609601278025, + want1: 59.024965435590794, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1 := gameAccuracy(tt.args.cps) + assert.Equalf(t, tt.want, got, "gameAccuracy(%v)", tt.args.cps) + assert.Equalf(t, tt.want1, got1, "gameAccuracy(%v)", tt.args.cps) + }) + } +} + +func Test_standardDeviation(t *testing.T) { + type args struct { + num []float64 + } + tests := []struct { + name string + args args + want float64 + }{ + { + name: "", + args: args{ + num: []float64{91.37426170872673, 37.04656899332343, 43.22998523826144, 36.02211364871788, 40.27589492090241, 41.43247132264411, 55.50076484124536}, + }, + want: 18.186779319663053, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, standardDeviation(tt.args.num), "standardDeviation(%v)", tt.args.num) + }) + } +}