/// <summary>
 /// Copy constructor.
 /// </summary>
 /// <param name="copyFrom"></param>
 public NeuronGene(NeuronGene copyFrom)
 {
     this.innovationId = copyFrom.innovationId;
     this.neuronType = copyFrom.neuronType;
     this.activationFunction = copyFrom.activationFunction;
     this.Layer = copyFrom.Layer;
 }
        private static void WriteNeuron(XmlElement xmlNeurons, NeuronGene neuronGene)
        {
            XmlElement xmlNeuron = XmlUtilities.AddElement(xmlNeurons, "neuron");

            XmlUtilities.AddAttribute(xmlNeuron, "id", neuronGene.InnovationId.ToString());
            XmlUtilities.AddAttribute(xmlNeuron, "type", XmlUtilities.GetNeuronTypeString(neuronGene.NeuronType));
            XmlUtilities.AddAttribute(xmlNeuron, "activationFunction", neuronGene.ActivationFunction.FunctionId);
        }
		public NewNeuronGeneStruct(	NeuronGene newNeuronGene,
									ConnectionGene newConnectionGene_Input,
									ConnectionGene newConnectionGene_Output)
		{
				this.NewNeuronGene = newNeuronGene;
				this.NewConnectionGene_Input = newConnectionGene_Input;
				this.NewConnectionGene_Output = newConnectionGene_Output;
		}
 public NeuronGene(NeuronGene copyFrom, uint _innovationId, double _layer, NeuronType _neuronType, IActivationFunction _activationFunction)
 {
     if (copyFrom != null)
     {
         this.innovationId = copyFrom.InnovationId;
         this.neuronType = copyFrom.NeuronType;
         this.activationFunction = copyFrom.ActivationFunction;
         this.Layer = copyFrom.Layer;
     }
     else
     {
         this.innovationId = _innovationId;
         this.neuronType = _neuronType;
         this.activationFunction = _activationFunction;
         this.Layer = _layer;
     }
 }
        private void Mutate_AddModule(EvolutionAlgorithm ea)
        {
            // Find all potential inputs, or quit if there are not enough. 
            // Neurons cannot be inputs if they are dummy input nodes created for another module.
            NeuronGeneList potentialInputs = new NeuronGeneList();
            foreach (NeuronGene n in neuronGeneList) {
                if (!(n.ActivationFunction is ModuleInputNeuron)) {
                    potentialInputs.Add(n);
                }
            }
            if (potentialInputs.Count < 1)
                return;

            // Find all potential outputs, or quit if there are not enough.
            // Neurons cannot be outputs if they are dummy input or output nodes created for another module, or network input or bias nodes.
            NeuronGeneList potentialOutputs = new NeuronGeneList();
            foreach (NeuronGene n in neuronGeneList) {
                if (n.NeuronType != NeuronType.Bias && n.NeuronType != NeuronType.Input
                        && !(n.ActivationFunction is ModuleInputNeuron)
                        && !(n.ActivationFunction is ModuleOutputNeuron)) {
                    potentialOutputs.Add(n);
                }
            }
            if (potentialOutputs.Count < 1)
                return;

            // Pick a new function for the new module.
            IModule func = ModuleFactory.GetRandom();

            // Choose inputs uniformly at random, with replacement.
            // Create dummy neurons to represent the module's inputs.
            // Create connections between the input nodes and the dummy neurons.
            IActivationFunction inputFunction = ActivationFunctionFactory.GetActivationFunction("ModuleInputNeuron");
            List<uint> inputDummies = new List<uint>(func.InputCount);
            for (int i = 0; i < func.InputCount; i++) {
                NeuronGene newNeuronGene = new NeuronGene(ea.NextInnovationId, NeuronType.Hidden, inputFunction);
                neuronGeneList.Add(newNeuronGene);

                uint sourceId = potentialInputs[Utilities.Next(potentialInputs.Count)].InnovationId;
                uint targetId = newNeuronGene.InnovationId;

                inputDummies.Add(targetId);

                // Create a new connection with a new ID and add it to the Genome.
                ConnectionGene newConnectionGene = new ConnectionGene(ea.NextInnovationId, sourceId, targetId,
                    (Utilities.NextDouble() * ea.NeatParameters.connectionWeightRange) - ea.NeatParameters.connectionWeightRange / 2.0);

                // Register the new connection with NewConnectionGeneTable.
                ConnectionEndpointsStruct connectionKey = new ConnectionEndpointsStruct(sourceId, targetId);
                ea.NewConnectionGeneTable.Add(connectionKey, newConnectionGene);

                // Add the new gene to this genome. We have a new ID so we can safely append the gene to the end 
                // of the list without risk of breaking the innovation ID order.
                connectionGeneList.Add(newConnectionGene);
            }

            // Choose outputs uniformly at random, with replacement.
            // Create dummy neurons to represent the module's outputs.
            // Create connections between the output nodes and the dummy neurons.
            IActivationFunction outputFunction = ActivationFunctionFactory.GetActivationFunction("ModuleOutputNeuron");
            List<uint> outputDummies = new List<uint>(func.OutputCount);
            for (int i = 0; i < func.OutputCount; i++) {
                NeuronGene newNeuronGene = new NeuronGene(ea.NextInnovationId, NeuronType.Hidden, outputFunction);
                neuronGeneList.Add(newNeuronGene);

                uint sourceId = newNeuronGene.InnovationId;
                uint targetId = potentialOutputs[Utilities.Next(potentialOutputs.Count)].InnovationId;

                outputDummies.Add(sourceId);

                // Create a new connection with a new ID and add it to the Genome.
                ConnectionGene newConnectionGene = new ConnectionGene(ea.NextInnovationId, sourceId, targetId,
                    (Utilities.NextDouble() * ea.NeatParameters.connectionWeightRange) - ea.NeatParameters.connectionWeightRange / 2.0);

                // Register the new connection with NewConnectionGeneTable.
                ConnectionEndpointsStruct connectionKey = new ConnectionEndpointsStruct(sourceId, targetId);
                ea.NewConnectionGeneTable.Add(connectionKey, newConnectionGene);

                // Add the new gene to this genome. We have a new ID so we can safely append the gene to the end 
                // of the list without risk of breaking the innovation ID order.
                connectionGeneList.Add(newConnectionGene);
            }

            // Pick a new ID for the new module and create it.
            // Modules do not participate in history comparisons, so we will always create a new innovation ID.
            // We can change this here if it becomes a problem.
            ModuleGene newModule = new ModuleGene(ea.NextInnovationId, func, inputDummies, outputDummies);
            moduleGeneList.Add(newModule);
        }
        /// <summary>
        /// Add a new node to the Genome. We do this by removing a connection at random and inserting 
        /// a new node and two new connections that make the same circuit as the original connection.
        /// 
        /// This way the new node is properly integrated into the network from the outset.
        /// </summary>
        /// <param name="ea"></param>
        private void Mutate_AddNode(EvolutionAlgorithm ea)
		{
			if(connectionGeneList.Count==0)
				return;

			// Select a connection at random.
			int connectionToReplaceIdx = (int)Math.Floor(Utilities.NextDouble() * connectionGeneList.Count);
			ConnectionGene connectionToReplace = connectionGeneList[connectionToReplaceIdx];
				
			// Delete the existing connection. JOEL: Why delete old connection?
			//connectionGeneList.RemoveAt(connectionToReplaceIdx);

			// Check if this connection has already been split on another genome. If so then we should re-use the
			// neuron ID and two connection ID's so that matching structures within the population maintain the same ID.
			object existingNeuronGeneStruct = ea.NewNeuronGeneStructTable[connectionToReplace.InnovationId];

			NeuronGene newNeuronGene;
			ConnectionGene newConnectionGene1;
			ConnectionGene newConnectionGene2;
            IActivationFunction actFunct;
			if(existingNeuronGeneStruct==null)
			{	// No existing matching structure, so generate some new ID's.

                //TODO: DAVID proper random activation function
				// Replace connectionToReplace with two new connections and a neuron.
                actFunct=ActivationFunctionFactory.GetRandomActivationFunction(ea.NeatParameters);
                //newNeuronGene = new NeuronGene(ea.NextInnovationId, NeuronType.Hidden, actFunct);

                newNeuronGene = new NeuronGene(null, ea.NextInnovationId, (neuronGeneList.GetNeuronById(connectionToReplace.SourceNeuronId).Layer + neuronGeneList.GetNeuronById(connectionToReplace.TargetNeuronId).Layer) / 2.0, NeuronType.Hidden, actFunct);
			
				newConnectionGene1 = new ConnectionGene(ea.NextInnovationId, connectionToReplace.SourceNeuronId, newNeuronGene.InnovationId, 1.0);
				newConnectionGene2 = new ConnectionGene(ea.NextInnovationId, newNeuronGene.InnovationId, connectionToReplace.TargetNeuronId, connectionToReplace.Weight);

				// Register the new ID's with NewNeuronGeneStructTable.
				ea.NewNeuronGeneStructTable.Add(connectionToReplace.InnovationId,
												new NewNeuronGeneStruct(newNeuronGene, newConnectionGene1, newConnectionGene2));
			}
			else
			{	// An existing matching structure has been found. Re-use its ID's

                //TODO: DAVID proper random activation function
				// Replace connectionToReplace with two new connections and a neuron.
                actFunct = ActivationFunctionFactory.GetRandomActivationFunction(ea.NeatParameters);
				NewNeuronGeneStruct tmpStruct = (NewNeuronGeneStruct)existingNeuronGeneStruct;
                //newNeuronGene = new NeuronGene(tmpStruct.NewNeuronGene.InnovationId, NeuronType.Hidden, actFunct);
                newNeuronGene = new NeuronGene(null, tmpStruct.NewNeuronGene.InnovationId, tmpStruct.NewNeuronGene.Layer, NeuronType.Hidden, actFunct);
				
				newConnectionGene1 = new ConnectionGene(tmpStruct.NewConnectionGene_Input.InnovationId, connectionToReplace.SourceNeuronId, newNeuronGene.InnovationId, 1.0);
				newConnectionGene2 = new ConnectionGene(tmpStruct.NewConnectionGene_Output.InnovationId, newNeuronGene.InnovationId, connectionToReplace.TargetNeuronId, connectionToReplace.Weight);
			}

			// Add the new genes to the genome.
			neuronGeneList.Add(newNeuronGene);
			connectionGeneList.InsertIntoPosition(newConnectionGene1);
			connectionGeneList.InsertIntoPosition(newConnectionGene2);
		}
        // Schrum: Module Mutation Duplicate creates a new module with
        // links copying those of another module.
        private void Module_Mutation_Duplicate(EvolutionAlgorithm ea)
        {
            // Push all output neurons together
            this.neuronGeneList.SortByNeuronOrder();
            int numModules = this.outputNeuronCount / this.outputsPerPolicy; // Should evenly divide
            int randomModule = Utilities.Next(numModules); // Duplicate this module
            // Because outputs come after inputs.
            // Although list is 0-indexed, the +1 is needed because the bias does not count as an input
            double outputLayer = neuronGeneList[1 + inputNeuronCount].Layer;
            // Create the new module one neuron per loop iteration
            for (int i = 0; i < outputsPerPolicy; i++)
            {
                // The activation function for the output layer
                IActivationFunction outputActFunction = ActivationFunctionFactory.GetActivationFunction("BipolarSigmoid");
                NeuronGene newNeuronGene = new NeuronGene(null, ea.NextInnovationId, outputLayer, NeuronType.Output, outputActFunction);
                neuronGeneList.Add(newNeuronGene);

                uint randomModuleInnovation = neuronGeneList[1 + inputNeuronCount + (randomModule * outputsPerPolicy) + i].InnovationId;
                // Copy each connection to the new module neuron
                int originalLength = ConnectionGeneList.Count; // Don't need to check the newly added connections
                for (int j = 0; j < originalLength; j++)
                {
                    ConnectionGene cg = ConnectionGeneList[j];
                    if (cg.TargetNeuronId == randomModuleInnovation)
                    {
                        // Copy the link
                        ConnectionGene connection = new ConnectionGene(ea.NextInnovationId, cg.SourceNeuronId, newNeuronGene.InnovationId, cg.Weight);
                        connectionGeneList.InsertIntoPosition(connection);
                    }
                }
                this.outputNeuronCount++; // Increase number of outputs
            }
        }
        // Schrum: Module Mutation Random creates a new module with
        // completely random incoming links.
        private void Module_Mutation_Random(EvolutionAlgorithm ea)
        {
            // Push all output neurons together
            this.neuronGeneList.SortByNeuronOrder();
            int numModules = this.outputNeuronCount / this.outputsPerPolicy; // Should evenly divide
            int randomModule = Utilities.Next(numModules);
            // Because outputs come after inputs.
            // Although list is 0-indexed, the +1 is needed because the bias does not count as an input
            double outputLayer = neuronGeneList[1 + inputNeuronCount].Layer;
            // Create the new module one neuron per loop iteration
            for (int i = 0; i < outputsPerPolicy; i++)
            {
                // The activation function for the output layer
                IActivationFunction outputActFunction = ActivationFunctionFactory.GetActivationFunction("BipolarSigmoid");
                NeuronGene newNeuronGene = new NeuronGene(null, ea.NextInnovationId, outputLayer, NeuronType.Output, outputActFunction);
                neuronGeneList.Add(newNeuronGene);

                // Count links to random output neuron: bias, then inputs, then random module, then neuron within that module
                uint randomModuleInnovation = neuronGeneList[1 + inputNeuronCount + (randomModule * outputsPerPolicy) + i].InnovationId;
                int numIncoming = 0;
                foreach (ConnectionGene cg in this.ConnectionGeneList)
                {
                    // Count the link
                    if (cg.TargetNeuronId == randomModuleInnovation)
                        numIncoming++;
                }

                // Give the new module (up to) the same number of links as some other module
                for (int j = 0; j < numIncoming; j++) // Will always create ay least one link
                {
                    uint randomSource = NeuronGeneList[Utilities.Next(NeuronGeneList.Count)].InnovationId;
                    // Magic equation stolen from Mutate_AddConnection below
                    double randomWeight = (Utilities.NextDouble() * ea.NeatParameters.connectionWeightRange/4.0) - ea.NeatParameters.connectionWeightRange/8.0;
                    if (!TestForExistingConnection(randomSource, newNeuronGene.InnovationId)) // Only create each connection once
                    {
                        ConnectionGene connection = new ConnectionGene(ea.NextInnovationId, randomSource, newNeuronGene.InnovationId, randomWeight);
                        connectionGeneList.InsertIntoPosition(connection);
                    }
                }
                this.outputNeuronCount++; // Increase number of outputs
            }
        }
		/// <summary>
		/// Add a new node to the Genome. We do this by removing a connection at random and inserting 
		/// a new node and two new connections that make the same circuit as the original connection.
		/// 
		/// This way the new node is properly integrated into the network from the outset.
		/// </summary>
		/// <param name="ea"></param>
		private void Mutate_AddNode(EvolutionAlgorithm ea)
		{
			if(connectionGeneList.Count==0)
				return;

            for (int attempts = 0; attempts < 5; attempts++)
            {
                // Select a connection at random.
                int connectionToReplaceIdx = (int)Math.Floor(Utilities.NextDouble() * connectionGeneList.Count);
                ConnectionGene connectionToReplace = connectionGeneList[connectionToReplaceIdx];

                // Delete the existing connection. JOEL: Why delete old connection?
                connectionGeneList.RemoveAt(connectionToReplaceIdx);

                // Check if this connection has already been split on another genome. If so then we should re-use the
                // neuron ID and two connection ID's so that matching structures within the population maintain the same ID.
                object existingNeuronGeneStruct = ea.NewNeuronGeneStructTable[connectionToReplace.InnovationId];

                NeuronGene newNeuronGene;
                ConnectionGene newConnectionGene1;
                ConnectionGene newConnectionGene2;
                IActivationFunction actFunct;

                // JUSTIN: Calculate the layer for this new neuron as halfway between the source and the target neuron layers
                float sourceLayer = neuronGeneList.GetNeuronById(connectionToReplace.SourceNeuronId).Layer;
                float targetLayer = neuronGeneList.GetNeuronById(connectionToReplace.TargetNeuronId).Layer;
                float newLayer = ((sourceLayer + targetLayer) / 2);

                if (existingNeuronGeneStruct == null)
                {	// No existing matching structure, so generate some new ID's.

                    //TODO: DAVID proper random activation function
                    // Replace connectionToReplace with two new connections and a neuron.
                    // JUSTIN: If using neatBrain, then do NOT get a random actv. function!!
                    actFunct = ea.neatBrain ? ActivationFunctionFactory.GetActivationFunction(HyperNEATParameters.substrateActivationFunction.FunctionId) : ActivationFunctionFactory.GetRandomActivationFunction(ea.NeatParameters);
                    newNeuronGene = new NeuronGene(ea.NextInnovationId, NeuronType.Hidden, actFunct, newLayer);
                    newConnectionGene1 = new ConnectionGene(ea.NextInnovationId, connectionToReplace.SourceNeuronId, newNeuronGene.InnovationId, 1.0);
                    newConnectionGene2 = new ConnectionGene(ea.NextInnovationId, newNeuronGene.InnovationId, connectionToReplace.TargetNeuronId, connectionToReplace.Weight);

                    // Register the new ID's with NewNeuronGeneStructTable.
                    ea.NewNeuronGeneStructTable.Add(connectionToReplace.InnovationId,
                                                    new NewNeuronGeneStruct(newNeuronGene, newConnectionGene1, newConnectionGene2));
                }
                else
                {	// An existing matching structure has been found. Re-use its ID's

                    //DAVID: Trying to add a node where one already exists, reject, is this good?
                    if (neuronGeneList.GetNeuronById(((NewNeuronGeneStruct)existingNeuronGeneStruct).NewNeuronGene.InnovationId) != null)
                    {
                        continue;
                    }
                    //TODO: DAVID proper random activation function
                    // Replace connectionToReplace with two new connections and a neuron.
                    // JUSTIN: If using neatBrain, then do NOT get a random actv. function!!
                    actFunct = ea.neatBrain ? ActivationFunctionFactory.GetActivationFunction(HyperNEATParameters.substrateActivationFunction.FunctionId) : ActivationFunctionFactory.GetRandomActivationFunction(ea.NeatParameters);
                    NewNeuronGeneStruct tmpStruct = (NewNeuronGeneStruct)existingNeuronGeneStruct;
                    newNeuronGene = new NeuronGene(tmpStruct.NewNeuronGene.InnovationId, NeuronType.Hidden, actFunct, newLayer);
                    newConnectionGene1 = new ConnectionGene(tmpStruct.NewConnectionGene_Input.InnovationId, connectionToReplace.SourceNeuronId, newNeuronGene.InnovationId, (Utilities.NextDouble() * ea.NeatParameters.connectionWeightRange) - ea.NeatParameters.connectionWeightRange / 2.0);
                    newConnectionGene2 = new ConnectionGene(tmpStruct.NewConnectionGene_Output.InnovationId, newNeuronGene.InnovationId, connectionToReplace.TargetNeuronId, (Utilities.NextDouble() * ea.NeatParameters.connectionWeightRange / 8.0) - ea.NeatParameters.connectionWeightRange / 16.0);
                }

                // Add the new genes to the genome.
                //Debug.Assert();
                neuronGeneList.Add(newNeuronGene);
                connectionGeneList.InsertIntoPosition(newConnectionGene1);
                connectionGeneList.InsertIntoPosition(newConnectionGene2);
                break;
            }
		}
		/// <summary>
		/// Create a default minimal genome that describes a NN with the given number of inputs and outputs.
		/// </summary>
		/// <returns></returns>
		public static IGenome CreateGenome(NeatParameters neatParameters, IdGenerator idGenerator, int inputNeuronCount, int outputNeuronCount, int outputsPerPolicy, float connectionProportion)
		{
            IActivationFunction actFunct;
			NeuronGene neuronGene; // temp variable.
			NeuronGeneList inputNeuronGeneList = new NeuronGeneList(); // includes bias neuron.
			NeuronGeneList outputNeuronGeneList = new NeuronGeneList();
			NeuronGeneList neuronGeneList = new NeuronGeneList();
			ConnectionGeneList connectionGeneList = new ConnectionGeneList();

			// IMPORTANT NOTE: The neurons must all be created prior to any connections. That way all of the genomes
			// will obtain the same innovation ID's for the bias,input and output nodes in the initial population.
			// Create a single bias neuron.
            //TODO: DAVID proper activation function change to NULL?
            actFunct = ActivationFunctionFactory.GetActivationFunction("NullFn");
            //neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Bias, actFunct);
            neuronGene = new NeuronGene(null, idGenerator.NextInnovationId, NeuronGene.INPUT_LAYER, NeuronType.Bias, actFunct);
			inputNeuronGeneList.Add(neuronGene);
			neuronGeneList.Add(neuronGene);

			// Create input neuron genes.
            actFunct = ActivationFunctionFactory.GetActivationFunction("NullFn");
			for(int i=0; i<inputNeuronCount; i++)
			{
                //TODO: DAVID proper activation function change to NULL?
                //neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Input, actFunct);
                neuronGene = new NeuronGene(null, idGenerator.NextInnovationId, NeuronGene.INPUT_LAYER, NeuronType.Input, actFunct);
				inputNeuronGeneList.Add(neuronGene);
				neuronGeneList.Add(neuronGene);
			}

			// Create output neuron genes. 
            //actFunct = ActivationFunctionFactory.GetActivationFunction("NullFn");
			for(int i=0; i<outputNeuronCount; i++)
			{
                actFunct = ActivationFunctionFactory.GetActivationFunction("BipolarSigmoid");
                //actFunct = ActivationFunctionFactory.GetRandomActivationFunction(neatParameters);
                //TODO: DAVID proper activation function
                //neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Output, actFunct);
                neuronGene = new NeuronGene(null, idGenerator.NextInnovationId, NeuronGene.OUTPUT_LAYER, NeuronType.Output, actFunct);
				outputNeuronGeneList.Add(neuronGene);
				neuronGeneList.Add(neuronGene);
			}

			// Loop over all possible connections from input to output nodes and create a number of connections based upon
			// connectionProportion.
			foreach(NeuronGene targetNeuronGene in outputNeuronGeneList)
			{
				foreach(NeuronGene sourceNeuronGene in inputNeuronGeneList)
				{
					// Always generate an ID even if we aren't going to use it. This is necessary to ensure connections
					// between the same neurons always have the same ID throughout the generated population.
					uint connectionInnovationId = idGenerator.NextInnovationId;

					if(Utilities.NextDouble() < connectionProportion)
					{	// Ok lets create a connection.
						connectionGeneList.Add(	new ConnectionGene(connectionInnovationId, 
							sourceNeuronGene.InnovationId,
							targetNeuronGene.InnovationId,
							(Utilities.NextDouble() * neatParameters.connectionWeightRange ) - neatParameters.connectionWeightRange/2.0));  // Weight 0 +-5
					}
				}
			}

            // Don't create any hidden nodes at this point. Fundamental to the NEAT way is to start minimally!
            // Schrum: Added outputsPerPolicy: If outputsPerPolicy == outputNeuronCount, then behaves like default NEAT
            return new NeatGenome(idGenerator.NextGenomeId, neuronGeneList, connectionGeneList, inputNeuronCount, outputNeuronCount, outputsPerPolicy);
		}
