I am done

This commit is contained in:
2024-10-30 22:14:35 +01:00
parent 720dc28c09
commit 40e2a747cf
36901 changed files with 5011519 additions and 0 deletions

View File

@ -0,0 +1,6 @@
from .connected import *
from .strongly_connected import *
from .weakly_connected import *
from .attracting import *
from .biconnected import *
from .semiconnected import *

View File

@ -0,0 +1,115 @@
"""Attracting components."""
import networkx as nx
from networkx.utils.decorators import not_implemented_for
__all__ = [
"number_attracting_components",
"attracting_components",
"is_attracting_component",
]
@not_implemented_for("undirected")
@nx._dispatchable
def attracting_components(G):
"""Generates the attracting components in `G`.
An attracting component in a directed graph `G` is a strongly connected
component with the property that a random walker on the graph will never
leave the component, once it enters the component.
The nodes in attracting components can also be thought of as recurrent
nodes. If a random walker enters the attractor containing the node, then
the node will be visited infinitely often.
To obtain induced subgraphs on each component use:
``(G.subgraph(c).copy() for c in attracting_components(G))``
Parameters
----------
G : DiGraph, MultiDiGraph
The graph to be analyzed.
Returns
-------
attractors : generator of sets
A generator of sets of nodes, one for each attracting component of G.
Raises
------
NetworkXNotImplemented
If the input graph is undirected.
See Also
--------
number_attracting_components
is_attracting_component
"""
scc = list(nx.strongly_connected_components(G))
cG = nx.condensation(G, scc)
for n in cG:
if cG.out_degree(n) == 0:
yield scc[n]
@not_implemented_for("undirected")
@nx._dispatchable
def number_attracting_components(G):
"""Returns the number of attracting components in `G`.
Parameters
----------
G : DiGraph, MultiDiGraph
The graph to be analyzed.
Returns
-------
n : int
The number of attracting components in G.
Raises
------
NetworkXNotImplemented
If the input graph is undirected.
See Also
--------
attracting_components
is_attracting_component
"""
return sum(1 for ac in attracting_components(G))
@not_implemented_for("undirected")
@nx._dispatchable
def is_attracting_component(G):
"""Returns True if `G` consists of a single attracting component.
Parameters
----------
G : DiGraph, MultiDiGraph
The graph to be analyzed.
Returns
-------
attracting : bool
True if `G` has a single attracting component. Otherwise, False.
Raises
------
NetworkXNotImplemented
If the input graph is undirected.
See Also
--------
attracting_components
number_attracting_components
"""
ac = list(attracting_components(G))
if len(ac) == 1:
return len(ac[0]) == len(G)
return False

View File

