/// <summary> /// Adds a new connection between 2 randomly chosen nodes. /// The first node can not be an output. /// The second node cannot be an input node if the first node is an input node, and the layer cannot be the same as the first node. /// /// If the connection that is going to be created already exists, there will be a maximum of 5 attempts to create a new one. /// </summary> /// <param name="trainingRoomSettings">The training room settings.</param> /// <param name="innovationFunction">The innovation function.</param> protected void AddConnectionMutation(TrainingRoomSettings trainingRoomSettings, Func <uint, uint, uint> innovationFunction) { // Set initial attempt counter to 0. int attemptsDone = 0; // Rebuild structure so we get layer information to make sure we don't get circular dependencies. RebuildStructure(); while (true) { // Prepare start and end node. Node startNode; Node endNode; // Set start node to a random node while start note is of type OutputNode. do { startNode = TempNodes[trainingRoomSettings.Random.Next(TempNodes.Count)]; } while (IsNodeAnOutputNode(startNode)); // Set end node to a random node while the layer of the start and end node are the same or // start node is of type InputNode and end node is of type InputNode. do { endNode = TempNodes[trainingRoomSettings.Random.Next(TempNodes.Count)]; } while (endNode.Layer == startNode.Layer || (IsNodeAnInputNode(startNode) && IsNodeAnInputNode(endNode))); // If end node layer is higher than start node layer swap them. if (endNode.Layer > startNode.Layer) { Node temp = endNode; endNode = startNode; startNode = temp; } // If the connection between the selected start and end node already exists continue the loop, until 5 attempts are done, // then return early. if (ConnectionExists(startNode, endNode)) { if (attemptsDone >= 5) { return; } attemptsDone += 1; continue; } // Create a new connection gene from the start and end nodes. ConnectionGene connection = CreateConnectionGene(innovationFunction(startNode.NodeIdentifier, endNode.NodeIdentifier), startNode.NodeIdentifier, endNode.NodeIdentifier, trainingRoomSettings.Random.NextDouble() * 2 - 1); // Add the connection gene. ConnectionGenes.Add(connection); // Update the max innovation. _maxInnovation = Math.Max(connection.InnovationNumber, _maxInnovation); // Break the loop. break; } }
/// <summary> /// Checks if the organism is of the same species. /// It compares the given organism to a randomly chosen organism from the species. /// </summary> /// <param name="organism">The organism to check.</param> /// <param name="trainingRoomSettings">The training rooms settings.</param> /// <returns>Returns <c>true</c> if he organism is of the same species; otherwise, <c>false</c>.</returns> internal bool IsSameSpecies(Organism organism, TrainingRoomSettings trainingRoomSettings) { // If the generation is 0, only 1 species should exist. // So, there is no need check for the same species. if (organism.Generation == 0) { return(true); } // Get all organisms from the previous generation. List <Organism> lastGenOrganisms = Organisms.FindAll(org => org.Generation == organism.Generation - 1); // If the last generation does not contain any organisms, that indicates that the species is new. if (!lastGenOrganisms.Any()) { // The new species will only have organisms from the current generation. // So, we make the current organisms the representatives of the last generation. lastGenOrganisms = Organisms; } // Gets the randomly selected organism from the last generation organisms. Organism organismToCompare = lastGenOrganisms[trainingRoomSettings.Random.Next(lastGenOrganisms.Count)]; // Compares if the organism is from the same species. return(organismToCompare.IsSameSpecies(organism, trainingRoomSettings)); }
/// <summary> /// Adds a node by selecting a random connection, disabling it and replacing it by 2 genes. /// The first gene goes from the original input node, to the new node with a weight of 1. /// The second gene goes from the the new node to the old output node with the same weight as the original connection. /// </summary> /// <param name="trainingRoomSettings">The training room settings.</param> /// <param name="getAndIncreaseNodeIdFunction">The get and increase node id function.</param> /// <param name="innovationFunction">The innovation function.</param> private void AddNodeMutation(TrainingRoomSettings trainingRoomSettings, Func <uint> getAndIncreaseNodeIdFunction, Func <uint, uint, uint> innovationFunction) { // If the connection genes count is 0, then there are not connection genes to mutate. if (ConnectionGenes.Count == 0) { return; } // Find a random connection. ConnectionGene theChosenOne = ConnectionGenes.ElementAt(trainingRoomSettings.Random.Next(ConnectionGenes.Count)); // Disable the connection gene. theChosenOne.Enabled = false; // Generate the new node's id. uint newNodeId = getAndIncreaseNodeIdFunction(); // Get the old in and out node identifiers. uint oldInId = theChosenOne.InNodeIdentifier; uint oldOutId = theChosenOne.OutNodeIdentifier; // Create a connectionGene from oldIn (a) to new (c) and from new (c) to oldOut (b) ConnectionGene aToC = CreateConnectionGene(innovationFunction(oldInId, newNodeId), oldInId, newNodeId, 1); ConnectionGene cToB = CreateConnectionGene(innovationFunction(newNodeId, oldOutId), newNodeId, oldOutId, theChosenOne.Weight); // Add new connection genes. ConnectionGenes.Add(aToC); ConnectionGenes.Add(cToB); // Update the max innovation. _maxInnovation = Math.Max(Math.Max(cToB.InnovationNumber, aToC.InnovationNumber), _maxInnovation); }
/// <summary> /// Mutates the the organism based on the training room settings. /// </summary> /// <param name="trainingRoomSettings">The training room settings.</param> /// <param name="getAndIncreaseNodeIdFunction">The get and increase node id function.</param> /// <param name="innovationFunction">The innovation function.</param> public void Mutate(TrainingRoomSettings trainingRoomSettings, Func <uint> getAndIncreaseNodeIdFunction, Func <uint, uint, uint> innovationFunction) { // If the random value is lower than the add connection chance add a connection mutation. if (trainingRoomSettings.Random.NextDouble() < trainingRoomSettings.AddConnectionChance) { AddConnectionMutation(trainingRoomSettings, innovationFunction); } // If the random value is lower than the add node chance add a node mutation. if (trainingRoomSettings.Random.NextDouble() < trainingRoomSettings.AddNodeChance) { AddNodeMutation(trainingRoomSettings, getAndIncreaseNodeIdFunction, innovationFunction); } // If the random value is higher than the mutate weight chance or the connection gene count is lower than 0 return early. if (trainingRoomSettings.Random.NextDouble() > trainingRoomSettings.MutateWeightChance || ConnectionGenes.Count <= 0) { return; } // Find a random connection gene. ConnectionGene connectionGene = ConnectionGenes[trainingRoomSettings.Random.Next(ConnectionGenes.Count)]; // If the random value is lower than the weight reassign chance set a new weight else add weight. if (trainingRoomSettings.Random.NextDouble() < trainingRoomSettings.WeightReassignChance) { connectionGene.Weight = trainingRoomSettings.Random.NextDouble() * 2 - 1; } else { connectionGene.Weight += (trainingRoomSettings.Random.NextDouble() * 2 - 1) * 0.1; } //TODO: Inputs mutate? }
/// <summary> /// Checks if the other organism is of the same species. /// </summary> /// <param name="organism">The other organism.</param> /// <param name="trainingRoomSettings">The training room settings.</param> /// <returns>Returns <c>true</c> if the other organism is of the same species; otherwise, <c>false</c>.</returns> public bool IsSameSpecies(Organism organism, TrainingRoomSettings trainingRoomSettings) { //TODO: Check inputs and outputs when they mutate // Sets the initial excess. int excess = 0; // If the organism has connection genes then add the amount of connection genes // where the innovation number is higher than the max innovation to the excess. if (organism.ConnectionGenes.Any()) { excess += organism.ConnectionGenes.Count(gene => gene.InnovationNumber > _maxInnovation); } // Sets initial variables. double weightDiff = 0; int disjoint = 0; int weightDiffCount = 0; // For each connection gene where the given organism contains a single connection gene with the same innovation number // add the weight difference to the weight difference variable and add 1 to the weight difference counter; otherwise, add one // to the disjoint counter. foreach (ConnectionGene gene in ConnectionGenes) { ConnectionGene otherGene = organism.ConnectionGenes.SingleOrDefault(gen => gen.InnovationNumber == gene.InnovationNumber); if (otherGene != default) { weightDiff += Math.Abs(gene.Weight - otherGene.Weight); weightDiffCount++; } else { disjoint++; } } // If the weight difference count is not 0 than compound weight difference to weight difference count. if (weightDiffCount != 0) { weightDiff /= weightDiffCount; } // Set genome count to the max between connection genes count of the current organism and the other organism connection genes count. int genomeCount = Math.Max(ConnectionGenes.Count, organism.ConnectionGenes.Count); // The coefficients SpeciesExcessGeneWeight, SpeciesDisjointGeneWeight, and SpeciesAverageWeightDiffWeight // 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 // (N can be set to 1 if both genomes are small, i.e., consist of fewer than 20 genes). if (genomeCount < 20) { genomeCount = 1; } // Depending on the training room settings return whether the organism is of the same species. return(((trainingRoomSettings.SpeciesExcessGeneWeight * excess) / genomeCount + (trainingRoomSettings.SpeciesDisjointGeneWeight * disjoint) / genomeCount + trainingRoomSettings.SpeciesAverageWeightDiffWeight * weightDiff) < trainingRoomSettings.Threshold); }
/// <summary> /// Clones the organism. /// </summary> /// <param name="trainingRoomSettings">The training room settings.</param> /// <returns>Returns a clone of the organism.</returns> public virtual Organism Clone(TrainingRoomSettings trainingRoomSettings) { // Prepares a new id for the organisms to clone with. Guid newGuid = Guid.NewGuid(); // Create a new organism with the given node id and training room settings. return(new Organism(newGuid, trainingRoomSettings, Generation, ConnectionGenes.Select(gene => gene.Clone(newGuid)).ToList())); //TODO: When inputs can mutate they should also be cloned }
/// <summary> /// Initializes an instance of the <see cref="Organism"/> class. /// </summary> /// <param name="id">The id.</param> /// <param name="trainingRoomSettings">The training room settings.</param> /// <param name="generation">The current generation.</param> /// <param name="connectionGenes">The connection genes.</param> public Organism(Guid id, TrainingRoomSettings trainingRoomSettings, uint generation, List <ConnectionGene> connectionGenes) { Id = id; Generation = generation; ConnectionGenes = new List <ConnectionGene>(connectionGenes); if (connectionGenes.Any()) { _maxInnovation = ConnectionGenes.Max(p => p.InnovationNumber); } GenerateInputAndOutputNodes(trainingRoomSettings); Name = GenerateName(trainingRoomSettings.Random.Next); }
/// <summary> /// Generates the input and output nodes. /// </summary> /// <param name="trainingRoomSettings">The training room settings.</param> protected virtual void GenerateInputAndOutputNodes(TrainingRoomSettings trainingRoomSettings) { Inputs = new List <OrganismInputNode>((int)trainingRoomSettings.InputCount); for (uint nodeIdentifier = 0; nodeIdentifier < trainingRoomSettings.InputCount; nodeIdentifier++) { Inputs.Add(new OrganismInputNode(this, new InputNode(nodeIdentifier))); } Outputs = new List <OrganismOutputNode>((int)trainingRoomSettings.OutputCount); for (uint nodeIdentifier = 0; nodeIdentifier < trainingRoomSettings.OutputCount; nodeIdentifier++) { Outputs.Add(new OrganismOutputNode(this, new OutputNode(nodeIdentifier + trainingRoomSettings.InputCount))); } }
/// <summary> /// Initializes an instance of the <see cref="Organism"/> class. /// </summary> /// <param name="generation">The current generation.</param> /// <param name="trainingRoomSettings">The training room settings.</param> public Organism(uint generation, TrainingRoomSettings trainingRoomSettings) { // Generates a new guid for the organism, this is needed so EF can set the // foreign shadow key: OrganismId, on OrganismInputNode/OrganismOutputNode. Id = Guid.NewGuid(); // Sets the current generation. Generation = generation; // Initializes the list of connection genes. ConnectionGenes = new List <ConnectionGene>(); // Initializes the Input & Output lists for the many to many relations. GenerateInputAndOutputNodes(trainingRoomSettings); // Generates a random name for the organism. Name = GenerateName(trainingRoomSettings.Random.Next); }
/// <summary> /// Initializes an instance of the <see cref="Organism"/> class. /// </summary> /// <param name="trainingRoomSettings">The training room settings.</param> /// <param name="innovationFunction">The innovation function.</param> /// <param name="generation">The generation.</param> public Organism(TrainingRoomSettings trainingRoomSettings, Func <uint, uint, uint> innovationFunction, uint generation) : this(generation, trainingRoomSettings) { foreach (OrganismInputNode organismInputNode in Inputs) { InputNode inputNode = organismInputNode.InputNode; foreach (OrganismOutputNode organismOutputNode in Outputs) { OutputNode outputNode = organismOutputNode.OutputNode; // Create a new connection gene from the start and end nodes. ConnectionGene connection = CreateConnectionGene(innovationFunction(inputNode.NodeIdentifier, outputNode.NodeIdentifier), inputNode.NodeIdentifier, outputNode.NodeIdentifier, trainingRoomSettings.Random.NextDouble() * 2 - 1); // Add the connection gene. ConnectionGenes.Add(connection); } } }
/// <summary> /// Gets a random organism from this species. /// </summary> /// <param name="generation">The current generation.</param> /// <param name="trainingRoomSettings">The training room settings.</param> /// <returns>Returns a randomly chosen <see cref="Organism"/>.</returns> internal Organism GetRandomOrganism(uint generation, TrainingRoomSettings trainingRoomSettings) { // Gets the current generation organisms. List <Organism> currentGen = Organisms.FindAll(o => o.Generation == generation); // Return the only organism or a random organism. if (currentGen.Count == 1) { return(currentGen.First()); } if (currentGen.Count > 1) { return(currentGen[trainingRoomSettings.Random.Next(currentGen.Count)]); } // Should never happen! throw new Exception($"The organisms does not contain a organism at the generation: {generation}"); }
/// <summary> /// Creates an organism based on the genes of 2 parent organisms. /// For each gene in parent1 we check if parent2 has a gene with the same innovation number, if this is the case we randomly choose a parent to get the gene from. /// If a gene only exists in one parent we add it no matter what. /// </summary> /// <param name="parent2Organism">The other parent.</param> /// <param name="trainingRoomSettings">The training room settings.</param> /// <param name="organismFactory">The organism factory.</param> /// <returns>Returns a child organism based on the genes of this organism and the passed organism.</returns> public Organism Crossover(Organism parent2Organism, TrainingRoomSettings trainingRoomSettings, IFactory <Organism, OrganismFactoryArgument> organismFactory) { // Pre-generates a child id for creating connection genes. Guid childId = Guid.NewGuid(); // Copies the organisms from parent2. List <ConnectionGene> parent2 = parent2Organism.ConnectionGenes.Select(gene => gene.Clone(childId)).ToList(); foreach (ConnectionGene gene in ConnectionGenes) { // Prepares a gene to remove and add. ConnectionGene geneToRemove = default; // Clone the gene to add with the new id. ConnectionGene geneToAdd = gene.Clone(childId); // Tries to find a connection gene based on the current connection gene in the parent, and if not found, gives a default. ConnectionGene gene2 = parent2.SingleOrDefault(gen => gen.InnovationNumber == gene.InnovationNumber); // If the gene is not a default connection gene. if (gene2 != default) { // Set it for removal. geneToRemove = gene2; // Depending on a random set the current gene or a random gene to add. if (trainingRoomSettings.Random.NextDouble() > 0.5) { geneToAdd = gene2.Clone(childId); } } // If the gene to remove is not default, remove it from its parent. if (geneToRemove != default) { parent2.Remove(geneToRemove); } // If the gene to add is not enabled and a random is lower than the enable connection chance, enable the gene. if (!geneToAdd.Enabled && trainingRoomSettings.Random.NextDouble() < trainingRoomSettings.EnableConnectionChance) { geneToAdd.Enabled = true; } // Add gene to the temporary child genes list. _childGenes.Add(geneToAdd); } // Add all remaining genes in the parent to the child genes. foreach (ConnectionGene geneToAdd in parent2) { //Make sure this gene doesn't cause a loop if (!ConnectionLoops(geneToAdd.InNodeIdentifier, geneToAdd.OutNodeIdentifier, _childGenes)) { _childGenes.Add(geneToAdd); } } // Create a new organism with all new genes. Organism organism = organismFactory.Create(new OrganismFactoryArgument { Id = childId, TrainingRoomSettings = trainingRoomSettings, ConnectionGenes = _childGenes, Generation = Generation + 1, CreationType = OrganismCreationType.NEW_WITH_GENES }); // Clears the temporary child genes list for re-use. _childGenes.Clear(); // Return the newly created organism. return(organism); }
/// <summary> /// Initializes an instance of the <see cref="Organism"/> class. /// With an initial generation of 0. /// </summary> /// <param name="trainingRoomSettings">The training room settings.</param> /// <param name="innovationFunction">The innovation function.</param> public Organism(TrainingRoomSettings trainingRoomSettings, Func <uint, uint, uint> innovationFunction) : this(trainingRoomSettings, innovationFunction, 0) { }