Skip to content

Commit

Permalink
Refactor Offspring class to improve code readability and maintainabil…
Browse files Browse the repository at this point in the history
…ity (#310)

Co-authored-by: Nigel Leck <[email protected]>
  • Loading branch information
nleck and nigelleck authored Mar 10, 2024
1 parent 61b55eb commit 9c878e1
Show file tree
Hide file tree
Showing 2 changed files with 257 additions and 23 deletions.
77 changes: 54 additions & 23 deletions src/architecture/Offspring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,38 +32,26 @@ export class Offspring {

const neuronMap = new Map<string, Neuron>();
const connectionsMap = new Map<string, SynapseExport[]>();
function cloneConnections(
creature: Creature,
connections: SynapseInternal[],
): SynapseExport[] {
const tmpConnections: SynapseExport[] = [];

connections.forEach((connection) => {
const c: SynapseExport = {
fromUUID: creature.neurons[connection.from].uuid,
toUUID: creature.neurons[connection.to].uuid,
weight: connection.weight,
type: connection.type,
};
tmpConnections.push(c);
});

return tmpConnections;
}

for (const node of mother.neurons) {
if (node.type !== "input") {
neuronMap.set(node.uuid, node);
const connections = mother.inwardConnections(node.index);
connectionsMap.set(node.uuid, cloneConnections(mother, connections));
connectionsMap.set(
node.uuid,
Offspring.cloneConnections(mother, connections),
);
}
}

for (const node of father.neurons) {
if (node.type !== "input") {
if (Math.random() >= 0.5) {
const connections = father.inwardConnections(node.index);
const tmpConnections = cloneConnections(father, connections);
const tmpConnections = Offspring.cloneConnections(
father,
connections,
);

neuronMap.set(node.uuid, node);
connectionsMap.set(node.uuid, tmpConnections);
Expand Down Expand Up @@ -102,7 +90,7 @@ export class Offspring {
const connections = parent.inwardConnections(fromNeuron.index);
connectionsMap.set(
fromNeuron.uuid,
cloneConnections(parent, connections),
Offspring.cloneConnections(parent, connections),
);
addedMissing = true;
}
Expand Down Expand Up @@ -150,7 +138,7 @@ export class Offspring {
}

try {
Offspring.sortNodes(
Offspring.sortNeurons(
tmpNodes,
mother.neurons,
father.neurons,
Expand Down Expand Up @@ -250,7 +238,26 @@ export class Offspring {
}
}

static sortNodes(
public static cloneConnections(
creature: Creature,
connections: SynapseInternal[],
): SynapseExport[] {
const tmpConnections: SynapseExport[] = [];

connections.forEach((connection) => {
const c: SynapseExport = {
fromUUID: creature.neurons[connection.from].uuid,
toUUID: creature.neurons[connection.to].uuid,
weight: connection.weight,
type: connection.type,
};
tmpConnections.push(c);
});

return tmpConnections;
}

public static sortNeurons(
child: Neuron[],
mother: Neuron[],
father: Neuron[],
Expand Down Expand Up @@ -284,6 +291,30 @@ export class Offspring {
} else if (b.type == "output") {
return -1;
}
if (a.index == b.index) {
if (a.uuid == b.uuid) {
throw new Error(`Duplicate uuid ${a.uuid}`);
}
const mumIndxA = motherMap.get(a.uuid);
if (mumIndxA == undefined) {
return -1;
}
const dadIndxA = fatherMap.get(a.uuid);
if (dadIndxA == undefined) {
return 1;
}
const mumIndxB = motherMap.get(b.uuid);
if (mumIndxB == undefined) {
return -1;
}

const dadIndxB = fatherMap.get(b.uuid);
if (dadIndxB == undefined) {
return 1;
}

return mumIndxA - mumIndxB;
}
return a.index - b.index;
});
const usedIndx = new Set<number>();
Expand Down
203 changes: 203 additions & 0 deletions test/Offspring/SortNeurons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import { Creature, CreatureExport } from "../../mod.ts";
import { Offspring } from "../../src/architecture/Offspring.ts";
import { SynapseExport } from "../../src/architecture/SynapseInterfaces.ts";
import { assertEquals } from "https://deno.land/[email protected]/assert/assert_equals.ts";

function makeMum() {
const json: CreatureExport = {
neurons: [
{ type: "hidden", uuid: "common-a", squash: "IDENTITY", bias: 0.1 },

{ type: "hidden", uuid: "mum-a", squash: "IDENTITY", bias: -0.9 },

{ type: "hidden", uuid: "common-b", squash: "IDENTITY", bias: 0.1 },
{ type: "hidden", uuid: "mum-b", squash: "IDENTITY", bias: -0.8 },

{ type: "hidden", uuid: "common-c", squash: "IDENTITY", bias: 0.1 },
{ type: "hidden", uuid: "mum-c", squash: "IDENTITY", bias: 0 },
{ type: "hidden", uuid: "common-d", squash: "IDENTITY", bias: 0.1 },
{
type: "output",
squash: "IDENTITY",
uuid: "output-0",
bias: 1,
},
{
type: "output",
squash: "IDENTITY",
uuid: "output-1",
bias: 0,
},
],
synapses: [
{ fromUUID: "input-0", toUUID: "common-b", weight: -0.3 },
{ fromUUID: "input-1", toUUID: "common-a", weight: -0.3 },
{ fromUUID: "common-a", toUUID: "mum-a", weight: 0.3 },
{ fromUUID: "mum-a", toUUID: "common-b", weight: 0.3 },

{ fromUUID: "common-b", toUUID: "mum-b", weight: 0.6 },
{ fromUUID: "mum-b", toUUID: "common-c", weight: 0.31 },
{ fromUUID: "common-c", toUUID: "mum-c", weight: 0.33 },
{ fromUUID: "mum-c", toUUID: "common-d", weight: 0.33 },
{ fromUUID: "common-c", toUUID: "output-0", weight: 0.31 },
{ fromUUID: "common-d", toUUID: "output-0", weight: 0.32 },
{ fromUUID: "common-a", toUUID: "output-1", weight: 0.34 },
],
input: 3,
output: 2,
};
const creature = Creature.fromJSON(json);
creature.validate();

return creature;
}

function makeDad() {
const json: CreatureExport = {
neurons: [
{ type: "hidden", uuid: "common-a", squash: "IDENTITY", bias: 0.1 },

{ type: "hidden", uuid: "dad-a", squash: "IDENTITY", bias: -0.9 },

{ type: "hidden", uuid: "common-b", squash: "IDENTITY", bias: 0.1 },
{ type: "hidden", uuid: "dad-b", squash: "IDENTITY", bias: -0.8 },
{ type: "hidden", uuid: "dad-b2", squash: "IDENTITY", bias: -0.8 },

// { type: "hidden", uuid: "common-c", squash: "IDENTITY", bias: 0.1 },
{ type: "hidden", uuid: "dad-c", squash: "IDENTITY", bias: 0 },
{ type: "hidden", uuid: "common-d", squash: "IDENTITY", bias: 0.1 },
{
type: "output",
squash: "IDENTITY",
uuid: "output-0",
bias: 1,
},
{
type: "output",
squash: "IDENTITY",
uuid: "output-1",
bias: 0,
},
],
synapses: [
{ fromUUID: "input-0", toUUID: "common-b", weight: -0.3 },
{ fromUUID: "input-1", toUUID: "common-a", weight: -0.3 },
{ fromUUID: "common-a", toUUID: "dad-a", weight: 0.3 },
{ fromUUID: "dad-a", toUUID: "common-b", weight: 0.3 },

{ fromUUID: "common-b", toUUID: "dad-b", weight: 0.6 },
{ fromUUID: "dad-b", toUUID: "output-0", weight: 0.31 },
{ fromUUID: "common-a", toUUID: "dad-b2", weight: 0.33 },
{ fromUUID: "dad-b2", toUUID: "dad-c", weight: 0.33 },
{ fromUUID: "dad-c", toUUID: "common-d", weight: 0.33 },
{ fromUUID: "dad-b", toUUID: "output-1", weight: 0.32 },
{ fromUUID: "common-d", toUUID: "output-0", weight: 0.33 },
{ fromUUID: "common-a", toUUID: "output-1", weight: 0.32 },
],
input: 3,
output: 2,
};
const creature = Creature.fromJSON(json);
creature.validate();

return creature;
}

function makeChild() {
const json: CreatureExport = {
neurons: [
{ type: "hidden", uuid: "dad-a", squash: "IDENTITY", bias: -0.9 },
{ type: "hidden", uuid: "mum-a", squash: "IDENTITY", bias: -0.9 },

{ type: "hidden", uuid: "common-b", squash: "IDENTITY", bias: 0.1 },
{ type: "hidden", uuid: "dad-b", squash: "IDENTITY", bias: -0.8 },

{ type: "hidden", uuid: "common-c", squash: "IDENTITY", bias: 0.1 },

{ type: "hidden", uuid: "dad-c", squash: "IDENTITY", bias: 0 },
{ type: "hidden", uuid: "mum-c", squash: "IDENTITY", bias: 0 },
{ type: "hidden", uuid: "common-d", squash: "IDENTITY", bias: 0.1 },

{ type: "hidden", uuid: "common-a", squash: "IDENTITY", bias: 0.1 },
{
type: "output",
squash: "IDENTITY",
uuid: "output-0",
bias: 1,
},
{
type: "output",
squash: "IDENTITY",
uuid: "output-1",
bias: 0,
},
],
synapses: [
{ fromUUID: "input-0", toUUID: "common-b", weight: -0.3 },
{ fromUUID: "input-1", toUUID: "common-a", weight: -0.3 },
{ fromUUID: "input-2", toUUID: "dad-a", weight: 0.3 },
{ fromUUID: "dad-a", toUUID: "mum-a", weight: 0.3 },
{ fromUUID: "mum-a", toUUID: "common-b", weight: 0.3 },

{ fromUUID: "common-b", toUUID: "dad-b", weight: 0.6 },
{ fromUUID: "dad-b", toUUID: "common-c", weight: 0.31 },
{ fromUUID: "common-c", toUUID: "mum-c", weight: 0.33 },
{ fromUUID: "common-c", toUUID: "dad-c", weight: 0.33 },
{ fromUUID: "dad-c", toUUID: "output-1", weight: 0.33 },
{ fromUUID: "mum-c", toUUID: "common-d", weight: 0.33 },
{ fromUUID: "common-c", toUUID: "output-0", weight: 0.31 },
{ fromUUID: "common-d", toUUID: "output-0", weight: 0.32 },
{ fromUUID: "common-a", toUUID: "output-1", weight: 0.34 },
],
input: 3,
output: 2,
};
const creature = Creature.fromJSON(json);
creature.validate();

return creature;
}

Deno.test(
"Sort Neurons",
() => {
const mum = makeMum();
const dad = makeDad();
const child = makeChild();

const connectionsMap = new Map<string, SynapseExport[]>();

for (const node of child.neurons) {
if (node.type !== "input") {
const connections = child.inwardConnections(node.index);
connectionsMap.set(
node.uuid,
Offspring.cloneConnections(child, connections),
);
}
}

const scrambled = child.neurons.slice().sort(() => Math.random() - 0.5);

const sorted = scrambled.slice();
Offspring.sortNeurons(
sorted,
mum.neurons,
dad.neurons,
connectionsMap,
);

sorted.forEach((n) => console.info(n.uuid));

assertEquals(sorted[0].uuid, "input-0");
assertEquals(sorted[1].uuid, "input-1");
assertEquals(sorted[2].uuid, "input-2");
assertEquals(sorted[3].uuid, "common-a");
assertEquals(sorted[4].uuid, "dad-a");
assertEquals(sorted[5].uuid, "mum-a");

assertEquals(sorted[sorted.length - 3].uuid, "dad-c");
assertEquals(sorted[sorted.length - 2].uuid, "output-0");
assertEquals(sorted[sorted.length - 1].uuid, "output-1");
},
);

0 comments on commit 9c878e1

Please sign in to comment.