· 10 years ago · Sep 01, 2016, 08:58 PM
1using System.Collections.Generic;
2using System.Collections;
3using UnityEngine;
4using System.Linq;
5
6/// <summary>
7/// Handels mutation, crossover, specification, feedforward activation and creation of neural network's genotype.
8/// </summary>
9public class NEATNet {
10
11 private NEATConsultor consultor; //Handles consultor genome sequence
12
13 private List<NEATGene> geneList; //list of the genome sequence for this neural network
14 private List<NEATNode> nodeList; //list of nodes for this neural network
15
16 private int numberOfInputs; //Number of input perceptrons of neural network (including bias)
17 private int numberOfOutputs; //Number of output perceptrons
18 private int[] netID = new int[2]; //ID of this neural network
19
20 private float time; //time to run test on this neural network
21 private float timeLived; //time the neural network actually lived in the test enviroment
22 private float netFitness; //fitness of this neural network
23
24 /// <summary>
25 /// Creating neural network structure from deep copying another network
26 /// </summary>
27 /// <param name="copy">Neural network to deep copy</param>
28 public NEATNet(NEATNet copy) {
29 this.consultor = copy.consultor; //shallow copy consultor
30 this.numberOfInputs = copy.numberOfInputs; //copy number of inputs
31 this.numberOfOutputs = copy.numberOfOutputs; //copy number of outputs
32
33 CopyNodes(copy.nodeList); //deep copy node list
34 CopyGenes(copy.geneList); //deep copy gene list
35
36 this.netID = new int[2]; //reset ID
37 this.time = 0f; //reset time
38 this.netFitness = 0f; //reset fitness
39 this.timeLived = 0f; //reset time lived
40 }
41
42 /// <summary>
43 /// Creating neural network structure using neat packet from database
44 /// </summary>
45 /// <param name="packet">Neat packet received from database</param>
46 /// <param name="consultor">Consultor with master genome and specification information</param>
47 public NEATNet(NEATPacket packet, NEATConsultor consultor) {
48 this.consultor = consultor; //shallow copy consultor
49 this.numberOfInputs = packet.node_inputs; //copy number of inputs
50 this.numberOfOutputs = packet.node_outputs; //copy number of outputs
51
52 int numberOfNodes = packet.node_total; //number of nodes in the network from database
53 int numberOfgenes = packet.gene_total; //number of genes in the network from database
54 int informationSize = NEATGene.GENE_INFORMATION_SIZE; //size of genome information
55
56 geneList = new List<NEATGene>(); //create an empty gene list
57
58 InitilizeNodes(); //initialize initial nodes
59
60 for (int i = numberOfInputs + numberOfOutputs; i < numberOfNodes; i++) { //run through the left over nodes, since (numberOfInputs + numberOfOutputs) where created by initilize node method
61 NEATNode node = new NEATNode(i, NEATNode.HIDDEN_NODE); //create node with index i as id and will be hidden node
62 nodeList.Add(node); //add node to node list
63 }
64
65 float[] geneInformation = packet.genome.Split('_').Select(x => float.Parse(x)).ToArray(); //using Linq libary and delimiters, parse and spilt string genome from neat packet into float array
66
67 for (int i = 0; i < geneInformation.Length; i+=informationSize) { //run through all gene information, 4 information make up 1 gene, thus increment by 4
68 int inno = this.consultor.CheckGeneExistance((int)geneInformation[i], (int)geneInformation[i + 1]); //check if this gene exists in the consultor
69 NEATGene gene = new NEATGene(inno, (int)geneInformation[i], (int)geneInformation[i + 1], geneInformation[i + 2], geneInformation[i + 3] == 1.0? true:false); //create gene
70 geneList.Add(gene); //add gene to the gene list
71 }
72
73 this.netID = new int[2]; //reset ID
74 this.time = 0f; //reset time
75 this.netFitness = 0f; //reset fitness
76 this.timeLived = 0f; //reset time lived
77 }
78
79 /// <summary>
80 /// Creating a primitive network structure (every input connect to every output) from provided parameters
81 /// </summary>
82 /// <param name="consultor">Consultor with master genome and specification information</param>
83 /// <param name="netID">ID of the network</param>
84 /// <param name="numberOfInputs">Number of input perceptrons</param>
85 /// <param name="numberOfOutputs">Number of output perceptrons</param>
86 /// <param name="time">Time to test the network</param>
87 public NEATNet(NEATConsultor consultor, int[] netID, int numberOfInputs, int numberOfOutputs, float time) {
88 this.consultor = consultor; //shallow copy consultor
89 this.netID = new int[] {netID[0], netID[1]}; //copy ID
90 this.numberOfInputs = numberOfInputs; //copy number of inputs
91 this.numberOfOutputs = numberOfOutputs; //copy number of outputs
92 this.time = time; //copy time to test
93
94 this.netFitness = 0f; //reset net fitness
95 this.timeLived = 0f; //reset time lived
96
97 InitilizeNodes(); //initialize initial nodes
98 InitilizeGenes(); //initialize initial gene sequence
99 }
100
101 /// <summary>
102 /// Creating an already designed network structure from given node and gene lists
103 /// </summary>
104 /// <param name="consultor">Consultor with master genome and specification information</param>
105 /// <param name="numberOfInputs">Number of input perceptrons</param>
106 /// <param name="numberOfOutputs">Number of output perceptrons</param>
107 /// <param name="copyNodes">Node list to deep copy</param>
108 /// <param name="copyGenes">Gene list to deep copy</param>
109 public NEATNet(NEATConsultor consultor, int numberOfInputs, int numberOfOutputs, List<NEATNode> copyNodes, List<NEATGene> copyGenes) {
110 this.consultor = consultor; //shallow copy consultor
111 this.numberOfInputs = numberOfInputs; //copy number of inputs
112 this.numberOfOutputs = numberOfOutputs; //copy number of outputs
113
114 CopyNodes(copyNodes); //deep copy node list
115 CopyGenes(copyGenes); //deep copy gene list
116
117 this.netID = new int[2]; //reset ID
118 this.time = 0f; //reset time
119 this.netFitness = 0f; //reset fitness
120 this.timeLived = 0f; //reset time lived
121 }
122
123 /// <summary>
124 /// Initilizing initial node list with given number of input perceptrons which includes the bias node
125 /// </summary>
126 private void InitilizeNodes() {
127 nodeList = new List<NEATNode>(); //create an empty node list
128
129 NEATNode node = null;
130
131 for (int i = 0; i < numberOfInputs; i++) { //run through number of input perceptrons
132
133 if(i == (numberOfInputs - 1)) //if this is the last input
134 node = new NEATNode(i,NEATNode.INPUT_BIAS_NODE); //make it a input bias type node with index i as node ID
135 else //if this is not the last input
136 node = new NEATNode(i, NEATNode.INPUT_NODE); //make it a input type node with index i as node ID
137
138 nodeList.Add(node); //add node to the node list
139 }
140
141 for (int i = numberOfInputs; i < numberOfInputs+numberOfOutputs; i++){ //run through number of output perceptrons
142 node = new NEATNode(i, NEATNode.OUTPUT_NODE); //make it a putput type node with index i as node ID
143 nodeList.Add(node); //add node to the node list
144 }
145 }
146
147 /// <summary>
148 /// Initilizing initial gene list with given number of input and output perceptrons to create a primitive genome (all inputs connected to all outputs)
149 /// </summary>
150 private void InitilizeGenes() {
151 geneList = new List<NEATGene>(); //create an empty gene list
152
153 for (int i = 0; i < numberOfInputs; i++){ //run through number of inputs
154 for (int j = numberOfInputs; j < numberOfInputs+numberOfOutputs; j++){ //run through number of outputs
155 int inno = consultor.CheckGeneExistance(i,j); //check if gene exists in consultor
156 NEATGene gene = new NEATGene(inno, i, j, 1f, true); // create gene with default weight of 1.0 and and is active
157
158 InsertNewGene(gene); //insert gene to correct location in gene list
159 }
160 }
161 }
162
163 /// <summary>
164 /// Returns the fitness of this network
165 /// </summary>
166 /// <returns>Fitness</returns>
167 public float GetNetFitness() {
168 return netFitness; //reutrn fitness
169 }
170
171 /// <summary>
172 /// Returns the time this network has lived
173 /// </summary>
174 /// <returns>Time lived</returns>
175 public float GetTimeLived() {
176 return timeLived; //return time lived
177 }
178
179 /// <summary>
180 /// Set ID of the network
181 /// </summary>
182 /// <param name="netID">Network ID to set</param>
183 public void SetNetID(int[] netID) {
184 this.netID = new int[] {netID[0], netID[1]}; //set ID
185 }
186
187 /// <summary>
188 /// Set fitness to given fitness
189 /// </summary>
190 /// <param name="netFitness">Fitness to set network fitness to</param>
191 public void SetNetFitness(float netFitness) {
192 this.netFitness = netFitness; //set fitness
193 }
194
195 /// <summary>
196 /// Add given fitness to the current fitness
197 /// </summary>
198 /// <param name="netFitness">Fitness to add</param>
199 public void AddNetFitness(float netFitness) {
200 this.netFitness += netFitness; //increment by given fitness
201 }
202
203 /// <summary>
204 /// Set time lived of this network
205 /// </summary>
206 /// <param name="timeLived">Time lived to set</param>
207 public void SetTimeLived(float timeLived) {
208 this.timeLived = timeLived; //set time lived
209 }
210
211 /// <summary>
212 /// Add given time lived to current time lived
213 /// </summary>
214 /// <param name="timeLived">Time lived to add</param>
215 public void AddTimeLived(float timeLived) {
216 this.timeLived += timeLived; //increment by given time lived
217 }
218
219 /// <summary>
220 /// Return ID of this network
221 /// </summary>
222 /// <returns>ID of this network</returns>
223 public int[] GetNetID() {
224 return netID; //return network ID
225 }
226
227 /// <summary>
228 /// Return test time of this network
229 /// </summary>
230 /// <returns>Test time</returns>
231 public float GetTestTime() {
232 return time; //return test time
233 }
234
235 /// <summary>
236 /// Return total number of nodes (perceptrons) in this network
237 /// </summary>
238 /// <returns>Number of total nodes</returns>
239 public int GetNodeCount() {
240 return nodeList.Count; //return node code
241 }
242
243 /// <summary>
244 /// Return number of genes in the genome
245 /// </summary>
246 /// <returns>Number of genes in the genome</returns>
247 public int GetGeneCount() {
248 return geneList.Count; //gene count
249 }
250
251 /// <summary>
252 /// Return number of input perceptrons
253 /// </summary>
254 /// <returns>Number of input nodes</returns>
255 public int GetNumberOfInputNodes() {
256 return numberOfInputs; //return number of inputs
257 }
258
259 /// <summary>
260 /// Return number of output perceptrons
261 /// </summary>
262 /// <returns>Number of output nodes</returns>
263 public int GetNumberOfOutputNodes() {
264 return numberOfOutputs; //return number of outputs
265 }
266
267 /// <summary>
268 /// Return consultor of this network
269 /// </summary>
270 /// <returns>Consultor</returns>
271 public NEATConsultor GetConsultor() {
272 return consultor; //return consultor
273 }
274
275 /// <summary>
276 /// Set test time to given time
277 /// </summary>
278 /// <param name="time">Test time</param>
279 public void SetTestTime(float time) {
280 this.time = time; //set test time
281 }
282
283 /// <summary>
284 /// Compile and return gene connections information which include weight, in node, and out node in a 2D array
285 /// </summary>
286 /// <returns>Array of gene connections information in a 2D array</returns>
287 public float[][] GetGeneDrawConnections() {
288 int numberOfGenes = geneList.Count; //copy gene count
289
290 float[][] connections = null; //2D connections to return
291
292 List<float[]> connectionList = new List<float[]>(); //empty connections list to fill with genome details
293
294 for (int i = 0; i < numberOfGenes; i++) { //run through all genes
295 NEATGene gene = geneList[i]; // get gene at index i
296
297 float[] details = new float[3]; //will copy in node ID, out node ID and weight
298
299 details[0] = gene.GetInID(); //copy in node ID
300 details[1] = gene.GetOutID(); //copy out node ID
301
302 if (gene.GetGeneState() == true) //gene is enabled
303 details[2] = gene.GetWeight(); //copy weight
304 else //gene is disabled
305 details[2] = 0f; //set to 0
306
307 connectionList.Add(details); //add detail to the connection list
308 }
309
310 connections = connectionList.ToArray(); //convert connection list to 2D connection array
311 return connections; //return 2D connection array
312 }
313
314 /// <summary>
315 /// Compile and return genome in a large string to be saved in a database
316 /// </summary>
317 /// <returns>Genome string</returns>
318 public string GetGenomeString() {
319 string genome = ""; //genome to return
320 int numberOfGenes = geneList.Count; //get number of genes
321
322 for (int i = 0; i < numberOfGenes; i++) { //run through all genes
323 NEATGene gene = geneList[i]; //get gene at index i
324 genome += gene.GetGeneString(); //concatenate gene string to genome
325
326 if (i < numberOfGenes - 1) { //if this is not the last index
327 genome += "_"; //add seperation underscore to seperate 2 different genomes
328 }
329 }
330
331 return genome; //return string genome
332 }
333
334 /// <summary>
335 /// Change network's input perceptron values to the given input array
336 /// </summary>
337 /// <param name="inputs">Replacing input perceptron values with this array</param>
338 public void SetInputValues(float[] inputs) {
339 for (int i = 0; i < numberOfInputs; i++) { //run through number of inputs
340 if (nodeList[i].GetNodeType() == NEATNode.INPUT_NODE) { //only if this is a input node
341 nodeList[i].SetValue(inputs[i]); //change value of node to given value at index i
342 }
343 else { //if this is not an input type node
344 break;
345 }
346 }
347 }
348
349 /// <summary>
350 /// Compile and return all node values in an array
351 /// </summary>
352 /// <returns>All node values in an array</returns>
353 private float[] GetAllNodeValues() {
354 float[] values = new float[nodeList.Count]; //create an array with the szie of number of nodes
355
356 for (int i = 0; i < values.Length; i++){ //run through number of nodes
357 values[i] = nodeList[i].GetValue(); //set node values
358 }
359 return values; //return all nodes value array
360 }
361
362 /// <summary>
363 /// Compile and return only input node values in an array
364 /// </summary>
365 /// <returns>Only input node values in an array</returns>
366 private float[] GetInputValues(){
367 float[] values = new float[numberOfInputs]; //create an array with size of number of input nodes
368
369 for (int i = 0; i < numberOfInputs; i++){ //run through number of inputs
370 values[i] = nodeList[i].GetValue(); //set input nodes value
371 }
372
373 return values; //return input nodes value array
374 }
375
376 /// <summary>
377 /// Compile and return only output node values in an array
378 /// </summary>
379 /// <returns>Only ouput node values in an array</returns>
380 private float[] GetOutputValues(){
381 float[] values = new float[numberOfOutputs]; //create an array with size of number of output nodes
382
383 for (int i = 0; i < numberOfOutputs; i++) { //run through number of outputs
384 values[i] = nodeList[i + numberOfInputs].GetValue(); //set output nodes value
385 }
386
387 return values; //return output nodes value array
388 }
389
390 /// <summary>
391 /// Compile and return only hidden node values in an array
392 /// </summary>
393 /// <returns>Only hidden node values in an array</returns>
394 private float[] GetHiddenValues(){
395 int numberOfHiddens = nodeList.Count - (numberOfInputs + numberOfOutputs); //get number of hidden nodes that exist
396 float[] values = new float[numberOfHiddens]; //create an array with size of number of hidden nodes
397
398 for (int i = 0; i < numberOfHiddens; i++){ //run through number of hiddens
399 values[i] = nodeList[i + numberOfInputs + numberOfOutputs].GetValue(); //set hidden nodes value
400 }
401
402 return values; //return hidden nodes value array
403 }
404
405 /// <summary>
406 /// Create node list from deep copying a given node list
407 /// </summary>
408 /// <param name="copyNodes">Node list to deep copy</param>
409 private void CopyNodes(List<NEATNode> copyNodes) {
410 nodeList = new List<NEATNode>(); //create an empty node list
411 int numberOfNodes = copyNodes.Count; //number of nodes to copy
412
413 for (int i = 0; i < numberOfNodes; i++) { //run through number of nodes to copy
414 NEATNode node = new NEATNode(copyNodes[i]); //create deep copy of node at index i
415 nodeList.Add(node); //add node to node list
416 }
417 }
418
419 /// <summary>
420 /// Create gene list from deep copying a given gene list
421 /// </summary>
422 /// <param name="copyGenes">Gene list to deep copy</param>
423 private void CopyGenes(List<NEATGene> copyGenes) {
424 geneList = new List<NEATGene>(); //create an empty node list
425 int numberOfGenes = copyGenes.Count; //number of nodes to copy
426
427 for (int i = 0; i < numberOfGenes; i++) { //run through number of genes to copy
428 NEATGene gene = new NEATGene(copyGenes[i]); //create deep copy of gene at index i
429 geneList.Add(gene); //add gene to gene list
430 }
431 }
432
433 /// <summary>
434 /// Feed-forward the neural network by creating a temporary phenotype from the genotype
435 /// </summary>
436 /// <param name="inputs">Inputs to set as the input perceptron values</param>
437 /// <returns>An array of output values after feed-forward</returns>
438 public float[] FireNet(float[] inputs){
439 int numberOfGenes = geneList.Count; //get number of genes
440
441 SetInputValues(inputs); //set input values to the input nodes
442
443 //feed forward reccurent net
444 float[] tempValues = GetAllNodeValues(); //create a temporary storage of previous node values (used as a phenotype)
445
446 for (int i = 0; i < numberOfGenes; i++) { //run through number of genes
447 NEATGene gene = geneList[i]; //get gene at index i
448 bool on = gene.GetGeneState(); //get state of the gene
449
450 if (on == true) { //if gene is active
451 int inID = gene.GetInID(); //get in node ID
452 int outID = gene.GetOutID(); //get out node ID
453 float weight = gene.GetWeight(); //get weight of the connection
454
455 NEATNode outNode = nodeList[outID]; //get out node
456
457 float inNodeValue = tempValues[inID]; //get in node's value
458 float outNodeValue = tempValues[outID]; //get out node's value
459
460 float newOutNodeValue = outNodeValue + (inNodeValue*weight); //calculate new out node's value
461 outNode.SetValue(newOutNodeValue); //set new value to the out node
462 }
463 }
464
465 //Activation
466 for (int i = 0; i < nodeList.Count; i++) { //run through number of nodes
467 nodeList[i].Activation(); //provide an activation function over all nodes
468 }
469
470 return GetOutputValues(); //return output
471 }
472
473 /// <summary>
474 /// Mutating this neural network
475 /// </summary>
476 public void Mutate() {
477 int randomNumber = Random.Range(1, 101); //random number between 1 and 100
478 int chance = 25; //25% chance of mutation
479
480 if (randomNumber <= chance) { //random number is below chance
481 AddConnection(); //add connection between 2 nodes
482 }
483 else if (randomNumber <= (chance*2)) {//random number is below chance*2
484 AddNode(); //add a new node bettwen an existing connection
485 }
486
487 MutateWeight(); //mutate weight
488 }
489
490 /// <summary>
491 /// Adding a connection between 2 previously unconnected nodes (except no inputs shall ever connect to other inputs)
492 /// </summary>
493 private void AddConnection(){
494 int randomNodeID1, randomNodeID2, inno; //random node ID's and innovation number
495 int totalAttemptsAllowed = (int)Mathf.Pow(nodeList.Count,2); //total attempts allowed to find two unconnected nodes
496
497 bool found = false; //used to check if a connection is found
498
499 while (totalAttemptsAllowed > 0 && found == false) { //if connection is found and greater than 0 attempts left
500 randomNodeID1 = Random.Range(0, nodeList.Count); //pick a random node
501 randomNodeID2 = Random.Range(numberOfInputs, nodeList.Count); //pick a random node that is not the input
502
503 if (!ConnectionExists(randomNodeID1, randomNodeID2)) { //if connection does not exist with random node 1 as in node and random node 2 and out node
504 inno = consultor.CheckGeneExistance(randomNodeID1, randomNodeID2); //get the new innovation number
505 NEATGene gene = new NEATGene(inno, randomNodeID1, randomNodeID2, 1f, true); //create gene which is enabled and 1 as default weight
506
507 InsertNewGene(gene); //add gene to the gene list
508
509 found = true; //connection made
510 }
511 else if(nodeList[randomNodeID1].GetNodeType() > 1 && !ConnectionExists(randomNodeID2, randomNodeID1)) { //if random node 1 isn't input type and connection does not exist with random node 2 as in node and random node 1 and out node
512 inno = consultor.CheckGeneExistance(randomNodeID2, randomNodeID1); //get the new innovation number
513 NEATGene gene = new NEATGene(inno, randomNodeID2, randomNodeID1, 1f, true); //create gene which is enabled and 1 as default weight
514
515 InsertNewGene(gene); //add gene to the gene list
516
517 found = true; //connection made
518 }
519
520 if(randomNodeID1 == randomNodeID2) //both random nodes are equal
521 totalAttemptsAllowed --; //only one attemp removed becuase only 1 connection can be made
522 else //both nodes are different
523 totalAttemptsAllowed -= 2; //two connections can be made
524 }
525
526 if (found == false) { //if not found and attempts ran out
527 AddNode(); //
528 }
529 }
530
531 /// <summary>
532 /// Adding a new node between an already existing connection.
533 /// Disable the existing connection, add a node which with connection that bbecomes the out node to the old connections in node, and a connection with in node to the old connection out node.
534 /// The first new connections gets a weight of 1.
535 /// The second second new connections gets a weight of the old weight
536 /// </summary>
537 private void AddNode(){
538 int firstID, secondID, thirdID, inno; //first ID is old connections in node, third ID is old connections out node, second ID is the new node, and new innovation number for the connections
539 //int randomGeneIndex = Random.Range(0, geneList.Count); //find a random gene
540
541 float oldWeight; //weight from the old gene
542
543 //NEATGene oldGene = geneList[randomGeneIndex]; //get old gene
544
545 NEATGene oldGene = null; //find a random old gene
546 bool found = false; //used to check if old gene is found
547
548 while (!found) { //run till found
549 int randomGeneIndex = Random.Range(0, geneList.Count); //pick random gene
550 oldGene = geneList[randomGeneIndex]; //get gene at random index
551 if (oldGene.GetGeneState() == true) { //if gene is active
552 found = true; //found
553 }
554 }
555
556 oldGene.SetGeneState(false); //disable this gene
557 firstID = oldGene.GetInID(); //get in node ID
558 thirdID = oldGene.GetOutID(); //get out node ID
559 oldWeight = oldGene.GetWeight(); //get old weight
560
561 NEATNode newNode = new NEATNode(nodeList.Count, NEATNode.HIDDEN_NODE); //create new hidden node
562 nodeList.Add(newNode); //add new node to the node list
563 secondID = newNode.GetNodeID(); //get new node's ID
564
565 inno = consultor.CheckGeneExistance(firstID, secondID); //get new innovation number for new gene
566 NEATGene newGene1 = new NEATGene(inno, firstID, secondID, 1f, true); //create new gene
567
568 inno = consultor.CheckGeneExistance(secondID, thirdID); //get new innovation number for new gene
569 NEATGene newGene2 = new NEATGene(inno, secondID, thirdID, oldWeight, true); //create new gene
570
571 //add genes to gene list
572 InsertNewGene(newGene1);
573 InsertNewGene(newGene2);
574 }
575
576 /// <summary>
577 /// Run through all genes and randomly apply various muations with a chance of 1%
578 /// </summary>
579 private void MutateWeight() {
580 int numberOfGenes = geneList.Count; //number of genes
581
582 for (int i = 0; i < numberOfGenes; i++) { //run through all genes
583 NEATGene gene = geneList[i]; // get gene at index i
584 float weight = 0;
585
586 int randomNumber = Random.Range(1, 101); //random number between 1 and 100
587
588 if (randomNumber <= 1) { //if 1
589 //flip sign of weight
590 weight = gene.GetWeight();
591 weight *= -1f;
592 gene.SetWeight(weight);
593 }
594 else if (randomNumber <= 2) { //if 2
595 //pick random weight between -1 and 1
596 weight = Random.Range(-1f,1f);
597 gene.SetWeight(weight);
598 }
599 else if (randomNumber <= 3) { //if 3
600 //randomly increase by 0% to 100%
601 float factor = Random.Range(0f,1f) + 1f;
602 weight = gene.GetWeight() * factor;
603 gene.SetWeight(weight);
604 }
605 else if (randomNumber <= 4) { //if 4
606 //randomly decrease by 0% to 100%
607 float factor = Random.Range(0f, 1f);
608 weight = gene.GetWeight() * factor;
609 gene.SetWeight(weight);
610 }
611 else if (randomNumber <= 5) { //if 5
612 //flip activation state for gene
613 //gene.SetGeneState(!gene.GetGeneState());
614 }
615 }
616
617 }
618
619 /// <summary>
620 /// Check if a connection exists in this gene list
621 /// </summary>
622 /// <param name="inID">In node in gene</param>
623 /// <param name="outID">Out node in gene</param>
624 /// <returns>True or false if connection exists in gene list</returns>
625 private bool ConnectionExists(int inID, int outID) {
626 int numberOfGenes = geneList.Count; //number of genes
627
628 for (int i = 0; i < numberOfGenes; i++) { //run through gene list
629 int nodeInID = geneList[i].GetInID(); //get in node
630 int nodeOutID = geneList[i].GetOutID(); //get out node
631
632 if (nodeInID == inID && nodeOutID == outID) { //check if nodes match given parameters
633 return true; //return true
634 }
635 }
636
637 return false; //return false if no match
638 }
639
640 /// <summary>
641 /// Set all node values to 0
642 /// </summary>
643 public void ClearNodeValues() {
644 int numberOfNodes = nodeList.Count; //number of nodes
645
646 for (int i = 0; i < numberOfNodes; i++) { //run through all nodes
647 nodeList[i].SetValue(0f); //set values to 0
648 }
649 }
650
651 /// <summary>
652 /// Insert new gene into its proper location the gene list.
653 /// All genes are orders in asending order based on their innovation number.
654 /// </summary>
655 /// <param name="gene">Gene to inset into the gene list</param>
656 private void InsertNewGene(NEATGene gene) {
657 int inno = gene.GetInnovation(); //get innovation number
658 int insertIndex = FindInnovationInsertIndex(inno); //get insert index
659
660 if (insertIndex == geneList.Count) { //if insert index is equal to the size of the genome
661 geneList.Add(gene); //add gene
662 }
663 else { //otherwise
664 geneList.Insert(insertIndex, gene); //add gene to the given insert index location
665 }
666 }
667
668 /// <summary>
669 /// Find the correct location to insert a given innovation number.
670 /// Using bianry search to find insert location.
671 /// </summary>
672 /// <param name="inno">Innovation to insert</param>
673 /// <returns>Location to insert the innovation number</returns>
674 private int FindInnovationInsertIndex(int inno) {
675 int numberOfGenes = geneList.Count; //number of genes
676 int startIndex = 0; //start index
677 int endIndex = numberOfGenes - 1; //end index
678
679 if (numberOfGenes == 0) { //if there are no genes
680 return 0; //first location to insert
681 }
682 else if (numberOfGenes == 1) { //if there is only 1 gene
683 if (inno > geneList[0].GetInnovation()) { //if innovation is greater than the girst gene's innovation
684 return 1; //insert into second location
685 }
686 else {
687 return 0; //insert into first location
688 }
689 }
690
691 while (true) { //run till found
692 int middleIndex = (endIndex + startIndex)/2; //find middle index (middle of start and end)
693 int middleInno = geneList[middleIndex].GetInnovation(); //get middle index's innovation number
694
695 if(endIndex-startIndex == 1) { //if there is only 1 index between start and end index (base case on recursion)
696 int endInno = geneList[endIndex].GetInnovation(); //get end inde's innovation
697 int startInno = geneList[startIndex].GetInnovation(); //get start index's innovation
698
699 if (inno < startInno) { //innovation is less than start innovation
700 return startIndex; //return start index
701 }
702 else if (inno > endInno) { //innovation is greater than end innovation
703 return endIndex + 1; //return end index + 1
704 }
705 else {
706 return endIndex; //otherwise right in end index
707 }
708 }
709 else if (inno > middleInno) { //innovation is greater than middle innovation
710 startIndex = middleIndex; //new start index will be the middle
711 }
712 else { //innovation is less than middle innovation
713 endIndex = middleIndex; //new end index is middle index
714 }
715 }
716 }
717
718 /// <summary>
719 /// Create a mutated deep copy of a given neural network
720 /// </summary>
721 /// <param name="net">Neural network copy to mutate</param>
722 /// <returns>Mutated deep copy of the given neural network</returns>
723 internal static NEATNet CreateMutateCopy(NEATNet net) {
724 NEATNet copy = new NEATNet(net); //create deep copy of net
725 copy.Mutate(); //mutate copy
726
727 return copy; //return mutated deep copy
728 }
729
730 /// <summary>
731 /// Corssover between two parents neural networks to create a child neural network.
732 /// Crossover method is as described by the NEAT algorithm.
733 /// </summary>
734 /// <param name="parent1">Neural network parent</param>
735 /// <param name="parent2">Neural network parent</param>
736 /// <returns>Child neural network</returns>
737 internal static NEATNet Corssover (NEATNet parent1, NEATNet parent2) {
738 NEATNet child = null; //child to create
739
740 Hashtable geneHash = new Hashtable(); //hash table to be used to compared genes from the two parents
741
742 List<NEATGene> childGeneList = new List<NEATGene>(); //new gene child gene list to be created
743 List<NEATNode> childNodeList = null; //new child node list to be created
744
745 List<NEATGene> geneList1 = parent1.geneList; //get gene list of the parent 1
746 List<NEATGene> geneList2 = parent2.geneList; //get gene list of parent 2
747
748 NEATConsultor consultor = parent1.GetConsultor(); //get consultor (consultor is the same for all neural network as it's just a pointer location)
749
750 int numberOfGenes1 = geneList1.Count; //get number of genes in parent 1
751 int numberOfGenes2 = geneList2.Count; //get number of genes in parent 2
752 int numberOfInputs = parent1.GetNumberOfInputNodes(); //number of inputs (same for both parents)
753 int numberOfOutputs = parent1.GetNumberOfOutputNodes(); //number of outputs (same for both parents)
754
755 if (parent1.GetNodeCount() > parent2.GetNodeCount()) { //if parents 1 has more nodes than parent 2
756 childNodeList = parent1.nodeList; //copy parent 1's node list
757 }
758 else { //otherwise parent 2 has euqal and more nodes than parent 1
759 childNodeList = parent2.nodeList; //copy parent 2's node list
760 }
761
762 for (int i = 0; i < numberOfGenes1; i++) { //run through all genes in parent 1
763 geneHash.Add(geneList1[i].GetInnovation(),new NEATGene[] { geneList1[i], null}); //add into the hash with innovation number as the key and gene array of size 2 as value
764 }
765
766 for (int i = 0; i < numberOfGenes2; i++) { //run through all genes in parent 2
767 int innovationNumber = geneList2[i].GetInnovation(); //get innovation number
768
769 if (geneHash.ContainsKey(innovationNumber) == true) { //if there is a key in the hash with the given innovation number
770 NEATGene[] geneValue = (NEATGene[])geneHash[innovationNumber]; //get gene array value with the innovation key
771 geneValue[1] = geneList2[i]; //since this array already contains value in first location, we can add the new gene in the second location
772 geneHash.Remove(innovationNumber); //remove old value with the key
773 geneHash.Add(innovationNumber, geneValue); //add new value with the key
774 }
775 else { //there exists no key with the given innovation number
776 geneHash.Add(innovationNumber, new NEATGene[] { null , geneList2[i] }); //add into the hash with innovation number as the key and gene array of size 2 as value
777 }
778 }
779
780 ICollection keysCol = geneHash.Keys; //get all keys in the hash
781
782 NEATGene gene = null; //
783
784 int[] keys = new int[keysCol.Count]; //int array with size of nuumber of keys in the hash
785
786 keysCol.CopyTo(keys,0); //copy Icollentions keys list to keys array
787 keys = keys.OrderBy(i => i).ToArray(); //order keys in asending order
788
789 for (int i = 0; i < keys.Length; i++) { //run through all keys
790 NEATGene[] geneValue = (NEATGene[])geneHash[keys[i]]; //get value at each index
791
792 //compare value is used to compare gene activation states in each parent
793 int compareValue = -1;
794 //0 = both genes are true, 1 = both are false, 2 = one is false other is true
795 //3 = gene is dominant in one of the parents and is true, 4 = gene is dominant in one of the parents and is false
796
797 if (geneValue[0] != null && geneValue[1] != null) { //gene eixts in both parents
798 int randomIndex = Random.Range(0, 2);
799
800 if (geneValue[0].GetGeneState() == true && geneValue[1].GetGeneState() == true) { //gene is true in both
801 compareValue = 0; //set compared value to 0
802 }
803 else if (geneValue[0].GetGeneState() == false && geneValue[1].GetGeneState() == false) { //gene is false in both
804 compareValue = 1; //set compared value to 1
805 }
806 else { //gene is true in one and false in the other
807 compareValue = 2; //set compared value to 2
808 }
809
810 gene = CrossoverCopyGene(geneValue[randomIndex], compareValue); //randomly pick a gene from eaither parent and create deep copy
811 childGeneList.Add(gene); //add gene to the child gene list
812 }
813 else if (parent1.GetNetFitness() > parent2.GetNetFitness()) { //parent 1's fitness is greater than parent 2
814 if (geneValue[0] != null) { //gene value at first index from parent 1 exists
815 if (geneValue[0].GetGeneState() == true) { //gene is active
816 compareValue = 3; //set compared value to 3
817 }
818 else { //gene is not active
819 compareValue = 4; //set compared value to 4
820 }
821
822 gene = CrossoverCopyGene(geneValue[0], compareValue); //deep copy parent 1's gene
823 childGeneList.Add(gene); //add gene to the child gene list
824 }
825 }
826 else if (parent1.GetNetFitness() < parent2.GetNetFitness()) { //parent 2's fitness is greater than parent 1
827 if (geneValue[1] != null) { //gene value at second index from parent 2 exists
828 if (geneValue[1].GetGeneState() == true) { //gene is active
829 compareValue = 3; //set compared value to 3
830 }
831 else { //gene is not active
832 compareValue = 4; //set compared value to 4
833 }
834
835 gene = CrossoverCopyGene(geneValue[1], compareValue); //deep copy parent 2's gene
836 childGeneList.Add(gene); //add gene to the child gene list
837 }
838 }
839 else if (geneValue[0] != null) { //both parents have equal fitness and gene value at first index from parent 1 exists
840 if (geneValue[0].GetGeneState() == true){ //gene is active
841 compareValue = 3; //set compared value to 3
842 }
843 else { //gene is not active
844 compareValue = 4; //set compared value to 4
845 }
846
847 gene = CrossoverCopyGene(geneValue[0], compareValue); //deep copy parent 1's gene
848 childGeneList.Add(gene); //add gene to the child gene list
849 }
850 else if (geneValue[1] != null) { //both parents have equal fitness and gene value at second index from parent 2 exists
851 if (geneValue[1].GetGeneState() == true) { //gene is active
852 compareValue = 3; //set compared value to 3
853 }
854 else { //gene is not active
855 compareValue = 4; //set compared value to 4
856 }
857
858 gene = CrossoverCopyGene(geneValue[1], compareValue); //deep copy parent 2's gene
859 childGeneList.Add(gene); //add gene to the child gene list
860 }
861 }
862
863 child = new NEATNet(consultor, numberOfInputs, numberOfOutputs, childNodeList, childGeneList); //create new child neural network
864 return child; //return newly created neural network
865 }
866
867 /// <summary>
868 /// Created a deep copy of a given gene.
869 /// This gene can be muated with a small chance based on the compare value.
870 /// Deactivated genes have a small chance of being activated based on the compare value.
871 /// </summary>
872 /// <param name="copyGene">Gene to deep copy</param>
873 /// <param name="compareValue">Value to use when activating a gene</param>
874 /// <returns>Deep copied gene</returns>
875 private static NEATGene CrossoverCopyGene(NEATGene copyGene, int compareValue) {
876 NEATGene gene = new NEATGene(copyGene); //deep copy gene
877
878 int randomNumber = Random.Range(0, 5); //0-4
879
880 if (compareValue == 2) { //if gene is false in both parents
881 randomNumber = Random.Range(0, 11); //0-10
882 if (randomNumber == 0) { //9% chance of activating this gene
883 gene.SetGeneState(true); //activate
884 }
885 }
886 else if (gene.GetGeneState() == false && randomNumber == 0) { //gene is false and 20% chance of activating this gene
887 gene.SetGeneState(true); //activate
888 }
889
890 /*if (compareValue == 1) {
891 int randomNumber = Random.Range(0, 26);
892 if (randomNumber == 0) {
893 gene.SetGeneState(false);
894 }
895 }
896 else if (compareValue == 2) {
897 int randomNumber = Random.Range(0, 26);
898 if (randomNumber == 0) {
899 gene.SetGeneState(true);
900 }
901 }
902 else {
903 int randomNumber = Random.Range(0, 11);
904 if (randomNumber == 0) {
905 gene.SetGeneState(!gene.GetGeneState());
906 }
907 }*/
908
909 return gene; //return new gene
910 }
911
912 /// <summary>
913 /// Check whether two neural networks belong to the same species based on defined coefficient values in the consultor
914 /// </summary>
915 /// <param name="net1">Neural network to compare</param>
916 /// <param name="net2">Neural network to compare</param>
917 /// <returns>True of false whether they belong to the same species</returns>
918 internal static bool SameSpeciesV2(NEATNet net1, NEATNet net2) {
919 Hashtable geneHash = new Hashtable(); //hash table to be used to compared genes from the two networks
920 NEATConsultor consultor = net1.consultor; //get consultor (consultor is the same for all neural network as it's just a pointer location)
921 NEATGene[] geneValue; //will be used to check whether a gene exists in both networks
922
923 List<NEATGene> geneList1 = net1.geneList; //get first network
924 List<NEATGene> geneList2 = net2.geneList; //get second network
925
926 ICollection keysCol; //will be used to get keys from gene hash
927 int[] keys; //will be used to get keys arrray from ICollections
928
929 int numberOfGenes1 = geneList1.Count; //get number of genes in network 1
930 int numberOfGenes2 = geneList2.Count; //get number of genes in network 2
931 int largerGenomeSize = numberOfGenes1 > numberOfGenes2 ? numberOfGenes1 : numberOfGenes2; //get one that is larger between the 2 network
932 int excessGenes = 0; //number of excess genes (genes that do match and are outside the innovation number of the other network)
933 int disjointGenes = 0; //number of disjoint gene (genes that do not match in the two networks)
934 int equalGenes = 0; //number of genes both neural network have
935
936 float disjointCoefficient = consultor.GetDisjointCoefficient(); //get disjoint coefficient from consultor
937 float excessCoefficient = consultor.GetExcessCoefficient(); //get excess coefficient from consultor
938 float averageWeightDifferenceCoefficient = consultor.GetAverageWeightDifferenceCoefficient(); //get average weight difference coefficient
939 float deltaThreshold = consultor.GetDeltaThreshold(); //get threshold
940 float similarity = 0; //similarity of the two networks
941 float averageWeightDifference = 0; //average weight difference of the two network's equal genes
942
943 bool foundAllExcess = false; //if all excess genes are found
944 bool isFirstGeneExcess = false; //if net 1 contains the excess genes
945
946 for (int i = 0; i < geneList1.Count; i++) { //run through net 1's genes
947 int innovation = geneList1[i].GetInnovation(); //get innovation number of gene
948
949 geneValue = new NEATGene[] {geneList1[i], null}; //add into the hash with innovation number as the key and gene array of size 2 as value
950 geneHash.Add(innovation, geneValue); //add into the hash with innovation number as the key and gene array of size 2 as value
951 }
952
953 for (int i = 0; i < geneList2.Count; i++) { //run through net 2's genes
954 int innovation = geneList2[i].GetInnovation(); //get innovation number of gene
955
956 if (!geneHash.ContainsKey(innovation)) { //if innovation key does not exist
957 geneValue = new NEATGene[] {null, geneList2[i]}; //create array of size 2 with new gene in the second position
958 geneHash.Add(innovation, geneValue); //add into the hash with innovation number as the key and gene array of size 2 as value
959 }
960 else { //key exists
961 geneValue = (NEATGene[]) geneHash[innovation]; //get value
962 geneValue[1] = geneList2[i]; //add into second position net 2's gene
963 }
964 }
965
966 keysCol = geneHash.Keys; //get all keys from gene hash
967 keys = new int[keysCol.Count]; //create array with size of number of keys
968 keysCol.CopyTo(keys, 0); //copy all keys from ICollections to array
969 keys = keys.OrderBy(i => i).ToArray(); //order keys in ascending order
970
971 for (int i = keys.Length-1; i >= 0; i--) { //run through all keys backwards (to get all excess gene's first)
972 geneValue = (NEATGene[])geneHash[keys[i]]; //get value with key
973
974 if (foundAllExcess == false) { //if all excess genes have not been found
975 if (i == keys.Length - 1 && geneValue[1] == null) { //this is the first itteration and second gene location is null
976 isFirstGeneExcess = true; //excess genes exit in net 1
977 }
978
979 if (isFirstGeneExcess == true && geneValue[1] == null) { //excess gene exist in net 1 and there is no gene in second location of the value
980 excessGenes++; //this is an excess gene and increment excess gene
981 }
982 else if (isFirstGeneExcess == false && geneValue[0] == null) { //excess gene exist in net 12 and there is no gene in first location of the value
983 excessGenes++; //this is an excess gene and increment excess gene
984 }
985 else { //no excess genes
986 foundAllExcess = true; //all excess genes are found
987 }
988
989 }
990
991 if(foundAllExcess == true){ //if all excess genes are found
992 if (geneValue[0] != null && geneValue[1] != null) { //both gene location are not null
993 equalGenes++; //increment equal genes
994 averageWeightDifference += Mathf.Abs(geneValue[0].GetWeight() - geneValue[1].GetWeight()); //add absolute difference between 2 weight
995 }
996 else { //this is disjoint gene
997 disjointGenes++; //increment disjoint
998 }
999 }
1000 }
1001
1002 averageWeightDifference = averageWeightDifference / (float)equalGenes; //get average weight difference of equal genes
1003
1004 //similarity formula -> Sim = (AVG_DIFF * AVG_COFF) + (((DISJ*DISJ_COFF) + (EXSS*EXSS_COFF)) /GENOME_SIZE)
1005 similarity = (averageWeightDifference * averageWeightDifferenceCoefficient) + //calculate weight difference disparity
1006 (((float)disjointGenes * disjointCoefficient) / (float)largerGenomeSize) + //calculate disjoint disparity
1007 (((float)excessGenes * excessCoefficient) / (float)largerGenomeSize); //calculate excess disparity
1008
1009 //if similairty is <= to threshold then return true, otherwise false
1010 return similarity<=deltaThreshold; //return boolean compare value
1011 }
1012
1013 /// <summary>
1014 /// ---ONLY USED FOR DEBUGGING---
1015 /// Prints all neural network details.
1016 /// </summary>
1017 public void PrintDetails() {
1018 int numberOfNodes = nodeList.Count; //get number of nodes
1019 int numberOfGenes = geneList.Count; //get number of genes
1020
1021 //Print various node details to Unity Log
1022 Debug.Log("-----------------");
1023
1024 for (int i = 0; i < numberOfNodes; i++) {
1025 NEATNode node = nodeList[i];
1026 Debug.Log("ID:" + node.GetNodeID() + ", Type:" + node.GetNodeType());
1027 }
1028
1029 Debug.Log("-----------------");
1030
1031 for (int i = 0; i < numberOfGenes; i++) {
1032 NEATGene gene = geneList[i];
1033 Debug.LogWarning("Inno " + gene.GetInnovation() + ", In:" + gene.GetInID() + ", Out:" + gene.GetOutID() + ", On:" + gene.GetGeneState() + ", Wi:" + gene.GetWeight());
1034 }
1035
1036 Debug.Log("-----------------");
1037 }
1038
1039}