@ -0,0 +1,394 @@
"""Biconnected components and articulation points."""
from itertools import chain
import networkx as nx
from networkx.utils.decorators import not_implemented_for
__all__ = [
"biconnected_components",
"biconnected_component_edges",
"is_biconnected",
"articulation_points",
]
@not_implemented_for("directed")
@nx._dispatchable
def is_biconnected(G):
"""Returns True if the graph is biconnected, False otherwise.
A graph is biconnected if, and only if, it cannot be disconnected by
removing only one node (and all edges incident on that node). If
removing a node increases the number of disconnected components
in the graph, that node is called an articulation point, or cut
vertex. A biconnected graph has no articulation points.
Parameters
----------
G : NetworkX Graph
An undirected graph.
Returns
-------
biconnected : bool
True if the graph is biconnected, False otherwise.
Raises
------
NetworkXNotImplemented
If the input graph is not undirected.
Examples
--------
>>> G = nx.path_graph(4)
>>> print(nx.is_biconnected(G))
False
>>> G.add_edge(0, 3)
>>> print(nx.is_biconnected(G))
True
See Also
--------
biconnected_components
articulation_points
biconnected_component_edges
is_strongly_connected
is_weakly_connected
is_connected
is_semiconnected
Notes
-----
The algorithm to find articulation points and biconnected
components is implemented using a non-recursive depth-first-search
(DFS) that keeps track of the highest level that back edges reach
in the DFS tree. A node `n` is an articulation point if, and only
if, there exists a subtree rooted at `n` such that there is no
back edge from any successor of `n` that links to a predecessor of
`n` in the DFS tree. By keeping track of all the edges traversed
by the DFS we can obtain the biconnected components because all
edges of a bicomponent will be traversed consecutively between
articulation points.
References
----------
.. [1] Hopcroft, J.; Tarjan, R. (1973).
"Efficient algorithms for graph manipulation".
Communications of the ACM 16: 372378. doi:10.1145/362248.362272
"""
bccs = biconnected_components(G)
try:
bcc = next(bccs)
except StopIteration:
# No bicomponents (empty graph?)
return False
try:
next(bccs)
except StopIteration:
# Only one bicomponent
return len(bcc) == len(G)
else:
# Multiple bicomponents
return False
@not_implemented_for("directed")
@nx._dispatchable
def biconnected_component_edges(G):
"""Returns a generator of lists of edges, one list for each biconnected
component of the input graph.
Biconnected components are maximal subgraphs such that the removal of a
node (and all edges incident on that node) will not disconnect the
subgraph. Note that nodes may be part of more than one biconnected
component. Those nodes are articulation points, or cut vertices.
However, each edge belongs to one, and only one, biconnected component.
Notice that by convention a dyad is considered a biconnected component.
Parameters
----------
G : NetworkX Graph
An undirected graph.
Returns
-------
edges : generator of lists
Generator of lists of edges, one list for each bicomponent.
Raises
------
NetworkXNotImplemented
If the input graph is not undirected.
Examples
--------
>>> G = nx.barbell_graph(4, 2)
>>> print(nx.is_biconnected(G))
False
>>> bicomponents_edges = list(nx.biconnected_component_edges(G))
>>> len(bicomponents_edges)
5
>>> G.add_edge(2, 8)
>>> print(nx.is_biconnected(G))
True
>>> bicomponents_edges = list(nx.biconnected_component_edges(G))
>>> len(bicomponents_edges)
1
See Also
--------
is_biconnected,
biconnected_components,
articulation_points,
Notes
-----
The algorithm to find articulation points and biconnected
components is implemented using a non-recursive depth-first-search
(DFS) that keeps track of the highest level that back edges reach
in the DFS tree. A node `n` is an articulation point if, and only
if, there exists a subtree rooted at `n` such that there is no
back edge from any successor of `n` that links to a predecessor of
`n` in the DFS tree. By keeping track of all the edges traversed
by the DFS we can obtain the biconnected components because all
edges of a bicomponent will be traversed consecutively between
articulation points.
References
----------
.. [1] Hopcroft, J.; Tarjan, R. (1973).
"Efficient algorithms for graph manipulation".
Communications of the ACM 16: 372378. doi:10.1145/362248.362272
"""
yield from _biconnected_dfs(G, components=True)
@not_implemented_for("directed")
@nx._dispatchable
def biconnected_components(G):
"""Returns a generator of sets of nodes, one set for each biconnected
component of the graph
Biconnected components are maximal subgraphs such that the removal of a
node (and all edges incident on that node) will not disconnect the
subgraph. Note that nodes may be part of more than one biconnected
component. Those nodes are articulation points, or cut vertices. The
removal of articulation points will increase the number of connected
components of the graph.
Notice that by convention a dyad is considered a biconnected component.
Parameters
----------
G : NetworkX Graph
An undirected graph.
Returns
-------
nodes : generator
Generator of sets of nodes, one set for each biconnected component.
Raises
------
NetworkXNotImplemented
If the input graph is not undirected.
Examples
--------
>>> G = nx.lollipop_graph(5, 1)
>>> print(nx.is_biconnected(G))
False
>>> bicomponents = list(nx.biconnected_components(G))
>>> len(bicomponents)
2
>>> G.add_edge(0, 5)
>>> print(nx.is_biconnected(G))
True
>>> bicomponents = list(nx.biconnected_components(G))
>>> len(bicomponents)
1
You can generate a sorted list of biconnected components, largest
first, using sort.
>>> G.remove_edge(0, 5)
>>> [len(c) for c in sorted(nx.biconnected_components(G), key=len, reverse=True)]
[5, 2]
If you only want the largest connected component, it's more
efficient to use max instead of sort.
>>> Gc = max(nx.biconnected_components(G), key=len)
To create the components as subgraphs use:
``(G.subgraph(c).copy() for c in biconnected_components(G))``
See Also
--------
is_biconnected
articulation_points
biconnected_component_edges
k_components : this function is a special case where k=2
bridge_components : similar to this function, but is defined using
2-edge-connectivity instead of 2-node-connectivity.
Notes
-----
The algorithm to find articulation points and biconnected
components is implemented using a non-recursive depth-first-search
(DFS) that keeps track of the highest level that back edges reach
in the DFS tree. A node `n` is an articulation point if, and only
if, there exists a subtree rooted at `n` such that there is no
back edge from any successor of `n` that links to a predecessor of
`n` in the DFS tree. By keeping track of all the edges traversed
by the DFS we can obtain the biconnected components because all
edges of a bicomponent will be traversed consecutively between
articulation points.
References
----------
.. [1] Hopcroft, J.; Tarjan, R. (1973).
"Efficient algorithms for graph manipulation".
Communications of the ACM 16: 372378. doi:10.1145/362248.362272
"""
for comp in _biconnected_dfs(G, components=True):
yield set(chain.from_iterable(comp))
@not_implemented_for("directed")
@nx._dispatchable
def articulation_points(G):
"""Yield the articulation points, or cut vertices, of a graph.
An articulation point or cut vertex is any node whose removal (along with
all its incident edges) increases the number of connected components of
a graph. An undirected connected graph without articulation points is
biconnected. Articulation points belong to more than one biconnected
component of a graph.
Notice that by convention a dyad is considered a biconnected component.
Parameters
----------
G : NetworkX Graph
An undirected graph.
Yields
------
node
An articulation point in the graph.
Raises
------
NetworkXNotImplemented
If the input graph is not undirected.
Examples
--------
>>> G = nx.barbell_graph(4, 2)
>>> print(nx.is_biconnected(G))
False
>>> len(list(nx.articulation_points(G)))
4
>>> G.add_edge(2, 8)
>>> print(nx.is_biconnected(G))
True
>>> len(list(nx.articulation_points(G)))
0
See Also
--------
is_biconnected
biconnected_components
biconnected_component_edges
Notes
-----
The algorithm to find articulation points and biconnected
components is implemented using a non-recursive depth-first-search
(DFS) that keeps track of the highest level that back edges reach
in the DFS tree. A node `n` is an articulation point if, and only
if, there exists a subtree rooted at `n` such that there is no
back edge from any successor of `n` that links to a predecessor of
`n` in the DFS tree. By keeping track of all the edges traversed
by the DFS we can obtain the biconnected components because all
edges of a bicomponent will be traversed consecutively between
articulation points.
References
----------
.. [1] Hopcroft, J.; Tarjan, R. (1973).
"Efficient algorithms for graph manipulation".
Communications of the ACM 16: 372378. doi:10.1145/362248.362272
"""
seen = set()
for articulation in _biconnected_dfs(G, components=False):
if articulation not in seen:
seen.add(articulation)
yield articulation
@not_implemented_for("directed")
def _biconnected_dfs(G, components=True):
# depth-first search algorithm to generate articulation points
# and biconnected components
visited = set()
for start in G:
if start in visited:
continue
discovery = {start: 0} # time of first discovery of node during search
low = {start: 0}
root_children = 0
visited.add(start)
edge_stack = []
stack = [(start, start, iter(G[start]))]
edge_index = {}
while stack:
grandparent, parent, children = stack[-1]
try:
child = next(children)
if grandparent == child:
continue
if child in visited:
if discovery[child] <= discovery[parent]: # back edge
low[parent] = min(low[parent], discovery[child])
if components:
edge_index[parent, child] = len(edge_stack)
edge_stack.append((parent, child))
else:
low[child] = discovery[child] = len(discovery)
visited.add(child)
stack.append((parent, child, iter(G[child])))
if components:
edge_index[parent, child] = len(edge_stack)
edge_stack.append((parent, child))
except StopIteration:
stack.pop()
if len(stack) > 1:
if low[parent] >= discovery[grandparent]:
if components:
ind = edge_index[grandparent, parent]
yield edge_stack[ind:]
del edge_stack[ind:]
else:
yield grandparent
low[grandparent] = min(low[parent], low[grandparent])
elif stack: # length 1 so grandparent is root
root_children += 1
if components:
ind = edge_index[grandparent, parent]
yield edge_stack[ind:]
del edge_stack[ind:]
if not components:
# root node is articulation point if it has more than 1 child
if root_children > 1:
yield start

View File

