/// <summary> /// Merges the node x into the fixed target node into. /// /// Edges between these nodes, edges to x and related distance information is updated. /// /// x is marked as to be removed from the search space. If x was the start node, into /// will now be the start node. /// </summary> /// <returns>All neighbors of x before merging. These are the nodes that had their adjacency /// information changed.</returns> protected IEnumerable <int> MergeInto(int x, int into) { if (!NodeStates.IsFixedTarget(into)) { throw new ArgumentException("Nodes can only be merged into fixed target nodes", "into"); } _data.DistanceLookup.IndexToNode(into).MergeWith(_data.DistanceLookup.IndexToNode(x), _data.DistanceLookup.GetShortestPath(x, into)); _data.DistanceLookup.MergeInto(x, into); EdgeSet.Remove(x, into); var intoNeighbors = EdgeSet.NeighborsOf(into); var xNeighbors = EdgeSet.NeighborsOf(x); var neighbors = intoNeighbors.Union(xNeighbors); foreach (var neighbor in xNeighbors) { EdgeSet.Remove(x, neighbor); } foreach (var neighbor in neighbors) { EdgeSet.Add(into, neighbor, _data.DistanceLookup[into, neighbor]); } if (StartNodeIndex == x) { _data.StartNodeIndex = into; } NodeStates.MarkNodeAsRemoved(x); return(xNeighbors); }
protected override int ExecuteTest() { // The test only makes sense with at least 2 terminals. if (NodeStates.FixedTargetNodeCount <= 1) { return(0); } var removedNodes = 0; var untested = new HashSet <int>(NodeStates.FixedTargetNodeIndices); // For each terminal z with degree of at least 2 while (untested.Any()) { var z = untested.First(); untested.Remove(z); var neighbors = EdgeSet.NeighborsOf(z); if (neighbors.Count < 2) { continue; } // Determine the shortest and second shortest edge incident to z. // For the second shortest, only the weight is of interest. var tuple = ShortestTwoEdgesOf(EdgeSet.EdgesOf(z)); var shortest = tuple.Item1; var secondShortestWeight = tuple.Item2; // v is the node which is connected to z via the shortest edge. var v = shortest.N1 == z ? shortest.N2 : shortest.N1; // The shortest edge belongs to at least one Steiner minimal tree, if // secondShortestWeight >= shortest.Weight + distance(v, y) for any terminal y, y != z var canBeContracted = NodeStates.FixedTargetNodeIndices .Where(y => z != y) .Any(y => secondShortestWeight >= shortest.Weight + DistanceLookup[v, y]); // If such a y exists, we can merge v into z. if (canBeContracted) { // z was changed and can be tested again. untested.Add(z); // v no longer exists and as such must not be tested. untested.Remove(v); MergeInto(v, z); removedNodes++; } } return(removedNodes); }
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); }
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); }
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); }