Darwin
We now are ready to implement the core of the evolutionary algorithm. Our goal is to run a simulation of an evolution for a number of generations. Before we can tie the preceding chapters together, we still need to provide a few key ingredients
- mutation, a means to change a Program in the hopes to be better adopted to the environment.
- crossover, a means for two Programs to have offspring, having parts of their parents DNA.
Nadezhda
mutate
We will mutate a program in the following way. For each part of the program, we
will decide if we are changing it or not. The MutateDecision is responsible
for making this decision.
1 pub trait MutateDecision {
2 fn should_mutate(&mut self) -> bool;
3 }
We will provide a trait Mutable that accepts a MutateDecision in a mutate
function. It will return a mutate variant of Self, in our case a Program.
1 pub trait Mutatable {
2 fn mutate(&self, decide: &mut MutateDecision) -> Self;
3 }
One way of implementing Mutatable for Program is shown below.
1 impl Mutatable for Program {
2 fn mutate(&self, mut decide: &mut MutateDecision) -> Program {
3 match *self {
4 Program::Forward(ref contained_program) => {
5 if decide.should_mutate() {
6 Program::Backward(Box::new(contained_program.mutate(decide)))
7 } else {
8 Program::Forward(Box::new(contained_program.mutate(decide)))
9 }
10 },
11 Program::Backward(ref contained_program) => {
12 if decide.should_mutate() {
13 Program::Forward(Box::new(contained_program.mutate(decide)))
14 } else {
15 Program::Backward(Box::new(contained_program.mutate(decide)))
16 }
17 },
18 Program::Stop => Program::Stop,
19 }
20 }
21 }
Crossover
Crossover is combining the DNA of two parent to produce offspring. We will always use the two-for-two method. I.e. take two parents and produce two offspring.
For the Nadezhda setting, the high level crossover algorithm is outlined below. We have parents A and B.
- Randomly chose a cut-points in programs A and B.
- The two children are
- Everything before cut-point of A followed by everything after cut-point of B.
- Everything before cut-point of B followed by everything after cut-point of A.
Evolve
Evolution is the culmination of our hard work. This ties in all the ingredients we have been exploring. The gist of evolution is
- Generate a random population.
- Until you are satisfied repeat.
- Create a successor population.
A succession can be made in a number of ways, but variations do not seem to change the overall effect. Below we list some characteristic steps.
- Determine the fitness of the individuals in the generation.
- Pass top individuals directly into the next generation.
- Repeat until population is big enough
- Cross two parents and add children to the population.
- Mutate an individual and add to the population.
- A combination of the above.
Exercises
- Implement a RandomDecision. I.e. implement the trait
MutateDecisionby performing a random decision. - Can the mutate function shown change the length of a
Program? - Implement crossover for Nadezhda.