UNPKG

win-nsga

Version:

Create and manage an evolutionary algorithm that runs the NSGA-II multiobjective search algorithm (bit redundant). This is a WIN module.

322 lines (256 loc) 10.3 kB
var Q = require('q'); module.exports = multiobjective; //information to rank each genome function RankInfo() { var self = this; //when iterating, we count how many genomes dominate other genomes self.dominationCount = 0; //who does this genome dominate self.dominates = []; //what is this genome's rank (i.e. what pareto front is it on) self.rank = 0; //has this genome been ranked self.ranked = false; //just a quick reset of all the variables self.reset = function(){ self.rank = 0; self.ranked = false; self.dominationCount = 0; self.dominates = []; }; //send me back, boss return self; }; //create sort population function function SortPopulation(pop, popEvals) { // console.log("First: ", pop); //sort genomes by fitness / age -- as genomes are often sorted //higher fitness, younger age wins pop.sort(function(x,y){ var xeval = popEvals[x.wid]; var yeval = popEvals[y.wid]; var fitnessDelta = yeval.fitness - xeval.fitness; if (fitnessDelta < 0.0) return -1; else if (fitnessDelta > 0.0) return 1; var ageDelta = xeval.age - yeval.age; // Convert result to an int. if (ageDelta < 0) return -1; else if (ageDelta > 0) return 1; return 0; }); // for(var i=0; i < pop.length; i++) // console.log("Tehn :".red, pop[i].wid, " eval: ".magenta, popEvals[pop[i].wid]); }; //class to assign multiobjective fitness to individuals (fitness based on what pareto front they are on) function multiobjective(activeEvals, backEmit, log) { var self = this; self.backEmit = backEmit; self.qEmit = backEmit.qCall; self.log = log; self.popSorted = false; self.activeIDs = {}; self.activePopulation = []; self.activeEvaluations = activeEvals; self.ranks = []; //if genome x dominates y, increment y's dominated count, add y to x's dominated list self.updateDomination = function(x, y, r1, r2) { if(self.dominates(x,y)) { r1.dominates.push(r2); r2.dominationCount++; // self.log("\t\nX Dominates".magenta, x.objectives, "\t\nY:".red, y.objectives); } // else // self.log("\t\nX Does Not Dominate".red, x.objectives, "\t\nY:".cyan, y.objectives); }; //function to check whether genome x dominates genome y, usually defined as being no worse on all //objectives, and better at at least one self.dominates = function(x, y) { var better=false; var objx = x.objectives, objy = y.objectives; var sz = objx.length; //if x is ever worse than y, it cannot dominate y //also check if x is better on at least one for(var i=0;i<sz-1;i++) { if(objx[i]<objy[i]) return false; if(objx[i]>objy[i]) better=true; } //genomic novelty check, disabled for now //threshold set to 0 -- Paul since genome is local var thresh=0.0; if((objx[sz-1]+thresh)<(objy[sz-1])) return false; if((objx[sz-1]>(objy[sz-1]+thresh))) better=true; return better; }; //distance function between two lists of objectives, used to see if two individuals are unique self.distance = function(x, y) { var delta=0.0; var len = x.length; for(var i=0;i<len;i++) { var d=x[i]-y[i]; delta+=d*d; } return delta; }; //add an existing population from hypersharpNEAT to the multiobjective population maintained in //this class, step taken before evaluating multiobjective population through the rank function self.addPopulation = function(genomes) { //add unique genomes to our population for(var i=0;i< genomes.length;i++) { var blacklist=false; var wid = genomes[i].wid; //no duplicates please if(self.activeIDs[wid]) blacklist = true; //add genome if it is unique //we might not need to make copies if(!blacklist) { //push directly into population, don't use copy -- should test if this is a good idea? self.activePopulation.push(genomes[i]); self.activeIDs[wid] = genomes[i]; } } //no longer sorted by fitness/age self.popSorted = false; }; self.sortPopulation = function() { if(!self.popSorted) { SortPopulation(self.activePopulation, self.activeEvaluations); self.popSorted = true; } } //when we merge a population or two together, often the population will overflow, and we need to cut //it down. to do so, we just remove the last x individuals, which will be in the less significant //pareto fronts self.truncatePopulation = function(size) { var toRemove = self.activePopulation.length - size; self.log("population size before: " + self.activePopulation.length, " Removing: ", toRemove); //remove the tail after sorting if(toRemove > 0) { //sorts population if necessary self.sortPopulation(); //now we have to remove everything that does us no good //this means at position size, remove toRemove length of items //this should clear the bottom of the list IN PLACE self.activePopulation.splice(size, toRemove); } //changes to population, make sure to update our lookup self.log("population size after: " + self.activePopulation.length); return self.activePopulation; }; self.rankGenomes = function() { var defer = Q.defer(); var population = self.activePopulation; var evaluations = self.activeEvaluations; //what's the pop size, how many are we ranking var size = population.length; self.qEmit("evaluate:measureObjectives", population, evaluations) .then(function(objectiveList) { if(!objectiveList || objectiveList.length != population.length) throw new Error("Measure Objectives must return an array equal to the size of the population measured."); //have to apply the objectives to our list of evaluations for(var i=0; i < objectiveList.length;i++) { //pull the wid of the object var iWID = population[i].wid; //set the objectives to be the objectives passed back evaluations[iWID].objectives = objectiveList[i]; // self.log("Rank Eval: ".cyan, iWID, " : ", self.activeEvaluations[iWID]); } //reset rank information for(var i=0;i<size;i++) { if(self.ranks.length<i+1) self.ranks.push(new RankInfo()); else self.ranks[i].reset(); } //calculate domination by testing each genome against every other genome for(var i=0;i<size;i++) { //pull the wid of the object var iWID = population[i].wid; // self.log("\n\nExamining: ".green, iWID); for(var j=0;j<size;j++) { var jWID = population[j].wid; //send in evaluations for both genomes -- which will then update the rankings //don't rank against yourself -- what a silly waste if(iWID != jWID) self.updateDomination(evaluations[iWID], evaluations[jWID],self.ranks[i],self.ranks[j]); } } //successively peel off non-dominated fronts (e.g. those genomes no longer dominated by any in //the remaining population) var front = []; var ranked_count=0; var current_rank=1; while(ranked_count < size) { //search for non-dominated front for(var i=0;i<size;i++) { //continue if already ranked if(self.ranks[i].ranked) continue; //if not dominated, add to front if(self.ranks[i].dominationCount==0) { front.push(i); self.ranks[i].ranked=true; self.ranks[i].rank = current_rank; } } var front_size = front.length; self.log("Front " + current_rank + ", size: " + front_size); //now take all the non-dominated individuals, see who they dominated, and decrease //those genomes' domination counts, because we are removing this front from consideration //to find the next front of individuals non-dominated by the remaining individuals in //the population for(var i=0;i<front_size;i++) { var r = self.ranks[front[i]]; for (var z=0; z < r.dominates.length; z++) { var dominated = r.dominates[z]; dominated.dominationCount--; } } ranked_count+=front_size; front = []; current_rank++; } //fitness = popsize-rank (better way might be maxranks+1-rank), but doesn't matter //because speciation is not used and tournament selection is employed for(var i=0;i<size;i++) { var pWID = population[i].wid; evaluations[pWID].fitness = (size+1)-self.ranks[i].rank; } //new fitness values -- everything is fair sort game now! self.popSorted = false; //sorting based on "fitness"/age -- fitness = rank self.sortPopulation(); // for(var i=0;i<size;i++) { // var pWID = population[i].wid; // self.log("Full evals with rank: ".red, evaluations[pWID]); // } //after sorting, there is nothing left to remark about defer.resolve(); }) .fail(function(err) { defer.reject(err); }); return defer.promise; }; //send it on back yo return self; }; multiobjective.SortPopulation = SortPopulation;