public void ConnectThroughInput() { // Simple acyclic graph. var connList = new List <DirectedConnection> { new DirectedConnection(0, 3), new DirectedConnection(1, 3), new DirectedConnection(2, 3), new DirectedConnection(2, 4), new DirectedConnection(4, 1) }; // Create graph. connList.Sort(); var digraph = DirectedGraphBuilder.Create(connList, 3, 2); // Assert is acyclic. var cyclicGraphAnalysis = new CyclicGraphAnalysis(); Assert.IsTrue(!cyclicGraphAnalysis.IsCyclic(digraph)); // Depth analysis. GraphDepthInfo depthInfo = AcyclicGraphDepthAnalysis.CalculateNodeDepths(digraph); // Assertions. Assert.AreEqual(4, depthInfo._networkDepth); Assert.AreEqual(5, depthInfo._nodeDepthArr.Length); // Node depths. Assert.AreEqual(0, depthInfo._nodeDepthArr[0]); Assert.AreEqual(2, depthInfo._nodeDepthArr[1]); Assert.AreEqual(0, depthInfo._nodeDepthArr[2]); Assert.AreEqual(3, depthInfo._nodeDepthArr[3]); Assert.AreEqual(1, depthInfo._nodeDepthArr[4]); }
/// <summary> /// Create a NeatGenome with the given meta data, connection genes and supplementary data. /// </summary> /// <param name="id">Genome ID.</param> /// <param name="birthGeneration">Birth generation.</param> /// <param name="connGenes">Connection genes.</param> /// <param name="hiddenNodeIdArr">An array of the hidden node IDs in the genome, in ascending order.</param> /// <returns>A new NeatGenome instance.</returns> public NeatGenome <T> Create( int id, int birthGeneration, ConnectionGenes <T> connGenes, int[] hiddenNodeIdArr) { int inputCount = _metaNeatGenome.InputNodeCount; int outputCount = _metaNeatGenome.OutputNodeCount; int inputOutputCount = _metaNeatGenome.InputOutputNodeCount; // Create a mapping from node IDs to node indexes. Dictionary <int, int> nodeIdxById = BuildNodeIndexById(hiddenNodeIdArr); // Create a DictionaryNodeIdMap. DictionaryNodeIdMap nodeIndexByIdMap = new DictionaryNodeIdMap(inputCount, nodeIdxById); // Create a digraph from the genome. DirectedGraph digraph = NeatGenomeBuilderUtils.CreateDirectedGraph( _metaNeatGenome, connGenes, nodeIndexByIdMap); // Calc the depth of each node in the digraph. GraphDepthInfo depthInfo = _graphDepthAnalysis.CalculateNodeDepths(digraph); // Create a weighted acyclic digraph. // Note. This also outputs connectionIndexMap. For each connection in the acyclic graph this gives // the index of the same connection in the genome; this is because connections are re-ordered based // on node depth in the acyclic graph. AcyclicDirectedGraph acyclicDigraph = AcyclicDirectedGraphBuilderUtils.CreateAcyclicDirectedGraph( digraph, depthInfo, out int[] newIdByOldId, out int[] connectionIndexMap, ref _timesortWorkArr, ref _timesortWorkVArr); // TODO: Write unit tests to cover this! // Update nodeIdxById with the new depth based node index allocations. // Notes. // The current nodeIndexByIdMap maps node IDs (also know as innovation IDs in NEAT) to a compact // ID space in which any gaps have been removed, i.e. a compacted set of IDs that can be used as indexes, // i.e. if there are N nodes in total then the highest node ID will be N-1. // // Here we map the new compact IDs to an alternative ID space that is also compact, but ensures that nodeIDs // reflect the depth of a node in the acyclic graph. UpdateNodeIndexById(nodeIdxById, hiddenNodeIdArr, newIdByOldId); // Create the neat genome. return(new NeatGenome <T>( _metaNeatGenome, id, birthGeneration, connGenes, hiddenNodeIdArr, nodeIndexByIdMap, acyclicDigraph, connectionIndexMap)); }
public void TestAddAcyclicConnection_CumulativeAdditions() { var pop = CreateNeatPopulation(); var genomeBuilder = NeatGenomeBuilderFactory <double> .Create(pop.MetaNeatGenome); var rootGenome = pop.GenomeList[0]; var strategy = new AddAcyclicConnectionStrategy <double>( pop.MetaNeatGenome, genomeBuilder, pop.GenomeIdSeq, pop.InnovationIdSeq, pop.GenerationSeq); IRandomSource rng = RandomDefaults.CreateRandomSource(); var nodeIdSet = GetNodeIdSet(rootGenome); CyclicGraphAnalysis cyclicGraphAnalysis = new CyclicGraphAnalysis(); AcyclicGraphDepthAnalysis graphDepthAnalysis = new AcyclicGraphDepthAnalysis(); // Run the inner loop test multiple times. // Note. The add-connection mutations are random, thus each loop accumulates a different set of mutations. for (int i = 0; i < 50; i++) { var parentGenome = rootGenome; // Accumulate random mutations for some number of loops. for (int j = 0; j < 20;) { var childGenome = strategy.CreateChildGenome(rootGenome, rng); // Note. the strategy will return a null if it cannot find an acyclic connection to add; // test for this and try again. The test will be for N successful mutations rather than N attempts. if (null == childGenome) { continue; } // The child genome should have one more connection than parent. Assert.AreEqual(rootGenome.ConnectionGenes.Length + 1, childGenome.ConnectionGenes.Length); // The child genome's new connection should not be a duplicate of any of the existing/parent connections. var connSet = GetDirectedConnectionSet(rootGenome); var childConnSet = GetDirectedConnectionSet(childGenome); var newConnList = new List <DirectedConnection>(childConnSet.Except(connSet)); Assert.AreEqual(1, newConnList.Count); // The connection genes should be sorted. Assert.IsTrue(SortUtils.IsSortedAscending(childGenome.ConnectionGenes._connArr)); // The child genome should have the same set of node IDs as the parent. var childNodeIdSet = GetNodeIdSet(childGenome); Assert.IsTrue(nodeIdSet.SetEquals(childNodeIdSet)); // The child genome should describe an acyclic graph, i.e. the new connection should not have // formed a cycle in the graph. var digraph = childGenome.DirectedGraph; Assert.IsFalse(cyclicGraphAnalysis.IsCyclic(digraph)); // Run the acyclic graph depth analysis algorithm. GraphDepthInfo depthInfo = graphDepthAnalysis.CalculateNodeDepths(childGenome.DirectedGraph); // Run again with the alternative algorithm (that uses function recursion). GraphDepthInfo depthInfo2 = AcyclicGraphDepthAnalysisByRecursion.CalculateNodeDepths(childGenome.DirectedGraph); Assert.AreEqual(nodeIdSet.Count, depthInfo._nodeDepthArr.Length); Assert.AreEqual(nodeIdSet.Count, depthInfo2._nodeDepthArr.Length); TestUtils.Compare(depthInfo2._nodeDepthArr, depthInfo._nodeDepthArr); // Set the child genome to be the new parent, thus we accumulate random new connections over time. parentGenome = childGenome; // Increment for successful tests only. j++; } } }