Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update quality.sh and Creature.ts files #308

Merged
merged 2 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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