From 3793cddeb12993da492f9934b59849442c68aa12 Mon Sep 17 00:00:00 2001 From: sanine Date: Mon, 30 Oct 2023 00:05:00 -0500 Subject: refactor gene mutations --- src/genome/genome.js | 99 ++++++++++++++++++++++---------------------- src/genome/genome.test.js | 102 +++++++++++++++++++++++++++++++++++++--------- 2 files changed, 131 insertions(+), 70 deletions(-) diff --git a/src/genome/genome.js b/src/genome/genome.js index 0f7275f..c288d02 100644 --- a/src/genome/genome.js +++ b/src/genome/genome.js @@ -3,56 +3,6 @@ import { network } from '../mind/topology'; -export const mutation_type = Object.freeze({ - none: 'none', - source: 'source', - sink: 'sink', - weight: 'weight', -}); - - -// clamp a number in the range [0, infinity) -function nonneg(x) { - if (x < 0) { - return 0; - } else { - return x; - } -} - - -// mutate a gene -export function mutate(gene, type, value) { - const [ source, sink, weight ] = gene; - - switch(type) { - case mutation_type.none: - return [...gene]; - - case mutation_type.source: - if (value <= 0.5) { - return [ nonneg(source-1), sink, weight ]; - } else { - return [ source+1, sink, weight ]; - } - - case mutation_type.sink: - if (value <= 0.5) { - return [ source, nonneg(sink-1), weight ]; - } else { - return [ source, sink+1, weight ]; - } - - case mutation_type.weight: - const w = (8*value) - 4; - return [ source, sink, 0.5*(w + weight) ]; - - default: - throw new Error(`unknown mutation type: '${type}'`); - }; -} - - // check if a given genome is valid and compute its size export function get_size(num_input, num_output, genome) { const [ max_index, max_weight ] = genome.reduce( @@ -89,3 +39,52 @@ export function parse_genome(num_input, num_output, genome) { return n; } + + +// --===== mutations =====-- + +function clamp(value, min, max) { + if (value > max) { return max; } + if (value < min) { return min; } + return value; +} + +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, + ]; +} + + +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, + ]; +} + + +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), + ]; +} diff --git a/src/genome/genome.test.js b/src/genome/genome.test.js index 8152f04..e1c8711 100644 --- a/src/genome/genome.test.js +++ b/src/genome/genome.test.js @@ -1,29 +1,14 @@ 'use strict'; import { - mutation_type, mutate, get_size, parse_genome, + mut_gene_source, + mut_gene_sink, + mut_gene_weight, } from './genome'; -test('basic gene mutations', () => { - expect(mutate([0, 1, 2], mutation_type.none, 0)).toEqual([0, 1, 2]); - - expect(mutate([0, 1, 2], mutation_type.source, 0.2)).toEqual([0, 1, 2]); - expect(mutate([1, 1, 2], mutation_type.source, 0.2)).toEqual([0, 1, 2]); - expect(mutate([0, 1, 2], mutation_type.source, 0.8)).toEqual([1, 1, 2]); - - expect(mutate([0, 1, 2], mutation_type.sink, 0.2)).toEqual([0, 0, 2]); - expect(mutate([0, 1, 2], mutation_type.sink, 0.8)).toEqual([0, 2, 2]); - expect(mutate([0, 0, 2], mutation_type.sink, 0.2)).toEqual([0, 0, 2]); - - expect(mutate([0, 1, 2], mutation_type.weight, 0.5)).toEqual([0, 1, 1]); - expect(mutate([0, 1, 2], mutation_type.weight, 0.0)).toEqual([0, 1, -1]); - expect(mutate([0, 1, 2], mutation_type.weight, 1.0)).toEqual([0, 1, 3]); -}); - - test('genome validation and size', () => { expect(get_size(0, 0, [ [ 0, 0, 1.0 ] ])).toBe(1); expect(get_size(2, 1, [ [ 0, 2, 1 ] ])).toBe(3); @@ -39,8 +24,6 @@ test('parse a genome into a neural net', () => { [1, 2, 1] ]); - console.log(n); - expect(n.input_count).toBe(1); expect(n.output_count).toBe(1); expect(n.compute([2], [-1])).toEqual([ @@ -48,3 +31,82 @@ test('parse a genome into a neural net', () => { [ Math.tanh( 2-1 ) ], ]); }); + + +test('mutate gene source', () => { + const n_input = 3; + const n_internal = 4; + const n_output = 5; + + expect(mut_gene_source( + n_input, n_internal, n_output, + [0, 4, 0], + 0.0 + )).toEqual([0, 4, 0]); + + expect(mut_gene_source( + n_input, n_internal, n_output, + [0, 4, 0], + 1.0 + )).toEqual([1, 4, 0]); + + expect(mut_gene_source( + n_input, n_internal, n_output, + [6, 4, 0], + 0.0 + )).toEqual([5, 4, 0]); + + expect(mut_gene_source( + n_input, n_internal, n_output, + [6, 4, 0], + 1.0 + )).toEqual([6, 4, 0]); +}); + + +test('mutate gene sink', () => { + const n_input = 3; + const n_internal = 4; + const n_output = 5; + + expect(mut_gene_sink( + n_input, n_internal, n_output, + [0, 7, 0], + 0.0 + )).toEqual([0, 7, 0]); + + expect(mut_gene_sink( + n_input, n_internal, n_output, + [0, 7, 0], + 1.0 + )).toEqual([0, 8, 0]); + + expect(mut_gene_sink( + n_input, n_internal, n_output, + [6, 11, 0], + 0.0 + )).toEqual([6, 10, 0]); + + expect(mut_gene_sink( + n_input, n_internal, n_output, + [6, 11, 0], + 1.0 + )).toEqual([6, 11, 0]); +}); + + +test('mutate gene weight', () => { + const weight_max = 4.0; + + expect(mut_gene_weight( + weight_max, [0, 0, 1], 0.0 + )).toEqual([0, 0, (2 - 4)/3]); + + expect(mut_gene_weight( + weight_max, [0, 0, -4], 1.0 + )).toEqual([0, 0, (-8 + 4)/3]); + + expect(mut_gene_weight( + weight_max, [0, 0, 3], 0.5 + )).toEqual([0, 0, (6+0)/3]); +}); -- cgit v1.2.1