├── .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 |
4 |
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
313 | /// Adds a connection to the list that will eventually be copied into a child of this genome during sexual reproduction.
314 | /// A helper function that is only called by CreateOffspring_Sexual_ProcessCorrelationItem().
315 | ///
316 | /// Specifies the connection to add to this genome.
317 | /// If there is already a connection from the same source to the same target,
318 | /// that connection is replaced when overwriteExisting is true and remains (no change is made) when overwriteExisting is false.
319 | //TODO: Use gid or innovationID?
320 | NeatGenome.Help.createOffspringSexual_AddGene = function(connectionList, connectionTable, connection, overwriteExisting)
321 | {
322 |
323 | var conKey = connection.gid;
324 |
325 | // Check if a matching gene has already been added.
326 | var oIdx = connectionTable[conKey];
327 |
328 | if(oIdx==null)
329 | { // No matching gene has been added.
330 | // Register this new gene with the newConnectionGeneTable - store its index within newConnectionGeneList.
331 | connectionTable[conKey] = connectionList.length;
332 |
333 | // Add the gene to the list.
334 | connectionList.push(NeatConnection.Copy(connection));
335 | }
336 | else if(overwriteExisting)
337 | {
338 | // Overwrite the existing matching gene with this one. In fact only the weight value differs between two
339 | // matching connection genes, so just overwrite the existing genes weight value.
340 |
341 | // Remember that we stored the gene's index in newConnectionGeneTable. So use it here.
342 | connectionList[oIdx].weight = connection.weight;
343 | }
344 | };
345 |
346 | ///
347 | /// Given a description of a connection in two parents, decide how to copy it into their child.
348 | /// A helper function that is only called by CreateOffspring_Sexual().
349 | ///
350 | /// Describes a connection and whether it exists on one parent, the other, or both.
351 | /// If this is 1, then the first parent is more fit; if 2 then the second parent. Other values are not defined.
352 | /// If this is true, add disjoint and excess genes to the child; otherwise, leave them out.
353 | /// Not used.
354 | NeatGenome.Help.createOffspringSexual_ProcessCorrelationItem
355 | = function(connectionList, connectionTable, correlationItem, fitSwitch, combineDisjointExcessFlag)
356 | {
357 | switch(correlationItem.correlationType)
358 | {
359 | // Disjoint and excess genes.
360 | case neatHelp.CorrelationType.disjointConnectionGene:
361 | case neatHelp.CorrelationType.excessConnectionGene:
362 | {
363 | // If the gene is in the fittest parent then override any existing entry in the connectionGeneTable.
364 | if(fitSwitch==1 && correlationItem.connection1!=null)
365 | {
366 | NeatGenome.Help.createOffspringSexual_AddGene(connectionList, connectionTable, correlationItem.connection1, true);
367 | return;
368 | }
369 |
370 | if(fitSwitch==2 && correlationItem.connection2!=null)
371 | {
372 | NeatGenome.Help.createOffspringSexual_AddGene(connectionList, connectionTable, correlationItem.connection2, true);
373 | return;
374 | }
375 |
376 | // The disjoint/excess gene is on the less fit parent.
377 | //if(Utilities.NextDouble() < np.pDisjointExcessGenesRecombined) // Include the gene n% of the time from whichever parent contains it.
378 | if(combineDisjointExcessFlag)
379 | {
380 | if(correlationItem.connection1!=null)
381 | {
382 | NeatGenome.Help.createOffspringSexual_AddGene(connectionList, connectionTable, correlationItem.connection1, false);
383 | return;
384 | }
385 | if(correlationItem.connection2!=null)
386 | {
387 | NeatGenome.Help.createOffspringSexual_AddGene(connectionList, connectionTable, correlationItem.connection2, false);
388 | return;
389 | }
390 | }
391 | break;
392 | }
393 |
394 | case neatHelp.CorrelationType.matchedConnectionGenes:
395 | {
396 | if(utilities.RouletteWheel.singleThrow(0.5))
397 | {
398 | // Override any existing entries in the table.
399 | NeatGenome.Help.createOffspringSexual_AddGene(connectionList, connectionTable, correlationItem.connection1, true);
400 | }
401 | else
402 | {
403 | // Override any existing entries in the table.
404 | NeatGenome.Help.createOffspringSexual_AddGene(connectionList, connectionTable, correlationItem.connection2, true);
405 | }
406 | break;
407 | }
408 | }
409 | };
410 |
411 |
412 | ///
413 | /// Correlate the ConnectionGenes within the two ConnectionGeneLists - based upon innovation number.
414 | /// Return an ArrayList of ConnectionGene[2] structures - pairs of matching ConnectionGenes.
415 | ///
416 | ///
417 | ///
418 | /// Resulting correlation
419 | NeatGenome.Help.correlateConnectionListsByInnovation
420 | = function(list1, list2)
421 | {
422 | var correlationResults = new neatHelp.CorrelationResults();
423 |
424 | //----- Test for special cases.
425 | if(!list1.length && !list2.length)
426 | { // Both lists are empty!
427 | return correlationResults;
428 | }
429 |
430 | if(!list1.length)
431 | { // All list2 genes are excess.
432 | correlationResults.correlationStatistics.excessConnectionCount = list2.length;
433 |
434 | list2.forEach(function(connection){
435 | //add a bunch of excess genes to our new creation!
436 | correlationResults.correlationList.push(new neatHelp.CorrelationItem(neatHelp.CorrelationType.excessConnectionGene, null, connection));
437 | });
438 | //done with correlating al; genes since list1 is empty
439 | return correlationResults;
440 | }
441 |
442 | // i believe there is a bug in the C# code, but it's completely irrelevant cause you'll never have 0 connections and for it to be sensical!
443 | if(!list2.length)
444 | { // All list1 genes are excess.
445 | correlationResults.correlationStatistics.excessConnectionCount = list1.length;
446 |
447 | list1.forEach(function(connection){
448 | //add a bunch of excess genes to our new creation!
449 | correlationResults.correlationList.push(new neatHelp.CorrelationItem(neatHelp.CorrelationType.excessConnectionGene, connection, null));
450 | });
451 |
452 | //done with correlating al; genes since list2 is empty
453 | return correlationResults;
454 | }
455 |
456 | //----- Both ConnectionGeneLists contain genes - compare the contents.
457 | var list1Idx=0;
458 | var list2Idx=0;
459 | var connection1 = list1[list1Idx];
460 | var connection2 = list2[list2Idx];
461 |
462 | for(;;)
463 | {
464 |
465 | if(uuid.isLessThan(connection2.gid, connection1.gid))
466 | {
467 | // connectionGene2 is disjoint.
468 | correlationResults.correlationList.push(new neatHelp.CorrelationItem(neatHelp.CorrelationType.disjointConnectionGene, null, connection2));
469 | correlationResults.correlationStatistics.disjointConnectionCount++;
470 |
471 | // Move to the next gene in list2.
472 | list2Idx++;
473 | }
474 | else if(connection1.gid == connection2.gid)
475 | {
476 | correlationResults.correlationList.push(new neatHelp.CorrelationItem(neatHelp.CorrelationType.matchedConnectionGenes, connection1, connection2));
477 | correlationResults.correlationStatistics.connectionWeightDelta += Math.abs(connection1.weight-connection2.weight);
478 | correlationResults.correlationStatistics.matchingCount++;
479 |
480 | // Move to the next gene in both lists.
481 | list1Idx++;
482 | list2Idx++;
483 | }
484 | else // (connectionGene2.InnovationId > connectionGene1.InnovationId)
485 | {
486 | // connectionGene1 is disjoint.
487 | correlationResults.correlationList.push(new neatHelp.CorrelationItem(neatHelp.CorrelationType.disjointConnectionGene, connection1, null));
488 | correlationResults.correlationStatistics.disjointConnectionCount++;
489 |
490 | // Move to the next gene in list1.
491 | list1Idx++;
492 | }
493 |
494 | // Check if we have reached the end of one (or both) of the lists. If we have reached the end of both then
495 | // we execute the first if block - but it doesn't matter since the loop is not entered if both lists have
496 | // been exhausted.
497 | if(list1Idx >= list1.length)
498 | {
499 | // All remaining list2 genes are excess.
500 | for(; list2Idx= list2.length)
509 | {
510 | // All remaining list1 genes are excess.
511 | for(; list1Idx otherParent.fitness)
558 | fitSwitch = 1;
559 | else if(self.fitness < otherParent.fitness)
560 | fitSwitch = 2;
561 | else
562 | { // Select one of the parents at random to be the 'master' genome during crossover.
563 | if(utilities.nextDouble() < 0.5)
564 | fitSwitch = 1;
565 | else
566 | fitSwitch = 2;
567 | }
568 |
569 | var combineDisjointExcessFlag = utilities.nextDouble() < np.pDisjointExcessGenesRecombined;
570 |
571 | // Loop through the correlationResults, building a table of ConnectionGenes from the parents that will make it into our
572 | // new [single] offspring. We use a table keyed on connection end points to prevent passing connections to the offspring
573 | // that may have the same end points but a different innovation number - effectively we filter out duplicate connections.
574 | // var idxBound = correlationResults.correlationList.length;
575 | correlationResults.correlationList.forEach(function(correlationItem)
576 | {
577 | NeatGenome.Help.createOffspringSexual_ProcessCorrelationItem(newConnectionList, newConnectionTable, correlationItem, fitSwitch, combineDisjointExcessFlag);
578 | });
579 |
580 |
581 |
582 | //----- Neuron Genes.
583 | // Build a neuronGeneList by analysing each connection's neuron end-point IDs.
584 | // This strategy has the benefit of eliminating neurons that are no longer connected too.
585 | // Remember to always keep all input, output and bias neurons though!
586 | var newNodeList = [];
587 |
588 | // Keep a table of the NeuronGene ID's keyed by ID so that we can keep track of which ones have been added.
589 | // Key = innovation ID, value = null for some reason.
590 |
591 | var newNodeTable = {};
592 |
593 | // Get the input/output neurons from this parent. All Genomes share these neurons, they do not change during a run.
594 | // idxBound = neuronGeneList.Count;
595 |
596 | self.nodes.forEach(function(node)
597 | {
598 | if(node.nodeType != NodeType.hidden)
599 | {
600 | newNodeList.push(NeatNode.Copy(node));
601 | newNodeTable[node.gid] = node;
602 | }
603 | // else
604 | // { // No more bias, input or output nodes. break the loop.
605 | // break;
606 | // }
607 | });
608 |
609 | // Now analyse the connections to determine which NeuronGenes are required in the offspring.
610 | // Loop through every connection in the child, and add to the child those hidden neurons that are sources or targets of the connection.
611 | // idxBound = newConnectionGeneList.Count;
612 |
613 |
614 | var nodeLookup = NeatGenome.Help.CreateGIDLookup(self.nodes);
615 | var otherNodeLookup = NeatGenome.Help.CreateGIDLookup(otherParent.nodes);
616 | // var connLookup = NeatGenome.Help.CreateGIDLookup(self.connections);
617 |
618 | newConnectionList.forEach(function(connection)
619 | {
620 | var node;
621 |
622 | if(!newNodeTable[connection.sourceID])
623 | {
624 | //TODO: DAVID proper activation function
625 | // We can safely assume that any missing NeuronGenes at this point are hidden heurons.
626 | node = nodeLookup[connection.sourceID];
627 | if (node)
628 | newNodeList.push(NeatNode.Copy(node));
629 | else{
630 | node = otherNodeLookup[connection.sourceID];
631 | if(!node)
632 | throw new Error("Connection references source node that does not exist in either parent: " + JSON.stringify(connection));
633 |
634 | newNodeList.push(NeatNode.Copy(otherNodeLookup[connection.sourceID]));
635 | }
636 | //newNeuronGeneList.Add(new NeuronGene(connectionGene.SourceNeuronId, NeuronType.Hidden, ActivationFunctionFactory.GetActivationFunction("SteepenedSigmoid")));
637 | newNodeTable[connection.sourceID] = node;
638 | }
639 |
640 | if(!newNodeTable[connection.targetID])
641 | {
642 | //TODO: DAVID proper activation function
643 | // We can safely assume that any missing NeuronGenes at this point are hidden heurons.
644 | node = nodeLookup[connection.targetID];
645 | if (node != null)
646 | newNodeList.push(NeatNode.Copy(node));
647 | else{
648 | node = otherNodeLookup[connection.targetID];
649 | if(!node)
650 | throw new Error("Connection references target node that does not exist in either parent: " + JSON.stringify(connection));
651 |
652 | newNodeList.push(NeatNode.Copy(otherNodeLookup[connection.targetID]));
653 | }
654 |
655 | //newNeuronGeneList.Add(new NeuronGene(connectionGene.TargetNeuronId, NeuronType.Hidden, ActivationFunctionFactory.GetActivationFunction("SteepenedSigmoid")));
656 | newNodeTable[connection.targetID] = node;
657 | }
658 | });
659 |
660 | // TODO: Inefficient code?
661 | newNodeList.sort(function(a,b){
662 | var compare = uuid.isLessThan(a.gid, b.gid) ?
663 | -1 : //is less than -- a- b = -1
664 | (a.gid == b.gid) ? 0 : //is possible equal to or greater
665 | 1;//is greater than definitely
666 | return compare
667 | });
668 |
669 | // newConnectionGeneList is already sorted because it was generated by passing over the list returned by
670 | // CorrelateConnectionGeneLists() - which is always in order.
671 | return new NeatGenome(NeatGenome.Help.nextGenomeID(), newNodeList,newConnectionList, self.inputNodeCount, self.outputNodeCount, self.debug);
672 |
673 | //No module support in here
674 |
675 | // Determine which modules to pass on to the child in the same way.
676 | // For each module in this genome or in the other parent, if it was referenced by even one connection add it and all its dummy neurons to the child.
677 | // List newModuleGeneList = new List();
678 | //
679 | // // Build a list of modules the child might have, which is a union of the parents' module lists, but they are all copies so we can't just do a union.
680 | // List unionParentModules = new List(moduleGeneList);
681 | // foreach (ModuleGene moduleGene in otherParent.moduleGeneList) {
682 | // bool alreadySeen = false;
683 | // foreach (ModuleGene match in unionParentModules) {
684 | // if (moduleGene.InnovationId == match.InnovationId) {
685 | // alreadySeen = true;
686 | // break;
687 | // }
688 | // }
689 | // if (!alreadySeen) {
690 | // unionParentModules.Add(moduleGene);
691 | // }
692 | // }
693 |
694 | // foreach (ModuleGene moduleGene in unionParentModules) {
695 | // // Examine each neuron in the child to determine whether it is part of a module.
696 | // foreach (List dummyNeuronList in new List[] { moduleGene.InputIds, moduleGene.OutputIds })
697 | // {
698 | // foreach (long dummyNeuronId in dummyNeuronList)
699 | // {
700 | // if (newNeuronGeneTable.ContainsKey(dummyNeuronId)) {
701 | // goto childHasModule;
702 | // }
703 | // }
704 | // }
705 | //
706 | // continue; // the child does not contain this module, so continue the loop and check for the next module.
707 | // childHasModule: // the child does contain this module, so make sure the child gets all the nodes the module requires to work.
708 | //
709 | // // Make sure the child has all the neurons in the given module.
710 | // newModuleGeneList.Add(new ModuleGene(moduleGene));
711 | // foreach (List dummyNeuronList in new List[] { moduleGene.InputIds, moduleGene.OutputIds })
712 | // {
713 | // foreach (long dummyNeuronId in dummyNeuronList)
714 | // {
715 | // if (!newNeuronGeneTable.ContainsKey(dummyNeuronId)) {
716 | // newNeuronGeneTable.Add(dummyNeuronId, null);
717 | // NeuronGene neuronGene = this.neuronGeneList.GetNeuronById(dummyNeuronId);
718 | // if (neuronGene != null) {
719 | // newNeuronGeneList.Add(new NeuronGene(neuronGene));
720 | // } else {
721 | // newNeuronGeneList.Add(new NeuronGene(otherParent.NeuronGeneList.GetNeuronById(dummyNeuronId)));
722 | // }
723 | // }
724 | // }
725 | // }
726 | // }
727 |
728 |
729 |
730 | };
731 |
732 |
733 | ///
734 | /// Decode the genome's 'DNA' into a working network.
735 | ///
736 | ///
737 | NeatGenome.prototype.networkDecode = function(activationFn)
738 | {
739 | var self = this;
740 |
741 | return neatDecoder.DecodeToFloatFastConcurrentNetwork(self, activationFn);
742 | };
743 |
744 |
745 | ///
746 | /// Clone this genome.
747 | ///
748 | ///
749 | NeatGenome.prototype.clone = function()
750 | {
751 | var self = this;
752 | return NeatGenome.Copy(self, NeatGenome.Help.nextGenomeID());
753 | };
754 |
755 | NeatGenome.prototype.compatFormer = function(comparisonGenome, np) {
756 | /* A very simple way of implementing this routine is to call CorrelateConnectionGeneLists and to then loop
757 | * through the correlation items, calculating a compatibility score as we go. However, this routine
758 | * is heavily used and in performance tests was shown consume 40% of the CPU time for the core NEAT code.
759 | * Therefore this new routine has been rewritten with it's own version of the logic within
760 | * CorrelateConnectionGeneLists. This allows us to only keep comparing genes up to the point where the
761 | * threshold is passed. This also eliminates the need to build the correlation results list, this difference
762 | * alone is responsible for a 200x performance improvement when testing with a 1664 length genome!!
763 | *
764 | * A further optimisation is achieved by comparing the genes starting at the end of the genomes which is
765 | * where most disparities are located - new novel genes are always attached to the end of genomes. This
766 | * has the result of complicating the routine because we must now invoke additional logic to determine
767 | * which genes are excess and when the first disjoint gene is found. This is done with an extra integer:
768 | *
769 | * int excessGenesSwitch=0; // indicates to the loop that it is handling the first gene.
770 | * =1; // Indicates that the first gene was excess and on genome 1.
771 | * =2; // Indicates that the first gene was excess and on genome 2.
772 | * =3; // Indicates that there are no more excess genes.
773 | *
774 | * This extra logic has a slight performance hit, but this is minor especially in comparison to the savings that
775 | * are expected to be achieved overall during a NEAT search.
776 | *
777 | * If you have trouble understanding this logic then it might be best to work through the previous version of
778 | * this routine (below) that scans through the genomes from start to end, and which is a lot simpler.
779 | *
780 | */
781 | var self = this;
782 |
783 | //this can be replaced with the following code:
784 |
785 |
786 |
787 |
788 | var list1 = self.connections;
789 | var list2 = comparisonGenome.connections;
790 |
791 | //
792 | // var compatibility = 0;
793 | // var correlation = NeatGenome.Help.correlateConnectionListsByInnovation(list1, list2);
794 | // compatibility += correlation.correlationStatistics.excessConnectionCount*np.compatibilityExcessCoeff;
795 | // compatibility += correlation.correlationStatistics.disjointConnectionCount*np.compatibilityDisjointCoeff;
796 | // compatibility += correlation.correlationStatistics.connectionWeightDelta*np.compatibilityWeightDeltaCoeff;
797 | // return compatibility;
798 |
799 |
800 | var excessGenesSwitch=0;
801 |
802 | // Store these heavily used values locally.
803 | var list1Count = list1.length;
804 | var list2Count = list2.length;
805 |
806 | //----- Test for special cases.
807 | if(list1Count==0 && list2Count==0)
808 | { // Both lists are empty! No disparities, therefore the genomes are compatible!
809 | return 0.0;
810 | }
811 |
812 | if(list1Count==0)
813 | { // All list2 genes are excess.
814 | return ((list2.length * np.compatibilityExcessCoeff));
815 | }
816 |
817 | if(list2Count==0)
818 | {
819 | // All list1 genes are excess.
820 | return ((list1Count * np.compatibilityExcessCoeff));
821 | }
822 |
823 | //----- Both ConnectionGeneLists contain genes - compare the contents.
824 | var compatibility = 0.0;
825 | var list1Idx=list1Count-1;
826 | var list2Idx=list2Count-1;
827 | var connection1 = list1[list1Idx];
828 | var connection2 = list2[list2Idx];
829 | for(;;)
830 | {
831 | if(connection1.gid == connection2.gid)
832 | {
833 | // No more excess genes. It's quicker to set this every time than to test if is not yet 3.
834 | excessGenesSwitch=3;
835 |
836 | // Matching genes. Increase compatibility by weight difference * coeff.
837 | compatibility += Math.abs(connection1.weight-connection2.weight) * np.compatibilityWeightDeltaCoeff;
838 |
839 | // Move to the next gene in both lists.
840 | list1Idx--;
841 | list2Idx--;
842 | }
843 | else if(!uuid.isLessThan(connection2.gid, connection1.gid))
844 | {
845 | // Most common test case(s) at top for efficiency.
846 | if(excessGenesSwitch==3)
847 | { // No more excess genes. Therefore this mismatch is disjoint.
848 | compatibility += np.compatibilityDisjointCoeff;
849 | }
850 | else if(excessGenesSwitch==2)
851 | { // Another excess gene on genome 2.
852 | compatibility += np.compatibilityExcessCoeff;
853 | }
854 | else if(excessGenesSwitch==1)
855 | { // We have found the first non-excess gene.
856 | excessGenesSwitch=3;
857 | compatibility += np.compatibilityDisjointCoeff;
858 | }
859 | else //if(excessGenesSwitch==0)
860 | { // First gene is excess, and is on genome 2.
861 | excessGenesSwitch = 2;
862 | compatibility += np.compatibilityExcessCoeff;
863 | }
864 |
865 | // Move to the next gene in list2.
866 | list2Idx--;
867 | }
868 | else // (connectionGene2.InnovationId < connectionGene1.InnovationId)
869 | {
870 | // Most common test case(s) at top for efficiency.
871 | if(excessGenesSwitch==3)
872 | { // No more excess genes. Therefore this mismatch is disjoint.
873 | compatibility += np.compatibilityDisjointCoeff;
874 | }
875 | else if(excessGenesSwitch==1)
876 | { // Another excess gene on genome 1.
877 | compatibility += np.compatibilityExcessCoeff;
878 | }
879 | else if(excessGenesSwitch==2)
880 | { // We have found the first non-excess gene.
881 | excessGenesSwitch=3;
882 | compatibility += np.compatibilityDisjointCoeff;
883 | }
884 | else //if(excessGenesSwitch==0)
885 | { // First gene is excess, and is on genome 1.
886 | excessGenesSwitch = 1;
887 | compatibility += np.compatibilityExcessCoeff;
888 | }
889 |
890 | // Move to the next gene in list1.
891 | list1Idx--;
892 | }
893 |
894 |
895 | // Check if we have reached the end of one (or both) of the lists. If we have reached the end of both then
896 | // we execute the first 'if' block - but it doesn't matter since the loop is not entered if both lists have
897 | // been exhausted.
898 | if(list1Idx < 0)
899 | {
900 | // All remaining list2 genes are disjoint.
901 | compatibility += (list2Idx+1) * np.compatibilityDisjointCoeff;
902 | return (compatibility); //< np.compatibilityThreshold);
903 | }
904 |
905 | if(list2Idx < 0)
906 | {
907 | // All remaining list1 genes are disjoint.
908 | compatibility += (list1Idx+1) * np.compatibilityDisjointCoeff;
909 | return (compatibility); //< np.compatibilityThreshold);
910 | }
911 |
912 | connection1 = list1[list1Idx];
913 | connection2 = list2[list2Idx];
914 | }
915 | };
916 |
917 | NeatGenome.prototype.compat = function(comparisonGenome, np) {
918 |
919 | var self = this;
920 | var list1 = self.connections;
921 | var list2 = comparisonGenome.connections;
922 |
923 | var compatibility = 0;
924 | var correlation = NeatGenome.Help.correlateConnectionListsByInnovation(list1, list2);
925 | compatibility += correlation.correlationStatistics.excessConnectionCount*np.compatibilityExcessCoeff;
926 | compatibility += correlation.correlationStatistics.disjointConnectionCount*np.compatibilityDisjointCoeff;
927 | compatibility += correlation.correlationStatistics.connectionWeightDelta*np.compatibilityWeightDeltaCoeff;
928 | return compatibility;
929 |
930 | };
931 |
932 | NeatGenome.prototype.isCompatibleWithGenome= function(comparisonGenome, np)
933 | {
934 | var self = this;
935 |
936 | return (self.compat(comparisonGenome, np) < np.compatibilityThreshold);
937 | };
938 |
939 | NeatGenome.Help.InOrderInnovation = function(aObj)
940 | {
941 | var prevId = 0;
942 |
943 | for(var i=0; i< aObj.length; i++){
944 | var connection = aObj[i];
945 | if(uuid.isLessThan(connection.gid, prevId))
946 | return false;
947 | prevId = connection.gid;
948 | }
949 |
950 | return true;
951 | };
952 |
953 |
954 | ///
955 | /// For debug purposes only.
956 | ///
957 | /// Returns true if genome integrity checks out OK.
958 | NeatGenome.prototype.performIntegrityCheck = function()
959 | {
960 | var self = this;
961 | return NeatGenome.Help.InOrderInnovation(self.connections);
962 | };
963 |
964 |
965 | NeatGenome.prototype.mutate = function(newNodeTable, newConnectionTable, np)
966 | {
967 | var self = this;
968 |
969 | // Determine the type of mutation to perform.
970 | var probabilities = [];
971 | probabilities.push(np.pMutateAddNode);
972 | // probabilities.push(0);//np.pMutateAddModule);
973 | probabilities.push(np.pMutateAddConnection);
974 | probabilities.push(np.pMutateDeleteConnection);
975 | probabilities.push(np.pMutateDeleteSimpleNeuron);
976 | probabilities.push(np.pMutateConnectionWeights);
977 | probabilities.push(np.pMutateChangeActivations);
978 |
979 | var outcome = utilities.RouletteWheel.singleThrowArray(probabilities);
980 | switch(outcome)
981 | {
982 | case 0:
983 | self.mutate_AddNode(newNodeTable);
984 | return 0;
985 | case 1:
986 | // self.mutate_Ad Mutate_AddModule(ea);
987 | self.mutate_AddConnection(newConnectionTable,np);
988 | return 1;
989 | case 2:
990 | self.mutate_DeleteConnection();
991 | return 2;
992 | case 3:
993 | self.mutate_DeleteSimpleNeuronStructure(newConnectionTable, np);
994 | return 3;
995 | case 4:
996 | self.mutate_ConnectionWeights(np);
997 | return 4;
998 | case 5:
999 | self.mutate_ChangeActivation(np);
1000 | return 5;
1001 | }
1002 | };
1003 |
1004 |
1005 |
1006 | //NeuronGene creator
1007 | ///
1008 | /// Add a new node to the Genome. We do this by removing a connection at random and inserting
1009 | /// a new node and two new connections that make the same circuit as the original connection.
1010 | ///
1011 | /// This way the new node is properly integrated into the network from the outset.
1012 | ///
1013 | ///
1014 | NeatGenome.prototype.mutate_AddNode = function(newNodeTable, connToSplit)
1015 | {
1016 | var self = this;
1017 |
1018 | if(!self.connections.length)
1019 | return null;
1020 |
1021 | // Select a connection at random.
1022 | var connectionToReplaceIdx = Math.floor(utilities.nextDouble() * self.connections.length);
1023 | var connectionToReplace = connToSplit || self.connections[connectionToReplaceIdx];
1024 |
1025 | // Delete the existing connection. JOEL: Why delete old connection?
1026 | //connectionGeneList.RemoveAt(connectionToReplaceIdx);
1027 |
1028 | // Check if this connection has already been split on another genome. If so then we should re-use the
1029 | // neuron ID and two connection ID's so that matching structures within the population maintain the same ID.
1030 | var existingNeuronGeneStruct = newNodeTable[connectionToReplace.gid];
1031 |
1032 | var newNode;
1033 | var newConnection1;
1034 | var newConnection2;
1035 | var actFunct;
1036 |
1037 | var nodeLookup = NeatGenome.Help.CreateGIDLookup(self.nodes);
1038 |
1039 | //we could attempt to mutate the same node TWICE -- causing big issues, since we'll double add that node
1040 |
1041 | var acnt = 0;
1042 | var attempts = 5;
1043 | //while we
1044 | while(acnt++ < attempts && existingNeuronGeneStruct && nodeLookup[existingNeuronGeneStruct.node.gid])
1045 | {
1046 | connectionToReplaceIdx = Math.floor(utilities.nextDouble() * self.connections.length);
1047 | connectionToReplace = connToSplit || self.connections[connectionToReplaceIdx];
1048 | existingNeuronGeneStruct = newNodeTable[connectionToReplace.gid];
1049 | }
1050 |
1051 | //we have failed to produce a new node to split!
1052 | if(acnt == attempts && existingNeuronGeneStruct && nodeLookup[existingNeuronGeneStruct.node.gid])
1053 | return;
1054 |
1055 | if(!existingNeuronGeneStruct)
1056 | { // No existing matching structure, so generate some new ID's.
1057 |
1058 | //TODO: DAVID proper random activation function
1059 | // Replace connectionToReplace with two new connections and a neuron.
1060 | actFunct= CPPNactivationFactory.getRandomActivationFunction();
1061 | //newNeuronGene = new NeuronGene(ea.NextInnovationId, NeuronType.Hidden, actFunct);
1062 |
1063 | var nextID = NeatGenome.Help.nextInnovationID();//connectionToReplace.gid);
1064 |
1065 | newNode = new NeatNode(nextID, actFunct,
1066 | (nodeLookup[connectionToReplace.sourceID].layer + nodeLookup[connectionToReplace.targetID].layer)/2.0,
1067 | {type: NodeType.hidden});
1068 |
1069 | nextID = NeatGenome.Help.nextInnovationID();
1070 | newConnection1 = new NeatConnection(nextID, 1.0, {sourceID: connectionToReplace.sourceID, targetID:newNode.gid});
1071 |
1072 | nextID = NeatGenome.Help.nextInnovationID();
1073 | newConnection2 = new NeatConnection(nextID, connectionToReplace.weight, {sourceID: newNode.gid, targetID: connectionToReplace.targetID});
1074 |
1075 | // Register the new ID's with NewNeuronGeneStructTable.
1076 | newNodeTable[connectionToReplace.gid] = {node: newNode, connection1: newConnection1, connection2: newConnection2};
1077 | }
1078 | else
1079 | { // An existing matching structure has been found. Re-use its ID's
1080 |
1081 | //TODO: DAVID proper random activation function
1082 | // Replace connectionToReplace with two new connections and a neuron.
1083 | actFunct = CPPNactivationFactory.getRandomActivationFunction();
1084 | var tmpStruct = existingNeuronGeneStruct;
1085 | //newNeuronGene = new NeuronGene(tmpStruct.NewNeuronGene.InnovationId, NeuronType.Hidden, actFunct);
1086 | newNode = NeatNode.Copy(tmpStruct.node);
1087 | newNode.nodeType = NodeType.hidden;
1088 | //new NeuronGene(null, tmpStruct.NewNeuronGene.gid, tmpStruct.NewNeuronGene.Layer, NeuronType.Hidden, actFunct, this.step);
1089 |
1090 | newConnection1 = new NeatConnection(tmpStruct.connection1.gid, 1.0, {sourceID: connectionToReplace.sourceID, targetID:newNode.gid});
1091 | // new ConnectionGene(tmpStruct.NewConnectionGene_Input.gid, connectionToReplace.SourceNeuronId, newNeuronGene.gid, 1.0);
1092 | newConnection2 = new NeatConnection(tmpStruct.connection2.gid, connectionToReplace.weight, {sourceID: newNode.gid, targetID: connectionToReplace.targetID});
1093 | // new ConnectionGene(tmpStruct.NewConnectionGene_Output.gid, newNeuronGene.gid, connectionToReplace.TargetNeuronId, connectionToReplace.Weight);
1094 | }
1095 |
1096 | // Add the new genes to the genome.
1097 | self.nodes.push(newNode);
1098 | NeatGenome.Help.insertByInnovation(newConnection1, self.connections);
1099 | NeatGenome.Help.insertByInnovation(newConnection2, self.connections);
1100 |
1101 | //in javascript, we return the new node and connections created, since it's so easy!
1102 | // return {node: newNode, connection1: newConnection1, newConnection2: newConnection2};
1103 |
1104 | };
1105 |
1106 | //Modules not implemented
1107 | // NeatGenome.prototype.mutate_AddModule = function(np)
1108 | // {
1109 | // }
1110 |
1111 | NeatGenome.prototype.testForExistingConnectionInnovation = function(sourceID, targetID)
1112 | {
1113 | var self = this;
1114 | // console.log('looking for source: ' + sourceID + ' target: ' + targetID);
1115 |
1116 | for(var i=0; i< self.connections.length; i++){
1117 | var connection = self.connections[i];
1118 | if(connection.sourceID == sourceID && connection.targetID == targetID){
1119 | return connection;
1120 | }
1121 | }
1122 |
1123 | return null;
1124 | };
1125 |
1126 | //messes with the activation functions
1127 | NeatGenome.prototype.mutate_ChangeActivation = function(np)
1128 | {
1129 | //let's select a node at random (so long as it's not an input)
1130 | var self = this;
1131 |
1132 | for(var i=0; i < self.nodes.length; i++)
1133 | {
1134 | //not going to change the inputs
1135 | if(i < self.inputAndBiasNodeCount)
1136 | continue;
1137 |
1138 | if(utilities.nextDouble() < np.pNodeMutateActivationRate)
1139 | {
1140 | self.nodes[i].activationFunction = CPPNactivationFactory.getRandomActivationFunction().functionID;
1141 | }
1142 | }
1143 | };
1144 |
1145 | //add a connection, sourcetargetconnect specifies the source, target or both nodes you'd like to connect (optionally)
1146 | NeatGenome.prototype.mutate_AddConnection = function(newConnectionTable, np, sourceTargetConnect)
1147 | {
1148 | //if we didn't send specifics, just create an empty object
1149 | sourceTargetConnect = sourceTargetConnect || {};
1150 |
1151 | var self = this;
1152 | // We are always guaranteed to have enough neurons to form connections - because the input/output neurons are
1153 | // fixed. Any domain that doesn't require input/outputs is a bit nonsensical!
1154 |
1155 | // Make a fixed number of attempts at finding a suitable connection to add.
1156 |
1157 | if(self.nodes.length>1)
1158 | { // At least 2 neurons, so we have a chance at creating a connection.
1159 |
1160 | for(var attempts=0; attempts<5; attempts++)
1161 | {
1162 | // Select candidate source and target neurons. Any neuron can be used as the source. Input neurons
1163 | // should not be used as a target
1164 | var srcNeuronIdx;
1165 | var tgtNeuronIdx;
1166 |
1167 |
1168 |
1169 | // Find all potential inputs, or quit if there are not enough.
1170 | // Neurons cannot be inputs if they are dummy input nodes of a module.
1171 | var potentialInputs = [];
1172 |
1173 | self.nodes.forEach(function(n)
1174 | {
1175 | if(n.activationFunction.functionID !== 'ModuleInputNeuron')
1176 | potentialInputs.push(n);
1177 | });
1178 |
1179 |
1180 | if (potentialInputs.length < 1)
1181 | return false;
1182 |
1183 | var potentialOutputs = [];
1184 |
1185 | // Find all potential outputs, or quit if there are not enough.
1186 | // Neurons cannot be outputs if they are dummy input or output nodes of a module, or network input or bias nodes.
1187 | self.nodes.forEach(function(n)
1188 | {
1189 | if(n.nodeType != NodeType.bias && n.nodeType != NodeType.input &&
1190 | n.activationFunction.functionID !== 'ModuleInputNeuron'
1191 | && n.activationFunction.functionID !== 'ModuleOutputNeuron')
1192 | potentialOutputs.push(n);
1193 | });
1194 |
1195 | if (potentialOutputs.length < 1)
1196 | return false;
1197 |
1198 | var sourceNeuron = sourceTargetConnect.source || potentialInputs[utilities.next(potentialInputs.length)];
1199 | var targetNeuron = sourceTargetConnect.target || potentialOutputs[utilities.next(potentialOutputs.length)];
1200 |
1201 | // Check if a connection already exists between these two neurons.
1202 | var sourceID = sourceNeuron.gid;
1203 | var targetID = targetNeuron.gid;
1204 |
1205 | //we don't allow recurrent connections, we can't let the target layers be <= src
1206 | if(np.disallowRecurrence && targetNeuron.layer <= sourceNeuron.layer)
1207 | continue;
1208 |
1209 | if(!self.testForExistingConnectionInnovation(sourceID, targetID))
1210 | {
1211 | // Check if a matching mutation has already occured on another genome.
1212 | // If so then re-use the connection ID.
1213 | var connectionKey = "(" + sourceID + "," + targetID + ")";
1214 | var existingConnection = newConnectionTable[connectionKey];
1215 | var newConnection;
1216 | var nextID = NeatGenome.Help.nextInnovationID();
1217 | if(existingConnection==null)
1218 | { // Create a new connection with a new ID and add it to the Genome.
1219 | newConnection = new NeatConnection(nextID,
1220 | (utilities.nextDouble()*np.connectionWeightRange/4.0) - np.connectionWeightRange/8.0,
1221 | {sourceID: sourceID, targetID: targetID});
1222 |
1223 | // new ConnectionGene(ea.NextInnovationId, sourceID, targetID,
1224 | // (Utilities.NextDouble() * ea.NeatParameters.connectionWeightRange/4.0) - ea.NeatParameters.connectionWeightRange/8.0);
1225 |
1226 | // Register the new connection with NewConnectionGeneTable.
1227 | newConnectionTable[connectionKey] = newConnection;
1228 |
1229 | // Add the new gene to this genome. We have a new ID so we can safely append the gene to the end
1230 | // of the list without risk of breaking the innovation ID order.
1231 | self.connections.push(newConnection);
1232 | }
1233 | else
1234 | { // Create a new connection, re-using the ID from existingConnection, and add it to the Genome.
1235 | newConnection = new NeatConnection(existingConnection.gid,
1236 | (utilities.nextDouble()*np.connectionWeightRange/4.0) - np.connectionWeightRange/8.0,
1237 | {sourceID: sourceID, targetID: targetID});
1238 |
1239 | // new ConnectionGene(existingConnection.InnovationId, sourceId, targetID,
1240 | // (Utilities.NextDouble() * ea.NeatParameters.connectionWeightRange/4.0) - ea.NeatParameters.connectionWeightRange/8.0);
1241 |
1242 | // Add the new gene to this genome. We are re-using an ID so we must ensure the connection gene is
1243 | // inserted into the correct position (sorted by innovation ID).
1244 | NeatGenome.Help.insertByInnovation(newConnection, self.connections);
1245 | // connectionGeneList.InsertIntoPosition(newConnection);
1246 | }
1247 |
1248 |
1249 |
1250 | return true;
1251 | }
1252 | }
1253 | }
1254 |
1255 | // We couldn't find a valid connection to create. Instead of doing nothing lets perform connection
1256 | // weight mutation.
1257 | self.mutate_ConnectionWeights(np);
1258 |
1259 | return false;
1260 | };
1261 |
1262 | NeatGenome.prototype.mutate_ConnectionWeights = function(np)
1263 | {
1264 | var self = this;
1265 | // Determine the type of weight mutation to perform.
1266 | var probabilties = [];
1267 |
1268 | np.connectionMutationParameterGroupList.forEach(function(connMut){
1269 | probabilties.push(connMut.activationProportion);
1270 | });
1271 |
1272 | // Get a reference to the group we will be using.
1273 | var paramGroup = np.connectionMutationParameterGroupList[utilities.RouletteWheel.singleThrowArray(probabilties)];
1274 |
1275 | // Perform mutations of the required type.
1276 | if(paramGroup.selectionType== neatParameters.ConnectionSelectionType.proportional)
1277 | {
1278 | var mutationOccured=false;
1279 | var connectionCount = self.connections.length;
1280 | self.connections.forEach(function(connection){
1281 |
1282 | if(utilities.nextDouble() < paramGroup.proportion)
1283 | {
1284 | self.mutateConnectionWeight(connection, np, paramGroup);
1285 | mutationOccured = true;
1286 | }
1287 |
1288 | });
1289 |
1290 | if(!mutationOccured && connectionCount>0)
1291 | { // Perform at least one mutation. Pick a gene at random.
1292 | self.mutateConnectionWeight(self.connections[utilities.next(connectionCount)], // (Utilities.NextDouble() * connectionCount)],
1293 | np,
1294 | paramGroup);
1295 | }
1296 | }
1297 | else // if(paramGroup.SelectionType==ConnectionSelectionType.FixedQuantity)
1298 | {
1299 | // Determine how many mutations to perform. At least one - if there are any genes.
1300 | var connectionCount = self.connections.length;
1301 |
1302 | var mutations = Math.min(connectionCount, Math.max(1, paramGroup.quantity));
1303 | if(mutations==0) return;
1304 |
1305 | // The mutation loop. Here we pick an index at random and scan forward from that point
1306 | // for the first non-mutated gene. This prevents any gene from being mutated more than once without
1307 | // too much overhead. In fact it's optimal for small numbers of mutations where clashes are unlikely
1308 | // to occur.
1309 | for(var i=0; i
1373 | /// If the neuron is a hidden neuron and no connections connect to it then it is redundant.
1374 | /// No neuron is redundant that is part of a module (although the module itself might be found redundant separately).
1375 | ///
1376 | NeatGenome.prototype.isNeuronRedundant=function(nodeLookup, nid)
1377 | {
1378 | var self = this;
1379 | var node = nodeLookup[nid];
1380 | if (node.nodeType != NodeType.hidden
1381 | || node.activationFunction.functionID === 'ModuleInputNeuron'
1382 | || node.activationFunction.functionID === 'ModuleOutputNeuron')
1383 | return false;
1384 |
1385 | return !self.isNeuronConnected(nid);
1386 | };
1387 |
1388 | NeatGenome.prototype.isNeuronConnected = function(nid)
1389 | {
1390 | var self = this;
1391 | for(var i=0; i < self.connections.length; i++)
1392 | {
1393 | var connection = self.connections[i];
1394 |
1395 | if(connection.sourceID == nid)
1396 | return true;
1397 | if(connection.targetID == nid)
1398 | return true;
1399 |
1400 | }
1401 |
1402 | return false;
1403 | };
1404 |
1405 |
1406 | NeatGenome.prototype.mutate_DeleteConnection = function(connection)
1407 | {
1408 | var self = this;
1409 | if(self.connections.length ==0)
1410 | return;
1411 |
1412 | self.nodeLookup = NeatGenome.Help.CreateGIDLookup(self.nodes);
1413 |
1414 | // Select a connection at random.
1415 | var connectionToDeleteIdx = utilities.next(self.connections.length);
1416 |
1417 | if(connection){
1418 | for(var i=0; i< self.connections.length; i++){
1419 | if(connection.gid == self.connections[i].gid)
1420 | {
1421 | connectionToDeleteIdx = i;
1422 | break;
1423 | }
1424 | }
1425 | }
1426 |
1427 | var connectionToDelete = connection || self.connections[connectionToDeleteIdx];
1428 |
1429 | // Delete the connection.
1430 | self.connections.splice(connectionToDeleteIdx,1);
1431 |
1432 | var srcIx = -1;
1433 | var tgtIx = -1;
1434 |
1435 | self.nodes.forEach(function(node,i){
1436 |
1437 | if(node.sourceID == connectionToDelete.sourceID)
1438 | srcIx = i;
1439 |
1440 | if(node.targetID == connectionToDelete.targetID)
1441 | tgtIx = i;
1442 | });
1443 |
1444 | // Remove any neurons that may have been left floating.
1445 | if(self.isNeuronRedundant(self.nodeLookup ,connectionToDelete.sourceID)){
1446 | self.nodes.splice(srcIx,1);//(connectionToDelete.sourceID);
1447 | }
1448 |
1449 | // Recurrent connection has both end points at the same neuron!
1450 | if(connectionToDelete.sourceID !=connectionToDelete.targetID){
1451 | if(self.isNeuronRedundant(self.nodeLookup, connectionToDelete.targetID))
1452 | self.nodes.splice(tgtIx,1);//neuronGeneList.Remove(connectionToDelete.targetID);
1453 | }
1454 | };
1455 |
1456 | NeatGenome.BuildNeuronConnectionLookupTable_NewConnection = function(nodeConnectionLookup,nodeTable, gid, connection, inOrOut)
1457 | {
1458 | // Is this neuron already known to the lookup table?
1459 | var lookup = nodeConnectionLookup[gid];
1460 |
1461 | if(lookup==null)
1462 | { // Creae a new lookup entry for this neuron Id.
1463 | lookup = {node: nodeTable[gid], incoming: [], outgoing: [] };
1464 | nodeConnectionLookup[gid] = lookup;
1465 | }
1466 |
1467 | // Register the connection with the NeuronConnectionLookup object.
1468 | lookup[inOrOut].push(connection);
1469 | };
1470 | NeatGenome.prototype.buildNeuronConnectionLookupTable = function()
1471 | {
1472 | var self = this;
1473 | self.nodeLookup = NeatGenome.Help.CreateGIDLookup(self.nodes);
1474 |
1475 | var nodeConnectionLookup = {};
1476 |
1477 | self.connections.forEach(function(connection){
1478 |
1479 | //what node is this connections target? That makes this an incoming connection
1480 | NeatGenome.BuildNeuronConnectionLookupTable_NewConnection(nodeConnectionLookup,
1481 | self.nodeLookup,connection.targetID, connection, 'incoming');
1482 |
1483 | //what node is this connectino's source? That makes this an outgoing connection for the node
1484 | NeatGenome.BuildNeuronConnectionLookupTable_NewConnection(nodeConnectionLookup,
1485 | self.nodeLookup, connection.sourceID, connection, 'outgoing');
1486 | });
1487 |
1488 | return nodeConnectionLookup;
1489 | };
1490 |
1491 | ///
1492 | /// We define a simple neuron structure as a neuron that has a single outgoing or single incoming connection.
1493 | /// With such a structure we can easily eliminate the neuron and shift it's connections to an adjacent neuron.
1494 | /// If the neuron's non-linearity was not being used then such a mutation is a simplification of the network
1495 | /// structure that shouldn't adversly affect its functionality.
1496 | ///
1497 | NeatGenome.prototype.mutate_DeleteSimpleNeuronStructure = function(newConnectionTable, np)
1498 | {
1499 |
1500 | var self = this;
1501 |
1502 | // We will use the NeuronConnectionLookupTable to find the simple structures.
1503 | var nodeConnectionLookup = self.buildNeuronConnectionLookupTable();
1504 |
1505 |
1506 | // Build a list of candidate simple neurons to choose from.
1507 | var simpleNeuronIdList = [];
1508 |
1509 | for(var lookupKey in nodeConnectionLookup)
1510 | {
1511 | var lookup = nodeConnectionLookup[lookupKey];
1512 |
1513 |
1514 | // If we test the connection count with <=1 then we also pick up neurons that are in dead-end circuits,
1515 | // RemoveSimpleNeuron is then able to delete these neurons from the network structure along with any
1516 | // associated connections.
1517 | // All neurons that are part of a module would appear to be dead-ended, but skip removing them anyway.
1518 | if (lookup.node.nodeType == NodeType.hidden
1519 | && !(lookup.node.activationFunction.functionID == 'ModuleInputNeuron')
1520 | && !(lookup.node.activationFunction.functionID == 'ModuleOutputNeuron') ) {
1521 | if((lookup.incoming.length<=1) || (lookup.outgoing.length<=1))
1522 | simpleNeuronIdList.push(lookup.node.gid);
1523 | }
1524 | }
1525 |
1526 | // Are there any candiate simple neurons?
1527 | if(simpleNeuronIdList.length==0)
1528 | { // No candidate neurons. As a fallback lets delete a connection.
1529 | self.mutate_DeleteConnection();
1530 | return false;
1531 | }
1532 |
1533 | // Pick a simple neuron at random.
1534 | var idx = utilities.next(simpleNeuronIdList.length);//Math.floor(utilities.nextDouble() * simpleNeuronIdList.length);
1535 | var nid = simpleNeuronIdList[idx];
1536 | self.removeSimpleNeuron(nodeConnectionLookup, nid, newConnectionTable, np);
1537 |
1538 | return true;
1539 | };
1540 |
1541 | NeatGenome.prototype.removeSimpleNeuron = function(nodeConnectionLookup, nid, newConnectionTable, np)
1542 | {
1543 | var self = this;
1544 | // Create new connections that connect all of the incoming and outgoing neurons
1545 | // that currently exist for the simple neuron.
1546 | var lookup = nodeConnectionLookup[nid];
1547 |
1548 | lookup.incoming.forEach(function(incomingConnection)
1549 | {
1550 | lookup.outgoing.forEach(function(outgoingConnection){
1551 |
1552 | if(!self.testForExistingConnectionInnovation(incomingConnection.sourceID, outgoingConnection.targetID))
1553 | { // Connection doesnt already exists.
1554 |
1555 | // Test for matching connection within NewConnectionGeneTable.
1556 | var connectionKey = "(" + incomingConnection.sourceID + "," + outgoingConnection.targetID + ")";
1557 |
1558 | //new ConnectionEndpointsStruct(incomingConnection.SourceNeuronId,
1559 | // outgoi//ngConnection.TargetNeuronId);
1560 | var existingConnection = newConnectionTable[connectionKey];
1561 | var newConnection;
1562 | var nextID = NeatGenome.Help.nextInnovationID();
1563 | if(existingConnection==null)
1564 | { // No matching connection found. Create a connection with a new ID.
1565 | newConnection = new NeatConnection(nextID,
1566 | (utilities.nextDouble() * np.connectionWeightRange) - np.connectionWeightRange/2.0,
1567 | {sourceID:incomingConnection.sourceID, targetID: outgoingConnection.targetID});
1568 | // new ConnectionGene(ea.NextInnovationId,
1569 | // incomingConnection.SourceNeuronId,
1570 | // outgoingConnection.TargetNeuronId,
1571 | // (Utilities.NextDouble() * ea.NeatParameters.connectionWeightRange) - ea.NeatParameters.connectionWeightRange/2.0);
1572 |
1573 | // Register the new ID with NewConnectionGeneTable.
1574 | newConnectionTable[connectionKey] = newConnection;
1575 |
1576 | // Add the new gene to the genome.
1577 | self.connections.push(newConnection);
1578 | }
1579 | else
1580 | { // Matching connection found. Re-use its ID.
1581 | newConnection = new NeatConnection(existingConnection.gid,
1582 | (utilities.nextDouble() * np.connectionWeightRange) - np.connectionWeightRange/2.0,
1583 | {sourceID:incomingConnection.sourceID, targetID: outgoingConnection.targetID});
1584 |
1585 | // Add the new gene to the genome. Use InsertIntoPosition() to ensure we don't break the sort
1586 | // order of the connection genes.
1587 | NeatGenome.Help.insertByInnovation(newConnection, self.connections);
1588 | }
1589 |
1590 | }
1591 |
1592 | });
1593 |
1594 | });
1595 |
1596 |
1597 | lookup.incoming.forEach(function(incomingConnection, inIx)
1598 | {
1599 | for(var i=0; i < self.connections.length; i++)
1600 | {
1601 | if(self.connections[i].gid == incomingConnection.gid)
1602 | {
1603 | self.connections.splice(i,1);
1604 | break;
1605 | }
1606 | }
1607 | });
1608 |
1609 | lookup.outgoing.forEach(function(outgoingConnection, inIx)
1610 | {
1611 | if(outgoingConnection.targetID != nid)
1612 | {
1613 | for(var i=0; i < self.connections.length; i++)
1614 | {
1615 | if(self.connections[i].gid == outgoingConnection.gid)
1616 | {
1617 | self.connections.splice(i,1);
1618 | break;
1619 | }
1620 | }
1621 | }
1622 | });
1623 |
1624 | // Delete the simple neuron - it no longer has any connections to or from it.
1625 | for(var i=0; i < self.nodes.length; i++)
1626 | {
1627 | if(self.nodes[i].gid == nid)
1628 | {
1629 | self.nodes.splice(i,1);
1630 | break;
1631 | }
1632 | }
1633 |
1634 |
1635 | };
1636 |
--------------------------------------------------------------------------------
/genome/neatNode.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies.
3 | */
4 | //none
5 |
6 | /**
7 | * Expose `NeatNode`.
8 | */
9 |
10 | module.exports = NeatNode;
11 |
12 | /**
13 | * Initialize a new NeatNode.
14 | *
15 | * @param {String} gid
16 | * @param {Object,String} aFunc
17 | * @param {Number} layer
18 | * @param {Object} typeObj
19 | * @api public
20 | */
21 | function NeatNode(gid, aFunc, layer, typeObj) {
22 |
23 | var self = this;
24 |
25 | //gids are strings not numbers -- make it so
26 | self.gid = typeof gid === "number" ? "" + gid : gid;
27 | //we only story the string of the activation funciton
28 | //let cppns deal with actual act functions
29 | self.activationFunction = aFunc.functionID || aFunc;
30 |
31 | self.nodeType = typeObj.type;
32 |
33 | self.layer = (typeof layer === 'string' ? parseFloat(layer) : layer);
34 |
35 | //TODO: Create step tests, include in constructor
36 | self.step = 0;
37 |
38 | self.bias = 0;
39 | }
40 |
41 | NeatNode.INPUT_LAYER = 0.0;
42 | NeatNode.OUTPUT_LAYER = 10.0;
43 |
44 | NeatNode.Copy = function(otherNode)
45 | {
46 | return new NeatNode(otherNode.gid, otherNode.activationFunction, otherNode.layer, {type: otherNode.nodeType});
47 | };
--------------------------------------------------------------------------------
/neat.js:
--------------------------------------------------------------------------------
1 | var neatjs = {};
2 |
3 | //export the cppn library
4 | module.exports = neatjs;
5 |
6 | //nodes and connections!
7 | neatjs.neatNode = require('./genome/neatNode.js');
8 | neatjs.neatConnection = require('./genome/neatConnection.js');
9 | neatjs.neatGenome = require('./genome/neatGenome.js');
10 |
11 | //all the activations your heart could ever hope for
12 | neatjs.iec = require('./evolution/iec.js');
13 | neatjs.multiobjective = require('./evolution/multiobjective.js');
14 | neatjs.novelty = require('./evolution/novelty.js');
15 |
16 | //neatHelp
17 | neatjs.neatDecoder = require('./neatHelp/neatDecoder.js');
18 | neatjs.neatHelp = require('./neatHelp/neatHelp.js');
19 | neatjs.neatParameters = require('./neatHelp/neatParameters.js');
20 |
21 | //and the utilities to round it out!
22 | neatjs.genomeSharpToJS = require('./utility/genomeSharpToJS.js');
23 |
24 | //exporting the node type
25 | neatjs.NodeType = require('./types/nodeType.js');
26 |
27 |
28 |
--------------------------------------------------------------------------------
/neatHelp/neatDecoder.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 CPPN = cppnjs.cppn;
11 | var CPPNConnection = cppnjs.cppnConnection;
12 |
13 | //going to need to read node types appropriately
14 | var NodeType = require('../types/nodeType.js');
15 |
16 | /**
17 | * Expose `NeatDecoder`.
18 | */
19 |
20 | var neatDecoder = {};
21 |
22 | module.exports = neatDecoder;
23 |
24 | /**
25 | * Decodes a neatGenome in a cppn.
26 | *
27 | * @param {Object} ng
28 | * @param {String} activationFunction
29 | * @api public
30 | */
31 | neatDecoder.DecodeToFloatFastConcurrentNetwork = function(ng, activationFunction)
32 | {
33 | var outputNeuronCount = ng.outputNodeCount;
34 | var neuronGeneCount = ng.nodes.length;
35 |
36 | var biasList = [];
37 | for(var b=0; b< neuronGeneCount; b++)
38 | biasList.push(0);
39 |
40 | // Slightly inefficient - determine the number of bias nodes. Fortunately there is not actually
41 | // any reason to ever have more than one bias node - although there may be 0.
42 |
43 | var activationFunctionArray = [];
44 | for(var i=0; i < neuronGeneCount; i++){
45 | activationFunctionArray.push("");
46 | }
47 |
48 | var nodeIdx=0;
49 | for(; nodeIdx=0 && fastConnectionArray[connectionIdx].targetNeuronIdx>=0, "invalid idx");
90 |
91 | fastConnectionArray[connectionIdx].weight = connection.weight;
92 | fastConnectionArray[connectionIdx].learningRate = connection.learningRate;
93 | fastConnectionArray[connectionIdx].a = connection.a;
94 | fastConnectionArray[connectionIdx].b = connection.b;
95 | fastConnectionArray[connectionIdx].c = connection.c;
96 |
97 | // connectionIdx++;
98 | }
99 |
100 | // Now sort the connection array on sourceNeuronIdx, secondary sort on targetNeuronIdx.
101 | // Using Array.Sort is 10 times slower than the hand-coded sorting routine. See notes on that routine for more
102 | // information. Also note that in tests that this sorting did no t actually improve the speed of the network!
103 | // However, it may have a benefit for CPUs with small caches or when networks are very large, and since the new
104 | // sort takes up hardly any time for even large networks, it seems reasonable to leave in the sort.
105 | //Array.Sort(fastConnectionArray, fastConnectionComparer);
106 | //if(fastConnectionArray.Length>1)
107 | // QuickSortFastConnections(0, fastConnectionArray.Length-1);
108 |
109 | return new CPPN(biasNodeCount, inputNeuronCount,
110 | outputNeuronCount, neuronGeneCount,
111 | fastConnectionArray, biasList, activationFunctionArray);
112 |
113 | };
114 |
115 |
--------------------------------------------------------------------------------
/neatHelp/neatHelp.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies.
3 | */
4 | var uuid = require('win-utils').cuid;
5 | /**
6 | * Expose `neatHelp`.
7 | */
8 |
9 | var neatHelp = {};
10 |
11 | module.exports = neatHelp;
12 |
13 | //define helper types!
14 | neatHelp.CorrelationType =
15 | {
16 | matchedConnectionGenes : 0,
17 | disjointConnectionGene : 1,
18 | excessConnectionGene : 2
19 | };
20 |
21 | neatHelp.CorrelationStatistics = function(){
22 |
23 | var self= this;
24 | self.matchingCount = 0;
25 | self.disjointConnectionCount = 0;
26 | self.excessConnectionCount = 0;
27 | self.connectionWeightDelta = 0;
28 | };
29 |
30 | neatHelp.CorrelationItem = function(correlationType, conn1, conn2)
31 | {
32 | var self= this;
33 | self.correlationType = correlationType;
34 | self.connection1 = conn1;
35 | self.connection2 = conn2;
36 | };
37 |
38 |
39 | neatHelp.CorrelationResults = function()
40 | {
41 | var self = this;
42 |
43 | self.correlationStatistics = new neatHelp.CorrelationStatistics();
44 | self.correlationList = [];
45 |
46 | };
47 |
48 | //TODO: Integrity check by GlobalID
49 | neatHelp.CorrelationResults.prototype.performIntegrityCheckByInnovation = function()
50 | {
51 | var prevInnovationId= "";
52 |
53 | var self = this;
54 |
55 | for(var i=0; i< self.correlationList.length; i++){
56 | var correlationItem = self.correlationList[i];
57 |
58 | switch(correlationItem.correlationType)
59 | {
60 | // Disjoint and excess genes.
61 | case neatHelp.CorrelationType.disjointConnectionGene:
62 | case neatHelp.CorrelationType.excessConnectionGene:
63 | // Disjoint or excess gene.
64 | if( (!correlationItem.connection1 && !correlationItem.connection2)
65 | || (correlationItem.connection1 && correlationItem.connection2))
66 | { // Precisely one gene should be present.
67 | return false;
68 | }
69 | if(correlationItem.connection1)
70 | {
71 | if(uuid.isLessThan(correlationItem.connection1.gid, prevInnovationId) || correlationItem.connection1.gid == prevInnovationId)
72 | return false;
73 |
74 | prevInnovationId = correlationItem.connection1.gid;
75 | }
76 | else // ConnectionGene2 is present.
77 | {
78 | if(uuid.isLessThan(correlationItem.connection2.gid, prevInnovationId) || correlationItem.connection2.gid == prevInnovationId)
79 | return false;
80 |
81 | prevInnovationId = correlationItem.connection2.gid;
82 | }
83 |
84 | break;
85 | case neatHelp.CorrelationType.matchedConnectionGenes:
86 |
87 | if(!correlationItem.connection1 || !correlationItem.connection2)
88 | return false;
89 |
90 | if( (correlationItem.connection1.gid != correlationItem.connection2.gid)
91 | || (correlationItem.connection1.sourceID != correlationItem.connection2.sourceID)
92 | || (correlationItem.connection1.targetID != correlationItem.connection2.targetID))
93 | return false;
94 |
95 | // Innovation ID's should be in order and not duplicated.
96 | if(uuid.isLessThan(correlationItem.connection1.gid, prevInnovationId) || correlationItem.connection1.gid == prevInnovationId)
97 | return false;
98 |
99 | prevInnovationId = correlationItem.connection1.gid;
100 |
101 | break;
102 | }
103 | }
104 |
105 | return true;
106 | };
107 |
--------------------------------------------------------------------------------
/neatHelp/neatParameters.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies.
3 | */
4 | //none
5 |
6 | /**
7 | * Expose `neatParameters`.
8 | */
9 | module.exports = NeatParameters;
10 |
11 | var DEFAULT_POPULATION_SIZE = 150;
12 | var DEFAULT_P_INITIAL_POPULATION_INTERCONNECTIONS = 1.00;//DAVID 0.05F;
13 |
14 | var DEFAULT_P_OFFSPRING_ASEXUAL = 0.5;
15 | var DEFAULT_P_OFFSPRING_SEXUAL = 0.5;
16 | var DEFAULT_P_INTERSPECIES_MATING = 0.01;
17 |
18 | var DEFAULT_P_DISJOINGEXCESSGENES_RECOMBINED = 0.1;
19 |
20 | //----- High level mutation proportions
21 | var DEFAULT_P_MUTATE_CONNECTION_WEIGHTS = 0.988;
22 | var DEFAULT_P_MUTATE_ADD_NODE = 0.002;
23 | var DEFAULT_P_MUTATE_ADD_MODULE = 0.0;
24 | var DEFAULT_P_MUTATE_ADD_CONNECTION = 0.018;
25 | var DEFAULT_P_MUTATE_CHANGE_ACTIVATIONS = 0.001;
26 | var DEFAULT_P_MUTATE_DELETE_CONNECTION = 0.001;
27 | var DEFAULT_P_MUTATE_DELETE_SIMPLENEURON = 0.00;
28 | var DEFAULT_N_MUTATE_ACTIVATION = 0.01;
29 |
30 | //-----
31 | var DEFAULT_COMPATIBILITY_THRESHOLD = 8 ;
32 | var DEFAULT_COMPATIBILITY_DISJOINT_COEFF = 1.0;
33 | var DEFAULT_COMPATIBILITY_EXCESS_COEFF = 1.0;
34 | var DEFAULT_COMPATIBILITY_WEIGHTDELTA_COEFF = 0.05;
35 |
36 | var DEFAULT_ELITISM_PROPORTION = 0.2;
37 | var DEFAULT_SELECTION_PROPORTION = 0.2;
38 |
39 | var DEFAULT_TARGET_SPECIES_COUNT_MIN = 6;
40 | var DEFAULT_TARGET_SPECIES_COUNT_MAX = 10;
41 |
42 | var DEFAULT_SPECIES_DROPOFF_AGE = 200;
43 |
44 | var DEFAULT_PRUNINGPHASE_BEGIN_COMPLEXITY_THRESHOLD = 50;
45 | var DEFAULT_PRUNINGPHASE_BEGIN_FITNESS_STAGNATION_THRESHOLD = 10;
46 | var DEFAULT_PRUNINGPHASE_END_COMPLEXITY_STAGNATION_THRESHOLD = 15;
47 |
48 | var DEFAULT_CONNECTION_WEIGHT_RANGE = 10.0;
49 | // public const double DEFAULT_CONNECTION_MUTATION_SIGMA = 0.015;
50 |
51 | var DEFAULT_ACTIVATION_PROBABILITY = 1.0;
52 |
53 | NeatParameters.ConnectionPerturbationType =
54 | {
55 | ///
56 | /// Reset weights.
57 | ///
58 | reset : 0,
59 |
60 | ///
61 | /// Jiggle - even distribution
62 | ///
63 | jiggleEven :1
64 |
65 | ///
66 | /// Jiggle - normal distribution
67 | ///
68 | // jiggleND : 2
69 | };
70 | NeatParameters.ConnectionSelectionType =
71 | {
72 | ///
73 | /// Select a proportion of the weights in a genome.
74 | ///
75 | proportional :0,
76 |
77 | ///
78 | /// Select a fixed number of weights in a genome.
79 | ///
80 | fixedQuantity :1
81 | };
82 |
83 | NeatParameters.ConnectionMutationParameterGroup = function(
84 | activationProportion,
85 | perturbationType,
86 | selectionType,
87 | proportion,
88 | quantity,
89 | perturbationFactor,
90 | sigma)
91 | {
92 | var self = this;
93 | ///
94 | /// This group's activation proportion - relative to the totalled
95 | /// ActivationProportion for all groups.
96 | ///
97 | self.activationProportion = activationProportion;
98 |
99 | ///
100 | /// The type of mutation that this group represents.
101 | ///
102 | self.perturbationType = perturbationType;
103 |
104 | ///
105 | /// The type of connection selection that this group represents.
106 | ///
107 | self.selectionType = selectionType;
108 |
109 | ///
110 | /// Specifies the proportion for SelectionType.Proportional
111 | ///
112 | self.proportion=proportion ;
113 |
114 | ///
115 | /// Specifies the quantity for SelectionType.FixedQuantity
116 | ///
117 | self.quantity= quantity;
118 |
119 | ///
120 | /// The perturbation factor for ConnectionPerturbationType.JiggleEven.
121 | ///
122 | self.perturbationFactor= perturbationFactor;
123 |
124 | ///
125 | /// Sigma for for ConnectionPerturbationType.JiggleND.
126 | ///
127 | self.sigma= sigma;
128 | };
129 |
130 | NeatParameters.ConnectionMutationParameterGroup.Copy = function(copyFrom)
131 | {
132 | return new NeatParameters.ConnectionMutationParameterGroup(
133 | copyFrom.ActivationProportion,
134 | copyFrom.PerturbationType,
135 | copyFrom.SelectionType,
136 | copyFrom.Proportion,
137 | copyFrom.Quantity,
138 | copyFrom.PerturbationFactor,
139 | copyFrom.Sigma
140 | );
141 | };
142 |
143 | function NeatParameters()
144 | {
145 | var self = this;
146 | self.histogramBins = [];
147 | self.archiveThreshold=3.00;
148 | self.tournamentSize=4;
149 | self.noveltySearch=false;
150 | self.noveltyHistogram=false;
151 | self.noveltyFixed=false;
152 | self.noveltyFloat=false;
153 | self.multiobjective=false;
154 |
155 | self.allowSelfConnections = false;
156 |
157 | self.populationSize = DEFAULT_POPULATION_SIZE;
158 | self.pInitialPopulationInterconnections = DEFAULT_P_INITIAL_POPULATION_INTERCONNECTIONS;
159 |
160 | self.pOffspringAsexual = DEFAULT_P_OFFSPRING_ASEXUAL;
161 | self.pOffspringSexual = DEFAULT_P_OFFSPRING_SEXUAL;
162 | self.pInterspeciesMating = DEFAULT_P_INTERSPECIES_MATING;
163 |
164 | self.pDisjointExcessGenesRecombined = DEFAULT_P_DISJOINGEXCESSGENES_RECOMBINED;
165 |
166 | //----- High level mutation proportions
167 | self.pMutateConnectionWeights = DEFAULT_P_MUTATE_CONNECTION_WEIGHTS;
168 | self.pMutateAddNode = DEFAULT_P_MUTATE_ADD_NODE;
169 | self.pMutateAddModule = DEFAULT_P_MUTATE_ADD_MODULE;
170 | self.pMutateAddConnection = DEFAULT_P_MUTATE_ADD_CONNECTION;
171 | self.pMutateDeleteConnection = DEFAULT_P_MUTATE_DELETE_CONNECTION;
172 | self.pMutateDeleteSimpleNeuron = DEFAULT_P_MUTATE_DELETE_SIMPLENEURON;
173 | self.pMutateChangeActivations = DEFAULT_P_MUTATE_CHANGE_ACTIVATIONS;
174 | self.pNodeMutateActivationRate = DEFAULT_N_MUTATE_ACTIVATION;
175 |
176 | //----- Build a default ConnectionMutationParameterGroupList.
177 | self.connectionMutationParameterGroupList = [];
178 |
179 | self.connectionMutationParameterGroupList.push(new NeatParameters.ConnectionMutationParameterGroup(0.125, NeatParameters.ConnectionPerturbationType.jiggleEven,
180 | NeatParameters.ConnectionSelectionType.proportional, 0.5, 0, 0.05, 0.0));
181 |
182 | self.connectionMutationParameterGroupList.push(new NeatParameters.ConnectionMutationParameterGroup(0.5, NeatParameters.ConnectionPerturbationType.jiggleEven,
183 | NeatParameters.ConnectionSelectionType.proportional, 0.1, 0, 0.05, 0.0));
184 |
185 | self.connectionMutationParameterGroupList.push(new NeatParameters.ConnectionMutationParameterGroup(0.125, NeatParameters.ConnectionPerturbationType.jiggleEven,
186 | NeatParameters.ConnectionSelectionType.fixedQuantity, 0.0, 1, 0.05, 0.0));
187 |
188 | self.connectionMutationParameterGroupList.push(new NeatParameters.ConnectionMutationParameterGroup(0.125, NeatParameters.ConnectionPerturbationType.reset,
189 | NeatParameters.ConnectionSelectionType.proportional, 0.1, 0, 0.0, 0.0));
190 |
191 | self.connectionMutationParameterGroupList.push(new NeatParameters.ConnectionMutationParameterGroup(0.125, NeatParameters.ConnectionPerturbationType.reset,
192 | NeatParameters.ConnectionSelectionType.fixedQuantity, 0.0, 1, 0.0, 0.0));
193 |
194 | //-----
195 | self.compatibilityThreshold = DEFAULT_COMPATIBILITY_THRESHOLD;
196 | self.compatibilityDisjointCoeff = DEFAULT_COMPATIBILITY_DISJOINT_COEFF;
197 | self.compatibilityExcessCoeff = DEFAULT_COMPATIBILITY_EXCESS_COEFF;
198 | self.compatibilityWeightDeltaCoeff = DEFAULT_COMPATIBILITY_WEIGHTDELTA_COEFF;
199 |
200 | self.elitismProportion = DEFAULT_ELITISM_PROPORTION;
201 | self.selectionProportion = DEFAULT_SELECTION_PROPORTION;
202 |
203 | self.targetSpeciesCountMin = DEFAULT_TARGET_SPECIES_COUNT_MIN;
204 | self.targetSpeciesCountMax = DEFAULT_TARGET_SPECIES_COUNT_MAX;
205 |
206 | self.pruningPhaseBeginComplexityThreshold = DEFAULT_PRUNINGPHASE_BEGIN_COMPLEXITY_THRESHOLD;
207 | self.pruningPhaseBeginFitnessStagnationThreshold = DEFAULT_PRUNINGPHASE_BEGIN_FITNESS_STAGNATION_THRESHOLD;
208 | self.pruningPhaseEndComplexityStagnationThreshold = DEFAULT_PRUNINGPHASE_END_COMPLEXITY_STAGNATION_THRESHOLD;
209 |
210 | self.speciesDropoffAge = DEFAULT_SPECIES_DROPOFF_AGE;
211 |
212 | self.connectionWeightRange = DEFAULT_CONNECTION_WEIGHT_RANGE;
213 |
214 | //DAVID
215 | self.activationProbabilities = [];//new double[4];
216 | self.activationProbabilities.push(DEFAULT_ACTIVATION_PROBABILITY);
217 | self.activationProbabilities.push(0);
218 | self.activationProbabilities.push(0);
219 | self.activationProbabilities.push(0);
220 | };
221 |
222 | NeatParameters.Copy = function(copyFrom)
223 | {
224 | var self = new NeatParameters();
225 |
226 | //paul - joel originally
227 | self.noveltySearch = copyFrom.noveltySearch;
228 | self.noveltyHistogram = copyFrom.noveltyHistogram;
229 | self.noveltyFixed = copyFrom.noveltyFixed;
230 | self.noveltyFloat = copyFrom.noveltyFloat;
231 | self.histogramBins = copyFrom.histogramBins;
232 |
233 |
234 | self.allowSelfConnections = copyFrom.allowSelfConnections;
235 |
236 | self.populationSize = copyFrom.populationSize;
237 |
238 | self.pOffspringAsexual = copyFrom.pOffspringAsexual;
239 | self.pOffspringSexual = copyFrom.pOffspringSexual;
240 | self.pInterspeciesMating = copyFrom.pInterspeciesMating;
241 |
242 | self.pDisjointExcessGenesRecombined = copyFrom.pDisjointExcessGenesRecombined;
243 |
244 | self.pMutateConnectionWeights = copyFrom.pMutateConnectionWeights;
245 | self.pMutateAddNode = copyFrom.pMutateAddNode;
246 | self.pMutateAddModule = copyFrom.pMutateAddModule;
247 | self.pMutateAddConnection = copyFrom.pMutateAddConnection;
248 | self.pMutateDeleteConnection = copyFrom.pMutateDeleteConnection;
249 | self.pMutateDeleteSimpleNeuron = copyFrom.pMutateDeleteSimpleNeuron;
250 |
251 | // Copy the list.
252 | self.connectionMutationParameterGroupList = [];
253 | copyFrom.connectionMutationParameterGroupList.forEach(function(c){
254 | self.connectionMutationParameterGroupList.push(NeatParameters.ConnectionMutationParameterGroup.Copy(c));
255 |
256 | });
257 |
258 | self.compatibilityThreshold = copyFrom.compatibilityThreshold;
259 | self.compatibilityDisjointCoeff = copyFrom.compatibilityDisjointCoeff;
260 | self.compatibilityExcessCoeff = copyFrom.compatibilityExcessCoeff;
261 | self.compatibilityWeightDeltaCoeff = copyFrom.compatibilityWeightDeltaCoeff;
262 |
263 | self.elitismProportion = copyFrom.elitismProportion;
264 | self.selectionProportion = copyFrom.selectionProportion;
265 |
266 | self.targetSpeciesCountMin = copyFrom.targetSpeciesCountMin;
267 | self.targetSpeciesCountMax = copyFrom.targetSpeciesCountMax;
268 |
269 | self.pruningPhaseBeginComplexityThreshold = copyFrom.pruningPhaseBeginComplexityThreshold;
270 | self.pruningPhaseBeginFitnessStagnationThreshold = copyFrom.pruningPhaseBeginFitnessStagnationThreshold;
271 | self.pruningPhaseEndComplexityStagnationThreshold = copyFrom.pruningPhaseEndComplexityStagnationThreshold;
272 |
273 | self.speciesDropoffAge = copyFrom.speciesDropoffAge;
274 |
275 | self.connectionWeightRange = copyFrom.connectionWeightRange;
276 |
277 | return self;
278 | };
279 |
280 |
281 |
--------------------------------------------------------------------------------
/package.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 | "keywords": [
6 | "Neural Network",
7 | "NN",
8 | "Artificial Neural Networks",
9 | "ANN",
10 | "CPPN",
11 | "NEAT",
12 | "HyperNEAT"
13 | ],
14 | "author": {
15 | "name": "Paul Szerlip",
16 | "email": "Paul.Szerlip@cs.ucf.edu",
17 | "url": "http://designforcode.com/"
18 | },
19 | "main": "neat.js",
20 | "repository": {
21 | "type": "git",
22 | "url": "https://github.com/OptimusLime/neatjs.git"
23 | },
24 | "bugs": "https://github.com/OptimusLime/neatjs/issues",
25 | "license": "MIT",
26 | "dependencies": {
27 | "cppnjs": "0.x.x",
28 | "win-utils": "*"
29 | },
30 | "devDependencies": {
31 | "mocha": "1.1.x",
32 | "should": "1.1.x",
33 | "xml2js": "0.2.x"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/test/connectionTest.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var should = require('should');
3 |
4 | var NeatConnection = require('../genome/neatConnection.js');
5 |
6 | describe('Creating a new connection',function(){
7 | var connection;
8 |
9 | var sourceID = 5;
10 | var targetID = 8;
11 | var gid = Math.floor(Math.random()*1000);
12 |
13 | var weight = .4;
14 |
15 | //test the other vanilla connection type
16 | before(function(done){
17 |
18 | connection = new NeatConnection(gid, weight, {sourceID: sourceID, targetID: targetID});
19 | done();
20 | });
21 |
22 | it('should have matching gid, weight, source, and target',function(){
23 | connection.gid.should.equal(gid.toString());
24 | connection.weight.should.equal(weight);
25 | connection.sourceID.should.equal(sourceID.toString());
26 | connection.targetID.should.equal(targetID.toString());
27 | });
28 |
29 |
30 | //test the other stringify reversing functions
31 | before(function(done){
32 |
33 | connection = new NeatConnection(gid.toString(), weight.toString(), {sourceID: sourceID.toString(), targetID: targetID.toString()});
34 | done();
35 | });
36 |
37 | it('should have not stringified gid, weight, source, and target',function(){
38 | (typeof connection.gid).should.not.equal('number');
39 | (typeof connection.weight).should.not.equal('string');
40 | (typeof connection.sourceID).should.not.equal('number');
41 | (typeof connection.targetID).should.not.equal('number');
42 | });
43 |
44 | //test the other vanilla connection type
45 | before(function(done){
46 |
47 | connection = new NeatConnection(gid, weight, {sourceID: sourceID, targetID: targetID});
48 | connection = NeatConnection.Copy(connection);
49 | done();
50 | });
51 |
52 | it('should have cloned gid, weight, source, and target',function(){
53 | connection.gid.should.equal(gid.toString());
54 | connection.weight.should.equal(weight);
55 | connection.sourceID.should.equal(sourceID.toString());
56 | connection.targetID.should.equal(targetID.toString());
57 | });
58 |
59 | });
60 |
--------------------------------------------------------------------------------
/test/exampleGenome.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/test/genomeConversionTest.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var should = require('should');
3 | var fs = require('fs');
4 | var util = require('util');
5 | var xml2js = require('xml2js');
6 |
7 | var genomeSharpToJS = require('../utility/genomeSharpToJS.js');
8 |
9 | describe('Testing C# to JS Genome converstion',function(){
10 |
11 | it('should correclty load an example iesor seed genome', function(done){
12 |
13 | var attrkey = '$';
14 | var parser = new xml2js.Parser({explicitArray : false, attrkey: attrkey, mergeAttrs: true});
15 |
16 |
17 | parser.addListener('end', function(result) {
18 |
19 |
20 | var genome = result['genome'];
21 |
22 | //slightly confusing, but how it's parsed by xml2js
23 | var neurons = genome['neurons']['neuron'];
24 | var connections = genome['connections']['connection'];
25 |
26 | // console.log(util.inspect(genome, false, null));
27 |
28 | // console.log(genome);
29 | //now we should have a defined genome, let's pass it along
30 | var ng = genomeSharpToJS.ConvertCSharpToJS(genome);
31 |
32 |
33 | ng.nodes.length.should.equal(neurons.length);
34 | ng.connections.length.should.equal(connections.length);
35 |
36 | for(var n =0; n < ng.nodes.length; n++)
37 | {
38 | var node = ng.nodes[n];
39 | var other = neurons[n];
40 |
41 | node.gid.should.equal((other.id));
42 | node.activationFunction.should.equal(other.activationFunction);
43 | node.layer.should.equal(parseFloat(other.layer));
44 | node.nodeType.should.equal(genomeSharpToJS.NeuronTypeToNodeType(other.type));
45 | }
46 | for(var c =0; c < ng.connections.length; c++)
47 | {
48 | var conn = ng.connections[c];
49 | var other = connections[c];
50 | other.weight = parseFloat(other.weight);
51 |
52 | conn.gid.should.equal((other['innov-id']))
53 | parseFloat(conn.weight.toFixed(3)).should.equal(parseFloat(other.weight.toFixed(3)));
54 | conn.sourceID.should.equal((other['src-id']));
55 | conn.targetID.should.equal((other['tgt-id']));
56 | }
57 |
58 | done();
59 | });
60 |
61 | fs.readFile(__dirname + '/exampleGenome.xml', 'utf8', function (err,data) {
62 | if (err) {
63 | console.log(err);
64 | throw err;
65 | }
66 | //we need to parse the data, and create some genomes!
67 | parser.parseString(data);//, function (err, result) {
68 |
69 |
70 |
71 | });
72 | });
73 | });
74 |
--------------------------------------------------------------------------------
/test/helpTest.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var should = require('should');
3 |
4 | var NeatParameters = require('../neatHelp/neatParameters.js');
5 |
6 | describe('Incomplete: testing neatParameters.NeatParameters()',function(){
7 |
8 | it('NeatParameters(): should have certain parameters set by defaul', function(done){
9 |
10 | var np = new NeatParameters();
11 |
12 | //make sure we have the right size group, and setup
13 | np.connectionMutationParameterGroupList.length.should.equal(5);
14 | np.connectionMutationParameterGroupList[0].perturbationType.should.equal(NeatParameters.ConnectionPerturbationType.jiggleEven);
15 | np.connectionMutationParameterGroupList[1].perturbationType.should.equal(NeatParameters.ConnectionPerturbationType.jiggleEven);
16 | np.connectionMutationParameterGroupList[2].perturbationType.should.equal(NeatParameters.ConnectionPerturbationType.jiggleEven);
17 | np.connectionMutationParameterGroupList[3].perturbationType.should.equal(NeatParameters.ConnectionPerturbationType.reset);
18 | np.connectionMutationParameterGroupList[4].perturbationType.should.equal(NeatParameters.ConnectionPerturbationType.reset);
19 |
20 | np.connectionMutationParameterGroupList[0].selectionType.should.equal(NeatParameters.ConnectionSelectionType.proportional);
21 | np.connectionMutationParameterGroupList[1].selectionType.should.equal(NeatParameters.ConnectionSelectionType.proportional);
22 | np.connectionMutationParameterGroupList[2].selectionType.should.equal(NeatParameters.ConnectionSelectionType.fixedQuantity);
23 | np.connectionMutationParameterGroupList[3].selectionType.should.equal(NeatParameters.ConnectionSelectionType.proportional);
24 | np.connectionMutationParameterGroupList[4].selectionType.should.equal(NeatParameters.ConnectionSelectionType.fixedQuantity);
25 |
26 | done();
27 |
28 | });
29 |
30 |
31 | it('NeatDecoder(): should be able to correctly decode genome to CPPN, test against file?', function(done){
32 |
33 | ("Yet to write this test").should.equal("oops");
34 |
35 | });
36 |
37 |
38 | });
--------------------------------------------------------------------------------
/test/htmlLoadTest.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Mocha Tests
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
44 |
45 |
--------------------------------------------------------------------------------
/test/neatTest.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var should = require('should');
3 |
4 | // Create a new test suite for our Bank Account
5 |
6 | describe('NEAT Tests-',function(){
7 | //Test NeatNodes
8 |
9 | //testing high level neat features
10 |
11 | });
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/nodeTest.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var should = require('should');
3 |
4 | var cppnjs = require('cppnjs');
5 | var CPPNActivationFactory = cppnjs.cppnActivationFactory;
6 |
7 | var NodeType = require('../types/nodeType.js');
8 | var NeatNode = require('../genome/neatNode.js');
9 | // var uuid = require('win-utils').cuid;
10 |
11 | var testActivation = function(functionString, value, functionValue)
12 | {
13 | var aFunc;
14 | before(function(done){
15 | aFunc = CPPNActivationFactory.getActivationFunction(functionString);
16 | done();
17 | });
18 |
19 | it(functionString + ' should have {functionID: ' + functionString + '}' ,function(){
20 | aFunc.should.have.property('functionID', functionString);
21 | });
22 |
23 | it(functionString + ' should calculate {f(' + value + ') = ' + functionValue + '}' ,function(){
24 | parseFloat(aFunc.calculate(value).toFixed(15)).should.equal(functionValue);
25 | });
26 | };
27 |
28 | //implemented the following:
29 | //BipolarSigmoid
30 | //PlainSigmoid
31 | //Gaussian
32 | //Linear
33 | //NullFn
34 | //Sine
35 | //StepFunction
36 |
37 | var activationFunctionIDs = [
38 | 'BipolarSigmoid',
39 | 'PlainSigmoid',
40 | 'Gaussian',
41 | 'Linear',
42 | 'NullFn',
43 | 'Sine',
44 | 'Sine2',
45 | 'StepFunction'
46 | ];
47 |
48 | describe('Creating Activation Functions:',function(){
49 |
50 | testActivation('BipolarSigmoid', 0, 0);
51 | testActivation('PlainSigmoid', 0,.5);
52 | testActivation('Gaussian', 0,1);
53 | testActivation('Linear', -1,1);
54 | testActivation('NullFn', 1,0);
55 | testActivation('Sine2', Math.PI/2, 0);
56 | testActivation('Sine', Math.PI/2, 1);
57 | testActivation('StepFunction', .00001,1);
58 |
59 | // it('Should have preloaded all functions now-- missing any means we dont have tests' ,function(done){
60 | //
61 | // //if we aren't equal, we're not testing everything!
62 | // activationFunctionIDs.length.should.equal(CPPNActivationFactory.functions.length);
63 | // done();
64 | // });
65 | //
66 |
67 | //finally, test out a fake activation function
68 | (function(){
69 | var aFunc = CPPNActivationFactory.getActivationFunction('FakeActivation')
70 | }).should.throw("Activation Function doesn't exist!");
71 | });
72 |
73 | describe('Random Activation Test',function(){
74 | var aFunc;
75 | before(function(done){
76 | aFunc = CPPNActivationFactory.getRandomActivationFunction()
77 | done();
78 | });
79 |
80 | it('should have random function from activationFunction IDs' ,function(){
81 | var whichFunction;
82 |
83 | activationFunctionIDs.forEach(function(aFunctionID){
84 | if(aFunctionID == aFunc.functionID)
85 | {
86 | whichFunction = aFunctionID;
87 | }
88 | });
89 |
90 | //if we don't pick one of our functions, then which function will be undefined
91 | //there will be a function ID (guaranteed by above test), so they won't be equal
92 | aFunc.functionID.should.equal(whichFunction);
93 | });
94 | });
95 |
96 | describe('Creating a new node',function(){
97 | var node;
98 | var aFunc;
99 | var inCount = 1;
100 | var outCount = 1;
101 | var gid = Math.floor(Math.random()*1000);
102 | var layer = 5;
103 | var aFunctionID = 'BipolarSigmoid';
104 | var type = NodeType.hidden;
105 |
106 | // cantorPair tests invalid now
107 | // before(function(done){
108 | // aFunc = CPPNActivationFactory.getActivationFunction(aFunctionID);
109 | // done();
110 | // });
111 | //
112 | // it('should be able to tell node type from gid, ins, and outs',function(){
113 | //
114 | // gid = cantorPair.xy2cp(0,0);
115 | // node = new NeatNode(gid, aFunc, layer, {inCount: inCount, outCount: outCount});
116 | // node.type.should.equal(NodeType.bias);
117 | //
118 | // gid = cantorPair.xy2cp(1,1);
119 | // node = new NeatNode(gid, aFunc, layer, {inCount: inCount, outCount: outCount});
120 | // node.type.should.equal(NodeType.input);
121 | //
122 | // gid = cantorPair.xy2cp(2,2);
123 | // node = new NeatNode(gid, aFunc, layer, {inCount: inCount, outCount: outCount});
124 | // node.type.should.equal(NodeType.output);
125 | //
126 | // gid = cantorPair.xy2cp(3,3);
127 | // node = new NeatNode(gid, aFunc, layer, {inCount: inCount, outCount: outCount});
128 | // node.type.should.equal(NodeType.hidden);
129 | //
130 | // gid = cantorPair.xy2cp(0,3);
131 | // node = new NeatNode(gid, aFunc, layer, {inCount: inCount, outCount: outCount});
132 | // node.type.should.equal(NodeType.hidden);
133 | // });
134 |
135 | before(function(done){
136 | aFunc = CPPNActivationFactory.getActivationFunction(aFunctionID);
137 | node = new NeatNode(gid, aFunc, layer, {type: type});
138 | done();
139 | });
140 |
141 | it('should have a gid, activation, layer, and type',function(){
142 | node.should.have.property('gid', gid.toString());
143 | node.should.have.property('activationFunction',aFunc.functionID);
144 | node.should.have.property('layer',layer);
145 | node.should.have.property('nodeType',type);
146 | });
147 |
148 |
149 | before(function(done){
150 | aFunc = CPPNActivationFactory.getActivationFunction(aFunctionID);
151 | node = new NeatNode(gid, aFunc, layer.toString(), {type: type});
152 | done();
153 | });
154 |
155 | it('should not be number version of gid, or string version of layer',function(){
156 | //gid is number, node.gid is string => shoudln't be equal
157 | node.gid.should.not.equal(gid);
158 | node.layer.should.not.equal(layer.toString());
159 | (typeof node.activationFunction).should.equal('string');
160 | });
161 |
162 |
163 |
164 | });
165 |
166 | describe('Copying a node',function(){
167 | var node;
168 | var aFunc;
169 | var gid = 0;
170 | var layer = 5;
171 | var aFunctionID = 'BipolarSigmoid';
172 | var type = NodeType.hidden;
173 |
174 | before(function(done){
175 | aFunc = CPPNActivationFactory.getActivationFunction(aFunctionID);
176 | node = new NeatNode(gid, aFunc, layer, {type: type});
177 | node = NeatNode.Copy(node);
178 | done();
179 | });
180 |
181 | it('should have copied gid, activation, layer, and type',function(){
182 | node.should.have.property('gid', gid.toString());
183 | node.should.have.property('activationFunction', aFunc.functionID);
184 | node.should.have.property('layer',layer);
185 | node.should.have.property('nodeType',type);
186 | });
187 | });
--------------------------------------------------------------------------------
/test/utilityTest.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var should = require('should');
3 |
4 | var cppnjs = require('cppnjs');
5 | var utilities = cppnjs.utilities;
6 |
7 | describe('Testing utilities.next()',function(){
8 |
9 | it('next should correctly generate random numbers', function(done){
10 | var randomMax = 10;
11 | var testLength = randomMax*100;
12 |
13 | for(var i=0; i < testLength; i++)
14 | {
15 | var guess = utilities.next(randomMax);
16 | guess.should.be.lessThan(randomMax);
17 | }
18 |
19 | done();
20 | });
21 |
22 | it('RouletteWheel.singleThrowArray(): single throw should correctly generate guesses from array', function(done){
23 |
24 | var probabilities = [];
25 |
26 | var pLength, selection;
27 | var randomMax = 10;
28 | var testLength = 5000;
29 |
30 | for(var i=0; i < testLength; i++)
31 | {
32 |
33 | pLength = utilities.next(randomMax) + 3;
34 | probabilities = [];
35 | selection = utilities.next(pLength);
36 |
37 | for(var p =0; p< pLength; p++)
38 | {
39 | probabilities.push(( p == selection ? 1 : 0));
40 | }
41 |
42 | var guess = utilities.RouletteWheel.singleThrowArray(probabilities);
43 | guess.should.be.equal(selection);
44 |
45 | probabilities = [];
46 | selection = utilities.next(pLength);
47 | var selection2 = utilities.next(pLength);
48 |
49 | while(pLength > 1 && selection2 == selection)
50 | selection2 = utilities.next(pLength);
51 |
52 | for(var p =0; p< pLength; p++)
53 | {
54 | probabilities.push((p == selection || p == selection2 ? .5 : 0));
55 | }
56 |
57 | guess = utilities.RouletteWheel.singleThrowArray(probabilities);
58 | (guess == selection || guess == selection2).should.be.equal(true);
59 | }
60 |
61 | done();
62 | });
63 | it('RouletteWheel.singleThrow(): should generate bool', function(done){
64 |
65 | var testLength = 2000;
66 | for(var i=0; i< testLength; i++){
67 | utilities.RouletteWheel.singleThrow(1).should.equal(true);
68 | }
69 |
70 | for(var i=0; i< testLength; i++){
71 | utilities.RouletteWheel.singleThrow(0).should.equal(false);
72 | }
73 |
74 | done();
75 | });
76 | //
77 |
78 | // it('cantor pair should maybe not get super big', function(done){
79 | // var randomMax = 100;
80 | // var testLength = 1000;//randomMax*100;
81 | //
82 | // var startX =100000, startY =100000;
83 | // var lastGuess = cantorPair.xy2cp(startX,startX);
84 | //
85 | // for(var i=0; i < testLength; i++)
86 | // {
87 | // var guess = cantorPair.xy2cp(startX,startY);
88 | // isFinite(guess).should.be.equal(isFinite(1));
89 | // console.log(guess);
90 | // lastGuess = guess;
91 | // startX+= 1;
92 | // startY +=1;
93 | // }
94 | //
95 | // lastGuess.should.be.greaterThan(randomMax);
96 | //
97 | //
98 | // done();
99 | // })
100 | });
--------------------------------------------------------------------------------
/types/nodeType.js:
--------------------------------------------------------------------------------
1 | var NodeType =
2 | {
3 | bias : "Bias",
4 | input: "Input",
5 | output: "Output",
6 | hidden: "Hidden",
7 | other : "Other"
8 | };
9 |
10 | module.exports = NodeType;
--------------------------------------------------------------------------------
/utility/genomeSharpToJS.js:
--------------------------------------------------------------------------------
1 | //Convert between C# SharpNEAT Genotype encoded in XML into a JS genotype in JSON
2 | //pretty simple
3 |
4 | /**
5 | * Module dependencies.
6 | */
7 |
8 | var NeatGenome = require('../genome/neatGenome.js');
9 | var NeatNode = require('../genome/neatNode.js');
10 | var NeatConnection = require('../genome/neatConnection.js');
11 | var NodeType = require('../types/nodeType.js');
12 |
13 |
14 | /**
15 | * Expose `GenomeConverter`.
16 | */
17 |
18 | var converter = {};
19 |
20 | //we export the convert object, with two functions
21 | module.exports = converter;
22 |
23 | converter.NeuronTypeToNodeType = function(type)
24 | {
25 | switch(type)
26 | {
27 | case "bias":
28 | return NodeType.bias;
29 | case "in":
30 | return NodeType.input;
31 | case "out":
32 | return NodeType.output;
33 | case "hid":
34 | return NodeType.hidden;
35 | default:
36 | throw new Error("inpropper C# neuron type detected");
37 | }
38 | };
39 |
40 | converter.ConvertCSharpToJS = function(xmlGenome)
41 | {
42 |
43 | //we need to parse through a c# version of genome, and make a js genome from it
44 |
45 | var aNeurons = xmlGenome['neurons']['neuron'] || xmlGenome['neurons'];
46 | var aConnections = xmlGenome['connections']['connection'] || xmlGenome['connections'];
47 |
48 |
49 | //we will use nodes and connections to make our genome
50 | var nodes = [], connections = [];
51 | var inCount = 0, outCount = 0;
52 |
53 | for(var i=0; i < aNeurons.length; i++)
54 | {
55 | var csNeuron = aNeurons[i];
56 | var jsNode = new NeatNode(csNeuron.id, csNeuron.activationFunction, csNeuron.layer, {type: converter.NeuronTypeToNodeType(csNeuron.type)});
57 | nodes.push(jsNode);
58 |
59 | if(csNeuron.type == 'in') inCount++;
60 | else if(csNeuron.type == 'out') outCount++;
61 | }
62 |
63 | for(var i=0; i < aConnections.length; i++)
64 | {
65 | var csConnection = aConnections[i];
66 | var jsConnection = new NeatConnection(csConnection['innov-id'], csConnection.weight, {sourceID:csConnection['src-id'], targetID: csConnection['tgt-id']});
67 | connections.push(jsConnection);
68 | }
69 |
70 | var ng = new NeatGenome(xmlGenome['id'], nodes, connections, inCount, outCount);
71 | ng.adaptable = (xmlGenome['adaptable'] == 'True');
72 | ng.modulated = (xmlGenome['modulated'] == 'True');
73 | ng.fitness = xmlGenome['fitness'];
74 | ng.realFitness = xmlGenome['realfitness'];
75 | ng.age = xmlGenome['age'];
76 |
77 | return ng;
78 | };
79 |
--------------------------------------------------------------------------------