@ -0,0 +1,216 @@
"""Connected components."""
import networkx as nx
from networkx.utils.decorators import not_implemented_for
from ...utils import arbitrary_element
__all__ = [
"number_connected_components",
"connected_components",
"is_connected",
"node_connected_component",
]
@not_implemented_for("directed")
@nx._dispatchable
def connected_components(G):
"""Generate connected components.
Parameters
----------
G : NetworkX graph
An undirected graph
Returns
-------
comp : generator of sets
A generator of sets of nodes, one for each component of G.
Raises
------
NetworkXNotImplemented
If G is directed.
Examples
--------
Generate a sorted list of connected components, largest first.
>>> G = nx.path_graph(4)
>>> nx.add_path(G, [10, 11, 12])
>>> [len(c) for c in sorted(nx.connected_components(G), key=len, reverse=True)]
[4, 3]
If you only want the largest connected component, it's more
efficient to use max instead of sort.
>>> largest_cc = max(nx.connected_components(G), key=len)
To create the induced subgraph of each component use:
>>> S = [G.subgraph(c).copy() for c in nx.connected_components(G)]
See Also
--------
strongly_connected_components
weakly_connected_components
Notes
-----
For undirected graphs only.
"""
seen = set()
n = len(G)
for v in G:
if v not in seen:
c = _plain_bfs(G, n, v)
seen.update(c)
yield c
@not_implemented_for("directed")
@nx._dispatchable
def number_connected_components(G):
"""Returns the number of connected components.
Parameters
----------
G : NetworkX graph
An undirected graph.
Returns
-------
n : integer
Number of connected components
Raises
------
NetworkXNotImplemented
If G is directed.
Examples
--------
>>> G = nx.Graph([(0, 1), (1, 2), (5, 6), (3, 4)])
>>> nx.number_connected_components(G)
3
See Also
--------
connected_components
number_weakly_connected_components
number_strongly_connected_components
Notes
-----
For undirected graphs only.
"""
return sum(1 for cc in connected_components(G))
@not_implemented_for("directed")
@nx._dispatchable
def is_connected(G):
"""Returns True if the graph is connected, False otherwise.
Parameters
----------
G : NetworkX Graph
An undirected graph.
Returns
-------
connected : bool
True if the graph is connected, false otherwise.
Raises
------
NetworkXNotImplemented
If G is directed.
Examples
--------
>>> G = nx.path_graph(4)
>>> print(nx.is_connected(G))
True
See Also
--------
is_strongly_connected
is_weakly_connected
is_semiconnected
is_biconnected
connected_components
Notes
-----
For undirected graphs only.
"""
n = len(G)
if n == 0:
raise nx.NetworkXPointlessConcept(
"Connectivity is undefined for the null graph."
)
return sum(1 for node in _plain_bfs(G, n, arbitrary_element(G))) == len(G)
@not_implemented_for("directed")
@nx._dispatchable
def node_connected_component(G, n):
"""Returns the set of nodes in the component of graph containing node n.
Parameters
----------
G : NetworkX Graph
An undirected graph.
n : node label
A node in G
Returns
-------
comp : set
A set of nodes in the component of G containing node n.
Raises
------
NetworkXNotImplemented
If G is directed.
Examples
--------
>>> G = nx.Graph([(0, 1), (1, 2), (5, 6), (3, 4)])
>>> nx.node_connected_component(G, 0) # nodes of component that contains node 0
{0, 1, 2}
See Also
--------
connected_components
Notes
-----
For undirected graphs only.
"""
return _plain_bfs(G, len(G), n)
def _plain_bfs(G, n, source):
"""A fast BFS node generator"""
adj = G._adj
seen = {source}
nextlevel = [source]
while nextlevel:
thislevel = nextlevel
nextlevel = []
for v in thislevel:
for w in adj[v]:
if w not in seen:
seen.add(w)
nextlevel.append(w)
if len(seen) == n:
return seen
return seen

View File

@ -0,0 +1,71 @@
"""Semiconnectedness."""
import networkx as nx
from networkx.utils import not_implemented_for, pairwise
__all__ = ["is_semiconnected"]
@not_implemented_for("undirected")
@nx._dispatchable
def is_semiconnected(G):
r"""Returns True if the graph is semiconnected, False otherwise.
A graph is semiconnected if and only if for any pair of nodes, either one
is reachable from the other, or they are mutually reachable.
This function uses a theorem that states that a DAG is semiconnected
if for any topological sort, for node $v_n$ in that sort, there is an
edge $(v_i, v_{i+1})$. That allows us to check if a non-DAG `G` is
semiconnected by condensing the graph: i.e. constructing a new graph `H`
with nodes being the strongly connected components of `G`, and edges
(scc_1, scc_2) if there is a edge $(v_1, v_2)$ in `G` for some
$v_1 \in scc_1$ and $v_2 \in scc_2$. That results in a DAG, so we compute
the topological sort of `H` and check if for every $n$ there is an edge
$(scc_n, scc_{n+1})$.
Parameters
----------
G : NetworkX graph
A directed graph.
Returns
-------
semiconnected : bool
True if the graph is semiconnected, False otherwise.
Raises
------
NetworkXNotImplemented
If the input graph is undirected.
NetworkXPointlessConcept
If the graph is empty.
Examples
--------
>>> G = nx.path_graph(4, create_using=nx.DiGraph())
>>> print(nx.is_semiconnected(G))
True
>>> G = nx.DiGraph([(1, 2), (3, 2)])
>>> print(nx.is_semiconnected(G))
False
See Also
--------
is_strongly_connected
is_weakly_connected
is_connected
is_biconnected
"""
if len(G) == 0:
raise nx.NetworkXPointlessConcept(
"Connectivity is undefined for the null graph."
)
if not nx.is_weakly_connected(G):
return False
H = nx.condensation(G)
return all(H.has_edge(u, v) for u, v in pairwise(nx.topological_sort(H)))

View File

