| """Functions for finding chains in a graph.""" |
|
|
| import networkx as nx |
| from networkx.utils import not_implemented_for |
|
|
| __all__ = ["chain_decomposition"] |
|
|
|
|
| @not_implemented_for("directed") |
| @not_implemented_for("multigraph") |
| @nx._dispatchable |
| def chain_decomposition(G, root=None): |
| """Returns the chain decomposition of a graph. |
| |
| The *chain decomposition* of a graph with respect a depth-first |
| search tree is a set of cycles or paths derived from the set of |
| fundamental cycles of the tree in the following manner. Consider |
| each fundamental cycle with respect to the given tree, represented |
| as a list of edges beginning with the nontree edge oriented away |
| from the root of the tree. For each fundamental cycle, if it |
| overlaps with any previous fundamental cycle, just take the initial |
| non-overlapping segment, which is a path instead of a cycle. Each |
| cycle or path is called a *chain*. For more information, see [1]_. |
| |
| Parameters |
| ---------- |
| G : undirected graph |
| |
| root : node (optional) |
| A node in the graph `G`. If specified, only the chain |
| decomposition for the connected component containing this node |
| will be returned. This node indicates the root of the depth-first |
| search tree. |
| |
| Yields |
| ------ |
| chain : list |
| A list of edges representing a chain. There is no guarantee on |
| the orientation of the edges in each chain (for example, if a |
| chain includes the edge joining nodes 1 and 2, the chain may |
| include either (1, 2) or (2, 1)). |
| |
| Raises |
| ------ |
| NodeNotFound |
| If `root` is not in the graph `G`. |
| |
| Examples |
| -------- |
| >>> G = nx.Graph([(0, 1), (1, 4), (3, 4), (3, 5), (4, 5)]) |
| >>> list(nx.chain_decomposition(G)) |
| [[(4, 5), (5, 3), (3, 4)]] |
| |
| Notes |
| ----- |
| The worst-case running time of this implementation is linear in the |
| number of nodes and number of edges [1]_. |
| |
| References |
| ---------- |
| .. [1] Jens M. Schmidt (2013). "A simple test on 2-vertex- |
| and 2-edge-connectivity." *Information Processing Letters*, |
| 113, 241–244. Elsevier. <https://doi.org/10.1016/j.ipl.2013.01.016> |
| |
| """ |
|
|
| def _dfs_cycle_forest(G, root=None): |
| """Builds a directed graph composed of cycles from the given graph. |
| |
| `G` is an undirected simple graph. `root` is a node in the graph |
| from which the depth-first search is started. |
| |
| This function returns both the depth-first search cycle graph |
| (as a :class:`~networkx.DiGraph`) and the list of nodes in |
| depth-first preorder. The depth-first search cycle graph is a |
| directed graph whose edges are the edges of `G` oriented toward |
| the root if the edge is a tree edge and away from the root if |
| the edge is a non-tree edge. If `root` is not specified, this |
| performs a depth-first search on each connected component of `G` |
| and returns a directed forest instead. |
| |
| If `root` is not in the graph, this raises :exc:`KeyError`. |
| |
| """ |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| H = nx.DiGraph() |
| nodes = [] |
| for u, v, d in nx.dfs_labeled_edges(G, source=root): |
| if d == "forward": |
| |
| |
| |
| if u == v: |
| H.add_node(v, parent=None) |
| nodes.append(v) |
| else: |
| H.add_node(v, parent=u) |
| H.add_edge(v, u, nontree=False) |
| nodes.append(v) |
| |
| |
| |
| elif d == "nontree" and v not in H[u]: |
| H.add_edge(v, u, nontree=True) |
| else: |
| |
| |
| pass |
| return H, nodes |
|
|
| def _build_chain(G, u, v, visited): |
| """Generate the chain starting from the given nontree edge. |
| |
| `G` is a DFS cycle graph as constructed by |
| :func:`_dfs_cycle_graph`. The edge (`u`, `v`) is a nontree edge |
| that begins a chain. `visited` is a set representing the nodes |
| in `G` that have already been visited. |
| |
| This function yields the edges in an initial segment of the |
| fundamental cycle of `G` starting with the nontree edge (`u`, |
| `v`) that includes all the edges up until the first node that |
| appears in `visited`. The tree edges are given by the 'parent' |
| node attribute. The `visited` set is updated to add each node in |
| an edge yielded by this function. |
| |
| """ |
| while v not in visited: |
| yield u, v |
| visited.add(v) |
| u, v = v, G.nodes[v]["parent"] |
| yield u, v |
|
|
| |
| if root is not None and root not in G: |
| raise nx.NodeNotFound(f"Root node {root} is not in graph") |
|
|
| |
| |
| |
| H, nodes = _dfs_cycle_forest(G, root) |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| visited = set() |
| for u in nodes: |
| visited.add(u) |
| |
| edges = ((u, v) for u, v, d in H.out_edges(u, data="nontree") if d) |
| for u, v in edges: |
| |
| |
| chain = list(_build_chain(H, u, v, visited)) |
| yield chain |
|
|