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 Statistics
For this example, we will use the onemax
test function, which is already included in EvoLP:
@doc onemax
The OneMax function returns the sum of the individual. For an individual of length $n$, maximum is achieved with $n$ ones.
$\text{OneMax}(\mathbf{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:
@doc binary_vector_pop
binary_vector_pop(n, l; rng=Random.GLOBAL_RNG)
Generate a population of
n
vector binary individuals, each of lengthl
.
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 = 15
firstborn = binary_vector_pop(1, ind_size)[1]
15-element BitVector:
0
0
1
1
0
0
1
0
0
1
1
0
1
0
1
Since the 1+1 EA works on a single individual, we only have the mutation step. We can set up the appropriate mutation operator: BitwiseMutator
.
@doc BitwiseMutator
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.06666666666666667)
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(LittleDict{AbstractString, Function, Vector{AbstractString}, Vector{Function}}("fit" => identity), NamedTuple{(:fit,)}[])
We are now ready to use the oneplusone
built-in algorithm:
@doc oneplusone
oneplusone(f::Function, ind, k_max, M)
oneplusone(logger::Logbook, f::Function, ind, k_max, M)
1+1 EA algorithm.
Arguments
f
: Objective function to minimiseind
: Individual to start the evolutionk_max
: Maximum number of iterationsM::Mutator
: A mutation method. See mutation.Returns a
Result
.
result = oneplusone(logbook, f, firstborn, 50, Mut);
The output was suppressed so that we can analyse each part of the result separately using the Result
functions:
@show optimum(result)
@show optimizer(result)
@show iterations(result)
@show f_calls(result)
optimum(result) = -15
optimizer(result) = Bool[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
iterations(result) = 50
f_calls(result) = 102
We 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 = [-7],)
(fit = [-7],)
(fit = [-8],)
(fit = [-9],)
(fit = [-9],)
(fit = [-9],)
(fit = [-9],)
(fit = [-9],)
(fit = [-9],)
(fit = [-9],)
(fit = [-9],)
(fit = [-9],)
(fit = [-9],)
(fit = [-10],)
(fit = [-10],)
(fit = [-10],)
(fit = [-10],)
(fit = [-11],)
(fit = [-11],)
(fit = [-11],)