@ -0,0 +1,351 @@
"""Strongly connected components."""
import networkx as nx
from networkx.utils.decorators import not_implemented_for
__all__ = [
"number_strongly_connected_components",
"strongly_connected_components",
"is_strongly_connected",
"kosaraju_strongly_connected_components",
"condensation",
]
@not_implemented_for("undirected")
@nx._dispatchable
def strongly_connected_components(G):
"""Generate nodes in strongly connected components of graph.
Parameters
----------
G : NetworkX Graph
A directed graph.
Returns
-------
comp : generator of sets
A generator of sets of nodes, one for each strongly connected
component of G.
Raises
------
NetworkXNotImplemented
If G is undirected.
Examples
--------
Generate a sorted list of strongly connected components, largest first.
>>> G = nx.cycle_graph(4, create_using=nx.DiGraph())
>>> nx.add_cycle(G, [10, 11, 12])
>>> [
... len(c)
... for c in sorted(nx.strongly_connected_components(G), key=len, reverse=True)
... ]
[4, 3]
If you only want the largest component, it's more efficient to
use max instead of sort.
>>> largest = max(nx.strongly_connected_components(G), key=len)
See Also
--------
connected_components
weakly_connected_components
kosaraju_strongly_connected_components
Notes
-----
Uses Tarjan's algorithm[1]_ with Nuutila's modifications[2]_.
Nonrecursive version of algorithm.
References
----------
.. [1] Depth-first search and linear graph algorithms, R. Tarjan
SIAM Journal of Computing 1(2):146-160, (1972).
.. [2] On finding the strongly connected components in a directed graph.
E. Nuutila and E. Soisalon-Soinen
Information Processing Letters 49(1): 9-14, (1994)..
"""
preorder = {}
lowlink = {}
scc_found = set()
scc_queue = []
i = 0 # Preorder counter
neighbors = {v: iter(G[v]) for v in G}
for source in G:
if source not in scc_found:
queue = [source]
while queue:
v = queue[-1]
if v not in preorder:
i = i + 1
preorder[v] = i
done = True
for w in neighbors[v]:
if w not in preorder:
queue.append(w)
done = False
break
if done:
lowlink[v] = preorder[v]
for w in G[v]:
if w not in scc_found:
if preorder[w] > preorder[v]:
lowlink[v] = min([lowlink[v], lowlink[w]])
else:
lowlink[v] = min([lowlink[v], preorder[w]])
queue.pop()
if lowlink[v] == preorder[v]:
scc = {v}
while scc_queue and preorder[scc_queue[-1]] > preorder[v]:
k = scc_queue.pop()
scc.add(k)
scc_found.update(scc)
yield scc
else:
scc_queue.append(v)
@not_implemented_for("undirected")
@nx._dispatchable
def kosaraju_strongly_connected_components(G, source=None):
"""Generate nodes in strongly connected components of graph.
Parameters
----------
G : NetworkX Graph
A directed graph.
Returns
-------
comp : generator of sets
A generator of sets of nodes, one for each strongly connected
component of G.
Raises
------
NetworkXNotImplemented
If G is undirected.
Examples
--------
Generate a sorted list of strongly connected components, largest first.
>>> G = nx.cycle_graph(4, create_using=nx.DiGraph())
>>> nx.add_cycle(G, [10, 11, 12])
>>> [
... len(c)
... for c in sorted(
... nx.kosaraju_strongly_connected_components(G), key=len, reverse=True
... )
... ]
[4, 3]
If you only want the largest component, it's more efficient to
use max instead of sort.
>>> largest = max(nx.kosaraju_strongly_connected_components(G), key=len)
See Also
--------
strongly_connected_components
Notes
-----
Uses Kosaraju's algorithm.
"""
post = list(nx.dfs_postorder_nodes(G.reverse(copy=False), source=source))
seen = set()
while post:
r = post.pop()
if r in seen:
continue
c = nx.dfs_preorder_nodes(G, r)
new = {v for v in c if v not in seen}
seen.update(new)
yield new
@not_implemented_for("undirected")
@nx._dispatchable
def number_strongly_connected_components(G):
"""Returns number of strongly connected components in graph.
Parameters
----------
G : NetworkX graph
A directed graph.
Returns
-------
n : integer
Number of strongly connected components
Raises
------
NetworkXNotImplemented
If G is undirected.
Examples
--------
>>> G = nx.DiGraph(
... [(0, 1), (1, 2), (2, 0), (2, 3), (4, 5), (3, 4), (5, 6), (6, 3), (6, 7)]
... )
>>> nx.number_strongly_connected_components(G)
3
See Also
--------
strongly_connected_components
number_connected_components
number_weakly_connected_components
Notes
-----
For directed graphs only.
"""
return sum(1 for scc in strongly_connected_components(G))
@not_implemented_for("undirected")
@nx._dispatchable
def is_strongly_connected(G):
"""Test directed graph for strong connectivity.
A directed graph is strongly connected if and only if every vertex in
the graph is reachable from every other vertex.
Parameters
----------
G : NetworkX Graph
A directed graph.
Returns
-------
connected : bool
True if the graph is strongly connected, False otherwise.
Examples
--------
>>> G = nx.DiGraph([(0, 1), (1, 2), (2, 3), (3, 0), (2, 4), (4, 2)])
>>> nx.is_strongly_connected(G)
True
>>> G.remove_edge(2, 3)
>>> nx.is_strongly_connected(G)
False
Raises
------
NetworkXNotImplemented
If G is undirected.
See Also
--------
is_weakly_connected
is_semiconnected
is_connected
is_biconnected
strongly_connected_components
Notes
-----
For directed graphs only.
"""
if len(G) == 0:
raise nx.NetworkXPointlessConcept(
"""Connectivity is undefined for the null graph."""
)
return len(next(strongly_connected_components(G))) == len(G)
@not_implemented_for("undirected")
@nx._dispatchable(returns_graph=True)
def condensation(G, scc=None):
"""Returns the condensation of G.
The condensation of G is the graph with each of the strongly connected
components contracted into a single node.
Parameters
----------
G : NetworkX DiGraph
A directed graph.
scc: list or generator (optional, default=None)
Strongly connected components. If provided, the elements in
`scc` must partition the nodes in `G`. If not provided, it will be
calculated as scc=nx.strongly_connected_components(G).
Returns
-------
C : NetworkX DiGraph
The condensation graph C of G. The node labels are integers
corresponding to the index of the component in the list of
strongly connected components of G. C has a graph attribute named
'mapping' with a dictionary mapping the original nodes to the
nodes in C to which they belong. Each node in C also has a node
attribute 'members' with the set of original nodes in G that
form the SCC that the node in C represents.
Raises
------
NetworkXNotImplemented
If G is undirected.
Examples
--------
Contracting two sets of strongly connected nodes into two distinct SCC
using the barbell graph.
>>> G = nx.barbell_graph(4, 0)
>>> G.remove_edge(3, 4)
>>> G = nx.DiGraph(G)
>>> H = nx.condensation(G)
>>> H.nodes.data()
NodeDataView({0: {'members': {0, 1, 2, 3}}, 1: {'members': {4, 5, 6, 7}}})
>>> H.graph["mapping"]
{0: 0, 1: 0, 2: 0, 3: 0, 4: 1, 5: 1, 6: 1, 7: 1}
Contracting a complete graph into one single SCC.
>>> G = nx.complete_graph(7, create_using=nx.DiGraph)
>>> H = nx.condensation(G)
>>> H.nodes
NodeView((0,))
>>> H.nodes.data()
NodeDataView({0: {'members': {0, 1, 2, 3, 4, 5, 6}}})
Notes
-----
After contracting all strongly connected components to a single node,
the resulting graph is a directed acyclic graph.
"""
if scc is None:
scc = nx.strongly_connected_components(G)
mapping = {}
members = {}
C = nx.DiGraph()
# Add mapping dict as graph attribute
C.graph["mapping"] = mapping
if len(G) == 0:
return C
for i, component in enumerate(scc):
members[i] = component
mapping.update((n, i) for n in component)
number_of_components = i + 1
C.add_nodes_from(range(number_of_components))
C.add_edges_from(
(mapping[u], mapping[v]) for u, v in G.edges() if mapping[u] != mapping[v]
)
# Add a list of members (ie original nodes) to each node (ie scc) in C.
nx.set_node_attributes(C, members, "members")
return C

View File

