Skip to content

Commit

Permalink
Update quality.sh and Creature.ts files (#308)
Browse files Browse the repository at this point in the history
Co-authored-by: Nigel Leck <[email protected]>
  • Loading branch information
nleck and nigelleck authored Mar 8, 2024
1 parent 7467e32 commit 2f5f2c9
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 53 deletions.
4 changes: 2 additions & 2 deletions quality.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
rm -rf .trace .test
deno test --allow-all --trace-leaks --v8-flags=--max-old-space-size=8192 --parallel --config ./test/deno.json
5 changes: 3 additions & 2 deletions src/Creature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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}`,
);
}

Expand Down
109 changes: 76 additions & 33 deletions src/architecture/Offspring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
) {
Expand Down Expand Up @@ -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<string, number>();
Expand Down Expand Up @@ -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");
}
});
}
Expand Down Expand Up @@ -236,6 +257,11 @@ export class Offspring {
connectionsMap: Map<string, SynapseExport[]>,
) {
const childMap = new Map<string, number>();

mother.forEach((neuron, indx) => {
if (neuron.type == "input") childMap.set(neuron.uuid, indx);
});

const motherMap = new Map<string, number>();
const fatherMap = new Map<string, number>();

Expand All @@ -261,8 +287,9 @@ export class Offspring {
return a.index - b.index;
});
const usedIndx = new Set<number>();
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;
Expand All @@ -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);
Expand All @@ -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;
}
});
}
Expand Down
15 changes: 5 additions & 10 deletions test/ActivationNames.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import { fail } from "https://deno.land/[email protected]/assert/fail.ts";
import { Activations } from "../src/methods/activations/Activations.ts";
import { assertNotEquals } from "https://deno.land/[email protected]/assert/assert_not_equals.ts";
import { assert } from "https://deno.land/[email protected]/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`);
});
12 changes: 6 additions & 6 deletions test/Creature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down

0 comments on commit 2f5f2c9

Please sign in to comment.