Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
luisfuturist committed Mar 18, 2024
0 parents commit e21c66b
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 0 deletions.
78 changes: 78 additions & 0 deletions examples/typing-monkeys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { evolve, stats } from "../src/lib"
import { Organism } from "../src/types";

const target = "hello world";

const { population, populationFitness } = evolve({
mutationRate: 0.009,
populationSize: 200,
genomeLength: target.length,
gene: () => randomChar(),
fitness: (o) => {
let score = 0;

for (let i = 0; i < o.genome.length; i++) {
if (o.genome[i] === target[i]) {
score++;
}
}

return score / o.genome.length;
},
matingPool: (population, populationFitness, maxFitness) => {
const matingPool: typeof population[number][] = [];

for (let i = 0; i < population.length; i++) {
const fitness = map(populationFitness[i], 0, maxFitness, 0, 1);
const n = Math.floor(fitness * 100);

for (let j = 0; j < n; j++) {
const o = population[i];
matingPool.push(o);
}
}

return matingPool;
},
crossover: <G>(a: Organism<G>, b: Organism<G>) => {
const midpoint = a.genome.length / 2;

const genome = [...a.genome.slice(0, midpoint), ...b.genome.slice(midpoint)];

const child: Organism<G> = {
genome,
};

return child;
},
mutate: (child, mutationRate, gene) => {
for (let i = 0; i < child.genome.length; i++) {
if (Math.random() < mutationRate) {
child.genome[i] = gene();
}
}
},
shouldFinish: ({ generation }) => {
return generation >= 400;
},
});

const s = stats(population, populationFitness)
console.log(s)
console.log(s.fittest.genome.join(""))

function randomChar() {
const chars = 'abcdefghijklmnopqrstuvwxyz ';
const randomIndex = Math.floor(Math.random() * chars.length);
return chars[randomIndex];
}

function map(value, currentMin, currentMax, targetMin, targetMax) {
// Calculate the proportion of the value relative to the current range
let proportion = (value - currentMin) / (currentMax - currentMin);

// Scale the proportion to fit within the target range
let mappedValue = proportion * (targetMax - targetMin) + targetMin;

return mappedValue;
}
1 change: 1 addition & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "./examples/typing-monkeys"
139 changes: 139 additions & 0 deletions src/lib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { CalculatePopulationFitnessOptions, CreateMatingPoolOptions, CreatePopulationOptions, EvolutionStats, EvolveOptions, GeneFn, Genome, MateOptions, Organism, Population, PopulationFitness } from "./types";

export function createPopulation<G>(
n: number,
options: CreatePopulationOptions<G>,
) {
const pop: Population<G> = [];

for (let i = 0; i < n; i++) {
pop.push(options.organism(i));
}

return pop;
}

export function createGenome<G>(
length: number,
gene: GeneFn<G>,
): Genome<G> {
return Array.from({ length }).map(() => gene());
}

export function calculatePopulationFitness<G>(
population: Population<G>,
options: CalculatePopulationFitnessOptions<G>,
) {
const populationFitness: number[] = [];

for (let i = 0; i < population.length; i++) {
const o = population[i];
populationFitness[i] = options.fitness(o);
}

return populationFitness;
}

export function createMatingPool<G>(
population: Population<G>,
populationFitness: PopulationFitness,
options: CreateMatingPoolOptions<G>,
) {
let maxFitness = 0;

for (let i = 0; i < population.length; i++) {
const fitness = populationFitness[i];

if (fitness > maxFitness) {
maxFitness = fitness;
}
}

return options.matingPool(population, populationFitness, maxFitness);
}

export function mate<G>(
population: Population<G>,
matingPool: Organism<G>[],
options: MateOptions<G>,
) {
const newPop: Population<G> = [];

for (let i = 0; i < population.length; i++) {
const a = Math.floor(Math.random() * matingPool.length);
const b = Math.floor(Math.random() * matingPool.length);

const partnerA = matingPool[a];
const partnerB = matingPool[b];
let child = options.crossover(partnerA, partnerB);
options.mutate(child, options.mutationRate, options.gene);

newPop.push(child);
}

return newPop;
}

export function evolve<G>(options: EvolveOptions<G>) {
const params = {
populationSize: 100,
mutationRate: 0.05,
...options,
} satisfies EvolveOptions<G>;

let generation = 0;

let population = createPopulation(params.populationSize, {
organism: () => ({
genome: createGenome(options.genomeLength, options.gene),
}),
});
let populationFitness = calculatePopulationFitness(population, {
fitness: params.fitness,
});

options.onGeneratePopulation?.({ population, populationFitness, context: { generation } });

while (!options.shouldFinish({ generation })) {
populationFitness = calculatePopulationFitness(population, {
fitness: params.fitness,
});

const matingPool = createMatingPool(population, populationFitness, {
matingPool: params.matingPool,
});

const newGeneration = mate(population, matingPool, {
crossover: params.crossover,
mutate: params.mutate,
mutationRate: params.mutationRate,
gene: params.gene,
})

population = newGeneration;

generation++;

options.onGeneratePopulation?.({ population, populationFitness, context: { generation } });
}

return { population, populationFitness };
}

export function stats<G>(
population: Population<G>,
populationFitness: PopulationFitness,
): EvolutionStats<G> {
const fittestIndex = populationFitness.indexOf(Math.max(...populationFitness));
const fittest = population[fittestIndex];

const totalFitness = populationFitness.reduce((acc, val) => acc + val, 0);
const avgFitness = totalFitness / populationFitness.length;

return {
fittest,
fittestIndex,
avgFitness,
totalFitness,
};
}
66 changes: 66 additions & 0 deletions src/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
export type Gene<G = any> = G;
export type Genome<G> = Gene<G>[];
export type Organism<G> = {
genome: Genome<G>
};

export type Population<G> = Organism<G>[];

export type OrganismFn<G> = (i: number) => Organism<G>;
export type GeneFn<G> = () => Gene<G>;

export type CrossoverFn<G> = (a: Organism<G>, b: Organism<G>) => Organism<G>;
export type MutationFn<G> = (child: Organism<G>, mutationRate: number, gene: GeneFn<G>) => void;
export type FitnessFn<G> = (o: Organism<G>) => number;
export type MatingPoolFn<G> = (population: Population<G>, populationFitness: PopulationFitness, maxFitness: number) => Population<G>;

export type PopulationFitness = number[];

export type EvolutionStats<G> = {
fittest: Organism<G>
fittestIndex: number
avgFitness: number
totalFitness: number
};

export type GenerationEvent<G> = {
context: ProblemContext<G>
population: Population<G>
populationFitness: PopulationFitness
};

export type ProblemContext<G> = { generation: number };

export type CreatePopulationOptions<G> = {
organism: OrganismFn<G>
};

export type CreateMatingPoolOptions<G> = {
matingPool: MatingPoolFn<G>
};

export type CalculatePopulationFitnessOptions<G> = {
fitness: FitnessFn<G>
};

export type MateOptions<G> = {
mutationRate: number
crossover: CrossoverFn<G>
mutate: MutationFn<G>
gene: GeneFn<G>
};

type EvolveOptions<G> = {
populationSize?: number
genomeLength: number
gene: GeneFn<G>
matingPool: MatingPoolFn<G>
crossover: CrossoverFn<G>
mutationRate?: number
mutate: MutationFn<G>
fitness: FitnessFn<G>

shouldFinish: (c: ProblemContext<G>) => boolean

onGeneratePopulation?: (e: GenerationEvent<G>) => void
};

0 comments on commit e21c66b

Please sign in to comment.