├── .gitignore ├── .idea ├── .name ├── encodings.xml ├── jsLibraryMappings.xml ├── misc.xml ├── modules.xml ├── neatjs.iml ├── scopes │ └── scope_settings.xml └── vcs.xml ├── .npmignore ├── Makefile ├── README.txt ├── component.json ├── evolution ├── iec.js ├── multiobjective.js └── novelty.js ├── genome ├── neatConnection.js ├── neatGenome.js └── neatNode.js ├── neat.js ├── neatHelp ├── neatDecoder.js ├── neatHelp.js └── neatParameters.js ├── package.json ├── test ├── connectionTest.js ├── exampleGenome.xml ├── genomeConversionTest.js ├── genomeTest.js ├── helpTest.js ├── htmlLoadTest.html ├── neatTest.js ├── nodeTest.js └── utilityTest.js ├── types └── nodeType.js └── utility └── genomeSharpToJS.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/workspace.xml 2 | node_modules 3 | obj 4 | bin 5 | npm-debug* 6 | packages 7 | [Oo]bj 8 | [Bb]in 9 | *.user 10 | *.suo 11 | *.[Cc]ache 12 | *.bak 13 | *.ncb 14 | *.log 15 | *.DS_Store 16 | [Tt]humbs.db 17 | _ReSharper.* 18 | *.resharper 19 | Ankh.NoLoad 20 | 21 | components 22 | build -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | neatjs -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/jsLibraryMappings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | http://www.w3.org/1999/xhtml 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/neatjs.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | docs/ 3 | examples/ 4 | support/ 5 | test/ 6 | cppnjs/ 7 | .DS_Store -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REPORTER = dot 2 | 3 | test: 4 | @NODE_ENV=test ./node_modules/.bin/mocha 5 | 6 | .PHONY: test 7 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | NeuroEvolution for Augmenting Topologies (NEAT) written in Javascript (for browser or nodejs) tested using Mocha. 2 | 3 | Library is currently in development, and is 95% well tested. Genomes are well functioning, and mutation/crossover are well defined. 4 | 5 | Left to be tested: evolution - IEC/NSGA-II/Novelty search with local competition. These will all be created in the near future. 6 | 7 | Another library - neat-ui will be created for using neatjs in a browser environment or a node environment with UI elements for visualization. 8 | 9 | NPM Usage- 10 | 11 | var neatjs = require('neatjs'); 12 | 13 | var neatGenome = neatjs.loadLibraryFile('neatjs', 'neatGenome'); 14 | 15 | //real documentation to come soon... 16 | 17 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "neatjs", 3 | "version": "0.3.1", 4 | "description": "NeuroEvolution of Augmenting Topologies (NEAT) implemented in Javascript (with tests done in Mocha for verification). Can be used as a node module or in a browser", 5 | "repo": "OptimusLime/neatjs", 6 | "keywords": [ 7 | "Neural Network", 8 | "NN", 9 | "Artificial Neural Networks", 10 | "ANN", 11 | "CPPN", 12 | "NEAT", 13 | "HyperNEAT" 14 | ], 15 | "dependencies": { 16 | "optimuslime/cppnjs": "0.2.x", 17 | "optimuslime/win-utils": "0.x.x" 18 | }, 19 | "development": {}, 20 | "license": "MIT", 21 | "main": "neat.js", 22 | "scripts": [ 23 | "neat.js", 24 | "evolution/iec.js", 25 | "evolution/multiobjective.js", 26 | "evolution/novelty.js", 27 | "genome/neatConnection.js", 28 | "genome/neatNode.js", 29 | "genome/neatGenome.js", 30 | "neatHelp/neatDecoder.js", 31 | "neatHelp/neatHelp.js", 32 | "neatHelp/neatParameters.js", 33 | "types/nodeType.js", 34 | "utility/genomeSharpToJS.js" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /evolution/iec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | 5 | var NeatGenome = require('../genome/neatGenome.js'); 6 | 7 | //pull in variables from cppnjs 8 | var cppnjs = require('cppnjs'); 9 | var utilities = cppnjs.utilities; 10 | 11 | /** 12 | * Expose `iec objects`. 13 | */ 14 | module.exports = GenericIEC; 15 | 16 | //seeds are required -- and are expected to be the correct neatGenome types 17 | function GenericIEC(np, seeds, iecOptions) 18 | { 19 | var self = this; 20 | 21 | self.options = iecOptions || {}; 22 | self.np = np; 23 | 24 | //we keep track of new nodes and connections for the session 25 | self.newNodes = {}; 26 | self.newConnections = {}; 27 | 28 | //we can send in a seed genome -- to create generic objects when necessary 29 | self.seeds = seeds; 30 | 31 | for(var s=0; s < seeds.length; s++) 32 | { 33 | var seed = seeds[s]; 34 | for(var c =0; c < seed.connections.length; c++) 35 | { 36 | var sConn = seed.connections[c]; 37 | var cid = '(' + sConn.sourceID + ',' + sConn.targetID + ')'; 38 | self.newConnections[cid] = sConn; 39 | } 40 | } 41 | 42 | self.cloneSeed = function(){ 43 | 44 | var seedIx = utilities.next(self.seeds.length); 45 | 46 | var seedCopy = NeatGenome.Copy(self.seeds[seedIx]); 47 | if(self.options.seedMutationCount) 48 | { 49 | for(var i=0; i < self.options.seedMutationCount; i++) 50 | seedCopy.mutate(self.newNodes, self.newConnections, self.np); 51 | } 52 | return seedCopy; 53 | }; 54 | 55 | self.markParentConnections = function(parents){ 56 | 57 | for(var s=0; s < parents.length; s++) 58 | { 59 | var parent = parents[s]; 60 | for(var c =0; c < parent.connections.length; c++) 61 | { 62 | var sConn = parent.connections[c]; 63 | var cid = '(' + sConn.sourceID + ',' + sConn.targetID + ')'; 64 | self.newConnections[cid] = sConn; 65 | } 66 | } 67 | 68 | }; 69 | 70 | 71 | //this function handles creating a genotype from sent in parents. 72 | //it's pretty simple -- however many parents you have, select a random number of them, and attempt to mate them 73 | self.createNextGenome = function(parents) 74 | { 75 | self.markParentConnections(parents); 76 | //IF we have 0 parents, we create a genome with the default configurations 77 | var ng; 78 | var initialMutationCount = self.options.initialMutationCount || 0, 79 | postXOMutationCount = self.options.postMutationCount || 0; 80 | 81 | var responsibleParents = []; 82 | 83 | switch(parents.length) 84 | { 85 | case 0: 86 | 87 | //parents are empty -- start from scratch! 88 | ng = self.cloneSeed(); 89 | 90 | for(var m=0; m < initialMutationCount; m++) 91 | ng.mutate(self.newNodes, self.newConnections, self.np); 92 | 93 | //no responsible parents 94 | 95 | break; 96 | case 1: 97 | 98 | //we have one parent 99 | //asexual reproduction 100 | ng = parents[0].createOffspringAsexual(self.newNodes, self.newConnections, self.np); 101 | 102 | //parent at index 0 responsible 103 | responsibleParents.push(0); 104 | 105 | for(var m=0; m < postXOMutationCount; m++) 106 | ng.mutate(self.newNodes, self.newConnections, self.np); 107 | 108 | break; 109 | default: 110 | //greater than 1 individual as a possible parent 111 | 112 | //at least 1 parent, and at most self.activeParents.count # of parents 113 | var parentCount = 1 + utilities.next(parents.length); 114 | 115 | if(parentCount == 1) 116 | { 117 | //select a single parent for offspring 118 | var rIx = utilities.next(parents.length); 119 | 120 | ng = parents[rIx].createOffspringAsexual(self.newNodes, self.newConnections, self.np); 121 | //1 responsible parent at index 0 122 | responsibleParents.push(rIx); 123 | break; 124 | } 125 | 126 | //we expect active parents to be small, so we grab parentCount number of parents from a small array of parents 127 | var parentIxs = utilities.RouletteWheel.selectXFromSmallObject(parentCount, parents); 128 | 129 | var p1 = parents[parentIxs[0]], p2; 130 | //if I have 3 parents, go in order composing the objects 131 | 132 | responsibleParents.push(parentIxs[0]); 133 | 134 | //p1 mates with p2 to create o1, o1 mates with p3, to make o2 -- p1,p2,p3 are all combined now inside of o2 135 | for(var i=1; i < parentIxs.length; i++) 136 | { 137 | p2 = parents[parentIxs[i]]; 138 | ng = p1.createOffspringSexual(p2, self.np); 139 | p1 = ng; 140 | responsibleParents.push(parentIxs[i]); 141 | } 142 | 143 | for(var m=0; m < postXOMutationCount; m++) 144 | ng.mutate(self.newNodes, self.newConnections, self.np); 145 | 146 | 147 | break; 148 | } 149 | 150 | //we have our genome, let's send it back 151 | 152 | //the reason we don't end it inisde the switch loop is that later, we might be interested in saving this genome from some other purpose 153 | return {offspring: ng, parents: responsibleParents}; 154 | }; 155 | 156 | }; 157 | 158 | -------------------------------------------------------------------------------- /evolution/multiobjective.js: -------------------------------------------------------------------------------- 1 | //here we have everything for NSGA-II mutliobjective search and neatjs 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var NeatGenome = require('../genome/neatGenome.js'); 7 | var Novelty = require('./novelty.js'); 8 | 9 | 10 | //pull in variables from cppnjs 11 | var cppnjs = require('cppnjs'); 12 | var utilities = cppnjs.utilities; 13 | 14 | 15 | /** 16 | * Expose `MultiobjectiveSearch`. 17 | */ 18 | 19 | module.exports = MultiobjectiveSearch; 20 | 21 | 22 | //information to rank each genome 23 | MultiobjectiveSearch.RankInfo = function() 24 | { 25 | var self = this; 26 | //when iterating, we count how many genomes dominate other genomes 27 | self.dominationCount = 0; 28 | //who does this genome dominate 29 | self.dominates = []; 30 | //what is this genome's rank (i.e. what pareto front is it on) 31 | self.rank = 0; 32 | //has this genome been ranked 33 | self.ranked = false; 34 | }; 35 | MultiobjectiveSearch.RankInfo.prototype.reset = function(){ 36 | 37 | var self = this; 38 | self.rank = 0; 39 | self.ranked = false; 40 | self.dominationCount = 0; 41 | self.dominates = []; 42 | }; 43 | 44 | MultiobjectiveSearch.Help = {}; 45 | 46 | MultiobjectiveSearch.Help.SortPopulation = function(pop) 47 | { 48 | //sort genomes by fitness / age -- as genomes are often sorted 49 | pop.sort(function(x,y){ 50 | 51 | var fitnessDelta = y.fitness - x.fitness; 52 | if (fitnessDelta < 0.0) 53 | return -1; 54 | else if (fitnessDelta > 0.0) 55 | return 1; 56 | 57 | var ageDelta = x.age - y.age; 58 | 59 | // Convert result to an int. 60 | if (ageDelta < 0) 61 | return -1; 62 | else if (ageDelta > 0) 63 | return 1; 64 | 65 | return 0; 66 | 67 | }); 68 | }; 69 | 70 | //class to assign multiobjective fitness to individuals (fitness based on what pareto front they are on) 71 | MultiobjectiveSearch.multiobjectiveUtilities = function(np) 72 | { 73 | 74 | var self = this; 75 | 76 | self.np = np; 77 | self.population = []; 78 | self.populationIDs = {}; 79 | self.ranks = []; 80 | self.nov = new Novelty(10.0); 81 | self.doNovelty = false; 82 | self.generation = 0; 83 | 84 | self.localCompetition = false; 85 | 86 | self.measureNovelty = function() 87 | { 88 | var count = self.population.length; 89 | 90 | self.nov.initialize(self.population); 91 | 92 | //reset locality and competition for each genome 93 | for(var i=0; i < count; i++) 94 | { 95 | var genome = self.population[i]; 96 | 97 | genome.locality=0.0; 98 | genome.competition=0.0; 99 | 100 | //we measure all objectives locally -- just to make it simpler 101 | for(var o=0; o < genome.objectives.length; o++) 102 | genome.localObjectivesCompetition[o] = 0.0; 103 | } 104 | 105 | var ng; 106 | var max = 0.0, min = 100000000000.0; 107 | 108 | for (var i = 0; i< count; i++) 109 | { 110 | ng = self.population[i]; 111 | var fit = self.nov.measureNovelty(ng); 112 | 113 | //reset our fitness value to be local, yeah boyee 114 | //the first objective is fitness which is replaced with local fitness -- how many did you beat around you 115 | // # won / total number of neighbors = % competitive 116 | ng.objectives[0] = ng.competition / ng.nearestNeighbors; 117 | ng.objectives[ng.objectives.length - 2] = fit + 0.01; 118 | 119 | //the last local measure is the genome novelty measure 120 | var localGenomeNovelty = ng.localObjectivesCompetition[ng.objectives.length-1]; 121 | 122 | //genomic novelty is measured locally as well 123 | console.log("Genomic Novelty: " + ng.objectives[ng.objectives.length - 1] + " After: " + localGenomeNovelty / ng.nearestNeighbors); 124 | 125 | //this makes genomic novelty into a local measure 126 | ng.objectives[ng.objectives.length - 1] = localGenomeNovelty / ng.nearestNeighbors; 127 | 128 | if(fit>max) max=fit; 129 | if(fitobjy[i]) better=true; 159 | } 160 | 161 | //genomic novelty check, disabled for now 162 | //threshold set to 0 -- Paul since genome is local 163 | var thresh=0.0; 164 | if((objx[sz-1]+thresh)<(objy[sz-1])) return false; 165 | if((objx[sz-1]>(objy[sz-1]+thresh))) better=true; 166 | 167 | return better; 168 | }; 169 | 170 | //distance function between two lists of objectives, used to see if two individuals are unique 171 | self.distance = function(x, y) { 172 | var delta=0.0; 173 | var len = x.length; 174 | for(var i=0;i max_conn) 212 | max_conn = xx.connections.length; 213 | 214 | //int ccount=xx.ConnectionGeneList.Count; 215 | for(var g2=0; g2 < self.population.length; g2++) { 216 | yy = self.population[g2]; 217 | if(g==g2) 218 | continue; 219 | 220 | //measure genomic compatability using neatparams 221 | var d = xx.compat(yy, np); 222 | //if(d 0) 378 | self.population.splice(size, toRemove); 379 | 380 | //changes to population, make sure to update our lookup 381 | self.populationIDs = NeatGenome.Help.CreateGIDLookup(self.population); 382 | 383 | console.log("population size after: " + self.population.length); 384 | 385 | return self.population; 386 | }; 387 | 388 | }; 389 | 390 | function MultiobjectiveSearch(seedGenomes, genomeEvaluationFunctions, neatParameters, searchParameters) 391 | { 392 | var self=this; 393 | 394 | //functions for evaluating genomes in a population 395 | self.genomeEvaluationFunctions = genomeEvaluationFunctions; 396 | 397 | self.generation = 0; 398 | self.np = neatParameters; 399 | self.searchParameters = searchParameters; 400 | 401 | //for now, we just set seed genomes as population 402 | //in reality, we should use seed genomes as seeds into population determined by search parameters 403 | //i.e. 5 seed genomes -> 50 population size 404 | //TODO: Turn seed genomes into full first population 405 | self.population = seedGenomes; 406 | 407 | //create genome lookup once we have population 408 | self.populationIDs = NeatGenome.Help.CreateGIDLookup(seedGenomes); 409 | 410 | 411 | //see end of multiobjective search declaration for initailization code 412 | self.multiobjective= new MultiobjectiveSearch.multiobjectiveUtilities(neatParameters); 413 | self.np.compatibilityThreshold = 100000000.0; //disable speciation w/ multiobjective 414 | 415 | self.initializePopulation = function() 416 | { 417 | // The GenomeFactories normally won't bother to ensure that like connections have the same ID 418 | // throughout the population (because it's not very easy to do in most cases). Therefore just 419 | // run this routine to search for like connections and ensure they have the same ID. 420 | // Note. This could also be done periodically as part of the search, remember though that like 421 | // connections occuring within a generation are already fixed - using a more efficient scheme. 422 | self.matchConnectionIDs(); 423 | 424 | // Evaluate the whole population. 425 | self.evaluatePopulation(); 426 | 427 | //TODO: Add in some concept of speciation for NSGA algorithm -- other than genomic novelty? 428 | //We don't do speciation for NSGA-II algorithm 429 | 430 | // Now we have fitness scores and no speciated population we can calculate fitness stats for the 431 | // population as a whole -- and save best genomes 432 | //recall that speciation is NOT part of NSGA-II 433 | self.updateFitnessStats(); 434 | 435 | }; 436 | 437 | self.matchConnectionIDs = function() 438 | { 439 | var connectionIdTable = {}; 440 | 441 | var genomeBound = self.population.length; 442 | for(var genomeIdx=0; genomeIdx self.bestFitness) 499 | { 500 | self.bestFitness = ng.realFitness; 501 | self.bestGenome = ng; 502 | } 503 | self.totalNeuronCount += ng.nodes.length; 504 | self.totalConnectionCount += ng.connections.length; 505 | self.totalFitness += ng.realFitness; 506 | } 507 | 508 | self.avgComplexity = (self.totalNeuronCount + self.totalConnectionCount)/self.population.length; 509 | self.meanFitness = self.totalFitness/self.population.length; 510 | 511 | }; 512 | 513 | self.tournamentSelect = function(genomes) 514 | { 515 | var bestFound= 0.0; 516 | var bestGenome=null; 517 | var bound = genomes.length; 518 | 519 | //grab the best of 4 by default, can be more attempts than that 520 | for(var i=0;i bestFound) { 523 | bestFound=next.fitness; 524 | bestGenome=next; 525 | } 526 | } 527 | 528 | return bestGenome; 529 | }; 530 | 531 | 532 | self.evaluatePopulation= function() 533 | { 534 | //for each genome, we need to check if we should evaluate the individual, and then evaluate the individual 535 | 536 | //default everyone is evaluated 537 | var shouldEvaluate = self.genomeEvaluationFunctions.shouldEvaluateGenome || function(){return true;}; 538 | var defaultFitness = self.genomeEvaluationFunctions.defaultFitness || 0.0001; 539 | 540 | if(!self.genomeEvaluationFunctions.evaluateGenome) 541 | throw new Error("No evaluation function defined, how are you supposed to run evolution?"); 542 | 543 | var evaluateGenome = self.genomeEvaluationFunctions.evaluateGenome; 544 | 545 | for(var i=0; i < self.population.length; i++) 546 | { 547 | var ng = self.population[i]; 548 | 549 | var fit = defaultFitness; 550 | 551 | if(shouldEvaluate(ng)) 552 | { 553 | fit = evaluateGenome(ng, self.np); 554 | } 555 | 556 | ng.fitness = fit; 557 | ng.realFitness = fit; 558 | } 559 | 560 | }; 561 | 562 | self.performOneGeneration = function() 563 | { 564 | //No speciation in multiobjective 565 | //therefore no species to check for removal 566 | 567 | //----- Stage 1. Create offspring / cull old genomes / add offspring to population. 568 | var regenerate = false; 569 | 570 | self.multiobjective.addPopulation(self.population); 571 | self.multiobjective.rankGenomes(); 572 | 573 | 574 | //cut the population down to the desired size 575 | self.multiobjective.truncatePopulation(self.population.length); 576 | //no speciation necessary 577 | 578 | //here we can decide if we want to save to WIN 579 | 580 | self.updateFitnessStats(); 581 | 582 | if(!regenerate) 583 | { 584 | self.createOffSpring(); 585 | 586 | //we need to trim population to the elite count, then replace 587 | //however, this doesn't affect the multiobjective population -- just the population held in search at the time 588 | MultiobjectiveSearch.Help.SortPopulation(self.population); 589 | var eliteCount = Math.floor(self.np.elitismProportion*self.population.length); 590 | 591 | //remove everything but the most elite! 592 | self.population.splice(eliteCount, self.population.length - eliteCount); 593 | 594 | // Add offspring to the population. 595 | var genomeBound = self.offspringList.length; 596 | for(var genomeIdx=0; genomeIdx 5) 89 | { 90 | self.archiveThreshold *= 1.3; 91 | } 92 | 93 | //for all of our additions to the archive, 94 | //check against others to see if entered into archive 95 | for(var i=0; i < length; i++) 96 | { 97 | if(self.measureAgainstArchive(self.pendingAddition[i], false)) 98 | self.archive.push(self.pendingAddition[i]); 99 | } 100 | 101 | //clear it all out 102 | self.pendingAddition = []; 103 | }; 104 | 105 | Novelty.prototype.measureAgainstArchive = function(neatgenome, addToPending) 106 | { 107 | var self = this; 108 | 109 | for(var genome in self.archive) 110 | { 111 | var dist = novelty.Behavior.distance(neatgenome.behavior, genome.behavior); 112 | 113 | if(dist > self.maxDistSeen) 114 | { 115 | self.maxDistSeen = dist; 116 | console.log('Most novel dist: ' + self.maxDistSeen); 117 | } 118 | 119 | if(dist < self.archiveThreshold) 120 | return false; 121 | 122 | } 123 | 124 | if(addToPending) 125 | { 126 | self.pendingAddition.push(neatgenome); 127 | } 128 | 129 | return true; 130 | }; 131 | 132 | //measure the novelty of an organism against the fixed population 133 | Novelty.prototype.measureNovelty = function(neatgenome) 134 | { 135 | var sum = 0.0; 136 | var self = this; 137 | 138 | if(!self.initialized) 139 | return Number.MIN_VALUE; 140 | 141 | var noveltyList = []; 142 | 143 | for(var genome in self.measureAgainst) 144 | { 145 | noveltyList.push( 146 | {distance: novelty.Behavior.distance(genome, neatgenome.behavior), 147 | genome: genome} 148 | ); 149 | } 150 | 151 | for(var genome in self.archive) 152 | { 153 | noveltyList.push( 154 | {distance: novelty.Behavior.distance(genome, neatgenome.behavior), 155 | genome: genome} 156 | ); 157 | } 158 | 159 | //see if we should add this genome to the archive 160 | self.measureAgainstArchive(neatgenome,true); 161 | 162 | noveltyList.sort(function(a,b){return b.distance - a.distance}); 163 | var nn = self.nearestNeighbors; 164 | if(noveltyList.length < self.nearestNeighbors) { 165 | nn=noveltyList.length; 166 | } 167 | 168 | neatgenome.nearestNeighbors = nn; 169 | 170 | //Paul - reset local competition and local genome novelty -- might have been incrementing over time 171 | //Not sure if that's the intention of the algorithm to keep around those scores to signify longer term success 172 | //this would have a biasing effect on individuals that have been around for longer 173 | // neatgenome.competition = 0; 174 | // neatgenome.localGenomeNovelty = 0; 175 | 176 | //TODO: Verify this is working - are local objectives set up, is this measuring properly? 177 | for (var x = 0; x < nn; x++) 178 | { 179 | sum += noveltyList[x].distance; 180 | 181 | if (neatgenome.realFitness > noveltyList[x].genome.realFitness) 182 | neatgenome.competition += 1; 183 | 184 | //compare all the objectives, and locally determine who you are beating 185 | for(var o =0; o < neatgenome.objectives.length; o++) 186 | { 187 | if(neatgenome.objectives[o] > noveltyList[x].genome.objectives[o]) 188 | neatgenome.localObjectivesCompetition[o] += 1; 189 | } 190 | 191 | noveltyList[x].genome.locality += 1; 192 | // sum+=10000.0; //was 100 193 | } 194 | //neatgenome.locality = 0; 195 | //for(int x=0;xnoveltyList[x].Second.RealFitness) 200 | // neatgenome.competition+=1; 201 | 202 | // noveltyList[x].Second.locality+=1; 203 | // //Paul: This might not be the correct meaning of locality, but I am hijacking it instead 204 | // //count how many genomes we are neighbored to 205 | // //then, if we take neatgenome.competition/neatgenome.locality - we get percentage of genomes that were beaten locally! 206 | // neatgenome.locality += 1; 207 | // // sum+=10000.0; //was 100 208 | //} 209 | return Math.max(sum, .0001); 210 | } 211 | 212 | //Todo REFINE... adding highest fitness might 213 | //not correspond with most novel? 214 | Novelty.prototype.add_most_novel = function(genomes) 215 | { 216 | var self = this; 217 | 218 | var max_novelty =0; 219 | var best= null; 220 | 221 | for(var i=0;i max_novelty) 224 | { 225 | best = genomes[i]; 226 | max_novelty = genomes[i].fitness; 227 | } 228 | } 229 | self.archive.push(best); 230 | }; 231 | 232 | 233 | Novelty.prototype.initialize = function(genomes) 234 | { 235 | var self = this; 236 | self.initialized = true; 237 | 238 | self.measureAgainst = []; 239 | 240 | if(genomes !=null){ 241 | for(var i=0;i mostNovel) 308 | { 309 | mostNovel = closest[x]; 310 | mostNovelIndex = x; 311 | } 312 | } 313 | 314 | dirty[mostNovelIndex] = true; 315 | newList.push(NeatGenome.Copy(list[mostNovelIndex],0)); 316 | last_added = mostNovelIndex; 317 | } 318 | 319 | self.measureAgainst = newList; 320 | }; 321 | 322 | Novelty.prototype.updatePopulationFitness = function(genomes) 323 | { 324 | var self = this; 325 | 326 | for (var i = 0; i < genomes.length; i++) 327 | { 328 | //we might not need to make copies 329 | self.measureAgainst[i].realFitness = genomes[i].realFitness; 330 | } 331 | }; 332 | -------------------------------------------------------------------------------- /genome/neatConnection.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | //none 6 | 7 | /** 8 | * Expose `NeatConnection`. 9 | */ 10 | 11 | module.exports = NeatConnection; 12 | 13 | /** 14 | * Initialize a new NeatConnection. 15 | * 16 | * @param {String} gid 17 | * @param {Number} weight 18 | * @param {Object} srcTgtObj 19 | * @api public 20 | */ 21 | 22 | function NeatConnection(gid, weight, srcTgtObj) { 23 | 24 | var self = this; 25 | //Connection can be inferred by the cantor pair in the gid, however, in other systems, we'll need a source and target ID 26 | 27 | //gid must be a string 28 | self.gid = typeof gid === "number" ? "" + gid : gid;//(typeof gid === 'string' ? parseFloat(gid) : gid); 29 | self.weight = (typeof weight === 'string' ? parseFloat(weight) : weight); 30 | 31 | //node ids are strings now -- so make sure to save as string always 32 | self.sourceID = (typeof srcTgtObj.sourceID === 'number' ? "" + (srcTgtObj.sourceID) : srcTgtObj.sourceID); 33 | self.targetID = (typeof srcTgtObj.targetID === 'number' ? "" + (srcTgtObj.targetID) : srcTgtObj.targetID); 34 | 35 | //learning rates and modulatory information contained here, not generally used or tested 36 | self.a =0; 37 | self.b =0; 38 | self.c =0; 39 | self.d =0; 40 | self.modConnection=0; 41 | self.learningRate=0; 42 | 43 | self.isMutated=false; 44 | } 45 | 46 | 47 | NeatConnection.Copy = function(connection) 48 | { 49 | return new NeatConnection(connection.gid, connection.weight, {sourceID: connection.sourceID, targetID: connection.targetID}); 50 | }; -------------------------------------------------------------------------------- /genome/neatGenome.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | 5 | //pull in our cppn lib 6 | var cppnjs = require('cppnjs'); 7 | 8 | //grab our activation factory, cppn object and connections 9 | var CPPNactivationFactory = cppnjs.cppnActivationFactory; 10 | var utilities = cppnjs.utilities; 11 | 12 | //neatjs imports 13 | var novelty = require('../evolution/novelty.js'); 14 | var NeatConnection = require('./neatConnection.js'); 15 | var NeatNode = require('./neatNode.js'); 16 | 17 | //help and params 18 | var neatHelp = require('../neatHelp/neatHelp.js'); 19 | var neatParameters = require('../neatHelp/neatParameters.js'); 20 | var neatDecoder = require('../neatHelp/neatDecoder.js'); 21 | 22 | var wUtils = require('win-utils'); 23 | var uuid = wUtils.cuid; 24 | 25 | //going to need to read node types appropriately 26 | var NodeType = require('../types/nodeType.js'); 27 | 28 | /** 29 | * Expose `NeatGenome`. 30 | */ 31 | 32 | module.exports = NeatGenome; 33 | 34 | /** 35 | * Decodes a neatGenome in a cppn. 36 | * 37 | * @param {String} gid 38 | * @param {Array} nodes 39 | * @param {Array} connections 40 | * @param {Number} incount 41 | * @param {Number} outcount 42 | * @param {Boolean} debug 43 | * @api public 44 | */ 45 | function NeatGenome(gid, nodes, connections, incount, outcount, debug) { 46 | 47 | var self = this; 48 | 49 | self.gid = gid; 50 | self.fitness = 0; 51 | 52 | // From C#: Ensure that the connectionGenes are sorted by innovation ID at all times. 53 | self.nodes = nodes; 54 | self.connections = connections; 55 | 56 | //we start a fresh set of mutations for each genome we create! 57 | self.mutations = []; 58 | 59 | self.debug = debug; 60 | 61 | //keep track of behavior for novelty 62 | self.behavior = new novelty.Behavior(); 63 | //keep track of "real" fitness - that is the objective measure we've observed 64 | self.realFitness = 0; 65 | self.age = 0; 66 | 67 | self.localObjectivesCompetition = []; 68 | 69 | self.meta = {}; 70 | 71 | //TODO: Hash nodes, connections, and meta to make a global ID! 128-bit md5 hash? 72 | //WIN will assign a globalID or gid 73 | // self.gid = //get hash 74 | 75 | 76 | // From C#: For efficiency we store the number of input and output neurons. These two quantities do not change 77 | // throughout the life of a genome. Note that inputNeuronCount does NOT include the bias neuron! use inputAndBiasNeuronCount. 78 | // We also keep all input(including bias) neurons at the start of the neuronGeneList followed by 79 | // the output neurons. 80 | self.inputNodeCount= incount; 81 | self.inputAndBiasNodeCount= incount+1; 82 | self.outputNodeCount= outcount; 83 | self.inputBiasOutputNodeCount= self.inputAndBiasNodeCount + self.outputNodeCount; 84 | self.inputBiasOutputNodeCountMinus2= self.inputBiasOutputNodeCount -2; 85 | 86 | 87 | 88 | self.networkAdaptable= false; 89 | self.networkModulatory= false; 90 | // Temp tables. 91 | self.connectionLookup = null; 92 | self.nodeLookup = null; 93 | 94 | /// From C#: A table that keeps a track of which connections have added to the sexually reproduced child genome. 95 | /// This is cleared on each call to CreateOffspring_Sexual() and is only declared at class level to 96 | /// prevent having to re-allocate the table and it's associated memory on each invokation. 97 | // self.newConnectionTable = null; 98 | // self.newNodeTable= null; 99 | // self.newConnectionList= null; 100 | 101 | self.parent = null; 102 | 103 | } 104 | 105 | //Define the helper functions here! 106 | NeatGenome.Help = {}; 107 | 108 | var genomeCount = 0; 109 | 110 | NeatGenome.Help.nextGenomeID = function() 111 | { 112 | return genomeCount++; 113 | }; 114 | NeatGenome.Help.currentGenomeID = function(){ 115 | return genomeCount; 116 | }; 117 | NeatGenome.Help.resetGenomeID = function(value){ 118 | if(value ===undefined ){ 119 | genomeCount = 0; 120 | return; 121 | } 122 | genomeCount = value; 123 | }; 124 | 125 | 126 | var innovationCount = 0; 127 | var lastID = -1; 128 | var hitCount = 0; 129 | //wouldn't work with multithreaded/multi-process environment 130 | NeatGenome.Help.nextInnovationID = function(ix) 131 | { 132 | if(ix !== undefined) 133 | return "" + ix; 134 | 135 | //generate random string quickly (unlikely to cause local collisions on any machine) 136 | //no more number based stuff -- all string now 137 | return uuid(); 138 | // var id = 1000*(new Date().valueOf());//innovationCount++; 139 | // if(lastID === id) 140 | // hitCount++; 141 | // else 142 | // hitCount = 0; 143 | 144 | 145 | // lastID = id; 146 | // return id + (hitCount%1000); 147 | }; 148 | 149 | // NeatGenome.Help.currentInnovationID = function(){ 150 | // return innovationCount; 151 | // }; 152 | // NeatGenome.Help.resetInnovationID = function(value){ 153 | // if(value === undefined ){ 154 | // innovationCount = 0; 155 | // return; 156 | // } 157 | 158 | // innovationCount = value; 159 | // }; 160 | 161 | 162 | NeatGenome.Help.insertByInnovation = function(connection, connectionList) 163 | { 164 | var self = connectionList; 165 | // Determine the insert idx with a linear search, starting from the end 166 | // since mostly we expect to be adding genes that belong only 1 or 2 genes 167 | // from the end at most. 168 | var idx= connectionList.length-1; 169 | for(; idx>-1; idx--) 170 | { 171 | if(uuid.isLessThan(self[idx].gid, connection.gid)) 172 | { // Insert idx found. 173 | break; 174 | } 175 | } 176 | connectionList.splice(idx+1, 0, connection); 177 | }; 178 | 179 | NeatGenome.Help.CreateGIDLookup = function(arObject) 180 | { 181 | var lookup = {}; 182 | arObject.forEach(function(o) 183 | { 184 | lookup[o.gid] = o; 185 | }); 186 | 187 | return lookup; 188 | 189 | }; 190 | 191 | 192 | //NeuronGene creator 193 | /// 194 | /// Create a default minimal genome that describes a NN with the given number of inputs and outputs. 195 | /// 196 | /// 197 | //{connectionWeightRange: val, connectionProportion: val} 198 | NeatGenome.Help.CreateGenomeByInnovation = function(ins, outs, connParams, existing) 199 | { 200 | //existing is for seing if a connection innovation id already exists according to local believers/shamans 201 | existing = existing || {}; 202 | //create our ins and outs, 203 | var inputNodeList = [], outputNodeList = [], nodeList = [], connectionList = []; 204 | 205 | var aFunc = CPPNactivationFactory.getActivationFunction('NullFn'); 206 | 207 | var iCount = 0; 208 | 209 | // IMPORTANT NOTE: The neurons must all be created prior to any connections. That way all of the genomes 210 | // will obtain the same innovation ID's for the bias,input and output nodes in the initial population. 211 | // Create a single bias neuron. 212 | var node = new NeatNode(NeatGenome.Help.nextInnovationID(iCount++), aFunc, NeatNode.INPUT_LAYER, {type: NodeType.bias}); 213 | //null, idGenerator.NextInnovationId, NeuronGene.INPUT_LAYER, NeuronType.Bias, actFunct, stepCount); 214 | inputNodeList.push(node); 215 | nodeList.push(node); 216 | 217 | 218 | // Create input neuron genes. 219 | aFunc = CPPNactivationFactory.getActivationFunction('NullFn'); 220 | for(var i=0; i