From 592858a02375478716c9fe0c9b9f0cff88055294 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Wed, 19 Feb 2020 18:19:12 +0100 Subject: [PATCH 01/51] Started improving code writing. Some parts of the code are now obsolete and the genetic algorithm does not work with with different types of chromossome, that's my goal from now on. --- src/ga.jl | 19 +++++++++++++++++-- src/mutations.jl | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/ga.jl b/src/ga.jl index 1016711..7baa1ed 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -1,3 +1,16 @@ + +abstract type AbstractGene end + +mutable struct BinaryGene + mutation ::Function +end + +mutable struct FloatGene + mutation ::Function +end + +################################################################## + # Genetic Algorithms # ================== # objfun: Objective fitness function @@ -77,10 +90,12 @@ function ga(objfun::Function, N::Int; j = (i == populationSize) ? i-1 : i+1 if rand() < crossoverRate debug && println("MATE $(offidx[i])+$(offidx[j])>: $(population[selected[offidx[i]]]) : $(population[selected[offidx[j]]])") - offspring[i], offspring[j] = crossover(population[selected[offidx[i]]], population[selected[offidx[j]]]) + offspring[i], offspring[j] = + crossover(population[selected[offidx[i]]], population[selected[offidx[j]]]) debug && println("MATE >$(offidx[i])+$(offidx[j]): $(offspring[i]) : $(offspring[j])") else - offspring[i], offspring[j] = population[selected[i]], population[selected[j]] + offspring[i], offspring[j] = + population[selected[i]], population[selected[j]] end end diff --git a/src/mutations.jl b/src/mutations.jl index 4b9e439..3ca5756 100644 --- a/src/mutations.jl +++ b/src/mutations.jl @@ -59,7 +59,7 @@ function domainrange(valrange::Vector, m::Int = 20) δ = zeros(m) for i in 1:length(recombinant) for j in 1:m - δ[j] = (rand() < prob) ? δ[j] = 2.0^(-j) : 0.0 + δ[j] = (rand() < prob) ? 2.0^(-j) : 0.0 end if rand() > 0.5 recombinant[i] += sum(δ)*valrange[i] From 48ba05aa6f3648fbb3884298e672473a2117714d Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Thu, 20 Feb 2020 18:22:38 +0100 Subject: [PATCH 02/51] Continued improving code The individual, instead of being a vector of values, is a vector of AbstractGene. Each entry of the vector is a gene, of any type supported (binary, integer and float). Now the individual can have different types of genes. --- src/Evolutionary.jl | 4 +- src/ga.jl | 114 +++++++++++++++++++++++++++----------------- src/mutations.jl | 53 +++++++++++++++++--- 3 files changed, 119 insertions(+), 52 deletions(-) diff --git a/src/Evolutionary.jl b/src/Evolutionary.jl index 691402d..a5f5c06 100644 --- a/src/Evolutionary.jl +++ b/src/Evolutionary.jl @@ -17,7 +17,7 @@ using Random es, cmaes, ga const Strategy = Dict{Symbol,Any} - const Individual = Union{Vector, Matrix, Function, Nothing} + const Individual = Vector{T} where T <: AbstractGene # Wrapping function for strategy function strategy(; kwargs...) @@ -50,7 +50,7 @@ using Random else individual = ones(N) end - return individual + return individual end # Collecting interim values diff --git a/src/ga.jl b/src/ga.jl index 7baa1ed..46746d1 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -1,71 +1,97 @@ abstract type AbstractGene end -mutable struct BinaryGene - mutation ::Function +mutable struct BinaryGene <: AbstractGene + value ::Bool end -mutable struct FloatGene - mutation ::Function +mutable struct IntegerGene <: AbstractGene + value ::BitVector + mutation ::Symbol +end + +mutable struct FloatGene <: AbstractGene + value ::Vector{Float64} + range ::Vector{Float64} + m ::Int64 + function FloatGene(value ::Union{Float64, Vector{Float64}} , + range ::Union{Float64, Vector{Float64}} , + m ::Int64 ) + if typeof(value) != typeof(range) + error("both arguments must share same type") + end + if typeof(value) == Vector{Float64} + if length(value) != length(range) + error("both arguments must have same length") + else + return new(value, range) + end + elseif typeof(value) == Float64 + return new(Float64[value], Float64[range]) + end + end end ################################################################## # Genetic Algorithms # ================== -# 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. -# 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. -# mutationRate: Probability of chromosome to be mutated -# ɛ: Positive integer specifies how many individuals in the current generation -# are guaranteed to survive to the next generation. -# Floating number specifies fraction of population. +# 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. +# 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. +# mutationRate : Probability of chromosome to be mutated +# ɛ : Positive integer specifies how many individuals in the current generation +# are guaranteed to survive to the next generation. +# Floating number specifies fraction of population. # -function ga(objfun::Function, N::Int; - initPopulation::Individual = ones(N), - 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), - iterations::Integer = 100*N, - tol = 0.0, - tolIter = 10, - verbose = false, - debug = false, - interim = false) +function ga( objfun ::Function , + initpopulation ::Individual , + populationSize ::Int64 ; + lowerBounds ::Union{Nothing, Vector} = nothing , + upperBounds ::Union{Nothing, Vector} = nothing , + crossoverRate ::Float64 = 0.8 , + mutationRate ::Float64 = 0.1 , + ɛ ::Real = 0 , + iterations ::Integer = 100 , + tol ::Real = 0.0 , + tolIter ::Int64 = 10 , + verbose ::Bool = false , + debug ::Bool = false , + interim ::Bool = false , + parallel ::Bool = false ) store = Dict{Symbol,Any}() - + # Setup parameters elite = isa(ɛ, Int) ? ɛ : round(Int, ɛ * populationSize) fitFunc = inverseFunc(objfun) # Initialize population - individual = getIndividual(initPopulation, N) + # individual = getIndividual(initPopulation, N) fitness = zeros(populationSize) - population = Array{typeof(individual)}(undef, populationSize) + population = Vector{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 + # else + # error("Cannot generate population") + # end + # fitness[i] = fitFunc(population[i]) + # debug && println("INIT $(i): $(population[i]) : $(fitness[i])") + # end 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 - else - error("Cannot generate population") - end + population[i] = initpopulation fitness[i] = fitFunc(population[i]) debug && println("INIT $(i): $(population[i]) : $(fitness[i])") end diff --git a/src/mutations.jl b/src/mutations.jl index 3ca5756..d273f8c 100644 --- a/src/mutations.jl +++ b/src/mutations.jl @@ -41,13 +41,20 @@ end # Binary mutations # ---------------- -function flip(recombinant::Vector{Bool}) +function flip(recombinant::BitVector) s = length(recombinant) pos = rand(1:s) recombinant[pos] = !recombinant[pos] return recombinant end +function singleflip(recombinant ::Bool) + if rand() > 0.5 + recombinant = !recombinant + end + return recombinant +end + # Real valued mutation # Mühlenbein, H. and Schlierkamp-Voosen, D.: Predictive Models for the Breeder Genetic Algorithm: I. Continuous Parameter Optimization. Evolutionary Computation, 1 (1), pp. 25-49, 1993. # -------------------- @@ -72,10 +79,15 @@ function domainrange(valrange::Vector, m::Int = 20) return mutation end +function mutate(gene ::FloatGene) + + +end + # Combinatorial mutations (applicable to binary vectors) # ------------------------------------------------------ -function inversion(recombinant::T) where {T <: Vector} +function inversion(recombinant::BitVector) l = length(recombinant) from, to = rand(1:l, 2) from, to = from > to ? (to, from) : (from, to) @@ -86,7 +98,7 @@ function inversion(recombinant::T) where {T <: Vector} return recombinant end -function insertion(recombinant::T) where {T <: Vector} +function insertion(recombinant::BitVector) l = length(recombinant) from, to = rand(1:l, 2) val = recombinant[from] @@ -94,14 +106,14 @@ function insertion(recombinant::T) where {T <: Vector} return insert!(recombinant, to, val) end -function swap2(recombinant::T) where {T <: Vector} +function swap2(recombinant::BitVector) l = length(recombinant) from, to = rand(1:l, 2) swap!(recombinant, from, to) return recombinant end -function scramble(recombinant::T) where {T <: Vector} +function scramble(recombinant::BitVector) l = length(recombinant) from, to = rand(1:l, 2) from, to = from > to ? (to, from) : (from, to) @@ -117,7 +129,7 @@ function scramble(recombinant::T) where {T <: Vector} return recombinant end -function shifting(recombinant::T) where {T <: Vector} +function shifting(recombinant::BitVector) l = length(recombinant) from, to, where = sort(rand(1:l, 3)) patch = recombinant[from:to] @@ -149,3 +161,32 @@ function mutationwrapper(gamutation::Function) wrapper(recombinant::T, s::S) where {T <: Vector, S <: Strategy} = gamutation(recombinant) return wrapper end + +############################################### + +function mutate(gene ::IntegerGene) + mut_func = nothing + mutatetype = gene.mutatetype + if mutatetype == :flip + mut_func = flip + elseif mutatetype == :inversion + mut_func = inversion + elseif mutatetype == :insertion + mut_func = insertion + elseif mutatetype == :swap2 + mut_func = swap2 + elseif mutatetype == :scramble + mut_func = scramble + elseif mutatetype == :shifting + mut_func = shifting + else + error("Unknown mutation type") + end + gene.value = mut_func(gene.value) + return nothing +end + +function mutate(gene ::BinaryGene, mutatetype ::Symbol) + gene.value = singleflip(gene.value) + return nothing +end From 192c283dc76625245262e2e0277620fa5fa0596b Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Fri, 21 Feb 2020 17:09:19 +0100 Subject: [PATCH 03/51] Continued improving code --- src/Evolutionary.jl | 2 +- src/ga.jl | 71 +++++++++++++++++++--------- src/mutations.jl | 54 ++++++++++++++-------- src/recombinations.jl | 105 +++++++++++++++++++++--------------------- src/selections.jl | 80 +++++++++++++++++++++++--------- 5 files changed, 196 insertions(+), 116 deletions(-) diff --git a/src/Evolutionary.jl b/src/Evolutionary.jl index a5f5c06..5ac3dc1 100644 --- a/src/Evolutionary.jl +++ b/src/Evolutionary.jl @@ -17,7 +17,7 @@ using Random es, cmaes, ga const Strategy = Dict{Symbol,Any} - const Individual = Vector{T} where T <: AbstractGene + const Chromossome = Vector{T} where T <: AbstractGene # Wrapping function for strategy function strategy(; kwargs...) diff --git a/src/ga.jl b/src/ga.jl index 46746d1..f3709d6 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -13,25 +13,43 @@ end mutable struct FloatGene <: AbstractGene value ::Vector{Float64} range ::Vector{Float64} - m ::Int64 - function FloatGene(value ::Union{Float64, Vector{Float64}} , - range ::Union{Float64, Vector{Float64}} , - m ::Int64 ) - if typeof(value) != typeof(range) - error("both arguments must share same type") - end - if typeof(value) == Vector{Float64} - if length(value) != length(range) - error("both arguments must have same length") - else - return new(value, range) - end - elseif typeof(value) == Float64 - return new(Float64[value], Float64[range]) - end + m ::Int64 + function FloatGene(value ::Vector{Float64} , + range ::Vector{Float64} , + m ::Int64 ) + return new(val, ran, m) end end +function FloatGene(value ::Float64, range ::Float64; m ::Integer = 20) + return FloatGene(Float64[value], Float64[range], m) +end + +function FloatGene(value ::Vector{Float64}, range ::Float64; m ::Int64 = 20) + vec = Float64[range for i in value] + return FloatGene(value, vec, m) +end + +mutable struct Chromossome + n ::Int64 + chromossome ::Vector{<:AbstractGene} + crossover ::Symbol + selection ::Symbol + function Chromossome( chromossome ::Vector{<:AbstractGene} , + crossover ::Symbol , + selection ::Symbol ) + n = length(chromossome) + return new(n, chromossome, crossover, selection) + end +end + +function Chromossome( chromossome ::AbstractGene , + crossover ::Symbol , + selection ::Symbol ) + chrom = AbstractGene[chromossome] + return Chromossome(chrom, crossover, selection) +end + ################################################################## # Genetic Algorithms @@ -49,7 +67,7 @@ end # Floating number specifies fraction of population. # function ga( objfun ::Function , - initpopulation ::Individual , + initpopulation ::Chromossome , populationSize ::Int64 ; lowerBounds ::Union{Nothing, Vector} = nothing , upperBounds ::Union{Nothing, Vector} = nothing , @@ -115,10 +133,14 @@ function ga( objfun ::Function , for i in 1:2:populationSize j = (i == populationSize) ? i-1 : i+1 if rand() < crossoverRate - debug && println("MATE $(offidx[i])+$(offidx[j])>: $(population[selected[offidx[i]]]) : $(population[selected[offidx[j]]])") + debug && + println("MATE $(offidx[i])+$(offidx[j])>: " * + "$(population[selected[offidx[i]]]) : " * + "$(population[selected[offidx[j]]])" ) offspring[i], offspring[j] = crossover(population[selected[offidx[i]]], population[selected[offidx[j]]]) - debug && println("MATE >$(offidx[i])+$(offidx[j]): $(offspring[i]) : $(offspring[j])") + debug && + println("MATE >$(offidx[i])+$(offidx[j]): $(offspring[i]) : $(offspring[j])") else offspring[i], offspring[j] = population[selected[i]], population[selected[j]] @@ -129,7 +151,7 @@ function ga( objfun ::Function , for i in 1:populationSize if rand() < mutationRate debug && println("MUTATED $(i)>: $(offspring[i])") - mutation(offspring[i]) + mutate(offspring[i]) debug && println("MUTATED >$(i): $(offspring[i])") end end @@ -138,7 +160,9 @@ function ga( objfun ::Function , if elite > 0 for i in 1:elite subs = rand(1:populationSize) - debug && println("ELITE $(fitidx[i])=>$(subs): $(population[fitidx[i]]) => $(offspring[subs])") + debug && + println("ELITE $(fitidx[i])=>$(subs): " * + "$(population[fitidx[i]]) => $(offspring[subs])" ) offspring[subs] = population[fitidx[i]] end end @@ -159,10 +183,11 @@ function ga( objfun ::Function , 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 + # if fitness tolerance is met for specified number of steps if fittol <= tol if fittolitr > tolIter break diff --git a/src/mutations.jl b/src/mutations.jl index d273f8c..0c1def0 100644 --- a/src/mutations.jl +++ b/src/mutations.jl @@ -41,7 +41,7 @@ end # Binary mutations # ---------------- -function flip(recombinant::BitVector) +function flip(recombinant ::BitVector) s = length(recombinant) pos = rand(1:s) recombinant[pos] = !recombinant[pos] @@ -58,9 +58,9 @@ end # Real valued mutation # Mühlenbein, H. and Schlierkamp-Voosen, D.: Predictive Models for the Breeder Genetic Algorithm: I. Continuous Parameter Optimization. Evolutionary Computation, 1 (1), pp. 25-49, 1993. # -------------------- -function domainrange(valrange::Vector, m::Int = 20) +function domainrange(valrange:: Vector{Float64}, m ::Int = 20) prob = 1.0 / m - function mutation(recombinant::T) where {T <: Vector} + function mutation(recombinant ::Vector{Float64}) d = length(recombinant) @assert length(valrange) == d "Range matrix must have $(d) columns" δ = zeros(m) @@ -79,15 +79,9 @@ function domainrange(valrange::Vector, m::Int = 20) return mutation end -function mutate(gene ::FloatGene) - - -end - - # Combinatorial mutations (applicable to binary vectors) # ------------------------------------------------------ -function inversion(recombinant::BitVector) +function inversion(recombinant ::BitVector) l = length(recombinant) from, to = rand(1:l, 2) from, to = from > to ? (to, from) : (from, to) @@ -98,7 +92,7 @@ function inversion(recombinant::BitVector) return recombinant end -function insertion(recombinant::BitVector) +function insertion(recombinant ::BitVector) l = length(recombinant) from, to = rand(1:l, 2) val = recombinant[from] @@ -106,14 +100,14 @@ function insertion(recombinant::BitVector) return insert!(recombinant, to, val) end -function swap2(recombinant::BitVector) +function swap2(recombinant ::BitVector) l = length(recombinant) from, to = rand(1:l, 2) swap!(recombinant, from, to) return recombinant end -function scramble(recombinant::BitVector) +function scramble(recombinant ::BitVector) l = length(recombinant) from, to = rand(1:l, 2) from, to = from > to ? (to, from) : (from, to) @@ -129,7 +123,7 @@ function scramble(recombinant::BitVector) return recombinant end -function shifting(recombinant::BitVector) +function shifting(recombinant ::BitVector) l = length(recombinant) from, to, where = sort(rand(1:l, 3)) patch = recombinant[from:to] @@ -151,14 +145,15 @@ end # Utils # ===== -function swap!(v::T, from::Int, to::Int) where {T <: Vector} +function swap!(v ::T, from ::Int, to ::Int) where {T <: Vector} val = v[from] v[from] = v[to] v[to] = val end -function mutationwrapper(gamutation::Function) - wrapper(recombinant::T, s::S) where {T <: Vector, S <: Strategy} = gamutation(recombinant) +function mutationwrapper(gamutation ::Function) + wrapper(recombinant::T, s::S) where {T <: Vector, S <: Strategy} = + gamutation(recombinant) return wrapper end @@ -186,7 +181,30 @@ function mutate(gene ::IntegerGene) return nothing end -function mutate(gene ::BinaryGene, mutatetype ::Symbol) +function mutate(gene ::BinaryGene) gene.value = singleflip(gene.value) return nothing end + +function mutate(gene ::FloatGene) + prob = 1.0 / gene.m + δ = zeros(gene.m) + for i in 1:length(gene.value) + for j in 1:gene.m + δ[j] = (rand() < prob) ? 2.0^(-j) : 0.0 + end + if rand() > 0.5 + gene.value[i] += sum(δ)*gene.range[i] + else + gene.value[i] -= sum(δ)*gene.range[i] + end + end + return nothing +end + +function mutate(chromossome ::Chromossome) + for gene in chromossome + mutate(gene) + end + return nothing +end diff --git a/src/recombinations.jl b/src/recombinations.jl index 1273b54..3be7c0e 100644 --- a/src/recombinations.jl +++ b/src/recombinations.jl @@ -1,17 +1,18 @@ + # Recombinations # ============== -function average(population::Vector{T}) where {T <: Vector} +function average(population ::Vector{T}) where {T <: Vector} obj = zeros(eltype(T), length(population[1])) - l = length(population) + l = length(population) for i in 1:l obj += population[i] end - return obj./l + return obj ./ l end -function marriage(population::Vector{T}) where {T <: Vector} - s = length(population) - l = length(population[1]) +function marriage(population ::Vector{T}) where {T <: Vector} + s = length(population) + l = length(population[1]) obj = zeros(eltype(T), l) for i in 1:l obj[i] = population[rand(1:s)][i] @@ -21,25 +22,25 @@ end # Strategy recombinations # ======================= -function averageSigma1(ss::Vector{S}) where {S <: Strategy} +function averageSigma1(ss ::Vector{S}) where {S <: Strategy} s = copy(ss[1]) σ = 0.0 l = length(ss) for i in 1:l σ += ss[i][:σ] end - s[:σ] = σ/l + s[:σ] = σ / l return s end -function averageSigmaN(ss::Vector{S}) where {S <: Strategy} +function averageSigmaN(ss ::Vector{S}) where {S <: Strategy} s = copy(ss[1]) σ = zeros(length(ss[1][:σ])) l = length(ss) for i in 1:l σ += ss[i][:σ] end - s[:σ] = σ./l + s[:σ] = σ ./ l return s end @@ -51,10 +52,10 @@ end # ----------------- # Single point crossover -function singlepoint(v1::T, v2::T) where {T <: Vector} - l = length(v1) - c1 = copy(v1) - c2 = copy(v2) +function singlepoint(v1 ::T, v2 ::T) where {T <: Vector} + l = length(v1) + c1 = copy(v1) + c2 = copy(v2) pos = rand(1:l) for i in pos:l vswap!(c1, c2, i) @@ -63,8 +64,8 @@ function singlepoint(v1::T, v2::T) where {T <: Vector} end # Two point crossover -function twopoint(v1::T, v2::T) where {T <: Vector} - l = length(v1) +function twopoint(v1 ::T, v2 ::T) where {T <: Vector} + l = length(v1) c1 = copy(v1) c2 = copy(v2) from, to = rand(1:l, 2) @@ -76,10 +77,10 @@ function twopoint(v1::T, v2::T) where {T <: Vector} end # Uniform crossover -function uniform(v1::T, v2::T) where {T <: Vector} - l = length(v1) - c1 = copy(v1) - c2 = copy(v2) +function uniform(v1 ::T, v2 ::T) where {T <: Vector} + l = length(v1) + c1 = copy(v1) + c2 = copy(v2) xch = rand(Bool, l) for i in 1:l if xch[i] @@ -93,10 +94,10 @@ end # Real valued crossovers # ---------------------- -function discrete(v1::T, v2::T) where {T <: Vector} - l = length(v1) - c1 = similar(v1) - c2 = similar(v2) +function discrete(v1 ::T, v2 ::T) where {T <: Vector} + l = length(v1) + c1 = similar(v1) + c2 = similar(v2) sltc = rand(Bool, 2, l) for i in 1:l c1[i] = sltc[1,i] ? v1[i] : v2[i] @@ -106,8 +107,8 @@ function discrete(v1::T, v2::T) where {T <: Vector} end # Weighted arithmetic mean -function waverage(w::Vector{Float64}) - function wavexvr(v1::T, v2::T) where {T <: Vector} +function waverage(w ::Vector{Float64}) + function wavexvr(v1 ::T, v2 ::T) where {T <: Vector} c1 = (v1+v2)./w return c1, copy(c1) end @@ -115,12 +116,12 @@ function waverage(w::Vector{Float64}) end # Intermediate recombination -function intermediate(d::Float64 = 0.0) - function intermxvr(v1::T, v2::T) where {T <: Vector} - l = length(v1) - α = (1.0+2d) * rand(l) .- d +function intermediate(d ::Float64 = 0.0) + function intermxvr(v1 ::T, v2 ::T) where {T <: Vector} + l = length(v1) + α = (1.0+2d) * rand(l) .- d c1 = v2 .+ α .* (v1 - v2) - α = (1.0+2d) * rand(l) .- d + α = (1.0+2d) * rand(l) .- d c2 = v1 .+ α .* (v2 - v1) return c1, c2 end @@ -128,8 +129,8 @@ function intermediate(d::Float64 = 0.0) end # Line recombination -function line(d::Float64 = 0.0) - function linexvr(v1::T, v2::T) where {T <: Vector} +function line(d ::Float64 = 0.0) + function linexvr(v1 ::T, v2 ::T) where {T <: Vector} α1, α2 = (1.0+2d) * rand(2) .- d c1 = v2 .+ α2 * (v1 - v2) c2 = v1 .+ α1 * (v2 - v1) @@ -143,7 +144,7 @@ end # ---------------------- # Partially mapped crossover -function pmx(v1::T, v2::T) where {T <: Vector} +function pmx(v1 ::T, v2 ::T) where {T <: Vector} s = length(v1) from, to = rand(1:s, 2) from, to = from > to ? (to, from) : (from, to) @@ -164,7 +165,7 @@ function pmx(v1::T, v2::T) where {T <: Vector} tmpin = in1 while tmpin > 0 tmpin = inmap(c2[in1], c1, from, to) - in1 = tmpin > 0 ? tmpin : in1 + in1 = tmpin > 0 ? tmpin : in1 end c1[i] = v1[in1] end @@ -176,7 +177,7 @@ function pmx(v1::T, v2::T) where {T <: Vector} tmpin = in2 while tmpin > 0 tmpin = inmap(c1[in2], c2, from, to) - in2 = tmpin > 0 ? tmpin : in2 + in2 = tmpin > 0 ? tmpin : in2 end c2[i] = v2[in2] end @@ -185,7 +186,7 @@ function pmx(v1::T, v2::T) where {T <: Vector} end # Order crossover -function ox1(v1::T, v2::T) where {T <: Vector} +function ox1(v1 ::T, v2 ::T) where {T <: Vector} s = length(v1) from, to = rand(1:s, 2) from, to = from > to ? (to, from) : (from, to) @@ -211,13 +212,13 @@ function ox1(v1::T, v2::T) where {T <: Vector} end # Cycle crossover -function cx(v1::T, v2::T) where {T <: Vector} - s = length(v1) +function cx(v1 ::T, v2 ::T) where {T <: Vector} + s = length(v1) c1 = zeros(v1) c2 = zeros(v2) f1 = true #switch - k = 1 + k = 1 while k > 0 idx = k if f1 @@ -242,8 +243,8 @@ function cx(v1::T, v2::T) where {T <: Vector} end # Order-based crossover -function ox2(v1::T, v2::T) where {T <: Vector} - s = length(v1) +function ox2(v1 ::T, v2 ::T) where {T <: Vector} + s = length(v1) c1 = copy(v1) c2 = copy(v2) @@ -258,11 +259,11 @@ function ox2(v1::T, v2::T) where {T <: Vector} for i in 1:s if !in(v2[i],c1) - tmpin = inmap(zero(T),c1,1,s) + tmpin = inmap(zero(T),c1,1,s) c1[tmpin] = v2[i] end if !in(v1[i],c2) - tmpin = inmap(zero(T),c2,1,s) + tmpin = inmap(zero(T),c2,1,s) c2[tmpin] = v1[i] end end @@ -270,8 +271,8 @@ function ox2(v1::T, v2::T) where {T <: Vector} end # Position-based crossover -function pos(v1::T, v2::T) where {T <: Vector} - s = length(v1) +function pos(v1 ::T, v2 ::T) where {T <: Vector} + s = length(v1) c1 = zeros(v1) c2 = zeros(v2) @@ -284,26 +285,26 @@ function pos(v1::T, v2::T) where {T <: Vector} for i in 1:s if !in(v1[i],c1) - tmpin = inmap(zero(T),c1,1,s) + tmpin = inmap(zero(T),c1,1,s) c1[tmpin] = v1[i] end if !in(v2[i],c2) - tmpin = inmap(zero(T),c2,1,s) + tmpin = inmap(zero(T),c2,1,s) c2[tmpin] = v2[i] end end - return c1,c2 + return c1, c2 end # Utils # ===== -function vswap!(v1::T, v2::T, idx::Int) where {T <: Vector} - val = v1[idx] +function vswap!(v1 ::T, v2 ::T, idx ::Int) where {T <: Vector} + val = v1[idx] v1[idx] = v2[idx] v2[idx] = val end -function inmap(v::T, c::Vector{T}, from::Int, to::Int) where{T} +function inmap(v ::T, c ::Vector{T}, from ::Int, to ::Int) where {T} exists = 0 for j in from:to if exists == 0 && v == c[j] diff --git a/src/selections.jl b/src/selections.jl index 23234a5..ac16f63 100644 --- a/src/selections.jl +++ b/src/selections.jl @@ -1,25 +1,25 @@ -# GA seclections +# GA selections # ============== -# Rank-based fitness assignment +# Rank-based fitness assignment (RBS) # sp - selective linear presure in [1.0, 2.0] -function ranklinear(sp::Float64) +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{<:Real}, N::Int) + function rank(fitness ::Vector{<:Real}, N ::Int) λ = length(fitness) idx = sortperm(fitness) ranks = zeros(λ) for i in 1:λ - ranks[i] = ( 2 - sp + 2*(sp - 1)*(idx[i] - 1) / (λ - 1) ) / λ + ranks[i] = ( 2 - sp + 2*(sp-1)*(idx[i]-1) / (λ-1) ) / λ end return pselection(ranks, N) end return rank end -# (μ, λ)-uniform ranking selection -function uniformranking(μ::Int) - function uniformrank(fitness::Vector{<:Real}, N::Int) +# (μ, λ)-uniform ranking selection (URS) +function uniformranking(μ ::Int) + function uniformrank(fitness ::Vector{<:Real}, N ::Int) λ = length(fitness) idx = sortperm(fitness, rev=true) @assert μ < λ "μ should be less then $(λ)" @@ -32,14 +32,14 @@ function uniformranking(μ::Int) return uniformrank end -# Roulette wheel (proportionate selection) selection -function roulette(fitness::Vector{<:Real}, N::Int) - prob = fitness./sum(fitness) +# Roulette wheel (proportionate selection) selection (RWS) +function roulette(fitness ::Vector{<:Real}, N ::Int) + prob = fitness ./ sum(fitness) return pselection(prob, N) end -# Stochastic universal sampling (SUS) -function sus(fitness::Vector{<:Real}, N::Int) +# Stochastic universal sampling selection (SUSS) +function sus(fitness ::Vector{<:Real}, N ::Int) F = sum(fitness) P = F/N start = P*rand() @@ -56,18 +56,18 @@ function sus(fitness::Vector{<:Real}, N::Int) return selected end -# Truncation selection -function truncation(fitness::Vector{<:Real}, N::Int) +# Truncation selection (TrS) +function truncation(fitness ::Vector{<:Real}, N ::Int) λ = length(fitness) - @assert λ >= N "Cannot select more then $(λ) elements" + @assert λ >= N "Cannot select more than $(λ) elements" idx = sortperm(fitness, rev=true) return idx[1:N] end -# Tournament selection -function tournament(groupSize :: Int) +# Tournament selection (ToS) +function tournament(groupSize ::Int) @assert groupSize > 0 "Group size must be positive" - function tournamentN(fitness::Vector{<:Real}, N::Int) + function tournamentN(fitness ::Vector{<:Real}, N ::Int) selection = fill(0,N) nFitness = length(fitness) @@ -78,12 +78,12 @@ function tournament(groupSize :: Int) contender = unique(vcat(contender, rand(1:nFitness, groupSize - length(contender)))) end - winner = first(contender) + winner = first(contender) winnerFitness = fitness[winner] for idx = 2:groupSize c = contender[idx] if winnerFitness < fitness[c] - winner = c + winner = c winnerFitness = fitness[c] end end @@ -95,9 +95,45 @@ function tournament(groupSize :: Int) return tournamentN end +# :RBS - Rank-Based Selection +# :URS - Uniform-Ranking Selection +# :RWS - Roulette Wheel Selection +# :SUSS - Stochastic Universal Sampling Selection +# :TrS - Truncation Selection +# :ToS - Tournament Selection +function selection(; sp ::Union{Nothing, Float64} = nothing , + μ ::Union{Nothing, Int64 } = nothing , + groupsize ::Union{Nothing, Int64 } = nothing ) + if chrom.selection == :RBS + if isnothing(sp) + error("need to specify `sp` value") + end + return ranklinear(sp) + elseif chrom.selection == :URS + if isnothing(μ) + error("need to specify `μ` value") + end + return uniformranking(μ) + elseif chrom.selection == :RWS + return roulette + elseif chrom.selection == :SUSS + return sus + elseif chrom.selection == :TrS + return truncation + elseif chrom.selection == :ToS + if isnothing(groupsize) + error("need to specify `groupsize` value") + end + return tournament(groupsize) + else + error("Unknown parameter " * string(chrom.selection)) + end +end + +########################################################### # Utils: selection -function pselection(prob::Vector{<:Real}, N::Int) +function pselection(prob ::Vector{<:Real}, N ::Int) cp = cumsum(prob) selected = Array{Int}(undef, N) for i in 1:N From 50484fd74ade9494e40cf69083ac911484cd9826 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Sat, 22 Feb 2020 17:41:39 +0100 Subject: [PATCH 04/51] continued improving code --- src/ga.jl | 55 ++++++++++++++++++++++++++++++++++++++++--- src/mutations.jl | 26 ++++++++++++-------- src/recombinations.jl | 37 +++++++++++++++++++++++------ src/selections.jl | 5 +++- 4 files changed, 102 insertions(+), 21 deletions(-) diff --git a/src/ga.jl b/src/ga.jl index f3709d6..2c7bda7 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -1,15 +1,44 @@ +export BinaryGene, IntegerGene, FloatGene, Chromossome +export ga + +#################################################################### + abstract type AbstractGene end +#################################################################### + mutable struct BinaryGene <: AbstractGene value ::Bool + BinaryGene(value ::Bool) = new(value) +end + +function BinaryGene() + return BinaryGene(rand(Bool)) end +#################################################################### + mutable struct IntegerGene <: AbstractGene value ::BitVector mutation ::Symbol + function IntegerGene(value ::BitVector, mutation ::Symbol) + @eval mutate(gene ::IntegerGene) = int_mutate($mutation)(gene.value) + return new(value, mutation) + end +end + +function IntegerGene(value ::BitVector) + return IntegerGene(value, :FM) +end + +function IntegerGene(n ::Int64) + value = BitVector(undef, rand(1:n)) + return IntegerGene(value, :FM) end +#################################################################### + mutable struct FloatGene <: AbstractGene value ::Vector{Float64} range ::Vector{Float64} @@ -17,11 +46,14 @@ mutable struct FloatGene <: AbstractGene function FloatGene(value ::Vector{Float64} , range ::Vector{Float64} , m ::Int64 ) - return new(val, ran, m) + if length(value) != length(range) + error("vectors mush have the same length") + end + return new(value, range, m) end end -function FloatGene(value ::Float64, range ::Float64; m ::Integer = 20) +function FloatGene(value ::Float64, range ::Float64; m ::Int64 = 20) return FloatGene(Float64[value], Float64[range], m) end @@ -30,6 +62,19 @@ function FloatGene(value ::Vector{Float64}, range ::Float64; m ::Int64 = 20) return FloatGene(value, vec, m) end +function FloatGene(value ::Vector{Float64}; m ::Int64 = 20) + range = rand(Float64, length(value)) + return FloatGene(value, range, m) +end + +function FloatGene(n ::Int64) + value = rand(Float64, n) + range = rand(Float64, n) + return FloatGene(value, range, 20) +end + +#################################################################### + mutable struct Chromossome n ::Int64 chromossome ::Vector{<:AbstractGene} @@ -50,7 +95,11 @@ function Chromossome( chromossome ::AbstractGene , return Chromossome(chrom, crossover, selection) end -################################################################## +function Chromossome() + chrom = AbstractGene[BinaryGene(), IntegerGene(3), FloatGene(1)] +end + +#################################################################### # Genetic Algorithms # ================== diff --git a/src/mutations.jl b/src/mutations.jl index 0c1def0..b4fd6c9 100644 --- a/src/mutations.jl +++ b/src/mutations.jl @@ -159,26 +159,32 @@ end ############################################### -function mutate(gene ::IntegerGene) +# This function serves only to choose the mutation function for binary functions. The actual `mutate` +# function is created in the `IntegerGene` structure. +# FM - Flip Mutation +# InvM - Inversion Mutation +# InsM - Insertion Mutation +# SwM - Swap Mutation +# ScrM - Scramble Mutation +# ShM - Shifting Mutation +function int_mutate(mutateype ::Symbol) mut_func = nothing - mutatetype = gene.mutatetype - if mutatetype == :flip + if mutatetype == :FM mut_func = flip - elseif mutatetype == :inversion + elseif mutatetype == :InvM mut_func = inversion - elseif mutatetype == :insertion + elseif mutatetype == :InsM mut_func = insertion - elseif mutatetype == :swap2 + elseif mutatetype == :SwM mut_func = swap2 - elseif mutatetype == :scramble + elseif mutatetype == :ScrM mut_func = scramble - elseif mutatetype == :shifting + elseif mutatetype == :ShM mut_func = shifting else error("Unknown mutation type") end - gene.value = mut_func(gene.value) - return nothing + return mut_func end function mutate(gene ::BinaryGene) diff --git a/src/recombinations.jl b/src/recombinations.jl index 3be7c0e..1f7d96a 100644 --- a/src/recombinations.jl +++ b/src/recombinations.jl @@ -1,4 +1,4 @@ - + # Recombinations # ============== function average(population ::Vector{T}) where {T <: Vector} @@ -44,6 +44,8 @@ function averageSigmaN(ss ::Vector{S}) where {S <: Strategy} return s end +#################################################################### + # ================== # Genetic algorithms # ================== @@ -81,27 +83,27 @@ function uniform(v1 ::T, v2 ::T) where {T <: Vector} l = length(v1) c1 = copy(v1) c2 = copy(v2) - xch = rand(Bool, l) for i in 1:l - if xch[i] + if rand(Bool) vswap!(c1, c2, i) end end return c1, c2 end +#################################################################### # Real valued crossovers # ---------------------- +# Discrete Crossover function discrete(v1 ::T, v2 ::T) where {T <: Vector} l = length(v1) c1 = similar(v1) c2 = similar(v2) - sltc = rand(Bool, 2, l) for i in 1:l - c1[i] = sltc[1,i] ? v1[i] : v2[i] - c2[i] = sltc[2,i] ? v2[i] : v1[i] + c1[i] = rand(Bool) ? v1[i] : v2[i] + c2[i] = rand(Bool) ? v2[i] : v1[i] end return c1, c2 end @@ -109,7 +111,7 @@ end # Weighted arithmetic mean function waverage(w ::Vector{Float64}) function wavexvr(v1 ::T, v2 ::T) where {T <: Vector} - c1 = (v1+v2)./w + c1 = (v1+v2) ./ w return c1, copy(c1) end return wavexvr @@ -139,6 +141,7 @@ function line(d ::Float64 = 0.0) return linexvr end +#################################################################### # Permutation crossovers # ---------------------- @@ -296,6 +299,26 @@ function pos(v1 ::T, v2 ::T) where {T <: Vector} return c1, c2 end +#################################################################### + +# SPX - Single Point Crossover - singlepoint +# TPX - Two Point Crossover - twopoint +# UX - Uniform Crossover - uniform +# DX - Discrete Crossover - discrete +# WMX - Weighted Mean Crossover - waverage(w ::Vector{Float6}) +# IRX - Intermediate Recombination Crossover - intermediate(d ::Float64) +# LRX - Line Recombination Crossover - line(d ::Float64) +# PMX - Partially Mapped Crossover - pmx +# O1X - Order 1 Crossover - ox1 +# O2X - Order 2 Crossover - ox2 +# CX - Cycle Crossover - cx +# PX - Position-based Crossover - pos +function crossover() +end + +#################################################################### + + # Utils # ===== function vswap!(v1 ::T, v2 ::T, idx ::Int) where {T <: Vector} diff --git a/src/selections.jl b/src/selections.jl index ac16f63..74adca6 100644 --- a/src/selections.jl +++ b/src/selections.jl @@ -1,3 +1,4 @@ + # GA selections # ============== @@ -95,6 +96,8 @@ function tournament(groupSize ::Int) return tournamentN end +#################################################################### + # :RBS - Rank-Based Selection # :URS - Uniform-Ranking Selection # :RWS - Roulette Wheel Selection @@ -130,7 +133,7 @@ function selection(; sp ::Union{Nothing, Float64} = nothing , end end -########################################################### +#################################################################### # Utils: selection function pselection(prob ::Vector{<:Real}, N ::Int) From 1e5a3b25a2230fe4d8408508de29e0f33af78399 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Sun, 23 Feb 2020 11:52:58 +0100 Subject: [PATCH 05/51] Continued improving code --- src/ga.jl | 31 ++++++++++++++++++++++++++----- src/mutations.jl | 4 +++- src/recombinations.jl | 42 +++++++++++++++++++++++++++++++++++++++--- src/selections.jl | 7 ++++--- 4 files changed, 72 insertions(+), 12 deletions(-) diff --git a/src/ga.jl b/src/ga.jl index 2c7bda7..79b1919 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -23,7 +23,11 @@ mutable struct IntegerGene <: AbstractGene value ::BitVector mutation ::Symbol function IntegerGene(value ::BitVector, mutation ::Symbol) - @eval mutate(gene ::IntegerGene) = int_mutate($mutation)(gene.value) + @eval begin + function mutate(gene ::IntegerGene) + return int_mutate($mutation)(gene.value) + end + end return new(value, mutation) end end @@ -80,10 +84,21 @@ mutable struct Chromossome chromossome ::Vector{<:AbstractGene} crossover ::Symbol selection ::Symbol - function Chromossome( chromossome ::Vector{<:AbstractGene} , - crossover ::Symbol , - selection ::Symbol ) - n = length(chromossome) + function Chromossome( chromossome ::Vector{<:AbstractGene} , + crossover ::Symbol , + selection ::Symbol ; + w ::Union{Nothing , + Vector{Float64}} = nothing , + d ::Union{Nothing , + Float64 } = nothing ) + @eval begin + function int_crossover(chrom ::Chromossome) + + end + end + + + n = length(chromossome) return new(n, chromossome, crossover, selection) end end @@ -97,6 +112,9 @@ end function Chromossome() chrom = AbstractGene[BinaryGene(), IntegerGene(3), FloatGene(1)] + selection = :RWS + crossover = :SPX + return Chromossome(chrom, crossover, selection) end #################################################################### @@ -143,6 +161,9 @@ function ga( objfun ::Function , population = Vector{Individual}(undef, populationSize) offspring = similar(population) + # choose selection function + # selection = initpopulation. + # Generate population # for i in 1:populationSize # if isa(initPopulation, Vector) diff --git a/src/mutations.jl b/src/mutations.jl index b4fd6c9..60dadfe 100644 --- a/src/mutations.jl +++ b/src/mutations.jl @@ -16,6 +16,7 @@ function anisotropic(recombinant::T, s::S) where {T <: Vector, S <: Strategy} return recombinant end +#################################################################### # Strategy mutation operators # =========================== @@ -35,6 +36,7 @@ function anisotropicSigma(s::S) where {S <: Strategy} return strategy(σ = σ, τ = s[:τ], τ0 = s[:τ0]) end +#################################################################### # Genetic mutations # ================= @@ -157,7 +159,7 @@ function mutationwrapper(gamutation ::Function) return wrapper end -############################################### +#################################################################### # This function serves only to choose the mutation function for binary functions. The actual `mutate` # function is created in the `IntegerGene` structure. diff --git a/src/recombinations.jl b/src/recombinations.jl index 1f7d96a..0782af4 100644 --- a/src/recombinations.jl +++ b/src/recombinations.jl @@ -305,7 +305,7 @@ end # TPX - Two Point Crossover - twopoint # UX - Uniform Crossover - uniform # DX - Discrete Crossover - discrete -# WMX - Weighted Mean Crossover - waverage(w ::Vector{Float6}) +# WMX - Weighted Mean Crossover - waverage(w ::Vector{Float64}) # IRX - Intermediate Recombination Crossover - intermediate(d ::Float64) # LRX - Line Recombination Crossover - line(d ::Float64) # PMX - Partially Mapped Crossover - pmx @@ -313,12 +313,48 @@ end # O2X - Order 2 Crossover - ox2 # CX - Cycle Crossover - cx # PX - Position-based Crossover - pos -function crossover() +function crossover_func( cross ::Symbol ; + w ::Union{Nothing, Vector{Float64}} = nothing , + d ::Union{Nothing, Float64 } = nothing ) + if cross == :SPX + return singlepoint + elseif cross == :TPX + return twopoint + elseif cross == :UX + return uniform + elseif cross == :DX + return discrete + elseif cross == :WMX + if isnothing(w) + error("value `w` must be given a value") + end + return waverage(w) + elseif cross == :IRX + if isnothing(d) + error("value `d` must be given a value") + end + return intermediate(d) + elseif cross == :LRX + if isnothing(d) + error("value `d` must be given a value") + end + return line(d) + elseif cross == :PMX + return pmx + elseif cross == :O1X + return ox1 + elseif cross == :O2X + return ox2 + elseif cross == :CX + return cx + elseif cross == :PX + return pos + end + return nothing end #################################################################### - # Utils # ===== function vswap!(v1 ::T, v2 ::T, idx ::Int) where {T <: Vector} diff --git a/src/selections.jl b/src/selections.jl index 74adca6..0c0717a 100644 --- a/src/selections.jl +++ b/src/selections.jl @@ -104,9 +104,10 @@ end # :SUSS - Stochastic Universal Sampling Selection # :TrS - Truncation Selection # :ToS - Tournament Selection -function selection(; sp ::Union{Nothing, Float64} = nothing , - μ ::Union{Nothing, Int64 } = nothing , - groupsize ::Union{Nothing, Int64 } = nothing ) +function selection( chrom ::Chromossome ; + sp ::Union{Nothing, Float64} = nothing , + μ ::Union{Nothing, Int64 } = nothing , + groupsize ::Union{Nothing, Int64 } = nothing ) if chrom.selection == :RBS if isnothing(sp) error("need to specify `sp` value") From cee890dc18a723c20abc60d6ead20a8efc9dff18 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Mon, 24 Feb 2020 13:46:54 +0100 Subject: [PATCH 06/51] Continued improving code Already did some testing regarding mutations of any gene, and they're working fine. Now the issue are the crossover and selection functions, since there's one per Genetic Algorithm run. Need to think better on wht'a best and more efficient. --- src/Evolutionary.jl | 136 +++++++++++++------------- src/ga.jl | 116 ---------------------- src/mutations.jl | 9 +- src/recombinations.jl | 52 +--------- src/selections.jl | 35 ------- src/structs.jl | 221 ++++++++++++++++++++++++++++++++++++++++++ src/structs.jl~ | 117 ++++++++++++++++++++++ 7 files changed, 417 insertions(+), 269 deletions(-) create mode 100644 src/structs.jl create mode 100644 src/structs.jl~ diff --git a/src/Evolutionary.jl b/src/Evolutionary.jl index 5ac3dc1..e68e23d 100644 --- a/src/Evolutionary.jl +++ b/src/Evolutionary.jl @@ -1,83 +1,89 @@ + module Evolutionary + using Random - export Strategy, strategy, inverse, mutationwrapper, - # ES mutations - isotropic, anisotropic, isotropicSigma, anisotropicSigma, - # GA mutations - flip, domainrange, inversion, insertion, swap2, scramble, shifting, - # ES recombinations - average, marriage, averageSigma1, averageSigmaN, - # GA recombinations - singlepoint, twopoint, uniform, - discrete, waverage, intermediate, line, - pmx, ox1, cx, ox2, pos, - # GA selections - ranklinear, uniformranking, roulette, sus, tournament, truncation, - # Optimization methods - es, cmaes, ga - - const Strategy = Dict{Symbol,Any} - const Chromossome = Vector{T} where T <: AbstractGene - - # Wrapping function for strategy - function strategy(; kwargs...) - result = Dict{Symbol,Any}() - for (k, v) in kwargs - result[k] = v - end - return result + +export Strategy, strategy, inverse, mutationwrapper, + # ES mutations + isotropic, anisotropic, isotropicSigma, anisotropicSigma, + # GA mutations + flip, domainrange, inversion, insertion, swap2, scramble, shifting, + # ES recombinations + average, marriage, averageSigma1, averageSigmaN, + # GA recombinations + singlepoint, twopoint, uniform, + discrete, waverage, intermediate, line, + pmx, ox1, cx, ox2, pos, + # GA selections + ranklinear, uniformranking, roulette, sus, tournament, truncation, + # Optimization methods + es, cmaes, ga + +const Strategy = Dict{Symbol,Any} + +const Individual = Nothing + +# Wrapping function for strategy +function strategy(; kwargs...) + result = Dict{Symbol,Any}() + for (k, v) in kwargs + result[k] = v end + return result +end - # Inverse function for reversing optimization direction - function inverseFunc(f::Function) - function fitnessFunc(x::T) where {T <: Vector} - return 1.0/(f(x)+eps()) - end - return fitnessFunc +# Inverse function for reversing optimization direction +function inverseFunc(f::Function) + function fitnessFunc(x::T) where {T <: Vector} + return 1.0/(f(x)+eps()) end + return fitnessFunc +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) - else - individual = ones(N) - end - return individual +# 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) + else + individual = ones(N) end + return individual +end - # Collecting interim values - function keep(interim, v, vv, col) - if interim - if !haskey(col, v) - col[v] = typeof(vv)[] - end - push!(col[v], vv) +# Collecting interim values +function keep(interim, v, vv, col) + if interim + if !haskey(col, v) + col[v] = typeof(vv)[] end + push!(col[v], vv) end +end +# Structures +include("structs.jl") - # ES & GA recombination functions - include("recombinations.jl") +# ES & GA recombination functions +include("recombinations.jl") - # ES & GA mutation functions - include("mutations.jl") +# ES & GA mutation functions +include("mutations.jl") - # GA selection functions - include("selections.jl") +# GA selection functions +include("selections.jl") - # Evolution Strategy - include("es.jl") - include("cmaes.jl") +# Evolution Strategy +include("es.jl") +include("cmaes.jl") - # Genetic Algorithms - include("ga.jl") +# Genetic Algorithms +include("ga.jl") end diff --git a/src/ga.jl b/src/ga.jl index 79b1919..01c8dc4 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -1,121 +1,5 @@ -export BinaryGene, IntegerGene, FloatGene, Chromossome -export ga -#################################################################### - -abstract type AbstractGene end - -#################################################################### - -mutable struct BinaryGene <: AbstractGene - value ::Bool - BinaryGene(value ::Bool) = new(value) -end - -function BinaryGene() - return BinaryGene(rand(Bool)) -end - -#################################################################### - -mutable struct IntegerGene <: AbstractGene - value ::BitVector - mutation ::Symbol - function IntegerGene(value ::BitVector, mutation ::Symbol) - @eval begin - function mutate(gene ::IntegerGene) - return int_mutate($mutation)(gene.value) - end - end - return new(value, mutation) - end -end - -function IntegerGene(value ::BitVector) - return IntegerGene(value, :FM) -end - -function IntegerGene(n ::Int64) - value = BitVector(undef, rand(1:n)) - return IntegerGene(value, :FM) -end - -#################################################################### - -mutable struct FloatGene <: AbstractGene - value ::Vector{Float64} - range ::Vector{Float64} - m ::Int64 - function FloatGene(value ::Vector{Float64} , - range ::Vector{Float64} , - m ::Int64 ) - if length(value) != length(range) - error("vectors mush have the same length") - end - return new(value, range, m) - end -end - -function FloatGene(value ::Float64, range ::Float64; m ::Int64 = 20) - return FloatGene(Float64[value], Float64[range], m) -end - -function FloatGene(value ::Vector{Float64}, range ::Float64; m ::Int64 = 20) - vec = Float64[range for i in value] - return FloatGene(value, vec, m) -end - -function FloatGene(value ::Vector{Float64}; m ::Int64 = 20) - range = rand(Float64, length(value)) - return FloatGene(value, range, m) -end - -function FloatGene(n ::Int64) - value = rand(Float64, n) - range = rand(Float64, n) - return FloatGene(value, range, 20) -end - -#################################################################### - -mutable struct Chromossome - n ::Int64 - chromossome ::Vector{<:AbstractGene} - crossover ::Symbol - selection ::Symbol - function Chromossome( chromossome ::Vector{<:AbstractGene} , - crossover ::Symbol , - selection ::Symbol ; - w ::Union{Nothing , - Vector{Float64}} = nothing , - d ::Union{Nothing , - Float64 } = nothing ) - @eval begin - function int_crossover(chrom ::Chromossome) - - end - end - - - n = length(chromossome) - return new(n, chromossome, crossover, selection) - end -end - -function Chromossome( chromossome ::AbstractGene , - crossover ::Symbol , - selection ::Symbol ) - chrom = AbstractGene[chromossome] - return Chromossome(chrom, crossover, selection) -end - -function Chromossome() - chrom = AbstractGene[BinaryGene(), IntegerGene(3), FloatGene(1)] - selection = :RWS - crossover = :SPX - return Chromossome(chrom, crossover, selection) -end #################################################################### diff --git a/src/mutations.jl b/src/mutations.jl index 60dadfe..7c562a8 100644 --- a/src/mutations.jl +++ b/src/mutations.jl @@ -1,3 +1,8 @@ + +export mutate + +#################################################################### + # Mutation operators # ================== @@ -169,7 +174,7 @@ end # SwM - Swap Mutation # ScrM - Scramble Mutation # ShM - Shifting Mutation -function int_mutate(mutateype ::Symbol) +function int_mutate(mutatetype ::Symbol) mut_func = nothing if mutatetype == :FM mut_func = flip @@ -211,7 +216,7 @@ function mutate(gene ::FloatGene) end function mutate(chromossome ::Chromossome) - for gene in chromossome + for gene in chromossome.chromossome mutate(gene) end return nothing diff --git a/src/recombinations.jl b/src/recombinations.jl index 0782af4..3e01636 100644 --- a/src/recombinations.jl +++ b/src/recombinations.jl @@ -301,57 +301,7 @@ end #################################################################### -# SPX - Single Point Crossover - singlepoint -# TPX - Two Point Crossover - twopoint -# UX - Uniform Crossover - uniform -# DX - Discrete Crossover - discrete -# WMX - Weighted Mean Crossover - waverage(w ::Vector{Float64}) -# IRX - Intermediate Recombination Crossover - intermediate(d ::Float64) -# LRX - Line Recombination Crossover - line(d ::Float64) -# PMX - Partially Mapped Crossover - pmx -# O1X - Order 1 Crossover - ox1 -# O2X - Order 2 Crossover - ox2 -# CX - Cycle Crossover - cx -# PX - Position-based Crossover - pos -function crossover_func( cross ::Symbol ; - w ::Union{Nothing, Vector{Float64}} = nothing , - d ::Union{Nothing, Float64 } = nothing ) - if cross == :SPX - return singlepoint - elseif cross == :TPX - return twopoint - elseif cross == :UX - return uniform - elseif cross == :DX - return discrete - elseif cross == :WMX - if isnothing(w) - error("value `w` must be given a value") - end - return waverage(w) - elseif cross == :IRX - if isnothing(d) - error("value `d` must be given a value") - end - return intermediate(d) - elseif cross == :LRX - if isnothing(d) - error("value `d` must be given a value") - end - return line(d) - elseif cross == :PMX - return pmx - elseif cross == :O1X - return ox1 - elseif cross == :O2X - return ox2 - elseif cross == :CX - return cx - elseif cross == :PX - return pos - end - return nothing -end + #################################################################### diff --git a/src/selections.jl b/src/selections.jl index 0c0717a..2c2ca94 100644 --- a/src/selections.jl +++ b/src/selections.jl @@ -98,41 +98,6 @@ end #################################################################### -# :RBS - Rank-Based Selection -# :URS - Uniform-Ranking Selection -# :RWS - Roulette Wheel Selection -# :SUSS - Stochastic Universal Sampling Selection -# :TrS - Truncation Selection -# :ToS - Tournament Selection -function selection( chrom ::Chromossome ; - sp ::Union{Nothing, Float64} = nothing , - μ ::Union{Nothing, Int64 } = nothing , - groupsize ::Union{Nothing, Int64 } = nothing ) - if chrom.selection == :RBS - if isnothing(sp) - error("need to specify `sp` value") - end - return ranklinear(sp) - elseif chrom.selection == :URS - if isnothing(μ) - error("need to specify `μ` value") - end - return uniformranking(μ) - elseif chrom.selection == :RWS - return roulette - elseif chrom.selection == :SUSS - return sus - elseif chrom.selection == :TrS - return truncation - elseif chrom.selection == :ToS - if isnothing(groupsize) - error("need to specify `groupsize` value") - end - return tournament(groupsize) - else - error("Unknown parameter " * string(chrom.selection)) - end -end #################################################################### diff --git a/src/structs.jl b/src/structs.jl new file mode 100644 index 0000000..13c8848 --- /dev/null +++ b/src/structs.jl @@ -0,0 +1,221 @@ + +export BinaryGene, IntegerGene, FloatGene +export Crossover, Selection, Chromossome + +#################################################################### + +abstract type AbstractGene end + +#################################################################### + +mutable struct BinaryGene <: AbstractGene + value ::Bool + + BinaryGene(value ::Bool) = new(value) +end + +function BinaryGene() + return BinaryGene(rand(Bool)) +end + +#################################################################### + +mutable struct IntegerGene <: AbstractGene + value ::BitVector + mutation ::Symbol + + function IntegerGene(value ::BitVector, mutation ::Symbol) + int_func = int_mutate(mutation) + @eval mutate(gene ::IntegerGene) = $int_func(gene.value) + return new(value, mutation) + end +end + +function IntegerGene(value ::BitVector) + return IntegerGene(value, :FM) +end + +function IntegerGene(n ::Int64) + value = BitVector(undef, n) + return IntegerGene(value, :FM) +end + +#################################################################### + +mutable struct FloatGene <: AbstractGene + value ::Vector{Float64} + range ::Vector{Float64} + m ::Int64 + + function FloatGene(value ::Vector{Float64} , + range ::Vector{Float64} , + m ::Int64 ) + if length(value) != length(range) + error("vectors mush have the same length") + end + return new(value, range, m) + end +end + +function FloatGene(value ::Float64, range ::Float64; m ::Int64 = 20) + return FloatGene(Float64[value], Float64[range], m) +end + +function FloatGene(value ::Vector{Float64}, range ::Float64; m ::Int64 = 20) + vec = Float64[range for i in value] + return FloatGene(value, vec, m) +end + +function FloatGene(value ::Vector{Float64}; m ::Int64 = 20) + range = rand(Float64, length(value)) + return FloatGene(value, range, m) +end + +function FloatGene(value ::Float64; m ::Int64 = 20) + return FloatGene(value, rand(); m=m) +end + +function FloatGene(n ::Int64) + value = rand(Float64, n) + range = rand(Float64, n) + return FloatGene(value, range, 20) +end + +#################################################################### + +# :SPX - Single Point Crossover - singlepoint +# :TPX - Two Point Crossover - twopoint +# :UX - Uniform Crossover - uniform +# :DX - Discrete Crossover - discrete +# :WMX - Weighted Mean Crossover - waverage(w ::Vector{Float64}) +# :IRX - Intermediate Recombination Crossover - intermediate(d ::Float64) +# :LRX - Line Recombination Crossover - line(d ::Float64) +# :PMX - Partially Mapped Crossover - pmx +# :O1X - Order 1 Crossover - ox1 +# :O2X - Order 2 Crossover - ox2 +# :CX - Cycle Crossover - cx +# :PX - Position-based Crossover - pos +mutable struct Crossover + cross ::Symbol + func ::Function + w ::Union{Nothing, Vector{Float64}} + d ::Union{Nothing, Float64 } + + function Crossover(cross ::Symbol ; + w ::Union{Nothing, Vector{Float64}} = nothing , + d ::Union{Nothing, Float64 } = nothing ) + function crossover_func( cross ::Symbol ; + w ::Union{Nothing , + Vector{Float64}} = nothing , + d ::Union{Nothing , + Float64 } = nothing ) + if cross == :SPX + return singlepoint + elseif cross == :TPX + return twopoint + elseif cross == :UX + return uniform + elseif cross == :DX + return discrete + elseif cross == :WMX + if isnothing(w) + error("value `w` must be given a value") + end + return waverage(w) + elseif cross == :IRX + if isnothing(d) + error("value `d` must be given a value") + end + return intermediate(d) + elseif cross == :LRX + if isnothing(d) + error("value `d` must be given a value") + end + return line(d) + elseif cross == :PMX + return pmx + elseif cross == :O1X + return ox1 + elseif cross == :O2X + return ox2 + elseif cross == :CX + return cx + elseif cross == :PX + return pos + end + return nothing + end + func = crossover_func(cross; w=w, d=d) + return new(cross, func, w, d) + end +end + +#################################################################### + +# :RBS - Rank-Based Selection +# :URS - Uniform-Ranking Selection +# :RWS - Roulette Wheel Selection +# :SUSS - Stochastic Universal Sampling Selection +# :TrS - Truncation Selection +# :ToS - Tournament Selection +mutable struct Selection + select ::Symbol + func ::Function + sp ::Union{Nothing, Float64} + μ ::Union{Nothing, Int64 } + gsize ::Union{Nothing, Int64 } + + function Selection( select ::Symbol ; + sp ::Union{Nothing, Float64} = nothing , + μ ::Union{Nothing, Int64 } = nothing , + groupsize ::Union{Nothing, Int64 } = nothing ) + function selection( select ::Symbol ; + sp ::Union{Nothing, Float64} = nothing , + μ ::Union{Nothing, Int64 } = nothing , + groupsize ::Union{Nothing, Int64 } = nothing ) + if select == :RBS + if isnothing(sp) + error("need to specify `sp` value") + end + return ranklinear(sp) + elseif select == :URS + if isnothing(μ) + error("need to specify `μ` value") + end + return uniformranking(μ) + elseif select == :RWS + return roulette + elseif select == :SUSS + return sus + elseif select == :TrS + return truncation + elseif select == :ToS + if isnothing(groupsize) + error("need to specify `groupsize` value") + end + return tournament(groupsize) + else + error("Unknown parameter " * string(select)) + end + end + func = selection(select; sp=sp, μ=μ, groupsize=groupsize) + return new(select, func, sp, μ, groupsize) + end +end + +#################################################################### + +mutable struct Chromossome + n ::Int64 + chromossome ::Vector{<:AbstractGene} + + function Chromossome(chromossome ::Vector{<:AbstractGene}) + n = length(chromossome) + return new(n, chromossome, cross, select) + end +end + +function Chromossome(gene ::AbstractGene) + chromossome = AbstractGene[gene] + return Chromossome(chromossome) +end diff --git a/src/structs.jl~ b/src/structs.jl~ new file mode 100644 index 0000000..aeb6a3a --- /dev/null +++ b/src/structs.jl~ @@ -0,0 +1,117 @@ + +export BinaryGene, IntegerGene, FloatGene, Chromossome +export ga +export AbstractGene + +#################################################################### + +abstract type AbstractGene end + +#################################################################### + +mutable struct BinaryGene <: AbstractGene + value ::Bool + + BinaryGene(value ::Bool) = new(value) +end + +function BinaryGene() + return BinaryGene(rand(Bool)) +end + +#################################################################### + +mutable struct IntegerGene <: AbstractGene + value ::BitVector + mutation ::Symbol + + function IntegerGene(value ::BitVector, mutation ::Symbol) + int_func = int_mutate(mutation) + @eval mutate(gene ::IntegerGene) = int_func(gene.value) + return new(value, mutation) + end +end + +function IntegerGene(value ::BitVector) + return IntegerGene(value, :FM) +end + +function IntegerGene(n ::Int64) + value = BitVector(undef, rand(1:n)) + return IntegerGene(value, :FM) +end + +#################################################################### + +mutable struct FloatGene <: AbstractGene + value ::Vector{Float64} + range ::Vector{Float64} + m ::Int64 + + function FloatGene(value ::Vector{Float64} , + range ::Vector{Float64} , + m ::Int64 ) + if length(value) != length(range) + error("vectors mush have the same length") + end + return new(value, range, m) + end +end + +function FloatGene(value ::Float64, range ::Float64; m ::Int64 = 20) + return FloatGene(Float64[value], Float64[range], m) +end + +function FloatGene(value ::Vector{Float64}, range ::Float64; m ::Int64 = 20) + vec = Float64[range for i in value] + return FloatGene(value, vec, m) +end + +function FloatGene(value ::Vector{Float64}; m ::Int64 = 20) + range = rand(Float64, length(value)) + return FloatGene(value, range, m) +end + +function FloatGene(n ::Int64) + value = rand(Float64, n) + range = rand(Float64, n) + return FloatGene(value, range, 20) +end + +#################################################################### + +mutable struct Chromossome + n ::Int64 + chromossome ::Vector{<:AbstractGene} + crossover ::Crossover + selection ::Selection + + function Chromossome( chromossome ::Vector{<:AbstractGene} , + cross ::Crossover, select ::Selection ) + n = length(chromossome) + return new(n, chromossome, cross, select) + end +end + +function Chromossome( chromossome ::Vector{<:AbstractGene} ; + cross ::Symbol = :SPX , + select ::Symbol = :RWS ) + crossover = Crossover(cross) + selection = Selection(select) + return Chromossome(chromossome, crossover, selection) +end + +function Chromossome( gene ::AbstractGene , + cross ::Crossover , + select ::Selection ) + chromossome = AbstractGene[gene] + return Chromossome(chromossome, cross, select) +end + +function Chromossome( gene ::AbstractGene ; + cross ::Symbol = :SPX , + select ::Symbol = :RWS ) + crossover = Crossover(cross) + selection = Selection(select) + return Chromossome(gene, crossover, selection) +end From 8991a1828bdfa3a25a2710d751638e5a84df474e Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Tue, 25 Feb 2020 16:02:23 +0100 Subject: [PATCH 07/51] Continued improving code Started debugging the entire code, created a new file just for the structures and some other global code. Have to test the selection function --- src/ga.jl | 84 ++++++++++++++++++++----------------- src/mutations.jl | 6 +-- src/recombinations.jl | 52 ++++++++++++++++------- src/structs.jl | 96 +++++++++++++++++++++---------------------- 4 files changed, 133 insertions(+), 105 deletions(-) diff --git a/src/ga.jl b/src/ga.jl index 01c8dc4..e0922f5 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -1,5 +1,5 @@ - +export ga #################################################################### @@ -17,21 +17,23 @@ # are guaranteed to survive to the next generation. # Floating number specifies fraction of population. # -function ga( objfun ::Function , - initpopulation ::Chromossome , - populationSize ::Int64 ; - lowerBounds ::Union{Nothing, Vector} = nothing , - upperBounds ::Union{Nothing, Vector} = nothing , - crossoverRate ::Float64 = 0.8 , - mutationRate ::Float64 = 0.1 , - ɛ ::Real = 0 , - iterations ::Integer = 100 , - tol ::Real = 0.0 , - tolIter ::Int64 = 10 , - verbose ::Bool = false , - debug ::Bool = false , - interim ::Bool = false , - parallel ::Bool = false ) +function ga( objfun ::Function , + initpopulation ::Vector{<:AbstractGene} , + populationSize ::Int64 ; + cross ::Union{Symbol,Crossover} = :SPX , + select ::Union{Symbol,Selection} = :RWS , + lowerBounds ::Union{Nothing, Vector } = nothing , + upperBounds ::Union{Nothing, Vector } = nothing , + crossoverRate ::Float64 = 0.8 , + mutationRate ::Float64 = 0.1 , + ɛ ::Real = 0 , + iterations ::Integer = 100 , + tol ::Real = 0.0 , + tolIter ::Int64 = 10 , + verbose ::Bool = false , + debug ::Bool = false , + interim ::Bool = false , + parallel ::Bool = false ) store = Dict{Symbol,Any}() @@ -40,28 +42,10 @@ function ga( objfun ::Function , fitFunc = inverseFunc(objfun) # Initialize population - # individual = getIndividual(initPopulation, N) fitness = zeros(populationSize) - population = Vector{Individual}(undef, populationSize) + population = Vector{Vector{<:AbstractGene}}(undef, populationSize) offspring = similar(population) - # choose selection function - # selection = initpopulation. - - # 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 - # else - # error("Cannot generate population") - # end - # fitness[i] = fitFunc(population[i]) - # debug && println("INIT $(i): $(population[i]) : $(fitness[i])") - # end for i in 1:populationSize population[i] = initpopulation fitness[i] = fitFunc(population[i]) @@ -70,6 +54,30 @@ function ga( objfun ::Function , fitidx = sortperm(fitness, rev = true) keep(interim, :fitness, copy(fitness), store) + # choose crossover function + if typeof(cross) == Symbol + try + crossover = Crossover(cross).func + catch + error("Crossover mode $cross needs extra arguments. Consider creating " * + "the crossover function using the Crossover structure." ) + end + else + crossover = cross.func + end + + # Choose selection function + if typeof(select) == Symbol + try + selection = Selection(select).func + catch + error("Selection mode $select needs extra arguments. Consider creating " * + "the selection function using the Selection structure." ) + end + else + selection = select.func + end + # Generate and evaluate offspring itr = 1 bestFitness = 0.0 @@ -88,9 +96,9 @@ function ga( objfun ::Function , j = (i == populationSize) ? i-1 : i+1 if rand() < crossoverRate debug && - println("MATE $(offidx[i])+$(offidx[j])>: " * - "$(population[selected[offidx[i]]]) : " * - "$(population[selected[offidx[j]]])" ) + println( "MATE $(offidx[i])+$(offidx[j])>: " * + "$(population[selected[offidx[i]]]) : " * + "$(population[selected[offidx[j]]])" ) offspring[i], offspring[j] = crossover(population[selected[offidx[i]]], population[selected[offidx[j]]]) debug && diff --git a/src/mutations.jl b/src/mutations.jl index 7c562a8..8c14b03 100644 --- a/src/mutations.jl +++ b/src/mutations.jl @@ -215,9 +215,9 @@ function mutate(gene ::FloatGene) return nothing end -function mutate(chromossome ::Chromossome) - for gene in chromossome.chromossome +function mutate(chromossome ::Vector{<:AbstractGene}) + for gene in chromossome mutate(gene) end - return nothing + return end diff --git a/src/recombinations.jl b/src/recombinations.jl index 3e01636..233daeb 100644 --- a/src/recombinations.jl +++ b/src/recombinations.jl @@ -1,4 +1,9 @@ - + +export crossover +export GAVector + +#################################################################### + # Recombinations # ============== function average(population ::Vector{T}) where {T <: Vector} @@ -50,11 +55,13 @@ end # Genetic algorithms # ================== +const GAVector = Union{T, BitVector} where T <: Vector + # Binary crossovers # ----------------- # Single point crossover -function singlepoint(v1 ::T, v2 ::T) where {T <: Vector} +function singlepoint(v1 ::T, v2 ::T) where {T <: AbstractVector} l = length(v1) c1 = copy(v1) c2 = copy(v2) @@ -66,7 +73,7 @@ function singlepoint(v1 ::T, v2 ::T) where {T <: Vector} end # Two point crossover -function twopoint(v1 ::T, v2 ::T) where {T <: Vector} +function twopoint(v1 ::T, v2 ::T) where {T <: AbstractVector} l = length(v1) c1 = copy(v1) c2 = copy(v2) @@ -79,7 +86,7 @@ function twopoint(v1 ::T, v2 ::T) where {T <: Vector} end # Uniform crossover -function uniform(v1 ::T, v2 ::T) where {T <: Vector} +function uniform(v1 ::T, v2 ::T) where {T <: AbstractVector} l = length(v1) c1 = copy(v1) c2 = copy(v2) @@ -97,7 +104,7 @@ end # ---------------------- # Discrete Crossover -function discrete(v1 ::T, v2 ::T) where {T <: Vector} +function discrete(v1 ::T, v2 ::T) where {T <: AbstractVector} l = length(v1) c1 = similar(v1) c2 = similar(v2) @@ -110,7 +117,7 @@ end # Weighted arithmetic mean function waverage(w ::Vector{Float64}) - function wavexvr(v1 ::T, v2 ::T) where {T <: Vector} + function wavexvr(v1 ::T, v2 ::T) where {T <: AbstractVector} c1 = (v1+v2) ./ w return c1, copy(c1) end @@ -119,7 +126,7 @@ end # Intermediate recombination function intermediate(d ::Float64 = 0.0) - function intermxvr(v1 ::T, v2 ::T) where {T <: Vector} + function intermxvr(v1 ::T, v2 ::T) where {T <: AbstractVector} l = length(v1) α = (1.0+2d) * rand(l) .- d c1 = v2 .+ α .* (v1 - v2) @@ -132,7 +139,7 @@ end # Line recombination function line(d ::Float64 = 0.0) - function linexvr(v1 ::T, v2 ::T) where {T <: Vector} + function linexvr(v1 ::T, v2 ::T) where {T <: AbstractVector} α1, α2 = (1.0+2d) * rand(2) .- d c1 = v2 .+ α2 * (v1 - v2) c2 = v1 .+ α1 * (v2 - v1) @@ -147,7 +154,7 @@ end # ---------------------- # Partially mapped crossover -function pmx(v1 ::T, v2 ::T) where {T <: Vector} +function pmx(v1 ::T, v2 ::T) where {T <: AbstractVector} s = length(v1) from, to = rand(1:s, 2) from, to = from > to ? (to, from) : (from, to) @@ -189,7 +196,7 @@ function pmx(v1 ::T, v2 ::T) where {T <: Vector} end # Order crossover -function ox1(v1 ::T, v2 ::T) where {T <: Vector} +function ox1(v1 ::T, v2 ::T) where {T <: AbstractVector} s = length(v1) from, to = rand(1:s, 2) from, to = from > to ? (to, from) : (from, to) @@ -215,7 +222,7 @@ function ox1(v1 ::T, v2 ::T) where {T <: Vector} end # Cycle crossover -function cx(v1 ::T, v2 ::T) where {T <: Vector} +function cx(v1 ::T, v2 ::T) where {T <: AbstractVector} s = length(v1) c1 = zeros(v1) c2 = zeros(v2) @@ -246,7 +253,7 @@ function cx(v1 ::T, v2 ::T) where {T <: Vector} end # Order-based crossover -function ox2(v1 ::T, v2 ::T) where {T <: Vector} +function ox2(v1 ::T, v2 ::T) where {T <: AbstractVector} s = length(v1) c1 = copy(v1) c2 = copy(v2) @@ -274,7 +281,7 @@ function ox2(v1 ::T, v2 ::T) where {T <: Vector} end # Position-based crossover -function pos(v1 ::T, v2 ::T) where {T <: Vector} +function pos(v1 ::T, v2 ::T) where {T <: AbstractVector} s = length(v1) c1 = zeros(v1) c2 = zeros(v2) @@ -301,13 +308,30 @@ end #################################################################### +export singlepoint + +function crossover(gene1 ::T, gene2 ::T) where {T <: IntegerGene} + return crossover(gene1.value, gene2.value) +end + +function crossover(gene1 ::T, gene2 ::T) where {T <: FloatGene} + return crossover(gene1.value, gene2.value) +end +function crossover(chromo1 ::T, chromo2 ::T) where {T <: Vector{AbstractGene}} + c1 = copy(chromo1) + c2 = copy(chromo2) + for i in 1:length(chromo1) + c1[i].value, c2[i].value = crossover(chromo1[i], chromo2[i]) + end + return c1, c2 +end #################################################################### # Utils # ===== -function vswap!(v1 ::T, v2 ::T, idx ::Int) where {T <: Vector} +function vswap!(v1 ::T, v2 ::T, idx ::Int) where {T <: AbstractVector} val = v1[idx] v1[idx] = v2[idx] v2[idx] = val diff --git a/src/structs.jl b/src/structs.jl index 13c8848..7f3391c 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -1,6 +1,7 @@ export BinaryGene, IntegerGene, FloatGene export Crossover, Selection, Chromossome +export AbstractGene #################################################################### @@ -37,6 +38,9 @@ end function IntegerGene(n ::Int64) value = BitVector(undef, n) + for i in 1:n + value[i] = rand(Bool) + end return IntegerGene(value, :FM) end @@ -47,9 +51,9 @@ mutable struct FloatGene <: AbstractGene range ::Vector{Float64} m ::Int64 - function FloatGene(value ::Vector{Float64} , - range ::Vector{Float64} , - m ::Int64 ) + function FloatGene( value ::Vector{Float64} , + range ::Vector{Float64} , + m ::Int64 ) if length(value) != length(range) error("vectors mush have the same length") end @@ -97,7 +101,6 @@ end # :PX - Position-based Crossover - pos mutable struct Crossover cross ::Symbol - func ::Function w ::Union{Nothing, Vector{Float64}} d ::Union{Nothing, Float64 } @@ -109,44 +112,50 @@ mutable struct Crossover Vector{Float64}} = nothing , d ::Union{Nothing , Float64 } = nothing ) + cross_func = nothing if cross == :SPX - return singlepoint + cross_func = singlepoint elseif cross == :TPX - return twopoint + cross_func = twopoint elseif cross == :UX - return uniform + cross_func = uniform elseif cross == :DX - return discrete + cross_func = discrete elseif cross == :WMX if isnothing(w) error("value `w` must be given a value") end - return waverage(w) + cross_func = waverage(w) elseif cross == :IRX if isnothing(d) error("value `d` must be given a value") end - return intermediate(d) + cross_func = intermediate(d) elseif cross == :LRX if isnothing(d) error("value `d` must be given a value") end - return line(d) + cross_func = line(d) elseif cross == :PMX - return pmx + cross_func = pmx elseif cross == :O1X - return ox1 + cross_func = ox1 elseif cross == :O2X - return ox2 + cross_func = ox2 elseif cross == :CX - return cx + cross_func = cx elseif cross == :PX - return pos + cross_func = pos + end + @eval begin + function crossover(v1::T, v2 ::T) where {T <: AbstractVector} + return $cross_func(v1, v2) + end end return nothing end - func = crossover_func(cross; w=w, d=d) - return new(cross, func, w, d) + crossover_func(cross, w=w, d=d) + return new(cross, w, d) end end @@ -160,62 +169,49 @@ end # :ToS - Tournament Selection mutable struct Selection select ::Symbol - func ::Function sp ::Union{Nothing, Float64} - μ ::Union{Nothing, Int64 } - gsize ::Union{Nothing, Int64 } + μ ::Union{Nothing, Int64} + gsize ::Union{Nothing, Int64} function Selection( select ::Symbol ; sp ::Union{Nothing, Float64} = nothing , - μ ::Union{Nothing, Int64 } = nothing , - groupsize ::Union{Nothing, Int64 } = nothing ) + μ ::Union{Nothing, Int64} = nothing , + groupsize ::Union{Nothing, Int64} = nothing ) function selection( select ::Symbol ; sp ::Union{Nothing, Float64} = nothing , - μ ::Union{Nothing, Int64 } = nothing , - groupsize ::Union{Nothing, Int64 } = nothing ) + μ ::Union{Nothing, Int64} = nothing , + groupsize ::Union{Nothing, Int64} = nothing ) + selec_func = nothing if select == :RBS if isnothing(sp) error("need to specify `sp` value") end - return ranklinear(sp) + selec_func = ranklinear(sp) elseif select == :URS if isnothing(μ) error("need to specify `μ` value") end - return uniformranking(μ) + selec_func = uniformranking(μ) elseif select == :RWS - return roulette + selec_func = roulette elseif select == :SUSS - return sus + selec_func = sus elseif select == :TrS - return truncation + selec_func = truncation elseif select == :ToS if isnothing(groupsize) error("need to specify `groupsize` value") end - return tournament(groupsize) + selec_func = tournament(groupsize) else error("Unknown parameter " * string(select)) end end - func = selection(select; sp=sp, μ=μ, groupsize=groupsize) - return new(select, func, sp, μ, groupsize) - end -end - -#################################################################### - -mutable struct Chromossome - n ::Int64 - chromossome ::Vector{<:AbstractGene} - - function Chromossome(chromossome ::Vector{<:AbstractGene}) - n = length(chromossome) - return new(n, chromossome, cross, select) + @eval begin + function selection(fit ::Vector{<:Real}, N ::Int) + return $selec_func(fit, N) + end + end + return new(select, sp, μ, groupsize) end end - -function Chromossome(gene ::AbstractGene) - chromossome = AbstractGene[gene] - return Chromossome(chromossome) -end From b9d23cb2c5acaac5ae06864cc88e5126637b04d4 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Wed, 26 Feb 2020 16:57:06 +0100 Subject: [PATCH 08/51] Continued improving code --- src/ga.jl | 37 +++------------ src/structs.jl | 76 +++++++++++++++---------------- src/structs.jl~ | 117 ------------------------------------------------ 3 files changed, 42 insertions(+), 188 deletions(-) delete mode 100644 src/structs.jl~ diff --git a/src/ga.jl b/src/ga.jl index e0922f5..bfb83ca 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -20,13 +20,11 @@ export ga function ga( objfun ::Function , initpopulation ::Vector{<:AbstractGene} , populationSize ::Int64 ; - cross ::Union{Symbol,Crossover} = :SPX , - select ::Union{Symbol,Selection} = :RWS , lowerBounds ::Union{Nothing, Vector } = nothing , upperBounds ::Union{Nothing, Vector } = nothing , - crossoverRate ::Float64 = 0.8 , - mutationRate ::Float64 = 0.1 , - ɛ ::Real = 0 , + crossoverRate ::Float64 = 0.5 , + mutationRate ::Float64 = 0.5 , + ϵ ::Real = 0 , iterations ::Integer = 100 , tol ::Real = 0.0 , tolIter ::Int64 = 10 , @@ -38,7 +36,7 @@ function ga( objfun ::Function , store = Dict{Symbol,Any}() # Setup parameters - elite = isa(ɛ, Int) ? ɛ : round(Int, ɛ * populationSize) + elite = isa(ϵ, Int) ? ϵ : round(Int, ϵ * populationSize) fitFunc = inverseFunc(objfun) # Initialize population @@ -53,30 +51,6 @@ function ga( objfun ::Function , end fitidx = sortperm(fitness, rev = true) keep(interim, :fitness, copy(fitness), store) - - # choose crossover function - if typeof(cross) == Symbol - try - crossover = Crossover(cross).func - catch - error("Crossover mode $cross needs extra arguments. Consider creating " * - "the crossover function using the Crossover structure." ) - end - else - crossover = cross.func - end - - # Choose selection function - if typeof(select) == Symbol - try - selection = Selection(select).func - catch - error("Selection mode $select needs extra arguments. Consider creating " * - "the selection function using the Selection structure." ) - end - else - selection = select.func - end # Generate and evaluate offspring itr = 1 @@ -146,7 +120,8 @@ function ga( objfun ::Function , # Verbose step verbose && - println("BEST: $(bestFitness): $(population[bestIndividual]), G: $(itr)") + println("BEST: $(round(bestFitness, digits=3)): " * + "$(population[bestIndividual]), G: $(itr)") # Terminate: # if fitness tolerance is met for specified number of steps diff --git a/src/structs.jl b/src/structs.jl index 7f3391c..e901093 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -1,7 +1,8 @@ +export AbstractGene export BinaryGene, IntegerGene, FloatGene export Crossover, Selection, Chromossome -export AbstractGene +export selection, crossover, bin #################################################################### @@ -15,9 +16,7 @@ mutable struct BinaryGene <: AbstractGene BinaryGene(value ::Bool) = new(value) end -function BinaryGene() - return BinaryGene(rand(Bool)) -end +BinaryGene() = BinaryGene(rand(Bool)) #################################################################### @@ -44,6 +43,14 @@ function IntegerGene(n ::Int64) return IntegerGene(value, :FM) end +function bin(gene ::IntegerGene) + bin_val = 0 + for (i,j) in enumerate(gene.value) + bin_val += j ? 2^(i-1) : 0 + end + return bin_val +end + #################################################################### mutable struct FloatGene <: AbstractGene @@ -107,11 +114,6 @@ mutable struct Crossover function Crossover(cross ::Symbol ; w ::Union{Nothing, Vector{Float64}} = nothing , d ::Union{Nothing, Float64 } = nothing ) - function crossover_func( cross ::Symbol ; - w ::Union{Nothing , - Vector{Float64}} = nothing , - d ::Union{Nothing , - Float64 } = nothing ) cross_func = nothing if cross == :SPX cross_func = singlepoint @@ -147,14 +149,12 @@ mutable struct Crossover elseif cross == :PX cross_func = pos end + @eval begin function crossover(v1::T, v2 ::T) where {T <: AbstractVector} return $cross_func(v1, v2) end end - return nothing - end - crossover_func(cross, w=w, d=d) return new(cross, w, d) end end @@ -177,36 +177,32 @@ mutable struct Selection sp ::Union{Nothing, Float64} = nothing , μ ::Union{Nothing, Int64} = nothing , groupsize ::Union{Nothing, Int64} = nothing ) - function selection( select ::Symbol ; - sp ::Union{Nothing, Float64} = nothing , - μ ::Union{Nothing, Int64} = nothing , - groupsize ::Union{Nothing, Int64} = nothing ) - selec_func = nothing - if select == :RBS - if isnothing(sp) - error("need to specify `sp` value") - end - selec_func = ranklinear(sp) - elseif select == :URS - if isnothing(μ) - error("need to specify `μ` value") - end - selec_func = uniformranking(μ) - elseif select == :RWS - selec_func = roulette - elseif select == :SUSS - selec_func = sus - elseif select == :TrS - selec_func = truncation - elseif select == :ToS - if isnothing(groupsize) - error("need to specify `groupsize` value") - end - selec_func = tournament(groupsize) - else - error("Unknown parameter " * string(select)) + selec_func = nothing + if select == :RBS + if isnothing(sp) + error("need to specify `sp` value") + end + selec_func = ranklinear(sp) + elseif select == :URS + if isnothing(μ) + error("need to specify `μ` value") + end + selec_func = uniformranking(μ) + elseif select == :RWS + selec_func = roulette + elseif select == :SUSS + selec_func = sus + elseif select == :TrS + selec_func = truncation + elseif select == :ToS + if isnothing(groupsize) + error("need to specify `groupsize` value") end + selec_func = tournament(groupsize) + else + error("Unknown parameter " * string(select)) end + @eval begin function selection(fit ::Vector{<:Real}, N ::Int) return $selec_func(fit, N) diff --git a/src/structs.jl~ b/src/structs.jl~ deleted file mode 100644 index aeb6a3a..0000000 --- a/src/structs.jl~ +++ /dev/null @@ -1,117 +0,0 @@ - -export BinaryGene, IntegerGene, FloatGene, Chromossome -export ga -export AbstractGene - -#################################################################### - -abstract type AbstractGene end - -#################################################################### - -mutable struct BinaryGene <: AbstractGene - value ::Bool - - BinaryGene(value ::Bool) = new(value) -end - -function BinaryGene() - return BinaryGene(rand(Bool)) -end - -#################################################################### - -mutable struct IntegerGene <: AbstractGene - value ::BitVector - mutation ::Symbol - - function IntegerGene(value ::BitVector, mutation ::Symbol) - int_func = int_mutate(mutation) - @eval mutate(gene ::IntegerGene) = int_func(gene.value) - return new(value, mutation) - end -end - -function IntegerGene(value ::BitVector) - return IntegerGene(value, :FM) -end - -function IntegerGene(n ::Int64) - value = BitVector(undef, rand(1:n)) - return IntegerGene(value, :FM) -end - -#################################################################### - -mutable struct FloatGene <: AbstractGene - value ::Vector{Float64} - range ::Vector{Float64} - m ::Int64 - - function FloatGene(value ::Vector{Float64} , - range ::Vector{Float64} , - m ::Int64 ) - if length(value) != length(range) - error("vectors mush have the same length") - end - return new(value, range, m) - end -end - -function FloatGene(value ::Float64, range ::Float64; m ::Int64 = 20) - return FloatGene(Float64[value], Float64[range], m) -end - -function FloatGene(value ::Vector{Float64}, range ::Float64; m ::Int64 = 20) - vec = Float64[range for i in value] - return FloatGene(value, vec, m) -end - -function FloatGene(value ::Vector{Float64}; m ::Int64 = 20) - range = rand(Float64, length(value)) - return FloatGene(value, range, m) -end - -function FloatGene(n ::Int64) - value = rand(Float64, n) - range = rand(Float64, n) - return FloatGene(value, range, 20) -end - -#################################################################### - -mutable struct Chromossome - n ::Int64 - chromossome ::Vector{<:AbstractGene} - crossover ::Crossover - selection ::Selection - - function Chromossome( chromossome ::Vector{<:AbstractGene} , - cross ::Crossover, select ::Selection ) - n = length(chromossome) - return new(n, chromossome, cross, select) - end -end - -function Chromossome( chromossome ::Vector{<:AbstractGene} ; - cross ::Symbol = :SPX , - select ::Symbol = :RWS ) - crossover = Crossover(cross) - selection = Selection(select) - return Chromossome(chromossome, crossover, selection) -end - -function Chromossome( gene ::AbstractGene , - cross ::Crossover , - select ::Selection ) - chromossome = AbstractGene[gene] - return Chromossome(chromossome, cross, select) -end - -function Chromossome( gene ::AbstractGene ; - cross ::Symbol = :SPX , - select ::Symbol = :RWS ) - crossover = Crossover(cross) - selection = Selection(select) - return Chromossome(gene, crossover, selection) -end From 743ac2723b6fbc1187526da1f99704a337cb5b85 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Thu, 27 Feb 2020 13:34:33 +0100 Subject: [PATCH 09/51] Continued improving code Changed the way the fitting tolerance is calculated, since it was giving some weird results, now it's simpler. Changed the infinite while loop for a for loop to be able to parallelize it in the future. --- src/Evolutionary.jl | 2 +- src/ga.jl | 27 +++++++-------------------- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/src/Evolutionary.jl b/src/Evolutionary.jl index e68e23d..78a3441 100644 --- a/src/Evolutionary.jl +++ b/src/Evolutionary.jl @@ -1,7 +1,7 @@ module Evolutionary -using Random +using Random, Base.Threads export Strategy, strategy, inverse, mutationwrapper, # ES mutations diff --git a/src/ga.jl b/src/ga.jl index bfb83ca..93a4467 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -51,6 +51,8 @@ function ga( objfun ::Function , end fitidx = sortperm(fitness, rev = true) keep(interim, :fitness, copy(fitness), store) + + # Generate and evaluate offspring itr = 1 @@ -58,7 +60,7 @@ function ga( objfun ::Function , bestIndividual = 0 fittol = 0.0 fittolitr = 1 - while true + for iter in 1:iterations debug && println("BEST: $(fitidx)") # Select offspring @@ -111,9 +113,7 @@ function ga( objfun ::Function , end fitidx = sortperm(fitness, rev = true) bestIndividual = fitidx[1] - curGenFitness = Float64(objfun(population[bestIndividual])) - fittol = abs(bestFitness - curGenFitness) - bestFitness = curGenFitness + bestFitness = Float64(objfun(population[bestIndividual])) keep(interim, :fitness, copy(fitness), store) keep(interim, :bestFitness, bestFitness, store) @@ -121,25 +121,12 @@ function ga( objfun ::Function , # Verbose step verbose && println("BEST: $(round(bestFitness, digits=3)): " * - "$(population[bestIndividual]), G: $(itr)") + "$(population[bestIndividual]), G: $(iter)") - # Terminate: - # if fitness tolerance is met for specified number of steps - if fittol <= tol - if fittolitr > tolIter - break - else - fittolitr += 1 - end - else - fittolitr = 1 - end - # if number of iterations more then specified - if itr >= iterations + if bestFitness <= tol + itr = iter break end - itr += 1 end - return population[bestIndividual], bestFitness, itr, fittol, store end From 44dbce5a85c520da1d7b3fc6c325fd87f834d43d Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Thu, 27 Feb 2020 17:05:52 +0100 Subject: [PATCH 10/51] Continued improving code The FloatGene has already been tested and works fine. Now I have to start building parallel code. --- src/ga.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ga.jl b/src/ga.jl index 93a4467..30d1cb1 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -123,8 +123,8 @@ function ga( objfun ::Function , println("BEST: $(round(bestFitness, digits=3)): " * "$(population[bestIndividual]), G: $(iter)") + itr = iter if bestFitness <= tol - itr = iter break end end From 7beca54833737da056a68c82588f8ab293a2a962 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Sat, 29 Feb 2020 17:36:03 +0100 Subject: [PATCH 11/51] wrote documentation for every major function created. Also updated README to add the functions and behaviours created. --- README.md | 143 +++++++++++++++++++++++++++++++++++++++++- src/Evolutionary.jl | 10 ++- src/ga.jl | 28 ++++++++- src/mutations.jl | 31 ++++++++- src/recombinations.jl | 25 ++++++-- src/selections.jl | 9 ++- src/structs.jl | 125 +++++++++++++++++++++++++++++++++++- 7 files changed, 352 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index c034179..e5cb132 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,152 @@ pkg> add https://github.com/wildart/Evolutionary.jl.git#v0.2.0 ## Functionalities -#### Algorithms + +### Creating Chromossomes + +There are three types of genes that can be used for optimization, the `BinaryGene`, the `IntegerGene` and the `FloatGene`. + + +#### `BinaryGene` + +A `BinaryGene` contains simply a boolean value. It can be created in two ways: + +```julia +# option 1 +gene = BinaryGene(true) +# option 2 +gene = BinaryGene() +``` + +The first way sets the initial value to `true`, while the second case sets the initial value to a random boolean value. + + +#### `IntegerGene` + +A `IntegerGene` is a container for a `BitVector` that is used to determine the integer number using base-2 binary arithmetic. To create a `IntegerGene`: + +```julia +# option 1 +gene = IntegerGene(BitVector(undef, 3), :FM) +# option 2 +gene = IntegerGene(BitVector(undef, 3)) +# option 3 +gene = IntegerGene(3) + +int_number = bin(gene) +``` + +The first one sets a `BitVector` with three elements and sets the Flip Mutation as the mutation algorithm for this gene. The second one sets the Flip Mutation as the default mutation algorithm. The last creates a random `BitVector` with length 3 and the Flip Mutation as the default mutation algorithm. To know all the mutation algorithms implemented and their corresponding symbols, as well as more information about these functions, just type `?IntegerGene` in the command prompt. The function `bin` is a function created to convert the `BitVector` into an integer number using base-2 binary arithmetic. + + +#### `FloatGene` + +A `FloatGene` is a gene that is comprised by real numbers. Can have one or more values, so you can combine all real valued variables in a single `FloatGene`. There are several ways to create a `FloatGene`, so let's name a few: + +```julia +# option 1 +gene = FloatGene(values, ranges; m ::Int = 20) +# option 2 +gene = FloatGene(values, range; m ::Int = 20) +# option 3 +gene = FloatGene(value, range; m ::Int = 20) +# option 4 +gene = FloatGene(values; m ::Int = 20) +# option 5 +gene = FloatGene(value; m ::Int = 20) +# option 6 +gene = FloatGene(n) +``` + +Plural `values` and `ranges` mean vector, while singular `value` and `range` mean scalar. In options 4 and 5, both `range` and `ranges` are created randomly, according to the size of `value` or `values`. Option 6 creates random variables and random ranges of size `n`. + + +### Running the Genetic Algorithm + +Now that we know how to create genes, we need to create a population and an objective function to run our genetic algorithm. + +#### Example 1 + +In this example we want to find the index of a vector associated to the midpoint of said vector. Given: + +```julia +x = 0.0:0.01:10 +``` + +We want to find the index that corresponds to the value `5.0`. First let's create our chromossome, which in this case will comprise of one `IntegerGene`: + +```julia +gene = IntegerGene(4) +chromossome = [gene] +``` + +**NOTE:** when dealing with integer numbers, make sure the length of your vector is big enough to embrace the possible value. In this case, for a vector of length 4, the maximum integer value is 16, so our expected result can be represented by this vector. + +Our objective function could be something like: + +```julia +function objfun(chrom ::Vector{<:AbstractGene}) + ind = bin(chrom[1]) + return abs( x[ind] - (x[end]-x[1]) / 2 ) +end +``` + +Now we have to choose the crossover and selection algorithms: + +```julia +Crossover(:SPX) # single point crossover +Selection(:RWS) # roulette wheel selection +``` + +And now we can run our genetic algorithm: + +```julia +N = 100 # population size +ga(objfun, chromossome, N) +``` + +If you couldn't get the right result, you can increase the population size or increase the number of iterations. The optional arguments are well explained if you go to the command prompt and type `?ga`. + + +#### Example 2 + +Using the same vector `x`, now we want to determine the midpoint of said vector, which will be a real number. In that case: + +```julia +gene = FloatGene(1.0) # random range value +chromossome = [gene] +``` + +Now our objective function will be slightly different: + +```julia +function objfun(chrom ::Vector{<:AbstractGene}) + return abs(chrom.value[1] - (x[end] - x[1]) / 2) +end +``` + +After choosing crossover and selection algorithms: + +```julia +Crossover(:SPX) # single point crossover +Selection(:RWS) # roulette wheel selection +``` + +We run the genetic algorithm in the same way: + +```julia +N = 100 +ga(objfun, chromossome, N) +``` + + +### Algorithms - (μ/ρ(+/,)λ)-SA-ES - (μ/μ_I,λ)-CMA-ES - Genetic Algorithms (GA) -#### Operators +### Operators - Mutations - (an)isotropic mutation (for ES) diff --git a/src/Evolutionary.jl b/src/Evolutionary.jl index 78a3441..8508c04 100644 --- a/src/Evolutionary.jl +++ b/src/Evolutionary.jl @@ -17,12 +17,18 @@ export Strategy, strategy, inverse, mutationwrapper, # GA selections ranklinear, uniformranking, roulette, sus, tournament, truncation, # Optimization methods - es, cmaes, ga + es, cmaes, ga, + # Constants + GAVector const Strategy = Dict{Symbol,Any} const Individual = Nothing +const GAVector = Union{T, BitVector} where T <: Vector + +#################################################################### + # Wrapping function for strategy function strategy(; kwargs...) result = Dict{Symbol,Any}() @@ -67,7 +73,7 @@ function keep(interim, v, vv, col) end end -# Structures +# General Structures include("structs.jl") # ES & GA recombination functions diff --git a/src/ga.jl b/src/ga.jl index 30d1cb1..2a7e38b 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -1,3 +1,8 @@ +##### ga.jl ##### + +# In this file you will find the Genetic Algorithm. + +#################################################################### export ga @@ -13,10 +18,29 @@ export ga # crossoverRate : The fraction of the population at the next generation, not including elite # children, that is created by the crossover function. # mutationRate : Probability of chromosome to be mutated -# ɛ : Positive integer specifies how many individuals in the current generation +# ϵ : Positive integer specifies how many individuals in the current generation # are guaranteed to survive to the next generation. # Floating number specifies fraction of population. # +""" + ga( objfun ::Function , + initpopulation ::Vector{<:AbstractGene} , + populationSize ::Int64 ; + lowerBounds ::Union{Nothing, Vector } = nothing , + upperBounds ::Union{Nothing, Vector } = nothing , + crossoverRate ::Float64 = 0.5 , + mutationRate ::Float64 = 0.5 , + ϵ ::Real = 0 , + iterations ::Integer = 100 , + tol ::Real = 0.0 , + tolIter ::Int64 = 10 , + verbose ::Bool = false , + debug ::Bool = false , + interim ::Bool = false , + parallel ::Bool = false ) + +Runs the Genetic Algorithm using the objective function `objfun`, the initial population `initpopulation` and the population size `populationSize`. `objfun` is the function to MINIMIZE. +""" function ga( objfun ::Function , initpopulation ::Vector{<:AbstractGene} , populationSize ::Int64 ; @@ -51,8 +75,6 @@ function ga( objfun ::Function , end fitidx = sortperm(fitness, rev = true) keep(interim, :fitness, copy(fitness), store) - - # Generate and evaluate offspring itr = 1 diff --git a/src/mutations.jl b/src/mutations.jl index 8c14b03..1b31f30 100644 --- a/src/mutations.jl +++ b/src/mutations.jl @@ -1,3 +1,9 @@ +##### mutations.jl ##### + +# In this file you will several functions that correspond to specific types of mutations. +# Has functions for both Evolution Strategies and Genetic Algorithms. + +#################################################################### export mutate @@ -174,7 +180,13 @@ end # SwM - Swap Mutation # ScrM - Scramble Mutation # ShM - Shifting Mutation -function int_mutate(mutatetype ::Symbol) + +""" + mutate(gene ::IntegerGene) + +Mutates `gene` according to the mutation function chosen in the `IntegerGene` structure. +""" +function mutate(mutatetype ::Symbol) mut_func = nothing if mutatetype == :FM mut_func = flip @@ -194,11 +206,21 @@ function int_mutate(mutatetype ::Symbol) return mut_func end +""" + mutate(gene ::BinaryGene) + +Mutates `gene` using the Single Flip Mutation. +""" function mutate(gene ::BinaryGene) gene.value = singleflip(gene.value) return nothing end +""" + mutate(gene ::FloatGene) + +Mutates `gene` using Real Valued Mutation. +""" function mutate(gene ::FloatGene) prob = 1.0 / gene.m δ = zeros(gene.m) @@ -215,9 +237,14 @@ function mutate(gene ::FloatGene) return nothing end +""" + mutate(chromossome ::Vector{<:AbstractGene}) + +Mutates each entry of `chromossome` according to the mutations chosen. +""" function mutate(chromossome ::Vector{<:AbstractGene}) for gene in chromossome mutate(gene) end - return + return nothing end diff --git a/src/recombinations.jl b/src/recombinations.jl index 233daeb..be1688c 100644 --- a/src/recombinations.jl +++ b/src/recombinations.jl @@ -1,6 +1,12 @@ +##### recombinations.jl ##### + +# In this file you will find all the functions regarding recombinations and crossovers. +# Recombinations are used specially for Evolution Strategies, while crossovers are used +# specially for Genetic Algorithms. + +#################################################################### export crossover -export GAVector #################################################################### @@ -55,8 +61,6 @@ end # Genetic algorithms # ================== -const GAVector = Union{T, BitVector} where T <: Vector - # Binary crossovers # ----------------- @@ -308,16 +312,29 @@ end #################################################################### -export singlepoint +""" + crossover(gene1 ::T, gene2 ::T) where {T <: IntegerGene} +Crosses two `IntegerGene` genes according to the crossover function chosen. +""" function crossover(gene1 ::T, gene2 ::T) where {T <: IntegerGene} return crossover(gene1.value, gene2.value) end +""" + crossover(gene1 ::T, gene2 ::T) where {T <: FloatGene} + +Crosses two `FloatGene` genes according to the crossover function chosen. +""" function crossover(gene1 ::T, gene2 ::T) where {T <: FloatGene} return crossover(gene1.value, gene2.value) end +""" + crossover(chromo1 ::T, chromo2 ::T) where {T <: Vector{AbstractGene}} + +`chromo1` and `chromo2` are two vectors of genes. Crosses each entry of both chromossomes according to the crossover function chosen. +""" function crossover(chromo1 ::T, chromo2 ::T) where {T <: Vector{AbstractGene}} c1 = copy(chromo1) c2 = copy(chromo2) diff --git a/src/selections.jl b/src/selections.jl index 2c2ca94..2edcd02 100644 --- a/src/selections.jl +++ b/src/selections.jl @@ -1,3 +1,9 @@ +##### selections.jl ##### + +# In this file you will all the functions regarding population selection. +# All functions can be used for both Evolution Strategies and Genetic Algorithms. + +#################################################################### # GA selections # ============== @@ -96,9 +102,6 @@ function tournament(groupSize ::Int) return tournamentN end -#################################################################### - - #################################################################### # Utils: selection diff --git a/src/structs.jl b/src/structs.jl index e901093..f26fc41 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -1,40 +1,83 @@ +##### structs.jl ##### + +# In this file you will find all the structures needed to generalize code. +# You will find also some functions to help create the global structures. + +#################################################################### export AbstractGene export BinaryGene, IntegerGene, FloatGene -export Crossover, Selection, Chromossome +export Crossover, Selection export selection, crossover, bin #################################################################### +""" +Abstract Type to represent all types of genes supported. +""" abstract type AbstractGene end #################################################################### +""" + BinaryGene(value ::Bool) + +Creates a `BinaryGene` structure. This gene represents a `Bool` variable (1 or 0). Useful to make decisions (will a reactor work or not, will a SMB pathway be used or not, etc.). +""" mutable struct BinaryGene <: AbstractGene value ::Bool BinaryGene(value ::Bool) = new(value) end +""" + BinaryGene() + +Creates a `BinaryGene` structure with a random `Bool` value. +""" BinaryGene() = BinaryGene(rand(Bool)) #################################################################### +""" + IntegerGene(value ::BitVector, mutation ::Symbol) + +Creates a `IntegerGene` structure. This gene represents an integer variable as `BitVector`. To convert the `BitVector` in an integer, just look at the `bin` function from this package by typing `?bin` on the command prompt. `mutation` is a symbol that represents the type of mutation used for the bit vector. The below table shows all the mutation types supported: + +| Symbol | Algorithm | +|-----|-----| +| :FM | Flip Mutation | +| :InvM | Inversion Mutation | +| :InsM | Insertion Mutation | +| :SwM | Swap Mutation | +| :ScrM | Scramble Mutation | +| :ShM | Shifting Mutation | +""" mutable struct IntegerGene <: AbstractGene value ::BitVector mutation ::Symbol function IntegerGene(value ::BitVector, mutation ::Symbol) - int_func = int_mutate(mutation) + int_func = mutate(mutation) @eval mutate(gene ::IntegerGene) = $int_func(gene.value) return new(value, mutation) end end +""" + IntegerGene(value ::BitVector) + +Creates a `IntegerGene` structure with the default mutation being Flip Mutation. +""" function IntegerGene(value ::BitVector) return IntegerGene(value, :FM) end +""" + IntegerGene(n ::Int64) + +Creates a `IntegerGene` structure in which the `BitVector` is of length `n` with random `Bool` values. +""" function IntegerGene(n ::Int64) value = BitVector(undef, n) for i in 1:n @@ -43,6 +86,11 @@ function IntegerGene(n ::Int64) return IntegerGene(value, :FM) end +""" + bin(gene ::IntegerGene) + +Returns the integer number represented by the `BitVector` of `gene`. +""" function bin(gene ::IntegerGene) bin_val = 0 for (i,j) in enumerate(gene.value) @@ -53,6 +101,11 @@ end #################################################################### +""" + FloatGene(value ::Vector{Float64}, range ::Vector{Float64}, m ::Int64) + +Creates a `FloatGene` structure. `value` is a vector with the variables to be changed. `range` is a vector with the minimum and maximum values a variable can take. `m` is just a parameter that changes how much in a mutation the variables change, the bigger the value, the bigger the change in each mutation. If the range of a variable is 0.5, then the biggest mutation a variable can suffer is 0.5 for instance. +""" mutable struct FloatGene <: AbstractGene value ::Vector{Float64} range ::Vector{Float64} @@ -62,30 +115,55 @@ mutable struct FloatGene <: AbstractGene range ::Vector{Float64} , m ::Int64 ) if length(value) != length(range) - error("vectors mush have the same length") + error("vectors must have the same length") end return new(value, range, m) end end +""" + FloatGene(value ::Float64, range ::Float64; m ::Int64 = 20) + +Creates a `FloatGene` structure. Handy for creating just one real number variable. +""" function FloatGene(value ::Float64, range ::Float64; m ::Int64 = 20) return FloatGene(Float64[value], Float64[range], m) end +""" + FloatGene(value ::Vector{Float64}, range ::Float64; m ::Int64 = 20) + +Creates a `FloatGene` structure. Handy for creating a vector of real numbers with the same range. +""" function FloatGene(value ::Vector{Float64}, range ::Float64; m ::Int64 = 20) vec = Float64[range for i in value] return FloatGene(value, vec, m) end +""" + FloatGene(value ::Vector{Float64}; m ::Int64 = 20) + +Creates a `FloatGene` structure. Handy for creating a vector of real numbers with a random range. +""" function FloatGene(value ::Vector{Float64}; m ::Int64 = 20) range = rand(Float64, length(value)) return FloatGene(value, range, m) end +""" + FloatGene(value ::Float64; m ::Int64 = 20) + +Creates a `FloatGene` structure. Handy for creating one variable with a random range. +""" function FloatGene(value ::Float64; m ::Int64 = 20) return FloatGene(value, rand(); m=m) end +""" + FloatGene(n ::Int64) + +Creates a `FloatGene` structure. Creates a vector of length `n` with random variables and random ranges. Used particularly for testing purposes. +""" function FloatGene(n ::Int64) value = rand(Float64, n) range = rand(Float64, n) @@ -106,6 +184,29 @@ end # :O2X - Order 2 Crossover - ox2 # :CX - Cycle Crossover - cx # :PX - Position-based Crossover - pos + +""" + Crossover(cross ::Symbol ; + w ::Union{Nothing, Vector{Float64}} = nothing , + d ::Union{Nothing, Float64 } = nothing ) + +Creates a `Crossover` structure. `cross` is a Symbol that represents the type of crossover that would be used. `w` and `d` are not mandatory but need to be set for some types of crossovers. All algorithms will be shown in the table below: + +| Symbol | Algorithm | Optional Arguments | +|----|----|---| +| :SPX | Single Point Crossover | not needed | +| :TPX | Two Point Crossover | not needed | +| :UX | Uniform Crossover | not needed | +| :DX | Discrete Crossover | not needed | +| :WMX | Weighted Mean Crossover | needs `w` | +| :IRX | Intermediate Recombination Crossover | needs `d` | +| :LRX | Line Recombination Crossover | needs `d` | +| :PMX | Partially Mapped Crossover | not needed | +| :O1X | Order 1 Crossover | not needed | +| :O2X | Order 2 Crossover | not needed | +| :CX | Cycle Crossover | not needed | +| :PX | Position-based Crossover | not needed | +""" mutable struct Crossover cross ::Symbol w ::Union{Nothing, Vector{Float64}} @@ -167,6 +268,24 @@ end # :SUSS - Stochastic Universal Sampling Selection # :TrS - Truncation Selection # :ToS - Tournament Selection + +""" + Selection( select ::Symbol ; + sp ::Union{Nothing, Float64} = nothing , + μ ::Union{Nothing, Int64} = nothing , + groupsize ::Union{Nothing, Int64} = nothing ) + +Creates a `Selection` structure. `select` is a symbol that represents the type of selection that will be used. `sp`, `μ` and `groupsize` are optional but need to be set for some types of selections. All algorithms will be shown in the table below: + +| Symbol | Algorithm | Optional Arguments | +|----|----|----| +| :RBS | Rank-based Selection | needs `sp` | +| :URS | Uniform-Ranking Selection | needs `μ` | +| :RWS | Roulette Wheel Selection | not needed | +| :SUSS | Stochastic Universal Sampling Selection | not needed | +| :TrS | Truncation Selection | not needed | +| :ToS | Tournament Selection | needs `groupsize` | +""" mutable struct Selection select ::Symbol sp ::Union{Nothing, Float64} From ea16587243697ab1179871929a4c39474468401b Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Wed, 4 Mar 2020 16:05:33 +0100 Subject: [PATCH 12/51] Continued improving code Finally fixed the problem regarding the parent population being also updated during crossovers, forgot that, for structures, a `copy` is not enough, a `deepcopy` was needed. --- src/ga.jl | 77 ++++++++++++++++++++++--------------- src/recombinations.jl | 8 ++-- src/structs.jl | 89 +++++++++++++++++++++---------------------- 3 files changed, 95 insertions(+), 79 deletions(-) diff --git a/src/ga.jl b/src/ga.jl index 2a7e38b..124f768 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -42,13 +42,13 @@ export ga Runs the Genetic Algorithm using the objective function `objfun`, the initial population `initpopulation` and the population size `populationSize`. `objfun` is the function to MINIMIZE. """ function ga( objfun ::Function , - initpopulation ::Vector{<:AbstractGene} , + initpopulation ::Vector{Vector{<:AbstractGene}} , populationSize ::Int64 ; lowerBounds ::Union{Nothing, Vector } = nothing , upperBounds ::Union{Nothing, Vector } = nothing , crossoverRate ::Float64 = 0.5 , mutationRate ::Float64 = 0.5 , - ϵ ::Real = 0 , + ϵ ::Bool = false , iterations ::Integer = 100 , tol ::Real = 0.0 , tolIter ::Int64 = 10 , @@ -59,9 +59,8 @@ function ga( objfun ::Function , store = Dict{Symbol,Any}() - # Setup parameters - elite = isa(ϵ, Int) ? ϵ : round(Int, ϵ * populationSize) - fitFunc = inverseFunc(objfun) + fitFunc = objfun + # fitFunc = inverseFunc(objfun) # Initialize population fitness = zeros(populationSize) @@ -69,19 +68,19 @@ function ga( objfun ::Function , offspring = similar(population) for i in 1:populationSize - population[i] = initpopulation + population[i] = initpopulation[i] fitness[i] = fitFunc(population[i]) debug && println("INIT $(i): $(population[i]) : $(fitness[i])") end - fitidx = sortperm(fitness, rev = true) + fitidx = sortperm(fitness) keep(interim, :fitness, copy(fitness), store) # Generate and evaluate offspring - itr = 1 + generations = 1 bestFitness = 0.0 bestIndividual = 0 - fittol = 0.0 - fittolitr = 1 + full_fitness = Vector{Float64}(undef, 2*populationSize) + full_pop = Vector{Vector{<:AbstractGene}}(undef, 2*populationSize) for iter in 1:iterations debug && println("BEST: $(fitidx)") @@ -106,7 +105,7 @@ function ga( objfun ::Function , population[selected[i]], population[selected[j]] end end - + # Perform mutation for i in 1:populationSize if rand() < mutationRate @@ -115,27 +114,31 @@ function ga( objfun ::Function , debug && println("MUTATED >$(i): $(offspring[i])") end end - + + + # Elitism - if elite > 0 - for i in 1:elite - subs = rand(1:populationSize) - debug && - println("ELITE $(fitidx[i])=>$(subs): " * - "$(population[fitidx[i]]) => $(offspring[subs])" ) - offspring[subs] = population[fitidx[i]] + # When true, always picks N best individuals from the full population + # (parents+offspring), which is size 2*N. + # When false, does everything randomly + if ϵ + full_pop[1:populationSize] = population + full_pop[populationSize+1:end] = offspring + full_fitness = fitFunc.(full_pop) + fitidx = sortperm(full_fitness) + for i in 1:populationSize + population[i] = full_pop[fitidx[i]] + fitness[i] = fitFunc(population[i]) + end + else + for i in 1:populationSize + population[i] = offspring[i] + fitness[i] = fitFunc(population[i]) + debug && println("FIT $(i): $(fitness[i])") end end - # New generation - for i in 1:populationSize - population[i] = offspring[i] - fitness[i] = fitFunc(offspring[i]) - debug && println("FIT $(i): $(fitness[i])") - end - fitidx = sortperm(fitness, rev = true) - bestIndividual = fitidx[1] - bestFitness = Float64(objfun(population[bestIndividual])) + bestFitness, bestIndividual = findmin(fitness) keep(interim, :fitness, copy(fitness), store) keep(interim, :bestFitness, bestFitness, store) @@ -145,10 +148,24 @@ function ga( objfun ::Function , println("BEST: $(round(bestFitness, digits=3)): " * "$(population[bestIndividual]), G: $(iter)") - itr = iter + generations = iter if bestFitness <= tol break end end - return population[bestIndividual], bestFitness, itr, fittol, store + + # result presentation + printstyled("\nRESULTS :\n", color=:bold) + println("number of generations = " * string(generations)) + println("best Fitness = " * string(bestFitness)) + println("") + println("genes of best individual :") + for gene in population[bestIndividual] + for (i,j) in enumerate(gene.value) + println("$(gene.name[i]) = $j") + end + end + println("") + + return population[bestIndividual], bestFitness, generations, store end diff --git a/src/recombinations.jl b/src/recombinations.jl index be1688c..a596188 100644 --- a/src/recombinations.jl +++ b/src/recombinations.jl @@ -331,13 +331,13 @@ function crossover(gene1 ::T, gene2 ::T) where {T <: FloatGene} end """ - crossover(chromo1 ::T, chromo2 ::T) where {T <: Vector{AbstractGene}} + crossover(chromo1 ::T, chromo2 ::T) where {T <: Vector{<:AbstractGene}} `chromo1` and `chromo2` are two vectors of genes. Crosses each entry of both chromossomes according to the crossover function chosen. """ -function crossover(chromo1 ::T, chromo2 ::T) where {T <: Vector{AbstractGene}} - c1 = copy(chromo1) - c2 = copy(chromo2) +function crossover(chromo1 ::T, chromo2 ::T) where {T <: Vector{<:AbstractGene}} + c1 = deepcopy(chromo1) + c2 = deepcopy(chromo2) for i in 1:length(chromo1) c1[i].value, c2[i].value = crossover(chromo1[i], chromo2[i]) end diff --git a/src/structs.jl b/src/structs.jl index f26fc41..927e841 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -13,21 +13,24 @@ export selection, crossover, bin #################################################################### """ -Abstract Type to represent all types of genes supported. +Abstract Type that represents all types of genes supported. """ abstract type AbstractGene end #################################################################### """ - BinaryGene(value ::Bool) + BinaryGene(value ::Bool, name ::AbstractString) -Creates a `BinaryGene` structure. This gene represents a `Bool` variable (1 or 0). Useful to make decisions (will a reactor work or not, will a SMB pathway be used or not, etc.). +Creates a `BinaryGene` structure. This gene represents a `Bool` variable (1 or 0). Useful to make decisions (will a reactor work or not, will a SMB pathway be used or not, etc.). `name` is the variable name for presentation purposes. """ mutable struct BinaryGene <: AbstractGene value ::Bool + name ::AbstractString - BinaryGene(value ::Bool) = new(value) + function BinaryGene(value ::Bool, name ::AbstractString) + return new(value, name) + end end """ @@ -35,7 +38,7 @@ end Creates a `BinaryGene` structure with a random `Bool` value. """ -BinaryGene() = BinaryGene(rand(Bool)) +BinaryGene() = BinaryGene(rand(Bool), "bin_var") #################################################################### @@ -56,11 +59,13 @@ Creates a `IntegerGene` structure. This gene represents an integer variable as ` mutable struct IntegerGene <: AbstractGene value ::BitVector mutation ::Symbol + name ::AbstractString - function IntegerGene(value ::BitVector, mutation ::Symbol) + function IntegerGene(value ::BitVector, mutation ::Symbol, + name ::AbstractString) int_func = mutate(mutation) @eval mutate(gene ::IntegerGene) = $int_func(gene.value) - return new(value, mutation) + return new(value, mutation, name) end end @@ -69,8 +74,8 @@ end Creates a `IntegerGene` structure with the default mutation being Flip Mutation. """ -function IntegerGene(value ::BitVector) - return IntegerGene(value, :FM) +function IntegerGene(value ::BitVector, name ::AbstractString) + return IntegerGene(value, :FM, name) end """ @@ -78,12 +83,12 @@ end Creates a `IntegerGene` structure in which the `BitVector` is of length `n` with random `Bool` values. """ -function IntegerGene(n ::Int64) +function IntegerGene(n ::Int64, name ::AbstractString) value = BitVector(undef, n) for i in 1:n value[i] = rand(Bool) end - return IntegerGene(value, :FM) + return IntegerGene(value, :FM, name) end """ @@ -104,20 +109,22 @@ end """ FloatGene(value ::Vector{Float64}, range ::Vector{Float64}, m ::Int64) -Creates a `FloatGene` structure. `value` is a vector with the variables to be changed. `range` is a vector with the minimum and maximum values a variable can take. `m` is just a parameter that changes how much in a mutation the variables change, the bigger the value, the bigger the change in each mutation. If the range of a variable is 0.5, then the biggest mutation a variable can suffer is 0.5 for instance. +Creates a `FloatGene` structure. `value` is a vector with the variables to be changed. `range` is a vector with the minimum and maximum values a variable can take. `m` is just a parameter that changes how much in a mutation the variables change, the bigger the value, the bigger the change in each mutation. If the range of a variable is 0.5, then the biggest mutation a variable can suffer in one iteration is 0.5 for instance. """ mutable struct FloatGene <: AbstractGene value ::Vector{Float64} range ::Vector{Float64} m ::Int64 + name ::Vector{<:AbstractString} - function FloatGene( value ::Vector{Float64} , - range ::Vector{Float64} , - m ::Int64 ) + function FloatGene( value ::Vector{Float64} , + range ::Vector{Float64} , + m ::Int64 , + name ::Vector{<:AbstractString} ) if length(value) != length(range) error("vectors must have the same length") end - return new(value, range, m) + return new(value, range, m, name) end end @@ -126,8 +133,11 @@ end Creates a `FloatGene` structure. Handy for creating just one real number variable. """ -function FloatGene(value ::Float64, range ::Float64; m ::Int64 = 20) - return FloatGene(Float64[value], Float64[range], m) +function FloatGene( value ::Float64 , + range ::Float64 , + name ::AbstractString ; + m ::Int64 = 20 ) + return FloatGene(Float64[value], Float64[range], m, [name]) end """ @@ -135,9 +145,12 @@ end Creates a `FloatGene` structure. Handy for creating a vector of real numbers with the same range. """ -function FloatGene(value ::Vector{Float64}, range ::Float64; m ::Int64 = 20) +function FloatGene( value ::Vector{Float64} , + range ::Float64 , + name ::Vector{<:AbstractString} ; + m ::Int64 = 20 ) vec = Float64[range for i in value] - return FloatGene(value, vec, m) + return FloatGene(value, vec, m, name) end """ @@ -145,9 +158,11 @@ end Creates a `FloatGene` structure. Handy for creating a vector of real numbers with a random range. """ -function FloatGene(value ::Vector{Float64}; m ::Int64 = 20) +function FloatGene( value ::Vector{Float64} , + name ::Vector{<:AbstractString} ; + m ::Int64 = 20 ) range = rand(Float64, length(value)) - return FloatGene(value, range, m) + return FloatGene(value, range, m, name) end """ @@ -164,27 +179,18 @@ end Creates a `FloatGene` structure. Creates a vector of length `n` with random variables and random ranges. Used particularly for testing purposes. """ -function FloatGene(n ::Int64) +function FloatGene(n ::Int64, name ::AbstractString) value = rand(Float64, n) range = rand(Float64, n) - return FloatGene(value, range, 20) + vec_name = Vector{AbstractString}(undef, n) + for i in 1:n + vec_name[i] = string(name, "_", i) + end + return FloatGene(value, range, 20, vec_name) end #################################################################### -# :SPX - Single Point Crossover - singlepoint -# :TPX - Two Point Crossover - twopoint -# :UX - Uniform Crossover - uniform -# :DX - Discrete Crossover - discrete -# :WMX - Weighted Mean Crossover - waverage(w ::Vector{Float64}) -# :IRX - Intermediate Recombination Crossover - intermediate(d ::Float64) -# :LRX - Line Recombination Crossover - line(d ::Float64) -# :PMX - Partially Mapped Crossover - pmx -# :O1X - Order 1 Crossover - ox1 -# :O2X - Order 2 Crossover - ox2 -# :CX - Cycle Crossover - cx -# :PX - Position-based Crossover - pos - """ Crossover(cross ::Symbol ; w ::Union{Nothing, Vector{Float64}} = nothing , @@ -252,7 +258,7 @@ mutable struct Crossover end @eval begin - function crossover(v1::T, v2 ::T) where {T <: AbstractVector} + function crossover(v1 ::T, v2 ::T) where {T <: Vector{<:Real}} return $cross_func(v1, v2) end end @@ -262,13 +268,6 @@ end #################################################################### -# :RBS - Rank-Based Selection -# :URS - Uniform-Ranking Selection -# :RWS - Roulette Wheel Selection -# :SUSS - Stochastic Universal Sampling Selection -# :TrS - Truncation Selection -# :ToS - Tournament Selection - """ Selection( select ::Symbol ; sp ::Union{Nothing, Float64} = nothing , From aa51096da367a4c6ed88d2565fe6cc7a5da2c7e0 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Wed, 4 Mar 2020 16:52:43 +0100 Subject: [PATCH 13/51] Fixed bug regarding type inference in the crossover function created by the Crossover structure --- src/ga.jl | 23 ++++++++++++----------- src/structs.jl | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/ga.jl b/src/ga.jl index 124f768..b0ee036 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -58,9 +58,6 @@ function ga( objfun ::Function , parallel ::Bool = false ) store = Dict{Symbol,Any}() - - fitFunc = objfun - # fitFunc = inverseFunc(objfun) # Initialize population fitness = zeros(populationSize) @@ -69,7 +66,7 @@ function ga( objfun ::Function , for i in 1:populationSize population[i] = initpopulation[i] - fitness[i] = fitFunc(population[i]) + fitness[i] = objfun(population[i]) debug && println("INIT $(i): $(population[i]) : $(fitness[i])") end fitidx = sortperm(fitness) @@ -115,8 +112,6 @@ function ga( objfun ::Function , end end - - # Elitism # When true, always picks N best individuals from the full population # (parents+offspring), which is size 2*N. @@ -124,16 +119,16 @@ function ga( objfun ::Function , if ϵ full_pop[1:populationSize] = population full_pop[populationSize+1:end] = offspring - full_fitness = fitFunc.(full_pop) + full_fitness = objfun.(full_pop) fitidx = sortperm(full_fitness) for i in 1:populationSize population[i] = full_pop[fitidx[i]] - fitness[i] = fitFunc(population[i]) + fitness[i] = objfun(population[i]) end else for i in 1:populationSize population[i] = offspring[i] - fitness[i] = fitFunc(population[i]) + fitness[i] = objfun(population[i]) debug && println("FIT $(i): $(fitness[i])") end end @@ -161,8 +156,14 @@ function ga( objfun ::Function , println("") println("genes of best individual :") for gene in population[bestIndividual] - for (i,j) in enumerate(gene.value) - println("$(gene.name[i]) = $j") + if isa(gene, FloatGene) + for (i,j) in enumerate(gene.value) + println("$(gene.name[i]) = $j") + end + elseif isa(gene, IntegerGene) + println("$(gene.name) = $(bin(gene))") + else + println("$(gene.name) = $(gene.value)") end end println("") diff --git a/src/structs.jl b/src/structs.jl index 927e841..38b26bb 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -258,7 +258,7 @@ mutable struct Crossover end @eval begin - function crossover(v1 ::T, v2 ::T) where {T <: Vector{<:Real}} + function crossover(v1 ::T, v2 ::T) where {T <: AbstractVector} return $cross_func(v1, v2) end end From eb55c7d1cd277287dd02a0357e6b327b2ec08a4b Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Thu, 5 Mar 2020 11:21:27 +0100 Subject: [PATCH 14/51] Created function to present results --- src/ga.jl | 196 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 115 insertions(+), 81 deletions(-) diff --git a/src/ga.jl b/src/ga.jl index b0ee036..9eda712 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -18,10 +18,8 @@ export ga # crossoverRate : The fraction of the population at the next generation, not including elite # children, that is created by the crossover function. # mutationRate : Probability of chromosome to be mutated -# ϵ : Positive integer specifies how many individuals in the current generation -# are guaranteed to survive to the next generation. -# Floating number specifies fraction of population. -# +# ϵ : Boolean to decide if the N best ones will surely survive or +# it's all random """ ga( objfun ::Function , initpopulation ::Vector{<:AbstractGene} , @@ -42,8 +40,7 @@ export ga Runs the Genetic Algorithm using the objective function `objfun`, the initial population `initpopulation` and the population size `populationSize`. `objfun` is the function to MINIMIZE. """ function ga( objfun ::Function , - initpopulation ::Vector{Vector{<:AbstractGene}} , - populationSize ::Int64 ; + population ::Vector{Vector{<:AbstractGene}} ; lowerBounds ::Union{Nothing, Vector } = nothing , upperBounds ::Union{Nothing, Vector } = nothing , crossoverRate ::Float64 = 0.5 , @@ -60,12 +57,11 @@ function ga( objfun ::Function , store = Dict{Symbol,Any}() # Initialize population - fitness = zeros(populationSize) - population = Vector{Vector{<:AbstractGene}}(undef, populationSize) + populationSize = length(population) + fitness = Vector{Float64}(undef, populationSize) offspring = similar(population) for i in 1:populationSize - population[i] = initpopulation[i] fitness[i] = objfun(population[i]) debug && println("INIT $(i): $(population[i]) : $(fitness[i])") end @@ -73,100 +69,138 @@ function ga( objfun ::Function , keep(interim, :fitness, copy(fitness), store) # Generate and evaluate offspring + isfit = false generations = 1 bestFitness = 0.0 bestIndividual = 0 full_fitness = Vector{Float64}(undef, 2*populationSize) full_pop = Vector{Vector{<:AbstractGene}}(undef, 2*populationSize) - for iter in 1:iterations - debug && println("BEST: $(fitidx)") - - # Select offspring - selected = selection(fitness, populationSize) - - # Perform mating - offidx = randperm(populationSize) - for i in 1:2:populationSize - j = (i == populationSize) ? i-1 : i+1 - if rand() < crossoverRate - debug && - println( "MATE $(offidx[i])+$(offidx[j])>: " * - "$(population[selected[offidx[i]]]) : " * - "$(population[selected[offidx[j]]])" ) - offspring[i], offspring[j] = - crossover(population[selected[offidx[i]]], population[selected[offidx[j]]]) - debug && - println("MATE >$(offidx[i])+$(offidx[j]): $(offspring[i]) : $(offspring[j])") - else - offspring[i], offspring[j] = - population[selected[i]], population[selected[j]] - end - end - - # Perform mutation - for i in 1:populationSize - if rand() < mutationRate - debug && println("MUTATED $(i)>: $(offspring[i])") - mutate(offspring[i]) - debug && println("MUTATED >$(i): $(offspring[i])") + + elapsed_time = @elapsed begin + for iter in 1:iterations + debug && println("BEST: $(fitidx)") + + # Select offspring + selected = selection(fitness, populationSize) + + # Perform mating + offidx = randperm(populationSize) + for i in 1:2:populationSize + j = (i == populationSize) ? i-1 : i+1 + if rand() < crossoverRate + debug && + println( "MATE $(offidx[i])+$(offidx[j])>: " * + "$(population[selected[offidx[i]]]) : " * + "$(population[selected[offidx[j]]])" ) + offspring[i], offspring[j] = + crossover(population[selected[offidx[i]]], population[selected[offidx[j]]]) + debug && + println("MATE >$(offidx[i])+$(offidx[j]): $(offspring[i]) : $(offspring[j])") + else + offspring[i], offspring[j] = + population[selected[i]], population[selected[j]] + end end - end - - # Elitism - # When true, always picks N best individuals from the full population - # (parents+offspring), which is size 2*N. - # When false, does everything randomly - if ϵ - full_pop[1:populationSize] = population - full_pop[populationSize+1:end] = offspring - full_fitness = objfun.(full_pop) - fitidx = sortperm(full_fitness) + + # Perform mutation for i in 1:populationSize - population[i] = full_pop[fitidx[i]] - fitness[i] = objfun(population[i]) + if rand() < mutationRate + debug && println("MUTATED $(i)>: $(offspring[i])") + mutate(offspring[i]) + debug && println("MUTATED >$(i): $(offspring[i])") + end end - else - for i in 1:populationSize - population[i] = offspring[i] - fitness[i] = objfun(population[i]) - debug && println("FIT $(i): $(fitness[i])") + + # Elitism + # When true, always picks N best individuals from the full population + # (parents+offspring), which is size 2*N. + # When false, does everything randomly + if ϵ + full_pop[1:populationSize] = population + full_pop[populationSize+1:end] = offspring + full_fitness = objfun.(full_pop) + fitidx = sortperm(full_fitness) + for i in 1:populationSize + population[i] = full_pop[fitidx[i]] + fitness[i] = objfun(population[i]) + end + else + for i in 1:populationSize + population[i] = offspring[i] + fitness[i] = objfun(population[i]) + debug && println("FIT $(i): $(fitness[i])") + end end - end - bestFitness, bestIndividual = findmin(fitness) + bestFitness, bestIndividual = findmin(fitness) - keep(interim, :fitness, copy(fitness), store) - keep(interim, :bestFitness, bestFitness, store) + keep(interim, :fitness, copy(fitness), store) + keep(interim, :bestFitness, bestFitness, store) - # Verbose step - verbose && - println("BEST: $(round(bestFitness, digits=3)): " * - "$(population[bestIndividual]), G: $(iter)") + # Verbose step + verbose && + println("BEST: $(round(bestFitness, digits=3)): " * + "$(population[bestIndividual]), G: $(iter)") - generations = iter - if bestFitness <= tol - break + generations = iter + if bestFitness <= tol + isfit = true + break + end end end - # result presentation - printstyled("\nRESULTS :\n", color=:bold) - println("number of generations = " * string(generations)) - println("best Fitness = " * string(bestFitness)) - println("") - println("genes of best individual :") - for gene in population[bestIndividual] + data_presentation(population[bestIndividual], generations, + bestFitness, isfit, elapsed_time) + + return population[bestIndividual], bestFitness, generations, store +end + +function data_presentation( individual ::Vector{<:AbstractGene} , + generations ::Integer , + bestFitness ::Float64 , + isfit ::Bool , + elapsed_time ::Float64 ) + + optim_time = round(elapsed_time, digits=3) + + params = Vector{AbstractString}(undef, 0) + values = Vector{Real}(undef, 0) + for gene in individual if isa(gene, FloatGene) for (i,j) in enumerate(gene.value) - println("$(gene.name[i]) = $j") + push!(params, gene.name[i]) + push!(values, j) end elseif isa(gene, IntegerGene) - println("$(gene.name) = $(bin(gene))") + push!(params, gene.name) + push!(values, bin(gene)) else - println("$(gene.name) = $(gene.value)") + push!(params, gene.name) + push!(values, gene.value) end end - println("") - return population[bestIndividual], bestFitness, generations, store + table = string("| parameter | value |\n" , + "|-----------|-------|\n" ) + for (i,j) in enumerate(params) + table *= "| $j | $(values[i]) |\n" + end + @doc table present + + printstyled("\nRESULTS :\n", color=:bold) + println("number of generations = " * string(generations)) + println("best Fitness = " * string(bestFitness)) + println("Run time = $optim_time seconds") + println("") + printstyled("GENES OF BEST INDIVIDUAL :\n", color=:bold) + display(@doc present) + println("") + if isfit + printstyled("OPTIMIZATION SUCCESSFUL\n" , color=:bold) + else + printstyled("OPTIMIZATION UNSUCCESSFUL\n", color=:bold) + end + + return nothing end From 0e01bcd359cb41667de8feee697515b03a50f86c Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Sun, 8 Mar 2020 11:11:48 +0100 Subject: [PATCH 15/51] Continued improving code Finally finished the first prototype for parallelizing the Genetic Algorithm using DistributedArrays package. It works quite well, but now it needs to be modified to be able to use piping for communication with external programs. --- Project.toml | 2 + src/Evolutionary.jl | 27 ++-- src/ga.jl | 279 ++++++++++++++++++++++++++++-------------- src/mutations.jl | 2 +- src/recombinations.jl | 2 +- src/structs.jl | 8 -- 6 files changed, 203 insertions(+), 117 deletions(-) diff --git a/Project.toml b/Project.toml index 574dd32..6f175f7 100644 --- a/Project.toml +++ b/Project.toml @@ -3,6 +3,8 @@ uuid = "86b6b26d-c046-49b6-aa0b-5f0f74682bd6" version = "0.2.0" [deps] +Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" +DistributedArrays = "aaf54ef3-cdf8-58ed-94cc-d582ad619b94" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" diff --git a/src/Evolutionary.jl b/src/Evolutionary.jl index 8508c04..7188648 100644 --- a/src/Evolutionary.jl +++ b/src/Evolutionary.jl @@ -2,28 +2,25 @@ module Evolutionary using Random, Base.Threads +using Distributed +using DistributedArrays, DistributedArrays.SPMD -export Strategy, strategy, inverse, mutationwrapper, - # ES mutations - isotropic, anisotropic, isotropicSigma, anisotropicSigma, - # GA mutations - flip, domainrange, inversion, insertion, swap2, scramble, shifting, - # ES recombinations - average, marriage, averageSigma1, averageSigmaN, - # GA recombinations - singlepoint, twopoint, uniform, - discrete, waverage, intermediate, line, - pmx, ox1, cx, ox2, pos, - # GA selections - ranklinear, uniformranking, roulette, sus, tournament, truncation, +export # Optimization methods es, cmaes, ga, # Constants - GAVector + GAVector, Individual, AbstractGene + +#################################################################### + +""" +Abstract Type that represents all types of genes supported. +""" +abstract type AbstractGene end const Strategy = Dict{Symbol,Any} -const Individual = Nothing +const Individual = Vector{<:AbstractGene} const GAVector = Union{T, BitVector} where T <: Vector diff --git a/src/ga.jl b/src/ga.jl index 9eda712..680f8a5 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -39,128 +39,222 @@ export ga Runs the Genetic Algorithm using the objective function `objfun`, the initial population `initpopulation` and the population size `populationSize`. `objfun` is the function to MINIMIZE. """ -function ga( objfun ::Function , - population ::Vector{Vector{<:AbstractGene}} ; - lowerBounds ::Union{Nothing, Vector } = nothing , - upperBounds ::Union{Nothing, Vector } = nothing , - crossoverRate ::Float64 = 0.5 , - mutationRate ::Float64 = 0.5 , - ϵ ::Bool = false , - iterations ::Integer = 100 , - tol ::Real = 0.0 , - tolIter ::Int64 = 10 , - verbose ::Bool = false , - debug ::Bool = false , - interim ::Bool = false , - parallel ::Bool = false ) - - store = Dict{Symbol,Any}() +function ga( objfun ::Function , + population ::Vector{Individual} ; + crossoverRate ::Float64 = 0.5 , + mutationRate ::Float64 = 0.5 , + ϵ ::Bool = true , + iterations ::Integer = 100 , + tol ::Real = 0.0 , + parallel ::Bool = false ) # Initialize population - populationSize = length(population) - fitness = Vector{Float64}(undef, populationSize) - offspring = similar(population) + N = length(population) + fitness = Vector{Float64}(undef, N) - for i in 1:populationSize - fitness[i] = objfun(population[i]) - debug && println("INIT $(i): $(population[i]) : $(fitness[i])") + for i in 1:N + @inbounds fitness[i] = objfun(population[i]) end fitidx = sortperm(fitness) - keep(interim, :fitness, copy(fitness), store) + + # save optional arguments in a dictionary + # to pass to generation function + pars = Dict{Symbol, Any}() + pars[:crossoverRate] = crossoverRate + pars[:mutationRate ] = mutationRate + pars[:ϵ ] = ϵ + pars[:iterations ] = iterations + pars[:tol ] = tol # Generate and evaluate offspring - isfit = false - generations = 1 - bestFitness = 0.0 - bestIndividual = 0 - full_fitness = Vector{Float64}(undef, 2*populationSize) - full_pop = Vector{Vector{<:AbstractGene}}(undef, 2*populationSize) + if parallel + population = distribute(population) + fitness = distribute(fitness) + elapsed_time = @elapsed begin + spmd(generations_parallel, objfun, + population, fitness, pars) + end + else + elapsed_time = @elapsed begin + generations(objfun, population, N, fitness, pars) + end + end + + bestFitness, bestIndividual = findmin(fitness) + if bestFitness <= tol + isfit = true + else + isfit = false + end + + # result presentation + data_presentation( population[bestIndividual], iterations, + bestFitness, isfit, elapsed_time ) + + return population[bestIndividual], bestFitness +end + +#################################################################### + +# DArray{Individual,1,Vector{Individual}} +function generations( objfun ::Function , + population ::Vector{Individual} , + N ::Integer , + fitness ::Vector{Float64} , + pars ::Dict{Symbol,Any} ) - elapsed_time = @elapsed begin - for iter in 1:iterations - debug && println("BEST: $(fitidx)") - - # Select offspring - selected = selection(fitness, populationSize) - - # Perform mating - offidx = randperm(populationSize) - for i in 1:2:populationSize - j = (i == populationSize) ? i-1 : i+1 - if rand() < crossoverRate - debug && - println( "MATE $(offidx[i])+$(offidx[j])>: " * - "$(population[selected[offidx[i]]]) : " * - "$(population[selected[offidx[j]]])" ) + # Variable initialization + generations = 1 + bestIndividual = 0 + bestFitness = 0.0 + offspring = Vector{Individual}(undef, N) + full_pop = Vector{Individual}(undef, 2*N) + full_fitness = Vector{Float64 }(undef, 2*N) + + for iter in 1:pars[:iterations] + + # Select offspring + selected = selection(fitness, N) + + # Perform mating + offidx = randperm(N) + for i in 1:2:N + j = (i == N) ? i-1 : i+1 + if rand() < pars[:crossoverRate] + @inbounds begin offspring[i], offspring[j] = - crossover(population[selected[offidx[i]]], population[selected[offidx[j]]]) - debug && - println("MATE >$(offidx[i])+$(offidx[j]): $(offspring[i]) : $(offspring[j])") - else + crossover( population[selected[offidx[i]]] , + population[selected[offidx[j]]] ) + end + else + @inbounds begin offspring[i], offspring[j] = population[selected[i]], population[selected[j]] end end - - # Perform mutation - for i in 1:populationSize - if rand() < mutationRate - debug && println("MUTATED $(i)>: $(offspring[i])") - mutate(offspring[i]) - debug && println("MUTATED >$(i): $(offspring[i])") - end + end + + # Perform mutation + for i in 1:N + if rand() < pars[:mutationRate] + @inbounds mutate(offspring[i]) end - - # Elitism - # When true, always picks N best individuals from the full population - # (parents+offspring), which is size 2*N. - # When false, does everything randomly - if ϵ - full_pop[1:populationSize] = population - full_pop[populationSize+1:end] = offspring + end + + # Elitism + # When true, always picks N best individuals from the full population + # (parents+offspring), which is size 2*N. + # When false, does everything randomly + if pars[:ϵ] + @inbounds begin + full_pop[ 1: N] = population + full_pop[N+1:2*N] = offspring full_fitness = objfun.(full_pop) fitidx = sortperm(full_fitness) - for i in 1:populationSize + end + for i in 1:N + @inbounds begin population[i] = full_pop[fitidx[i]] - fitness[i] = objfun(population[i]) + fitness[i] = objfun(population[i]) end - else - for i in 1:populationSize + end + else + for i in 1:N + @inbounds begin population[i] = offspring[i] - fitness[i] = objfun(population[i]) - debug && println("FIT $(i): $(fitness[i])") + fitness[i] = objfun(population[i]) end end + end + end - bestFitness, bestIndividual = findmin(fitness) + return nothing +end + +#################################################################### - keep(interim, :fitness, copy(fitness), store) - keep(interim, :bestFitness, bestFitness, store) +function generations_parallel( objfun ::Function , + popul ::DArray{Individual,1,Vector{Individual}} , + fit ::DArray{Float64,1,Vector{Float64}} , + pars ::Dict{Symbol,Any} ) - # Verbose step - verbose && - println("BEST: $(round(bestFitness, digits=3)): " * - "$(population[bestIndividual]), G: $(iter)") + # Variable initialization + population = popul[:L] + fitness = fit[:L] + N = length(population) + generations = 1 + bestIndividual = 0 + bestFitness = 0.0 + offspring = Vector{Individual}(undef, N) + full_pop = Vector{Individual}(undef, 2*N) + full_fitness = Vector{Float64 }(undef, 2*N) - generations = iter - if bestFitness <= tol - isfit = true - break + # Generate and evaluate offspring + for iter in 1:pars[:iterations] + + # Select offspring + selected = selection(fitness, N) + + # Perform mating + offidx = randperm(N) + for i in 1:2:N + j = (i == N) ? i-1 : i+1 + if rand() < pars[:crossoverRate] + @inbounds begin + offspring[i], offspring[j] = + crossover( population[selected[offidx[i]]] , + population[selected[offidx[j]]] ) + end + else + @inbounds begin + offspring[i], offspring[j] = + population[selected[i]], population[selected[j]] + end + end + end + + # Perform mutation + for i in 1:N + if rand() < pars[:mutationRate] + @inbounds mutate(offspring[i]) + end + end + + # Elitism + # When true, always picks N best individuals from the full population + # (parents+offspring), which is size 2*N. + # When false, does everything randomly + if pars[:ϵ] + @inbounds begin + full_pop[1 : N] = population + full_pop[1+N:2*N] = offspring + full_fitness = objfun.(full_pop) + fitidx = sortperm(full_fitness) + for i in 1:N + population[i] = full_pop[fitidx[i]] + fitness[i] = objfun(population[i]) + end + end + else + @inbounds begin + for i in 1:N + population[i] = offspring[i] + fitness[i] = objfun(population[i]) + end end end end - # result presentation - data_presentation(population[bestIndividual], generations, - bestFitness, isfit, elapsed_time) - - return population[bestIndividual], bestFitness, generations, store + + return nothing end -function data_presentation( individual ::Vector{<:AbstractGene} , - generations ::Integer , - bestFitness ::Float64 , - isfit ::Bool , - elapsed_time ::Float64 ) +#################################################################### + +function data_presentation( individual ::Individual , + generations ::Integer , + bestFitness ::Float64 , + isfit ::Bool , + elapsed_time ::Float64 ) optim_time = round(elapsed_time, digits=3) @@ -204,3 +298,4 @@ function data_presentation( individual ::Vector{<:AbstractGene} , return nothing end + diff --git a/src/mutations.jl b/src/mutations.jl index 1b31f30..5f8314c 100644 --- a/src/mutations.jl +++ b/src/mutations.jl @@ -242,7 +242,7 @@ end Mutates each entry of `chromossome` according to the mutations chosen. """ -function mutate(chromossome ::Vector{<:AbstractGene}) +function mutate(chromossome ::Individual) for gene in chromossome mutate(gene) end diff --git a/src/recombinations.jl b/src/recombinations.jl index a596188..45bc3ab 100644 --- a/src/recombinations.jl +++ b/src/recombinations.jl @@ -335,7 +335,7 @@ end `chromo1` and `chromo2` are two vectors of genes. Crosses each entry of both chromossomes according to the crossover function chosen. """ -function crossover(chromo1 ::T, chromo2 ::T) where {T <: Vector{<:AbstractGene}} +function crossover(chromo1 ::T, chromo2 ::T) where {T <: Individual} c1 = deepcopy(chromo1) c2 = deepcopy(chromo2) for i in 1:length(chromo1) diff --git a/src/structs.jl b/src/structs.jl index 38b26bb..f9d6d3e 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -5,20 +5,12 @@ #################################################################### -export AbstractGene export BinaryGene, IntegerGene, FloatGene export Crossover, Selection export selection, crossover, bin #################################################################### -""" -Abstract Type that represents all types of genes supported. -""" -abstract type AbstractGene end - -#################################################################### - """ BinaryGene(value ::Bool, name ::AbstractString) From 67ae40447853ecca88641c9dbf5192852f7d0fb3 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Sun, 8 Mar 2020 11:21:17 +0100 Subject: [PATCH 16/51] minor aesthetic changes --- src/ga.jl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/ga.jl b/src/ga.jl index 680f8a5..ae1193a 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -96,7 +96,6 @@ end #################################################################### -# DArray{Individual,1,Vector{Individual}} function generations( objfun ::Function , population ::Vector{Individual} , N ::Integer , @@ -230,16 +229,18 @@ function generations_parallel( objfun ::Function full_pop[1+N:2*N] = offspring full_fitness = objfun.(full_pop) fitidx = sortperm(full_fitness) - for i in 1:N + end + for i in 1:N + @inbounds begin population[i] = full_pop[fitidx[i]] - fitness[i] = objfun(population[i]) + fitness[i] = objfun(population[i]) end end else - @inbounds begin - for i in 1:N + for i in 1:N + @inbounds begin population[i] = offspring[i] - fitness[i] = objfun(population[i]) + fitness[i] = objfun(population[i]) end end end From 0a6bb64bdd30deb684dee7e6c9a98846b647c8d2 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Sun, 8 Mar 2020 11:47:51 +0100 Subject: [PATCH 17/51] added inbounds in most of the low-level functions --- src/mutations.jl | 26 ++++++++------- src/recombinations.jl | 76 ++++++++++++++++++++++++------------------- src/selections.jl | 6 ++-- 3 files changed, 59 insertions(+), 49 deletions(-) diff --git a/src/mutations.jl b/src/mutations.jl index 5f8314c..26a4c56 100644 --- a/src/mutations.jl +++ b/src/mutations.jl @@ -57,7 +57,7 @@ end function flip(recombinant ::BitVector) s = length(recombinant) pos = rand(1:s) - recombinant[pos] = !recombinant[pos] + @inbounds recombinant[pos] = !recombinant[pos] return recombinant end @@ -79,12 +79,12 @@ function domainrange(valrange:: Vector{Float64}, m ::Int = 20) δ = zeros(m) for i in 1:length(recombinant) for j in 1:m - δ[j] = (rand() < prob) ? 2.0^(-j) : 0.0 + @inbounds δ[j] = (rand() < prob) ? 2.0^(-j) : 0.0 end if rand() > 0.5 - recombinant[i] += sum(δ)*valrange[i] + @inbounds recombinant[i] += sum(δ)*valrange[i] else - recombinant[i] -= sum(δ)*valrange[i] + @inbounds recombinant[i] -= sum(δ)*valrange[i] end end return recombinant @@ -126,11 +126,11 @@ function scramble(recombinant ::BitVector) from, to = from > to ? (to, from) : (from, to) diff = to - from + 1 if diff > 1 - patch = recombinant[from:to] + @inbounds patch = recombinant[from:to] idx = randperm(diff) # println("$(from)-$(to), P: $(patch), I: $(idx)") for i in 1:(diff) - recombinant[from+i-1] = patch[idx[i]] + @inbounds recombinant[from+i-1] = patch[idx[i]] end end return recombinant @@ -139,18 +139,18 @@ end function shifting(recombinant ::BitVector) l = length(recombinant) from, to, where = sort(rand(1:l, 3)) - patch = recombinant[from:to] + @inbounds patch = recombinant[from:to] diff = where - to if diff > 0 # move values after tail of patch to the patch head position #println([from, to, where, diff]) for i in 1:diff - recombinant[from+i-1] = recombinant[to+i] + @inbounds recombinant[from+i-1] = recombinant[to+i] end # place patch values in order start = from + diff for i in 1:length(patch) - recombinant[start+i-1] = patch[i] + @inbounds recombinant[start+i-1] = patch[i] end end return recombinant @@ -159,9 +159,11 @@ end # Utils # ===== function swap!(v ::T, from ::Int, to ::Int) where {T <: Vector} - val = v[from] - v[from] = v[to] - v[to] = val + @inbounds begin + val = v[from] + v[from] = v[to] + v[to] = val + end end function mutationwrapper(gamutation ::Function) diff --git a/src/recombinations.jl b/src/recombinations.jl index 45bc3ab..48014fa 100644 --- a/src/recombinations.jl +++ b/src/recombinations.jl @@ -113,8 +113,8 @@ function discrete(v1 ::T, v2 ::T) where {T <: AbstractVector} c1 = similar(v1) c2 = similar(v2) for i in 1:l - c1[i] = rand(Bool) ? v1[i] : v2[i] - c2[i] = rand(Bool) ? v2[i] : v1[i] + @inbounds c1[i] = rand(Bool) ? v1[i] : v2[i] + @inbounds c2[i] = rand(Bool) ? v2[i] : v1[i] end return c1, c2 end @@ -166,34 +166,34 @@ function pmx(v1 ::T, v2 ::T) where {T <: AbstractVector} c2 = similar(v2) # Swap - c1[from:to] = v2[from:to] - c2[from:to] = v1[from:to] + @inbounds c1[from:to] = v2[from:to] + @inbounds c2[from:to] = v1[from:to] # Fill in from parents for i in vcat(1:from-1, to+1:s) # Check conflicting offspring in1 = inmap(v1[i], c1, from, to) if in1 == 0 - c1[i] = v1[i] + @inbounds c1[i] = v1[i] else tmpin = in1 while tmpin > 0 - tmpin = inmap(c2[in1], c1, from, to) + @inbounds tmpin = inmap(c2[in1], c1, from, to) in1 = tmpin > 0 ? tmpin : in1 end - c1[i] = v1[in1] + @inbounds c1[i] = v1[in1] end in2 = inmap(v2[i], c2, from, to) if in2 == 0 - c2[i] = v2[i] + @inbounds c2[i] = v2[i] else tmpin = in2 while tmpin > 0 - tmpin = inmap(c1[in2], c2, from, to) + @inbounds tmpin = inmap(c1[in2], c2, from, to) in2 = tmpin > 0 ? tmpin : in2 end - c2[i] = v2[in2] + @inbounds c2[i] = v2[in2] end end return c1, c2 @@ -207,8 +207,10 @@ function ox1(v1 ::T, v2 ::T) where {T <: AbstractVector} c1 = zeros(v1) c2 = zeros(v2) # Swap - c1[from:to] = v2[from:to] - c2[from:to] = v1[from:to] + @inbounds begin + c1[from:to] = v2[from:to] + c2[from:to] = v1[from:to] + end # Fill in from parents k = to+1 > s ? 1 : to+1 #child1 index j = to+1 > s ? 1 : to+1 #child2 index @@ -216,11 +218,11 @@ function ox1(v1 ::T, v2 ::T) where {T <: AbstractVector} while in(v1[k],c1) k = k+1 > s ? 1 : k+1 end - c1[i] = v1[k] + @inbounds c1[i] = v1[k] while in(v2[j],c2) j = j+1 > s ? 1 : j+1 end - c2[i] = v2[j] + @inbounds c2[i] = v2[j] end return c1, c2 end @@ -238,16 +240,16 @@ function cx(v1 ::T, v2 ::T) where {T <: AbstractVector} if f1 #cycle from v1 while c1[idx] == zero(T) - c1[idx] = v1[idx] - c2[idx] = v2[idx] - idx = inmap(v2[idx],v1,1,s) + @inbounds c1[idx] = v1[idx] + @inbounds c2[idx] = v2[idx] + idx = inmap(v2[idx],v1,1,s) end else #cycle from v2 while c2[idx] == zero(T) - c1[idx] = v2[idx] - c2[idx] = v1[idx] - idx = inmap(v1[idx],v2,1,s) + @inbounds c1[idx] = v2[idx] + @inbounds c2[idx] = v1[idx] + idx = inmap(v1[idx],v2,1,s) end end f1 $= true @@ -264,21 +266,23 @@ function ox2(v1 ::T, v2 ::T) where {T <: AbstractVector} for i in 1:s if rand(Bool) - idx1 = inmap(v2[i],v1,1,s) - idx2 = inmap(v1[i],v2,1,s) - c1[idx1] = zero(T) - c2[idx2] = zero(T) + @inbounds begin + idx1 = inmap(v2[i],v1,1,s) + idx2 = inmap(v1[i],v2,1,s) + c1[idx1] = zero(T) + c2[idx2] = zero(T) + end end end for i in 1:s if !in(v2[i],c1) tmpin = inmap(zero(T),c1,1,s) - c1[tmpin] = v2[i] + @inbounds c1[tmpin] = v2[i] end if !in(v1[i],c2) tmpin = inmap(zero(T),c2,1,s) - c2[tmpin] = v1[i] + @inbounds c2[tmpin] = v1[i] end end return c1,c2 @@ -292,19 +296,21 @@ function pos(v1 ::T, v2 ::T) where {T <: AbstractVector} for i in 1:s if rand(Bool) - c1[i] = v2[i] - c2[i] = v1[i] + @inbounds begin + c1[i] = v2[i] + c2[i] = v1[i] + end end end for i in 1:s if !in(v1[i],c1) tmpin = inmap(zero(T),c1,1,s) - c1[tmpin] = v1[i] + @inbounds c1[tmpin] = v1[i] end if !in(v2[i],c2) tmpin = inmap(zero(T),c2,1,s) - c2[tmpin] = v2[i] + @inbounds c2[tmpin] = v2[i] end end return c1, c2 @@ -339,7 +345,7 @@ function crossover(chromo1 ::T, chromo2 ::T) where {T <: Individual} c1 = deepcopy(chromo1) c2 = deepcopy(chromo2) for i in 1:length(chromo1) - c1[i].value, c2[i].value = crossover(chromo1[i], chromo2[i]) + @inbounds c1[i].value, c2[i].value = crossover(chromo1[i], chromo2[i]) end return c1, c2 end @@ -349,9 +355,11 @@ end # Utils # ===== function vswap!(v1 ::T, v2 ::T, idx ::Int) where {T <: AbstractVector} - val = v1[idx] - v1[idx] = v2[idx] - v2[idx] = val + @inbounds begin + val = v1[idx] + v1[idx] = v2[idx] + v2[idx] = val + end end function inmap(v ::T, c ::Vector{T}, from ::Int, to ::Int) where {T} diff --git a/src/selections.jl b/src/selections.jl index 2edcd02..c6b28f8 100644 --- a/src/selections.jl +++ b/src/selections.jl @@ -17,7 +17,7 @@ function ranklinear(sp ::Float64) idx = sortperm(fitness) ranks = zeros(λ) for i in 1:λ - ranks[i] = ( 2 - sp + 2*(sp-1)*(idx[i]-1) / (λ-1) ) / λ + @inbounds ranks[i] = ( 2 - sp + 2*(sp-1)*(idx[i]-1) / (λ-1) ) / λ end return pselection(ranks, N) end @@ -32,7 +32,7 @@ function uniformranking(μ ::Int) @assert μ < λ "μ should be less then $(λ)" ranks = similar(fitness, Float64) for i in 1:μ - ranks[idx[i]] = 1/μ + @inbounds ranks[idx[i]] = 1/μ end return pselection(ranks, N) end @@ -91,7 +91,7 @@ function tournament(groupSize ::Int) c = contender[idx] if winnerFitness < fitness[c] winner = c - winnerFitness = fitness[c] + @inbounds winnerFitness = fitness[c] end end From 61cc3a33807262b24982bb7e5e5a6254819a1a04 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Sun, 8 Mar 2020 12:08:33 +0100 Subject: [PATCH 18/51] minor aesthetic changes --- src/ga.jl | 4 ++-- src/structs.jl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ga.jl b/src/ga.jl index ae1193a..d3a1279 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -276,8 +276,8 @@ function data_presentation( individual ::Individual , end end - table = string("| parameter | value |\n" , - "|-----------|-------|\n" ) + table = string( "| parameter | value |\n" , + "|-----------|-------|\n" ) for (i,j) in enumerate(params) table *= "| $j | $(values[i]) |\n" end diff --git a/src/structs.jl b/src/structs.jl index f9d6d3e..cee433f 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -78,7 +78,7 @@ Creates a `IntegerGene` structure in which the `BitVector` is of length `n` with function IntegerGene(n ::Int64, name ::AbstractString) value = BitVector(undef, n) for i in 1:n - value[i] = rand(Bool) + @inbounds value[i] = rand(Bool) end return IntegerGene(value, :FM, name) end From 678b49859bcc97ef30a5e977851004fd2c222d9a Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Mon, 9 Mar 2020 16:19:24 +0100 Subject: [PATCH 19/51] started creating ways to communicate with external programs --- src/ga.jl | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ga.jl b/src/ga.jl index d3a1279..3ff2dc3 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -46,7 +46,8 @@ function ga( objfun ::Function , ϵ ::Bool = true , iterations ::Integer = 100 , tol ::Real = 0.0 , - parallel ::Bool = false ) + parallel ::Bool = false , + piping ::Bool = false ) # Initialize population N = length(population) @@ -65,6 +66,12 @@ function ga( objfun ::Function , pars[:ϵ ] = ϵ pars[:iterations ] = iterations pars[:tol ] = tol + + # If the objective function uses an external program, use piping + if piping + else + end + # Generate and evaluate offspring if parallel From d20633d0faf2e1a9a23fa28d4e80e624f9b9603a Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Tue, 10 Mar 2020 14:35:36 +0100 Subject: [PATCH 20/51] added first prototype for communication with external programs --- src/structs.jl | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/structs.jl b/src/structs.jl index cee433f..2b9aaa5 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -8,6 +8,7 @@ export BinaryGene, IntegerGene, FloatGene export Crossover, Selection export selection, crossover, bin +export GAExternal #################################################################### @@ -321,3 +322,73 @@ mutable struct Selection return new(select, sp, μ, groupsize) end end + +#################################################################### + +""" + function GAExternal( program ::AbstractString , + pipename ::AbstractString ; + parallel ::Bool = false ) + +Creates communication pipes for the external program `program`. If `parallel` is `true`, then, considering N workers available, N pipes for reading and N pipes for writing will be created. `pipename` is just a handle for the name of the pipes. If `pipename` is `pipe`, then the pipe names will be `pipe_in` and `pipe_out` for `parallel` as false and `pipe_inn` and `pipe_outn` for `parallel` as true, such as `n` being one of the N workers. +""" +mutable struct GAExternal + program ::AbstractString + pipes_in ::DArray{String,1,Vector{String}} + pipes_out ::DArray{String,1,Vector{String}} + parallel ::Bool + + function GAExternal( program ::AbstractString , + pipename ::AbstractString ; + parallel ::Bool = false ) + pipes = Dict{String,Vector{String}}() + pipes["in" ] = Vector{String}(undef, 0) + pipes["out"] = Vector{String}(undef, 0) + if parallel + # create one pipe for reading and another for writing + # for each worker + for i in ["in","out"] + for p in workers() + f = string(pipename, "_", i, p) + push!(pipes[i], f) + rm(f, force=true) + run(`mkfifo $f`) + + end + end + else + # create one pipe for reading and another for writing + for i in ["in","out"] + f = string(pipename, "_", i) + push!(pipes[i], f) + rm(f, force=true) + run(`mkfifo $f`) + end + end + + pin = distribute(pipes["in" ]) + pout = distribute(pipes["out"]) + + # activate writing pipes to a big amount of time + # reading pipes do not need, the program needs to + # write explicitly to the pipe internally, + # otherwise the processes block + for p in pipes["in"] + @spawnat :any run(pipeline(`sleep 100000000`, p)) + @spawnat :any run(pipeline(`$program`; stdin=p)) + end + + # remove all pipes when exiting julia + function external_atexit() + for k in keys(pipes) + for p in pipes[k] + rm(p, force=true) + end + end + return nothing + end + atexit(external_atexit) + + return new(program, pin, pout, parallel) + end +end From 641462732e4f9d9acf76af613fe1365911c1e140 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Tue, 10 Mar 2020 17:46:07 +0100 Subject: [PATCH 21/51] introduced functionality for external programs in the ga function --- src/ga.jl | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/ga.jl b/src/ga.jl index 3ff2dc3..ca9dd8b 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -39,22 +39,28 @@ export ga Runs the Genetic Algorithm using the objective function `objfun`, the initial population `initpopulation` and the population size `populationSize`. `objfun` is the function to MINIMIZE. """ -function ga( objfun ::Function , - population ::Vector{Individual} ; - crossoverRate ::Float64 = 0.5 , - mutationRate ::Float64 = 0.5 , - ϵ ::Bool = true , - iterations ::Integer = 100 , - tol ::Real = 0.0 , - parallel ::Bool = false , - piping ::Bool = false ) +function ga( objfun ::Function , + population ::Vector{Individual} ; + crossoverRate ::Float64 = 0.5 , + mutationRate ::Float64 = 0.5 , + ϵ ::Bool = true , + iterations ::Integer = 100 , + tol ::Real = 0.0 , + parallel ::Bool = false , + piping ::Union{Nothing,GAExternal} = nothing ) # Initialize population N = length(population) fitness = Vector{Float64}(undef, N) + if piping == nothing + objfunc = objfun + else + objfunc(x ::Vector{Individual}) = objfun(x, piping) + end + for i in 1:N - @inbounds fitness[i] = objfun(population[i]) + @inbounds fitness[i] = objfunc(population[i]) end fitidx = sortperm(fitness) @@ -67,23 +73,17 @@ function ga( objfun ::Function , pars[:iterations ] = iterations pars[:tol ] = tol - # If the objective function uses an external program, use piping - if piping - else - end - - # Generate and evaluate offspring if parallel population = distribute(population) fitness = distribute(fitness) elapsed_time = @elapsed begin - spmd(generations_parallel, objfun, + spmd(generations_parallel, objfunc, population, fitness, pars) end else elapsed_time = @elapsed begin - generations(objfun, population, N, fitness, pars) + generations(objfunc, population, N, fitness, pars) end end From d96186ef7ba714e01a99c17bb1983c8c155c3648 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Fri, 13 Mar 2020 14:46:44 +0100 Subject: [PATCH 22/51] Fixed piping communication Now communication through FIFOs works using parallel computation. For non-parallel computation it does not work because the pipes must be launched in separate processes. --- Project.toml | 1 + src/Evolutionary.jl | 1 + src/ga.jl | 62 ++++++++++++------------ src/structs.jl | 114 ++++++++++++++++++++++++++++---------------- 4 files changed, 106 insertions(+), 72 deletions(-) diff --git a/Project.toml b/Project.toml index 6f175f7..330f7ba 100644 --- a/Project.toml +++ b/Project.toml @@ -6,6 +6,7 @@ version = "0.2.0" Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" DistributedArrays = "aaf54ef3-cdf8-58ed-94cc-d582ad619b94" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" diff --git a/src/Evolutionary.jl b/src/Evolutionary.jl index 7188648..dea58e5 100644 --- a/src/Evolutionary.jl +++ b/src/Evolutionary.jl @@ -4,6 +4,7 @@ module Evolutionary using Random, Base.Threads using Distributed using DistributedArrays, DistributedArrays.SPMD +using Printf export # Optimization methods diff --git a/src/ga.jl b/src/ga.jl index ca9dd8b..9a5018b 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -53,19 +53,15 @@ function ga( objfun ::Function , N = length(population) fitness = Vector{Float64}(undef, N) + # check if piping is used if piping == nothing - objfunc = objfun + func = objfun else - objfunc(x ::Vector{Individual}) = objfun(x, piping) + func = (x) -> objfun(x, piping) end - - for i in 1:N - @inbounds fitness[i] = objfunc(population[i]) - end - fitidx = sortperm(fitness) # save optional arguments in a dictionary - # to pass to generation function + # to pass to one of the generation functions pars = Dict{Symbol, Any}() pars[:crossoverRate] = crossoverRate pars[:mutationRate ] = mutationRate @@ -75,15 +71,21 @@ function ga( objfun ::Function , # Generate and evaluate offspring if parallel - population = distribute(population) - fitness = distribute(fitness) + if piping == nothing + works = workers()[1:Sys.CPU_THREADS] + else + works = piping.avail_workers + end + population = distribute(population;procs=works) + fitness = distribute(fitness;procs=works) elapsed_time = @elapsed begin - spmd(generations_parallel, objfunc, - population, fitness, pars) + spmd(generations_parallel, func, + population, fitness, pars; + pids=works) end else elapsed_time = @elapsed begin - generations(objfunc, population, N, fitness, pars) + generations(func, population, N, fitness, pars) end end @@ -115,8 +117,9 @@ function generations( objfun ::Function , bestFitness = 0.0 offspring = Vector{Individual}(undef, N) full_pop = Vector{Individual}(undef, 2*N) - full_fitness = Vector{Float64 }(undef, 2*N) + full_fit = Vector{Float64 }(undef, 2*N) + # Generate and evaluate offspring for iter in 1:pars[:iterations] # Select offspring @@ -155,13 +158,13 @@ function generations( objfun ::Function , @inbounds begin full_pop[ 1: N] = population full_pop[N+1:2*N] = offspring - full_fitness = objfun.(full_pop) - fitidx = sortperm(full_fitness) + full_fit = objfun.(full_pop) + fitidx = sortperm(full_fitness) end for i in 1:N @inbounds begin population[i] = full_pop[fitidx[i]] - fitness[i] = objfun(population[i]) + fitness[i] = full_fit[fitidx[i]] end end else @@ -193,11 +196,11 @@ function generations_parallel( objfun ::Function bestFitness = 0.0 offspring = Vector{Individual}(undef, N) full_pop = Vector{Individual}(undef, 2*N) - full_fitness = Vector{Float64 }(undef, 2*N) + full_fit = Vector{Float64 }(undef, 2*N) # Generate and evaluate offspring for iter in 1:pars[:iterations] - + # Select offspring selected = selection(fitness, N) @@ -227,20 +230,20 @@ function generations_parallel( objfun ::Function end # Elitism - # When true, always picks N best individuals from the full population - # (parents+offspring), which is size 2*N. + # When true, always picks N best individuals from the + # full population (parents+offspring), which is size 2*N. # When false, does everything randomly if pars[:ϵ] @inbounds begin full_pop[1 : N] = population full_pop[1+N:2*N] = offspring - full_fitness = objfun.(full_pop) - fitidx = sortperm(full_fitness) + full_fit = objfun.(full_pop) + fitidx = sortperm(full_fit) end for i in 1:N @inbounds begin population[i] = full_pop[fitidx[i]] - fitness[i] = objfun(population[i]) + fitness[i] = full_fit[fitidx[i]] end end else @@ -264,7 +267,7 @@ function data_presentation( individual ::Individual , isfit ::Bool , elapsed_time ::Float64 ) - optim_time = round(elapsed_time, digits=3) + optim_time = round(elapsed_time, digits=5) params = Vector{AbstractString}(undef, 0) values = Vector{Real}(undef, 0) @@ -283,12 +286,11 @@ function data_presentation( individual ::Individual , end end - table = string( "| parameter | value |\n" , - "|-----------|-------|\n" ) + table = @sprintf("| parameter | %-20s |\n", "value") + table *= @sprintf("|-----------|----------------------|\n") for (i,j) in enumerate(params) - table *= "| $j | $(values[i]) |\n" + table *= @sprintf("| %-9s | %-20s |\n", j, values[i]) end - @doc table present printstyled("\nRESULTS :\n", color=:bold) println("number of generations = " * string(generations)) @@ -296,7 +298,7 @@ function data_presentation( individual ::Individual , println("Run time = $optim_time seconds") println("") printstyled("GENES OF BEST INDIVIDUAL :\n", color=:bold) - display(@doc present) + println(table) println("") if isfit printstyled("OPTIMIZATION SUCCESSFUL\n" , color=:bold) diff --git a/src/structs.jl b/src/structs.jl index 2b9aaa5..844d69e 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -36,39 +36,39 @@ BinaryGene() = BinaryGene(rand(Bool), "bin_var") #################################################################### """ - IntegerGene(value ::BitVector, mutation ::Symbol) + IntegerGene(value ::BitVector, name ::AbstractString) -Creates a `IntegerGene` structure. This gene represents an integer variable as `BitVector`. To convert the `BitVector` in an integer, just look at the `bin` function from this package by typing `?bin` on the command prompt. `mutation` is a symbol that represents the type of mutation used for the bit vector. The below table shows all the mutation types supported: - -| Symbol | Algorithm | -|-----|-----| -| :FM | Flip Mutation | -| :InvM | Inversion Mutation | -| :InsM | Insertion Mutation | -| :SwM | Swap Mutation | -| :ScrM | Scramble Mutation | -| :ShM | Shifting Mutation | +Creates a `IntegerGene` structure. This gene represents an integer variable as `BitVector`. To convert the `BitVector` in an integer, just look at the `bin` function from this package by typing `?bin` on the command prompt. `name` is a stringt that represents the name of the variable. It's needed for result presentation purposes. """ mutable struct IntegerGene <: AbstractGene value ::BitVector - mutation ::Symbol name ::AbstractString - function IntegerGene(value ::BitVector, mutation ::Symbol, - name ::AbstractString) - int_func = mutate(mutation) - @eval mutate(gene ::IntegerGene) = $int_func(gene.value) - return new(value, mutation, name) + function IntegerGene(value ::BitVector, name ::AbstractString) + return new(value, name) end end """ - IntegerGene(value ::BitVector) + IntegerGene(mutation ::Symbol) + +Chooses the mutation type. Does NOT create a structure. This dispatch was created because, when creating a population of integer genes, each gene would create this function, which was unnecessary work. You still need to run one of the other dispatches to create the gene. + +Below are the types of mutations supported: -Creates a `IntegerGene` structure with the default mutation being Flip Mutation. +| Symbol | Algorithm | +|-----|-----| +| :FM | Flip Mutation | +| :InvM | Inversion Mutation | +| :InsM | Insertion Mutation | +| :SwM | Swap Mutation | +| :ScrM | Scramble Mutation | +| :ShM | Shifting Mutation | """ -function IntegerGene(value ::BitVector, name ::AbstractString) - return IntegerGene(value, :FM, name) +function IntegerGene(mutation ::Symbol) + int_func = mutate(mutation) + @eval mutate(gene ::IntegerGene) = $int_func(gene.value) + return nothing end """ @@ -81,7 +81,7 @@ function IntegerGene(n ::Int64, name ::AbstractString) for i in 1:n @inbounds value[i] = rand(Bool) end - return IntegerGene(value, :FM, name) + return IntegerGene(value, name) end """ @@ -117,6 +117,11 @@ mutable struct FloatGene <: AbstractGene if length(value) != length(range) error("vectors must have the same length") end + for i in name + if length(i) > 9 + error("variable name $i too long, must be less than 9 characters") + end + end return new(value, range, m, name) end end @@ -177,7 +182,7 @@ function FloatGene(n ::Int64, name ::AbstractString) range = rand(Float64, n) vec_name = Vector{AbstractString}(undef, n) for i in 1:n - vec_name[i] = string(name, "_", i) + vec_name[i] = string(name, i) end return FloatGene(value, range, 20, vec_name) end @@ -333,27 +338,30 @@ end Creates communication pipes for the external program `program`. If `parallel` is `true`, then, considering N workers available, N pipes for reading and N pipes for writing will be created. `pipename` is just a handle for the name of the pipes. If `pipename` is `pipe`, then the pipe names will be `pipe_in` and `pipe_out` for `parallel` as false and `pipe_inn` and `pipe_outn` for `parallel` as true, such as `n` being one of the N workers. """ mutable struct GAExternal - program ::AbstractString - pipes_in ::DArray{String,1,Vector{String}} - pipes_out ::DArray{String,1,Vector{String}} - parallel ::Bool - - function GAExternal( program ::AbstractString , - pipename ::AbstractString ; - parallel ::Bool = false ) + program ::AbstractString + pipes_in ::DArray{String,1,Vector{String}} + pipes_out ::DArray{String,1,Vector{String}} + rch ::Dict{AbstractString,RemoteChannel} + avail_workers ::Vector{Int64} + parallel ::Bool + + function GAExternal( program ::AbstractString , + pipename ::AbstractString ; + nworkers ::Int64 = 8 , + parallel ::Bool = false ) pipes = Dict{String,Vector{String}}() pipes["in" ] = Vector{String}(undef, 0) pipes["out"] = Vector{String}(undef, 0) + avail_workers = workers()[1:nworkers] if parallel # create one pipe for reading and another for writing # for each worker for i in ["in","out"] - for p in workers() + for p in avail_workers f = string(pipename, "_", i, p) push!(pipes[i], f) rm(f, force=true) run(`mkfifo $f`) - end end else @@ -366,19 +374,41 @@ mutable struct GAExternal end end - pin = distribute(pipes["in" ]) - pout = distribute(pipes["out"]) + pin = distribute(pipes["in" ]; procs=avail_workers) + pout = distribute(pipes["out"]; procs=avail_workers) - # activate writing pipes to a big amount of time - # reading pipes do not need, the program needs to - # write explicitly to the pipe internally, - # otherwise the processes block + # activate writing pipes for a big amount of time for p in pipes["in"] @spawnat :any run(pipeline(`sleep 100000000`, p)) @spawnat :any run(pipeline(`$program`; stdin=p)) end - - # remove all pipes when exiting julia + + # Create remote channels to save return value(s) + # from the external program + rch = Dict{String,RemoteChannel}() + for p in pipes["out"] + rch[p] = RemoteChannel(()->Channel{Float64}(1)) + end + + # Open reading pipes in separate processes. + # The pipes have to be open before writing to them, + # otherwise we get SIGPIPE errors when writing. + function spawn_readpipes(pipe, rch) + f = open(pipe, "r") + while true + x = @elapsed begin + b = readline(f) + put!(rch[pipe], parse(Float64, b)) + end + end + return nothing + end + for (i,p) in enumerate(pipes["out"]) + v = nworkers+1 + i + @spawnat v spawn_readpipes(p, rch) + end + + # delete all pipes when exiting julia function external_atexit() for k in keys(pipes) for p in pipes[k] @@ -389,6 +419,6 @@ mutable struct GAExternal end atexit(external_atexit) - return new(program, pin, pout, parallel) + return new(program, pin, pout, rch, avail_workers, parallel) end end From c840ff306f67dd292cc7e50183e928ac4b53673c Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Sun, 15 Mar 2020 14:00:41 +0100 Subject: [PATCH 23/51] Increased piping performance The previous prototype was quite slow due to using remote channels and the pipe reading not being made in the process it was running. Now the remote channels are gone and the pipe reading and the objective function are determined in the same process, which is much more efficient and faster. --- src/ga.jl | 63 ++++++++++++++++++++++++++++++-------------------- src/structs.jl | 30 ++++++++---------------- 2 files changed, 47 insertions(+), 46 deletions(-) diff --git a/src/ga.jl b/src/ga.jl index 9a5018b..adb4a2e 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -119,6 +119,41 @@ function generations( objfun ::Function , full_pop = Vector{Individual}(undef, 2*N) full_fit = Vector{Float64 }(undef, 2*N) + # Elitism + # When true, always picks N best individuals from the full population + # (parents+offspring), which is size 2*N. + # When false, does everything randomly + function elitism_true() + @inbounds begin + full_pop[ 1: N] = population + full_pop[N+1:2*N] = offspring + full_fit = objfun.(full_pop) + fitidx = sortperm(full_fitness) + end + for i in 1:N + @inbounds begin + population[i] = full_pop[fitidx[i]] + fitness[i] = full_fit[fitidx[i]] + end + end + return nothing + end + function elitism_false() + for i in 1:N + @inbounds begin + population[i] = offspring[i] + fitness[i] = objfun(population[i]) + end + end + return nothing + end + + if pars[:ϵ] + elitism = elitism_true + else + elitism = elitism_false + end + # Generate and evaluate offspring for iter in 1:pars[:iterations] @@ -147,34 +182,12 @@ function generations( objfun ::Function , for i in 1:N if rand() < pars[:mutationRate] @inbounds mutate(offspring[i]) + #mutate(offspring[i]) end end - # Elitism - # When true, always picks N best individuals from the full population - # (parents+offspring), which is size 2*N. - # When false, does everything randomly - if pars[:ϵ] - @inbounds begin - full_pop[ 1: N] = population - full_pop[N+1:2*N] = offspring - full_fit = objfun.(full_pop) - fitidx = sortperm(full_fitness) - end - for i in 1:N - @inbounds begin - population[i] = full_pop[fitidx[i]] - fitness[i] = full_fit[fitidx[i]] - end - end - else - for i in 1:N - @inbounds begin - population[i] = offspring[i] - fitness[i] = objfun(population[i]) - end - end - end + elitism() + end return nothing diff --git a/src/structs.jl b/src/structs.jl index 844d69e..1cc5c2c 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -341,14 +341,13 @@ mutable struct GAExternal program ::AbstractString pipes_in ::DArray{String,1,Vector{String}} pipes_out ::DArray{String,1,Vector{String}} - rch ::Dict{AbstractString,RemoteChannel} avail_workers ::Vector{Int64} parallel ::Bool - function GAExternal( program ::AbstractString , - pipename ::AbstractString ; - nworkers ::Int64 = 8 , - parallel ::Bool = false ) + function GAExternal( program ::AbstractString , + pipename ::AbstractString ; + nworkers ::Int64 = Sys.CPU_THREADS , + parallel ::Bool = false ) pipes = Dict{String,Vector{String}}() pipes["in" ] = Vector{String}(undef, 0) pipes["out"] = Vector{String}(undef, 0) @@ -383,29 +382,16 @@ mutable struct GAExternal @spawnat :any run(pipeline(`$program`; stdin=p)) end - # Create remote channels to save return value(s) - # from the external program - rch = Dict{String,RemoteChannel}() - for p in pipes["out"] - rch[p] = RemoteChannel(()->Channel{Float64}(1)) - end - # Open reading pipes in separate processes. # The pipes have to be open before writing to them, # otherwise we get SIGPIPE errors when writing. - function spawn_readpipes(pipe, rch) + function spawn_readpipes(pipe) f = open(pipe, "r") - while true - x = @elapsed begin - b = readline(f) - put!(rch[pipe], parse(Float64, b)) - end - end return nothing end for (i,p) in enumerate(pipes["out"]) v = nworkers+1 + i - @spawnat v spawn_readpipes(p, rch) + @spawnat v spawn_readpipes(p) end # delete all pipes when exiting julia @@ -415,10 +401,12 @@ mutable struct GAExternal rm(p, force=true) end end + run(`killall ampl`) + run(`killall sleep`) return nothing end atexit(external_atexit) - return new(program, pin, pout, rch, avail_workers, parallel) + return new(program, pin, pout, avail_workers, parallel) end end From 268072e0ba6cee08fb48855421e3ece65e4d58ed Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Wed, 18 Mar 2020 16:04:15 +0100 Subject: [PATCH 24/51] Started mesing around with clusters When not using external programs the GA code worls well with both computers connected, now I have to figure out out to run properly using external programs --- src/ga.jl | 36 ++++++++++++++++++------------------ src/mutations.jl | 6 ++++-- src/selections.jl | 2 +- src/structs.jl | 23 ++++++++++++++--------- 4 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/ga.jl b/src/ga.jl index adb4a2e..ffbe604 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -39,15 +39,16 @@ export ga Runs the Genetic Algorithm using the objective function `objfun`, the initial population `initpopulation` and the population size `populationSize`. `objfun` is the function to MINIMIZE. """ -function ga( objfun ::Function , - population ::Vector{Individual} ; - crossoverRate ::Float64 = 0.5 , - mutationRate ::Float64 = 0.5 , - ϵ ::Bool = true , - iterations ::Integer = 100 , - tol ::Real = 0.0 , - parallel ::Bool = false , - piping ::Union{Nothing,GAExternal} = nothing ) +function ga( objfun ::Function , + population ::Vector{Individual} ; + crossoverRate ::Float64 = 0.5 , + mutationRate ::Float64 = 0.5 , + ϵ ::Bool = true , + iterations ::Integer = 100 , + tol ::Real = 0.0 , + parallel ::Bool = false , + piping ::Union{Nothing,GAExternal} = nothing , + nworkers ::Integer = Sys.CPU_THREADS ) # Initialize population N = length(population) @@ -72,7 +73,7 @@ function ga( objfun ::Function , # Generate and evaluate offspring if parallel if piping == nothing - works = workers()[1:Sys.CPU_THREADS] + works = workers()[1:nworkers] else works = piping.avail_workers end @@ -180,10 +181,7 @@ function generations( objfun ::Function , # Perform mutation for i in 1:N - if rand() < pars[:mutationRate] - @inbounds mutate(offspring[i]) - #mutate(offspring[i]) - end + @inbounds mutate(offspring[i], pars[:mutationRate]) end elitism() @@ -237,9 +235,7 @@ function generations_parallel( objfun ::Function # Perform mutation for i in 1:N - if rand() < pars[:mutationRate] - @inbounds mutate(offspring[i]) - end + @inbounds mutate(offspring[i], pars[:mutationRate]) end # Elitism @@ -302,7 +298,11 @@ function data_presentation( individual ::Individual , table = @sprintf("| parameter | %-20s |\n", "value") table *= @sprintf("|-----------|----------------------|\n") for (i,j) in enumerate(params) - table *= @sprintf("| %-9s | %-20s |\n", j, values[i]) + s_val = string(values[i]) + if length(s_val) > 20 + s_val = s_val[1:20] + end + table *= @sprintf("| %-9s | %-20s |\n", j, s_val) end printstyled("\nRESULTS :\n", color=:bold) diff --git a/src/mutations.jl b/src/mutations.jl index 26a4c56..2350a70 100644 --- a/src/mutations.jl +++ b/src/mutations.jl @@ -244,9 +244,11 @@ end Mutates each entry of `chromossome` according to the mutations chosen. """ -function mutate(chromossome ::Individual) +function mutate(chromossome ::Individual, rate ::Float64) for gene in chromossome - mutate(gene) + if rand() < rate + mutate(gene) + end end return nothing end diff --git a/src/selections.jl b/src/selections.jl index c6b28f8..9b22bba 100644 --- a/src/selections.jl +++ b/src/selections.jl @@ -111,7 +111,7 @@ function pselection(prob ::Vector{<:Real}, N ::Int) for i in 1:N j = 1 r = rand() - while cp[j] < r + while (cp[j] < r && j <= N) j += 1 end selected[i] = j diff --git a/src/structs.jl b/src/structs.jl index 1cc5c2c..f7511d7 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -359,8 +359,8 @@ mutable struct GAExternal for p in avail_workers f = string(pipename, "_", i, p) push!(pipes[i], f) - rm(f, force=true) - run(`mkfifo $f`) + remotecall_fetch(rm, p, f, force=true) + remotecall_fetch(run, p, `mkfifo $f`) end end else @@ -377,9 +377,14 @@ mutable struct GAExternal pout = distribute(pipes["out"]; procs=avail_workers) # activate writing pipes for a big amount of time - for p in pipes["in"] - @spawnat :any run(pipeline(`sleep 100000000`, p)) - @spawnat :any run(pipeline(`$program`; stdin=p)) + for (i,p) in enumerate(pipes["in"]) + id = workers()[i] + id1 = rand(workers()) + while id1 == id + id1 = rand(workers()) + end + @spawnat id1 run(pipeline(`sleep 100000000`, p)) + @spawnat id1 run(pipeline(`$program`; stdin=p)) end # Open reading pipes in separate processes. @@ -397,12 +402,12 @@ mutable struct GAExternal # delete all pipes when exiting julia function external_atexit() for k in keys(pipes) - for p in pipes[k] - rm(p, force=true) + for (i,p) in enumerate(pipes[k]) + id = i+1 + remotecall_fetch(rm, id, p) end end - run(`killall ampl`) - run(`killall sleep`) + return nothing end atexit(external_atexit) From e1778898ec22d35dbd727217aaaeefb3aaf2fa58 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Mon, 23 Mar 2020 12:40:14 +0100 Subject: [PATCH 25/51] Finished running external programs in a cluster Now the GA is capable of spawning the calculations inside a cluster. There are a lot of limitations and the package cannot do that on its own, but I'll create some scripts to do that, since there are several limitations regarding the number of processes needed to run external programs --- Project.toml | 1 + src/Evolutionary.jl | 1 + src/ga.jl | 169 ++++++++++++++++++++++++++++++++++++++++---- src/structs.jl | 83 +++++++++++----------- 4 files changed, 197 insertions(+), 57 deletions(-) diff --git a/Project.toml b/Project.toml index 330f7ba..1ef9bba 100644 --- a/Project.toml +++ b/Project.toml @@ -3,6 +3,7 @@ uuid = "86b6b26d-c046-49b6-aa0b-5f0f74682bd6" version = "0.2.0" [deps] +Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" DistributedArrays = "aaf54ef3-cdf8-58ed-94cc-d582ad619b94" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" diff --git a/src/Evolutionary.jl b/src/Evolutionary.jl index dea58e5..fd348f9 100644 --- a/src/Evolutionary.jl +++ b/src/Evolutionary.jl @@ -5,6 +5,7 @@ using Random, Base.Threads using Distributed using DistributedArrays, DistributedArrays.SPMD using Printf +using Dates export # Optimization methods diff --git a/src/ga.jl b/src/ga.jl index ffbe604..05c52d3 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -48,7 +48,8 @@ function ga( objfun ::Function , tol ::Real = 0.0 , parallel ::Bool = false , piping ::Union{Nothing,GAExternal} = nothing , - nworkers ::Integer = Sys.CPU_THREADS ) + nworkers ::Integer = Sys.CPU_THREADS , + output ::AbstractString = "" ) # Initialize population N = length(population) @@ -99,7 +100,7 @@ function ga( objfun ::Function , # result presentation data_presentation( population[bestIndividual], iterations, - bestFitness, isfit, elapsed_time ) + bestFitness, isfit, elapsed_time, output ) return population[bestIndividual], bestFitness end @@ -267,16 +268,18 @@ function generations_parallel( objfun ::Function return nothing end - + #################################################################### function data_presentation( individual ::Individual , generations ::Integer , bestFitness ::Float64 , isfit ::Bool , - elapsed_time ::Float64 ) + elapsed_time ::Float64 , + output ::String ) - optim_time = round(elapsed_time, digits=5) + optim_time = round(elapsed_time, digits=5) + bestFitness = round(bestFitness, digits=15) params = Vector{AbstractString}(undef, 0) values = Vector{Real}(undef, 0) @@ -289,22 +292,38 @@ function data_presentation( individual ::Individual , elseif isa(gene, IntegerGene) push!(params, gene.name) push!(values, bin(gene)) - else + elseif isa(gene, BinaryGene) push!(params, gene.name) push!(values, gene.value) + else + nothing end end - - table = @sprintf("| parameter | %-20s |\n", "value") - table *= @sprintf("|-----------|----------------------|\n") + + val_maxsize, str_vals = present_numbers(values, 15) + name_maxsize = present_names(params) + if val_maxsize < length("value") + val_maxsize = length("value") + end + if name_maxsize < length("parameter") + name_maxsize = length("parameter") + end + i_str = "| %-$(name_maxsize)s | %-$(val_maxsize)s |\n" + table = @eval @sprintf($i_str, "parameter", "value") + iv = "|" + for i in 1:name_maxsize+2 + iv *= "-" + end + iv *= "|" + for i in 1:val_maxsize+2 + iv *= "-" + end + table *= iv * "|\n" for (i,j) in enumerate(params) - s_val = string(values[i]) - if length(s_val) > 20 - s_val = s_val[1:20] - end - table *= @sprintf("| %-9s | %-20s |\n", j, s_val) + s_val = str_vals[i] + table *= @eval @sprintf($i_str, $j, $s_val) end - + printstyled("\nRESULTS :\n", color=:bold) println("number of generations = " * string(generations)) println("best Fitness = " * string(bestFitness)) @@ -319,6 +338,126 @@ function data_presentation( individual ::Individual , printstyled("OPTIMIZATION UNSUCCESSFUL\n", color=:bold) end + if output != "" + open(output, "w") do f + write(f, "Result File of Genetic Algorithm, $(now())\n\n") + write(f, "RESULTS :\n") + write(f, string("number of generations = ", generations, "\n")) + write(f, string("best Fitness = ", bestFitness, "\n")) + write(f, "Run time = $optim_time seconds\n") + write(f, "\n") + write(f, "GENES OF BEST INDIVIDUAL :\n") + write(f, table) + write(f, "\n") + if isfit + write(f, "OPTIMIZATION SUCCESSFUL\n") + else + write(f, "OPTIMIZATION UNSUCCESSFUL\n") + end + end + end + return nothing end +#################################################################### + +function present_numbers(values ::Vector{<:Real}, round_digits ::Int64) + for (i,j) in enumerate(values) + if isa(j, Float64) + values[i] = round(j, digits=round_digits) + end + end + + str_vals = [string(j) for j in values] + for (i,j) in enumerate(str_vals) + if j == "true" + str_vals[i] = "1" + elseif j == "false" + str_vals[i] = "0" + else + nothing + end + end + + left_dot = Vector{AbstractString}(undef, length(values)) + right_dot = Vector{AbstractString}(undef, length(values)) + for (i,j) in enumerate(str_vals) + j_spl = split(j, ".") + if length(j_spl) == 2 + left = j_spl[1] + right = j_spl[2] + else + left = j_spl[1] + right = "" + end + left_dot[i] = left + right_dot[i] = right + end + + left_maxsize = 0 + for i in left_dot + l = length(i) + if l > left_maxsize + left_maxsize = l + end + end + + right_maxsize = 0 + for i in right_dot + l = length(i) + if l > right_maxsize + right_maxsize = l + end + end + + for (i,j) in enumerate(left_dot) + ind = left_maxsize - length(j) + str = "" + if ind > 0 + for k in 1:ind + str *= " " + end + end + left_dot[i] = str * j + end + + for i in 1:length(str_vals) + if right_dot[i] == "" + str_vals[i] = left_dot[i] + if right_maxsize == 0 + iv = right_maxsize + else + iv = right_maxsize+1 + end + for i in 1:iv + str_vals[i] *= " " + end + else + str_vals[i] = string(left_dot[i],".",right_dot[i]) + end + end + + str_maxsize = 0 + for i in str_vals + l = length(i) + if l > str_maxsize + str_maxsize = l + end + end + + return str_maxsize, str_vals +end + +#################################################################### + +function present_names(names ::Vector{AbstractString}) + name_maxsize = 0 + for i in names + l = length(i) + if l > name_maxsize + name_maxsize = l + end + end + return name_maxsize +end diff --git a/src/structs.jl b/src/structs.jl index f7511d7..96c3c67 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -31,7 +31,14 @@ end Creates a `BinaryGene` structure with a random `Bool` value. """ -BinaryGene() = BinaryGene(rand(Bool), "bin_var") +BinaryGene(name ::AbstractString) = BinaryGene(rand(Bool), name) + +""" + BinaryGene() + +Creates a `BinaryGene` structure with a random `Bool` value and a default variable name. +""" +BinaryGene() = BinaryGene(rand(Bool), "bin") #################################################################### @@ -56,14 +63,14 @@ Chooses the mutation type. Does NOT create a structure. This dispatch was create Below are the types of mutations supported: -| Symbol | Algorithm | -|-----|-----| -| :FM | Flip Mutation | -| :InvM | Inversion Mutation | -| :InsM | Insertion Mutation | -| :SwM | Swap Mutation | -| :ScrM | Scramble Mutation | -| :ShM | Shifting Mutation | +| Symbol | Algorithm | +|--------|--------------------| +| :FM | Flip Mutation | +| :InvM | Inversion Mutation | +| :InsM | Insertion Mutation | +| :SwM | Swap Mutation | +| :ScrM | Scramble Mutation | +| :ShM | Shifting Mutation | """ function IntegerGene(mutation ::Symbol) int_func = mutate(mutation) @@ -117,11 +124,6 @@ mutable struct FloatGene <: AbstractGene if length(value) != length(range) error("vectors must have the same length") end - for i in name - if length(i) > 9 - error("variable name $i too long, must be less than 9 characters") - end - end return new(value, range, m, name) end end @@ -196,20 +198,20 @@ end Creates a `Crossover` structure. `cross` is a Symbol that represents the type of crossover that would be used. `w` and `d` are not mandatory but need to be set for some types of crossovers. All algorithms will be shown in the table below: -| Symbol | Algorithm | Optional Arguments | -|----|----|---| -| :SPX | Single Point Crossover | not needed | -| :TPX | Two Point Crossover | not needed | -| :UX | Uniform Crossover | not needed | -| :DX | Discrete Crossover | not needed | -| :WMX | Weighted Mean Crossover | needs `w` | -| :IRX | Intermediate Recombination Crossover | needs `d` | -| :LRX | Line Recombination Crossover | needs `d` | -| :PMX | Partially Mapped Crossover | not needed | -| :O1X | Order 1 Crossover | not needed | -| :O2X | Order 2 Crossover | not needed | -| :CX | Cycle Crossover | not needed | -| :PX | Position-based Crossover | not needed | +| Symbol | Algorithm | Optional Arguments | +|--------|--------------------------------------|--------------------| +| :SPX | Single Point Crossover | not needed | +| :TPX | Two Point Crossover | not needed | +| :UX | Uniform Crossover | not needed | +| :DX | Discrete Crossover | not needed | +| :WMX | Weighted Mean Crossover | needs `w` | +| :IRX | Intermediate Recombination Crossover | needs `d` | +| :LRX | Line Recombination Crossover | needs `d` | +| :PMX | Partially Mapped Crossover | not needed | +| :O1X | Order 1 Crossover | not needed | +| :O2X | Order 2 Crossover | not needed | +| :CX | Cycle Crossover | not needed | +| :PX | Position-based Crossover | not needed | """ mutable struct Crossover cross ::Symbol @@ -274,14 +276,14 @@ end Creates a `Selection` structure. `select` is a symbol that represents the type of selection that will be used. `sp`, `μ` and `groupsize` are optional but need to be set for some types of selections. All algorithms will be shown in the table below: -| Symbol | Algorithm | Optional Arguments | -|----|----|----| -| :RBS | Rank-based Selection | needs `sp` | -| :URS | Uniform-Ranking Selection | needs `μ` | -| :RWS | Roulette Wheel Selection | not needed | -| :SUSS | Stochastic Universal Sampling Selection | not needed | -| :TrS | Truncation Selection | not needed | -| :ToS | Tournament Selection | needs `groupsize` | +| Symbol | Algorithm | Optional Arguments | +|--------|-----------------------------------------|--------------------| +| :RBS | Rank-based Selection | needs `sp` | +| :URS | Uniform-Ranking Selection | needs `μ` | +| :RWS | Roulette Wheel Selection | not needed | +| :SUSS | Stochastic Universal Sampling Selection | not needed | +| :TrS | Truncation Selection | not needed | +| :ToS | Tournament Selection | needs `groupsize` | """ mutable struct Selection select ::Symbol @@ -378,12 +380,9 @@ mutable struct GAExternal # activate writing pipes for a big amount of time for (i,p) in enumerate(pipes["in"]) - id = workers()[i] - id1 = rand(workers()) - while id1 == id - id1 = rand(workers()) - end - @spawnat id1 run(pipeline(`sleep 100000000`, p)) + id = 2*nworkers+1 + i + id1 = 3*nworkers+1 + i + @spawnat id run(pipeline(`sleep 100000000`, p)) @spawnat id1 run(pipeline(`$program`; stdin=p)) end From 97d539de621e055b67f9a4713e14a293d73a5744 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Wed, 25 Mar 2020 16:22:05 +0100 Subject: [PATCH 26/51] minor revisions --- src/Evolutionary.jl | 2 +- src/ga.jl | 8 +++++--- src/structs.jl | 17 ++++++++++++----- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/Evolutionary.jl b/src/Evolutionary.jl index fd348f9..0d46b94 100644 --- a/src/Evolutionary.jl +++ b/src/Evolutionary.jl @@ -22,7 +22,7 @@ abstract type AbstractGene end const Strategy = Dict{Symbol,Any} -const Individual = Vector{<:AbstractGene} +const Individual = Vector{AbstractGene} const GAVector = Union{T, BitVector} where T <: Vector diff --git a/src/ga.jl b/src/ga.jl index 05c52d3..e044079 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -49,7 +49,8 @@ function ga( objfun ::Function , parallel ::Bool = false , piping ::Union{Nothing,GAExternal} = nothing , nworkers ::Integer = Sys.CPU_THREADS , - output ::AbstractString = "" ) + output ::AbstractString = "" , + showprint ::Bool = true ) # Initialize population N = length(population) @@ -99,8 +100,8 @@ function ga( objfun ::Function , end # result presentation - data_presentation( population[bestIndividual], iterations, - bestFitness, isfit, elapsed_time, output ) + data_presentation( population[bestIndividual], iterations, bestFitness, + isfit, elapsed_time, showprint, output ) return population[bestIndividual], bestFitness end @@ -276,6 +277,7 @@ function data_presentation( individual ::Individual , bestFitness ::Float64 , isfit ::Bool , elapsed_time ::Float64 , + showprint ::Bool , output ::String ) optim_time = round(elapsed_time, digits=5) diff --git a/src/structs.jl b/src/structs.jl index 96c3c67..d83321b 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -115,12 +115,12 @@ mutable struct FloatGene <: AbstractGene value ::Vector{Float64} range ::Vector{Float64} m ::Int64 - name ::Vector{<:AbstractString} + name ::Vector{AbstractString} function FloatGene( value ::Vector{Float64} , range ::Vector{Float64} , m ::Int64 , - name ::Vector{<:AbstractString} ) + name ::Vector{String} ) if length(value) != length(range) error("vectors must have the same length") end @@ -128,6 +128,13 @@ mutable struct FloatGene <: AbstractGene end end +function FloatGene( value ::Vector{Float64} , + range ::Vector{Float64} , + name ::Vector{String} ; + m ::Int64 = 20 ) + return FloatGene(value, range, m, name) +end + """ FloatGene(value ::Float64, range ::Float64; m ::Int64 = 20) @@ -147,7 +154,7 @@ Creates a `FloatGene` structure. Handy for creating a vector of real numbers wit """ function FloatGene( value ::Vector{Float64} , range ::Float64 , - name ::Vector{<:AbstractString} ; + name ::Vector{String} ; m ::Int64 = 20 ) vec = Float64[range for i in value] return FloatGene(value, vec, m, name) @@ -159,7 +166,7 @@ end Creates a `FloatGene` structure. Handy for creating a vector of real numbers with a random range. """ function FloatGene( value ::Vector{Float64} , - name ::Vector{<:AbstractString} ; + name ::Vector{String} ; m ::Int64 = 20 ) range = rand(Float64, length(value)) return FloatGene(value, range, m, name) @@ -182,7 +189,7 @@ Creates a `FloatGene` structure. Creates a vector of length `n` with random vari function FloatGene(n ::Int64, name ::AbstractString) value = rand(Float64, n) range = rand(Float64, n) - vec_name = Vector{AbstractString}(undef, n) + vec_name = Vector{String}(undef, n) for i in 1:n vec_name[i] = string(name, i) end From 545b1214870fde291dd83fa7f70a8351f715c5d2 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Fri, 27 Mar 2020 13:46:22 +0100 Subject: [PATCH 27/51] introduced file with backup strategies --- src/Evolutionary.jl | 3 ++ src/backup.jl | 107 ++++++++++++++++++++++++++++++++++++++++ src/backup.jl~ | 117 ++++++++++++++++++++++++++++++++++++++++++++ src/ga.jl | 44 +++++++++++++++-- src/structs.jl | 8 +-- 5 files changed, 270 insertions(+), 9 deletions(-) create mode 100644 src/backup.jl create mode 100644 src/backup.jl~ diff --git a/src/Evolutionary.jl b/src/Evolutionary.jl index 0d46b94..a0f0200 100644 --- a/src/Evolutionary.jl +++ b/src/Evolutionary.jl @@ -88,6 +88,9 @@ include("selections.jl") include("es.jl") include("cmaes.jl") +# Backup functions +include("backup.jl") + # Genetic Algorithms include("ga.jl") diff --git a/src/backup.jl b/src/backup.jl new file mode 100644 index 0000000..001af06 --- /dev/null +++ b/src/backup.jl @@ -0,0 +1,107 @@ + +export backup, reverse_backup + +#################################################################### + +function backup(f ::IOStream, gene ::IntegerGene) + write(f, 'I') + write(f, Int8(length(gene.value))) + for i in gene.value + write(f, i) + end + write(f, Int8(-length(gene.name)), gene.name) + return nothing +end + +function backup(f ::IOStream, gene ::FloatGene) + write(f, 'F') + write(f, gene.m) + write(f, Int8(length(gene.value))) + for i in gene.value + write(f, i) + end + for i in gene.range + write(f, i) + end + for i in gene.name + l = Int8(-length(i)) + write(f, l, i) + end + return nothing +end + +function backup(f ::IOStream, gene ::BinaryGene) + write(f, 'B') + write(f, gene.value) + write(f, Int8(-length(gene.name)), gene.name) + return nothing +end + +function backup(ngens ::Int64, chrom ::Vector{Individual}, + file ::AbstractString) + file = "backup-files/$file" + chromossome = chrom + psize = length(chromossome ) + gsize = length(chromossome[1]) + open(file, "w") do f + write(f, ngens, psize, gsize) + for i in chromossome + for j in i + backup(f, j) + end + end + end + return nothing +end + +#################################################################### + +function reverse_backup(filename ::AbstractString) + f = open(filename, "r") + + ngens = read(f, Int64) + popsize = read(f, Int64) + genesize = read(f, Int64) + population = Vector{Individual}(undef, popsize) + for p in 1:popsize + population[p] = Individual(undef, genesize) + for g in 1:genesize + id = read(f, Char) + if id == 'I' + nvals = read(f, Int8) + bit_vec = BitVector(undef, nvals) + readbytes!(f, reinterpret(UInt8, bit_vec)) + strsize = -read(f, Int8) + name = Vector{UInt8}(undef, strsize) + readbytes!(f, name) + population[p][g] = + IntegerGene(bit_vec, String(name)) + elseif id == 'F' + m = read(f, Int64) + nvals = read(f, Int8 ) + names = Vector{String }(undef, nvals) + values = Vector{Float64}(undef, nvals) + ranges = Vector{Float64}(undef, nvals) + readbytes!(f, reinterpret(UInt8, values)) + readbytes!(f, reinterpret(UInt8, ranges)) + for i in 1:nvals + strsize = -read(f, Int8) + name = Vector{UInt8}(undef, strsize) + readbytes!(f, name) + names[i] = String(name) + end + population[p][g] = + FloatGene(values, ranges, m, names) + elseif id == 'B' + value = read(f, Bool) + strsize = -read(f, Int8) + name = Vector{UInt8}(undef, strsize) + readbytes!(f, name) + population[p][g] = BinaryGene(value, String(name)) + end + end + end + + close(f) + return ngens, population +end diff --git a/src/backup.jl~ b/src/backup.jl~ new file mode 100644 index 0000000..df04920 --- /dev/null +++ b/src/backup.jl~ @@ -0,0 +1,117 @@ + +export backup, reverse_backup + +#################################################################### + +function backup(f ::IOStream, gene ::IntegerGene) + write(f, 'I') + write(f, Int8(length(gene.value))) + for i in gene.value + write(f, i) + end + write(f, Int8(-length(gene.name)), gene.name) + return nothing +end + +function backup(f ::IOStream, gene ::FloatGene) + write(f, 'F') + write(f, gene.m) + write(f, Int8(length(gene.value))) + for i in gene.value + write(f, i) + end + for i in gene.range + write(f, i) + end + for i in gene.name + l = Int8(-length(i)) + write(f, l, i) + end + return nothing +end + +function backup(f ::IOStream, gene ::BinaryGene) + write(f, 'B') + write(f, gene.value) + write(f, Int8(-length(gene.name)), gene.name) + return nothing +end + +function backup(ngens ::Int64, chrom ::Vector{Individual}) + chromossome = copy(chrom) + psize = length(chromossome ) + gsize = length(chromossome[1]) + file = "Backup_GA_$(now())" + open(file, "w") do f + write(f, ngens, psize, gsize) + for i in chromossome + for j in i + backup(f, j) + end + end + end + return nothing +end + +function backup(ngens ::Vector{Int64}, chrom ::Vector{Individual}) + return backup(ngens[1], chrom) +end + +function backup( ngens ::DArray{Individual,1,Vector{Individual}} , + pop ::DArray{Individual,1,Vector{Individual}} ) + population = convert(Vector{Individual}, pop) + generations = findmin(ngens)[1] + return backup(generations, population) +end + +#################################################################### + +function reverse_backup(filename ::AbstractString) + f = open(filename, "r") + + ngens = read(f, Int64) + popsize = read(f, Int64) + genesize = read(f, Int64) + population = Vector{Individual}(undef, popsize) + for p in 1:popsize + population[p] = Individual(undef, genesize) + for g in 1:genesize + id = read(f, Char) + if id == 'I' + nvals = read(f, Int8) + bit_vec = BitVector(undef, nvals) + readbytes!(f, reinterpret(UInt8, bit_vec)) + strsize = -read(f, Int8) + name = Vector{UInt8}(undef, strsize) + readbytes!(f, name) + population[p][g] = + IntegerGene(bit_vec, String(name)) + elseif id == 'F' + m = read(f, Int64) + nvals = read(f, Int8 ) + names = Vector{String }(undef, nvals) + values = Vector{Float64}(undef, nvals) + ranges = Vector{Float64}(undef, nvals) + readbytes!(f, reinterpret(UInt8, values)) + readbytes!(f, reinterpret(UInt8, ranges)) + for i in 1:nvals + strsize = -read(f, Int8) + name = Vector{UInt8}(undef, strsize) + readbytes!(f, name) + names[i] = String(name) + end + population[p][g] = + FloatGene(values, ranges, m, names) + elseif id == 'B' + value = read(f, Bool) + strsize = -read(f, Int8) + name = Vector{UInt8}(undef, strsize) + readbytes!(f, name) + population[p][g] = BinaryGene(value, String(name)) + end + end + end + + close(f) + return ngens, population +end diff --git a/src/ga.jl b/src/ga.jl index e044079..7b4de77 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -5,6 +5,7 @@ #################################################################### export ga +const ch = Channel{Bool}(1) #################################################################### @@ -50,7 +51,9 @@ function ga( objfun ::Function , piping ::Union{Nothing,GAExternal} = nothing , nworkers ::Integer = Sys.CPU_THREADS , output ::AbstractString = "" , - showprint ::Bool = true ) + showprint ::Bool = true , + isbackup ::Bool = true , + backuptime ::Float64 = 0.5 ) # Initialize population N = length(population) @@ -71,22 +74,35 @@ function ga( objfun ::Function , pars[:ϵ ] = ϵ pars[:iterations ] = iterations pars[:tol ] = tol + pars[:backuptime ] = isbackup ? backuptime : Inf - # Generate and evaluate offspring + # create folder for backup files + if !isdir("backup-files") + mkdir("backup-files") + println("folder created") + end + + # choose run method depending if it's parallel + # or serial processing if parallel if piping == nothing works = workers()[1:nworkers] else works = piping.avail_workers end + + # create distributed arrays for parallel processing population = distribute(population;procs=works) fitness = distribute(fitness;procs=works) + + # run generations elapsed_time = @elapsed begin spmd(generations_parallel, func, population, fitness, pars; pids=works) end else + # run generations elapsed_time = @elapsed begin generations(func, population, N, fitness, pars) end @@ -131,7 +147,7 @@ function generations( objfun ::Function , full_pop[ 1: N] = population full_pop[N+1:2*N] = offspring full_fit = objfun.(full_pop) - fitidx = sortperm(full_fitness) + fitidx = sortperm(full_fit) end for i in 1:N @inbounds begin @@ -157,8 +173,17 @@ function generations( objfun ::Function , elitism = elitism_false end + + t_ref = time() # Generate and evaluate offspring for iter in 1:pars[:iterations] + + # backup process + dt = time() - t_ref + if dt > pars[:backuptime] + backup(iter, population) + t_ref = time() + end # Select offspring selected = selection(fitness, N) @@ -187,7 +212,7 @@ function generations( objfun ::Function , end elitism() - + end return nothing @@ -199,7 +224,7 @@ function generations_parallel( objfun ::Function popul ::DArray{Individual,1,Vector{Individual}} , fit ::DArray{Float64,1,Vector{Float64}} , pars ::Dict{Symbol,Any} ) - + println("made it here") # Variable initialization population = popul[:L] fitness = fit[:L] @@ -211,9 +236,18 @@ function generations_parallel( objfun ::Function full_pop = Vector{Individual}(undef, 2*N) full_fit = Vector{Float64 }(undef, 2*N) + t_ref = time() # Generate and evaluate offspring for iter in 1:pars[:iterations] + # backup process + dt = time() - t_ref + if dt > pars[:backuptime] + file = "Backup_GA_worker$(myid())" + backup(iter, population, file) + t_ref = time() + end + # Select offspring selected = selection(fitness, N) diff --git a/src/structs.jl b/src/structs.jl index d83321b..5ad7200 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -387,9 +387,9 @@ mutable struct GAExternal # activate writing pipes for a big amount of time for (i,p) in enumerate(pipes["in"]) - id = 2*nworkers+1 + i - id1 = 3*nworkers+1 + i - @spawnat id run(pipeline(`sleep 100000000`, p)) + id = 1*nworkers+1 + i + id1 = 2*nworkers+1 + i + @spawnat id run(pipeline(`sleep 100000000`; stdout=p)) @spawnat id1 run(pipeline(`$program`; stdin=p)) end @@ -401,7 +401,7 @@ mutable struct GAExternal return nothing end for (i,p) in enumerate(pipes["out"]) - v = nworkers+1 + i + v = 3*nworkers+1 + i @spawnat v spawn_readpipes(p) end From 7cdac16ba38b6658a4c2613d9b0eddc181d59392 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Fri, 27 Mar 2020 13:57:09 +0100 Subject: [PATCH 28/51] minor revisions --- src/ga.jl | 82 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 32 deletions(-) diff --git a/src/ga.jl b/src/ga.jl index 7b4de77..9438784 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -76,12 +76,6 @@ function ga( objfun ::Function , pars[:tol ] = tol pars[:backuptime ] = isbackup ? backuptime : Inf - # create folder for backup files - if !isdir("backup-files") - mkdir("backup-files") - println("folder created") - end - # choose run method depending if it's parallel # or serial processing if parallel @@ -173,7 +167,12 @@ function generations( objfun ::Function , elitism = elitism_false end - + # create folder for backup files + if !isdir("backup-files") + mkdir("backup-files") + println("folder created") + end + t_ref = time() # Generate and evaluate offspring for iter in 1:pars[:iterations] @@ -236,6 +235,48 @@ function generations_parallel( objfun ::Function full_pop = Vector{Individual}(undef, 2*N) full_fit = Vector{Float64 }(undef, 2*N) + # Elitism + # When true, always picks N best individuals from the full population + # (parents+offspring), which is size 2*N. + # When false, does everything randomly + function elitism_true() + @inbounds begin + full_pop[ 1: N] = population + full_pop[N+1:2*N] = offspring + full_fit = objfun.(full_pop) + fitidx = sortperm(full_fit) + end + for i in 1:N + @inbounds begin + population[i] = full_pop[fitidx[i]] + fitness[i] = full_fit[fitidx[i]] + end + end + return nothing + end + function elitism_false() + for i in 1:N + @inbounds begin + population[i] = offspring[i] + fitness[i] = objfun(population[i]) + end + end + return nothing + end + + if pars[:ϵ] + elitism = elitism_true + else + elitism = elitism_false + end + + # create folder for backup files + println("trying to create backup folder...") + if !isdir("backup-files") + mkdir("backup-files") + end + println("created") + t_ref = time() # Generate and evaluate offspring for iter in 1:pars[:iterations] @@ -274,31 +315,8 @@ function generations_parallel( objfun ::Function @inbounds mutate(offspring[i], pars[:mutationRate]) end - # Elitism - # When true, always picks N best individuals from the - # full population (parents+offspring), which is size 2*N. - # When false, does everything randomly - if pars[:ϵ] - @inbounds begin - full_pop[1 : N] = population - full_pop[1+N:2*N] = offspring - full_fit = objfun.(full_pop) - fitidx = sortperm(full_fit) - end - for i in 1:N - @inbounds begin - population[i] = full_pop[fitidx[i]] - fitness[i] = full_fit[fitidx[i]] - end - end - else - for i in 1:N - @inbounds begin - population[i] = offspring[i] - fitness[i] = objfun(population[i]) - end - end - end + elitism() + end return nothing From 907936fc376edffccaa918238e6814beb1e271d1 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Fri, 27 Mar 2020 16:25:17 +0100 Subject: [PATCH 29/51] Fixed bugs There was a problem regarding the creation of the backup folder in external computers. Now it is fixed and the backup process works in a cluster as well. --- src/backup.jl | 9 +++++---- src/backup.jl~ | 18 ++++-------------- src/ga.jl | 24 +++++++++--------------- 3 files changed, 18 insertions(+), 33 deletions(-) diff --git a/src/backup.jl b/src/backup.jl index 001af06..44fe468 100644 --- a/src/backup.jl +++ b/src/backup.jl @@ -37,14 +37,14 @@ function backup(f ::IOStream, gene ::BinaryGene) return nothing end -function backup(ngens ::Int64, chrom ::Vector{Individual}, - file ::AbstractString) +function backup(ngens ::Int64, tgens ::Int64, + chrom ::Vector{Individual}, file ::AbstractString) file = "backup-files/$file" chromossome = chrom psize = length(chromossome ) gsize = length(chromossome[1]) open(file, "w") do f - write(f, ngens, psize, gsize) + write(f, ngens, tgens, psize, gsize) for i in chromossome for j in i backup(f, j) @@ -60,6 +60,7 @@ function reverse_backup(filename ::AbstractString) f = open(filename, "r") ngens = read(f, Int64) + tgens = read(f, Int64) popsize = read(f, Int64) genesize = read(f, Int64) population = Vector{Individual}(undef, popsize) @@ -103,5 +104,5 @@ function reverse_backup(filename ::AbstractString) end close(f) - return ngens, population + return ngens, tgens, population end diff --git a/src/backup.jl~ b/src/backup.jl~ index df04920..001af06 100644 --- a/src/backup.jl~ +++ b/src/backup.jl~ @@ -37,11 +37,12 @@ function backup(f ::IOStream, gene ::BinaryGene) return nothing end -function backup(ngens ::Int64, chrom ::Vector{Individual}) - chromossome = copy(chrom) +function backup(ngens ::Int64, chrom ::Vector{Individual}, + file ::AbstractString) + file = "backup-files/$file" + chromossome = chrom psize = length(chromossome ) gsize = length(chromossome[1]) - file = "Backup_GA_$(now())" open(file, "w") do f write(f, ngens, psize, gsize) for i in chromossome @@ -53,17 +54,6 @@ function backup(ngens ::Int64, chrom ::Vector{Individual}) return nothing end -function backup(ngens ::Vector{Int64}, chrom ::Vector{Individual}) - return backup(ngens[1], chrom) -end - -function backup( ngens ::DArray{Individual,1,Vector{Individual}} , - pop ::DArray{Individual,1,Vector{Individual}} ) - population = convert(Vector{Individual}, pop) - generations = findmin(ngens)[1] - return backup(generations, population) -end - #################################################################### function reverse_backup(filename ::AbstractString) diff --git a/src/ga.jl b/src/ga.jl index 9438784..8308ca1 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -84,6 +84,14 @@ function ga( objfun ::Function , else works = piping.avail_workers end + if isbackup + for w in works + if !remotecall_fetch(isdir, w, "backup-files") + remotecall_fetch(mkdir, w, "backup-files") + println("folder created") + end + end + end # create distributed arrays for parallel processing population = distribute(population;procs=works) @@ -166,12 +174,6 @@ function generations( objfun ::Function , else elitism = elitism_false end - - # create folder for backup files - if !isdir("backup-files") - mkdir("backup-files") - println("folder created") - end t_ref = time() # Generate and evaluate offspring @@ -223,7 +225,6 @@ function generations_parallel( objfun ::Function popul ::DArray{Individual,1,Vector{Individual}} , fit ::DArray{Float64,1,Vector{Float64}} , pars ::Dict{Symbol,Any} ) - println("made it here") # Variable initialization population = popul[:L] fitness = fit[:L] @@ -270,13 +271,6 @@ function generations_parallel( objfun ::Function elitism = elitism_false end - # create folder for backup files - println("trying to create backup folder...") - if !isdir("backup-files") - mkdir("backup-files") - end - println("created") - t_ref = time() # Generate and evaluate offspring for iter in 1:pars[:iterations] @@ -285,7 +279,7 @@ function generations_parallel( objfun ::Function dt = time() - t_ref if dt > pars[:backuptime] file = "Backup_GA_worker$(myid())" - backup(iter, population, file) + backup(iter, pars[:iterations], population, file) t_ref = time() end From 0f7ac0a066a5cb067675fd8cc67a46abfa7e7b3c Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Wed, 1 Apr 2020 12:01:21 +0200 Subject: [PATCH 30/51] minor revisions --- src/backup.jl | 22 ++++++++++++++++++++-- src/structs.jl | 1 - 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/backup.jl b/src/backup.jl index 44fe468..960aad5 100644 --- a/src/backup.jl +++ b/src/backup.jl @@ -56,8 +56,8 @@ end #################################################################### -function reverse_backup(filename ::AbstractString) - f = open(filename, "r") +function reverse_backup(file ::AbstractString) + f = open(file, "r") ngens = read(f, Int64) tgens = read(f, Int64) @@ -106,3 +106,21 @@ function reverse_backup(filename ::AbstractString) close(f) return ngens, tgens, population end + +function reverse_backup(files ::Vector{<:AbstractString}) + tgens = Vector{Int64 }(undef, length(files)) + gens = Vector{Int64 }(undef, length(files)) + pop = Vector{Individual}(undef, 0 ) + + for (i,f) in enumerate(files) + ngen, tgen, popul = reverse_backup(f) + gens[i] = ngen + tgens[i] = tgen + append!(pop, popul) + end + + min_gens = findmin( gens)[1] + tot_gens = findmax(tgens)[1] + + return min_gens, tot_gens, pop +end diff --git a/src/structs.jl b/src/structs.jl index 5ad7200..3f3bf95 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -413,7 +413,6 @@ mutable struct GAExternal remotecall_fetch(rm, id, p) end end - return nothing end atexit(external_atexit) From 16bb48a4b4f2b4dc18400763e39bdf6ed1d3e34c Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Wed, 1 Apr 2020 12:03:55 +0200 Subject: [PATCH 31/51] added a function to help set up local computer or clusters for parallel runs of the Genetic Algorithm --- src/structs.jl | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/structs.jl b/src/structs.jl index 3f3bf95..ee127b5 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -420,3 +420,35 @@ mutable struct GAExternal return new(program, pin, pout, avail_workers, parallel) end end + +#################################################################### + +function distributed_ga( ; + localcpu ::Int64 = Sys.CPU_THREADS , + cluster ::Vector{<:Tuple} = [()] , + dir ::AbstractString = pwd() ) + + nworkers = localcpu + if cluster != [()] + for i in cluster + nworkers += i[2] + end + end + + @eval using Distributed + for i in 1:4 + if localcpu != 0 + addprocs(localcpu) + end + if cluster != [()] + addprocs(cluster, dir=dir) + end + end + + @eval @everywhere using Distributed + @eval @everywhere using DistributedArrays + @eval @everywhere using DistributedArrays.SPMD + @eval @everywhere using Evolutionary + + return nworkers +end From e88dd28d277af5d07cbb1e7c83d11b3629508fcd Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Wed, 1 Apr 2020 14:27:34 +0200 Subject: [PATCH 32/51] improved documentation --- src/backup.jl | 47 ++++++++++++++++++++++++++++++++++++++++++-- src/ga.jl | 51 ++++++++++++++++++++++++++++++------------------ src/mutations.jl | 2 +- src/structs.jl | 17 ++++++++++++---- 4 files changed, 91 insertions(+), 26 deletions(-) diff --git a/src/backup.jl b/src/backup.jl index 960aad5..add7e98 100644 --- a/src/backup.jl +++ b/src/backup.jl @@ -1,8 +1,19 @@ +##### backup.jl ##### + +# In this file you'll find backup and reverse backup strategies for the +# Genetic Algorithm. + +#################################################################### export backup, reverse_backup #################################################################### +""" + backup(f ::IOStream, gene ::IntegerGene) + +Writes the current state of a `IntegerGene` structure to a buffer. +""" function backup(f ::IOStream, gene ::IntegerGene) write(f, 'I') write(f, Int8(length(gene.value))) @@ -13,6 +24,11 @@ function backup(f ::IOStream, gene ::IntegerGene) return nothing end +""" + backup(f ::IOStream, gene ::FloatGene) + +Writes the current state of a `FloatGene` structure to a buffer. +""" function backup(f ::IOStream, gene ::FloatGene) write(f, 'F') write(f, gene.m) @@ -30,6 +46,11 @@ function backup(f ::IOStream, gene ::FloatGene) return nothing end +""" + backup(f ::IOStream, gene ::BinaryGene) + +Writes the current state of a `BinaryGene` structure to a buffer. +""" function backup(f ::IOStream, gene ::BinaryGene) write(f, 'B') write(f, gene.value) @@ -37,8 +58,18 @@ function backup(f ::IOStream, gene ::BinaryGene) return nothing end -function backup(ngens ::Int64, tgens ::Int64, - chrom ::Vector{Individual}, file ::AbstractString) +""" + backup( ngens ::Int64 , + tgens ::Int64 , + chrom ::Vector{Individual} , + file ::AbstractString ) + +Writes number of generations `ngens`, total numberof generations `tgens` and the population `chrom` into file `file`. Always writes to folder `backup-files` that is created, if inexistent, inside the `ga` function. +""" +function backup( ngens ::Int64 , + tgens ::Int64 , + chrom ::Vector{Individual} , + file ::AbstractString ) file = "backup-files/$file" chromossome = chrom psize = length(chromossome ) @@ -56,6 +87,11 @@ end #################################################################### +""" + reverse_backup(file ::AbstractString) + +Reads backup file `file` and saves the number of generations run, the total number of generations supposed to run and the population into variables for later continuing the Genetic Algorithm. +""" function reverse_backup(file ::AbstractString) f = open(file, "r") @@ -107,6 +143,13 @@ function reverse_backup(file ::AbstractString) return ngens, tgens, population end +""" + reverse_backup(files ::Vector{<:AbstractString}) + +This should be used only for backup files of a parallel run, since each worker writes its own backup file. + +Reads backup files `files` and returns the number of generations run in the slowest worker, the total number of generations supposed to be run and the entire population. +""" function reverse_backup(files ::Vector{<:AbstractString}) tgens = Vector{Int64 }(undef, length(files)) gens = Vector{Int64 }(undef, length(files)) diff --git a/src/ga.jl b/src/ga.jl index 8308ca1..fd6d266 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -5,7 +5,6 @@ #################################################################### export ga -const ch = Channel{Bool}(1) #################################################################### @@ -22,23 +21,37 @@ const ch = Channel{Bool}(1) # ϵ : Boolean to decide if the N best ones will surely survive or # it's all random """ - ga( objfun ::Function , - initpopulation ::Vector{<:AbstractGene} , - populationSize ::Int64 ; - lowerBounds ::Union{Nothing, Vector } = nothing , - upperBounds ::Union{Nothing, Vector } = nothing , - crossoverRate ::Float64 = 0.5 , - mutationRate ::Float64 = 0.5 , - ϵ ::Real = 0 , - iterations ::Integer = 100 , - tol ::Real = 0.0 , - tolIter ::Int64 = 10 , - verbose ::Bool = false , - debug ::Bool = false , - interim ::Bool = false , - parallel ::Bool = false ) - -Runs the Genetic Algorithm using the objective function `objfun`, the initial population `initpopulation` and the population size `populationSize`. `objfun` is the function to MINIMIZE. + ga( objfun ::Function , + population ::Vector{Individual} ; + crossoverRate ::Float64 = 0.5 , + mutationRate ::Float64 = 0.5 , + ϵ ::Bool = true , + iterations ::Integer = 100 , + tol ::Real = 0.0 , + parallel ::Bool = false , + piping ::Union{Nothing,GAExternal} = nothing , + nworkers ::Integer = Sys.CPU_THREADS , + output ::AbstractString = "" , + showprint ::Bool = true , + isbackup ::Bool = true , + backuptime ::Float64 = 1.0 ) + +Runs the Genetic Algorithm using the objective function `objfun`, the initial population `initpopulation` and the population size `populationSize`. `objfun` is the function to MINIMIZE. The table below shows how the optional arguments behave: + +| Optional Argument | Behaviour | +|-------------------|-----------| +| crossoverRate | rate in which the population mates | +| mutationRate | rate in which a gene mutates | +| ϵ | set elitism to true or false | +| iterations | number of iterations to be run | +| tol | objective function tolerance | +| parallel | sets parallelization to true or false | +| piping | if piping is different from `nothing`, uses external program | +| nworkers | number of cores to be used. Only works if parallel is set to true | +| output | writes optimization output to a file | +| showprint | set screen output to true or false | +| isbackup | sets backup to true or false | +| backuptime | backup interval in seconds| """ function ga( objfun ::Function , population ::Vector{Individual} ; @@ -53,7 +66,7 @@ function ga( objfun ::Function , output ::AbstractString = "" , showprint ::Bool = true , isbackup ::Bool = true , - backuptime ::Float64 = 0.5 ) + backuptime ::Float64 = 1.0 ) # Initialize population N = length(population) diff --git a/src/mutations.jl b/src/mutations.jl index 2350a70..250d9b8 100644 --- a/src/mutations.jl +++ b/src/mutations.jl @@ -240,7 +240,7 @@ function mutate(gene ::FloatGene) end """ - mutate(chromossome ::Vector{<:AbstractGene}) + mutate(chromossome ::Vector{<:AbstractGene}, rate ::Float64) Mutates each entry of `chromossome` according to the mutations chosen. """ diff --git a/src/structs.jl b/src/structs.jl index ee127b5..3cffc68 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -340,11 +340,12 @@ end #################################################################### """ - function GAExternal( program ::AbstractString , - pipename ::AbstractString ; - parallel ::Bool = false ) + function GAExternal( program ::AbstractString , + pipename ::AbstractString ; + nworkers ::Int64 = Sys.CPU_THREADS , + parallel ::Bool = false ) -Creates communication pipes for the external program `program`. If `parallel` is `true`, then, considering N workers available, N pipes for reading and N pipes for writing will be created. `pipename` is just a handle for the name of the pipes. If `pipename` is `pipe`, then the pipe names will be `pipe_in` and `pipe_out` for `parallel` as false and `pipe_inn` and `pipe_outn` for `parallel` as true, such as `n` being one of the N workers. +Creates communication pipes for the external program `program`. If `parallel` is `true`, then, considering N workers available, N pipes for reading and N pipes for writing will be created. `pipename` is just a handle for the name of the pipes. If `pipename` is `pipe`, then the pipe names will be `pipe_in` and `pipe_out` for `parallel` as false and `pipe_inn` and `pipe_outn` for `parallel` as true, with `n` being one of the N workers. `nworkers` is the number of cores to be used, including the number of cores of a remote computer. `parallel` sets the the external program to run in several workers. """ mutable struct GAExternal program ::AbstractString @@ -423,6 +424,14 @@ end #################################################################### +""" + distributed_ga( ; + localcpu ::Int64 = Sys.CPU_THREADS , + cluster ::Vector{<:Tuple} = [()] , + dir ::AbstractString = pwd() ) + +Function to help set up the local computer or a cluster for parallel run of the Genetic Algorithm. `localcpu` is the number of cores to be used in your local computer. `cluster` is a vector of machine specifications. To know more about this, type `?addprocs` in the command prompt after importing the `Distributed` package. `dir` is the directory where you want julia to run in each remote computer. +""" function distributed_ga( ; localcpu ::Int64 = Sys.CPU_THREADS , cluster ::Vector{<:Tuple} = [()] , From a7be461840acd3f514a0705d087990476e0478f3 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Mon, 6 Apr 2020 11:39:14 +0200 Subject: [PATCH 33/51] Reduced the use of the objective function For each iteration the objective function was being calculated 2*N times (parents+offspring). The thing is that N calculations were already performed in the previous iteration, so only the offspring needs to be evaluated in each iteration. --- src/ga.jl | 60 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/src/ga.jl b/src/ga.jl index fd6d266..b1197ce 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -131,7 +131,7 @@ function ga( objfun ::Function , end # result presentation - data_presentation( population[bestIndividual], iterations, bestFitness, + data_presentation( population[bestIndividual], N, iterations, bestFitness, isfit, elapsed_time, showprint, output ) return population[bestIndividual], bestFitness @@ -153,21 +153,28 @@ function generations( objfun ::Function , full_pop = Vector{Individual}(undef, 2*N) full_fit = Vector{Float64 }(undef, 2*N) + for i in 1:N + fitness[i] = objfun(population[i]) + full_fit[i] = fitness[i] + full_pop[i] = population[i] + end + # Elitism # When true, always picks N best individuals from the full population # (parents+offspring), which is size 2*N. # When false, does everything randomly function elitism_true() @inbounds begin - full_pop[ 1: N] = population full_pop[N+1:2*N] = offspring - full_fit = objfun.(full_pop) + full_fit[N+1:2*N] = objfun.(offspring) fitidx = sortperm(full_fit) end for i in 1:N @inbounds begin population[i] = full_pop[fitidx[i]] fitness[i] = full_fit[fitidx[i]] + full_fit[i] = fitness[i] + full_pop[i] = population[i] end end return nothing @@ -249,16 +256,19 @@ function generations_parallel( objfun ::Function full_pop = Vector{Individual}(undef, 2*N) full_fit = Vector{Float64 }(undef, 2*N) + fitness = objfun.(population) + full_fit[1:N] = fitness + full_pop[1:N] = population + # Elitism # When true, always picks N best individuals from the full population # (parents+offspring), which is size 2*N. - # When false, does everything randomly + # When false, only uses offspring function elitism_true() @inbounds begin - full_pop[ 1: N] = population full_pop[N+1:2*N] = offspring - full_fit = objfun.(full_pop) - fitidx = sortperm(full_fit) + full_fit[N+1:2*N] = objfun.(offspring) + fitidx = sortperm(full_fit) end for i in 1:N @inbounds begin @@ -266,6 +276,10 @@ function generations_parallel( objfun ::Function fitness[i] = full_fit[fitidx[i]] end end + @inbounds begin + full_fit[1:N] = copy(fitness) + full_pop[1:N] = deepcopy(population) + end return nothing end function elitism_false() @@ -312,7 +326,8 @@ function generations_parallel( objfun ::Function else @inbounds begin offspring[i], offspring[j] = - population[selected[i]], population[selected[j]] + deepcopy(population[selected[i]]), + deepcopy(population[selected[j]]) end end end @@ -332,6 +347,7 @@ end #################################################################### function data_presentation( individual ::Individual , + popsize ::Integer , generations ::Integer , bestFitness ::Float64 , isfit ::Bool , @@ -385,24 +401,28 @@ function data_presentation( individual ::Individual , table *= @eval @sprintf($i_str, $j, $s_val) end - printstyled("\nRESULTS :\n", color=:bold) - println("number of generations = " * string(generations)) - println("best Fitness = " * string(bestFitness)) - println("Run time = $optim_time seconds") - println("") - printstyled("GENES OF BEST INDIVIDUAL :\n", color=:bold) - println(table) - println("") - if isfit - printstyled("OPTIMIZATION SUCCESSFUL\n" , color=:bold) - else - printstyled("OPTIMIZATION UNSUCCESSFUL\n", color=:bold) + if showprint + printstyled("\nRESULTS :\n", color=:bold) + println("population size = $popsize" ) + println("number of generations = $generations" ) + println("best Fitness = $bestFitness" ) + println("Run time = $optim_time seconds") + println("") + printstyled("GENES OF BEST INDIVIDUAL :\n", color=:bold) + println(table) + println("") + if isfit + printstyled("OPTIMIZATION SUCCESSFUL\n" , color=:bold) + else + printstyled("OPTIMIZATION UNSUCCESSFUL\n", color=:bold) + end end if output != "" open(output, "w") do f write(f, "Result File of Genetic Algorithm, $(now())\n\n") write(f, "RESULTS :\n") + write(f, string("population size = ", popsize , "\n")) write(f, string("number of generations = ", generations, "\n")) write(f, string("best Fitness = ", bestFitness, "\n")) write(f, "Run time = $optim_time seconds\n") From 62ba6a52c44aaa3ed8cc53feb7b5af8658cd2a9b Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Sun, 21 Jun 2020 22:38:09 +0200 Subject: [PATCH 34/51] updates --- src/structs.jl | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/structs.jl b/src/structs.jl index 3cffc68..ba21fb9 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -8,7 +8,7 @@ export BinaryGene, IntegerGene, FloatGene export Crossover, Selection export selection, crossover, bin -export GAExternal +export GAExternal, distributed_ga #################################################################### @@ -349,8 +349,8 @@ Creates communication pipes for the external program `program`. If `parallel` is """ mutable struct GAExternal program ::AbstractString - pipes_in ::DArray{String,1,Vector{String}} - pipes_out ::DArray{String,1,Vector{String}} + pipes_in ::Union{Vector{<:AbstractString},DArray{String,1,Vector{String}}} + pipes_out ::Union{Vector{<:AbstractString},DArray{String,1,Vector{String}}} avail_workers ::Vector{Int64} parallel ::Bool @@ -373,6 +373,8 @@ mutable struct GAExternal remotecall_fetch(run, p, `mkfifo $f`) end end + pin = distribute(pipes["in" ]; procs=avail_workers) + pout = distribute(pipes["out"]; procs=avail_workers) else # create one pipe for reading and another for writing for i in ["in","out"] @@ -381,13 +383,15 @@ mutable struct GAExternal rm(f, force=true) run(`mkfifo $f`) end + pin = pipes["in"] + pout = pipes["out"] end - pin = distribute(pipes["in" ]; procs=avail_workers) - pout = distribute(pipes["out"]; procs=avail_workers) + #pin = distribute(pipes["in" ]; procs=avail_workers) + #pout = distribute(pipes["out"]; procs=avail_workers) # activate writing pipes for a big amount of time - for (i,p) in enumerate(pipes["in"]) + for (i,p) in enumerate(pin) id = 1*nworkers+1 + i id1 = 2*nworkers+1 + i @spawnat id run(pipeline(`sleep 100000000`; stdout=p)) @@ -401,7 +405,7 @@ mutable struct GAExternal f = open(pipe, "r") return nothing end - for (i,p) in enumerate(pipes["out"]) + for (i,p) in enumerate(pout) v = 3*nworkers+1 + i @spawnat v spawn_readpipes(p) end From 0e44d0161f12a9855fdedbff178b2fc9128846cd Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Wed, 13 Jan 2021 14:44:02 +0000 Subject: [PATCH 35/51] minor changes in function documentations --- src/structs.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structs.jl b/src/structs.jl index ba21fb9..664cf11 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -345,7 +345,7 @@ end nworkers ::Int64 = Sys.CPU_THREADS , parallel ::Bool = false ) -Creates communication pipes for the external program `program`. If `parallel` is `true`, then, considering N workers available, N pipes for reading and N pipes for writing will be created. `pipename` is just a handle for the name of the pipes. If `pipename` is `pipe`, then the pipe names will be `pipe_in` and `pipe_out` for `parallel` as false and `pipe_inn` and `pipe_outn` for `parallel` as true, with `n` being one of the N workers. `nworkers` is the number of cores to be used, including the number of cores of a remote computer. `parallel` sets the the external program to run in several workers. +Creates communication pipes for the external program `program`. If `parallel` is `true`, then, considering N workers available, N pipes for reading and N pipes for writing will be created. `pipename` is just a handle for the name of the pipes. If `pipename` is `pipe`, then the pipe names will be `pipe_in` and `pipe_out` if `parallel=false` and `pipe_inn` and `pipe_outn` if `parallel=true`, with `n` being one of the N workers. `nworkers` is the number of cores to be used, including the number of cores of a remote computer. `parallel` sets the the external program to run in several workers. """ mutable struct GAExternal program ::AbstractString From 30c649115b9628d4b8f95608882e472ac79ff8b9 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Wed, 13 Jan 2021 14:50:42 +0000 Subject: [PATCH 36/51] minor changes to function documentations --- src/ga.jl | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/ga.jl b/src/ga.jl index b1197ce..807e73c 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -8,18 +8,6 @@ export ga #################################################################### -# Genetic Algorithms -# ================== -# 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. -# 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. -# mutationRate : Probability of chromosome to be mutated -# ϵ : Boolean to decide if the N best ones will surely survive or -# it's all random """ ga( objfun ::Function , population ::Vector{Individual} ; @@ -36,7 +24,7 @@ export ga isbackup ::Bool = true , backuptime ::Float64 = 1.0 ) -Runs the Genetic Algorithm using the objective function `objfun`, the initial population `initpopulation` and the population size `populationSize`. `objfun` is the function to MINIMIZE. The table below shows how the optional arguments behave: +Runs the Genetic Algorithm using the objective function `objfun` and the initial population `population`. `objfun` is the function to MINIMIZE. The table below shows how the optional arguments behave: | Optional Argument | Behaviour | |-------------------|-----------| From d400bed8ae5b725cea14cd35a5928911fa51c6c2 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Tue, 19 Jan 2021 18:32:56 +0000 Subject: [PATCH 37/51] Added initialization function Since most of the optimization problems, before starting the actual optimization runs, need to run some initialization scripts/functions, this function was created to ease the pain of those initializations. --- src/structs.jl | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/structs.jl b/src/structs.jl index 664cf11..dad3fb8 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -428,6 +428,45 @@ end #################################################################### +""" + ext_init(gaext ::GAExternal, codeline ::AbstractString) + +Function to help initialize the external program. + +When using external programs, you usually have an initial script to be run before performing the genetic algorithm. After creating the `GAExternal` structure, just send it to this function along with the line of code to initialize the optimization process. + +## Example + +In case of AMPL, if we have a script with the initial model called `init.ampl`, then we could do: + +``` +ext = GAExternal("ampl", "pipe") +ext_init(ext, "include init.ampl;") +``` +""" +function ext_init(gaext ::GAExternal, codeline ::AbstractString) + function ext(gaext ::GAExternal) + if gaext.parallel + pin = gaext.pipes_in[:L] + else + pin = gaext.pipes_in + end + for p in pin + open(p, "w") do file + write(file, codeline) + end + end + end + if gaext.parallel + spmd(ext, gaext, pids=gaext.avail_workers) + else + ext(gaext) + end + return nothing +end + +#################################################################### + """ distributed_ga( ; localcpu ::Int64 = Sys.CPU_THREADS , From 4317816f2a2fb14e978c2a27e455eaf1f7ef33ee Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Tue, 19 Jan 2021 20:13:11 +0000 Subject: [PATCH 38/51] had forgotten to export ext_init function --- src/structs.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structs.jl b/src/structs.jl index dad3fb8..898bf67 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -8,7 +8,7 @@ export BinaryGene, IntegerGene, FloatGene export Crossover, Selection export selection, crossover, bin -export GAExternal, distributed_ga +export GAExternal, ext_init, distributed_ga #################################################################### From 9405af58d6c5e04a4a7ca579418e3a00ecaa72f4 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Fri, 5 Feb 2021 12:55:40 +0000 Subject: [PATCH 39/51] minor modifications on memory allocations --- src/ga.jl | 26 +++++++++++--------------- src/structs.jl | 34 +++++++++++++++++----------------- 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/src/ga.jl b/src/ga.jl index 807e73c..7e2738b 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -64,7 +64,7 @@ function ga( objfun ::Function , if piping == nothing func = objfun else - func = (x) -> objfun(x, piping) + func(x) = objfun(x, piping) end # save optional arguments in a dictionary @@ -142,7 +142,7 @@ function generations( objfun ::Function , full_fit = Vector{Float64 }(undef, 2*N) for i in 1:N - fitness[i] = objfun(population[i]) + fitness[i] = objfun.(population[i]) full_fit[i] = fitness[i] full_pop[i] = population[i] end @@ -150,29 +150,25 @@ function generations( objfun ::Function , # Elitism # When true, always picks N best individuals from the full population # (parents+offspring), which is size 2*N. - # When false, does everything randomly + # When false, only uses offspring function elitism_true() @inbounds begin full_pop[N+1:2*N] = offspring full_fit[N+1:2*N] = objfun.(offspring) fitidx = sortperm(full_fit) end - for i in 1:N - @inbounds begin - population[i] = full_pop[fitidx[i]] - fitness[i] = full_fit[fitidx[i]] - full_fit[i] = fitness[i] - full_pop[i] = population[i] - end + @inbounds begin + population[1:N] = full_pop[fitidx[1:N]] + fitness[1:N] = full_fit[fitidx[1:N]] + full_fit[1:N] = fitness[1:N] + full_pop[1:N] = population[1:N] end return nothing end function elitism_false() - for i in 1:N - @inbounds begin - population[i] = offspring[i] - fitness[i] = objfun(population[i]) - end + @inbounds begin + population[1:N] = offspring[1:N] + fitness[1:N] = objfun.(population[1:N]) end return nothing end diff --git a/src/structs.jl b/src/structs.jl index 898bf67..a4c6fdf 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -45,7 +45,7 @@ BinaryGene() = BinaryGene(rand(Bool), "bin") """ IntegerGene(value ::BitVector, name ::AbstractString) -Creates a `IntegerGene` structure. This gene represents an integer variable as `BitVector`. To convert the `BitVector` in an integer, just look at the `bin` function from this package by typing `?bin` on the command prompt. `name` is a stringt that represents the name of the variable. It's needed for result presentation purposes. +Creates a `IntegerGene` structure. This gene represents an integer variable as `BitVector`. To convert the `BitVector` in an integer, just look at the `bin` function from this package by typing `?bin` on the command prompt. `name` is a string that represents the name of the variable. It's needed for result presentation purposes. """ mutable struct IntegerGene <: AbstractGene value ::BitVector @@ -109,7 +109,7 @@ end """ FloatGene(value ::Vector{Float64}, range ::Vector{Float64}, m ::Int64) -Creates a `FloatGene` structure. `value` is a vector with the variables to be changed. `range` is a vector with the minimum and maximum values a variable can take. `m` is just a parameter that changes how much in a mutation the variables change, the bigger the value, the bigger the change in each mutation. If the range of a variable is 0.5, then the biggest mutation a variable can suffer in one iteration is 0.5 for instance. +Creates a `FloatGene` structure. `value` is a vector with the variables to be changed. `range` is a vector with the minimum and maximum values a variable can take. `m` is just a parameter that changes how much in a mutation the variables change, the bigger the value, the bigger the change in each mutation. If the range of a variable is 0.5, then the biggest mutation a variable can suffer in one iteration is 0.5, for instance. """ mutable struct FloatGene <: AbstractGene value ::Vector{Float64} @@ -117,10 +117,10 @@ mutable struct FloatGene <: AbstractGene m ::Int64 name ::Vector{AbstractString} - function FloatGene( value ::Vector{Float64} , - range ::Vector{Float64} , - m ::Int64 , - name ::Vector{String} ) + function FloatGene( value ::Vector{Float64} , + range ::Vector{Float64} , + m ::Int64 , + name ::Vector{String} ) if length(value) != length(range) error("vectors must have the same length") end @@ -128,10 +128,10 @@ mutable struct FloatGene <: AbstractGene end end -function FloatGene( value ::Vector{Float64} , - range ::Vector{Float64} , - name ::Vector{String} ; - m ::Int64 = 20 ) +function FloatGene( value ::Vector{Float64} , + range ::Vector{Float64} , + name ::Vector{String} ; + m ::Int64 = 20 ) return FloatGene(value, range, m, name) end @@ -152,10 +152,10 @@ end Creates a `FloatGene` structure. Handy for creating a vector of real numbers with the same range. """ -function FloatGene( value ::Vector{Float64} , - range ::Float64 , - name ::Vector{String} ; - m ::Int64 = 20 ) +function FloatGene( value ::Vector{Float64} , + range ::Float64 , + name ::Vector{String} ; + m ::Int64 = 20 ) vec = Float64[range for i in value] return FloatGene(value, vec, m, name) end @@ -165,9 +165,9 @@ end Creates a `FloatGene` structure. Handy for creating a vector of real numbers with a random range. """ -function FloatGene( value ::Vector{Float64} , - name ::Vector{String} ; - m ::Int64 = 20 ) +function FloatGene( value ::Vector{Float64} , + name ::Vector{String} ; + m ::Int64 = 20 ) range = rand(Float64, length(value)) return FloatGene(value, range, m, name) end From eca208677fe4949a2b780518c4cd77de454f552b Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Fri, 5 Feb 2021 12:56:06 +0000 Subject: [PATCH 40/51] added test for parallel code --- test/midpoint.jl | 36 ++++++++++++++++++++++++++++++++++++ test/midpoint.jl~ | 5 +++++ 2 files changed, 41 insertions(+) create mode 100644 test/midpoint.jl create mode 100644 test/midpoint.jl~ diff --git a/test/midpoint.jl b/test/midpoint.jl new file mode 100644 index 0000000..63539d3 --- /dev/null +++ b/test/midpoint.jl @@ -0,0 +1,36 @@ + +using Evolutionary + +nworks = 2 +distributed_ga(localcpu=nworks) + +@everywhere using SpecialFunctions +@everywhere using DataAnalysis + +x = -5.0:0.01:5.0 +y = erf.(x) + +gene = IntegerGene(10, "index") + +npop = 100 * nworks +pop = Vector{Individual}(undef, npop) + +for i in 1:npop + pop[i] = AbstractGene[gene] +end + +@everywhere IntegerGene(:FM) +@everywhere Crossover(:SPX) +@everywhere Selection(:RWS) + +println("Creating objfun function...\n") +@everywhere function objfun(chrom ::Individual) + return abs( bin(chrom[1]) - 501 ) +end + +println("Starting ga...") +bestFit, bestGene = ga( objfun, pop, + parallel = true, + nworkers = nworks) + +nothing diff --git a/test/midpoint.jl~ b/test/midpoint.jl~ new file mode 100644 index 0000000..2693a74 --- /dev/null +++ b/test/midpoint.jl~ @@ -0,0 +1,5 @@ + +using Distributed + +distributed_ga() + From 46168a8714736bf5e46a8055579f5c3ef43fd52c Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Fri, 5 Feb 2021 16:37:26 +0000 Subject: [PATCH 41/51] minor changes --- test/midpoint.jl | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/test/midpoint.jl b/test/midpoint.jl index 63539d3..88bdd27 100644 --- a/test/midpoint.jl +++ b/test/midpoint.jl @@ -4,15 +4,9 @@ using Evolutionary nworks = 2 distributed_ga(localcpu=nworks) -@everywhere using SpecialFunctions -@everywhere using DataAnalysis - -x = -5.0:0.01:5.0 -y = erf.(x) - gene = IntegerGene(10, "index") -npop = 100 * nworks +npop = 10 * nworks pop = Vector{Individual}(undef, npop) for i in 1:npop @@ -29,8 +23,10 @@ println("Creating objfun function...\n") end println("Starting ga...") -bestFit, bestGene = ga( objfun, pop, - parallel = true, - nworkers = nworks) +bestGene, bestFit = ga( objfun, pop, + parallel = true , + nworkers = nworks , + iterations = 10 ) + nothing From 6f5e11d18a02ff45f4741d440c3750bbde159bf1 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Fri, 5 Feb 2021 16:37:35 +0000 Subject: [PATCH 42/51] Fixed memory allocation bug For a long time there was an issue with the fitness array in the parallel calculation function. For some reason said function was not modifying the fitness vector in-place. Now that bug is finally fixed. --- src/ga.jl | 92 +++++++++++++++++++++++++------------------------------ 1 file changed, 41 insertions(+), 51 deletions(-) diff --git a/src/ga.jl b/src/ga.jl index 7e2738b..756e1c7 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -89,7 +89,7 @@ function ga( objfun ::Function , for w in works if !remotecall_fetch(isdir, w, "backup-files") remotecall_fetch(mkdir, w, "backup-files") - println("folder created") + println("backup folder created") end end end @@ -119,7 +119,7 @@ function ga( objfun ::Function , end # result presentation - data_presentation( population[bestIndividual], N, iterations, bestFitness, + data_presentation( population[bestIndividual], N, nworkers, iterations, bestFitness, isfit, elapsed_time, showprint, output ) return population[bestIndividual], bestFitness @@ -134,19 +134,14 @@ function generations( objfun ::Function , pars ::Dict{Symbol,Any} ) # Variable initialization - generations = 1 - bestIndividual = 0 - bestFitness = 0.0 offspring = Vector{Individual}(undef, N) full_pop = Vector{Individual}(undef, 2*N) full_fit = Vector{Float64 }(undef, 2*N) - for i in 1:N - fitness[i] = objfun.(population[i]) - full_fit[i] = fitness[i] - full_pop[i] = population[i] - end - + fitness = objfun.(population) + full_fit[1:N] = copy(fitness) + full_pop[1:N] = deepcopy(population) + # Elitism # When true, always picks N best individuals from the full population # (parents+offspring), which is size 2*N. @@ -158,17 +153,19 @@ function generations( objfun ::Function , fitidx = sortperm(full_fit) end @inbounds begin - population[1:N] = full_pop[fitidx[1:N]] - fitness[1:N] = full_fit[fitidx[1:N]] - full_fit[1:N] = fitness[1:N] - full_pop[1:N] = population[1:N] + population = full_pop[fitidx[1:N]] + fitness = full_fit[fitidx[1:N]] + end + @inbounds begin + full_fit[1:N] = copy(fitness) + full_pop[1:N] = deepcopy(population) end return nothing end function elitism_false() @inbounds begin - population[1:N] = offspring[1:N] - fitness[1:N] = objfun.(population[1:N]) + population = deepcopy(offspring) + fitness = objfun.(population) end return nothing end @@ -230,48 +227,39 @@ function generations_parallel( objfun ::Function fit ::DArray{Float64,1,Vector{Float64}} , pars ::Dict{Symbol,Any} ) # Variable initialization - population = popul[:L] - fitness = fit[:L] - N = length(population) - generations = 1 - bestIndividual = 0 - bestFitness = 0.0 - offspring = Vector{Individual}(undef, N) - full_pop = Vector{Individual}(undef, 2*N) - full_fit = Vector{Float64 }(undef, 2*N) - - fitness = objfun.(population) - full_fit[1:N] = fitness - full_pop[1:N] = population + N = length(popul[:L]) + offspring = Vector{Individual}(undef, N) + full_pop = Vector{Individual}(undef, 2N) + full_fit = Vector{Float64 }(undef, 2N) + + fit[:L][1:N] = objfun.(popul[:L]) + full_fit[1:N] = copy(fit[:L]) + full_pop[1:N] = deepcopy(popul[:L]) # Elitism # When true, always picks N best individuals from the full population - # (parents+offspring), which is size 2*N. + # (parents+offspring), which is size 2N. # When false, only uses offspring function elitism_true() @inbounds begin - full_pop[N+1:2*N] = offspring - full_fit[N+1:2*N] = objfun.(offspring) + full_pop[N+1:2N] = offspring + full_fit[N+1:2N] = objfun.(offspring) fitidx = sortperm(full_fit) end - for i in 1:N - @inbounds begin - population[i] = full_pop[fitidx[i]] - fitness[i] = full_fit[fitidx[i]] - end + @inbounds begin + popul[:L] = full_pop[fitidx[1:N]] + fit[:L] = full_fit[fitidx[1:N]] end @inbounds begin - full_fit[1:N] = copy(fitness) - full_pop[1:N] = deepcopy(population) + full_fit[1:N] = copy(fit[:L]) + full_pop[1:N] = deepcopy(popul[:L]) end return nothing end function elitism_false() - for i in 1:N - @inbounds begin - population[i] = offspring[i] - fitness[i] = objfun(population[i]) - end + @inbounds begin + popul[:L][1:N] = offspring[1:N] + fit[:L][1:N] = objfun.(popul[:L][1:N]) end return nothing end @@ -290,12 +278,12 @@ function generations_parallel( objfun ::Function dt = time() - t_ref if dt > pars[:backuptime] file = "Backup_GA_worker$(myid())" - backup(iter, pars[:iterations], population, file) + backup(iter, pars[:iterations], popul[:L], file) t_ref = time() end # Select offspring - selected = selection(fitness, N) + selected = selection(fit[:L], N) # Perform mating offidx = randperm(N) @@ -304,14 +292,14 @@ function generations_parallel( objfun ::Function if rand() < pars[:crossoverRate] @inbounds begin offspring[i], offspring[j] = - crossover( population[selected[offidx[i]]] , - population[selected[offidx[j]]] ) + crossover( popul[:L][selected[offidx[i]]] , + popul[:L][selected[offidx[j]]] ) end else @inbounds begin offspring[i], offspring[j] = - deepcopy(population[selected[i]]), - deepcopy(population[selected[j]]) + deepcopy(popul[:L][selected[i]]), + deepcopy(popul[:L][selected[j]]) end end end @@ -332,6 +320,7 @@ end function data_presentation( individual ::Individual , popsize ::Integer , + nworkers ::Integer , generations ::Integer , bestFitness ::Float64 , isfit ::Bool , @@ -388,6 +377,7 @@ function data_presentation( individual ::Individual , if showprint printstyled("\nRESULTS :\n", color=:bold) println("population size = $popsize" ) + println("number of threads = $nworkers" ) println("number of generations = $generations" ) println("best Fitness = $bestFitness" ) println("Run time = $optim_time seconds") From 9b707cb19339a6606a582cd3e25805d42e42bca8 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Fri, 12 Feb 2021 20:17:36 +0000 Subject: [PATCH 43/51] Created structure for GA output Now the output of the genetic algorithm is a structure. Now the output is much more complete and more organized to be easily used in post-optimization calculations. --- src/ga.jl | 100 ++++++++++++++++++++++++++---------------------------- 1 file changed, 49 insertions(+), 51 deletions(-) diff --git a/src/ga.jl b/src/ga.jl index 756e1c7..4b9d652 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -6,23 +6,33 @@ export ga +mutable struct OptimResults + N ::Int64 + bestFit ::Float64 + bestInd ::Individual + time ::Float64 + threads ::Int64 + iter ::Int64 + +end + #################################################################### """ - ga( objfun ::Function , - population ::Vector{Individual} ; - crossoverRate ::Float64 = 0.5 , - mutationRate ::Float64 = 0.5 , - ϵ ::Bool = true , - iterations ::Integer = 100 , - tol ::Real = 0.0 , - parallel ::Bool = false , - piping ::Union{Nothing,GAExternal} = nothing , - nworkers ::Integer = Sys.CPU_THREADS , - output ::AbstractString = "" , - showprint ::Bool = true , - isbackup ::Bool = true , - backuptime ::Float64 = 1.0 ) + ga( objfun ::Function , + population ::Vector{Individual} ; + crossoverRate ::Float64 = 0.5 , + mutationRate ::Float64 = 0.5 , + ϵ ::Bool = true , + iterations ::Integer = 100 , + tol ::Real = 0.0 , + parallel ::Bool = false , + piping ::Union{Nothing,GAExternal} = nothing , + nworkers ::Integer = 1 , + output ::AbstractString = "" , + showprint ::Bool = true , + isbackup ::Bool = true , + backuptime ::Float64 = 1.0 ) Runs the Genetic Algorithm using the objective function `objfun` and the initial population `population`. `objfun` is the function to MINIMIZE. The table below shows how the optional arguments behave: @@ -35,26 +45,26 @@ Runs the Genetic Algorithm using the objective function `objfun` and the initial | tol | objective function tolerance | | parallel | sets parallelization to true or false | | piping | if piping is different from `nothing`, uses external program | -| nworkers | number of cores to be used. Only works if parallel is set to true | +| nworkers | number of threads to be used. Only works if parallel is set to true | | output | writes optimization output to a file | | showprint | set screen output to true or false | | isbackup | sets backup to true or false | | backuptime | backup interval in seconds| """ -function ga( objfun ::Function , - population ::Vector{Individual} ; - crossoverRate ::Float64 = 0.5 , - mutationRate ::Float64 = 0.5 , - ϵ ::Bool = true , - iterations ::Integer = 100 , - tol ::Real = 0.0 , - parallel ::Bool = false , - piping ::Union{Nothing,GAExternal} = nothing , - nworkers ::Integer = Sys.CPU_THREADS , - output ::AbstractString = "" , - showprint ::Bool = true , - isbackup ::Bool = true , - backuptime ::Float64 = 1.0 ) +function ga( objfun ::Function , + population ::Vector{Individual} ; + crossoverRate ::Float64 = 0.5 , + mutationRate ::Float64 = 0.5 , + ϵ ::Bool = true , + iterations ::Integer = 100 , + tol ::Real = 0.0 , + parallel ::Bool = false , + piping ::Union{Nothing,GAExternal} = nothing , + nworkers ::Integer = 1 , + output ::AbstractString = "" , + showprint ::Bool = true , + isbackup ::Bool = true , + backuptime ::Float64 = 1.0 ) # Initialize population N = length(population) @@ -112,17 +122,13 @@ function ga( objfun ::Function , end bestFitness, bestIndividual = findmin(fitness) - if bestFitness <= tol - isfit = true - else - isfit = false - end # result presentation data_presentation( population[bestIndividual], N, nworkers, iterations, bestFitness, - isfit, elapsed_time, showprint, output ) + elapsed_time, showprint, output ) - return population[bestIndividual], bestFitness + return OptimResults(N, bestFitness, population[bestIndividual], elapsed_time, + nworkers, iterations) end #################################################################### @@ -323,7 +329,6 @@ function data_presentation( individual ::Individual , nworkers ::Integer , generations ::Integer , bestFitness ::Float64 , - isfit ::Bool , elapsed_time ::Float64 , showprint ::Bool , output ::String ) @@ -385,30 +390,23 @@ function data_presentation( individual ::Individual , printstyled("GENES OF BEST INDIVIDUAL :\n", color=:bold) println(table) println("") - if isfit - printstyled("OPTIMIZATION SUCCESSFUL\n" , color=:bold) - else - printstyled("OPTIMIZATION UNSUCCESSFUL\n", color=:bold) - end + printstyled("OPTIMIZATION FINISHED SUCCESSFULLY\n" , color=:bold) end if output != "" open(output, "w") do f write(f, "Result File of Genetic Algorithm, $(now())\n\n") write(f, "RESULTS :\n") - write(f, string("population size = ", popsize , "\n")) - write(f, string("number of generations = ", generations, "\n")) - write(f, string("best Fitness = ", bestFitness, "\n")) - write(f, "Run time = $optim_time seconds\n") + write(f, string("population size = ", popsize , "\n")) + write(f, string("number of threads = ", nworkers , "\n")) + write(f, string("number of generations = ", generations, "\n")) + write(f, string("best Fitness = ", bestFitness, "\n")) + write(f, string("Run time = ", optim_time , "seconds\n")) write(f, "\n") write(f, "GENES OF BEST INDIVIDUAL :\n") write(f, table) write(f, "\n") - if isfit - write(f, "OPTIMIZATION SUCCESSFUL\n") - else - write(f, "OPTIMIZATION UNSUCCESSFUL\n") - end + write(f, "OPTIMIZATION FINISHED SUCCESSFULLY\n") end end From 10c7bc469b4a94292d00e1b5f6e89a59d7d29cdd Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Fri, 19 Feb 2021 18:42:06 +0000 Subject: [PATCH 44/51] Started implementing boundary functionalities Before this branch the variable boundaries were checked inside the objective function, returning an Inf value when they were not inside boundaries. This was not ideal, since some individuals did not have a chance to improve in further iterations because they were automatically discarded. Now the boundary checking is made inside the mutation functions. In this way, all the individuals have genes that respect the boundaries, reducing the number of objective function evaluations that would be Inf. --- src/mutations.jl | 20 ++++++- src/structs.jl | 148 ++++++++++++++++++++++++++++------------------- 2 files changed, 107 insertions(+), 61 deletions(-) diff --git a/src/mutations.jl b/src/mutations.jl index 250d9b8..d4a52e1 100644 --- a/src/mutations.jl +++ b/src/mutations.jl @@ -226,16 +226,26 @@ Mutates `gene` using Real Valued Mutation. function mutate(gene ::FloatGene) prob = 1.0 / gene.m δ = zeros(gene.m) - for i in 1:length(gene.value) + + function mutation_help(value ::Float64, range ::Float64) for j in 1:gene.m δ[j] = (rand() < prob) ? 2.0^(-j) : 0.0 end if rand() > 0.5 - gene.value[i] += sum(δ)*gene.range[i] + value += sum(δ)*range else - gene.value[i] -= sum(δ)*gene.range[i] + value -= sum(δ)*range end + return value end + + for i in 1:length(gene.value) + gene.value[i] = mutation_help(gene.value[i], gene.range[i]) + while !isbound(gene, i) + gene.value[i] = mutation_help(gene.value[i], gene.range[i]) + end + end + return nothing end @@ -248,6 +258,10 @@ function mutate(chromossome ::Individual, rate ::Float64) for gene in chromossome if rand() < rate mutate(gene) + else + while !isbound(gene) + mutate(gene) + end end end return nothing diff --git a/src/structs.jl b/src/structs.jl index a4c6fdf..83be21f 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -48,12 +48,10 @@ BinaryGene() = BinaryGene(rand(Bool), "bin") Creates a `IntegerGene` structure. This gene represents an integer variable as `BitVector`. To convert the `BitVector` in an integer, just look at the `bin` function from this package by typing `?bin` on the command prompt. `name` is a string that represents the name of the variable. It's needed for result presentation purposes. """ mutable struct IntegerGene <: AbstractGene - value ::BitVector - name ::AbstractString - - function IntegerGene(value ::BitVector, name ::AbstractString) - return new(value, name) - end + value ::BitVector + lbound ::Real + ubound ::Real + name ::AbstractString end """ @@ -83,12 +81,14 @@ end Creates a `IntegerGene` structure in which the `BitVector` is of length `n` with random `Bool` values. """ -function IntegerGene(n ::Int64, name ::AbstractString) - value = BitVector(undef, n) +function IntegerGene(n ::Int64, name ::AbstractString, + lbound ::Real = -Inf, ubound ::Real = Inf ) + value = BitVector(undef, n) + bounds = [lbound, ubound] for i in 1:n @inbounds value[i] = rand(Bool) end - return IntegerGene(value, name) + return IntegerGene(value, bounds, name) end """ @@ -112,27 +112,52 @@ end Creates a `FloatGene` structure. `value` is a vector with the variables to be changed. `range` is a vector with the minimum and maximum values a variable can take. `m` is just a parameter that changes how much in a mutation the variables change, the bigger the value, the bigger the change in each mutation. If the range of a variable is 0.5, then the biggest mutation a variable can suffer in one iteration is 0.5, for instance. """ mutable struct FloatGene <: AbstractGene - value ::Vector{Float64} - range ::Vector{Float64} - m ::Int64 - name ::Vector{AbstractString} + value ::Vector{Float64} + range ::Vector{Float64} + name ::Vector{AbstractString} + m ::Int64 + lbound ::Vector{<:Real} + ubound ::Vector{<:Real} - function FloatGene( value ::Vector{Float64} , - range ::Vector{Float64} , - m ::Int64 , - name ::Vector{String} ) + function FloatGene( value ::Vector{Float64} , + range ::Vector{Float64} , + name ::Vector{String} , + m ::Int64 , + lbound ::Union{Vector{<:Real},Real}, + ubound ::Union{Vector{<:Real},Real}) if length(value) != length(range) - error("vectors must have the same length") + error("value and range have different lengths") + end + if typeof(lbound) <: Real + lb = Float64[lbound for i in value] + else + if length(value) != length(lbound) + error("value and lbound have different lengths") + else + lb = lbound + end + end + if typeof(ubound) <: Real + ub = Float64[ubound for i in value] + else + if length(value) != length(ubound) + error("value and ubound have different lengths") + else + ub = ubound + end end - return new(value, range, m, name) + return new(value, range, name, m, lb, ub) end end -function FloatGene( value ::Vector{Float64} , - range ::Vector{Float64} , - name ::Vector{String} ; - m ::Int64 = 20 ) - return FloatGene(value, range, m, name) +function FloatGene( value ::Vector{Float64} , + range ::Vector{Float64} , + name ::Vector{String} ; + m ::Int64 = 20 , + lb ::Union{Vector{<:Real},Real} = -Inf, + ub ::Union{Vector{<:Real},Real} = Inf) + + return FloatGene(value, range, name, m, lb, ub) end """ @@ -140,11 +165,13 @@ end Creates a `FloatGene` structure. Handy for creating just one real number variable. """ -function FloatGene( value ::Float64 , - range ::Float64 , - name ::AbstractString ; - m ::Int64 = 20 ) - return FloatGene(Float64[value], Float64[range], m, [name]) +function FloatGene( value ::Float64 , + range ::Float64 , + name ::AbstractString ; + m ::Int64 = 20 , + lb ::Union{Vector{<:Real},Real} = -Inf, + ub ::Union{Vector{<:Real},Real} = Inf) + return FloatGene([value], [range], [name], m, lb, ub) end """ @@ -152,33 +179,14 @@ end Creates a `FloatGene` structure. Handy for creating a vector of real numbers with the same range. """ -function FloatGene( value ::Vector{Float64} , - range ::Float64 , - name ::Vector{String} ; - m ::Int64 = 20 ) - vec = Float64[range for i in value] - return FloatGene(value, vec, m, name) -end - -""" - FloatGene(value ::Vector{Float64}; m ::Int64 = 20) - -Creates a `FloatGene` structure. Handy for creating a vector of real numbers with a random range. -""" -function FloatGene( value ::Vector{Float64} , - name ::Vector{String} ; - m ::Int64 = 20 ) - range = rand(Float64, length(value)) - return FloatGene(value, range, m, name) -end - -""" - FloatGene(value ::Float64; m ::Int64 = 20) - -Creates a `FloatGene` structure. Handy for creating one variable with a random range. -""" -function FloatGene(value ::Float64; m ::Int64 = 20) - return FloatGene(value, rand(); m=m) +function FloatGene( value ::Vector{Float64} , + range ::Float64 , + name ::Vector{String} ; + m ::Int64 = 20 , + lb ::Union{Vector{<:Real},Real} = -Inf, + ub ::Union{Vector{<:Real},Real} = Inf) + range_vec = Float64[range for i in value] + return FloatGene(value, range_vec, name, m, lb, ub) end """ @@ -186,16 +194,40 @@ end Creates a `FloatGene` structure. Creates a vector of length `n` with random variables and random ranges. Used particularly for testing purposes. """ -function FloatGene(n ::Int64, name ::AbstractString) +function FloatGene(n ::Int64, name ::AbstractString; m ::Int64 = 20) value = rand(Float64, n) range = rand(Float64, n) vec_name = Vector{String}(undef, n) for i in 1:n vec_name[i] = string(name, i) end - return FloatGene(value, range, 20, vec_name) + return FloatGene(value, range, vec_name, m=m) +end + +#################################################################### + +function isbound(gene ::FloatGene, i ::Integer) + return gene.value[i] >= gene.lbound[i] && gene.value[i] <= gene.ubound[i] end +function isbound(gene ::FloatGene) + lb = findmin( gene.value .- gene.lbound )[1] + ub = findmin( gene.ubound .- gene.value )[1] + if lb < 0.0 + return false + end + if ub < 0.0 + return false + end + return true +end + +function isbound(gene ::IntegerGene) + return bin(gene) >= gene.lbound && bin(gene) <= gene.ubound +end + +isbound(gene ::BinaryGene) = true + #################################################################### """ From d9aa448a1d9b25c640ec569d81f4e65864c3bcb5 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Sat, 20 Feb 2021 08:33:48 +0000 Subject: [PATCH 45/51] simplified boundary evaluation --- src/mutations.jl | 23 +++++++---------------- src/structs.jl | 11 +++-------- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/src/mutations.jl b/src/mutations.jl index d4a52e1..a041381 100644 --- a/src/mutations.jl +++ b/src/mutations.jl @@ -226,25 +226,17 @@ Mutates `gene` using Real Valued Mutation. function mutate(gene ::FloatGene) prob = 1.0 / gene.m δ = zeros(gene.m) - - function mutation_help(value ::Float64, range ::Float64) - for j in 1:gene.m + for i in 1:length(gene.value) + for j in 1:gene.m δ[j] = (rand() < prob) ? 2.0^(-j) : 0.0 end if rand() > 0.5 - value += sum(δ)*range + gene.value[i] += sum(δ)*gene.range[i] else - value -= sum(δ)*range + gene.value[i] -= sum(δ)*gene.range[i] end return value end - - for i in 1:length(gene.value) - gene.value[i] = mutation_help(gene.value[i], gene.range[i]) - while !isbound(gene, i) - gene.value[i] = mutation_help(gene.value[i], gene.range[i]) - end - end return nothing end @@ -258,10 +250,9 @@ function mutate(chromossome ::Individual, rate ::Float64) for gene in chromossome if rand() < rate mutate(gene) - else - while !isbound(gene) - mutate(gene) - end + end + while !isbound(gene) + mutate(gene) end end return nothing diff --git a/src/structs.jl b/src/structs.jl index 83be21f..ca0d350 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -81,14 +81,13 @@ end Creates a `IntegerGene` structure in which the `BitVector` is of length `n` with random `Bool` values. """ -function IntegerGene(n ::Int64, name ::AbstractString, - lbound ::Real = -Inf, ubound ::Real = Inf ) +function IntegerGene(n ::Int64, name ::AbstractString; + lb ::Real = -Inf, ub ::Real = Inf ) value = BitVector(undef, n) - bounds = [lbound, ubound] for i in 1:n @inbounds value[i] = rand(Bool) end - return IntegerGene(value, bounds, name) + return IntegerGene(value, lb, ub, name) end """ @@ -206,10 +205,6 @@ end #################################################################### -function isbound(gene ::FloatGene, i ::Integer) - return gene.value[i] >= gene.lbound[i] && gene.value[i] <= gene.ubound[i] -end - function isbound(gene ::FloatGene) lb = findmin( gene.value .- gene.lbound )[1] ub = findmin( gene.ubound .- gene.value )[1] From e1cc8b130522f74e75a26440b71576a6db4f3603 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Mon, 22 Feb 2021 11:23:38 +0000 Subject: [PATCH 46/51] tests on how to maximize warmup efficiency --- test/midpoint.jl | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/test/midpoint.jl b/test/midpoint.jl index 88bdd27..f02ecbe 100644 --- a/test/midpoint.jl +++ b/test/midpoint.jl @@ -6,9 +6,14 @@ distributed_ga(localcpu=nworks) gene = IntegerGene(10, "index") +npop = 2 * nworks +warmup_pop = Vector{Individual}(undef, npop) +for i in 1:npop + warmup_pop[i] = AbstractGene[gene] +end + npop = 10 * nworks pop = Vector{Individual}(undef, npop) - for i in 1:npop pop[i] = AbstractGene[gene] end @@ -17,16 +22,20 @@ end @everywhere Crossover(:SPX) @everywhere Selection(:RWS) -println("Creating objfun function...\n") @everywhere function objfun(chrom ::Individual) return abs( bin(chrom[1]) - 501 ) end -println("Starting ga...") -bestGene, bestFit = ga( objfun, pop, - parallel = true , - nworkers = nworks , - iterations = 10 ) +for i in 1:2 + res = ga( objfun, warmup_pop, + parallel = true , + nworkers = nworks , + iterations = 2 ) +end +res = ga( objfun, pop, + parallel = true , + nworkers = nworks , + iterations = 10 ) nothing From 98e5009a78c200c401042f675d8a6be73b1732a2 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Mon, 1 Mar 2021 18:34:08 +0000 Subject: [PATCH 47/51] Added backup support for boundary values The only thing that was missing regarding the insertion of boundary values in FloatGene and IntegerGene types was the backupo of said values. Now the backup and reverse_backup functions already have the functionality of saving the boundary values in the backup files and in the population variable, respectively. --- src/backup.jl | 41 ++++++++++++++++++++-------------- src/structs.jl | 4 +++- test/backup-bounds.jl | 17 ++++++++++++++ test/backup-bounds.jl~ | 17 ++++++++++++++ test/backup-files/backup-test | Bin 0 -> 81 bytes test/backup-test | 0 6 files changed, 61 insertions(+), 18 deletions(-) create mode 100644 test/backup-bounds.jl create mode 100644 test/backup-bounds.jl~ create mode 100644 test/backup-files/backup-test create mode 100644 test/backup-test diff --git a/src/backup.jl b/src/backup.jl index add7e98..6916477 100644 --- a/src/backup.jl +++ b/src/backup.jl @@ -20,6 +20,7 @@ function backup(f ::IOStream, gene ::IntegerGene) for i in gene.value write(f, i) end + write(f, Float64(gene.lbound), Float64(gene.ubound)) write(f, Int8(-length(gene.name)), gene.name) return nothing end @@ -33,16 +34,16 @@ function backup(f ::IOStream, gene ::FloatGene) write(f, 'F') write(f, gene.m) write(f, Int8(length(gene.value))) - for i in gene.value - write(f, i) - end - for i in gene.range - write(f, i) + for i in [:value, :range, :lbound, :ubound] + for j in getfield(gene, i) + write(f, j) + end end for i in gene.name l = Int8(-length(i)) write(f, l, i) end + return nothing end @@ -64,19 +65,18 @@ end chrom ::Vector{Individual} , file ::AbstractString ) -Writes number of generations `ngens`, total numberof generations `tgens` and the population `chrom` into file `file`. Always writes to folder `backup-files` that is created, if inexistent, inside the `ga` function. +Writes number of generations `ngens`, total number of generations `tgens` and the population `chrom` into file `file`. Always writes to folder `backup-files` that is created, if nonexistent, inside the `ga` function. """ function backup( ngens ::Int64 , tgens ::Int64 , - chrom ::Vector{Individual} , + pop ::Vector{Individual} , file ::AbstractString ) file = "backup-files/$file" - chromossome = chrom - psize = length(chromossome ) - gsize = length(chromossome[1]) + psize = length(pop ) + gsize = length(pop[1]) open(file, "w") do f write(f, ngens, tgens, psize, gsize) - for i in chromossome + for i in pop for j in i backup(f, j) end @@ -99,28 +99,35 @@ function reverse_backup(file ::AbstractString) tgens = read(f, Int64) popsize = read(f, Int64) genesize = read(f, Int64) + population = Vector{Individual}(undef, popsize) for p in 1:popsize population[p] = Individual(undef, genesize) for g in 1:genesize id = read(f, Char) if id == 'I' - nvals = read(f, Int8) + nvals = read(f, Int8) bit_vec = BitVector(undef, nvals) readbytes!(f, reinterpret(UInt8, bit_vec)) + lb = read(f, Float64) + ub = read(f, Float64) strsize = -read(f, Int8) - name = Vector{UInt8}(undef, strsize) + name = Vector{UInt8}(undef, strsize) readbytes!(f, name) population[p][g] = - IntegerGene(bit_vec, String(name)) + IntegerGene(bit_vec, lb, ub, String(name)) elseif id == 'F' - m = read(f, Int64) - nvals = read(f, Int8 ) + m = read(f, Int64) + nvals = read(f, Int8 ) names = Vector{String }(undef, nvals) values = Vector{Float64}(undef, nvals) ranges = Vector{Float64}(undef, nvals) + lb = Vector{Float64}(undef, nvals) + ub = Vector{Float64}(undef, nvals) readbytes!(f, reinterpret(UInt8, values)) readbytes!(f, reinterpret(UInt8, ranges)) + readbytes!(f, reinterpret(UInt8, lb )) + readbytes!(f, reinterpret(UInt8, ub )) for i in 1:nvals strsize = -read(f, Int8) name = Vector{UInt8}(undef, strsize) @@ -128,7 +135,7 @@ function reverse_backup(file ::AbstractString) names[i] = String(name) end population[p][g] = - FloatGene(values, ranges, m, names) + FloatGene(values, ranges, names, m, lb, ub) elseif id == 'B' value = read(f, Bool) strsize = -read(f, Int8) diff --git a/src/structs.jl b/src/structs.jl index ca0d350..ad4457f 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -196,11 +196,13 @@ Creates a `FloatGene` structure. Creates a vector of length `n` with random vari function FloatGene(n ::Int64, name ::AbstractString; m ::Int64 = 20) value = rand(Float64, n) range = rand(Float64, n) + lb = rand(Float64, n) + ub = rand(Float64, n) vec_name = Vector{String}(undef, n) for i in 1:n vec_name[i] = string(name, i) end - return FloatGene(value, range, vec_name, m=m) + return FloatGene(value, range, vec_name, m, lb, ub) end #################################################################### diff --git a/test/backup-bounds.jl b/test/backup-bounds.jl new file mode 100644 index 0000000..4315852 --- /dev/null +++ b/test/backup-bounds.jl @@ -0,0 +1,17 @@ + +using Evolutionary + + int_gene = IntegerGene(10, "integer", lb=-2, ub=5) +float_gene = FloatGene( 1, "float") + +pop = [AbstractGene[float_gene]] + +open("backup-test", "w") do f + backup(1, 1, pop, "backup-test") +end + +backup_pop = reverse_backup("backup-files/backup-test") + +display(float_gene) +display(backup_pop[3][1][1]) + diff --git a/test/backup-bounds.jl~ b/test/backup-bounds.jl~ new file mode 100644 index 0000000..713c9ca --- /dev/null +++ b/test/backup-bounds.jl~ @@ -0,0 +1,17 @@ + +using Evolutionary + + int_gene = IntegerGene(10, "integer", lb=-2, ub=5) +float_gene = FloatGene( 1, "float" ) + +pop = [AbstractGene[float_gene]] + +open("backup-test", "w") do f + backup(10, 10, pop, "backup-test") +end + +backup_int = reverse_backup("backup-files/backup-test") + +display(int_gene) +display(backup_int) + diff --git a/test/backup-files/backup-test b/test/backup-files/backup-test new file mode 100644 index 0000000000000000000000000000000000000000..95f84b92d6818089b421b2a241f03e841989ec1b GIT binary patch literal 81 zcmZQ%fB;4uw3`S_;)T!R6D-w_?Hhb|xV)BpWB+32-1 Date: Wed, 3 Mar 2021 01:21:11 +0000 Subject: [PATCH 48/51] fixed bug in mutate function --- src/mutations.jl | 2 -- test/test | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 test/test diff --git a/src/mutations.jl b/src/mutations.jl index a041381..5a8056b 100644 --- a/src/mutations.jl +++ b/src/mutations.jl @@ -235,9 +235,7 @@ function mutate(gene ::FloatGene) else gene.value[i] -= sum(δ)*gene.range[i] end - return value end - return nothing end diff --git a/test/test b/test/test new file mode 100644 index 0000000..f933cd8 --- /dev/null +++ b/test/test @@ -0,0 +1,14 @@ +Result File of Genetic Algorithm, 2021-02-11T19:18:56.33 + +RESULTS : +population size = 20 +number of generations = 10 +best Fitness = -497.0 +Run time = 1.90818 seconds + +GENES OF BEST INDIVIDUAL : +| parameter | value | +|-----------|-------| +| index | 4 | + +OPTIMIZATION SUCCESSFUL From 2faa82a5f05b323641bbbeb8c0620c4ffee87b0b Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Tue, 9 Mar 2021 19:21:31 +0000 Subject: [PATCH 49/51] Improved boundary checks Before this update boundary checking was performed AFTER the new variables were saved in the gene vector, which made it much more difficult to have new values inside boundaries. In this update, boundary checking is made BEFORE new values are saved, drastically increasing the probability of having new values inside boundaries. --- src/mutations.jl | 6 +- src/nohup.out | 213 +++++++++++++++++++++++++++++++++++++++++++++++ src/structs.jl | 2 + 3 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 src/nohup.out diff --git a/src/mutations.jl b/src/mutations.jl index 5a8056b..235a048 100644 --- a/src/mutations.jl +++ b/src/mutations.jl @@ -226,8 +226,9 @@ Mutates `gene` using Real Valued Mutation. function mutate(gene ::FloatGene) prob = 1.0 / gene.m δ = zeros(gene.m) + val = copy(gene.value) for i in 1:length(gene.value) - for j in 1:gene.m + for j in 1:gene.m δ[j] = (rand() < prob) ? 2.0^(-j) : 0.0 end if rand() > 0.5 @@ -236,6 +237,9 @@ function mutate(gene ::FloatGene) gene.value[i] -= sum(δ)*gene.range[i] end end + if !isbound(gene) + gene.value = val + end return nothing end diff --git a/src/nohup.out b/src/nohup.out new file mode 100644 index 0000000..d58fc21 --- /dev/null +++ b/src/nohup.out @@ -0,0 +1,213 @@ +Using desktop session i3-with-shmlog + +New 'TITAN:1 (tsantos)' desktop is TITAN:1 + +Starting desktop session i3-with-shmlog + + +Xvnc TigerVNC 1.11.0 - built Nov 24 2020 19:41:47 +Copyright (C) 1999-2020 TigerVNC Team and many others (see README.rst) +See https://www.tigervnc.org for information on TigerVNC. +Underlying X server release 12009000, The X.Org Foundation + + +Tue Mar 9 14:56:31 2021 + vncext: VNC extension running! + vncext: Listening for VNC connections on all interface(s), port 5901 + vncext: created VNC server for screen 0 +xinit: XFree86_VT property unexpectedly has 0 items instead of 1 +Running X session wrapper +Loading profile from /etc/profile +Loading profile from /home/tsantos/.profile +Loading xinit script /etc/X11/xinit/xinitrc.d/40-libcanberra-gtk-module.sh +Loading xinit script /etc/X11/xinit/xinitrc.d/50-systemd-user.sh +Loading xinit script /etc/X11/xinit/xinitrc.d/80xapp-gtk3-module.sh +X session wrapper complete, running session i3-with-shmlog +i3status: trying to auto-detect output_format setting +i3status: auto-detected "i3bar" + +(nm-applet:449345): libnotify-WARNING **: 14:56:31.556: Failed to connect to proxy + +(nm-applet:449345): nm-applet-WARNING **: 14:56:31.558: Failed to show notification: GDBus.Error:org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.Notifications was not provided by any .service files + +Tue Mar 9 14:56:39 2021 + Connections: accepted: 10.188.129.211::47108 + SConnection: Client needs protocol version 3.8 + SConnection: Client requests security type VeNCrypt(19) + SVeNCrypt: Client requests security type TLSVnc (258) + +Tue Mar 9 14:56:42 2021 + VNCSConnST: Server default pixel format depth 24 (32bpp) little-endian rgb888 + ComparingUpdateTracker: 0 pixels in / 0 pixels out + ComparingUpdateTracker: (1:-nan ratio) +[../i3-4.19.1/i3bar/src/xcb.c:1042] ERROR: PropertyNotify received for unknown window 00c00002 +[../i3-4.19.1/i3bar/src/xcb.c:1042] ERROR: PropertyNotify received for unknown window 00c00010 +Failed to connect to session manager: Failed to connect to the session manager: SESSION_MANAGER environment variable not defined + +Tue Mar 9 14:57:27 2021 + VNCSConnST: closing 10.188.129.211::47108: read: Connection reset by peer + (104) + EncodeManager: Framebuffer updates: 274 + EncodeManager: CopyRect: + EncodeManager: Copies: 1 rects, 1.86427 Mpixels + EncodeManager: 16 B (1:466068 ratio) + EncodeManager: Tight: + EncodeManager: Solid: 1.104 krects, 10.2704 Mpixels + EncodeManager: 17.25 KiB (1:2326.46 ratio) + EncodeManager: Bitmap RLE: 115 rects, 158.695 kpixels + EncodeManager: 3.69434 KiB (1:168.163 ratio) + EncodeManager: Indexed RLE: 1.668 krects, 1.22236 Mpixels + EncodeManager: 295.543 KiB (1:16.2224 ratio) + EncodeManager: Tight (JPEG): + EncodeManager: Full Colour: 902 rects, 3.95051 Mpixels + EncodeManager: 3.83352 MiB (1:3.93381 ratio) + EncodeManager: Total: 3.79 krects, 17.4662 Mpixels + EncodeManager: 4.14261 MiB (1:16.0941 ratio) + TLS: TLS session wasn't terminated gracefully + TcpSocket: unable to get peer name for socket + Connections: closed: ::0 + ComparingUpdateTracker: 88.3256 Mpixels in / 17.3844 Mpixels out + ComparingUpdateTracker: (1:5.08075 ratio) + +Tue Mar 9 15:34:34 2021 + Connections: accepted: 10.188.129.211::49298 + SConnection: Client needs protocol version 3.8 + SConnection: Client requests security type VeNCrypt(19) + SVeNCrypt: Client requests security type TLSVnc (258) + +Tue Mar 9 15:34:38 2021 + VNCSConnST: Server default pixel format depth 24 (32bpp) little-endian rgb888 + +Tue Mar 9 15:37:15 2021 + ComparingUpdateTracker: 311.247 Mpixels in / 25.4744 Mpixels out + ComparingUpdateTracker: (1:12.218 ratio) +[../i3-4.19.1/i3bar/src/xcb.c:1042] ERROR: PropertyNotify received for unknown window 00c00017 +[../i3-4.19.1/i3bar/src/xcb.c:1042] ERROR: PropertyNotify received for unknown window 00c00022 + +Tue Mar 9 15:37:25 2021 + ComparingUpdateTracker: 17.4547 Mpixels in / 183.04 kpixels out + ComparingUpdateTracker: (1:95.3601 ratio) +[../i3-4.19.1/i3bar/src/xcb.c:1042] ERROR: PropertyNotify received for unknown window 00c00029 +[../i3-4.19.1/i3bar/src/xcb.c:1042] ERROR: PropertyNotify received for unknown window 00c00034 + +Tue Mar 9 15:55:52 2021 + ComparingUpdateTracker: 2.04047 Gpixels in / 42.4778 Mpixels out + ComparingUpdateTracker: (1:48.0363 ratio) +[../i3-4.19.1/i3bar/src/xcb.c:1042] ERROR: PropertyNotify received for unknown window 00c0003b +[../i3-4.19.1/i3bar/src/xcb.c:1042] ERROR: PropertyNotify received for unknown window 00c00046 + +Tue Mar 9 15:58:14 2021 + VNCSConnST: closing 10.188.129.211::49298: Clean disconnection + EncodeManager: Framebuffer updates: 6209 + EncodeManager: Tight: + EncodeManager: Solid: 6.588 krects, 41.0567 Mpixels + EncodeManager: 102.938 KiB (1:1558.76 ratio) + EncodeManager: Bitmap RLE: 1.035 krects, 486.724 kpixels + EncodeManager: 31.042 KiB (1:61.6389 ratio) + EncodeManager: Indexed RLE: 13.527 krects, 8.44021 Mpixels + EncodeManager: 2.38765 MiB (1:13.5496 ratio) + EncodeManager: Tight (JPEG): + EncodeManager: Full Colour: 11.964 krects, 38.7551 Mpixels + EncodeManager: 48.1775 MiB (1:3.07147 ratio) + EncodeManager: Total: 33.114 krects, 88.7388 Mpixels + EncodeManager: 50.696 MiB (1:6.68475 ratio) + TLS: TLS session wasn't terminated gracefully + Connections: closed: 10.188.129.211::49298 + ComparingUpdateTracker: 364.299 Mpixels in / 4.21978 Mpixels out + ComparingUpdateTracker: (1:86.3312 ratio) + +Tue Mar 9 16:21:27 2021 + Connections: accepted: 10.188.130.45::42508 + SConnection: Client needs protocol version 3.8 + SConnection: Client requests security type VeNCrypt(19) + SVeNCrypt: Client requests security type TLSVnc (258) + +Tue Mar 9 16:21:30 2021 + VNCSConnST: Server default pixel format depth 24 (32bpp) little-endian rgb888 + ComparingUpdateTracker: 1.95072 Mpixels in / 1.95072 Mpixels out + ComparingUpdateTracker: (1:1 ratio) +[../i3-4.19.1/i3bar/src/xcb.c:1042] ERROR: PropertyNotify received for unknown window 00c0004d +[../i3-4.19.1/i3bar/src/xcb.c:1042] ERROR: PropertyNotify received for unknown window 00c00054 + +Tue Mar 9 16:30:02 2021 + ComparingUpdateTracker: 3.15435 Gpixels in / 128.089 Mpixels out + ComparingUpdateTracker: (1:24.6263 ratio) +[../i3-4.19.1/i3bar/src/xcb.c:1042] ERROR: PropertyNotify received for unknown window 00c0005d +[../i3-4.19.1/i3bar/src/xcb.c:1042] ERROR: PropertyNotify received for unknown window 00c00068 + +Tue Mar 9 16:54:31 2021 + ComparingUpdateTracker: 68.2603 Gpixels in / 6.85368 Mpixels out + ComparingUpdateTracker: (1:9959.66 ratio) +[../i3-4.19.1/i3bar/src/xcb.c:1042] ERROR: PropertyNotify received for unknown window 00c0006f + +Tue Mar 9 17:13:14 2021 + ComparingUpdateTracker: 7.50816 Gpixels in / 77.1551 Mpixels out + ComparingUpdateTracker: (1:97.3125 ratio) +[../i3-4.19.1/i3bar/src/xcb.c:1042] ERROR: PropertyNotify received for unknown window 00c0007a +[../i3-4.19.1/i3bar/src/xcb.c:1042] ERROR: PropertyNotify received for unknown window 00c00083 + +Tue Mar 9 17:35:06 2021 + VNCSConnST: closing 10.188.130.45::42508: read: Connection timed out (110) + EncodeManager: Framebuffer updates: 20319 + EncodeManager: Tight: + EncodeManager: Solid: 9.733 krects, 86.9644 Mpixels + EncodeManager: 152.078 KiB (1:2234.5 ratio) + EncodeManager: Bitmap RLE: 2.425 krects, 899.574 kpixels + EncodeManager: 73.2334 KiB (1:48.3711 ratio) + EncodeManager: Indexed RLE: 33.098 krects, 24.8016 Mpixels + EncodeManager: 7.59894 MiB (1:12.5003 ratio) + EncodeManager: Tight (JPEG): + EncodeManager: Full Colour: 28.851 krects, 114.414 Mpixels + EncodeManager: 110.042 MiB (1:3.96927 ratio) + EncodeManager: Total: 74.107 krects, 227.079 Mpixels + EncodeManager: 117.861 MiB (1:7.35689 ratio) + TLS: TLS session wasn't terminated gracefully + TcpSocket: unable to get peer name for socket + Connections: closed: ::0 + ComparingUpdateTracker: 4.33595 Gpixels in / 9.41729 Mpixels out + ComparingUpdateTracker: (1:460.424 ratio) + +Tue Mar 9 18:56:31 2021 + Connections: accepted: 10.188.130.110::47296 + SConnection: Client needs protocol version 3.8 + +Tue Mar 9 18:56:32 2021 + SConnection: Client requests security type VeNCrypt(19) + SVeNCrypt: Client requests security type TLSVnc (258) + +Tue Mar 9 18:56:35 2021 + VNCSConnST: Server default pixel format depth 24 (32bpp) little-endian rgb888 + +Tue Mar 9 18:56:37 2021 + ComparingUpdateTracker: 13.1578 Mpixels in / 0 pixels out + ComparingUpdateTracker: (1:inf ratio) +[../i3-4.19.1/i3bar/src/xcb.c:1042] ERROR: PropertyNotify received for unknown window 00c0008a + +Tue Mar 9 19:18:08 2021 + VNCSConnST: closing 10.188.130.110::47296: Clean disconnection + EncodeManager: Framebuffer updates: 7566 + EncodeManager: Tight: + EncodeManager: Solid: 4.717 krects, 30.9767 Mpixels + EncodeManager: 73.7031 KiB (1:1642.51 ratio) + EncodeManager: Bitmap RLE: 1.488 krects, 545.124 kpixels + EncodeManager: 44.8174 KiB (1:47.9017 ratio) + EncodeManager: Indexed RLE: 10.532 krects, 7.64444 Mpixels + EncodeManager: 2.26153 MiB (1:12.9478 ratio) + EncodeManager: Tight (JPEG): + EncodeManager: Full Colour: 8.629 krects, 44.2715 Mpixels + EncodeManager: 40.3813 MiB (1:4.18464 ratio) + EncodeManager: Total: 25.366 krects, 83.4377 Mpixels + EncodeManager: 42.7585 MiB (1:7.45067 ratio) + TLS: TLS session wasn't terminated gracefully + Connections: closed: 10.188.130.110::47296 + ComparingUpdateTracker: 5.85247 Gpixels in / 73.6139 Mpixels out + ComparingUpdateTracker: (1:79.5023 ratio) + +Tue Mar 9 19:18:37 2021 + Connections: accepted: 10.188.130.110::47554 + SConnection: Client needs protocol version 3.8 + SConnection: Client requests security type VeNCrypt(19) + SVeNCrypt: Client requests security type TLSVnc (258) + +Tue Mar 9 19:18:41 2021 + VNCSConnST: Server default pixel format depth 24 (32bpp) little-endian rgb888 diff --git a/src/structs.jl b/src/structs.jl index ad4457f..8c52d22 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -211,9 +211,11 @@ function isbound(gene ::FloatGene) lb = findmin( gene.value .- gene.lbound )[1] ub = findmin( gene.ubound .- gene.value )[1] if lb < 0.0 + @info("lower boundary violated, retrying...") return false end if ub < 0.0 + @info("upper boundary violated, retrying...") return false end return true From bd44dd9ff1987dad375230c6090a2245798b7596 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Wed, 24 Mar 2021 11:18:00 +0000 Subject: [PATCH 50/51] minor changes --- src/ga.jl | 34 ++++++++++++++++++++++++---------- src/mutations.jl | 3 ++- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/ga.jl b/src/ga.jl index 4b9d652..84b5bdd 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -64,7 +64,7 @@ function ga( objfun ::Function , output ::AbstractString = "" , showprint ::Bool = true , isbackup ::Bool = true , - backuptime ::Float64 = 1.0 ) + backuptime ::Union{Integer,Float64} = 1 ) # Initialize population N = length(population) @@ -105,8 +105,8 @@ function ga( objfun ::Function , end # create distributed arrays for parallel processing - population = distribute(population;procs=works) - fitness = distribute(fitness;procs=works) + population = distribute(population, procs=works) + fitness = distribute( fitness, procs=works) # run generations elapsed_time = @elapsed begin @@ -277,15 +277,28 @@ function generations_parallel( objfun ::Function end t_ref = time() + iter_backup = 0 + iter_cter = 0 # Generate and evaluate offspring for iter in 1:pars[:iterations] - + iter_cter = iter # backup process - dt = time() - t_ref - if dt > pars[:backuptime] - file = "Backup_GA_worker$(myid())" - backup(iter, pars[:iterations], popul[:L], file) - t_ref = time() + if typeof(pars[:backuptime]) <: Real + dt = time() - t_ref + if dt > pars[:backuptime] + file = "Backup_GA_worker$(myid())" + backup(iter, pars[:iterations], popul[:L], file) + t_ref = time() + end + elseif typeof(pars[:backuptime]) <: Integer + dt = iter - iter_backup + if dt > pars[:backuptime] + file = "Backup_GA_worker$(myid())" + backup(iter, pars[:iterations], popul[:L], file) + iter_backup = iter + end + else + @error("Type of backuptime not recognized, backup unsuccessful") end # Select offspring @@ -318,7 +331,8 @@ function generations_parallel( objfun ::Function elitism() end - + + @info("Worker $(myid()-1) finished after $(iter_cter) iterations") return nothing end diff --git a/src/mutations.jl b/src/mutations.jl index 235a048..a9f95fb 100644 --- a/src/mutations.jl +++ b/src/mutations.jl @@ -238,7 +238,8 @@ function mutate(gene ::FloatGene) end end if !isbound(gene) - gene.value = val + @info((gene.value,val)) + gene.value = copy(val) end return nothing end From 1ba9433f594fff2c0d475fdb21134319a2dcbe9a Mon Sep 17 00:00:00 2001 From: tiagosantos <32461027+tpdsantos@users.noreply.github.com> Date: Sat, 18 Feb 2023 17:14:07 +0000 Subject: [PATCH 51/51] Create CITATION.cff --- CITATION.cff | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 CITATION.cff diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..8425e4a --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,8 @@ +cff-version: 0.0.1 +authors: + - family-names: Santos + given-names: Tiago + orcid: https://orcid.org/0000-0003-2463-2881 +title: "Evolutionary.jl" +version: 0.0.2 +date-released: 2020-05-22