math-optim/algo/ga/somat3a.go
leo 74109521b0
All checks were successful
continuous-integration/drone/push Build is passing
ga: implement SOMA T3A algorithm
2023-02-21 23:51:52 +01:00

280 lines
7.2 KiB
Go

// Copyright 2023 wanderer <a_mirre at utb dot cz>
// SPDX-License-Identifier: GPL-3.0-or-later
package ga
import (
"sort"
"time"
"git.dotya.ml/wanderer/math-optim/bench/cec2020"
"git.dotya.ml/wanderer/math-optim/stats"
"golang.org/x/exp/rand"
"gonum.org/v1/gonum/stat/distuv"
)
// SOMAT3A holds the settings for an instance of SOMA T3A algorithm.
// nolint: unused
type SOMAT3A struct {
// Generations is the number of generations to evolve for. Disable limit
// with -1.
Generations int
// BenchMinIters is the number of iterations that the bench function will
// be re-run.
BenchMinIters int
// Dimensions in which to look for a solution.
Dimensions []int
// NP is the initial population size.
NP int
// K is the number of individuals to choose the leader from.
K int
// M denotes how many individuals are picked from the population to form a
// `team` during the organisation phase, can be thought of as "team size".
M int
// N is the number of best individuals in each team selected for actual
// migration.
N int
// Njumps is the fixed number of jumps that each chosen migrating
// individual performs on their way to the leader.
Njumps int
// BenchName is the human-friendly name of the benchmarking function.
BenchName string
// ch is a channel for writing back computed results.
ch chan []stats.Stats
// chAlgoMeans is a channel for writing back algo means.
chAlgoMeans chan *stats.AlgoBenchMean
// rng is a random number generator.
rng distuv.Uniform
// initialised denotes the initialisation state of the struct.
initialised bool
}
var somat3aLogger = newLogger(" ***  SOMA T3A")
// Init performs checks on the input params and initialises an instance of
// SOMAT3A.
func (s *SOMAT3A) Init(
generations, benchMinIters, np, k, m, n, njumps int,
dimensions []int,
bench string,
ch chan []stats.Stats,
chAlgoMeans chan *stats.AlgoBenchMean,
) error {
switch {
case generations == 0:
return errGenerationsIsZero
case generations == -1:
somat3aLogger.Println("Generations is '-1', disabling generation limits")
case benchMinIters < 1:
return errBenchMinItersIsLTOne
case np == 0:
return errNPIsZero
case k == 0:
return errKIsZero
case m == 0:
return errMIsZero
case n == 0:
return errNIsZero
case njumps == 0:
return errNjumpsIsZero
case len(dimensions) == 0:
return errDimensionsUnset
case bench == "":
return errBenchUnset
}
s.Generations = generations
s.BenchMinIters = benchMinIters
s.NP = np
s.K = k
s.M = m
s.N = n
s.Njumps = njumps
s.Dimensions = dimensions
s.BenchName = bench
s.ch = ch
s.chAlgoMeans = chAlgoMeans
s.initialised = true
gaLogger.Printf("%s init done for bench %s", s.getAlgoName(), s.BenchName)
return nil
}
// Run runs the SOMA T3A algo for each of `s.Dimensions` with benchmarking
// function `s.BenchName`, repeats the run `s.BenchMinIters` times and returns
// the stats via chans.
func (s *SOMAT3A) Run() {
if !s.initialised {
somat3aLogger.Fatalln(s.getAlgoName(), "needs to be initialised before calling Run(), exiting..")
}
var somat3aStats []stats.Stats
// testing purposes.
// s.BenchMinIters = 5
somat3aMeans := &stats.AlgoBenchMean{
Algo: s.getAlgoName(),
BenchMeans: make([]stats.BenchMean, 0, len(s.Dimensions)),
}
for _, dim := range s.Dimensions {
maxFES := cec2020.GetMaxFES(dim)
// TODO(me): check the slides for the formula to calculate this.
// 4 fes per member per njump.
// fesPerIter := int(float64(maxFES / (4 * s.NP * s.Njumps)))
fesPerIter := int(float64(maxFES / (4 * s.NP)))
somat3aStatDimX := &stats.Stats{
Algo: s.getAlgoName(),
Dimens: dim,
Iterations: s.BenchMinIters,
Generations: maxFES,
NP: s.NP,
}
funcStats := &stats.FuncStats{BenchName: s.BenchName}
dimXMean := &stats.BenchMean{
Bench: s.BenchName,
Dimens: dim,
Iterations: s.BenchMinIters,
Generations: maxFES,
Neighbours: -1,
}
uniDist := distuv.Uniform{
Min: cec2020.SearchRange.Min(),
Max: cec2020.SearchRange.Max(),
}
if maxFES == -1 {
somat3aLogger.Fatalf("could not get maxFES for current dim (%d), bailing", dim)
}
somat3aLogger.Printf("running bench \"%s\" for %dD, maxFES: %d\n",
s.BenchName, dim, maxFES,
)
funcStats.BenchResults = make([]stats.BenchRound, s.BenchMinIters)
// rand.Seed(uint64(time.Now().UnixNano()))
// create and seed a source of pseudo-randomness
src := rand.NewSource(uint64(rand.Int63()))
// track execution duration.
start := time.Now()
for iter := 0; iter < s.BenchMinIters; iter++ {
somat3aLogger.Printf("run: %d, bench: %s, %dD, started at %s",
iter, s.BenchName, dim, start,
)
funcStats.BenchResults[iter].Iteration = iter
uniDist.Src = src
// uniDist.Src = rand.NewSource(uint64(rand.Int63()))
// create a population with known params.
pop := newSOMAT3APopulation(s.BenchName, s.NP, s.K, s.M, s.N, s.Njumps, dim)
// set population seed.
pop.Seed = uint64(time.Now().UnixNano())
// initialise the population.
pop.Init()
bestResult := s.evaluate(pop)
// the core.
for i := 0; i < fesPerIter; i++ {
funcStats.BenchResults[iter].Results = append(
funcStats.BenchResults[iter].Results,
bestResult,
)
// call evolve where the inner workngs of SOMA T3A run.
s.evolve(pop)
// take measurements of current population fitness.
r := s.evaluate(pop)
// save if better.
if r < bestResult {
bestResult = r
}
// this block makes sure we properly count func evaluations for
// the purpose of correctly comparable plot comparison. i.e.
// append the winning (current best) value 4*NP-1 (the first
// best is already saved at this point) times to represent the
// fact that while evaluating (and comparing) other population
// individuals to the current best value is taking place in the
// background, the current best value itself is kept around and
// symbolically saved as the best of the Generation.
for x := 0; x < 4*s.NP-1; x++ {
funcStats.BenchResults[iter].Results = append(
funcStats.BenchResults[iter].Results,
bestResult,
)
}
}
}
elapsed := time.Since(start)
somat3aLogger.Printf("completed: bench: %s, %dD, computing took %s\n",
s.BenchName, dim, elapsed,
)
// get mean vals.
dimXMean.MeanVals = stats.GetMeanVals(funcStats.BenchResults, maxFES)
funcStats.MeanVals = dimXMean.MeanVals
somat3aStatDimX.BenchFuncStats = append(
somat3aStatDimX.BenchFuncStats, *funcStats,
)
somat3aStats = append(somat3aStats, *somat3aStatDimX)
somat3aMeans.BenchMeans = append(somat3aMeans.BenchMeans, *dimXMean)
}
sort.Sort(somat3aMeans)
s.chAlgoMeans <- somat3aMeans
s.ch <- somat3aStats
}
func (s *SOMAT3A) evaluate(pop *SOMAT3APopulation) float64 {
bestIndividual := pop.Population[pop.GetBestIdx()]
return pop.ProblemFunc(bestIndividual.CurX)
}
func (s *SOMAT3A) evolve(pop *SOMAT3APopulation) {
// organise.
leader, migrants := pop.organise()
// migrate.
pop.migrate(leader, migrants)
// actualise (position, best values..).
pop.actualise(migrants)
}
func (s *SOMAT3A) getAlgoName() string {
return "SOMA T3A"
}
// NewSOMAT3A returns a pointer to a new, uninitialised SOMAT3A instance.
func NewSOMAT3A() *SOMAT3A {
return &SOMAT3A{}
}