Beispiel #1
0
        protected override int ExecuteTest()
        {
            if (NodeStates.FixedTargetNodeCount <= 1 || NodeStates.VariableTargetNodeCount > 0)
            {
                return(0);
            }

            var removedNodes = 0;

            var mst = new MinimalSpanningTree(NodeStates.FixedTargetNodeIndices.ToList(), DistanceLookup);

            mst.Span(StartNodeIndex);
            var maxEdgeDistance = mst.SpanningEdges.Max(e => e.Priority);

            for (var i = 0; i < SearchSpaceSize; i++)
            {
                if (NodeStates.IsTarget(i) || NodeStates.IsRemoved(i))
                {
                    continue;
                }

                // Theoretically, this can be sped up by using Voronoi partitions. The Voronoi base of i is the
                // terminal with the smallest distance to i by definition, so only the distance to that terminal
                // has to be checked.
                if (NodeStates.FixedTargetNodeIndices.All(t => DistanceLookup[i, t] >= maxEdgeDistance))
                {
                    EdgeSet.EdgesOf(i).ForEach(EdgeSet.Remove);
                    NodeStates.MarkNodeAsRemoved(i);
                    removedNodes++;
                }
            }

            return(removedNodes);
        }
Beispiel #2
0
        private void RemoveNode(int index)
        {
            if (NodeStates.IsTarget(index))
            {
                throw new ArgumentException("Target nodes can't be removed", "index");
            }

            var neighbors = EdgeSet.NeighborsOf(index);

            switch (neighbors.Count)
            {
            case 0:
                break;

            case 1:
                EdgeSet.Remove(index, neighbors[0]);
                break;

            case 2:
                // Merge the two incident edges.
                var left      = neighbors[0];
                var right     = neighbors[1];
                var newWeight = EdgeSet[index, left].Weight + EdgeSet[index, right].Weight;
                EdgeSet.Remove(index, left);
                EdgeSet.Remove(index, right);
                if (newWeight <= DistanceLookup[left, right])
                {
                    EdgeSet.Add(left, right, newWeight);
                }
                break;

            default:
                throw new ArgumentException("Removing nodes with more than 2 neighbors is not supported", "index");
            }

            NodeStates.MarkNodeAsRemoved(index);
        }
        protected override int ExecuteTest()
        {
            var edges        = new GraphEdge[SearchSpaceSize];
            var removedNodes = 0;

            for (var i = 0; i < SearchSpaceSize; i++)
            {
                // This test only checks non-terminals.
                if (NodeStates.IsTarget(i))
                {
                    continue;
                }

                // Nodes with less than 3 neighbors are covered by DegreeTest
                // Nodes are limited to 7 neighbors because this test is exponential in the neighbor count.
                var neighbors = EdgeSet.NeighborsOf(i);
                if (neighbors.Count < 3 || neighbors.Count > 7)
                {
                    continue;
                }

                // Cache the edges. They might be removed from EdgeSet when we need them.
                foreach (var neighbor in neighbors)
                {
                    edges[neighbor] = EdgeSet[i, neighbor];
                }

                // Check whether each subset satisfies the condition.
                var canBeRemoved = true;
                foreach (var subset in GetAllSubsets(neighbors))
                {
                    // Only subsets of at least size 3 are relevant.
                    if (subset.Count < 3)
                    {
                        continue;
                    }

                    // Sum up the weights of all edges between the nodes of the subsets and i.
                    var edgeSum = subset.Sum(j => edges[j].Weight);
                    // Build the MST of the fully connected graph of the nodes in the subset with the bottleneck
                    // Steiner distances as edge weights.
                    var mst = new MinimalSpanningTree(subset, SMatrix);
                    mst.Span(subset[0]);
                    // Sum up the edge weights of the MST.
                    var mstSum = mst.SpanningEdges.Sum(e => e.Priority);
                    // The condition is only satisfied if edgeSum >= mstSum.
                    if (edgeSum < mstSum)
                    {
                        canBeRemoved = false;
                        break;
                    }
                }

                if (!canBeRemoved)
                {
                    continue;
                }

                // Remove i and replace its edges.
                foreach (var neighbor in neighbors)
                {
                    // Remove the old edges between i and its neighbors.
                    var edge = edges[neighbor];
                    EdgeSet.Remove(edge);
                    // For each pair of neighbors create a new edge.
                    foreach (var neighbor2 in neighbors)
                    {
                        if (neighbor >= neighbor2)
                        {
                            continue;
                        }
                        // The weight of the new edge between the two neighbors is the sum of their edge weights to i.
                        var edge2         = edges[neighbor2];
                        var newEdgeWeight = edge.Weight + edge2.Weight;
                        // Only add this edge if it wouldn't be removed by the Paths with many terminals test.
                        if (newEdgeWeight <= SMatrix[neighbor, neighbor2])
                        {
                            EdgeSet.Add(neighbor, neighbor2, newEdgeWeight);
                        }
                    }
                }

                NodeStates.MarkNodeAsRemoved(i);
                removedNodes++;
            }
            return(removedNodes);
        }
