UNPKG

@alenaksu/neatjs

Version:

NEAT (Neuroevolution of Augmenting Topologies) implementation in JavaScript

1,106 lines (926 loc) 31.7 kB
class ConnectionGene { constructor() { let innovation = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; let form = arguments.length > 1 ? arguments[1] : undefined; let to = arguments.length > 2 ? arguments[2] : undefined; let weight = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 1; let enabled = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; this.from = form; this.to = to; this.weight = weight; this.enabled = enabled; this.innovation = innovation; } disable() { this.enabled = false; } copy() { return new ConnectionGene(this.innovation, this.from, this.to, this.weight, this.enabled); } } function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } function _toArray(arr) { return _arrayWithHoles(arr) || _iterableToArray(arr) || _nonIterableRest(); } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); } function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } class Species { constructor() { /** * The organisms of the species */ this.organisms = []; /** * Mark the species for extinction */ this.extinct = false; /** * Species' age */ this.age = 0; /** * Age from last improvement */ this.ageOfLastImprovement = 0; /** * Max fitness ever */ this.maxFitness = 0; /** * Average fitness */ this.averageFitness = 0; /** * Number of expected offspring in proportion to the sum of adjusted fitnesses */ this.expectedOffspring = 0; } addOrganism(organism) { if (!this.specimen) this.specimen = organism; this.organisms.push(organism); organism.species = this; } removeOrganism(organism) { const index = this.organisms.indexOf(organism); if (~index) this.organisms.splice(index, 1); } getSpecimen() { return this.specimen; } getChampion() { return this.organisms[0]; } adjustFitness(config) { let totalFitness = 0; this.extinct = !!(this.age - this.ageOfLastImprovement + 1 > config.dropoffAge); this.organisms.forEach(organism => { organism.originalFitness = organism.fitness; if (this.extinct) { // Penalty for a long period of stagnation (divide fitness by 100) organism.fitness *= 0.01; } if (this.age <= 10) { // boost young organisms organism.fitness *= config.ageSignificance; } organism.fitness = Math.max(organism.fitness, 0.0001) / this.organisms.length; totalFitness += organism.originalFitness; }); this.organisms.sort(descending(i => i.fitness)); var _getRandomItems = getRandomItems(this.organisms, 1); var _getRandomItems2 = _slicedToArray(_getRandomItems, 1); this.specimen = _getRandomItems2[0]; this.averageFitness = totalFitness / this.organisms.length; // update age of last improvement if (this.organisms[0].originalFitness > this.maxFitness) { this.maxFitness = this.organisms[0].originalFitness; this.ageOfLastImprovement = this.age; } const removeFrom = Math.floor(this.organisms.length * config.survivalThreshold + 1); for (let i = removeFrom; i < this.organisms.length; i++) this.organisms[i].kill = true; } /** * Perform mating and mutation to form next generation. * The sorted_species is ordered to have best species in the beginning. * Returns list of baby organisms as a result of reproduction of all organisms in this species. * @param generation */ reproduce(config, generation, population, sortedSpecies) { const organisms = this.organisms, expectedOffspring = this.expectedOffspring; if (expectedOffspring && !organisms.length) return; const _organisms = _toArray(organisms), babies = _organisms.slice(0); const champ = babies[0]; let champAdded = false; // TODO stolen babies let superChamp = population.getSuperChamp(); for (let i = 0; i < expectedOffspring; i++) { let baby; if (superChamp && superChamp === champ && superChamp.expectedOffspring > 0) { // If we have a population champion, finish off some special clones let organism = superChamp.copy(0, generation); if (superChamp.expectedOffspring === 1) organism = mutateGenome(config, organism); superChamp.expectedOffspring--; baby = organism; } else if (!champAdded && expectedOffspring > 5) { // Champion of species with more than 5 networks is copied unchanged baby = champ.copy(0, generation); champAdded = true; } else if (rnd() < config.mutateOnlyProbability) { // Mutate only const _getRandomItems3 = getRandomItems(babies, 1), _getRandomItems4 = _slicedToArray(_getRandomItems3, 1), mom = _getRandomItems4[0]; baby = mutateGenome(config, mom.copy(0, generation)); } else { // mate const _getRandomItems5 = getRandomItems(babies, 1), _getRandomItems6 = _slicedToArray(_getRandomItems5, 1), mom = _getRandomItems6[0]; let dad; if (rnd() > config.interspeciesMateRate) { var _getRandomItems7 = getRandomItems(babies, 1); var _getRandomItems8 = _slicedToArray(_getRandomItems7, 1); dad = _getRandomItems8[0]; } else { // Interspecies mate let tries = 0, randomSpecies = this; while (randomSpecies === this && tries++ < 5) { const species = getRandomSpecies(sortedSpecies); if (species.organisms.length) randomSpecies = species; } dad = randomSpecies.getChampion(); } baby = crossover(dad, mom, config); if (rnd() < config.mutateOnlyProbability || compatibility(mom, dad, config) === 0) mutateGenome(config, baby); } baby.generation = generation; speciateOrganism(config, baby, population.species); } } } var NodeType; (function (NodeType) { NodeType[NodeType["Bias"] = 0] = "Bias"; NodeType[NodeType["Input"] = 1] = "Input"; NodeType[NodeType["Hidden"] = 2] = "Hidden"; NodeType[NodeType["Output"] = 3] = "Output"; })(NodeType || (NodeType = {})); var NeuronType; (function (NeuronType) { NeuronType[NeuronType["Neuron"] = 0] = "Neuron"; NeuronType[NeuronType["Sensor"] = 1] = "Sensor"; })(NeuronType || (NeuronType = {})); /** * Picks n random items from the given list * @param list * @param n */ function getRandomItems(list, n) { let result = []; let source = [...list]; n = Math.min(n, list.length); while (result.length < n) { let i = Math.floor(rnd(source.length)); result.push(...source.splice(i, 1)); } return result; } /** * Returns a random boolean */ function randomBool() { return Math.random() > 0.5; } /** * Compute the mean of the given values * @param values */ function mean() { for (var _len = arguments.length, values = new Array(_len), _key = 0; _key < _len; _key++) { values[_key] = arguments[_key]; } return values.length ? values.reduce((sum, n) => sum + n, 0) / values.length : 0; } /** * Returns a random number between from/to arguments * @param to * @param from */ function rnd() { let to = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1; let from = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; return Math.random() * (to - from) + from; } /** * Returns a normally distribuited random number (Box-Muller transform) */ function* gaussian() { let mean = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; let standardDeviation = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1; let u, v, s; while (true) { do { v = rnd(1, -1); u = rnd(1, -1); s = u ** 2 + v ** 2; } while (s === 0 || s >= 1); s = Math.sqrt(-2 * Math.log(s) / s); yield s * u * standardDeviation + mean; yield s * v * standardDeviation + mean; } } /** * Generate a random UUIDv4 (rfc4122 compliant) */ function uuid() { const uuid = [8, 4, 4, 4, 12].map(segmentLength => { let segment = Array(segmentLength); for (let i = 0; i < segmentLength; i++) // ToUint32 http://www.ecma-international.org/ecma-262/5.1/#sec-11.7.3 segment[i] = Math.random() * 0xf >>> 0; return segment; }); uuid[2][0] &= 0x3; uuid[2][1] |= 0x8; uuid[3][0] = 0x4; return uuid.map(segment => segment.map(n => n.toString(16)).join('')).join('-'); } /** * Compute the compatibility distance between two genomes * @param genome1 * @param genome2 * @param config */ function compatibility(genome1, genome2, config) { // TODO memoizing? consider add an id and use it for that purpose let innovationNumbers = new Set([...genome1.connections.keys(), ...genome2.connections.keys()]); let excess = Math.abs(genome1.connections.size - genome2.connections.size), disjoint = -excess, matching = [], N = Math.max(genome1.connections.size, genome2.connections.size, 1); innovationNumbers.forEach(innovation => { const gene1 = genome1.connections.get(innovation), gene2 = genome2.connections.get(innovation); if (gene1 && gene2) { matching.push(Math.abs(gene1.weight - gene2.weight)); } else if (!gene1 || !gene2) { disjoint++; } }); return (excess * config.excessCoefficient + disjoint * config.disjointCoefficient) / N + mean(...matching) * config.weightDifferenceCoefficient; } function sigmoid(x) { let slope = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 4.924273; return 1 / (1 + Math.exp(-slope * x)); } /** * Check whether the connection creates a loop inside the network * @param connection * @param connections */ function isRecurrent(connection, connections) { const startNode = connection.from; const stack = [connection]; while (stack.length) { connection = stack.shift(); if (connection.to.id === startNode.id) return true; stack.push(...connections.filter(gene => gene.from.id === connection.to.id)); } return false; } /** * Mate 2 organisms * @param genome1 * @param genome2 */ function crossover(genome1, genome2, config) { const child = new Organism(); // [moreFit, lessFit] const _sort = [genome1, genome2].sort(descending(i => i.fitness)), _sort2 = _slicedToArray(_sort, 2), hFitParent = _sort2[0], lFitParent = _sort2[1]; let innovationNumbers = new Set([...hFitParent.connections.keys(), ...lFitParent.connections.keys()]); // Ensure that all sensors and ouputs are added to the organism hFitParent.nodes.forEach(node => { if (isSensor(node) || isOutput(node)) child.addNode(node.copy()); }); // lFitParent.nodes.forEach(node => { // switch (node.type) { // case NodeType.Input: // case NodeType.Output: // case NodeType.Hidden: // child.addNode(node.copy()); // } // }); Array.from(innovationNumbers.values()).sort(ascending()).forEach(innovationNumber => { const hConnection = hFitParent.connections.get(innovationNumber), lConnection = lFitParent.connections.get(innovationNumber); const connection = hConnection && lConnection ? // Matching gene randomBool() && config.feedForwardOnly && !isRecurrent(hConnection, child.getConnections()) ? hConnection.copy() : lConnection.copy() : // excess/disjoint (hConnection || lConnection).copy(); // Prevent the creation of RNNs if feed-forward only if (config.feedForwardOnly && isRecurrent(connection, child.getConnections())) return; child.connections.set(innovationNumber, connection); connection.from = connection.from.copy(); connection.to = connection.to.copy(); child.addNode(connection.from); child.addNode(connection.to); }); return child; } /** * Perform genome's mutations * @param config * @param organism */ function mutateGenome(config, organism) { if (rnd() < config.mutateAddNodeProbability) { organism.mutateAddNode(config); } else if (rnd() < config.mutateAddConnectionProbability) { organism.mutateAddConnection(config); } else { if (rnd() < config.mutateConnectionWeightsProbability) organism.mutateConnectionsWeights(config); if (rnd() < config.mutateToggleEnableProbability) organism.mutateToggleEnable(); if (rnd() < config.reEnableGeneProbability) organism.reEnableGene(); } return organism; } /** * Returns a random species form the given set, tending towards better species * @param sortedSpecies A sorted set of species */ function getRandomSpecies(sortedSpecies) { const random = Math.min(Math.round(gaussian().next().value), 1); const index = wrapNumber(0, sortedSpecies.length - 1, random); // const multiplier = Math.min(gaussian().next().value / 4, 1); // const index = Math.floor(multiplier * (species.length - 1) + 0.5); return sortedSpecies[index]; } /** * Puts the organism inside a compatibile species * @param config * @param organism * @param species */ function speciateOrganism(config, organism, allSpecies) { const compatibilityThreshold = config.compatibilityThreshold; const found = allSpecies.length > 0 && allSpecies.some(species => { if (!species.organisms.length) return false; const isCompatible = compatibility(organism, species.getSpecimen(), config) < compatibilityThreshold; if (isCompatible) species.addOrganism(organism); return isCompatible; }); if (!found) { const species = new Species(); species.addOrganism(organism); allSpecies.push(species); } } /** * Sorts an array from largest to smallest * @param keyFn */ function descending() { let keyFn = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : i => i; return (a, b) => keyFn(b) - keyFn(a); } /** * Sorts an array from smallest to largest * @param keyFn */ function ascending() { let keyFn = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : i => i; return (a, b) => keyFn(a) - keyFn(b); } // TODO const byFitness = i => i.fitness; const byMaxFitness = i => i.maxFitness; const byInnovation = i => i.innovation; const byType = i => i.type; /** * Keeps the given value between the specified range * @param min * @param max * @param value */ function wrapNumber(min, max, value) { const l = max - min + 1; return ((value - min) % l + l) % l + min; } const isSensor = gene => gene.type === NodeType.Input || gene.type === NodeType.Bias; const isOutput = gene => gene.type === NodeType.Output; /** * Create a new innovation number generator */ function* Innovation() { let i = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; while (true) yield i++; } class NodeGene { constructor(type) { let id = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : uuid(); this.id = id; this.type = type; } copy() { return new NodeGene(this.type, this.id); } } class Genome { constructor() { let id = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : uuid(); this.connections = new Map(); this.nodes = new Map(); this.id = id; } copy() { const genome = new this.constructor(); this.connections.forEach((gene, key) => { genome.connections.set(key, gene.copy()); }); this.nodes.forEach((node, key) => { genome.nodes.set(key, node.copy()); }); return genome; } /** * Returns a list of enabled connections */ getConnections() { return Array.from(this.connections.values()).filter(gene => gene.enabled); } /** * Checks whether a connection between two nodes exists * @param node1 * @param node2 */ connectionExists(node1, node2) { return Array.from(this.connections.values()).some(connection => connection.from.id === node1.id && connection.to.id === node2.id); } addConnection(connection) { this.connections.set(connection.innovation, connection); } addNode(node) { if (!this.nodes.has(node.id)) this.nodes.set(node.id, node); } /** * Adds a connection mutation */ mutateAddConnection(config) { let maxTries = config.addConnectionTries; let nodes = Array.from(this.nodes.values()); const connections = Array.from(this.connections.values()); while (maxTries--) { const _getRandomItems = getRandomItems(nodes.filter(node => !isOutput(node)), 1), _getRandomItems2 = _slicedToArray(_getRandomItems, 1), form = _getRandomItems2[0]; const _getRandomItems3 = getRandomItems(nodes.filter(node => // don't allow sensors to get input !isSensor(node) && // exclude self loops node !== form && ( // consider connections between output nodes recurrent form.type === NodeType.Output ? node.type !== NodeType.Output : true)), 1), _getRandomItems4 = _slicedToArray(_getRandomItems3, 1), to = _getRandomItems4[0]; const connection = new ConnectionGene(config.innovation.next().value, form, to, rnd(config.mutationPower, -config.mutationPower)); const isValid = // connection already exists !this.connectionExists(form, to) && ( // is a RNN !config.feedForwardOnly || !isRecurrent(connection, connections)); if (isValid) { this.addConnection(connection); return; } } } mutateAddNode(config) { if (!this.connections.size) return; const _getRandomItems5 = getRandomItems(this.getConnections(), 1), _getRandomItems6 = _slicedToArray(_getRandomItems5, 1), connection = _getRandomItems6[0]; const node = new NodeGene(NodeType.Hidden); connection.disable(); this.addConnection(new ConnectionGene(config.innovation.next().value, connection.from, node)); this.addConnection(new ConnectionGene(config.innovation.next().value, node, connection.to, connection.weight)); this.addNode(node); } /** * Enable first disabled gene */ reEnableGene() { for (const connection of this.connections.values()) { if (!connection.enabled) { connection.enabled = true; return; } } } /** * Mutate a connection by enabling/disabling * @param times */ mutateToggleEnable() { let times = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1; const connections = Array.from(this.connections.values()); while (times--) { const _getRandomItems7 = getRandomItems(connections, 1), _getRandomItems8 = _slicedToArray(_getRandomItems7, 1), connection = _getRandomItems8[0]; if (connection.enabled) { const isSafe = connections.some(c => c.from !== connection.from || !c.enabled || c.innovation === connection.innovation); connection.enabled = !isSafe; } else connection.enabled = true; } } /** * Mutate all connections */ mutateConnectionsWeights(_ref) { let mutationPower = _ref.mutationPower, genomeWeightPerturbated = _ref.genomeWeightPerturbated; this.connections.forEach(connection => { const random = rnd(mutationPower, -mutationPower); if (connection.enabled) { if (rnd() < genomeWeightPerturbated) connection.weight += random;else connection.weight = random; } }); } } class Neuron { constructor(type, id) { this.value = 0; this.bias = 0; this.in = []; this.out = []; this.type = type; this.id = id; } } class Link { constructor(from, to) { let weight = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; let enabled = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; this.weight = 0; this.enabled = true; this.from = from; this.to = to; this.weight = weight; this.enabled = enabled; } } class Network { constructor(neurons, links) { this.neurons = new Map(); this.activation = sigmoid; const inputs = [], outputs = []; neurons.forEach((_ref) => { let type = _ref.type, id = _ref.id; const neuron = new Neuron(type, id); this.neurons.set(id, neuron); switch (type) { case NodeType.Bias: case NodeType.Input: inputs.push(neuron); break; case NodeType.Output: outputs.push(neuron); break; } }); links.forEach((_ref2) => { let from = _ref2.from, to = _ref2.to, weight = _ref2.weight, enabled = _ref2.enabled; const fromNeuron = this.neurons.get(from); const toNeuron = this.neurons.get(to); const link = new Link(fromNeuron, toNeuron, weight, enabled); fromNeuron.out.push(link); toNeuron.in.push(link); }); this.inputs = inputs; this.outputs = outputs; } toJSON() { const neruons = this.neurons; const neurons = [], links = []; neruons.forEach((_ref3) => { let id = _ref3.id, bias = _ref3.bias, type = _ref3.type, out = _ref3.out; neurons.push({ id, bias, type }); links.push(...out.map((_ref4) => { let from = _ref4.from, to = _ref4.to, weight = _ref4.weight, enabled = _ref4.enabled; return { from: from.id, to: to.id, weight, enabled }; })); }); return { // config, neurons, links }; } /** * Activate the network (feed-forward only) * @param inputs */ activate(inputs) { this.inputs.map((input, i) => { input.value = inputs[i]; }); // TODO add hebbian learning // https://en.wikibooks.org/wiki/Artificial_Neural_Networks/Hebbian_Learning // https://apaszke.github.io/lstm-explained.html // neurons.forEach(neuron => { // const dotProduct = neuron.in.reduce( // (sum, link) => sum + link.from.value * link.weight, // neuron.value // ); // neuron.value = this.activation(neuron.bias + dotProduct); // }); // return this.outputs; const done = new Set(); const stack = [...this.inputs]; const state = {}; while (stack.length) { const neuron = stack.shift(); if (done.has(neuron)) continue; if (neuron.in.length) { const dotProduct = neuron.in.filter(l => l.enabled).reduce((sum, link) => sum + link.from.value * link.weight, 0); state[neuron.id] = (state[neuron.id] || 0) + dotProduct; } done.add(neuron); stack.push(...neuron.out.filter(l => l.enabled).map(link => link.to)); } this.neurons.forEach(neuron => { if (neuron.id in state) neuron.value = this.activation(state[neuron.id] + neuron.bias); }); return this.outputs; } } /** * Organisms are Genomes and Networks with fitness informations * i.e. The genotype and phenotype together */ class Organism extends Genome { constructor() { let fitness = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; let generation = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; super(); /** * A measure of fitness before adjustment */ this.originalFitness = 0; /** * Mark for killing */ this.kill = false; /** * Generation in which Organism is from */ this.generation = 0; /** * Number of children this Organism may have */ this.expectedOffspring = 0; this.fitness = fitness; this.generation = generation; } copy() { let fitness = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; let generation = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; let clone = super.copy(); clone.fitness = fitness; clone.originalFitness = this.originalFitness; clone.generation = generation; return clone; } getNetwork() { if (!this.network) { const nodes = Array.from(this.nodes.values()).map((_ref) => { let type = _ref.type, id = _ref.id; return { type, id }; }); const connections = Array.from(this.connections.values()).sort(ascending(i => i.innovation)).map((_ref2) => { let from = _ref2.from, to = _ref2.to, weight = _ref2.weight, enabled = _ref2.enabled; return { from: from.id, to: to.id, weight: weight, enabled: enabled }; }); this.network = new Network(nodes, connections); } return this.network; } } 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, _ref) { let nodes = _ref.nodes, connections = _ref.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 = this.species, config = this.config, generation = this.generation; 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) { var _this = this; let maxRuns = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Infinity; let delay = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; return new Promise( /*#__PURE__*/ function () { var _ref2 = _asyncToGenerator(function* (resolve, reject) { const config = _this.config; while (!Number.isFinite(maxRuns) || maxRuns--) { for (const org of _this.organisms) { const net = org.getNetwork(); org.fitness = yield fitnessFn(net, org, _this); if (org.fitness >= config.fitnessThreshold) return resolve(org); } _this.epoch(); if (delay) yield new Promise(resolve => setTimeout(resolve, delay)); } reject(); }); return function (_x, _x2) { return _ref2.apply(this, arguments); }; }()); } } const DefaultConfig = { dropoffAge: 15, excessCoefficient: 1.0, disjointCoefficient: 1.0, weightDifferenceCoefficient: 1, compatibilityThreshold: 3.0, compatibilityModifier: 0.3, compatibilityModifierTarget: 10, adjustCompatibilityThreshold: false, mutationPower: 2.5, reEnableGeneProbability: 0.05, mutateConnectionWeightsProbability: 0.9, genomeWeightPerturbated: 0.9, ageSignificance: 1, survivalThreshold: 0.2, populationSize: 100, mutateOnlyProbability: 0.2, mutateAddNodeProbability: 0.03, mutateAddConnectionProbability: 0.05, mutateToggleEnableProbability: 0, interspeciesMateRate: 0.001, fitnessThreshold: 0.9, addConnectionTries: 20, innovation: Innovation(), feedForwardOnly: true }; export { ConnectionGene, DefaultConfig, Genome, Innovation, NeuronType, NodeGene, NodeType, Organism, Population, Species, ascending, byFitness, byInnovation, byMaxFitness, byType, compatibility, crossover, descending, gaussian, getRandomItems, getRandomSpecies, isOutput, isRecurrent, isSensor, mean, mutateGenome, randomBool, rnd, sigmoid, speciateOrganism, uuid, wrapNumber };