Skip to content

Commit

Permalink
drop phenotype in Indiv representation, update BinaryCoding
Browse files Browse the repository at this point in the history
  • Loading branch information
gsoleilhac committed Oct 31, 2020
1 parent e5311cc commit 7eb74e8
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 93 deletions.
13 changes: 0 additions & 13 deletions README.md
Expand Up @@ -65,7 +65,6 @@ result = nsga_max(popsize, nbgen, z, init, fCV = CV)
The revelant fields of an individual *`indiv`* are :
* genotype : `indiv.x`
* objective values : `indiv.y`
* phenotype : `indiv.pheno`
* rank : `indiv.rank`
* constraint violation value : `indiv.CV`

Expand Down Expand Up @@ -111,18 +110,6 @@ nsga_max(popsize, nbgen, z, init, fCV = CV, fmut = two_bits_flip!, pmut = 0.2)

*For permutations genotypes, the default mutation randomly [swaps](https://github.com/gsoleilhac/NSGAII.jl/blob/master/src/mutation.jl#L12-L18) two indices.*

### Genotype and Phenotype

So far, we haven't made any difference between the genotype and the phenotype ; the default decoding function used here is the *identity* function

You can provide your own with the keywords `fdecode` and `fdecode!` which will work in-place.

Note : if your decode function takes a genotype `G` and returns a phenotype `P`, make sure your crossovers and mutations functions work on type `G`, and that your evaluation and (if provided) your constraint-violation functions work on type `P`.
`fdecode!` should take as parameters a genotype `G` and a phenotype `P` and modify it in-place.

See [BinaryCoding](https://github.com/gsoleilhac/NSGAII.jl#binarycoding) to easily encode/decode real variables.


### Seeding

Starting solutions can be provided as a vector with the keyword `seed`, for example :
Expand Down
51 changes: 31 additions & 20 deletions src/NSGAII.jl
@@ -1,7 +1,7 @@
__precompile__()
module NSGAII

export nsga, nsga_max, BinaryCoding #, nsga_binary
export nsga, nsga_max, BinaryCoding

using ProgressMeter
using Random
Expand All @@ -15,33 +15,44 @@ include("binarycoding.jl")
# include("vOptWrapper.jl")

function nsga(popSize::Integer, nbGen::Integer, z::Function, init::Function ;
fdecode = identity, fdecode! = (g,p)-> (p.=g;nothing), fCV = x->0., pmut = 0.05, fmut = default_mutation!,
fcross = default_crossover!, seed = typeof(init())[], fplot = x->nothing, plotevery = 1, showprogress = true)
X = create_indiv(init(), fdecode, z, fCV)
return _nsga(X, Min(), popSize, nbGen, init, z, fdecode, fdecode!, fCV , pmut, fmut, fcross, seed, fplot, plotevery, showprogress ? 0.5 : Inf)
fCV = x -> 0., pmut = 0.05, fmut = default_mutation!, fcross = default_crossover!,
seed = typeof(init())[], fplot = x -> nothing, plotevery = 1, showprogress = true)

X = createIndiv(init(), z, fCV)

return _nsga(X, Min(), popSize, nbGen, init, z, fCV, pmut, fmut, fcross, seed, fplot, plotevery, showprogress ? 0.5 : Inf)
end

function nsga(popSize::Integer, nbGen::Integer, z::Function, bc::BinaryCoding ;
fCV = x->0., pmut = 0.05, fmut = default_mutation!, fcross = default_crossover!,
seed = Vector{Float64}[], fplot = x->nothing, plotevery = 1, showprogress = true)
init = ()->bitrand(bc.nbbitstotal)
X = create_indiv(init(), x->decode(x, bc), z, fCV)
return _nsga(X, Min(), popSize, nbGen, init, z, x->decode(x, bc), (g,f)->decode!(g, bc, f), fCV , pmut, fmut, fcross, encode.(seed, (bc,)), fplot, plotevery, showprogress ? 0.5 : Inf)
fCV = x -> 0., pmut = 0.05, fmut = indiv -> default_mutation!(indiv.x), fcross = (indivs...) -> default_crossover!(getproperty.(indivs, :x)...),
seed = Vector{Float64}[], fplot = x -> nothing, plotevery = 1, showprogress = true)

