From d9505e27de5aaea5e7bcb680bc3c9d5f64751fd4 Mon Sep 17 00:00:00 2001 From: Chad Kirby Date: Thu, 6 Apr 2023 10:14:05 -0700 Subject: [PATCH] Add custom randomFunction option to geneticAlgorithmConstructor This commit introduces a new randomFunction option to the geneticAlgorithmConstructor to (among other things) support the use of seeded random number generators (RNG) for generating consistent results across different runs. Deterministic outputs make testing easier, and improves the user experience. Changes: Added the randomFunction option to the constructor with a default value of Math.random(). Replaced all occurrences of Math.random() with settings.randomFunction in the index.js file. Updated the test file to include a new test case for verifying the functionality of the randomFunction option. --- index.js | 12 ++++++++---- test/basic.js | 29 ++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 76dde6e..1b69be8 100644 --- a/index.js +++ b/index.js @@ -10,6 +10,8 @@ module.exports = function geneticAlgorithmConstructor(options) { doesABeatBFunction : undefined, + randomFunction : function() { return Math.random() }, + population : [], populationSize : 100, }} @@ -23,6 +25,8 @@ module.exports = function geneticAlgorithmConstructor(options) { settings.doesABeatBFunction = settings.doesABeatBFunction || defaults.doesABeatBFunction + settings.randomFunction = settings.randomFunction || defaults.randomFunction + settings.population = settings.population || defaults.population if ( settings.population.length <= 0 ) throw Error("population must be an array and contain at least 1 phenotypes") @@ -39,7 +43,7 @@ module.exports = function geneticAlgorithmConstructor(options) { while( settings.population.length < settings.populationSize ) { settings.population.push( mutate( - cloneJSON( settings.population[ Math.floor( Math.random() * size ) ] ) + cloneJSON( settings.population[ Math.floor( settings.randomFunction() * size ) ] ) ) ) } @@ -55,7 +59,7 @@ module.exports = function geneticAlgorithmConstructor(options) { function crossover(phenotype) { phenotype = cloneJSON(phenotype) - var mate = settings.population[ Math.floor(Math.random() * settings.population.length ) ] + var mate = settings.population[ Math.floor(settings.randomFunction() * settings.population.length ) ] mate = cloneJSON(mate) return settings.crossoverFunction(phenotype,mate)[0] } @@ -78,7 +82,7 @@ module.exports = function geneticAlgorithmConstructor(options) { nextGeneration.push(phenotype) if ( doesABeatB( phenotype , competitor )) { - if ( Math.random() < 0.5 ) { + if ( settings.randomFunction() < 0.5 ) { nextGeneration.push(mutate(phenotype)) } else { nextGeneration.push(crossover(phenotype)) @@ -96,7 +100,7 @@ module.exports = function geneticAlgorithmConstructor(options) { function randomizePopulationOrder( ) { for( var index = 0 ; index < settings.population.length ; index++ ) { - var otherIndex = Math.floor( Math.random() * settings.population.length ) + var otherIndex = Math.floor( settings.randomFunction() * settings.population.length ) var temp = settings.population[otherIndex] settings.population[otherIndex] = settings.population[index] settings.population[index] = temp diff --git a/test/basic.js b/test/basic.js index 4cf11f6..12959e9 100644 --- a/test/basic.js +++ b/test/basic.js @@ -98,6 +98,33 @@ module.exports = { assert.equal( true , ga.bestScore() > 1 , "Error : untrue : " + ga.bestScore() + " > 1"); - } + }, + 'custom randomFunction works': function (beforeExit, assert) { + let customRandomCalled = false; + const customRandomFunction = () => { + customRandomCalled = true; + return Math.random(); + }; + + var config = { + mutationFunction: function (phenotype) { + return phenotype; + }, + crossoverFunction: function (a, b) { + return [a, b]; + }, + fitnessFunction: function (phenotype) { + return 0; + }, + population: [{ name: "alice" }], + randomFunction: customRandomFunction, + }; + + var geneticalgorithm = geneticAlgorithmConstructor(config); + + geneticalgorithm.evolve(); + assert.equal(true, customRandomCalled, "customRandomFunction was not called"); + }, + }