'use strict'; import { random_choice } from '../util.js'; import { network } from '../mind/topology.js'; // check if a given genome is valid and compute its size export function validate_genome(genome) { const { n_input, n_internal, n_output } = genome; console.log(n_input + n_internal); return genome.genes.reduce( (acc, [source, sink, weight]) => acc && ( (source < n_input+n_internal) && (sink >= n_input) ), true ); } // parse a genome into a useable neural net export function parse_genome(genome) { const { n_input, n_internal, n_output } = genome; const n = genome.genes.reduce( (acc, [source, sink, weight]) => acc.connect(source, sink, weight), network(n_input, n_internal, n_output) ); return n; } // --===== mutations =====-- function clamp(value, min, max) { if (value > max) { return max; } if (value < min) { return min; } return value; } // adjust the source input of a gene export function mut_gene_source(n_input, n_internal, n_output, gene, r) { const [source, sink, weight] = gene; const new_source = r < 0.5 ? source-1 : source+1; return [ clamp(new_source, 0, n_input+n_internal-1), sink, weight, ]; } // adjust the sink target of a gene export function mut_gene_sink(n_input, n_internal, n_output, gene, r) { const [source, sink, weight] = gene; const new_sink = r < 0.5 ? sink-1 : sink+1; return [ source, clamp(new_sink, n_input+n_internal, n_input+n_internal+n_output-1), weight, ]; } // modify a gene's weight // only adjusts the weight by performing a weighted average, so as to // more gently modify the generated net export function mut_gene_weight(weight_max, gene, r) { const [source, sink, weight] = gene; const rr = (2*r)-1; const move = weight_max * rr; const new_weight = (2*weight + move)/3; return [ source, sink, clamp(new_weight, -weight_max, weight_max), ]; } // expand the size of the neural net encoded by the genome // relabels internal indices so that there is one extra internal neuron export function mut_genome_expand(genome, r) { const expand_index = Math.floor(genome.n_internal * r) + genome.n_input; const new_genes = genome.genes.map(([source, sink, weight]) => [ source >= expand_index ? source+1 : source, sink >= expand_index ? sink+1 : sink, weight, ]); return { ...genome, n_internal: genome.n_internal+1, genes: new_genes, }; } // contract the size of the neural net encoded by the genome // relabels internal indices so that there is one less internal neuron export function mut_genome_contract(genome, r) { const { n_input, n_internal, n_output } = genome; const contract_idx = Math.floor(n_internal * r) + n_input; // decrement sources on the contract index too, to prevent invalid genomes const new_source = (source) => source >= contract_idx ? source-1 : source; // decrement sinks only after the contract index const new_sink = (sink) => sink > contract_idx ? sink-1 : sink; const new_genes = genome.genes.map(([source, sink, weight]) => [ new_source(source), new_sink(sink), weight, ]); return { ...genome, n_internal: n_internal-1, genes: new_genes, }; } // append a newly generated gene to the end of the genome export function mut_genome_insert(genome, weight_max, r1=Math.random(), r2=Math.random(), r3=Math.random()) { const { n_input, n_internal, n_output } = genome; const source = Math.floor((n_input + n_internal) * r1); const sink = Math.floor((n_internal + n_output) * r2) + n_input; const weight = weight_max * ((2*r3)-1); return { ...genome, genes: [...genome.genes, [source, sink, weight]], }; } // delete a gene from the genome export function mut_genome_delete(genome, r) { const del_idx = Math.floor(r * genome.genes.length); const genes = genome.genes.filter((_, idx) => idx != del_idx); return { ...genome, genes }; } function mut_gene( [n_input, n_internal, n_output, genome], weight_max, r1, r2, r3 ) { const gene_idx = Math.floor(genome.length * r1); const mod = random_choice(['source', 'sink', 'weight'], r2); let new_gene; if (mod == 'source') { new_gene = mut_gene_source( n_input, n_internal, n_output, genome[gene_idx], r3 ); } else if (mod == 'sink') { new_gene = mut_gene_sink( n_input, n_internal, n_output, genome[gene_idx], r3 ); } else { new_gene = mut_gene_weight( weight_max, genome[gene_idx], r3 ); } const new_genome = genome.map((gene, idx) => { if (idx == gene_idx) { return new_gene; } return gene; }); return [ n_input, n_internal, n_output, new_genome ]; } export function mutate_genome(obj, weight_max) { const mut = random_choice([ 'gene', 'gene', 'gene', 'gene', 'gene', 'gene', 'gene', 'gene', 'gene', 'insert', 'delete', 'insert', 'delete', 'expand', 'contract', ], Math.random()); if (mut == 'gene') { return mut_gene( obj, weight_max, Math.random(), Math.random(), Math.random() ); } else if (mut == 'insert') { return mut_genome_insert( obj, weight_max, Math.random(), Math.random(), Math.random() ); } else if (mut == 'delete') { return mut_genome_delete(obj, Math.random()); } else if (mut == 'expand') { return mut_genome_expand(obj, Math.random()); } else if (mut == 'contract') { return mut_genome_contract(obj, Math.random()); } else { throw new Error(`bad mut value: ${mut}`); } }