generated from michael/webpack-base
identify an especially tricky bug
This commit is contained in:
parent
9e624f225e
commit
c7abcbb03a
@ -25,10 +25,10 @@ const FEATURES = {
|
|||||||
HEAD_Y: 'HEAD_Y',
|
HEAD_Y: 'HEAD_Y',
|
||||||
APPLE_REL_X: 'APPLE_REL_X',
|
APPLE_REL_X: 'APPLE_REL_X',
|
||||||
APPLE_REL_Y: 'APPLE_REL_Y',
|
APPLE_REL_Y: 'APPLE_REL_Y',
|
||||||
TAIL_ABOVE: 'TAIL_ABOVE',
|
// TAIL_ABOVE: 'TAIL_ABOVE',
|
||||||
TAIL_BELOW: 'TAIL_BELOW',
|
// TAIL_BELOW: 'TAIL_BELOW',
|
||||||
TAIL_LEFT: 'TAIL_LEFT',
|
// TAIL_LEFT: 'TAIL_LEFT',
|
||||||
TAIL_RIGHT: 'TAIL_RIGHT',
|
// TAIL_RIGHT: 'TAIL_RIGHT',
|
||||||
};
|
};
|
||||||
const OUTPUTS = {
|
const OUTPUTS = {
|
||||||
U: 'U',
|
U: 'U',
|
||||||
@ -43,10 +43,10 @@ const _BASE_GENOME_SNAKE_BRAIN_NEAT_EDGES = completeBipartiteEdges(
|
|||||||
FEATURES.HEAD_Y,
|
FEATURES.HEAD_Y,
|
||||||
FEATURES.APPLE_REL_X,
|
FEATURES.APPLE_REL_X,
|
||||||
FEATURES.APPLE_REL_Y,
|
FEATURES.APPLE_REL_Y,
|
||||||
FEATURES.TAIL_ABOVE,
|
// FEATURES.TAIL_ABOVE,
|
||||||
FEATURES.TAIL_BELOW,
|
// FEATURES.TAIL_BELOW,
|
||||||
FEATURES.TAIL_LEFT,
|
// FEATURES.TAIL_LEFT,
|
||||||
FEATURES.TAIL_RIGHT,
|
// FEATURES.TAIL_RIGHT,
|
||||||
],
|
],
|
||||||
[OUTPUTS.U, OUTPUTS.D, OUTPUTS.L, OUTPUTS.R],
|
[OUTPUTS.U, OUTPUTS.D, OUTPUTS.L, OUTPUTS.R],
|
||||||
);
|
);
|
||||||
@ -108,10 +108,10 @@ export class NEATSnakeBrain {
|
|||||||
act.set(FEATURES.HEAD_Y, head.y);
|
act.set(FEATURES.HEAD_Y, head.y);
|
||||||
act.set(FEATURES.APPLE_REL_X, appleRel.x);
|
act.set(FEATURES.APPLE_REL_X, appleRel.x);
|
||||||
act.set(FEATURES.APPLE_REL_Y, appleRel.y);
|
act.set(FEATURES.APPLE_REL_Y, appleRel.y);
|
||||||
act.set(FEATURES.TAIL_ABOVE, Math.max(...above, BOARD_SQUARES));
|
// act.set(FEATURES.TAIL_ABOVE, Math.max(...above, BOARD_SQUARES));
|
||||||
act.set(FEATURES.TAIL_BELOW, Math.max(...below, BOARD_SQUARES));
|
// act.set(FEATURES.TAIL_BELOW, Math.max(...below, BOARD_SQUARES));
|
||||||
act.set(FEATURES.TAIL_LEFT, Math.max(...left, BOARD_SQUARES));
|
// act.set(FEATURES.TAIL_LEFT, Math.max(...left, BOARD_SQUARES));
|
||||||
act.set(FEATURES.TAIL_RIGHT, Math.max(...right, BOARD_SQUARES));
|
// act.set(FEATURES.TAIL_RIGHT, Math.max(...right, BOARD_SQUARES));
|
||||||
|
|
||||||
this.brain.think(act);
|
this.brain.think(act);
|
||||||
|
|
||||||
|
@ -228,7 +228,7 @@
|
|||||||
* - Effectively pass data from inputs, through hidden nodes, to outputs
|
* - Effectively pass data from inputs, through hidden nodes, to outputs
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { edgesToNodes, NodeID, traceParents, RawEdge } from './network';
|
import { edgesToNodes, NodeID, traceParents, RawEdge, traceChildren } from './network';
|
||||||
import { keyMax, mapInvert, mapMap, randchoice, randint, randomNegPos, setDifference, setMap } from './util';
|
import { keyMax, mapInvert, mapMap, randchoice, randint, randomNegPos, setDifference, setMap } from './util';
|
||||||
|
|
||||||
export interface GeneData {
|
export interface GeneData {
|
||||||
@ -258,6 +258,33 @@ let g_node_id = 1;
|
|||||||
let g_innovation_number = 1;
|
let g_innovation_number = 1;
|
||||||
let g_species_id = 1;
|
let g_species_id = 1;
|
||||||
|
|
||||||
|
function hashGenome(genome: Genome) {
|
||||||
|
const cyrb53 = (str: string, seed = 0) => {
|
||||||
|
let h1 = 0xdeadbeef ^ seed;
|
||||||
|
let h2 = 0x41c6ce57 ^ seed;
|
||||||
|
for (let i = 0, ch; i < str.length; i++) {
|
||||||
|
ch = str.charCodeAt(i);
|
||||||
|
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||||
|
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||||
|
}
|
||||||
|
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
|
||||||
|
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||||
|
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
|
||||||
|
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||||
|
|
||||||
|
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
||||||
|
};
|
||||||
|
let hash = 0;
|
||||||
|
for (const gene of genome) {
|
||||||
|
hash += cyrb53(gene.src_id);
|
||||||
|
hash += cyrb53(gene.dst_id);
|
||||||
|
hash += gene.data.weight;
|
||||||
|
hash += gene.data.enabled ? 1 : 0;
|
||||||
|
hash += gene.data.innovation;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
export function resetGlobalIDs(config: { node_id: number; innovation_number: number; species_id: number }) {
|
export function resetGlobalIDs(config: { node_id: number; innovation_number: number; species_id: number }) {
|
||||||
g_node_id = config.node_id;
|
g_node_id = config.node_id;
|
||||||
g_innovation_number = config.innovation_number;
|
g_innovation_number = config.innovation_number;
|
||||||
@ -508,7 +535,8 @@ export function mutateNewConn(newConn: { src_id: NodeID; dst_id: NodeID }, newWe
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findAcyclicNewConns<DataT>(rawEdges: RawEdge<DataT>[]): { src_id: NodeID; dst_id: NodeID }[] {
|
// TODO: improve name since also excluding source nodes
|
||||||
|
export function findAcyclicNonSourceNewConns<DataT>(rawEdges: RawEdge<DataT>[]): { src_id: NodeID; dst_id: NodeID }[] {
|
||||||
// finds potential new connections that are acyclic and are not already connected
|
// finds potential new connections that are acyclic and are not already connected
|
||||||
|
|
||||||
// NOTE: there's some performance stuff here that could definitely be improved
|
// NOTE: there's some performance stuff here that could definitely be improved
|
||||||
@ -518,12 +546,22 @@ export function findAcyclicNewConns<DataT>(rawEdges: RawEdge<DataT>[]): { src_id
|
|||||||
// a node that is connected to one of its parents creates a cycle
|
// a node that is connected to one of its parents creates a cycle
|
||||||
const allNodeIDs = new Set(nodes.keys());
|
const allNodeIDs = new Set(nodes.keys());
|
||||||
const parents = traceParents(nodes);
|
const parents = traceParents(nodes);
|
||||||
|
const children = traceChildren(nodes);
|
||||||
|
|
||||||
|
// TODO: think on this more:
|
||||||
|
// adding a new connection needs to not connect a source -> source or a sink -> sink
|
||||||
|
// or else the innovation number system gets messed up
|
||||||
|
const sources = new Set(Array.from(parents.entries()).flatMap(([k, v]) => (v.size === 0 ? [k] : [])));
|
||||||
|
const sinks = new Set(Array.from(children.entries()).flatMap(([k, v]) => (v.size === 0 ? [k] : [])));
|
||||||
|
console.log({ sources, sinks });
|
||||||
|
|
||||||
const acyclic = new Map<NodeID, Set<NodeID>>();
|
const acyclic = new Map<NodeID, Set<NodeID>>();
|
||||||
for (const [nodeID, nodeParents] of parents.entries()) {
|
for (const [nodeID, nodeParents] of parents.entries()) {
|
||||||
const nodeParentIDs = setMap(nodeParents, n => n.id);
|
const nodeParentIDs = setMap(nodeParents, n => n.id);
|
||||||
const nodeIDsAcyclic = setDifference(allNodeIDs, nodeParentIDs);
|
const nodeIDsAcyclic = setDifference(allNodeIDs, nodeParentIDs);
|
||||||
nodeIDsAcyclic.delete(nodeID);
|
nodeIDsAcyclic.delete(nodeID);
|
||||||
acyclic.set(nodeID, nodeIDsAcyclic);
|
const nodeIDsAcyclicNonSource = setDifference(nodeIDsAcyclic, sources);
|
||||||
|
acyclic.set(nodeID, nodeIDsAcyclicNonSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
// flatten
|
// flatten
|
||||||
@ -552,6 +590,9 @@ export interface MutateConfig {
|
|||||||
export function mutate(genome: Genome, config: MutateConfig): Genome {
|
export function mutate(genome: Genome, config: MutateConfig): Genome {
|
||||||
const { mutate_rate, assign_rate, assign_mag, perturb_mag, new_node_rate, new_connection_rate } = config;
|
const { mutate_rate, assign_rate, assign_mag, perturb_mag, new_node_rate, new_connection_rate } = config;
|
||||||
|
|
||||||
|
console.log('mutating: ', hashGenome(genome), genome);
|
||||||
|
findAcyclicNonSourceNewConns(genome);
|
||||||
|
|
||||||
const newGenome = genome.map(gene => {
|
const newGenome = genome.map(gene => {
|
||||||
if (Math.random() >= mutate_rate) return gene; // this connection should not be mutated
|
if (Math.random() >= mutate_rate) return gene; // this connection should not be mutated
|
||||||
if (Math.random() < assign_rate) {
|
if (Math.random() < assign_rate) {
|
||||||
@ -574,8 +615,10 @@ export function mutate(genome: Genome, config: MutateConfig): Genome {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Math.random() < new_connection_rate) {
|
if (Math.random() < new_connection_rate) {
|
||||||
|
// TODO: figure out why we're not hitting a
|
||||||
|
// TODO: disallow "source" nodes from being new connection destinations
|
||||||
// create a new connection between two *previously unconnected* nodes
|
// create a new connection between two *previously unconnected* nodes
|
||||||
const options = findAcyclicNewConns(newGenome);
|
const options = findAcyclicNonSourceNewConns(newGenome);
|
||||||
if (options.length === 0) {
|
if (options.length === 0) {
|
||||||
// TODO: remove this warn once this starts working
|
// TODO: remove this warn once this starts working
|
||||||
// this is mostly a sanity check / useful for metrics
|
// this is mostly a sanity check / useful for metrics
|
||||||
@ -583,10 +626,17 @@ export function mutate(genome: Genome, config: MutateConfig): Genome {
|
|||||||
} else {
|
} else {
|
||||||
// choose a random connection
|
// choose a random connection
|
||||||
const newConn = randchoice(options);
|
const newConn = randchoice(options);
|
||||||
|
console.log('adding new connection...', hashGenome(genome), genome, newConn);
|
||||||
|
if (new Set(['U', 'D', 'L', 'R']).has(newConn.dst_id)) {
|
||||||
|
debugger;
|
||||||
|
}
|
||||||
const newGene = mutateNewConn(newConn, assign_mag * randomNegPos());
|
const newGene = mutateNewConn(newConn, assign_mag * randomNegPos());
|
||||||
newGenome.push(newGene);
|
newGenome.push(newGene);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findAcyclicNonSourceNewConns(newGenome);
|
||||||
}
|
}
|
||||||
|
console.log('created: ', hashGenome(newGenome), newGenome);
|
||||||
|
|
||||||
return newGenome;
|
return newGenome;
|
||||||
}
|
}
|
||||||
@ -619,6 +669,7 @@ export function computeNextGeneration(
|
|||||||
const mom = randchoice(winners);
|
const mom = randchoice(winners);
|
||||||
const dad = chooseMate(mom, winnersPopulation, mcc);
|
const dad = chooseMate(mom, winnersPopulation, mcc);
|
||||||
const crossed = crossGenomes(mom, dad, fitness, cc);
|
const crossed = crossGenomes(mom, dad, fitness, cc);
|
||||||
|
// TODO: crossed has cycles!
|
||||||
const baby = mutate(crossed, mc);
|
const baby = mutate(crossed, mc);
|
||||||
|
|
||||||
// assign to a species + add to next generation
|
// assign to a species + add to next generation
|
||||||
|
@ -80,7 +80,7 @@ export function traceParents<DataT>(nodes: Network<Node<DataT>>) {
|
|||||||
function traceNodeParents(node: Node<DataT>, depth: number): Set<Node<DataT>> {
|
function traceNodeParents(node: Node<DataT>, depth: number): Set<Node<DataT>> {
|
||||||
console.log('tracing: ', node.id, depth);
|
console.log('tracing: ', node.id, depth);
|
||||||
// TODO: there's a particularly nasty issue where maybe nodes are not just from their own genome?
|
// TODO: there's a particularly nasty issue where maybe nodes are not just from their own genome?
|
||||||
if (depth > 1000) {
|
if (depth > 20) {
|
||||||
debugger;
|
debugger;
|
||||||
}
|
}
|
||||||
if (parents.has(node.id)) return parents.get(node.id)!;
|
if (parents.has(node.id)) return parents.get(node.id)!;
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
tournamentSelectionWithChampions,
|
tournamentSelectionWithChampions,
|
||||||
compatibilityDistance,
|
compatibilityDistance,
|
||||||
crossGenomes,
|
crossGenomes,
|
||||||
findAcyclicNewConns,
|
findAcyclicNonSourceNewConns,
|
||||||
Genome,
|
Genome,
|
||||||
mutateAssign,
|
mutateAssign,
|
||||||
mutateNewConn,
|
mutateNewConn,
|
||||||
@ -357,7 +357,7 @@ function testMutateNewConn() {
|
|||||||
}
|
}
|
||||||
addTest(testMutateNewConn);
|
addTest(testMutateNewConn);
|
||||||
|
|
||||||
function testFindAcyclicNewConns() {
|
function testFindAcyclicNonSourceNewConns() {
|
||||||
/*
|
/*
|
||||||
* all edges pointing down
|
* all edges pointing down
|
||||||
*
|
*
|
||||||
@ -377,22 +377,22 @@ function testFindAcyclicNewConns() {
|
|||||||
{ src_id: 'D', dst_id: 'F', data: null },
|
{ src_id: 'D', dst_id: 'F', data: null },
|
||||||
];
|
];
|
||||||
|
|
||||||
const options = findAcyclicNewConns(edges);
|
const options = findAcyclicNonSourceNewConns(edges);
|
||||||
const expected = [
|
const expected = [
|
||||||
{ src_id: 'A', dst_id: 'B' },
|
// not A -> B - B is a source
|
||||||
{ src_id: 'A', dst_id: 'F' },
|
{ src_id: 'A', dst_id: 'F' },
|
||||||
{ src_id: 'B', dst_id: 'A' },
|
// not B -> A - A is a source
|
||||||
{ src_id: 'B', dst_id: 'C' },
|
{ src_id: 'B', dst_id: 'C' },
|
||||||
{ src_id: 'B', dst_id: 'E' },
|
{ src_id: 'B', dst_id: 'E' },
|
||||||
{ src_id: 'B', dst_id: 'F' },
|
{ src_id: 'B', dst_id: 'F' },
|
||||||
{ src_id: 'C', dst_id: 'B' },
|
// not C -> B - B is a source
|
||||||
{ src_id: 'C', dst_id: 'D' },
|
{ src_id: 'C', dst_id: 'D' },
|
||||||
{ src_id: 'C', dst_id: 'F' },
|
{ src_id: 'C', dst_id: 'F' },
|
||||||
{ src_id: 'D', dst_id: 'C' },
|
{ src_id: 'D', dst_id: 'C' },
|
||||||
// not E -> B
|
// not E -> B - B is a parent of E
|
||||||
{ src_id: 'E', dst_id: 'F' },
|
{ src_id: 'E', dst_id: 'F' },
|
||||||
// not F -> A
|
// not F -> A - A is a parent of F
|
||||||
// not F -> B
|
// not F -> B - B is a parent of F
|
||||||
{ src_id: 'F', dst_id: 'C' },
|
{ src_id: 'F', dst_id: 'C' },
|
||||||
{ src_id: 'F', dst_id: 'E' },
|
{ src_id: 'F', dst_id: 'E' },
|
||||||
];
|
];
|
||||||
@ -406,7 +406,7 @@ function testFindAcyclicNewConns() {
|
|||||||
|
|
||||||
assertDeepEqual(options, expected);
|
assertDeepEqual(options, expected);
|
||||||
}
|
}
|
||||||
addTest(testFindAcyclicNewConns);
|
addTest(testFindAcyclicNonSourceNewConns);
|
||||||
|
|
||||||
function testComputeNextGeneration() {
|
function testComputeNextGeneration() {
|
||||||
function makeGenome(weight: number) {
|
function makeGenome(weight: number) {
|
||||||
|
Loading…
Reference in New Issue
Block a user