math-optim/algo/randomSearch.go
surtur 5038e75a38
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
go(plot): svg instead of png for 100-fold speed-up
2022-06-18 21:22:19 +02:00

248 lines
6.3 KiB
Go

// Copyright 2022 wanderer <a_mirre at utb dot cz>
// SPDX-License-Identifier: GPL-3.0-or-later
package algo
import (
"fmt"
"log"
"os"
"git.dotya.ml/wanderer/math-optim/bench"
"git.dotya.ml/wanderer/math-optim/stats"
"gonum.org/v1/gonum/stat/distuv"
)
func getRandomSearchLogPrefix() string {
return " ***  random search:"
}
func fmtRandomSearchOut(input string) string {
return getRandomSearchLogPrefix() + " " + input
}
func printRandomSearch(input string) {
if _, err := fmt.Fprintln(os.Stderr, fmtRandomSearchOut(input)); err != nil {
fmt.Fprintf(
os.Stdout,
getRandomSearchLogPrefix(),
"error while printing to stderr: %q\n * original message was: %q",
err, input,
)
}
}
func genValsRandomSearch(dimens uint, vals []float64, uniform *distuv.Uniform) {
for i := uint(0); i < dimens; i++ {
// using Uniform.Rand from gonum's stat/distuv package.
// https://pkg.go.dev/gonum.org/v1/gonum/stat/distuv#Uniform.Rand
// boundaries are already set at this point.
vals[i] = uniform.Rand()
}
}
// singleRandomSearch performs a single iteration of the 'RandomSearch' algorithm.
// it takes a couple of arguments:
// * dimens uint: number of dimensions of the objective function
// * f func([]float64) float64: bench func to execute (see Functions map in
// bench/functions.go)
// * min/max float64: the upper/lower limit of the uniform distribution span,
// which is relevant to the objective function.
func singleRandomSearch(dimens uint, f func([]float64) float64, min, max float64) ([]float64, float64) {
vals := make([]float64, dimens)
// create a continuous uniform distribution representation within min/max bounds
uniformDist := distuv.Uniform{Min: min, Max: max}
genValsRandomSearch(dimens, vals, &uniformDist)
// result of the bench function.
res := f(vals)
return vals, res
}
// TODO(me): split this up
// nolint: gocognit
func RandomSearch(fes uint) {
if fes == 0 {
log.Fatalln(" random search: fes set to 0, bailing")
}
picPrefix := "res/"
var valsSchwefel []Values
var valsDeJong1st []Values
var valsDeJong2nd []Values
var bestResultsSchwefel []float64
var bestResultsDeJong1st []float64
var bestResultsDeJong2nd []float64
// randomSearchStats stores Stats for all the function/dimension
// combinations for this algo.
var randomSearchStats []stats.Stats
// iterations needed to establish a minimal viable statistical baseline
minIters := 30
//nolint: staticcheck
for _, dimens := range bench.Dimensions {
randomSearchStatXD := &stats.Stats{
Algo: "Random Search",
Dimens: int(dimens),
Iterations: minIters,
Generations: bench.MaxFES,
}
schwefelStats := &stats.FuncStats{BenchName: "Schwefel"}
deJong1stStats := &stats.FuncStats{BenchName: "De Jong 1st"}
deJong2ndStats := &stats.FuncStats{BenchName: "De Jong 2nd"}
randomSearchStatXD.Dimens = int(dimens)
var resultsSchwefel Values
var resultsDeJong1st Values
var resultsDeJong2nd Values
log.Println("dimens:", dimens)
// current iteration is named iter
for iter := 0; iter < minIters; iter++ {
if iter == 29 {
log.Println("iter:", iter, "(last for dimen)")
}
var bestResult float64
for j := 0; j < int(fes); j++ {
// run Schwefel.
v, r := singleRandomSearch(
dimens,
bench.Functions["Schwefel"],
bench.SchwefelParams.Min(),
bench.SchwefelParams.Max(),
)
valsSchwefel = append(valsSchwefel, v)
resultsSchwefel = append(resultsSchwefel, r)
// collect results after each call to bench function.
schwefelStats.Solution = append(
schwefelStats.Solution,
stats.BenchRound{Iteration: iter, Results: resultsSchwefel},
)
switch j {
// first iteration
case 0:
bestResult = r
default:
// any other than the first iteration and a better solution
if r < bestResult {
bestResult = r
}
}
bestResultsSchwefel = append(bestResultsSchwefel, bestResult)
}
// add schwefel results (for the current dimension) to the stats
// slice.
randomSearchStatXD.BenchFuncStats = append(
randomSearchStatXD.BenchFuncStats,
*schwefelStats,
)
for k := 0; k < int(fes); k++ {
// run De Jong 1st.
v, r := singleRandomSearch(
dimens,
bench.Functions["De Jong 1st"],
bench.DeJong1Params.Min(),
bench.DeJong1Params.Max(),
)
valsDeJong1st = append(valsDeJong1st, v)
resultsDeJong1st = append(resultsDeJong1st, r)
// collect results after each call to bench function.
deJong1stStats.Solution = append(
deJong1stStats.Solution,
stats.BenchRound{Iteration: iter, Results: resultsDeJong1st},
)
// first iteration or better solution
if k == 0 || r < bestResult {
bestResult = r
}
bestResultsDeJong1st = append(bestResultsDeJong1st, bestResult)
}
// add De Jong 1st results (for the current dimension) to the stats
// slice.
randomSearchStatXD.BenchFuncStats = append(
randomSearchStatXD.BenchFuncStats,
*deJong1stStats,
)
for l := 0; l < int(fes); l++ {
// run De Jong 2nd.
v, r := singleRandomSearch(
dimens,
bench.Functions["De Jong 2nd"],
bench.DeJong2Params.Min(),
bench.DeJong2Params.Max(),
)
valsDeJong2nd = append(valsDeJong2nd, v)
resultsDeJong2nd = append(resultsDeJong2nd, r)
// collect results after each call to bench function.
deJong2ndStats.Solution = append(
deJong2ndStats.Solution,
stats.BenchRound{Iteration: iter, Results: resultsDeJong2nd},
)
// first iteration or better solution
if l == 0 || r < bestResult {
bestResult = r
}
bestResultsDeJong2nd = append(bestResultsDeJong2nd, bestResult)
}
// add De Jong 2nd results (for the current dimension) to the stats
// slice.
randomSearchStatXD.BenchFuncStats = append(
randomSearchStatXD.BenchFuncStats,
*deJong2ndStats,
)
// append the stats slice for the current dimension to the (per
// algo) stats slice.
randomSearchStats = append(randomSearchStats, *randomSearchStatXD)
if iter == 29 {
log.Println("do plot")
doCombined30Plot(
schwefelStats,
dimens,
picPrefix+"schw-"+fmt.Sprint(dimens)+"D.svg",
)
doCombined30Plot(deJong1stStats,
dimens,
picPrefix+"dj1-"+fmt.Sprint(dimens)+"D.svg",
)
doCombined30Plot(deJong2ndStats,
dimens,
picPrefix+"dj2-"+fmt.Sprint(dimens)+"D.svg",
)
}
}
}
}