// Copyright 2022 wanderer // 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.png", ) doCombined30Plot(deJong1stStats, dimens, picPrefix+"dj1-"+fmt.Sprint(dimens)+"D.png", ) doCombined30Plot(deJong2ndStats, dimens, picPrefix+"dj2-"+fmt.Sprint(dimens)+"D.png", ) } } } }