@ -0,0 +1,70 @@
import pytest
import networkx as nx
from networkx import NetworkXNotImplemented
class TestAttractingComponents:
@classmethod
def setup_class(cls):
cls.G1 = nx.DiGraph()
cls.G1.add_edges_from(
[
(5, 11),
(11, 2),
(11, 9),
(11, 10),
(7, 11),
(7, 8),
(8, 9),
(3, 8),
(3, 10),
]
)
cls.G2 = nx.DiGraph()
cls.G2.add_edges_from([(0, 1), (0, 2), (1, 1), (1, 2), (2, 1)])
cls.G3 = nx.DiGraph()
cls.G3.add_edges_from([(0, 1), (1, 2), (2, 1), (0, 3), (3, 4), (4, 3)])
cls.G4 = nx.DiGraph()
def test_attracting_components(self):
ac = list(nx.attracting_components(self.G1))
assert {2} in ac
assert {9} in ac
assert {10} in ac
ac = list(nx.attracting_components(self.G2))
ac = [tuple(sorted(x)) for x in ac]
assert ac == [(1, 2)]
ac = list(nx.attracting_components(self.G3))
ac = [tuple(sorted(x)) for x in ac]
assert (1, 2) in ac
assert (3, 4) in ac
assert len(ac) == 2
ac = list(nx.attracting_components(self.G4))
assert ac == []
def test_number_attacting_components(self):
assert nx.number_attracting_components(self.G1) == 3
assert nx.number_attracting_components(self.G2) == 1
assert nx.number_attracting_components(self.G3) == 2
assert nx.number_attracting_components(self.G4) == 0
def test_is_attracting_component(self):
assert not nx.is_attracting_component(self.G1)
assert not nx.is_attracting_component(self.G2)
assert not nx.is_attracting_component(self.G3)
g2 = self.G3.subgraph([1, 2])
assert nx.is_attracting_component(g2)
assert not nx.is_attracting_component(self.G4)
def test_connected_raise(self):
G = nx.Graph()
with pytest.raises(NetworkXNotImplemented):
next(nx.attracting_components(G))
pytest.raises(NetworkXNotImplemented, nx.number_attracting_components, G)
pytest.raises(NetworkXNotImplemented, nx.is_attracting_component, G)

View File

@ -0,0 +1,248 @@
import pytest
import networkx as nx
from networkx import NetworkXNotImplemented
def assert_components_edges_equal(x, y):
sx = {frozenset(frozenset(e) for e in c) for c in x}
sy = {frozenset(frozenset(e) for e in c) for c in y}
assert sx == sy
def assert_components_equal(x, y):
sx = {frozenset(c) for c in x}
sy = {frozenset(c) for c in y}
assert sx == sy
def test_barbell():
G = nx.barbell_graph(8, 4)
nx.add_path(G, [7, 20, 21, 22])
nx.add_cycle(G, [22, 23, 24, 25])
pts = set(nx.articulation_points(G))
assert pts == {7, 8, 9, 10, 11, 12, 20, 21, 22}
answer = [
{12, 13, 14, 15, 16, 17, 18, 19},
{0, 1, 2, 3, 4, 5, 6, 7},
{22, 23, 24, 25},
{11, 12},
{10, 11},
{9, 10},
{8, 9},
{7, 8},
{21, 22},
{20, 21},
{7, 20},
]
assert_components_equal(list(nx.biconnected_components(G)), answer)
G.add_edge(2, 17)
pts = set(nx.articulation_points(G))
assert pts == {7, 20, 21, 22}
def test_articulation_points_repetitions():
G = nx.Graph()
G.add_edges_from([(0, 1), (1, 2), (1, 3)])
assert list(nx.articulation_points(G)) == [1]
def test_articulation_points_cycle():
G = nx.cycle_graph(3)
nx.add_cycle(G, [1, 3, 4])
pts = set(nx.articulation_points(G))
assert pts == {1}
def test_is_biconnected():
G = nx.cycle_graph(3)
assert nx.is_biconnected(G)
nx.add_cycle(G, [1, 3, 4])
assert not nx.is_biconnected(G)
def test_empty_is_biconnected():
G = nx.empty_graph(5)
assert not nx.is_biconnected(G)
G.add_edge(0, 1)
assert not nx.is_biconnected(G)
def test_biconnected_components_cycle():
G = nx.cycle_graph(3)
nx.add_cycle(G, [1, 3, 4])
answer = [{0, 1, 2}, {1, 3, 4}]
assert_components_equal(list(nx.biconnected_components(G)), answer)
def test_biconnected_components1():
# graph example from
# https://web.archive.org/web/20121229123447/http://www.ibluemojo.com/school/articul_algorithm.html
edges = [
(0, 1),
(0, 5),
(0, 6),
(0, 14),
(1, 5),
(1, 6),
(1, 14),
(2, 4),
(2, 10),
(3, 4),
(3, 15),
(4, 6),
(4, 7),
(4, 10),
(5, 14),
(6, 14),
(7, 9),
(8, 9),
(8, 12),
(8, 13),
(10, 15),
(11, 12),
(11, 13),
(12, 13),
]
G = nx.Graph(edges)
pts = set(nx.articulation_points(G))
assert pts == {4, 6, 7, 8, 9}
comps = list(nx.biconnected_component_edges(G))
answer = [
[(3, 4), (15, 3), (10, 15), (10, 4), (2, 10), (4, 2)],
[(13, 12), (13, 8), (11, 13), (12, 11), (8, 12)],
[(9, 8)],
[(7, 9)],
[(4, 7)],
[(6, 4)],
[(14, 0), (5, 1), (5, 0), (14, 5), (14, 1), (6, 14), (6, 0), (1, 6), (0, 1)],
]
assert_components_edges_equal(comps, answer)
def test_biconnected_components2():
G = nx.Graph()
nx.add_cycle(G, "ABC")
nx.add_cycle(G, "CDE")
nx.add_cycle(G, "FIJHG")
nx.add_cycle(G, "GIJ")
G.add_edge("E", "G")
comps = list(nx.biconnected_component_edges(G))
answer = [
[
tuple("GF"),
tuple("FI"),
tuple("IG"),
tuple("IJ"),
tuple("JG"),
tuple("JH"),
tuple("HG"),
],
[tuple("EG")],
[tuple("CD"), tuple("DE"), tuple("CE")],
[tuple("AB"), tuple("BC"), tuple("AC")],
]
assert_components_edges_equal(comps, answer)
def test_biconnected_davis():
D = nx.davis_southern_women_graph()
bcc = list(nx.biconnected_components(D))[0]
assert set(D) == bcc # All nodes in a giant bicomponent
# So no articulation points
assert len(list(nx.articulation_points(D))) == 0
def test_biconnected_karate():
K = nx.karate_club_graph()
answer = [
{
0,
1,
2,
3,
7,
8,
9,
12,
13,
14,
15,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
},
{0, 4, 5, 6, 10, 16},
{0, 11},
]
bcc = list(nx.biconnected_components(K))
assert_components_equal(bcc, answer)
assert set(nx.articulation_points(K)) == {0}
def test_biconnected_eppstein():
# tests from http://www.ics.uci.edu/~eppstein/PADS/Biconnectivity.py
G1 = nx.Graph(
{
0: [1, 2, 5],
1: [0, 5],
2: [0, 3, 4],
3: [2, 4, 5, 6],
4: [2, 3, 5, 6],
5: [0, 1, 3, 4],
6: [3, 4],
}
)
G2 = nx.Graph(
{
0: [2, 5],
1: [3, 8],
2: [0, 3, 5],
3: [1, 2, 6, 8],
4: [7],
5: [0, 2],
6: [3, 8],
7: [4],
8: [1, 3, 6],
}
)
assert nx.is_biconnected(G1)
assert not nx.is_biconnected(G2)
answer_G2 = [{1, 3, 6, 8}, {0, 2, 5}, {2, 3}, {4, 7}]
bcc = list(nx.biconnected_components(G2))
assert_components_equal(bcc, answer_G2)
def test_null_graph():
G = nx.Graph()
assert not nx.is_biconnected(G)
assert list(nx.biconnected_components(G)) == []
assert list(nx.biconnected_component_edges(G)) == []
assert list(nx.articulation_points(G)) == []
def test_connected_raise():
DG = nx.DiGraph()
with pytest.raises(NetworkXNotImplemented):
next(nx.biconnected_components(DG))
with pytest.raises(NetworkXNotImplemented):
next(nx.biconnected_component_edges(DG))
with pytest.raises(NetworkXNotImplemented):
next(nx.articulation_points(DG))
pytest.raises(NetworkXNotImplemented, nx.is_biconnected, DG)

