def sequential_subgraph_nodes(g: nx.DiGraph, size: int) -> List[List[Union[str, int]]]: if not nx.is_weakly_connected(g): raise nx.NetworkXUnfeasible( "sequential solutions are not possible for disconnected graphs.") if size <= 1: raise nx.NetworkXUnfeasible( "the minimum directed subgraph length is 2 nodes.") g = nx.DiGraph(g.edges()) # make a copy because we'll modify the structure graphs = [] while len(g.nodes()) > 1: sg = find_leafy_branch_larger_than_size(g, size) sg_nodes = list(nx.lexicographical_topological_sort(sg)) graphs.append(sg_nodes) # trim the upstream nodes out of the graph, except the upstream root us_nodes = [n for n, deg in sg.out_degree if deg > 0] g = g.subgraph([n for n in g.nodes() if n not in us_nodes]) # rinse and repeat until there's one or fewer nodes left in the graph return graphs
def maximal_independent_set(G, nodes=None): if not nodes: nodes = set([random.choice(G.nodes())]) # pick a random node else: nodes = set(nodes) if not nodes.issubset(G): raise nx.NetworkXUnfeasible("%s is not a subset of the nodes of G" % nodes) # All neighbors of nodes neighbors = set.union(*[set(G.neighbors(v)) for v in nodes]) if set.intersection(neighbors, nodes): raise nx.NetworkXUnfeasible("%s is not an independent set of G" % nodes) indep_nodes = list(nodes) # initial available_nodes = set(G.nodes()).difference(neighbors.union( nodes)) # available_nodes = all nodes - (nodes + nodes' neighbors) while available_nodes: # pick a random node from the available nodes node = random.choice(list(available_nodes)) indep_nodes.append(node) available_nodes.difference_update(G.neighbors(node) + [ node ]) # available_nodes = available_nodes - (node + node's neighbors) return indep_nodes
def maximal_independent_set(G, nodes=None): """Return a random maximal independent set guaranteed to contain a given set of nodes. An independent set is a set of nodes such that the subgraph of G induced by these nodes contains no edges. A maximal independent set is an independent set such that it is not possible to add a new node and still get an independent set. Parameters ---------- G : NetworkX graph nodes : list or iterable Nodes that must be part of the independent set. This set of nodes must be independent. Returns ------- indep_nodes : list List of nodes that are part of a maximal independent set. Raises ------ NetworkXUnfeasible If the nodes in the provided list are not part of the graph or do not form an independent set, an exception is raised. Examples -------- >>> G = nx.path_graph(5) >>> nx.maximal_independent_set(G) # doctest: +SKIP [4, 0, 2] >>> nx.maximal_independent_set(G, [1]) # doctest: +SKIP [1, 3] Notes ----- This algorithm does not solve the maximum independent set problem. """ if not nodes: nodes = set([random.choice(list(G))]) else: nodes = set(nodes) if not nodes.issubset(G): raise nx.NetworkXUnfeasible("%s is not a subset of the nodes of G" % nodes) neighbors = set.union(*[set(G.neighbors(v)) for v in nodes]) if set.intersection(neighbors, nodes): raise nx.NetworkXUnfeasible("%s is not an independent set of G" % nodes) indep_nodes = list(nodes) available_nodes = set(G.nodes()).difference(neighbors.union(nodes)) while available_nodes: node = random.choice(list(available_nodes)) indep_nodes.append(node) available_nodes.difference_update(list(G.neighbors(node)) + [node]) return indep_nodes
def topological_sort(G, nbunch): # nonrecursive version seen = set() order = [] explored = set() if nbunch is None: nbunch = G.nodes() for v in nbunch: # process all vertices in G if v in explored: continue fringe = [v] # nodes yet to look at while fringe: w = fringe[-1] # depth first search if w in explored: # already looked down this branch fringe.pop() continue seen.add(w) # mark as seen # Check successors for cycles and for new nodes new_nodes = [] for n in G[w]: if n not in explored: if n in seen: # CYCLE !! raise nx.NetworkXUnfeasible( "Graph contains a cycle at %s." % n) new_nodes.append(n) if new_nodes: # Add new_nodes to fringe fringe.extend(new_nodes) else: # No new nodes so w is fully explored explored.add(w) order.append(w) fringe.pop() # done considering this node return list(reversed(order))
def inner_sort(candidates, dep_counts): best_groups = [] best_score = (inf, inf) for type in candidates: next_candidates = deepcopy(candidates) next_dep_counts = deepcopy(dep_counts) next_group = next_candidates.pop(type) for node in next_group: for _, child in deps.edges(node): next_dep_counts[child] -= 1 if next_dep_counts[child] == 0: child_type = deps.nodes[child]['group'] next_candidates.setdefault(child_type, []).append(child) del next_dep_counts[child] remaining_groups = inner_sort( next_candidates, next_dep_counts, ) score = len(remaining_groups), type if score < best_score: best_score = score best_groups = [ (type, sorted(next_group, key=by_order)), *remaining_groups, ] if dep_counts and not candidates: raise nx.NetworkXUnfeasible("graph contains a cycle") return best_groups
def _relabel_inplace(G, mapping): old_labels = set(mapping.keys()) new_labels = set(mapping.values()) if len(old_labels & new_labels) > 0: # labels sets overlap # can we topological sort and still do the relabeling? D = nx.DiGraph(list(mapping.items())) D.remove_edges_from(nx.selfloop_edges(D)) try: nodes = reversed(list(nx.topological_sort(D))) except nx.NetworkXUnfeasible as err: raise nx.NetworkXUnfeasible( "The node label sets are overlapping and no ordering can " "resolve the mapping. Use copy=True.") from err else: # non-overlapping label sets nodes = old_labels multigraph = G.is_multigraph() directed = G.is_directed() for old in nodes: # Test that old is in both mapping and G, otherwise ignore. try: new = mapping[old] G.add_node(new, **G.nodes[old]) except KeyError: continue if new == old: continue if multigraph: new_edges = [(new, new if old == target else target, key, data) for (_, target, key, data) in G.edges(old, data=True, keys=True)] if directed: new_edges += [ (new if old == source else source, new, key, data) for (source, _, key, data) in G.in_edges(old, data=True, keys=True) ] # Ensure new edges won't overwrite existing ones seen = set() for i, (source, target, key, data) in enumerate(new_edges): if target in G[source] and key in G[source][target]: new_key = 0 if not isinstance(key, (int, float)) else key while new_key in G[source][target] or (target, new_key) in seen: new_key += 1 new_edges[i] = (source, target, new_key, data) seen.add((target, new_key)) else: new_edges = [(new, new if old == target else target, data) for (_, target, data) in G.edges(old, data=True)] if directed: new_edges += [(new if old == source else source, new, data) for (source, _, data) in G.in_edges(old, data=True)] G.remove_node(old) G.add_edges_from(new_edges) return G
def deterministic_topological_sort(dag): # This function is stolen from networkx.algorithms.dag.topological_sort # made the returned order deterministic by pre-sorting the nodes by their ID seen = set() order = [] explored = set() nbunch = sorted(dag.nodes_iter()) # <-- SORTED for v in nbunch: # process all vertices in G if v in explored: continue fringe = [v] # nodes yet to look at while fringe: w = fringe[-1] # depth first search if w in explored: # already looked down this branch fringe.pop() continue seen.add(w) # mark as seen # Check successors for cycles and for new nodes new_nodes = [] for n in sorted(six.iterkeys(dag[w])): # <-- SORTED if n not in explored: if n in seen: #CYCLE !! raise nx.NetworkXUnfeasible("Graph contains a cycle.") new_nodes.append(n) if new_nodes: # Add new_nodes to fringe fringe.extend(new_nodes) else: # No new nodes so w is fully explored explored.add(w) order.append(w) fringe.pop() # done considering this node return list(reversed(order))
def _topo_sort_nodes(dag) -> iset: """ Topo-sort dag by execution order & operation-insertion order to break ties. This means (probably!?) that the first inserted win the `needs`, but the last one win the `provides` (and the final solution). Inform user in case of cycles. """ node_keys = dict(zip(dag.nodes, count())) try: return iset(nx.lexicographical_topological_sort(dag, key=node_keys.get)) except nx.NetworkXUnfeasible as ex: import sys from textwrap import dedent tb = sys.exc_info()[2] msg = dedent(f""" {ex} TIP: Launch a post-mortem debugger, move 3 frames UP, and plot the `graphtik.planning.Network' class in `self` to discover the cycle. If GRAPHTIK_DEBUG enabled, this plot will be stored tmp-folder automatically :-) """) raise nx.NetworkXUnfeasible(msg).with_traceback(tb)
def flow_with_demands(graph): """Computes a flow with demands over the given graph. Args: graph: A directed graph with nodes annotated with 'demand' properties and edges annotated with 'capacity' properties. Returns: A dict of dicts containing the flow on each edge. For instance, flow[s1][s2] should provide the flow along edge (s1, s2). Raises: NetworkXUnfeasible: An error is thrown if there is no flow satisfying the demands. """ # TODO: Implement the function. def reduction_to_max_flow(graph): graph.add_node('s') graph.add_node('t') graph.node['s']['demand'] = 0 graph.node['t']['demand'] = 0 for state in graph.nodes(): if state != 's' and state != 't': if graph.node[state]['demand'] < 0: graph.add_edge('s', state) graph.edge['s'][state][ 'capacity'] = -graph.node[state]['demand'] graph.node['s']['demand'] += graph.node[state]['demand'] elif graph.node[state]['demand'] > 0: graph.add_edge(state, 't') graph.edge[state]['t']['capacity'] = graph.node[state][ 'demand'] graph.node['t']['demand'] += graph.node[state]['demand'] return graph graph = reduction_to_max_flow(graph) R = nx.algorithms.flow.ford_fulkerson(graph, 's', 't') flow_dict = R.graph['flow_dict'] flow_value = R.graph['flow_value'] if flow_value != graph.node['t']['demand']: raise nx.NetworkXUnfeasible("No flow satisfying the demand") for s1 in flow_dict.keys(): for s2 in flow_dict[s1].keys(): if s2 == 't': flow_dict[s1].pop('t', None) break for s1 in flow_dict.keys(): if s1 == 's': flow_dict.pop('s', None) elif s1 == 't': flow_dict.pop('t', None) graph.remove_node('s') graph.remove_node('t') return flow_dict
def topsort(G: ssp.spmatrix, nodes=None, reverse=False): order = [] seen = set() explored = set() if nodes is None: nodes = range(G.shape[0]) for v in nodes: # process all vertices in G if v in explored: continue fringe = [v] # nodes yet to look at while fringe: w = fringe[-1] # depth first search if w in explored: # already looked down this branch fringe.pop() continue seen.add(w) # mark as seen # Check successors for cycles and for new nodes new_nodes = [] for n in G[w].nonzero()[1]: if n not in explored: if n in seen: # CYCLE !! raise nx.NetworkXUnfeasible("Graph contains a cycle.") new_nodes.append(n) if new_nodes: # Add new_nodes to fringe fringe.extend(new_nodes) else: # No new nodes so w is fully explored explored.add(w) order.append(w) fringe.pop() # done considering this node if reverse: return order else: return list(reversed(order))
def phase3(self): # build potential remaining edges and choose with rejection sampling potential_edges = combinations(self.remaining_degree, 2) # build auxilliary graph of potential edges not already in graph # t = [] # for (u, v) in potential_edges: # if not self.graph.has_edge(u, v): # t.append((u, v)) # print t # exit(0) H = nx.Graph([(u, v) for (u, v) in potential_edges if not self.graph.has_edge(u, v)]) # nx.draw_graphviz(H, with_labels=True, node_size=800, alpha=0.5) # plt.show() # exit(0) while self.remaining_degree: if not self.suitable_edge(): raise nx.NetworkXUnfeasible('no suitable edges left') while True: u, v = sorted(random.choice(H.edges())) if random.random() < self.q(u, v): break if random.random() < self.p(u, v): # accept edge self.graph.add_edge(u, v) self.update_remaining(u, v, aux_graph=H)
def _relabel_inplace(G, mapping): old_labels = set(mapping.keys()) new_labels = set(mapping.values()) if len(old_labels & new_labels) > 0: # labels sets overlap # can we topological sort and still do the relabeling? D = nx.DiGraph(list(mapping.items())) D.remove_edges_from(nx.selfloop_edges(D)) try: nodes = reversed(list(nx.topological_sort(D))) except nx.NetworkXUnfeasible as e: raise nx.NetworkXUnfeasible( "The node label sets are overlapping and no ordering can " "resolve the mapping. Use copy=True." ) from e else: # non-overlapping label sets nodes = old_labels multigraph = G.is_multigraph() directed = G.is_directed() for old in nodes: try: new = mapping[old] except KeyError: continue if new == old: continue try: G.add_node(new, **G.nodes[old]) except KeyError as e: raise KeyError(f"Node {old} is not in the graph") from e if multigraph: new_edges = [ (new, new if old == target else target, key, data) for (_, target, key, data) in G.edges(old, data=True, keys=True) ] if directed: new_edges += [ (new if old == source else source, new, key, data) for (source, _, key, data) in G.in_edges(old, data=True, keys=True) ] else: new_edges = [ (new, new if old == target else target, data) for (_, target, data) in G.edges(old, data=True) ] if directed: new_edges += [ (new if old == source else source, new, data) for (source, _, data) in G.in_edges(old, data=True) ] G.remove_node(old) G.add_edges_from(new_edges) return G
def __init__(self, degree, rng): if not nx.is_graphical(degree): raise nx.NetworkXUnfeasible("degree sequence is not graphical") self.rng = rng self.degree = list(degree) # node labels are integers 0,...,n-1 self.m = sum(self.degree) / 2.0 # number of edges try: self.dmax = max(self.degree) # maximum degree except ValueError: self.dmax = 0
def _dfs(G, seen, explored, v): seen.add(v) for w in G[v]: if w not in seen: if not _dfs(G, seen, explored, w): return elif w in seen and w not in explored: # cycle Found--- no topological sort raise nx.NetworkXUnfeasible("Graph contains a cycle.") explored.insert(0, v) # inverse order of when explored return v
def __init__(self, degree, seed=None): if not nx.is_valid_degree_sequence(degree): raise nx.NetworkXUnfeasible('degree sequence is not graphical') if seed is not None: random.seed(seed) self.degree = list(degree) # node labels are integers 0,...,n-1 self.m = sum(self.degree)/2.0 # number of edges try: self.dmax = max(self.degree) # maximum degree except ValueError: self.dmax = 0
def _build_residual_network(G, demand, capacity, weight): """Build a residual network and initialize a zero flow. """ if sum(G.nodes[u].get(demand, 0) for u in G) != 0: raise nx.NetworkXUnfeasible("Sum of the demands should be 0.") R = nx.MultiDiGraph() R.add_nodes_from((u, { "excess": -G.nodes[u].get(demand, 0), "potential": 0 }) for u in G) inf = float("inf") # Detect selfloops with infinite capacities and negative weights. for u, v, e in nx.selfloop_edges(G, data=True): if e.get(weight, 0) < 0 and e.get(capacity, inf) == inf: raise nx.NetworkXUnbounded( "Negative cost cycle of infinite capacity found. " "Min cost flow may be unbounded below.") # Extract edges with positive capacities. Self loops excluded. if G.is_multigraph(): edge_list = [(u, v, k, e) for u, v, k, e in G.edges(data=True, keys=True) if u != v and e.get(capacity, inf) > 0] else: edge_list = [(u, v, 0, e) for u, v, e in G.edges(data=True) if u != v and e.get(capacity, inf) > 0] # Simulate infinity with the larger of the sum of absolute node imbalances # the sum of finite edge capacities or any positive value if both sums are # zero. This allows the infinite-capacity edges to be distinguished for # unboundedness detection and directly participate in residual capacity # calculation. inf = (max( sum(abs(R.nodes[u]["excess"]) for u in R), 2 * sum(e[capacity] for u, v, k, e in edge_list if capacity in e and e[capacity] != inf), ) or 1) for u, v, k, e in edge_list: r = min(e.get(capacity, inf), inf) w = e.get(weight, 0) # Add both (u, v) and (v, u) into the residual network marked with the # original key. (key[1] == True) indicates the (u, v) is in the # original network. R.add_edge(u, v, key=(k, True), capacity=r, weight=w, flow=0) R.add_edge(v, u, key=(k, False), capacity=0, weight=-w, flow=0) # Record the value simulating infinity. R.graph["inf"] = inf _detect_unboundedness(R) return R
def _dfs(v): ancestors.add(v) for w in G[v]: if w in ancestors: raise nx.NetworkXUnfeasible("Graph contains a cycle.") if w not in explored: _dfs(w) ancestors.remove(v) explored.add(v) order.append(v)
def _relabel_inplace(G, mapping): old_labels = set(mapping.keys()) new_labels = set(mapping.values()) if len(old_labels & new_labels) > 0: # labels sets overlap # can we topological sort and still do the relabeling? D = nx.DiGraph(list(mapping.items())) D.remove_edges_from(D.selfloop_edges()) try: nodes = nx.topological_sort(D) except nx.NetworkXUnfeasible: raise nx.NetworkXUnfeasible('The node label sets are overlapping ' 'and no ordering can resolve the ' 'mapping. Use copy=True.') nodes.reverse() # reverse topological order else: # non-overlapping label sets nodes = old_labels multigraph = G.is_multigraph() directed = G.is_directed() for old in nodes: try: new = mapping[old] except KeyError: continue try: G.add_node(new, attr_dict=G.node[old]) except KeyError: raise KeyError("Node %s is not in the graph" % old) if multigraph: new_edges = [(new, old == target and new or target, key, data) for (_, target, key, data) in G.edges(old, data=True, keys=True)] if directed: new_edges += [ (old == source and new or source, new, key, data) for (source, _, key, data) in G.in_edges(old, data=True, keys=True) ] else: new_edges = [(new, old == target and new or target, data) for (_, target, data) in G.edges(old, data=True)] if directed: new_edges += [(old == source and new or source, new, data) for (source, _, data) in G.in_edges(old, data=True)] G.remove_node(old) G.add_edges_from(new_edges) return G
def phase_all(self): # choose node pairs from (degree) weighted distribution self.aux_range = range(len(self.stublist)) while self.stublist: if not self.suitable_edge(): raise nx.NetworkXUnfeasible('no suitable edges left') # sampling while avoiding self loop u, v = sorted(random.sample(self.aux_range, 2)) # avoid multiple edges if self.graph.has_edge(self.stublist[u], self.stublist[v]): continue if random.random() < self.p(self.stublist[u], self.stublist[v]): # accept edge self.graph.add_edge(self.stublist[u], self.stublist[v]) self.update_stublist(u, v)
def bridge_augmentation(G, avail=None, weight=None): """Finds the a set of edges that bridge connects G. Equivalent to :func:`k_edge_augmentation` when k=2, and partial=False. Adding the resulting edges to G will make it 2-edge-connected. If no constraints are specified the returned set of edges is minimum an optimal, otherwise the solution is approximated. Parameters ---------- G : NetworkX graph An undirected graph. avail : dict or a set of 2 or 3 tuples For more details, see :func:`k_edge_augmentation`. weight : string key to use to find weights if ``avail`` is a set of 3-tuples. For more details, see :func:`k_edge_augmentation`. Yields ------ edge : tuple Edges in the bridge-augmentation of G Raises ------ NetworkXUnfeasible: If no bridge-augmentation exists. Notes ----- If there are no constraints the solution can be computed in linear time using :func:`unconstrained_bridge_augmentation`. Otherwise, the problem becomes NP-hard and is the solution is approximated by :func:`weighted_bridge_augmentation`. See Also -------- :func:`k_edge_augmentation` """ if G.number_of_nodes() < 3: raise nx.NetworkXUnfeasible( 'impossible to bridge connect less than 3 nodes') if avail is None: return unconstrained_bridge_augmentation(G) else: return weighted_bridge_augmentation(G, avail, weight=weight)
def phase3(self): # build potential remaining edges and choose with rejection sampling potential_edges = combinations(self.remaining_degree, 2) # build auxilliary graph of potential edges not already in graph H = nx.Graph([(u,v) for (u,v) in potential_edges if not self.graph.has_edge(u,v)]) while self.remaining_degree: if not self.suitable_edge(): raise nx.NetworkXUnfeasible('no suitable edges left') while True: u,v = sorted(random.choice(list(H.edges()))) if random.random() < self.q(u,v): break if random.random() < self.p(u,v): # accept edge self.graph.add_edge(u,v) self.update_remaining(u,v, aux_graph=H)
def bernstein(self, a, b): """Check whether Bernstein's conditions for parallelizability of pairwise sequential actions hold for nodes a and b. The conditions are: out(a) ∩ in(b) = ∅ in(a) ∩ out(b) = ∅ out(a) ∩ out(b) = ∅ Violation of the first condition introduces a flow dependency, corresponding to the first segment producing a result used by the second segment. The second condition represents an anti-dependency, when the second segment produces a variable needed by the first segment. The third and final condition represents an output dependency: when two segments write to the same location, the result comes from the logically last executed segment. Bernstein, Arthur J. "Analysis of programs for parallel processing." IEEE Transactions on Electronic Computers 5 (1966): 757-763. """ if a not in self or b not in self: raise networkx.NetworkXUnfeasible('node(s) not in graph') # "Statements", in Bernstein's parlance, actually mean edges, # in the sense that s1: a = x + y maps inputs (x, y) to output # a. Unioning the node itself to the set of its predecessors # and-or successors achieves the same effect. I think. a_in = set(self.predecessors(a)).union([a]) a_out = set(self.successors(a)).union([a]) b_in = set(self.predecessors(b)).union([b]) b_out = set(self.successors(b)).union([b]) return ( (a_out.intersection(b_in) == set()) and (a_in.intersection(b_out) == set()) # # skip the third condition (shared outputs) # and (a_out.intersection(b_out) == set()) )
def find_leafy_branch_larger_than_size(g: nx.DiGraph, size: int = 1) -> nx.DiGraph: """This algorithm will sort the graph `G` and return the outermost contiguous subgraph that is larger than `size` """ if not nx.is_weakly_connected(g): raise nx.NetworkXUnfeasible( "Graphs must be directed and weakly connected.") # exit early if our graph is already the right size if len(g) <= size: return g # Start at the leaves and work through branches. # Return first subgraph larger than or equal to `size`. for node in nx.lexicographical_topological_sort(g): # pragma: no branch us = get_all_predecessors(g, node) us.add(node) if len(us) >= size: return g.subgraph(us)
def weighted_one_edge_augmentation(G, avail, weight=None, partial=False): """Finds the minimum weight set of edges to connect G if one exists. This is a variant of the weighted MST problem. Example ------- >>> G = nx.Graph([(1, 2), (2, 3), (4, 5)]) >>> G.add_nodes_from([6, 7, 8]) >>> # any edge not in avail has an implicit weight of infinity >>> avail = [(1, 3), (1, 5), (4, 7), (4, 8), (6, 1), (8, 1), (8, 2)] >>> sorted(weighted_one_edge_augmentation(G, avail)) [(1, 5), (4, 7), (6, 1), (8, 1)] >>> # find another solution by giving large weights to edges in the >>> # previous solution (note some of the old edges must be used) >>> avail = [(1, 3), (1, 5, 99), (4, 7, 9), (6, 1, 99), (8, 1, 99), (8, 2)] >>> sorted(weighted_one_edge_augmentation(G, avail)) [(1, 5), (4, 7), (6, 1), (8, 2)] """ avail_uv, avail_w = _unpack_available_edges(avail, weight=weight, G=G) # Collapse CCs in the original graph into nodes in a metagraph # Then find an MST of the metagraph instead of the original graph C = collapse(G, nx.connected_components(G)) mapping = C.graph['mapping'] # Assign each available edge to an edge in the metagraph candidate_mapping = _lightest_meta_edges(mapping, avail_uv, avail_w) # nx.set_edge_attributes(C, name='weight', values=0) C.add_edges_from((mu, mv, { 'weight': w, 'generator': uv }) for (mu, mv), uv, w in candidate_mapping) # Find MST of the meta graph meta_mst = nx.minimum_spanning_tree(C) if not partial and not nx.is_connected(meta_mst): raise nx.NetworkXUnfeasible( 'Not possible to connect G with available edges') # Yield the edge that generated the meta-edge for mu, mv, d in meta_mst.edges(data=True): if 'generator' in d: edge = d['generator'] yield edge
def prior_topological_sort(G): """ References ---------- .. [1] Manber, U. (1989). *Introduction to Algorithms - A Creative Approach.* Addison-Wesley. [2] networkx dag algorithms implementation """ if not G.is_directed(): raise nx.NetworkXError( "Topological sort not defined on undirected graphs.") indegree_map = {v: d for v, d in G.in_degree() if d > 0} # These nodes have zero indegree and ready to be returned. zero_indegree = [v for v, d in G.in_degree() if d == 0] def nofork(element): if element[2].startswith('fork'): return 1 return 0 while zero_indegree: node = zero_indegree.pop() if node not in G: # мы достали одну вершину raise RuntimeError("Graph changed during iteration") edg = list(G.edges(node, keys=True)) # достанем её потомков, сортанём рёбра в верной последовательности действий edg = sorted(edg, key=nofork) for _, child, _ in edg: try: indegree_map[child] -= 1 except KeyError: raise RuntimeError("Graph changed during iteration") if indegree_map[child] == 0: zero_indegree.append(child) del indegree_map[child] yield node if indegree_map: raise nx.NetworkXUnfeasible("Graph contains a cycle or graph changed " "during iteration")
def bridge_augmentation(G, avail=None, weight=None): """Finds the a set of edges that bridge connects G. Adding these edges to G will make it 2-edge-connected. If no constraints are specified the returned set of edges is minimum an optimal, otherwise the solution is approximated. Notes ----- If there are no constraints the solution can be computed in linear time using :func:`unconstrained_bridge_augmentation`. Otherwise, the problem becomes NP-hard and is the solution is approximated by :func:`weighted_bridge_augmentation`. """ if G.number_of_nodes() < 3: raise nx.NetworkXUnfeasible( 'impossible to bridge connect less than 3 nodes') if avail is None: return unconstrained_bridge_augmentation(G) else: return weighted_bridge_augmentation(G, avail, weight=weight)
def topsort(G, nbunch=None): if not G.is_directed(): raise nx.NetworkXError( "Topological sort not defined on undirected graphs.") # nonrecursive version seen = {} order_explored = [] # provide order and explored = {} # fast search without more general priorityDictionary if nbunch is None: nbunch = G.nodes_iter() for v in nbunch: # process all vertices in G if v in explored: continue fringe = [v] # nodes yet to look at while fringe: w = fringe[-1] # depth first search if w in explored: # already looked down this branch fringe.pop() continue seen[w] = 1 # mark as seen # Check successors for cycles and for new nodes new_nodes = [] for n in G[w]: if n not in explored: if n in seen: #CYCLE !! trace_loop(G, n) #print_children(G,n) #print("Cycle detected at edge " + str(w) + "-->" + str(n)) raise nx.NetworkXUnfeasible("Graph contains a cycle.") new_nodes.append(n) if new_nodes: # Add new_nodes to fringe fringe.extend(new_nodes) else: # No new nodes so w is fully explored explored[w] = 1 order_explored.insert(0, w) # reverse order explored fringe.pop() # done considering this node return order_explored
def flow_with_demands(graph): """Computes a flow with demands over the given graph. Args: graph: A directed graph with nodes annotated with 'demand' properties and edges annotated with 'capacity' properties. Returns: A dict of dicts containing the flow on each edge. For instance, flow[s1][s2] should provide the flow along edge (s1, s2). Raises: NetworkXUnfeasible: An error is thrown if there is no flow satisfying the demands. """ G = graph.copy() demand_satisfied = sum([G.node[i]['demand'] for i in G.nodes()]) == 0 if not demand_satisfied: raise nx.NetworkXUnfeasible( 'An error is thrown if there is no flow satisfying the demands.') reduced_graph = reduce_graph(G) G_f = ford_fulkerson(reduced_graph, 'source', 'sink') flow = calc_flow(G_f, G) return flow
def tree_layout(G): """ Draw an unrooted tree layout. Use the weights as edge length. Args: G: a networkx graph. If G is not a tree, then an exception is raised Returns: a dictionary of node positions """ pos = {} ## the graph must be a tree if not nx.is_tree(G): raise nx.NetworkXUnfeasible() ## identify leafs and arc length unit leafs = [n for n in G if G.degree(n) == 1] da = 1.0/len(leafs) * 2.0 * np.pi ## determine arclengths, positions n0 = leafs[0] n1 = next(nx.all_neighbors(G, n0)) ## a leaf only has one neighbor arclengths = {} positions = {n0 : np.array([0.0, 0.0])} distribute_arclengths(G, n0, n1, da, da, arclengths) ## first da assigned to leaf n0 compute_positions(G, n0, n1, arclengths, positions) return positions
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