/// <summary> /// Tests if the proposed new connection newConn would form a cycle if added to the existing directed /// acyclic graph described by connArr. /// </summary> /// <param name="connArr">A set of connections that describe a directed acyclic graph.</param> /// <param name="newConn">A proposed new connection to add to the graph.</param> public bool IsConnectionCyclic( DirectedConnection[] connArr, INodeIdMap nodeIdxByIdMap, int totalNodeCount, DirectedConnection newConn) { #if DEBUG // Check for attempts to re-enter this method. if (1 == Interlocked.CompareExchange(ref _reentranceFlag, 1, 0)) { throw new InvalidOperationException("Attempt to re-enter non reentrant method."); } #endif EnsureNodeCapacity(totalNodeCount); _nodeIdxByIdMap = nodeIdxByIdMap; try { return(IsConnectionCyclicInner(connArr, newConn)); } finally { // Ensure cleanup occurs before we return so that we can guarantee the class instance is ready for // re-use on the next call. Cleanup(); } }
public void TestIsConnectionCyclic3() { var cyclicTest = new CyclicConnectionTest(); var connArr = new DirectedConnection[8]; connArr[0] = new DirectedConnection(0, 10); connArr[1] = new DirectedConnection(0, 20); connArr[2] = new DirectedConnection(0, 30); connArr[3] = new DirectedConnection(10, 40); connArr[4] = new DirectedConnection(40, 20); connArr[5] = new DirectedConnection(20, 50); connArr[6] = new DirectedConnection(30, 60); connArr[7] = new DirectedConnection(30, 20); Array.Sort(connArr); INodeIdMap nodeIdxByIdMap = DirectedGraphUtils.CompileNodeIdMap_InputOutputCount_HiddenNodeIdArr( 0, new int[] { 0, 10, 20, 30, 40, 50, 60 }); // True tests (cycle). Assert.IsTrue(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(20, 10))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(20, 40))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(50, 20))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(50, 0))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(50, 40))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(60, 0))); // False tests (no cycle). Assert.IsFalse(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(30, 50))); Assert.IsFalse(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(10, 30))); Assert.IsFalse(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(60, 10))); Assert.IsFalse(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(60, 20))); Assert.IsFalse(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(60, 40))); Assert.IsFalse(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(30, 40))); }
public void TestIsConnectionCyclic2() { var cyclicTest = new CyclicConnectionTest(); var connArr = new DirectedConnection[8]; connArr[0] = new DirectedConnection(0, 1); connArr[1] = new DirectedConnection(0, 2); connArr[2] = new DirectedConnection(0, 3); connArr[3] = new DirectedConnection(1, 4); connArr[4] = new DirectedConnection(4, 2); connArr[5] = new DirectedConnection(2, 5); connArr[6] = new DirectedConnection(3, 6); connArr[7] = new DirectedConnection(3, 2); Array.Sort(connArr); INodeIdMap nodeIdxByIdMap = DirectedGraphUtils.CompileNodeIdMap_InputOutputCount_HiddenNodeIdArr( 0, new int[] { 0, 1, 2, 3, 4, 5, 6 }); // True tests (cycle). Assert.IsTrue(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(2, 1))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(2, 4))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(5, 2))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(5, 0))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(5, 4))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(6, 0))); // False tests (no cycle). Assert.IsFalse(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(3, 5))); Assert.IsFalse(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(1, 3))); Assert.IsFalse(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(6, 1))); Assert.IsFalse(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(6, 2))); Assert.IsFalse(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(6, 4))); Assert.IsFalse(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 7, new DirectedConnection(3, 4))); }
public void TestIsConnectionCyclic1() { var cyclicTest = new CyclicConnectionTest(); var connArr = new DirectedConnection[3]; connArr[0] = new DirectedConnection(0, 1); connArr[1] = new DirectedConnection(1, 2); connArr[2] = new DirectedConnection(2, 3); INodeIdMap nodeIdxByIdMap = DirectedGraphUtils.CompileNodeIdMap_InputOutputCount_HiddenNodeIdArr( 0, new int[] { 0, 1, 2, 3 }); // True tests (cycle). Assert.IsTrue(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 4, new DirectedConnection(0, 0))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 4, new DirectedConnection(1, 1))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 4, new DirectedConnection(2, 2))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 4, new DirectedConnection(3, 3))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 4, new DirectedConnection(1, 0))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 4, new DirectedConnection(2, 0))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 4, new DirectedConnection(3, 0))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 4, new DirectedConnection(2, 1))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 4, new DirectedConnection(3, 1))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 4, new DirectedConnection(3, 2))); // False tests (no cycle). Assert.IsFalse(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 4, new DirectedConnection(0, 2))); Assert.IsFalse(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 4, new DirectedConnection(0, 3))); Assert.IsFalse(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 4, new DirectedConnection(1, 3))); Assert.IsFalse(cyclicTest.IsConnectionCyclic(connArr, nodeIdxByIdMap, 4, new DirectedConnection(2, 3))); }
private bool TryGetConnectionInner(NeatGenome <T> parent, out DirectedConnection conn, out int insertIdx) { int inputCount = _metaNeatGenome.InputNodeCount; int outputCount = _metaNeatGenome.OutputNodeCount; int hiddenCount = parent.HiddenNodeIdArray.Length; // Select a source node at random. // Note. Valid source nodes are input and hidden nodes. Output nodes are not source node candidates // for acyclic nets, because that can prevent future connections from targeting the output if it would // create a cycle. int inputHiddenCount = inputCount + hiddenCount; int srcIdx = _rng.Next(inputHiddenCount); if (srcIdx >= inputCount) { srcIdx += outputCount; } int srcId = GetNodeIdFromIndex(parent, srcIdx); // Select a target node at random. // Note. Valid target nodes are all hidden and output nodes (cannot be an input node). int outputHiddenCount = outputCount + hiddenCount; int tgtId = GetNodeIdFromIndex(parent, inputCount + _rng.Next(outputHiddenCount));; // Test for simplest cyclic connectivity - node connects to itself. if (srcId == tgtId) { conn = default(DirectedConnection); insertIdx = default(int); return(false); } // Test if the chosen connection already exists. // Note. Connection genes are always sorted by sourceId then targetId, so we can use a binary search to // find an existing connection in O(log(n)) time. conn = new DirectedConnection(srcId, tgtId); if ((insertIdx = Array.BinarySearch(parent.ConnectionGenes._connArr, conn)) >= 0) { // The proposed new connection already exists. conn = default(DirectedConnection); insertIdx = default(int); return(false); } // Test if the connection will form a cycle in the wider network. int totalNodeCount = _metaNeatGenome.InputOutputNodeCount + hiddenCount; if (_cyclicTest.IsConnectionCyclic(parent.ConnectionGenes._connArr, parent.NodeIndexByIdMap, totalNodeCount, conn)) { conn = default(DirectedConnection); insertIdx = default(int); return(false); } // Get the position in parent.ConnectionGeneArray that the new connection should be inserted at (to maintain sort order). insertIdx = ~insertIdx; return(true); }
public void IsConnectionCyclic1() { var cyclicCheck = new CyclicConnectionCheck(); var connArr = new DirectedConnection[3]; connArr[0] = new DirectedConnection(0, 1); connArr[1] = new DirectedConnection(1, 2); connArr[2] = new DirectedConnection(2, 3); // True tests (cycle). Assert.True(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(0, 0))); Assert.True(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(1, 1))); Assert.True(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(2, 2))); Assert.True(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(3, 3))); Assert.True(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(1, 0))); Assert.True(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(2, 0))); Assert.True(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(3, 0))); Assert.True(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(2, 1))); Assert.True(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(3, 1))); Assert.True(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(3, 2))); // False tests (no cycle). Assert.False(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(0, 2))); Assert.False(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(0, 3))); Assert.False(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(1, 3))); Assert.False(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(2, 3))); }
public void GetConnectionIndexBySourceNodeId() { var connArr = new DirectedConnection[9]; connArr[0] = new DirectedConnection(1, 70); connArr[1] = new DirectedConnection(4, 14); connArr[2] = new DirectedConnection(6, 23); connArr[3] = new DirectedConnection(7, 36); connArr[4] = new DirectedConnection(10, 18); connArr[5] = new DirectedConnection(10, 31); connArr[6] = new DirectedConnection(14, 2); connArr[7] = new DirectedConnection(20, 24); connArr[8] = new DirectedConnection(25, 63); int idx = DirectedConnectionUtils.GetConnectionIndexBySourceNodeId(connArr, 10); Assert.Equal(4, idx); idx = DirectedConnectionUtils.GetConnectionIndexBySourceNodeId(connArr, 25); Assert.Equal(8, idx); idx = DirectedConnectionUtils.GetConnectionIndexBySourceNodeId(connArr, 26); Assert.Equal(~9, idx); idx = DirectedConnectionUtils.GetConnectionIndexBySourceNodeId(connArr, 1); Assert.Equal(0, idx); idx = DirectedConnectionUtils.GetConnectionIndexBySourceNodeId(connArr, 0); Assert.Equal(~0, idx); }
private bool TryGetConnectionInner(NeatGenome <T> parent, out DirectedConnection conn, out int insertIdx) { int inputCount = _metaNeatGenome.InputNodeCount; int outputCount = _metaNeatGenome.OutputNodeCount; int hiddenCount = parent.HiddenNodeIdArray.Length; // Select a source node at random. // Note. this can be any node (input, output or hidden). int totalNodeCount = parent.MetaNeatGenome.InputOutputNodeCount + hiddenCount; int srcId = GetNodeIdFromIndex(parent, _rng.Next(totalNodeCount)); // Select a target node at random. // Note. This cannot be an input node (so must be a hidden or output node). int outputHiddenCount = outputCount + hiddenCount; int tgtId = GetNodeIdFromIndex(parent, inputCount + _rng.Next(outputHiddenCount)); // Test if the chosen connection already exists. // Note. Connection genes are always sorted by sourceId then targetId, so we can use a binary search to // find an existing connection in O(log(n)) time. conn = new DirectedConnection(srcId, tgtId); if ((insertIdx = Array.BinarySearch(parent.ConnectionGenes._connArr, conn)) < 0) { // The proposed new connection does not already exist, therefore we can use it. // Get the position in parent.ConnectionGeneArray that the new connection should be inserted at (to maintain sort order). insertIdx = ~insertIdx; return(true); } conn = default(DirectedConnection); insertIdx = default(int); return(false); }
private static void AssertConnections( ConnectionGenes <T> connGenes, DirectedGraph digraph, INodeIdMap nodeIndexByIdMap, int[] connectionIndexMap) { // Connection counts. Debug.Assert(connGenes._connArr.Length == digraph.ConnectionIdArrays.Length); // Connection order. Debug.Assert(SortUtils.IsSortedAscending(connGenes._connArr)); Debug.Assert(IsSortedAscending(digraph.ConnectionIdArrays)); // Connection node ID mappings. DirectedConnection[] connArr = connGenes._connArr; int[] srcIdArr = digraph.ConnectionIdArrays._sourceIdArr; int[] tgtIdArr = digraph.ConnectionIdArrays._targetIdArr; for (int i = 0; i < connGenes._connArr.Length; i++) { DirectedConnection connGene = connArr[i]; // Determine the index of he equivalent connection in the digraph. int genomeConnIdx = (null == connectionIndexMap) ? i : connectionIndexMap[i]; Debug.Assert(nodeIndexByIdMap.Map(connArr[genomeConnIdx].SourceId) == srcIdArr[i]); Debug.Assert(nodeIndexByIdMap.Map(connArr[genomeConnIdx].TargetId) == tgtIdArr[i]); } }
public void IsConnectionCyclic2() { var cyclicCheck = new CyclicConnectionCheck(); var connArr = new DirectedConnection[8]; connArr[0] = new DirectedConnection(0, 1); connArr[1] = new DirectedConnection(0, 2); connArr[2] = new DirectedConnection(0, 3); connArr[3] = new DirectedConnection(1, 4); connArr[4] = new DirectedConnection(4, 2); connArr[5] = new DirectedConnection(2, 5); connArr[6] = new DirectedConnection(3, 6); connArr[7] = new DirectedConnection(3, 2); Array.Sort(connArr); // True tests (cycle). Assert.True(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(2, 1))); Assert.True(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(2, 4))); Assert.True(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(5, 2))); Assert.True(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(5, 0))); Assert.True(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(5, 4))); Assert.True(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(6, 0))); // False tests (no cycle). Assert.False(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(3, 5))); Assert.False(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(1, 3))); Assert.False(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(6, 1))); Assert.False(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(6, 2))); Assert.False(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(6, 4))); Assert.False(cyclicCheck.IsConnectionCyclic(connArr, new DirectedConnection(3, 4))); }
public void TestIsConnectionCyclic2() { var cyclicTest = new CyclicConnectionTest(); var connArr = new DirectedConnection[8]; connArr[0] = new DirectedConnection(0, 5); connArr[1] = new DirectedConnection(0, 2); connArr[2] = new DirectedConnection(0, 3); connArr[3] = new DirectedConnection(5, 4); connArr[4] = new DirectedConnection(4, 2); connArr[5] = new DirectedConnection(2, 1); connArr[6] = new DirectedConnection(3, 6); connArr[7] = new DirectedConnection(3, 2); Array.Sort(connArr); DirectedGraph digraph = DirectedGraphBuilder.Create(connArr, 1, 1); // True tests (cycle). Assert.IsTrue(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(2, 5))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(2, 4))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(1, 2))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(1, 0))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(1, 4))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(6, 0))); // False tests (no cycle). Assert.IsFalse(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(3, 1))); Assert.IsFalse(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(5, 3))); Assert.IsFalse(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(6, 5))); Assert.IsFalse(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(6, 2))); Assert.IsFalse(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(6, 4))); Assert.IsFalse(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(3, 4))); }
private bool IsConnectionCyclicInner(IList <DirectedConnection> connList, DirectedConnection newConn) { // Test if the new connection is pointing to itself. if (newConn.SourceId == newConn.TargetId) { return(true); } // Note. We traverse forwards starting at the new connection's target node. If the new connection's source node is // encountered during traversal then the connection would form a cycle in the graph as a whole, and we return true. int startNodeId = newConn.TargetId; // Search for outgoing connections from the starting node. int connIdx = DirectedConnectionUtils.GetConnectionIndexBySourceNodeId(connList, startNodeId); if (connIdx < 0) { // The current node has no outgoing connections, therefore newConn does not form a cycle. return(false); } // Initialise and run the graph traversal algorithm. InitGraphTraversal(startNodeId, connIdx); // Note. we pass newConn.SourceId as the terminalNodeId; if traversal reaches this node then a cycle has been detected. return(TraverseGraph(connList, newConn.SourceId)); }
public void TestIsConnectionCyclic1() { var cyclicTest = new CyclicConnectionTest(); var connArr = new DirectedConnection[3]; connArr[0] = new DirectedConnection(0, 2); connArr[1] = new DirectedConnection(2, 3); connArr[2] = new DirectedConnection(3, 1); DirectedGraph digraph = DirectedGraphBuilder.Create(connArr, 1, 1); // True tests (cycle). Assert.IsTrue(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(0, 0))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(1, 1))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(2, 2))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(3, 3))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(2, 0))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(3, 0))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(1, 0))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(3, 2))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(1, 2))); Assert.IsTrue(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(1, 3))); // False tests (no cycle). Assert.IsFalse(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(0, 3))); Assert.IsFalse(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(0, 1))); Assert.IsFalse(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(2, 1))); Assert.IsFalse(cyclicTest.IsConnectionCyclic(digraph, new DirectedConnection(3, 1))); }
public void Simple_DefinedNodes_NodeIdGap() { var metaNeatGenome = new MetaNeatGenome <double>(0, 10, false, new ReLU()); var genomeBuilder = NeatGenomeBuilderFactory <double> .Create(metaNeatGenome); // Simple acyclic graph. var connGenes = new ConnectionGenes <double>(4); connGenes[0] = (100, 103, 0.0); connGenes[1] = (101, 103, 1.0); connGenes[2] = (102, 103, 2.0); connGenes[3] = (102, 104, 3.0); // Wrap in a genome. var genome = genomeBuilder.Create(0, 0, connGenes); // Note. The genome builder creates a digraph representation of the genome and attaches/caches it on the genome object. var digraph = genome.DirectedGraph; // The gaps in the node IDs should be removed such that node IDs form a contiguous span starting from zero. var connArrExpected = new DirectedConnection[4]; var weightArrExpected = new double[4]; connArrExpected[0] = new DirectedConnection(10, 13); weightArrExpected[0] = 0.0; connArrExpected[1] = new DirectedConnection(11, 13); weightArrExpected[1] = 1.0; connArrExpected[2] = new DirectedConnection(12, 13); weightArrExpected[2] = 2.0; connArrExpected[3] = new DirectedConnection(12, 14); weightArrExpected[3] = 3.0; // The graph should be unchanged from the input connections. CompareConnectionLists(connArrExpected, digraph.ConnectionIdArrays); // Check the node count. Assert.AreEqual(15, digraph.TotalNodeCount); }
/// <summary> /// Calculates the L2/Euclidean centroid for the given set of points. /// Note. In Euclidean space the centroid is given by calculating the componentwise mean over the /// set of points. /// </summary> /// <remarks> /// The euclidean centroid is a central position within a set of points that minimizes the sum of the squared /// distance between each of those points and the centroid. As such it can also be thought of as being an exemplar /// for a set of points. /// </remarks> public static ConnectionGenes <double> CalculateEuclideanCentroid(IEnumerable <ConnectionGenes <double> > coordList) { // Special case. One item in list, therefore it is the centroid. int count = coordList.Count(); if (1 == count) { return(coordList.First()); } // Each coordinate element has an ID. Here we calculate the total for each ID across all CoordinateVectors, // then divide the totals by the number of CoordinateVectors to get the average for each ID. That is, we // calculate the componentwise mean. // // Coord elements within a CoordinateVector must be sorted by ID, therefore we use a SortedDictionary here // when building the centroid coordinate to eliminate the need to sort elements later. // // We use SortedDictionary and not SortedList for performance. SortedList is fastest for insertion // only if the inserts are in order (sorted). However, this is generally not the case here because although // coordinate IDs are sorted within the source CoordinateVectors, not all IDs exist within all CoordinateVectors // therefore a low ID may be presented to coordElemTotals after a higher ID. var coordElemTotals = new SortedDictionary <DirectedConnection, WeightRef>(); // Loop over coords. foreach (ConnectionGenes <double> coord in coordList) { // Loop over each element within the current coord. DirectedConnection[] connArr = coord._connArr; double[] weightArr = coord._weightArr; for (int i = 0; i < connArr.Length; i++) { DirectedConnection conn = connArr[i]; double weight = weightArr[i]; // If the ID has previously been encountered then add the current element value to it, otherwise // add a new double[1] to hold the value.. // Note that we wrap the double value in an object so that we do not have to re-insert values // to increment them. In tests this approach was about 40% faster (including GC overhead). // TODO: Review the use of WeightRef as a wrapper; this generates a lot of object allocations that we could avoid, e.g. // with a custom dictionary implementation that allows accumulating a numeric value for an existing entry; to do this with a // Dictionary requires the approach used here, or re-looking up the slot to update it. if (coordElemTotals.TryGetValue(conn, out WeightRef weightRef)) { weightRef.Weight += weight; } else { coordElemTotals.Add(conn, new WeightRef(weight)); } } } // Create and return the centroid. return(CreateCentroid(coordElemTotals, count)); }
public void DepthNodeReorderTest() { var metaNeatGenome = new MetaNeatGenome <double>(2, 2, true, new ReLU()); var genomeBuilder = NeatGenomeBuilderFactory <double> .Create(metaNeatGenome); // Define graph connections. var connGenes = new ConnectionGenes <double>(5); connGenes[0] = (0, 4, 0.0); connGenes[1] = (4, 5, 1.0); connGenes[2] = (5, 2, 2.0); connGenes[3] = (1, 2, 3.0); connGenes[4] = (2, 3, 4.0); connGenes.Sort(); // Wrap in a genome. var genome = genomeBuilder.Create(0, 0, connGenes); // Note. The genome builder creates a digraph representation of the genome and attaches/caches it on the genome object. var acyclicDigraph = (DirectedGraphAcyclic)genome.DirectedGraph; Assert.NotNull(acyclicDigraph); // Simulate the actual weight array that would occur in e.g. a WeightedDirectedGraphAcyclic or NeuralNetAcyclic. double[] weightArrActual = new double[connGenes._weightArr.Length]; for (int i = 0; i < weightArrActual.Length; i++) { weightArrActual[i] = connGenes._weightArr[genome.ConnectionIndexMap[i]]; } // The nodes should have IDs allocated based on depth, i.e. the layer they are in. // And connections should be ordered by source node ID. var connArrExpected = new DirectedConnection[5]; var weightArrExpected = new double[5]; connArrExpected[0] = new DirectedConnection(0, 2); weightArrExpected[0] = 0.0; connArrExpected[1] = new DirectedConnection(1, 4); weightArrExpected[1] = 3.0; connArrExpected[2] = new DirectedConnection(2, 3); weightArrExpected[2] = 1.0; connArrExpected[3] = new DirectedConnection(3, 4); weightArrExpected[3] = 2.0; connArrExpected[4] = new DirectedConnection(4, 5); weightArrExpected[4] = 4.0; // Compare actual and expected connections. CompareConnectionLists(connArrExpected, weightArrExpected, acyclicDigraph.ConnectionIds, weightArrActual); // Test layer info. LayerInfo[] layerArrExpected = new LayerInfo[5]; layerArrExpected[0] = new LayerInfo(2, 2); layerArrExpected[1] = new LayerInfo(3, 3); layerArrExpected[2] = new LayerInfo(4, 4); layerArrExpected[3] = new LayerInfo(5, 5); layerArrExpected[4] = new LayerInfo(6, 5); Assert.Equal(5, acyclicDigraph.LayerArray.Length); // Check the node count. Assert.Equal(6, acyclicDigraph.TotalNodeCount); }
/// <summary> /// Create a new child genome from a given parent genome. /// </summary> /// <param name="parent">The parent genome.</param> /// <returns>A new child genome.</returns> public NeatGenome <T> CreateChildGenome(NeatGenome <T> parent) { Debug.Assert(_metaNeatGenome == parent.MetaNeatGenome, "Parent genome has unexpected MetaNeatGenome."); // Attempt to find a new connection that we can add to the genome. DirectedConnection directedConn; if (!TryGetConnection(parent, out directedConn, out int insertIdx)) { // Failed to find a new connection. return(null); } // Determine the connection weight. // 50% of the time use weights very close to zero. // Note. this recreates the strategy used in SharpNEAT 2.x. // ENHANCEMENT: Reconsider the distribution of new weights and if there are better approaches (distributions) we could use. T weight = _rng.NextBool() ? _weightDistB.Sample() : _weightDistA.Sample(); // Create a new connection gene array that consists of the parent connection genes plus the new gene // inserted at the correct (sorted) position. var parentConnArr = parent.ConnectionGenes._connArr; var parentWeightArr = parent.ConnectionGenes._weightArr; int parentLen = parentConnArr.Length; // Create the child genome's ConnectionGenes object. int childLen = parentLen + 1; var connGenes = new ConnectionGenes <T>(childLen); var connArr = connGenes._connArr; var weightArr = connGenes._weightArr; // Copy genes up to insertIdx. Array.Copy(parentConnArr, connArr, insertIdx); Array.Copy(parentWeightArr, weightArr, insertIdx); // Copy the new genome into its insertion point. connArr[insertIdx] = new DirectedConnection( directedConn.SourceId, directedConn.TargetId); weightArr[insertIdx] = weight; // Copy remaining genes (if any). Array.Copy(parentConnArr, insertIdx, connArr, insertIdx + 1, parentLen - insertIdx); Array.Copy(parentWeightArr, insertIdx, weightArr, insertIdx + 1, parentLen - insertIdx); // Create and return a new genome. // Notes. // The set of hidden node IDs remains unchanged from the parent, therefore we are able to re-use parent.HiddenNodeIdArray. // However, the presence of a new connection invalidates parent.NodeIndexByIdMap for use in the new genome, because the allocated // node indexes are dependent on node depth in the acyclic graph, which in turn can be modified by the presence of a new connection. return(_genomeBuilder.Create( _genomeIdSeq.Next(), _generationSeq.Peek, connGenes, parent.HiddenNodeIdArray)); }
/// <summary> /// Create a new child genome from a given parent genome. /// </summary> /// <param name="parent">The parent genome.</param> /// <param name="rng">Random source.</param> /// <returns>A new child genome.</returns> public NeatGenome <T>?CreateChildGenome(NeatGenome <T> parent, IRandomSource rng) { // Attempt to find a new connection that we can add to the genome. if (!TryGetConnection(parent, rng, out DirectedConnection directedConn, out int insertIdx)) { // Failed to find a new connection. return(null); } // Determine the connection weight. // 50% of the time use weights very close to zero. // Note. this recreates the strategy used in SharpNEAT 2.x. // ENHANCEMENT: Reconsider the distribution of new weights and if there are better approaches (distributions) we could use. T weight = rng.NextBool() ? _weightSamplerB.Sample(rng) : _weightSamplerA.Sample(rng); // Create a new connection gene array that consists of the parent connection genes plus the new gene // inserted at the correct (sorted) position. var parentConnArr = parent.ConnectionGenes._connArr; var parentWeightArr = parent.ConnectionGenes._weightArr; int parentLen = parentConnArr.Length; // Create the child genome's ConnectionGenes object. int childLen = parentLen + 1; var connGenes = new ConnectionGenes <T>(childLen); var connArr = connGenes._connArr; var weightArr = connGenes._weightArr; // Copy genes up to insertIdx. Array.Copy(parentConnArr, connArr, insertIdx); Array.Copy(parentWeightArr, weightArr, insertIdx); // Copy the new genome into its insertion point. connArr[insertIdx] = new DirectedConnection( directedConn.SourceId, directedConn.TargetId); weightArr[insertIdx] = weight; // Copy remaining genes (if any). Array.Copy(parentConnArr, insertIdx, connArr, insertIdx + 1, parentLen - insertIdx); Array.Copy(parentWeightArr, insertIdx, weightArr, insertIdx + 1, parentLen - insertIdx); // Create and return a new genome. // Note. The set of hidden node IDs remains unchanged from the parent, therefore we are able to re-use // both parent.HiddenNodeIdArray and NodeIndexByIdMap. return(_genomeBuilder.Create( _genomeIdSeq.Next(), _generationSeq.Peek, connGenes, parent.HiddenNodeIdArray, parent.NodeIndexByIdMap)); }
/// <summary> /// Calculates the L2/Euclidean centroid for the given set of points. /// </summary> /// <param name="pointList">The set of points.</param> /// <returns>A new instance of <see cref="ConnectionGenes{T}"/>.</returns> /// <remarks> /// The euclidean centroid is a central position within a set of points that minimizes the sum of the squared /// distance between each of those points and the centroid. As such it can also be thought of as being an exemplar /// for a set of points. /// /// In Euclidean space the centroid is obtained by calculating the componentwise mean over the set of points. /// </remarks> public static ConnectionGenes <double> CalculateEuclideanCentroid( IList <ConnectionGenes <double> > pointList) { if (pointList.Count == 1) { // Special case. One item in list, therefore it is the centroid. return(pointList[0]); } else if (pointList.Count == 0) { // This scenario isn't intended to occur; see https://github.com/colgreen/sharpneat-refactor/issues/5 return(new ConnectionGenes <double>(0)); } // ENHANCEMENT: Obtain dictionary from a pool to avoid allocation and initialisation cost on each call to this method. // Each coordinate element has an ID. Here we calculate the total for each ID across all CoordinateVectors, // then divide the totals by the number of CoordinateVectors to get the average for each ID. That is, we // calculate the componentwise mean. var coordElemTotals = new Dictionary <DirectedConnection, double>( Math.Max(pointList[0].Length, 32)); // Loop over coords. foreach (ConnectionGenes <double> point in pointList) { // Loop over each element within the current coord. DirectedConnection[] connArr = point._connArr; double[] weightArr = point._weightArr; for (int i = 0; i < connArr.Length; i++) { DirectedConnection conn = connArr[i]; double weight = weightArr[i]; // ENHANCEMENT: Updating an existing entry here requires a second lookup; in principle this could be avoided, // e.g. by using a custom dictionary implementation with InsertOrSum() method. // If the ID has previously been encountered then add the current element value to it, otherwise // add a new entry to the dictionary. // TODO: [.NET 6+] Use Marshal.GetValueRefOrAddDefault here to avoid the second lookup for adding a missing item. if (coordElemTotals.TryGetValue(conn, out double weightAcc)) { coordElemTotals[conn] = weightAcc + weight; } else { coordElemTotals.Add(conn, weight); } } } // Create and return the centroid. return(CreateCentroid(coordElemTotals, pointList.Count)); }
/// <summary> /// Calculates the L2/Euclidean centroid for the given set of points. /// Note. In Euclidean space the centroid is given by calculating the componentwise mean over the /// set of points. /// </summary> /// <remarks> /// The euclidean centroid is a central position within a set of points that minimizes the sum of the squared /// distance between each of those points and the centroid. As such it can also be thought of as being an exemplar /// for a set of points. /// </remarks> public static ConnectionGenes <double> CalculateEuclideanCentroid( IEnumerable <ConnectionGenes <double> > coordList) { // Special case. One item in list, therefore it is the centroid. int count = coordList.Count(); if (1 == count) { return(coordList.First()); } // ENHANCEMENT: Obtain dictionary from a pool to avoid allocation and initialisation cost on each call to this method. // Each coordinate element has an ID. Here we calculate the total for each ID across all CoordinateVectors, // then divide the totals by the number of CoordinateVectors to get the average for each ID. That is, we // calculate the componentwise mean. var coordElemTotals = new Dictionary <DirectedConnection, double>(64); // Loop over coords. foreach (ConnectionGenes <double> coord in coordList) { // Loop over each element within the current coord. DirectedConnection[] connArr = coord._connArr; double[] weightArr = coord._weightArr; for (int i = 0; i < connArr.Length; i++) { DirectedConnection conn = connArr[i]; double weight = weightArr[i]; // ENHANCEMENT: Updating an existing entry here requires a second lookup; in principle this could be avoided, // e.g. by using a custom dictionary implementation with InsertOrSum() method. // If the ID has previously been encountered then add the current element value to it, otherwise // add a new entry to the dictionary. if (coordElemTotals.TryGetValue(conn, out double weightAcc)) { coordElemTotals[conn] = weightAcc + weight; } else { coordElemTotals.Add(conn, weight); } } } // Create and return the centroid. return(CreateCentroid(coordElemTotals, count)); }
private bool TryGetConnection(NeatGenome <T> parent, out DirectedConnection conn, out int insertIdx) { // Make several attempts at find a new connection, if not successful then give up. for (int attempts = 0; attempts < 5; attempts++) { if (TryGetConnectionInner(parent, out conn, out insertIdx)) { return(true); } } conn = default(DirectedConnection); insertIdx = default(int); return(false); }
/// <summary> /// Tests if the proposed new connection newConn would form a cycle if added to the existing directed /// acyclic graph described by connArr. /// </summary> /// <param name="connList">A set of connections that describe a directed acyclic graph.</param> /// <param name="newConn">A proposed new connection to add to the graph.</param> public bool IsConnectionCyclic(IList <DirectedConnection> connList, DirectedConnection newConn) { #if DEBUG // Check for attempts to re-enter this method. if (1 == Interlocked.CompareExchange(ref _reentranceFlag, 1, 0)) { throw new InvalidOperationException("Attempt to re-enter non reentrant method."); } #endif try { return(IsConnectionCyclicInner(connList, newConn)); } finally { // Ensure cleanup occurs before we return so that we can guarantee the class instance is ready for // re-use on the next call. Cleanup(); } }
/// <summary> /// Calculates the L2/Euclidean centroid for the given set of points. /// Note. In Euclidean space the centroid is given by calculating the componentwise mean over the /// set of points. /// </summary> /// <remarks> /// The euclidean centroid is a central position within a set of points that minimizes the sum of the squared /// distance between each of those points and the centroid. As such it can also be thought of as being an exemplar /// for a set of points. /// </remarks> public static ConnectionGenes <double> CalculateEuclideanCentroid(IEnumerable <ConnectionGenes <double> > coordList) { // Special case. One item in list, therefore it is the centroid. int count = coordList.Count(); if (1 == count) { return(coordList.First()); } // Each coordinate element has an ID. Here we calculate the total for each ID across all CoordinateVectors, // then divide the totals by the number of CoordinateVectors to get the average for each ID. That is, we // calculate the componentwise mean. // // Coord elements within a CoordinateVector must be sorted by ID, therefore we use a SortedDictionary here // when building the centroid coordinate to eliminate the need to sort elements later. // // We use SortedDictionary and not SortedList for performance. SortedList is fastest for insertion // only if the inserts are in order (sorted). However, this is generally not the case here because although // coordinate IDs are sorted within the source CoordinateVectors, not all IDs exist within all CoordinateVectors // therefore a low ID may be presented to coordElemTotals after a higher ID. var coordElemTotals = new SortedDictionary <DirectedConnection, double[]>(); // Loop over coords. foreach (ConnectionGenes <double> coord in coordList) { // Loop over each element within the current coord. DirectedConnection[] connArr = coord._connArr; double[] weightArr = coord._weightArr; for (int i = 0; i < connArr.Length; i++) { DirectedConnection conn = connArr[i]; double weight = weightArr[i]; // If the ID has previously been encountered then add the current element value to it, otherwise // add a new double[1] to hold the value.. // Note that we wrap the double value in an object so that we do not have to re-insert values // to increment them. In tests this approach was about 40% faster (including GC overhead). if (coordElemTotals.TryGetValue(conn, out double[] doubleWrapper))
private static bool ValidateConnections( ConnectionGenes <T> connGenes, DirectedGraph digraph, INodeIdMap nodeIndexByIdMap, int[] connectionIndexMap) { // Connection counts. if (connGenes._connArr.Length != digraph.ConnectionIdArrays.Length) { return(false); } // Connection order. if (!SortUtils.IsSortedAscending(connGenes._connArr) || !IsSortedAscending(digraph.ConnectionIdArrays)) { return(false); } // Connection node ID mappings. DirectedConnection[] connArr = connGenes._connArr; int[] srcIdArr = digraph.ConnectionIdArrays._sourceIdArr; int[] tgtIdArr = digraph.ConnectionIdArrays._targetIdArr; for (int i = 0; i < connGenes._connArr.Length; i++) { DirectedConnection connGene = connArr[i]; // Determine the index of he equivalent connection in the digraph. int genomeConnIdx = (null == connectionIndexMap) ? i : connectionIndexMap[i]; if (nodeIndexByIdMap.Map(connArr[genomeConnIdx].SourceId) != srcIdArr[i] || nodeIndexByIdMap.Map(connArr[genomeConnIdx].TargetId) != tgtIdArr[i]) { return(false); } } return(true); }
/// <summary> /// Creates a single randomly initialised genome. /// </summary> private NeatGenome <T> CreateGenome() { // Determine how many connections to create in the new genome, as a proportion of all possible connections // between the input and output nodes. int connectionCount = (int)NumericsUtils.ProbabilisticRound(_connectionDefArr.Length * _connectionsProportion, _rng); // Ensure there is at least one connection. connectionCount = Math.Max(1, connectionCount); // Select a random subset of all possible connections between the input and output nodes. int[] sampleArr = new int[connectionCount]; DiscreteDistributionUtils.SampleUniformWithoutReplacement( _connectionDefArr.Length, sampleArr, _rng); // Sort the samples. // Note. This results in the neural net connections being sorted by sourceID then targetID. Array.Sort(sampleArr); // Create the connection gene arrays and populate them. var connGenes = new ConnectionGenes <T>(connectionCount); var connArr = connGenes._connArr; var weightArr = connGenes._weightArr; for (int i = 0; i < sampleArr.Length; i++) { DirectedConnection cdef = _connectionDefArr[sampleArr[i]]; connArr[i] = new DirectedConnection( cdef.SourceId, cdef.TargetId); weightArr[i] = _connWeightDist.Sample(_metaNeatGenome.ConnectionWeightRange, true); } // Get create a new genome with a new ID, birth generation of zero. int id = _genomeIdSeq.Next(); return(_genomeBuilder.Create(id, 0, connGenes)); }
private NeatPopulationFactory( MetaNeatGenome <T> metaNeatGenome, double connectionsProportion, IRandomSource rng) { _metaNeatGenome = metaNeatGenome; _genomeBuilder = NeatGenomeBuilderFactory <T> .Create(metaNeatGenome); _connectionsProportion = connectionsProportion; // Define the set of all possible connections between the input and output nodes (fully interconnected). int inputCount = metaNeatGenome.InputNodeCount; int outputCount = metaNeatGenome.OutputNodeCount; _connectionDefArr = new DirectedConnection[inputCount * outputCount]; // Notes. // Nodes are assigned innovation IDs. By convention the input nodes are assigned IDs first starting at zero, then the output nodes. // Thus, because all of the evolved networks have a fixed number of inputs and outputs, the IDs of these nodes are always fixed. int firstOutputNodeId = inputCount; for (int srcId = 0, i = 0; srcId < inputCount; srcId++) { for (int tgtIdx = 0; tgtIdx < outputCount; tgtIdx++) { _connectionDefArr[i++] = new DirectedConnection(srcId, firstOutputNodeId + tgtIdx); } } // Init RNG and ID sequences. _rng = rng; _genomeIdSeq = new Int32Sequence(); int nextInnovationId = inputCount + outputCount; _innovationIdSeq = new Int32Sequence(nextInnovationId); // Init random connection weight source. _connWeightDist = ContinuousDistributionFactory.CreateUniformDistribution <T>(_metaNeatGenome.ConnectionWeightRange, true); }
/// <summary> /// Calculates the distance between two positions. /// </summary> public double CalcDistance(ConnectionGenes <double> p1, ConnectionGenes <double> p2) { DirectedConnection[] connArr1 = p1._connArr; DirectedConnection[] connArr2 = p2._connArr; double[] weightArr1 = p1._weightArr; double[] weightArr2 = p2._weightArr; // Store these heavily used values locally. int length1 = connArr1.Length; int length2 = connArr2.Length; // Test for special cases. if (0 == length1 && 0 == length2) { // Both arrays are empty. No disparities, therefore the distance is zero. return(0.0); } double distance = 0.0; if (0 == length1) { // All p2 genes are mismatches. for (int i = 0; i < length2; i++) { distance += Math.Abs(weightArr2[i]); } return((_mismatchDistanceConstant * length2) + (distance * _mismatchDistanceCoeff)); } if (0 == length2) { // All p1 elements are mismatches. for (int i = 0; i < length1; i++) { distance += Math.Abs(weightArr1[i]); } return((_mismatchDistanceConstant * length1) + (distance * _mismatchDistanceCoeff)); } // Both arrays contain elements. int arr1Idx = 0; int arr2Idx = 0; DirectedConnection conn1 = connArr1[arr1Idx]; DirectedConnection conn2 = connArr2[arr2Idx]; double weight1 = weightArr1[arr1Idx]; double weight2 = weightArr2[arr2Idx]; for (;;) { if (conn1 < conn2) { // p2 doesn't specify a value in this dimension therefore we take its position to be 0. distance += _mismatchDistanceConstant + (Math.Abs(weight1) * _mismatchDistanceCoeff); // Move to the next element in p1. arr1Idx++; } else if (conn1 == conn2) { // Matching elements. distance += Math.Abs(weight1 - weight2) * _matchDistanceCoeff; // Move to the next element in both arrays. arr1Idx++; arr2Idx++; } else // conn2 > conn1 { // p1 doesn't specify a value in this dimension therefore we take its position to be 0. distance += _mismatchDistanceConstant + (Math.Abs(weight2) * _mismatchDistanceCoeff); // Move to the next element in p2. arr2Idx++; } // Check if we have exhausted one or both of the arrays. if (arr1Idx == length1) { // All remaining p2 elements are mismatches. for (int i = arr2Idx; i < length2; i++) { distance += _mismatchDistanceConstant + (Math.Abs(weightArr2[i]) * _mismatchDistanceCoeff); } return(distance); } if (arr2Idx == length2) { // All remaining p1 elements are mismatches. for (int i = arr1Idx; i < connArr1.Length; i++) { distance += _mismatchDistanceConstant + (Math.Abs(weightArr1[i]) * _mismatchDistanceCoeff); } return(distance); } conn1 = connArr1[arr1Idx]; conn2 = connArr2[arr2Idx]; weight1 = weightArr1[arr1Idx]; weight2 = weightArr2[arr2Idx]; } }
/// <summary> /// Tests if the distance between two positions is less than some threshold. /// /// A simple way of implementing this method would be to calculate the distance between the /// two coordinates and test if it is less than the threshold. However, that approach requires that all of the /// elements in both CoordinateVectors be fully compared. We can improve performance in the general case /// by testing if the threshold has been passed after each vector element comparison thus allowing an early exit /// from the method for many calls. Further to this, we can begin comparing from the ends of the vectors where /// differences are most likely to occur. /// </summary> public bool TestDistance(ConnectionGenes <double> p1, ConnectionGenes <double> p2, double threshold) { DirectedConnection[] connArr1 = p1._connArr; DirectedConnection[] connArr2 = p2._connArr; double[] weightArr1 = p1._weightArr; double[] weightArr2 = p2._weightArr; // Store these heavily used values locally. int length1 = connArr1.Length; int length2 = connArr2.Length; // Test for special case. if (0 == length1 && 0 == length2) { // Both arrays are empty. No disparities, therefore the distance is zero. return(0.0 < threshold); } double distance = 0.0; if (0 == length1) { // All p2 elements are mismatches. // p1 doesn't specify a value in these dimensions therefore we take its position to be 0 in all of them. for (int i = 0; i < length2; i++) { distance += Math.Abs(weightArr2[i]); } distance = (_mismatchDistanceConstant * length2) + (distance * _mismatchDistanceCoeff); return(distance < threshold); } if (0 == length2) { // All p1 elements are mismatches. // p2 doesn't specify a value in these dimensions therefore we take its position to be 0 in all of them. for (int i = 0; i < length1; i++) { distance += Math.Abs(weightArr1[i]); } distance = (_mismatchDistanceConstant * length1) + (distance * _mismatchDistanceCoeff); return(distance < threshold); } // Both arrays contain elements. Compare the contents starting from the ends where the greatest discrepancies // between coordinates are expected to occur. In the general case this should result in less element comparisons // before the threshold is passed and we exit the method. int arr1Idx = length1 - 1; int arr2Idx = length2 - 1; DirectedConnection conn1 = connArr1[arr1Idx]; DirectedConnection conn2 = connArr2[arr2Idx]; double weight1 = weightArr1[arr1Idx]; double weight2 = weightArr2[arr2Idx]; for (;;) { if (conn1 > conn2) { // p2 doesn't specify a value in this dimension therefore we take its position to be 0. distance += _mismatchDistanceConstant + (Math.Abs(weight1) * _mismatchDistanceCoeff); // Move to the next element in p1. arr1Idx--; } else if (conn1 == conn2) { // Matching elements. distance += Math.Abs(weight1 - weight2) * _matchDistanceCoeff; // Move to the next element in both arrays. arr1Idx--; arr2Idx--; } else // conn2 > conn1 { // p1 doesn't specify a value in this dimension therefore we take its position to be 0. distance += _mismatchDistanceConstant + (Math.Abs(weight2) * _mismatchDistanceCoeff); // Move to the next element in p12 arr2Idx--; } // Test the threshold. if (distance >= threshold) { return(false); } // Check if we have exhausted one or both of the arrays. if (arr1Idx < 0) { // Any remaining p2 elements are mismatches. for (int i = arr2Idx; i > -1; i--) { distance += _mismatchDistanceConstant + (Math.Abs(weightArr2[i]) * _mismatchDistanceCoeff); } return(distance < threshold); } if (arr2Idx < 0) { // All remaining p1 elements are mismatches. for (int i = arr1Idx; i > -1; i--) { distance += _mismatchDistanceConstant + (Math.Abs(weightArr1[i]) * _mismatchDistanceCoeff); } return(distance < threshold); } conn1 = connArr1[arr1Idx]; conn2 = connArr2[arr2Idx]; weight1 = weightArr1[arr1Idx]; weight2 = weightArr2[arr2Idx]; } }
public MockConnection(IWampFormatter <TMessage> formatter) { mSideAToSideBConnection = new DirectedConnection(mSideBToSideA, mSideAToSideB, formatter); mSideBToSideAConnection = new DirectedConnection(mSideAToSideB, mSideBToSideA, formatter); }
/// <summary> /// Create a new child genome from a given parent genome. /// </summary> /// <param name="parent">The parent genome.</param> /// <param name="rng">Random source.</param> /// <returns>A new child genome.</returns> public NeatGenome <T>?CreateChildGenome(NeatGenome <T> parent, IRandomSource rng) { if (0 == parent.ConnectionGenes.Length) { // No connections to split (nodes are added by splitting an existing connection). return(null); } // Select a connection at random. int splitConnIdx = rng.Next(parent.ConnectionGenes.Length); var splitConn = parent.ConnectionGenes._connArr[splitConnIdx]; // The selected connection will be replaced with a new node and two new connections; // get an innovation ID for the new node. int addedNodeId = GetInnovationID(splitConn, parent, out bool newInnovationIdsFlag); // Create the two new connections. var newConnArr = new DirectedConnection[] { new DirectedConnection(splitConn.SourceId, addedNodeId), new DirectedConnection(addedNodeId, splitConn.TargetId) }; // Get weights for the new connections. // Connection 1 gets the weight from the original connection; connection 2 gets a fixed // weight of _metaNeatGenome.ConnectionWeightRange. // ENHANCEMENT: Consider a better choice of weights for the new connections; this scheme has been // copied from sharpneat 2.x as a starting point, but can likely be improved upon. var newWeightArr = new T[] { parent.ConnectionGenes._weightArr[splitConnIdx], (T)Convert.ChangeType(_metaNeatGenome.ConnectionWeightScale, typeof(T)) }; // Ensure newConnArr is sorted. // Later on we'll determine their insertion indexes into the connection array, therefore this ensures that // the insert indexes will be sorted correctly. if (newConnArr[0].CompareTo(newConnArr[1]) > 0) { var tmpConn = newConnArr[0]; newConnArr[0] = newConnArr[1]; newConnArr[1] = tmpConn; T tmpWeight = newWeightArr[0]; newWeightArr[0] = newWeightArr[1]; newWeightArr[1] = tmpWeight; } // Create a new connection gene array that consists of the parent connection genes, // with the connection that was split removed, and the two new connection genes that // replace it inserted at the correct (sorted) positions. var parentConnArr = parent.ConnectionGenes._connArr; var parentWeightArr = parent.ConnectionGenes._weightArr; int parentLen = parentConnArr.Length; // Create the child genome's ConnectionGenes object. int childLen = parentLen + 1; var connGenes = new ConnectionGenes <T>(childLen); var connArr = connGenes._connArr; var weightArr = connGenes._weightArr; // Build an array of parent indexes to stop at when copying from the parent to the child connection array. // Note. Each index is combined with a second value; an index into newConnArr for insertions, // and -1 for the split index (the connection to be removed) int insertIdx1 = ~Array.BinarySearch(parent.ConnectionGenes._connArr, newConnArr[0]); int insertIdx2 = ~Array.BinarySearch(parent.ConnectionGenes._connArr, newConnArr[1]); (int, int)[] stopIdxArr = new []