tournament-selection-genetic-algorithm
Version:
A tournament selection based genetic algorithm class.
190 lines (162 loc) • 6 kB
JavaScript
class GeneticAlgorithm {
/**
* GeneticAlgorithm
* @param {number} geneLength the size Genotypes can be.
* @param {Array} genes the available values a Genotype can consist of.
*/
constructor(geneLength = 10,genes = [0,1]) {
this.geneLength = geneLength;
this.genes = genes;
this.crossoverFn = this.singlePointCrossover;
this.mutationProbability = 0.01;
this.demes = true;
}
/**
* Generate a population of Genotypes of length 'size'
* @param {number} size
*/
generatePopulation(size) {
const population = [];
for(let i = 0; i < size; i++) {
const genotype = [];
for(let i = 0; i < this.geneLength; i++) {
const randomGene = this.genes[Math.floor(Math.random()*this.genes.length)];
genotype.push(randomGene);
}
population.push(genotype);
}
this.setPopulation(population);
}
setPopulation(data) {
this.population = data;
for(let i = 0; i < this.population.length; i++) {
this.population[i].generation = 0;
}
}
/**
* Select two random indices of the population
*/
selectRandomGenotypes() {
let index1,index2;
if(this.demes) {
index1 = Math.floor(Math.random()*this.population.length);
index2 = index1 + ( (Math.random() < 0.5 && index1 > 0) || index1 == this.population.length-1 ? -1 : 1);
} else {
while(index1 === index2) {
index1 = Math.floor(Math.random()*this.population.length);
index2 = Math.floor(Math.random()*this.population.length);
}
}
return [index1,index2];
}
/**
* Compare fitness of genotypes and return winner and loser indicies.
* If fitnesses are equal, then still return one as
* winner and one as loser to allow for mutation.
* @param {Genotype} G1
* @param {Genotype} G2
*/
tournament(G1,G2) {
const f1 = this.fitness(G1);
const f2 = this.fitness(G2);
if(f1 > f2) {
return [0,1];
} else {
return [1,0];
}
}
/**
* Mutate the supplied Genotype with the supplied genes
* @param {Genotype} G
* @param {number} probability
*/
mutate(G,probability = 0.01) {
for(let i = 0; i < G.length; i++) {
if(Math.random() < probability) {
G[Math.floor(Math.random()*G.length)] = this.genes[Math.floor(Math.random()*this.genes.length)];
}
}
return G;
}
/**
* Return the fitness of the supplied Genotype
* @param {Genotype} G
*/
fitness(G) {
throw new Error('Fitness function must be overidden!');
}
/**
* Split each Genotype by a random index and concatenate.
* @param {Genotype} winner
* @param {Genotype} loser
*/
singlePointCrossover(winner,loser) {
const crossoverIndex = Math.floor(Math.random()*winner.length);
return [].concat(winner.slice().splice(0,crossoverIndex),loser.slice().splice(crossoverIndex));
}
/**
* Split each Genotype by a two random indices and concatenate.
* @param {Genotype} winner
* @param {Genotype} loser
*/
twoPointCrossover(winner,loser) {
const sections = [
Math.floor(Math.random()*winner.length),
Math.floor(Math.random()*winner.length)
].sort((a,b) => a-b);
return [].concat(
winner.slice().splice(0,sections[0]),
loser.slice().splice(sections[0],(sections[1]-sections[0])),
winner.slice().splice(sections[1])
);
}
/**
* Create a new Genotype by randomly selecting genes from each of the winner and loser.
* @param {Genotype} winner
* @param {Genotype} loser
*/
uniformCrossover(winner,loser) {
const offspring = [];
const probability = 0.5
for(let i = 0; i < winner.length; i++) {
if(Math.random() < probability) {
offspring.push(winner[i]);
} else {
offspring.push(loser[i]);
}
}
return offspring;
}
/**
* Just keep the winner.
* @param {Genotype} winner
* @param {Genotype} loser
*/
noCrossover(winner,loser) {
return winner;
}
/**
* Run the algorithm
* @param {number} populationCount
* @param {number} generationCount
*/
run(populationCount = 100, generationCount = 100) {
this.generatePopulation(populationCount);
for(let i = 0; i < generationCount * populationCount; i++) {
// select tornament contestants
const contestantIndicies = this.selectRandomGenotypes();
const generation = this.population[contestantIndicies[0]].generation;
// decide winner and loser
const outcome = this.tournament(this.population[contestantIndicies[0]],this.population[contestantIndicies[1]]);
// crossover and apply new genotype to loser
this.population[contestantIndicies[outcome[1]]] = this.crossoverFn(this.population[contestantIndicies[0]],this.population[contestantIndicies[1]]);
// mutate loser slightly
this.population[contestantIndicies[outcome[1]]] = this.mutate(this.population[contestantIndicies[outcome[1]]],this.mutationProbability);
// Increase generation (based on winner's generation)
this.population[contestantIndicies[outcome[1]]].generation = generation+1;
// winner's fitness
const winnersFitness = this.fitness(this.population[contestantIndicies[outcome[0]]]);
}
}
};
module.exports = GeneticAlgorithm;