init = () -> BinaryCodedIndiv(bitrand(bc.nbbitstotal), zeros(bc.nbvar))
_z = indiv -> (decode!(indiv, bc) ; z(indiv.p))

X = createIndiv(init(), _z, indiv -> fCV(indiv.p))
return _nsga(X, Min(), popSize, nbGen, init, _z, fCV , pmut, fmut, fcross, encode.(seed, Ref(bc)), fplot, plotevery, showprogress ? 0.5 : Inf)
end

function nsga_max(popSize::Integer, nbGen::Integer, z::Function, init::Function ;
fdecode = identity, fdecode! = (g,p)-> (p.=g;nothing), fCV = x->0., pmut = 0.05, fmut = default_mutation!,
fcross = default_crossover!, seed = typeof(init())[], fplot = x->nothing, plotevery = 1, showprogress = true)
X = create_indiv(init(), fdecode, z, fCV)
return _nsga(X, Max(), popSize, nbGen, init, z, fdecode, fdecode!, fCV , pmut, fmut, fcross, seed, fplot, plotevery, showprogress ? 0.5 : Inf)
fCV = x -> 0., pmut = 0.05, fmut = default_mutation!, fcross = default_crossover!,
seed = typeof(init())[], fplot = x -> nothing, plotevery = 1, showprogress = true)

X = createIndiv(init(), z, fCV)

return _nsga(X, Max(), popSize, nbGen, init, z, fCV, pmut, fmut, fcross, seed, fplot, plotevery, showprogress ? 0.5 : Inf)
end

function nsga_max(popSize::Integer, nbGen::Integer, z::Function, bc::BinaryCoding ;
fCV = x->0., pmut = 0.05, fmut = default_mutation!, fcross = default_crossover!,
seed = Vector{Float64}[], fplot = x->nothing, plotevery = 1, showprogress = true)
init = ()->bitrand(bc.nbbitstotal)
X = create_indiv(init(), x->decode(x, bc), z, fCV)
return _nsga(X, Max(), popSize, nbGen, init, z, x->decode(x, bc), (g,f)->decode!(g, bc, f), fCV , pmut, fmut, fcross, encode.(seed, (bc,)), fplot, plotevery, showprogress ? 0.5 : Inf)
function nsga_max(popSize::Integer, nbGen::Integer, z::Function, bc::BinaryCoding ;
fCV = x -> 0., pmut = 0.05, fmut = indiv -> default_mutation!(indiv.x), fcross = (indivs...) -> default_crossover!(getproperty.(indivs, :x)...),
seed = Vector{Float64}[], fplot = x -> nothing, plotevery = 1, showprogress = true)

init = () -> BinaryCodedIndiv(bitrand(bc.nbbitstotal), zeros(bc.nbvar))
_z = indiv -> (decode!(indiv, bc) ; z(indiv.p))

X = createIndiv(init(), _z, indiv -> fCV(indiv.p))
return _nsga(X, Max(), popSize, nbGen, init, _z, fCV , pmut, fmut, fcross, encode.(seed, Ref(bc)), fplot, plotevery, showprogress ? 0.5 : Inf)
end


end # module
41 changes: 20 additions & 21 deletions src/binarycoding.jl
Expand Up @@ -7,6 +7,11 @@ struct BinaryCoding
nbbitstotal::Int
end

struct BinaryCodedIndiv
x::BitVector
p::Vector{Float64}
end

function BinaryCoding::Int, types::Vector{Symbol}, lb, ub)
@assert length(types) == length(lb) == length(ub)
@assert all(lb .< ub)
Expand All @@ -29,54 +34,48 @@ function BinaryCoding(ϵ::Int, types::Vector{Symbol}, lb, ub)
end
BinaryCoding::Int, lb, ub) = BinaryCoding(ϵ, fill(:Cont, length(lb)), lb, ub)


function decode(x, d::BinaryCoding)
res = zeros(d.nbvar)
decode!(x, d, res)
res
end

function decode!(x, d::BinaryCoding, res::Vector{Float64})
function decode!(indiv::BinaryCodedIndiv, d::BinaryCoding)
j = 0
for i = 1:d.nbvar
j += d.nbbits[i]
if d.types[i] == :Bin
res[i] = x[j] == 1 ? 1. : 0.
indiv.p[i] = indiv.x[j] == 1 ? 1. : 0.
else
val = zero(UInt128)
puis = one(UInt128)
for ind = j:-1:j-d.nbbits[i]+1
x[ind] && (val += puis)
indiv.x[ind] && (val += puis)
puis *= 2
end