Beispiel #4
0
        protected override int ExecuteTest()
        {
            var removedNodes = 0;

            var untested       = new HashSet <int>(Enumerable.Range(0, SearchSpaceSize));
            var dependentNodes = new Dictionary <int, List <int> >();

            while (untested.Any())
            {
                var i = untested.First();
                untested.Remove(i);

                var neighbors = EdgeSet.NeighborsOf(i);

                if (!NodeStates.IsTarget(i))
                {
                    if (neighbors.Count == 1)
                    {
                        // Non target nodes with one neighbor can be removed.
                        untested.Add(neighbors[0]);
                        RemoveNode(i);
                        removedNodes++;
                    }
                    else if (neighbors.Count == 2)
                    {
                        // Non target nodes with two neighbors can be removed and their neighbors
                        // connected directly.
                        untested.Add(neighbors[0]);
                        untested.Add(neighbors[1]);
                        RemoveNode(i);
                        removedNodes++;
                    }
                }
                else if (NodeStates.IsFixedTarget(i))
                {
                    if (neighbors.Count == 1)
                    {
                        var other = neighbors[0];
                        if (EdgeSet.NeighborsOf(other).Count > 2 || NodeStates.IsTarget(other))
                        {
                            // Fixed target nodes with one neighbor can be merged with their neighbor since
                            // it must always be taken.
                            untested.Add(i);
                            untested.Remove(other);
                            untested.UnionWith(MergeInto(other, i));
                            removedNodes++;
                        }
                        else
                        {
                            // Node can only be merged once other has been processed. Other might be a dead end.
                            Debug.Assert(untested.Contains(other));
                            dependentNodes.Add(other, i);
                        }
                    }
                    else if (neighbors.Count > 1)
                    {
                        // Edges from one target node to another that are of minimum cost among the edges of
                        // one of the nodes can be in any optimal solution. Therefore both target nodes can be merged.
                        var minimumEdgeCost        = neighbors.Min(other => DistanceLookup[i, other]);
                        var minimumTargetNeighbors =
                            neighbors.Where(other => DistanceLookup[i, other] == minimumEdgeCost && NodeStates.IsFixedTarget(other));
                        foreach (var other in minimumTargetNeighbors)
                        {
                            untested.Add(i);
                            untested.Remove(other);
                            untested.UnionWith(MergeInto(other, i));
                            removedNodes++;
                        }
                    }
                }

                List <int> dependent;
                if (dependentNodes.TryGetValue(i, out dependent))
                {
                    untested.UnionWith(dependent);
                }
            }

            return(removedNodes);
        }
Beispiel #5
0
        /// <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);
        }
Beispiel #6
0
        /// <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());
        }
Beispiel #7
0
        protected override int ExecuteTest()
        {
            var removedNodes = 0;

            var untested = new HashSet <int>(Enumerable.Range(0, SearchSpaceSize));

            while (untested.Any())
            {
                var i = untested.First();
                untested.Remove(i);

                var neighbors = EdgeSet.NeighborsOf(i);

                if (!NodeStates.IsTarget(i))
                {
                    if (neighbors.Count == 1)
                    {
                        // Non target nodes with one neighbor can be removed.
                        untested.Add(neighbors[0]);
                        RemoveNode(i);
                        removedNodes++;
                    }
                    else if (neighbors.Count == 2)
                    {
                        // Non target nodes with two neighbors can be removed and their neighbors
                        // connected directly.
                        untested.Add(neighbors[0]);
                        untested.Add(neighbors[1]);
                        RemoveNode(i);
                        removedNodes++;
                    }
                }
                else if (NodeStates.IsFixedTarget(i))
                {
                    if (neighbors.Count == 1)
                    {
                        var other = neighbors[0];
                        // Fixed target nodes with one neighbor can be merged with their neighbor since
                        // it must always be taken.
                        // If the neighbor is no target and of degree 1 or 2, it will be removed by the tests above,
                        // after which this node will be processed again.
                        if (EdgeSet.NeighborsOf(other).Count <= 2 && !NodeStates.IsTarget(other))
                        {
                            continue;
                        }
                        // If this node is the only fixed target, the neighbor must not be merged because
                        //  if this is the only target, the neighbor and the rest of the tree will not be in the optimal solution,
                        //  if there are only variable target nodes, the neighbor will not be in the optimal solution if the variable nodes are not taken.
                        if (NodeStates.FixedTargetNodeCount == 1)
                        {
                            continue;
                        }
                        untested.Add(i);
                        untested.Remove(other);
                        untested.UnionWith(MergeInto(other, i));
                        removedNodes++;
                    }
                    else if (neighbors.Count > 1)
                    {
                        // Edges from one target node to another that are of minimum cost among the edges of
                        // one of the nodes can be in any optimal solution. Therefore both target nodes can be merged.
                        var minimumEdgeCost        = neighbors.Min(other => DistanceLookup[i, other]);
                        var minimumTargetNeighbors =
                            neighbors.Where(other => DistanceLookup[i, other] == minimumEdgeCost && NodeStates.IsFixedTarget(other));
                        foreach (var other in minimumTargetNeighbors)
                        {
                            untested.Add(i);
                            untested.Remove(other);
                            untested.UnionWith(MergeInto(other, i));
                            removedNodes++;
                        }
                    }
                }
            }

            return(removedNodes);
        }