Genetic Programming EngineBuild Status

This Genetic Programming library in Rust was developed for the "Fly me to the Moon" workshop.

Purpose

Genetic Programming is about expressing the solution to a particular problem in an Abstract Syntax Tree (i.e., as a representation of a computer program that will solve the problem), and then using genetic operations to generate and improve the population of programs. Hopefully, eventually the population will converge to a set of programs that successfully solve the problem in question.

Genetic Programming works with by evolving subsequent generations of an initially randomized population. The create a new generation, we apply 3 genetic operations to individual programs from the last generation.

The genetic operations are:

You'll notice that all of these genetic operations contain the notion of a "picking an individual". The library provides tournament selection, which picks N random individuals, and then selects the best individual from those N. This gives fitter individuals a better chance of being chosen.

Mutation and crossover are implemented at the AST node level. We'll pick a random node from the AST and replace it with another node (either a randomly generated subtree, or a randomly picked node of the same type from the other parent).

How to use this library

This library provides the evolution framework. To use it, you need to supply:

Grammar nodes

Grammar nodes are specified as enums which need to implement some particular traits. Most of these trait implementations can be autogenerated using a macro, though:

#[derive(Clone)]
enum Statement {
    IfFoodAhead(Box<Statement>, Box<Statement>),
    Prog2(Box<Statement>, Box<Statement>),
    Prog3(Box<Statement>, Box<Statement>, Box<Statement>),
    Command(Box<Command>)
}

impl_astnode!(Statement, 1,
              int IfFoodAhead(then, els),
              int Prog2(one, two),
              int Prog3(one, two, three),
              leaf Command(cmd));

Fitness function

The fitness function has the following shape:

fn fitness(program: &Program, rng: &mut Rng) -> SomeFitnessStruct

Where Program is the root type of the AST, and SomeFitnessStruct is any object that implements Fitness. Use the struct to retain any information you want to persist after evolving (for example, a trace of a simulation). If that's not required, a SimpleFitness object can be used.

Ultimately, the score is provided in a ScoreCard object, which contains both labels and scores. Scores may be negative to encode penalties:

SimpleFitness::new(vec![
    ("food_eaten", ant.food_eaten as f32),
    ("complexity_penalty", (depth(ant) as f32) * -10.)
])