An advanced genetic algorithm library that aims to be performant, tested and easy to use.
npm install genetic-iter
const GeneticIter = require('genetic-iter')
// Initialize it the way you want
const geneticIter = new GeneticIter({
optimize: GeneticIter.Optimize.Maximize,
select1: GeneticIter.Select1.bestOf2,
select2: GeneticIter.Select2.bestOf2,
mutate: x => x,
crossover: (a, b) => [a, b],
seed: Math.random,
fitness: (a) => a * 2,
populationSize: 250,
crossoverChance: 0.9,
fittestAlwaysSurvives: true
})
and you start iterating over a population of solutions:
let counter = 0
for (const pop of geneticIter) {
// pop[0] is the current best solution
// every solution has a fitness score
// It is a good idea terminating the iteration when the fitness score is good enough
if (pop[0].fitness > 1.9999) {
return pop[0].individual // individual is the solution itself
}
// it is a good idea to terminate the search after a certain number of attempts
if (counter++ === 1000) return 'No solution'
}
The use of the iterator protocol makes easy to implement asynchronous iteration (without blocking the event loop) or even store the current population and resume the computation in a later time.
The only required option is seed/initialPopulation
and fitness
. But this probably isn't going to be useful if you don't define at least mutate
and/or crossover
.
A function that returns a random individual when called. This is not used if you use initialPopulation
This is the initial population. It is an alternative to use seed.
A function that accepts an individual and returns that individual's fitness score.
A comparator that that gets 2 individuals with their fitness score. For example:
(a, b) => b.fitness - a.fitness
You can use the constants: GeneticIter.optimize.Maximize or GeneticIter.optimize.Minimize
(default)
A function that accepts an individual, and returns a mutated version of that individual. This is mandatory if crossoverChance is less than 1.
A function that accepts two parent individuals, and returns an array containing two children individuals created from those parents. It is mandatory if crossoverChance is greater than 0.
A function that selects a single individual out of a population. See the Selection section below. Defaults to GeneticIter.Select1.bestOf2
.
A function that selects a two individuals out of a population. See the Selection section below. Defaults to GeneticIter.Select2.bestOf2
.
How many individuals there should be in the population. Defaults to 250
.
The chance for a crossover to happen. Defaults to 0
.
Whether or not the fittest individual should always move on to the next generation. Defaults to true
.
There are a number of selection behaviors pre-written if the default isn't what you want. The select function takes as argument the current population (an array) and the GeneticIter configuration.
Selects a random individual from the population.
Picks two random individuals from the population, and selects the fitter one.
Picks three random individuals from the population, and selects the fittest one.
Picks n
random individuals from the population, and selects the fittest one.
Selects the fittest individual from the population.
Selects two random individuals from the population.
Selects two individuals using the GeneticIter.Select1.bestOf2
selection behavior.
Selects two individuals using the GeneticIter.Select1.bestOf3
selection behavior.
Selects two individuals using the Select1.bestOfN(n)
selection behavior.
Selects the fittest individual and a random individual from the population.
The geneticIter is iterable using the for..of loop. Every iteration returns an array of objects. Every object contains a solution (individual) and the respective fitness score (fitness).
const delay = require('delay');
(async () => {
let counter = 0
for (const pop of geneticIter) {
// ...
if (counter++ % 100 === 0) {
await delay(10) // takes 10 ms of pause every 100 attempts
}
}
})();
let savePoint
let counter = 0
for (const pop of geneticIter) {
// ...
if (counter++ === 10000) {
savePoint = geneticIter.extractIndividuals() // extract the individuals
}
}
// ...
const geneticIter2 = new GeneticIter({
initialPopulation: savePoint,
// ...
})
for (const pop of geneticIter2) {
// ...
You can get some stats about the population like this:
for (const pop of geneticIter) {
// ...
console.log(this.calculateStats(pop))
// returns information about the fitness:
// max: maximum
// min: minimum
// mean: mean
// stdev: standard deviation
Note: it impacts performance, so it should not be done on every iteration.
genetic-js was the main source of inspiration, for the algorithm and the testing (but it was unwieldy to use because of the integration with webworkers). Evolutionary showed a better api using iteration (but I needed a tested and optimised alternative).