Minimal neuroevolution library. A Genome is a list of synapse genes (sourceLayer, sourceIndex, sinkLayer, sinkIndex, weight); a Brain builds the network from it, prunes paths that never reach an output, and feeds inputs through it.
Brain.feed() fires synapses sequentially in gene order (not topologically) — this is by design: gene order is part of the phenotype and evolution can exploit it. Every other execution backend in this library reproduces that exact behavior.
pnpm add github:c0sm1cdus7/brainThe package compiles itself on install (postinstall: tsc), so consumers need no build step.
import { Genome, Brain } from "brain";
const genome = Genome.create(300, {
inputLayerLength: 32,
hiddenLayers: 1,
outputLayerLength: 2
});
const brain = new Brain(genome);
const output = brain.feed(input); // number[] of length <= outputLayerLength
const offspring = Genome.crossover(parentA, parentB, mutationRate);BrainPack flattens many brains into contiguous typed arrays and replays the exact Brain.feed() operations over Float64Array — results are bit-identical to Brain.feed() (spec-enforced) and faster, since it skips the object graph.
import { BrainPack } from "brain";
const pack = BrainPack.fromGenomes(genomes);
const outputs = pack.feedAll(inputs); // one input vector per agent
const one = pack.feed(agentIndex, input);One GPU thread per agent; each thread walks its synapse chain in exact gene order. Backed by Dawn via the optional webgpu package — no browser involved: it targets Metal on macOS, Vulkan on Linux and D3D12 on Windows, and runs headless (a display server is not required; on Linux servers the vendor GPU driver with its Vulkan ICD is enough).
pnpm add webgpu # optional peer dependency, only where you want GPU executionimport { BrainPack, GpuRuntime } from "brain";
const runtime = await GpuRuntime.create(); // null if webgpu isn't installed or no GPU exists
const feeder = runtime ? runtime.createPack(BrainPack.fromGenomes(genomes)) : BrainPack.fromGenomes(genomes);
const outputs = await feeder.feedAll(inputs);
feeder.destroy?.(); // per generation (frees GPU buffers)
runtime?.destroy(); // when done entirely
console.log(runtime?.description); // e.g. "apple metal-3 apple-m1-pro"Precision: GPUs expose no float64, so the GPU path computes in float32 — same operations, same order, ~1e-6 output drift vs the CPU float64 path (spec-enforced tolerance, and verified to not flip ±0.5 threshold decisions away from the threshold). Results are deterministic run-to-run on the same machine; different GPU vendors may differ in the last ULP.
Anything that feeds populations can accept a BatchFeeder (implemented by both packs). The bundled grid-world Simulation takes a feederFactory per generation:
const simulation = new Simulation(mapSize, {
...options,
feederFactory: (genomes) => runtime.createPack(BrainPack.fromGenomes(genomes))
});
await simulation.run(steps);pnpm test # vitest: bit-exactness, GPU parity, evolution specs (GPU specs skip gracefully without a GPU)
pnpm start # demo: Brain vs BrainPack vs GpuBrainPack timings + parity on this machine