From 9cfd383012726a71d081f0791126d7a747a86076 Mon Sep 17 00:00:00 2001 From: Alban Mouton through Travis-CI Date: Thu, 13 Aug 2015 23:26:02 +0200 Subject: [PATCH 1/6] work in progress for async fitness --- lib/genetic.js | 158 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 104 insertions(+), 54 deletions(-) diff --git a/lib/genetic.js b/lib/genetic.js index 7564024..46f6444 100644 --- a/lib/genetic.js +++ b/lib/genetic.js @@ -107,6 +107,51 @@ var Genetic = Genetic || (function(){ this.usingWebWorker = false; + this.mapFitnessAsync = function(entities, callback) { + var self = this; + + var scores = entities.map(function() { + return null; + }); + var nbScores = 0; + + entities.forEach(function(entity, i) { + + var setScore = function(score) { + scores[i] = score; + nbScores += 1; + if (nbScores === entities.length) { + callback(scores); + } + }; + + setTimeout(function() { + // Supports fitness function sync or async based on number of expected arguments + if (self.fitness.length === 1) { + setScore(self.fitness(entity)); + } else { + self.fitness(entity, setScore); + } + }, 0); + }); + }; + + this.seriesAsync = function(nb, func) { + var self = this; + var results = []; + + var recursion = function(i) { + setTimeout(function() { + if(i < nb) { + func.call(self, function recursionCallback() { + recursion(i + 1); + }); + } + }, 0); + }; + + recursion(0); + }; this.start = function() { var i; @@ -122,68 +167,73 @@ var Genetic = Genetic || (function(){ this.entities.push(Clone(this.seed())); } - for (i=0;i Date: Fri, 14 Aug 2015 13:32:37 +0200 Subject: [PATCH 2/6] fix async, do not break compatibility --- lib/genetic.js | 33 ++--- lib/genetic.js~ | 332 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 346 insertions(+), 19 deletions(-) create mode 100644 lib/genetic.js~ diff --git a/lib/genetic.js b/lib/genetic.js index 46f6444..3b08135 100644 --- a/lib/genetic.js +++ b/lib/genetic.js @@ -110,9 +110,7 @@ var Genetic = Genetic || (function(){ this.mapFitnessAsync = function(entities, callback) { var self = this; - var scores = entities.map(function() { - return null; - }); + var scores = []; var nbScores = 0; entities.forEach(function(entity, i) { @@ -125,14 +123,12 @@ var Genetic = Genetic || (function(){ } }; - setTimeout(function() { - // Supports fitness function sync or async based on number of expected arguments - if (self.fitness.length === 1) { - setScore(self.fitness(entity)); - } else { - self.fitness(entity, setScore); - } - }, 0); + // Supports fitness function sync or async based on number of expected arguments + if (self.fitness.length === 1) { + setScore(self.fitness(entity)); + } else { + self.fitness(entity, setScore); + } }); }; @@ -141,17 +137,16 @@ var Genetic = Genetic || (function(){ var results = []; var recursion = function(i) { - setTimeout(function() { - if(i < nb) { - func.call(self, function recursionCallback() { - recursion(i + 1); - }); - } - }, 0); + if(i < nb) { + func.call(self, i, function recursionCallback() { + recursion(i + 1); + }); + } }; recursion(0); }; + this.start = function() { var i; @@ -167,7 +162,7 @@ var Genetic = Genetic || (function(){ this.entities.push(Clone(this.seed())); } - this.seriesAsync(this.configuration.iterations, function runGeneration(generationCallback){ + this.seriesAsync(this.configuration.iterations, function runGeneration(i, generationCallback){ // reset for each generation self.internalGenState = {}; diff --git a/lib/genetic.js~ b/lib/genetic.js~ new file mode 100644 index 0000000..46f6444 --- /dev/null +++ b/lib/genetic.js~ @@ -0,0 +1,332 @@ + +var Genetic = Genetic || (function(){ + + 'use strict'; + + // facilitates communcation between web workers + var Serialization = { + "stringify": function (obj) { + return JSON.stringify(obj, function (key, value) { + if (value instanceof Function || typeof value == "function") return "__func__:" + value.toString(); + if (value instanceof RegExp) return "__regex__:" + value; + return value; + }); + }, "parse": function (str) { + return JSON.parse(str, function (key, value) { + if (typeof value != "string") return value; + if (value.lastIndexOf("__func__:", 0) === 0) return eval('(' + value.slice(9) + ')'); + if (value.lastIndexOf("__regex__:", 0) === 0) return eval('(' + value.slice(10) + ')'); + return value; + }); + } + }; + + var Clone = function(obj) { + if (obj == null || typeof obj != "object") + return obj; + + return JSON.parse(JSON.stringify(obj)); + }; + + var Optimize = { + "Maximize": function (a, b) { return a >= b; } + , "Minimize": function (a, b) { return a < b; } + }; + + var Select1 = { + "Tournament2": function(pop) { + var n = pop.length; + var a = pop[Math.floor(Math.random()*n)]; + var b = pop[Math.floor(Math.random()*n)]; + return this.optimize(a.fitness, b.fitness) ? a.entity : b.entity; + }, "Tournament3": function(pop) { + var n = pop.length; + var a = pop[Math.floor(Math.random()*n)]; + var b = pop[Math.floor(Math.random()*n)]; + var c = pop[Math.floor(Math.random()*n)]; + var best = this.optimize(a.fitness, b.fitness) ? a : b; + best = this.optimize(best.fitness, c.fitness) ? best : c; + return best.entity; + }, "Fittest": function (pop) { + return pop[0].entity; + }, "Random": function (pop) { + return pop[Math.floor(Math.random()*pop.length)].entity; + }, "RandomLinearRank": function (pop) { + this.internalGenState["rlr"] = this.internalGenState["rlr"]||0; + return pop[Math.floor(Math.random()*Math.min(pop.length,(this.internalGenState["rlr"]++)))].entity; + }, "Sequential": function (pop) { + this.internalGenState["seq"] = this.internalGenState["seq"]||0; + return pop[(this.internalGenState["seq"]++)%pop.length].entity; + } + }; + + var Select2 = { + "Tournament2": function(pop) { + return [Select1.Tournament2.call(this, pop), Select1.Tournament2.call(this, pop)]; + }, "Tournament3": function(pop) { + return [Select1.Tournament3.call(this, pop), Select1.Tournament3.call(this, pop)]; + }, "Random": function (pop) { + return [Select1.Random.call(this, pop), Select1.Random.call(this, pop)]; + }, "RandomLinearRank": function (pop) { + return [Select1.RandomLinearRank.call(this, pop), Select1.RandomLinearRank.call(this, pop)]; + }, "Sequential": function (pop) { + return [Select1.Sequential.call(this, pop), Select1.Sequential.call(this, pop)]; + }, "FittestRandom": function (pop) { + return [Select1.Fittest.call(this, pop), Select1.Random.call(this, pop)]; + } + }; + + function Genetic() { + + // population + this.fitness = null; + this.seed = null; + this.mutate = null; + this.crossover = null; + this.select1 = null; + this.select2 = null; + this.optimize = null; + this.generation = null; + this.notification = null; + + this.configuration = { + "size": 250 + , "crossover": 0.9 + , "mutation": 0.2 + , "iterations": 100 + , "fittestAlwaysSurvives": true + , "maxResults": 100 + , "webWorkers": true + , "skip": 0 + }; + + this.userData = {}; + this.internalGenState = {}; + + this.entities = []; + + this.usingWebWorker = false; + + this.mapFitnessAsync = function(entities, callback) { + var self = this; + + var scores = entities.map(function() { + return null; + }); + var nbScores = 0; + + entities.forEach(function(entity, i) { + + var setScore = function(score) { + scores[i] = score; + nbScores += 1; + if (nbScores === entities.length) { + callback(scores); + } + }; + + setTimeout(function() { + // Supports fitness function sync or async based on number of expected arguments + if (self.fitness.length === 1) { + setScore(self.fitness(entity)); + } else { + self.fitness(entity, setScore); + } + }, 0); + }); + }; + + this.seriesAsync = function(nb, func) { + var self = this; + var results = []; + + var recursion = function(i) { + setTimeout(function() { + if(i < nb) { + func.call(self, function recursionCallback() { + recursion(i + 1); + }); + } + }, 0); + }; + + recursion(0); + }; + this.start = function() { + + var i; + var self = this; + + function mutateOrNot(entity) { + // applies mutation based on mutation probability + return Math.random() <= self.configuration.mutation && self.mutate ? self.mutate(Clone(entity)) : entity; + } + + // seed the population + for (i=0;i Date: Sat, 15 Aug 2015 22:33:10 +0200 Subject: [PATCH 3/6] add support for calling functions from caller context in async fitness --- js/genetic-0.1.14.js | 224 ++++++++++++++++++++++++++++----------- js/genetic-0.1.14.min.js | 2 +- lib/genetic.js | 79 +++++++++++--- test/async.js | 92 ++++++++++++++++ 4 files changed, 322 insertions(+), 75 deletions(-) create mode 100644 test/async.js diff --git a/js/genetic-0.1.14.js b/js/genetic-0.1.14.js index b40a230..3c48e52 100644 --- a/js/genetic-0.1.14.js +++ b/js/genetic-0.1.14.js @@ -107,89 +107,173 @@ var Genetic = Genetic || (function(){ this.userData = {}; this.internalGenState = {}; + this.callerFunctions = {}; this.entities = []; this.usingWebWorker = false; + this.mapFitnessAsync = function(entities, callback) { + var _this = this; + + var scores = []; + var nbScores = 0; + + entities.forEach(function(entity, i) { + + var setScore = function(score) { + scores[i] = score; + nbScores += 1; + if (nbScores === entities.length) { + callback(scores); + } + }; + + // Supports fitness function sync or async based on number of expected arguments + if (_this.fitness.length === 1) { + setScore(_this.fitness(entity)); + } else { + _this.fitness(entity, setScore); + } + }); + }; + + this.seriesAsync = function(nb, func) { + var self = this; + var results = []; + + var recursion = function(i) { + if(i < nb) { + func.call(self, i, function recursionCallback() { + recursion(i + 1); + }); + } + }; + + recursion(0); + }; + this.start = function() { - var i; var self = this; + // prepare wrapping around caller functions + for(var k in this.callerFunctions) { + this.callerFunctions[k] = this.wrapCallerFunction(k); + } + function mutateOrNot(entity) { // applies mutation based on mutation probability return Math.random() <= self.configuration.mutation && self.mutate ? self.mutate(Clone(entity)) : entity; } // seed the population - for (i=0;i=b},Minimize:function(a,b){return a=b},Minimize:function(a,b){return a Date: Sun, 16 Aug 2015 21:09:58 +0200 Subject: [PATCH 4/6] add html launcher for async tests --- test/index.html | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 test/index.html diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..a8198de --- /dev/null +++ b/test/index.html @@ -0,0 +1,25 @@ + + + + Mocha Tests + + + +
+ + + + + + + + + + + + + + \ No newline at end of file From a99ca0bb9daf890881dfd017e161097587e39c34 Mon Sep 17 00:00:00 2001 From: Alban Mouton through Travis-CI Date: Tue, 18 Aug 2015 20:57:37 +0200 Subject: [PATCH 5/6] do not use Serialization for caler functions. Not necessary and costly for performance --- lib/genetic.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/genetic.js b/lib/genetic.js index 788409a..019a85c 100644 --- a/lib/genetic.js +++ b/lib/genetic.js @@ -250,7 +250,7 @@ var Genetic = Genetic || (function(){ postMessage({ callerFunction: funcName, id: _this.callbackId, - arguments: Serialization.stringify(args) + arguments: args }); _this.callbackId += 1; }; @@ -262,7 +262,7 @@ var Genetic = Genetic || (function(){ this.onmessage = function(e) { if (e.data && this.callerFunctionsCallbacks[e.data.id]) { - this.callerFunctionsCallbacks[e.data.id].apply(this, Serialization.parse(e.data.arguments)); + this.callerFunctionsCallbacks[e.data.id].apply(this, e.data.arguments); delete this.callerFunctionsCallbacks[e.data.id]; } else { this.start(); @@ -332,18 +332,19 @@ var Genetic = Genetic || (function(){ if (this.usingWebWorker) { // webworker + var blob = new Blob([blobScript]); var worker = new Worker(window.URL.createObjectURL(blob)); worker.onmessage = function(e) { var data = e.data; if (data.callerFunction) { // receive a request from the work to run a function in caller context, do it then respond by postMessage - self.callerFunctions[data.callerFunction].apply(self, Serialization.parse(data.arguments).concat(function(){ + self.callerFunctions[data.callerFunction].apply(self, data.arguments.concat(function(){ var args = Array.prototype.slice.call(arguments); worker.postMessage({ callerFunctionCallback: data.callerFunction, id: data.id, - arguments: Serialization.stringify(args) + arguments: args }); })); } else { From 234678d75fcafa3762bff8b6efec7a57e9cb2a88 Mon Sep 17 00:00:00 2001 From: Alban Mouton through Travis-CI Date: Mon, 31 Aug 2015 22:01:36 +0200 Subject: [PATCH 6/6] remove temp file --- lib/genetic.js~ | 332 ------------------------------------------------ 1 file changed, 332 deletions(-) delete mode 100644 lib/genetic.js~ diff --git a/lib/genetic.js~ b/lib/genetic.js~ deleted file mode 100644 index 46f6444..0000000 --- a/lib/genetic.js~ +++ /dev/null @@ -1,332 +0,0 @@ - -var Genetic = Genetic || (function(){ - - 'use strict'; - - // facilitates communcation between web workers - var Serialization = { - "stringify": function (obj) { - return JSON.stringify(obj, function (key, value) { - if (value instanceof Function || typeof value == "function") return "__func__:" + value.toString(); - if (value instanceof RegExp) return "__regex__:" + value; - return value; - }); - }, "parse": function (str) { - return JSON.parse(str, function (key, value) { - if (typeof value != "string") return value; - if (value.lastIndexOf("__func__:", 0) === 0) return eval('(' + value.slice(9) + ')'); - if (value.lastIndexOf("__regex__:", 0) === 0) return eval('(' + value.slice(10) + ')'); - return value; - }); - } - }; - - var Clone = function(obj) { - if (obj == null || typeof obj != "object") - return obj; - - return JSON.parse(JSON.stringify(obj)); - }; - - var Optimize = { - "Maximize": function (a, b) { return a >= b; } - , "Minimize": function (a, b) { return a < b; } - }; - - var Select1 = { - "Tournament2": function(pop) { - var n = pop.length; - var a = pop[Math.floor(Math.random()*n)]; - var b = pop[Math.floor(Math.random()*n)]; - return this.optimize(a.fitness, b.fitness) ? a.entity : b.entity; - }, "Tournament3": function(pop) { - var n = pop.length; - var a = pop[Math.floor(Math.random()*n)]; - var b = pop[Math.floor(Math.random()*n)]; - var c = pop[Math.floor(Math.random()*n)]; - var best = this.optimize(a.fitness, b.fitness) ? a : b; - best = this.optimize(best.fitness, c.fitness) ? best : c; - return best.entity; - }, "Fittest": function (pop) { - return pop[0].entity; - }, "Random": function (pop) { - return pop[Math.floor(Math.random()*pop.length)].entity; - }, "RandomLinearRank": function (pop) { - this.internalGenState["rlr"] = this.internalGenState["rlr"]||0; - return pop[Math.floor(Math.random()*Math.min(pop.length,(this.internalGenState["rlr"]++)))].entity; - }, "Sequential": function (pop) { - this.internalGenState["seq"] = this.internalGenState["seq"]||0; - return pop[(this.internalGenState["seq"]++)%pop.length].entity; - } - }; - - var Select2 = { - "Tournament2": function(pop) { - return [Select1.Tournament2.call(this, pop), Select1.Tournament2.call(this, pop)]; - }, "Tournament3": function(pop) { - return [Select1.Tournament3.call(this, pop), Select1.Tournament3.call(this, pop)]; - }, "Random": function (pop) { - return [Select1.Random.call(this, pop), Select1.Random.call(this, pop)]; - }, "RandomLinearRank": function (pop) { - return [Select1.RandomLinearRank.call(this, pop), Select1.RandomLinearRank.call(this, pop)]; - }, "Sequential": function (pop) { - return [Select1.Sequential.call(this, pop), Select1.Sequential.call(this, pop)]; - }, "FittestRandom": function (pop) { - return [Select1.Fittest.call(this, pop), Select1.Random.call(this, pop)]; - } - }; - - function Genetic() { - - // population - this.fitness = null; - this.seed = null; - this.mutate = null; - this.crossover = null; - this.select1 = null; - this.select2 = null; - this.optimize = null; - this.generation = null; - this.notification = null; - - this.configuration = { - "size": 250 - , "crossover": 0.9 - , "mutation": 0.2 - , "iterations": 100 - , "fittestAlwaysSurvives": true - , "maxResults": 100 - , "webWorkers": true - , "skip": 0 - }; - - this.userData = {}; - this.internalGenState = {}; - - this.entities = []; - - this.usingWebWorker = false; - - this.mapFitnessAsync = function(entities, callback) { - var self = this; - - var scores = entities.map(function() { - return null; - }); - var nbScores = 0; - - entities.forEach(function(entity, i) { - - var setScore = function(score) { - scores[i] = score; - nbScores += 1; - if (nbScores === entities.length) { - callback(scores); - } - }; - - setTimeout(function() { - // Supports fitness function sync or async based on number of expected arguments - if (self.fitness.length === 1) { - setScore(self.fitness(entity)); - } else { - self.fitness(entity, setScore); - } - }, 0); - }); - }; - - this.seriesAsync = function(nb, func) { - var self = this; - var results = []; - - var recursion = function(i) { - setTimeout(function() { - if(i < nb) { - func.call(self, function recursionCallback() { - recursion(i + 1); - }); - } - }, 0); - }; - - recursion(0); - }; - this.start = function() { - - var i; - var self = this; - - function mutateOrNot(entity) { - // applies mutation based on mutation probability - return Math.random() <= self.configuration.mutation && self.mutate ? self.mutate(Clone(entity)) : entity; - } - - // seed the population - for (i=0;i