Beispiel #1
0
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
Beispiel #2
0
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
Beispiel #3
0
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
Beispiel #4
0
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))
Beispiel #5
0
    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
Beispiel #6
0
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
Beispiel #7
0
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))
Beispiel #8
0
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)
Beispiel #9
0
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
Beispiel #10
0
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)
Beispiel #12
0
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
Beispiel #13
0
 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
Beispiel #14
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
Beispiel #15
0
 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
Beispiel #17
0
    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)
Beispiel #18
0
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
Beispiel #19
0
 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)
Beispiel #21
0
 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)
Beispiel #22
0
    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())
        )
Beispiel #23
0
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)
Beispiel #24
0
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
Beispiel #25
0
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")
Beispiel #26
0
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)
Beispiel #27
0
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
Beispiel #28
0
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
Beispiel #29
0
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