|
"""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 |
|
|