View File

@ -0,0 +1,138 @@
import pytest
import networkx as nx
from networkx import NetworkXNotImplemented
from networkx import convert_node_labels_to_integers as cnlti
from networkx.classes.tests import dispatch_interface
class TestConnected:
@classmethod
def setup_class(cls):
G1 = cnlti(nx.grid_2d_graph(2, 2), first_label=0, ordering="sorted")
G2 = cnlti(nx.lollipop_graph(3, 3), first_label=4, ordering="sorted")
G3 = cnlti(nx.house_graph(), first_label=10, ordering="sorted")
cls.G = nx.union(G1, G2)
cls.G = nx.union(cls.G, G3)
cls.DG = nx.DiGraph([(1, 2), (1, 3), (2, 3)])
cls.grid = cnlti(nx.grid_2d_graph(4, 4), first_label=1)
cls.gc = []
G = nx.DiGraph()
G.add_edges_from(
[
(1, 2),
(2, 3),
(2, 8),
(3, 4),
(3, 7),
(4, 5),
(5, 3),
(5, 6),
(7, 4),
(7, 6),
(8, 1),
(8, 7),
]
)
C = [[3, 4, 5, 7], [1, 2, 8], [6]]
cls.gc.append((G, C))
G = nx.DiGraph()
G.add_edges_from([(1, 2), (1, 3), (1, 4), (4, 2), (3, 4), (2, 3)])
C = [[2, 3, 4], [1]]
cls.gc.append((G, C))
G = nx.DiGraph()
G.add_edges_from([(1, 2), (2, 3), (3, 2), (2, 1)])
C = [[1, 2, 3]]
cls.gc.append((G, C))
# Eppstein's tests
G = nx.DiGraph({0: [1], 1: [2, 3], 2: [4, 5], 3: [4, 5], 4: [6], 5: [], 6: []})
C = [[0], [1], [2], [3], [4], [5], [6]]
cls.gc.append((G, C))
G = nx.DiGraph({0: [1], 1: [2, 3, 4], 2: [0, 3], 3: [4], 4: [3]})
C = [[0, 1, 2], [3, 4]]
cls.gc.append((G, C))
G = nx.DiGraph()
C = []
cls.gc.append((G, C))
def test_connected_components(self):
# Test duplicated below
cc = nx.connected_components
G = self.G
C = {
frozenset([0, 1, 2, 3]),
frozenset([4, 5, 6, 7, 8, 9]),
frozenset([10, 11, 12, 13, 14]),
}
assert {frozenset(g) for g in cc(G)} == C
def test_connected_components_nx_loopback(self):
# This tests the @nx._dispatchable mechanism, treating nx.connected_components
# as if it were a re-implementation from another package.
# Test duplicated from above
cc = nx.connected_components
G = dispatch_interface.convert(self.G)
C = {
frozenset([0, 1, 2, 3]),
frozenset([4, 5, 6, 7, 8, 9]),
frozenset([10, 11, 12, 13, 14]),
}
if "nx_loopback" in nx.config.backends or not nx.config.backends:
# If `nx.config.backends` is empty, then `_dispatchable.__call__` takes a
# "fast path" and does not check graph inputs, so using an unknown backend
# here will still work.
assert {frozenset(g) for g in cc(G)} == C
else:
# This raises, because "nx_loopback" is not registered as a backend.
with pytest.raises(
ImportError, match="'nx_loopback' backend is not installed"
):
cc(G)
def test_number_connected_components(self):
ncc = nx.number_connected_components
assert ncc(self.G) == 3
def test_number_connected_components2(self):
ncc = nx.number_connected_components
assert ncc(self.grid) == 1
def test_connected_components2(self):
cc = nx.connected_components
G = self.grid
C = {frozenset([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])}
assert {frozenset(g) for g in cc(G)} == C
def test_node_connected_components(self):
ncc = nx.node_connected_component
G = self.grid
C = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
assert ncc(G, 1) == C
def test_is_connected(self):
assert nx.is_connected(self.grid)
G = nx.Graph()
G.add_nodes_from([1, 2])
assert not nx.is_connected(G)
def test_connected_raise(self):
with pytest.raises(NetworkXNotImplemented):
next(nx.connected_components(self.DG))
pytest.raises(NetworkXNotImplemented, nx.number_connected_components, self.DG)
pytest.raises(NetworkXNotImplemented, nx.node_connected_component, self.DG, 1)
pytest.raises(NetworkXNotImplemented, nx.is_connected, self.DG)
pytest.raises(nx.NetworkXPointlessConcept, nx.is_connected, nx.Graph())
def test_connected_mutability(self):
G = self.grid
seen = set()
for component in nx.connected_components(G):
assert len(seen & component) == 0
seen.update(component)
component.clear()

View File

@ -0,0 +1,55 @@
from itertools import chain
import pytest
import networkx as nx
class TestIsSemiconnected:
def test_undirected(self):
pytest.raises(nx.NetworkXNotImplemented, nx.is_semiconnected, nx.Graph())
pytest.raises(nx.NetworkXNotImplemented, nx.is_semiconnected, nx.MultiGraph())
def test_empty(self):
pytest.raises(nx.NetworkXPointlessConcept, nx.is_semiconnected, nx.DiGraph())
pytest.raises(
nx.NetworkXPointlessConcept, nx.is_semiconnected, nx.MultiDiGraph()
)
def test_single_node_graph(self):
G = nx.DiGraph()
G.add_node(0)
assert nx.is_semiconnected(G)
def test_path(self):
G = nx.path_graph(100, create_using=nx.DiGraph())
assert nx.is_semiconnected(G)
G.add_edge(100, 99)
assert not nx.is_semiconnected(G)
def test_cycle(self):
G = nx.cycle_graph(100, create_using=nx.DiGraph())
assert nx.is_semiconnected(G)
G = nx.path_graph(100, create_using=nx.DiGraph())
G.add_edge(0, 99)
assert nx.is_semiconnected(G)
def test_tree(self):
G = nx.DiGraph()
G.add_edges_from(
chain.from_iterable([(i, 2 * i + 1), (i, 2 * i + 2)] for i in range(100))
)
assert not nx.is_semiconnected(G)
def test_dumbbell(self):
G = nx.cycle_graph(100, create_using=nx.DiGraph())
G.add_edges_from((i + 100, (i + 1) % 100 + 100) for i in range(100))
assert not nx.is_semiconnected(G) # G is disconnected.
G.add_edge(100, 99)
assert nx.is_semiconnected(G)
def test_alternating_path(self):
G = nx.DiGraph(
chain.from_iterable([(i, i - 1), (i, i + 1)] for i in range(0, 100, 2))
)
assert not nx.is_semiconnected(G)