if d.types[i] == :Cont
res[i] = d.lb[i] + val * (d.ub[i] - d.lb[i]) / (UInt128(2)^d.nbbits[i] - 1)
indiv.p[i] = d.lb[i] + val * (d.ub[i] - d.lb[i]) / (UInt128(2)^d.nbbits[i] - 1)
else
res[i] = val + d.lb[i]
indiv.p[i] = val + d.lb[i]
end
end
end
res
indiv
end

function encode(x, d::BinaryCoding)::BitVector
res = BitVector()
sizehint!(res, d.nbbitstotal)
encode(p, d) = encode([p], d)
function encode(p::AbstractVector, d::BinaryCoding)::BinaryCodedIndiv
res = BinaryCodedIndiv(BitVector(), p)
sizehint!(res.x, d.nbbitstotal)
for i = 1:d.nbvar
if d.types[i] == :Int
tab = reverse(digits(Bool, round(Int, x[i] - d.lb[i]), base = 2, pad = d.nbbits[i]))
append!(res, tab)
tab = reverse(digits(Bool, round(Int, res.p[i] - d.lb[i]), base = 2, pad = d.nbbits[i]))
append!(res.x, tab)
elseif d.types[i] == :Bin
push!(res, x[i]!=0)
push!(res.x, p[i]!=0)
else
t = (x[i] - d.lb[i]) / (d.ub[i] - d.lb[i]) * (UInt128(2)^d.nbbits[i] - 1)
t = (p[i] - d.lb[i]) / (d.ub[i] - d.lb[i]) * (UInt128(2)^d.nbbits[i] - 1)
target = round(UInt128, t)
if target == UInt128(2)^d.nbbits[i] - 1
target -= 1
end
tab = reverse(digits(Bool, target, base = 2, pad = d.nbbits[i]))
append!(res, tab)
append!(res.x, tab)
end
end
res
Expand Down
6 changes: 3 additions & 3 deletions src/crossover.jl
@@ -1,6 +1,8 @@
function crossover!(ind_a, ind_b, fcross, child_a, child_b)
fcross(ind_a.x, ind_b.x, child_a.x, child_b.x)
end
(default_crossover!(pa::T, pb::T, ca, cb)) where T<:AbstractVector{Bool} = two_point_crossover!(pa, pb, ca, cb)
(default_crossover!(pa::T, pb::T, ca, cb)) where T<:AbstractVector{Int} = PMX_crossover!(pa, pb, ca, cb)

function two_point_crossover!(bits_a, bits_b, child1, child2)
cut_a = cut_b = rand(2:length(bits_a)-1)
Expand All @@ -17,7 +19,6 @@ function two_point_crossover!(bits_a, bits_b, child1, child2)
copyto!(child2, cut_a, bits_a, cut_a, cut_b-cut_a+1)
copyto!(child2, cut_b+1, bits_b, cut_b+1, length(bits_a)-cut_b)
end
(default_crossover!(pa::T, pb::T, ca, cb)) where T<:AbstractVector{Bool} = two_point_crossover!(pa, pb, ca, cb)

function PMX_crossover!(pa, pb, ca, cb)
cut_a = cut_b = rand(1:length(pa))
Expand Down Expand Up @@ -51,5 +52,4 @@ function PMX_crossover!(pa, pb, ca, cb)
ca[j] = pb[i]
end
end
end
(default_crossover!(pa::T, pb::T, ca, cb)) where T<:AbstractVector{Int} = PMX_crossover!(pa, pb, ca, cb)
end
28 changes: 13 additions & 15 deletions src/functions.jl
@@ -1,13 +1,13 @@
function _nsga(::indiv{G,Ph,Y}, sense, popSize, nbGen, init, z, fdecode, fdecode!,
fCV , pmut, fmut, fcross, seed, fplot, plotevery, refreshtime)::Vector{indiv{G,Ph,Y}} where {G,Ph,Y}
function _nsga(::Indiv{G, Y}, sense, popSize, nbGen, init, z,
fCV, pmut, fmut, fcross, seed, fplot, plotevery, refreshtime)::Vector{Indiv{G, Y}} where {G,Y}

