diff --git a/algo/de/doc.go b/algo/de/doc.go new file mode 100644 index 0000000..c6dff43 --- /dev/null +++ b/algo/de/doc.go @@ -0,0 +1,6 @@ +// Copyright 2023 wanderer +// SPDX-License-Identifier: GPL-3.0-or-later + +// Package de contains implementation details of Differential Evolution kind of +// algorithms. +package de diff --git a/algo/de/jDE.go b/algo/de/jDE.go new file mode 100644 index 0000000..64e2751 --- /dev/null +++ b/algo/de/jDE.go @@ -0,0 +1,139 @@ +// Copyright 2023 wanderer +// SPDX-License-Identifier: GPL-3.0-or-later + +package de + +import ( + "log" + "os" + "sync" + + "git.dotya.ml/wanderer/math-optim/stats" +) + +// JDE is a holder for the settings of an instance of a self-adapting +// differential evolution (jDE) algorithm. +type JDE struct { + // Generations denotes the number of generations the population evolves + // for. Special value -1 disables limiting the number of generations. + Generations int + // Dimensions to solve the problem for. + Dimensions []int + // F is the differential weight (mutation/weighting factor). + F float64 + // CR is the crossover probability constant. + CR float64 + // MutationStrategy selects the mutation strategy, i.e. the variant of the + // jDE algorithm (0..17), see mutationStrategies.go for more details. + MutationStrategy int + // AdptScheme is the parameter self-adaptation scheme (0..1). + AdptScheme int + // NP is the initial population size. + NP int + // Population is a pointer to population + Population *Population + // BenchName is a name of the problem to optimise. + BenchName string + // initialised denotes the initialisation state of the struct. + initialised bool +} + +const ( + // jDEMinNP is the minimum size of the initial population for jDE. + jDEMinNP = 4 + // fMin is the minimum allowed value of the differential weight. + fMin = 0.5 + // fMax is the maximum allowed value of the differential weight. + fMax = 2.0 + // crMin is the minimum allowed value of the crossover probability constant. + crMin = 0.2 + // crMax is the maximum allowed value of the crossover probability constant. + crMax = 0.9 +) + +// jDELogger declares and initialises a "custom" jDE logger. +var jDELogger = log.New(os.Stderr, " *** δ jDE:", log.Ldate|log.Ltime|log.Lshortfile) + +// Init initialises the jDE algorithm, performs sanity checks on the inputs. +func (j *JDE) Init(generations, mutStrategy, adptScheme, np int, f, cr float64, dimensions []int, bench string, ch chan []stats.Stats) { + if j == nil { + jDELogger.Fatalln("jDE needs to be initialised before calling RunjDE, exiting...") + } + + // check input parameters. + switch { + case generations == 0: + jDELogger.Fatalln("Generations cannot be 0, got", generations) + + case generations == -1: + jDELogger.Println("Generations is '-1', disabling generation limits..") + + case mutStrategy < 0 || mutStrategy > 17: + jDELogger.Fatalln("Mutation strategy needs to be from the interval <0; 17>, got", mutStrategy) + + case adptScheme < 0 || adptScheme > 1: + jDELogger.Fatalln("Parameter self-adaptation scheme needs to be from the interval <0; 1>, got", adptScheme) + + case np < jDEMinNP: + jDELogger.Fatalf("NP needs to be greater than %d, got: %d\n.", jDEMinNP, np) + + case f < fMin || f > fMax: + jDELogger.Fatalf("F needs to be from the interval <%f;%f>, got: %f\n.", fMin, fMax, f) + + case cr < crMin || cr > crMax: + jDELogger.Fatalf("CR needs to be from the interval <%f;>%f, got: %f\n.", crMin, crMax, cr) + + case len(dimensions) == 0: + jDELogger.Fatalf("Dimensions cannot be empty, got: %+v\n", dimensions) + + case bench == "": + jDELogger.Fatalln("Bench cannot be empty, got:", bench) + } + + j.Generations = generations + j.MutationStrategy = mutStrategy + j.AdptScheme = adptScheme + j.NP = np + j.F = f + j.CR = cr + j.Dimensions = dimensions + j.BenchName = bench + j.initialised = true + + pop := newPopulation(bench, j.NP) + + pop.Init() +} + +// Evolve evolves a population by running the jDE (self-adapting Differential +// Evolution) algorithm on the passed population until termination conditions +// are met. +func (j *JDE) Evolve(maxFES int, wg *sync.WaitGroup) {} + +// Run self-adapting differential evolution algorithm. +func RunjDE(jDE *JDE) {} + +// NewjDE returns a pointer to a new, uninitialised jDE instance. +func NewjDE() *JDE { + return &JDE{} +} + +// LogPrintln wraps the jDE logger's Println func. +func LogPrintln(v ...any) { + jDELogger.Println(v...) +} + +// LogPrintf wraps the jDE logger's Printf func. +func LogPrintf(s string, v ...any) { + jDELogger.Printf(s, v...) +} + +// LogFatalln wraps the jDE logger's Fatalln func. +func LogFatalln(s string) { + jDELogger.Fatalln(s) +} + +// LogFatalf wraps the jDE logger's Fatalf func. +func LogFatalf(s string, v ...any) { + jDELogger.Fatalf(s, v...) +} diff --git a/algo/de/mutationStrategies.go b/algo/de/mutationStrategies.go new file mode 100644 index 0000000..eedfc02 --- /dev/null +++ b/algo/de/mutationStrategies.go @@ -0,0 +1,51 @@ +// Copyright 2023 wanderer +// SPDX-License-Identifier: GPL-3.0-or-later + +package de + +const ( + // DEBest1Exp is the DE/best/1/exp strategy. + DEBest1Exp int = iota + // DERand1Exp is the DE/rand/1/exp strategy. + DERand1Exp + // DERandtoBest1Exp is the DE/rand-to-best/1/exp strategy. + DERandtoBest1Exp + // DEBest2Exp is the DE/best/2/exp strategy. + DEBest2Exp + // DERand2Exp is the DE/rand/2/exp strategy. + DERand2Exp + + // binomial cross-over strategies. + + // DEBest1Bin is the DE/best/1/bin strategy. + DEBest1Bin + // DERand1Bin is the DE/rand/1/bin strategy. + DERand1Bin + // DERandtoBest1Bin is the DE/rand-to-best/1/bin strategy. + DERandtoBest1Bin + // DEBest2Bin is the DE/best/2/bin strategy. + DEBest2Bin + // DERand2Bin is the DE/rand/2/bin strategy. + DERand2Bin + + // 3-wide changes. + + // DEBest3Exp is the DE/best/3/exp strategy. + DEBest3Exp + // DEBest3Bin is the DE/best/3/bin strategy. + DEBest3Bin + // DERand3Exp is the DE/rand/3/exp strategy. + DERand3Exp + // DERand3Bin is the DE/rand/3/bin strategy. + DERand3Bin + + // DERandtoCurrent2E is the DE/rand-to-current/2/exp strategy. + DERandtoCurrent2Exp + // DERandtoCurrent2Bin is the DE/rand-to-current/2/bin strategy. + DERandtoCurrent2Bin + + // DERandtoBestandCurrent2Exp is the DE/rand-to-best-and-current/2/exp strategy. + DERandtoBestandCurrent2Exp + // DERandtoBestandCurrent2Bin is the DE/rand-to-best-and-current/2/bin strategy. + DERandtoBestandCurrent2Bin +) diff --git a/algo/de/population.go b/algo/de/population.go new file mode 100644 index 0000000..8b3353d --- /dev/null +++ b/algo/de/population.go @@ -0,0 +1,75 @@ +// Copyright 2023 wanderer +// SPDX-License-Identifier: GPL-3.0-or-later + +package de + +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 representats a single population individual. +type PopulationIndividual struct { + CurX DecisionVector + CurV DecisionVector + CurC ConstraintVector + 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 + C ConstraintVector + F FitnessVector +} + +// Population groups population individuals (agents) with metadata about the population. +type Population struct { + // Population is a slice of population individuals. + Population []PopulationIndividual + // Problem is the current benchmarking function this population is attempting to optimise. + Problem string + // Seed is the value used to (re)init population. + Seed uint64 +} + +// GetIndividal returns a reference to individual at position n. +func (p *Population) GetIndividual(n uint) *PopulationIndividual { return &PopulationIndividual{} } +func (p *Population) GetBestIdx() int { return 0 } +func (p *Population) GetWorstIdx() int { return 0 } +func (p *Population) SetX(n int, nuX DecisionVector) {} +func (p *Population) SetV(n int, nuV DecisionVector) {} + +// Init initialises all individuals. +func (p *Population) Init() {} + +// Reinit reinitialises all individuals. +func (p *Population) Reinit() {} + +// ReinitN reinitialises the individual at position n. +func (p *Population) ReinitN(n uint) {} +func (p *Population) Clear() {} + +// meanVelocity computes the mean current velocity of all individuals in the population. +func (p *Population) MeanVelocity() float64 { return 0.0 } + +// newPopulation returns a pointer to a new, uninitialised population. +func newPopulation(benchProblem string, np int) *Population { + p := &Population{} + + p.Problem = benchProblem + + // pre-alloc. + p.Population = make([]PopulationIndividual, 0, np) + + return p +} diff --git a/algo/jDE.go b/algo/jDE.go deleted file mode 100644 index ce56f82..0000000 --- a/algo/jDE.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2022 wanderer -// SPDX-License-Identifier: GPL-3.0-or-later - -package algo - -// Evolve runs an evolution algorithm on population (WIP). -func Evolve(population any) any { - return population -}