From 332887faee1b2ada7d5c74c53caa688c5c6f6d7a Mon Sep 17 00:00:00 2001 From: "J.Vovk" Date: Thu, 23 Feb 2023 22:26:38 +0100 Subject: [PATCH] Rewritten callbacks to async/await --- .gitignore | 5 +- benchmark.js | 97 ++--- example_1.js | 62 ++- example_2_training_another_trainer.js | 4 +- example_3.js | 3 +- example_4.js | 3 +- example_5.js | 25 +- vovk-ga.js | 596 ++++++++++++-------------- 8 files changed, 390 insertions(+), 405 deletions(-) diff --git a/.gitignore b/.gitignore index 40e774a..a8f19a6 100644 --- a/.gitignore +++ b/.gitignore @@ -103,4 +103,7 @@ dist # TernJS port file .tern-port -package-lock.json \ No newline at end of file +package-lock.json + + +*test* \ No newline at end of file diff --git a/benchmark.js b/benchmark.js index dd6c3d7..8861913 100644 --- a/benchmark.js +++ b/benchmark.js @@ -14,6 +14,7 @@ // @ts-ignore const cli_input = require('minimist')(process.argv.slice(2)); +const delay = ms => new Promise(r => setTimeout(r, ms)) const padStart = (str, len, def) => { str = str.toString(); while (str.length < len) str = (def || ' ') + str; return str } const random = Math.random const gaussianRand = v => new Array(v > 0 && v < Infinity ? v : 5).fill(0).map(random).reduce((sum, x) => sum += x, 0) / (v > 0 && v < Infinity ? v : 5) @@ -21,7 +22,10 @@ const noise = (mean, stdev) => mean + (2 * gaussianRand() * stdev) - stdev const round = Math.round const floor = Math.floor const ceil = Math.ceil -const sgn = x => x >= 0 ? 1 : -1 +const sgn = x => { + console.log("sgn(", x, ")"); + return x === 0 ? 0 : x > 0 ? 1 : -1; +}; const pi = Math.PI const e = Math.E const abs = Math.abs @@ -41,7 +45,7 @@ const benchmarks = { `\tMultivariate test: Ackley function\n\n` + `\t2-dimensional input with domain: |xi| ≤ 30\n` + `\tThe global minimum is 0 at point: ( 0, 0 )\n\n` + - `\tExample command: node benchmark --gen=20 --pop=200 --mutChance=0.25 --mutPower=0.1 --crossover=0.15 --function=mvfAckley`, + `\tExample command: node benchmark --gen=20 --n=10 --pop=200 --mutChance=0.25 --mutPower=0.1 --crossover=0.15 --function=mvfAckley`, params: { variable: ['x0', 'x1'], type: 'number', range: { min: -30, max: 30 } }, default: {}, targetFitness: 0, @@ -62,7 +66,7 @@ const benchmarks = { `\tMultivariate test: Beale function\n\n` + `\t2-dimensional input with domain: |xi| ≤ 4.5\n` + `\tThe global minimum is 0 at point: ( 3, 0.5 )\n\n` + - `\tExample command: node benchmark --gen=20 --pop=200 --mutChance=0.25 --mutPower=0.1 --crossover=0.15 --function=mvfBeale`, + `\tExample command: node benchmark --gen=20 --n=10 --pop=200 --mutChance=0.25 --mutPower=0.1 --crossover=0.15 --function=mvfBeale`, params: { variable: ['x0', 'x1'], type: 'number', range: { min: -4.5, max: 4.5 } }, default: {}, targetFitness: 0, @@ -80,7 +84,7 @@ const benchmarks = { `\tMultivariate test: Bohachevsky (1) function\n\n` + `\t2-dimensional input with domain: |xi| ≤ 50\n` + `\tThe global minimum is 0 at the zeroth vector: ( 0, 0 )\n\n` + - `\tExample command: node benchmark --gen=20 --pop=200 --mutChance=0.25 --mutPower=0.1 --crossover=0.15 --function=mvfBohachevsky1`, + `\tExample command: node benchmark --gen=20 --n=10 --pop=200 --mutChance=0.25 --mutPower=0.1 --crossover=0.15 --function=mvfBohachevsky1`, params: { variable: ['x0', 'x1'], type: 'number', range: { min: -50, max: 50 } }, default: {}, targetFitness: 0, @@ -97,7 +101,7 @@ const benchmarks = { `\tMultivariate test: Bohachevsky (2) function\n\n` + `\t2-dimensional input with domain: |xi| ≤ 50\n` + `\tThe global minimum is 0 at the zeroth vector: ( 0, 0 )\n\n` + - `\tExample command: node benchmark --gen=20 --pop=200 --mutChance=0.25 --mutPower=0.1 --crossover=0.15 --function=mvfBohachevsky2`, + `\tExample command: node benchmark --gen=20 --n=10 --pop=200 --mutChance=0.25 --mutPower=0.1 --crossover=0.15 --function=mvfBohachevsky2`, params: { variable: ['x0', 'x1'], type: 'number', range: { min: -50, max: 50 } }, default: {}, targetFitness: 0, @@ -115,7 +119,7 @@ const benchmarks = { `\tMultivariate test: Booth function\n\n` + `\t2-dimensional input with domain: |xi| ≤ 10\n` + `\tThe global minimum is 0 at point: ( 1, 3 )\n\n` + - `\tExample command: node benchmark --gen=20 --pop=200 --mutChance=0.25 --mutPower=0.1 --crossover=0.15 --function=mvfBooth`, + `\tExample command: node benchmark --gen=20 --n=10 --pop=200 --mutChance=0.25 --mutPower=0.1 --crossover=0.15 --function=mvfBooth`, params: { variable: ['x0', 'x1'], type: 'number', range: { min: -10, max: 10 } }, default: {}, targetFitness: 0, @@ -133,7 +137,7 @@ const benchmarks = { `\tMultivariate test: Box-Betts exponential quadratic sum\n\n` + `\t3-dimensional input with domain: 0.9 ≤ {x1,x3} ≤ 1.2, 9 ≤ x2 ≤ 11.2\n` + `\tThe global minimum is 0 at point: ( 1, 10, 1 )\n\n` + - `\tExample command: node benchmark --gen=20 --pop=200 --mutChance=0.3 --mutPower=100 --crossover=0.15 --function=mvfBoxBetts`, + `\tExample command: node benchmark --gen=20 --n=10 --pop=200 --mutChance=0.3 --mutPower=100 --crossover=0.15 --function=mvfBoxBetts`, params: [ { variable: ['x1', 'x3'], type: 'number', range: { min: 0.9, max: 1.2 } }, { variable: 'x2', type: 'number', range: { min: 9, max: 11.2 } } @@ -157,7 +161,7 @@ const benchmarks = { `\tMultivariate test: Branin (1) function\n\n` + `\t2-dimensional input with domain: -5 < x0 ≤ 10, 0 ≤ x1 ≤ 15\n` + `\tThe global minimum is 0.398 at points: ( -3.142, 12.275 ), ( 3.142, 2.275 ), ( 9.425, 2.425 )\n\n` + - `\tExample command: node benchmark --gen=20 --pop=200 --mutChance=0.25 --mutPower=0.1 --crossover=0.15 --function=mvfBranin1`, + `\tExample command: node benchmark --gen=20 --n=10 --pop=200 --mutChance=0.25 --mutPower=0.1 --crossover=0.15 --function=mvfBranin1`, params: [ { variable: 'x0', type: 'number', range: { min: -4.9, max: 10 } }, { variable: 'x1', type: 'number', range: { min: 0, max: 15 } }, @@ -177,7 +181,7 @@ const benchmarks = { `\tMultivariate test: Branin (2) function\n\n` + `\t2-dimensional input with domain: |xi| ≤ 10\n` + `\tThe global minimum is 0 at point: ( 0.402369, 0.287406 )\n\n` + - `\tExample command: node benchmark --gen=20 --pop=2000 --mutChance=0.25 --mutPower=1000 --crossover=0.15 --function=mvfBranin2`, + `\tExample command: node benchmark --gen=20 --n=10 --pop=2000 --mutChance=0.25 --mutPower=1000 --crossover=0.15 --function=mvfBranin2`, params: { variable: ['x0', 'x1'], type: 'number', range: { min: -10, max: 10 } }, //default: { x0: 0.402369, x1: 0.287406 }, //default: { x0: 1.597461, x1: -0.287405 }, @@ -197,7 +201,7 @@ const benchmarks = { `\tMultivariate test: three-hump camel back function\n\n` + `\t2-dimensional input with domain: |xi| ≤ 5\n` + `\tThe global minimum is 0 at point: ( 0, 0 )\n\n` + - `\tExample command: node benchmark --gen=20 --pop=2000 --mutChance=0.15 --mutPower=100 --crossover=0.15 --function=mvfCamel3`, + `\tExample command: node benchmark --gen=20 --n=10 --pop=2000 --mutChance=0.15 --mutPower=100 --crossover=0.15 --function=mvfCamel3`, params: { variable: ['x0', 'x1'], type: 'number', range: { min: -5, max: 5 } }, default: {}, targetFitness: 0, @@ -214,7 +218,7 @@ const benchmarks = { `\tMultivariate test: six-hump camel back function\n\n` + `\t2-dimensional input with domain: |xi| ≤ 5\n` + `\tThe global minimum is -1.0316 at point: ( -0.08983, 0.7126 )\n\n` + - `\tExample command: node benchmark --gen=20 --pop=2000 --mutChance=0.15 --mutPower=100 --crossover=0.15 --function=mvfCamel6`, + `\tExample command: node benchmark --gen=20 --n=10 --pop=2000 --mutChance=0.15 --mutPower=100 --crossover=0.15 --function=mvfCamel6`, params: { variable: ['x0', 'x1'], type: 'number', range: { min: -5, max: 5 } }, default: {}, targetFitness: -1.0316, @@ -233,7 +237,7 @@ const benchmarks = { `\tMultivariate test: Chichinadze function\n\n` + `\t2-dimensional input with domain: -30 < x0 ≤ 30, -10 ≤ x1 ≤ 10\n` + `\tThe global minimum is -42.944387 at point: ( 6.189866, 0.5 )\n\n` + - `\tExample command: node benchmark --gen=20 --pop=2000 --mutChance=0.25 --mutPower=10 --crossover=0.15 --function=mvfChichinadze`, + `\tExample command: node benchmark --gen=20 --n=10 --pop=2000 --mutChance=0.25 --mutPower=10 --crossover=0.15 --function=mvfChichinadze`, params: [ { variable: 'x0', type: 'number', range: { min: -30, max: 30 } }, { variable: 'x1', type: 'number', range: { min: -10, max: 10 } }, @@ -255,7 +259,7 @@ const benchmarks = { `\tMultivariate test: Colville function\n\n` + `\t4-dimensional input with domain: |xi| ≤ 10\n` + `\tThe global minimum is 0 at point: ( 1, 1, 1, 1 )\n\n` + - `\tExample command: node benchmark --gen=20 --pop=2000 --mutChance=0.15 --mutPower=100 --crossover=0.15 --function=mvfColville`, + `\tExample command: node benchmark --gen=20 --n=10 --pop=2000 --mutChance=0.15 --mutPower=100 --crossover=0.15 --function=mvfColville`, params: { variable: ['x0', 'x1', 'x2', 'x3'], type: 'number', range: { min: -10, max: 10 } }, default: {}, targetFitness: 0, @@ -274,7 +278,7 @@ const benchmarks = { `\tMultivariate test: Corana function\n\n` + `\t4-dimensional input with domain: |xi| ≤ 100\n` + `\tThe global minimum is 0 at point: ( 1, 1, 1, 1 )\n\n` + - `\tExample command: node benchmark --gen=20 --pop=5000 --mutChance=0.15 --mutPower=10 --crossover=0.15 --function=mvfCorana`, + `\tExample command: node benchmark --gen=20 --n=10 --pop=5000 --mutChance=0.15 --mutPower=10 --crossover=0.15 --function=mvfCorana`, params: { variable: ['x0', 'x1', 'x2', 'x3'], type: 'number', range: { min: -100, max: 100 } }, default: {}, targetFitness: 0, @@ -296,7 +300,7 @@ const benchmarks = { `\tMultivariate test: Easom [Eas90] function\n\n` + `\t2-dimensional input with domain: |xi| ≤ 100\n` + `\tThe global minimum is -1 at point: ( π, π )\n\n` + - `\tExample command: node benchmark --gen=20 --pop=2500 --mutChance=0.25 --mutPower=10 --crossover=0.15 --function=mvfEas90`, + `\tExample command: node benchmark --gen=20 --n=10 --pop=2500 --mutChance=0.25 --mutPower=10 --crossover=0.15 --function=mvfEas90`, params: { variable: ['x0', 'x1'], type: 'number', range: { min: -100, max: 100 } }, default: {}, targetFitness: -1, @@ -315,7 +319,7 @@ const benchmarks = { `\tMultivariate test: Egg holder function\n\n` + `\t2(or n)-dimensional input with domain: |xi| ≤ 512\n` + `\tThe global minimum is -959.6407 at point: ( 512, 404.2319 )\n\n` + - `\tExample command: node benchmark --gen=20 --pop=500 --mutChance=0.35 --mutPower=1 --crossover=0.15 --function=mvfEggholder`, + `\tExample command: node benchmark --gen=20 --n=10 --pop=500 --mutChance=0.35 --mutPower=1 --crossover=0.15 --function=mvfEggholder`, params: { variable: ['x0', 'x1'], type: 'number', range: { min: -512, max: 512 } }, default: {}, targetFitness: -959.6407, @@ -341,7 +345,7 @@ const benchmarks = { `\tMultivariate test: Exp2 function\n\n` + `\t2-dimensional input with domain: 0 ≤ xi ≤ 20\n` + `\tThe global minimum is 0 at point: ( 1, 10 )\n\n` + - `\tExample command: node benchmark --gen=20 --pop=500 --mutChance=0.35 --mutPower=100 --crossover=0.15 --function=mvfExp2`, + `\tExample command: node benchmark --gen=20 --n=10 --pop=500 --mutChance=0.35 --mutPower=100 --crossover=0.15 --function=mvfExp2`, params: { variable: ['x0', 'x1'], type: 'number', range: { min: -0, max: 20 } }, default: {}, targetFitness: 0, @@ -361,7 +365,7 @@ const benchmarks = { `\tMultivariate test: Gear function\n\n` + `\t2-dimensional input with domain: 12 ≤ xi ≤ 60\n` + `\tThe global minimum is 2.7e-12 at point: ( 16, 19, 49, 43 )\n\n` + - `\tExample command: node benchmark --gen=20 --pop=25000 --mutChance=0.05 --mutPower=100000000 --crossover=0.15 --function=mvfGear`, + `\tExample command: node benchmark --gen=20 --n=10 --pop=25000 --mutChance=0.05 --mutPower=100000000 --crossover=0.15 --function=mvfGear`, params: { variable: ['x0', 'x1', 'x2', 'x3'], type: 'number', range: { min: 12, max: 60 } }, default: {}, targetFitness: 2.7e-12, @@ -380,7 +384,7 @@ const benchmarks = { `\tMultivariate test: Goldstein-Price function\n\n` + `\t2-dimensional input with domain: |xi| ≤ 2\n` + `\tThe global minimum is 3 at point: ( 0, -1 )\n\n` + - `\tExample command: node benchmark --gen=20 --pop=2500 --mutChance=0.35 --mutPower=10 --crossover=0.15 --function=mvfGoldsteinPrice`, + `\tExample command: node benchmark --gen=20 --n=10 --pop=2500 --mutChance=0.35 --mutPower=10 --crossover=0.15 --function=mvfGoldsteinPrice`, params: { variable: ['x0', 'x1'], type: 'number', range: { min: -2, max: 2 } }, default: {}, targetFitness: 3, @@ -400,12 +404,11 @@ const benchmarks = { -const trainBenchmarFunction = (BM, options, callback) => { - callback = callback || (() => { }) +const trainBenchmarkFunction = async (BM, options) => { const name = Number.isInteger(BM) ? 'F' + BM : BM const bench = typeof name === 'string' ? benchmarks[name] : BM options = options || {} - const { population, crossover, mutationChance, mutationPower, generations: gens } = options + const { population, crossover, mutationChance, mutationPower, numSurvivors, generations: gens } = options const GA = require('./vovk-ga') const trainer = new GA() if (bench.info) console.log(`Benchmark info:\n` + bench.info); @@ -413,7 +416,7 @@ const trainBenchmarFunction = (BM, options, callback) => { trainer.initialize(bench.default) trainer.setParameters(bench.params) trainer.setMaxPopulation(population || 50) - //trainer.setSurvivors(3) + trainer.setSurvivors(numSurvivors || 3) trainer.letBestSurvive(true) trainer.setSurvivorsPERCENT(0.05) trainer.setCrossoverChance(crossover || 0.25) @@ -424,20 +427,17 @@ const trainBenchmarFunction = (BM, options, callback) => { trainer.setStopFunction(bench.stopFunction) const generations = gens || 10 //trainer.letBestSurvive(true) - setTimeout(() => { - console.log(`Starting benchmark for ${name}`) - trainer.evolve(generations, status => { - const fitness = +status.fitness - console.log(`Gen: ${padStart(status.generation, 3, ' ')} Pop: ${status.population} finished in ${padStart(status.time, 4, ' ')} ms Best fitness: ${fitness >= 0 ? ' ' + fitness.toFixed(9) : fitness.toFixed(9)} ${Object.keys(status.parameters).length < 6 ? ` => Best sample: ${JSON.stringify(status.parameters)}` : ''}`) - }).then(results => { - const fitness = +results.fitness - console.log(`${results.failed ? 'FAILED' : `FINISHED`} in ${padStart(results.totalTime, 4, ' ')} ms Best fitness: ${fitness >= 0 ? ' ' + fitness.toFixed(9) : fitness.toFixed(9)} => Best sample: ${JSON.stringify(results.parameters)}`) - console.log(`Test ${bench.fn.toString()} \nFinal fitness: ${bench.fn(results.parameters)} `); - callback(results) - }).catch(e => { - throw e - }) - }, 1000) + await delay(1000) + console.log(`Starting benchmark for ${name}`) + const updateProgress = status => { + const fitness = +status.fitness + console.log(`Gen: ${padStart(status.generation, 3, ' ')} Pop: ${status.population} finished in ${padStart(status.time, 4, ' ')} ms Best fitness: ${fitness >= 0 ? ' ' + fitness.toFixed(9) : fitness.toFixed(9)} ${Object.keys(status.parameters).length < 6 ? ` => Best sample: ${JSON.stringify(status.parameters)}` : ''}`) + } + const results = await trainer.evolve(generations, updateProgress) + const fitness = +results.fitness + console.log(`${results.failed ? 'FAILED' : `FINISHED`} in ${padStart(results.totalTime, 4, ' ')} ms Best fitness: ${fitness >= 0 ? ' ' + fitness.toFixed(9) : fitness.toFixed(9)} => Best sample: ${JSON.stringify(results.parameters)}`) + console.log(`Test ${bench.fn.toString()} \nFinal fitness: ${bench.fn(results.parameters)} `); + return results } // @ts-ignore @@ -445,16 +445,7 @@ const info = process.argv.reduce((hasInfo, arg) => hasInfo = hasInfo || (arg === let function_input = cli_input['f'] || cli_input['function'] const functionName = isNaN(+function_input) ? function_input : 'F' + function_input const temp_f = benchmarks[functionName] -if (temp_f) { - const name = functionName - const gens = cli_input['g'] || cli_input['gen'] || cli_input['generations'] || 10 - const pop = cli_input['p'] || cli_input['pop'] || cli_input['population'] || 50 - const crossover = cli_input['c'] || cli_input['cross'] || cli_input['crossover'] || 0.25 - const mutChance = cli_input['mc'] || cli_input['mutChance'] || 0.2 - const mutPower = cli_input['mp'] || cli_input['mutPower'] || 100 - if (name) trainBenchmarFunction(name, { generations: gens, population: pop, mutationChance: mutChance, mutationPower: mutPower, crossover: crossover }) - else throw `Benchmark function name '${cli_input[0]}' doesn't exist, plase use this command '--function=mvfBeale'` -} else { +if (!temp_f) { Object.keys(benchmarks).forEach((name, index) => { let message = '' if (index > 0) message += `-------------------------------------\n` @@ -462,4 +453,14 @@ if (temp_f) { message += `Benchmark ${index + 1}` + bench.info console.log(message) }) -} \ No newline at end of file + process.exit(0) +} +const name = functionName +if (!name) throw `Benchmark function name '${cli_input[0]}' doesn't exist, plase use this command '--function=mvfBeale'` +const gens = cli_input['g'] || cli_input['gen'] || cli_input['generations'] || 10 +const pop = cli_input['p'] || cli_input['pop'] || cli_input['population'] || 50 +const numSurvivors = cli_input['n'] || cli_input['numSurvivors'] || 1 +const crossover = cli_input['c'] || cli_input['cross'] || cli_input['crossover'] || 0.25 +const mutChance = cli_input['mc'] || cli_input['mutChance'] || 0.2 +const mutPower = cli_input['mp'] || cli_input['mutPower'] || 100 +trainBenchmarkFunction(name, { generations: gens, population: pop, numSurvivors, mutationChance: mutChance, mutationPower: mutPower, crossover: crossover }) \ No newline at end of file diff --git a/example_1.js b/example_1.js index 608898d..e3fa6f9 100644 --- a/example_1.js +++ b/example_1.js @@ -6,36 +6,35 @@ const trainer = new GA() console.log(`Example 1: Simple function optimization`) -const MY_FUNCTION = (x, y, z) => Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2)) +const MY_FUNCTION = (x, y, z) => Math.sqrt(x ** 2 + y ** 2 + z ** 2) +const desired_output = Math.PI // Desired function output // (1) Direct return fitness function // const myFitnessFunction = (sample) => { // const { x, y, z } = sample -// let desired_output = Math.PI // Desired function output -// let actual_output = MY_FUNCTION(x, y, z) // My function -// let loss = Math.abs(desired_output - actual_output) +// const desired_output = Math.PI // Desired function output +// const actual_output = MY_FUNCTION(x, y, z) // My function +// const loss = Math.abs(desired_output - actual_output) // return loss // } -// (2) Callback return fitness function -// const myFitnessFunction = (sample, callback) => { -// const { x, y, z } = sample -// let desired_output = Math.PI // Desired function output -// let actual_output = MY_FUNCTION(x, y, z) // My function -// let loss = Math.abs(desired_output - actual_output) -// callback(loss) -// } +// (2) Promise return fitness function +// const myFitnessFunction = (sample) => new Promise((resolve, reject) => { +// try { +// const { x, y, z } = sample +// const actual_output = MY_FUNCTION(x, y, z) // My function +// const loss = Math.abs(desired_output - actual_output) +// resolve(loss) +// } catch (e) { reject(e) } +// }) -// (3) Promise return fitness function -const myFitnessFunction = (sample) => new Promise((resolve, reject) => { - try { - const { x, y, z } = sample - let desired_output = Math.PI // Desired function output - let actual_output = MY_FUNCTION(x, y, z) // My function - let loss = Math.abs(desired_output - actual_output) - resolve(loss) - } catch (e) { reject(e) } -}) +// (3) Promise return fitness function with async/await +const myFitnessFunction = async (sample) => { + const { x, y, z } = sample + const actual_output = MY_FUNCTION(x, y, z) // My function + const loss = Math.abs(desired_output - actual_output) + return loss +} const parameters = [ { variable: 'x', type: 'number', range: { min: -20, max: 20 } }, @@ -44,9 +43,6 @@ const parameters = [ // { variable: 'p', type: 'option', options: [1.0, 1.2, 1.4, 1.6, 1.8, 2.0] } ] - - - const logProgress = progress => { console.log(progress.message) } @@ -57,11 +53,6 @@ const logResult = result => { console.log(`Test ${MY_FUNCTION.toString()} ---> ${MY_FUNCTION(x, y, z)}`) } -const catchError = e => { - if (e === 'timeout') console.log('Training timeout!') - else throw e -} - const config = { debug: false, maxPopulation: 1000, @@ -79,6 +70,11 @@ const config = { fitnessTimeout: 10000 } -trainer.configure(config).evolve(100, logProgress) - .then(logResult) - .catch(catchError) \ No newline at end of file +const main = async () => { + trainer.configure(config) + const result = await trainer.evolve(100, logProgress) + logResult(result) +} + + +main().catch(console.error) \ No newline at end of file diff --git a/example_2_training_another_trainer.js b/example_2_training_another_trainer.js index 038efa3..d847689 100644 --- a/example_2_training_another_trainer.js +++ b/example_2_training_another_trainer.js @@ -20,11 +20,9 @@ const mvfBohachevsky2 = { params: { variable: ['x0', 'x1'], type: 'number', range: { min: -50, max: 50 } }, default: {}, targetFitness: 0, - fn: (sample, callback) => { - callback = callback || (() => { }) + fn: (sample) => { const { x0, x1 } = sample const output = sq(x0) + 2 * sq(x1) - 0.3 * cos(3 * pi * x0) * cos(4 * pi * x1) + 0.3 - callback(output) return output } } diff --git a/example_3.js b/example_3.js index 6e598e1..210d679 100644 --- a/example_3.js +++ b/example_3.js @@ -17,10 +17,9 @@ const test = (x, y) => { return output } -const fitnessFunction = (sample, /* callback */) => { // optional async callback +const fitnessFunction = (sample) => { const { x, y } = sample const output = test(x, y) - //callback(output) return output - desiredOutput } diff --git a/example_4.js b/example_4.js index 93787f8..a3f230c 100644 --- a/example_4.js +++ b/example_4.js @@ -11,7 +11,7 @@ const desiredOutput = 'To be or not to be, that is the question.' const parameters = { variable: 'myMessage', size: desiredOutput.length, type: 'string', options: 'abcdefghijklmonpqrstuvxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ,.!?-_' } -const fitnessFunction = (sample, /* callback */) => { // optional async callback +const fitnessFunction = (sample) => { const { myMessage } = sample let correctCharacters = 0 desiredOutput.split('').forEach((c, i) => { @@ -19,7 +19,6 @@ const fitnessFunction = (sample, /* callback */) => { // optional async callba correctCharacters++; }) const score = correctCharacters / desiredOutput.length - //callback(score) return score } diff --git a/example_5.js b/example_5.js index 759b82c..7f84171 100644 --- a/example_5.js +++ b/example_5.js @@ -93,8 +93,8 @@ const fitnessFunction = (sample, /* callback */) => { // optional async callba const conf = { //debug: true, maxPopulation: 10000, - survivorsPERCENT: 0.10, - crossoverChance: 0.05, + survivorsPERCENT: 0.005, + crossoverChance: 0.15, mutationChance: 0.05, mutationPower: 1, bestSurvive: true, @@ -135,6 +135,25 @@ const catchError = e => { setTimeout(() => { console.log(`Starting training...`) trainer.configure(conf).evolve(100, logProgress) - .then(logResult) + .then(result => { + logResult(result) + /* + Plot data: + 1. Points: + 0,0 + 10,10 + 5,5 + 2. Path: + 1 2 3 + */ + console.log(`Plot data:`) + console.log(`1. Points:`) + dataSet.cities.forEach(city => { + console.log(`\t${city.x},${city.y}`) + }) + console.log(`2. Path:`) + const { path } = result.parameters + console.log(`\t${path.map(x => x + 1).join(' ')}`) + }) .catch(catchError) }, 1000) \ No newline at end of file diff --git a/vovk-ga.js b/vovk-ga.js index 8643e51..5c317d9 100644 --- a/vovk-ga.js +++ b/vovk-ga.js @@ -21,28 +21,31 @@ // @ts-check 'use strict' -const { isString } = require("util") - const random = Math.random const floor = Math.floor const round = Math.round const abs = Math.abs -const PI = Math.PI -class TinyPause { constructor(nr_of_calls) { this.call_index = 0; this.nr_of_calls = +nr_of_calls || 10000; }; execute = f => { this.call_index++; if (this.call_index >= this.nr_of_calls) { this.call_index = 0; setTimeout(f, 0); } else f() } } -const sanityPause = new TinyPause(10000).execute -const sanityPauseB = new TinyPause(1000).execute const isPromise = o => !!o && (typeof o === 'object' || typeof o === 'function') && typeof o.then === 'function' const isValidFitnessResult = x => x >= 0 || x < 0 || isNaN(x) const chance = pct => random() <= pct const isArray = a => Array.isArray(a) const isNumber = n => !isArray(n) && n !== null && (n >= 0 || n < 0) +const isString = s => typeof s === 'string' const constrain = (x, min, max) => x > max ? max : x < min ? min : x const snapNumber = (n, s) => { const sign = n < 0 ? -1 : 1; n = abs(n); const L = n % s; const output = n - ((L / s <= 0.5) ? L : L - s); return output * sign } -const forEachObj = (o, cb) => Object.keys(o).forEach((k, i) => cb(o[k], k, i)) -const cloneChild = original => { let cloned_child = {}; forEachObj(original, (prop, key) => cloned_child[key] = prop); return cloned_child } +const cloneChild = (original) => { + const child = {} + const keys = Object.keys(original) + const len = keys.length + for (let i = 0; i < len; i++) { + const key = keys[i] + child[key] = original[key] + } + return child +} const generateChildID = (child, keys) => keys.map(k => child[k]).join('') const getUniquePopulation = (population, params) => { const result = []; const map = new Map(); const keys = params.map(p => p.variable); for (const child of population) { const id = generateChildID(child, keys); if (!map.has(id)) { map.set(id, true); result.push(child); } } return result } @@ -55,9 +58,7 @@ const stirArray = (A, cnt) => { cnt = cnt || 1; const B = A.map(x => x); for (le const randomOfRange = (min, max) => random() * (max - min) + min const randomOfRangeInt = (min, max) => round(randomOfRange(min, max)) const randomArrayItem = A => A[randomOfRangeInt(0, A.length - 1)] -const randomArrayItemProb = (A, prob) => { const hashMap = A.map((a, i) => ({ x: a, i: i, p: prob[i] })).sort((a, b) => a.p < b.p ? 1 : -1); const avgSum = hashMap.reduce((p, x) => p + x.p, 0); hashMap.forEach(h => h.p /= avgSum); let index = hashMap.length; const r = random(); let porbSum = 0; while (index > 0 && porbSum < r) { index--; porbSum += hashMap[index].p } return hashMap[index].i } -const padStart = (str, len, def) => { str = str.toString(); while (str.length < len) str = (def || ' ') + str; return str } /* Example parameter structures @@ -155,114 +156,102 @@ const mutateParameter = (value, param, options, child_proportional_position) => } } -const generatePopulation = (generations, options) => new Promise((resolve, reject) => { - try { - const myPopulation = [] - // If this is the first generation ... - if (generations.length === 0) { - // Generate new population based on initial parameter values OR random parameter values - for (let i = 0; i < options.max_population; i++) { - const child = {} - options.parameters.forEach(param => { - child[param.variable] = options.initialParams[param.variable] || generateRandomParameter(param) - }) +const generatePopulation = (generations, options) => { + const myPopulation = [] + // If this is the first generation ... + if (generations.length === 0) { + // Generate new population based on initial parameter values OR random parameter values + for (let i = 0; i < options.max_population; i++) { + const child = {} + options.parameters.forEach(param => { + child[param.variable] = options.initialParams[param.variable] || generateRandomParameter(param) + }) + myPopulation.push(child) + } + } else { + const previousBest = generations[generations.length - 1].best + if (options.bestSurvive) { + // Retain best parents in new population + previousBest.forEach(best_child => { + const child = cloneChild(best_child) + delete child.__fitness__ + myPopulation.push(child) + }) + // Fill remaining population with random previous best + for (let i = previousBest.length - 1; i < options.max_population; i++) { + const child = cloneChild(randomArrayItem(previousBest)) + delete child.__fitness__ myPopulation.push(child) } } else { - const previousBest = generations[generations.length - 1].best - if (options.bestSurvive) { - // Retain best parents in new population - previousBest.forEach(best_child => { - const child = cloneChild(best_child) - delete child.__fitness__ - myPopulation.push(child) - }) - // Fill remaining population with random previous best - for (let i = previousBest.length - 1; i < options.max_population; i++) { - const child = cloneChild(randomArrayItem(previousBest)) - delete child.__fitness__ - myPopulation.push(child) - } - } else { - // Generate all new generation based on previous best - for (let i = 0; i < options.max_population; i++) { - const child = cloneChild(randomArrayItem(previousBest)) - delete child.__fitness__ - myPopulation.push(child) - } + // Generate all new generation based on previous best + for (let i = 0; i < options.max_population; i++) { + const child = cloneChild(randomArrayItem(previousBest)) + delete child.__fitness__ + myPopulation.push(child) } } - sanityPause(() => { - if (options.verbose) console.log('Created population', myPopulation) - resolve(myPopulation) - }) - } catch (e) { reject(e) } -}) + } + if (options.verbose) console.log('Created population', myPopulation) + return myPopulation +} -const crossoverPopulation = (population, options) => new Promise((resolve, reject) => { - try { - if (options.bestSurvive) { - // Do crossover only on new population, do not crossover original parents - const newPopulation = population.slice(options.nrOfSurvivors) - newPopulation.forEach(child_a => { - options.parameters.forEach(param => { - if (chance(options.crossoverChance)) { - const child_b = randomArrayItem(newPopulation) - const param_a = child_a[param.variable] - const param_b = child_b[param.variable] - child_a[param.variable] = param_b - child_b[param.variable] = param_a - } - }) +const crossoverPopulation = (population, options) => { + if (options.bestSurvive) { + // Do crossover only on new population, do not crossover original parents + const newPopulation = population.slice(options.nrOfSurvivors) + newPopulation.forEach(child_a => { + options.parameters.forEach(param => { + if (chance(options.crossoverChance)) { + const child_b = randomArrayItem(newPopulation) + const param_a = child_a[param.variable] + const param_b = child_b[param.variable] + child_a[param.variable] = param_b + child_b[param.variable] = param_a + } }) - } else { - // Do crossover on whole population - population.forEach(child_a => { - options.parameters.forEach(param => { - if (chance(options.crossoverChance)) { - const child_b = randomArrayItem(population) - const param_a = child_a[param.variable] - const param_b = child_b[param.variable] - child_a[param.variable] = param_b - child_b[param.variable] = param_a - } - }) + }) + } else { + // Do crossover on whole population + population.forEach(child_a => { + options.parameters.forEach(param => { + if (chance(options.crossoverChance)) { + const child_b = randomArrayItem(population) + const param_a = child_a[param.variable] + const param_b = child_b[param.variable] + child_a[param.variable] = param_b + child_b[param.variable] = param_a + } }) - } - sanityPause(() => { - if (options.verbose) console.log('Crossover population', population) - resolve(population) }) - } catch (e) { reject(e) } -}) + } + if (options.verbose) console.log('Crossover population', population) + return population +} -const mutatePopulation = (population, options) => new Promise((resolve, reject) => { - try { - if (options.bestSurvive) { - // Do mutation only on new population, do not mutate original parents - const newPopulation = population.slice(options.nrOfSurvivors) - newPopulation.forEach((child, index) => { - const child_proportional_position = index / newPopulation.length - options.parameters.forEach(param => { - child[param.variable] = mutateParameter(child[param.variable], param, options, child_proportional_position) - }) +const mutatePopulation = (population, options) => { + if (options.bestSurvive) { + // Do mutation only on new population, do not mutate original parents + const newPopulation = population.slice(options.nrOfSurvivors) + newPopulation.forEach((child, index) => { + const child_proportional_position = index / newPopulation.length + options.parameters.forEach(param => { + child[param.variable] = mutateParameter(child[param.variable], param, options, child_proportional_position) }) - } else { - // Do mutation on whole population - population.forEach((child, index) => { - const child_proportional_position = index / population.length - options.parameters.forEach(param => { - child[param.variable] = mutateParameter(child[param.variable], param, options, child_proportional_position) - }) + }) + } else { + // Do mutation on whole population + population.forEach((child, index) => { + const child_proportional_position = index / population.length + options.parameters.forEach(param => { + child[param.variable] = mutateParameter(child[param.variable], param, options, child_proportional_position) }) - } - sanityPause(() => { - if (options.verbose) console.log('Mutated population', population) - resolve(population) }) - } catch (e) { reject(e) } -}) + } + if (options.verbose) console.log('Mutated population', population) + return population +} const fitnessSort = (target, a, b) => { if (a.__failed__) return 1 @@ -280,157 +269,142 @@ const fitnessSort = (target, a, b) => { return (A_dif > B_dif ? +1 : -1) } -const fitnessTestPopulationASYNC = (population, options) => new Promise((resolve, reject) => { + +const fitnessTestChildASYNC = async (child, options) => { + const startTime = +new Date() + const fitnessOutput = options.calculateFitness(child) + const fitness = isPromise(fitnessOutput) ? await fitnessOutput : fitnessOutput + if (!isNumber(fitness)) throw new Error(`Fitness function must return a number (or a promise that resolves to a number) but returned [${typeof fitness}]: "${fitness}"`) + const duration = +new Date() - startTime + if (!isValidFitnessResult(fitness)) throw 'Invalid fitness result' + if (options.verbose) console.log(`Child fitness response in ${duration} ms. Fitness: ${fitness} Output: ${JSON.stringify(child)}`) + return fitness +} + +const fitnessTestPopulationASYNC = async (population, options) => { // Filter out identical children and only keep uniques - try { - let uniquePopulation = getUniquePopulation(population, options.parameters) - const sequence = options.sequence - let i = 0 - const startTime = +new Date() - let timeout = false - const timeout_event = setTimeout(() => { // In case population fitness check takes too long, return timeout error - timeout = true - reject('timeout') - }, options.fitnessTimeout) - const onEachFitnessDone = (child, fitness) => { - i++ - if (options.verbose) console.log(`Child ${padStart(`#${i}`, 5, ' ')} fitness response in ${+new Date() - startTime} ms. Fitness: ${fitness} Output: ${JSON.stringify(child)}`) - if (isNumber(fitness)) child.__fitness__ = fitness - else child.__failed__ = true - if (i === uniquePopulation.length && !timeout) { - clearTimeout(timeout_event) // Disable timeout beacuse population fitness check finished in time - uniquePopulation = uniquePopulation.sort((a, b) => fitnessSort(options.fitnessTargetValue, a, b)) - sanityPause(() => { - if (options.verbose) console.log('Fitness tested population done', uniquePopulation) - resolve(uniquePopulation) - }) - } else { - if (sequence) sanityPauseB(() => testChild(uniquePopulation[i])) + const sequence = options.sequence + const uniquePopulation = getUniquePopulation(population, options.parameters) + let timeout = false + const timeout_event = setTimeout(() => { // In case population fitness check takes too long, return timeout error + timeout = true + throw 'timeout' + }, options.fitnessTimeout) + if (options.verbose) console.log('Testing a population of', uniquePopulation.length) + if (sequence) { + for (let i = 0; i < uniquePopulation.length; i++) { + const child = uniquePopulation[i] + try { + const fitness = await fitnessTestChildASYNC(child, options) + child.__fitness__ = fitness + } catch (e) { + child.__failed__ = true } + if (timeout) break } - - const testChild = child => { - let gotResponse = false - const fitnessCallback = result => { - if (!gotResponse) { - gotResponse = true - sanityPause(() => onEachFitnessDone(child, result)) - } + } else { // Parallel + await Promise.all(uniquePopulation.map(async (child) => { + try { + const fitness = await fitnessTestChildASYNC(child, options) + child.__fitness__ = fitness + } catch (e) { + child.__failed__ = true } - const fitnessOutput = options.calculateFitness(child, fitnessCallback) // Handle async callback - if (isPromise(fitnessOutput)) fitnessOutput.then(fitnessCallback).catch(reject) // Handle async promise - else if (isValidFitnessResult(fitnessOutput)) fitnessCallback(fitnessOutput) // Handle directly returned output - } - - if (options.verbose) console.log('Testing a population of', uniquePopulation.length) - if (sequence) testChild(uniquePopulation[0]) - else uniquePopulation.forEach(testChild) - } catch (e) { reject(e) } -}) - -const populationSelection = (population, options) => new Promise((resolve, reject) => { - try { - population = population.sort((a, b) => fitnessSort(options.fitnessTargetValue, a, b)) - if (options.verbose) console.log(`Selection top ${options.survivorPercent ? `${(options.survivors * 100)}% => ${options.nrOfSurvivors}/${population.length}` : `${options.survivorPercent}/${population.length}`}`) - sanityPause(() => resolve(population.slice(0, options.nrOfSurvivors))) - } catch (e) { reject(e) } -}) - - - - -const EVLOLVE = (options, progress_callback) => new Promise((resolve, reject) => { - try { - progress_callback = progress_callback || ((x) => { }) - options = options || {} - const O = options - - O.currentGeneration = O.currentGeneration || 0 - O.maxGenerations = O.maxGenerations > 0 ? O.maxGenerations : 1 - O.totalTime = O.totalTime || 0 - O.generations = O.generations || 1 - O.parameters = O.parameters - O.initialParams = O.initialParams - O.max_population = O.max_population || 100 - O.survivors = O.survivors - O.nrOfSurvivors = O.nrOfSurvivors - O.survivorPercent = O.survivorPercent - O.fitnessTargetValue = O.fitnessTargetValue || 0 - O.fitnessTargetTolerance = O.fitnessTargetTolerance || 0 - O.fitnessTimeout = O.fitnessTimeout - O.stopFitness = O.stopFitness - O.crossoverChance = O.crossoverChance - O.mutation = O.mutation || { chance: 0.1, power: 0.1 } - O.proportional_mutation_factor = O.proportional_mutation_factor - O.calculateFitness = O.calculateFitness - O.bestSurvive = O.bestSurvive || true - O.sequence = O.sequence || false - O.verbose = O.verbose || false - - if (options.verbose) console.log(options) - //throw '' - const iterate = () => { - options.currentGeneration++ - let startTime = +new Date() - - const finishGeneration = best => { - const thisGeneration = { - index: options.generations.length + 1, - duration: +new Date() - startTime, - best: best - } - if (options.verbose) console.log(`Generation ${thisGeneration.index} finished. Best fitness: ${thisGeneration.best[0].__fitness__}`) - - // Only remember single best from each past generation, except for the last generation remember all survivors - if (options.generations.length > 0) options.generations[options.generations.length - 1].best = [options.generations[options.generations.length - 1].best[0]] - - options.generations.push(thisGeneration) - if (options.generations.length > 5000) options.generations.shift() - - let new_proportional_mutation_factor = abs(options.fitnessTargetValue - thisGeneration.best[0].__fitness__) - if (options.verbose) console.log(`Proportional mutation power changed: ${options.proportional_mutation_factor} => ${new_proportional_mutation_factor}`) + })) + } + clearTimeout(timeout_event) // Disable timeout beacuse population fitness check finished in time + if (options.verbose) console.log('Fitness tested population done', uniquePopulation) + return uniquePopulation +} - let thisBest = cloneChild(thisGeneration.best[0]) - let fitness = thisBest.__fitness__ - delete thisBest.__fitness__ +const populationSelection = (population, options) => { + population = population.sort((a, b) => fitnessSort(options.fitnessTargetValue, a, b)) + if (options.verbose) console.log(`Selection top ${options.survivorPercent ? `${(options.survivors * 100)}% => ${options.nrOfSurvivors}/${population.length}` : `${options.survivorPercent}/${population.length}`}`) + return population.slice(0, options.nrOfSurvivors) +} - const generationsDone = options.currentGeneration >= options.maxGenerations - const targetFitnessDone = options.fitnessTargetValue - fitness === 0 || abs(options.fitnessTargetValue - fitness) < options.fitnessTargetTolerance - const stopFitnessDone = options.stopFitness(fitness) - const finished = generationsDone || targetFitnessDone || stopFitnessDone - options.totalTime += thisGeneration.duration; - options.proportional_mutation_factor = new_proportional_mutation_factor - - const output = { - finished, - generation: thisGeneration.index, - population: options.max_population, - time: thisGeneration.duration, - totalTime: options.totalTime, - fitness: fitness, - parameters: thisBest, - proportional_mutation_factor: new_proportional_mutation_factor - } - if (options.verbose && finished) console.log('Evolution finished it seems ...') - progress_callback(output) - if (finished) resolve(output) - else iterate() - } - - generatePopulation(options.generations, options) - .then(P => crossoverPopulation(P, options)) - .then(P => mutatePopulation(P, options)) - .then(P => fitnessTestPopulationASYNC(P, options)) - .then(P => populationSelection(P, options)) - .then(P => finishGeneration(P)) - .catch(reject) +const EVOLVE = async (options, progress_callback) => { + options = options || {} + const O = options + + O.currentGeneration = O.currentGeneration || 0 + O.maxGenerations = O.maxGenerations > 0 ? O.maxGenerations : 1 + O.totalTime = O.totalTime || 0 + O.generations = O.generations || 1 + O.parameters = O.parameters + O.initialParams = O.initialParams + O.max_population = O.max_population || 100 + O.survivors = O.survivors + O.nrOfSurvivors = O.nrOfSurvivors + O.survivorPercent = O.survivorPercent + O.fitnessTargetValue = O.fitnessTargetValue || 0 + O.fitnessTargetTolerance = O.fitnessTargetTolerance || 0 + O.fitnessTimeout = O.fitnessTimeout + O.stopFitness = O.stopFitness + O.crossoverChance = O.crossoverChance + O.mutation = O.mutation || { chance: 0.1, power: 0.1 } + O.proportional_mutation_factor = O.proportional_mutation_factor + O.calculateFitness = O.calculateFitness + O.bestSurvive = O.bestSurvive || true + O.sequence = O.sequence || false + O.verbose = O.verbose || false + + if (options.verbose) console.log(options) + + while (true) { + options.currentGeneration++ + const startTime = +new Date() + const P1 = await generatePopulation(options.generations, options) + const P2 = await crossoverPopulation(P1, options) + const P3 = await mutatePopulation(P2, options) + const P4 = await fitnessTestPopulationASYNC(P3, options) + const P5 = await populationSelection(P4, options) + + const thisGeneration = { + index: options.generations.length + 1, + duration: +new Date() - startTime, + best: P5 + } + if (options.verbose) console.log(`Generation ${thisGeneration.index} finished. Best fitness: ${thisGeneration.best[0].__fitness__}`) + // Only remember single best from each past generation, except for the last generation remember all survivors + if (options.generations.length > 0) options.generations[options.generations.length - 1].best = [options.generations[options.generations.length - 1].best[0]] + options.generations.push(thisGeneration) + if (options.generations.length > 5000) options.generations.shift() + + const new_proportional_mutation_factor = abs(options.fitnessTargetValue - thisGeneration.best[0].__fitness__) + if (options.verbose) console.log(`Proportional mutation power changed: ${options.proportional_mutation_factor} => ${new_proportional_mutation_factor}`) + + const thisBest = cloneChild(thisGeneration.best[0]) + const fitness = thisBest.__fitness__ + delete thisBest.__fitness__ + + const generationsDone = options.currentGeneration >= options.maxGenerations + const targetFitnessDone = options.fitnessTargetValue - fitness === 0 || abs(options.fitnessTargetValue - fitness) < options.fitnessTargetTolerance + const stopFitnessDone = options.stopFitness(fitness) + + const finished = generationsDone || targetFitnessDone || stopFitnessDone + + options.totalTime += thisGeneration.duration; + options.proportional_mutation_factor = new_proportional_mutation_factor + + const output = { + finished, + generation: thisGeneration.index, + population: options.max_population, + time: thisGeneration.duration, + totalTime: options.totalTime, + fitness: fitness, + parameters: thisBest, + proportional_mutation_factor: new_proportional_mutation_factor } - iterate() - } catch (e) { reject(e) } -}) + if (options.verbose && finished) console.log('Evolution finished it seems ...') + if (progress_callback) progress_callback(output) + if (finished) return output + } +} class Trainer { @@ -447,6 +421,7 @@ class Trainer { mutation: { chance: 0.5, power: 0.1 }, crossoverChance: 0.1, proportional_mutation_factor: 1.0, + /** @type { any[] } */ parameters: [], initialParams: {}, calculateFitness: () => 0, @@ -458,7 +433,7 @@ class Trainer { this.configure(config) }; - /** @param {{ parameters?: object | Array; sequence?: boolean; debug?: boolean; initialValues?: any; maxPopulation?: number; survivors?: number; survivorsPERCENT?: number; crossoverChance?: number; mutationChance?: number; mutationPower?: number; fitnessTargetValue?: number; fitnessTargetTolerance? : number; bestSurvive?: boolean; fitnessFunction?: any; fitnessTimeout?: any; }} config */ + /** @param {{ parameters?: object | Array; sequence?: boolean; debug?: boolean; initialValues?: any; maxPopulation?: number; survivors?: number; survivorsPERCENT?: number; crossoverChance?: number; mutationChance?: number; mutationPower?: number; fitnessTargetValue?: number; fitnessTargetTolerance? : number; bestSurvive?: boolean; fitnessFunction?: any; fitnessTimeout?: any; } | undefined } config */ configure = (config) => { config = config || {} if (config.parameters !== undefined) this.setParameters(config.parameters) @@ -597,74 +572,69 @@ class Trainer { // 3. Do mutate // 4. Do fitness test // 5. Select best - evolveOneGeneration = () => new Promise((resolve, reject) => { - try { - const nrOfSurvivors = this.__internal__.survivorPercent ? Math.ceil(this.__internal__.survivors * this.__internal__.max_population) : this.__internal__.survivors - const options = { - currentGeneration: 0, - maxGenerations: 1, - totalTime: 0, - generations: this.__internal__.generations, - parameters: this.__internal__.parameters, - initialParams: this.__internal__.initialParams, - max_population: this.__internal__.max_population, - survivors: this.__internal__.survivors, - nrOfSurvivors: nrOfSurvivors, - survivorPercent: this.__internal__.survivorPercent, - fitnessTargetValue: this.__internal__.fitnessTargetValue, - fitnessTargetTolerance: this.__internal__.fitnessTargetTolerance, - fitnessTimeout: this.__internal__.fitnessTimeout, - stopFitness: this.__internal__.stopFitness, - crossoverChance: this.__internal__.crossoverChance, - mutation: this.__internal__.mutation || { chance: 0.1, power: 0.1 }, - proportional_mutation_factor: this.__internal__.proportional_mutation_factor, - calculateFitness: this.__internal__.calculateFitness, - bestSurvive: this.__internal__.bestSurvive, - verbose: this.__internal__.verbose - } - EVLOLVE(options, progress => { - progress.totalTime = progress.time - this.__internal__.proportional_mutation_factor = progress.proportional_mutation_factor - }).then(resolve).catch(reject) - } catch (e) { reject(e) } - }) + evolveOneGeneration = () => { + const nrOfSurvivors = this.__internal__.survivorPercent ? Math.ceil(this.__internal__.survivors * this.__internal__.max_population) : this.__internal__.survivors + const options = { + currentGeneration: 0, + maxGenerations: 1, + totalTime: 0, + generations: this.__internal__.generations, + parameters: this.__internal__.parameters, + initialParams: this.__internal__.initialParams, + max_population: this.__internal__.max_population, + survivors: this.__internal__.survivors, + nrOfSurvivors: nrOfSurvivors, + survivorPercent: this.__internal__.survivorPercent, + fitnessTargetValue: this.__internal__.fitnessTargetValue, + fitnessTargetTolerance: this.__internal__.fitnessTargetTolerance, + fitnessTimeout: this.__internal__.fitnessTimeout, + stopFitness: this.__internal__.stopFitness, + crossoverChance: this.__internal__.crossoverChance, + mutation: this.__internal__.mutation || { chance: 0.1, power: 0.1 }, + proportional_mutation_factor: this.__internal__.proportional_mutation_factor, + calculateFitness: this.__internal__.calculateFitness, + bestSurvive: this.__internal__.bestSurvive, + verbose: this.__internal__.verbose + } + return EVOLVE(options, progress => { + progress.totalTime = progress.time + this.__internal__.proportional_mutation_factor = progress.proportional_mutation_factor + }) + } /** @param {number} generation_count * @param {(x: any) => void} [progress_callback] */ - evolve = (generation_count, progress_callback) => new Promise((resolve, reject) => { - try { - progress_callback = progress_callback || ((x) => { }) - const nrOfSurvivors = this.__internal__.survivorPercent ? Math.ceil(this.__internal__.survivors * this.__internal__.max_population) : this.__internal__.survivors - const options = { - currentGeneration: 0, - maxGenerations: generation_count, - totalTime: 0, - generations: this.__internal__.generations, - parameters: this.__internal__.parameters, - initialParams: this.__internal__.initialParams, - max_population: this.__internal__.max_population, - survivors: this.__internal__.survivors, - nrOfSurvivors: nrOfSurvivors, - survivorPercent: this.__internal__.survivorPercent, - fitnessTargetValue: this.__internal__.fitnessTargetValue, - fitnessTargetTolerance: this.__internal__.fitnessTargetTolerance, - fitnessTimeout: this.__internal__.fitnessTimeout, - stopFitness: this.__internal__.stopFitness, - crossoverChance: this.__internal__.crossoverChance, - mutation: this.__internal__.mutation || { chance: 0.1, power: 0.1 }, - proportional_mutation_factor: this.__internal__.proportional_mutation_factor, - calculateFitness: this.__internal__.calculateFitness, - bestSurvive: this.__internal__.bestSurvive, - sequence: this.__internal__.sequence, - verbose: this.__internal__.verbose, - } - EVLOLVE(options, progress => { - progress.message = `Gen: ${progress.generation.toString().padStart(3, ' ')} Pop: ${progress.population} PMF: ${progress.proportional_mutation_factor.toFixed(3)} finished in ${progress.time.toString().padStart(4, ' ')} ms Best fitness: ${progress.fitness >= 0 ? ' ' + progress.fitness.toFixed(15) : progress.fitness.toFixed(15)} => ${JSON.stringify(progress.parameters).substring(0, 150)}` - progress_callback(progress) - }).then(result => { - result.message = `FINISHED in ${result.totalTime.toString().padStart(4, ' ')} ms Best fitness: ${result.fitness >= 0 ? ' ' + result.fitness.toFixed(15) : result.fitness.toFixed(15)} => ${JSON.stringify(result.parameters)}` - resolve(result) - }).catch(reject) - } catch (e) { reject(e) } - }) + evolve = async (generation_count, progress_callback) => { + const nrOfSurvivors = this.__internal__.survivorPercent ? Math.ceil(this.__internal__.survivors * this.__internal__.max_population) : this.__internal__.survivors + const options = { + currentGeneration: 0, + maxGenerations: generation_count, + totalTime: 0, + generations: this.__internal__.generations, + parameters: this.__internal__.parameters, + initialParams: this.__internal__.initialParams, + max_population: this.__internal__.max_population, + survivors: this.__internal__.survivors, + nrOfSurvivors: nrOfSurvivors, + survivorPercent: this.__internal__.survivorPercent, + fitnessTargetValue: this.__internal__.fitnessTargetValue, + fitnessTargetTolerance: this.__internal__.fitnessTargetTolerance, + fitnessTimeout: this.__internal__.fitnessTimeout, + stopFitness: this.__internal__.stopFitness, + crossoverChance: this.__internal__.crossoverChance, + mutation: this.__internal__.mutation || { chance: 0.1, power: 0.1 }, + proportional_mutation_factor: this.__internal__.proportional_mutation_factor, + calculateFitness: this.__internal__.calculateFitness, + bestSurvive: this.__internal__.bestSurvive, + sequence: this.__internal__.sequence, + verbose: this.__internal__.verbose, + } + const logProgress = progress_callback ? ((progress) => { + progress.message = `Gen: ${progress.generation.toString().padStart(3, ' ')} Pop: ${progress.population} PMF: ${progress.proportional_mutation_factor.toFixed(3)} finished in ${progress.time.toString().padStart(4, ' ')} ms Best fitness: ${progress.fitness >= 0 ? ' ' + progress.fitness.toFixed(15) : progress.fitness.toFixed(15)} => ${JSON.stringify(progress.parameters).substring(0, 150)}` + progress_callback(progress) + }) : undefined + const result = await EVOLVE(options, logProgress) + result.message = `FINISHED in ${result.totalTime.toString().padStart(4, ' ')} ms Best fitness: ${result.fitness >= 0 ? ' ' + result.fitness.toFixed(15) : result.fitness.toFixed(15)} => ${JSON.stringify(result.parameters)}` + return result + } } module.exports = Trainer \ No newline at end of file