public VerboseNetworkCompatibilityBreakdown GenerateVerboseCompatibility(INeatNetwork left, INeatNetwork right) { var comparedGenome = AlignGenomes(left, right); // https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.28.5457&rep=rep1&type=pdf page 11 // The number of excess and disjoint genes between a pair of genomes is a natural measure of their com-patibility distance. The more disjoint two genomes are, theless evolutionary history they share, and thusthe less compatible they are. Therefore, we can measure the compatibility distanceÆof different structuresin NEAT as a simple linear combination of the number of excess(E) and disjoint (D) genes, as well as theaverage weight differences of matching genes (W), including disabled genes. The coefficients, ExcessCoefficient, DisjointCoefficient, and WeightCoefficient, allow us to adjust the importance of the three factors, and the factor N, the number of genes in the larger genome, normalizes for genome size int N = left.Innovations.Length >= right.Innovations.Length ? left.Innovations.Length : right.Innovations.Length; double excess = ExcessCoefficient * comparedGenome.Excess.Length; double disjoint = DisjointCoefficient * (comparedGenome.LeftDisjoint.Length + comparedGenome.RightDisjoint.Length); double averageWeight = AverageWeightDifference(comparedGenome.Aligned); excess /= N; disjoint /= N; double compatibility = excess + disjoint + (WeightCoefficient * averageWeight); return(new VerboseNetworkCompatibilityBreakdown() { N = N, Disjoint = comparedGenome.LeftDisjoint.Length + comparedGenome.RightDisjoint.Length, AverageWeightDifference = averageWeight, Excess = comparedGenome.Excess.Length, Compatibility = compatibility }); }
public double[] Evaluate(double[] Inputs, INeatNetwork network) { IMatrix <double> propogated = new Double.Matrix(Inputs.Length, 1, Inputs); for (int i = 0; i < network.Matrices.Length; i++) { propogated = Propogator.Forward(network.Matrices[i], propogated, Activator); } return(propogated.ToOneDimension()); }
public virtual async Task <InitializeNetworkResult> InitializeConnections(INeatNetwork network) { // first get an array for the new weights for the new connections // since this is the default mutater we will use random weights // get a array of new weights for the network // by default all inputs should be connected to all outputs int numberOfConnections = network.InputNodes * network.OutputNodes; double[] rolls = await Helpers.Random.NextDoubleArray(numberOfConnections); // reset the network since we are starting a new one network.Reset(); // create placeholders to avoid GCC string innHash; IInnovation newInn; int rollIndex = 0; for (int i = 0; i < network.InputNodes; i++) { for (int x = network.InputNodes; x < network.InputNodes + network.OutputNodes; x++) { newInn = new Innovation() { Enabled = true, InputNode = (ushort)i, OutputNode = (ushort)x, Weight = rolls[rollIndex++] }; innHash = newInn.Hash(); if (network.InnovationHashes.Add(innHash)) { await network.AddInnovation(newInn); } } } network.GeneratePhenotype(); return(InitializeNetworkResult.success); }
public virtual async Task <MutationResult> Mutate(INeatNetwork network) { double val = await Helpers.Random.NextUDoubleAsync(); MutationResult result = MutationResult.success; bool Between(double value, double lower, double upper) { return(value <= upper && value >= lower); } bool addNode; bool addConnection; if (AddConnectionChance > AddNodeChance) { // 0 1 // |- Add Node -|---- Add Connection ----|-------------- Roll Fail -------------| addNode = Between(val, 0, AddNodeChance); addConnection = Between(val, AddNodeChance, AddNodeChance + AddConnectionChance); } else { // 0 1 // |- Add Connection -|---- Add Node ----|-------------- Roll Fail -------------| addNode = Between(val, 0, AddConnectionChance); addConnection = Between(val, AddConnectionChance, AddNodeChance + AddConnectionChance); } if (addNode) { var status = await AddNode(network); // cast the add node result to a mutation result result = status switch { AddNodeResult.success => MutationResult.success, AddNodeResult.error => MutationResult.error, AddNodeResult.noEligibleConnections => MutationResult.noValidMutations, AddNodeResult.alreadyExists => MutationResult.noValidMutations, _ => MutationResult.error, }; } else if (addConnection) { var status = await AddConnection(network); // cast the add connection result to a mutation result result = status switch { AddConnectionResult.success => MutationResult.success, AddConnectionResult.error => MutationResult.error, AddConnectionResult.noEligibleNodes => MutationResult.noValidMutations, AddConnectionResult.alreadyExists => MutationResult.noValidMutations, _ => MutationResult.error, }; } // mutate weights await MutateWeights(network); return(result); }
public async Task <MutationResult> MutateWeights(INeatNetwork network) { // make sure we don't accidentally try to modify an array that is invalid if (network?.Innovations?.Length is null or 0) { return(MutationResult.noValidMutations); } // get the rolls all at once instead of in a loop since accessing the asynchronous RNG is expensive double[] rolls = await Helpers.Random.NextUDoubleArray(network.Innovations.Length); double[] weights = await Helpers.Random.NextDoubleArray(network.Innovations.Length); void _Mutate(double[] rolls, double[] weights) { bool reassignmentLessThanMutate = WeightReassignmentChance <= MutateWeightChance; double LowVal = reassignmentLessThanMutate ? WeightReassignmentChance : MutateWeightChance; double HighVal = reassignmentLessThanMutate ? MutateWeightChance : WeightReassignmentChance; WeightModification lowRoll; WeightModification highRoll; void ReassignValue(ref IInnovation val, ref double newVal) { val.Weight = newVal; } void ModifyValue(ref IInnovation val, ref double roll) { val.Weight += roll * WeightMutationModifier; } if (reassignmentLessThanMutate) { lowRoll = ReassignValue; highRoll = ModifyValue; } else { lowRoll = ModifyValue; highRoll = ReassignValue; } // carve out contigous memory for the innovations and rolls for faster access(there may be hundreds of entries we may need to modify) Span <IInnovation> innSpan = new(network.Innovations); Span <double> rollSpan = new(rolls); Span <double> weightSpan = new(weights); for (int i = 0; i < network.Innovations.Length; i++) { // roll to see if we modify the value at all ref double val = ref rollSpan[i]; // 0 1 // |---- Low Roll ----|------- High Roll -------|-- No Roll --| if (val <= LowVal) { lowRoll(ref innSpan[i], ref weightSpan[i]); } else if (val > LowVal && val <= HighVal) { highRoll(ref innSpan[i], ref weightSpan[i]); } } }
/// <summary> /// Crosses two parents to derive a new genome for a new child <see cref="INeatNetwork"/> /// </summary> /// <param name="left"></param> /// <param name="right"></param> /// <param name="fitnessState"></param> /// <returns></returns> public IInnovation[] DeriveGenome(INeatNetwork left, INeatNetwork right, FitnessState fitnessState) { // align the genomes so we can select and create a new genome var GenomeMatches = AlignGenomes(left, right); // per https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.28.5457&rep=rep1&type=pdf pg 12 // Matching genes are inherited randomly, whereas disjoint genes(those that do not match in the middle) and excess genes (those that do not match in the end) are inherited from the more fit parent. Whith equal fitnesses, disjoint and excess genes are also inherited randomly. // if the right length is larger that means any excess in the genome belongs to the right int additionalGenomeSize = 0; bool inheritWholeExcess = false; // check to see who is responsible for the excess genes bool rightExcess = right.Innovations.Length > left.Innovations.Length; bool inheritRandomExcess = false; int amountOfDisjointGenesToInherit = 0; int amountOfExcessGenesToInherit = 0; // determine if there is any excess or disjoint genes to inherit if (GenomeMatches.Excess.Length != 0 || GenomeMatches.LeftDisjoint.Length != 0 || GenomeMatches.RightDisjoint.Length != 0) { switch (fitnessState) { // if the right is more fit and the excess belongs to the right inherit the excess and the right disjoints case FitnessState.RightMoreFit: inheritWholeExcess = rightExcess; if (inheritWholeExcess) { additionalGenomeSize = GenomeMatches.Excess.Length + GenomeMatches.RightDisjoint.Length; } break; // if the left is more fit and the excess belongs to the left inherit the excess and the left disjoints case FitnessState.LeftMoreFit: inheritWholeExcess = !rightExcess; if (inheritWholeExcess) { additionalGenomeSize = GenomeMatches.Excess.Length + GenomeMatches.LeftDisjoint.Length; } break; // if both are equally fit randomly inherit excess and disjoint genes case FitnessState.EqualFitness: inheritRandomExcess = true; inheritWholeExcess = false; // determine the max disjoint to inherit if (Helpers.Random.NextUDouble() >= 0.5d && GenomeMatches.LeftDisjoint.Length > 0) { amountOfDisjointGenesToInherit = Helpers.Random.Next(0, GenomeMatches.LeftDisjoint.Length); } else if (GenomeMatches.RightDisjoint.Length > 0) { amountOfDisjointGenesToInherit = Helpers.Random.Next(0, GenomeMatches.RightDisjoint.Length); } amountOfExcessGenesToInherit = Helpers.Random.Next(0, GenomeMatches.Excess.Length + 1); additionalGenomeSize = amountOfExcessGenesToInherit + amountOfDisjointGenesToInherit; break; } } // divide the length by 2 since the array scheme holds both left and right aligned genes int startingIndex = GenomeMatches.Aligned.Length >> 1; // becuase we do not know the IInnovation[] result = new IInnovation[startingIndex + additionalGenomeSize]; Span <IInnovation> newGenome = new(result); // get the rolls for the entire genome at once to avoid costly semaphore hits double[] rolls = Helpers.Random.NextUDoubleArray(newGenome.Length).Result; for (int i = 0; i < startingIndex; i++) { // randomly select genes if (rolls[i] > 0.5d) { // select the left gene newGenome[i] = GenomeMatches.Aligned[i << 1]; // left right left right left right // 0 1 2 3 4 5 // left = i * 2 } else { // select the right gene newGenome[i] = GenomeMatches.Aligned[(i << 1) + 1]; // left right left right left right // 0 1 2 3 4 5 // right = i * 2 + 1 } } // now that we have inserted all of the randomly chosen aligned genes we should add all of the excess and disjoint(if we determined earlier that it's appropriate // // copy the excess over to the destination array, we dont have to figure out who it belongs to yet becuase we only get 1 parents excess form the align genes method if (inheritWholeExcess) { // the size of the destination array has already been assigned so we dont have to resize and carve out another coiontiguous block of memory for the array Span <IInnovation> excess = new(GenomeMatches.Excess); excess.CopyTo(newGenome.Slice(startingIndex, excess.Length)); // now copy over the disjointed values if (rightExcess) { Span <IInnovation> disjointed = new(GenomeMatches.RightDisjoint); disjointed.CopyTo(newGenome.Slice(startingIndex + excess.Length)); } else { Span <IInnovation> disjointed = new(GenomeMatches.LeftDisjoint); disjointed.CopyTo(newGenome.Slice(startingIndex + excess.Length)); } } else if (inheritRandomExcess) { // since we should inherit random genes from both parents start off by inheriting the whole excess Span <IInnovation> excess = new(GenomeMatches.Excess); for (int i = 0; i < amountOfExcessGenesToInherit; i++) { newGenome[i + startingIndex] = excess[(i + Helpers.Random.Next(0, 101)) % amountOfExcessGenesToInherit]; } int index = startingIndex + amountOfExcessGenesToInherit; // now randomly inherit excess genes for (int i = 0; i < amountOfDisjointGenesToInherit; i++) { if (Helpers.Random.NextUDouble() >= 0.5d) { // left if (GenomeMatches.LeftDisjoint.Length - 1 > i) { newGenome[index++] = GenomeMatches.LeftDisjoint[i]; } else if (GenomeMatches.RightDisjoint.Length - 1 > i) { newGenome[index++] = GenomeMatches.RightDisjoint[i]; } } else { // right if (GenomeMatches.RightDisjoint.Length - 1 > i) { newGenome[index++] = GenomeMatches.RightDisjoint[i]; } else if (GenomeMatches.LeftDisjoint.Length - 1 > i) { newGenome[index++] = GenomeMatches.LeftDisjoint[i]; } } } } return(result); }
/// <summary> /// Aligns two genomes together so they can be compared and used to derive another genome /// </summary> /// <param name="left"></param> /// <param name="right"></param> /// <returns></returns> internal (IInnovation[] Aligned, IInnovation[] Excess, IInnovation[] LeftDisjoint, IInnovation[] RightDisjoint) AlignGenomes(INeatNetwork left, INeatNetwork right) { IInnovation[] Aligned = Array.Empty <IInnovation>(); int AlignedIndex = 0; IInnovation[] LeftDisjoint = Array.Empty <IInnovation>(); int LeftDisjointIndex = 0; IInnovation[] RightDisjoint = Array.Empty <IInnovation>(); int RightDisjointIndex = 0; IInnovation[] Excess = Array.Empty <IInnovation>(); int ExcessIndex = 0; Span <IInnovation> leftInnovations = new(left.Innovations); HashSet <string> AlignedHashes = new(); int index = 0; foreach (var item in leftInnovations) { index++; string hash = item.Hash(); // check to see if the innovation is contained in both networks, if it is, then it is an aligned gene if (right.InnovationHashes.Contains(hash) && AlignedHashes.Add(hash)) { // since the hash is in the others list then its aligned Array.Resize(ref Aligned, ++AlignedIndex); Aligned[AlignedIndex - 1] = item; // leave an emtpy spot for the rights innovation Array.Resize(ref Aligned, ++AlignedIndex); Aligned[AlignedIndex - 1] = null; } else { // check to see if we are past the end of the rights array, if we are then all innovations thereafter are considered disjoint if (index > right.Innovations.Length) { // excess Array.Resize(ref Excess, ++ExcessIndex); Excess[ExcessIndex - 1] = item; } else { // disjoint Array.Resize(ref LeftDisjoint, ++LeftDisjointIndex); LeftDisjoint[LeftDisjointIndex - 1] = item; } } } Span <IInnovation> rightInnovations = new(right.Innovations); index = 0; AlignedIndex = 1; ExcessIndex = 0; AlignedHashes.Clear(); foreach (var item in rightInnovations) { index++; string hash = item.Hash(); // check to see if the innovation is contained in both networks, if it is, then it is an aligned gene if (left.InnovationHashes.Contains(hash) && AlignedHashes.Add(hash)) { // since the hash is in the others list then its aligned Aligned[AlignedIndex] = item; AlignedIndex += 2; } else { // check to see if we are past the end of the rights array, if we are then all innovations thereafter are considered disjoint if (index >= left.Innovations.Length) { // excess Array.Resize(ref Excess, ++ExcessIndex); Excess[ExcessIndex - 1] = item; } else { // disjoint Array.Resize(ref RightDisjoint, ++RightDisjointIndex); RightDisjoint[RightDisjointIndex - 1] = item; } } } return(Aligned, Excess, LeftDisjoint, RightDisjoint); }