NSGAII.jl
NSGAII.jl copied to clipboard
A NSGA-II implementation in Julia
References
Installation
julia> ]
pkg> add NSGAII
Usage
Example : Bi-Objective Knapsack
using NSGAII
n = 20 #Number of items
p1 = [77,94,71,63,96,82,85,75,72,91,99,63,84,87,79,94,90,60,69,62] #Coeffs objective 1
p2 = [65,90,90,77,95,84,70,94,66,92,74,97,60,60,65,97,93,60,69,74] #Coeffs objective 2
w = [80,87,68,72,66,77,99,85,70,93,98,72,100,89,67,86,91,79,71,99] #Items weights
c = 900 #Knapsack capacity
The four mandatory parameters of NSGAII are
- the size of the population
- the number of generations
- an initialization function
- an evaluation function
using Random: bitrand
using LinearAlgebra: dot
popsize = 100
nbgen = 200
init() = bitrand(n) #our genotype is a binary vector of size n, initialized randomly
z(x) = dot(x, p1), dot(x, p2) #and our objectives are the sum of the items we pick
Now, this would be enough to run nsga-2 with
nsga_max(popsize, nbgen, z, init)
But we need to add the constraint that all items must fit in the knapsack.
For this we define a constraint-violation function that returns 0 only if the solution is feasible,
and a value > 0 otherwise.
function CV(x)
sumW = dot(x, w)
return sumW <= c ? 0 : sumW - c
end
#We can now call
result = nsga_max(popsize, nbgen, z, init, fCV = CV)
result will be a vector of individuals.
The revelant fields of an individual indiv are :
- genotype :
indiv.x - objective values :
indiv.y - rank :
indiv.rank - constraint violation value :
indiv.CV
Crossover
If the solutions are encoded as bitstrings, a 2-point crossover will be used by default, but we can define our own and assign it with the keyword fcross:
function one_point_crossover!(parent_a, parent_b, child_a, child_b)
n = length(parent_a)
cut = rand(1:n-1)
child_a[1:cut] .= parent_a[1:cut]
child_a[cut+1:n] .= parent_b[cut+1:n]
child_b[1:cut] .= parent_b[1:cut]
child_b[cut+1:n] .= parent_a[cut+1:n]
end
nsga_max(popsize, nbgen, z, init, fCV = CV, fcross = one_point_crossover!)
For permutations genotypes, the default crossover is the PMX (Partially-Mapped Crossover)
Mutation
The default mutation for a binary vector is the bitstring mutation where each bit has a probability 1/l to be flipped (where l is the length of the vector)
As with crossovers, we can define or own mutation operator and assign it with the keyword fmut. The probability of mutation can be changed with the keyword pmut.
Let's say we want our mutation to flip two random bits :
function two_bits_flip!(bits)
for i = 1:2
n = rand(1:length(bits))
bits[n] = 1 - bits[n]
end
end
nsga_max(popsize, nbgen, z, init, fCV = CV, fmut = two_bits_flip!, pmut = 0.2)
For permutations genotypes, the default mutation randomly swaps two indices.
Seeding
Starting solutions can be provided as a vector with the keyword seed, for example :
x1 = greedy(p1, w, c)
x2 = greedy(p2, w, c)
x3 = greedy(p1 .+ p2, w, c)
nsga_max(popsize, nbgen, z, init, fCV = CV, seed = [x1, x2, x3])
Make sure the type of your seeds is the same as the one given by calling init() !
Plotting
A plot function can be passed with the keyword fplot, by default the population is plotted at every generation but this can be changed with the keyword plotevery.
Example with PyPlot :
using PyPlot
function plot_pop(P)
clf() #clears the figure
P = filter(indiv -> indiv.rank == 1, P) #keep only the non-dominated solutions
plot(map(x -> x.y[1], P), map(x -> x.y[2], P), "bo", markersize = 1)
sleep(0.1)
end
nsga_max(popsize, nbgen, z, init, fCV = CV, fplot = plot_pop, plotevery = 5)
BinaryCoding
You can use BinaryCoding(ϵ::Int, lb::Vector, ub::Vector) to encode real variables with a precision ϵ, and with lower and upper bounds lb and ub
Example : MOP7 from Van Valedhuizen’s Test Suite

using NSGAII
using Plots: scatter3d
f1(x1,x2) = ((x1-2)^2)/2 + ((x2+1)^2)/13 + 3
f2(x1,x2) = ((x1+x2-3)^2)/36 + ((-x1+x2+2)^2)/8 - 17
f3(x1,x2) = ((x1+2x2-1)^2)/175 + ((-x1+2x2)^2)/17 - 13
z(x) = f1(x[1], x[2]), f2(x[1], x[2]), f3(x[1], x[2])
#Encodes two variables -400 <= x_i <= 400, with a precision of 1E-4
const bc = BinaryCoding(4, [-400, -400], [400, 400])
function plot_pop(pop)
pop = filter(indiv -> indiv.rank <= 1, pop) #keeps only the non-dominated solutions
scatter3d(map(x -> x.y[1], pop), map(x -> x.y[2], pop), map(x -> x.y[3], pop), markersize = 1) |> display
sleep(0.1)
end
nsga(500, 100, z, bc, seed = [[1.,-1.],[2.5,0.5],[0.5,0.25]], fplot = plot_pop)
- The initialization function isn't needed anymore.
- The seed is passed as a vector of phenotypes, not a vector of genotypes, it is automatically encoded.
You can also use BinaryCoding(ϵ::Int, types, lb, ub) to encode a mix of integer, continuous or binary variables, with types a vector of symbols : ( :Int | :Cont | :Bin ).
Misc
The progress bar can be disabled by calling nsga(..., showprogress = false)