diff options
author | sanine <sanine.not@pm.me> | 2023-10-30 00:05:00 -0500 |
---|---|---|
committer | sanine <sanine.not@pm.me> | 2023-10-30 00:05:00 -0500 |
commit | 3793cddeb12993da492f9934b59849442c68aa12 (patch) | |
tree | b6b420b4ab854bd06da87f7933c0bb4a95c0ae8a | |
parent | ac0e4eb51ca2fd595814031087039932729199ae (diff) |
refactor gene mutations
-rw-r--r-- | src/genome/genome.js | 99 | ||||
-rw-r--r-- | 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]);
+});
|