// Copyright 2023 wanderer // SPDX-License-Identifier: GPL-3.0-or-later package de import ( "log" "os" "sync" "git.dotya.ml/wanderer/math-optim/bench" "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() } // Run self-adapting differential evolution algorithm. func (j *JDE) Run() { if j == nil { jDELogger.Fatalln("jDE is nil, NewjDE() needs to be called first. exiting...") } if !j.initialised { jDELogger.Fatalln("jDE needs to be initialised before calling Run(), exiting...") } // have a wait group. var wg sync.WaitGroup // we're be spawning goroutines per dimension and that is the number of // goroutines we need to wait for. wg.Add(len(j.Dimensions)) // run Evolve for for all dimensions. for _, dim := range j.Dimensions { maxFES := bench.GetGAMaxFES(dim) j.evolve(maxFES, &wg) } // wait for all. wg.Wait() } // 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) {} // 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...) }