1+1 EA for maximising a pseudoboolean function
This tutorial showcases how to use the built-in 1+1 Evolutionary Algorithm (EA).
First, we import our modules like so:
using EvoLP
using OrderedCollections
using StatisticsFor this example, we will use the onemax test function, which is already included in EvoLP:
EvoLP.onemax — Function
The OneMax function returns the sum of the individual. For an individual of length $n$, maximum is achieved with $n$ ones.
\[\text{OneMax}(\boldsymbol{x}) = \sum_{i=1}^n x_i\]
In an EA we use vectors as individuals. The 1+1 EA features 1 parent and 1 offspring each iteration. Let's start creating the first individual. We can generate it manually, or use a generator. Let's do the latter:
EvoLP.binary_vector_pop — Function
binary_vector_pop(μ, n; rng=Random.GLOBAL_RNG)Generate a population of μ vector binary individuals, each of length n.
Examples
julia> using EvoLP
julia> binary_vector_pop(2, 5)
2-element Vector{BitVector}:
[1, 0, 1, 1, 0]
[0, 1, 0, 0, 0]It is important to note that the return value of the binary_vector_pop generator is a population: a list. This means we only want the first (and only) element inside:
ind_size = 16
firstborn = binary_vector_pop(1, ind_size)[1]16-element BitVector:
0
0
1
0
1
1
1
1
1
1
0
0
0
0
0
0Since the 1+1 EA works on a single individual, we only have the mutation step. We can set up the appropriate mutation operator: BitwiseMutator.
EvoLP.BitwiseMutator — Type
Bitwise mutation with probability χ of flipping each bit.
This mutation operator needs a probability $\lambda$ for flipping each bit, so we pass it like so:
Mut = BitwiseMutator(1/ind_size)BitwiseMutator(0.0625)Now on to the fitness function. Since EvoLP is built for minimisation, in order to do maximisation we need to optimise for the negative of OneMax:
f(x) = -onemax(x)f (generic function with 1 method)Let's use the Logbook to record the fitness value on each iteration. We can do so by the Base.identity function as it will return the same value as the fitness:
statnames = ["fit"]
callables = [identity]
thedict = LittleDict(statnames, callables)
logbook = Logbook(thedict)Logbook(OrderedCollections.LittleDict{AbstractString, Function, Vector{AbstractString}, Vector{Function}}("fit" => identity), NamedTuple{(:fit,)}[])We are now ready to use the oneplusone built-in algorithm:
EvoLP.oneplusone — Function
oneplusone(f, ind, k_max, M)
oneplusone(logger::Logbook, f, ind, k_max, M)1+1 Evolutionary Algorithm.
Arguments
f::Function: objective function to minimise.ind::AbstractVector: individual to start the evolution.k_max::Integer: number of iterations.M::Mutator: one of the availableMutator.
Returns a Result.
result = oneplusone(logbook, f, firstborn, 64, Mut);Result(-13, Bool[1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1], Vector{Bool}[[1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1]], 64, 128, 0.075633009)The output was suppressed so that we can analyse each part of the result separately using the Result functions:
@show optimum(result)-13@show optimizer(result)16-element Vector{Bool}:
1
0
1
1
1
1
0
1
1
1
1
0
1
1
1
1@show f_calls(result)128We can also take a look at the logbook records and see how the statistics changed throughout the run (although in this case we just logged the fitness):
first(logbook.records, 20)20-element Vector{NamedTuple{(:fit,)}}:
(fit = [-9],)
(fit = [-9],)
(fit = [-9],)
(fit = [-9],)
(fit = [-9],)
(fit = [-9],)
(fit = [-9],)
(fit = [-10],)
(fit = [-10],)
(fit = [-11],)
(fit = [-11],)
(fit = [-11],)
(fit = [-11],)
(fit = [-11],)
(fit = [-11],)
(fit = [-11],)
(fit = [-11],)
(fit = [-11],)
(fit = [-11],)
(fit = [-11],)