// Copyright 2023 wanderer // SPDX-License-Identifier: GPL-3.0-or-later package ga import ( "git.dotya.ml/wanderer/math-optim/bench/cec2020" "golang.org/x/exp/rand" "gonum.org/v1/gonum/stat/distuv" ) type ( // DecisionVector is a []float64 abstraction representing the decision vector. DecisionVector []float64 // FitnessVector is a []float64 abstraction representing the fitness vector. FitnessVector []float64 // ConstraintVector is a []float64 abstraction representing the constraint vector. ConstraintVector []float64 ) // PopulationIndividual represents a single population individual. type PopulationIndividual struct { CurX DecisionVector CurF FitnessVector BestX DecisionVector BestC ConstraintVector BestF FitnessVector } // ChampionIndividual is a representation of the best individual currently // available in the population. type ChampionIndividual struct { X DecisionVector // CR float64 // F float64 } // Population groups population individuals (agents) with metadata about the population. type Population struct { // Population is a slice of population individuals. Population []PopulationIndividual // Champion represents the best individual of the population. Champion ChampionIndividual // Problem is the current benchmarking function this population is attempting to optimise. Problem string // ProblemFunction is the actual function to optimise. ProblemFunc func([]float64) float64 // Dimen is the dimensionality of the problem being optimised. Dimen int // Seed is the value used to (re)init population. Seed uint64 // f is the differential weight (mutation/weighting factor) adapted over time. f []float64 // cr is the crossover probability constant adapted over time. cr []float64 // BestF is the best recorded value of the differential weight F. BestF float64 // BestCR is the best recorded value of the differential weight CR. BestCR float64 // CurF is the current value of F. CurF float64 // CurCR is the current value of the differential weight CR. CurCR float64 } // GetIndividual returns a reference to individual at position n. func (p *Population) GetIndividual(n uint) *PopulationIndividual { return &PopulationIndividual{} } // GetBestIdx returns the index of the best population individual. func (p *Population) GetBestIdx() int { f := p.ProblemFunc bestIndividual := 0 // the first one is the best one. bestVal := f(p.Population[0].CurX) for i, v := range p.Population { current := f(v.CurX) if current < bestVal { bestIndividual = i } } return bestIndividual } // GetWorstIdx returns the index of the worst population individual. func (p *Population) GetWorstIdx() int { f := p.ProblemFunc worstIndividual := 0 // the first one is the worst one. worstVal := f(p.Population[0].CurX) for i, v := range p.Population { current := f(v.CurX) if current > worstVal { worstIndividual = i } } return worstIndividual } // Init initialises all individuals to random values. func (p *Population) Init() { uniform := distuv.Uniform{ Min: cec2020.SearchRange.Min(), Max: cec2020.SearchRange.Max(), } uniform.Src = rand.NewSource(p.Seed) // gaLogger.Printf("population initialisation - popCount: %d, seed: %d\n", // len(p.Population), p.Seed, // ) for i, v := range p.Population { v.CurX = make([]float64, p.Dimen) for j := 0; j < p.Dimen; j++ { v.CurX[j] = uniform.Rand() } p.Population[i] = v } p.f = make([]float64, p.Size()) p.cr = make([]float64, p.Size()) p.Champion = ChampionIndividual{X: p.Population[p.GetBestIdx()].CurX} } // Reinit reinitialises all individuals. func (p *Population) Reinit() { p.Init() } // ReinitN reinitialises the individual at position n. func (p *Population) ReinitN(n uint) {} // Clear sets all vectors to 0. func (p *Population) Clear() { if p.Population != nil { for _, v := range p.Population { v.CurX = make([]float64, p.Dimen) v.CurF = make([]float64, p.Dimen) v.BestX = make([]float64, p.Dimen) v.BestC = make([]float64, p.Dimen) v.BestF = make([]float64, p.Dimen) } } } func (p *Population) SelectDonors(currentIdx int) []PopulationIndividual { popCount := p.Size() idcs := make([]int, 0, popCount-1) // gather indices. for k := 0; k < popCount; k++ { if k != currentIdx { idcs = append(idcs, k) } } // randomly choose 3 of those idcs. selectedIdcs := make([]int, 0) selectedA := false selectedB := false selectedC := false for !selectedA { candidateA := rand.Intn(len(idcs)) % len(idcs) if candidateA != currentIdx { selectedIdcs = append(selectedIdcs, candidateA) selectedA = true } } for !selectedB { a := selectedIdcs[0] candidateB := rand.Intn(len(idcs)) % len(idcs) if candidateB != currentIdx && candidateB != a { selectedIdcs = append(selectedIdcs, candidateB) selectedB = true } } for !selectedC { a := selectedIdcs[0] b := selectedIdcs[1] candidateC := rand.Intn(len(idcs)) % len(idcs) if candidateC != currentIdx && candidateC != a && candidateC != b { selectedIdcs = append(selectedIdcs, candidateC) selectedC = true } } // selected contains the selected population individuals. selected := make([]PopulationIndividual, 0) // select individuals for donation. for _, idx := range selectedIdcs { for k := 0; k < popCount; k++ { if k == idx { selected = append(selected, p.Population[idx]) } } } return selected } // MeanVelocity computes the mean current velocity of all individuals in the population. func (p *Population) MeanVelocity() float64 { return 0.0 } // Size returns the number of population individuals. func (p *Population) Size() int { return len(p.Population) } // newPopulation returns a pointer to a new, uninitialised population. func newPopulation(benchProblem string, np, dimen int) *Population { p := &Population{ Problem: benchProblem, ProblemFunc: cec2020.Functions[benchProblem], Dimen: dimen, Population: make([]PopulationIndividual, np), } return p }