This is an extract from my github repository, which implements a general GA and applies it to three various problem. You can check it out it here.
In this post:
The genetic algorithm is a method for solving optimization problems that is based on natural selection. The genetic algorithm repeatedly improves a population of individuals. At each iteration, the GA selects parents from the current population of individuals (ranking), which then produce the children for the next generation (breeding). Over many generations, the population converges to an optimal solution by replacing the weakest individuals by the children.
This process can be easily described using the following pseudocode:
create population
while (stopping criteria not met)
select parents from population
generate children
replace worst individual with child in the population
extract the best individual
To summarize: Population is the set of all the individuals (also called chromosomes). Each chromosome carries some information about itself stored and segmented into genes.
There are four critical parts of the algorithm and many different methods of how to approach them. Let's take a look at some of them and see how the (Micro) GA can be implemented on a high level. I will use this implementation as a base to our concrete problem later on.
public Population(int size) {
individuals = new Chromosome[size];
random = new Random();
for (int i = 0; i < size; i++) {
int[] genes = new int[10];
for (int j = 0; j < genes.length; j++) {
genes[j] = random.nextInt(2);
}
individuals[i] = new Chromosome(genes);
}
}
public Chromosome select() {
Chromosome best = null;
for (int i = 0; i < 5; i++) {
Chromosome individual = individuals[random.nextInt(individuals.length)];
if (best == null || individual.getFitness() > best.getFitness()) {
best = individual;
}
}
return best;
}
We actually call this function twice to generate two parents on which we then perform the crossover. In the main iteration loop, this will look like this:
// Select parents
Chromosome parent1 = population.select();
Chromosome parent2 = population.select();
// Crossover
Chromosome[] children = parent1.crossover(parent2);
parent1
and the rest from parent2
.
public Chromosome[] crossover(Chromosome other) {
int[] childGenes = new int[genes.length];
int[] otherChildGenes = new int[genes.length];
int midpoint = random.nextInt(genes.length);
for (int i = 0; i < midpoint; i++) {
childGenes[i] = genes[i];
otherChildGenes[i] = other.genes[i];
}
for (int i = midpoint; i < genes.length; i++) {
childGenes[i] = other.genes[i];
otherChildGenes[i] = genes[i];
}
Chromosome first_child = new Chromosome(childGenes);
Chromosome second_child = new Chromosome(otherChildGenes);
Chromosome[] children = {child1, child2};
return children;
}
We then replace this new child with the weakest individual (individual) with the lowest fitness function.
public void replaceWorst(Chromosome child) {
int worstIndex = 0;
double worstFitness = Double.MAX_VALUE;
for (int i = 0; i < individuals.length; i++) {
if (individuals[i].getFitness() < worstFitness) {
worstIndex = i;
worstFitness = individuals[i].getFitness();
}
}
individuals[worstIndex] = child;
}
while (generation <= MAX_GENERATIONS) {
/*
* Main Algorithm Loop
*/
generation++;
}
Now that we've understood the basic principles behind the GA, let's try to apply it to a specific problem. Let's assume we want to optimize part of the design of an aircraft to maximize lift. We model this problem as having a few components for which we want to maximize the lift force. To limit the ranges of the components, we assume we are modeling this problem on an airliner (e.g. Boeing 777 and similar). Let's model the aspects of the problem onto the components of the algorithm now.
Chromosome best = null;
for (int i = 0; i < 10; i++) {
Chromosome individual = individuals[random.nextInt(individuals.length)];
if (best == null || individual.getFitness() > best.getFitness()) {
best = individual;
}
}
return best;
double[] childGenes = new double[genes.length];
double[] otherChildGenes = new double[genes.length];
for (int i = 0; i < childGenes.length; i++) {
boolean mask = random.nextBoolean();
if (mask) {
childGenes[i] = this.getGenes()[i];
otherChildGenes[i] = other.getGenes()[i];
} else {
childGenes[i] = other.getGenes()[i];
otherChildGenes[i] = this.getGenes()[i];
}
}
Chromosome first_child = new Chromosome(childGenes);
Chromosome second_child = new Chromosome(otherChildGenes);
Chromosome[] children = {first_child, second_child};
return children;
After we've performed the crossover, we will replace the worst children by the new offspring.
POPULATION_SIZE = 150
and stopping criteria constant MAX_GENERATIONS = 100
.
Generation: 1
Best fitness: 8121325.844336658
Generation: 2
Best fitness: 8121325.844336658
Generation: 3
Best fitness: 8121325.844336658
...
...
...
Generation: 73
Best fitness: 9273070.677375384
Generation: 74
Best fitness: 9273070.677375384
Generation: 75
Best fitness: 9273070.677375384
Generation: 76
Best fitness: 1.2647211860884406E7
...
...
Generation: 96
Best fitness: 1.2647211860884406E7
Generation: 97
Best fitness: 1.2647211860884406E7
Generation: 98
Best fitness: 1.2647211860884406E7
Generation: 99
Best fitness: 1.2647211860884406E7
Generation: 100
Best fitness: 1.2647211860884406E7
------Component--------|----Value----
Speed of the aircraift | 345.61
Wing area | 189.95
Angle of attack | 0.41
Oswald eff. factor | 0.12
Wing aspect ratio | 5.29
We can reason that the delievered result is somewhat reasonable by realizing that a loaded airplane can weigh up to $600.000 kg$ is corresponding to at least $6MN$ of needed lift.
At generation 100, the best configuration has a lift force of roughly $9.5 MN$ (computed average of 10 simulations). Hence in the range of reasonable results.
In real life achieving this lift by an airplane might be impossible (and maybe also unnecessary) - it's more about a half of our computed maximum for not so extremely heavy airplanes. Result is coming from simplifying and theoretizing about the components and their ranges.
How quickly this algorithm (in terms of generations) increases the maximum lift is visible from the following plot of average over 5 independent simulations.
See the complete implementation and two more similar examples on my GitHub.