View File

@ -0,0 +1,193 @@
import pytest
import networkx as nx
from networkx import NetworkXNotImplemented
class TestStronglyConnected:
@classmethod
def setup_class(cls):
cls.gc = []
G = nx.DiGraph()
G.add_edges_from(
[
(1, 2),
(2, 3),
(2, 8),
(3, 4),
(3, 7),
(4, 5),
(5, 3),
(5, 6),
(7, 4),
(7, 6),
(8, 1),
(8, 7),
]
)
C = {frozenset([3, 4, 5, 7]), frozenset([1, 2, 8]), frozenset([6])}
cls.gc.append((G, C))
G = nx.DiGraph()
G.add_edges_from([(1, 2), (1, 3), (1, 4), (4, 2), (3, 4), (2, 3)])
C = {frozenset([2, 3, 4]), frozenset([1])}
cls.gc.append((G, C))
G = nx.DiGraph()
G.add_edges_from([(1, 2), (2, 3), (3, 2), (2, 1)])
C = {frozenset([1, 2, 3])}
cls.gc.append((G, C))
# Eppstein's tests
G = nx.DiGraph({0: [1], 1: [2, 3], 2: [4, 5], 3: [4, 5], 4: [6], 5: [], 6: []})
C = {
frozenset([0]),
frozenset([1]),
frozenset([2]),
frozenset([3]),
frozenset([4]),
frozenset([5]),
frozenset([6]),
}
cls.gc.append((G, C))
G = nx.DiGraph({0: [1], 1: [2, 3, 4], 2: [0, 3], 3: [4], 4: [3]})
C = {frozenset([0, 1, 2]), frozenset([3, 4])}
cls.gc.append((G, C))
def test_tarjan(self):
scc = nx.strongly_connected_components
for G, C in self.gc:
assert {frozenset(g) for g in scc(G)} == C
def test_kosaraju(self):
scc = nx.kosaraju_strongly_connected_components
for G, C in self.gc:
assert {frozenset(g) for g in scc(G)} == C
def test_number_strongly_connected_components(self):
ncc = nx.number_strongly_connected_components
for G, C in self.gc:
assert ncc(G) == len(C)
def test_is_strongly_connected(self):
for G, C in self.gc:
if len(C) == 1:
assert nx.is_strongly_connected(G)
else:
assert not nx.is_strongly_connected(G)
def test_contract_scc1(self):
G = nx.DiGraph()
G.add_edges_from(
[
(1, 2),
(2, 3),
(2, 11),
(2, 12),
(3, 4),
(4, 3),
(4, 5),
(5, 6),
(6, 5),
(6, 7),
(7, 8),
(7, 9),
(7, 10),
(8, 9),
(9, 7),
(10, 6),
(11, 2),
(11, 4),
(11, 6),
(12, 6),
(12, 11),
]
)
scc = list(nx.strongly_connected_components(G))
cG = nx.condensation(G, scc)
# DAG
assert nx.is_directed_acyclic_graph(cG)
# nodes
assert sorted(cG.nodes()) == [0, 1, 2, 3]
# edges
mapping = {}
for i, component in enumerate(scc):
for n in component:
mapping[n] = i
edge = (mapping[2], mapping[3])
assert cG.has_edge(*edge)
edge = (mapping[2], mapping[5])
assert cG.has_edge(*edge)
edge = (mapping[3], mapping[5])
assert cG.has_edge(*edge)
def test_contract_scc_isolate(self):
# Bug found and fixed in [1687].
G = nx.DiGraph()
G.add_edge(1, 2)
G.add_edge(2, 1)
scc = list(nx.strongly_connected_components(G))
cG = nx.condensation(G, scc)
assert list(cG.nodes()) == [0]
assert list(cG.edges()) == []
def test_contract_scc_edge(self):
G = nx.DiGraph()
G.add_edge(1, 2)
G.add_edge(2, 1)
G.add_edge(2, 3)
G.add_edge(3, 4)
G.add_edge(4, 3)
scc = list(nx.strongly_connected_components(G))
cG = nx.condensation(G, scc)
assert sorted(cG.nodes()) == [0, 1]
if 1 in scc[0]:
edge = (0, 1)
else:
edge = (1, 0)
assert list(cG.edges()) == [edge]
def test_condensation_mapping_and_members(self):
G, C = self.gc[1]
C = sorted(C, key=len, reverse=True)
cG = nx.condensation(G)
mapping = cG.graph["mapping"]
assert all(n in G for n in mapping)
assert all(0 == cN for n, cN in mapping.items() if n in C[0])
assert all(1 == cN for n, cN in mapping.items() if n in C[1])
for n, d in cG.nodes(data=True):
assert set(C[n]) == cG.nodes[n]["members"]
def test_null_graph(self):
G = nx.DiGraph()
assert list(nx.strongly_connected_components(G)) == []
assert list(nx.kosaraju_strongly_connected_components(G)) == []
assert len(nx.condensation(G)) == 0
pytest.raises(
nx.NetworkXPointlessConcept, nx.is_strongly_connected, nx.DiGraph()
)
def test_connected_raise(self):
G = nx.Graph()
with pytest.raises(NetworkXNotImplemented):
next(nx.strongly_connected_components(G))
with pytest.raises(NetworkXNotImplemented):
next(nx.kosaraju_strongly_connected_components(G))
pytest.raises(NetworkXNotImplemented, nx.is_strongly_connected, G)
pytest.raises(NetworkXNotImplemented, nx.condensation, G)
strong_cc_methods = (
nx.strongly_connected_components,
nx.kosaraju_strongly_connected_components,
)
@pytest.mark.parametrize("get_components", strong_cc_methods)
def test_connected_mutability(self, get_components):
DG = nx.path_graph(5, create_using=nx.DiGraph)
G = nx.disjoint_union(DG, DG)
seen = set()
for component in get_components(G):
assert len(seen & component) == 0
seen.update(component)
component.clear()

View File

