/// <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> private void AddConnectionMutation() { int attemptsDone = 0; while (true) { // NOTE: Rebuild structure so we get layer information to make sure we don't get circle dependencies RebuildStructure(); Node startNode; Node endNode; do { startNode = _nodes.ElementAt(TrainingRoom.Random.Next(_nodes.Count)).Value; } while (startNode.NodeType == NodeType.Output); do { endNode = _nodes.ElementAt(TrainingRoom.Random.Next(_nodes.Count)).Value; } while (endNode.Layer == startNode.Layer || (startNode.NodeType == NodeType.Input && endNode.NodeType == NodeType.Input)); if (endNode.Layer > startNode.Layer) { Node temp = endNode; endNode = startNode; startNode = temp; } if (ConnectionExists(startNode, endNode)) { if (attemptsDone >= 5) { return; } attemptsDone += 1; continue; } ConnectionGene connection = new ConnectionGene(Id, startNode.Id, endNode.Id, TrainingRoom.Random.NextDouble() * 2 - 1, TrainingRoom.GetInnovationNumber(startNode.Id, endNode.Id)); ConnectionGenes.Add(connection); _maxInnovation = Math.Max(connection.InnovationNumber, _maxInnovation); break; } }
/// <summary> /// Checks if the other brain is the same species as this brain. /// </summary> /// <param name="other">The other brain.</param> /// <returns>Returns <c>true</c> if the other brain is the same species as this brain; otherwise, <c>false</c>.</returns> public bool IsSameSpecies(Brain other) { // TODO: Redo this method and optimize it int excess = 0; if (other.ConnectionGenes.Any()) { excess += other.ConnectionGenes.Count(gene => gene.InnovationNumber > _maxInnovation); } double weightDiff = 0; int disjoint = 0; int weightDiffCount = 0; foreach (ConnectionGene gene in ConnectionGenes) { ConnectionGene otherGene = other.ConnectionGenes.SingleOrDefault(gen => gen.InnovationNumber == gene.InnovationNumber); if (otherGene != default) { weightDiff += Math.Abs(gene.Weight - otherGene.Weight); weightDiffCount++; } else { disjoint++; } } weightDiff = weightDiffCount == 0 ? 0 : weightDiff / weightDiffCount; int genomeCount = Math.Max(ConnectionGenes.Count, other.ConnectionGenes.Count); if (genomeCount < 20) { genomeCount = 1; } return(((TrainingRoom.TrainingRoomSettings.SpeciesExcessGeneWeight * excess) / genomeCount + (TrainingRoom.TrainingRoomSettings.SpeciesDisjointGeneWeight * disjoint) / genomeCount + TrainingRoom.TrainingRoomSettings.SpeciesAverageWeightDiffWeight * weightDiff) < TrainingRoom.TrainingRoomSettings.Threshold); }
/// <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> private 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 = GetRandomNode(); } while (startNode is OutputNode); // 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 = GetRandomNode(); } while (endNode.Layer == startNode.Layer || (startNode is InputNode && endNode is InputNode)); // 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 = new ConnectionGene(Id, 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; } Node GetRandomNode() { //TODO: Why not use the hidden nodes list instead of the connection genes? List <Node> nodes = ConnectionGenes.SelectMany(p => new[] { p.InNode, p.OutNode }).Distinct().ToList(); nodes.AddRange(Inputs.Select(p => p.InputNode)); nodes.AddRange(Outputs.Select(p => p.OutputNode)); return(nodes[trainingRoomSettings.Random.Next(nodes.Count)]); } }