public override IGenome CreateOffspring_Sexual(EvolutionAlgorithm ea, IGenome parent) { NeatGenome otherParent = parent as NeatGenome; if (otherParent == null) return null; // Build a list of connections in either this genome or the other parent. CorrelationResults correlationResults = CorrelateConnectionGeneLists(connectionGeneList, otherParent.connectionGeneList); Debug.Assert(correlationResults.PerformIntegrityCheck(), "CorrelationResults failed integrity check."); //----- Connection Genes. // We will temporarily store the offspring's genes in newConnectionGeneList and keeping track of which genes // exist with newConnectionGeneTable. Here we ensure these objects are created, and if they already existed // then ensure they are cleared. Clearing existing objects is more efficient that creating new ones because // allocated memory can be re-used. // Key = connection key, value = index in newConnectionGeneList. if(newConnectionGeneTable==null) { // Provide a capacity figure to the new Hashtable. The offspring will be the same length (or thereabouts). newConnectionGeneTable = new Hashtable(connectionGeneList.Count); } else { newConnectionGeneTable.Clear(); } //TODO: No 'capacity' constructor on CollectionBase. Create modified/custom CollectionBase. // newConnectionGeneList must be constructed on each call because it is passed to a new NeatGenome // at construction time and a permanent reference to the list is kept. newConnectionGeneList = new ConnectionGeneList(ConnectionGeneList.Count); // A switch that stores which parent is fittest 1 or 2. Chooses randomly if both are equal. More efficient to calculate this just once. byte fitSwitch; if(Fitness > otherParent.Fitness) fitSwitch = 1; else if(Fitness < otherParent.Fitness) fitSwitch = 2; else { // Select one of the parents at random to be the 'master' genome during crossover. if(Utilities.NextDouble() < 0.5) fitSwitch = 1; else fitSwitch = 2; } bool combineDisjointExcessFlag = Utilities.NextDouble() < ea.NeatParameters.pDisjointExcessGenesRecombined; // Loop through the correlationResults, building a table of ConnectionGenes from the parents that will make it into our // new [single] offspring. We use a table keyed on connection end points to prevent passing connections to the offspring // that may have the same end points but a different innovation number - effectively we filter out duplicate connections. int idxBound = correlationResults.CorrelationItemList.Count; for(int i=0; i<idxBound; i++) { CreateOffspring_Sexual_ProcessCorrelationItem((CorrelationItem)correlationResults.CorrelationItemList[i], fitSwitch, combineDisjointExcessFlag, ea.NeatParameters); } //----- Neuron Genes. // Build a neuronGeneList by analysing each connection's neuron end-point IDs. // This strategy has the benefit of eliminating neurons that are no longer connected too. // Remember to always keep all input, output and bias neurons though! NeuronGeneList newNeuronGeneList = new NeuronGeneList(neuronGeneList.Count); // Keep a table of the NeuronGene ID's keyed by ID so that we can keep track of which ones have been added. // Key = innovation ID, value = null for some reason. if(newNeuronGeneTable==null) newNeuronGeneTable = new Hashtable(neuronGeneList.Count); else newNeuronGeneTable.Clear(); // Get the input/output neurons from this parent. All Genomes share these neurons, they do not change during a run. idxBound = neuronGeneList.Count; for(int i=0; i<idxBound; i++) { if(neuronGeneList[i].NeuronType != NeuronType.Hidden) { newNeuronGeneList.Add(new NeuronGene(neuronGeneList[i])); newNeuronGeneTable.Add(neuronGeneList[i].InnovationId, null); } else { // No more bias, input or output nodes. break the loop. break; } } // Now analyse the connections to determine which NeuronGenes are required in the offspring. // Loop through every connection in the child, and add to the child those hidden neurons that are sources or targets of the connection. idxBound = newConnectionGeneList.Count; for(int i=0; i<idxBound; i++) { NeuronGene neuronGene; ConnectionGene connectionGene = newConnectionGeneList[i]; if(!newNeuronGeneTable.ContainsKey(connectionGene.SourceNeuronId)) { //TODO: DAVID proper activation function // We can safely assume that any missing NeuronGenes at this point are hidden heurons. neuronGene = this.neuronGeneList.GetNeuronById(connectionGene.SourceNeuronId); if (neuronGene != null) newNeuronGeneList.Add(new NeuronGene(neuronGene)); else newNeuronGeneList.Add(new NeuronGene(otherParent.NeuronGeneList.GetNeuronById(connectionGene.SourceNeuronId))); //newNeuronGeneList.Add(new NeuronGene(connectionGene.SourceNeuronId, NeuronType.Hidden, ActivationFunctionFactory.GetActivationFunction("SteepenedSigmoid"))); newNeuronGeneTable.Add(connectionGene.SourceNeuronId, null); } if(!newNeuronGeneTable.ContainsKey(connectionGene.TargetNeuronId)) { //TODO: DAVID proper activation function // We can safely assume that any missing NeuronGenes at this point are hidden heurons. neuronGene = this.neuronGeneList.GetNeuronById(connectionGene.TargetNeuronId); if (neuronGene != null) newNeuronGeneList.Add(new NeuronGene(neuronGene)); else newNeuronGeneList.Add(new NeuronGene(otherParent.NeuronGeneList.GetNeuronById(connectionGene.TargetNeuronId))); //newNeuronGeneList.Add(new NeuronGene(connectionGene.TargetNeuronId, NeuronType.Hidden, ActivationFunctionFactory.GetActivationFunction("SteepenedSigmoid"))); newNeuronGeneTable.Add(connectionGene.TargetNeuronId, null); } } // Determine which modules to pass on to the child in the same way. // For each module in this genome or in the other parent, if it was referenced by even one connection add it and all its dummy neurons to the child. List<ModuleGene> newModuleGeneList = new List<ModuleGene>(); // Build a list of modules the child might have, which is a union of the parents' module lists, but they are all copies so we can't just do a union. List<ModuleGene> unionParentModules = new List<ModuleGene>(moduleGeneList); foreach (ModuleGene moduleGene in otherParent.moduleGeneList) { bool alreadySeen = false; foreach (ModuleGene match in unionParentModules) { if (moduleGene.InnovationId == match.InnovationId) { alreadySeen = true; break; } } if (!alreadySeen) { unionParentModules.Add(moduleGene); } } foreach (ModuleGene moduleGene in unionParentModules) { // Examine each neuron in the child to determine whether it is part of a module. foreach (List<uint> dummyNeuronList in new List<uint>[] { moduleGene.InputIds, moduleGene.OutputIds }) { foreach (uint dummyNeuronId in dummyNeuronList) { if (newNeuronGeneTable.ContainsKey(dummyNeuronId)) { goto childHasModule; } } } continue; // the child does not contain this module, so continue the loop and check for the next module. childHasModule: // the child does contain this module, so make sure the child gets all the nodes the module requires to work. // Make sure the child has all the neurons in the given module. newModuleGeneList.Add(new ModuleGene(moduleGene)); foreach (List<uint> dummyNeuronList in new List<uint>[] { moduleGene.InputIds, moduleGene.OutputIds }) { foreach (uint dummyNeuronId in dummyNeuronList) { if (!newNeuronGeneTable.ContainsKey(dummyNeuronId)) { newNeuronGeneTable.Add(dummyNeuronId, null); NeuronGene neuronGene = this.neuronGeneList.GetNeuronById(dummyNeuronId); if (neuronGene != null) { newNeuronGeneList.Add(new NeuronGene(neuronGene)); } else { newNeuronGeneList.Add(new NeuronGene(otherParent.NeuronGeneList.GetNeuronById(dummyNeuronId))); } } } } } // TODO: Inefficient code? newNeuronGeneList.SortByInnovationId(); // Schrum: Need to calculate this because of Module Mutation adding extra outputs int revisedOutputCount = 0; foreach(NeuronGene n in newNeuronGeneList) { if (n.NeuronType == NeuronType.Output) revisedOutputCount++; } // newConnectionGeneList is already sorted because it was generated by passing over the list returned by // CorrelateConnectionGeneLists() - which is always in order. // Schrum: Modified to add outputsPerPolicy as a parameter, and use revisedOutputCount return new NeatGenome(ea.NextGenomeId, newNeuronGeneList, newModuleGeneList, newConnectionGeneList, inputNeuronCount, revisedOutputCount, outputsPerPolicy); }
public override IGenome CreateOffspring_Sexual(EvolutionAlgorithm ea, IGenome parent) { CorrelationResults correlationResults = CorrelateConnectionGeneLists(connectionGeneList, ((NeatGenome)parent).connectionGeneList); Debug.Assert(correlationResults.PerformIntegrityCheck(), "CorrelationResults failed integrity check."); //----- Connection Genes. // We will temporarily store the offspring's genes in newConnectionGeneList and keeping track of which genes // exist with newConnectionGeneTable. Here we ensure these objects are created, and if they already existed // then ensure they are cleared. Clearing existing objects is more efficient that creating new ones because // allocated memory can be re-used. if(newConnectionGeneTable==null) { // Provide a capacity figure to the new Hashtable. The offspring will be the same length (or thereabouts). newConnectionGeneTable = new Hashtable(connectionGeneList.Count); } else { newConnectionGeneTable.Clear(); } //TODO: No 'capacity' constructor on CollectionBase. Create modified/custom CollectionBase. // newConnectionGeneList must be constructed on each call because it is passed to a new NeatGenome // at construction time and a permanent reference to the list is kept. newConnectionGeneList = new ConnectionGeneList(ConnectionGeneList.Count); // A switch that stores which parent is fittest 1 or 2. 0 if both are equal. More efficient to calculate this just once. byte fitSwitch; if(Fitness > parent.Fitness) fitSwitch = 1; else if(Fitness < parent.Fitness) fitSwitch = 2; else { // Select one of the parents at random to be the 'master' genome during crossover. if(Utilities.NextDouble() < 0.5) fitSwitch = 1; else fitSwitch = 2; } bool combineDisjointExcessFlag = Utilities.NextDouble() < ea.NeatParameters.pDisjointExcessGenesRecombined ? true : false; // Loop through the correlationResults, building a table of ConnectionGenes from the parent's that will make it into our // new [single] offspring. We use a table keyed on connection end points to prevent passing connections to the offspring // that may have the same end points but a different innovation number - effectively we filter out duplicate connections. int idxBound = correlationResults.CorrelationItemList.Count; for(int i=0; i<idxBound; i++) { CreateOffspring_Sexual_ProcessCorrelationItem((CorrelationItem)correlationResults.CorrelationItemList[i], fitSwitch, combineDisjointExcessFlag, ea.NeatParameters); } //----- Neuron Genes. // Build a neuronGeneList by analysing each connection's neuron end-point IDs. // This strategy has the benefit of eliminating neurons that are no longer connected too. // Remember to always keep all input, output and bias neurons though! NeuronGeneList newNeuronGeneList = new NeuronGeneList(neuronGeneList.Count); // Keep a table of the NeuronGene ID's keyed by ID so that we can keep track of which ones have been added. if(newNeuronGeneTable==null) newNeuronGeneTable = new Hashtable(neuronGeneList.Count); else newNeuronGeneTable.Clear(); // Get the input/output neurons from this parent. All Genomes share these neurons, they do not change during a run. idxBound = neuronGeneList.Count; for(int i=0; i<idxBound; i++) { if(neuronGeneList[i].NeuronType != NeuronType.Hidden) { newNeuronGeneList.Add(new NeuronGene(neuronGeneList[i])); newNeuronGeneTable.Add(neuronGeneList[i].InnovationId, null); } else { // No more bias, input or output nodes. break the loop. break; } } // Now analyse the connections to determine which NeuronGenes are required in the offspring. idxBound = newConnectionGeneList.Count; for(int i=0; i<idxBound; i++) { NeuronGene neuronGene; ConnectionGene connectionGene = newConnectionGeneList[i]; if(!newNeuronGeneTable.ContainsKey(connectionGene.SourceNeuronId)) { //TODO: DAVID proper activation function // We can safely assume that any missing NeuronGenes at this point are hidden heurons. neuronGene = this.neuronGeneList.GetNeuronById(connectionGene.SourceNeuronId); if (neuronGene != null) newNeuronGeneList.Add(new NeuronGene(neuronGene)); else newNeuronGeneList.Add(new NeuronGene(((NeatGenome)parent).NeuronGeneList.GetNeuronById(connectionGene.SourceNeuronId))); //newNeuronGeneList.Add(new NeuronGene(connectionGene.SourceNeuronId, NeuronType.Hidden, ActivationFunctionFactory.GetActivationFunction("SteepenedSigmoid"))); newNeuronGeneTable.Add(connectionGene.SourceNeuronId, null); } if(!newNeuronGeneTable.ContainsKey(connectionGene.TargetNeuronId)) { //TODO: DAVID proper activation function // We can safely assume that any missing NeuronGenes at this point are hidden heurons. neuronGene = this.neuronGeneList.GetNeuronById(connectionGene.TargetNeuronId); if (neuronGene != null) newNeuronGeneList.Add(new NeuronGene(neuronGene)); else newNeuronGeneList.Add(new NeuronGene(((NeatGenome)parent).NeuronGeneList.GetNeuronById(connectionGene.TargetNeuronId))); //newNeuronGeneList.Add(new NeuronGene(connectionGene.TargetNeuronId, NeuronType.Hidden, ActivationFunctionFactory.GetActivationFunction("SteepenedSigmoid"))); newNeuronGeneTable.Add(connectionGene.TargetNeuronId, null); } } // TODO: Inefficient code? newNeuronGeneList.SortByInnovationId(); // newConnectionGeneList is already sorted because it was generated by passing over the list returned by // CorrelateConnectionGeneLists() - which is always in order. return new NeatGenome(ea.NextGenomeId, newNeuronGeneList, newConnectionGeneList, inputNeuronCount, outputNeuronCount); }