math-optim/algo/randomSearch.go
surtur 26065d3f10
All checks were successful
continuous-integration/drone/push Build is passing
go(algo): seed prng with "time.Now().UnixNano()"
2022-07-05 21:46:09 +02:00

167 lines
4.5 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"
"time"
"git.dotya.ml/wanderer/math-optim/bench"
"git.dotya.ml/wanderer/math-optim/stats"
"golang.org/x/exp/rand"
"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,
Src: rand.NewSource(uint64(time.Now().UnixNano())),
}
genValsRandomSearch(dimens, vals, &uniformDist)
// result of the bench function.
res := f(vals)
return vals, res
}
func RandomSearchNG(maxFES, benchMinIters int, theD []int, benchFunc string) []stats.Stats {
// perform basic sanity checks.
if maxFES <= 0 {
log.Fatalln(fmtRandomSearchOut("maxFES cannot be <= 0, bailing"))
} else if benchMinIters <= 0 {
log.Fatalln(fmtRandomSearchOut("benchMinIters cannot be <= 0, bailing"))
} else if _, ok := bench.Functions[benchFunc]; !ok {
log.Fatalln(fmtRandomSearchOut(
"unknown benchFunc used: '" + benchFunc + "', bailing",
))
}
for i := range theD {
if theD[i] <= 0 {
log.Fatalln(fmtRandomSearchOut(" no dimension in D can be <= 0, bailing"))
}
}
// use func-local vars.
var (
fes int
localD []int
minIters int
randomSearchStats []stats.Stats
)
fes = maxFES
localD = theD
minIters = benchMinIters
// iterate over whatever was passed to us with theD - dimens slice.
for _, dimens := range localD {
randomSearchStatDimX := &stats.Stats{
Algo: "Random Search",
Dimens: dimens,
Iterations: minIters,
Generations: fes,
}
funcStats := &stats.FuncStats{BenchName: benchFunc}
benchFuncParams := bench.FunctionParams[benchFunc]
printRandomSearch("running bench \"" + benchFunc + "\" for " +
fmt.Sprint(randomSearchStatDimX.Dimens) + "D")
funcStats.Solution = make([]stats.BenchRound, minIters)
// perform the while dance 'minIters' times for "statistical relevance"
for iter := 0; iter < minIters; iter++ {
// have a fresh bestResult for each of the 'minIters' runs.
var bestResult float64
// set current iteration in funcStats.
funcStats.Solution[iter].Iteration = iter
// run the benchmarking function 'fes' times.
for i := 0; i < fes; i++ {
_, r := singleRandomSearch(
uint(dimens),
bench.Functions[benchFunc],
benchFuncParams.Min(),
benchFuncParams.Max(),
)
// is a switch efficient, or should an if statement be used..?
// TODO(me): perhaps benchmark this...
switch i {
case 0:
// if it's our first, bestResult is the result we just got.
bestResult = r
default:
// since we're minimising, the lower 'r' is the better.
if r < bestResult {
bestResult = r
}
}
// save the 'best' result, since we only care about those.
funcStats.Solution[iter].Results = append(
funcStats.Solution[iter].Results,
bestResult,
)
}
}
// save cumulative results of 'minIters' runs.
randomSearchStatDimX.BenchFuncStats = append(
randomSearchStatDimX.BenchFuncStats,
*funcStats,
)
// save stats for each dimension to a stats slice.
randomSearchStats = append(randomSearchStats, *randomSearchStatDimX)
}
return randomSearchStats
}