@alenaksu/neatjs
Version:
NEAT (Neuroevolution of Augmenting Topologies) implementation in JavaScript
1,106 lines (926 loc) • 31.7 kB
JavaScript
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 };