From c7fb224c7d17691aaff0f5ce63987df9f3329c97 Mon Sep 17 00:00:00 2001 From: Art Wild Date: Tue, 28 Aug 2018 13:18:57 -0400 Subject: [PATCH 01/12] added project file --- Project.toml | 14 ++++++++++++++ README.md | 7 +++++++ 2 files changed, 21 insertions(+) create mode 100644 Project.toml diff --git a/Project.toml b/Project.toml new file mode 100644 index 0000000..cd8846c --- /dev/null +++ b/Project.toml @@ -0,0 +1,14 @@ +name = "Evolutionary" +uuid = "86b6b26d-c046-49b6-aa0b-5f0f74682bd6" +version = "0.2.0" + +[deps] +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[extras] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Test"] diff --git a/README.md b/README.md index 7a325b3..7148561 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,17 @@ A Julia package for [evolutionary](http://www.scholarpedia.org/article/Evolution ## Installation +For julia 0.6 and lower, run following command + ```julia Pkg.add("Evolutionary") ``` +For julia 0.7 and higher, run in the package manager mode +``` +pkg> add https://github.com/wildart/Evolutionary.jl.git#v0.2.0 +``` + ## Functionalities #### Algorithms From e38537beb9c562d56ffcb45c5bdcda96fe1cc9cf Mon Sep 17 00:00:00 2001 From: Andrew Haselgrove Date: Wed, 18 Sep 2019 16:08:54 +1000 Subject: [PATCH 02/12] Separated out functionalities from initPopulation `initPopulation` was a heavily overloaded parameter, with a variety of types corresponding to a range of possible behaviours. This commit: - Removes the option to pass a vector representing the search space. This is specific and unclear, and behaviour can easily be easily represented by one of the other options - Restricts `initPopulation` to be a vector of individuals (where an individual is a single member of the population) - Adds a new parameter `creation` to represent a function to create an individual. Default behaviour has not been changed, but is now represented by a creation function. --- src/Evolutionary.jl | 19 ++++++------------- src/cmaes.jl | 5 +++-- src/es.jl | 25 ++++++++++++------------- src/ga.jl | 24 ++++++++++-------------- test/knapsack.jl | 6 +++--- test/n-queens.jl | 4 ++-- test/rosenbrock.jl | 9 ++++----- 7 files changed, 40 insertions(+), 52 deletions(-) diff --git a/src/Evolutionary.jl b/src/Evolutionary.jl index 69594de..d1abf5a 100644 --- a/src/Evolutionary.jl +++ b/src/Evolutionary.jl @@ -17,7 +17,6 @@ using Random es, cmaes, ga const Strategy = Dict{Symbol,Any} - const Individual = Union{Vector, Matrix, Function, Nothing} # Wrapping function for strategy function strategy(; kwargs...) @@ -37,20 +36,14 @@ using Random end # Obtain individual - function getIndividual(init::Individual, N::Int) - if isa(init, Vector) - @assert length(init) == N "Dimensionality of initial population must be $(N)" - individual = init - elseif isa(init, Matrix) - @assert size(init, 1) == N "Dimensionality of initial population must be $(N)" - populationSize = size(init, 2) - individual = init[:, 1] - elseif isa(init, Function) # Creation function - individual = init(N) + function getIndividual(init::Union{Nothing, Vector}, creation::Function, N::Int) + if !isnothing(init) + individual = init[1] + @assert length(individual) == N "Dimensionality of initial population must be $(N)" else - individual = ones(N) + individual = creation(N) end - return individual + return individual end # Collecting interim values diff --git a/src/cmaes.jl b/src/cmaes.jl index e3095f8..276ba42 100644 --- a/src/cmaes.jl +++ b/src/cmaes.jl @@ -9,8 +9,9 @@ using LinearAlgebra using Statistics function cmaes( objfun::Function, N::Int; - initPopulation::Individual = ones(N), + initPopulation::Union{Nothing, Vector} = nothing, initStrategy::Strategy = strategy(τ = sqrt(N), τ_c = N^2, τ_σ = sqrt(N)), + creation::Function = (n -> rand(n)), μ::Integer = 1, λ::Integer = 1, iterations::Integer = 1_000, @@ -20,7 +21,7 @@ function cmaes( objfun::Function, N::Int; @assert μ < λ "Offspring population must be larger then parent population" # Initialize parent population - individual = getIndividual(initPopulation, N) + individual = getIndividual(initPopulation, creation, N) population = fill(individual, μ) offspring = Array{typeof(individual)}(undef, λ) fitpop = fill(Inf, μ) diff --git a/src/es.jl b/src/es.jl index a3a8c48..2e6be67 100644 --- a/src/es.jl +++ b/src/es.jl @@ -11,13 +11,14 @@ # Plus-selection: parents are deterministically selected from the set of both the parents and offspring # function es( objfun::Function, N::Int; - initPopulation::Individual = ones(N), + initPopulation::Union{Nothing, Vector} = nothing, initStrategy::Strategy = strategy(), - recombination::Function = (rs->rs[1]), - srecombination::Function = (ss->ss[1]), - mutation::Function = ((r,m)->r), - smutation::Function = (s->s), - termination::Function = (x->false), + creation::Function = (n -> rand(n)), + recombination::Function = (rs -> rs[1]), + srecombination::Function = (ss -> ss[1]), + mutation::Function = ((r, m) -> r), + smutation::Function = (s -> s), + termination::Function = (x -> false), μ::Integer = 1, ρ::Integer = μ, λ::Integer = 1, @@ -34,16 +35,14 @@ function es( objfun::Function, N::Int; store = Dict{Symbol,Any}() # Initialize parent population - individual = getIndividual(initPopulation, N) + individual = getIndividual(initPopulation, creation, N) population = fill(individual, μ) fitness = zeros(μ) for i in 1:μ - if isa(initPopulation, Vector) - population[i] = initPopulation.*rand(N) - elseif isa(initPopulation, Matrix) - population[i] = initPopulation[:, i] - else # Creation function - population[i] = initPopulation(N) + if !isnothing(initPopulation) + population[i] = initPopulation[i] + else + population[i] = creation(N) end fitness[i] = objfun(population[i]) debug && println("INIT $(i): $(population[i]) : $(fitness[i])") diff --git a/src/ga.jl b/src/ga.jl index 2038b55..d34ee0f 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -2,8 +2,7 @@ # ================== # objfun: Objective fitness function # N: Search space dimensionality -# initPopulation: Search space dimension ranges as a vector, or initial population values as matrix, -# or generation function which produce individual population entities. +# initPopulation: Initial population values as matrix # populationSize: Size of the population # crossoverRate: The fraction of the population at the next generation, not including elite children, # that is created by the crossover function. @@ -13,16 +12,17 @@ # Floating number specifies fraction of population. # function ga(objfun::Function, N::Int; - initPopulation::Individual = ones(N), + initPopulation::Union{Nothing, Vector} = nothing, lowerBounds::Union{Nothing, Vector} = nothing, upperBounds::Union{Nothing, Vector} = nothing, populationSize::Int = 50, crossoverRate::Float64 = 0.8, mutationRate::Float64 = 0.1, ɛ::Real = 0, - selection::Function = ((x,n)->1:n), - crossover::Function = ((x,y)->(y,x)), - mutation::Function = (x->x), + creation::Function = (n -> rand(n)), + selection::Function = ((x, n) -> 1:n), + crossover::Function = ((x, y) -> (y, x)), + mutation::Function = (x -> x), iterations::Integer = 100*N, tol = 0.0, tolIter = 10, @@ -37,21 +37,17 @@ function ga(objfun::Function, N::Int; fitFunc = inverseFunc(objfun) # Initialize population - individual = getIndividual(initPopulation, N) + individual = getIndividual(initPopulation, creation, N) fitness = zeros(populationSize) population = Array{typeof(individual)}(undef, populationSize) offspring = similar(population) # Generate population for i in 1:populationSize - if isa(initPopulation, Vector) - population[i] = initPopulation.*rand(eltype(initPopulation), N) - elseif isa(initPopulation, Matrix) - population[i] = initPopulation[:, i] - elseif isa(initPopulation, Function) - population[i] = initPopulation(N) # Creation function + if !isnothing(initPopulation) + population[i] = initPopulation[i] else - error("Cannot generate population") + population[i] = creation(N) end fitness[i] = fitFunc(population[i]) debug && println("INIT $(i): $(population[i]) : $(fitness[i])") diff --git a/test/knapsack.jl b/test/knapsack.jl index db24ed2..06ef201 100644 --- a/test/knapsack.jl +++ b/test/knapsack.jl @@ -7,12 +7,12 @@ return (total_mass <= 20) ? sum(utility .* n) : 0 end - initpop = collect(rand(Bool,length(mass))) + creation = n -> rand(Bool, n) best, invbestfit, generations, tolerance, history = ga( x -> 1 / fitness(x), # Function to MINIMISE - length(initpop), # Length of chromosome - initPopulation = initpop, + length(mass), # Length of chromosome + creation = creation, selection = roulette, # Options: sus mutation = inversion, # Options: crossover = singlepoint, # Options: diff --git a/test/n-queens.jl b/test/n-queens.jl index cc803b5..ccc2008 100644 --- a/test/n-queens.jl +++ b/test/n-queens.jl @@ -25,8 +25,8 @@ # Testing: GA solution with various mutations for muts in [inversion, insertion, swap2, scramble, shifting] result, fitness, cnt = ga(nqueens, N; - initPopulation = generatePositions, populationSize = P, + creation = generatePositions, selection = sus, crossover = pmx, mutation = muts) @@ -37,7 +37,7 @@ # Testing: ES for muts in [inversion, insertion, swap2, scramble, shifting] result, fitness, cnt = es(nqueens, N; - initPopulation = generatePositions, + creation = generatePositions, mutation = mutationwrapper(muts), μ = 15, ρ = 1, λ = P) println("(15+$(P))-ES:$(string(muts)) => F: $(fitness), C: $(cnt), OBJ: $(result)") diff --git a/test/rosenbrock.jl b/test/rosenbrock.jl index 24a00ad..c5eb58d 100644 --- a/test/rosenbrock.jl +++ b/test/rosenbrock.jl @@ -25,8 +25,8 @@ # Testing: (15/15+100)-σ-Self-Adaptation-ES # with isotropic mutation operator y' := y + σ(N_1(0, 1), ..., N_N(0, 1)) result, fitness, cnt = es(rosenbrock, N; - initPopulation = [.5, .5], - initStrategy = strategy(σ = 1.0, τ = 1/sqrt(2*N)), + initStrategy = strategy(σ = 1.0, τ = 1/sqrt(2*N)), + creation = (n -> 0.5 .* rand(n)), recombination = average, srecombination = averageSigma1, mutation = isotropic, smutation = isotropicSigma, μ = 15, λ = 100, iterations = 1000) @@ -36,7 +36,7 @@ # Testing: (15/15+100)-σ-Self-Adaptation-ES # with non-isotropic mutation operator y' := y + (σ_1 N_1(0, 1), ..., σ_N N_N(0, 1)) result, fitness, cnt = es(rosenbrock, N; - initPopulation = rand(N,25), + initPopulation = [rand(N) for _ in 1:15], initStrategy = strategy(σ = .5ones(N), τ = 1/sqrt(2*N), τ0 = 1/sqrt(N)), recombination = average, srecombination = averageSigmaN, mutation = anisotropic, smutation = anisotropicSigma, @@ -52,9 +52,8 @@ # Testing: GA result, fitness, cnt = ga(rosenbrock, N; - initPopulation = (n -> rand(n)), populationSize = 100, - ɛ = 0.1, + ɛ = 0.1, selection = sus, crossover = intermediate(0.25), mutation = domainrange(fill(0.5,N))) From 99f69fc9ca699686cf86cbd20ee0314d8624e089 Mon Sep 17 00:00:00 2001 From: Andrew Haselgrove Date: Fri, 27 Sep 2019 17:23:46 +1000 Subject: [PATCH 03/12] Fix early stopping criterion --- Manifest.toml | 23 +++++++++++++++++++++++ Project.toml | 2 +- src/cmaes.jl | 2 +- src/ga.jl | 4 ++-- test/knapsack.jl | 1 + test/n-queens.jl | 3 ++- test/rastrigin.jl | 4 ++-- 7 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 Manifest.toml diff --git a/Manifest.toml b/Manifest.toml new file mode 100644 index 0000000..dcef4a6 --- /dev/null +++ b/Manifest.toml @@ -0,0 +1,23 @@ +# This file is machine-generated - editing it directly is not advised + +[[Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" + +[[LinearAlgebra]] +deps = ["Libdl"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + +[[Random]] +deps = ["Serialization"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[SparseArrays]] +deps = ["LinearAlgebra", "Random"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + +[[Statistics]] +deps = ["LinearAlgebra", "SparseArrays"] +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" diff --git a/Project.toml b/Project.toml index cd8846c..dab1277 100644 --- a/Project.toml +++ b/Project.toml @@ -4,8 +4,8 @@ version = "0.2.0" [deps] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/src/cmaes.jl b/src/cmaes.jl index 276ba42..69929b9 100644 --- a/src/cmaes.jl +++ b/src/cmaes.jl @@ -73,7 +73,7 @@ function cmaes( objfun::Function, N::Int; # termination condition count += 1 - if count == iterations || σ < tol + if count == iterations || σ <= tol break end verbose && println("BEST: $(fitpop[1]): $(σ)") diff --git a/src/ga.jl b/src/ga.jl index d34ee0f..ae9c295 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -118,8 +118,8 @@ function ga(objfun::Function, N::Int; # Terminate: # if fitness tolerance is met for specified number of steps - if fittol < tol - if fittolitr > tolIter + if fittol <= tol + if (tolIter != -1) && (fittolitr > tolIter) break else fittolitr += 1 diff --git a/test/knapsack.jl b/test/knapsack.jl index 06ef201..4d9221f 100644 --- a/test/knapsack.jl +++ b/test/knapsack.jl @@ -20,6 +20,7 @@ crossoverRate = 0.5, ɛ = 0.1, # Elitism iterations = 20, + tolIter = 20, populationSize = 50, interim = true); diff --git a/test/n-queens.jl b/test/n-queens.jl index ccc2008..edc8227 100644 --- a/test/n-queens.jl +++ b/test/n-queens.jl @@ -1,7 +1,7 @@ @testset "n-Queens" begin N = 8 - P = 50 + P = 100 generatePositions(N::Int) = collect(1:N)[randperm(N)] # Vector of N cols filled with numbers from 1:N specifying row position @@ -32,6 +32,7 @@ mutation = muts) println("GA:PMX:$(string(muts))(N=$(N), P=$(P)) => F: $(fitness), C: $(cnt), OBJ: $(result)") @test nqueens(result) == 0 + @test cnt < 800 # Test early stopping end # Testing: ES diff --git a/test/rastrigin.jl b/test/rastrigin.jl index 6a6060f..59df6a1 100644 --- a/test/rastrigin.jl +++ b/test/rastrigin.jl @@ -5,7 +5,7 @@ @test ≈(result, zeros(N), atol=tol) @test ≈(fitness,0.0, atol=tol) else - @warn("Found local minimum!!!") + # @warn("Found local minimum!!!") @test sum(round.(abs.(result))) < N end end @@ -50,7 +50,7 @@ crossover = xovr, mutation = ms, tol = 1e-5) - # println("GA(p=$(P),x=.8,μ=.1,ɛ=0.1) => F: $(fitness), C: $(cnt), OBJ: $(result)") + println("GA(p=$(P),x=.8,μ=.1,ɛ=0.1) => F: $(fitness), C: $(cnt), OBJ: $(result)") test_result(result, fitness, N, 1e-1) end end From f549c10d7aa32bfd7239aab10cd41f6ce80bbefa Mon Sep 17 00:00:00 2001 From: Andrew Haselgrove Date: Mon, 30 Sep 2019 15:31:55 +1000 Subject: [PATCH 04/12] Fix bug with array allocation for tournament selection --- src/selections.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/selections.jl b/src/selections.jl index 725ebc6..ce7e15e 100644 --- a/src/selections.jl +++ b/src/selections.jl @@ -44,7 +44,7 @@ function sus(fitness::Vector{Float64}, N::Int) P = F/N start = P*rand() pointers = [start+P*i for i = 0:(N-1)] - selected = Array{Int}(undef, N) + selected = Vector{Int}(undef, N) i = c = 1 for P in pointers while sum(fitness[1:i]) < P @@ -65,7 +65,7 @@ end function tournament(groupSize :: Int) groupSize <= 0 && error("Group size needs to be positive") function tournamentN(fitness::Vector{Float64}, N::Int) - selection = Array{Int}(N) + selection = Vector{Int}(undef, N) nFitness = length(fitness) @@ -96,7 +96,7 @@ end # Utils: selection function pselection(prob::Vector{Float64}, N::Int) cp = cumsum(prob) - selected = Array{Int}(undef, N) + selected = Vector{Int}(undef, N) for i in 1:N j = 1 r = rand() From 55e9227fc2ebb8b0d52593775c6d1afa143091fa Mon Sep 17 00:00:00 2001 From: Andrew Haselgrove Date: Mon, 30 Sep 2019 16:19:21 +1000 Subject: [PATCH 05/12] Refactor vlookup functionality out of pselection and add probability assertion --- src/selections.jl | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/selections.jl b/src/selections.jl index ce7e15e..b8fe35b 100644 --- a/src/selections.jl +++ b/src/selections.jl @@ -95,15 +95,24 @@ end # Utils: selection function pselection(prob::Vector{Float64}, N::Int) - cp = cumsum(prob) selected = Vector{Int}(undef, N) + + cp = cumsum(prob) + @assert cp[end] ≈ 1 "Sum of probability vector must equal 1" + for i in 1:N - j = 1 - r = rand() - while cp[j] < r - j += 1 - end - selected[i] = j + selected[i] = vlookup(cp, rand()) end return selected end + +# Utils: vlookup +function vlookup(range::Vector{<:Number}, value::Number) + for i in eachindex(range) + if range[i] >= value + return i + end + end + + return -1 +end From 31b085eea3ed6bbfcd0da70accd44226b11b33d6 Mon Sep 17 00:00:00 2001 From: Andrew Haselgrove Date: Mon, 30 Sep 2019 16:42:28 +1000 Subject: [PATCH 06/12] Refactor names for clarity --- src/selections.jl | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/src/selections.jl b/src/selections.jl index b8fe35b..da72a29 100644 --- a/src/selections.jl +++ b/src/selections.jl @@ -1,4 +1,4 @@ -# GA seclections +# GA selections # ============== # Rank-based fitness assignment @@ -7,12 +7,24 @@ function ranklinear(sp::Float64) @assert 1.0 <= sp <= 2.0 "Selective pressure has to be in range [1.0, 2.0]." function rank(fitness::Vector{Float64}, N::Int) λ = length(fitness) +<<<<<<< Updated upstream + rank = sortperm(fitness) + + prob = Vector{Float64}(undef, λ) +======= idx = sortperm(fitness) - ranks = zeros(λ) + + ranks = Vector{Float64}(undef, λ) +>>>>>>> Stashed changes for i in 1:λ - ranks[i] = ( 2.0- sp + 2.0*(sp - 1.0)*(idx[i] - 1.0) / (λ - 1.0) ) / λ + prob[i] = ( 2.0- sp + 2.0*(sp - 1.0)*(rank[i] - 1.0) / (λ - 1.0) ) / λ end + +<<<<<<< Updated upstream + return pselection(prob, N) +======= return pselection(ranks, N) +>>>>>>> Stashed changes end return rank end @@ -21,13 +33,18 @@ end function uniformranking(μ::Int) function uniformrank(fitness::Vector{Float64}, N::Int) λ = length(fitness) - idx = sortperm(fitness, rev=true) +<<<<<<< Updated upstream @assert μ < λ "μ should be less then $(λ)" - ranks = zeros(fitness) - for i in 1:μ - ranks[idx[i]] = 1/μ - end + + prob = fill(1/μ, μ) + return pselection(prob, N) +======= + @assert μ < λ "μ should equal $(λ)" + + ranks = fill(1/μ, μ) + return pselection(ranks, N) +>>>>>>> Stashed changes end return uniformrank end @@ -40,11 +57,13 @@ end # Stochastic universal sampling (SUS) function sus(fitness::Vector{Float64}, N::Int) + selected = Vector{Int}(undef, N) + F = sum(fitness) P = F/N + start = P*rand() pointers = [start+P*i for i = 0:(N-1)] - selected = Vector{Int}(undef, N) i = c = 1 for P in pointers while sum(fitness[1:i]) < P @@ -53,6 +72,7 @@ function sus(fitness::Vector{Float64}, N::Int) selected[c] = i c += 1 end + return selected end From 12a93e804dde4de8ecdcb736b3be29886fae6138 Mon Sep 17 00:00:00 2001 From: Andrew Haselgrove Date: Mon, 30 Sep 2019 16:51:21 +1000 Subject: [PATCH 07/12] Simplify uniformranking --- src/Evolutionary.jl | 2 +- src/selections.jl | 33 +++++---------------------------- 2 files changed, 6 insertions(+), 29 deletions(-) diff --git a/src/Evolutionary.jl b/src/Evolutionary.jl index d1abf5a..91f0810 100644 --- a/src/Evolutionary.jl +++ b/src/Evolutionary.jl @@ -12,7 +12,7 @@ using Random discrete, waverage, intermediate, line, pmx, ox1, cx, ox2, pos, # GA selections - ranklinear, uniformranking, roulette, sus, tournament, #truncation + ranklinear, rankuniform, roulette, sus, tournament, #truncation # Optimization methods es, cmaes, ga diff --git a/src/selections.jl b/src/selections.jl index da72a29..0378ef4 100644 --- a/src/selections.jl +++ b/src/selections.jl @@ -7,46 +7,23 @@ function ranklinear(sp::Float64) @assert 1.0 <= sp <= 2.0 "Selective pressure has to be in range [1.0, 2.0]." function rank(fitness::Vector{Float64}, N::Int) λ = length(fitness) -<<<<<<< Updated upstream rank = sortperm(fitness) prob = Vector{Float64}(undef, λ) -======= - idx = sortperm(fitness) - - ranks = Vector{Float64}(undef, λ) ->>>>>>> Stashed changes for i in 1:λ prob[i] = ( 2.0- sp + 2.0*(sp - 1.0)*(rank[i] - 1.0) / (λ - 1.0) ) / λ end -<<<<<<< Updated upstream return pselection(prob, N) -======= - return pselection(ranks, N) ->>>>>>> Stashed changes end return rank end -# (μ, λ)-uniform ranking selection -function uniformranking(μ::Int) - function uniformrank(fitness::Vector{Float64}, N::Int) - λ = length(fitness) -<<<<<<< Updated upstream - @assert μ < λ "μ should be less then $(λ)" - - prob = fill(1/μ, μ) - return pselection(prob, N) -======= - @assert μ < λ "μ should equal $(λ)" - - ranks = fill(1/μ, μ) - - return pselection(ranks, N) ->>>>>>> Stashed changes - end - return uniformrank +# uniform ranking selection +function rankuniform(fitness::Vector{Float64}, N::Int) + μ = length(fitness) + prob = fill(1/μ, μ) + return pselection(prob, N) end # Roulette wheel (proportionate selection) selection From d06fef1615192f5b7f7ab78931396e002d41c007 Mon Sep 17 00:00:00 2001 From: Andrew Haselgrove Date: Mon, 30 Sep 2019 17:05:43 +1000 Subject: [PATCH 08/12] Improve coverage of selections --- test/rastrigin.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/rastrigin.jl b/test/rastrigin.jl index 59df6a1..68ec846 100644 --- a/test/rastrigin.jl +++ b/test/rastrigin.jl @@ -38,7 +38,7 @@ test_result(result, fitness, N, 1e-1) # Testing: GA - selections = [roulette, sus, ranklinear(1.5)] + selections = [roulette, sus, ranklinear(1.5), rankuniform, tournament(4)] crossovers = [discrete, intermediate(0.), intermediate(0.25), line(0.2)] mutations = [domainrange(fill(0.5,N)), domainrange(fill(1.0,N))] From 129196b2cbba8c643063445e5ad436954523cdef Mon Sep 17 00:00:00 2001 From: Andrew Haselgrove Date: Wed, 18 Sep 2019 16:08:54 +1000 Subject: [PATCH 09/12] Separated out functionalities from initPopulation `initPopulation` was a heavily overloaded parameter, with a variety of types corresponding to a range of possible behaviours. This commit: - Removes the option to pass a vector representing the search space. This is specific and unclear, and behaviour can easily be easily represented by one of the other options - Restricts `initPopulation` to be a vector of individuals (where an individual is a single member of the population) - Adds a new parameter `creation` to represent a function to create an individual. Default behaviour has not been changed, but is now represented by a creation function. --- src/es.jl | 6 +++--- src/ga.jl | 6 +++--- test/n-queens.jl | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/es.jl b/src/es.jl index 2e6be67..60549b1 100644 --- a/src/es.jl +++ b/src/es.jl @@ -39,10 +39,10 @@ function es( objfun::Function, N::Int; population = fill(individual, μ) fitness = zeros(μ) for i in 1:μ - if !isnothing(initPopulation) - population[i] = initPopulation[i] - else + if isnothing(initPopulation) population[i] = creation(N) + else + population[i] = initPopulation[i] end fitness[i] = objfun(population[i]) debug && println("INIT $(i): $(population[i]) : $(fitness[i])") diff --git a/src/ga.jl b/src/ga.jl index ae9c295..2d5cc65 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -44,10 +44,10 @@ function ga(objfun::Function, N::Int; # Generate population for i in 1:populationSize - if !isnothing(initPopulation) - population[i] = initPopulation[i] - else + if isnothing(initPopulation) population[i] = creation(N) + else + population[i] = initPopulation[i] end fitness[i] = fitFunc(population[i]) debug && println("INIT $(i): $(population[i]) : $(fitness[i])") diff --git a/test/n-queens.jl b/test/n-queens.jl index edc8227..f6bc8c6 100644 --- a/test/n-queens.jl +++ b/test/n-queens.jl @@ -2,7 +2,7 @@ N = 8 P = 100 - generatePositions(N::Int) = collect(1:N)[randperm(N)] + generatePositions(N::Int, _=nothing) = collect(1:N)[randperm(N)] # Vector of N cols filled with numbers from 1:N specifying row position function nqueens(queens::Vector{Int}) From 70dc0fce61317aa3b300eb4a29401c2609d4f083 Mon Sep 17 00:00:00 2001 From: Andrew Haselgrove Date: Wed, 18 Sep 2019 16:37:33 +1000 Subject: [PATCH 10/12] Parameterised functions and removed getIndividual The `getIndividual` function was performing unnecessary work, so is factored out in this commit. Instead, a sample individual is passed as a parameter. This has the added benefit of making it clear to a reader what kind of data is being worked with. Note: n-queens P increased to 200 due to flaky test. of accessing members --- src/Evolutionary.jl | 13 +------------ src/cmaes.jl | 23 ++++++++++++++--------- src/es.jl | 18 +++++++++--------- src/ga.jl | 24 ++++++++++++------------ test/knapsack.jl | 2 +- test/n-queens.jl | 8 ++++---- test/rastrigin.jl | 7 ++++--- test/rosenbrock.jl | 12 ++++++------ test/schwefel.jl | 3 ++- test/sphere.jl | 4 ++-- 10 files changed, 55 insertions(+), 59 deletions(-) diff --git a/src/Evolutionary.jl b/src/Evolutionary.jl index 91f0810..b710f8a 100644 --- a/src/Evolutionary.jl +++ b/src/Evolutionary.jl @@ -29,23 +29,12 @@ using Random # Inverse function for reversing optimization direction function inverseFunc(f::Function) - function fitnessFunc(x::T) where {T <: Vector} + function fitnessFunc(x) return 1.0/(f(x)+eps()) end return fitnessFunc end - # Obtain individual - function getIndividual(init::Union{Nothing, Vector}, creation::Function, N::Int) - if !isnothing(init) - individual = init[1] - @assert length(individual) == N "Dimensionality of initial population must be $(N)" - else - individual = creation(N) - end - return individual - end - # Collecting interim values function keep(interim, v, vv, col) if interim diff --git a/src/cmaes.jl b/src/cmaes.jl index 69929b9..c96a0b5 100644 --- a/src/cmaes.jl +++ b/src/cmaes.jl @@ -8,26 +8,31 @@ # using LinearAlgebra using Statistics -function cmaes( objfun::Function, N::Int; - initPopulation::Union{Nothing, Vector} = nothing, - initStrategy::Strategy = strategy(τ = sqrt(N), τ_c = N^2, τ_σ = sqrt(N)), - creation::Function = (n -> rand(n)), +function cmaes( objfun::Function, individual::T; + initParent::Union{Nothing, T} = nothing, + initStrategy::Strategy = strategy(τ = sqrt(length(individual)), τ_c = length(individual)^2, τ_σ = sqrt(length(individual))), + creation::Function = (n -> rand(eltype(T), n)), μ::Integer = 1, λ::Integer = 1, iterations::Integer = 1_000, tol::Float64 = 1e-10, - verbose = false) + verbose = false) where {T} @assert μ < λ "Offspring population must be larger then parent population" + @assert ndims(individual) == 1 "Individual must be 1D" # Initialize parent population - individual = getIndividual(initPopulation, creation, N) - population = fill(individual, μ) - offspring = Array{typeof(individual)}(undef, λ) + N = length(individual) + population = Array{T}(undef, μ) + offspring = Array{T}(undef, λ) fitpop = fill(Inf, μ) fitoff = fill(Inf, λ) - parent = copy(individual) + if !isnothing(initParent) + parent = initParent + else + parent = creation(N) + end C = Diagonal{Float64}(I, N) s = zeros(N) s_σ = zeros(N) diff --git a/src/es.jl b/src/es.jl index 60549b1..61d9402 100644 --- a/src/es.jl +++ b/src/es.jl @@ -10,10 +10,10 @@ # Comma-selection (μ<λ must hold): parents are deterministically selected from the set of the offspring # Plus-selection: parents are deterministically selected from the set of both the parents and offspring # -function es( objfun::Function, N::Int; - initPopulation::Union{Nothing, Vector} = nothing, +function es( objfun::Function, individual::T; + initPopulation::Union{Nothing, Vector{T}} = nothing, initStrategy::Strategy = strategy(), - creation::Function = (n -> rand(n)), + creation::Function = (dims -> rand(eltype(T), dims)), recombination::Function = (rs -> rs[1]), srecombination::Function = (ss -> ss[1]), mutation::Function = ((r, m) -> r), @@ -23,9 +23,9 @@ function es( objfun::Function, N::Int; ρ::Integer = μ, λ::Integer = 1, selection::Symbol = :plus, - iterations::Integer = N*100, + iterations::Integer = 100*prod(size(individual)), verbose = false, debug = false, - interim = false) + interim = false) where {T} @assert ρ <= μ "Number of parents involved in the procreation of an offspring should be no more then total number of parents" if selection == :comma @@ -33,21 +33,21 @@ function es( objfun::Function, N::Int; end store = Dict{Symbol,Any}() + dims = size(individual) # Initialize parent population - individual = getIndividual(initPopulation, creation, N) - population = fill(individual, μ) + population = Array{T}(undef, μ) fitness = zeros(μ) for i in 1:μ if isnothing(initPopulation) - population[i] = creation(N) + population[i] = creation(dims) else population[i] = initPopulation[i] end fitness[i] = objfun(population[i]) debug && println("INIT $(i): $(population[i]) : $(fitness[i])") end - offspring = Array{typeof(individual)}(undef, λ) + offspring = Array{T}(undef, λ) fitoff = fill(Inf, λ) stgpop = fill(initStrategy, μ) stgoff = fill(initStrategy, λ) diff --git a/src/ga.jl b/src/ga.jl index 2d5cc65..7af2469 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -1,7 +1,7 @@ # Genetic Algorithms # ================== # objfun: Objective fitness function -# N: Search space dimensionality +# individual: Sample data structure representing an individual # initPopulation: Initial population values as matrix # populationSize: Size of the population # crossoverRate: The fraction of the population at the next generation, not including elite children, @@ -11,41 +11,41 @@ # are guaranteed to survive to the next generation. # Floating number specifies fraction of population. # -function ga(objfun::Function, N::Int; - initPopulation::Union{Nothing, Vector} = nothing, - lowerBounds::Union{Nothing, Vector} = nothing, - upperBounds::Union{Nothing, Vector} = nothing, +function ga(objfun::Function, individual::T; + initPopulation::Union{Nothing, Vector{T}} = nothing, + lowerBounds::Union{Nothing, Vector{T}} = nothing, + upperBounds::Union{Nothing, Vector{T}} = nothing, populationSize::Int = 50, crossoverRate::Float64 = 0.8, mutationRate::Float64 = 0.1, ɛ::Real = 0, - creation::Function = (n -> rand(n)), + creation::Function = (dims -> rand(eltype(T), dims)), selection::Function = ((x, n) -> 1:n), crossover::Function = ((x, y) -> (y, x)), mutation::Function = (x -> x), - iterations::Integer = 100*N, + iterations::Integer = 100*prod(size(individual)), tol = 0.0, tolIter = 10, verbose = false, debug = false, - interim = false) + interim = false) where {T} - store = Dict{Symbol,Any}() + store = Dict{Symbol, Any}() # Setup parameters + dims = size(individual) elite = isa(ɛ, Int) ? ɛ : round(Int, ɛ * populationSize) fitFunc = inverseFunc(objfun) # Initialize population - individual = getIndividual(initPopulation, creation, N) fitness = zeros(populationSize) - population = Array{typeof(individual)}(undef, populationSize) + population = Array{T}(undef, populationSize) offspring = similar(population) # Generate population for i in 1:populationSize if isnothing(initPopulation) - population[i] = creation(N) + population[i] = creation(dims) else population[i] = initPopulation[i] end diff --git a/test/knapsack.jl b/test/knapsack.jl index 4d9221f..2b48094 100644 --- a/test/knapsack.jl +++ b/test/knapsack.jl @@ -11,7 +11,7 @@ best, invbestfit, generations, tolerance, history = ga( x -> 1 / fitness(x), # Function to MINIMISE - length(mass), # Length of chromosome + Array{Bool}(undef, length(mass)), # Solution structure creation = creation, selection = roulette, # Options: sus mutation = inversion, # Options: diff --git a/test/n-queens.jl b/test/n-queens.jl index f6bc8c6..26d4d23 100644 --- a/test/n-queens.jl +++ b/test/n-queens.jl @@ -2,10 +2,10 @@ N = 8 P = 100 - generatePositions(N::Int, _=nothing) = collect(1:N)[randperm(N)] + generatePositions((N,)::Tuple{<:Integer}) = collect(1:N)[randperm(N)] # Vector of N cols filled with numbers from 1:N specifying row position - function nqueens(queens::Vector{Int}) + function nqueens(queens::Vector{<:Integer}) n = length(queens) fitness = 0 for i=1:(n-1) @@ -24,7 +24,7 @@ # Testing: GA solution with various mutations for muts in [inversion, insertion, swap2, scramble, shifting] - result, fitness, cnt = ga(nqueens, N; + result, fitness, cnt = ga(nqueens, Vector{Int}(undef, N); populationSize = P, creation = generatePositions, selection = sus, @@ -37,7 +37,7 @@ # Testing: ES for muts in [inversion, insertion, swap2, scramble, shifting] - result, fitness, cnt = es(nqueens, N; + result, fitness, cnt = es(nqueens, Vector{Int}(undef, N); creation = generatePositions, mutation = mutationwrapper(muts), μ = 15, ρ = 1, λ = P) diff --git a/test/rastrigin.jl b/test/rastrigin.jl index 68ec846..cb1147e 100644 --- a/test/rastrigin.jl +++ b/test/rastrigin.jl @@ -22,7 +22,7 @@ # Testing: (μ/μ_I,λ)-σ-Self-Adaptation-ES # with non-isotropic mutation operator y' := y + (σ_1 N_1(0, 1), ..., σ_N N_N(0, 1)) - result, fitness, cnt = es( rastrigin, N; + result, fitness, cnt = es( rastrigin, Vector{Float64}(undef, N); initStrategy = strategy(σ = .5ones(N), τ = 1/sqrt(2*N), τ0 = 1/sqrt(N)), recombination = average, srecombination = averageSigmaN, mutation = anisotropic, smutation = anisotropicSigma, @@ -33,7 +33,8 @@ test_result(result, fitness, N, 1e-1) # Testing: CMA-ES - result, fitness, cnt = cmaes( rastrigin, N; μ = 15, λ = P, tol = 1e-8) + result, fitness, cnt = cmaes( rastrigin, Vector{Float64}(undef, N); + μ = 15, λ = P, tol = 1e-8) println("(15/15,$(P))-CMA-ES => F: $(fitness), C: $(cnt), OBJ: $(result)") test_result(result, fitness, N, 1e-1) @@ -43,7 +44,7 @@ mutations = [domainrange(fill(0.5,N)), domainrange(fill(1.0,N))] @testset "GA settings" for ss in selections, xovr in crossovers, ms in mutations - result, fitness, cnt = ga( rastrigin, N; + result, fitness, cnt = ga( rastrigin, Vector{Float64}(undef, N); populationSize = P, ɛ = 0.1, selection = ss, diff --git a/test/rosenbrock.jl b/test/rosenbrock.jl index c5eb58d..1cdb25c 100644 --- a/test/rosenbrock.jl +++ b/test/rosenbrock.jl @@ -15,7 +15,7 @@ # Testing: (15/5+100)-ES # with isotropic mutation operator y' := y + σ(N_1(0, 1), ..., N_N(0, 1)) - result, fitness, cnt = es(rosenbrock, N; + result, fitness, cnt = es(rosenbrock, Vector{Float64}(undef, N); initStrategy = strategy(σ = 1.0), recombination = average, mutation = isotropic, μ = 15, ρ = 5, λ = 100, iterations = 1000) @@ -24,9 +24,9 @@ # Testing: (15/15+100)-σ-Self-Adaptation-ES # with isotropic mutation operator y' := y + σ(N_1(0, 1), ..., N_N(0, 1)) - result, fitness, cnt = es(rosenbrock, N; + result, fitness, cnt = es(rosenbrock, Vector{Float64}(undef, N); initStrategy = strategy(σ = 1.0, τ = 1/sqrt(2*N)), - creation = (n -> 0.5 .* rand(n)), + creation = (n -> 0.5 .* rand(Float64, n)), recombination = average, srecombination = averageSigma1, mutation = isotropic, smutation = isotropicSigma, μ = 15, λ = 100, iterations = 1000) @@ -35,7 +35,7 @@ # Testing: (15/15+100)-σ-Self-Adaptation-ES # with non-isotropic mutation operator y' := y + (σ_1 N_1(0, 1), ..., σ_N N_N(0, 1)) - result, fitness, cnt = es(rosenbrock, N; + result, fitness, cnt = es(rosenbrock, Vector{Float64}(undef, N); initPopulation = [rand(N) for _ in 1:15], initStrategy = strategy(σ = .5ones(N), τ = 1/sqrt(2*N), τ0 = 1/sqrt(N)), recombination = average, srecombination = averageSigmaN, @@ -45,13 +45,13 @@ test_result(result, fitness, N, 1e-1) # Testing: CMA-ES - result, fitness, cnt = cmaes(rosenbrock, N; + result, fitness, cnt = cmaes(rosenbrock, Vector{Float64}(undef, N); μ = 3, λ = 12, iterations = 100_000, tol = 1e-3) println("(3/3,12)-CMA-ES => F: $(fitness), C: $(cnt), OBJ: $(result)") test_result(result, fitness, N, 1e-1) # Testing: GA - result, fitness, cnt = ga(rosenbrock, N; + result, fitness, cnt = ga(rosenbrock, Vector{Float64}(undef, N); populationSize = 100, ɛ = 0.1, selection = sus, diff --git a/test/schwefel.jl b/test/schwefel.jl index afa5bf1..f7a0cb9 100644 --- a/test/schwefel.jl +++ b/test/schwefel.jl @@ -14,7 +14,8 @@ N = 30 # Testing: CMA-ES - result, fitness, cnt = cmaes(schwefel, N; μ = 3, λ = 12, iterations = 1000) + result, fitness, cnt = cmaes(schwefel, Vector{Float64}(undef, N); + μ = 3, λ = 12, iterations = 1000) println("(3/3,12)-CMA-ES (Schwefel) => F: $(fitness), C: $(cnt)") @test ≈(result, zeros(N), atol=1e-5) diff --git a/test/sphere.jl b/test/sphere.jl index 3ca4490..2cf0b75 100644 --- a/test/sphere.jl +++ b/test/sphere.jl @@ -16,7 +16,7 @@ # Testing: (μ/μ_I, λ)-σ-Self-Adaptation-ES # with isotropic mutation operator y' := y + σ(N_1(0, 1), ..., N_N(0, 1)) - result, fitness, cnt = es(sphere, N; + result, fitness, cnt = es(sphere, Vector{Float64}(undef, N); initStrategy = strategy(σ = 1.0, τ = 1/sqrt(2*N)), recombination = average, srecombination = averageSigma1, mutation = isotropic, smutation = isotropicSigma, @@ -27,7 +27,7 @@ # Testing: GA result, fitness, cnt = - ga( sphere, N; + ga( sphere, Vector{Float64}(undef, N); populationSize = 4P, mutationRate = 0.05, ɛ = 0.1, From e3d98b16146a3e717712e1313441bcbe338b0737 Mon Sep 17 00:00:00 2001 From: Andrew Haselgrove Date: Wed, 25 Sep 2019 16:54:26 +1000 Subject: [PATCH 11/12] Unified naming conventions across solution methods of accessing members --- src/cmaes.jl | 14 +++++++------- src/es.jl | 8 ++++---- src/ga.jl | 10 +++++----- test/knapsack.jl | 2 +- test/sphere.jl | 2 +- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/cmaes.jl b/src/cmaes.jl index c96a0b5..39c25c6 100644 --- a/src/cmaes.jl +++ b/src/cmaes.jl @@ -25,7 +25,7 @@ function cmaes( objfun::Function, individual::T; N = length(individual) population = Array{T}(undef, μ) offspring = Array{T}(undef, λ) - fitpop = fill(Inf, μ) + fitness = fill(Inf, μ) fitoff = fill(Inf, λ) if !isnothing(initParent) @@ -41,7 +41,7 @@ function cmaes( objfun::Function, individual::T; W = zeros(N, λ) # Generation cycle - count = 0 + itr = 0 while true SqrtC = (C + C') / 2.0 try @@ -63,7 +63,7 @@ function cmaes( objfun::Function, individual::T; idx = sortperm(fitoff)[1:μ] for i in 1:μ population[i] = offspring[idx[i]] - fitpop[i] = fitoff[idx[i]] + fitness[i] = fitoff[idx[i]] end w = vec(mean(W[:,idx], dims=2)) @@ -77,12 +77,12 @@ function cmaes( objfun::Function, individual::T; σ = σ*exp(((s_σ'*s_σ)[1] - N)/(2*N*sqrt(N))) # (L6) # termination condition - count += 1 - if count == iterations || σ <= tol + itr += 1 + if itr == iterations || σ <= tol break end - verbose && println("BEST: $(fitpop[1]): $(σ)") + verbose && println("BEST: $(fitness[1]): $(σ)") end - return population[1], fitpop[1], count + return population[1], fitness[1], itr end diff --git a/src/es.jl b/src/es.jl index 61d9402..11c0234 100644 --- a/src/es.jl +++ b/src/es.jl @@ -55,7 +55,7 @@ function es( objfun::Function, individual::T; keep(interim, :fitness, copy(fitness), store) # Generation cycle - count = 0 + itr = 0 while true for i in 1:λ @@ -103,12 +103,12 @@ function es( objfun::Function, individual::T; keep(interim, :fitoff, copy(fitoff), store) # termination condition - count += 1 - if count == iterations || termination(stgpop[1]) + itr += 1 + if itr == iterations || termination(stgpop[1]) break end verbose && println("BEST: $(fitness[1]): $(stgpop[1])") end - return population[1], fitness[1], count, store + return population[1], fitness[1], itr, store end diff --git a/src/ga.jl b/src/ga.jl index 7af2469..6120ef8 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -25,7 +25,7 @@ function ga(objfun::Function, individual::T; mutation::Function = (x -> x), iterations::Integer = 100*prod(size(individual)), tol = 0.0, - tolIter = 10, + tolitr = 10, verbose = false, debug = false, interim = false) where {T} @@ -56,7 +56,7 @@ function ga(objfun::Function, individual::T; keep(interim, :fitness, copy(fitness), store) # Generate and evaluate offspring - itr = 1 + itr = 0 bestFitness = 0.0 bestIndividual = 0 fittol = 0.0 @@ -114,12 +114,13 @@ function ga(objfun::Function, individual::T; keep(interim, :bestFitness, bestFitness, store) # Verbose step - verbose && println("BEST: $(bestFitness): $(population[bestIndividual]), G: $(itr)") + verbose && println("BEST: $(bestFitness): $(population[bestIndividual]), G: $(itr)") # Terminate: # if fitness tolerance is met for specified number of steps + itr += 1 if fittol <= tol - if (tolIter != -1) && (fittolitr > tolIter) + if (tolitr != -1) && (fittolitr > tolitr) break else fittolitr += 1 @@ -131,7 +132,6 @@ function ga(objfun::Function, individual::T; if itr >= iterations break end - itr += 1 end return population[bestIndividual], bestFitness, itr, fittol, store diff --git a/test/knapsack.jl b/test/knapsack.jl index 2b48094..98f5388 100644 --- a/test/knapsack.jl +++ b/test/knapsack.jl @@ -20,7 +20,7 @@ crossoverRate = 0.5, ɛ = 0.1, # Elitism iterations = 20, - tolIter = 20, + tolitr = 20, populationSize = 50, interim = true); diff --git a/test/sphere.jl b/test/sphere.jl index 2cf0b75..58535d3 100644 --- a/test/sphere.jl +++ b/test/sphere.jl @@ -34,7 +34,7 @@ selection = sus, crossover = intermediate(0.25), mutation = domainrange(fill(0.5,N)), - tol = 1e-5, tolIter = 15) + tol = 1e-5, tolitr = 15) println("GA(pop=$(P),xover=0.8,μ=0.1) => F: $(fitness), C: $(cnt)") test_result(result, fitness, N, 1e-2) From 0cd421d0c206ded341cc458edef01e6e15275ce0 Mon Sep 17 00:00:00 2001 From: Andrew Haselgrove Date: Wed, 25 Sep 2019 16:58:55 +1000 Subject: [PATCH 12/12] Convert method return values to named tuples for ease of accessing members --- src/cmaes.jl | 6 +++++- src/es.jl | 7 ++++++- src/ga.jl | 8 +++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/cmaes.jl b/src/cmaes.jl index 39c25c6..8ec454c 100644 --- a/src/cmaes.jl +++ b/src/cmaes.jl @@ -84,5 +84,9 @@ function cmaes( objfun::Function, individual::T; verbose && println("BEST: $(fitness[1]): $(σ)") end - return population[1], fitness[1], itr + return ( + bestIndividual=population[1], + bestFitness=fitness[1], + itr=itr + ) end diff --git a/src/es.jl b/src/es.jl index 11c0234..d1f3bd2 100644 --- a/src/es.jl +++ b/src/es.jl @@ -110,5 +110,10 @@ function es( objfun::Function, individual::T; verbose && println("BEST: $(fitness[1]): $(stgpop[1])") end - return population[1], fitness[1], itr, store + return ( + bestIndividual=population[1], + bestFitness=fitness[1], + itr=itr, + store=store + ) end diff --git a/src/ga.jl b/src/ga.jl index 6120ef8..4b631e3 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -134,5 +134,11 @@ function ga(objfun::Function, individual::T; end end - return population[bestIndividual], bestFitness, itr, fittol, store + return ( + bestIndividual=population[bestIndividual], + bestFitness=bestFitness, + itr=itr, + fittol=fittol, + store=store + ) end