def test_tree_all_pairs_lowest_common_ancestor8(self): """Raises right errors if not a tree.""" # Cycle G = nx.DiGraph() G.add_cycle((1, 2)) assert_raises(nx.NetworkXError, list, nx.tree_all_pairs_lowest_common_ancestor(G)) # DAG G = nx.DiGraph([(0, 2), (1, 2)]) assert_raises(nx.NetworkXError, list, nx.tree_all_pairs_lowest_common_ancestor(G))
def test_tree_all_pairs_lowest_common_ancestor7(self): """Works on disconnected nodes.""" G = nx.DiGraph() G.add_node(1) assert_equal({(1, 1): 1}, dict(nx.tree_all_pairs_lowest_common_ancestor(G))) G.add_node(0) assert_equal({(1, 1): 1}, dict(nx.tree_all_pairs_lowest_common_ancestor(G, 1))) assert_equal({(0, 0): 0}, dict(nx.tree_all_pairs_lowest_common_ancestor(G, 0))) assert_raises(nx.NetworkXError, list, nx.tree_all_pairs_lowest_common_ancestor(G))
def test_tree_all_pairs_lowest_common_ancestor5(self): """Handles invalid input correctly.""" empty_digraph = nx.tree_all_pairs_lowest_common_ancestor(nx.DiGraph()) assert_raises(nx.NetworkXPointlessConcept, list, empty_digraph) empty_graph = nx.tree_all_pairs_lowest_common_ancestor(nx.Graph()) assert_raises(nx.NetworkXNotImplemented, list, empty_graph) nonempty_graph = nx.tree_all_pairs_lowest_common_ancestor(self.DG.to_undirected()) assert_raises(nx.NetworkXNotImplemented, list, nonempty_graph) bad_pairs_digraph = nx.tree_all_pairs_lowest_common_ancestor(self.DG, pairs=[(-1, -2)]) assert_raises(nx.NetworkXError, list, bad_pairs_digraph)
def test_tree_all_pairs_lowest_common_ancestor3(self): """Specifying no pairs same as specifying all.""" all_pairs = itertools.chain(itertools.combinations(self.DG.nodes(), 2), ((node, node) for node in self.DG.nodes_iter())) ans = dict(nx.tree_all_pairs_lowest_common_ancestor(self.DG, 0, all_pairs)) self.assert_has_same_pairs(ans, self.ans)
def setUp(self): self.DG = nx.DiGraph() self.DG.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]) self.ans = dict(nx.tree_all_pairs_lowest_common_ancestor(self.DG, 0)) gold = dict([((n, n), n) for n in self.DG]) gold.update(dict(((0, i), 0) for i in range(1, 7))) gold.update({(1, 2): 0, (1, 3): 1, (1, 4): 1, (1, 5): 0, (1, 6): 0, (2, 3): 0, (2, 4): 0, (2, 5): 2, (2, 6): 2, (3, 4): 1, (3, 5): 0, (3, 6): 0, (4, 5): 0, (4, 6): 0, (5, 6): 2}) self.gold = gold
def weighted_bridge_augmentation(G, avail, weight=None): """Finds an approximate min-weight 2-edge-augmentation of G. This is an implementation of the approximation algorithm detailed in [1]_. It chooses a set of edges from avail to add to G that renders it 2-edge-connected if such a subset exists. This is done by finding a minimum spanning arborescence of a specially constructed metagraph. Parameters ---------- G : NetworkX graph An undirected graph. avail : set of 2 or 3 tuples. candidate edges (with optional weights) to choose from weight : string key to use to find weights if avail is a set of 3-tuples where the third item in each tuple is a dictionary. Yields ------ edge : tuple Edges in the subset of avail chosen to bridge augment G. Notes ----- Finding a weighted 2-edge-augmentation is NP-hard. Any edge not in ``avail`` is considered to have a weight of infinity. The approximation factor is 2 if ``G`` is connected and 3 if it is not. Runs in :math:`O(m + n log(n))` time References ---------- .. [1] Khuller, Samir, and Ramakrishna Thurimella. (1993) Approximation algorithms for graph augmentation. http://www.sciencedirect.com/science/article/pii/S0196677483710102 See Also -------- :func:`bridge_augmentation` :func:`k_edge_augmentation` Example ------- >>> G = nx.path_graph((1, 2, 3, 4)) >>> # When the weights are equal, (1, 4) is the best >>> avail = [(1, 4, 1), (1, 3, 1), (2, 4, 1)] >>> sorted(weighted_bridge_augmentation(G, avail)) [(1, 4)] >>> # Giving (1, 4) a high weight makes the two edge solution the best. >>> avail = [(1, 4, 1000), (1, 3, 1), (2, 4, 1)] >>> sorted(weighted_bridge_augmentation(G, avail)) [(1, 3), (2, 4)] >>> #------ >>> G = nx.path_graph((1, 2, 3, 4)) >>> G.add_node(5) >>> avail = [(1, 5, 11), (2, 5, 10), (4, 3, 1), (4, 5, 1)] >>> sorted(weighted_bridge_augmentation(G, avail=avail)) [(1, 5), (4, 5)] >>> avail = [(1, 5, 11), (2, 5, 10), (4, 3, 1), (4, 5, 51)] >>> sorted(weighted_bridge_augmentation(G, avail=avail)) [(1, 5), (2, 5), (4, 5)] """ if weight is None: weight = 'weight' # If input G is not connected the approximation factor increases to 3 if not nx.is_connected(G): H = G.copy() connectors = list(one_edge_augmentation(H, avail=avail, weight=weight)) H.add_edges_from(connectors) for edge in connectors: yield edge else: connectors = [] H = G if len(avail) == 0: if nx.has_bridges(H): raise nx.NetworkXUnfeasible('no augmentation possible') avail_uv, avail_w = _unpack_available_edges(avail, weight=weight, G=H) # Collapse input into a metagraph. Meta nodes are bridge-ccs bridge_ccs = nx.connectivity.bridge_components(H) C = collapse(H, bridge_ccs) # Use the meta graph to shrink avail to a small feasible subset mapping = C.graph['mapping'] # Choose the minimum weight feasible edge in each group meta_to_wuv = { (mu, mv): (w, uv) for (mu, mv), uv, w in _lightest_meta_edges(mapping, avail_uv, avail_w) } # Mapping of terms from (Khuller and Thurimella): # C : G_0 = (V, E^0) # This is the metagraph where each node is a 2-edge-cc in G. # The edges in C represent bridges in the original graph. # (mu, mv) : E - E^0 # they group both avail and given edges in E # T : \Gamma # D : G^D = (V, E_D) # The paper uses ancestor because children point to parents, which is # contrary to networkx standards. So, we actually need to run # nx.least_common_ancestor on the reversed Tree. # Pick an arbitrary leaf from C as the root root = next(n for n in C.nodes() if C.degree(n) == 1) # Root C into a tree TR by directing all edges away from the root # Note in their paper T directs edges towards the root TR = nx.dfs_tree(C, root) # Add to D the directed edges of T and set their weight to zero # This indicates that it costs nothing to use edges that were given. D = nx.reverse(TR).copy() nx.set_edge_attributes(D, name='weight', values=0) # The LCA of mu and mv in T is the shared ancestor of mu and mv that is # located farthest from the root. lca_gen = nx.tree_all_pairs_lowest_common_ancestor( TR, root=root, pairs=meta_to_wuv.keys()) for (mu, mv), lca in lca_gen: w, uv = meta_to_wuv[(mu, mv)] if lca == mu: # If u is an ancestor of v in TR, then add edge u->v to D D.add_edge(lca, mv, weight=w, generator=uv) elif lca == mv: # If v is an ancestor of u in TR, then add edge v->u to D D.add_edge(lca, mu, weight=w, generator=uv) else: # If neither u nor v is a ancestor of the other in TR # let t = lca(TR, u, v) and add edges t->u and t->v # Track the original edge that GENERATED these edges. D.add_edge(lca, mu, weight=w, generator=uv) D.add_edge(lca, mv, weight=w, generator=uv) # Then compute a minimum rooted branching try: # Note the original edges must be directed towards to root for the # branching to give us a bridge-augmentation. A = _minimum_rooted_branching(D, root) except nx.NetworkXException: # If there is no branching then augmentation is not possible raise nx.NetworkXUnfeasible('no 2-edge-augmentation possible') # For each edge e, in the branching that did not belong to the directed # tree T, add the correponding edge that **GENERATED** it (this is not # necesarilly e itself!) # ensure the third case does not generate edges twice bridge_connectors = set() for mu, mv in A.edges(): data = D.get_edge_data(mu, mv) if 'generator' in data: # Add the avail edge that generated the branching edge. edge = data['generator'] bridge_connectors.add(edge) for edge in bridge_connectors: yield edge
def test_tree_all_pairs_lowest_common_ancestor6(self): """Works on subtrees.""" ans = dict(nx.tree_all_pairs_lowest_common_ancestor(self.DG, 1)) gold = dict(((pair, lca) for (pair, lca) in self.gold.iteritems() if all((n in (1, 3, 4) for n in pair)))) self.assert_has_same_pairs(gold, ans)
def test_tree_all_pairs_lowest_common_ancestor2(self): """Specifying only some pairs gives only those pairs.""" some_pairs = dict(nx.tree_all_pairs_lowest_common_ancestor(self.DG, 0, [(0, 1), (0, 1), (1, 0)])) assert_true((0, 1) in some_pairs or (1, 0) in some_pairs) assert_equal(len(some_pairs), 1)
def test_tree_all_pairs_lowest_common_ancestor1(self): """Specifying the root is optional.""" assert_equal(self.ans, dict(nx.tree_all_pairs_lowest_common_ancestor(self.DG)))
def __init__(self, T, T_label_to_node, T_node_to_labels): self.LCA_dict = dict() self.LCA_label_dict = T_label_to_node self.LCA_node2lbl = T_node_to_labels for lca in nx.tree_all_pairs_lowest_common_ancestor(T): self.LCA_dict[(lca[0][0], lca[0][1])] = lca[1]