/// <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);
        }
Exemple #2
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());
        }