| """ |
| ========================== |
| Bipartite Graph Algorithms |
| ========================== |
| """ |
|
|
| import networkx as nx |
| from networkx.algorithms.components import connected_components |
| from networkx.exception import AmbiguousSolution |
|
|
| __all__ = [ |
| "is_bipartite", |
| "is_bipartite_node_set", |
| "color", |
| "sets", |
| "density", |
| "degrees", |
| ] |
|
|
|
|
| @nx._dispatchable |
| def color(G): |
| """Returns a two-coloring of the graph. |
| |
| Raises an exception if the graph is not bipartite. |
| |
| Parameters |
| ---------- |
| G : NetworkX graph |
| |
| Returns |
| ------- |
| color : dictionary |
| A dictionary keyed by node with a 1 or 0 as data for each node color. |
| |
| Raises |
| ------ |
| NetworkXError |
| If the graph is not two-colorable. |
| |
| Examples |
| -------- |
| >>> from networkx.algorithms import bipartite |
| >>> G = nx.path_graph(4) |
| >>> c = bipartite.color(G) |
| >>> print(c) |
| {0: 1, 1: 0, 2: 1, 3: 0} |
| |
| You can use this to set a node attribute indicating the bipartite set: |
| |
| >>> nx.set_node_attributes(G, c, "bipartite") |
| >>> print(G.nodes[0]["bipartite"]) |
| 1 |
| >>> print(G.nodes[1]["bipartite"]) |
| 0 |
| """ |
| if G.is_directed(): |
| import itertools |
|
|
| def neighbors(v): |
| return itertools.chain.from_iterable([G.predecessors(v), G.successors(v)]) |
|
|
| else: |
| neighbors = G.neighbors |
|
|
| color = {} |
| for n in G: |
| if n in color or len(G[n]) == 0: |
| continue |
| queue = [n] |
| color[n] = 1 |
| while queue: |
| v = queue.pop() |
| c = 1 - color[v] |
| for w in neighbors(v): |
| if w in color: |
| if color[w] == color[v]: |
| raise nx.NetworkXError("Graph is not bipartite.") |
| else: |
| color[w] = c |
| queue.append(w) |
| |
| color.update(dict.fromkeys(nx.isolates(G), 0)) |
| return color |
|
|
|
|
| @nx._dispatchable |
| def is_bipartite(G): |
| """Returns True if graph G is bipartite, False if not. |
| |
| Parameters |
| ---------- |
| G : NetworkX graph |
| |
| Examples |
| -------- |
| >>> from networkx.algorithms import bipartite |
| >>> G = nx.path_graph(4) |
| >>> print(bipartite.is_bipartite(G)) |
| True |
| |
| See Also |
| -------- |
| color, is_bipartite_node_set |
| """ |
| try: |
| color(G) |
| return True |
| except nx.NetworkXError: |
| return False |
|
|
|
|
| @nx._dispatchable |
| def is_bipartite_node_set(G, nodes): |
| """Returns True if nodes and G/nodes are a bipartition of G. |
| |
| Parameters |
| ---------- |
| G : NetworkX graph |
| |
| nodes: list or container |
| Check if nodes are a one of a bipartite set. |
| |
| Examples |
| -------- |
| >>> from networkx.algorithms import bipartite |
| >>> G = nx.path_graph(4) |
| >>> X = set([1, 3]) |
| >>> bipartite.is_bipartite_node_set(G, X) |
| True |
| |
| Notes |
| ----- |
| An exception is raised if the input nodes are not distinct, because in this |
| case some bipartite algorithms will yield incorrect results. |
| For connected graphs the bipartite sets are unique. This function handles |
| disconnected graphs. |
| """ |
| S = set(nodes) |
|
|
| if len(S) < len(nodes): |
| |
| raise AmbiguousSolution( |
| "The input node set contains duplicates.\n" |
| "This may lead to incorrect results when using it in bipartite algorithms.\n" |
| "Consider using set(nodes) as the input" |
| ) |
|
|
| for CC in (G.subgraph(c).copy() for c in connected_components(G)): |
| X, Y = sets(CC) |
| if not ( |
| (X.issubset(S) and Y.isdisjoint(S)) or (Y.issubset(S) and X.isdisjoint(S)) |
| ): |
| return False |
| return True |
|
|
|
|
| @nx._dispatchable |
| def sets(G, top_nodes=None): |
| """Returns bipartite node sets of graph G. |
| |
| Raises an exception if the graph is not bipartite or if the input |
| graph is disconnected and thus more than one valid solution exists. |
| See :mod:`bipartite documentation <networkx.algorithms.bipartite>` |
| for further details on how bipartite graphs are handled in NetworkX. |
| |
| Parameters |
| ---------- |
| G : NetworkX graph |
| |
| top_nodes : container, optional |
| Container with all nodes in one bipartite node set. If not supplied |
| it will be computed. But if more than one solution exists an exception |
| will be raised. |
| |
| Returns |
| ------- |
| X : set |
| Nodes from one side of the bipartite graph. |
| Y : set |
| Nodes from the other side. |
| |
| Raises |
| ------ |
| AmbiguousSolution |
| Raised if the input bipartite graph is disconnected and no container |
| with all nodes in one bipartite set is provided. When determining |
| the nodes in each bipartite set more than one valid solution is |
| possible if the input graph is disconnected. |
| NetworkXError |
| Raised if the input graph is not bipartite. |
| |
| Examples |
| -------- |
| >>> from networkx.algorithms import bipartite |
| >>> G = nx.path_graph(4) |
| >>> X, Y = bipartite.sets(G) |
| >>> list(X) |
| [0, 2] |
| >>> list(Y) |
| [1, 3] |
| |
| See Also |
| -------- |
| color |
| |
| """ |
| if G.is_directed(): |
| is_connected = nx.is_weakly_connected |
| else: |
| is_connected = nx.is_connected |
| if top_nodes is not None: |
| X = set(top_nodes) |
| Y = set(G) - X |
| else: |
| if not is_connected(G): |
| msg = "Disconnected graph: Ambiguous solution for bipartite sets." |
| raise nx.AmbiguousSolution(msg) |
| c = color(G) |
| X = {n for n, is_top in c.items() if is_top} |
| Y = {n for n, is_top in c.items() if not is_top} |
| return (X, Y) |
|
|
|
|
| @nx._dispatchable(graphs="B") |
| def density(B, nodes): |
| """Returns density of bipartite graph B. |
| |
| Parameters |
| ---------- |
| B : NetworkX graph |
| |
| nodes: list or container |
| Nodes in one node set of the bipartite graph. |
| |
| Returns |
| ------- |
| d : float |
| The bipartite density |
| |
| Examples |
| -------- |
| >>> from networkx.algorithms import bipartite |
| >>> G = nx.complete_bipartite_graph(3, 2) |
| >>> X = set([0, 1, 2]) |
| >>> bipartite.density(G, X) |
| 1.0 |
| >>> Y = set([3, 4]) |
| >>> bipartite.density(G, Y) |
| 1.0 |
| |
| Notes |
| ----- |
| The container of nodes passed as argument must contain all nodes |
| in one of the two bipartite node sets to avoid ambiguity in the |
| case of disconnected graphs. |
| See :mod:`bipartite documentation <networkx.algorithms.bipartite>` |
| for further details on how bipartite graphs are handled in NetworkX. |
| |
| See Also |
| -------- |
| color |
| """ |
| n = len(B) |
| m = nx.number_of_edges(B) |
| nb = len(nodes) |
| nt = n - nb |
| if m == 0: |
| d = 0.0 |
| else: |
| if B.is_directed(): |
| d = m / (2 * nb * nt) |
| else: |
| d = m / (nb * nt) |
| return d |
|
|
|
|
| @nx._dispatchable(graphs="B", edge_attrs="weight") |
| def degrees(B, nodes, weight=None): |
| """Returns the degrees of the two node sets in the bipartite graph B. |
| |
| Parameters |
| ---------- |
| B : NetworkX graph |
| |
| nodes: list or container |
| Nodes in one node set of the bipartite graph. |
| |
| weight : string or None, optional (default=None) |
| The edge attribute that holds the numerical value used as a weight. |
| If None, then each edge has weight 1. |
| The degree is the sum of the edge weights adjacent to the node. |
| |
| Returns |
| ------- |
| (degX,degY) : tuple of dictionaries |
| The degrees of the two bipartite sets as dictionaries keyed by node. |
| |
| Examples |
| -------- |
| >>> from networkx.algorithms import bipartite |
| >>> G = nx.complete_bipartite_graph(3, 2) |
| >>> Y = set([3, 4]) |
| >>> degX, degY = bipartite.degrees(G, Y) |
| >>> dict(degX) |
| {0: 2, 1: 2, 2: 2} |
| |
| Notes |
| ----- |
| The container of nodes passed as argument must contain all nodes |
| in one of the two bipartite node sets to avoid ambiguity in the |
| case of disconnected graphs. |
| See :mod:`bipartite documentation <networkx.algorithms.bipartite>` |
| for further details on how bipartite graphs are handled in NetworkX. |
| |
| See Also |
| -------- |
| color, density |
| """ |
| bottom = set(nodes) |
| top = set(B) - bottom |
| return (B.degree(top, weight), B.degree(bottom, weight)) |
|
|