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 diff --git a/Project.toml b/Project.toml index 574dd32..1ef9bba 100644 --- a/Project.toml +++ b/Project.toml @@ -3,7 +3,11 @@ 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" +Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" 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 691402d..a0f0200 100644 --- a/src/Evolutionary.jl +++ b/src/Evolutionary.jl @@ -1,83 +1,97 @@ + 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 Individual = Union{Vector, Matrix, Function, Nothing} - - # Wrapping function for strategy - function strategy(; kwargs...) - result = Dict{Symbol,Any}() - for (k, v) in kwargs - result[k] = v - end - return result + +using Random, Base.Threads +using Distributed +using DistributedArrays, DistributedArrays.SPMD +using Printf +using Dates + +export + # Optimization methods + es, cmaes, ga, + # Constants + GAVector, Individual, AbstractGene + +#################################################################### + +""" +Abstract Type that represents all types of genes supported. +""" +abstract type AbstractGene end + +const Strategy = Dict{Symbol,Any} + +const Individual = Vector{AbstractGene} + +const GAVector = Union{T, BitVector} where T <: Vector + +#################################################################### + +# 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 + +# General 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") +# Backup functions +include("backup.jl") - # Genetic Algorithms - include("ga.jl") +# Genetic Algorithms +include("ga.jl") end diff --git a/src/backup.jl b/src/backup.jl new file mode 100644 index 0000000..6916477 --- /dev/null +++ b/src/backup.jl @@ -0,0 +1,176 @@ +##### 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))) + 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 + +""" + 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) + write(f, Int8(length(gene.value))) + 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 + +""" + 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) + write(f, Int8(-length(gene.name)), gene.name) + return nothing +end + +""" + backup( ngens ::Int64 , + tgens ::Int64 , + chrom ::Vector{Individual} , + file ::AbstractString ) + +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 , + pop ::Vector{Individual} , + file ::AbstractString ) + file = "backup-files/$file" + psize = length(pop ) + gsize = length(pop[1]) + open(file, "w") do f + write(f, ngens, tgens, psize, gsize) + for i in pop + for j in i + backup(f, j) + end + end + end + return nothing +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") + + ngens = read(f, Int64) + 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) + 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) + readbytes!(f, name) + population[p][g] = + IntegerGene(bit_vec, lb, ub, 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) + 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) + readbytes!(f, name) + names[i] = String(name) + end + population[p][g] = + FloatGene(values, ranges, names, m, lb, ub) + 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, 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)) + 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/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/ga.jl b/src/ga.jl index 1016711..84b5bdd 100644 --- a/src/ga.jl +++ b/src/ga.jl @@ -1,142 +1,530 @@ -# 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. -# -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) - - store = Dict{Symbol,Any}() - - # Setup parameters - elite = isa(ɛ, Int) ? ɛ : round(Int, ɛ * populationSize) - fitFunc = inverseFunc(objfun) +##### ga.jl ##### + +# In this file you will find the Genetic Algorithm. + +#################################################################### + +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 = 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: + +| 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 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 = 1 , + output ::AbstractString = "" , + showprint ::Bool = true , + isbackup ::Bool = true , + backuptime ::Union{Integer,Float64} = 1 ) # Initialize population - individual = getIndividual(initPopulation, N) - fitness = zeros(populationSize) - population = Array{typeof(individual)}(undef, populationSize) - offspring = similar(population) - - # Generate population - for i in 1:populationSize - if isa(initPopulation, Vector) - population[i] = initPopulation.*rand(eltype(initPopulation), N) - elseif isa(initPopulation, Matrix) - population[i] = initPopulation[:, i] - elseif isa(initPopulation, Function) - population[i] = initPopulation(N) # Creation function + N = length(population) + fitness = Vector{Float64}(undef, N) + + # check if piping is used + if piping == nothing + func = objfun + else + func(x) = objfun(x, piping) + end + + # save optional arguments in a dictionary + # to pass to one of the generation functions + pars = Dict{Symbol, Any}() + pars[:crossoverRate] = crossoverRate + pars[:mutationRate ] = mutationRate + pars[:ϵ ] = ϵ + pars[:iterations ] = iterations + pars[:tol ] = tol + pars[:backuptime ] = isbackup ? backuptime : Inf + + # choose run method depending if it's parallel + # or serial processing + if parallel + if piping == nothing + works = workers()[1:nworkers] else - error("Cannot generate population") + 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("backup folder created") + end + end + 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 + end + + bestFitness, bestIndividual = findmin(fitness) + + # result presentation + data_presentation( population[bestIndividual], N, nworkers, iterations, bestFitness, + elapsed_time, showprint, output ) + + return OptimResults(N, bestFitness, population[bestIndividual], elapsed_time, + nworkers, iterations) +end + +#################################################################### + +function generations( objfun ::Function , + population ::Vector{Individual} , + N ::Integer , + fitness ::Vector{Float64} , + pars ::Dict{Symbol,Any} ) + + # Variable initialization + 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] = 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. + # 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 - fitness[i] = fitFunc(population[i]) - debug && println("INIT $(i): $(population[i]) : $(fitness[i])") + @inbounds begin + 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 = deepcopy(offspring) + fitness = objfun.(population) + end + return nothing end - fitidx = sortperm(fitness, rev = true) - keep(interim, :fitness, copy(fitness), store) + if pars[:ϵ] + elitism = elitism_true + else + elitism = elitism_false + end + + t_ref = time() # Generate and evaluate offspring - itr = 1 - bestFitness = 0.0 - bestIndividual = 0 - fittol = 0.0 - fittolitr = 1 - while true - debug && println("BEST: $(fitidx)") + 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, populationSize) - + selected = selection(fitness, N) + # 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])") + 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 - offspring[i], offspring[j] = population[selected[i]], population[selected[j]] + @inbounds begin + offspring[i], offspring[j] = + population[selected[i]], population[selected[j]] + end end end - + # Perform mutation - for i in 1:populationSize - if rand() < mutationRate - debug && println("MUTATED $(i)>: $(offspring[i])") - mutation(offspring[i]) - debug && println("MUTATED >$(i): $(offspring[i])") + for i in 1:N + @inbounds mutate(offspring[i], pars[:mutationRate]) + end + + elitism() + + end + + return nothing +end + +#################################################################### + +function generations_parallel( objfun ::Function , + popul ::DArray{Individual,1,Vector{Individual}} , + fit ::DArray{Float64,1,Vector{Float64}} , + pars ::Dict{Symbol,Any} ) + # Variable initialization + 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 2N. + # When false, only uses offspring + function elitism_true() + @inbounds begin + full_pop[N+1:2N] = offspring + full_fit[N+1:2N] = objfun.(offspring) + fitidx = sortperm(full_fit) + 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(fit[:L]) + full_pop[1:N] = deepcopy(popul[:L]) + end + return nothing + end + function elitism_false() + @inbounds begin + popul[:L][1:N] = offspring[1:N] + fit[:L][1:N] = objfun.(popul[:L][1:N]) + end + return nothing + end + + if pars[:ϵ] + elitism = elitism_true + else + elitism = elitism_false + 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 + 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 - # 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]] + # Select offspring + selected = selection(fit[:L], 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( popul[:L][selected[offidx[i]]] , + popul[:L][selected[offidx[j]]] ) + end + else + @inbounds begin + offspring[i], offspring[j] = + deepcopy(popul[:L][selected[i]]), + deepcopy(popul[:L][selected[j]]) + end + end + end + + # Perform mutation + for i in 1:N + @inbounds mutate(offspring[i], pars[:mutationRate]) + end + + elitism() + + end + + @info("Worker $(myid()-1) finished after $(iter_cter) iterations") + return nothing +end + +#################################################################### + +function data_presentation( individual ::Individual , + popsize ::Integer , + nworkers ::Integer , + generations ::Integer , + bestFitness ::Float64 , + elapsed_time ::Float64 , + showprint ::Bool , + output ::String ) + + optim_time = round(elapsed_time, digits=5) + bestFitness = round(bestFitness, digits=15) + + 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) + push!(params, gene.name[i]) + push!(values, j) end + elseif isa(gene, IntegerGene) + push!(params, gene.name) + push!(values, bin(gene)) + elseif isa(gene, BinaryGene) + push!(params, gene.name) + push!(values, gene.value) + else + nothing end + end - # New generation - for i in 1:populationSize - population[i] = offspring[i] - fitness[i] = fitFunc(offspring[i]) - debug && println("FIT $(i): $(fitness[i])") + 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 = str_vals[i] + table *= @eval @sprintf($i_str, $j, $s_val) + end + + 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") + println("") + printstyled("GENES OF BEST INDIVIDUAL :\n", color=:bold) + println(table) + println("") + 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 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") + write(f, "OPTIMIZATION FINISHED SUCCESSFULLY\n") end - fitidx = sortperm(fitness, rev = true) - bestIndividual = fitidx[1] - curGenFitness = Float64(objfun(population[bestIndividual])) - fittol = abs(bestFitness - curGenFitness) - bestFitness = curGenFitness + end - keep(interim, :fitness, copy(fitness), store) - keep(interim, :bestFitness, bestFitness, store) + return nothing +end - # Verbose step - verbose && println("BEST: $(bestFitness): $(population[bestIndividual]), G: $(itr)") +#################################################################### - # Terminate: - # if fitness tolerance is met for specified number of steps - if fittol <= tol - if fittolitr > tolIter - break +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 - fittolitr += 1 + iv = right_maxsize+1 + end + for i in 1:iv + str_vals[i] *= " " end else - fittolitr = 1 + str_vals[i] = string(left_dot[i],".",right_dot[i]) end - # if number of iterations more then specified - if itr >= iterations - break + end + + str_maxsize = 0 + for i in str_vals + l = length(i) + if l > str_maxsize + str_maxsize = l end - itr += 1 end - return population[bestIndividual], bestFitness, itr, fittol, store + 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/mutations.jl b/src/mutations.jl index 4b9e439..a9f95fb 100644 --- a/src/mutations.jl +++ b/src/mutations.jl @@ -1,3 +1,14 @@ +##### 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 + +#################################################################### + # Mutation operators # ================== @@ -16,6 +27,7 @@ function anisotropic(recombinant::T, s::S) where {T <: Vector, S <: Strategy} return recombinant end +#################################################################### # Strategy mutation operators # =========================== @@ -35,36 +47,44 @@ function anisotropicSigma(s::S) where {S <: Strategy} return strategy(σ = σ, τ = s[:τ], τ0 = s[:τ0]) end +#################################################################### # Genetic mutations # ================= # Binary mutations # ---------------- -function flip(recombinant::Vector{Bool}) +function flip(recombinant ::BitVector) s = length(recombinant) pos = rand(1:s) - recombinant[pos] = !recombinant[pos] + @inbounds 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. # -------------------- -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) for i in 1:length(recombinant) for j in 1:m - δ[j] = (rand() < prob) ? δ[j] = 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 @@ -72,10 +92,9 @@ function domainrange(valrange::Vector, m::Int = 20) return mutation 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 +105,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,44 +113,44 @@ 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) 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 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] + @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 @@ -139,13 +158,105 @@ end # Utils # ===== -function swap!(v::T, from::Int, to::Int) where {T <: Vector} - val = v[from] - v[from] = v[to] - v[to] = val +function swap!(v ::T, from ::Int, to ::Int) where {T <: Vector} + @inbounds begin + val = v[from] + v[from] = v[to] + v[to] = val + end 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 + +#################################################################### + +# 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 + +""" + 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 + elseif mutatetype == :InvM + mut_func = inversion + elseif mutatetype == :InsM + mut_func = insertion + elseif mutatetype == :SwM + mut_func = swap2 + elseif mutatetype == :ScrM + mut_func = scramble + elseif mutatetype == :ShM + mut_func = shifting + else + error("Unknown mutation type") + end + 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) + val = copy(gene.value) + 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 + if !isbound(gene) + @info((gene.value,val)) + gene.value = copy(val) + end + return nothing +end + +""" + mutate(chromossome ::Vector{<:AbstractGene}, rate ::Float64) + +Mutates each entry of `chromossome` according to the mutations chosen. +""" +function mutate(chromossome ::Individual, rate ::Float64) + for gene in chromossome + if rand() < rate + mutate(gene) + end + while !isbound(gene) + mutate(gene) + end + 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/recombinations.jl b/src/recombinations.jl index 1273b54..48014fa 100644 --- a/src/recombinations.jl +++ b/src/recombinations.jl @@ -1,17 +1,29 @@ +##### 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 + +#################################################################### + # 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,28 +33,30 @@ 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 +#################################################################### + # ================== # Genetic algorithms # ================== @@ -51,10 +65,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 <: AbstractVector} + l = length(v1) + c1 = copy(v1) + c2 = copy(v2) pos = rand(1:l) for i in pos:l vswap!(c1, c2, i) @@ -63,8 +77,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 <: AbstractVector} + l = length(v1) c1 = copy(v1) c2 = copy(v2) from, to = rand(1:l, 2) @@ -76,51 +90,51 @@ 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) - xch = rand(Bool, l) +function uniform(v1 ::T, v2 ::T) where {T <: AbstractVector} + l = length(v1) + c1 = copy(v1) + c2 = copy(v2) for i in 1:l - if xch[i] + if rand(Bool) vswap!(c1, c2, i) end end return c1, c2 end +#################################################################### # Real valued crossovers # ---------------------- -function discrete(v1::T, v2::T) where {T <: Vector} - l = length(v1) - c1 = similar(v1) - c2 = similar(v2) - sltc = rand(Bool, 2, l) +# Discrete Crossover +function discrete(v1 ::T, v2 ::T) where {T <: AbstractVector} + l = length(v1) + c1 = similar(v1) + c2 = similar(v2) for i in 1:l - c1[i] = sltc[1,i] ? v1[i] : v2[i] - c2[i] = sltc[2,i] ? 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 # Weighted arithmetic mean -function waverage(w::Vector{Float64}) - function wavexvr(v1::T, v2::T) where {T <: Vector} - c1 = (v1+v2)./w +function waverage(w ::Vector{Float64}) + function wavexvr(v1 ::T, v2 ::T) where {T <: AbstractVector} + c1 = (v1+v2) ./ w return c1, copy(c1) end return wavexvr 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 <: AbstractVector} + 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 +142,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 <: AbstractVector} α1, α2 = (1.0+2d) * rand(2) .- d c1 = v2 .+ α2 * (v1 - v2) c2 = v1 .+ α1 * (v2 - v1) @@ -138,12 +152,13 @@ function line(d::Float64 = 0.0) return linexvr end +#################################################################### # Permutation crossovers # ---------------------- # 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) @@ -151,49 +166,51 @@ function pmx(v1::T, v2::T) where {T <: Vector} 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) - in1 = tmpin > 0 ? tmpin : in1 + @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) - in2 = tmpin > 0 ? tmpin : in2 + @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 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) 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 @@ -201,38 +218,38 @@ function ox1(v1::T, v2::T) where {T <: Vector} 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 # Cycle crossover -function cx(v1::T, v2::T) where {T <: Vector} - s = length(v1) +function cx(v1 ::T, v2 ::T) where {T <: AbstractVector} + s = length(v1) c1 = zeros(v1) c2 = zeros(v2) f1 = true #switch - k = 1 + k = 1 while k > 0 idx = k 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 @@ -242,68 +259,110 @@ 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 <: AbstractVector} + s = length(v1) c1 = copy(v1) c2 = copy(v2) 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] + tmpin = inmap(zero(T),c1,1,s) + @inbounds c1[tmpin] = v2[i] end if !in(v1[i],c2) - tmpin = inmap(zero(T),c2,1,s) - c2[tmpin] = v1[i] + tmpin = inmap(zero(T),c2,1,s) + @inbounds c2[tmpin] = v1[i] end end return c1,c2 end # Position-based crossover -function pos(v1::T, v2::T) where {T <: Vector} - s = length(v1) +function pos(v1 ::T, v2 ::T) where {T <: AbstractVector} + s = length(v1) c1 = zeros(v1) c2 = zeros(v2) 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] + tmpin = inmap(zero(T),c1,1,s) + @inbounds c1[tmpin] = v1[i] end if !in(v2[i],c2) - tmpin = inmap(zero(T),c2,1,s) - c2[tmpin] = v2[i] + tmpin = inmap(zero(T),c2,1,s) + @inbounds c2[tmpin] = v2[i] end end - return c1,c2 + return c1, c2 end +#################################################################### + +""" + 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 <: Individual} + c1 = deepcopy(chromo1) + c2 = deepcopy(chromo2) + for i in 1:length(chromo1) + @inbounds 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} - val = v1[idx] - v1[idx] = v2[idx] - v2[idx] = val +function vswap!(v1 ::T, v2 ::T, idx ::Int) where {T <: AbstractVector} + @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} +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..9b22bba 100644 --- a/src/selections.jl +++ b/src/selections.jl @@ -1,45 +1,52 @@ -# GA seclections +##### 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 # ============== -# 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) ) / λ + @inbounds 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 $(λ)" ranks = similar(fitness, Float64) for i in 1:μ - ranks[idx[i]] = 1/μ + @inbounds ranks[idx[i]] = 1/μ end return pselection(ranks, N) end 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 +63,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,13 +85,13 @@ 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 - winnerFitness = fitness[c] + winner = c + @inbounds winnerFitness = fitness[c] end end @@ -95,15 +102,16 @@ function tournament(groupSize :: Int) return tournamentN 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 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 new file mode 100644 index 0000000..8c52d22 --- /dev/null +++ b/src/structs.jl @@ -0,0 +1,537 @@ +##### 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 BinaryGene, IntegerGene, FloatGene +export Crossover, Selection +export selection, crossover, bin +export GAExternal, ext_init, distributed_ga + +#################################################################### + +""" + 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.). `name` is the variable name for presentation purposes. +""" +mutable struct BinaryGene <: AbstractGene + value ::Bool + name ::AbstractString + + function BinaryGene(value ::Bool, name ::AbstractString) + return new(value, name) + end +end + +""" + BinaryGene() + +Creates a `BinaryGene` structure with a random `Bool` value. +""" +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") + +#################################################################### + +""" + 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 string that represents the name of the variable. It's needed for result presentation purposes. +""" +mutable struct IntegerGene <: AbstractGene + value ::BitVector + lbound ::Real + ubound ::Real + name ::AbstractString +end + +""" + 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: + +| 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) + @eval mutate(gene ::IntegerGene) = $int_func(gene.value) + return nothing +end + +""" + IntegerGene(n ::Int64) + +Creates a `IntegerGene` structure in which the `BitVector` is of length `n` with random `Bool` values. +""" +function IntegerGene(n ::Int64, name ::AbstractString; + lb ::Real = -Inf, ub ::Real = Inf ) + value = BitVector(undef, n) + for i in 1:n + @inbounds value[i] = rand(Bool) + end + return IntegerGene(value, lb, ub, name) +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) + bin_val += j ? 2^(i-1) : 0 + end + return bin_val +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. +""" +mutable struct FloatGene <: AbstractGene + 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} , + name ::Vector{String} , + m ::Int64 , + lbound ::Union{Vector{<:Real},Real}, + ubound ::Union{Vector{<:Real},Real}) + if length(value) != length(range) + 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, name, m, lb, ub) + end +end + +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 + +""" + 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 , + 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 + +""" + 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 , + 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 + +""" + 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, 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, lb, ub) +end + +#################################################################### + +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 +end + +function isbound(gene ::IntegerGene) + return bin(gene) >= gene.lbound && bin(gene) <= gene.ubound +end + +isbound(gene ::BinaryGene) = true + +#################################################################### + +""" + 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}} + d ::Union{Nothing, Float64 } + + function Crossover(cross ::Symbol ; + w ::Union{Nothing, Vector{Float64}} = nothing , + d ::Union{Nothing, Float64 } = nothing ) + cross_func = nothing + if cross == :SPX + cross_func = singlepoint + elseif cross == :TPX + cross_func = twopoint + elseif cross == :UX + cross_func = uniform + elseif cross == :DX + cross_func = discrete + elseif cross == :WMX + if isnothing(w) + error("value `w` must be given a value") + end + cross_func = waverage(w) + elseif cross == :IRX + if isnothing(d) + error("value `d` must be given a value") + end + cross_func = intermediate(d) + elseif cross == :LRX + if isnothing(d) + error("value `d` must be given a value") + end + cross_func = line(d) + elseif cross == :PMX + cross_func = pmx + elseif cross == :O1X + cross_func = ox1 + elseif cross == :O2X + cross_func = ox2 + elseif cross == :CX + cross_func = cx + 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 new(cross, w, d) + end +end + +#################################################################### + +""" + 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} + μ ::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 ) + 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) + end + end + return new(select, sp, μ, groupsize) + end +end + +#################################################################### + +""" + 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` 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 + 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 + + 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) + 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 avail_workers + f = string(pipename, "_", i, p) + push!(pipes[i], f) + remotecall_fetch(rm, p, f, force=true) + 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"] + f = string(pipename, "_", i) + push!(pipes[i], f) + 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) + + # activate writing pipes for a big amount of time + for (i,p) in enumerate(pin) + 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 + + # 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) + f = open(pipe, "r") + return nothing + end + for (i,p) in enumerate(pout) + v = 3*nworkers+1 + i + @spawnat v spawn_readpipes(p) + end + + # delete all pipes when exiting julia + function external_atexit() + for k in keys(pipes) + for (i,p) in enumerate(pipes[k]) + id = i+1 + remotecall_fetch(rm, id, p) + end + end + return nothing + end + atexit(external_atexit) + + return new(program, pin, pout, avail_workers, parallel) + end +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 , + 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} = [()] , + 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 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 0000000..95f84b9 Binary files /dev/null and b/test/backup-files/backup-test differ diff --git a/test/backup-test b/test/backup-test new file mode 100644 index 0000000..e69de29 diff --git a/test/midpoint.jl b/test/midpoint.jl new file mode 100644 index 0000000..f02ecbe --- /dev/null +++ b/test/midpoint.jl @@ -0,0 +1,41 @@ + +using Evolutionary + +nworks = 2 +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 + +@everywhere IntegerGene(:FM) +@everywhere Crossover(:SPX) +@everywhere Selection(:RWS) + +@everywhere function objfun(chrom ::Individual) + return abs( bin(chrom[1]) - 501 ) +end + +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 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() + 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