@ -0,0 +1,96 @@
import pytest
import networkx as nx
from networkx import NetworkXNotImplemented
class TestWeaklyConnected:
@classmethod
def setup_class(cls):
cls.gc = []
G = nx.DiGraph()
G.add_edges_from(
[
(1, 2),
(2, 3),
(2, 8),
(3, 4),
(3, 7),
(4, 5),
(5, 3),
(5, 6),
(7, 4),
(7, 6),
(8, 1),
(8, 7),
]
)
C = [[3, 4, 5, 7], [1, 2, 8], [6]]
cls.gc.append((G, C))
G = nx.DiGraph()
G.add_edges_from([(1, 2), (1, 3), (1, 4), (4, 2), (3, 4), (2, 3)])
C = [[2, 3, 4], [1]]
cls.gc.append((G, C))
G = nx.DiGraph()
G.add_edges_from([(1, 2), (2, 3), (3, 2), (2, 1)])
C = [[1, 2, 3]]
cls.gc.append((G, C))
# Eppstein's tests
G = nx.DiGraph({0: [1], 1: [2, 3], 2: [4, 5], 3: [4, 5], 4: [6], 5: [], 6: []})
C = [[0], [1], [2], [3], [4], [5], [6]]
cls.gc.append((G, C))
G = nx.DiGraph({0: [1], 1: [2, 3, 4], 2: [0, 3], 3: [4], 4: [3]})
C = [[0, 1, 2], [3, 4]]
cls.gc.append((G, C))
def test_weakly_connected_components(self):
for G, C in self.gc:
U = G.to_undirected()
w = {frozenset(g) for g in nx.weakly_connected_components(G)}
c = {frozenset(g) for g in nx.connected_components(U)}
assert w == c
def test_number_weakly_connected_components(self):
for G, C in self.gc:
U = G.to_undirected()
w = nx.number_weakly_connected_components(G)
c = nx.number_connected_components(U)
assert w == c
def test_is_weakly_connected(self):
for G, C in self.gc:
U = G.to_undirected()
assert nx.is_weakly_connected(G) == nx.is_connected(U)
def test_null_graph(self):
G = nx.DiGraph()
assert list(nx.weakly_connected_components(G)) == []
assert nx.number_weakly_connected_components(G) == 0
with pytest.raises(nx.NetworkXPointlessConcept):
next(nx.is_weakly_connected(G))
def test_connected_raise(self):
G = nx.Graph()
with pytest.raises(NetworkXNotImplemented):
next(nx.weakly_connected_components(G))
pytest.raises(NetworkXNotImplemented, nx.number_weakly_connected_components, G)
pytest.raises(NetworkXNotImplemented, nx.is_weakly_connected, G)
def test_connected_mutability(self):
DG = nx.path_graph(5, create_using=nx.DiGraph)
G = nx.disjoint_union(DG, DG)
seen = set()
for component in nx.weakly_connected_components(G):
assert len(seen & component) == 0
seen.update(component)
component.clear()
def test_is_weakly_connected_empty_graph_raises():
G = nx.DiGraph()
with pytest.raises(nx.NetworkXPointlessConcept, match="Connectivity is undefined"):
nx.is_weakly_connected(G)

View File

@ -0,0 +1,197 @@
"""Weakly connected components."""
import networkx as nx
from networkx.utils.decorators import not_implemented_for
__all__ = [
"number_weakly_connected_components",
"weakly_connected_components",
"is_weakly_connected",
]
@not_implemented_for("undirected")
@nx._dispatchable
def weakly_connected_components(G):
"""Generate weakly connected components of G.
Parameters
----------
G : NetworkX graph
A directed graph
Returns
-------
comp : generator of sets
A generator of sets of nodes, one for each weakly connected
component of G.
Raises
------
NetworkXNotImplemented
If G is undirected.
Examples
--------
Generate a sorted list of weakly connected components, largest first.
>>> G = nx.path_graph(4, create_using=nx.DiGraph())
>>> nx.add_path(G, [10, 11, 12])
>>> [
... len(c)
... for c in sorted(nx.weakly_connected_components(G), key=len, reverse=True)
... ]
[4, 3]
If you only want the largest component, it's more efficient to
use max instead of sort:
>>> largest_cc = max(nx.weakly_connected_components(G), key=len)
See Also
--------
connected_components
strongly_connected_components
Notes
-----
For directed graphs only.
"""
seen = set()
n = len(G) # must be outside the loop to avoid performance hit with graph views
for v in G:
if v not in seen:
c = set(_plain_bfs(G, n, v))
seen.update(c)
yield c
@not_implemented_for("undirected")
@nx._dispatchable
def number_weakly_connected_components(G):
"""Returns the number of weakly connected components in G.
Parameters
----------
G : NetworkX graph
A directed graph.
Returns
-------
n : integer
Number of weakly connected components
Raises
------
NetworkXNotImplemented
If G is undirected.
Examples
--------
>>> G = nx.DiGraph([(0, 1), (2, 1), (3, 4)])
>>> nx.number_weakly_connected_components(G)
2
See Also
--------
weakly_connected_components
number_connected_components
number_strongly_connected_components
Notes
-----
For directed graphs only.
"""
return sum(1 for wcc in weakly_connected_components(G))
@not_implemented_for("undirected")
@nx._dispatchable
def is_weakly_connected(G):
"""Test directed graph for weak connectivity.
A directed graph is weakly connected if and only if the graph
is connected when the direction of the edge between nodes is ignored.
Note that if a graph is strongly connected (i.e. the graph is connected
even when we account for directionality), it is by definition weakly
connected as well.
Parameters
----------
G : NetworkX Graph
A directed graph.
Returns
-------
connected : bool
True if the graph is weakly connected, False otherwise.
Raises
------
NetworkXNotImplemented
If G is undirected.
Examples
--------
>>> G = nx.DiGraph([(0, 1), (2, 1)])
>>> G.add_node(3)
>>> nx.is_weakly_connected(G) # node 3 is not connected to the graph
False
>>> G.add_edge(2, 3)
>>> nx.is_weakly_connected(G)
True
See Also
--------
is_strongly_connected
is_semiconnected
is_connected
is_biconnected
weakly_connected_components
Notes
-----
For directed graphs only.
"""
if len(G) == 0:
raise nx.NetworkXPointlessConcept(
"""Connectivity is undefined for the null graph."""
)
return len(next(weakly_connected_components(G))) == len(G)
def _plain_bfs(G, n, source):
"""A fast BFS node generator
The direction of the edge between nodes is ignored.
For directed graphs only.
"""
Gsucc = G._succ
Gpred = G._pred
seen = {source}
nextlevel = [source]
yield source
while nextlevel:
thislevel = nextlevel
nextlevel = []
for v in thislevel:
for w in Gsucc[v]:
if w not in seen:
seen.add(w)
nextlevel.append(w)
yield w
for w in Gpred[v]:
if w not in seen:
seen.add(w)
nextlevel.append(w)
yield w
if len(seen) == n:
return