add setUnion and make setIntersection more versatile

This commit is contained in:
Michael Peters 2024-09-04 20:27:50 -07:00
parent c7abcbb03a
commit a0a2e7c9bb
5 changed files with 58 additions and 30 deletions

View File

@ -343,5 +343,5 @@ export default function runCanvas(canvas: HTMLCanvasElement, pipeRef: MutableRef
}
keys.bindKeys();
engine.run(update, render);
// engine.run(update, render);
}

View File

@ -536,7 +536,7 @@ export function mutateNewConn(newConn: { src_id: NodeID; dst_id: NodeID }, newWe
}
// TODO: improve name since also excluding source nodes
export function findAcyclicNonSourceNewConns<DataT>(rawEdges: RawEdge<DataT>[]): { src_id: NodeID; dst_id: NodeID }[] {
export function findAcyclicInternalNewConns<DataT>(rawEdges: RawEdge<DataT>[]): { src_id: NodeID; dst_id: NodeID }[] {
// finds potential new connections that are acyclic and are not already connected
// NOTE: there's some performance stuff here that could definitely be improved
@ -548,12 +548,11 @@ export function findAcyclicNonSourceNewConns<DataT>(rawEdges: RawEdge<DataT>[]):
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
// TODO: exclude *both* sources and sinks
// - sources are defined during think
// - sinks should not be connected? <-- they probably could be, but not clean imo
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>>();
for (const [nodeID, nodeParents] of parents.entries()) {
@ -591,7 +590,7 @@ export function mutate(genome: Genome, config: MutateConfig): Genome {
const { mutate_rate, assign_rate, assign_mag, perturb_mag, new_node_rate, new_connection_rate } = config;
console.log('mutating: ', hashGenome(genome), genome);
findAcyclicNonSourceNewConns(genome);
findAcyclicInternalNewConns(genome);
const newGenome = genome.map(gene => {
if (Math.random() >= mutate_rate) return gene; // this connection should not be mutated
@ -618,7 +617,7 @@ export function mutate(genome: Genome, config: MutateConfig): Genome {
// 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
const options = findAcyclicNonSourceNewConns(newGenome);
const options = findAcyclicInternalNewConns(newGenome);
if (options.length === 0) {
// TODO: remove this warn once this starts working
// this is mostly a sanity check / useful for metrics
@ -634,7 +633,7 @@ export function mutate(genome: Genome, config: MutateConfig): Genome {
newGenome.push(newGene);
}
findAcyclicNonSourceNewConns(newGenome);
findAcyclicInternalNewConns(newGenome);
}
console.log('created: ', hashGenome(newGenome), newGenome);

View File

@ -27,34 +27,45 @@ export function keyMax<T>(values: Iterable<T>, keyFunc: (v: T) => number) {
return best.value;
}
// TODO: add test for mapInvert
export function mapInvert<K, V>(m: Map<K, V>): Map<V, Set<K>> {
const newM = new Map<V, Set<K>>();
for (const [k, v] of m.entries()) {
if (newM.has(v)) {
newM.get(v)!.add(k);
} else {
newM.set(v, new Set([k]));
}
}
return newM;
const newM = new Map<V, Set<K>>();
for (const [k, v] of m.entries()) {
if (newM.has(v)) {
newM.get(v)!.add(k);
} else {
newM.set(v, new Set([k]));
}
}
return newM;
}
export function setMap<T, U>(s: Set<T>, mapper: (t: T) => U): Set<U> {
return new Set(Array.from(s).map(mapper));
}
// TODO: add test for mapMap
export function mapMap<K1, V1, K2, V2>(m: Map<K1, V1>, mapper: (k: K1, v: V1) => [k: K2, v: V2]): Map<K2, V2> {
return new Map(Array.from(m.entries()).map(([k, v]) => mapper(k, v)));
return new Map(Array.from(m.entries()).map(([k, v]) => mapper(k, v)));
}
export function setIntersection<T>(a: Set<T>, b: Set<T>) {
// a - b
const intersection = new Set(a);
for (const e of a) if (!b.has(e)) intersection.delete(e);
export function setIntersection<T>(...sets: Set<T>[]) {
// a & b & ...
if (sets.length === 0) return new Set();
const intersection = new Set(sets[0]!);
for (const set of sets.slice(1)) {
for (const e of intersection) if (!set.has(e)) intersection.delete(e);
}
return intersection;
}
export function setUnion<T>(...sets: Set<T>[]) {
// a & b & ...
if (sets.length === 0) return new Set();
const union = new Set(sets[0]!);
for (const set of sets.slice(1)) {
for (const e of set) union.add(e);
}
return union;
}
export function setDifference<T>(a: Set<T>, b: Set<T>) {
// a - b
const diff = new Set(a);

View File

@ -4,7 +4,7 @@ import {
tournamentSelectionWithChampions,
compatibilityDistance,
crossGenomes,
findAcyclicNonSourceNewConns,
findAcyclicInternalNewConns,
Genome,
mutateAssign,
mutateNewConn,
@ -357,7 +357,7 @@ function testMutateNewConn() {
}
addTest(testMutateNewConn);
function testFindAcyclicNonSourceNewConns() {
function testFindAcyclicInternalNewConns() {
/*
* all edges pointing down
*
@ -377,7 +377,7 @@ function testFindAcyclicNonSourceNewConns() {
{ src_id: 'D', dst_id: 'F', data: null },
];
const options = findAcyclicNonSourceNewConns(edges);
const options = findAcyclicInternalNewConns(edges);
const expected = [
// not A -> B - B is a source
{ src_id: 'A', dst_id: 'F' },
@ -406,7 +406,7 @@ function testFindAcyclicNonSourceNewConns() {
assertDeepEqual(options, expected);
}
addTest(testFindAcyclicNonSourceNewConns);
addTest(testFindAcyclicInternalNewConns);
function testComputeNextGeneration() {
function makeGenome(weight: number) {

View File

@ -1,4 +1,4 @@
import { keyMax, mapInvert, mapMap, setDifference, setMap } from '../site/snake/util';
import { keyMax, mapInvert, mapMap, setDifference, setIntersection, setMap, setUnion } from '../site/snake/util';
import { addTest, assert, assertSetEqual } from './tests';
function testKeyMax() {
@ -34,6 +34,24 @@ function testSetMap() {
}
addTest(testSetMap);
function testSetIntersection() {
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
const expected = new Set([2, 3]);
const actual = setIntersection(a, b);
assertSetEqual(actual, expected);
}
addTest(testSetIntersection);
function testSetUnion() {
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
const expected = new Set([1, 2, 3, 4]);
const actual = setUnion(a, b);
assertSetEqual(actual, expected);
}
addTest(testSetUnion);
function testMapMap() {
const src = new Map();
src.set('A', 1);