double fitnessFunction(BitArray representation) { MinimalSpanningTree mst = dnaToMst(representation); // This is the bottleneck, quite obviously. mst.Span(startFrom: startNodes); int usedNodes = mst.UsedNodeCount; // TODO: Investigate fitness function return(1500 - usedNodes); }
/// <summary> /// Tells the genetic algorithm to advance one generation and processes /// the resulting (possibly) improved solution. /// </summary> public void EvolutionStep() { if (!_initialized) { throw new InvalidOperationException("Solver not initialized!"); } ga.NewGeneration(); if ((_bestDNA == null) || (GeneticAnnealingAlgorithm.SetBits(ga.GetBestDNA().Xor(_bestDNA)) != 0)) { _bestDNA = ga.GetBestDNA(); MinimalSpanningTree bestMst = dnaToMst(_bestDNA); bestMst.Span(startFrom: startNodes); // #DEBUG#: Pass true to show the used steiner nodes. BestSolution = SpannedMstToSkillnodes(bestMst, false); } }
public void TestMST() { /// 0 -- 1 -- 2 -- 3 /// \ | / /// \ | / /// 4 -- 5 -- 6 -- 7 bool[,] graph1 = { { false, true, false, false, true, false, false, false }, { true, false, true, false, false, false, false, false }, { false, true, false, true, false, true, false, false }, { false, false, true, false, false, true, false, false }, { true, false, false, false, false, true, false, false }, { false, false, true, true, true, false, true, false }, { false, false, false, false, false, true, false, true }, { false, false, false, false, false, false, true, false }, }; SearchGraph searchGraph1 = SearchGraphFromData(graph1); Dictionary<int, GraphNode> graphNodes1 = GetGraphNodesIdIndex(searchGraph1); DistanceLookup distanceLookup = new DistanceLookup(); HashSet<GraphNode> mstNodes1 = new HashSet<GraphNode> { graphNodes1[3], graphNodes1[5], graphNodes1[7] }; MinimalSpanningTree mst1 = new MinimalSpanningTree(mstNodes1); mst1.Span(startFrom: graphNodes1[0]); Assert.IsTrue(mst1.SpanningEdges.Count == 3, "Wrong amount of spanning edges"); /// This can fail even if the mst would be valid! The test only works for /// the current implementation of the mst algorithm, but I can't find a /// better way to do this with the current data structures... Assert.IsTrue(mst1.SpanningEdges[0].inside.Id == 0 && mst1.SpanningEdges[0].outside.Id == 5, "First edge is wrong"); Assert.IsTrue(mst1.SpanningEdges[1].inside.Id == 5 && mst1.SpanningEdges[1].outside.Id == 3, "Second edge is wrong"); Assert.IsTrue(mst1.SpanningEdges[2].inside.Id == 5 && mst1.SpanningEdges[2].outside.Id == 7, "Third edge is wrong"); Assert.IsTrue(mst1.UsedNodeCount == 5, "Wrong MST length"); /// Test unconnected graph /// 0 -- 1 2 -- 3 /// | / /// | / /// 4 -- 5 bool[,] graph2 = { { false, true, false, false, false, false }, { true, false, false, false, false, false }, { false, false, false, true, false, true }, { false, false, true, false, false, true }, { false, false, false, false, false, true }, { false, false, true, true, true, false }, }; SearchGraph searchGraph2 = SearchGraphFromData(graph2); Dictionary<int, GraphNode> graphNodes2 = GetGraphNodesIdIndex(searchGraph2); HashSet<GraphNode> mstNodes2 = new HashSet<GraphNode> { graphNodes2[0], graphNodes2[2], graphNodes2[4] }; bool pass = false; try { MinimalSpanningTree mst = new MinimalSpanningTree(mstNodes2); mst.Span(graphNodes2[3]); } catch (DistanceLookup.GraphNotConnectedException) { pass = true; } Assert.IsTrue(pass, "No exception thrown for disconnected graph"); }
/// <summary> /// Finds the nodes in the search graph that can be potential steiner /// nodes. Those form the search space base. /// </summary> private void buildSearchSpaceBase() { searchSpaceBase = new List<GraphNode>(); MinimalSpanningTree leastSolution = new MinimalSpanningTree(targetNodes, distances); leastSolution.Span(startFrom: startNodes); int maxEdgeDistance = 0; foreach (GraphEdge edge in leastSolution.SpanningEdges) { int edgeDistance = distances.GetDistance(edge); if (edgeDistance > maxEdgeDistance) maxEdgeDistance = edgeDistance; } /* int maxTargetDistance = 0; foreach (GraphNode targetNode in targetNodes) { int targetDistance = distances.GetDistance(targetNode, startNodes); if (targetDistance > maxTargetDistance) maxTargetDistance = targetDistance; }*/ // Find potential steiner points that are in reasonable vicinity. /// TODO: This can surely be improved in some shape or form, but I /// can't figure it out right now. Since the GA also has to work well /// with larger input sizes, I won't investigate this right now. foreach (GraphNode node in searchGraph.nodeDict.Values) { // This can be a steiner node only if it has more than 2 neighbors. if (node.Adjacent.Count > 2) { /* * While this would mathematically be correct, it's not a * good criterium for the skill tree. I don't think the * relevant cases can appear and this permits way too many * nodes to be considered that will never be included in an * actual solution. * /// If every target node is closer to the start than to a certain /// steiner node, that node can't be needed for the steiner tree. bool add = false; foreach (GraphNode targetNode in targetNodes) if (distances.GetDistance(targetNode, node) < distances.GetDistance(targetNode, startNodes)) add = true; if (add) searchSpaceBase.Add(node); */ /* /// This is a pretty handwavy approach... If anybody figures /// out a case that causes this to fail, let me know please! if (distances.GetDistance(node, startNodes) < 1.2 * maxTargetDistance) searchSpaceBase.Add(node); */ // This should be a reasonable approach. bool add = false; foreach (GraphNode targetNode in targetNodes) if (distances.GetDistance(targetNode, node) < maxEdgeDistance) add = true; if (add) searchSpaceBase.Add(node); } } /* ONLY FOR WHEN NODES HAVE INDIVIDUAL WEIGHTS * foreach (ushort nodeId in targetSkillnodes) { searchSpaceBase.Add(SkillTree.Skillnodes[nodeId]); }*/ }
/// <summary> /// Converts an MST spanning a set of GraphNodes back into its equivalent /// as a HashSet of SkillNode IDs. /// </summary> /// <param name="mst">The spanned MinimalSpanningTree.</param> /// <param name="visualize">A debug parameter that highlights all used /// GraphNodes' SkillNode equivalents in the tree.</param> /// <returns>A HashSet containing the node IDs of all SkillNodes spanned /// by the MST.</returns> HashSet <ushort> SpannedMstToSkillnodes(MinimalSpanningTree mst, bool visualize) { if (!mst.IsSpanned) { throw new Exception("The passed MST is not spanned!"); } HashSet <ushort> newSkilledNodes = new HashSet <ushort>(); foreach (GraphEdge edge in mst.SpanningEdges) { ushort target = edge.outside.Id; HashSet <ushort> start; if (edge.inside is Supernode) { start = tree.SkilledNodes; } else { start = new HashSet <ushort>() { edge.inside.Id } }; var path = tree.GetShortestPathTo(target, start); newSkilledNodes = new HashSet <ushort>(newSkilledNodes.Concat(path)); } if (visualize) { tree._nodeHighlighter.UnhighlightAllNodes(NodeHighlighter.HighlightState.FromAttrib); foreach (GraphNode steinerNode in mst.mstNodes) { tree._nodeHighlighter.HighlightNode(SkillTree.Skillnodes[steinerNode.Id], NodeHighlighter.HighlightState.FromAttrib); } } //tree.DrawHighlights(tree._nodeHighlighter); return(newSkilledNodes); } MinimalSpanningTree dnaToMst(BitArray dna) { List <GraphNode> usedSteinerPoints = new List <GraphNode>(); for (int i = 0; i < dna.Length; i++) { if (dna[i]) { usedSteinerPoints.Add(searchSpaceBase[i]); } } HashSet <GraphNode> mstNodes = new HashSet <GraphNode>(usedSteinerPoints); mstNodes.Add(startNodes); foreach (GraphNode targetNode in targetNodes) { mstNodes.Add(targetNode); } return(new MinimalSpanningTree(mstNodes, distances)); } double fitnessFunction(BitArray representation) { MinimalSpanningTree mst = dnaToMst(representation); // This is the bottleneck, quite obviously. mst.Span(startFrom: startNodes); int usedNodes = mst.UsedNodeCount; // TODO: Investigate fitness function return(1500 - usedNodes); } }
/// <summary> /// Finds the nodes in the search graph that can be potential steiner /// nodes. Those form the search space base. /// </summary> private void buildSearchSpaceBase() { searchSpaceBase = new List <GraphNode>(); MinimalSpanningTree leastSolution = new MinimalSpanningTree(targetNodes, distances); leastSolution.Span(startFrom: startNodes); int maxEdgeDistance = 0; foreach (GraphEdge edge in leastSolution.SpanningEdges) { int edgeDistance = distances.GetDistance(edge); if (edgeDistance > maxEdgeDistance) { maxEdgeDistance = edgeDistance; } } /* * int maxTargetDistance = 0; * foreach (GraphNode targetNode in targetNodes) * { * int targetDistance = distances.GetDistance(targetNode, startNodes); * if (targetDistance > maxTargetDistance) * maxTargetDistance = targetDistance; * }*/ // Find potential steiner points that are in reasonable vicinity. /// TODO: This can surely be improved in some shape or form, but I /// can't figure it out right now. Since the GA also has to work well /// with larger input sizes, I won't investigate this right now. foreach (GraphNode node in searchGraph.nodeDict.Values) { // This can be a steiner node only if it has more than 2 neighbors. if (node.Adjacent.Count > 2) { /* * While this would mathematically be correct, it's not a * good criterium for the skill tree. I don't think the * relevant cases can appear and this permits way too many * nodes to be considered that will never be included in an * actual solution. * * /// If every target node is closer to the start than to a certain * /// steiner node, that node can't be needed for the steiner tree. * bool add = false; * foreach (GraphNode targetNode in targetNodes) * if (distances.GetDistance(targetNode, node) < distances.GetDistance(targetNode, startNodes)) * add = true; * if (add) * searchSpaceBase.Add(node); */ /* * /// This is a pretty handwavy approach... If anybody figures * /// out a case that causes this to fail, let me know please! * if (distances.GetDistance(node, startNodes) < 1.2 * maxTargetDistance) * searchSpaceBase.Add(node); */ // This should be a reasonable approach. bool add = false; foreach (GraphNode targetNode in targetNodes) { if (distances.GetDistance(targetNode, node) < maxEdgeDistance) { add = true; } } if (add) { searchSpaceBase.Add(node); } } } /* ONLY FOR WHEN NODES HAVE INDIVIDUAL WEIGHTS * foreach (ushort nodeId in targetSkillnodes) * { * searchSpaceBase.Add(SkillTree.Skillnodes[nodeId]); * }*/ }