popSize = max(popSize, length(seed))
isodd(popSize) && (popSize += 1)
P = Vector{indiv{G,Ph,Y}}(undef, 2*popSize)
P[1:popSize-length(seed)] .= [create_indiv(init(), fdecode, z, fCV) for _ = 1:popSize-length(seed)]
P = Vector{Indiv{G, Y}}(undef, 2 * popSize)
P[1:(popSize - length(seed))] .= [createIndiv(init(), z, fCV) for _ = 1:(popSize - length(seed))]
for i = 1:length(seed)
P[popSize-length(seed)+i] = create_indiv(convert(G, seed[i]), fdecode, z, fCV)
if fCV(P[popSize-length(seed)+i].pheno) > 0
P[popSize - length(seed) + i] = createIndiv(convert(G, seed[i]), z, fCV)
if fCV(P[popSize - length(seed) + i].x) > 0
@warn "element $i of the seed is unfeasible"
end
end
Expand All @@ -17,20 +17,18 @@ function _nsga(::indiv{G,Ph,Y}, sense, popSize, nbGen, init, z, fdecode, fdecode
fast_non_dominated_sort!(view(P, 1:popSize), sense)

@showprogress refreshtime for gen = 1:nbGen
# for gen = 1:nbGen

for i = 1:2:popSize

pa = tournament_selection(P)
pb = tournament_selection(P)

crossover!(pa, pb, fcross, P[popSize+i], P[popSize+i+1])
crossover!(pa, pb, fcross, P[popSize + i], P[popSize + i + 1])

rand() < pmut && mutate!(P[popSize+i], fmut)
rand() < pmut && mutate!(P[popSize+i+1], fmut)
rand() < pmut && mutate!(P[popSize + i], fmut)
rand() < pmut && mutate!(P[popSize + i + 1], fmut)

eval!(P[popSize+i], fdecode!, z, fCV)
eval!(P[popSize+i+1], fdecode!, z, fCV)
eval!(P[popSize + i], z, fCV)
eval!(P[popSize + i + 1], z, fCV)
end

fast_non_dominated_sort!(P, sense)
Expand All @@ -46,7 +44,7 @@ function _nsga(::indiv{G,Ph,Y}, sense, popSize, nbGen, init, z, fdecode, fdecode
end
indnext == 0 && (indnext = length(P))
crowding_distance_assignment!(view(P, ind+1:indnext))
sort!(view(P, ind+1:indnext), by = x -> x.crowding, rev = true, alg = PartialQuickSort(popSize-ind))
sort!(view(P, (ind + 1):indnext), by = x -> x.crowding, rev = true, alg = PartialQuickSort(popSize - ind))
end

gen % plotevery == 0 && fplot(P)
Expand Down Expand Up @@ -96,7 +94,7 @@ function fast_non_dominated_sort!(pop::AbstractVector{T}, sense) where {T}
nothing
end

function crowding_distance_assignment!(pop::AbstractVector{indiv{X, G, NTuple{N, T}}}) where {X, G, N, T}
function crowding_distance_assignment!(pop::AbstractVector{Indiv{X, NTuple{N, T}}}) where {X, N, T}
if N == 2
sort!(pop, by = x -> x.y[1])
pop[1].y[1] == pop[end].y[1] && return #Don't waste time if all indivs are the same
Expand Down
31 changes: 15 additions & 16 deletions src/indivs.jl
@@ -1,23 +1,23 @@
mutable struct indiv{G, P, Y}#Genotype, Phenotype, Type(Y_N)
mutable struct Indiv{G, Y} # Genotype, Type(Y_N)
x::G
pheno::P
y::Y
CV::Float64
rank::UInt16
crowding::Float64
dom_count::UInt16
dom_list::Vector{UInt16}
indiv(x::G, pheno::P, y::Y, cv) where {G,P,Y} = new{G, P, Y}(x, pheno, y, cv, zero(UInt16), 0., zero(UInt16), UInt16[])
Indiv(x::G, y::Y, cv) where {G, Y} = new{G, Y}(x, y, cv, zero(UInt16), 0., zero(UInt16), UInt16[])
end
function create_indiv(x, fdecode, z, fCV)
pheno = fdecode(x)
indiv(x, pheno, z(pheno), fCV(pheno))
function createIndiv(x, z, fCV)
y = z(x)
cv = fCV(x)
Indiv(x, y, cv)
end

struct Max end
struct Min end

function dominates(::Min, a::indiv, b::indiv)
function dominates(::Min, a::Indiv, b::Indiv)
a.CV != b.CV && return a.CV < b.CV
res = false
for i in eachindex(a.y)
Expand All @@ -27,7 +27,7 @@ function dominates(::Min, a::indiv, b::indiv)
res
end

function dominates(::Max, a::indiv, b::indiv)
function dominates(::Max, a::Indiv, b::Indiv)
a.CV != b.CV && return a.CV < b.CV
res = false
for i in eachindex(a.y)
Expand All @@ -37,10 +37,10 @@ function dominates(::Max, a::indiv, b::indiv)
res
end

Base.:(==)(a::indiv, b::indiv) = a.x == b.x
Base.hash(a::indiv) = hash(a.x)
Base.isless(a::indiv, b::indiv) = a.rank < b.rank || a.rank == b.rank && a.crowding >= b.crowding #Comparison operator for tournament selection
Base.show(io::IO, ind::indiv) = print(io, "indiv($(repr_pheno(ind.pheno)) : $(ind.y) | rank : $(ind.rank))")
Base.:(==)(a::Indiv, b::Indiv) = a.x == b.x
Base.hash(a::Indiv) = hash(a.x)
Base.isless(a::Indiv, b::Indiv) = a.rank < b.rank || a.rank == b.rank && a.crowding >= b.crowding #Comparison operator for tournament selection
Base.show(io::IO, ind::Indiv) = print(io, "Indiv($(repr_pheno(ind.x)) : $(ind.y) | rank : $(ind.rank))")
repr_pheno(x) = repr(x)
function repr_pheno(x::Union{BitVector, Vector{Bool}})
res = map(x -> x ? '1' : '0', x)
Expand All @@ -51,9 +51,8 @@ function repr_pheno(x::Union{BitVector, Vector{Bool}})
end
end

function eval!(indiv::indiv, fdecode!::Function, z::Function, fCV::Function)
fdecode!(indiv.x, indiv.pheno)
indiv.CV = fCV(indiv.pheno)
indiv.CV 0 && (indiv.y = z(indiv.pheno))
function eval!(indiv::Indiv, z::Function, fCV::Function)
indiv.CV = fCV(indiv.x)
indiv.CV 0 && (indiv.y = z(indiv.x))
indiv
end
7 changes: 4 additions & 3 deletions src/mutation.jl
@@ -1,4 +1,7 @@
mutate!(ind::indiv, fmut!) = fmut!(ind.x)
mutate!(ind::Indiv, fmut!) = fmut!(ind.x)
default_mutation!(p::Vector{Int}) = rand_swap!(p)
default_mutation!(b::T) where T<:AbstractVector{Bool} = rand_flip!(b)

function rand_flip!(bits)
nb = length(bits)
for i = 1:nb
Expand All @@ -7,7 +10,6 @@ function rand_flip!(bits)
end
end
end
default_mutation!(b::T) where T<:AbstractVector{Bool} = rand_flip!(b)

function rand_swap!(perm::Vector{Int})
i = j = rand(1:length(perm))
Expand All @@ -16,5 +18,4 @@ function rand_swap!(perm::Vector{Int})
end
@inbounds perm[i], perm[j] = perm[j], perm[i]
end
default_mutation!(p::Vector{Int}) = rand_swap!(p)

4 changes: 2 additions & 2 deletions test/runtests.jl
Expand Up @@ -24,5 +24,5 @@ res = nsga(500, 200, z, d, seed = seed)
const bc = BinaryCoding(6, [:Cont,:Cont,:Int,:Int,:Bin,:Int], [-10,-10,-10,10,0,0], [10,10,10,20,1,2])
seed = [-9.5, 9.5, 5, 15, 1, 1]
bincoded = NSGAII.encode(seed, bc)
decoded = NSGAII.decode(bincoded, bc)
@test all(decoded .≈ seed)
NSGAII.decode!(bincoded, bc)
@test all(bincoded.p .≈ seed)

0 comments on commit 7eb74e8

Please sign in to comment.