From 2f5f2c9f818e58f20411965418db0bdb35849088 Mon Sep 17 00:00:00 2001 From: Nigel Leck Date: Fri, 8 Mar 2024 21:16:33 +1100 Subject: [PATCH] Update quality.sh and Creature.ts files (#308) Co-authored-by: Nigel Leck --- quality.sh | 4 +- src/Creature.ts | 5 +- src/architecture/Offspring.ts | 109 ++++++++++++++++++++++++---------- test/ActivationNames.ts | 15 ++--- test/Creature.ts | 12 ++-- 5 files changed, 92 insertions(+), 53 deletions(-) diff --git a/quality.sh b/quality.sh index 7b8fab8d..05f99a46 100755 --- a/quality.sh +++ b/quality.sh @@ -3,5 +3,5 @@ set -e deno fmt src test bench mod.ts deno lint src test bench mod.ts -rm -rf .trace -deno test --allow-all --trace-ops --v8-flags=--max-old-space-size=8192 --parallel --config ./test/deno.json \ No newline at end of file +rm -rf .trace .test +deno test --allow-all --trace-leaks --v8-flags=--max-old-space-size=8192 --parallel --config ./test/deno.json \ No newline at end of file diff --git a/src/Creature.ts b/src/Creature.ts index 2a57ac69..c8e3584d 100644 --- a/src/Creature.ts +++ b/src/Creature.ts @@ -627,8 +627,9 @@ export class Creature implements CreatureInternal { ); this.DEBUG = true; } - throw new Error( + throw new ValidationError( `${node.ID()}) output neuron has no inward connections`, + "NO_INWARD_CONNECTIONS", ); } break; @@ -639,7 +640,7 @@ export class Creature implements CreatureInternal { if (node.index !== indx) { throw new Error( - `${node.ID()}) node.index: ${node.index} does not match expected index`, + `${node.ID()}) node.index: ${node.index} does not match expected index ${indx}`, ); } diff --git a/src/architecture/Offspring.ts b/src/architecture/Offspring.ts index 8ebe2faa..7d8605dd 100644 --- a/src/architecture/Offspring.ts +++ b/src/architecture/Offspring.ts @@ -3,11 +3,20 @@ import { Creature } from "../Creature.ts"; import { SynapseExport, SynapseInternal } from "./SynapseInterfaces.ts"; import { Neuron } from "./Neuron.ts"; +class OffspringError extends Error { + constructor(message: string) { + super(message); + this.name = "OffspringError"; + } +} + export class Offspring { /** * Create an offspring from two parent networks */ - static bread(mother: Creature, father: Creature) { + static bread(mum: Creature, dad: Creature) { + const mother = Creature.fromJSON(mum.exportJSON()); + const father = Creature.fromJSON(dad.exportJSON()); if ( mother.input !== father.input || mother.output !== father.output ) { @@ -140,12 +149,20 @@ export class Offspring { } } - Offspring.sortNodes( - tmpNodes, - mother.neurons, - father.neurons, - connectionsMap, - ); + try { + Offspring.sortNodes( + tmpNodes, + mother.neurons, + father.neurons, + connectionsMap, + ); + } catch (e) { + if (e instanceof OffspringError) { + return undefined; + } + console.warn(e.message); + throw e; + } offspring.neurons.length = tmpNodes.length; const indxMap = new Map(); @@ -175,19 +192,23 @@ export class Offspring { const fromIndx = indxMap.get(c.fromUUID); const toIndx = indxMap.get(c.toUUID); - if (fromIndx != null && toIndx != null) { + if (fromIndx !== undefined && toIndx !== undefined) { if (fromIndx <= toIndx) { const toType = offspring.neurons[toIndx].type; if (toType == "hidden" || toType == "output") { - offspring.connect(fromIndx, toIndx, c.weight, c.type); + if (!offspring.getSynapse(fromIndx, toIndx)) { + offspring.connect(fromIndx, toIndx, c.weight, c.type); + } + } else { + throw new Error( + `Can't connect to ${toType} neuron at indx=${toIndx} of type ${toType}!`, + ); } } else { - console.info( + throw new Error( `${neuron.ID()} fromIndx=${fromIndx} > toIndx=${toIndx}`, ); } - } else { - throw new Error("Could not find nodes for connection"); } }); } @@ -236,6 +257,11 @@ export class Offspring { connectionsMap: Map, ) { const childMap = new Map(); + + mother.forEach((neuron, indx) => { + if (neuron.type == "input") childMap.set(neuron.uuid, indx); + }); + const motherMap = new Map(); const fatherMap = new Map(); @@ -261,8 +287,9 @@ export class Offspring { return a.index - b.index; }); const usedIndx = new Set(); - for (let attempts = 0; attempts < child.length; attempts++) { - let missing = false; + let missing = true; + for (let attempts = 0; missing && attempts < child.length; attempts++) { + missing = false; child.forEach((neuron) => { if (neuron.type != "input" && neuron.type != "output") { const uuid = neuron.uuid; @@ -272,29 +299,38 @@ export class Offspring { const fatherIndx = fatherMap.get(uuid); let indx = 0; - if (motherIndx) { + if (motherIndx !== undefined) { indx = motherIndx; - } else if (fatherIndx) { + } else if (fatherIndx !== undefined) { indx = fatherIndx; } else { throw new Error( - `Can't find ${uuid} in fatehr or mother creatures!`, + `Can't find ${uuid} in father or mother creatures!`, ); } connectionsMap.get(uuid)?.forEach((connection) => { - const fromUUID = connection.fromUUID; - if (!fromUUID.startsWith("input-") && !childMap.has(fromUUID)) { - const dependantIndx = childMap.get(fromUUID); - if (dependantIndx == undefined) { - indx = -1; - } else if (dependantIndx > indx) { - indx = dependantIndx + 1; + if (indx >= 0) { + const fromUUID = connection.fromUUID; + if (!fromUUID.startsWith("input-")) { + const dependantIndx = childMap.get(fromUUID); + if (dependantIndx == undefined) { + indx = -1; + } else if (dependantIndx >= indx) { + indx = dependantIndx + 1; + } } } }); if (indx >= 0) { - while (usedIndx.has(indx)) { - indx++; + if (usedIndx.has(indx)) { + childMap.forEach((childIndx, uuid) => { + if (childIndx >= indx) { + usedIndx.delete(childIndx); + childIndx++; + usedIndx.add(childIndx); + } + childMap.set(uuid, childIndx); + }); } usedIndx.add(indx); childMap.set(uuid, indx); @@ -304,31 +340,38 @@ export class Offspring { } } }); - if (!missing) { - break; - } } + + if (missing) { + throw new OffspringError("Can't find a solution to sort the nodes!"); + } + /** Second sort should only change the order of new nodes. */ child.sort((a: Neuron, b: Neuron) => { if (a.type == "output") { if (b.type != "output") { return 1; } - return Number.parseInt(a.uuid.substring(7)) - - Number.parseInt(b.uuid.substring(7)); + const aIndx = Number.parseInt(a.uuid.substring(7)); + const bIndx = Number.parseInt(b.uuid.substring(7)); + + return aIndx - bIndx; } else if (b.type == "output") { return -1; - } else if (a.type == "input" || b.type == "input") { + } else if (a.type == "input" && b.type == "input") { return a.index - b.index; } else { const aIndx = childMap.get(a.uuid); + if (aIndx == undefined) throw new Error(`Can't find ${a.uuid}`); const bIndx = childMap.get(b.uuid); + if (bIndx == undefined) throw new Error(`Can't find ${b.uuid}`); + /* * Sort by index in child array, if not input or output. * This will ensure that the order of the nodes is the same as the order of the nodes in the mother and father networks. * This is important for the crossover function to work correctly. */ - return (aIndx ? aIndx : 0) - (bIndx ? bIndx : 0); + return aIndx - bIndx; } }); } diff --git a/test/ActivationNames.ts b/test/ActivationNames.ts index 79cf86e8..788dd052 100644 --- a/test/ActivationNames.ts +++ b/test/ActivationNames.ts @@ -1,20 +1,15 @@ -import { fail } from "https://deno.land/std@0.218.0/assert/fail.ts"; import { Activations } from "../src/methods/activations/Activations.ts"; +import { assertNotEquals } from "https://deno.land/std@0.218.0/assert/assert_not_equals.ts"; +import { assert } from "https://deno.land/std@0.218.0/assert/assert.ts"; Deno.test("ActivationNames", () => { Activations.NAMES.forEach((name) => { const activation = Activations.find(name); - if (!activation) { - throw new Error(`Could not find activation for ${name}`); - } - if (name == "INVERSE") { - fail("INVERSE is not a valid activation"); - } + assert(activation !== undefined, `Could not find activation for ${name}`); + assertNotEquals(name, "INVERSE", "INVERSE is not a valid activation"); }); const inverse = Activations.find("INVERSE"); - if (!inverse) { - throw new Error(`Could not find activation for INVERSE`); - } + assert(inverse !== undefined, `Could not find activation for INVERSE`); }); diff --git a/test/Creature.ts b/test/Creature.ts index 61203d8d..ad9dd08a 100644 --- a/test/Creature.ts +++ b/test/Creature.ts @@ -278,18 +278,18 @@ Deno.test("SWAP_NODES", () => { }); Deno.test("gender-tag", () => { - const network1 = new Creature(2, 2); - const network2 = new Creature(2, 2); + const mum = new Creature(2, 2); + const dad = new Creature(2, 2); - addTag(network1.neurons[0], "gender", "male"); + addTag(mum.neurons[3], "gender", "male"); - addTag(network2.neurons[0], "gender", "female"); + addTag(dad.neurons[3], "gender", "female"); // Crossover - const child = Offspring.bread(network1, network2); + const child = Offspring.bread(mum, dad); if (child) { - const gender = getTag(child.neurons[0], "gender"); + const gender = getTag(child.neurons[3], "gender"); assert(gender == "male" || gender == "female", "No gender: " + gender); }