-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit e21c66b
Showing
4 changed files
with
284 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
import "./examples/typing-monkeys" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}; |