Exemple #11
0
        public static IGenome CreateGenomePreserveID(NeatParameters neatParameters, IdGenerator idGenerator, int inputNeuronCount, int outputNeuronCount, float connectionProportion)
        {
            IActivationFunction actFunct;
            NeuronGene          neuronGene;                                  // temp variable.
            NeuronGeneList      inputNeuronGeneList  = new NeuronGeneList(); // includes bias neuron.
            NeuronGeneList      outputNeuronGeneList = new NeuronGeneList();
            NeuronGeneList      neuronGeneList       = new NeuronGeneList();
            ConnectionGeneList  connectionGeneList   = new ConnectionGeneList();

            int nodeCount = 0;

            WINManager win = WINManager.SharedWIN;

            // IMPORTANT NOTE: The neurons must all be created prior to any connections. That way all of the genomes
            // will obtain the same innovation ID's for the bias,input and output nodes in the initial population.
            // Create a single bias neuron.
            //TODO: DAVID proper activation function change to NULL?
            actFunct = ActivationFunctionFactory.GetActivationFunction("NullFn");
            //neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Bias, actFunct);
            WINNode neuronNode = win.findOrInsertNodeWithProperties(idGenerator,
                                                                    WINNode.NodeWithProperties(nodeCount++, NeuronType.Bias)
                                                                    );

            neuronGene = new NeuronGene(null, neuronNode.UniqueID, NeuronGene.INPUT_LAYER, NeuronType.Bias, actFunct);
            inputNeuronGeneList.Add(neuronGene);
            neuronGeneList.Add(neuronGene);

            // Create input neuron genes.
            actFunct = ActivationFunctionFactory.GetActivationFunction("NullFn");
            for (int i = 0; i < inputNeuronCount; i++)
            {
                //TODO: DAVID proper activation function change to NULL?
                //neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Input, actFunct);
                neuronNode = win.findOrInsertNodeWithProperties(idGenerator, WINNode.NodeWithProperties(nodeCount++, NeuronType.Input));

                neuronGene = new NeuronGene(null, neuronNode.UniqueID, NeuronGene.INPUT_LAYER, NeuronType.Input, actFunct);
                inputNeuronGeneList.Add(neuronGene);
                neuronGeneList.Add(neuronGene);
            }

            // Create output neuron genes.
            //actFunct = ActivationFunctionFactory.GetActivationFunction("NullFn");
            for (int i = 0; i < outputNeuronCount; i++)
            {
                actFunct = ActivationFunctionFactory.GetActivationFunction("BipolarSigmoid");
                //actFunct = ActivationFunctionFactory.GetRandomActivationFunction(neatParameters);
                //TODO: DAVID proper activation function
                //neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Output, actFunct);
                neuronNode = win.findOrInsertNodeWithProperties(idGenerator, WINNode.NodeWithProperties(nodeCount++, NeuronType.Output));

                neuronGene = new NeuronGene(null, neuronNode.UniqueID, NeuronGene.OUTPUT_LAYER, NeuronType.Output, actFunct);
                outputNeuronGeneList.Add(neuronGene);
                neuronGeneList.Add(neuronGene);
            }


            int           currentConnCount = 0;
            WINConnection winConn;

            // Loop over all possible connections from input to output nodes and create a number of connections based upon
            // connectionProportion.
            foreach (NeuronGene targetNeuronGene in outputNeuronGeneList)
            {
                foreach (NeuronGene sourceNeuronGene in inputNeuronGeneList)
                {
                    // Always generate an ID even if we aren't going to use it. This is necessary to ensure connections
                    // between the same neurons always have the same ID throughout the generated population.
                    //PAUL NOTE:
                    //instead of generating and not using and id, we use the target and connection properties to uniquely identify a connection in WIN
                    //uint connectionInnovationId = idGenerator.NextInnovationId;

                    if (Utilities.NextDouble() < connectionProportion)
                    {
                        // Ok lets create a connection.
                        //first we search or create the winconnection object
                        winConn = win.findOrInsertConnectionWithProperties(idGenerator,
                                                                           WINConnection.ConnectionWithProperties(currentConnCount, sourceNeuronGene.InnovationId, targetNeuronGene.InnovationId));

                        //our winconn will have our innovationID, and our weight like normal
                        //this will also respect the idgenerator, since it gets sent in as well, for legacy purposes
                        connectionGeneList.Add(new ConnectionGene(winConn.UniqueID,
                                                                  sourceNeuronGene.InnovationId,
                                                                  targetNeuronGene.InnovationId,
                                                                  (Utilities.NextDouble() * neatParameters.connectionWeightRange) - neatParameters.connectionWeightRange / 2.0)
                                               );
                        //(Utilities.NextDouble() * neatParameters.connectionWeightRange) - neatParameters.connectionWeightRange / 2.0));  // Weight 0 +-5
                    }

                    currentConnCount++;
                }
            }
            //WIN will eventually be in control of all the genomes that are created as well, but not quite yet!
            //TODO: WIN should be generating genomeIDs explicitly

            // Don't create any hidden nodes at this point. Fundamental to the NEAT way is to start minimally!
            return(new NeatGenome(idGenerator.NextGenomeId, neuronGeneList, connectionGeneList, inputNeuronCount, outputNeuronCount));
        }
