/// <summary> /// Reduces the search space by removing nodes, removing edges and merging nodes. /// </summary> /// <returns>The nodes contained in the reduced search space.</returns> public IReadOnlyList <GraphNode> ReduceSearchSpace() { // Remove all non target nodes with 2 or less edges. (Steiner nodes always have more than 2 edges) var nodeCountBefore = _nodeStates.SearchSpaceSize; _nodeStates.SearchSpace = _nodeStates.SearchSpace.Where(n => n.Adjacent.Count > 2 || _nodeStates.IsTarget(n)).ToList(); Debug.WriteLine("First basic degree reduction:\n" + " removed nodes: " + (nodeCountBefore - _nodeStates.SearchSpaceSize) + " (of " + nodeCountBefore + " initial nodes)"); // Initialise the distance lookup. _data.DistanceLookup = new DistanceLookup(_nodeStates.SearchSpace); var distanceLookup = _data.DistanceLookup; // Distance indices were not set before. _nodeStates.ComputeFields(); // If a fixed target node is not connected to the start node, there obviously is no solution at all. if (_nodeStates.FixedTargetNodeIndices.Any(i => !distanceLookup.AreConnected(i, _data.StartNodeIndex))) { throw new GraphNotConnectedException(); } nodeCountBefore = _nodeStates.SearchSpaceSize; // Remove all unconnected nodes. _nodeStates.SearchSpace = distanceLookup.RemoveNodes(_nodeStates.SearchSpace.Where(n => !distanceLookup.AreConnected(n, StartNode))); Debug.WriteLine("Removed unconnected nodes:"); Debug.WriteLine(" removed nodes: " + (nodeCountBefore - _nodeStates.SearchSpaceSize)); // Even the basic degree reductions hurt the AdvancedSolver's performance. // Reenabling should be tested when other algorithm parts are changed/improved. // todo experiment with reductions and AdvancedSolver if (_nodeStates.VariableTargetNodeCount > 0) { return(_nodeStates.SearchSpace); } // Initialise node states and edges. Edges are calculated from the information in the // GraphNode. Adjacency information from GraphNode instances must not be used after this. _data.EdgeSet = ComputeEdges(); var initialNodeCount = _nodeStates.SearchSpaceSize; var initialEdgeCount = _data.EdgeSet.Count; Debug.WriteLine("Initial counts:\n" + " nodes: " + initialNodeCount + "\n" + " non targets: " + (_nodeStates.SearchSpaceSize - _nodeStates.TargetNodeCount) + "\n" + " variable targets: " + _nodeStates.VariableTargetNodeCount + "\n" + " fixed targets: " + _nodeStates.FixedTargetNodeCount + "\n" + " edges: " + initialEdgeCount); // Execute an initial DegreeTest. var degreeTest = new DegreeTest(_nodeStates, _data); var dummy = 0; degreeTest.RunTest(ref dummy, ref dummy); // Update _searchSpace, _edgeSet and _distanceLookup ContractSearchSpace(); // These values may become lower by merging nodes. Since the reductions based on these distance // don't help if there are many variable target nodes, it is not really worth it to always recalculate them. // It would either slow the preprocessing by like 30% or would need an approximation algorithm. _data.SMatrix = new BottleneckSteinerDistanceCalculator(distanceLookup).CalcBottleneckSteinerDistances(_nodeStates.FixedTargetNodeIndices); // The set of reduction test that are run until they are no longer able to reduce the search space. var tests = new List <SteinerReduction> { new FarAwayNonTerminalsTest(_nodeStates, _data), new PathsWithManyTerminalsTest(_nodeStates, _data), new NonTerminalsOfDegreeKTest(_nodeStates, _data), new NearestVertexTest(_nodeStates, _data) }; // Run every reduction test (each followed by a simple degree reduction test) until they are no longer able // to reduce the search space or 10 reduction rounds were executed. // (the 10 round limit is never actually reached from what I've seen) for (int i = 0; i < 10; i++) { var edgeElims = 0; var nodeElims = 0; foreach (var test in tests.Where(t => t.IsEnabled)) { if (!test.RunTest(ref edgeElims, ref nodeElims)) { test.IsEnabled = false; } degreeTest.RunTest(ref edgeElims, ref nodeElims); } Debug.WriteLine("Eliminations in round {0}:", i + 1); Debug.WriteLine(" removed nodes: " + nodeElims); Debug.WriteLine(" removed edges: " + edgeElims); // No test should be still enabled in this case so we can stop running them. if (edgeElims == 0) { break; } } // Calculate the final search space. ContractSearchSpace(); Debug.WriteLine("Final counts:\n" + " nodes: " + _nodeStates.SearchSpaceSize + " (of " + initialNodeCount + " after basic reductions)\n" + " non targets: " + (_nodeStates.SearchSpaceSize - _nodeStates.TargetNodeCount) + "\n" + " variable targets: " + _nodeStates.VariableTargetNodeCount + "\n" + " fixed targets: " + _nodeStates.FixedTargetNodeCount + "\n" + " edges: " + _data.EdgeSet.Count + " (of " + initialEdgeCount + " after basic reductions)"); return(_nodeStates.SearchSpace); }
/// <summary> /// Reduces the search space by removing nodes, removing edges and merging nodes. /// </summary> /// <returns>The nodes contained in the reduced search space.</returns> public SteinerPreprocessorResult ReduceSearchSpace() { // Remove all non target nodes with 2 or less edges. (Steiner nodes always have more than 2 edges) var nodeCountBefore = _nodeStates.SearchSpaceSize; _nodeStates.SearchSpace = _nodeStates.SearchSpace.Where(n => n.Adjacent.Count > 2 || _nodeStates.IsTarget(n)).ToList(); Debug.WriteLine("First basic degree reduction:\n" + " removed nodes: " + (nodeCountBefore - _nodeStates.SearchSpaceSize) + " (of " + nodeCountBefore + " initial nodes)"); // Initialize the distance lookup. _data.DistanceCalculator = new DistanceCalculator(_nodeStates.SearchSpace); var distanceLookup = _data.DistanceCalculator; #if PoESkillTree_EnableAlternativeFixedTargetNodeIndices foreach (var node in distanceLookup._nodes.Where(n => _nodeStates.TargetIds.Contains(n.Id)))//Update NodeIndexes { if (node.DistancesIndex == -1) #if DEBUG { Debug.WriteLine("Failed to index distance calculations for node:" + node.Id); #endif { _nodeStates.FixedTargetKeyedIndices.Remove(node.Id); //Remove from Dictionary if node has invalid index } #if DEBUG } #endif else { _nodeStates.FixedTargetKeyedIndices.AddOrUpdate(node.Id, node.DistancesIndex); } } #endif // Distance indices are set in ComputeFields() if PoESkillTree_DisableAlternativeFixedTargetNodeIndices is enabled.(old variation of the code otherwise Distance indice is updated in ReduceSearchSpace()) _nodeStates.ComputeFields(); // If a fixed target node is not connected to the start node, there obviously is no solution at all. if (_nodeStates.FixedTargetNodeIndices.Any(i => #if PoESkillTree_UseShortDistanceIndex _data.StartNodeIndex != null && #endif !distanceLookup.AreConnectedById(i, #if PoESkillTree_UseShortDistanceIndex (ushort) #endif _data.StartNodeIndex))) { throw new GraphNotConnectedException(); } nodeCountBefore = _nodeStates.SearchSpaceSize; // Remove all unconnected nodes. _nodeStates.SearchSpace = distanceLookup.RemoveNodes(_nodeStates.SearchSpace.Where(n => !distanceLookup.AreConnected(n, _data.StartNode))); Debug.WriteLine("Removed unconnected nodes:"); Debug.WriteLine(" removed nodes: " + (nodeCountBefore - _nodeStates.SearchSpaceSize)); // Even the basic degree reductions hurt the AdvancedSolver's performance. // Reenabling should be tested when other algorithm parts are changed/improved. // todo experiment with reductions and AdvancedSolver if (_nodeStates.VariableTargetNodeCount > 0) { return(CreateResult()); } // Initialize node states and edges. Edges are calculated from the information in the // GraphNode. Adjacency information from GraphNode instances must not be used after this. _data.EdgeSet = ComputeEdges(); var initialNodeCount = _nodeStates.SearchSpaceSize; var initialEdgeCount = _data.EdgeSet.Count; Debug.WriteLine("Initial counts:\n" + " nodes: " + initialNodeCount + "\n" + " non targets: " + (_nodeStates.SearchSpaceSize - _nodeStates.TargetNodeCount) + "\n" + " variable targets: " + _nodeStates.VariableTargetNodeCount + "\n" + " fixed targets: " + _nodeStates.FixedTargetNodeCount + "\n" + " edges: " + initialEdgeCount); // Execute an initial DegreeTest. var degreeTest = new DegreeTest(_nodeStates, _data); var dummy = 0; degreeTest.RunTest(ref dummy, ref dummy); // Update _searchSpace, _edgeSet and _distanceLookup ContractSearchSpace();//Potential bug here as Cache size decreases if distance indices not updated // These values may become lower by merging nodes. Since the reductions based on these distance // don't help if there are many variable target nodes, it is not really worth it to always recalculate them. // It would either slow the preprocessing by like 30% or would need an approximation algorithm. _data.SMatrix = new BottleneckSteinerDistanceCalculator(distanceLookup.DistanceLookup) .CalcBottleneckSteinerDistances(_nodeStates.FixedTargetNodeIndices); // The set of reduction test that are run until they are no longer able to reduce the search space. var tests = new List <SteinerReduction> { new FarAwayNonTerminalsTest(_nodeStates, _data), new PathsWithManyTerminalsTest(_nodeStates, _data), new NonTerminalsOfDegreeKTest(_nodeStates, _data), new NearestVertexTest(_nodeStates, _data) }; // Run every reduction test (each followed by a simple degree reduction test) until they are no longer able // to reduce the search space or 10 reduction rounds were executed. // (the 10 round limit is never actually reached from what I've seen) for (int i = 1; i < 11; ++i) { var edgeElims = 0; var nodeElims = 0; foreach (var test in tests.Where(t => t.IsEnabled)) { if (!test.RunTest(ref edgeElims, ref nodeElims)) { test.IsEnabled = false; } degreeTest.RunTest(ref edgeElims, ref nodeElims); } Debug.WriteLine("Eliminations in round {0}:", i); Debug.WriteLine(" removed nodes: " + nodeElims); Debug.WriteLine(" removed edges: " + edgeElims); // No test should be still enabled in this case so we can stop running them. if (edgeElims == 0) { break; } } // Calculate the final search space. ContractSearchSpace(); Debug.WriteLine("Final counts:\n" + " nodes: " + _nodeStates.SearchSpaceSize + " (of " + initialNodeCount + " after basic reductions)\n" + " non targets: " + (_nodeStates.SearchSpaceSize - _nodeStates.TargetNodeCount) + "\n" + " variable targets: " + _nodeStates.VariableTargetNodeCount + "\n" + " fixed targets: " + _nodeStates.FixedTargetNodeCount + "\n" + " edges: " + _data.EdgeSet.Count + " (of " + initialEdgeCount + " after basic reductions)"); return(CreateResult()); }