From 753d9fddf3c19e5459d8e63678f6ac1d91d86c52 Mon Sep 17 00:00:00 2001 From: GiulioZani Date: Tue, 1 Mar 2022 11:30:04 +0100 Subject: [PATCH] node --- GA/package-lock.json | 30 +++++++++++ GA/package.json | 14 +++++ GA/src/evolve.js | 91 ++++++++++++++++++++++++++++++++ GA/src/get_fitness.js | 23 ++++++++ GA/src/index.js | 65 +++++++++++++++++++++++ GA/{ => ts-src}/evolve.ts | 0 GA/{ => ts-src}/get_fitness.ts | 2 +- GA/{main.ts => ts-src/index.ts} | 24 +++++---- GA/{ => ts-src}/rastrigin.png | Bin GA/{ => ts-src}/rosenbrock.png | Bin GA/tsconfig.json | 10 ++++ 11 files changed, 249 insertions(+), 10 deletions(-) create mode 100644 GA/package-lock.json create mode 100644 GA/package.json create mode 100644 GA/src/evolve.js create mode 100644 GA/src/get_fitness.js create mode 100644 GA/src/index.js rename GA/{ => ts-src}/evolve.ts (100%) rename GA/{ => ts-src}/get_fitness.ts (94%) rename GA/{main.ts => ts-src/index.ts} (88%) rename GA/{ => ts-src}/rastrigin.png (100%) rename GA/{ => ts-src}/rosenbrock.png (100%) create mode 100644 GA/tsconfig.json diff --git a/GA/package-lock.json b/GA/package-lock.json new file mode 100644 index 0000000..c7f170d --- /dev/null +++ b/GA/package-lock.json @@ -0,0 +1,30 @@ +{ + "name": "genetic_algorithm", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "genetic_algorithm", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@types/node": "^17.0.21" + } + }, + "node_modules/@types/node": { + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", + "dev": true + } + }, + "dependencies": { + "@types/node": { + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", + "dev": true + } + } +} diff --git a/GA/package.json b/GA/package.json new file mode 100644 index 0000000..737f5aa --- /dev/null +++ b/GA/package.json @@ -0,0 +1,14 @@ +{ + "name": "genetic_algorithm", + "version": "1.0.0", + "description": "", + "main": "./src/index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "giulio zani", + "license": "ISC", + "devDependencies": { + "@types/node": "^17.0.21" + } +} diff --git a/GA/src/evolve.js b/GA/src/evolve.js new file mode 100644 index 0000000..312411b --- /dev/null +++ b/GA/src/evolve.js @@ -0,0 +1,91 @@ +const getArgMax = (values) => { + let max = values[0]; + let maxI = 0; + for (let i = 0; i < values.length; i++) { + const val = values[i]; + if (val > max) { + max = val; + maxI = i; + } + } + return maxI; +}; +const getArgMin = (values) => { + let max = values[0]; + let maxI = 0; + for (let i = 0; i < values.length; i++) { + const val = values[i]; + if (val < max) { + max = val; + maxI = i; + } + } + return maxI; +}; +const random = (a, b) => Math.random() * (b - a) + a; +const randomGenomes = (count, validGenomeRanges) => { + return Array(count) + .fill(0) + .map(() => validGenomeRanges.map((x) => random(x[0], x[1]))); +}; +const clip = (val, min, max) => Math.min(max, Math.max(min, val)); +const getMutation = (value, range, mutationImpact) => clip(value + + Math.random() * + mutationImpact * + (range[1] - range[0]) * + (Math.random() > 0.5 ? -1 : 1), range[0], range[1]); +const mutate = (genome, mutationRate, mutationImpact, validGenomeRanges) => { + const doMutate = Math.random() <= mutationRate; + const mutated = genome.map((x, i) => doMutate ? getMutation(x, validGenomeRanges[i], mutationImpact) + : x); + return mutated; +}; +const selectBest = (genomes, fitnesses, survivalThreshold = 0.3) => { + //const sortedGenomes = argSort(genomes, fitnesses); + const sortedGenomesWithFitnesses = genomes.map((g, i) => [g, fitnesses[i]]).sort((a, b) => a[1] - b[1]); + const sortedGenomes = sortedGenomesWithFitnesses.map((x) => x[0]); + const topIndex = Math.round(survivalThreshold * genomes.length); + const result = sortedGenomes.slice(0, topIndex); + return result; +}; +const mate = (g1, g2, mutationRate, mutationImpact, validGenomeRanges) => { + return mutate(g1.map((_, i) => (g1[i] + g2[i]) / 2), mutationRate, mutationImpact, validGenomeRanges); +}; +const getChildren = (genomes, mutationRate, mutationImpact, validGenomeRanges, count = 0) => { + if (count === 0) { + count = genomes.length; + } + const children = new Array(); + while (children.length < count) { + for (let i = genomes.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + children.push(mate(genomes[i], genomes[j], mutationRate, mutationImpact, validGenomeRanges)); + } + } + return children.slice(0, count); +}; +const getFitnesses = (threads, population) => new Promise((resolve, _reject) => { + const fitnesses = new Array(); + let completed = 0; + let sent = 0; + for (let i = 0; i < threads.length; i++) { + const thread = threads[i]; + const genome1 = population[sent]; + thread.postMessage([genome1, sent]); + sent += 1; + thread.onmessage = (e) => { + const [fitness, index] = e["data"]; + fitnesses[index] = fitness; + completed += 1; + if (sent < population.length) { + const genome2 = population[sent]; + thread.postMessage([genome2, sent]); + sent += 1; + } + else if (completed === population.length) { + resolve(fitnesses); + } + }; + } +}); +export { getArgMax, getArgMin, getChildren, getFitnesses, randomGenomes, selectBest, }; diff --git a/GA/src/get_fitness.js b/GA/src/get_fitness.js new file mode 100644 index 0000000..10e56ba --- /dev/null +++ b/GA/src/get_fitness.js @@ -0,0 +1,23 @@ +let getFitness = null; +addEventListener("message", async (event) => { + const parsedEvent = event["data"]; + if (typeof parsedEvent === "string") { + try { + getFitness = (await import(parsedEvent)).default; + } + catch { + throw `Module ${parsedEvent} doesn't exist!`; + } + } + else { + if (getFitness !== null) { + const [genome, index] = parsedEvent; + const fitness = getFitness(genome); + postMessage([fitness, index]); + } + else { + throw "Must initialize objective function first!"; + } + } +}); +export {}; diff --git a/GA/src/index.js b/GA/src/index.js new file mode 100644 index 0000000..40b4b74 --- /dev/null +++ b/GA/src/index.js @@ -0,0 +1,65 @@ +import * as path from 'path'; +import * as fs from 'fs'; +import { getArgMin, getChildren, getFitnesses, randomGenomes, selectBest, } from "./evolve"; +const evolve = async (mutationRate, mutationImpact, survivalThreshold, generationCount, populationSize, threadCount, objectiveFunctionLocation, objectiveFunctionName, validGenomeRanges, savePath) => { + let population = randomGenomes(populationSize, validGenomeRanges); + let fitnesses = []; + const threads = new Array(threadCount).fill(0).map(() => new Worker(new URL("./get_fitness.ts", import.meta.url).href, { + type: "module", + })); + const objectiveFunctionFileName = path.join(objectiveFunctionLocation, `${objectiveFunctionName}.ts`); + for (const thread of threads) { + thread.postMessage(objectiveFunctionFileName); + } + const history = []; + for (let generation = 0; generation < generationCount; generation++) { + fitnesses = await getFitnesses(threads, population); + const survivors = selectBest(population, fitnesses, survivalThreshold); + const children = getChildren(survivors, mutationRate, mutationImpact, validGenomeRanges, populationSize - survivors.length); + const novelIndividuals = randomGenomes(populationSize - (children.length + survivors.length), validGenomeRanges); + const argMin = getArgMin(fitnesses); + // console.log(fitnesses.map((x) => x).sort((x, y) => y - x)); + //console.log(fitnesses.slice().sort()) + console.log(`End of generation ${generation + 1}\nBest fitness:${fitnesses[argMin]}\nBest genome:\n${population[argMin]}\n`); + history.push([fitnesses, population]); + population = [...survivors, ...children, ...novelIndividuals]; + } + /* + await Deno.writeTextFile( + `histories/${objectiveFunctionName}_${ + String(performance.now()).replace(".", "") + }.json`, + JSON.stringify(history, null, 2), + ); + */ + /* + await Deno.writeTextFile( + path.join(savePath, `${objectiveFunctionName}_ga.json`), + JSON.stringify( + history.map((xs) => + xs[1].map((x) => { + return { position: x }; + }) + ), + null, + 2, + ), + ); + */ + return history; +}; +const main = async () => { + const config = JSON.parse(fs.readFileSync("./config.json")); + const { mutationRate, mutationImpact, survivalThreshold, generationCount, populationSize, threadCount, objectiveFunctionLocation, objectiveFunctionName, savePath, repeat, } = config; + const validGenomeRanges = config.validGenomeRanges; + const histories = []; + for (let i = 0; i < repeat; i++) { + histories.push(await evolve(mutationRate, mutationImpact, survivalThreshold, generationCount, populationSize, threadCount, objectiveFunctionLocation, objectiveFunctionName, validGenomeRanges, savePath)); + } + //await Deno.writeTextFile( + // `histories/${objectiveFunctionName}.json`, + // JSON.stringify(histories, null, 2), + //); + //Deno.exit(); +}; +await main(); diff --git a/GA/evolve.ts b/GA/ts-src/evolve.ts similarity index 100% rename from GA/evolve.ts rename to GA/ts-src/evolve.ts diff --git a/GA/get_fitness.ts b/GA/ts-src/get_fitness.ts similarity index 94% rename from GA/get_fitness.ts rename to GA/ts-src/get_fitness.ts index c9a25e3..c326044 100644 --- a/GA/get_fitness.ts +++ b/GA/ts-src/get_fitness.ts @@ -1,4 +1,4 @@ -import { Genome } from "./evolve.ts"; +import { Genome } from "./evolve"; let getFitness: ((a: Genome) => number) | null = null; addEventListener("message", async (event) => { diff --git a/GA/main.ts b/GA/ts-src/index.ts similarity index 88% rename from GA/main.ts rename to GA/ts-src/index.ts index 9722206..b7a3254 100644 --- a/GA/main.ts +++ b/GA/ts-src/index.ts @@ -1,4 +1,6 @@ -import * as path from "https://deno.land/std/path/mod.ts"; +import * as path from 'path'; +import * as fs from 'fs'; + import { Genome, getArgMin, @@ -6,8 +8,7 @@ import { getFitnesses, randomGenomes, selectBest, -} from "./evolve.ts"; -import config from "./config.json" assert { type: "json" }; +} from "./evolve"; const evolve = async ( mutationRate: number, @@ -18,9 +19,9 @@ const evolve = async ( threadCount: number, objectiveFunctionLocation: string, objectiveFunctionName: string, + validGenomeRanges: [number, number][], savePath: string, ) => { - const validGenomeRanges = config.validGenomeRanges as [number, number][]; let population = randomGenomes(populationSize, validGenomeRanges); let fitnesses: number[] = []; const threads = new Array(threadCount).fill(0).map( @@ -71,6 +72,7 @@ const evolve = async ( JSON.stringify(history, null, 2), ); */ + /* await Deno.writeTextFile( path.join(savePath, `${objectiveFunctionName}_ga.json`), JSON.stringify( @@ -83,9 +85,11 @@ const evolve = async ( 2, ), ); + */ return history; }; const main = async () => { + const config = JSON.parse(fs.readFileSync("./config.json") as unknown as string) const { mutationRate, mutationImpact, @@ -98,6 +102,7 @@ const main = async () => { savePath, repeat, } = config; + const validGenomeRanges = config.validGenomeRanges as [number, number][]; const histories = []; for (let i = 0; i < repeat; i++) { histories.push( @@ -110,15 +115,16 @@ const main = async () => { threadCount, objectiveFunctionLocation, objectiveFunctionName, + validGenomeRanges, savePath, ), ); } - await Deno.writeTextFile( - `histories/${objectiveFunctionName}.json`, - JSON.stringify(histories, null, 2), - ); - Deno.exit(); + //await Deno.writeTextFile( + // `histories/${objectiveFunctionName}.json`, + // JSON.stringify(histories, null, 2), + //); + //Deno.exit(); }; await main(); diff --git a/GA/rastrigin.png b/GA/ts-src/rastrigin.png similarity index 100% rename from GA/rastrigin.png rename to GA/ts-src/rastrigin.png diff --git a/GA/rosenbrock.png b/GA/ts-src/rosenbrock.png similarity index 100% rename from GA/rosenbrock.png rename to GA/ts-src/rosenbrock.png diff --git a/GA/tsconfig.json b/GA/tsconfig.json new file mode 100644 index 0000000..9cd4f29 --- /dev/null +++ b/GA/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions":{ + "module":"ESNext", + "target":"ESNext", + "outDir":"src", + "rootDir":"ts-src", + "alwaysStrict":true, + "strict":true + } +}