Fixed database typo and removed unnecessary class identifier.

This commit is contained in:
Batuhan Berk Başoğlu 2020-10-14 10:10:37 -04:00
parent 00ad49a143
commit 45fb349a7d
5098 changed files with 952558 additions and 85 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,111 @@
"""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")
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")
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")
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,380 @@
"""Biconnected components and articulation points."""
from itertools import chain
from networkx.utils.decorators import not_implemented_for
__all__ = [
"biconnected_components",
"biconnected_component_edges",
"is_biconnected",
"articulation_points",
]
@not_implemented_for("directed")
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
"""
bcc = list(biconnected_components(G))
if len(bcc) == 1:
return len(bcc[0]) == len(G)
return False # Multiple bicomponents or No bicomponents (empty graph?)
# if len(bcc) == 0: # No bicomponents (it could be an empty graph)
# return False
# return len(bcc[0]) == len(G)
@not_implemented_for("directed")
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")
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.
See Also
--------
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.
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
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")
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]))]
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_stack.append((parent, child))
else:
low[child] = discovery[child] = len(discovery)
visited.add(child)
stack.append((parent, child, iter(G[child])))
if components:
edge_stack.append((parent, child))
except StopIteration:
stack.pop()
if len(stack) > 1:
if low[parent] >= discovery[grandparent]:
if components:
ind = edge_stack.index((grandparent, parent))
yield edge_stack[ind:]
edge_stack = 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_stack.index((grandparent, parent))
yield 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,187 @@
"""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")
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()
for v in G:
if v not in seen:
c = _plain_bfs(G, v)
seen.update(c)
yield c
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
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")
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.
"""
if len(G) == 0:
raise nx.NetworkXPointlessConcept(
"Connectivity is undefined ", "for the null graph."
)
return sum(1 for node in _plain_bfs(G, arbitrary_element(G))) == len(G)
@not_implemented_for("directed")
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.
See Also
--------
connected_components
Notes
-----
For undirected graphs only.
"""
return _plain_bfs(G, n)
def _plain_bfs(G, source):
"""A fast BFS node generator"""
G_adj = G.adj
seen = set()
nextlevel = {source}
while nextlevel:
thislevel = nextlevel
nextlevel = set()
for v in thislevel:
if v not in seen:
seen.add(v)
nextlevel.update(G_adj[v])
return seen

View file

@ -0,0 +1,64 @@
"""Semiconnectedness."""
import networkx as nx
from networkx.utils import not_implemented_for, pairwise
__all__ = ["is_semiconnected"]
@not_implemented_for("undirected")
def is_semiconnected(G, topo_order=None):
"""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.
Parameters
----------
G : NetworkX graph
A directed graph.
topo_order: list or tuple, optional
A topological order for G (if None, the function will compute one)
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
G = nx.condensation(G)
if topo_order is None:
topo_order = nx.topological_sort(G)
return all(G.has_edge(u, v) for u, v in pairwise(topo_order))

View file

