@alenaksu/neatjs
Version:
NEAT (Neuroevolution of Augmenting Topologies) implementation in JavaScript
116 lines • 4.51 kB
JavaScript
import { Organism } from './Organism';
import { speciateOrganism, descending } from '../utils';
export class Population {
constructor(config) {
this.species = [];
this.organisms = [];
this.generation = 1;
this.size = config.populationSize;
this.config = config;
}
save() {
console.error('not implemented');
}
static from(config, { nodes, connections }) {
const population = new Population(config);
const organism = new Organism();
nodes.forEach(organism.addNode.bind(organism));
connections.forEach(organism.addConnection.bind(organism));
const size = config.populationSize;
for (let i = 0; i < size; i++) {
const organismCopy = organism.copy();
organismCopy.mutateConnectionsWeights(config);
population.addOrganism(organismCopy);
}
population.speciate();
return population;
}
getSuperChamp() {
return this.organisms.length
? this.organisms.reduce((champ, organism) => organism.originalFitness > champ.originalFitness
? organism
: champ)
: null;
}
addOrganism(organism) {
this.organisms.push(organism);
}
removeOrganism(organism) {
const index = this.organisms.indexOf(organism);
if (~index)
this.organisms.splice(index, 1);
}
speciate() {
this.organisms.forEach(organism => speciateOrganism(this.config, organism, this.species));
}
epoch() {
this.generation++;
const { species, config, generation } = this;
const organisms = [...this.organisms];
// Adjust compatibility threshold
if (config.adjustCompatibilityThreshold &&
species.length !== config.compatibilityModifierTarget) {
config.compatibilityThreshold +=
-config.compatibilityModifier *
(species.length > config.compatibilityModifierTarget ? -1 : 1);
config.compatibilityThreshold = Math.max(config.compatibilityThreshold, config.compatibilityModifier);
}
let overallAverage = 0;
// Adjust fitness of species' organisms
species.forEach(species => {
species.adjustFitness(config);
overallAverage += species.averageFitness;
});
organisms.forEach((organism, i) => {
// Remove all organisms marked for death
if (organism.kill) {
this.removeOrganism(organism);
organism.species.removeOrganism(organism);
}
else {
organism.expectedOffspring = Math.round(organism.originalFitness / overallAverage);
}
});
const sortedSpecies = [...species].sort(descending((i) => i.maxFitness));
// Reproduce all species
sortedSpecies.forEach(species => {
species.expectedOffspring = Math.round((species.averageFitness / overallAverage) * this.size);
species.reproduce(config, generation, this, sortedSpecies);
});
// Remove all the organism from the old generation
[...this.organisms].forEach(organism => {
this.removeOrganism(organism);
organism.species.removeOrganism(organism);
});
// Add species' organisms to current generation
this.species = species.filter(species => {
// Remove empty species
if (!species.organisms.length)
return false;
// Add organisms to population
else
this.organisms.push(...species.organisms);
species.age++;
return true;
});
// this.speciate();
}
run(fitnessFn, maxRuns = Infinity, delay = 0) {
return new Promise(async (resolve, reject) => {
const { config } = this;
while (!Number.isFinite(maxRuns) || maxRuns--) {
for (const org of this.organisms) {
const net = org.getNetwork();
org.fitness = await fitnessFn(net, org, this);
if (org.fitness >= config.fitnessThreshold)
return resolve(org);
}
this.epoch();
if (delay)
await new Promise(resolve => setTimeout(resolve, delay));
}
reject();
});
}
}
//# sourceMappingURL=Population.js.map