Пример #1
0
        /// <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;
            }
        }
Пример #2
0
        /// <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));
        }
Пример #3
0
        /// <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);
        }
Пример #4
0
        /// <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?
        }
Пример #5
0
        /// <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);
        }
Пример #6
0
        /// <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
        }
Пример #7
0
 /// <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);
 }
Пример #8
0
 /// <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)));
     }
 }
Пример #9
0
        /// <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);
        }
Пример #10
0
        /// <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);
                }
            }
        }
Пример #11
0
        /// <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}");
        }
Пример #12
0
        /// <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);
        }
Пример #13
0
 /// <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)
 {
 }