Exemple #12
0
        public static IGenome CreateGenomePreserveID(NeatParameters neatParameters, IdGenerator idGenerator, int inputNeuronCount, int outputNeuronCount, float connectionProportion)
        {
            IActivationFunction actFunct;
            NeuronGene neuronGene; // temp variable.
            NeuronGeneList inputNeuronGeneList = new NeuronGeneList(); // includes bias neuron.
            NeuronGeneList outputNeuronGeneList = new NeuronGeneList();
            NeuronGeneList neuronGeneList = new NeuronGeneList();
            ConnectionGeneList connectionGeneList = new ConnectionGeneList();

            int nodeCount = 0;

            WINManager win = WINManager.SharedWIN;

            // IMPORTANT NOTE: The neurons must all be created prior to any connections. That way all of the genomes
            // will obtain the same innovation ID's for the bias,input and output nodes in the initial population.
            // Create a single bias neuron.
            //TODO: DAVID proper activation function change to NULL?
            actFunct = ActivationFunctionFactory.GetActivationFunction("NullFn");
            //neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Bias, actFunct);
            WINNode neuronNode = win.findOrInsertNodeWithProperties(idGenerator,
                WINNode.NodeWithProperties(nodeCount++, NeuronType.Bias)
                );

            neuronGene = new NeuronGene(null, neuronNode.UniqueID, NeuronGene.INPUT_LAYER, NeuronType.Bias, actFunct);
            inputNeuronGeneList.Add(neuronGene);
            neuronGeneList.Add(neuronGene);

            // Create input neuron genes.
            actFunct = ActivationFunctionFactory.GetActivationFunction("NullFn");
            for (int i = 0; i < inputNeuronCount; i++)
            {
                //TODO: DAVID proper activation function change to NULL?
                //neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Input, actFunct);
                neuronNode = win.findOrInsertNodeWithProperties(idGenerator, WINNode.NodeWithProperties(nodeCount++, NeuronType.Input));

                neuronGene = new NeuronGene(null, neuronNode.UniqueID, NeuronGene.INPUT_LAYER, NeuronType.Input, actFunct);
                inputNeuronGeneList.Add(neuronGene);
                neuronGeneList.Add(neuronGene);
            }

            // Create output neuron genes.
            //actFunct = ActivationFunctionFactory.GetActivationFunction("NullFn");
            for (int i = 0; i < outputNeuronCount; i++)
            {
                actFunct = ActivationFunctionFactory.GetActivationFunction("BipolarSigmoid");
                //actFunct = ActivationFunctionFactory.GetRandomActivationFunction(neatParameters);
                //TODO: DAVID proper activation function
                //neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Output, actFunct);
                neuronNode = win.findOrInsertNodeWithProperties(idGenerator, WINNode.NodeWithProperties(nodeCount++, NeuronType.Output));

                neuronGene = new NeuronGene(null, neuronNode.UniqueID, NeuronGene.OUTPUT_LAYER, NeuronType.Output, actFunct);
                outputNeuronGeneList.Add(neuronGene);
                neuronGeneList.Add(neuronGene);
            }

            int currentConnCount = 0;
            WINConnection winConn;
            // Loop over all possible connections from input to output nodes and create a number of connections based upon
            // connectionProportion.
            foreach (NeuronGene targetNeuronGene in outputNeuronGeneList)
            {
                foreach (NeuronGene sourceNeuronGene in inputNeuronGeneList)
                {
                    // Always generate an ID even if we aren't going to use it. This is necessary to ensure connections
                    // between the same neurons always have the same ID throughout the generated population.
                    //PAUL NOTE:
                    //instead of generating and not using and id, we use the target and connection properties to uniquely identify a connection in WIN
                    //uint connectionInnovationId = idGenerator.NextInnovationId;

                    if (Utilities.NextDouble() < connectionProportion)
                    {
                        // Ok lets create a connection.
                        //first we search or create the winconnection object
                        winConn = win.findOrInsertConnectionWithProperties(idGenerator,
                            WINConnection.ConnectionWithProperties(currentConnCount, sourceNeuronGene.InnovationId, targetNeuronGene.InnovationId));

                        //our winconn will have our innovationID, and our weight like normal
                        //this will also respect the idgenerator, since it gets sent in as well, for legacy purposes
                        connectionGeneList.Add(new ConnectionGene(winConn.UniqueID,
                            sourceNeuronGene.InnovationId,
                            targetNeuronGene.InnovationId,
                            (Utilities.NextDouble() * neatParameters.connectionWeightRange) - neatParameters.connectionWeightRange / 2.0)
                            );
                            //(Utilities.NextDouble() * neatParameters.connectionWeightRange) - neatParameters.connectionWeightRange / 2.0));  // Weight 0 +-5
                    }

                    currentConnCount++;

                }
            }
            //WIN will eventually be in control of all the genomes that are created as well, but not quite yet!
            //TODO: WIN should be generating genomeIDs explicitly

            // Don't create any hidden nodes at this point. Fundamental to the NEAT way is to start minimally!
            return new NeatGenome(idGenerator.NextGenomeId, neuronGeneList, connectionGeneList, inputNeuronCount, outputNeuronCount);
        }
