127 lines
3.0 KiB
Go
127 lines
3.0 KiB
Go
// Copyright 2022 wanderer <a_mirre at utb dot cz>
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package stats
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
|
|
"git.dotya.ml/wanderer/math-optim/report"
|
|
"gonum.org/v1/gonum/floats"
|
|
"gonum.org/v1/gonum/stat"
|
|
)
|
|
|
|
// getColLayout returns a string slice of Latex table column alignment
|
|
// settings.
|
|
func getColLayout() []string {
|
|
return []string{"c", "c", "c", "c", "c"}
|
|
}
|
|
|
|
// getColNames returns names of table columns, i.e. statistical features we are
|
|
// interested in.
|
|
func getColNames() []string {
|
|
// the first column describes specific iteration settings and is therefore
|
|
// dynamically set, hence not present here nor mentioned in
|
|
// `getColLayout()`.
|
|
return []string{"min", "max", "mean", "median", "stddev"}
|
|
}
|
|
|
|
// SaveTable sifts through computed values, organises data in table-like
|
|
// structure as defined in the `report` pkg and passes it on to be fed to a
|
|
// tmpl, result of which is then saved in a `.tex` file (filename based on the
|
|
// algo name string).
|
|
func SaveTable(algo string, algoStats [][]Stats) {
|
|
table := report.NewTable()
|
|
|
|
table.Algo = algo
|
|
table.Header = getColNames()
|
|
table.ColLayout = getColLayout()
|
|
|
|
for _, singleFunc := range algoStats {
|
|
// append/merge(...) if necessary.
|
|
table.Rows = append(table.Rows, parseSingleBenchStats(singleFunc)...)
|
|
}
|
|
|
|
report.SaveTableToFile(*table)
|
|
}
|
|
|
|
// parseSingleBenchStats processes results of a particular bench and constructs
|
|
// statistics.
|
|
func parseSingleBenchStats(benchStats []Stats) []report.Row {
|
|
rows := make([]report.Row, 0)
|
|
|
|
for _, s := range benchStats {
|
|
for _, dim := range s.BenchFuncStats {
|
|
row := report.NewRow()
|
|
|
|
row.Title = "D=" + fmt.Sprint(s.Dimens) + ", f=" + dim.BenchName +
|
|
", G=" + fmt.Sprint(s.Generations) +
|
|
", I=" + fmt.Sprint(s.Iterations)
|
|
|
|
row.Title = makeRowTitle(
|
|
dim.BenchName,
|
|
s.Dimens,
|
|
s.Generations,
|
|
s.Iterations,
|
|
)
|
|
|
|
// collect the best.
|
|
var best []float64
|
|
|
|
for _, iter := range dim.Solution {
|
|
last := s.Generations - 1
|
|
|
|
best = append(best, iter.Results[last])
|
|
}
|
|
|
|
row.Values = statsFromBest(best)
|
|
|
|
rows = append(rows, *row)
|
|
}
|
|
}
|
|
|
|
return rows
|
|
}
|
|
|
|
func makeRowTitle(bench string, dimens, generations, iterations int) string {
|
|
return "D=" + fmt.Sprint(dimens) + ", f=" + bench +
|
|
", G=" + fmt.Sprint(generations) +
|
|
", I=" + fmt.Sprint(iterations)
|
|
}
|
|
|
|
// statsFromBest computes the actual statistics upon the slice of best results,
|
|
// returns a slice of float64s.
|
|
func statsFromBest(best []float64) []float64 {
|
|
s := make([]float64, len(getColNames()))
|
|
|
|
s[0] = floats.Min(best)
|
|
s[1] = floats.Max(best)
|
|
s[2] = stat.Mean(best, nil)
|
|
s[3] = median(best)
|
|
s[4] = stat.StdDev(best, nil)
|
|
|
|
return s
|
|
}
|
|
|
|
// as per https://gosamples.dev/calculate-median/.
|
|
func median(data []float64) float64 {
|
|
dataCopy := make([]float64, len(data))
|
|
copy(dataCopy, data)
|
|
|
|
sort.Float64s(dataCopy)
|
|
|
|
var median float64
|
|
|
|
//nolint: gocritic
|
|
if l := len(dataCopy); l == 0 {
|
|
return 0
|
|
} else if l%2 == 0 {
|
|
median = (dataCopy[l/2-1] + dataCopy[l/2]) / 2
|
|
} else {
|
|
median = dataCopy[l/2]
|
|
}
|
|
|
|
return median
|
|
}
|