Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 162 additions & 62 deletions js/genetic-0.1.14.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<this.configuration.size;++i) {
for (var i=0;i<this.configuration.size;++i) {
this.entities.push(Clone(this.seed()));
}

for (i=0;i<this.configuration.iterations;++i) {
this.seriesAsync(this.configuration.iterations, function runGeneration(i, generationCallback){
// reset for each generation
this.internalGenState = {};

// score and sort
var pop = this.entities
.map(function (entity) {
return {"fitness": self.fitness(entity), "entity": entity };
})
.sort(function (a, b) {
return self.optimize(a.fitness, b.fitness) ? -1 : 1;
});
self.internalGenState = {};

// generation notification
var mean = pop.reduce(function (a, b) { return a + b.fitness; }, 0)/pop.length;
var stdev = Math.sqrt(pop
.map(function (a) { return (a.fitness - mean) * (a.fitness - mean); })
.reduce(function (a, b) { return a+b; }, 0)/pop.length);
// Asynchronous score
self.mapFitnessAsync(self.entities, function(scores){

var pop = self.entities
.map(function (entity, i) {
return {"fitness": scores[i], "entity": entity };
})
.sort(function (a, b) {
return self.optimize(a.fitness, b.fitness) ? -1 : 1;
});

var stats = {
"maximum": pop[0].fitness
, "minimum": pop[pop.length-1].fitness
, "mean": mean
, "stdev": stdev
};
// generation notification
var mean = pop.reduce(function (a, b) { return a + b.fitness; }, 0)/pop.length;
var stdev = Math.sqrt(pop
.map(function (a) { return (a.fitness - mean) * (a.fitness - mean); })
.reduce(function (a, b) { return a+b; }, 0)/pop.length);

var stats = {
"maximum": pop[0].fitness
, "minimum": pop[pop.length-1].fitness
, "mean": mean
, "stdev": stdev
};

var r = this.generation ? this.generation(pop, i, stats) : true;
var isFinished = (typeof r != "undefined" && !r) || (i == this.configuration.iterations-1);

if (
this.notification
&& (isFinished || this.configuration["skip"] == 0 || i%this.configuration["skip"] == 0)
) {
this.sendNotification(pop.slice(0, this.maxResults), i, stats, isFinished);
}
var r = self.generation ? self.generation(pop, i, stats) : true;
var isFinished = (typeof r != "undefined" && !r) || (i == self.configuration.iterations-1);

if (isFinished)
break;

// crossover and mutate
var newPop = [];

if (this.configuration.fittestAlwaysSurvives) // lets the best solution fall through
newPop.push(pop[0].entity);

while (newPop.length < self.configuration.size) {
if (
this.crossover // if there is a crossover function
&& Math.random() <= this.configuration.crossover // base crossover on specified probability
&& newPop.length+1 < self.configuration.size // keeps us from going 1 over the max population size
self.notification
&& (isFinished || self.configuration["skip"] == 0 || i%self.configuration["skip"] == 0)
) {
var parents = this.select2(pop);
var children = this.crossover(Clone(parents[0]), Clone(parents[1])).map(mutateOrNot);
newPop.push(children[0], children[1]);
} else {
newPop.push(mutateOrNot(self.select1(pop)));
self.sendNotification(pop.slice(0, self.maxResults), i, stats, isFinished);
}
}

this.entities = newPop;
}
}

if (isFinished)
return;

// crossover and mutate
var newPop = [];

if (self.configuration.fittestAlwaysSurvives) // lets the best solution fall through
newPop.push(pop[0].entity);

while (newPop.length < self.configuration.size) {
if (
self.crossover // if there is a crossover function
&& Math.random() <= self.configuration.crossover // base crossover on specified probability
&& newPop.length+1 < self.configuration.size // keeps us from going 1 over the max population size
) {
var parents = self.select2(pop);
var children = self.crossover(Clone(parents[0]), Clone(parents[1])).map(mutateOrNot);
newPop.push(children[0], children[1]);
} else {
newPop.push(mutateOrNot(self.select1(pop)));
}
}

self.entities = newPop;

generationCallback();
});

});
};

this.callerFunctionsCallbacks = {};
this.callbackId = 0;
// In the context of a web work a callerFunction is wrapped in a postMessage/onmessage exchange
this.wrapCallerFunction = function(funcName) {
if (this.usingWebWorker) {
// do not use 'self' keyword, here. Weird conflict when in non worker mode
var _this = this;
return function(){
var args = Array.prototype.slice.call(arguments);
// This id will be used to create request/reply mechanism
_this.callerFunctionsCallbacks[_this.callbackId] = args.pop();
postMessage({
callerFunction: funcName,
id: _this.callbackId,
arguments: Serialization.stringify(args)
});
_this.callbackId += 1;
};
} else {
// self declared outside of scope
return self.callerFunctions[funcName];
}
};

this.onmessage = function(e) {
if (e.data && this.callerFunctionsCallbacks[e.data.id]) {
this.callerFunctionsCallbacks[e.data.id].apply(this, Serialization.parse(e.data.arguments));
delete this.callerFunctionsCallbacks[e.data.id];
} else {
this.start();
}
};

this.sendNotification = function(pop, generation, stats, isFinished) {
var response = {
"pop": pop.map(Serialization.stringify)
Expand All @@ -209,7 +293,7 @@ var Genetic = Genetic || (function(){
};
}

Genetic.prototype.evolve = function(config, userData) {
Genetic.prototype.evolve = function(config, userData, callerFunctions) {

var k;
for (k in config) {
Expand All @@ -219,6 +303,10 @@ var Genetic = Genetic || (function(){
for (k in userData) {
this.userData[k] = userData[k];
}

for (k in callerFunctions) {
this.callerFunctions[k] = callerFunctions[k];
}

// determine if we can use webworkers
this.usingWebWorker = this.configuration.webWorkers
Expand All @@ -243,7 +331,7 @@ var Genetic = Genetic || (function(){

// materialize our ga instance in the worker
blobScript += "var genetic = Serialization.parse(\"" + addslashes(Serialization.stringify(this)) + "\");\n";
blobScript += "onmessage = function(e) { genetic.start(); }\n";
blobScript += "onmessage = function(e) { genetic.onmessage(e); }\n";

var self = this;

Expand All @@ -252,19 +340,31 @@ var Genetic = Genetic || (function(){
var blob = new Blob([blobScript]);
var worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = function(e) {
var response = e.data;
self.notification(response.pop.map(Serialization.parse), response.generation, response.stats, response.isFinished);
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(){
var args = Array.prototype.slice.call(arguments);
worker.postMessage({
callerFunctionCallback: data.callerFunction,
id: data.id,
arguments: Serialization.stringify(args)
});
}));
} else {
self.notification(data.pop.map(Serialization.parse), data.generation, data.stats, data.isFinished);
}
};
worker.onerror = function(e) {
alert('ERROR: Line ' + e.lineno + ' in ' + e.filename + ': ' + e.message);
};
worker.postMessage("");
worker.postMessage("start");
} else {
// simulate webworker
(function(){
var onmessage;
eval(blobScript);
onmessage(null);
onmessage("start");
})();
}
}
Expand Down
Loading