Exemple #13
0
        /// <summary>
        /// Add a new node to the Genome. We do this by removing a connection at random and inserting 
        /// a new node and two new connections that make the same circuit as the original connection.
        /// 
        /// This way the new node is properly integrated into the network from the outset.
        /// </summary>
        /// <param name="ea"></param>
        private void Mutate_AddNode(NeatParameters neatParameters, IdGenerator idGen, Hashtable NewNeuronGeneStructTable)
        {
            if(connectionGeneList.Count==0)
                return;

            // Select a connection at random.
            int connectionToReplaceIdx = (int)Math.Floor(Utilities.NextDouble() * connectionGeneList.Count);
            ConnectionGene connectionToReplace = connectionGeneList[connectionToReplaceIdx];

            // Delete the existing connection. JOEL: Why delete old connection?
            //connectionGeneList.RemoveAt(connectionToReplaceIdx);

            // Check if this connection has already been split on another genome. If so then we should re-use the
            // neuron ID and two connection ID's so that matching structures within the population maintain the same ID.
            object existingNeuronGeneStruct = NewNeuronGeneStructTable[connectionToReplace.InnovationId];

            NeuronGene newNeuronGene;
            ConnectionGene newConnectionGene1;
            ConnectionGene newConnectionGene2;
            IActivationFunction actFunct;

            WINManager win = WINManager.SharedWIN;
            WINNode node;
            WINConnection connection;

            if(existingNeuronGeneStruct==null)
            {	// No existing matching structure, so generate some new ID's.

                //TODO: DAVID proper random activation function
                // Replace connectionToReplace with two new connections and a neuron.
                actFunct=ActivationFunctionFactory.GetRandomActivationFunction(neatParameters);
                //newNeuronGene = new NeuronGene(ea.NextInnovationId, NeuronType.Hidden, actFunct);

                //we should be making a call to WIN anytime we call for a new innovationID

                //here we create a new node, and two new connections
                //we don't send in a genomeID here, so we get it set for us!
                node = win.createWINNode(new PropertyObject() { { WINNode.SNodeString, NeuronType.Hidden.ToString()}});
                newNeuronGene = new NeuronGene(null, node.UniqueID, (neuronGeneList.GetNeuronById(connectionToReplace.SourceNeuronId).Layer + neuronGeneList.GetNeuronById(connectionToReplace.TargetNeuronId).Layer) / 2.0, NeuronType.Hidden, actFunct);

                //we don't send in any uniqueIDs so they are generated for us, and we update our idGen object
                connection = win.createWINConnection(WINConnection.ConnectionWithProperties(connectionToReplace.SourceNeuronId, newNeuronGene.InnovationId), idGen);
                newConnectionGene1 = new ConnectionGene(connection.UniqueID, connection.SourceID,  connection.TargetID , 1.0);

                //we don't send in any uniqueIDs so they are generated for us, and we update our idGen object
                connection = win.createWINConnection(WINConnection.ConnectionWithProperties(newNeuronGene.InnovationId, connectionToReplace.TargetNeuronId), idGen);
                newConnectionGene2 = new ConnectionGene(connection.UniqueID, connection.SourceID, connection.TargetID, connectionToReplace.Weight);

                // Register the new ID's with NewNeuronGeneStructTable.
                NewNeuronGeneStructTable.Add(connectionToReplace.InnovationId,
                                                new NewNeuronGeneStruct(newNeuronGene, newConnectionGene1, newConnectionGene2));
            }
            else
            {	// An existing matching structure has been found. Re-use its ID's

                //Since we don't call idGen.nextinnovationID,  we don't have to create anything new
                //however, we need to note the change in weights override previous weight values
                //this should be documented in WIN - either explicitly (calling a function to note changes) or implicitly (by scanning the changes in a saved genome)
                //Probably explicitly, calling a mutate function for a SingleStep (since we are in the SingleStep process of creating new individuals)

                //TODO: DAVID proper random activation function
                // Replace connectionToReplace with two new connections and a neuron.
                actFunct = ActivationFunctionFactory.GetRandomActivationFunction(neatParameters);
                NewNeuronGeneStruct tmpStruct = (NewNeuronGeneStruct)existingNeuronGeneStruct;
                //newNeuronGene = new NeuronGene(tmpStruct.NewNeuronGene.InnovationId, NeuronType.Hidden, actFunct);
                newNeuronGene = new NeuronGene(null, tmpStruct.NewNeuronGene.InnovationId, tmpStruct.NewNeuronGene.Layer, NeuronType.Hidden, actFunct);

                newConnectionGene1 = new ConnectionGene(tmpStruct.NewConnectionGene_Input.InnovationId, connectionToReplace.SourceNeuronId, newNeuronGene.InnovationId, 1.0);
                newConnectionGene2 = new ConnectionGene(tmpStruct.NewConnectionGene_Output.InnovationId, newNeuronGene.InnovationId, connectionToReplace.TargetNeuronId, connectionToReplace.Weight);
            }

            // Add the new genes to the genome.
            neuronGeneList.Add(newNeuronGene);
            connectionGeneList.InsertIntoPosition(newConnectionGene1);
            connectionGeneList.InsertIntoPosition(newConnectionGene2);
        }