@ -0,0 +1,398 @@
"""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",
"strongly_connected_components_recursive",
"kosaraju_strongly_connected_components",
"condensation",
]
@not_implemented_for("undirected")
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
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 G[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")
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 genrator 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}
yield new
seen.update(new)
@not_implemented_for("undirected")
def strongly_connected_components_recursive(G):
"""Generate nodes in strongly connected components of graph.
Recursive version of algorithm.
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_recursive(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_recursive(G), key=len)
To create the induced subgraph of the components use:
>>> S = [G.subgraph(c).copy() for c in nx.weakly_connected_components(G)]
See Also
--------
connected_components
Notes
-----
Uses Tarjan's algorithm[1]_ with Nuutila's modifications[2]_.
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)..
"""
def visit(v, cnt):
root[v] = cnt
visited[v] = cnt
cnt += 1
stack.append(v)
for w in G[v]:
if w not in visited:
yield from visit(w, cnt)
if w not in component:
root[v] = min(root[v], root[w])
if root[v] == visited[v]:
component[v] = root[v]
tmpc = {v} # hold nodes in this component
while stack[-1] != v:
w = stack.pop()
component[w] = root[v]
tmpc.add(w)
stack.remove(v)
yield tmpc
visited = {}
component = {}
root = {}
cnt = 0
stack = []
for source in G:
if source not in visited:
yield from visit(source, cnt)
@not_implemented_for("undirected")
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.
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")
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.
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(list(strongly_connected_components(G))[0]) == len(G)
@not_implemented_for("undirected")
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.
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,68 @@
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()
pytest.raises(NetworkXNotImplemented, 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,244 @@
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
# 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()
pytest.raises(NetworkXNotImplemented, nx.biconnected_components, DG)
pytest.raises(NetworkXNotImplemented, nx.biconnected_component_edges, DG)
pytest.raises(NetworkXNotImplemented, nx.articulation_points, DG)
pytest.raises(NetworkXNotImplemented, nx.is_biconnected, DG)

View file

@ -0,0 +1,111 @@
import pytest
import networkx as nx
from networkx import convert_node_labels_to_integers as cnlti
from networkx import NetworkXNotImplemented
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):
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_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):
pytest.raises(NetworkXNotImplemented, 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,53 @@
from itertools import chain
import networkx as nx
import pytest
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,216 @@
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_tarjan_recursive(self):
scc = nx.strongly_connected_components_recursive
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 list(nx.strongly_connected_components_recursive(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()
pytest.raises(NetworkXNotImplemented, nx.strongly_connected_components, G)
pytest.raises(
NetworkXNotImplemented, nx.kosaraju_strongly_connected_components, G
)
pytest.raises(
NetworkXNotImplemented, nx.strongly_connected_components_recursive, G
)
pytest.raises(NetworkXNotImplemented, nx.is_strongly_connected, G)
pytest.raises(
nx.NetworkXPointlessConcept, nx.is_strongly_connected, nx.DiGraph()
)
pytest.raises(NetworkXNotImplemented, nx.condensation, G)
# Commented out due to variability on Travis-CI hardware/operating systems
# def test_linear_time(self):
# # See Issue #2831
# count = 100 # base case
# dg = nx.DiGraph()
# dg.add_nodes_from([0, 1])
# for i in range(2, count):
# dg.add_node(i)
# dg.add_edge(i, 1)
# dg.add_edge(0, i)
# t = time.time()
# ret = tuple(nx.strongly_connected_components(dg))
# dt = time.time() - t
#
# count = 200
# dg = nx.DiGraph()
# dg.add_nodes_from([0, 1])
# for i in range(2, count):
# dg.add_node(i)
# dg.add_edge(i, 1)
# dg.add_edge(0, i)
# t = time.time()
# ret = tuple(nx.strongly_connected_components(dg))
# dt2 = time.time() - t
# assert_less(dt2, dt * 2.3) # should be 2 times longer for this graph

View file

@ -0,0 +1,78 @@
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
pytest.raises(nx.NetworkXPointlessConcept, nx.is_weakly_connected, G)
def test_connected_raise(self):
G = nx.Graph()
pytest.raises(NetworkXNotImplemented, nx.weakly_connected_components, G)
pytest.raises(NetworkXNotImplemented, nx.number_weakly_connected_components, G)
pytest.raises(NetworkXNotImplemented, nx.is_weakly_connected, G)

View file

@ -0,0 +1,168 @@
"""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")
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()
for v in G:
if v not in seen:
c = set(_plain_bfs(G, v))
yield c
seen.update(c)
@not_implemented_for("undirected")
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.
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")
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.
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(list(weakly_connected_components(G))[0]) == len(G)
def _plain_bfs(G, 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 = set()
nextlevel = {source}
while nextlevel:
thislevel = nextlevel
nextlevel = set()
for v in thislevel:
if v not in seen:
yield v
seen.add(v)
nextlevel.update(Gsucc[v])
nextlevel.update(Gpred[v])