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