UNPKG

@alenaksu/neatjs

Version:

NEAT (Neuroevolution of Augmenting Topologies) implementation in JavaScript

127 lines 4.57 kB
import { ConnectionGene } from './ConnectionGene'; import { NodeGene } from './NodeGene'; import { getRandomItems, rnd, uuid, isSensor, isOutput, isRecurrent } from '../utils'; import { NodeType } from '../types'; export class Genome { constructor(id = 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 [form] = getRandomItems(nodes.filter(node => !isOutput(node)), 1); const [to] = 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); 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 [connection] = getRandomItems(this.getConnections(), 1); 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(times = 1) { const connections = Array.from(this.connections.values()); while (times--) { const [connection] = getRandomItems(connections, 1); 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({ mutationPower, genomeWeightPerturbated }) { this.connections.forEach(connection => { const random = rnd(mutationPower, -mutationPower); if (connection.enabled) { if (rnd() < genomeWeightPerturbated) connection.weight += random; else connection.weight = random; } }); } } //# sourceMappingURL=Genome.js.map