Exemple #14
0
        private void Mutate_AddModule(NeatParameters neatParameters, IdGenerator idGen, Hashtable NewConnectionGeneTable)
        {
            // Find all potential inputs, or quit if there are not enough.
            // Neurons cannot be inputs if they are dummy input nodes created for another module.
            NeuronGeneList potentialInputs = new NeuronGeneList();
            foreach (NeuronGene n in neuronGeneList) {
                if (!(n.ActivationFunction is ModuleInputNeuron)) {
                    potentialInputs.Add(n);
                }
            }
            if (potentialInputs.Count < 1)
                return;

            // Find all potential outputs, or quit if there are not enough.
            // Neurons cannot be outputs if they are dummy input or output nodes created for another module, or network input or bias nodes.
            NeuronGeneList potentialOutputs = new NeuronGeneList();
            foreach (NeuronGene n in neuronGeneList) {
                if (n.NeuronType != NeuronType.Bias && n.NeuronType != NeuronType.Input
                        && !(n.ActivationFunction is ModuleInputNeuron)
                        && !(n.ActivationFunction is ModuleOutputNeuron)) {
                    potentialOutputs.Add(n);
                }
            }
            if (potentialOutputs.Count < 1)
                return;

            // Pick a new function for the new module.
            IModule func = ModuleFactory.GetRandom();

            WINManager win = WINManager.SharedWIN;
            WINNode node;
            WINConnection connection;

            // Choose inputs uniformly at random, with replacement.
            // Create dummy neurons to represent the module's inputs.
            // Create connections between the input nodes and the dummy neurons.
            IActivationFunction inputFunction = ActivationFunctionFactory.GetActivationFunction("ModuleInputNeuron");
            List<long> inputDummies = new List<long>(func.InputCount);
            for (int i = 0; i < func.InputCount; i++) {

                //we are calling nextinnovationID, this is the place for WIN!
                //in reality, win should know the activation function as well, but that's not currently implemented

                //here we create a new node, and two new connections
                //we don't send in a genomeID here, so we get it set for us!
                //do we need to inform it of the activation function? I think so?
                node = win.createWINNode(new PropertyObject() { { WINNode.SNodeString, NeuronType.Hidden.ToString()}});

                NeuronGene newNeuronGene = new NeuronGene(node.UniqueID, NeuronType.Hidden, inputFunction);
                neuronGeneList.Add(newNeuronGene);

                long sourceId = potentialInputs[Utilities.Next(potentialInputs.Count)].InnovationId;
                long targetId = newNeuronGene.InnovationId;

                inputDummies.Add(targetId);

                //aha! we must call the innovationID again, we check against win

                //we don't send in any uniqueIDs so they are generated for us, and we update our idGen object
                connection = win.createWINConnection(
                    WINConnection.ConnectionWithProperties(sourceId, targetId), idGen);

                // Create a new connection with a new ID and add it to the Genome.
                ConnectionGene newConnectionGene = new ConnectionGene(connection.UniqueID, connection.SourceID, connection.TargetID,
                    (Utilities.NextDouble() * neatParameters.connectionWeightRange) - neatParameters.connectionWeightRange / 2.0
                    );

                // Register the new connection with NewConnectionGeneTable.
                ConnectionEndpointsStruct connectionKey = new ConnectionEndpointsStruct(sourceId, targetId);
                NewConnectionGeneTable.Add(connectionKey, newConnectionGene);

                // Add the new gene to this genome. We have a new ID so we can safely append the gene to the end
                // of the list without risk of breaking the innovation ID order.
                connectionGeneList.Add(newConnectionGene);
            }

            // Choose outputs uniformly at random, with replacement.
            // Create dummy neurons to represent the module's outputs.
            // Create connections between the output nodes and the dummy neurons.
            IActivationFunction outputFunction = ActivationFunctionFactory.GetActivationFunction("ModuleOutputNeuron");
            List<long> outputDummies = new List<long>(func.OutputCount);
            for (int i = 0; i < func.OutputCount; i++) {

                node = win.createWINNode(new PropertyObject() { { WINNode.SNodeString, NeuronType.Hidden.ToString() } });

                NeuronGene newNeuronGene = new NeuronGene(node.UniqueID, NeuronType.Hidden, outputFunction);
                neuronGeneList.Add(newNeuronGene);

                long sourceId = newNeuronGene.InnovationId;
                long targetId = potentialOutputs[Utilities.Next(potentialOutputs.Count)].InnovationId;

                outputDummies.Add(sourceId);

                connection = win.createWINConnection(
                   WINConnection.ConnectionWithProperties(sourceId, targetId), idGen);

                // Create a new connection with a new ID and add it to the Genome.
                ConnectionGene newConnectionGene = new ConnectionGene(connection.UniqueID, connection.SourceID, connection.TargetID,
                   (Utilities.NextDouble() * neatParameters.connectionWeightRange) - neatParameters.connectionWeightRange / 2.0
                    );

                    //new ConnectionGene(idGen.NextInnovationId, sourceId, targetId,
                    //(Utilities.NextDouble() * neatParameters.connectionWeightRange) - neatParameters.connectionWeightRange / 2.0);

                // Register the new connection with NewConnectionGeneTable.
                ConnectionEndpointsStruct connectionKey = new ConnectionEndpointsStruct(sourceId, targetId);
                NewConnectionGeneTable.Add(connectionKey, newConnectionGene);

                // Add the new gene to this genome. We have a new ID so we can safely append the gene to the end
                // of the list without risk of breaking the innovation ID order.
                connectionGeneList.Add(newConnectionGene);
            }

            // Pick a new ID for the new module and create it.
            // Modules do not participate in history comparisons, so we will always create a new innovation ID.
            // We can change this here if it becomes a problem.

            //TODO: Paul check win conditions here
            //this is confusing from a WIN perspective, I don't know if I'm going to support modules
            //I can create a generic NextInnovationID object, but don't know if it's worth it

            ModuleGene newModule = new ModuleGene(idGen.NextInnovationId, func, inputDummies, outputDummies);
            moduleGeneList.Add(newModule);
        }
        /// <summary>
        /// Create a default minimal genome that describes a NN with the given number of inputs and outputs.
        /// </summary>
        /// <returns></returns>
        public static IGenome CreateGenome(NeatParameters neatParameters, IdGenerator idGenerator, int inputNeuronCount, int outputNeuronCount, float connectionProportion, bool neatBrain)
        {
            IActivationFunction actFunct;
            NeuronGene          neuronGene;                                  // temp variable.
            NeuronGeneList      inputNeuronGeneList  = new NeuronGeneList(); // includes bias neuron.
            NeuronGeneList      outputNeuronGeneList = new NeuronGeneList();
            NeuronGeneList      neuronGeneList       = new NeuronGeneList();
            ConnectionGeneList  connectionGeneList   = new ConnectionGeneList();

            // IMPORTANT NOTE: The neurons must all be created prior to any connections. That way all of the genomes
            // will obtain the same innovation ID's for the bias,input and output nodes in the initial population.
            // Create a single bias neuron.
            //TODO: DAVID proper activation function change to NULL?

            actFunct   = ActivationFunctionFactory.GetActivationFunction("NullFn");
            neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Bias, actFunct, 0f);
            inputNeuronGeneList.Add(neuronGene);
            neuronGeneList.Add(neuronGene);

            // Create input neuron genes.

            for (int i = 0; i < inputNeuronCount; i++)
            {
                //TODO: DAVID proper activation function change to NULL?
                neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Input, actFunct, 0f);
                inputNeuronGeneList.Add(neuronGene);
                neuronGeneList.Add(neuronGene);
            }

            // Create output neuron genes.
            if (neatBrain)
            {
                actFunct = ActivationFunctionFactory.GetActivationFunction("SteepenedSigmoidApproximation");
            }
            else
            {
                actFunct = ActivationFunctionFactory.GetActivationFunction("BipolarSigmoid");
            }
            for (int i = 0; i < outputNeuronCount; i++)
            {
                //actFunct = ActivationFunctionFactory.GetRandomActivationFunction(neatParameters);
                //TODO: DAVID proper activation function
                neuronGene = new NeuronGene(idGenerator.NextInnovationId, NeuronType.Output, actFunct, 1f);
                outputNeuronGeneList.Add(neuronGene);
                neuronGeneList.Add(neuronGene);
            }

            // Loop over all possible connections from input to output nodes and create a number of connections based upon
            // connectionProportion.
            foreach (NeuronGene targetNeuronGene in outputNeuronGeneList)
            {
                foreach (NeuronGene sourceNeuronGene in inputNeuronGeneList)
                {
                    // Always generate an ID even if we aren't going to use it. This is necessary to ensure connections
                    // between the same neurons always have the same ID throughout the generated population.
                    uint connectionInnovationId = idGenerator.NextInnovationId;

                    if (Utilities.NextDouble() < connectionProportion)
                    {                           // Ok lets create a connection.
                        connectionGeneList.Add(new ConnectionGene(connectionInnovationId,
                                                                  sourceNeuronGene.InnovationId,
                                                                  targetNeuronGene.InnovationId,
                                                                  (Utilities.NextDouble() * neatParameters.connectionWeightRange) - neatParameters.connectionWeightRange / 2.0)); // Weight 0 +-5
                    }
                }
            }

            // Don't create any hidden nodes at this point. Fundamental to the NEAT way is to start minimally!
            return(new NeatGenome(idGenerator.NextGenomeId, neuronGeneList, connectionGeneList, inputNeuronCount, outputNeuronCount));
        }
 /// <summary>
 /// Copy constructor.
 /// </summary>
 /// <param name="copyFrom"></param>
 public NeuronGene(NeuronGene copyFrom)
 {
     this.innovationId       = copyFrom.innovationId;
     this.neuronType         = copyFrom.neuronType;
     this.activationFunction = copyFrom.activationFunction;
 }
        // Schrum: Simple form of Module Mutation, MM(P)
        private void Module_Mutation_Previous(EvolutionAlgorithm ea)
        {
            // Push all output neurons together
            this.neuronGeneList.SortByNeuronOrder();
            int numModules = this.outputNeuronCount / this.outputsPerPolicy; // Should evenly divide
            int randomModule = Utilities.Next(numModules);
            // Because outputs come after inputs.
            // Although list is 0-indexed, the +1 is needed because the bias does not count as an input
            double outputLayer = neuronGeneList[1 + inputNeuronCount].Layer; 
            // Create the new module
            for (int i = 0; i < outputsPerPolicy; i++)
            {
                IActivationFunction outputActFunction = ActivationFunctionFactory.GetActivationFunction("BipolarSigmoid");
                NeuronGene newNeuronGene = new NeuronGene(null, ea.NextInnovationId, outputLayer, NeuronType.Output, outputActFunction);
                neuronGeneList.Add(newNeuronGene);
                // Link to the new neuron: bias, then inputs, then appropriate module, then neuron within that module
                uint sourceNeuron = neuronGeneList[1 + inputNeuronCount + (randomModule * outputsPerPolicy) + i].InnovationId;
                ConnectionGene connection = new ConnectionGene(ea.NextInnovationId, sourceNeuron, newNeuronGene.InnovationId, 1.0);
                connectionGeneList.InsertIntoPosition(connection);
                this.outputNeuronCount++; // Increase number of outputs
            }

            // Schrum: Debugging
            //Console.WriteLine("MM(P): outputNeuronCount=" + outputNeuronCount);
            // Schrum: More debugging
            /*
            this.neuronGeneList.SortByInnovationId();
            XmlDocument doc = new XmlDocument();
            XmlGenomeWriterStatic.Write(doc, (NeatGenome)this);
            FileInfo oFileInfo = new FileInfo("MMPNet.xml");
            doc.Save(oFileInfo.FullName);
            */
        }