Restore 0.1.5 version from stash

This commit is contained in:
liaibo
2025-12-08 19:56:24 +08:00
parent de189e938d
commit 8db3f4e32d
8578 changed files with 2703426 additions and 217 deletions
@@ -0,0 +1,23 @@
import networkx as nx
def test_is_at_free():
is_at_free = nx.asteroidal.is_at_free
cycle = nx.cycle_graph(6)
assert not is_at_free(cycle)
path = nx.path_graph(6)
assert is_at_free(path)
small_graph = nx.complete_graph(2)
assert is_at_free(small_graph)
petersen = nx.petersen_graph()
assert not is_at_free(petersen)
clique = nx.complete_graph(6)
assert is_at_free(clique)
line_clique = nx.line_graph(clique)
assert not is_at_free(line_clique)
@@ -0,0 +1,154 @@
"""Unit tests for the :mod:`networkx.algorithms.boundary` module."""
from itertools import combinations
import pytest
import networkx as nx
from networkx import convert_node_labels_to_integers as cnlti
from networkx.utils import edges_equal
class TestNodeBoundary:
"""Unit tests for the :func:`~networkx.node_boundary` function."""
def test_null_graph(self):
"""Tests that the null graph has empty node boundaries."""
null = nx.null_graph()
assert nx.node_boundary(null, []) == set()
assert nx.node_boundary(null, [], []) == set()
assert nx.node_boundary(null, [1, 2, 3]) == set()
assert nx.node_boundary(null, [1, 2, 3], [4, 5, 6]) == set()
assert nx.node_boundary(null, [1, 2, 3], [3, 4, 5]) == set()
def test_path_graph(self):
P10 = cnlti(nx.path_graph(10), first_label=1)
assert nx.node_boundary(P10, []) == set()
assert nx.node_boundary(P10, [], []) == set()
assert nx.node_boundary(P10, [1, 2, 3]) == {4}
assert nx.node_boundary(P10, [4, 5, 6]) == {3, 7}
assert nx.node_boundary(P10, [3, 4, 5, 6, 7]) == {2, 8}
assert nx.node_boundary(P10, [8, 9, 10]) == {7}
assert nx.node_boundary(P10, [4, 5, 6], [9, 10]) == set()
def test_complete_graph(self):
K10 = cnlti(nx.complete_graph(10), first_label=1)
assert nx.node_boundary(K10, []) == set()
assert nx.node_boundary(K10, [], []) == set()
assert nx.node_boundary(K10, [1, 2, 3]) == {4, 5, 6, 7, 8, 9, 10}
assert nx.node_boundary(K10, [4, 5, 6]) == {1, 2, 3, 7, 8, 9, 10}
assert nx.node_boundary(K10, [3, 4, 5, 6, 7]) == {1, 2, 8, 9, 10}
assert nx.node_boundary(K10, [4, 5, 6], []) == set()
assert nx.node_boundary(K10, K10) == set()
assert nx.node_boundary(K10, [1, 2, 3], [3, 4, 5]) == {4, 5}
def test_petersen(self):
"""Check boundaries in the petersen graph
cheeger(G,k)=min(|bdy(S)|/|S| for |S|=k, 0<k<=|V(G)|/2)
"""
def cheeger(G, k):
return min(len(nx.node_boundary(G, nn)) / k for nn in combinations(G, k))
P = nx.petersen_graph()
assert cheeger(P, 1) == pytest.approx(3.00, abs=1e-2)
assert cheeger(P, 2) == pytest.approx(2.00, abs=1e-2)
assert cheeger(P, 3) == pytest.approx(1.67, abs=1e-2)
assert cheeger(P, 4) == pytest.approx(1.00, abs=1e-2)
assert cheeger(P, 5) == pytest.approx(0.80, abs=1e-2)
def test_directed(self):
"""Tests the node boundary of a directed graph."""
G = nx.DiGraph([(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)])
S = {0, 1}
boundary = nx.node_boundary(G, S)
expected = {2}
assert boundary == expected
def test_multigraph(self):
"""Tests the node boundary of a multigraph."""
G = nx.MultiGraph(list(nx.cycle_graph(5).edges()) * 2)
S = {0, 1}
boundary = nx.node_boundary(G, S)
expected = {2, 4}
assert boundary == expected
def test_multidigraph(self):
"""Tests the edge boundary of a multidigraph."""
edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]
G = nx.MultiDiGraph(edges * 2)
S = {0, 1}
boundary = nx.node_boundary(G, S)
expected = {2}
assert boundary == expected
class TestEdgeBoundary:
"""Unit tests for the :func:`~networkx.edge_boundary` function."""
def test_null_graph(self):
null = nx.null_graph()
assert list(nx.edge_boundary(null, [])) == []
assert list(nx.edge_boundary(null, [], [])) == []
assert list(nx.edge_boundary(null, [1, 2, 3])) == []
assert list(nx.edge_boundary(null, [1, 2, 3], [4, 5, 6])) == []
assert list(nx.edge_boundary(null, [1, 2, 3], [3, 4, 5])) == []
def test_path_graph(self):
P10 = cnlti(nx.path_graph(10), first_label=1)
assert list(nx.edge_boundary(P10, [])) == []
assert list(nx.edge_boundary(P10, [], [])) == []
assert list(nx.edge_boundary(P10, [1, 2, 3])) == [(3, 4)]
assert sorted(nx.edge_boundary(P10, [4, 5, 6])) == [(4, 3), (6, 7)]
assert sorted(nx.edge_boundary(P10, [3, 4, 5, 6, 7])) == [(3, 2), (7, 8)]
assert list(nx.edge_boundary(P10, [8, 9, 10])) == [(8, 7)]
assert sorted(nx.edge_boundary(P10, [4, 5, 6], [9, 10])) == []
assert list(nx.edge_boundary(P10, [1, 2, 3], [3, 4, 5])) == [(2, 3), (3, 4)]
def test_complete_graph(self):
K10 = cnlti(nx.complete_graph(10), first_label=1)
def ilen(iterable):
return sum(1 for i in iterable)
assert list(nx.edge_boundary(K10, [])) == []
assert list(nx.edge_boundary(K10, [], [])) == []
assert ilen(nx.edge_boundary(K10, [1, 2, 3])) == 21
assert ilen(nx.edge_boundary(K10, [4, 5, 6, 7])) == 24
assert ilen(nx.edge_boundary(K10, [3, 4, 5, 6, 7])) == 25
assert ilen(nx.edge_boundary(K10, [8, 9, 10])) == 21
assert edges_equal(
nx.edge_boundary(K10, [4, 5, 6], [9, 10]),
[(4, 9), (4, 10), (5, 9), (5, 10), (6, 9), (6, 10)],
)
assert edges_equal(
nx.edge_boundary(K10, [1, 2, 3], [3, 4, 5]),
[(1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5)],
)
def test_directed(self):
"""Tests the edge boundary of a directed graph."""
G = nx.DiGraph([(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)])
S = {0, 1}
boundary = list(nx.edge_boundary(G, S))
expected = [(1, 2)]
assert boundary == expected
def test_multigraph(self):
"""Tests the edge boundary of a multigraph."""
G = nx.MultiGraph(list(nx.cycle_graph(5).edges()) * 2)
S = {0, 1}
boundary = list(nx.edge_boundary(G, S))
expected = [(0, 4), (0, 4), (1, 2), (1, 2)]
assert boundary == expected
def test_multidigraph(self):
"""Tests the edge boundary of a multidigraph."""
edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]
G = nx.MultiDiGraph(edges * 2)
S = {0, 1}
boundary = list(nx.edge_boundary(G, S))
expected = [(1, 2), (1, 2)]
assert boundary == expected
@@ -0,0 +1,144 @@
"""Unit tests for bridge-finding algorithms."""
import pytest
import networkx as nx
class TestBridges:
"""Unit tests for the bridge-finding function."""
def test_single_bridge(self):
edges = [
# DFS tree edges.
(1, 2),
(2, 3),
(3, 4),
(3, 5),
(5, 6),
(6, 7),
(7, 8),
(5, 9),
(9, 10),
# Nontree edges.
(1, 3),
(1, 4),
(2, 5),
(5, 10),
(6, 8),
]
G = nx.Graph(edges)
source = 1
bridges = list(nx.bridges(G, source))
assert bridges == [(5, 6)]
def test_barbell_graph(self):
# The (3, 0) barbell graph has two triangles joined by a single edge.
G = nx.barbell_graph(3, 0)
source = 0
bridges = list(nx.bridges(G, source))
assert bridges == [(2, 3)]
def test_multiedge_bridge(self):
edges = [
(0, 1),
(0, 2),
(1, 2),
(1, 2),
(2, 3),
(3, 4),
(3, 4),
]
G = nx.MultiGraph(edges)
assert list(nx.bridges(G)) == [(2, 3)]
class TestHasBridges:
"""Unit tests for the has bridges function."""
def test_single_bridge(self):
edges = [
# DFS tree edges.
(1, 2),
(2, 3),
(3, 4),
(3, 5),
(5, 6), # The only bridge edge
(6, 7),
(7, 8),
(5, 9),
(9, 10),
# Nontree edges.
(1, 3),
(1, 4),
(2, 5),
(5, 10),
(6, 8),
]
G = nx.Graph(edges)
assert nx.has_bridges(G) # Default root
assert nx.has_bridges(G, root=1) # arbitrary root in G
def test_has_bridges_raises_root_not_in_G(self):
G = nx.Graph()
G.add_nodes_from([1, 2, 3])
with pytest.raises(nx.NodeNotFound):
nx.has_bridges(G, root=6)
def test_multiedge_bridge(self):
edges = [
(0, 1),
(0, 2),
(1, 2),
(1, 2),
(2, 3),
(3, 4),
(3, 4),
]
G = nx.MultiGraph(edges)
assert nx.has_bridges(G)
# Make every edge a multiedge
G.add_edges_from([(0, 1), (0, 2), (2, 3)])
assert not nx.has_bridges(G)
def test_bridges_multiple_components(self):
G = nx.Graph()
nx.add_path(G, [0, 1, 2]) # One connected component
nx.add_path(G, [4, 5, 6]) # Another connected component
assert list(nx.bridges(G, root=4)) == [(4, 5), (5, 6)]
class TestLocalBridges:
"""Unit tests for the local_bridge function."""
@classmethod
def setup_class(cls):
cls.BB = nx.barbell_graph(4, 0)
cls.square = nx.cycle_graph(4)
cls.tri = nx.cycle_graph(3)
def test_nospan(self):
expected = {(3, 4), (4, 3)}
assert next(nx.local_bridges(self.BB, with_span=False)) in expected
assert set(nx.local_bridges(self.square, with_span=False)) == self.square.edges
assert list(nx.local_bridges(self.tri, with_span=False)) == []
def test_no_weight(self):
inf = float("inf")
expected = {(3, 4, inf), (4, 3, inf)}
assert next(nx.local_bridges(self.BB)) in expected
expected = {(u, v, 3) for u, v in self.square.edges}
assert set(nx.local_bridges(self.square)) == expected
assert list(nx.local_bridges(self.tri)) == []
def test_weight(self):
inf = float("inf")
G = self.square.copy()
G.edges[1, 2]["weight"] = 2
expected = {(u, v, 5 - wt) for u, v, wt in G.edges(data="weight", default=1)}
assert set(nx.local_bridges(G, weight="weight")) == expected
expected = {(u, v, 6) for u, v in G.edges}
lb = nx.local_bridges(G, weight=lambda u, v, d: 2)
assert set(lb) == expected
@@ -0,0 +1,109 @@
"""Unit tests for the broadcasting module."""
import math
import pytest
import networkx as nx
def test_example_tree_broadcast():
"""
Test the BROADCAST algorithm on the example in the paper titled: "Information Dissemination in Trees"
"""
edge_list = [
(0, 1),
(1, 2),
(2, 7),
(3, 4),
(5, 4),
(4, 7),
(6, 7),
(7, 9),
(8, 9),
(9, 13),
(13, 14),
(14, 15),
(14, 16),
(14, 17),
(13, 11),
(11, 10),
(11, 12),
(13, 18),
(18, 19),
(18, 20),
]
G = nx.Graph(edge_list)
b_T, b_C = nx.tree_broadcast_center(G)
assert b_T == 6
assert b_C == {13, 9}
# test broadcast time from specific vertex
assert nx.tree_broadcast_time(G, 17) == 8
assert nx.tree_broadcast_time(G, 3) == 9
# test broadcast time of entire tree
assert nx.tree_broadcast_time(G) == 10
@pytest.mark.parametrize("n", range(2, 12))
def test_path_broadcast(n):
G = nx.path_graph(n)
b_T, b_C = nx.tree_broadcast_center(G)
assert b_T == math.ceil(n / 2)
assert b_C == {
math.ceil(n / 2),
n // 2,
math.ceil(n / 2 - 1),
n // 2 - 1,
}
assert nx.tree_broadcast_time(G) == n - 1
def test_empty_graph_broadcast():
H = nx.empty_graph(1)
b_T, b_C = nx.tree_broadcast_center(H)
assert b_T == 0
assert b_C == {0}
assert nx.tree_broadcast_time(H) == 0
@pytest.mark.parametrize("n", range(4, 12))
def test_star_broadcast(n):
G = nx.star_graph(n)
b_T, b_C = nx.tree_broadcast_center(G)
assert b_T == n
assert b_C == set(G.nodes())
assert nx.tree_broadcast_time(G) == b_T
@pytest.mark.parametrize("n", range(2, 8))
def test_binomial_tree_broadcast(n):
G = nx.binomial_tree(n)
b_T, b_C = nx.tree_broadcast_center(G)
assert b_T == n
assert b_C == {0, 2 ** (n - 1)}
assert nx.tree_broadcast_time(G) == 2 * n - 1
@pytest.mark.parametrize("fn", [nx.tree_broadcast_center, nx.tree_broadcast_time])
@pytest.mark.parametrize("graph_type", [nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph])
def test_raises_graph_type(fn, graph_type):
"""Check that broadcast functions properly raise for directed and multigraph types."""
G = nx.path_graph(5, create_using=graph_type)
with pytest.raises(nx.NetworkXNotImplemented, match=r"not implemented for"):
fn(G)
@pytest.mark.parametrize("fn", [nx.tree_broadcast_center, nx.tree_broadcast_time])
@pytest.mark.parametrize("gen", [nx.empty_graph, nx.cycle_graph])
def test_raises_not_tree(fn, gen):
"""Check that broadcast functions properly raise for nontree graphs."""
G = gen(5)
with pytest.raises(nx.NotATree, match=r"not a tree"):
fn(G)
def test_raises_node_not_in_G():
"""Check that `tree_broadcast_time` properly raises for invalid nodes."""
G = nx.path_graph(5)
with pytest.raises(nx.NodeNotFound, match=r"node.*not in G"):
nx.tree_broadcast_time(G, node=10)
@@ -0,0 +1,136 @@
"""Unit tests for the chain decomposition functions."""
from itertools import cycle, islice
import pytest
import networkx as nx
def cycles(seq):
"""Yields cyclic permutations of the given sequence.
For example::
>>> list(cycles("abc"))
[('a', 'b', 'c'), ('b', 'c', 'a'), ('c', 'a', 'b')]
"""
n = len(seq)
cycled_seq = cycle(seq)
for x in seq:
yield tuple(islice(cycled_seq, n))
next(cycled_seq)
def cyclic_equals(seq1, seq2):
"""Decide whether two sequences are equal up to cyclic permutations.
For example::
>>> cyclic_equals("xyz", "zxy")
True
>>> cyclic_equals("xyz", "zyx")
False
"""
# Cast seq2 to a tuple since `cycles()` yields tuples.
seq2 = tuple(seq2)
return any(x == tuple(seq2) for x in cycles(seq1))
class TestChainDecomposition:
"""Unit tests for the chain decomposition function."""
def assertContainsChain(self, chain, expected):
# A cycle could be expressed in two different orientations, one
# forward and one backward, so we need to check for cyclic
# equality in both orientations.
reversed_chain = list(reversed([tuple(reversed(e)) for e in chain]))
for candidate in expected:
if cyclic_equals(chain, candidate):
break
if cyclic_equals(reversed_chain, candidate):
break
else:
self.fail("chain not found")
def test_decomposition(self):
edges = [
# DFS tree edges.
(1, 2),
(2, 3),
(3, 4),
(3, 5),
(5, 6),
(6, 7),
(7, 8),
(5, 9),
(9, 10),
# Nontree edges.
(1, 3),
(1, 4),
(2, 5),
(5, 10),
(6, 8),
]
G = nx.Graph(edges)
expected = [
[(1, 3), (3, 2), (2, 1)],
[(1, 4), (4, 3)],
[(2, 5), (5, 3)],
[(5, 10), (10, 9), (9, 5)],
[(6, 8), (8, 7), (7, 6)],
]
chains = list(nx.chain_decomposition(G, root=1))
assert len(chains) == len(expected)
def test_barbell_graph(self):
# The (3, 0) barbell graph has two triangles joined by a single edge.
G = nx.barbell_graph(3, 0)
chains = list(nx.chain_decomposition(G, root=0))
expected = [[(0, 1), (1, 2), (2, 0)], [(3, 4), (4, 5), (5, 3)]]
assert len(chains) == len(expected)
for chain in chains:
self.assertContainsChain(chain, expected)
def test_disconnected_graph(self):
"""Test for a graph with multiple connected components."""
G = nx.barbell_graph(3, 0)
H = nx.barbell_graph(3, 0)
mapping = dict(zip(range(6), "abcdef"))
nx.relabel_nodes(H, mapping, copy=False)
G = nx.union(G, H)
chains = list(nx.chain_decomposition(G))
expected = [
[(0, 1), (1, 2), (2, 0)],
[(3, 4), (4, 5), (5, 3)],
[("a", "b"), ("b", "c"), ("c", "a")],
[("d", "e"), ("e", "f"), ("f", "d")],
]
assert len(chains) == len(expected)
for chain in chains:
self.assertContainsChain(chain, expected)
def test_disconnected_graph_root_node(self):
"""Test for a single component of a disconnected graph."""
G = nx.barbell_graph(3, 0)
H = nx.barbell_graph(3, 0)
mapping = dict(zip(range(6), "abcdef"))
nx.relabel_nodes(H, mapping, copy=False)
G = nx.union(G, H)
chains = list(nx.chain_decomposition(G, root="a"))
expected = [
[("a", "b"), ("b", "c"), ("c", "a")],
[("d", "e"), ("e", "f"), ("f", "d")],
]
assert len(chains) == len(expected)
for chain in chains:
self.assertContainsChain(chain, expected)
def test_chain_decomposition_root_not_in_G(self):
"""Test chain decomposition when root is not in graph"""
G = nx.Graph()
G.add_nodes_from([1, 2, 3])
with pytest.raises(nx.NodeNotFound):
nx.has_bridges(G, root=6)
@@ -0,0 +1,129 @@
import pytest
import networkx as nx
class TestMCS:
@classmethod
def setup_class(cls):
# simple graph
connected_chordal_G = nx.Graph()
connected_chordal_G.add_edges_from(
[
(1, 2),
(1, 3),
(2, 3),
(2, 4),
(3, 4),
(3, 5),
(3, 6),
(4, 5),
(4, 6),
(5, 6),
]
)
cls.connected_chordal_G = connected_chordal_G
chordal_G = nx.Graph()
chordal_G.add_edges_from(
[
(1, 2),
(1, 3),
(2, 3),
(2, 4),
(3, 4),
(3, 5),
(3, 6),
(4, 5),
(4, 6),
(5, 6),
(7, 8),
]
)
chordal_G.add_node(9)
cls.chordal_G = chordal_G
non_chordal_G = nx.Graph()
non_chordal_G.add_edges_from([(1, 2), (1, 3), (2, 4), (2, 5), (3, 4), (3, 5)])
cls.non_chordal_G = non_chordal_G
self_loop_G = nx.Graph()
self_loop_G.add_edges_from([(1, 1)])
cls.self_loop_G = self_loop_G
@pytest.mark.parametrize("G", (nx.DiGraph(), nx.MultiGraph(), nx.MultiDiGraph()))
def test_is_chordal_not_implemented(self, G):
with pytest.raises(nx.NetworkXNotImplemented):
nx.is_chordal(G)
def test_is_chordal(self):
assert not nx.is_chordal(self.non_chordal_G)
assert nx.is_chordal(self.chordal_G)
assert nx.is_chordal(self.connected_chordal_G)
assert nx.is_chordal(nx.Graph())
assert nx.is_chordal(nx.complete_graph(3))
assert nx.is_chordal(nx.cycle_graph(3))
assert not nx.is_chordal(nx.cycle_graph(5))
assert nx.is_chordal(self.self_loop_G)
def test_induced_nodes(self):
G = nx.generators.classic.path_graph(10)
Induced_nodes = nx.find_induced_nodes(G, 1, 9, 2)
assert Induced_nodes == {1, 2, 3, 4, 5, 6, 7, 8, 9}
pytest.raises(
nx.NetworkXTreewidthBoundExceeded, nx.find_induced_nodes, G, 1, 9, 1
)
Induced_nodes = nx.find_induced_nodes(self.chordal_G, 1, 6)
assert Induced_nodes == {1, 2, 4, 6}
pytest.raises(nx.NetworkXError, nx.find_induced_nodes, self.non_chordal_G, 1, 5)
def test_graph_treewidth(self):
with pytest.raises(nx.NetworkXError, match="Input graph is not chordal"):
nx.chordal_graph_treewidth(self.non_chordal_G)
def test_chordal_find_cliques(self):
cliques = {
frozenset([9]),
frozenset([7, 8]),
frozenset([1, 2, 3]),
frozenset([2, 3, 4]),
frozenset([3, 4, 5, 6]),
}
assert set(nx.chordal_graph_cliques(self.chordal_G)) == cliques
with pytest.raises(nx.NetworkXError, match="Input graph is not chordal"):
set(nx.chordal_graph_cliques(self.non_chordal_G))
with pytest.raises(nx.NetworkXError, match="Input graph is not chordal"):
set(nx.chordal_graph_cliques(self.self_loop_G))
def test_chordal_find_cliques_path(self):
G = nx.path_graph(10)
cliqueset = nx.chordal_graph_cliques(G)
for u, v in G.edges():
assert frozenset([u, v]) in cliqueset or frozenset([v, u]) in cliqueset
def test_chordal_find_cliquesCC(self):
cliques = {frozenset([1, 2, 3]), frozenset([2, 3, 4]), frozenset([3, 4, 5, 6])}
cgc = nx.chordal_graph_cliques
assert set(cgc(self.connected_chordal_G)) == cliques
def test_complete_to_chordal_graph(self):
fgrg = nx.fast_gnp_random_graph
test_graphs = [
nx.barbell_graph(6, 2),
nx.cycle_graph(15),
nx.wheel_graph(20),
nx.grid_graph([10, 4]),
nx.ladder_graph(15),
nx.star_graph(5),
nx.bull_graph(),
fgrg(20, 0.3, seed=1),
]
for G in test_graphs:
H, a = nx.complete_to_chordal_graph(G)
assert nx.is_chordal(H)
assert len(a) == H.number_of_nodes()
if nx.is_chordal(G):
assert G.number_of_edges() == H.number_of_edges()
assert set(a.values()) == {0}
else:
assert len(set(a.values())) == H.number_of_nodes()
@@ -0,0 +1,300 @@
import pytest
import networkx as nx
from networkx import convert_node_labels_to_integers as cnlti
class TestCliques:
def setup_method(self):
z = [3, 4, 3, 4, 2, 4, 2, 1, 1, 1, 1]
self.G = cnlti(nx.generators.havel_hakimi_graph(z), first_label=1)
self.cl = list(nx.find_cliques(self.G))
H = nx.complete_graph(6)
H = nx.relabel_nodes(H, {i: i + 1 for i in range(6)})
H.remove_edges_from([(2, 6), (2, 5), (2, 4), (1, 3), (5, 3)])
self.H = H
def test_find_cliques1(self):
cl = list(nx.find_cliques(self.G))
rcl = nx.find_cliques_recursive(self.G)
expected = [[2, 6, 1, 3], [2, 6, 4], [5, 4, 7], [8, 9], [10, 11]]
assert sorted(map(sorted, cl)) == sorted(map(sorted, rcl))
assert sorted(map(sorted, cl)) == sorted(map(sorted, expected))
def test_selfloops(self):
self.G.add_edge(1, 1)
cl = list(nx.find_cliques(self.G))
rcl = list(nx.find_cliques_recursive(self.G))
assert set(map(frozenset, cl)) == set(map(frozenset, rcl))
answer = [{2, 6, 1, 3}, {2, 6, 4}, {5, 4, 7}, {8, 9}, {10, 11}]
assert len(answer) == len(cl)
assert all(set(c) in answer for c in cl)
def test_find_cliques2(self):
hcl = list(nx.find_cliques(self.H))
assert sorted(map(sorted, hcl)) == [[1, 2], [1, 4, 5, 6], [2, 3], [3, 4, 6]]
def test_find_cliques3(self):
# all cliques are [[2, 6, 1, 3], [2, 6, 4], [5, 4, 7], [8, 9], [10, 11]]
cl = list(nx.find_cliques(self.G, [2]))
rcl = nx.find_cliques_recursive(self.G, [2])
expected = [[2, 6, 1, 3], [2, 6, 4]]
assert sorted(map(sorted, rcl)) == sorted(map(sorted, expected))
assert sorted(map(sorted, cl)) == sorted(map(sorted, expected))
cl = list(nx.find_cliques(self.G, [2, 3]))
rcl = nx.find_cliques_recursive(self.G, [2, 3])
expected = [[2, 6, 1, 3]]
assert sorted(map(sorted, rcl)) == sorted(map(sorted, expected))
assert sorted(map(sorted, cl)) == sorted(map(sorted, expected))
cl = list(nx.find_cliques(self.G, [2, 6, 4]))
rcl = nx.find_cliques_recursive(self.G, [2, 6, 4])
expected = [[2, 6, 4]]
assert sorted(map(sorted, rcl)) == sorted(map(sorted, expected))
assert sorted(map(sorted, cl)) == sorted(map(sorted, expected))
cl = list(nx.find_cliques(self.G, [2, 6, 4]))
rcl = nx.find_cliques_recursive(self.G, [2, 6, 4])
expected = [[2, 6, 4]]
assert sorted(map(sorted, rcl)) == sorted(map(sorted, expected))
assert sorted(map(sorted, cl)) == sorted(map(sorted, expected))
with pytest.raises(ValueError):
list(nx.find_cliques(self.G, [2, 6, 4, 1]))
with pytest.raises(ValueError):
list(nx.find_cliques_recursive(self.G, [2, 6, 4, 1]))
def test_find_cliques_directed(self):
G = nx.path_graph(4, create_using=nx.DiGraph)
msg = "not implemented for directed"
with pytest.raises(nx.NetworkXNotImplemented, match=msg):
list(nx.find_cliques(G))
with pytest.raises(nx.NetworkXNotImplemented, match=msg):
list(nx.find_cliques_recursive(G))
def test_number_of_cliques(self):
G = self.G
assert nx.number_of_cliques(G, 1) == 1
assert list(nx.number_of_cliques(G, [1]).values()) == [1]
assert list(nx.number_of_cliques(G, [1, 2]).values()) == [1, 2]
assert nx.number_of_cliques(G, [1, 2]) == {1: 1, 2: 2}
assert nx.number_of_cliques(G, 2) == 2
assert nx.number_of_cliques(G) == {
1: 1,
2: 2,
3: 1,
4: 2,
5: 1,
6: 2,
7: 1,
8: 1,
9: 1,
10: 1,
11: 1,
}
assert nx.number_of_cliques(G, nodes=list(G)) == {
1: 1,
2: 2,
3: 1,
4: 2,
5: 1,
6: 2,
7: 1,
8: 1,
9: 1,
10: 1,
11: 1,
}
assert nx.number_of_cliques(G, nodes=[2, 3, 4]) == {2: 2, 3: 1, 4: 2}
assert nx.number_of_cliques(G, cliques=self.cl) == {
1: 1,
2: 2,
3: 1,
4: 2,
5: 1,
6: 2,
7: 1,
8: 1,
9: 1,
10: 1,
11: 1,
}
assert nx.number_of_cliques(G, list(G), cliques=self.cl) == {
1: 1,
2: 2,
3: 1,
4: 2,
5: 1,
6: 2,
7: 1,
8: 1,
9: 1,
10: 1,
11: 1,
}
def test_node_clique_number(self):
G = self.G
assert nx.node_clique_number(G, 1) == 4
assert list(nx.node_clique_number(G, [1]).values()) == [4]
assert list(nx.node_clique_number(G, [1, 2]).values()) == [4, 4]
assert nx.node_clique_number(G, [1, 2]) == {1: 4, 2: 4}
assert nx.node_clique_number(G, 1) == 4
assert nx.node_clique_number(G) == {
1: 4,
2: 4,
3: 4,
4: 3,
5: 3,
6: 4,
7: 3,
8: 2,
9: 2,
10: 2,
11: 2,
}
assert nx.node_clique_number(G, cliques=self.cl) == {
1: 4,
2: 4,
3: 4,
4: 3,
5: 3,
6: 4,
7: 3,
8: 2,
9: 2,
10: 2,
11: 2,
}
assert nx.node_clique_number(G, [1, 2], cliques=self.cl) == {1: 4, 2: 4}
assert nx.node_clique_number(G, 1, cliques=self.cl) == 4
def test_make_clique_bipartite(self):
G = self.G
B = nx.make_clique_bipartite(G)
assert sorted(B) == [-5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
# Project onto the nodes of the original graph.
H = nx.projected_graph(B, range(1, 12))
assert H.adj == G.adj
# Project onto the nodes representing the cliques.
H1 = nx.projected_graph(B, range(-5, 0))
# Relabel the negative numbers as positive ones.
H1 = nx.relabel_nodes(H1, {-v: v for v in range(1, 6)})
assert sorted(H1) == [1, 2, 3, 4, 5]
def test_make_max_clique_graph(self):
"""Tests that the maximal clique graph is the same as the bipartite
clique graph after being projected onto the nodes representing the
cliques.
"""
G = self.G
B = nx.make_clique_bipartite(G)
# Project onto the nodes representing the cliques.
H1 = nx.projected_graph(B, range(-5, 0))
# Relabel the negative numbers as nonnegative ones, starting at
# 0.
H1 = nx.relabel_nodes(H1, {-v: v - 1 for v in range(1, 6)})
H2 = nx.make_max_clique_graph(G)
assert H1.adj == H2.adj
def test_directed(self):
with pytest.raises(nx.NetworkXNotImplemented):
next(nx.find_cliques(nx.DiGraph()))
def test_find_cliques_trivial(self):
G = nx.Graph()
assert sorted(nx.find_cliques(G)) == []
assert sorted(nx.find_cliques_recursive(G)) == []
def test_make_max_clique_graph_create_using(self):
G = nx.Graph([(1, 2), (3, 1), (4, 1), (5, 6)])
E = nx.Graph([(0, 1), (0, 2), (1, 2)])
E.add_node(3)
assert nx.is_isomorphic(nx.make_max_clique_graph(G, create_using=nx.Graph), E)
class TestEnumerateAllCliques:
def test_paper_figure_4(self):
# Same graph as given in Fig. 4 of paper enumerate_all_cliques is
# based on.
# http://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=1559964&isnumber=33129
G = nx.Graph()
edges_fig_4 = [
("a", "b"),
("a", "c"),
("a", "d"),
("a", "e"),
("b", "c"),
("b", "d"),
("b", "e"),
("c", "d"),
("c", "e"),
("d", "e"),
("f", "b"),
("f", "c"),
("f", "g"),
("g", "f"),
("g", "c"),
("g", "d"),
("g", "e"),
]
G.add_edges_from(edges_fig_4)
cliques = list(nx.enumerate_all_cliques(G))
clique_sizes = list(map(len, cliques))
assert sorted(clique_sizes) == clique_sizes
expected_cliques = [
["a"],
["b"],
["c"],
["d"],
["e"],
["f"],
["g"],
["a", "b"],
["a", "b", "d"],
["a", "b", "d", "e"],
["a", "b", "e"],
["a", "c"],
["a", "c", "d"],
["a", "c", "d", "e"],
["a", "c", "e"],
["a", "d"],
["a", "d", "e"],
["a", "e"],
["b", "c"],
["b", "c", "d"],
["b", "c", "d", "e"],
["b", "c", "e"],
["b", "c", "f"],
["b", "d"],
["b", "d", "e"],
["b", "e"],
["b", "f"],
["c", "d"],
["c", "d", "e"],
["c", "d", "e", "g"],
["c", "d", "g"],
["c", "e"],
["c", "e", "g"],
["c", "f"],
["c", "f", "g"],
["c", "g"],
["d", "e"],
["d", "e", "g"],
["d", "g"],
["e", "g"],
["f", "g"],
["a", "b", "c"],
["a", "b", "c", "d"],
["a", "b", "c", "d", "e"],
["a", "b", "c", "e"],
]
assert sorted(map(sorted, cliques)) == sorted(map(sorted, expected_cliques))
@@ -0,0 +1,678 @@
import pytest
import networkx as nx
def test_square_clustering_adjacent_squares():
G = nx.Graph([(1, 2), (1, 3), (2, 4), (3, 4), (3, 5), (4, 6), (5, 6)])
# Corner nodes: C_4 == 0.5, central face nodes: C_4 = 1 / 3
expected = {1: 0.5, 2: 0.5, 3: 1 / 3, 4: 1 / 3, 5: 0.5, 6: 0.5}
assert nx.square_clustering(G) == expected
def test_square_clustering_2d_grid():
G = nx.grid_2d_graph(3, 3)
# Central node: 4 squares out of 20 potential
expected = {
(0, 0): 1 / 3,
(0, 1): 0.25,
(0, 2): 1 / 3,
(1, 0): 0.25,
(1, 1): 0.2,
(1, 2): 0.25,
(2, 0): 1 / 3,
(2, 1): 0.25,
(2, 2): 1 / 3,
}
assert nx.square_clustering(G) == expected
def test_square_clustering_multiple_squares_non_complete():
"""An example where all nodes are part of all squares, but not every node
is connected to every other."""
G = nx.Graph([(0, 1), (0, 2), (1, 3), (2, 3), (1, 4), (2, 4), (1, 5), (2, 5)])
expected = {n: 1 for n in G}
assert nx.square_clustering(G) == expected
class TestTriangles:
def test_empty(self):
G = nx.Graph()
assert list(nx.triangles(G).values()) == []
def test_path(self):
G = nx.path_graph(10)
assert list(nx.triangles(G).values()) == [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
assert nx.triangles(G) == {
0: 0,
1: 0,
2: 0,
3: 0,
4: 0,
5: 0,
6: 0,
7: 0,
8: 0,
9: 0,
}
def test_cubical(self):
G = nx.cubical_graph()
assert list(nx.triangles(G).values()) == [0, 0, 0, 0, 0, 0, 0, 0]
assert nx.triangles(G, 1) == 0
assert list(nx.triangles(G, [1, 2]).values()) == [0, 0]
assert nx.triangles(G, 1) == 0
assert nx.triangles(G, [1, 2]) == {1: 0, 2: 0}
def test_k5(self):
G = nx.complete_graph(5)
assert list(nx.triangles(G).values()) == [6, 6, 6, 6, 6]
assert sum(nx.triangles(G).values()) / 3 == 10
assert nx.triangles(G, 1) == 6
G.remove_edge(1, 2)
assert list(nx.triangles(G).values()) == [5, 3, 3, 5, 5]
assert nx.triangles(G, 1) == 3
G.add_edge(3, 3) # ignore self-edges
assert list(nx.triangles(G).values()) == [5, 3, 3, 5, 5]
assert nx.triangles(G, 3) == 5
def test_all_triangles_non_integer_nodes():
G = nx.Graph()
G.add_edges_from(
[
("a", "b"),
("b", "c"),
("c", "a"), # triangle: a-b-c
]
)
expected = {frozenset({"a", "b", "c"})}
assert {frozenset(t) for t in nx.all_triangles(G)} == expected
def test_all_triangles_overlapping():
G = nx.Graph()
G.add_edges_from(
[
(0, 1),
(1, 2),
(2, 0), # triangle: 0-1-2
(0, 2),
(2, 3),
(3, 0), # triangle: 0-2-3
]
)
expected = {frozenset({0, 1, 2}), frozenset({0, 2, 3})}
assert {frozenset(t) for t in nx.all_triangles(G)} == expected
def test_all_triangles_subset():
G = nx.Graph()
G.add_edges_from(
[
(0, 1),
(1, 2),
(2, 0), # triangle: 0-1-2
(2, 3),
(3, 4),
(4, 2), # triangle: 2-3-4
]
)
assert {frozenset(t) for t in nx.all_triangles(G, nbunch=[0, 1])} == {
frozenset({0, 1, 2})
}
def test_all_triangles_subset_empty():
G = nx.Graph()
G.add_edges_from(
[
(0, 1),
(1, 2),
(2, 0), # triangle: 0-1-2
(2, 3),
(3, 4),
(4, 2), # triangle: 2-3-4
(5, 2),
]
)
assert list(nx.all_triangles(G, nbunch=[5])) == []
def test_all_triangles_no_triangles():
G = nx.path_graph(4)
assert list(nx.all_triangles(G)) == []
def test_all_triangles_complete_graph_exact():
G = nx.complete_graph(4)
expected = {
frozenset({0, 1, 2}),
frozenset({0, 1, 3}),
frozenset({0, 2, 3}),
frozenset({1, 2, 3}),
}
assert {frozenset(t) for t in nx.all_triangles(G)} == expected
def test_all_triangles_directed_graph():
G = nx.DiGraph()
G.add_edges_from([(0, 1), (1, 2), (2, 0)])
with pytest.raises(nx.NetworkXNotImplemented):
list(nx.all_triangles(G))
@pytest.mark.parametrize("graph_type", [nx.Graph, nx.MultiGraph])
def test_all_triangles_multiedges(graph_type):
G = graph_type()
G.add_edges_from([(0, 1), (0, 2), (1, 2), (1, 2)])
assert {frozenset(t) for t in nx.all_triangles(G)} == {frozenset({0, 1, 2})}
class TestDirectedClustering:
def test_clustering(self):
G = nx.DiGraph()
assert list(nx.clustering(G).values()) == []
assert nx.clustering(G) == {}
def test_path(self):
G = nx.path_graph(10, create_using=nx.DiGraph())
assert list(nx.clustering(G).values()) == [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
]
assert nx.clustering(G) == {
0: 0,
1: 0,
2: 0,
3: 0,
4: 0,
5: 0,
6: 0,
7: 0,
8: 0,
9: 0,
}
assert nx.clustering(G, 0) == 0
def test_k5(self):
G = nx.complete_graph(5, create_using=nx.DiGraph())
assert list(nx.clustering(G).values()) == [1, 1, 1, 1, 1]
assert nx.average_clustering(G) == 1
G.remove_edge(1, 2)
assert list(nx.clustering(G).values()) == [
11 / 12,
1,
1,
11 / 12,
11 / 12,
]
assert nx.clustering(G, [1, 4]) == {1: 1, 4: 11 / 12}
G.remove_edge(2, 1)
assert list(nx.clustering(G).values()) == [
5 / 6,
1,
1,
5 / 6,
5 / 6,
]
assert nx.clustering(G, [1, 4]) == {1: 1, 4: 0.83333333333333337}
assert nx.clustering(G, 4) == 5 / 6
def test_triangle_and_edge(self):
G = nx.cycle_graph(3, create_using=nx.DiGraph())
G.add_edge(0, 4)
assert nx.clustering(G)[0] == 1 / 6
class TestDirectedWeightedClustering:
@classmethod
def setup_class(cls):
global np
np = pytest.importorskip("numpy")
def test_clustering(self):
G = nx.DiGraph()
assert list(nx.clustering(G, weight="weight").values()) == []
assert nx.clustering(G) == {}
def test_path(self):
G = nx.path_graph(10, create_using=nx.DiGraph())
assert list(nx.clustering(G, weight="weight").values()) == [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
]
assert nx.clustering(G, weight="weight") == {
0: 0,
1: 0,
2: 0,
3: 0,
4: 0,
5: 0,
6: 0,
7: 0,
8: 0,
9: 0,
}
def test_k5(self):
G = nx.complete_graph(5, create_using=nx.DiGraph())
assert list(nx.clustering(G, weight="weight").values()) == [1, 1, 1, 1, 1]
assert nx.average_clustering(G, weight="weight") == 1
G.remove_edge(1, 2)
assert list(nx.clustering(G, weight="weight").values()) == [
11 / 12,
1,
1,
11 / 12,
11 / 12,
]
assert nx.clustering(G, [1, 4], weight="weight") == {1: 1, 4: 11 / 12}
G.remove_edge(2, 1)
assert list(nx.clustering(G, weight="weight").values()) == [
5 / 6,
1,
1,
5 / 6,
5 / 6,
]
assert nx.clustering(G, [1, 4], weight="weight") == {
1: 1,
4: 0.83333333333333337,
}
def test_triangle_and_edge(self):
G = nx.cycle_graph(3, create_using=nx.DiGraph())
G.add_edge(0, 4, weight=2)
assert nx.clustering(G)[0] == 1 / 6
# Relaxed comparisons to allow graphblas-algorithms to pass tests
np.testing.assert_allclose(nx.clustering(G, weight="weight")[0], 1 / 12)
np.testing.assert_allclose(nx.clustering(G, 0, weight="weight"), 1 / 12)
class TestWeightedClustering:
@classmethod
def setup_class(cls):
global np
np = pytest.importorskip("numpy")
def test_clustering(self):
G = nx.Graph()
assert list(nx.clustering(G, weight="weight").values()) == []
assert nx.clustering(G) == {}
def test_path(self):
G = nx.path_graph(10)
assert list(nx.clustering(G, weight="weight").values()) == [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
]
assert nx.clustering(G, weight="weight") == {
0: 0,
1: 0,
2: 0,
3: 0,
4: 0,
5: 0,
6: 0,
7: 0,
8: 0,
9: 0,
}
def test_cubical(self):
G = nx.cubical_graph()
assert list(nx.clustering(G, weight="weight").values()) == [
0,
0,
0,
0,
0,
0,
0,
0,
]
assert nx.clustering(G, 1) == 0
assert list(nx.clustering(G, [1, 2], weight="weight").values()) == [0, 0]
assert nx.clustering(G, 1, weight="weight") == 0
assert nx.clustering(G, [1, 2], weight="weight") == {1: 0, 2: 0}
def test_k5(self):
G = nx.complete_graph(5)
assert list(nx.clustering(G, weight="weight").values()) == [1, 1, 1, 1, 1]
assert nx.average_clustering(G, weight="weight") == 1
G.remove_edge(1, 2)
assert list(nx.clustering(G, weight="weight").values()) == [
5 / 6,
1,
1,
5 / 6,
5 / 6,
]
assert nx.clustering(G, [1, 4], weight="weight") == {
1: 1,
4: 0.83333333333333337,
}
def test_triangle_and_edge(self):
G = nx.cycle_graph(3)
G.add_edge(0, 4, weight=2)
assert nx.clustering(G)[0] == 1 / 3
np.testing.assert_allclose(nx.clustering(G, weight="weight")[0], 1 / 6)
np.testing.assert_allclose(nx.clustering(G, 0, weight="weight"), 1 / 6)
def test_triangle_and_signed_edge(self):
G = nx.cycle_graph(3)
G.add_edge(0, 1, weight=-1)
G.add_edge(3, 0, weight=0)
assert nx.clustering(G)[0] == 1 / 3
assert nx.clustering(G, weight="weight")[0] == -1 / 3
class TestClustering:
@classmethod
def setup_class(cls):
pytest.importorskip("numpy")
def test_clustering(self):
G = nx.Graph()
assert list(nx.clustering(G).values()) == []
assert nx.clustering(G) == {}
def test_path(self):
G = nx.path_graph(10)
assert list(nx.clustering(G).values()) == [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
]
assert nx.clustering(G) == {
0: 0,
1: 0,
2: 0,
3: 0,
4: 0,
5: 0,
6: 0,
7: 0,
8: 0,
9: 0,
}
def test_cubical(self):
G = nx.cubical_graph()
assert list(nx.clustering(G).values()) == [0, 0, 0, 0, 0, 0, 0, 0]
assert nx.clustering(G, 1) == 0
assert list(nx.clustering(G, [1, 2]).values()) == [0, 0]
assert nx.clustering(G, 1) == 0
assert nx.clustering(G, [1, 2]) == {1: 0, 2: 0}
def test_k5(self):
G = nx.complete_graph(5)
assert list(nx.clustering(G).values()) == [1, 1, 1, 1, 1]
assert nx.average_clustering(G) == 1
G.remove_edge(1, 2)
assert list(nx.clustering(G).values()) == [
5 / 6,
1,
1,
5 / 6,
5 / 6,
]
assert nx.clustering(G, [1, 4]) == {1: 1, 4: 0.83333333333333337}
def test_k5_signed(self):
G = nx.complete_graph(5)
assert list(nx.clustering(G).values()) == [1, 1, 1, 1, 1]
assert nx.average_clustering(G) == 1
G.remove_edge(1, 2)
G.add_edge(0, 1, weight=-1)
assert list(nx.clustering(G, weight="weight").values()) == [
1 / 6,
-1 / 3,
1,
3 / 6,
3 / 6,
]
class TestTransitivity:
def test_transitivity(self):
G = nx.Graph()
assert nx.transitivity(G) == 0
def test_path(self):
G = nx.path_graph(10)
assert nx.transitivity(G) == 0
def test_cubical(self):
G = nx.cubical_graph()
assert nx.transitivity(G) == 0
def test_k5(self):
G = nx.complete_graph(5)
assert nx.transitivity(G) == 1
G.remove_edge(1, 2)
assert nx.transitivity(G) == 0.875
class TestSquareClustering:
def test_clustering(self):
G = nx.Graph()
assert list(nx.square_clustering(G).values()) == []
assert nx.square_clustering(G) == {}
def test_path(self):
G = nx.path_graph(10)
assert list(nx.square_clustering(G).values()) == [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
]
assert nx.square_clustering(G) == {
0: 0,
1: 0,
2: 0,
3: 0,
4: 0,
5: 0,
6: 0,
7: 0,
8: 0,
9: 0,
}
def test_cubical(self):
G = nx.cubical_graph()
assert list(nx.square_clustering(G).values()) == [
1 / 3,
1 / 3,
1 / 3,
1 / 3,
1 / 3,
1 / 3,
1 / 3,
1 / 3,
]
assert list(nx.square_clustering(G, [1, 2]).values()) == [1 / 3, 1 / 3]
assert nx.square_clustering(G, [1])[1] == 1 / 3
assert nx.square_clustering(G, 1) == 1 / 3
assert nx.square_clustering(G, [1, 2]) == {1: 1 / 3, 2: 1 / 3}
def test_k5(self):
G = nx.complete_graph(5)
assert list(nx.square_clustering(G).values()) == [1, 1, 1, 1, 1]
def test_bipartite_k5(self):
G = nx.complete_bipartite_graph(5, 5)
assert list(nx.square_clustering(G).values()) == [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
def test_lind_square_clustering(self):
"""Test C4 for figure 1 Lind et al (2005)"""
G = nx.Graph(
[
(1, 2),
(1, 3),
(1, 6),
(1, 7),
(2, 4),
(2, 5),
(3, 4),
(3, 5),
(6, 7),
(7, 8),
(6, 8),
(7, 9),
(7, 10),
(6, 11),
(6, 12),
(2, 13),
(2, 14),
(3, 15),
(3, 16),
]
)
G1 = G.subgraph([1, 2, 3, 4, 5, 13, 14, 15, 16])
G2 = G.subgraph([1, 6, 7, 8, 9, 10, 11, 12])
assert nx.square_clustering(G, [1])[1] == 3 / 43
assert nx.square_clustering(G1, [1])[1] == 2 / 6
assert nx.square_clustering(G2, [1])[1] == 1 / 5
def test_peng_square_clustering(self):
"""Test eq2 for figure 1 Peng et al (2008)"""
# Example graph from figure 1b
G = nx.Graph([(1, 2), (1, 3), (2, 4), (3, 4), (3, 5), (3, 6)])
# From table 1, row 2
expected = {1: 1 / 3, 2: 1, 3: 0.2, 4: 1 / 3, 5: 0, 6: 0}
assert nx.square_clustering(G) == expected
def test_self_loops_square_clustering(self):
G = nx.path_graph(5)
assert nx.square_clustering(G) == {0: 0, 1: 0, 2: 0, 3: 0, 4: 0}
G.add_edges_from([(0, 0), (1, 1), (2, 2)])
assert nx.square_clustering(G) == {0: 0, 1: 0, 2: 0, 3: 0, 4: 0}
class TestAverageClustering:
@classmethod
def setup_class(cls):
pytest.importorskip("numpy")
def test_empty(self):
G = nx.Graph()
with pytest.raises(ZeroDivisionError):
nx.average_clustering(G)
def test_average_clustering(self):
G = nx.cycle_graph(3)
G.add_edge(2, 3)
assert nx.average_clustering(G) == (1 + 1 + 1 / 3) / 4
assert nx.average_clustering(G, count_zeros=True) == (1 + 1 + 1 / 3) / 4
assert nx.average_clustering(G, count_zeros=False) == (1 + 1 + 1 / 3) / 3
assert nx.average_clustering(G, [1, 2, 3]) == (1 + 1 / 3) / 3
assert nx.average_clustering(G, [1, 2, 3], count_zeros=True) == (1 + 1 / 3) / 3
assert nx.average_clustering(G, [1, 2, 3], count_zeros=False) == (1 + 1 / 3) / 2
def test_average_clustering_signed(self):
G = nx.cycle_graph(3)
G.add_edge(2, 3)
G.add_edge(0, 1, weight=-1)
assert nx.average_clustering(G, weight="weight") == (-1 - 1 - 1 / 3) / 4
assert (
nx.average_clustering(G, weight="weight", count_zeros=True)
== (-1 - 1 - 1 / 3) / 4
)
assert (
nx.average_clustering(G, weight="weight", count_zeros=False)
== (-1 - 1 - 1 / 3) / 3
)
class TestDirectedAverageClustering:
@classmethod
def setup_class(cls):
pytest.importorskip("numpy")
def test_empty(self):
G = nx.DiGraph()
with pytest.raises(ZeroDivisionError):
nx.average_clustering(G)
def test_average_clustering(self):
G = nx.cycle_graph(3, create_using=nx.DiGraph())
G.add_edge(2, 3)
assert nx.average_clustering(G) == (1 + 1 + 1 / 3) / 8
assert nx.average_clustering(G, count_zeros=True) == (1 + 1 + 1 / 3) / 8
assert nx.average_clustering(G, count_zeros=False) == (1 + 1 + 1 / 3) / 6
assert nx.average_clustering(G, [1, 2, 3]) == (1 + 1 / 3) / 6
assert nx.average_clustering(G, [1, 2, 3], count_zeros=True) == (1 + 1 / 3) / 6
assert nx.average_clustering(G, [1, 2, 3], count_zeros=False) == (1 + 1 / 3) / 4
class TestGeneralizedDegree:
def test_generalized_degree(self):
G = nx.Graph()
assert nx.generalized_degree(G) == {}
def test_path(self):
G = nx.path_graph(5)
assert nx.generalized_degree(G, 0) == {0: 1}
assert nx.generalized_degree(G, 1) == {0: 2}
def test_cubical(self):
G = nx.cubical_graph()
assert nx.generalized_degree(G, 0) == {0: 3}
def test_k5(self):
G = nx.complete_graph(5)
assert nx.generalized_degree(G, 0) == {3: 4}
G.remove_edge(0, 1)
assert nx.generalized_degree(G, 0) == {2: 3}
assert nx.generalized_degree(G, [1, 2]) == {1: {2: 3}, 2: {2: 2, 3: 2}}
assert nx.generalized_degree(G) == {
0: {2: 3},
1: {2: 3},
2: {2: 2, 3: 2},
3: {2: 2, 3: 2},
4: {2: 2, 3: 2},
}
@@ -0,0 +1,80 @@
from collections import defaultdict
import pytest
pytest.importorskip("numpy")
pytest.importorskip("scipy")
import networkx as nx
from networkx.algorithms.communicability_alg import communicability, communicability_exp
class TestCommunicability:
def test_communicability(self):
answer = {
0: {0: 1.5430806348152435, 1: 1.1752011936438012},
1: {0: 1.1752011936438012, 1: 1.5430806348152435},
}
# answer={(0, 0): 1.5430806348152435,
# (0, 1): 1.1752011936438012,
# (1, 0): 1.1752011936438012,
# (1, 1): 1.5430806348152435}
result = communicability(nx.path_graph(2))
for k1, val in result.items():
for k2 in val:
assert answer[k1][k2] == pytest.approx(result[k1][k2], abs=1e-7)
def test_communicability2(self):
answer_orig = {
("1", "1"): 1.6445956054135658,
("1", "Albert"): 0.7430186221096251,
("1", "Aric"): 0.7430186221096251,
("1", "Dan"): 1.6208126320442937,
("1", "Franck"): 0.42639707170035257,
("Albert", "1"): 0.7430186221096251,
("Albert", "Albert"): 2.4368257358712189,
("Albert", "Aric"): 1.4368257358712191,
("Albert", "Dan"): 2.0472097037446453,
("Albert", "Franck"): 1.8340111678944691,
("Aric", "1"): 0.7430186221096251,
("Aric", "Albert"): 1.4368257358712191,
("Aric", "Aric"): 2.4368257358712193,
("Aric", "Dan"): 2.0472097037446457,
("Aric", "Franck"): 1.8340111678944691,
("Dan", "1"): 1.6208126320442937,
("Dan", "Albert"): 2.0472097037446453,
("Dan", "Aric"): 2.0472097037446457,
("Dan", "Dan"): 3.1306328496328168,
("Dan", "Franck"): 1.4860372442192515,
("Franck", "1"): 0.42639707170035257,
("Franck", "Albert"): 1.8340111678944691,
("Franck", "Aric"): 1.8340111678944691,
("Franck", "Dan"): 1.4860372442192515,
("Franck", "Franck"): 2.3876142275231915,
}
answer = defaultdict(dict)
for (k1, k2), v in answer_orig.items():
answer[k1][k2] = v
G1 = nx.Graph(
[
("Franck", "Aric"),
("Aric", "Dan"),
("Dan", "Albert"),
("Albert", "Franck"),
("Dan", "1"),
("Franck", "Albert"),
]
)
result = communicability(G1)
for k1, val in result.items():
for k2 in val:
assert answer[k1][k2] == pytest.approx(result[k1][k2], abs=1e-7)
result = communicability_exp(G1)
for k1, val in result.items():
for k2 in val:
assert answer[k1][k2] == pytest.approx(result[k1][k2], abs=1e-7)
@@ -0,0 +1,266 @@
import pytest
import networkx as nx
from networkx.utils import nodes_equal
class TestCore:
@classmethod
def setup_class(cls):
# G is the example graph in Figure 1 from Batagelj and
# Zaversnik's paper titled An O(m) Algorithm for Cores
# Decomposition of Networks, 2003,
# http://arXiv.org/abs/cs/0310049. With nodes labeled as
# shown, the 3-core is given by nodes 1-8, the 2-core by nodes
# 9-16, the 1-core by nodes 17-20 and node 21 is in the
# 0-core.
t1 = nx.convert_node_labels_to_integers(nx.tetrahedral_graph(), 1)
t2 = nx.convert_node_labels_to_integers(t1, 5)
G = nx.union(t1, t2)
G.add_edges_from(
[
(3, 7),
(2, 11),
(11, 5),
(11, 12),
(5, 12),
(12, 19),
(12, 18),
(3, 9),
(7, 9),
(7, 10),
(9, 10),
(9, 20),
(17, 13),
(13, 14),
(14, 15),
(15, 16),
(16, 13),
]
)
G.add_node(21)
cls.G = G
# Create the graph H resulting from the degree sequence
# [0, 1, 2, 2, 2, 2, 3] when using the Havel-Hakimi algorithm.
degseq = [0, 1, 2, 2, 2, 2, 3]
H = nx.havel_hakimi_graph(degseq)
mapping = {6: 0, 0: 1, 4: 3, 5: 6, 3: 4, 1: 2, 2: 5}
cls.H = nx.relabel_nodes(H, mapping)
def test_trivial(self):
"""Empty graph"""
G = nx.Graph()
assert nx.core_number(G) == {}
def test_core_number(self):
core = nx.core_number(self.G)
nodes_by_core = [sorted(n for n in core if core[n] == val) for val in range(4)]
assert nodes_equal(nodes_by_core[0], [21])
assert nodes_equal(nodes_by_core[1], [17, 18, 19, 20])
assert nodes_equal(nodes_by_core[2], [9, 10, 11, 12, 13, 14, 15, 16])
assert nodes_equal(nodes_by_core[3], [1, 2, 3, 4, 5, 6, 7, 8])
def test_core_number2(self):
core = nx.core_number(self.H)
nodes_by_core = [sorted(n for n in core if core[n] == val) for val in range(3)]
assert nodes_equal(nodes_by_core[0], [0])
assert nodes_equal(nodes_by_core[1], [1, 3])
assert nodes_equal(nodes_by_core[2], [2, 4, 5, 6])
def test_core_number_multigraph(self):
G = nx.complete_graph(3)
G = nx.MultiGraph(G)
G.add_edge(1, 2)
with pytest.raises(
nx.NetworkXNotImplemented, match="not implemented for multigraph type"
):
nx.core_number(G)
def test_core_number_self_loop(self):
G = nx.cycle_graph(3)
G.add_edge(0, 0)
with pytest.raises(
nx.NetworkXNotImplemented, match="Input graph has self loops"
):
nx.core_number(G)
def test_directed_core_number(self):
"""core number had a bug for directed graphs found in issue #1959"""
# small example where too timid edge removal can make cn[2] = 3
G = nx.DiGraph()
edges = [(1, 2), (2, 1), (2, 3), (2, 4), (3, 4), (4, 3)]
G.add_edges_from(edges)
assert nx.core_number(G) == {1: 2, 2: 2, 3: 2, 4: 2}
# small example where too aggressive edge removal can make cn[2] = 2
more_edges = [(1, 5), (3, 5), (4, 5), (3, 6), (4, 6), (5, 6)]
G.add_edges_from(more_edges)
assert nx.core_number(G) == {1: 3, 2: 3, 3: 3, 4: 3, 5: 3, 6: 3}
def test_main_core(self):
main_core_subgraph = nx.k_core(self.H)
assert sorted(main_core_subgraph.nodes()) == [2, 4, 5, 6]
def test_k_core(self):
# k=0
k_core_subgraph = nx.k_core(self.H, k=0)
assert sorted(k_core_subgraph.nodes()) == sorted(self.H.nodes())
# k=1
k_core_subgraph = nx.k_core(self.H, k=1)
assert sorted(k_core_subgraph.nodes()) == [1, 2, 3, 4, 5, 6]
# k = 2
k_core_subgraph = nx.k_core(self.H, k=2)
assert sorted(k_core_subgraph.nodes()) == [2, 4, 5, 6]
def test_k_core_multigraph(self):
core_number = nx.core_number(self.H)
H = nx.MultiGraph(self.H)
with pytest.raises(nx.NetworkXNotImplemented):
nx.k_core(H, k=0, core_number=core_number)
def test_main_crust(self):
main_crust_subgraph = nx.k_crust(self.H)
assert sorted(main_crust_subgraph.nodes()) == [0, 1, 3]
def test_k_crust(self):
# k = 0
k_crust_subgraph = nx.k_crust(self.H, k=2)
assert sorted(k_crust_subgraph.nodes()) == sorted(self.H.nodes())
# k=1
k_crust_subgraph = nx.k_crust(self.H, k=1)
assert sorted(k_crust_subgraph.nodes()) == [0, 1, 3]
# k=2
k_crust_subgraph = nx.k_crust(self.H, k=0)
assert sorted(k_crust_subgraph.nodes()) == [0]
def test_k_crust_multigraph(self):
core_number = nx.core_number(self.H)
H = nx.MultiGraph(self.H)
with pytest.raises(nx.NetworkXNotImplemented):
nx.k_crust(H, k=0, core_number=core_number)
def test_main_shell(self):
main_shell_subgraph = nx.k_shell(self.H)
assert sorted(main_shell_subgraph.nodes()) == [2, 4, 5, 6]
def test_k_shell(self):
# k=0
k_shell_subgraph = nx.k_shell(self.H, k=2)
assert sorted(k_shell_subgraph.nodes()) == [2, 4, 5, 6]
# k=1
k_shell_subgraph = nx.k_shell(self.H, k=1)
assert sorted(k_shell_subgraph.nodes()) == [1, 3]
# k=2
k_shell_subgraph = nx.k_shell(self.H, k=0)
assert sorted(k_shell_subgraph.nodes()) == [0]
def test_k_shell_multigraph(self):
core_number = nx.core_number(self.H)
H = nx.MultiGraph(self.H)
with pytest.raises(nx.NetworkXNotImplemented):
nx.k_shell(H, k=0, core_number=core_number)
def test_k_corona(self):
# k=0
k_corona_subgraph = nx.k_corona(self.H, k=2)
assert sorted(k_corona_subgraph.nodes()) == [2, 4, 5, 6]
# k=1
k_corona_subgraph = nx.k_corona(self.H, k=1)
assert sorted(k_corona_subgraph.nodes()) == [1]
# k=2
k_corona_subgraph = nx.k_corona(self.H, k=0)
assert sorted(k_corona_subgraph.nodes()) == [0]
def test_k_corona_multigraph(self):
core_number = nx.core_number(self.H)
H = nx.MultiGraph(self.H)
with pytest.raises(nx.NetworkXNotImplemented):
nx.k_corona(H, k=0, core_number=core_number)
def test_k_truss(self):
# k=-1
k_truss_subgraph = nx.k_truss(self.G, -1)
assert sorted(k_truss_subgraph.nodes()) == list(range(1, 21))
# k=0
k_truss_subgraph = nx.k_truss(self.G, 0)
assert sorted(k_truss_subgraph.nodes()) == list(range(1, 21))
# k=1
k_truss_subgraph = nx.k_truss(self.G, 1)
assert sorted(k_truss_subgraph.nodes()) == list(range(1, 21))
# k=2
k_truss_subgraph = nx.k_truss(self.G, 2)
assert sorted(k_truss_subgraph.nodes()) == list(range(1, 21))
# k=3
k_truss_subgraph = nx.k_truss(self.G, 3)
assert sorted(k_truss_subgraph.nodes()) == list(range(1, 13))
k_truss_subgraph = nx.k_truss(self.G, 4)
assert sorted(k_truss_subgraph.nodes()) == list(range(1, 9))
k_truss_subgraph = nx.k_truss(self.G, 5)
assert sorted(k_truss_subgraph.nodes()) == []
def test_k_truss_digraph(self):
G = nx.complete_graph(3)
G = nx.DiGraph(G)
G.add_edge(2, 1)
with pytest.raises(
nx.NetworkXNotImplemented, match="not implemented for directed type"
):
nx.k_truss(G, k=1)
def test_k_truss_multigraph(self):
G = nx.complete_graph(3)
G = nx.MultiGraph(G)
G.add_edge(1, 2)
with pytest.raises(
nx.NetworkXNotImplemented, match="not implemented for multigraph type"
):
nx.k_truss(G, k=1)
def test_k_truss_self_loop(self):
G = nx.cycle_graph(3)
G.add_edge(0, 0)
with pytest.raises(
nx.NetworkXNotImplemented, match="Input graph has self loops"
):
nx.k_truss(G, k=1)
def test_onion_layers(self):
layers = nx.onion_layers(self.G)
nodes_by_layer = [
sorted(n for n in layers if layers[n] == val) for val in range(1, 7)
]
assert nodes_equal(nodes_by_layer[0], [21])
assert nodes_equal(nodes_by_layer[1], [17, 18, 19, 20])
assert nodes_equal(nodes_by_layer[2], [10, 12, 13, 14, 15, 16])
assert nodes_equal(nodes_by_layer[3], [9, 11])
assert nodes_equal(nodes_by_layer[4], [1, 2, 4, 5, 6, 8])
assert nodes_equal(nodes_by_layer[5], [3, 7])
def test_onion_digraph(self):
G = nx.complete_graph(3)
G = nx.DiGraph(G)
G.add_edge(2, 1)
with pytest.raises(
nx.NetworkXNotImplemented, match="not implemented for directed type"
):
nx.onion_layers(G)
def test_onion_multigraph(self):
G = nx.complete_graph(3)
G = nx.MultiGraph(G)
G.add_edge(1, 2)
with pytest.raises(
nx.NetworkXNotImplemented, match="not implemented for multigraph type"
):
nx.onion_layers(G)
def test_onion_self_loop(self):
G = nx.cycle_graph(3)
G.add_edge(0, 0)
with pytest.raises(
nx.NetworkXNotImplemented, match="Input graph contains self loops"
):
nx.onion_layers(G)
@@ -0,0 +1,85 @@
import pytest
import networkx as nx
class TestMinEdgeCover:
"""Tests for :func:`networkx.algorithms.min_edge_cover`"""
def test_empty_graph(self):
G = nx.Graph()
assert nx.min_edge_cover(G) == set()
def test_graph_with_loop(self):
G = nx.Graph()
G.add_edge(0, 0)
assert nx.min_edge_cover(G) == {(0, 0)}
def test_graph_with_isolated_v(self):
G = nx.Graph()
G.add_node(1)
with pytest.raises(
nx.NetworkXException,
match="Graph has a node with no edge incident on it, so no edge cover exists.",
):
nx.min_edge_cover(G)
def test_graph_single_edge(self):
G = nx.Graph([(0, 1)])
assert nx.min_edge_cover(G) in ({(0, 1)}, {(1, 0)})
def test_graph_two_edge_path(self):
G = nx.path_graph(3)
min_cover = nx.min_edge_cover(G)
assert len(min_cover) == 2
for u, v in G.edges:
assert (u, v) in min_cover or (v, u) in min_cover
def test_bipartite_explicit(self):
G = nx.Graph()
G.add_nodes_from([1, 2, 3, 4], bipartite=0)
G.add_nodes_from(["a", "b", "c"], bipartite=1)
G.add_edges_from([(1, "a"), (1, "b"), (2, "b"), (2, "c"), (3, "c"), (4, "a")])
# Use bipartite method by prescribing the algorithm
min_cover = nx.min_edge_cover(
G, nx.algorithms.bipartite.matching.eppstein_matching
)
assert nx.is_edge_cover(G, min_cover)
assert len(min_cover) == 8
# Use the default method which is not specialized for bipartite
min_cover2 = nx.min_edge_cover(G)
assert nx.is_edge_cover(G, min_cover2)
assert len(min_cover2) == 4
def test_complete_graph_even(self):
G = nx.complete_graph(10)
min_cover = nx.min_edge_cover(G)
assert nx.is_edge_cover(G, min_cover)
assert len(min_cover) == 5
def test_complete_graph_odd(self):
G = nx.complete_graph(11)
min_cover = nx.min_edge_cover(G)
assert nx.is_edge_cover(G, min_cover)
assert len(min_cover) == 6
class TestIsEdgeCover:
"""Tests for :func:`networkx.algorithms.is_edge_cover`"""
def test_empty_graph(self):
G = nx.Graph()
assert nx.is_edge_cover(G, set())
def test_graph_with_loop(self):
G = nx.Graph()
G.add_edge(1, 1)
assert nx.is_edge_cover(G, {(1, 1)})
def test_graph_single_edge(self):
G = nx.Graph()
G.add_edge(0, 1)
assert nx.is_edge_cover(G, {(0, 0), (1, 1)})
assert nx.is_edge_cover(G, {(0, 1), (1, 0)})
assert nx.is_edge_cover(G, {(0, 1)})
assert not nx.is_edge_cover(G, {(0, 0)})
@@ -0,0 +1,171 @@
"""Unit tests for the :mod:`networkx.algorithms.cuts` module."""
import networkx as nx
class TestCutSize:
"""Unit tests for the :func:`~networkx.cut_size` function."""
def test_symmetric(self):
"""Tests that the cut size is symmetric."""
G = nx.barbell_graph(3, 0)
S = {0, 1, 4}
T = {2, 3, 5}
assert nx.cut_size(G, S, T) == 4
assert nx.cut_size(G, T, S) == 4
def test_single_edge(self):
"""Tests for a cut of a single edge."""
G = nx.barbell_graph(3, 0)
S = {0, 1, 2}
T = {3, 4, 5}
assert nx.cut_size(G, S, T) == 1
assert nx.cut_size(G, T, S) == 1
def test_directed(self):
"""Tests that each directed edge is counted once in the cut."""
G = nx.barbell_graph(3, 0).to_directed()
S = {0, 1, 2}
T = {3, 4, 5}
assert nx.cut_size(G, S, T) == 2
assert nx.cut_size(G, T, S) == 2
def test_directed_symmetric(self):
"""Tests that a cut in a directed graph is symmetric."""
G = nx.barbell_graph(3, 0).to_directed()
S = {0, 1, 4}
T = {2, 3, 5}
assert nx.cut_size(G, S, T) == 8
assert nx.cut_size(G, T, S) == 8
def test_multigraph(self):
"""Tests that parallel edges are each counted for a cut."""
G = nx.MultiGraph(["ab", "ab"])
assert nx.cut_size(G, {"a"}, {"b"}) == 2
class TestVolume:
"""Unit tests for the :func:`~networkx.volume` function."""
def test_graph(self):
G = nx.cycle_graph(4)
assert nx.volume(G, {0, 1}) == 4
def test_digraph(self):
G = nx.DiGraph([(0, 1), (1, 2), (2, 3), (3, 0)])
assert nx.volume(G, {0, 1}) == 2
def test_multigraph(self):
edges = list(nx.cycle_graph(4).edges())
G = nx.MultiGraph(edges * 2)
assert nx.volume(G, {0, 1}) == 8
def test_multidigraph(self):
edges = [(0, 1), (1, 2), (2, 3), (3, 0)]
G = nx.MultiDiGraph(edges * 2)
assert nx.volume(G, {0, 1}) == 4
def test_barbell(self):
G = nx.barbell_graph(3, 0)
assert nx.volume(G, {0, 1, 2}) == 7
assert nx.volume(G, {3, 4, 5}) == 7
class TestNormalizedCutSize:
"""Unit tests for the :func:`~networkx.normalized_cut_size` function."""
def test_graph(self):
G = nx.path_graph(4)
S = {1, 2}
T = set(G) - S
size = nx.normalized_cut_size(G, S, T)
# The cut looks like this: o-{-o--o-}-o
expected = 2 * ((1 / 4) + (1 / 2))
assert expected == size
# Test with no input T
assert expected == nx.normalized_cut_size(G, S)
def test_directed(self):
G = nx.DiGraph([(0, 1), (1, 2), (2, 3)])
S = {1, 2}
T = set(G) - S
size = nx.normalized_cut_size(G, S, T)
# The cut looks like this: o-{->o-->o-}->o
expected = 2 * ((1 / 2) + (1 / 1))
assert expected == size
# Test with no input T
assert expected == nx.normalized_cut_size(G, S)
class TestConductance:
"""Unit tests for the :func:`~networkx.conductance` function."""
def test_graph(self):
G = nx.barbell_graph(5, 0)
# Consider the singleton sets containing the "bridge" nodes.
# There is only one cut edge, and each set has volume five.
S = {4}
T = {5}
conductance = nx.conductance(G, S, T)
expected = 1 / 5
assert expected == conductance
# Test with no input T
G2 = nx.barbell_graph(3, 0)
# There is only one cut edge, and each set has volume seven.
S2 = {0, 1, 2}
assert nx.conductance(G2, S2) == 1 / 7
class TestEdgeExpansion:
"""Unit tests for the :func:`~networkx.edge_expansion` function."""
def test_graph(self):
G = nx.barbell_graph(5, 0)
S = set(range(5))
T = set(G) - S
expansion = nx.edge_expansion(G, S, T)
expected = 1 / 5
assert expected == expansion
# Test with no input T
assert expected == nx.edge_expansion(G, S)
class TestNodeExpansion:
"""Unit tests for the :func:`~networkx.node_expansion` function."""
def test_graph(self):
G = nx.path_graph(8)
S = {3, 4, 5}
expansion = nx.node_expansion(G, S)
# The neighborhood of S has cardinality five, and S has
# cardinality three.
expected = 5 / 3
assert expected == expansion
class TestBoundaryExpansion:
"""Unit tests for the :func:`~networkx.boundary_expansion` function."""
def test_graph(self):
G = nx.complete_graph(10)
S = set(range(4))
expansion = nx.boundary_expansion(G, S)
# The node boundary of S has cardinality six, and S has
# cardinality three.
expected = 6 / 4
assert expected == expansion
class TestMixingExpansion:
"""Unit tests for the :func:`~networkx.mixing_expansion` function."""
def test_graph(self):
G = nx.barbell_graph(5, 0)
S = set(range(5))
T = set(G) - S
expansion = nx.mixing_expansion(G, S, T)
# There is one cut edge, and the total number of edges in the
# graph is twice the total number of edges in a clique of size
# five, plus one more for the bridge.
expected = 1 / (2 * (5 * 4 + 1))
assert expected == expansion
@@ -0,0 +1,984 @@
import random
from itertools import chain, islice, tee
from math import inf
import pytest
import networkx as nx
from networkx.algorithms.traversal.edgedfs import FORWARD, REVERSE
def check_independent(basis):
if len(basis) == 0:
return
np = pytest.importorskip("numpy")
sp = pytest.importorskip("scipy") # Required by incidence_matrix
H = nx.Graph()
for b in basis:
nx.add_cycle(H, b)
inc = nx.incidence_matrix(H, oriented=True)
rank = np.linalg.matrix_rank(inc.toarray(), tol=None, hermitian=False)
assert inc.shape[1] - rank == len(basis)
class TestCycles:
@classmethod
def setup_class(cls):
G = nx.Graph()
nx.add_cycle(G, [0, 1, 2, 3])
nx.add_cycle(G, [0, 3, 4, 5])
nx.add_cycle(G, [0, 1, 6, 7, 8])
G.add_edge(8, 9)
cls.G = G
def is_cyclic_permutation(self, a, b):
n = len(a)
if len(b) != n:
return False
l = a + a
return any(l[i : i + n] == b for i in range(n))
def test_cycle_basis(self):
G = self.G
cy = nx.cycle_basis(G, 0)
sort_cy = sorted(sorted(c) for c in cy)
assert sort_cy == [[0, 1, 2, 3], [0, 1, 6, 7, 8], [0, 3, 4, 5]]
cy = nx.cycle_basis(G, 1)
sort_cy = sorted(sorted(c) for c in cy)
assert sort_cy == [[0, 1, 2, 3], [0, 1, 6, 7, 8], [0, 3, 4, 5]]
cy = nx.cycle_basis(G, 9)
sort_cy = sorted(sorted(c) for c in cy)
assert sort_cy == [[0, 1, 2, 3], [0, 1, 6, 7, 8], [0, 3, 4, 5]]
# test disconnected graphs
nx.add_cycle(G, "ABC")
cy = nx.cycle_basis(G, 9)
sort_cy = sorted(sorted(c) for c in cy[:-1]) + [sorted(cy[-1])]
assert sort_cy == [[0, 1, 2, 3], [0, 1, 6, 7, 8], [0, 3, 4, 5], ["A", "B", "C"]]
def test_cycle_basis2(self):
with pytest.raises(nx.NetworkXNotImplemented):
G = nx.DiGraph()
cy = nx.cycle_basis(G, 0)
def test_cycle_basis3(self):
with pytest.raises(nx.NetworkXNotImplemented):
G = nx.MultiGraph()
cy = nx.cycle_basis(G, 0)
def test_cycle_basis_ordered(self):
# see gh-6654 replace sets with (ordered) dicts
G = nx.cycle_graph(5)
G.update(nx.cycle_graph(range(3, 8)))
cbG = nx.cycle_basis(G)
perm = {1: 0, 0: 1} # switch 0 and 1
H = nx.relabel_nodes(G, perm)
cbH = [[perm.get(n, n) for n in cyc] for cyc in nx.cycle_basis(H)]
assert cbG == cbH
def test_cycle_basis_self_loop(self):
"""Tests the function for graphs with self loops"""
G = nx.Graph()
nx.add_cycle(G, [0, 1, 2, 3])
nx.add_cycle(G, [0, 0, 6, 2])
cy = nx.cycle_basis(G)
sort_cy = sorted(sorted(c) for c in cy)
assert sort_cy == [[0], [0, 1, 2], [0, 2, 3], [0, 2, 6]]
def test_simple_cycles(self):
edges = [(0, 0), (0, 1), (0, 2), (1, 2), (2, 0), (2, 1), (2, 2)]
G = nx.DiGraph(edges)
cc = sorted(nx.simple_cycles(G))
ca = [[0], [0, 1, 2], [0, 2], [1, 2], [2]]
assert len(cc) == len(ca)
for c in cc:
assert any(self.is_cyclic_permutation(c, rc) for rc in ca)
def test_simple_cycles_singleton(self):
G = nx.Graph([(0, 0)]) # self-loop
assert list(nx.simple_cycles(G)) == [[0]]
def test_unsortable(self):
# this test ensures that graphs whose nodes without an intrinsic
# ordering do not cause issues
G = nx.DiGraph()
nx.add_cycle(G, ["a", 1])
c = list(nx.simple_cycles(G))
assert len(c) == 1
def test_simple_cycles_small(self):
G = nx.DiGraph()
nx.add_cycle(G, [1, 2, 3])
c = sorted(nx.simple_cycles(G))
assert len(c) == 1
assert self.is_cyclic_permutation(c[0], [1, 2, 3])
nx.add_cycle(G, [10, 20, 30])
cc = sorted(nx.simple_cycles(G))
assert len(cc) == 2
ca = [[1, 2, 3], [10, 20, 30]]
for c in cc:
assert any(self.is_cyclic_permutation(c, rc) for rc in ca)
def test_simple_cycles_empty(self):
G = nx.DiGraph()
assert list(nx.simple_cycles(G)) == []
def worst_case_graph(self, k):
# see figure 1 in Johnson's paper
# this graph has exactly 3k simple cycles
G = nx.DiGraph()
for n in range(2, k + 2):
G.add_edge(1, n)
G.add_edge(n, k + 2)
G.add_edge(2 * k + 1, 1)
for n in range(k + 2, 2 * k + 2):
G.add_edge(n, 2 * k + 2)
G.add_edge(n, n + 1)
G.add_edge(2 * k + 3, k + 2)
for n in range(2 * k + 3, 3 * k + 3):
G.add_edge(2 * k + 2, n)
G.add_edge(n, 3 * k + 3)
G.add_edge(3 * k + 3, 2 * k + 2)
return G
def test_worst_case_graph(self):
# see figure 1 in Johnson's paper
for k in range(3, 10):
G = self.worst_case_graph(k)
l = len(list(nx.simple_cycles(G)))
assert l == 3 * k
def test_recursive_simple_and_not(self):
for k in range(2, 10):
G = self.worst_case_graph(k)
cc = sorted(nx.simple_cycles(G))
rcc = sorted(nx.recursive_simple_cycles(G))
assert len(cc) == len(rcc)
for c in cc:
assert any(self.is_cyclic_permutation(c, r) for r in rcc)
for rc in rcc:
assert any(self.is_cyclic_permutation(rc, c) for c in cc)
def test_simple_graph_with_reported_bug(self):
G = nx.DiGraph()
edges = [
(0, 2),
(0, 3),
(1, 0),
(1, 3),
(2, 1),
(2, 4),
(3, 2),
(3, 4),
(4, 0),
(4, 1),
(4, 5),
(5, 0),
(5, 1),
(5, 2),
(5, 3),
]
G.add_edges_from(edges)
cc = sorted(nx.simple_cycles(G))
assert len(cc) == 26
rcc = sorted(nx.recursive_simple_cycles(G))
assert len(cc) == len(rcc)
for c in cc:
assert any(self.is_cyclic_permutation(c, rc) for rc in rcc)
for rc in rcc:
assert any(self.is_cyclic_permutation(rc, c) for c in cc)
def pairwise(iterable):
a, b = tee(iterable)
next(b, None)
return zip(a, b)
def cycle_edges(c):
return pairwise(chain(c, islice(c, 1)))
def directed_cycle_edgeset(c):
return frozenset(cycle_edges(c))
def undirected_cycle_edgeset(c):
if len(c) == 1:
return frozenset(cycle_edges(c))
return frozenset(map(frozenset, cycle_edges(c)))
def multigraph_cycle_edgeset(c):
if len(c) <= 2:
return frozenset(cycle_edges(c))
else:
return frozenset(map(frozenset, cycle_edges(c)))
class TestCycleEnumeration:
@staticmethod
def K(n):
return nx.complete_graph(n)
@staticmethod
def D(n):
return nx.complete_graph(n).to_directed()
@staticmethod
def edgeset_function(g):
if g.is_directed():
return directed_cycle_edgeset
elif g.is_multigraph():
return multigraph_cycle_edgeset
else:
return undirected_cycle_edgeset
def check_cycle(self, g, c, es, cache, source, original_c, length_bound, chordless):
if length_bound is not None and len(c) > length_bound:
raise RuntimeError(
f"computed cycle {original_c} exceeds length bound {length_bound}"
)
if source == "computed":
if es in cache:
raise RuntimeError(
f"computed cycle {original_c} has already been found!"
)
else:
cache[es] = tuple(original_c)
else:
if es in cache:
cache.pop(es)
else:
raise RuntimeError(f"expected cycle {original_c} was not computed")
if not all(g.has_edge(*e) for e in es):
raise RuntimeError(
f"{source} claimed cycle {original_c} is not a cycle of g"
)
if chordless and len(g.subgraph(c).edges) > len(c):
raise RuntimeError(f"{source} cycle {original_c} is not chordless")
def check_cycle_algorithm(
self,
g,
expected_cycles,
length_bound=None,
chordless=False,
algorithm=None,
):
if algorithm is None:
algorithm = nx.chordless_cycles if chordless else nx.simple_cycles
# note: we shuffle the labels of g to rule out accidentally-correct
# behavior which occurred during the development of chordless cycle
# enumeration algorithms
relabel = list(range(len(g)))
rng = random.Random(42)
rng.shuffle(relabel)
label = dict(zip(g, relabel))
unlabel = dict(zip(relabel, g))
h = nx.relabel_nodes(g, label, copy=True)
edgeset = self.edgeset_function(h)
params = {}
if length_bound is not None:
params["length_bound"] = length_bound
cycle_cache = {}
for c in algorithm(h, **params):
original_c = [unlabel[x] for x in c]
es = edgeset(c)
self.check_cycle(
h, c, es, cycle_cache, "computed", original_c, length_bound, chordless
)
if isinstance(expected_cycles, int):
if len(cycle_cache) != expected_cycles:
raise RuntimeError(
f"expected {expected_cycles} cycles, got {len(cycle_cache)}"
)
return
for original_c in expected_cycles:
c = [label[x] for x in original_c]
es = edgeset(c)
self.check_cycle(
h, c, es, cycle_cache, "expected", original_c, length_bound, chordless
)
if len(cycle_cache):
for c in cycle_cache.values():
raise RuntimeError(
f"computed cycle {c} is valid but not in the expected cycle set!"
)
def check_cycle_enumeration_integer_sequence(
self,
g_family,
cycle_counts,
length_bound=None,
chordless=False,
algorithm=None,
):
for g, num_cycles in zip(g_family, cycle_counts):
self.check_cycle_algorithm(
g,
num_cycles,
length_bound=length_bound,
chordless=chordless,
algorithm=algorithm,
)
def test_directed_chordless_cycle_digons(self):
g = nx.DiGraph()
nx.add_cycle(g, range(5))
nx.add_cycle(g, range(5)[::-1])
g.add_edge(0, 0)
expected_cycles = [(0,), (1, 2), (2, 3), (3, 4)]
self.check_cycle_algorithm(g, expected_cycles, chordless=True)
self.check_cycle_algorithm(g, expected_cycles, chordless=True, length_bound=2)
expected_cycles = [c for c in expected_cycles if len(c) < 2]
self.check_cycle_algorithm(g, expected_cycles, chordless=True, length_bound=1)
def test_chordless_cycles_multigraph_self_loops(self):
G = nx.MultiGraph([(1, 1), (2, 2), (1, 2), (1, 2)])
expected_cycles = [[1], [2]]
self.check_cycle_algorithm(G, expected_cycles, chordless=True)
G.add_edges_from([(2, 3), (3, 4), (3, 4), (1, 3)])
expected_cycles = [[1], [2], [3, 4]]
self.check_cycle_algorithm(G, expected_cycles, chordless=True)
def test_directed_chordless_cycle_undirected(self):
g = nx.DiGraph([(1, 2), (2, 3), (3, 4), (4, 5), (5, 0), (5, 1), (0, 2)])
expected_cycles = [(0, 2, 3, 4, 5), (1, 2, 3, 4, 5)]
self.check_cycle_algorithm(g, expected_cycles, chordless=True)
g = nx.DiGraph()
nx.add_cycle(g, range(5))
nx.add_cycle(g, range(4, 9))
g.add_edge(7, 3)
expected_cycles = [(0, 1, 2, 3, 4), (3, 4, 5, 6, 7), (4, 5, 6, 7, 8)]
self.check_cycle_algorithm(g, expected_cycles, chordless=True)
g.add_edge(3, 7)
expected_cycles = [(0, 1, 2, 3, 4), (3, 7), (4, 5, 6, 7, 8)]
self.check_cycle_algorithm(g, expected_cycles, chordless=True)
expected_cycles = [(3, 7)]
self.check_cycle_algorithm(g, expected_cycles, chordless=True, length_bound=4)
g.remove_edge(7, 3)
expected_cycles = [(0, 1, 2, 3, 4), (4, 5, 6, 7, 8)]
self.check_cycle_algorithm(g, expected_cycles, chordless=True)
g = nx.DiGraph((i, j) for i in range(10) for j in range(i))
expected_cycles = []
self.check_cycle_algorithm(g, expected_cycles, chordless=True)
def test_chordless_cycles_directed(self):
G = nx.DiGraph()
nx.add_cycle(G, range(5))
nx.add_cycle(G, range(4, 12))
expected = [[*range(5)], [*range(4, 12)]]
self.check_cycle_algorithm(G, expected, chordless=True)
self.check_cycle_algorithm(
G, [c for c in expected if len(c) <= 5], length_bound=5, chordless=True
)
G.add_edge(7, 3)
expected.append([*range(3, 8)])
self.check_cycle_algorithm(G, expected, chordless=True)
self.check_cycle_algorithm(
G, [c for c in expected if len(c) <= 5], length_bound=5, chordless=True
)
G.add_edge(3, 7)
expected[-1] = [7, 3]
self.check_cycle_algorithm(G, expected, chordless=True)
self.check_cycle_algorithm(
G, [c for c in expected if len(c) <= 5], length_bound=5, chordless=True
)
expected.pop()
G.remove_edge(7, 3)
self.check_cycle_algorithm(G, expected, chordless=True)
self.check_cycle_algorithm(
G, [c for c in expected if len(c) <= 5], length_bound=5, chordless=True
)
def test_directed_chordless_cycle_diclique(self):
g_family = [self.D(n) for n in range(10)]
expected_cycles = [(n * n - n) // 2 for n in range(10)]
self.check_cycle_enumeration_integer_sequence(
g_family, expected_cycles, chordless=True
)
expected_cycles = [(n * n - n) // 2 for n in range(10)]
self.check_cycle_enumeration_integer_sequence(
g_family, expected_cycles, length_bound=2
)
def test_directed_chordless_loop_blockade(self):
g = nx.DiGraph((i, i) for i in range(10))
nx.add_cycle(g, range(10))
expected_cycles = [(i,) for i in range(10)]
self.check_cycle_algorithm(g, expected_cycles, chordless=True)
self.check_cycle_algorithm(g, expected_cycles, length_bound=1)
g = nx.MultiDiGraph(g)
g.add_edges_from((i, i) for i in range(0, 10, 2))
expected_cycles = [(i,) for i in range(1, 10, 2)]
self.check_cycle_algorithm(g, expected_cycles, chordless=True)
def test_simple_cycles_notable_clique_sequences(self):
# A000292: Number of labeled graphs on n+3 nodes that are triangles.
g_family = [self.K(n) for n in range(2, 12)]
expected = [0, 1, 4, 10, 20, 35, 56, 84, 120, 165, 220]
self.check_cycle_enumeration_integer_sequence(
g_family, expected, length_bound=3
)
def triangles(g, **kwargs):
yield from (c for c in nx.simple_cycles(g, **kwargs) if len(c) == 3)
# directed complete graphs have twice as many triangles thanks to reversal
g_family = [self.D(n) for n in range(2, 12)]
expected = [2 * e for e in expected]
self.check_cycle_enumeration_integer_sequence(
g_family, expected, length_bound=3, algorithm=triangles
)
def four_cycles(g, **kwargs):
yield from (c for c in nx.simple_cycles(g, **kwargs) if len(c) == 4)
# A050534: the number of 4-cycles in the complete graph K_{n+1}
expected = [0, 0, 0, 3, 15, 45, 105, 210, 378, 630, 990]
g_family = [self.K(n) for n in range(1, 12)]
self.check_cycle_enumeration_integer_sequence(
g_family, expected, length_bound=4, algorithm=four_cycles
)
# directed complete graphs have twice as many 4-cycles thanks to reversal
expected = [2 * e for e in expected]
g_family = [self.D(n) for n in range(1, 15)]
self.check_cycle_enumeration_integer_sequence(
g_family, expected, length_bound=4, algorithm=four_cycles
)
# A006231: the number of elementary circuits in a complete directed graph with n nodes
expected = [0, 1, 5, 20, 84, 409, 2365]
g_family = [self.D(n) for n in range(1, 8)]
self.check_cycle_enumeration_integer_sequence(g_family, expected)
# A002807: Number of cycles in the complete graph on n nodes K_{n}.
expected = [0, 0, 0, 1, 7, 37, 197, 1172]
g_family = [self.K(n) for n in range(8)]
self.check_cycle_enumeration_integer_sequence(g_family, expected)
def test_directed_chordless_cycle_parallel_multiedges(self):
g = nx.MultiGraph()
nx.add_cycle(g, range(5))
expected = [[*range(5)]]
self.check_cycle_algorithm(g, expected, chordless=True)
nx.add_cycle(g, range(5))
expected = [*cycle_edges(range(5))]
self.check_cycle_algorithm(g, expected, chordless=True)
nx.add_cycle(g, range(5))
expected = []
self.check_cycle_algorithm(g, expected, chordless=True)
g = nx.MultiDiGraph()
nx.add_cycle(g, range(5))
expected = [[*range(5)]]
self.check_cycle_algorithm(g, expected, chordless=True)
nx.add_cycle(g, range(5))
self.check_cycle_algorithm(g, [], chordless=True)
nx.add_cycle(g, range(5))
self.check_cycle_algorithm(g, [], chordless=True)
g = nx.MultiDiGraph()
nx.add_cycle(g, range(5))
nx.add_cycle(g, range(5)[::-1])
expected = [*cycle_edges(range(5))]
self.check_cycle_algorithm(g, expected, chordless=True)
nx.add_cycle(g, range(5))
self.check_cycle_algorithm(g, [], chordless=True)
def test_chordless_cycles_graph(self):
G = nx.Graph()
nx.add_cycle(G, range(5))
nx.add_cycle(G, range(4, 12))
expected = [[*range(5)], [*range(4, 12)]]
self.check_cycle_algorithm(G, expected, chordless=True)
self.check_cycle_algorithm(
G, [c for c in expected if len(c) <= 5], length_bound=5, chordless=True
)
G.add_edge(7, 3)
expected.append([*range(3, 8)])
expected.append([4, 3, 7, 8, 9, 10, 11])
self.check_cycle_algorithm(G, expected, chordless=True)
self.check_cycle_algorithm(
G, [c for c in expected if len(c) <= 5], length_bound=5, chordless=True
)
def test_chordless_cycles_giant_hamiltonian(self):
# ... o - e - o - e - o ... # o = odd, e = even
# ... ---/ \-----/ \--- ... # <-- "long" edges
#
# each long edge belongs to exactly one triangle, and one giant cycle
# of length n/2. The remaining edges each belong to a triangle
n = 1000
assert n % 2 == 0
G = nx.Graph()
for v in range(n):
if not v % 2:
G.add_edge(v, (v + 2) % n)
G.add_edge(v, (v + 1) % n)
expected = [[*range(0, n, 2)]] + [
[x % n for x in range(i, i + 3)] for i in range(0, n, 2)
]
self.check_cycle_algorithm(G, expected, chordless=True)
self.check_cycle_algorithm(
G, [c for c in expected if len(c) <= 3], length_bound=3, chordless=True
)
# ... o -> e -> o -> e -> o ... # o = odd, e = even
# ... <---/ \---<---/ \---< ... # <-- "long" edges
#
# this time, we orient the short and long edges in opposition
# the cycle structure of this graph is the same, but we need to reverse
# the long one in our representation. Also, we need to drop the size
# because our partitioning algorithm uses strongly connected components
# instead of separating graphs by their strong articulation points
n = 100
assert n % 2 == 0
G = nx.DiGraph()
for v in range(n):
G.add_edge(v, (v + 1) % n)
if not v % 2:
G.add_edge((v + 2) % n, v)
expected = [[*range(n - 2, -2, -2)]] + [
[x % n for x in range(i, i + 3)] for i in range(0, n, 2)
]
self.check_cycle_algorithm(G, expected, chordless=True)
self.check_cycle_algorithm(
G, [c for c in expected if len(c) <= 3], length_bound=3, chordless=True
)
def test_simple_cycles_acyclic_tournament(self):
n = 10
G = nx.DiGraph((x, y) for x in range(n) for y in range(x))
self.check_cycle_algorithm(G, [])
self.check_cycle_algorithm(G, [], chordless=True)
for k in range(n + 1):
self.check_cycle_algorithm(G, [], length_bound=k)
self.check_cycle_algorithm(G, [], length_bound=k, chordless=True)
def test_simple_cycles_graph(self):
testG = nx.cycle_graph(8)
cyc1 = tuple(range(8))
self.check_cycle_algorithm(testG, [cyc1])
testG.add_edge(4, -1)
nx.add_path(testG, [3, -2, -3, -4])
self.check_cycle_algorithm(testG, [cyc1])
testG.update(nx.cycle_graph(range(8, 16)))
cyc2 = tuple(range(8, 16))
self.check_cycle_algorithm(testG, [cyc1, cyc2])
testG.update(nx.cycle_graph(range(4, 12)))
cyc3 = tuple(range(4, 12))
expected = {
(0, 1, 2, 3, 4, 5, 6, 7), # cyc1
(8, 9, 10, 11, 12, 13, 14, 15), # cyc2
(4, 5, 6, 7, 8, 9, 10, 11), # cyc3
(4, 5, 6, 7, 8, 15, 14, 13, 12, 11), # cyc2 + cyc3
(0, 1, 2, 3, 4, 11, 10, 9, 8, 7), # cyc1 + cyc3
(0, 1, 2, 3, 4, 11, 12, 13, 14, 15, 8, 7), # cyc1 + cyc2 + cyc3
}
self.check_cycle_algorithm(testG, expected)
assert len(expected) == (2**3 - 1) - 1 # 1 disjoint comb: cyc1 + cyc2
# Basis size = 5 (2 loops overlapping gives 5 small loops
# E
# / \ Note: A-F = 10-15
# 1-2-3-4-5
# / | | \ cyc1=012DAB -- left
# 0 D F 6 cyc2=234E -- top
# \ | | / cyc3=45678F -- right
# B-A-9-8-7 cyc4=89AC -- bottom
# \ / cyc5=234F89AD -- middle
# C
#
# combinations of 5 basis elements: 2^5 - 1 (one includes no cycles)
#
# disjoint combs: (11 total) not simple cycles
# Any pair not including cyc5 => choose(4, 2) = 6
# Any triple not including cyc5 => choose(4, 3) = 4
# Any quad not including cyc5 => choose(4, 4) = 1
#
# we expect 31 - 11 = 20 simple cycles
#
testG = nx.cycle_graph(12)
testG.update(nx.cycle_graph([12, 10, 13, 2, 14, 4, 15, 8]).edges)
expected = (2**5 - 1) - 11 # 11 disjoint combinations
self.check_cycle_algorithm(testG, expected)
def test_simple_cycles_bounded(self):
# iteratively construct a cluster of nested cycles running in the same direction
# there should be one cycle of every length
d = nx.DiGraph()
expected = []
for n in range(10):
nx.add_cycle(d, range(n))
expected.append(n)
for k, e in enumerate(expected):
self.check_cycle_algorithm(d, e, length_bound=k)
# iteratively construct a path of undirected cycles, connected at articulation
# points. there should be one cycle of every length except 2: no digons
g = nx.Graph()
top = 0
expected = []
for n in range(10):
expected.append(n if n < 2 else n - 1)
if n == 2:
# no digons in undirected graphs
continue
nx.add_cycle(g, range(top, top + n))
top += n
for k, e in enumerate(expected):
self.check_cycle_algorithm(g, e, length_bound=k)
def test_simple_cycles_bound_corner_cases(self):
G = nx.cycle_graph(4)
DG = nx.cycle_graph(4, create_using=nx.DiGraph)
assert list(nx.simple_cycles(G, length_bound=0)) == []
assert list(nx.simple_cycles(DG, length_bound=0)) == []
assert list(nx.chordless_cycles(G, length_bound=0)) == []
assert list(nx.chordless_cycles(DG, length_bound=0)) == []
def test_simple_cycles_bound_error(self):
with pytest.raises(ValueError):
G = nx.DiGraph()
for c in nx.simple_cycles(G, -1):
assert False
with pytest.raises(ValueError):
G = nx.Graph()
for c in nx.simple_cycles(G, -1):
assert False
with pytest.raises(ValueError):
G = nx.Graph()
for c in nx.chordless_cycles(G, -1):
assert False
with pytest.raises(ValueError):
G = nx.DiGraph()
for c in nx.chordless_cycles(G, -1):
assert False
def test_chordless_cycles_clique(self):
g_family = [self.K(n) for n in range(2, 15)]
expected = [0, 1, 4, 10, 20, 35, 56, 84, 120, 165, 220, 286, 364]
self.check_cycle_enumeration_integer_sequence(
g_family, expected, chordless=True
)
# directed cliques have as many digons as undirected graphs have edges
expected = [(n * n - n) // 2 for n in range(15)]
g_family = [self.D(n) for n in range(15)]
self.check_cycle_enumeration_integer_sequence(
g_family, expected, chordless=True
)
# These tests might fail with hash randomization since they depend on
# edge_dfs. For more information, see the comments in:
# networkx/algorithms/traversal/tests/test_edgedfs.py
class TestFindCycle:
@classmethod
def setup_class(cls):
cls.nodes = [0, 1, 2, 3]
cls.edges = [(-1, 0), (0, 1), (1, 0), (1, 0), (2, 1), (3, 1)]
def test_graph_nocycle(self):
G = nx.Graph(self.edges)
pytest.raises(nx.exception.NetworkXNoCycle, nx.find_cycle, G, self.nodes)
def test_graph_cycle(self):
G = nx.Graph(self.edges)
G.add_edge(2, 0)
x = list(nx.find_cycle(G, self.nodes))
x_ = [(0, 1), (1, 2), (2, 0)]
assert x == x_
def test_graph_orientation_none(self):
G = nx.Graph(self.edges)
G.add_edge(2, 0)
x = list(nx.find_cycle(G, self.nodes, orientation=None))
x_ = [(0, 1), (1, 2), (2, 0)]
assert x == x_
def test_graph_orientation_original(self):
G = nx.Graph(self.edges)
G.add_edge(2, 0)
x = list(nx.find_cycle(G, self.nodes, orientation="original"))
x_ = [(0, 1, FORWARD), (1, 2, FORWARD), (2, 0, FORWARD)]
assert x == x_
def test_digraph(self):
G = nx.DiGraph(self.edges)
x = list(nx.find_cycle(G, self.nodes))
x_ = [(0, 1), (1, 0)]
assert x == x_
def test_digraph_orientation_none(self):
G = nx.DiGraph(self.edges)
x = list(nx.find_cycle(G, self.nodes, orientation=None))
x_ = [(0, 1), (1, 0)]
assert x == x_
def test_digraph_orientation_original(self):
G = nx.DiGraph(self.edges)
x = list(nx.find_cycle(G, self.nodes, orientation="original"))
x_ = [(0, 1, FORWARD), (1, 0, FORWARD)]
assert x == x_
def test_multigraph(self):
G = nx.MultiGraph(self.edges)
x = list(nx.find_cycle(G, self.nodes))
x_ = [(0, 1, 0), (1, 0, 1)] # or (1, 0, 2)
# Hash randomization...could be any edge.
assert x[0] == x_[0]
assert x[1][:2] == x_[1][:2]
def test_multidigraph(self):
G = nx.MultiDiGraph(self.edges)
x = list(nx.find_cycle(G, self.nodes))
x_ = [(0, 1, 0), (1, 0, 0)] # (1, 0, 1)
assert x[0] == x_[0]
assert x[1][:2] == x_[1][:2]
def test_digraph_ignore(self):
G = nx.DiGraph(self.edges)
x = list(nx.find_cycle(G, self.nodes, orientation="ignore"))
x_ = [(0, 1, FORWARD), (1, 0, FORWARD)]
assert x == x_
def test_digraph_reverse(self):
G = nx.DiGraph(self.edges)
x = list(nx.find_cycle(G, self.nodes, orientation="reverse"))
x_ = [(1, 0, REVERSE), (0, 1, REVERSE)]
assert x == x_
def test_multidigraph_ignore(self):
G = nx.MultiDiGraph(self.edges)
x = list(nx.find_cycle(G, self.nodes, orientation="ignore"))
x_ = [(0, 1, 0, FORWARD), (1, 0, 0, FORWARD)] # or (1, 0, 1, 1)
assert x[0] == x_[0]
assert x[1][:2] == x_[1][:2]
assert x[1][3] == x_[1][3]
def test_multidigraph_ignore2(self):
# Loop traversed an edge while ignoring its orientation.
G = nx.MultiDiGraph([(0, 1), (1, 2), (1, 2)])
x = list(nx.find_cycle(G, [0, 1, 2], orientation="ignore"))
x_ = [(1, 2, 0, FORWARD), (1, 2, 1, REVERSE)]
assert x == x_
def test_multidigraph_original(self):
# Node 2 doesn't need to be searched again from visited from 4.
# The goal here is to cover the case when 2 to be researched from 4,
# when 4 is visited from the first time (so we must make sure that 4
# is not visited from 2, and hence, we respect the edge orientation).
G = nx.MultiDiGraph([(0, 1), (1, 2), (2, 3), (4, 2)])
pytest.raises(
nx.exception.NetworkXNoCycle,
nx.find_cycle,
G,
[0, 1, 2, 3, 4],
orientation="original",
)
def test_dag(self):
G = nx.DiGraph([(0, 1), (0, 2), (1, 2)])
pytest.raises(
nx.exception.NetworkXNoCycle, nx.find_cycle, G, orientation="original"
)
x = list(nx.find_cycle(G, orientation="ignore"))
assert x == [(0, 1, FORWARD), (1, 2, FORWARD), (0, 2, REVERSE)]
def test_prev_explored(self):
# https://github.com/networkx/networkx/issues/2323
G = nx.DiGraph()
G.add_edges_from([(1, 0), (2, 0), (1, 2), (2, 1)])
pytest.raises(nx.NetworkXNoCycle, nx.find_cycle, G, source=0)
x = list(nx.find_cycle(G, 1))
x_ = [(1, 2), (2, 1)]
assert x == x_
x = list(nx.find_cycle(G, 2))
x_ = [(2, 1), (1, 2)]
assert x == x_
x = list(nx.find_cycle(G))
x_ = [(1, 2), (2, 1)]
assert x == x_
def test_no_cycle(self):
# https://github.com/networkx/networkx/issues/2439
G = nx.DiGraph()
G.add_edges_from([(1, 2), (2, 0), (3, 1), (3, 2)])
pytest.raises(nx.NetworkXNoCycle, nx.find_cycle, G, source=0)
pytest.raises(nx.NetworkXNoCycle, nx.find_cycle, G)
def assert_basis_equal(a, b):
assert sorted(a) == sorted(b)
class TestMinimumCycleBasis:
@classmethod
def setup_class(cls):
T = nx.Graph()
nx.add_cycle(T, [1, 2, 3, 4], weight=1)
T.add_edge(2, 4, weight=5)
cls.diamond_graph = T
def test_unweighted_diamond(self):
mcb = nx.minimum_cycle_basis(self.diamond_graph)
assert_basis_equal(mcb, [[2, 4, 1], [3, 4, 2]])
def test_weighted_diamond(self):
mcb = nx.minimum_cycle_basis(self.diamond_graph, weight="weight")
assert_basis_equal(mcb, [[2, 4, 1], [4, 3, 2, 1]])
def test_dimensionality(self):
# checks |MCB|=|E|-|V|+|NC|
ntrial = 10
for seed in range(1234, 1234 + ntrial):
rg = nx.erdos_renyi_graph(10, 0.3, seed=seed)
nnodes = rg.number_of_nodes()
nedges = rg.number_of_edges()
ncomp = nx.number_connected_components(rg)
mcb = nx.minimum_cycle_basis(rg)
assert len(mcb) == nedges - nnodes + ncomp
check_independent(mcb)
def test_complete_graph(self):
cg = nx.complete_graph(5)
mcb = nx.minimum_cycle_basis(cg)
assert all(len(cycle) == 3 for cycle in mcb)
check_independent(mcb)
def test_tree_graph(self):
tg = nx.balanced_tree(3, 3)
assert not nx.minimum_cycle_basis(tg)
def test_petersen_graph(self):
G = nx.petersen_graph()
mcb = list(nx.minimum_cycle_basis(G))
expected = [
[4, 9, 7, 5, 0],
[1, 2, 3, 4, 0],
[1, 6, 8, 5, 0],
[4, 3, 8, 5, 0],
[1, 6, 9, 4, 0],
[1, 2, 7, 5, 0],
]
assert len(mcb) == len(expected)
assert all(c in expected for c in mcb)
# check that order of the nodes is a path
for c in mcb:
assert all(G.has_edge(u, v) for u, v in nx.utils.pairwise(c, cyclic=True))
# check independence of the basis
check_independent(mcb)
def test_gh6787_variable_weighted_complete_graph(self):
N = 8
cg = nx.complete_graph(N)
cg.add_weighted_edges_from([(u, v, 9) for u, v in cg.edges])
cg.add_weighted_edges_from([(u, v, 1) for u, v in nx.cycle_graph(N).edges])
mcb = nx.minimum_cycle_basis(cg, weight="weight")
check_independent(mcb)
def test_gh6787_and_edge_attribute_names(self):
G = nx.cycle_graph(4)
G.add_weighted_edges_from([(0, 2, 10), (1, 3, 10)], weight="dist")
expected = [[1, 3, 0], [3, 2, 1, 0], [1, 2, 0]]
mcb = list(nx.minimum_cycle_basis(G, weight="dist"))
assert len(mcb) == len(expected)
assert all(c in expected for c in mcb)
# test not using a weight with weight attributes
expected = [[1, 3, 0], [1, 2, 0], [3, 2, 0]]
mcb = list(nx.minimum_cycle_basis(G))
assert len(mcb) == len(expected)
assert all(c in expected for c in mcb)
class TestGirth:
@pytest.mark.parametrize(
("G", "expected"),
(
(nx.chvatal_graph(), 4),
(nx.tutte_graph(), 4),
(nx.petersen_graph(), 5),
(nx.heawood_graph(), 6),
(nx.pappus_graph(), 6),
(nx.random_labeled_tree(10, seed=42), inf),
(nx.empty_graph(10), inf),
(nx.Graph(chain(cycle_edges(range(5)), cycle_edges(range(6, 10)))), 4),
(
nx.Graph(
[
(0, 6),
(0, 8),
(0, 9),
(1, 8),
(2, 8),
(2, 9),
(4, 9),
(5, 9),
(6, 8),
(6, 9),
(7, 8),
]
),
3,
),
),
)
def test_girth(self, G, expected):
assert nx.girth(G) == expected
@@ -0,0 +1,340 @@
from itertools import combinations
import pytest
import networkx as nx
def path_graph():
"""Return a path graph of length three."""
G = nx.path_graph(3, create_using=nx.DiGraph)
G.graph["name"] = "path"
nx.freeze(G)
return G
def fork_graph():
"""Return a three node fork graph."""
G = nx.DiGraph(name="fork")
G.add_edges_from([(0, 1), (0, 2)])
nx.freeze(G)
return G
def collider_graph():
"""Return a collider/v-structure graph with three nodes."""
G = nx.DiGraph(name="collider")
G.add_edges_from([(0, 2), (1, 2)])
nx.freeze(G)
return G
def naive_bayes_graph():
"""Return a simply Naive Bayes PGM graph."""
G = nx.DiGraph(name="naive_bayes")
G.add_edges_from([(0, 1), (0, 2), (0, 3), (0, 4)])
nx.freeze(G)
return G
def asia_graph():
"""Return the 'Asia' PGM graph."""
G = nx.DiGraph(name="asia")
G.add_edges_from(
[
("asia", "tuberculosis"),
("smoking", "cancer"),
("smoking", "bronchitis"),
("tuberculosis", "either"),
("cancer", "either"),
("either", "xray"),
("either", "dyspnea"),
("bronchitis", "dyspnea"),
]
)
nx.freeze(G)
return G
@pytest.fixture(name="path_graph")
def path_graph_fixture():
return path_graph()
@pytest.fixture(name="fork_graph")
def fork_graph_fixture():
return fork_graph()
@pytest.fixture(name="collider_graph")
def collider_graph_fixture():
return collider_graph()
@pytest.fixture(name="naive_bayes_graph")
def naive_bayes_graph_fixture():
return naive_bayes_graph()
@pytest.fixture(name="asia_graph")
def asia_graph_fixture():
return asia_graph()
@pytest.fixture()
def large_collider_graph():
edge_list = [("A", "B"), ("C", "B"), ("B", "D"), ("D", "E"), ("B", "F"), ("G", "E")]
G = nx.DiGraph(edge_list)
return G
@pytest.fixture()
def chain_and_fork_graph():
edge_list = [("A", "B"), ("B", "C"), ("B", "D"), ("D", "C")]
G = nx.DiGraph(edge_list)
return G
@pytest.fixture()
def no_separating_set_graph():
edge_list = [("A", "B")]
G = nx.DiGraph(edge_list)
return G
@pytest.fixture()
def large_no_separating_set_graph():
edge_list = [("A", "B"), ("C", "A"), ("C", "B")]
G = nx.DiGraph(edge_list)
return G
@pytest.fixture()
def collider_trek_graph():
edge_list = [("A", "B"), ("C", "B"), ("C", "D")]
G = nx.DiGraph(edge_list)
return G
@pytest.mark.parametrize(
"graph",
[path_graph(), fork_graph(), collider_graph(), naive_bayes_graph(), asia_graph()],
)
def test_markov_condition(graph):
"""Test that the Markov condition holds for each PGM graph."""
for node in graph.nodes:
parents = set(graph.predecessors(node))
non_descendants = graph.nodes - nx.descendants(graph, node) - {node} - parents
assert nx.is_d_separator(graph, {node}, non_descendants, parents)
def test_path_graph_dsep(path_graph):
"""Example-based test of d-separation for path_graph."""
assert nx.is_d_separator(path_graph, {0}, {2}, {1})
assert not nx.is_d_separator(path_graph, {0}, {2}, set())
def test_fork_graph_dsep(fork_graph):
"""Example-based test of d-separation for fork_graph."""
assert nx.is_d_separator(fork_graph, {1}, {2}, {0})
assert not nx.is_d_separator(fork_graph, {1}, {2}, set())
def test_collider_graph_dsep(collider_graph):
"""Example-based test of d-separation for collider_graph."""
assert nx.is_d_separator(collider_graph, {0}, {1}, set())
assert not nx.is_d_separator(collider_graph, {0}, {1}, {2})
def test_naive_bayes_dsep(naive_bayes_graph):
"""Example-based test of d-separation for naive_bayes_graph."""
for u, v in combinations(range(1, 5), 2):
assert nx.is_d_separator(naive_bayes_graph, {u}, {v}, {0})
assert not nx.is_d_separator(naive_bayes_graph, {u}, {v}, set())
def test_asia_graph_dsep(asia_graph):
"""Example-based test of d-separation for asia_graph."""
assert nx.is_d_separator(
asia_graph, {"asia", "smoking"}, {"dyspnea", "xray"}, {"bronchitis", "either"}
)
assert nx.is_d_separator(
asia_graph, {"tuberculosis", "cancer"}, {"bronchitis"}, {"smoking", "xray"}
)
def test_undirected_graphs_are_not_supported():
"""
Test that undirected graphs are not supported.
d-separation and its related algorithms do not apply in
the case of undirected graphs.
"""
g = nx.path_graph(3, nx.Graph)
with pytest.raises(nx.NetworkXNotImplemented):
nx.is_d_separator(g, {0}, {1}, {2})
with pytest.raises(nx.NetworkXNotImplemented):
nx.is_minimal_d_separator(g, {0}, {1}, {2})
with pytest.raises(nx.NetworkXNotImplemented):
nx.find_minimal_d_separator(g, {0}, {1})
def test_cyclic_graphs_raise_error():
"""
Test that cycle graphs should cause erroring.
This is because PGMs assume a directed acyclic graph.
"""
g = nx.cycle_graph(3, nx.DiGraph)
with pytest.raises(nx.NetworkXError):
nx.is_d_separator(g, {0}, {1}, {2})
with pytest.raises(nx.NetworkXError):
nx.find_minimal_d_separator(g, {0}, {1})
with pytest.raises(nx.NetworkXError):
nx.is_minimal_d_separator(g, {0}, {1}, {2})
def test_invalid_nodes_raise_error(asia_graph):
"""
Test that graphs that have invalid nodes passed in raise errors.
"""
# Check both set and node arguments
with pytest.raises(nx.NodeNotFound):
nx.is_d_separator(asia_graph, {0}, {1}, {2})
with pytest.raises(nx.NodeNotFound):
nx.is_d_separator(asia_graph, 0, 1, 2)
with pytest.raises(nx.NodeNotFound):
nx.is_minimal_d_separator(asia_graph, {0}, {1}, {2})
with pytest.raises(nx.NodeNotFound):
nx.is_minimal_d_separator(asia_graph, 0, 1, 2)
with pytest.raises(nx.NodeNotFound):
nx.find_minimal_d_separator(asia_graph, {0}, {1})
with pytest.raises(nx.NodeNotFound):
nx.find_minimal_d_separator(asia_graph, 0, 1)
def test_nondisjoint_node_sets_raise_error(collider_graph):
"""
Test that error is raised when node sets aren't disjoint.
"""
with pytest.raises(nx.NetworkXError):
nx.is_d_separator(collider_graph, 0, 1, 0)
with pytest.raises(nx.NetworkXError):
nx.is_d_separator(collider_graph, 0, 2, 0)
with pytest.raises(nx.NetworkXError):
nx.is_d_separator(collider_graph, 0, 0, 1)
with pytest.raises(nx.NetworkXError):
nx.is_d_separator(collider_graph, 1, 0, 0)
with pytest.raises(nx.NetworkXError):
nx.find_minimal_d_separator(collider_graph, 0, 0)
with pytest.raises(nx.NetworkXError):
nx.find_minimal_d_separator(collider_graph, 0, 1, included=0)
with pytest.raises(nx.NetworkXError):
nx.find_minimal_d_separator(collider_graph, 1, 0, included=0)
with pytest.raises(nx.NetworkXError):
nx.is_minimal_d_separator(collider_graph, 0, 0, set())
with pytest.raises(nx.NetworkXError):
nx.is_minimal_d_separator(collider_graph, 0, 1, set(), included=0)
with pytest.raises(nx.NetworkXError):
nx.is_minimal_d_separator(collider_graph, 1, 0, set(), included=0)
def test_is_minimal_d_separator(
large_collider_graph,
chain_and_fork_graph,
no_separating_set_graph,
large_no_separating_set_graph,
collider_trek_graph,
):
# Case 1:
# create a graph A -> B <- C
# B -> D -> E;
# B -> F;
# G -> E;
assert not nx.is_d_separator(large_collider_graph, {"B"}, {"E"}, set())
# minimal set of the corresponding graph
# for B and E should be (D,)
Zmin = nx.find_minimal_d_separator(large_collider_graph, "B", "E")
# check that the minimal d-separator is a d-separating set
assert nx.is_d_separator(large_collider_graph, "B", "E", Zmin)
# the minimal separating set should also pass the test for minimality
assert nx.is_minimal_d_separator(large_collider_graph, "B", "E", Zmin)
# function should also work with set arguments
assert nx.is_minimal_d_separator(large_collider_graph, {"A", "B"}, {"G", "E"}, Zmin)
assert Zmin == {"D"}
# Case 2:
# create a graph A -> B -> C
# B -> D -> C;
assert not nx.is_d_separator(chain_and_fork_graph, {"A"}, {"C"}, set())
Zmin = nx.find_minimal_d_separator(chain_and_fork_graph, "A", "C")
# the minimal separating set should pass the test for minimality
assert nx.is_minimal_d_separator(chain_and_fork_graph, "A", "C", Zmin)
assert Zmin == {"B"}
Znotmin = Zmin.union({"D"})
assert not nx.is_minimal_d_separator(chain_and_fork_graph, "A", "C", Znotmin)
# Case 3:
# create a graph A -> B
# there is no m-separating set between A and B at all, so
# no minimal m-separating set can exist
assert not nx.is_d_separator(no_separating_set_graph, {"A"}, {"B"}, set())
assert nx.find_minimal_d_separator(no_separating_set_graph, "A", "B") is None
# Case 4:
# create a graph A -> B with A <- C -> B
# there is no m-separating set between A and B at all, so
# no minimal m-separating set can exist
# however, the algorithm will initially propose C as a
# minimal (but invalid) separating set
assert not nx.is_d_separator(large_no_separating_set_graph, {"A"}, {"B"}, {"C"})
assert nx.find_minimal_d_separator(large_no_separating_set_graph, "A", "B") is None
# Test `included` and `excluded` args
# create graph A -> B <- C -> D
assert nx.find_minimal_d_separator(collider_trek_graph, "A", "D", included="B") == {
"B",
"C",
}
assert (
nx.find_minimal_d_separator(
collider_trek_graph, "A", "D", included="B", restricted="B"
)
is None
)
def test_is_minimal_d_separator_checks_dsep():
"""Test that is_minimal_d_separator checks for d-separation as well."""
g = nx.DiGraph()
g.add_edges_from(
[
("A", "B"),
("A", "E"),
("B", "C"),
("B", "D"),
("D", "C"),
("D", "F"),
("E", "D"),
("E", "F"),
]
)
assert not nx.is_d_separator(g, {"C"}, {"F"}, {"D"})
# since {'D'} and {} are not d-separators, we return false
assert not nx.is_minimal_d_separator(g, "C", "F", {"D"})
assert not nx.is_minimal_d_separator(g, "C", "F", set())
def test__reachable(large_collider_graph):
reachable = nx.algorithms.d_separation._reachable
g = large_collider_graph
x = {"F", "D"}
ancestors = {"A", "B", "C", "D", "F"}
assert reachable(g, x, ancestors, {"B"}) == {"B", "F", "D"}
assert reachable(g, x, ancestors, set()) == ancestors
@@ -0,0 +1,835 @@
from collections import deque
from itertools import combinations, permutations
import pytest
import networkx as nx
from networkx.utils import edges_equal, pairwise
# Recipe from the itertools documentation.
def _consume(iterator):
"Consume the iterator entirely."
# Feed the entire iterator into a zero-length deque.
deque(iterator, maxlen=0)
class TestDagLongestPath:
"""Unit tests computing the longest path in a directed acyclic graph."""
def test_empty(self):
G = nx.DiGraph()
assert nx.dag_longest_path(G) == []
def test_unweighted1(self):
edges = [(1, 2), (2, 3), (2, 4), (3, 5), (5, 6), (3, 7)]
G = nx.DiGraph(edges)
assert nx.dag_longest_path(G) == [1, 2, 3, 5, 6]
def test_unweighted2(self):
edges = [(1, 2), (2, 3), (3, 4), (4, 5), (1, 3), (1, 5), (3, 5)]
G = nx.DiGraph(edges)
assert nx.dag_longest_path(G) == [1, 2, 3, 4, 5]
def test_weighted(self):
G = nx.DiGraph()
edges = [(1, 2, -5), (2, 3, 1), (3, 4, 1), (4, 5, 0), (3, 5, 4), (1, 6, 2)]
G.add_weighted_edges_from(edges)
assert nx.dag_longest_path(G) == [2, 3, 5]
def test_undirected_not_implemented(self):
G = nx.Graph()
pytest.raises(nx.NetworkXNotImplemented, nx.dag_longest_path, G)
def test_unorderable_nodes(self):
"""Tests that computing the longest path does not depend on
nodes being orderable.
For more information, see issue #1989.
"""
# Create the directed path graph on four nodes in a diamond shape,
# with nodes represented as (unorderable) Python objects.
nodes = [object() for n in range(4)]
G = nx.DiGraph()
G.add_edge(nodes[0], nodes[1])
G.add_edge(nodes[0], nodes[2])
G.add_edge(nodes[2], nodes[3])
G.add_edge(nodes[1], nodes[3])
# this will raise NotImplementedError when nodes need to be ordered
nx.dag_longest_path(G)
def test_multigraph_unweighted(self):
edges = [(1, 2), (2, 3), (2, 3), (3, 4), (4, 5), (1, 3), (1, 5), (3, 5)]
G = nx.MultiDiGraph(edges)
assert nx.dag_longest_path(G) == [1, 2, 3, 4, 5]
def test_multigraph_weighted(self):
G = nx.MultiDiGraph()
edges = [
(1, 2, 2),
(2, 3, 2),
(1, 3, 1),
(1, 3, 5),
(1, 3, 2),
]
G.add_weighted_edges_from(edges)
assert nx.dag_longest_path(G) == [1, 3]
def test_multigraph_weighted_default_weight(self):
G = nx.MultiDiGraph([(1, 2), (2, 3)]) # Unweighted edges
G.add_weighted_edges_from([(1, 3, 1), (1, 3, 5), (1, 3, 2)])
# Default value for default weight is 1
assert nx.dag_longest_path(G) == [1, 3]
assert nx.dag_longest_path(G, default_weight=3) == [1, 2, 3]
class TestDagLongestPathLength:
"""Unit tests for computing the length of a longest path in a
directed acyclic graph.
"""
def test_unweighted(self):
edges = [(1, 2), (2, 3), (2, 4), (3, 5), (5, 6), (5, 7)]
G = nx.DiGraph(edges)
assert nx.dag_longest_path_length(G) == 4
edges = [(1, 2), (2, 3), (3, 4), (4, 5), (1, 3), (1, 5), (3, 5)]
G = nx.DiGraph(edges)
assert nx.dag_longest_path_length(G) == 4
# test degenerate graphs
G = nx.DiGraph()
G.add_node(1)
assert nx.dag_longest_path_length(G) == 0
def test_undirected_not_implemented(self):
G = nx.Graph()
pytest.raises(nx.NetworkXNotImplemented, nx.dag_longest_path_length, G)
def test_weighted(self):
edges = [(1, 2, -5), (2, 3, 1), (3, 4, 1), (4, 5, 0), (3, 5, 4), (1, 6, 2)]
G = nx.DiGraph()
G.add_weighted_edges_from(edges)
assert nx.dag_longest_path_length(G) == 5
def test_multigraph_unweighted(self):
edges = [(1, 2), (2, 3), (2, 3), (3, 4), (4, 5), (1, 3), (1, 5), (3, 5)]
G = nx.MultiDiGraph(edges)
assert nx.dag_longest_path_length(G) == 4
def test_multigraph_weighted(self):
G = nx.MultiDiGraph()
edges = [
(1, 2, 2),
(2, 3, 2),
(1, 3, 1),
(1, 3, 5),
(1, 3, 2),
]
G.add_weighted_edges_from(edges)
assert nx.dag_longest_path_length(G) == 5
class TestDAG:
@classmethod
def setup_class(cls):
pass
def test_topological_sort1(self):
DG = nx.DiGraph([(1, 2), (1, 3), (2, 3)])
for algorithm in [nx.topological_sort, nx.lexicographical_topological_sort]:
assert tuple(algorithm(DG)) == (1, 2, 3)
DG.add_edge(3, 2)
for algorithm in [nx.topological_sort, nx.lexicographical_topological_sort]:
pytest.raises(nx.NetworkXUnfeasible, _consume, algorithm(DG))
DG.remove_edge(2, 3)
for algorithm in [nx.topological_sort, nx.lexicographical_topological_sort]:
assert tuple(algorithm(DG)) == (1, 3, 2)
DG.remove_edge(3, 2)
assert tuple(nx.topological_sort(DG)) in {(1, 2, 3), (1, 3, 2)}
assert tuple(nx.lexicographical_topological_sort(DG)) == (1, 2, 3)
def test_is_directed_acyclic_graph(self):
G = nx.generators.complete_graph(2)
assert not nx.is_directed_acyclic_graph(G)
assert not nx.is_directed_acyclic_graph(G.to_directed())
assert not nx.is_directed_acyclic_graph(nx.Graph([(3, 4), (4, 5)]))
assert nx.is_directed_acyclic_graph(nx.DiGraph([(3, 4), (4, 5)]))
def test_topological_sort2(self):
DG = nx.DiGraph(
{
1: [2],
2: [3],
3: [4],
4: [5],
5: [1],
11: [12],
12: [13],
13: [14],
14: [15],
}
)
pytest.raises(nx.NetworkXUnfeasible, _consume, nx.topological_sort(DG))
assert not nx.is_directed_acyclic_graph(DG)
DG.remove_edge(1, 2)
_consume(nx.topological_sort(DG))
assert nx.is_directed_acyclic_graph(DG)
def test_topological_sort3(self):
DG = nx.DiGraph()
DG.add_edges_from([(1, i) for i in range(2, 5)])
DG.add_edges_from([(2, i) for i in range(5, 9)])
DG.add_edges_from([(6, i) for i in range(9, 12)])
DG.add_edges_from([(4, i) for i in range(12, 15)])
def validate(order):
assert isinstance(order, list)
assert set(order) == set(DG)
for u, v in combinations(order, 2):
assert not nx.has_path(DG, v, u)
validate(list(nx.topological_sort(DG)))
DG.add_edge(14, 1)
pytest.raises(nx.NetworkXUnfeasible, _consume, nx.topological_sort(DG))
def test_topological_sort4(self):
G = nx.Graph()
G.add_edge(1, 2)
# Only directed graphs can be topologically sorted.
pytest.raises(nx.NetworkXError, _consume, nx.topological_sort(G))
def test_topological_sort5(self):
G = nx.DiGraph()
G.add_edge(0, 1)
assert list(nx.topological_sort(G)) == [0, 1]
def test_topological_sort6(self):
for algorithm in [nx.topological_sort, nx.lexicographical_topological_sort]:
def runtime_error():
DG = nx.DiGraph([(1, 2), (2, 3), (3, 4)])
first = True
for x in algorithm(DG):
if first:
first = False
DG.add_edge(5 - x, 5)
def unfeasible_error():
DG = nx.DiGraph([(1, 2), (2, 3), (3, 4)])
first = True
for x in algorithm(DG):
if first:
first = False
DG.remove_node(4)
def runtime_error2():
DG = nx.DiGraph([(1, 2), (2, 3), (3, 4)])
first = True
for x in algorithm(DG):
if first:
first = False
DG.remove_node(2)
pytest.raises(RuntimeError, runtime_error)
pytest.raises(RuntimeError, runtime_error2)
pytest.raises(nx.NetworkXUnfeasible, unfeasible_error)
def test_all_topological_sorts_1(self):
DG = nx.DiGraph([(1, 2), (2, 3), (3, 4), (4, 5)])
assert list(nx.all_topological_sorts(DG)) == [[1, 2, 3, 4, 5]]
def test_all_topological_sorts_2(self):
DG = nx.DiGraph([(1, 3), (2, 1), (2, 4), (4, 3), (4, 5)])
assert sorted(nx.all_topological_sorts(DG)) == [
[2, 1, 4, 3, 5],
[2, 1, 4, 5, 3],
[2, 4, 1, 3, 5],
[2, 4, 1, 5, 3],
[2, 4, 5, 1, 3],
]
def test_all_topological_sorts_3(self):
def unfeasible():
DG = nx.DiGraph([(1, 2), (2, 3), (3, 4), (4, 2), (4, 5)])
# convert to list to execute generator
list(nx.all_topological_sorts(DG))
def not_implemented():
G = nx.Graph([(1, 2), (2, 3)])
# convert to list to execute generator
list(nx.all_topological_sorts(G))
def not_implemented_2():
G = nx.MultiGraph([(1, 2), (1, 2), (2, 3)])
list(nx.all_topological_sorts(G))
pytest.raises(nx.NetworkXUnfeasible, unfeasible)
pytest.raises(nx.NetworkXNotImplemented, not_implemented)
pytest.raises(nx.NetworkXNotImplemented, not_implemented_2)
def test_all_topological_sorts_4(self):
DG = nx.DiGraph()
for i in range(7):
DG.add_node(i)
assert sorted(map(list, permutations(DG.nodes))) == sorted(
nx.all_topological_sorts(DG)
)
def test_all_topological_sorts_multigraph_1(self):
DG = nx.MultiDiGraph([(1, 2), (1, 2), (2, 3), (3, 4), (3, 5), (3, 5), (3, 5)])
assert sorted(nx.all_topological_sorts(DG)) == sorted(
[[1, 2, 3, 4, 5], [1, 2, 3, 5, 4]]
)
def test_all_topological_sorts_multigraph_2(self):
N = 9
edges = []
for i in range(1, N):
edges.extend([(i, i + 1)] * i)
DG = nx.MultiDiGraph(edges)
assert list(nx.all_topological_sorts(DG)) == [list(range(1, N + 1))]
def test_ancestors(self):
G = nx.DiGraph()
ancestors = nx.algorithms.dag.ancestors
G.add_edges_from([(1, 2), (1, 3), (4, 2), (4, 3), (4, 5), (2, 6), (5, 6)])
assert ancestors(G, 6) == {1, 2, 4, 5}
assert ancestors(G, 3) == {1, 4}
assert ancestors(G, 1) == set()
pytest.raises(nx.NetworkXError, ancestors, G, 8)
def test_descendants(self):
G = nx.DiGraph()
descendants = nx.algorithms.dag.descendants
G.add_edges_from([(1, 2), (1, 3), (4, 2), (4, 3), (4, 5), (2, 6), (5, 6)])
assert descendants(G, 1) == {2, 3, 6}
assert descendants(G, 4) == {2, 3, 5, 6}
assert descendants(G, 3) == set()
pytest.raises(nx.NetworkXError, descendants, G, 8)
def test_transitive_closure(self):
G = nx.DiGraph([(1, 2), (2, 3), (3, 4)])
solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
assert edges_equal(nx.transitive_closure(G).edges(), solution, directed=True)
G = nx.DiGraph([(1, 2), (2, 3), (2, 4)])
solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4)]
assert edges_equal(nx.transitive_closure(G).edges(), solution, directed=True)
G = nx.DiGraph([(1, 2), (2, 3), (3, 1)])
solution = [(1, 2), (2, 1), (2, 3), (3, 2), (1, 3), (3, 1)]
soln = sorted(solution + [(n, n) for n in G])
assert edges_equal(
sorted(nx.transitive_closure(G).edges()), soln, directed=True
)
G = nx.Graph([(1, 2), (2, 3), (3, 4)])
solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
assert edges_equal(sorted(nx.transitive_closure(G).edges()), solution)
G = nx.MultiGraph([(1, 2), (2, 3), (3, 4)])
solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
assert edges_equal(sorted(nx.transitive_closure(G).edges()), solution)
G = nx.MultiDiGraph([(1, 2), (2, 3), (3, 4)])
solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
assert edges_equal(
sorted(nx.transitive_closure(G).edges()), solution, directed=True
)
# test if edge data is copied
G = nx.DiGraph([(1, 2, {"a": 3}), (2, 3, {"b": 0}), (3, 4)])
H = nx.transitive_closure(G)
for u, v in G.edges():
assert G.get_edge_data(u, v) == H.get_edge_data(u, v)
k = 10
G = nx.DiGraph((i, i + 1, {"f": "b", "weight": i}) for i in range(k))
H = nx.transitive_closure(G)
for u, v in G.edges():
assert G.get_edge_data(u, v) == H.get_edge_data(u, v)
G = nx.Graph()
with pytest.raises(nx.NetworkXError):
nx.transitive_closure(G, reflexive="wrong input")
def test_reflexive_transitive_closure(self):
G = nx.DiGraph([(1, 2), (2, 3), (3, 4)])
solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
soln = sorted(solution + [(n, n) for n in G])
assert edges_equal(nx.transitive_closure(G).edges(), solution, directed=True)
assert edges_equal(
nx.transitive_closure(G, False).edges(), solution, directed=True
)
assert edges_equal(nx.transitive_closure(G, True).edges(), soln, directed=True)
assert edges_equal(
nx.transitive_closure(G, None).edges(), solution, directed=True
)
G = nx.DiGraph([(1, 2), (2, 3), (2, 4)])
solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4)]
soln = sorted(solution + [(n, n) for n in G])
assert edges_equal(nx.transitive_closure(G).edges(), solution, directed=True)
assert edges_equal(
nx.transitive_closure(G, False).edges(), solution, directed=True
)
assert edges_equal(nx.transitive_closure(G, True).edges(), soln, directed=True)
assert edges_equal(
nx.transitive_closure(G, None).edges(), solution, directed=True
)
G = nx.DiGraph([(1, 2), (2, 3), (3, 1)])
solution = sorted([(1, 2), (2, 1), (2, 3), (3, 2), (1, 3), (3, 1)])
soln = sorted(solution + [(n, n) for n in G])
assert edges_equal(
sorted(nx.transitive_closure(G).edges()), soln, directed=True
)
assert edges_equal(
sorted(nx.transitive_closure(G, False).edges()), soln, directed=True
)
assert edges_equal(
sorted(nx.transitive_closure(G, None).edges()), solution, directed=True
)
assert edges_equal(
sorted(nx.transitive_closure(G, True).edges()), soln, directed=True
)
G = nx.Graph([(1, 2), (2, 3), (3, 4)])
solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
soln = sorted(solution + [(n, n) for n in G])
assert edges_equal(nx.transitive_closure(G).edges(), solution)
assert edges_equal(nx.transitive_closure(G, False).edges(), solution)
assert edges_equal(nx.transitive_closure(G, True).edges(), soln)
assert edges_equal(nx.transitive_closure(G, None).edges(), solution)
G = nx.MultiGraph([(1, 2), (2, 3), (3, 4)])
solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
soln = sorted(solution + [(n, n) for n in G])
assert edges_equal(nx.transitive_closure(G).edges(), solution)
assert edges_equal(nx.transitive_closure(G, False).edges(), solution)
assert edges_equal(nx.transitive_closure(G, True).edges(), soln)
assert edges_equal(nx.transitive_closure(G, None).edges(), solution)
G = nx.MultiDiGraph([(1, 2), (2, 3), (3, 4)])
solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
soln = sorted(solution + [(n, n) for n in G])
assert edges_equal(nx.transitive_closure(G).edges(), solution, directed=True)
assert edges_equal(
nx.transitive_closure(G, False).edges(), solution, directed=True
)
assert edges_equal(nx.transitive_closure(G, True).edges(), soln, directed=True)
assert edges_equal(
nx.transitive_closure(G, None).edges(), solution, directed=True
)
def test_transitive_closure_dag(self):
G = nx.DiGraph([(1, 2), (2, 3), (3, 4)])
transitive_closure = nx.algorithms.dag.transitive_closure_dag
solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
assert edges_equal(transitive_closure(G).edges(), solution, directed=True)
G = nx.DiGraph([(1, 2), (2, 3), (2, 4)])
solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4)]
assert edges_equal(transitive_closure(G).edges(), solution, directed=True)
G = nx.Graph([(1, 2), (2, 3), (3, 4)])
pytest.raises(nx.NetworkXNotImplemented, transitive_closure, G)
# test if edge data is copied
G = nx.DiGraph([(1, 2, {"a": 3}), (2, 3, {"b": 0}), (3, 4)])
H = transitive_closure(G)
for u, v in G.edges():
assert G.get_edge_data(u, v) == H.get_edge_data(u, v)
k = 10
G = nx.DiGraph((i, i + 1, {"foo": "bar", "weight": i}) for i in range(k))
H = transitive_closure(G)
for u, v in G.edges():
assert G.get_edge_data(u, v) == H.get_edge_data(u, v)
def test_transitive_reduction(self):
G = nx.DiGraph([(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)])
transitive_reduction = nx.algorithms.dag.transitive_reduction
solution = [(1, 2), (2, 3), (3, 4)]
assert edges_equal(transitive_reduction(G).edges(), solution, directed=True)
G = nx.DiGraph([(1, 2), (1, 3), (1, 4), (2, 3), (2, 4)])
transitive_reduction = nx.algorithms.dag.transitive_reduction
solution = [(1, 2), (2, 3), (2, 4)]
assert edges_equal(transitive_reduction(G).edges(), solution, directed=True)
G = nx.Graph([(1, 2), (2, 3), (3, 4)])
pytest.raises(nx.NetworkXNotImplemented, transitive_reduction, G)
def _check_antichains(self, solution, result):
sol = [frozenset(a) for a in solution]
res = [frozenset(a) for a in result]
assert set(sol) == set(res)
def test_antichains(self):
antichains = nx.algorithms.dag.antichains
G = nx.DiGraph([(1, 2), (2, 3), (3, 4)])
solution = [[], [4], [3], [2], [1]]
self._check_antichains(list(antichains(G)), solution)
G = nx.DiGraph([(1, 2), (2, 3), (2, 4), (3, 5), (5, 6), (5, 7)])
solution = [
[],
[4],
[7],
[7, 4],
[6],
[6, 4],
[6, 7],
[6, 7, 4],
[5],
[5, 4],
[3],
[3, 4],
[2],
[1],
]
self._check_antichains(list(antichains(G)), solution)
G = nx.DiGraph([(1, 2), (1, 3), (3, 4), (3, 5), (5, 6)])
solution = [
[],
[6],
[5],
[4],
[4, 6],
[4, 5],
[3],
[2],
[2, 6],
[2, 5],
[2, 4],
[2, 4, 6],
[2, 4, 5],
[2, 3],
[1],
]
self._check_antichains(list(antichains(G)), solution)
G = nx.DiGraph({0: [1, 2], 1: [4], 2: [3], 3: [4]})
solution = [[], [4], [3], [2], [1], [1, 3], [1, 2], [0]]
self._check_antichains(list(antichains(G)), solution)
G = nx.DiGraph()
self._check_antichains(list(antichains(G)), [[]])
G = nx.DiGraph()
G.add_nodes_from([0, 1, 2])
solution = [[], [0], [1], [1, 0], [2], [2, 0], [2, 1], [2, 1, 0]]
self._check_antichains(list(antichains(G)), solution)
def f(x):
return list(antichains(x))
G = nx.Graph([(1, 2), (2, 3), (3, 4)])
pytest.raises(nx.NetworkXNotImplemented, f, G)
G = nx.DiGraph([(1, 2), (2, 3), (3, 1)])
pytest.raises(nx.NetworkXUnfeasible, f, G)
def test_lexicographical_topological_sort(self):
G = nx.DiGraph([(1, 2), (2, 3), (1, 4), (1, 5), (2, 6)])
assert list(nx.lexicographical_topological_sort(G)) == [1, 2, 3, 4, 5, 6]
assert list(nx.lexicographical_topological_sort(G, key=lambda x: x)) == [
1,
2,
3,
4,
5,
6,
]
assert list(nx.lexicographical_topological_sort(G, key=lambda x: -x)) == [
1,
5,
4,
2,
6,
3,
]
def test_lexicographical_topological_sort2(self):
"""
Check the case of two or more nodes with same key value.
Want to avoid exception raised due to comparing nodes directly.
See Issue #3493
"""
class Test_Node:
def __init__(self, n):
self.label = n
self.priority = 1
def __repr__(self):
return f"Node({self.label})"
def sorting_key(node):
return node.priority
test_nodes = [Test_Node(n) for n in range(4)]
G = nx.DiGraph()
edges = [(0, 1), (0, 2), (0, 3), (2, 3)]
G.add_edges_from((test_nodes[a], test_nodes[b]) for a, b in edges)
sorting = list(nx.lexicographical_topological_sort(G, key=sorting_key))
assert sorting == test_nodes
def test_topological_generations():
G = nx.DiGraph(
{1: [2, 3], 2: [4, 5], 3: [7], 4: [], 5: [6, 7], 6: [], 7: []}
).reverse()
# order within each generation is inconsequential
generations = [sorted(gen) for gen in nx.topological_generations(G)]
expected = [[4, 6, 7], [3, 5], [2], [1]]
assert generations == expected
MG = nx.MultiDiGraph(G.edges)
MG.add_edge(2, 1)
generations = [sorted(gen) for gen in nx.topological_generations(MG)]
assert generations == expected
def test_topological_generations_empty():
G = nx.DiGraph()
assert list(nx.topological_generations(G)) == []
def test_topological_generations_cycle():
G = nx.DiGraph([[2, 1], [3, 1], [1, 2]])
with pytest.raises(nx.NetworkXUnfeasible):
list(nx.topological_generations(G))
def test_is_aperiodic_cycle():
G = nx.DiGraph()
nx.add_cycle(G, [1, 2, 3, 4])
assert not nx.is_aperiodic(G)
def test_is_aperiodic_cycle2():
G = nx.DiGraph()
nx.add_cycle(G, [1, 2, 3, 4])
nx.add_cycle(G, [3, 4, 5, 6, 7])
assert nx.is_aperiodic(G)
def test_is_aperiodic_cycle3():
G = nx.DiGraph()
nx.add_cycle(G, [1, 2, 3, 4])
nx.add_cycle(G, [3, 4, 5, 6])
assert not nx.is_aperiodic(G)
def test_is_aperiodic_cycle4():
G = nx.DiGraph()
nx.add_cycle(G, [1, 2, 3, 4])
G.add_edge(1, 3)
assert nx.is_aperiodic(G)
def test_is_aperiodic_selfloop():
G = nx.DiGraph()
nx.add_cycle(G, [1, 2, 3, 4])
G.add_edge(1, 1)
assert nx.is_aperiodic(G)
def test_is_aperiodic_null_graph_raises():
G = nx.DiGraph()
pytest.raises(nx.NetworkXPointlessConcept, nx.is_aperiodic, G)
def test_is_aperiodic_undirected_raises():
G = nx.Graph([(1, 2), (2, 3), (3, 1)])
pytest.raises(nx.NetworkXError, nx.is_aperiodic, G)
def test_is_aperiodic_disconnected_raises():
G = nx.DiGraph()
nx.add_cycle(G, [0, 1, 2])
G.add_edge(3, 3)
pytest.raises(nx.NetworkXError, nx.is_aperiodic, G)
def test_is_aperiodic_weakly_connected_raises():
G = nx.DiGraph([(1, 2), (2, 3)])
pytest.raises(nx.NetworkXError, nx.is_aperiodic, G)
def test_is_aperiodic_empty_graph():
G = nx.empty_graph(create_using=nx.DiGraph)
with pytest.raises(nx.NetworkXPointlessConcept, match="Graph has no nodes."):
nx.is_aperiodic(G)
def test_is_aperiodic_bipartite():
# Bipartite graph
G = nx.DiGraph(nx.davis_southern_women_graph())
assert not nx.is_aperiodic(G)
def test_is_aperiodic_single_node():
G = nx.DiGraph()
G.add_node(0)
assert not nx.is_aperiodic(G)
G.add_edge(0, 0)
assert nx.is_aperiodic(G)
class TestDagToBranching:
"""Unit tests for the :func:`networkx.dag_to_branching` function."""
def test_single_root(self):
"""Tests that a directed acyclic graph with a single degree
zero node produces an arborescence.
"""
G = nx.DiGraph([(0, 1), (0, 2), (1, 3), (2, 3)])
B = nx.dag_to_branching(G)
expected = nx.DiGraph([(0, 1), (1, 3), (0, 2), (2, 4)])
assert nx.is_arborescence(B)
assert nx.is_isomorphic(B, expected)
def test_multiple_roots(self):
"""Tests that a directed acyclic graph with multiple degree zero
nodes creates an arborescence with multiple (weakly) connected
components.
"""
G = nx.DiGraph([(0, 1), (0, 2), (1, 3), (2, 3), (5, 2)])
B = nx.dag_to_branching(G)
expected = nx.DiGraph([(0, 1), (1, 3), (0, 2), (2, 4), (5, 6), (6, 7)])
assert nx.is_branching(B)
assert not nx.is_arborescence(B)
assert nx.is_isomorphic(B, expected)
# # Attributes are not copied by this function. If they were, this would
# # be a good test to uncomment.
# def test_copy_attributes(self):
# """Tests that node attributes are copied in the branching."""
# G = nx.DiGraph([(0, 1), (0, 2), (1, 3), (2, 3)])
# for v in G:
# G.node[v]['label'] = str(v)
# B = nx.dag_to_branching(G)
# # Determine the root node of the branching.
# root = next(v for v, d in B.in_degree() if d == 0)
# assert_equal(B.node[root]['label'], '0')
# children = B[root]
# # Get the left and right children, nodes 1 and 2, respectively.
# left, right = sorted(children, key=lambda v: B.node[v]['label'])
# assert_equal(B.node[left]['label'], '1')
# assert_equal(B.node[right]['label'], '2')
# # Get the left grandchild.
# children = B[left]
# assert_equal(len(children), 1)
# left_grandchild = arbitrary_element(children)
# assert_equal(B.node[left_grandchild]['label'], '3')
# # Get the right grandchild.
# children = B[right]
# assert_equal(len(children), 1)
# right_grandchild = arbitrary_element(children)
# assert_equal(B.node[right_grandchild]['label'], '3')
def test_already_arborescence(self):
"""Tests that a directed acyclic graph that is already an
arborescence produces an isomorphic arborescence as output.
"""
A = nx.balanced_tree(2, 2, create_using=nx.DiGraph())
B = nx.dag_to_branching(A)
assert nx.is_isomorphic(A, B)
def test_already_branching(self):
"""Tests that a directed acyclic graph that is already a
branching produces an isomorphic branching as output.
"""
T1 = nx.balanced_tree(2, 2, create_using=nx.DiGraph())
T2 = nx.balanced_tree(2, 2, create_using=nx.DiGraph())
G = nx.disjoint_union(T1, T2)
B = nx.dag_to_branching(G)
assert nx.is_isomorphic(G, B)
def test_not_acyclic(self):
"""Tests that a non-acyclic graph causes an exception."""
with pytest.raises(nx.HasACycle):
G = nx.DiGraph(pairwise("abc", cyclic=True))
nx.dag_to_branching(G)
def test_undirected(self):
with pytest.raises(nx.NetworkXNotImplemented):
nx.dag_to_branching(nx.Graph())
def test_multigraph(self):
with pytest.raises(nx.NetworkXNotImplemented):
nx.dag_to_branching(nx.MultiGraph())
def test_multidigraph(self):
with pytest.raises(nx.NetworkXNotImplemented):
nx.dag_to_branching(nx.MultiDiGraph())
def test_ancestors_descendants_undirected():
"""Regression test to ensure ancestors and descendants work as expected on
undirected graphs."""
G = nx.path_graph(5)
nx.ancestors(G, 2) == nx.descendants(G, 2) == {0, 1, 3, 4}
def test_v_structures_raise():
G = nx.Graph()
with pytest.raises(nx.NetworkXNotImplemented, match="for undirected type"):
nx.dag.v_structures(G)
@pytest.mark.parametrize(
("edgelist", "expected"),
(
(
[(0, 1), (0, 2), (3, 2)],
{(0, 2, 3)},
),
(
[("A", "B"), ("C", "B"), ("D", "G"), ("D", "E"), ("G", "E")],
{("A", "B", "C")},
),
([(0, 1), (2, 1), (0, 2)], set()), # adjacent parents case: see gh-7385
),
)
def test_v_structures(edgelist, expected):
G = nx.DiGraph(edgelist)
v_structs = set(nx.dag.v_structures(G))
assert v_structs == expected
def test_colliders_raise():
G = nx.Graph()
with pytest.raises(nx.NetworkXNotImplemented, match="for undirected type"):
nx.dag.colliders(G)
@pytest.mark.parametrize(
("edgelist", "expected"),
(
(
[(0, 1), (0, 2), (3, 2)],
{(0, 2, 3)},
),
(
[("A", "B"), ("C", "B"), ("D", "G"), ("D", "E"), ("G", "E")],
{("A", "B", "C"), ("D", "E", "G")},
),
),
)
def test_colliders(edgelist, expected):
G = nx.DiGraph(edgelist)
colliders = set(nx.dag.colliders(G))
assert colliders == expected
@@ -0,0 +1,831 @@
import itertools
import math
from random import Random
import pytest
import networkx as nx
from networkx import convert_node_labels_to_integers as cnlti
from networkx.algorithms.distance_measures import _extrema_bounding
def test__extrema_bounding_invalid_compute_kwarg():
G = nx.path_graph(3)
with pytest.raises(ValueError, match="compute must be one of"):
_extrema_bounding(G, compute="spam")
class TestDistance:
def setup_method(self):
self.G = cnlti(nx.grid_2d_graph(4, 4), first_label=1, ordering="sorted")
@pytest.mark.parametrize("seed", list(range(10)))
@pytest.mark.parametrize("n", list(range(10, 20)))
@pytest.mark.parametrize("prob", [x / 10 for x in range(0, 10, 2)])
def test_use_bounds_on_off_consistency(self, seed, n, prob):
"""Test for consistency of distance metrics when using usebounds=True.
We validate consistency for `networkx.diameter`, `networkx.radius`, `networkx.periphery`
and `networkx.center` when passing `usebounds=True`. Expectation is that method
returns the same result whether we pass usebounds=True or not.
For this we generate random connected graphs and validate method returns the same.
"""
metrics = [nx.diameter, nx.radius, nx.periphery, nx.center]
max_weight = [5, 10, 1000]
rng = Random(seed)
# we compose it with a random tree to ensure graph is connected
G = nx.compose(
nx.random_labeled_tree(n, seed=rng),
nx.erdos_renyi_graph(n, prob, seed=rng),
)
for metric in metrics:
# checking unweighted case
assert metric(G) == metric(G, usebounds=True)
for w in max_weight:
for u, v in G.edges():
G[u][v]["w"] = rng.randint(0, w)
# checking weighted case
assert metric(G, weight="w") == metric(G, weight="w", usebounds=True)
def test_eccentricity(self):
assert nx.eccentricity(self.G, 1) == 6
e = nx.eccentricity(self.G)
assert e[1] == 6
sp = dict(nx.shortest_path_length(self.G))
e = nx.eccentricity(self.G, sp=sp)
assert e[1] == 6
e = nx.eccentricity(self.G, v=1)
assert e == 6
# This behavior changed in version 1.8 (ticket #739)
e = nx.eccentricity(self.G, v=[1, 1])
assert e[1] == 6
e = nx.eccentricity(self.G, v=[1, 2])
assert e[1] == 6
# test against graph with one node
G = nx.path_graph(1)
e = nx.eccentricity(G)
assert e[0] == 0
e = nx.eccentricity(G, v=0)
assert e == 0
pytest.raises(nx.NetworkXError, nx.eccentricity, G, 1)
# test against empty graph
G = nx.empty_graph()
e = nx.eccentricity(G)
assert e == {}
def test_diameter(self):
assert nx.diameter(self.G) == 6
def test_harmonic_diameter(self):
assert nx.harmonic_diameter(self.G) == pytest.approx(2.0477815699658715)
assert nx.harmonic_diameter(nx.star_graph(3)) == pytest.approx(1.333333)
def test_harmonic_diameter_empty(self):
assert math.isnan(nx.harmonic_diameter(nx.empty_graph()))
def test_harmonic_diameter_single_node(self):
assert math.isnan(nx.harmonic_diameter(nx.empty_graph(1)))
def test_harmonic_diameter_discrete(self):
assert math.isinf(nx.harmonic_diameter(nx.empty_graph(3)))
def test_harmonic_diameter_not_strongly_connected(self):
DG = nx.DiGraph()
DG.add_edge(0, 1)
assert nx.harmonic_diameter(DG) == 2
def test_harmonic_diameter_weighted_paths(self):
G = nx.star_graph(3)
# check defaults
G.add_weighted_edges_from([(*e, 1) for i, e in enumerate(G.edges)], "weight")
assert nx.harmonic_diameter(G) == pytest.approx(1.333333)
assert nx.harmonic_diameter(G, weight="weight") == pytest.approx(1.333333)
# check impact of weights and alternate weight name
G.add_weighted_edges_from([(*e, i) for i, e in enumerate(G.edges)], "dist")
assert nx.harmonic_diameter(G, weight="dist") == pytest.approx(1.8)
def test_radius(self):
assert nx.radius(self.G) == 4
def test_periphery(self):
assert set(nx.periphery(self.G)) == {1, 4, 13, 16}
def test_center_simple_tree(self):
G = nx.Graph([(1, 2), (1, 3), (2, 4), (2, 5)])
assert nx.center(G) == [1, 2]
@pytest.mark.parametrize("r", range(2, 5))
@pytest.mark.parametrize("h", range(1, 5))
def test_center_balanced_tree(self, r, h):
G = nx.balanced_tree(r, h)
assert nx.center(G) == [0]
def test_center(self):
assert set(nx.center(self.G)) == {6, 7, 10, 11}
@pytest.mark.parametrize("n", [1, 2, 99, 100])
def test_center_path_graphs(self, n):
G = nx.path_graph(n)
expected = {(n - 1) // 2, math.ceil((n - 1) / 2)}
assert set(nx.center(G)) == expected
def test_bound_diameter(self):
assert nx.diameter(self.G, usebounds=True) == 6
def test_bound_radius(self):
assert nx.radius(self.G, usebounds=True) == 4
def test_bound_periphery(self):
result = {1, 4, 13, 16}
assert set(nx.periphery(self.G, usebounds=True)) == result
def test_bound_center(self):
result = {6, 7, 10, 11}
assert set(nx.center(self.G, usebounds=True)) == result
def test_radius_exception(self):
G = nx.Graph()
G.add_edge(1, 2)
G.add_edge(3, 4)
pytest.raises(nx.NetworkXError, nx.diameter, G)
def test_eccentricity_infinite(self):
with pytest.raises(nx.NetworkXError):
G = nx.Graph([(1, 2), (3, 4)])
e = nx.eccentricity(G)
def test_eccentricity_undirected_not_connected(self):
with pytest.raises(nx.NetworkXError):
G = nx.Graph([(1, 2), (3, 4)])
e = nx.eccentricity(G, sp=1)
def test_eccentricity_directed_weakly_connected(self):
with pytest.raises(nx.NetworkXError):
DG = nx.DiGraph([(1, 2), (1, 3)])
nx.eccentricity(DG)
class TestWeightedDistance:
def setup_method(self):
G = nx.Graph()
G.add_edge(0, 1, weight=0.6, cost=0.6, high_cost=6)
G.add_edge(0, 2, weight=0.2, cost=0.2, high_cost=2)
G.add_edge(2, 3, weight=0.1, cost=0.1, high_cost=1)
G.add_edge(2, 4, weight=0.7, cost=0.7, high_cost=7)
G.add_edge(2, 5, weight=0.9, cost=0.9, high_cost=9)
G.add_edge(1, 5, weight=0.3, cost=0.3, high_cost=3)
self.G = G
self.weight_fn = lambda v, u, e: 2
def test_eccentricity_weight_None(self):
assert nx.eccentricity(self.G, 1, weight=None) == 3
e = nx.eccentricity(self.G, weight=None)
assert e[1] == 3
e = nx.eccentricity(self.G, v=1, weight=None)
assert e == 3
# This behavior changed in version 1.8 (ticket #739)
e = nx.eccentricity(self.G, v=[1, 1], weight=None)
assert e[1] == 3
e = nx.eccentricity(self.G, v=[1, 2], weight=None)
assert e[1] == 3
def test_eccentricity_weight_attr(self):
assert nx.eccentricity(self.G, 1, weight="weight") == 1.5
e = nx.eccentricity(self.G, weight="weight")
assert (
e
== nx.eccentricity(self.G, weight="cost")
!= nx.eccentricity(self.G, weight="high_cost")
)
assert e[1] == 1.5
e = nx.eccentricity(self.G, v=1, weight="weight")
assert e == 1.5
# This behavior changed in version 1.8 (ticket #739)
e = nx.eccentricity(self.G, v=[1, 1], weight="weight")
assert e[1] == 1.5
e = nx.eccentricity(self.G, v=[1, 2], weight="weight")
assert e[1] == 1.5
def test_eccentricity_weight_fn(self):
assert nx.eccentricity(self.G, 1, weight=self.weight_fn) == 6
e = nx.eccentricity(self.G, weight=self.weight_fn)
assert e[1] == 6
e = nx.eccentricity(self.G, v=1, weight=self.weight_fn)
assert e == 6
# This behavior changed in version 1.8 (ticket #739)
e = nx.eccentricity(self.G, v=[1, 1], weight=self.weight_fn)
assert e[1] == 6
e = nx.eccentricity(self.G, v=[1, 2], weight=self.weight_fn)
assert e[1] == 6
def test_diameter_weight_None(self):
assert nx.diameter(self.G, weight=None) == 3
def test_diameter_weight_attr(self):
assert (
nx.diameter(self.G, weight="weight")
== nx.diameter(self.G, weight="cost")
== 1.6
!= nx.diameter(self.G, weight="high_cost")
)
def test_diameter_weight_fn(self):
assert nx.diameter(self.G, weight=self.weight_fn) == 6
def test_radius_weight_None(self):
assert pytest.approx(nx.radius(self.G, weight=None)) == 2
def test_radius_weight_attr(self):
assert (
pytest.approx(nx.radius(self.G, weight="weight"))
== pytest.approx(nx.radius(self.G, weight="cost"))
== 0.9
!= nx.radius(self.G, weight="high_cost")
)
def test_radius_weight_fn(self):
assert nx.radius(self.G, weight=self.weight_fn) == 4
def test_periphery_weight_None(self):
for v in set(nx.periphery(self.G, weight=None)):
assert nx.eccentricity(self.G, v, weight=None) == nx.diameter(
self.G, weight=None
)
def test_periphery_weight_attr(self):
periphery = set(nx.periphery(self.G, weight="weight"))
assert (
periphery
== set(nx.periphery(self.G, weight="cost"))
== set(nx.periphery(self.G, weight="high_cost"))
)
for v in periphery:
assert (
nx.eccentricity(self.G, v, weight="high_cost")
!= nx.eccentricity(self.G, v, weight="weight")
== nx.eccentricity(self.G, v, weight="cost")
== nx.diameter(self.G, weight="weight")
== nx.diameter(self.G, weight="cost")
!= nx.diameter(self.G, weight="high_cost")
)
assert nx.eccentricity(self.G, v, weight="high_cost") == nx.diameter(
self.G, weight="high_cost"
)
def test_periphery_weight_fn(self):
for v in set(nx.periphery(self.G, weight=self.weight_fn)):
assert nx.eccentricity(self.G, v, weight=self.weight_fn) == nx.diameter(
self.G, weight=self.weight_fn
)
def test_center_weight_None(self):
for v in set(nx.center(self.G, weight=None)):
assert pytest.approx(nx.eccentricity(self.G, v, weight=None)) == nx.radius(
self.G, weight=None
)
def test_center_weight_attr(self):
center = set(nx.center(self.G, weight="weight"))
assert (
center
== set(nx.center(self.G, weight="cost"))
!= set(nx.center(self.G, weight="high_cost"))
)
for v in center:
assert (
nx.eccentricity(self.G, v, weight="high_cost")
!= pytest.approx(nx.eccentricity(self.G, v, weight="weight"))
== pytest.approx(nx.eccentricity(self.G, v, weight="cost"))
== nx.radius(self.G, weight="weight")
== nx.radius(self.G, weight="cost")
!= nx.radius(self.G, weight="high_cost")
)
assert nx.eccentricity(self.G, v, weight="high_cost") == nx.radius(
self.G, weight="high_cost"
)
def test_center_weight_fn(self):
for v in set(nx.center(self.G, weight=self.weight_fn)):
assert nx.eccentricity(self.G, v, weight=self.weight_fn) == nx.radius(
self.G, weight=self.weight_fn
)
def test_bound_diameter_weight_None(self):
assert nx.diameter(self.G, usebounds=True, weight=None) == 3
def test_bound_diameter_weight_attr(self):
assert (
nx.diameter(self.G, usebounds=True, weight="high_cost")
!= nx.diameter(self.G, usebounds=True, weight="weight")
== nx.diameter(self.G, usebounds=True, weight="cost")
== 1.6
!= nx.diameter(self.G, usebounds=True, weight="high_cost")
)
assert nx.diameter(self.G, usebounds=True, weight="high_cost") == nx.diameter(
self.G, usebounds=True, weight="high_cost"
)
def test_bound_diameter_weight_fn(self):
assert nx.diameter(self.G, usebounds=True, weight=self.weight_fn) == 6
def test_bound_radius_weight_None(self):
assert pytest.approx(nx.radius(self.G, usebounds=True, weight=None)) == 2
def test_bound_radius_weight_attr(self):
assert (
nx.radius(self.G, usebounds=True, weight="high_cost")
!= pytest.approx(nx.radius(self.G, usebounds=True, weight="weight"))
== pytest.approx(nx.radius(self.G, usebounds=True, weight="cost"))
== 0.9
!= nx.radius(self.G, usebounds=True, weight="high_cost")
)
assert nx.radius(self.G, usebounds=True, weight="high_cost") == nx.radius(
self.G, usebounds=True, weight="high_cost"
)
def test_bound_radius_weight_fn(self):
assert nx.radius(self.G, usebounds=True, weight=self.weight_fn) == 4
def test_bound_periphery_weight_None(self):
result = {1, 3, 4}
assert set(nx.periphery(self.G, usebounds=True, weight=None)) == result
def test_bound_periphery_weight_attr(self):
result = {4, 5}
assert (
set(nx.periphery(self.G, usebounds=True, weight="weight"))
== set(nx.periphery(self.G, usebounds=True, weight="cost"))
== result
)
def test_bound_periphery_weight_fn(self):
result = {1, 3, 4}
assert (
set(nx.periphery(self.G, usebounds=True, weight=self.weight_fn)) == result
)
def test_bound_center_weight_None(self):
result = {0, 2, 5}
assert set(nx.center(self.G, usebounds=True, weight=None)) == result
def test_bound_center_weight_attr(self):
result = {0}
assert (
set(nx.center(self.G, usebounds=True, weight="weight"))
== set(nx.center(self.G, usebounds=True, weight="cost"))
== result
)
def test_bound_center_weight_fn(self):
result = {0, 2, 5}
assert set(nx.center(self.G, usebounds=True, weight=self.weight_fn)) == result
class TestResistanceDistance:
@classmethod
def setup_class(cls):
global np
np = pytest.importorskip("numpy")
sp = pytest.importorskip("scipy")
def setup_method(self):
G = nx.Graph()
G.add_edge(1, 2, weight=2)
G.add_edge(2, 3, weight=4)
G.add_edge(3, 4, weight=1)
G.add_edge(1, 4, weight=3)
self.G = G
def test_resistance_distance_directed_graph(self):
G = nx.DiGraph()
with pytest.raises(nx.NetworkXNotImplemented):
nx.resistance_distance(G)
def test_resistance_distance_empty(self):
G = nx.Graph()
with pytest.raises(nx.NetworkXError):
nx.resistance_distance(G)
def test_resistance_distance_not_connected(self):
with pytest.raises(nx.NetworkXError):
self.G.add_node(5)
nx.resistance_distance(self.G, 1, 5)
def test_resistance_distance_nodeA_not_in_graph(self):
with pytest.raises(nx.NetworkXError):
nx.resistance_distance(self.G, 9, 1)
def test_resistance_distance_nodeB_not_in_graph(self):
with pytest.raises(nx.NetworkXError):
nx.resistance_distance(self.G, 1, 9)
def test_resistance_distance(self):
rd = nx.resistance_distance(self.G, 1, 3, "weight", True)
test_data = 1 / (1 / (2 + 4) + 1 / (1 + 3))
assert round(rd, 5) == round(test_data, 5)
def test_resistance_distance_noinv(self):
rd = nx.resistance_distance(self.G, 1, 3, "weight", False)
test_data = 1 / (1 / (1 / 2 + 1 / 4) + 1 / (1 / 1 + 1 / 3))
assert round(rd, 5) == round(test_data, 5)
def test_resistance_distance_no_weight(self):
rd = nx.resistance_distance(self.G, 1, 3)
assert round(rd, 5) == 1
def test_resistance_distance_neg_weight(self):
self.G[2][3]["weight"] = -4
rd = nx.resistance_distance(self.G, 1, 3, "weight", True)
test_data = 1 / (1 / (2 + -4) + 1 / (1 + 3))
assert round(rd, 5) == round(test_data, 5)
def test_multigraph(self):
G = nx.MultiGraph()
G.add_edge(1, 2, weight=2)
G.add_edge(2, 3, weight=4)
G.add_edge(3, 4, weight=1)
G.add_edge(1, 4, weight=3)
rd = nx.resistance_distance(G, 1, 3, "weight", True)
assert np.isclose(rd, 1 / (1 / (2 + 4) + 1 / (1 + 3)))
def test_resistance_distance_div0(self):
with pytest.raises(ZeroDivisionError):
self.G[1][2]["weight"] = 0
nx.resistance_distance(self.G, 1, 3, "weight")
def test_resistance_distance_same_node(self):
assert nx.resistance_distance(self.G, 1, 1) == 0
def test_resistance_distance_only_nodeA(self):
rd = nx.resistance_distance(self.G, nodeA=1)
test_data = {}
test_data[1] = 0
test_data[2] = 0.75
test_data[3] = 1
test_data[4] = 0.75
assert isinstance(rd, dict)
assert sorted(rd.keys()) == sorted(test_data.keys())
for key in rd:
assert np.isclose(rd[key], test_data[key])
def test_resistance_distance_only_nodeB(self):
rd = nx.resistance_distance(self.G, nodeB=1)
test_data = {}
test_data[1] = 0
test_data[2] = 0.75
test_data[3] = 1
test_data[4] = 0.75
assert isinstance(rd, dict)
assert sorted(rd.keys()) == sorted(test_data.keys())
for key in rd:
assert np.isclose(rd[key], test_data[key])
def test_resistance_distance_all(self):
rd = nx.resistance_distance(self.G)
assert isinstance(rd, dict)
assert round(rd[1][3], 5) == 1
class TestEffectiveGraphResistance:
@classmethod
def setup_class(cls):
global np
np = pytest.importorskip("numpy")
sp = pytest.importorskip("scipy")
def setup_method(self):
G = nx.Graph()
G.add_edge(1, 2, weight=2)
G.add_edge(1, 3, weight=1)
G.add_edge(2, 3, weight=4)
self.G = G
def test_effective_graph_resistance_directed_graph(self):
G = nx.DiGraph()
with pytest.raises(nx.NetworkXNotImplemented):
nx.effective_graph_resistance(G)
def test_effective_graph_resistance_empty(self):
G = nx.Graph()
with pytest.raises(nx.NetworkXError):
nx.effective_graph_resistance(G)
def test_effective_graph_resistance_not_connected(self):
G = nx.Graph([(1, 2), (3, 4)])
RG = nx.effective_graph_resistance(G)
assert np.isinf(RG)
def test_effective_graph_resistance(self):
RG = nx.effective_graph_resistance(self.G, "weight", True)
rd12 = 1 / (1 / (1 + 4) + 1 / 2)
rd13 = 1 / (1 / (1 + 2) + 1 / 4)
rd23 = 1 / (1 / (2 + 4) + 1 / 1)
assert np.isclose(RG, rd12 + rd13 + rd23)
def test_effective_graph_resistance_noinv(self):
RG = nx.effective_graph_resistance(self.G, "weight", False)
rd12 = 1 / (1 / (1 / 1 + 1 / 4) + 1 / (1 / 2))
rd13 = 1 / (1 / (1 / 1 + 1 / 2) + 1 / (1 / 4))
rd23 = 1 / (1 / (1 / 2 + 1 / 4) + 1 / (1 / 1))
assert np.isclose(RG, rd12 + rd13 + rd23)
def test_effective_graph_resistance_no_weight(self):
RG = nx.effective_graph_resistance(self.G)
assert np.isclose(RG, 2)
def test_effective_graph_resistance_neg_weight(self):
self.G[2][3]["weight"] = -4
RG = nx.effective_graph_resistance(self.G, "weight", True)
rd12 = 1 / (1 / (1 + -4) + 1 / 2)
rd13 = 1 / (1 / (1 + 2) + 1 / (-4))
rd23 = 1 / (1 / (2 + -4) + 1 / 1)
assert np.isclose(RG, rd12 + rd13 + rd23)
def test_effective_graph_resistance_multigraph(self):
G = nx.MultiGraph()
G.add_edge(1, 2, weight=2)
G.add_edge(1, 3, weight=1)
G.add_edge(2, 3, weight=1)
G.add_edge(2, 3, weight=3)
RG = nx.effective_graph_resistance(G, "weight", True)
edge23 = 1 / (1 / 1 + 1 / 3)
rd12 = 1 / (1 / (1 + edge23) + 1 / 2)
rd13 = 1 / (1 / (1 + 2) + 1 / edge23)
rd23 = 1 / (1 / (2 + edge23) + 1 / 1)
assert np.isclose(RG, rd12 + rd13 + rd23)
def test_effective_graph_resistance_div0(self):
with pytest.raises(ZeroDivisionError):
self.G[1][2]["weight"] = 0
nx.effective_graph_resistance(self.G, "weight")
def test_effective_graph_resistance_complete_graph(self):
N = 10
G = nx.complete_graph(N)
RG = nx.effective_graph_resistance(G)
assert np.isclose(RG, N - 1)
def test_effective_graph_resistance_path_graph(self):
N = 10
G = nx.path_graph(N)
RG = nx.effective_graph_resistance(G)
assert np.isclose(RG, (N - 1) * N * (N + 1) // 6)
class TestBarycenter:
"""Test :func:`networkx.algorithms.distance_measures.barycenter`."""
def barycenter_as_subgraph(self, g, **kwargs):
"""Return the subgraph induced on the barycenter of g"""
b = nx.barycenter(g, **kwargs)
assert isinstance(b, list)
assert set(b) <= set(g)
return g.subgraph(b)
def test_must_be_connected(self):
pytest.raises(nx.NetworkXNoPath, nx.barycenter, nx.empty_graph(5))
def test_sp_kwarg(self):
# Complete graph K_5. Normally it works...
K_5 = nx.complete_graph(5)
sp = dict(nx.shortest_path_length(K_5))
assert nx.barycenter(K_5, sp=sp) == list(K_5)
# ...but not with the weight argument
for u, v, data in K_5.edges.data():
data["weight"] = 1
pytest.raises(ValueError, nx.barycenter, K_5, sp=sp, weight="weight")
# ...and a corrupted sp can make it seem like K_5 is disconnected
del sp[0][1]
pytest.raises(nx.NetworkXNoPath, nx.barycenter, K_5, sp=sp)
def test_trees(self):
"""The barycenter of a tree is a single vertex or an edge.
See [West01]_, p. 78.
"""
prng = Random(0xDEADBEEF)
for i in range(50):
RT = nx.random_labeled_tree(prng.randint(1, 75), seed=prng)
b = self.barycenter_as_subgraph(RT)
if len(b) == 2:
assert b.size() == 1
else:
assert len(b) == 1
assert b.size() == 0
def test_this_one_specific_tree(self):
"""Test the tree pictured at the bottom of [West01]_, p. 78."""
g = nx.Graph(
{
"a": ["b"],
"b": ["a", "x"],
"x": ["b", "y"],
"y": ["x", "z"],
"z": ["y", 0, 1, 2, 3, 4],
0: ["z"],
1: ["z"],
2: ["z"],
3: ["z"],
4: ["z"],
}
)
b = self.barycenter_as_subgraph(g, attr="barycentricity")
assert list(b) == ["z"]
assert not b.edges
expected_barycentricity = {
0: 23,
1: 23,
2: 23,
3: 23,
4: 23,
"a": 35,
"b": 27,
"x": 21,
"y": 17,
"z": 15,
}
for node, barycentricity in expected_barycentricity.items():
assert g.nodes[node]["barycentricity"] == barycentricity
# Doubling weights should do nothing but double the barycentricities
for edge in g.edges:
g.edges[edge]["weight"] = 2
b = self.barycenter_as_subgraph(g, weight="weight", attr="barycentricity2")
assert list(b) == ["z"]
assert not b.edges
for node, barycentricity in expected_barycentricity.items():
assert g.nodes[node]["barycentricity2"] == barycentricity * 2
class TestKemenyConstant:
@classmethod
def setup_class(cls):
global np
np = pytest.importorskip("numpy")
sp = pytest.importorskip("scipy")
def setup_method(self):
G = nx.Graph()
w12 = 2
w13 = 3
w23 = 4
G.add_edge(1, 2, weight=w12)
G.add_edge(1, 3, weight=w13)
G.add_edge(2, 3, weight=w23)
self.G = G
def test_kemeny_constant_directed(self):
G = nx.DiGraph()
G.add_edge(1, 2)
G.add_edge(1, 3)
G.add_edge(2, 3)
with pytest.raises(nx.NetworkXNotImplemented):
nx.kemeny_constant(G)
def test_kemeny_constant_not_connected(self):
self.G.add_node(5)
with pytest.raises(nx.NetworkXError):
nx.kemeny_constant(self.G)
def test_kemeny_constant_no_nodes(self):
G = nx.Graph()
with pytest.raises(nx.NetworkXError):
nx.kemeny_constant(G)
def test_kemeny_constant_negative_weight(self):
G = nx.Graph()
w12 = 2
w13 = 3
w23 = -10
G.add_edge(1, 2, weight=w12)
G.add_edge(1, 3, weight=w13)
G.add_edge(2, 3, weight=w23)
with pytest.raises(nx.NetworkXError):
nx.kemeny_constant(G, weight="weight")
def test_kemeny_constant(self):
K = nx.kemeny_constant(self.G, weight="weight")
w12 = 2
w13 = 3
w23 = 4
test_data = (
3
/ 2
* (w12 + w13)
* (w12 + w23)
* (w13 + w23)
/ (
w12**2 * (w13 + w23)
+ w13**2 * (w12 + w23)
+ w23**2 * (w12 + w13)
+ 3 * w12 * w13 * w23
)
)
assert np.isclose(K, test_data)
def test_kemeny_constant_no_weight(self):
K = nx.kemeny_constant(self.G)
assert np.isclose(K, 4 / 3)
def test_kemeny_constant_multigraph(self):
G = nx.MultiGraph()
w12_1 = 2
w12_2 = 1
w13 = 3
w23 = 4
G.add_edge(1, 2, weight=w12_1)
G.add_edge(1, 2, weight=w12_2)
G.add_edge(1, 3, weight=w13)
G.add_edge(2, 3, weight=w23)
K = nx.kemeny_constant(G, weight="weight")
w12 = w12_1 + w12_2
test_data = (
3
/ 2
* (w12 + w13)
* (w12 + w23)
* (w13 + w23)
/ (
w12**2 * (w13 + w23)
+ w13**2 * (w12 + w23)
+ w23**2 * (w12 + w13)
+ 3 * w12 * w13 * w23
)
)
assert np.isclose(K, test_data)
def test_kemeny_constant_weight0(self):
G = nx.Graph()
w12 = 0
w13 = 3
w23 = 4
G.add_edge(1, 2, weight=w12)
G.add_edge(1, 3, weight=w13)
G.add_edge(2, 3, weight=w23)
K = nx.kemeny_constant(G, weight="weight")
test_data = (
3
/ 2
* (w12 + w13)
* (w12 + w23)
* (w13 + w23)
/ (
w12**2 * (w13 + w23)
+ w13**2 * (w12 + w23)
+ w23**2 * (w12 + w13)
+ 3 * w12 * w13 * w23
)
)
assert np.isclose(K, test_data)
def test_kemeny_constant_selfloop(self):
G = nx.Graph()
w11 = 1
w12 = 2
w13 = 3
w23 = 4
G.add_edge(1, 1, weight=w11)
G.add_edge(1, 2, weight=w12)
G.add_edge(1, 3, weight=w13)
G.add_edge(2, 3, weight=w23)
K = nx.kemeny_constant(G, weight="weight")
test_data = (
(2 * w11 + 3 * w12 + 3 * w13)
* (w12 + w23)
* (w13 + w23)
/ (
(w12 * w13 + w12 * w23 + w13 * w23)
* (w11 + 2 * w12 + 2 * w13 + 2 * w23)
)
)
assert np.isclose(K, test_data)
def test_kemeny_constant_complete_bipartite_graph(self):
# Theorem 1 in https://www.sciencedirect.com/science/article/pii/S0166218X20302912
n1 = 5
n2 = 4
G = nx.complete_bipartite_graph(n1, n2)
K = nx.kemeny_constant(G)
assert np.isclose(K, n1 + n2 - 3 / 2)
def test_kemeny_constant_path_graph(self):
# Theorem 2 in https://www.sciencedirect.com/science/article/pii/S0166218X20302912
n = 10
G = nx.path_graph(n)
K = nx.kemeny_constant(G)
assert np.isclose(K, n**2 / 3 - 2 * n / 3 + 1 / 2)
@@ -0,0 +1,85 @@
import pytest
import networkx as nx
from networkx import is_strongly_regular
@pytest.mark.parametrize(
"f", (nx.is_distance_regular, nx.intersection_array, nx.is_strongly_regular)
)
@pytest.mark.parametrize("graph_constructor", (nx.DiGraph, nx.MultiGraph))
def test_raises_on_directed_and_multigraphs(f, graph_constructor):
G = graph_constructor([(0, 1), (1, 2)])
with pytest.raises(nx.NetworkXNotImplemented):
f(G)
class TestDistanceRegular:
def test_is_distance_regular(self):
assert nx.is_distance_regular(nx.icosahedral_graph())
assert nx.is_distance_regular(nx.petersen_graph())
assert nx.is_distance_regular(nx.cubical_graph())
assert nx.is_distance_regular(nx.complete_bipartite_graph(3, 3))
assert nx.is_distance_regular(nx.tetrahedral_graph())
assert nx.is_distance_regular(nx.dodecahedral_graph())
assert nx.is_distance_regular(nx.pappus_graph())
assert nx.is_distance_regular(nx.heawood_graph())
assert nx.is_distance_regular(nx.cycle_graph(3))
# no distance regular
assert not nx.is_distance_regular(nx.path_graph(4))
def test_not_connected(self):
G = nx.cycle_graph(4)
nx.add_cycle(G, [5, 6, 7])
assert not nx.is_distance_regular(G)
def test_global_parameters(self):
b, c = nx.intersection_array(nx.cycle_graph(5))
g = nx.global_parameters(b, c)
assert list(g) == [(0, 0, 2), (1, 0, 1), (1, 1, 0)]
b, c = nx.intersection_array(nx.cycle_graph(3))
g = nx.global_parameters(b, c)
assert list(g) == [(0, 0, 2), (1, 1, 0)]
def test_intersection_array(self):
b, c = nx.intersection_array(nx.cycle_graph(5))
assert b == [2, 1]
assert c == [1, 1]
b, c = nx.intersection_array(nx.dodecahedral_graph())
assert b == [3, 2, 1, 1, 1]
assert c == [1, 1, 1, 2, 3]
b, c = nx.intersection_array(nx.icosahedral_graph())
assert b == [5, 2, 1]
assert c == [1, 2, 5]
@pytest.mark.parametrize("f", (nx.is_distance_regular, nx.is_strongly_regular))
def test_empty_graph_raises(f):
G = nx.Graph()
with pytest.raises(nx.NetworkXPointlessConcept, match="Graph has no nodes"):
f(G)
class TestStronglyRegular:
"""Unit tests for the :func:`~networkx.is_strongly_regular`
function.
"""
def test_cycle_graph(self):
"""Tests that the cycle graph on five vertices is strongly
regular.
"""
G = nx.cycle_graph(5)
assert is_strongly_regular(G)
def test_petersen_graph(self):
"""Tests that the Petersen graph is strongly regular."""
G = nx.petersen_graph()
assert is_strongly_regular(G)
def test_path_graph(self):
"""Tests that the path graph is not strongly regular."""
G = nx.path_graph(4)
assert not is_strongly_regular(G)
@@ -0,0 +1,299 @@
import pytest
import networkx as nx
class TestImmediateDominators:
@pytest.mark.parametrize("G", [nx.Graph(), nx.MultiGraph()])
def test_raises_undirected(self, G):
"""Check that `immediate_dominators` raises for undirected graphs."""
with pytest.raises(
nx.NetworkXNotImplemented, match=r"not implemented for undirected"
):
nx.immediate_dominators(G, 0)
def test_raises_node(self):
"""Check that `immediate_dominators` raises when `start` is not in the graph."""
G = nx.empty_graph(1, create_using=nx.DiGraph)
with pytest.raises(nx.NetworkXError, match=r"not in G"):
nx.immediate_dominators(G, 1)
def test_singleton(self):
G = nx.DiGraph()
G.add_node(0)
assert nx.immediate_dominators(G, 0) == {}
G.add_edge(0, 0)
assert nx.immediate_dominators(G, 0) == {}
@pytest.mark.parametrize("gen", [nx.path_graph, nx.cycle_graph])
@pytest.mark.parametrize("n", [5, 10, 20])
def test_path_and_cycle(self, gen, n):
"""Check `immediate_dominators` is correct for path and cycle graphs."""
G = gen(n, create_using=nx.DiGraph())
idom = nx.immediate_dominators(G, 0)
assert idom == {i: i - 1 for i in range(1, n)}
def test_unreachable(self):
n = 5
G = nx.path_graph(n, create_using=nx.DiGraph())
idom = nx.immediate_dominators(G, 1)
assert idom == {i: i - 1 for i in range(2, n)}
@pytest.mark.parametrize(
["edgelist", "start"],
[
([(1, 2), (2, 1), (3, 2), (4, 1), (5, 3), (5, 4)], 5),
(
[
(1, 2),
(2, 1),
(2, 3),
(3, 2),
(4, 2),
(4, 3),
(5, 1),
(6, 4),
(6, 5),
],
6,
),
],
)
def test_irreducible(self, edgelist, start):
"""
Check `immediate_dominators` on irreducible reference graphs.
Graphs taken from figures 2 and 4 of "A simple, fast dominance algorithm." (2006).
https://hdl.handle.net/1911/96345
"""
G = nx.DiGraph(edgelist)
idom = nx.immediate_dominators(G, start)
assert idom == dict.fromkeys(range(1, start), start)
def test_domrel_png(self):
# Graph taken from https://commons.wikipedia.org/wiki/File:Domrel.png
edges = [(1, 2), (2, 3), (2, 4), (2, 6), (3, 5), (4, 5), (5, 2)]
G = nx.DiGraph(edges)
result = nx.immediate_dominators(G, 1)
assert result == {2: 1, 3: 2, 4: 2, 5: 2, 6: 2}
# Test postdominance.
result = nx.immediate_dominators(G.reverse(copy=False), 6)
assert result == {1: 2, 2: 6, 3: 5, 4: 5, 5: 2}
def test_boost_example(self):
# Graph taken from Figure 1 of
# http://www.boost.org/doc/libs/1_56_0/libs/graph/doc/lengauer_tarjan_dominator.htm
edges = [(0, 1), (1, 2), (1, 3), (2, 7), (3, 4), (4, 5), (4, 6), (5, 7), (6, 4)]
G = nx.DiGraph(edges)
result = nx.immediate_dominators(G, 0)
assert result == {1: 0, 2: 1, 3: 1, 4: 3, 5: 4, 6: 4, 7: 1}
# Test postdominance.
result = nx.immediate_dominators(G.reverse(copy=False), 7)
assert result == {0: 1, 1: 7, 2: 7, 3: 4, 4: 5, 5: 7, 6: 4}
class TestDominanceFrontiers:
@pytest.mark.parametrize("G", [nx.Graph(), nx.MultiGraph()])
def test_raises_undirected(self, G):
"""Check that `dominance_frontiers` raises for undirected graphs."""
with pytest.raises(
nx.NetworkXNotImplemented, match=r"not implemented for undirected"
):
nx.dominance_frontiers(G, 0)
def test_raises_node(self):
"""Check that `dominance_frontiers` raises when `start` is not in the graph."""
G = nx.empty_graph(1, create_using=nx.DiGraph)
with pytest.raises(nx.NetworkXError, match=r"not in G"):
nx.dominance_frontiers(G, 1)
def test_singleton(self):
G = nx.DiGraph()
G.add_node(0)
assert nx.dominance_frontiers(G, 0) == {0: set()}
G.add_edge(0, 0)
assert nx.dominance_frontiers(G, 0) == {0: {0}}
@pytest.mark.parametrize("gen, df", [(nx.path_graph, set()), (nx.cycle_graph, {0})])
@pytest.mark.parametrize("n", [5, 10, 20])
def test_path_and_cycle(self, gen, df, n):
"""Check that `dominance_frontiers` is correct for path and cycle graphs."""
G = gen(n, create_using=nx.DiGraph())
assert nx.dominance_frontiers(G, 0) == dict.fromkeys(range(n), df)
def test_unreachable(self):
n = 5
G = nx.path_graph(n, create_using=nx.DiGraph())
assert nx.dominance_frontiers(G, 1) == dict.fromkeys(range(1, n), set())
def test_irreducible1(self):
"""
Graph taken from figure 2 of "A simple, fast dominance algorithm." (2006).
https://hdl.handle.net/1911/96345
"""
edges = [(1, 2), (2, 1), (3, 2), (4, 1), (5, 3), (5, 4)]
G = nx.DiGraph(edges)
assert nx.dominance_frontiers(G, 5) == {
1: {2},
2: {1},
3: {2},
4: {1},
5: set(),
}
def test_irreducible2(self):
"""
Graph taken from figure 4 of "A simple, fast dominance algorithm." (2006).
https://hdl.handle.net/1911/96345
"""
edges = [(1, 2), (2, 1), (2, 3), (3, 2), (4, 2), (4, 3), (5, 1), (6, 4), (6, 5)]
G = nx.DiGraph(edges)
assert nx.dominance_frontiers(G, 6) == {
1: {2},
2: {1, 3},
3: {2},
4: {2, 3},
5: {1},
6: set(),
}
def test_domrel_png(self):
# Graph taken from https://commons.wikipedia.org/wiki/File:Domrel.png
edges = [(1, 2), (2, 3), (2, 4), (2, 6), (3, 5), (4, 5), (5, 2)]
G = nx.DiGraph(edges)
assert nx.dominance_frontiers(G, 1) == {
1: set(),
2: {2},
3: {5},
4: {5},
5: {2},
6: set(),
}
# Test postdominance.
result = nx.dominance_frontiers(G.reverse(copy=False), 6)
assert result == {1: set(), 2: {2}, 3: {2}, 4: {2}, 5: {2}, 6: set()}
def test_boost_example(self):
# Graph taken from Figure 1 of
# http://www.boost.org/doc/libs/1_56_0/libs/graph/doc/lengauer_tarjan_dominator.htm
edges = [(0, 1), (1, 2), (1, 3), (2, 7), (3, 4), (4, 5), (4, 6), (5, 7), (6, 4)]
G = nx.DiGraph(edges)
assert nx.dominance_frontiers(G, 0) == {
0: set(),
1: set(),
2: {7},
3: {7},
4: {4, 7},
5: {7},
6: {4},
7: set(),
}
# Test postdominance.
result = nx.dominance_frontiers(G.reverse(copy=False), 7)
expected = {
0: set(),
1: set(),
2: {1},
3: {1},
4: {1, 4},
5: {1},
6: {4},
7: set(),
}
assert result == expected
def test_discard_issue(self):
# https://github.com/networkx/networkx/issues/2071
g = nx.DiGraph()
g.add_edges_from(
[
("b0", "b1"),
("b1", "b2"),
("b2", "b3"),
("b3", "b1"),
("b1", "b5"),
("b5", "b6"),
("b5", "b8"),
("b6", "b7"),
("b8", "b7"),
("b7", "b3"),
("b3", "b4"),
]
)
df = nx.dominance_frontiers(g, "b0")
assert df == {
"b4": set(),
"b5": {"b3"},
"b6": {"b7"},
"b7": {"b3"},
"b0": set(),
"b1": {"b1"},
"b2": {"b3"},
"b3": {"b1"},
"b8": {"b7"},
}
def test_loop(self):
g = nx.DiGraph()
g.add_edges_from([("a", "b"), ("b", "c"), ("b", "a")])
df = nx.dominance_frontiers(g, "a")
assert df == {"a": {"a"}, "b": {"a"}, "c": set()}
def test_missing_immediate_doms(self):
# see https://github.com/networkx/networkx/issues/2070
g = nx.DiGraph()
edges = [
("entry_1", "b1"),
("b1", "b2"),
("b2", "b3"),
("b3", "exit"),
("entry_2", "b3"),
]
# entry_1
# |
# b1
# |
# b2 entry_2
# | /
# b3
# |
# exit
g.add_edges_from(edges)
# formerly raised KeyError on entry_2 when parsing b3
# because entry_2 does not have immediate doms (no path)
nx.dominance_frontiers(g, "entry_1")
def test_loops_larger(self):
# from
# http://ecee.colorado.edu/~waite/Darmstadt/motion.html
g = nx.DiGraph()
edges = [
("entry", "exit"),
("entry", "1"),
("1", "2"),
("2", "3"),
("3", "4"),
("4", "5"),
("5", "6"),
("6", "exit"),
("6", "2"),
("5", "3"),
("4", "4"),
]
g.add_edges_from(edges)
df = nx.dominance_frontiers(g, "entry")
answer = {
"entry": set(),
"1": {"exit"},
"2": {"exit", "2"},
"3": {"exit", "3", "2"},
"4": {"exit", "4", "3", "2"},
"5": {"exit", "3", "2"},
"6": {"exit", "2"},
"exit": set(),
}
for n in df:
assert set(df[n]) == set(answer[n])
@@ -0,0 +1,115 @@
import pytest
import networkx as nx
def test_dominating_set():
G = nx.gnp_random_graph(100, 0.1)
D = nx.dominating_set(G)
assert nx.is_dominating_set(G, D)
D = nx.dominating_set(G, start_with=0)
assert nx.is_dominating_set(G, D)
def test_complete():
"""In complete graphs each node is a dominating set.
Thus the dominating set has to be of cardinality 1.
"""
K4 = nx.complete_graph(4)
assert len(nx.dominating_set(K4)) == 1
K5 = nx.complete_graph(5)
assert len(nx.dominating_set(K5)) == 1
def test_raise_dominating_set():
with pytest.raises(nx.NetworkXError):
G = nx.path_graph(4)
D = nx.dominating_set(G, start_with=10)
def test_is_dominating_set():
G = nx.path_graph(4)
d = {1, 3}
assert nx.is_dominating_set(G, d)
d = {0, 2}
assert nx.is_dominating_set(G, d)
d = {1}
assert not nx.is_dominating_set(G, d)
def test_wikipedia_is_dominating_set():
"""Example from https://en.wikipedia.org/wiki/Dominating_set"""
G = nx.cycle_graph(4)
G.add_edges_from([(0, 4), (1, 4), (2, 5)])
assert nx.is_dominating_set(G, {4, 3, 5})
assert nx.is_dominating_set(G, {0, 2})
assert nx.is_dominating_set(G, {1, 2})
def test_is_connected_dominating_set():
G = nx.path_graph(4)
D = {1, 2}
assert nx.is_connected_dominating_set(G, D)
D = {1, 3}
assert not nx.is_connected_dominating_set(G, D)
D = {2, 3}
assert nx.is_connected(nx.subgraph(G, D))
assert not nx.is_connected_dominating_set(G, D)
def test_null_graph_connected_dominating_set():
G = nx.Graph()
assert 0 == len(nx.connected_dominating_set(G))
def test_single_node_graph_connected_dominating_set():
G = nx.Graph()
G.add_node(1)
CD = nx.connected_dominating_set(G)
assert nx.is_connected_dominating_set(G, CD)
def test_raise_disconnected_graph_connected_dominating_set():
with pytest.raises(nx.NetworkXError):
G = nx.Graph()
G.add_node(1)
G.add_node(2)
nx.connected_dominating_set(G)
def test_complete_graph_connected_dominating_set():
K5 = nx.complete_graph(5)
assert 1 == len(nx.connected_dominating_set(K5))
K7 = nx.complete_graph(7)
assert 1 == len(nx.connected_dominating_set(K7))
def test_docstring_example_connected_dominating_set():
G = nx.Graph(
[
(1, 2),
(1, 3),
(1, 4),
(1, 5),
(1, 6),
(2, 7),
(3, 8),
(4, 9),
(5, 10),
(6, 11),
(7, 12),
(8, 12),
(9, 12),
(10, 12),
(11, 12),
]
)
assert {1, 2, 3, 4, 5, 6, 7} == nx.connected_dominating_set(G)
@pytest.mark.parametrize("seed", [1, 13, 29])
@pytest.mark.parametrize("n,k,p", [(10, 3, 0.2), (100, 10, 0.7), (1000, 50, 0.5)])
def test_connected_watts_strogatz_graph_connected_dominating_set(n, k, p, seed):
G = nx.connected_watts_strogatz_graph(n, k, p, seed=seed)
D = nx.connected_dominating_set(G)
assert nx.is_connected_dominating_set(G, D)
@@ -0,0 +1,58 @@
"""Unit tests for the :mod:`networkx.algorithms.efficiency` module."""
import networkx as nx
class TestEfficiency:
def setup_method(self):
# G1 is a disconnected graph
self.G1 = nx.Graph()
self.G1.add_nodes_from([1, 2, 3])
# G2 is a cycle graph
self.G2 = nx.cycle_graph(4)
# G3 is the triangle graph with one additional edge
self.G3 = nx.lollipop_graph(3, 1)
def test_efficiency_disconnected_nodes(self):
"""
When nodes are disconnected, efficiency is 0
"""
assert nx.efficiency(self.G1, 1, 2) == 0
def test_local_efficiency_disconnected_graph(self):
"""
In a disconnected graph the efficiency is 0
"""
assert nx.local_efficiency(self.G1) == 0
def test_efficiency(self):
assert nx.efficiency(self.G2, 0, 1) == 1
assert nx.efficiency(self.G2, 0, 2) == 1 / 2
def test_global_efficiency(self):
assert nx.global_efficiency(self.G2) == 5 / 6
def test_global_efficiency_complete_graph(self):
"""
Tests that the average global efficiency of the complete graph is one.
"""
for n in range(2, 10):
G = nx.complete_graph(n)
assert nx.global_efficiency(G) == 1
def test_local_efficiency_complete_graph(self):
"""
Test that the local efficiency for a complete graph with at least 3
nodes should be one. For a graph with only 2 nodes, the induced
subgraph has no edges.
"""
for n in range(3, 10):
G = nx.complete_graph(n)
assert nx.local_efficiency(G) == 1
def test_using_ego_graph(self):
"""
Test that the ego graph is used when computing local efficiency.
For more information, see GitHub issue #2710.
"""
assert nx.local_efficiency(self.G3) == 7 / 12
@@ -0,0 +1,314 @@
import collections
import pytest
import networkx as nx
@pytest.mark.parametrize("f", (nx.is_eulerian, nx.is_semieulerian))
def test_empty_graph_raises(f):
G = nx.Graph()
with pytest.raises(nx.NetworkXPointlessConcept, match="Connectivity is undefined"):
f(G)
class TestIsEulerian:
def test_is_eulerian(self):
assert nx.is_eulerian(nx.complete_graph(5))
assert nx.is_eulerian(nx.complete_graph(7))
assert nx.is_eulerian(nx.hypercube_graph(4))
assert nx.is_eulerian(nx.hypercube_graph(6))
assert not nx.is_eulerian(nx.complete_graph(4))
assert not nx.is_eulerian(nx.complete_graph(6))
assert not nx.is_eulerian(nx.hypercube_graph(3))
assert not nx.is_eulerian(nx.hypercube_graph(5))
assert not nx.is_eulerian(nx.petersen_graph())
assert not nx.is_eulerian(nx.path_graph(4))
def test_is_eulerian2(self):
# not connected
G = nx.Graph()
G.add_nodes_from([1, 2, 3])
assert not nx.is_eulerian(G)
# not strongly connected
G = nx.DiGraph()
G.add_nodes_from([1, 2, 3])
assert not nx.is_eulerian(G)
G = nx.MultiDiGraph()
G.add_edge(1, 2)
G.add_edge(2, 3)
G.add_edge(2, 3)
G.add_edge(3, 1)
assert not nx.is_eulerian(G)
class TestEulerianCircuit:
def test_eulerian_circuit_cycle(self):
G = nx.cycle_graph(4)
edges = list(nx.eulerian_circuit(G, source=0))
nodes = [u for u, v in edges]
assert nodes == [0, 3, 2, 1]
assert edges == [(0, 3), (3, 2), (2, 1), (1, 0)]
edges = list(nx.eulerian_circuit(G, source=1))
nodes = [u for u, v in edges]
assert nodes == [1, 2, 3, 0]
assert edges == [(1, 2), (2, 3), (3, 0), (0, 1)]
G = nx.complete_graph(3)
edges = list(nx.eulerian_circuit(G, source=0))
nodes = [u for u, v in edges]
assert nodes == [0, 2, 1]
assert edges == [(0, 2), (2, 1), (1, 0)]
edges = list(nx.eulerian_circuit(G, source=1))
nodes = [u for u, v in edges]
assert nodes == [1, 2, 0]
assert edges == [(1, 2), (2, 0), (0, 1)]
def test_eulerian_circuit_digraph(self):
G = nx.DiGraph()
nx.add_cycle(G, [0, 1, 2, 3])
edges = list(nx.eulerian_circuit(G, source=0))
nodes = [u for u, v in edges]
assert nodes == [0, 1, 2, 3]
assert edges == [(0, 1), (1, 2), (2, 3), (3, 0)]
edges = list(nx.eulerian_circuit(G, source=1))
nodes = [u for u, v in edges]
assert nodes == [1, 2, 3, 0]
assert edges == [(1, 2), (2, 3), (3, 0), (0, 1)]
def test_multigraph(self):
G = nx.MultiGraph()
nx.add_cycle(G, [0, 1, 2, 3])
G.add_edge(1, 2)
G.add_edge(1, 2)
edges = list(nx.eulerian_circuit(G, source=0))
nodes = [u for u, v in edges]
assert nodes == [0, 3, 2, 1, 2, 1]
assert edges == [(0, 3), (3, 2), (2, 1), (1, 2), (2, 1), (1, 0)]
def test_multigraph_with_keys(self):
G = nx.MultiGraph()
nx.add_cycle(G, [0, 1, 2, 3])
G.add_edge(1, 2)
G.add_edge(1, 2)
edges = list(nx.eulerian_circuit(G, source=0, keys=True))
nodes = [u for u, v, k in edges]
assert nodes == [0, 3, 2, 1, 2, 1]
assert edges[:2] == [(0, 3, 0), (3, 2, 0)]
assert collections.Counter(edges[2:5]) == collections.Counter(
[(2, 1, 0), (1, 2, 1), (2, 1, 2)]
)
assert edges[5:] == [(1, 0, 0)]
def test_not_eulerian(self):
with pytest.raises(nx.NetworkXError):
f = list(nx.eulerian_circuit(nx.complete_graph(4)))
class TestIsSemiEulerian:
def test_is_semieulerian(self):
# Test graphs with Eulerian paths but no cycles return True.
assert nx.is_semieulerian(nx.path_graph(4))
G = nx.path_graph(6, create_using=nx.DiGraph)
assert nx.is_semieulerian(G)
# Test graphs with Eulerian cycles return False.
assert not nx.is_semieulerian(nx.complete_graph(5))
assert not nx.is_semieulerian(nx.complete_graph(7))
assert not nx.is_semieulerian(nx.hypercube_graph(4))
assert not nx.is_semieulerian(nx.hypercube_graph(6))
class TestHasEulerianPath:
def test_has_eulerian_path_cyclic(self):
# Test graphs with Eulerian cycles return True.
assert nx.has_eulerian_path(nx.complete_graph(5))
assert nx.has_eulerian_path(nx.complete_graph(7))
assert nx.has_eulerian_path(nx.hypercube_graph(4))
assert nx.has_eulerian_path(nx.hypercube_graph(6))
def test_has_eulerian_path_non_cyclic(self):
# Test graphs with Eulerian paths but no cycles return True.
assert nx.has_eulerian_path(nx.path_graph(4))
G = nx.path_graph(6, create_using=nx.DiGraph)
assert nx.has_eulerian_path(G)
def test_has_eulerian_path_directed_graph(self):
# Test directed graphs and returns False
G = nx.DiGraph()
G.add_edges_from([(0, 1), (1, 2), (0, 2)])
assert not nx.has_eulerian_path(G)
# Test directed graphs without isolated node returns True
G = nx.DiGraph()
G.add_edges_from([(0, 1), (1, 2), (2, 0)])
assert nx.has_eulerian_path(G)
# Test directed graphs with isolated node returns False
G.add_node(3)
assert not nx.has_eulerian_path(G)
@pytest.mark.parametrize("G", (nx.Graph(), nx.DiGraph()))
def test_has_eulerian_path_not_weakly_connected(self, G):
G.add_edges_from([(0, 1), (2, 3), (3, 2)])
assert not nx.has_eulerian_path(G)
@pytest.mark.parametrize("G", (nx.Graph(), nx.DiGraph()))
def test_has_eulerian_path_unbalancedins_more_than_one(self, G):
G.add_edges_from([(0, 1), (2, 3)])
assert not nx.has_eulerian_path(G)
class TestFindPathStart:
def testfind_path_start(self):
find_path_start = nx.algorithms.euler._find_path_start
# Test digraphs return correct starting node.
G = nx.path_graph(6, create_using=nx.DiGraph)
assert find_path_start(G) == 0
edges = [(0, 1), (1, 2), (2, 0), (4, 0)]
assert find_path_start(nx.DiGraph(edges)) == 4
# Test graph with no Eulerian path return None.
edges = [(0, 1), (1, 2), (2, 3), (2, 4)]
assert find_path_start(nx.DiGraph(edges)) is None
class TestEulerianPath:
def test_eulerian_path(self):
x = [(4, 0), (0, 1), (1, 2), (2, 0)]
for e1, e2 in zip(x, nx.eulerian_path(nx.DiGraph(x))):
assert e1 == e2
def test_eulerian_path_straight_link(self):
G = nx.DiGraph()
result = [(1, 2), (2, 3), (3, 4), (4, 5)]
G.add_edges_from(result)
assert result == list(nx.eulerian_path(G))
assert result == list(nx.eulerian_path(G, source=1))
with pytest.raises(nx.NetworkXError):
list(nx.eulerian_path(G, source=3))
with pytest.raises(nx.NetworkXError):
list(nx.eulerian_path(G, source=4))
with pytest.raises(nx.NetworkXError):
list(nx.eulerian_path(G, source=5))
def test_eulerian_path_multigraph(self):
G = nx.MultiDiGraph()
result = [(2, 1), (1, 2), (2, 1), (1, 2), (2, 3), (3, 4), (4, 3)]
G.add_edges_from(result)
assert result == list(nx.eulerian_path(G))
assert result == list(nx.eulerian_path(G, source=2))
with pytest.raises(nx.NetworkXError):
list(nx.eulerian_path(G, source=3))
with pytest.raises(nx.NetworkXError):
list(nx.eulerian_path(G, source=4))
def test_eulerian_path_eulerian_circuit(self):
G = nx.DiGraph()
result = [(1, 2), (2, 3), (3, 4), (4, 1)]
result2 = [(2, 3), (3, 4), (4, 1), (1, 2)]
result3 = [(3, 4), (4, 1), (1, 2), (2, 3)]
G.add_edges_from(result)
assert result == list(nx.eulerian_path(G))
assert result == list(nx.eulerian_path(G, source=1))
assert result2 == list(nx.eulerian_path(G, source=2))
assert result3 == list(nx.eulerian_path(G, source=3))
def test_eulerian_path_undirected(self):
G = nx.Graph()
result = [(1, 2), (2, 3), (3, 4), (4, 5)]
result2 = [(5, 4), (4, 3), (3, 2), (2, 1)]
G.add_edges_from(result)
assert list(nx.eulerian_path(G)) in (result, result2)
assert result == list(nx.eulerian_path(G, source=1))
assert result2 == list(nx.eulerian_path(G, source=5))
with pytest.raises(nx.NetworkXError):
list(nx.eulerian_path(G, source=3))
with pytest.raises(nx.NetworkXError):
list(nx.eulerian_path(G, source=2))
def test_eulerian_path_multigraph_undirected(self):
G = nx.MultiGraph()
result = [(2, 1), (1, 2), (2, 1), (1, 2), (2, 3), (3, 4)]
G.add_edges_from(result)
assert result == list(nx.eulerian_path(G))
assert result == list(nx.eulerian_path(G, source=2))
with pytest.raises(nx.NetworkXError):
list(nx.eulerian_path(G, source=3))
with pytest.raises(nx.NetworkXError):
list(nx.eulerian_path(G, source=1))
@pytest.mark.parametrize(
("graph_type", "result"),
(
(nx.MultiGraph, [(0, 1, 0), (1, 0, 1)]),
(nx.MultiDiGraph, [(0, 1, 0), (1, 0, 0)]),
),
)
def test_eulerian_with_keys(self, graph_type, result):
G = graph_type([(0, 1), (1, 0)])
answer = nx.eulerian_path(G, keys=True)
assert list(answer) == result
class TestEulerize:
def test_disconnected(self):
with pytest.raises(nx.NetworkXError):
G = nx.from_edgelist([(0, 1), (2, 3)])
nx.eulerize(G)
def test_null_graph(self):
with pytest.raises(nx.NetworkXPointlessConcept):
nx.eulerize(nx.Graph())
def test_null_multigraph(self):
with pytest.raises(nx.NetworkXPointlessConcept):
nx.eulerize(nx.MultiGraph())
def test_on_empty_graph(self):
with pytest.raises(nx.NetworkXError):
nx.eulerize(nx.empty_graph(3))
def test_on_eulerian(self):
G = nx.cycle_graph(3)
H = nx.eulerize(G)
assert nx.is_isomorphic(G, H)
def test_on_eulerian_multigraph(self):
G = nx.MultiGraph(nx.cycle_graph(3))
G.add_edge(0, 1)
H = nx.eulerize(G)
assert nx.is_eulerian(H)
def test_on_complete_graph(self):
G = nx.complete_graph(4)
assert nx.is_eulerian(nx.eulerize(G))
assert nx.is_eulerian(nx.eulerize(nx.MultiGraph(G)))
def test_on_non_eulerian_graph(self):
G = nx.cycle_graph(18)
G.add_edge(0, 18)
G.add_edge(18, 19)
G.add_edge(17, 19)
G.add_edge(4, 20)
G.add_edge(20, 21)
G.add_edge(21, 22)
G.add_edge(22, 23)
G.add_edge(23, 24)
G.add_edge(24, 25)
G.add_edge(25, 26)
G.add_edge(26, 27)
G.add_edge(27, 28)
G.add_edge(28, 13)
assert not nx.is_eulerian(G)
G = nx.eulerize(G)
assert nx.is_eulerian(G)
assert nx.number_of_edges(G) == 39
@@ -0,0 +1,872 @@
import copy
import pytest
import networkx as nx
# Unit tests relevant for both functions in this module.
def test_positive_iters():
G1 = nx.empty_graph()
with pytest.raises(
ValueError,
match="The WL algorithm requires that `iterations` be positive",
):
nx.weisfeiler_lehman_graph_hash(G1, iterations=-3)
with pytest.raises(
ValueError,
match="The WL algorithm requires that `iterations` be positive",
):
nx.weisfeiler_lehman_subgraph_hashes(G1, iterations=-3)
with pytest.raises(
ValueError,
match="The WL algorithm requires that `iterations` be positive",
):
nx.weisfeiler_lehman_graph_hash(G1, iterations=0)
with pytest.raises(
ValueError,
match="The WL algorithm requires that `iterations` be positive",
):
nx.weisfeiler_lehman_subgraph_hashes(G1, iterations=0)
# Unit tests for the :func:`~networkx.weisfeiler_lehman_graph_hash` function
def test_empty_graph_hash():
"""
empty graphs should give hashes regardless of other params
"""
G1 = nx.empty_graph()
G2 = nx.empty_graph()
h1 = nx.weisfeiler_lehman_graph_hash(G1)
h2 = nx.weisfeiler_lehman_graph_hash(G2)
h3 = nx.weisfeiler_lehman_graph_hash(G2, edge_attr="edge_attr1")
h4 = nx.weisfeiler_lehman_graph_hash(G2, node_attr="node_attr1")
h5 = nx.weisfeiler_lehman_graph_hash(
G2, edge_attr="edge_attr1", node_attr="node_attr1"
)
h6 = nx.weisfeiler_lehman_graph_hash(G2, iterations=10)
assert h1 == h2
assert h1 == h3
assert h1 == h4
assert h1 == h5
assert h1 == h6
def test_directed():
"""
A directed graph with no bi-directional edges should yield different a graph hash
to the same graph taken as undirected if there are no hash collisions.
"""
r = 10
for i in range(r):
G_directed = nx.gn_graph(10 + r, seed=100 + i)
G_undirected = nx.to_undirected(G_directed)
h_directed = nx.weisfeiler_lehman_graph_hash(G_directed)
h_undirected = nx.weisfeiler_lehman_graph_hash(G_undirected)
assert h_directed != h_undirected
def test_reversed():
"""
A directed graph with no bi-directional edges should yield different a graph hash
to the same graph taken with edge directions reversed if there are no hash
collisions. Here we test a cycle graph which is the minimal counterexample
"""
G = nx.cycle_graph(5, create_using=nx.DiGraph)
nx.set_node_attributes(G, {n: str(n) for n in G.nodes()}, name="label")
G_reversed = G.reverse()
h = nx.weisfeiler_lehman_graph_hash(G, node_attr="label")
h_reversed = nx.weisfeiler_lehman_graph_hash(G_reversed, node_attr="label")
assert h != h_reversed
def test_isomorphic():
"""
graph hashes should be invariant to node-relabeling (when the output is reindexed
by the same mapping)
"""
n, r = 100, 10
p = 1.0 / r
for i in range(1, r + 1):
G1 = nx.erdos_renyi_graph(n, p * i, seed=200 + i)
G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
g1_hash = nx.weisfeiler_lehman_graph_hash(G1)
g2_hash = nx.weisfeiler_lehman_graph_hash(G2)
assert g1_hash == g2_hash
def test_isomorphic_edge_attr():
"""
Isomorphic graphs with differing edge attributes should yield different graph
hashes if the 'edge_attr' argument is supplied and populated in the graph,
and there are no hash collisions.
The output should still be invariant to node-relabeling
"""
n, r = 100, 10
p = 1.0 / r
for i in range(1, r + 1):
G1 = nx.erdos_renyi_graph(n, p * i, seed=300 + i)
for a, b in G1.edges:
G1[a][b]["edge_attr1"] = f"{a}-{b}-1"
G1[a][b]["edge_attr2"] = f"{a}-{b}-2"
g1_hash_with_edge_attr1 = nx.weisfeiler_lehman_graph_hash(
G1, edge_attr="edge_attr1"
)
g1_hash_with_edge_attr2 = nx.weisfeiler_lehman_graph_hash(
G1, edge_attr="edge_attr2"
)
g1_hash_no_edge_attr = nx.weisfeiler_lehman_graph_hash(G1, edge_attr=None)
assert g1_hash_with_edge_attr1 != g1_hash_no_edge_attr
assert g1_hash_with_edge_attr2 != g1_hash_no_edge_attr
assert g1_hash_with_edge_attr1 != g1_hash_with_edge_attr2
G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
g2_hash_with_edge_attr1 = nx.weisfeiler_lehman_graph_hash(
G2, edge_attr="edge_attr1"
)
g2_hash_with_edge_attr2 = nx.weisfeiler_lehman_graph_hash(
G2, edge_attr="edge_attr2"
)
assert g1_hash_with_edge_attr1 == g2_hash_with_edge_attr1
assert g1_hash_with_edge_attr2 == g2_hash_with_edge_attr2
def test_missing_edge_attr():
"""
If the 'edge_attr' argument is supplied but is missing from an edge in the graph,
we should raise a KeyError
"""
G = nx.Graph()
G.add_edges_from([(1, 2, {"edge_attr1": "a"}), (1, 3, {})])
pytest.raises(KeyError, nx.weisfeiler_lehman_graph_hash, G, edge_attr="edge_attr1")
def test_isomorphic_node_attr():
"""
Isomorphic graphs with differing node attributes should yield different graph
hashes if the 'node_attr' argument is supplied and populated in the graph, and
there are no hash collisions.
The output should still be invariant to node-relabeling
"""
n, r = 100, 10
p = 1.0 / r
for i in range(1, r + 1):
G1 = nx.erdos_renyi_graph(n, p * i, seed=400 + i)
for u in G1.nodes():
G1.nodes[u]["node_attr1"] = f"{u}-1"
G1.nodes[u]["node_attr2"] = f"{u}-2"
g1_hash_with_node_attr1 = nx.weisfeiler_lehman_graph_hash(
G1, node_attr="node_attr1"
)
g1_hash_with_node_attr2 = nx.weisfeiler_lehman_graph_hash(
G1, node_attr="node_attr2"
)
g1_hash_no_node_attr = nx.weisfeiler_lehman_graph_hash(G1, node_attr=None)
assert g1_hash_with_node_attr1 != g1_hash_no_node_attr
assert g1_hash_with_node_attr2 != g1_hash_no_node_attr
assert g1_hash_with_node_attr1 != g1_hash_with_node_attr2
G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
g2_hash_with_node_attr1 = nx.weisfeiler_lehman_graph_hash(
G2, node_attr="node_attr1"
)
g2_hash_with_node_attr2 = nx.weisfeiler_lehman_graph_hash(
G2, node_attr="node_attr2"
)
assert g1_hash_with_node_attr1 == g2_hash_with_node_attr1
assert g1_hash_with_node_attr2 == g2_hash_with_node_attr2
def test_missing_node_attr():
"""
If the 'node_attr' argument is supplied but is missing from a node in the graph,
we should raise a KeyError
"""
G = nx.Graph()
G.add_nodes_from([(1, {"node_attr1": "a"}), (2, {})])
G.add_edges_from([(1, 2), (2, 3), (3, 1), (1, 4)])
pytest.raises(KeyError, nx.weisfeiler_lehman_graph_hash, G, node_attr="node_attr1")
def test_isomorphic_edge_attr_and_node_attr():
"""
Isomorphic graphs with differing node attributes should yield different graph
hashes if the 'node_attr' and 'edge_attr' argument is supplied and populated in
the graph, and there are no hash collisions.
The output should still be invariant to node-relabeling
"""
n, r = 100, 10
p = 1.0 / r
for i in range(1, r + 1):
G1 = nx.erdos_renyi_graph(n, p * i, seed=500 + i)
for u in G1.nodes():
G1.nodes[u]["node_attr1"] = f"{u}-1"
G1.nodes[u]["node_attr2"] = f"{u}-2"
for a, b in G1.edges:
G1[a][b]["edge_attr1"] = f"{a}-{b}-1"
G1[a][b]["edge_attr2"] = f"{a}-{b}-2"
g1_hash_edge1_node1 = nx.weisfeiler_lehman_graph_hash(
G1, edge_attr="edge_attr1", node_attr="node_attr1"
)
g1_hash_edge2_node2 = nx.weisfeiler_lehman_graph_hash(
G1, edge_attr="edge_attr2", node_attr="node_attr2"
)
g1_hash_edge1_node2 = nx.weisfeiler_lehman_graph_hash(
G1, edge_attr="edge_attr1", node_attr="node_attr2"
)
g1_hash_no_attr = nx.weisfeiler_lehman_graph_hash(G1)
assert g1_hash_edge1_node1 != g1_hash_no_attr
assert g1_hash_edge2_node2 != g1_hash_no_attr
assert g1_hash_edge1_node1 != g1_hash_edge2_node2
assert g1_hash_edge1_node2 != g1_hash_edge2_node2
assert g1_hash_edge1_node2 != g1_hash_edge1_node1
G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
g2_hash_edge1_node1 = nx.weisfeiler_lehman_graph_hash(
G2, edge_attr="edge_attr1", node_attr="node_attr1"
)
g2_hash_edge2_node2 = nx.weisfeiler_lehman_graph_hash(
G2, edge_attr="edge_attr2", node_attr="node_attr2"
)
assert g1_hash_edge1_node1 == g2_hash_edge1_node1
assert g1_hash_edge2_node2 == g2_hash_edge2_node2
def test_digest_size():
"""
The hash string lengths should be as expected for a variety of graphs and
digest sizes
"""
n, r = 100, 10
p = 1.0 / r
for i in range(1, r + 1):
G = nx.erdos_renyi_graph(n, p * i, seed=1000 + i)
h16 = nx.weisfeiler_lehman_graph_hash(G)
h32 = nx.weisfeiler_lehman_graph_hash(G, digest_size=32)
assert h16 != h32
assert len(h16) == 16 * 2
assert len(h32) == 32 * 2
def test_directed_bugs():
"""
These were bugs for directed graphs as discussed in issue #7806
"""
Ga = nx.DiGraph()
Gb = nx.DiGraph()
Ga.add_nodes_from([1, 2, 3, 4])
Gb.add_nodes_from([1, 2, 3, 4])
Ga.add_edges_from([(1, 2), (3, 2)])
Gb.add_edges_from([(1, 2), (3, 4)])
Ga_hash = nx.weisfeiler_lehman_graph_hash(Ga)
Gb_hash = nx.weisfeiler_lehman_graph_hash(Gb)
assert Ga_hash != Gb_hash
Tree1 = nx.DiGraph()
Tree1.add_edges_from([(0, 4), (1, 5), (2, 6), (3, 7)])
Tree1.add_edges_from([(4, 8), (5, 8), (6, 9), (7, 9)])
Tree1.add_edges_from([(8, 10), (9, 10)])
nx.set_node_attributes(
Tree1, {10: "s", 8: "a", 9: "a", 4: "b", 5: "b", 6: "b", 7: "b"}, "weight"
)
Tree2 = copy.deepcopy(Tree1)
nx.set_node_attributes(Tree1, {0: "d", 1: "c", 2: "d", 3: "c"}, "weight")
nx.set_node_attributes(Tree2, {0: "d", 1: "d", 2: "c", 3: "c"}, "weight")
Tree1_hash_short = nx.weisfeiler_lehman_graph_hash(
Tree1, iterations=1, node_attr="weight"
)
Tree2_hash_short = nx.weisfeiler_lehman_graph_hash(
Tree2, iterations=1, node_attr="weight"
)
assert Tree1_hash_short == Tree2_hash_short
Tree1_hash = nx.weisfeiler_lehman_graph_hash(
Tree1, node_attr="weight"
) # Default is 3 iterations
Tree2_hash = nx.weisfeiler_lehman_graph_hash(Tree2, node_attr="weight")
assert Tree1_hash != Tree2_hash
def test_trivial_labels_isomorphism():
"""
Trivial labelling of the graph should not change isomorphism verdicts.
"""
n, r = 100, 10
p = 1.0 / r
for i in range(1, r + 1):
G1 = nx.erdos_renyi_graph(n, p * i, seed=500 + i)
G2 = nx.erdos_renyi_graph(n, p * i, seed=42 + i)
G1_hash = nx.weisfeiler_lehman_graph_hash(G1)
G2_hash = nx.weisfeiler_lehman_graph_hash(G2)
equal = G1_hash == G2_hash
nx.set_node_attributes(G1, values=1, name="weight")
nx.set_node_attributes(G2, values=1, name="weight")
G1_hash_node = nx.weisfeiler_lehman_graph_hash(G1, node_attr="weight")
G2_hash_node = nx.weisfeiler_lehman_graph_hash(G2, node_attr="weight")
equal_node = G1_hash_node == G2_hash_node
nx.set_edge_attributes(G1, values="a", name="e_weight")
nx.set_edge_attributes(G2, values="a", name="e_weight")
G1_hash_edge = nx.weisfeiler_lehman_graph_hash(G1, edge_attr="e_weight")
G2_hash_edge = nx.weisfeiler_lehman_graph_hash(G2, edge_attr="e_weight")
equal_edge = G1_hash_edge == G2_hash_edge
G1_hash_both = nx.weisfeiler_lehman_graph_hash(
G1, edge_attr="e_weight", node_attr="weight"
)
G2_hash_both = nx.weisfeiler_lehman_graph_hash(
G2, edge_attr="e_weight", node_attr="weight"
)
equal_both = G1_hash_both == G2_hash_both
assert equal == equal_node
assert equal_node == equal_edge
assert equal_edge == equal_both
def test_trivial_labels_isomorphism_directed():
"""
Trivial labelling of the graph should not change isomorphism verdicts on digraphs.
"""
n, r = 100, 10
p = 1.0 / r
for i in range(1, r + 1):
G1 = nx.erdos_renyi_graph(n, p * i, directed=True, seed=500 + i)
G2 = nx.erdos_renyi_graph(n, p * i, directed=True, seed=42 + i)
G1_hash = nx.weisfeiler_lehman_graph_hash(G1)
G2_hash = nx.weisfeiler_lehman_graph_hash(G2)
equal = G1_hash == G2_hash
nx.set_node_attributes(G1, values=1, name="weight")
nx.set_node_attributes(G2, values=1, name="weight")
G1_hash_node = nx.weisfeiler_lehman_graph_hash(G1, node_attr="weight")
G2_hash_node = nx.weisfeiler_lehman_graph_hash(G2, node_attr="weight")
equal_node = G1_hash_node == G2_hash_node
nx.set_edge_attributes(G1, values="a", name="e_weight")
nx.set_edge_attributes(G2, values="a", name="e_weight")
G1_hash_edge = nx.weisfeiler_lehman_graph_hash(G1, edge_attr="e_weight")
G2_hash_edge = nx.weisfeiler_lehman_graph_hash(G2, edge_attr="e_weight")
equal_edge = G1_hash_edge == G2_hash_edge
G1_hash_both = nx.weisfeiler_lehman_graph_hash(
G1, edge_attr="e_weight", node_attr="weight"
)
G2_hash_both = nx.weisfeiler_lehman_graph_hash(
G2, edge_attr="e_weight", node_attr="weight"
)
equal_both = G1_hash_both == G2_hash_both
assert equal == equal_node
assert equal_node == equal_edge
assert equal_edge == equal_both
# Specific case that was found to be a bug in issue #7806
# Without weights worked
Ga = nx.DiGraph()
Ga.add_nodes_from([1, 2, 3, 4])
Gb = copy.deepcopy(Ga)
Ga.add_edges_from([(1, 2), (3, 2)])
Gb.add_edges_from([(1, 2), (3, 4)])
Ga_hash = nx.weisfeiler_lehman_graph_hash(Ga)
Gb_hash = nx.weisfeiler_lehman_graph_hash(Gb)
assert Ga_hash != Gb_hash
# Now with trivial weights
nx.set_node_attributes(Ga, values=1, name="weight")
nx.set_node_attributes(Gb, values=1, name="weight")
Ga_hash = nx.weisfeiler_lehman_graph_hash(Ga, node_attr="weight")
Gb_hash = nx.weisfeiler_lehman_graph_hash(Gb, node_attr="weight")
assert Ga_hash != Gb_hash
def test_trivial_labels_hashes():
"""
Test that 'empty' labelling of nodes or edges shouldn't have a different impact
on the calculated hash. Note that we cannot assume that trivial weights have no
impact at all. Without (trivial) weights, a node will start with hashing its
degree. This step is omitted when there are weights.
"""
n, r = 100, 10
p = 1.0 / r
for i in range(1, r + 1):
G1 = nx.erdos_renyi_graph(n, p * i, seed=500 + i)
nx.set_node_attributes(G1, values="", name="weight")
first = nx.weisfeiler_lehman_graph_hash(G1, node_attr="weight")
nx.set_edge_attributes(G1, values="", name="e_weight")
second = nx.weisfeiler_lehman_graph_hash(G1, edge_attr="e_weight")
assert first == second
third = nx.weisfeiler_lehman_graph_hash(
G1, edge_attr="e_weight", node_attr="weight"
)
assert second == third
# Unit tests for the :func:`~networkx.weisfeiler_lehman_subgraph_hashes` function
def is_subiteration(a, b):
"""
returns True if that each hash sequence in 'a' is a prefix for
the corresponding sequence indexed by the same node in 'b'.
"""
return all(b[node][: len(hashes)] == hashes for node, hashes in a.items())
def hexdigest_sizes_correct(a, digest_size):
"""
returns True if all hex digest sizes are the expected length in a
node:subgraph-hashes dictionary. Hex digest string length == 2 * bytes digest
length since each pair of hex digits encodes 1 byte
(https://docs.python.org/3/library/hashlib.html)
"""
hexdigest_size = digest_size * 2
def list_digest_sizes_correct(l):
return all(len(x) == hexdigest_size for x in l)
return all(list_digest_sizes_correct(hashes) for hashes in a.values())
def test_empty_graph_subgraph_hash():
""" "
empty graphs should give empty dict subgraph hashes regardless of other params
"""
G = nx.empty_graph()
subgraph_hashes1 = nx.weisfeiler_lehman_subgraph_hashes(G)
subgraph_hashes2 = nx.weisfeiler_lehman_subgraph_hashes(G, edge_attr="edge_attr")
subgraph_hashes3 = nx.weisfeiler_lehman_subgraph_hashes(G, node_attr="edge_attr")
subgraph_hashes4 = nx.weisfeiler_lehman_subgraph_hashes(G, iterations=2)
subgraph_hashes5 = nx.weisfeiler_lehman_subgraph_hashes(G, digest_size=64)
assert subgraph_hashes1 == {}
assert subgraph_hashes2 == {}
assert subgraph_hashes3 == {}
assert subgraph_hashes4 == {}
assert subgraph_hashes5 == {}
def test_directed_subgraph_hash():
"""
A directed graph with no bi-directional edges should yield different subgraph
hashes to the same graph taken as undirected, if all hashes don't collide.
"""
r = 10
for i in range(r):
G_directed = nx.gn_graph(10 + r, seed=100 + i)
G_undirected = nx.to_undirected(G_directed)
directed_subgraph_hashes = nx.weisfeiler_lehman_subgraph_hashes(G_directed)
undirected_subgraph_hashes = nx.weisfeiler_lehman_subgraph_hashes(G_undirected)
assert directed_subgraph_hashes != undirected_subgraph_hashes
def test_reversed_subgraph_hash():
"""
A directed graph with no bi-directional edges should yield different subgraph
hashes to the same graph taken with edge directions reversed if there are no
hash collisions. Here we test a cycle graph which is the minimal counterexample
"""
G = nx.cycle_graph(5, create_using=nx.DiGraph)
nx.set_node_attributes(G, {n: str(n) for n in G.nodes()}, name="label")
G_reversed = G.reverse()
h = nx.weisfeiler_lehman_subgraph_hashes(G, node_attr="label")
h_reversed = nx.weisfeiler_lehman_subgraph_hashes(G_reversed, node_attr="label")
assert h != h_reversed
def test_isomorphic_subgraph_hash():
"""
the subgraph hashes should be invariant to node-relabeling when the output is
reindexed by the same mapping and all hashes don't collide.
"""
n, r = 100, 10
p = 1.0 / r
for i in range(1, r + 1):
G1 = nx.erdos_renyi_graph(n, p * i, seed=200 + i)
G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
g1_subgraph_hashes = nx.weisfeiler_lehman_subgraph_hashes(G1)
g2_subgraph_hashes = nx.weisfeiler_lehman_subgraph_hashes(G2)
assert g1_subgraph_hashes == {-1 * k: v for k, v in g2_subgraph_hashes.items()}
def test_isomorphic_edge_attr_subgraph_hash():
"""
Isomorphic graphs with differing edge attributes should yield different subgraph
hashes if the 'edge_attr' argument is supplied and populated in the graph, and
all hashes don't collide.
The output should still be invariant to node-relabeling
"""
n, r = 100, 10
p = 1.0 / r
for i in range(1, r + 1):
G1 = nx.erdos_renyi_graph(n, p * i, seed=300 + i)
for a, b in G1.edges:
G1[a][b]["edge_attr1"] = f"{a}-{b}-1"
G1[a][b]["edge_attr2"] = f"{a}-{b}-2"
g1_hash_with_edge_attr1 = nx.weisfeiler_lehman_subgraph_hashes(
G1, edge_attr="edge_attr1"
)
g1_hash_with_edge_attr2 = nx.weisfeiler_lehman_subgraph_hashes(
G1, edge_attr="edge_attr2"
)
g1_hash_no_edge_attr = nx.weisfeiler_lehman_subgraph_hashes(G1, edge_attr=None)
assert g1_hash_with_edge_attr1 != g1_hash_no_edge_attr
assert g1_hash_with_edge_attr2 != g1_hash_no_edge_attr
assert g1_hash_with_edge_attr1 != g1_hash_with_edge_attr2
G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
g2_hash_with_edge_attr1 = nx.weisfeiler_lehman_subgraph_hashes(
G2, edge_attr="edge_attr1"
)
g2_hash_with_edge_attr2 = nx.weisfeiler_lehman_subgraph_hashes(
G2, edge_attr="edge_attr2"
)
assert g1_hash_with_edge_attr1 == {
-1 * k: v for k, v in g2_hash_with_edge_attr1.items()
}
assert g1_hash_with_edge_attr2 == {
-1 * k: v for k, v in g2_hash_with_edge_attr2.items()
}
def test_missing_edge_attr_subgraph_hash():
"""
If the 'edge_attr' argument is supplied but is missing from an edge in the graph,
we should raise a KeyError
"""
G = nx.Graph()
G.add_edges_from([(1, 2, {"edge_attr1": "a"}), (1, 3, {})])
pytest.raises(
KeyError, nx.weisfeiler_lehman_subgraph_hashes, G, edge_attr="edge_attr1"
)
def test_isomorphic_node_attr_subgraph_hash():
"""
Isomorphic graphs with differing node attributes should yield different subgraph
hashes if the 'node_attr' argument is supplied and populated in the graph, and
all hashes don't collide.
The output should still be invariant to node-relabeling
"""
n, r = 100, 10
p = 1.0 / r
for i in range(1, r + 1):
G1 = nx.erdos_renyi_graph(n, p * i, seed=400 + i)
for u in G1.nodes():
G1.nodes[u]["node_attr1"] = f"{u}-1"
G1.nodes[u]["node_attr2"] = f"{u}-2"
g1_hash_with_node_attr1 = nx.weisfeiler_lehman_subgraph_hashes(
G1, node_attr="node_attr1"
)
g1_hash_with_node_attr2 = nx.weisfeiler_lehman_subgraph_hashes(
G1, node_attr="node_attr2"
)
g1_hash_no_node_attr = nx.weisfeiler_lehman_subgraph_hashes(G1, node_attr=None)
assert g1_hash_with_node_attr1 != g1_hash_no_node_attr
assert g1_hash_with_node_attr2 != g1_hash_no_node_attr
assert g1_hash_with_node_attr1 != g1_hash_with_node_attr2
G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
g2_hash_with_node_attr1 = nx.weisfeiler_lehman_subgraph_hashes(
G2, node_attr="node_attr1"
)
g2_hash_with_node_attr2 = nx.weisfeiler_lehman_subgraph_hashes(
G2, node_attr="node_attr2"
)
assert g1_hash_with_node_attr1 == {
-1 * k: v for k, v in g2_hash_with_node_attr1.items()
}
assert g1_hash_with_node_attr2 == {
-1 * k: v for k, v in g2_hash_with_node_attr2.items()
}
def test_missing_node_attr_subgraph_hash():
"""
If the 'node_attr' argument is supplied but is missing from a node in the graph,
we should raise a KeyError
"""
G = nx.Graph()
G.add_nodes_from([(1, {"node_attr1": "a"}), (2, {})])
G.add_edges_from([(1, 2), (2, 3), (3, 1), (1, 4)])
pytest.raises(
KeyError, nx.weisfeiler_lehman_subgraph_hashes, G, node_attr="node_attr1"
)
def test_isomorphic_edge_attr_and_node_attr_subgraph_hash():
"""
Isomorphic graphs with differing node attributes should yield different subgraph
hashes if the 'node_attr' and 'edge_attr' argument is supplied and populated in
the graph, and all hashes don't collide
The output should still be invariant to node-relabeling
"""
n, r = 100, 10
p = 1.0 / r
for i in range(1, r + 1):
G1 = nx.erdos_renyi_graph(n, p * i, seed=500 + i)
for u in G1.nodes():
G1.nodes[u]["node_attr1"] = f"{u}-1"
G1.nodes[u]["node_attr2"] = f"{u}-2"
for a, b in G1.edges:
G1[a][b]["edge_attr1"] = f"{a}-{b}-1"
G1[a][b]["edge_attr2"] = f"{a}-{b}-2"
g1_hash_edge1_node1 = nx.weisfeiler_lehman_subgraph_hashes(
G1, edge_attr="edge_attr1", node_attr="node_attr1"
)
g1_hash_edge2_node2 = nx.weisfeiler_lehman_subgraph_hashes(
G1, edge_attr="edge_attr2", node_attr="node_attr2"
)
g1_hash_edge1_node2 = nx.weisfeiler_lehman_subgraph_hashes(
G1, edge_attr="edge_attr1", node_attr="node_attr2"
)
g1_hash_no_attr = nx.weisfeiler_lehman_subgraph_hashes(G1)
assert g1_hash_edge1_node1 != g1_hash_no_attr
assert g1_hash_edge2_node2 != g1_hash_no_attr
assert g1_hash_edge1_node1 != g1_hash_edge2_node2
assert g1_hash_edge1_node2 != g1_hash_edge2_node2
assert g1_hash_edge1_node2 != g1_hash_edge1_node1
G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
g2_hash_edge1_node1 = nx.weisfeiler_lehman_subgraph_hashes(
G2, edge_attr="edge_attr1", node_attr="node_attr1"
)
g2_hash_edge2_node2 = nx.weisfeiler_lehman_subgraph_hashes(
G2, edge_attr="edge_attr2", node_attr="node_attr2"
)
assert g1_hash_edge1_node1 == {
-1 * k: v for k, v in g2_hash_edge1_node1.items()
}
assert g1_hash_edge2_node2 == {
-1 * k: v for k, v in g2_hash_edge2_node2.items()
}
def test_iteration_depth():
"""
All nodes should have the correct number of subgraph hashes in the output when
using degree as initial node labels.
Subsequent iteration depths for the same graph should be additive for each node
"""
n, r = 100, 10
p = 1.0 / r
for i in range(1, r + 1):
G = nx.erdos_renyi_graph(n, p * i, seed=600 + i)
depth3 = nx.weisfeiler_lehman_subgraph_hashes(G, iterations=3)
depth4 = nx.weisfeiler_lehman_subgraph_hashes(G, iterations=4)
depth5 = nx.weisfeiler_lehman_subgraph_hashes(G, iterations=5)
assert all(len(hashes) == 3 for hashes in depth3.values())
assert all(len(hashes) == 4 for hashes in depth4.values())
assert all(len(hashes) == 5 for hashes in depth5.values())
assert is_subiteration(depth3, depth4)
assert is_subiteration(depth4, depth5)
assert is_subiteration(depth3, depth5)
def test_iteration_depth_edge_attr():
"""
All nodes should have the correct number of subgraph hashes in the output when
setting initial node labels empty and using an edge attribute when aggregating
neighborhoods.
Subsequent iteration depths for the same graph should be additive for each node
"""
n, r = 100, 10
p = 1.0 / r
for i in range(1, r + 1):
G = nx.erdos_renyi_graph(n, p * i, seed=700 + i)
for a, b in G.edges:
G[a][b]["edge_attr1"] = f"{a}-{b}-1"
depth3 = nx.weisfeiler_lehman_subgraph_hashes(
G, edge_attr="edge_attr1", iterations=3
)
depth4 = nx.weisfeiler_lehman_subgraph_hashes(
G, edge_attr="edge_attr1", iterations=4
)
depth5 = nx.weisfeiler_lehman_subgraph_hashes(
G, edge_attr="edge_attr1", iterations=5
)
assert all(len(hashes) == 3 for hashes in depth3.values())
assert all(len(hashes) == 4 for hashes in depth4.values())
assert all(len(hashes) == 5 for hashes in depth5.values())
assert is_subiteration(depth3, depth4)
assert is_subiteration(depth4, depth5)
assert is_subiteration(depth3, depth5)
def test_iteration_depth_node_attr():
"""
All nodes should have the correct number of subgraph hashes in the output when
setting initial node labels to an attribute.
Subsequent iteration depths for the same graph should be additive for each node
"""
n, r = 100, 10
p = 1.0 / r
for i in range(1, r + 1):
G = nx.erdos_renyi_graph(n, p * i, seed=800 + i)
for u in G.nodes():
G.nodes[u]["node_attr1"] = f"{u}-1"
depth3 = nx.weisfeiler_lehman_subgraph_hashes(
G, node_attr="node_attr1", iterations=3
)
depth4 = nx.weisfeiler_lehman_subgraph_hashes(
G, node_attr="node_attr1", iterations=4
)
depth5 = nx.weisfeiler_lehman_subgraph_hashes(
G, node_attr="node_attr1", iterations=5
)
assert all(len(hashes) == 3 for hashes in depth3.values())
assert all(len(hashes) == 4 for hashes in depth4.values())
assert all(len(hashes) == 5 for hashes in depth5.values())
assert is_subiteration(depth3, depth4)
assert is_subiteration(depth4, depth5)
assert is_subiteration(depth3, depth5)
def test_iteration_depth_node_edge_attr():
"""
All nodes should have the correct number of subgraph hashes in the output when
setting initial node labels to an attribute and also using an edge attribute when
aggregating neighborhoods.
Subsequent iteration depths for the same graph should be additive for each node
"""
n, r = 100, 10
p = 1.0 / r
for i in range(1, r + 1):
G = nx.erdos_renyi_graph(n, p * i, seed=900 + i)
for u in G.nodes():
G.nodes[u]["node_attr1"] = f"{u}-1"
for a, b in G.edges:
G[a][b]["edge_attr1"] = f"{a}-{b}-1"
depth3 = nx.weisfeiler_lehman_subgraph_hashes(
G, edge_attr="edge_attr1", node_attr="node_attr1", iterations=3
)
depth4 = nx.weisfeiler_lehman_subgraph_hashes(
G, edge_attr="edge_attr1", node_attr="node_attr1", iterations=4
)
depth5 = nx.weisfeiler_lehman_subgraph_hashes(
G, edge_attr="edge_attr1", node_attr="node_attr1", iterations=5
)
assert all(len(hashes) == 3 for hashes in depth3.values())
assert all(len(hashes) == 4 for hashes in depth4.values())
assert all(len(hashes) == 5 for hashes in depth5.values())
assert is_subiteration(depth3, depth4)
assert is_subiteration(depth4, depth5)
assert is_subiteration(depth3, depth5)
def test_digest_size_subgraph_hash():
"""
The hash string lengths should be as expected for a variety of graphs and
digest sizes
"""
n, r = 100, 10
p = 1.0 / r
for i in range(1, r + 1):
G = nx.erdos_renyi_graph(n, p * i, seed=1000 + i)
digest_size16_hashes = nx.weisfeiler_lehman_subgraph_hashes(G)
digest_size32_hashes = nx.weisfeiler_lehman_subgraph_hashes(G, digest_size=32)
assert digest_size16_hashes != digest_size32_hashes
assert hexdigest_sizes_correct(digest_size16_hashes, 16)
assert hexdigest_sizes_correct(digest_size32_hashes, 32)
def test_initial_node_labels_subgraph_hash():
"""
Including the hashed initial label prepends an extra hash to the lists
"""
G = nx.path_graph(5)
nx.set_node_attributes(G, {i: int(0 < i < 4) for i in G}, "label")
# initial node labels:
# 0--1--1--1--0
without_initial_label = nx.weisfeiler_lehman_subgraph_hashes(G, node_attr="label")
assert all(len(v) == 3 for v in without_initial_label.values())
# 3 different 1 hop nhds
assert len({v[0] for v in without_initial_label.values()}) == 3
with_initial_label = nx.weisfeiler_lehman_subgraph_hashes(
G, node_attr="label", include_initial_labels=True
)
assert all(len(v) == 4 for v in with_initial_label.values())
# 2 different initial labels
assert len({v[0] for v in with_initial_label.values()}) == 2
# check hashes match otherwise
for u in G:
for a, b in zip(
with_initial_label[u][1:], without_initial_label[u], strict=True
):
assert a == b
@@ -0,0 +1,163 @@
import pytest
import networkx as nx
def test_valid_degree_sequence1():
n = 100
p = 0.3
for i in range(10):
G = nx.erdos_renyi_graph(n, p)
deg = (d for n, d in G.degree())
assert nx.is_graphical(deg, method="eg")
assert nx.is_graphical(deg, method="hh")
def test_valid_degree_sequence2():
n = 100
for i in range(10):
G = nx.barabasi_albert_graph(n, 1)
deg = (d for n, d in G.degree())
assert nx.is_graphical(deg, method="eg")
assert nx.is_graphical(deg, method="hh")
def test_string_input():
pytest.raises(nx.NetworkXException, nx.is_graphical, [], "foo")
pytest.raises(nx.NetworkXException, nx.is_graphical, ["red"], "hh")
pytest.raises(nx.NetworkXException, nx.is_graphical, ["red"], "eg")
def test_non_integer_input():
pytest.raises(nx.NetworkXException, nx.is_graphical, [72.5], "eg")
pytest.raises(nx.NetworkXException, nx.is_graphical, [72.5], "hh")
def test_negative_input():
assert not nx.is_graphical([-1], "hh")
assert not nx.is_graphical([-1], "eg")
class TestAtlas:
@classmethod
def setup_class(cls):
global atlas
from networkx.generators import atlas
cls.GAG = atlas.graph_atlas_g()
def test_atlas(self):
for graph in self.GAG:
deg = (d for n, d in graph.degree())
assert nx.is_graphical(deg, method="eg")
assert nx.is_graphical(deg, method="hh")
def test_small_graph_true():
z = [5, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1]
assert nx.is_graphical(z, method="hh")
assert nx.is_graphical(z, method="eg")
z = [10, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2]
assert nx.is_graphical(z, method="hh")
assert nx.is_graphical(z, method="eg")
z = [1, 1, 1, 1, 1, 2, 2, 2, 3, 4]
assert nx.is_graphical(z, method="hh")
assert nx.is_graphical(z, method="eg")
def test_small_graph_false():
z = [1000, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1]
assert not nx.is_graphical(z, method="hh")
assert not nx.is_graphical(z, method="eg")
z = [6, 5, 4, 4, 2, 1, 1, 1]
assert not nx.is_graphical(z, method="hh")
assert not nx.is_graphical(z, method="eg")
z = [1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4]
assert not nx.is_graphical(z, method="hh")
assert not nx.is_graphical(z, method="eg")
def test_directed_degree_sequence():
# Test a range of valid directed degree sequences
n, r = 100, 10
p = 1.0 / r
for i in range(r):
G = nx.erdos_renyi_graph(n, p * (i + 1), None, True)
din = (d for n, d in G.in_degree())
dout = (d for n, d in G.out_degree())
assert nx.is_digraphical(din, dout)
def test_small_directed_sequences():
dout = [5, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1]
din = [3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 1]
assert nx.is_digraphical(din, dout)
# Test nongraphical directed sequence
dout = [1000, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1]
din = [103, 102, 102, 102, 102, 102, 102, 102, 102, 102]
assert not nx.is_digraphical(din, dout)
# Test digraphical small sequence
dout = [1, 1, 1, 1, 1, 2, 2, 2, 3, 4]
din = [2, 2, 2, 2, 2, 2, 2, 2, 1, 1]
assert nx.is_digraphical(din, dout)
# Test nonmatching sum
din = [2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1]
assert not nx.is_digraphical(din, dout)
# Test for negative integer in sequence
din = [2, 2, 2, -2, 2, 2, 2, 2, 1, 1, 4]
assert not nx.is_digraphical(din, dout)
# Test for noninteger
din = dout = [1, 1, 1.1, 1]
assert not nx.is_digraphical(din, dout)
din = dout = [1, 1, "rer", 1]
assert not nx.is_digraphical(din, dout)
def test_multi_sequence():
# Test nongraphical multi sequence
seq = [1000, 3, 3, 3, 3, 2, 2, 2, 1, 1]
assert not nx.is_multigraphical(seq)
# Test small graphical multi sequence
seq = [6, 5, 4, 4, 2, 1, 1, 1]
assert nx.is_multigraphical(seq)
# Test for negative integer in sequence
seq = [6, 5, 4, -4, 2, 1, 1, 1]
assert not nx.is_multigraphical(seq)
# Test for sequence with odd sum
seq = [1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4]
assert not nx.is_multigraphical(seq)
# Test for noninteger
seq = [1, 1, 1.1, 1]
assert not nx.is_multigraphical(seq)
seq = [1, 1, "rer", 1]
assert not nx.is_multigraphical(seq)
def test_pseudo_sequence():
# Test small valid pseudo sequence
seq = [1000, 3, 3, 3, 3, 2, 2, 2, 1, 1]
assert nx.is_pseudographical(seq)
# Test for sequence with odd sum
seq = [1000, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1]
assert not nx.is_pseudographical(seq)
# Test for negative integer in sequence
seq = [1000, 3, 3, 3, 3, 2, 2, -2, 1, 1]
assert not nx.is_pseudographical(seq)
# Test for noninteger
seq = [1, 1, 1.1, 1]
assert not nx.is_pseudographical(seq)
seq = [1, 1, "rer", 1]
assert not nx.is_pseudographical(seq)
def test_numpy_degree_sequence():
np = pytest.importorskip("numpy")
ds = np.array([1, 2, 2, 2, 1], dtype=np.int64)
assert nx.is_graphical(ds, "eg")
assert nx.is_graphical(ds, "hh")
ds = np.array([1, 2, 2, 2, 1], dtype=np.float64)
assert nx.is_graphical(ds, "eg")
assert nx.is_graphical(ds, "hh")
ds = np.array([1.1, 2, 2, 2, 1], dtype=np.float64)
pytest.raises(nx.NetworkXException, nx.is_graphical, ds, "eg")
pytest.raises(nx.NetworkXException, nx.is_graphical, ds, "hh")
@@ -0,0 +1,46 @@
import pytest
import networkx as nx
def test_hierarchy_undirected():
G = nx.cycle_graph(5)
pytest.raises(nx.NetworkXError, nx.flow_hierarchy, G)
def test_hierarchy_cycle():
G = nx.cycle_graph(5, create_using=nx.DiGraph())
assert nx.flow_hierarchy(G) == 0.0
def test_hierarchy_tree():
G = nx.full_rary_tree(2, 16, create_using=nx.DiGraph())
assert nx.flow_hierarchy(G) == 1.0
def test_hierarchy_1():
G = nx.DiGraph()
G.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 1), (3, 4), (0, 4)])
assert nx.flow_hierarchy(G) == 0.5
def test_hierarchy_weight():
G = nx.DiGraph()
G.add_edges_from(
[
(0, 1, {"weight": 0.3}),
(1, 2, {"weight": 0.1}),
(2, 3, {"weight": 0.1}),
(3, 1, {"weight": 0.1}),
(3, 4, {"weight": 0.3}),
(0, 4, {"weight": 0.3}),
]
)
assert nx.flow_hierarchy(G, weight="weight") == 0.75
@pytest.mark.parametrize("n", (0, 1, 3))
def test_hierarchy_empty_graph(n):
G = nx.empty_graph(n, create_using=nx.DiGraph)
with pytest.raises(nx.NetworkXError, match=".*not applicable to empty graphs"):
nx.flow_hierarchy(G)
@@ -0,0 +1,24 @@
import networkx as nx
def test_2d_grid_graph():
# FC article claims 2d grid graph of size n is (3,3)-connected
# and (5,9)-connected, but I don't think it is (5,9)-connected
G = nx.grid_2d_graph(8, 8, periodic=True)
assert nx.is_kl_connected(G, 3, 3)
assert not nx.is_kl_connected(G, 5, 9)
(H, graphOK) = nx.kl_connected_subgraph(G, 5, 9, same_as_graph=True)
assert not graphOK
def test_small_graph():
G = nx.Graph()
G.add_edge(1, 2)
G.add_edge(1, 3)
G.add_edge(2, 3)
assert nx.is_kl_connected(G, 2, 2)
H = nx.kl_connected_subgraph(G, 2, 2)
(H, graphOK) = nx.kl_connected_subgraph(
G, 2, 2, low_memory=True, same_as_graph=True
)
assert graphOK
@@ -0,0 +1,26 @@
"""Unit tests for the :mod:`networkx.algorithms.isolates` module."""
import networkx as nx
def test_is_isolate():
G = nx.Graph()
G.add_edge(0, 1)
G.add_node(2)
assert not nx.is_isolate(G, 0)
assert not nx.is_isolate(G, 1)
assert nx.is_isolate(G, 2)
def test_isolates():
G = nx.Graph()
G.add_edge(0, 1)
G.add_nodes_from([2, 3])
assert sorted(nx.isolates(G)) == [2, 3]
def test_number_of_isolates():
G = nx.Graph()
G.add_edge(0, 1)
G.add_nodes_from([2, 3])
assert nx.number_of_isolates(G) == 2
@@ -0,0 +1,615 @@
import math
from functools import partial
import pytest
import networkx as nx
def _test_func(G, ebunch, expected, predict_func, **kwargs):
result = predict_func(G, ebunch, **kwargs)
exp_dict = {tuple(sorted([u, v])): score for u, v, score in expected}
res_dict = {tuple(sorted([u, v])): score for u, v, score in result}
assert len(exp_dict) == len(res_dict)
for p in exp_dict:
assert exp_dict[p] == pytest.approx(res_dict[p], abs=1e-7)
class TestResourceAllocationIndex:
@classmethod
def setup_class(cls):
cls.func = staticmethod(nx.resource_allocation_index)
cls.test = staticmethod(partial(_test_func, predict_func=cls.func))
def test_K5(self):
G = nx.complete_graph(5)
self.test(G, [(0, 1)], [(0, 1, 0.75)])
def test_P3(self):
G = nx.path_graph(3)
self.test(G, [(0, 2)], [(0, 2, 0.5)])
def test_S4(self):
G = nx.star_graph(4)
self.test(G, [(1, 2)], [(1, 2, 0.25)])
@pytest.mark.parametrize("graph_type", (nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph))
def test_notimplemented(self, graph_type):
G = graph_type([(0, 1), (1, 2)])
with pytest.raises(nx.NetworkXNotImplemented):
self.func(G, [(0, 2)])
def test_node_not_found(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (2, 3)])
with pytest.raises(nx.NodeNotFound):
self.func(G, [(0, 4)])
def test_no_common_neighbor(self):
G = nx.Graph()
G.add_nodes_from([0, 1])
self.test(G, [(0, 1)], [(0, 1, 0)])
def test_equal_nodes(self):
G = nx.complete_graph(4)
self.test(G, [(0, 0)], [(0, 0, 1)])
def test_all_nonexistent_edges(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (2, 3)])
self.test(G, None, [(0, 3, 0.5), (1, 2, 0.5), (1, 3, 0)])
class TestJaccardCoefficient:
@classmethod
def setup_class(cls):
cls.func = staticmethod(nx.jaccard_coefficient)
cls.test = staticmethod(partial(_test_func, predict_func=cls.func))
def test_K5(self):
G = nx.complete_graph(5)
self.test(G, [(0, 1)], [(0, 1, 0.6)])
def test_P4(self):
G = nx.path_graph(4)
self.test(G, [(0, 2)], [(0, 2, 0.5)])
@pytest.mark.parametrize("graph_type", (nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph))
def test_notimplemented(self, graph_type):
G = graph_type([(0, 1), (1, 2)])
with pytest.raises(nx.NetworkXNotImplemented):
self.func(G, [(0, 2)])
def test_node_not_found(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (2, 3)])
with pytest.raises(nx.NodeNotFound):
self.func(G, [(0, 4)])
def test_no_common_neighbor(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (2, 3)])
self.test(G, [(0, 2)], [(0, 2, 0)])
def test_isolated_nodes(self):
G = nx.Graph()
G.add_nodes_from([0, 1])
self.test(G, [(0, 1)], [(0, 1, 0)])
def test_all_nonexistent_edges(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (2, 3)])
self.test(G, None, [(0, 3, 0.5), (1, 2, 0.5), (1, 3, 0)])
class TestAdamicAdarIndex:
@classmethod
def setup_class(cls):
cls.func = staticmethod(nx.adamic_adar_index)
cls.test = staticmethod(partial(_test_func, predict_func=cls.func))
def test_K5(self):
G = nx.complete_graph(5)
self.test(G, [(0, 1)], [(0, 1, 3 / math.log(4))])
def test_P3(self):
G = nx.path_graph(3)
self.test(G, [(0, 2)], [(0, 2, 1 / math.log(2))])
def test_S4(self):
G = nx.star_graph(4)
self.test(G, [(1, 2)], [(1, 2, 1 / math.log(4))])
@pytest.mark.parametrize("graph_type", (nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph))
def test_notimplemented(self, graph_type):
with pytest.raises(nx.NetworkXNotImplemented):
G = graph_type([(0, 1), (1, 2)])
self.func(G, [(0, 2)])
def test_node_not_found(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (2, 3)])
with pytest.raises(nx.NodeNotFound):
self.func(G, [(0, 4)])
def test_no_common_neighbor(self):
G = nx.Graph()
G.add_nodes_from([0, 1])
self.test(G, [(0, 1)], [(0, 1, 0)])
def test_equal_nodes(self):
G = nx.complete_graph(4)
self.test(G, [(0, 0)], [(0, 0, 3 / math.log(3))])
def test_all_nonexistent_edges(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (2, 3)])
self.test(
G, None, [(0, 3, 1 / math.log(2)), (1, 2, 1 / math.log(2)), (1, 3, 0)]
)
class TestCommonNeighborCentrality:
@classmethod
def setup_class(cls):
cls.func = staticmethod(nx.common_neighbor_centrality)
cls.test = staticmethod(partial(_test_func, predict_func=cls.func))
def test_K5(self):
G = nx.complete_graph(5)
self.test(G, [(0, 1)], [(0, 1, 3.0)], alpha=1)
self.test(G, [(0, 1)], [(0, 1, 5.0)], alpha=0)
def test_P3(self):
G = nx.path_graph(3)
self.test(G, [(0, 2)], [(0, 2, 1.25)], alpha=0.5)
def test_S4(self):
G = nx.star_graph(4)
self.test(G, [(1, 2)], [(1, 2, 1.75)], alpha=0.5)
@pytest.mark.parametrize("graph_type", (nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph))
def test_notimplemented(self, graph_type):
G = graph_type([(0, 1), (1, 2)])
with pytest.raises(nx.NetworkXNotImplemented):
self.func(G, [(0, 2)])
def test_node_u_not_found(self):
G = nx.Graph()
G.add_edges_from([(1, 3), (2, 3)])
with pytest.raises(nx.NodeNotFound):
self.func(G, [(0, 1)])
def test_node_v_not_found(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (2, 3)])
with pytest.raises(nx.NodeNotFound):
self.func(G, [(0, 4)])
def test_no_common_neighbor(self):
G = nx.Graph()
G.add_nodes_from([0, 1])
self.test(G, [(0, 1)], [(0, 1, 0)])
def test_equal_nodes(self):
G = nx.complete_graph(4)
with pytest.raises(nx.NetworkXAlgorithmError):
self.test(G, [(0, 0)], [])
def test_equal_nodes_with_alpha_one_raises_error(self):
G = nx.complete_graph(4)
with pytest.raises(nx.NetworkXAlgorithmError):
self.test(G, [(0, 0)], [], alpha=1.0)
def test_all_nonexistent_edges(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (2, 3)])
self.test(G, None, [(0, 3, 1.5), (1, 2, 1.5), (1, 3, 2 / 3)], alpha=0.5)
class TestPreferentialAttachment:
@classmethod
def setup_class(cls):
cls.func = staticmethod(nx.preferential_attachment)
cls.test = staticmethod(partial(_test_func, predict_func=cls.func))
def test_K5(self):
G = nx.complete_graph(5)
self.test(G, [(0, 1)], [(0, 1, 16)])
def test_P3(self):
G = nx.path_graph(3)
self.test(G, [(0, 1)], [(0, 1, 2)])
def test_S4(self):
G = nx.star_graph(4)
self.test(G, [(0, 2)], [(0, 2, 4)])
@pytest.mark.parametrize("graph_type", (nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph))
def test_notimplemented(self, graph_type):
G = graph_type([(0, 1), (1, 2)])
with pytest.raises(nx.NetworkXNotImplemented):
self.func(G, [(0, 2)])
def test_node_not_found(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (2, 3)])
with pytest.raises(nx.NodeNotFound):
self.func(G, [(0, 4)])
def test_zero_degrees(self):
G = nx.Graph()
G.add_nodes_from([0, 1])
self.test(G, [(0, 1)], [(0, 1, 0)])
def test_all_nonexistent_edges(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (2, 3)])
self.test(G, None, [(0, 3, 2), (1, 2, 2), (1, 3, 1)])
class TestCNSoundarajanHopcroft:
@classmethod
def setup_class(cls):
cls.func = staticmethod(nx.cn_soundarajan_hopcroft)
cls.test = staticmethod(
partial(_test_func, predict_func=cls.func, community="community")
)
def test_K5(self):
G = nx.complete_graph(5)
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 0
G.nodes[2]["community"] = 0
G.nodes[3]["community"] = 0
G.nodes[4]["community"] = 1
self.test(G, [(0, 1)], [(0, 1, 5)])
def test_P3(self):
G = nx.path_graph(3)
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 1
G.nodes[2]["community"] = 0
self.test(G, [(0, 2)], [(0, 2, 1)])
def test_S4(self):
G = nx.star_graph(4)
G.nodes[0]["community"] = 1
G.nodes[1]["community"] = 1
G.nodes[2]["community"] = 1
G.nodes[3]["community"] = 0
G.nodes[4]["community"] = 0
self.test(G, [(1, 2)], [(1, 2, 2)])
@pytest.mark.parametrize("graph_type", (nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph))
def test_notimplemented(self, graph_type):
G = graph_type([(0, 1), (1, 2)])
G.add_nodes_from([0, 1, 2], community=0)
with pytest.raises(nx.NetworkXNotImplemented):
self.func(G, [(0, 2)])
def test_node_not_found(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (2, 3)])
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 1
G.nodes[2]["community"] = 0
G.nodes[3]["community"] = 0
with pytest.raises(nx.NodeNotFound):
self.func(G, [(0, 4)])
def test_no_common_neighbor(self):
G = nx.Graph()
G.add_nodes_from([0, 1])
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 0
self.test(G, [(0, 1)], [(0, 1, 0)])
def test_equal_nodes(self):
G = nx.complete_graph(3)
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 0
G.nodes[2]["community"] = 0
self.test(G, [(0, 0)], [(0, 0, 4)])
def test_different_community(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3)])
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 0
G.nodes[2]["community"] = 0
G.nodes[3]["community"] = 1
self.test(G, [(0, 3)], [(0, 3, 2)])
def test_no_community_information(self):
G = nx.complete_graph(5)
with pytest.raises(nx.NetworkXAlgorithmError):
list(self.func(G, [(0, 1)]))
def test_insufficient_community_information(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3)])
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 0
G.nodes[3]["community"] = 0
with pytest.raises(nx.NetworkXAlgorithmError):
list(self.func(G, [(0, 3)]))
def test_sufficient_community_information(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (1, 2), (1, 3), (2, 4), (3, 4), (4, 5)])
G.nodes[1]["community"] = 0
G.nodes[2]["community"] = 0
G.nodes[3]["community"] = 0
G.nodes[4]["community"] = 0
self.test(G, [(1, 4)], [(1, 4, 4)])
def test_custom_community_attribute_name(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3)])
G.nodes[0]["cmty"] = 0
G.nodes[1]["cmty"] = 0
G.nodes[2]["cmty"] = 0
G.nodes[3]["cmty"] = 1
self.test(G, [(0, 3)], [(0, 3, 2)], community="cmty")
def test_all_nonexistent_edges(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (2, 3)])
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 1
G.nodes[2]["community"] = 0
G.nodes[3]["community"] = 0
self.test(G, None, [(0, 3, 2), (1, 2, 1), (1, 3, 0)])
class TestRAIndexSoundarajanHopcroft:
@classmethod
def setup_class(cls):
cls.func = staticmethod(nx.ra_index_soundarajan_hopcroft)
cls.test = staticmethod(
partial(_test_func, predict_func=cls.func, community="community")
)
def test_K5(self):
G = nx.complete_graph(5)
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 0
G.nodes[2]["community"] = 0
G.nodes[3]["community"] = 0
G.nodes[4]["community"] = 1
self.test(G, [(0, 1)], [(0, 1, 0.5)])
def test_P3(self):
G = nx.path_graph(3)
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 1
G.nodes[2]["community"] = 0
self.test(G, [(0, 2)], [(0, 2, 0)])
def test_S4(self):
G = nx.star_graph(4)
G.nodes[0]["community"] = 1
G.nodes[1]["community"] = 1
G.nodes[2]["community"] = 1
G.nodes[3]["community"] = 0
G.nodes[4]["community"] = 0
self.test(G, [(1, 2)], [(1, 2, 0.25)])
@pytest.mark.parametrize("graph_type", (nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph))
def test_notimplemented(self, graph_type):
G = graph_type([(0, 1), (1, 2)])
G.add_nodes_from([0, 1, 2], community=0)
with pytest.raises(nx.NetworkXNotImplemented):
self.func(G, [(0, 2)])
def test_node_not_found(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (2, 3)])
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 1
G.nodes[2]["community"] = 0
G.nodes[3]["community"] = 0
with pytest.raises(nx.NodeNotFound):
self.func(G, [(0, 4)])
def test_no_common_neighbor(self):
G = nx.Graph()
G.add_nodes_from([0, 1])
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 0
self.test(G, [(0, 1)], [(0, 1, 0)])
def test_equal_nodes(self):
G = nx.complete_graph(3)
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 0
G.nodes[2]["community"] = 0
self.test(G, [(0, 0)], [(0, 0, 1)])
def test_different_community(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3)])
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 0
G.nodes[2]["community"] = 0
G.nodes[3]["community"] = 1
self.test(G, [(0, 3)], [(0, 3, 0)])
def test_no_community_information(self):
G = nx.complete_graph(5)
with pytest.raises(nx.NetworkXAlgorithmError):
list(self.func(G, [(0, 1)]))
def test_insufficient_community_information(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3)])
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 0
G.nodes[3]["community"] = 0
with pytest.raises(nx.NetworkXAlgorithmError):
list(self.func(G, [(0, 3)]))
def test_sufficient_community_information(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (1, 2), (1, 3), (2, 4), (3, 4), (4, 5)])
G.nodes[1]["community"] = 0
G.nodes[2]["community"] = 0
G.nodes[3]["community"] = 0
G.nodes[4]["community"] = 0
self.test(G, [(1, 4)], [(1, 4, 1)])
def test_custom_community_attribute_name(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3)])
G.nodes[0]["cmty"] = 0
G.nodes[1]["cmty"] = 0
G.nodes[2]["cmty"] = 0
G.nodes[3]["cmty"] = 1
self.test(G, [(0, 3)], [(0, 3, 0)], community="cmty")
def test_all_nonexistent_edges(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (2, 3)])
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 1
G.nodes[2]["community"] = 0
G.nodes[3]["community"] = 0
self.test(G, None, [(0, 3, 0.5), (1, 2, 0), (1, 3, 0)])
class TestWithinInterCluster:
@classmethod
def setup_class(cls):
cls.delta = 0.001
cls.func = staticmethod(nx.within_inter_cluster)
cls.test = staticmethod(
partial(
_test_func,
predict_func=cls.func,
delta=cls.delta,
community="community",
)
)
def test_K5(self):
G = nx.complete_graph(5)
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 0
G.nodes[2]["community"] = 0
G.nodes[3]["community"] = 0
G.nodes[4]["community"] = 1
self.test(G, [(0, 1)], [(0, 1, 2 / (1 + self.delta))])
def test_P3(self):
G = nx.path_graph(3)
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 1
G.nodes[2]["community"] = 0
self.test(G, [(0, 2)], [(0, 2, 0)])
def test_S4(self):
G = nx.star_graph(4)
G.nodes[0]["community"] = 1
G.nodes[1]["community"] = 1
G.nodes[2]["community"] = 1
G.nodes[3]["community"] = 0
G.nodes[4]["community"] = 0
self.test(G, [(1, 2)], [(1, 2, 1 / self.delta)])
@pytest.mark.parametrize("graph_type", (nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph))
def test_notimplemented(self, graph_type):
G = graph_type([(0, 1), (1, 2)])
G.add_nodes_from([0, 1, 2], community=0)
with pytest.raises(nx.NetworkXNotImplemented):
self.func(G, [(0, 2)])
def test_node_not_found(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (2, 3)])
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 1
G.nodes[2]["community"] = 0
G.nodes[3]["community"] = 0
with pytest.raises(nx.NodeNotFound):
self.func(G, [(0, 4)])
def test_no_common_neighbor(self):
G = nx.Graph()
G.add_nodes_from([0, 1])
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 0
self.test(G, [(0, 1)], [(0, 1, 0)])
def test_equal_nodes(self):
G = nx.complete_graph(3)
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 0
G.nodes[2]["community"] = 0
self.test(G, [(0, 0)], [(0, 0, 2 / self.delta)])
def test_different_community(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3)])
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 0
G.nodes[2]["community"] = 0
G.nodes[3]["community"] = 1
self.test(G, [(0, 3)], [(0, 3, 0)])
def test_no_inter_cluster_common_neighbor(self):
G = nx.complete_graph(4)
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 0
G.nodes[2]["community"] = 0
G.nodes[3]["community"] = 0
self.test(G, [(0, 3)], [(0, 3, 2 / self.delta)])
def test_no_community_information(self):
G = nx.complete_graph(5)
with pytest.raises(nx.NetworkXAlgorithmError):
list(self.func(G, [(0, 1)]))
def test_insufficient_community_information(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3)])
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 0
G.nodes[3]["community"] = 0
with pytest.raises(nx.NetworkXAlgorithmError):
list(self.func(G, [(0, 3)]))
def test_sufficient_community_information(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (1, 2), (1, 3), (2, 4), (3, 4), (4, 5)])
G.nodes[1]["community"] = 0
G.nodes[2]["community"] = 0
G.nodes[3]["community"] = 0
G.nodes[4]["community"] = 0
self.test(G, [(1, 4)], [(1, 4, 2 / self.delta)])
def test_invalid_delta(self):
G = nx.complete_graph(3)
G.add_nodes_from([0, 1, 2], community=0)
with pytest.raises(nx.NetworkXAlgorithmError):
self.func(G, [(0, 1)], 0)
with pytest.raises(nx.NetworkXAlgorithmError):
self.func(G, [(0, 1)], -0.5)
def test_custom_community_attribute_name(self):
G = nx.complete_graph(4)
G.nodes[0]["cmty"] = 0
G.nodes[1]["cmty"] = 0
G.nodes[2]["cmty"] = 0
G.nodes[3]["cmty"] = 0
self.test(G, [(0, 3)], [(0, 3, 2 / self.delta)], community="cmty")
def test_all_nonexistent_edges(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (2, 3)])
G.nodes[0]["community"] = 0
G.nodes[1]["community"] = 1
G.nodes[2]["community"] = 0
G.nodes[3]["community"] = 0
self.test(G, None, [(0, 3, 1 / self.delta), (1, 2, 0), (1, 3, 0)])
@@ -0,0 +1,459 @@
from itertools import chain, combinations, product
import pytest
import networkx as nx
tree_all_pairs_lca = nx.tree_all_pairs_lowest_common_ancestor
all_pairs_lca = nx.all_pairs_lowest_common_ancestor
def get_pair(dictionary, n1, n2):
if (n1, n2) in dictionary:
return dictionary[n1, n2]
else:
return dictionary[n2, n1]
class TestTreeLCA:
@classmethod
def setup_class(cls):
cls.DG = nx.DiGraph()
edges = [(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]
cls.DG.add_edges_from(edges)
cls.ans = dict(tree_all_pairs_lca(cls.DG, 0))
gold = {(n, n): n for n in cls.DG}
gold.update({(0, i): 0 for i in range(1, 7)})
gold.update(
{
(1, 2): 0,
(1, 3): 1,
(1, 4): 1,
(1, 5): 0,
(1, 6): 0,
(2, 3): 0,
(2, 4): 0,
(2, 5): 2,
(2, 6): 2,
(3, 4): 1,
(3, 5): 0,
(3, 6): 0,
(4, 5): 0,
(4, 6): 0,
(5, 6): 2,
}
)
cls.gold = gold
@staticmethod
def assert_has_same_pairs(d1, d2):
for a, b in ((min(pair), max(pair)) for pair in chain(d1, d2)):
assert get_pair(d1, a, b) == get_pair(d2, a, b)
def test_tree_all_pairs_lca_default_root(self):
assert dict(tree_all_pairs_lca(self.DG)) == self.ans
def test_tree_all_pairs_lca_return_subset(self):
test_pairs = [(0, 1), (0, 1), (1, 0)]
ans = dict(tree_all_pairs_lca(self.DG, 0, test_pairs))
assert (0, 1) in ans and (1, 0) in ans
assert len(ans) == 2
def test_tree_all_pairs_lca(self):
all_pairs = chain(combinations(self.DG, 2), ((node, node) for node in self.DG))
ans = dict(tree_all_pairs_lca(self.DG, 0, all_pairs))
self.assert_has_same_pairs(ans, self.ans)
def test_tree_all_pairs_gold_example(self):
ans = dict(tree_all_pairs_lca(self.DG))
self.assert_has_same_pairs(self.gold, ans)
def test_tree_all_pairs_lca_invalid_input(self):
empty_digraph = tree_all_pairs_lca(nx.DiGraph())
pytest.raises(nx.NetworkXPointlessConcept, list, empty_digraph)
bad_pairs_digraph = tree_all_pairs_lca(self.DG, pairs=[(-1, -2)])
pytest.raises(nx.NodeNotFound, list, bad_pairs_digraph)
def test_tree_all_pairs_lca_subtrees(self):
ans = dict(tree_all_pairs_lca(self.DG, 1))
gold = {
pair: lca
for (pair, lca) in self.gold.items()
if all(n in (1, 3, 4) for n in pair)
}
self.assert_has_same_pairs(gold, ans)
def test_tree_all_pairs_lca_disconnected_nodes(self):
G = nx.DiGraph()
G.add_node(1)
assert {(1, 1): 1} == dict(tree_all_pairs_lca(G))
G.add_node(0)
assert {(1, 1): 1} == dict(tree_all_pairs_lca(G, 1))
assert {(0, 0): 0} == dict(tree_all_pairs_lca(G, 0))
pytest.raises(nx.NetworkXError, list, tree_all_pairs_lca(G))
def test_tree_all_pairs_lca_error_if_input_not_tree(self):
# Cycle
G = nx.DiGraph([(1, 2), (2, 1)])
pytest.raises(nx.NetworkXError, list, tree_all_pairs_lca(G))
# DAG
G = nx.DiGraph([(0, 2), (1, 2)])
pytest.raises(nx.NetworkXError, list, tree_all_pairs_lca(G))
def test_tree_all_pairs_lca_generator(self):
pairs = iter([(0, 1), (0, 1), (1, 0)])
some_pairs = dict(tree_all_pairs_lca(self.DG, 0, pairs))
assert (0, 1) in some_pairs and (1, 0) in some_pairs
assert len(some_pairs) == 2
def test_tree_all_pairs_lca_nonexisting_pairs_exception(self):
lca = tree_all_pairs_lca(self.DG, 0, [(-1, -1)])
pytest.raises(nx.NodeNotFound, list, lca)
# check if node is None
lca = tree_all_pairs_lca(self.DG, None, [(-1, -1)])
pytest.raises(nx.NodeNotFound, list, lca)
def test_tree_all_pairs_lca_routine_bails_on_DAGs(self):
G = nx.DiGraph([(3, 4), (5, 4)])
pytest.raises(nx.NetworkXError, list, tree_all_pairs_lca(G))
def test_tree_all_pairs_lca_not_implemented(self):
NNI = nx.NetworkXNotImplemented
G = nx.Graph([(0, 1)])
with pytest.raises(NNI):
next(tree_all_pairs_lca(G))
with pytest.raises(NNI):
next(all_pairs_lca(G))
pytest.raises(NNI, nx.lowest_common_ancestor, G, 0, 1)
G = nx.MultiGraph([(0, 1)])
with pytest.raises(NNI):
next(tree_all_pairs_lca(G))
with pytest.raises(NNI):
next(all_pairs_lca(G))
pytest.raises(NNI, nx.lowest_common_ancestor, G, 0, 1)
def test_tree_all_pairs_lca_trees_without_LCAs(self):
G = nx.DiGraph()
G.add_node(3)
ans = list(tree_all_pairs_lca(G))
assert ans == [((3, 3), 3)]
class TestMultiTreeLCA(TestTreeLCA):
@classmethod
def setup_class(cls):
cls.DG = nx.MultiDiGraph()
edges = [(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]
cls.DG.add_edges_from(edges)
cls.ans = dict(tree_all_pairs_lca(cls.DG, 0))
# add multiedges
cls.DG.add_edges_from(edges)
gold = {(n, n): n for n in cls.DG}
gold.update({(0, i): 0 for i in range(1, 7)})
gold.update(
{
(1, 2): 0,
(1, 3): 1,
(1, 4): 1,
(1, 5): 0,
(1, 6): 0,
(2, 3): 0,
(2, 4): 0,
(2, 5): 2,
(2, 6): 2,
(3, 4): 1,
(3, 5): 0,
(3, 6): 0,
(4, 5): 0,
(4, 6): 0,
(5, 6): 2,
}
)
cls.gold = gold
class TestDAGLCA:
@classmethod
def setup_class(cls):
cls.DG = nx.DiGraph()
nx.add_path(cls.DG, (0, 1, 2, 3))
nx.add_path(cls.DG, (0, 4, 3))
nx.add_path(cls.DG, (0, 5, 6, 8, 3))
nx.add_path(cls.DG, (5, 7, 8))
cls.DG.add_edge(6, 2)
cls.DG.add_edge(7, 2)
cls.root_distance = nx.shortest_path_length(cls.DG, source=0)
cls.gold = {
(1, 1): 1,
(1, 2): 1,
(1, 3): 1,
(1, 4): 0,
(1, 5): 0,
(1, 6): 0,
(1, 7): 0,
(1, 8): 0,
(2, 2): 2,
(2, 3): 2,
(2, 4): 0,
(2, 5): 5,
(2, 6): 6,
(2, 7): 7,
(2, 8): 7,
(3, 3): 3,
(3, 4): 4,
(3, 5): 5,
(3, 6): 6,
(3, 7): 7,
(3, 8): 8,
(4, 4): 4,
(4, 5): 0,
(4, 6): 0,
(4, 7): 0,
(4, 8): 0,
(5, 5): 5,
(5, 6): 5,
(5, 7): 5,
(5, 8): 5,
(6, 6): 6,
(6, 7): 5,
(6, 8): 6,
(7, 7): 7,
(7, 8): 7,
(8, 8): 8,
}
cls.gold.update(((0, n), 0) for n in cls.DG)
def assert_lca_dicts_same(self, d1, d2, G=None):
"""Checks if d1 and d2 contain the same pairs and
have a node at the same distance from root for each.
If G is None use self.DG."""
if G is None:
G = self.DG
root_distance = self.root_distance
else:
roots = [n for n, deg in G.in_degree if deg == 0]
assert len(roots) == 1
root_distance = nx.shortest_path_length(G, source=roots[0])
for a, b in ((min(pair), max(pair)) for pair in chain(d1, d2)):
assert (
root_distance[get_pair(d1, a, b)] == root_distance[get_pair(d2, a, b)]
)
def test_all_pairs_lca_gold_example(self):
self.assert_lca_dicts_same(dict(all_pairs_lca(self.DG)), self.gold)
def test_all_pairs_lca_all_pairs_given(self):
all_pairs = list(product(self.DG.nodes(), self.DG.nodes()))
ans = all_pairs_lca(self.DG, pairs=all_pairs)
self.assert_lca_dicts_same(dict(ans), self.gold)
def test_all_pairs_lca_generator(self):
all_pairs = product(self.DG.nodes(), self.DG.nodes())
ans = all_pairs_lca(self.DG, pairs=all_pairs)
self.assert_lca_dicts_same(dict(ans), self.gold)
def test_all_pairs_lca_input_graph_with_two_roots(self):
G = self.DG.copy()
G.add_edge(9, 10)
G.add_edge(9, 4)
gold = self.gold.copy()
gold[9, 9] = 9
gold[9, 10] = 9
gold[9, 4] = 9
gold[9, 3] = 9
gold[10, 4] = 9
gold[10, 3] = 9
gold[10, 10] = 10
testing = dict(all_pairs_lca(G))
G.add_edge(-1, 9)
G.add_edge(-1, 0)
self.assert_lca_dicts_same(testing, gold, G)
def test_all_pairs_lca_nonexisting_pairs_exception(self):
pytest.raises(nx.NodeNotFound, all_pairs_lca, self.DG, [(-1, -1)])
def test_all_pairs_lca_pairs_without_lca(self):
G = self.DG.copy()
G.add_node(-1)
gen = all_pairs_lca(G, [(-1, -1), (-1, 0)])
assert dict(gen) == {(-1, -1): -1}
def test_all_pairs_lca_null_graph(self):
pytest.raises(nx.NetworkXPointlessConcept, all_pairs_lca, nx.DiGraph())
def test_all_pairs_lca_non_dags(self):
pytest.raises(nx.NetworkXError, all_pairs_lca, nx.DiGraph([(3, 4), (4, 3)]))
def test_all_pairs_lca_nonempty_graph_without_lca(self):
G = nx.DiGraph()
G.add_node(3)
ans = list(all_pairs_lca(G))
assert ans == [((3, 3), 3)]
def test_all_pairs_lca_bug_gh4942(self):
G = nx.DiGraph([(0, 2), (1, 2), (2, 3)])
ans = list(all_pairs_lca(G))
assert len(ans) == 9
def test_all_pairs_lca_default_kwarg(self):
G = nx.DiGraph([(0, 1), (2, 1)])
sentinel = object()
assert nx.lowest_common_ancestor(G, 0, 2, default=sentinel) is sentinel
def test_all_pairs_lca_identity(self):
G = nx.DiGraph()
G.add_node(3)
assert nx.lowest_common_ancestor(G, 3, 3) == 3
def test_all_pairs_lca_issue_4574(self):
G = nx.DiGraph()
G.add_nodes_from(range(17))
G.add_edges_from(
[
(2, 0),
(1, 2),
(3, 2),
(5, 2),
(8, 2),
(11, 2),
(4, 5),
(6, 5),
(7, 8),
(10, 8),
(13, 11),
(14, 11),
(15, 11),
(9, 10),
(12, 13),
(16, 15),
]
)
assert nx.lowest_common_ancestor(G, 7, 9) is None
def test_all_pairs_lca_one_pair_gh4942(self):
G = nx.DiGraph()
# Note: order edge addition is critical to the test
G.add_edge(0, 1)
G.add_edge(2, 0)
G.add_edge(2, 3)
G.add_edge(4, 0)
G.add_edge(5, 2)
assert nx.lowest_common_ancestor(G, 1, 3) == 2
class TestMultiDiGraph_DAGLCA(TestDAGLCA):
@classmethod
def setup_class(cls):
cls.DG = nx.MultiDiGraph()
nx.add_path(cls.DG, (0, 1, 2, 3))
# add multiedges
nx.add_path(cls.DG, (0, 1, 2, 3))
nx.add_path(cls.DG, (0, 4, 3))
nx.add_path(cls.DG, (0, 5, 6, 8, 3))
nx.add_path(cls.DG, (5, 7, 8))
cls.DG.add_edge(6, 2)
cls.DG.add_edge(7, 2)
cls.root_distance = nx.shortest_path_length(cls.DG, source=0)
cls.gold = {
(1, 1): 1,
(1, 2): 1,
(1, 3): 1,
(1, 4): 0,
(1, 5): 0,
(1, 6): 0,
(1, 7): 0,
(1, 8): 0,
(2, 2): 2,
(2, 3): 2,
(2, 4): 0,
(2, 5): 5,
(2, 6): 6,
(2, 7): 7,
(2, 8): 7,
(3, 3): 3,
(3, 4): 4,
(3, 5): 5,
(3, 6): 6,
(3, 7): 7,
(3, 8): 8,
(4, 4): 4,
(4, 5): 0,
(4, 6): 0,
(4, 7): 0,
(4, 8): 0,
(5, 5): 5,
(5, 6): 5,
(5, 7): 5,
(5, 8): 5,
(6, 6): 6,
(6, 7): 5,
(6, 8): 6,
(7, 7): 7,
(7, 8): 7,
(8, 8): 8,
}
cls.gold.update(((0, n), 0) for n in cls.DG)
def test_all_pairs_lca_self_ancestors():
"""Self-ancestors should always be the node itself, i.e. lca of (0, 0) is 0.
See gh-4458."""
# DAG for test - note order of node/edge addition is relevant
G = nx.DiGraph()
G.add_nodes_from(range(5))
G.add_edges_from([(1, 0), (2, 0), (3, 2), (4, 1), (4, 3)])
ap_lca = nx.all_pairs_lowest_common_ancestor
assert all(u == v == a for (u, v), a in ap_lca(G) if u == v)
MG = nx.MultiDiGraph(G)
assert all(u == v == a for (u, v), a in ap_lca(MG) if u == v)
MG.add_edges_from([(1, 0), (2, 0)])
assert all(u == v == a for (u, v), a in ap_lca(MG) if u == v)
def test_lca_on_null_graph():
G = nx.null_graph(create_using=nx.DiGraph)
with pytest.raises(
nx.NetworkXPointlessConcept, match="LCA meaningless on null graphs"
):
nx.lowest_common_ancestor(G, 0, 0)
def test_lca_on_cycle_graph():
G = nx.cycle_graph(6, create_using=nx.DiGraph)
with pytest.raises(
nx.NetworkXError, match="LCA only defined on directed acyclic graphs"
):
nx.lowest_common_ancestor(G, 0, 3)
def test_lca_multiple_valid_solutions():
G = nx.DiGraph()
G.add_nodes_from(range(4))
G.add_edges_from([(2, 0), (3, 0), (2, 1), (3, 1)])
assert nx.lowest_common_ancestor(G, 0, 1) in {2, 3}
def test_lca_dont_rely_on_single_successor():
# Nodes 0 and 1 have nodes 2 and 3 as immediate ancestors,
# and node 2 also has node 3 as an immediate ancestor.
G = nx.DiGraph()
G.add_nodes_from(range(4))
G.add_edges_from([(2, 0), (2, 1), (3, 1), (3, 0), (3, 2)])
assert nx.lowest_common_ancestor(G, 0, 1) == 2
@@ -0,0 +1,556 @@
import math
from itertools import permutations
import pytest
import networkx as nx
from networkx.utils import edges_equal
@pytest.mark.parametrize(
"fn", (nx.is_matching, nx.is_maximal_matching, nx.is_perfect_matching)
)
@pytest.mark.parametrize(
"edgeset",
(
{(0, 5)}, # Single edge, node not in G
{(5, 0)}, # for both edge orders
{(0, 5), (2, 3)}, # node not in G, but other edge is valid matching
{(5, 5), (2, 3)}, # Self-loop hits node not in G validation first
),
)
def test_is_matching_node_not_in_G(fn, edgeset):
"""All is_*matching functions have consistent exception message for node
not in G."""
G = nx.path_graph(4)
with pytest.raises(nx.NetworkXError, match="matching.*with node not in G"):
fn(G, edgeset)
@pytest.mark.parametrize(
"fn", (nx.is_matching, nx.is_maximal_matching, nx.is_perfect_matching)
)
@pytest.mark.parametrize(
"edgeset",
(
{(0, 1, 2), (2, 3)}, # 3-tuple
{(0,), (2, 3)}, # 1-tuple
),
)
def test_is_matching_invalid_edge(fn, edgeset):
"""All is_*matching functions have consistent exception message for invalid
edges in matching."""
G = nx.path_graph(4)
with pytest.raises(nx.NetworkXError, match=".*non-2-tuple edge.*"):
fn(G, edgeset)
@pytest.mark.parametrize("graph_type", (nx.MultiGraph, nx.DiGraph, nx.MultiDiGraph))
@pytest.mark.parametrize(
"fn", (nx.max_weight_matching, nx.min_weight_matching, nx.maximal_matching)
)
def test_wrong_graph_type(fn, graph_type):
G = graph_type()
with pytest.raises(nx.NetworkXNotImplemented):
fn(G)
class TestMaxWeightMatching:
"""Unit tests for the
:func:`~networkx.algorithms.matching.max_weight_matching` function.
"""
def test_trivial1(self):
"""Empty graph"""
G = nx.Graph()
assert nx.max_weight_matching(G) == set()
assert nx.min_weight_matching(G) == set()
def test_selfloop(self):
G = nx.Graph()
G.add_edge(0, 0, weight=100)
assert nx.max_weight_matching(G) == set()
assert nx.min_weight_matching(G) == set()
def test_single_edge(self):
G = nx.Graph()
G.add_edge(0, 1)
assert edges_equal(nx.max_weight_matching(G), {(0, 1)})
assert edges_equal(nx.min_weight_matching(G), {(0, 1)})
def test_two_path(self):
G = nx.Graph()
G.add_edge("one", "two", weight=10)
G.add_edge("two", "three", weight=11)
assert edges_equal(nx.max_weight_matching(G), {("two", "three")})
assert edges_equal(nx.min_weight_matching(G), {("one", "two")})
def test_path(self):
G = nx.Graph()
G.add_edge(1, 2, weight=5)
G.add_edge(2, 3, weight=11)
G.add_edge(3, 4, weight=5)
assert edges_equal(nx.max_weight_matching(G), {(2, 3)})
assert edges_equal(nx.max_weight_matching(G, weight=None), {(1, 2), (3, 4)})
assert edges_equal(nx.min_weight_matching(G), {(1, 2), (3, 4)})
assert edges_equal(nx.min_weight_matching(G, weight=None), {(1, 2), (3, 4)})
def test_square(self):
G = nx.Graph()
G.add_edge(1, 4, weight=2)
G.add_edge(2, 3, weight=2)
G.add_edge(1, 2, weight=1)
G.add_edge(3, 4, weight=4)
assert edges_equal(nx.max_weight_matching(G), {(1, 2), (3, 4)})
assert edges_equal(nx.min_weight_matching(G), {(1, 4), (2, 3)})
def test_edge_attribute_name(self):
G = nx.Graph()
G.add_edge("one", "two", weight=10, abcd=11)
G.add_edge("two", "three", weight=11, abcd=10)
assert edges_equal(nx.max_weight_matching(G, weight="abcd"), {("one", "two")})
assert edges_equal(nx.min_weight_matching(G, weight="abcd"), {("two", "three")})
def test_floating_point_weights(self):
G = nx.Graph()
G.add_edge(1, 2, weight=math.pi)
G.add_edge(2, 3, weight=math.exp(1))
G.add_edge(1, 3, weight=3.0)
G.add_edge(1, 4, weight=math.sqrt(2.0))
assert edges_equal(nx.max_weight_matching(G), {(1, 4), (2, 3)})
assert edges_equal(nx.min_weight_matching(G), {(1, 4), (2, 3)})
def test_negative_weights(self):
G = nx.Graph()
G.add_edge(1, 2, weight=2)
G.add_edge(1, 3, weight=-2)
G.add_edge(2, 3, weight=1)
G.add_edge(2, 4, weight=-1)
G.add_edge(3, 4, weight=-6)
assert edges_equal(nx.max_weight_matching(G), {(1, 2)})
assert edges_equal(
nx.max_weight_matching(G, maxcardinality=True), {(1, 3), (2, 4)}
)
assert edges_equal(nx.min_weight_matching(G), {(1, 2), (3, 4)})
def test_s_blossom(self):
"""Create S-blossom and use it for augmentation:"""
G = nx.Graph()
G.add_weighted_edges_from([(1, 2, 8), (1, 3, 9), (2, 3, 10), (3, 4, 7)])
answer = {(1, 2), (3, 4)}
assert edges_equal(nx.max_weight_matching(G), answer)
assert edges_equal(nx.min_weight_matching(G), answer)
G.add_weighted_edges_from([(1, 6, 5), (4, 5, 6)])
answer = {(1, 6), (2, 3), (4, 5)}
assert edges_equal(nx.max_weight_matching(G), answer)
assert edges_equal(nx.min_weight_matching(G), answer)
def test_s_t_blossom(self):
"""Create S-blossom, relabel as T-blossom, use for augmentation:"""
G = nx.Graph()
G.add_weighted_edges_from(
[(1, 2, 9), (1, 3, 8), (2, 3, 10), (1, 4, 5), (4, 5, 4), (1, 6, 3)]
)
answer = {(1, 6), (2, 3), (4, 5)}
assert edges_equal(nx.max_weight_matching(G), answer)
assert edges_equal(nx.min_weight_matching(G), answer)
G.add_edge(4, 5, weight=3)
G.add_edge(1, 6, weight=4)
assert edges_equal(nx.max_weight_matching(G), answer)
assert edges_equal(nx.min_weight_matching(G), answer)
G.remove_edge(1, 6)
G.add_edge(3, 6, weight=4)
answer = {(1, 2), (3, 6), (4, 5)}
assert edges_equal(nx.max_weight_matching(G), answer)
assert edges_equal(nx.min_weight_matching(G), answer)
def test_nested_s_blossom(self):
"""Create nested S-blossom, use for augmentation:"""
G = nx.Graph()
G.add_weighted_edges_from(
[
(1, 2, 9),
(1, 3, 9),
(2, 3, 10),
(2, 4, 8),
(3, 5, 8),
(4, 5, 10),
(5, 6, 6),
]
)
expected_edgeset = {(1, 3), (2, 4), (5, 6)}
expected = {frozenset(e) for e in expected_edgeset}
answer = {frozenset(e) for e in nx.max_weight_matching(G)}
assert answer == expected
answer = {frozenset(e) for e in nx.min_weight_matching(G)}
assert answer == expected
def test_nested_s_blossom_relabel(self):
"""Create S-blossom, relabel as S, include in nested S-blossom:"""
G = nx.Graph()
G.add_weighted_edges_from(
[
(1, 2, 10),
(1, 7, 10),
(2, 3, 12),
(3, 4, 20),
(3, 5, 20),
(4, 5, 25),
(5, 6, 10),
(6, 7, 10),
(7, 8, 8),
]
)
answer = {(1, 2), (3, 4), (5, 6), (7, 8)}
assert edges_equal(nx.max_weight_matching(G), answer)
assert edges_equal(nx.min_weight_matching(G), answer)
def test_nested_s_blossom_expand(self):
"""Create nested S-blossom, augment, expand recursively:"""
G = nx.Graph()
G.add_weighted_edges_from(
[
(1, 2, 8),
(1, 3, 8),
(2, 3, 10),
(2, 4, 12),
(3, 5, 12),
(4, 5, 14),
(4, 6, 12),
(5, 7, 12),
(6, 7, 14),
(7, 8, 12),
]
)
answer = {(1, 2), (3, 5), (4, 6), (7, 8)}
assert edges_equal(nx.max_weight_matching(G), answer)
assert edges_equal(nx.min_weight_matching(G), answer)
def test_s_blossom_relabel_expand(self):
"""Create S-blossom, relabel as T, expand:"""
G = nx.Graph()
G.add_weighted_edges_from(
[
(1, 2, 23),
(1, 5, 22),
(1, 6, 15),
(2, 3, 25),
(3, 4, 22),
(4, 5, 25),
(4, 8, 14),
(5, 7, 13),
]
)
answer = {(1, 6), (2, 3), (4, 8), (5, 7)}
assert edges_equal(nx.max_weight_matching(G), answer)
assert edges_equal(nx.min_weight_matching(G), answer)
def test_nested_s_blossom_relabel_expand(self):
"""Create nested S-blossom, relabel as T, expand:"""
G = nx.Graph()
G.add_weighted_edges_from(
[
(1, 2, 19),
(1, 3, 20),
(1, 8, 8),
(2, 3, 25),
(2, 4, 18),
(3, 5, 18),
(4, 5, 13),
(4, 7, 7),
(5, 6, 7),
]
)
answer = {(1, 8), (2, 3), (4, 7), (5, 6)}
assert edges_equal(nx.max_weight_matching(G), answer)
assert edges_equal(nx.min_weight_matching(G), answer)
def test_nasty_blossom1(self):
"""Create blossom, relabel as T in more than one way, expand,
augment:
"""
G = nx.Graph()
G.add_weighted_edges_from(
[
(1, 2, 45),
(1, 5, 45),
(2, 3, 50),
(3, 4, 45),
(4, 5, 50),
(1, 6, 30),
(3, 9, 35),
(4, 8, 35),
(5, 7, 26),
(9, 10, 5),
]
)
answer = {(1, 6), (2, 3), (4, 8), (5, 7), (9, 10)}
assert edges_equal(nx.max_weight_matching(G), answer)
assert edges_equal(nx.min_weight_matching(G), answer)
def test_nasty_blossom2(self):
"""Again but slightly different:"""
G = nx.Graph()
G.add_weighted_edges_from(
[
(1, 2, 45),
(1, 5, 45),
(2, 3, 50),
(3, 4, 45),
(4, 5, 50),
(1, 6, 30),
(3, 9, 35),
(4, 8, 26),
(5, 7, 40),
(9, 10, 5),
]
)
answer = {(1, 6), (2, 3), (4, 8), (5, 7), (9, 10)}
assert edges_equal(nx.max_weight_matching(G), answer)
assert edges_equal(nx.min_weight_matching(G), answer)
def test_nasty_blossom_least_slack(self):
"""Create blossom, relabel as T, expand such that a new
least-slack S-to-free dge is produced, augment:
"""
G = nx.Graph()
G.add_weighted_edges_from(
[
(1, 2, 45),
(1, 5, 45),
(2, 3, 50),
(3, 4, 45),
(4, 5, 50),
(1, 6, 30),
(3, 9, 35),
(4, 8, 28),
(5, 7, 26),
(9, 10, 5),
]
)
answer = {(1, 6), (2, 3), (4, 8), (5, 7), (9, 10)}
assert edges_equal(nx.max_weight_matching(G), answer)
assert edges_equal(nx.min_weight_matching(G), answer)
def test_nasty_blossom_augmenting(self):
"""Create nested blossom, relabel as T in more than one way"""
# expand outer blossom such that inner blossom ends up on an
# augmenting path:
G = nx.Graph()
G.add_weighted_edges_from(
[
(1, 2, 45),
(1, 7, 45),
(2, 3, 50),
(3, 4, 45),
(4, 5, 95),
(4, 6, 94),
(5, 6, 94),
(6, 7, 50),
(1, 8, 30),
(3, 11, 35),
(5, 9, 36),
(7, 10, 26),
(11, 12, 5),
]
)
answer = {(1, 8), (2, 3), (4, 6), (5, 9), (7, 10), (11, 12)}
assert edges_equal(nx.max_weight_matching(G), answer)
assert edges_equal(nx.min_weight_matching(G), answer)
def test_nasty_blossom_expand_recursively(self):
"""Create nested S-blossom, relabel as S, expand recursively:"""
G = nx.Graph()
G.add_weighted_edges_from(
[
(1, 2, 40),
(1, 3, 40),
(2, 3, 60),
(2, 4, 55),
(3, 5, 55),
(4, 5, 50),
(1, 8, 15),
(5, 7, 30),
(7, 6, 10),
(8, 10, 10),
(4, 9, 30),
]
)
answer = {(1, 2), (3, 5), (4, 9), (6, 7), (8, 10)}
assert edges_equal(nx.max_weight_matching(G), answer)
assert edges_equal(nx.min_weight_matching(G), answer)
def test_min_weight_matching_max_cardinality(self):
G = nx.Graph()
G.add_weighted_edges_from([(1, 2, 1000), (2, 3, 2), (3, 4, 3000)])
# The minimum-weight maximal matching is {(2, 3)}; the minimum-weight
# maximum-cardinality matching is {(1, 2), (3, 4)}. See gh-8062.
answer = {(1, 2), (3, 4)}
assert edges_equal(nx.min_weight_matching(G), answer)
class TestIsMatching:
"""Unit tests for the
:func:`~networkx.algorithms.matching.is_matching` function.
"""
def test_dict(self):
G = nx.path_graph(4)
assert nx.is_matching(G, {0: 1, 1: 0, 2: 3, 3: 2})
def test_empty_matching(self):
G = nx.path_graph(4)
assert nx.is_matching(G, set())
def test_single_edge(self):
G = nx.path_graph(4)
assert nx.is_matching(G, {(1, 2)})
def test_edge_order(self):
G = nx.path_graph(4)
assert nx.is_matching(G, {(0, 1), (2, 3)})
assert nx.is_matching(G, {(1, 0), (2, 3)})
assert nx.is_matching(G, {(0, 1), (3, 2)})
assert nx.is_matching(G, {(1, 0), (3, 2)})
def test_valid_matching(self):
G = nx.path_graph(4)
assert nx.is_matching(G, {(0, 1), (2, 3)})
def test_selfloops(self):
G = nx.path_graph(4)
# selfloop edge not in G
assert not nx.is_matching(G, {(0, 0), (1, 2), (2, 3)})
# selfloop edge in G
G.add_edge(0, 0)
assert not nx.is_matching(G, {(0, 0), (1, 2)})
def test_invalid_matching(self):
G = nx.path_graph(4)
assert not nx.is_matching(G, {(0, 1), (1, 2), (2, 3)})
def test_invalid_edge(self):
G = nx.path_graph(4)
assert not nx.is_matching(G, {(0, 3), (1, 2)})
G = nx.DiGraph(G.edges)
assert nx.is_matching(G, {(0, 1)})
assert not nx.is_matching(G, {(1, 0)})
class TestIsMaximalMatching:
"""Unit tests for the
:func:`~networkx.algorithms.matching.is_maximal_matching` function.
"""
def test_dict(self):
G = nx.path_graph(4)
assert nx.is_maximal_matching(G, {0: 1, 1: 0, 2: 3, 3: 2})
def test_valid(self):
G = nx.path_graph(4)
assert nx.is_maximal_matching(G, {(0, 1), (2, 3)})
def test_not_matching(self):
G = nx.path_graph(4)
assert not nx.is_maximal_matching(G, {(0, 1), (1, 2), (2, 3)})
assert not nx.is_maximal_matching(G, {(0, 3)})
G.add_edge(0, 0)
assert not nx.is_maximal_matching(G, {(0, 0)})
def test_not_maximal(self):
G = nx.path_graph(4)
assert not nx.is_maximal_matching(G, {(0, 1)})
class TestIsPerfectMatching:
"""Unit tests for the
:func:`~networkx.algorithms.matching.is_perfect_matching` function.
"""
def test_dict(self):
G = nx.path_graph(4)
assert nx.is_perfect_matching(G, {0: 1, 1: 0, 2: 3, 3: 2})
def test_valid(self):
G = nx.path_graph(4)
assert nx.is_perfect_matching(G, {(0, 1), (2, 3)})
def test_valid_not_path(self):
G = nx.cycle_graph(4)
G.add_edge(0, 4)
G.add_edge(1, 4)
G.add_edge(5, 2)
assert nx.is_perfect_matching(G, {(1, 4), (0, 3), (5, 2)})
def test_selfloops(self):
G = nx.path_graph(4)
# selfloop edge not in G
assert not nx.is_perfect_matching(G, {(0, 0), (1, 2), (2, 3)})
# selfloop edge in G
G.add_edge(0, 0)
assert not nx.is_perfect_matching(G, {(0, 0), (1, 2)})
def test_not_matching(self):
G = nx.path_graph(4)
assert not nx.is_perfect_matching(G, {(0, 3)})
assert not nx.is_perfect_matching(G, {(0, 1), (1, 2), (2, 3)})
def test_maximal_but_not_perfect(self):
G = nx.cycle_graph(4)
G.add_edge(0, 4)
G.add_edge(1, 4)
assert not nx.is_perfect_matching(G, {(1, 4), (0, 3)})
class TestMaximalMatching:
"""Unit tests for the
:func:`~networkx.algorithms.matching.maximal_matching`.
"""
def test_valid_matching(self):
edges = [(1, 2), (1, 5), (2, 3), (2, 5), (3, 4), (3, 6), (5, 6)]
G = nx.Graph(edges)
matching = nx.maximal_matching(G)
assert nx.is_maximal_matching(G, matching)
def test_single_edge_matching(self):
# In the star graph, any maximal matching has just one edge.
G = nx.star_graph(5)
matching = nx.maximal_matching(G)
assert 1 == len(matching)
assert nx.is_maximal_matching(G, matching)
def test_self_loops(self):
# Create the path graph with two self-loops.
G = nx.path_graph(3)
G.add_edges_from([(0, 0), (1, 1)])
matching = nx.maximal_matching(G)
assert len(matching) == 1
# The matching should never include self-loops.
assert not any(u == v for u, v in matching)
assert nx.is_maximal_matching(G, matching)
def test_ordering(self):
"""Tests that a maximal matching is computed correctly
regardless of the order in which nodes are added to the graph.
"""
for nodes in permutations(range(3)):
G = nx.Graph()
G.add_nodes_from(nodes)
G.add_edges_from([(0, 1), (0, 2)])
matching = nx.maximal_matching(G)
assert len(matching) == 1
assert nx.is_maximal_matching(G, matching)
@@ -0,0 +1,179 @@
"""Maximum weight clique test suite."""
import pytest
import networkx as nx
class TestMaximumWeightClique:
def test_basic_cases(self):
def check_basic_case(graph_func, expected_weight, weight_accessor):
graph = graph_func()
clique, weight = nx.algorithms.max_weight_clique(graph, weight_accessor)
assert verify_clique(
graph, clique, weight, expected_weight, weight_accessor
)
for graph_func, (expected_weight, expected_size) in TEST_CASES.items():
check_basic_case(graph_func, expected_weight, "weight")
check_basic_case(graph_func, expected_size, None)
def test_key_error(self):
graph = two_node_graph()
with pytest.raises(KeyError):
nx.algorithms.max_weight_clique(graph, "nonexistent-key")
def test_error_on_non_integer_weight(self):
graph = two_node_graph()
graph.nodes[2]["weight"] = 1.5
with pytest.raises(ValueError):
nx.algorithms.max_weight_clique(graph)
def test_unaffected_by_self_loops(self):
graph = two_node_graph()
graph.add_edge(1, 1)
graph.add_edge(2, 2)
clique, weight = nx.algorithms.max_weight_clique(graph, "weight")
assert verify_clique(graph, clique, weight, 30, "weight")
graph = three_node_independent_set()
graph.add_edge(1, 1)
clique, weight = nx.algorithms.max_weight_clique(graph, "weight")
assert verify_clique(graph, clique, weight, 20, "weight")
def test_30_node_prob(self):
G = nx.Graph()
G.add_nodes_from(range(1, 31))
for i in range(1, 31):
G.nodes[i]["weight"] = i + 1
# fmt: off
G.add_edges_from(
[
(1, 12), (1, 13), (1, 15), (1, 16), (1, 18), (1, 19), (1, 20),
(1, 23), (1, 26), (1, 28), (1, 29), (1, 30), (2, 3), (2, 4),
(2, 5), (2, 8), (2, 9), (2, 10), (2, 14), (2, 17), (2, 18),
(2, 21), (2, 22), (2, 23), (2, 27), (3, 9), (3, 15), (3, 21),
(3, 22), (3, 23), (3, 24), (3, 27), (3, 28), (3, 29), (4, 5),
(4, 6), (4, 8), (4, 21), (4, 22), (4, 23), (4, 26), (4, 28),
(4, 30), (5, 6), (5, 8), (5, 9), (5, 13), (5, 14), (5, 15),
(5, 16), (5, 20), (5, 21), (5, 22), (5, 25), (5, 28), (5, 29),
(6, 7), (6, 8), (6, 13), (6, 17), (6, 18), (6, 19), (6, 24),
(6, 26), (6, 27), (6, 28), (6, 29), (7, 12), (7, 14), (7, 15),
(7, 16), (7, 17), (7, 20), (7, 25), (7, 27), (7, 29), (7, 30),
(8, 10), (8, 15), (8, 16), (8, 18), (8, 20), (8, 22), (8, 24),
(8, 26), (8, 27), (8, 28), (8, 30), (9, 11), (9, 12), (9, 13),
(9, 14), (9, 15), (9, 16), (9, 19), (9, 20), (9, 21), (9, 24),
(9, 30), (10, 12), (10, 15), (10, 18), (10, 19), (10, 20),
(10, 22), (10, 23), (10, 24), (10, 26), (10, 27), (10, 29),
(10, 30), (11, 13), (11, 15), (11, 16), (11, 17), (11, 18),
(11, 19), (11, 20), (11, 22), (11, 29), (11, 30), (12, 14),
(12, 17), (12, 18), (12, 19), (12, 20), (12, 21), (12, 23),
(12, 25), (12, 26), (12, 30), (13, 20), (13, 22), (13, 23),
(13, 24), (13, 30), (14, 16), (14, 20), (14, 21), (14, 22),
(14, 23), (14, 25), (14, 26), (14, 27), (14, 29), (14, 30),
(15, 17), (15, 18), (15, 20), (15, 21), (15, 26), (15, 27),
(15, 28), (16, 17), (16, 18), (16, 19), (16, 20), (16, 21),
(16, 29), (16, 30), (17, 18), (17, 21), (17, 22), (17, 25),
(17, 27), (17, 28), (17, 30), (18, 19), (18, 20), (18, 21),
(18, 22), (18, 23), (18, 24), (19, 20), (19, 22), (19, 23),
(19, 24), (19, 25), (19, 27), (19, 30), (20, 21), (20, 23),
(20, 24), (20, 26), (20, 28), (20, 29), (21, 23), (21, 26),
(21, 27), (21, 29), (22, 24), (22, 25), (22, 26), (22, 29),
(23, 25), (23, 30), (24, 25), (24, 26), (25, 27), (25, 29),
(26, 27), (26, 28), (26, 30), (28, 29), (29, 30),
]
)
# fmt: on
clique, weight = nx.algorithms.max_weight_clique(G)
assert verify_clique(G, clique, weight, 111, "weight")
# ############################ Utility functions ############################
def verify_clique(
graph, clique, reported_clique_weight, expected_clique_weight, weight_accessor
):
for node1 in clique:
for node2 in clique:
if node1 == node2:
continue
if not graph.has_edge(node1, node2):
return False
if weight_accessor is None:
clique_weight = len(clique)
else:
clique_weight = sum(graph.nodes[v]["weight"] for v in clique)
if clique_weight != expected_clique_weight:
return False
if clique_weight != reported_clique_weight:
return False
return True
# ############################ Graph Generation ############################
def empty_graph():
return nx.Graph()
def one_node_graph():
graph = nx.Graph()
graph.add_nodes_from([1])
graph.nodes[1]["weight"] = 10
return graph
def two_node_graph():
graph = nx.Graph()
graph.add_nodes_from([1, 2])
graph.add_edges_from([(1, 2)])
graph.nodes[1]["weight"] = 10
graph.nodes[2]["weight"] = 20
return graph
def three_node_clique():
graph = nx.Graph()
graph.add_nodes_from([1, 2, 3])
graph.add_edges_from([(1, 2), (1, 3), (2, 3)])
graph.nodes[1]["weight"] = 10
graph.nodes[2]["weight"] = 20
graph.nodes[3]["weight"] = 5
return graph
def three_node_independent_set():
graph = nx.Graph()
graph.add_nodes_from([1, 2, 3])
graph.nodes[1]["weight"] = 10
graph.nodes[2]["weight"] = 20
graph.nodes[3]["weight"] = 5
return graph
def disconnected():
graph = nx.Graph()
graph.add_edges_from([(1, 2), (2, 3), (4, 5), (5, 6)])
graph.nodes[1]["weight"] = 10
graph.nodes[2]["weight"] = 20
graph.nodes[3]["weight"] = 5
graph.nodes[4]["weight"] = 100
graph.nodes[5]["weight"] = 200
graph.nodes[6]["weight"] = 50
return graph
# --------------------------------------------------------------------------
# Basic tests for all strategies
# For each basic graph function, specify expected weight of max weight clique
# and expected size of maximum clique
TEST_CASES = {
empty_graph: (0, 0),
one_node_graph: (10, 1),
two_node_graph: (30, 2),
three_node_clique: (35, 3),
three_node_independent_set: (20, 1),
disconnected: (300, 2),
}
@@ -0,0 +1,62 @@
"""
Tests for maximal (not maximum) independent sets.
"""
import random
import pytest
import networkx as nx
def test_random_seed():
G = nx.empty_graph(5)
assert nx.maximal_independent_set(G, seed=1) == [1, 0, 3, 2, 4]
@pytest.mark.parametrize("graph", [nx.complete_graph(5), nx.complete_graph(55)])
def test_K5(graph):
"""Maximal independent set for complete graphs"""
assert all(nx.maximal_independent_set(graph, [n]) == [n] for n in graph)
def test_exceptions():
"""Bad input should raise exception."""
G = nx.florentine_families_graph()
pytest.raises(nx.NetworkXUnfeasible, nx.maximal_independent_set, G, ["Smith"])
pytest.raises(
nx.NetworkXUnfeasible, nx.maximal_independent_set, G, ["Salviati", "Pazzi"]
)
# MaximalIndependentSet is not implemented for directed graphs
pytest.raises(nx.NetworkXNotImplemented, nx.maximal_independent_set, nx.DiGraph(G))
def test_florentine_family():
G = nx.florentine_families_graph()
indep = nx.maximal_independent_set(G, ["Medici", "Bischeri"])
assert set(indep) == {
"Medici",
"Bischeri",
"Castellani",
"Pazzi",
"Ginori",
"Lamberteschi",
}
def test_bipartite():
G = nx.complete_bipartite_graph(12, 34)
indep = nx.maximal_independent_set(G, [4, 5, 9, 10])
assert sorted(indep) == list(range(12))
def test_random_graphs():
"""Generate 5 random graphs of different types and sizes and
make sure that all sets are independent and maximal."""
for i in range(0, 50, 10):
G = nx.erdos_renyi_graph(i * 10 + 1, random.random())
IS = nx.maximal_independent_set(G)
assert G.subgraph(IS).number_of_edges() == 0
nbrs_of_MIS = set.union(*(set(G.neighbors(v)) for v in IS))
assert all(v in nbrs_of_MIS for v in set(G.nodes()).difference(IS))
@@ -0,0 +1,15 @@
import networkx as nx
from networkx.algorithms.moral import moral_graph
def test_get_moral_graph():
graph = nx.DiGraph()
graph.add_nodes_from([1, 2, 3, 4, 5, 6, 7])
graph.add_edges_from([(1, 2), (3, 2), (4, 1), (4, 5), (6, 5), (7, 5)])
H = moral_graph(graph)
assert not H.is_directed()
assert H.has_edge(1, 3)
assert H.has_edge(4, 6)
assert H.has_edge(6, 7)
assert H.has_edge(4, 7)
assert not H.has_edge(1, 5)
@@ -0,0 +1,140 @@
import pytest
pytest.importorskip("numpy")
pytest.importorskip("scipy")
import networkx as nx
from networkx.algorithms import node_classification
class TestHarmonicFunction:
def test_path_graph(self):
G = nx.path_graph(4)
label_name = "label"
G.nodes[0][label_name] = "A"
G.nodes[3][label_name] = "B"
predicted = node_classification.harmonic_function(G, label_name=label_name)
assert predicted[0] == "A"
assert predicted[1] == "A"
assert predicted[2] == "B"
assert predicted[3] == "B"
def test_no_labels(self):
with pytest.raises(nx.NetworkXError):
G = nx.path_graph(4)
node_classification.harmonic_function(G)
def test_no_nodes(self):
with pytest.raises(nx.NetworkXError):
G = nx.Graph()
node_classification.harmonic_function(G)
def test_no_edges(self):
with pytest.raises(nx.NetworkXError):
G = nx.Graph()
G.add_node(1)
G.add_node(2)
node_classification.harmonic_function(G)
def test_digraph(self):
with pytest.raises(nx.NetworkXNotImplemented):
G = nx.DiGraph()
G.add_edge(0, 1)
G.add_edge(1, 2)
G.add_edge(2, 3)
label_name = "label"
G.nodes[0][label_name] = "A"
G.nodes[3][label_name] = "B"
node_classification.harmonic_function(G)
def test_one_labeled_node(self):
G = nx.path_graph(4)
label_name = "label"
G.nodes[0][label_name] = "A"
predicted = node_classification.harmonic_function(G, label_name=label_name)
assert predicted[0] == "A"
assert predicted[1] == "A"
assert predicted[2] == "A"
assert predicted[3] == "A"
def test_nodes_all_labeled(self):
G = nx.karate_club_graph()
label_name = "club"
predicted = node_classification.harmonic_function(G, label_name=label_name)
for i in range(len(G)):
assert predicted[i] == G.nodes[i][label_name]
def test_labeled_nodes_are_not_changed(self):
G = nx.karate_club_graph()
label_name = "club"
label_removed = {0, 1, 2, 3, 4, 5, 6, 7}
for i in label_removed:
del G.nodes[i][label_name]
predicted = node_classification.harmonic_function(G, label_name=label_name)
label_not_removed = set(range(len(G))) - label_removed
for i in label_not_removed:
assert predicted[i] == G.nodes[i][label_name]
class TestLocalAndGlobalConsistency:
def test_path_graph(self):
G = nx.path_graph(4)
label_name = "label"
G.nodes[0][label_name] = "A"
G.nodes[3][label_name] = "B"
predicted = node_classification.local_and_global_consistency(
G, label_name=label_name
)
assert predicted[0] == "A"
assert predicted[1] == "A"
assert predicted[2] == "B"
assert predicted[3] == "B"
def test_no_labels(self):
with pytest.raises(nx.NetworkXError):
G = nx.path_graph(4)
node_classification.local_and_global_consistency(G)
def test_no_nodes(self):
with pytest.raises(nx.NetworkXError):
G = nx.Graph()
node_classification.local_and_global_consistency(G)
def test_no_edges(self):
with pytest.raises(nx.NetworkXError):
G = nx.Graph()
G.add_node(1)
G.add_node(2)
node_classification.local_and_global_consistency(G)
def test_digraph(self):
with pytest.raises(nx.NetworkXNotImplemented):
G = nx.DiGraph()
G.add_edge(0, 1)
G.add_edge(1, 2)
G.add_edge(2, 3)
label_name = "label"
G.nodes[0][label_name] = "A"
G.nodes[3][label_name] = "B"
node_classification.harmonic_function(G)
def test_one_labeled_node(self):
G = nx.path_graph(4)
label_name = "label"
G.nodes[0][label_name] = "A"
predicted = node_classification.local_and_global_consistency(
G, label_name=label_name
)
assert predicted[0] == "A"
assert predicted[1] == "A"
assert predicted[2] == "A"
assert predicted[3] == "A"
def test_nodes_all_labeled(self):
G = nx.karate_club_graph()
label_name = "club"
predicted = node_classification.local_and_global_consistency(
G, alpha=0, label_name=label_name
)
for i in range(len(G)):
assert predicted[i] == G.nodes[i][label_name]
@@ -0,0 +1,60 @@
import pytest
import networkx as nx
np = pytest.importorskip("numpy")
@pytest.mark.parametrize(
"k, weight, expected",
[
(None, None, 7.21), # infers 3 communities
(2, None, 11.7),
(None, "weight", 25.45),
(2, "weight", 38.8),
],
)
def test_non_randomness(k, weight, expected):
G = nx.karate_club_graph()
np.testing.assert_almost_equal(
nx.non_randomness(G, k, weight)[0], expected, decimal=2
)
def test_non_connected():
G = nx.Graph([(1, 2)])
G.add_node(3)
with pytest.raises(nx.NetworkXException, match="Non connected"):
nx.non_randomness(G)
def test_self_loops():
G = nx.Graph()
G.add_edge(1, 2)
G.add_edge(1, 1)
with pytest.raises(nx.NetworkXError, match="Graph must not contain self-loops"):
nx.non_randomness(G)
def test_empty_graph():
G = nx.empty_graph(1)
with pytest.raises(nx.NetworkXError, match=".*not applicable to empty graphs"):
nx.non_randomness(G)
@pytest.mark.parametrize("k", [-1, 0, 2, 5])
def test_value_error(k):
"""
Check that invalid values of k raise (must be between 1 and n - 1, inclusive,
and such that the probability is between 0 and 1, exclusive).
"""
G = nx.path_graph(5)
with pytest.raises(ValueError, match=r"invalid number of communities"):
nx.non_randomness(G, k=k)
@pytest.mark.parametrize("G", [nx.DiGraph(), nx.MultiGraph(), nx.MultiDiGraph()])
def test_not_implemented(G):
"""Check that non-randomness is not implemented for directed or multigraphs."""
with pytest.raises(nx.NetworkXNotImplemented, match=r"not implemented for"):
nx.non_randomness(G)
@@ -0,0 +1,27 @@
import networkx as nx
def test_chordal_graph():
G = nx.complete_graph(5)
assert nx.is_perfect_graph(G)
def test_odd_cycle():
G = nx.cycle_graph(5) # Induced odd cycle
assert not nx.is_perfect_graph(G)
def test_even_cycle():
G = nx.cycle_graph(6) # Even cycle is perfect
assert nx.is_perfect_graph(G)
def test_complement_of_odd_cycle():
G = nx.cycle_graph(7)
GC = nx.complement(G)
assert not nx.is_perfect_graph(GC)
def test_disconnected_union_of_cliques():
G = nx.disjoint_union(nx.complete_graph(3), nx.complete_graph(4))
assert nx.is_perfect_graph(G)
@@ -0,0 +1,274 @@
import math
import pytest
import networkx as nx
from networkx.algorithms.planar_drawing import triangulate_embedding
def test_graph1():
embedding_data = {0: [1, 2, 3], 1: [2, 0], 2: [3, 0, 1], 3: [2, 0]}
check_embedding_data(embedding_data)
def test_graph2():
embedding_data = {
0: [8, 6],
1: [2, 6, 9],
2: [8, 1, 7, 9, 6, 4],
3: [9],
4: [2],
5: [6, 8],
6: [9, 1, 0, 5, 2],
7: [9, 2],
8: [0, 2, 5],
9: [1, 6, 2, 7, 3],
}
check_embedding_data(embedding_data)
def test_circle_graph():
embedding_data = {
0: [1, 9],
1: [0, 2],
2: [1, 3],
3: [2, 4],
4: [3, 5],
5: [4, 6],
6: [5, 7],
7: [6, 8],
8: [7, 9],
9: [8, 0],
}
check_embedding_data(embedding_data)
def test_grid_graph():
embedding_data = {
(0, 1): [(0, 0), (1, 1), (0, 2)],
(1, 2): [(1, 1), (2, 2), (0, 2)],
(0, 0): [(0, 1), (1, 0)],
(2, 1): [(2, 0), (2, 2), (1, 1)],
(1, 1): [(2, 1), (1, 2), (0, 1), (1, 0)],
(2, 0): [(1, 0), (2, 1)],
(2, 2): [(1, 2), (2, 1)],
(1, 0): [(0, 0), (2, 0), (1, 1)],
(0, 2): [(1, 2), (0, 1)],
}
check_embedding_data(embedding_data)
def test_one_node_graph():
embedding_data = {0: []}
check_embedding_data(embedding_data)
def test_two_node_graph():
embedding_data = {0: [1], 1: [0]}
check_embedding_data(embedding_data)
def test_three_node_graph():
embedding_data = {0: [1, 2], 1: [0, 2], 2: [0, 1]}
check_embedding_data(embedding_data)
def test_multiple_component_graph1():
embedding_data = {0: [], 1: []}
check_embedding_data(embedding_data)
def test_multiple_component_graph2():
embedding_data = {0: [1, 2], 1: [0, 2], 2: [0, 1], 3: [4, 5], 4: [3, 5], 5: [3, 4]}
check_embedding_data(embedding_data)
def test_invalid_half_edge():
with pytest.raises(nx.NetworkXException):
embedding_data = {1: [2, 3, 4], 2: [1, 3, 4], 3: [1, 2, 4], 4: [1, 2, 3]}
embedding = nx.PlanarEmbedding()
embedding.set_data(embedding_data)
nx.combinatorial_embedding_to_pos(embedding)
def test_triangulate_embedding1():
embedding = nx.PlanarEmbedding()
embedding.add_node(1)
expected_embedding = {1: []}
check_triangulation(embedding, expected_embedding)
def test_triangulate_embedding2():
embedding = nx.PlanarEmbedding()
embedding.connect_components(1, 2)
expected_embedding = {1: [2], 2: [1]}
check_triangulation(embedding, expected_embedding)
def check_triangulation(embedding, expected_embedding):
res_embedding, _ = triangulate_embedding(embedding, True)
assert res_embedding.get_data() == expected_embedding, (
"Expected embedding incorrect"
)
res_embedding, _ = triangulate_embedding(embedding, False)
assert res_embedding.get_data() == expected_embedding, (
"Expected embedding incorrect"
)
def check_embedding_data(embedding_data):
"""Checks that the planar embedding of the input is correct"""
embedding = nx.PlanarEmbedding()
embedding.set_data(embedding_data)
pos_fully = nx.combinatorial_embedding_to_pos(embedding, False)
msg = "Planar drawing does not conform to the embedding (fully triangulation)"
assert planar_drawing_conforms_to_embedding(embedding, pos_fully), msg
check_edge_intersections(embedding, pos_fully)
pos_internally = nx.combinatorial_embedding_to_pos(embedding, True)
msg = "Planar drawing does not conform to the embedding (internal triangulation)"
assert planar_drawing_conforms_to_embedding(embedding, pos_internally), msg
check_edge_intersections(embedding, pos_internally)
def is_close(a, b, rel_tol=1e-09, abs_tol=0.0):
# Check if float numbers are basically equal, for python >=3.5 there is
# function for that in the standard library
return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
def point_in_between(a, b, p):
# checks if p is on the line between a and b
x1, y1 = a
x2, y2 = b
px, py = p
dist_1_2 = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
dist_1_p = math.sqrt((x1 - px) ** 2 + (y1 - py) ** 2)
dist_2_p = math.sqrt((x2 - px) ** 2 + (y2 - py) ** 2)
return is_close(dist_1_p + dist_2_p, dist_1_2)
def check_edge_intersections(G, pos):
"""Check all edges in G for intersections.
Raises an exception if an intersection is found.
Parameters
----------
G : NetworkX graph
pos : dict
Maps every node to a tuple (x, y) representing its position
"""
for a, b in G.edges():
for c, d in G.edges():
# Check if end points are different
if a != c and b != d and b != c and a != d:
x1, y1 = pos[a]
x2, y2 = pos[b]
x3, y3 = pos[c]
x4, y4 = pos[d]
determinant = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
if determinant != 0: # the lines are not parallel
# calculate intersection point, see:
# https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
px = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (
x3 * y4 - y3 * x4
) / determinant
py = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (
x3 * y4 - y3 * x4
) / determinant
# Check if intersection lies between the points
if point_in_between(pos[a], pos[b], (px, py)) and point_in_between(
pos[c], pos[d], (px, py)
):
msg = f"There is an intersection at {px},{py}"
raise nx.NetworkXException(msg)
# Check overlap
msg = "A node lies on a edge connecting two other nodes"
if (
point_in_between(pos[a], pos[b], pos[c])
or point_in_between(pos[a], pos[b], pos[d])
or point_in_between(pos[c], pos[d], pos[a])
or point_in_between(pos[c], pos[d], pos[b])
):
raise nx.NetworkXException(msg)
# No edge intersection found
class Vector:
"""Compare vectors by their angle without loss of precision
All vectors in direction [0, 1] are the smallest.
The vectors grow in clockwise direction.
"""
__slots__ = ["x", "y", "node", "quadrant"]
def __init__(self, x, y, node):
self.x = x
self.y = y
self.node = node
if self.x >= 0 and self.y > 0:
self.quadrant = 1
elif self.x > 0 and self.y <= 0:
self.quadrant = 2
elif self.x <= 0 and self.y < 0:
self.quadrant = 3
else:
self.quadrant = 4
def __eq__(self, other):
return self.quadrant == other.quadrant and self.x * other.y == self.y * other.x
def __lt__(self, other):
if self.quadrant < other.quadrant:
return True
elif self.quadrant > other.quadrant:
return False
else:
return self.x * other.y < self.y * other.x
def __ne__(self, other):
return self != other
def __le__(self, other):
return not other < self
def __gt__(self, other):
return other < self
def __ge__(self, other):
return not self < other
def planar_drawing_conforms_to_embedding(embedding, pos):
"""Checks if pos conforms to the planar embedding
Returns true iff the neighbors are actually oriented in the orientation
specified of the embedding
"""
for v in embedding:
nbr_vectors = []
v_pos = pos[v]
for nbr in embedding[v]:
new_vector = Vector(pos[nbr][0] - v_pos[0], pos[nbr][1] - v_pos[1], nbr)
nbr_vectors.append(new_vector)
# Sort neighbors according to their phi angle
nbr_vectors.sort()
for idx, nbr_vector in enumerate(nbr_vectors):
cw_vector = nbr_vectors[(idx + 1) % len(nbr_vectors)]
ccw_vector = nbr_vectors[idx - 1]
if (
embedding[v][nbr_vector.node]["cw"] != cw_vector.node
or embedding[v][nbr_vector.node]["ccw"] != ccw_vector.node
):
return False
if cw_vector.node != nbr_vector.node and cw_vector == nbr_vector:
# Lines overlap
return False
if ccw_vector.node != nbr_vector.node and ccw_vector == nbr_vector:
# Lines overlap
return False
return True
@@ -0,0 +1,556 @@
import pytest
import networkx as nx
from networkx.algorithms.planarity import (
check_planarity_recursive,
get_counterexample,
get_counterexample_recursive,
)
class TestLRPlanarity:
"""Nose Unit tests for the :mod:`networkx.algorithms.planarity` module.
Tests three things:
1. Check that the result is correct
(returns planar if and only if the graph is actually planar)
2. In case a counter example is returned: Check if it is correct
3. In case an embedding is returned: Check if its actually an embedding
"""
@staticmethod
def check_graph(G, is_planar=None):
"""Raises an exception if the lr_planarity check returns a wrong result
Parameters
----------
G : NetworkX graph
is_planar : bool
The expected result of the planarity check.
If set to None only counter example or embedding are verified.
"""
# obtain results of planarity check
is_planar_lr, result = nx.check_planarity(G, True)
is_planar_lr_rec, result_rec = check_planarity_recursive(G, True)
if is_planar is not None:
# set a message for the assert
if is_planar:
msg = "Wrong planarity check result. Should be planar."
else:
msg = "Wrong planarity check result. Should be non-planar."
# check if the result is as expected
assert is_planar == is_planar_lr, msg
assert is_planar == is_planar_lr_rec, msg
if is_planar_lr:
# check embedding
check_embedding(G, result)
check_embedding(G, result_rec)
else:
# check counter example
check_counterexample(G, result)
check_counterexample(G, result_rec)
def test_simple_planar_graph(self):
e = [
(1, 2),
(2, 3),
(3, 4),
(4, 6),
(6, 7),
(7, 1),
(1, 5),
(5, 2),
(2, 4),
(4, 5),
(5, 7),
]
self.check_graph(nx.Graph(e), is_planar=True)
def test_planar_with_selfloop(self):
e = [
(1, 1),
(2, 2),
(3, 3),
(4, 4),
(5, 5),
(1, 2),
(1, 3),
(1, 5),
(2, 5),
(2, 4),
(3, 4),
(3, 5),
(4, 5),
]
self.check_graph(nx.Graph(e), is_planar=True)
def test_k3_3(self):
self.check_graph(nx.complete_bipartite_graph(3, 3), is_planar=False)
def test_k5(self):
self.check_graph(nx.complete_graph(5), is_planar=False)
def test_multiple_components_planar(self):
e = [(1, 2), (2, 3), (3, 1), (4, 5), (5, 6), (6, 4)]
self.check_graph(nx.Graph(e), is_planar=True)
def test_multiple_components_non_planar(self):
G = nx.complete_graph(5)
# add another planar component to the non planar component
# G stays non planar
G.add_edges_from([(6, 7), (7, 8), (8, 6)])
self.check_graph(G, is_planar=False)
def test_non_planar_with_selfloop(self):
G = nx.complete_graph(5)
# add self loops
for i in range(5):
G.add_edge(i, i)
self.check_graph(G, is_planar=False)
def test_non_planar1(self):
# tests a graph that has no subgraph directly isomorph to K5 or K3_3
e = [
(1, 5),
(1, 6),
(1, 7),
(2, 6),
(2, 3),
(3, 5),
(3, 7),
(4, 5),
(4, 6),
(4, 7),
]
self.check_graph(nx.Graph(e), is_planar=False)
def test_loop(self):
# test a graph with a selfloop
e = [(1, 2), (2, 2)]
G = nx.Graph(e)
self.check_graph(G, is_planar=True)
def test_comp(self):
# test multiple component graph
e = [(1, 2), (3, 4)]
G = nx.Graph(e)
G.remove_edge(1, 2)
self.check_graph(G, is_planar=True)
def test_goldner_harary(self):
# test goldner-harary graph (a maximal planar graph)
e = [
(1, 2),
(1, 3),
(1, 4),
(1, 5),
(1, 7),
(1, 8),
(1, 10),
(1, 11),
(2, 3),
(2, 4),
(2, 6),
(2, 7),
(2, 9),
(2, 10),
(2, 11),
(3, 4),
(4, 5),
(4, 6),
(4, 7),
(5, 7),
(6, 7),
(7, 8),
(7, 9),
(7, 10),
(8, 10),
(9, 10),
(10, 11),
]
G = nx.Graph(e)
self.check_graph(G, is_planar=True)
def test_planar_multigraph(self):
G = nx.MultiGraph([(1, 2), (1, 2), (1, 2), (1, 2), (2, 3), (3, 1)])
self.check_graph(G, is_planar=True)
def test_non_planar_multigraph(self):
G = nx.MultiGraph(nx.complete_graph(5))
G.add_edges_from([(1, 2)] * 5)
self.check_graph(G, is_planar=False)
def test_planar_digraph(self):
G = nx.DiGraph([(1, 2), (2, 3), (2, 4), (4, 1), (4, 2), (1, 4), (3, 2)])
self.check_graph(G, is_planar=True)
def test_non_planar_digraph(self):
G = nx.DiGraph(nx.complete_graph(5))
G.remove_edge(1, 2)
G.remove_edge(4, 1)
self.check_graph(G, is_planar=False)
def test_single_component(self):
# Test a graph with only a single node
G = nx.Graph()
G.add_node(1)
self.check_graph(G, is_planar=True)
def test_graph1(self):
G = nx.Graph(
[
(3, 10),
(2, 13),
(1, 13),
(7, 11),
(0, 8),
(8, 13),
(0, 2),
(0, 7),
(0, 10),
(1, 7),
]
)
self.check_graph(G, is_planar=True)
def test_graph2(self):
G = nx.Graph(
[
(1, 2),
(4, 13),
(0, 13),
(4, 5),
(7, 10),
(1, 7),
(0, 3),
(2, 6),
(5, 6),
(7, 13),
(4, 8),
(0, 8),
(0, 9),
(2, 13),
(6, 7),
(3, 6),
(2, 8),
]
)
self.check_graph(G, is_planar=False)
def test_graph3(self):
G = nx.Graph(
[
(0, 7),
(3, 11),
(3, 4),
(8, 9),
(4, 11),
(1, 7),
(1, 13),
(1, 11),
(3, 5),
(5, 7),
(1, 3),
(0, 4),
(5, 11),
(5, 13),
]
)
self.check_graph(G, is_planar=False)
def test_counterexample_planar(self):
with pytest.raises(nx.NetworkXException):
# Try to get a counterexample of a planar graph
G = nx.Graph()
G.add_node(1)
get_counterexample(G)
def test_counterexample_planar_recursive(self):
with pytest.raises(nx.NetworkXException):
# Try to get a counterexample of a planar graph
G = nx.Graph()
G.add_node(1)
get_counterexample_recursive(G)
def test_edge_removal_from_planar_embedding(self):
# PlanarEmbedding.check_structure() must succeed after edge removal
edges = ((0, 1), (1, 2), (2, 3), (3, 4), (4, 0), (0, 2), (0, 3))
G = nx.Graph(edges)
cert, P = nx.check_planarity(G)
assert cert is True
P.remove_edge(0, 2)
self.check_graph(P, is_planar=True)
P.add_half_edge_ccw(1, 3, 2)
P.add_half_edge_cw(3, 1, 2)
self.check_graph(P, is_planar=True)
P.remove_edges_from(((0, 3), (1, 3)))
self.check_graph(P, is_planar=True)
@pytest.mark.parametrize("graph_type", (nx.Graph, nx.MultiGraph))
def test_graph_planar_embedding_to_undirected(self, graph_type):
G = graph_type([(0, 1), (0, 1), (1, 2), (2, 3), (3, 0), (0, 2)])
is_planar, P = nx.check_planarity(G)
assert is_planar
U = P.to_undirected()
assert isinstance(U, nx.Graph)
assert all((d == {} for _, _, d in U.edges(data=True)))
@pytest.mark.parametrize(
"reciprocal, as_view", [(True, True), (True, False), (False, True)]
)
def test_planar_embedding_to_undirected_invalid_parameters(
self, reciprocal, as_view
):
G = nx.Graph([(0, 1), (1, 2), (2, 3), (3, 0), (0, 2)])
is_planar, P = nx.check_planarity(G)
assert is_planar
with pytest.raises(ValueError, match="is not supported for PlanarEmbedding."):
P.to_undirected(reciprocal=reciprocal, as_view=as_view)
def check_embedding(G, embedding):
"""Raises an exception if the combinatorial embedding is not correct
Parameters
----------
G : NetworkX graph
embedding : a dict mapping nodes to a list of edges
This specifies the ordering of the outgoing edges from a node for
a combinatorial embedding
Notes
-----
Checks the following things:
- The type of the embedding is correct
- The nodes and edges match the original graph
- Every half edge has its matching opposite half edge
- No intersections of edges (checked by Euler's formula)
"""
if not isinstance(embedding, nx.PlanarEmbedding):
raise nx.NetworkXException("Bad embedding. Not of type nx.PlanarEmbedding")
# Check structure
embedding.check_structure()
# Check that graphs are equivalent
assert set(G.nodes) == set(embedding.nodes), (
"Bad embedding. Nodes don't match the original graph."
)
# Check that the edges are equal
g_edges = set()
for edge in G.edges:
if edge[0] != edge[1]:
g_edges.add((edge[0], edge[1]))
g_edges.add((edge[1], edge[0]))
assert g_edges == set(embedding.edges), (
"Bad embedding. Edges don't match the original graph."
)
def check_counterexample(G, sub_graph):
"""Raises an exception if the counterexample is wrong.
Parameters
----------
G : NetworkX graph
subdivision_nodes : set
A set of nodes inducing a subgraph as a counterexample
"""
# 1. Create the sub graph
sub_graph = nx.Graph(sub_graph)
# 2. Remove self loops
for u in sub_graph:
if sub_graph.has_edge(u, u):
sub_graph.remove_edge(u, u)
# keep track of nodes we might need to contract
contract = list(sub_graph)
# 3. Contract Edges
while len(contract) > 0:
contract_node = contract.pop()
if contract_node not in sub_graph:
# Node was already contracted
continue
degree = sub_graph.degree[contract_node]
# Check if we can remove the node
if degree == 2:
# Get the two neighbors
neighbors = iter(sub_graph[contract_node])
u = next(neighbors)
v = next(neighbors)
# Save nodes for later
contract.append(u)
contract.append(v)
# Contract edge
sub_graph.remove_node(contract_node)
sub_graph.add_edge(u, v)
# 4. Check for isomorphism with K5 or K3_3 graphs
if len(sub_graph) == 5:
if not nx.is_isomorphic(nx.complete_graph(5), sub_graph):
raise nx.NetworkXException("Bad counter example.")
elif len(sub_graph) == 6:
if not nx.is_isomorphic(nx.complete_bipartite_graph(3, 3), sub_graph):
raise nx.NetworkXException("Bad counter example.")
else:
raise nx.NetworkXException("Bad counter example.")
class TestPlanarEmbeddingClass:
def test_add_half_edge(self):
embedding = nx.PlanarEmbedding()
embedding.add_half_edge(0, 1)
with pytest.raises(
nx.NetworkXException, match="Invalid clockwise reference node."
):
embedding.add_half_edge(0, 2, cw=3)
with pytest.raises(
nx.NetworkXException, match="Invalid counterclockwise reference node."
):
embedding.add_half_edge(0, 2, ccw=3)
with pytest.raises(
nx.NetworkXException, match="Only one of cw/ccw can be specified."
):
embedding.add_half_edge(0, 2, cw=1, ccw=1)
with pytest.raises(
nx.NetworkXException,
match=(
r"Node already has out-half-edge\(s\), either"
" cw or ccw reference node required."
),
):
embedding.add_half_edge(0, 2)
# these should work
embedding.add_half_edge(0, 2, cw=1)
embedding.add_half_edge(0, 3, ccw=1)
assert sorted(embedding.edges(data=True)) == [
(0, 1, {"ccw": 2, "cw": 3}),
(0, 2, {"cw": 1, "ccw": 3}),
(0, 3, {"cw": 2, "ccw": 1}),
]
def test_get_data(self):
embedding = self.get_star_embedding(4)
data = embedding.get_data()
data_cmp = {0: [3, 2, 1], 1: [0], 2: [0], 3: [0]}
assert data == data_cmp
def test_edge_removal(self):
embedding = nx.PlanarEmbedding()
embedding.set_data(
{
1: [2, 5, 7],
2: [1, 3, 4, 5],
3: [2, 4],
4: [3, 6, 5, 2],
5: [7, 1, 2, 4],
6: [4, 7],
7: [6, 1, 5],
}
)
# remove_edges_from() calls remove_edge(), so both are tested here
embedding.remove_edges_from(((5, 4), (1, 5)))
embedding.check_structure()
embedding_expected = nx.PlanarEmbedding()
embedding_expected.set_data(
{
1: [2, 7],
2: [1, 3, 4, 5],
3: [2, 4],
4: [3, 6, 2],
5: [7, 2],
6: [4, 7],
7: [6, 1, 5],
}
)
assert nx.utils.graphs_equal(embedding, embedding_expected)
def test_missing_edge_orientation(self):
embedding = nx.PlanarEmbedding({1: {2: {}}, 2: {1: {}}})
with pytest.raises(nx.NetworkXException):
# Invalid structure because the orientation of the edge was not set
embedding.check_structure()
def test_invalid_edge_orientation(self):
embedding = nx.PlanarEmbedding(
{
1: {2: {"cw": 2, "ccw": 2}},
2: {1: {"cw": 1, "ccw": 1}},
1: {3: {}},
3: {1: {}},
}
)
with pytest.raises(nx.NetworkXException):
embedding.check_structure()
def test_missing_half_edge(self):
embedding = nx.PlanarEmbedding()
embedding.add_half_edge(1, 2)
with pytest.raises(nx.NetworkXException):
# Invalid structure because other half edge is missing
embedding.check_structure()
def test_not_fulfilling_euler_formula(self):
embedding = nx.PlanarEmbedding()
for i in range(5):
ref = None
for j in range(5):
if i != j:
embedding.add_half_edge(i, j, cw=ref)
ref = j
with pytest.raises(nx.NetworkXException):
embedding.check_structure()
def test_missing_reference(self):
embedding = nx.PlanarEmbedding()
with pytest.raises(nx.NetworkXException, match="Invalid reference node."):
embedding.add_half_edge(1, 2, ccw=3)
def test_connect_components(self):
embedding = nx.PlanarEmbedding()
embedding.connect_components(1, 2)
def test_successful_face_traversal(self):
embedding = nx.PlanarEmbedding()
embedding.add_half_edge(1, 2)
embedding.add_half_edge(2, 1)
face = embedding.traverse_face(1, 2)
assert face == [1, 2]
def test_unsuccessful_face_traversal(self):
embedding = nx.PlanarEmbedding(
{1: {2: {"cw": 3, "ccw": 2}}, 2: {1: {"cw": 3, "ccw": 1}}}
)
with pytest.raises(nx.NetworkXException):
embedding.traverse_face(1, 2)
def test_forbidden_methods(self):
embedding = nx.PlanarEmbedding()
embedding.add_node(42) # no exception
embedding.add_nodes_from([(23, 24)]) # no exception
with pytest.raises(NotImplementedError):
embedding.add_edge(1, 3)
with pytest.raises(NotImplementedError):
embedding.add_edges_from([(0, 2), (1, 4)])
with pytest.raises(NotImplementedError):
embedding.add_weighted_edges_from([(0, 2, 350), (1, 4, 125)])
@staticmethod
def get_star_embedding(n):
embedding = nx.PlanarEmbedding()
ref = None
for i in range(1, n):
embedding.add_half_edge(0, i, cw=ref)
ref = i
embedding.add_half_edge(i, 0)
return embedding
@@ -0,0 +1,57 @@
"""Unit tests for the :mod:`networkx.algorithms.polynomials` module."""
import pytest
import networkx as nx
sympy = pytest.importorskip("sympy")
# Mapping of input graphs to a string representation of their tutte polynomials
_test_tutte_graphs = {
nx.complete_graph(1): "1",
nx.complete_graph(4): "x**3 + 3*x**2 + 4*x*y + 2*x + y**3 + 3*y**2 + 2*y",
nx.cycle_graph(5): "x**4 + x**3 + x**2 + x + y",
nx.diamond_graph(): "x**3 + 2*x**2 + 2*x*y + x + y**2 + y",
}
_test_chromatic_graphs = {
nx.complete_graph(1): "x",
nx.complete_graph(4): "x**4 - 6*x**3 + 11*x**2 - 6*x",
nx.cycle_graph(5): "x**5 - 5*x**4 + 10*x**3 - 10*x**2 + 4*x",
nx.diamond_graph(): "x**4 - 5*x**3 + 8*x**2 - 4*x",
nx.path_graph(5): "x**5 - 4*x**4 + 6*x**3 - 4*x**2 + x",
}
@pytest.mark.parametrize(("G", "expected"), _test_tutte_graphs.items())
def test_tutte_polynomial(G, expected):
assert nx.tutte_polynomial(G).equals(expected)
@pytest.mark.parametrize("G", _test_tutte_graphs.keys())
def test_tutte_polynomial_disjoint(G):
"""Tutte polynomial factors into the Tutte polynomials of its components.
Verify this property with the disjoint union of two copies of the input graph.
"""
t_g = nx.tutte_polynomial(G)
H = nx.disjoint_union(G, G)
t_h = nx.tutte_polynomial(H)
assert sympy.simplify(t_g * t_g).equals(t_h)
@pytest.mark.parametrize(("G", "expected"), _test_chromatic_graphs.items())
def test_chromatic_polynomial(G, expected):
assert nx.chromatic_polynomial(G).equals(expected)
@pytest.mark.parametrize("G", _test_chromatic_graphs.keys())
def test_chromatic_polynomial_disjoint(G):
"""Chromatic polynomial factors into the Chromatic polynomials of its
components. Verify this property with the disjoint union of two copies of
the input graph.
"""
x_g = nx.chromatic_polynomial(G)
H = nx.disjoint_union(G, G)
x_h = nx.chromatic_polynomial(H)
assert sympy.simplify(x_g * x_g).equals(x_h)
@@ -0,0 +1,37 @@
import pytest
import networkx as nx
class TestReciprocity:
# test overall reciprocity by passing whole graph
def test_reciprocity_digraph(self):
DG = nx.DiGraph([(1, 2), (2, 1)])
reciprocity = nx.reciprocity(DG)
assert reciprocity == 1.0
# test empty graph's overall reciprocity which will throw an error
def test_overall_reciprocity_empty_graph(self):
with pytest.raises(nx.NetworkXError):
DG = nx.DiGraph()
nx.overall_reciprocity(DG)
# test for reciprocity for a list of nodes
def test_reciprocity_graph_nodes(self):
DG = nx.DiGraph([(1, 2), (2, 3), (3, 2)])
reciprocity = nx.reciprocity(DG, [1, 2])
expected_reciprocity = {1: 0.0, 2: 0.6666666666666666}
assert reciprocity == expected_reciprocity
# test for reciprocity for a single node
def test_reciprocity_graph_node(self):
DG = nx.DiGraph([(1, 2), (2, 3), (3, 2)])
reciprocity = nx.reciprocity(DG, 2)
assert reciprocity == 0.6666666666666666
# test for reciprocity for an isolated node
def test_reciprocity_graph_isolated_nodes(self):
with pytest.raises(nx.NetworkXError):
DG = nx.DiGraph([(1, 2)])
DG.add_node(4)
nx.reciprocity(DG, 4)
@@ -0,0 +1,88 @@
import pytest
import networkx as nx
import networkx.algorithms.regular as reg
import networkx.generators as gen
class TestKFactor:
@pytest.mark.parametrize("n", [3, 4, 5])
def test_k_factor_cycle(self, n):
g = nx.cycle_graph(n)
kf = nx.k_factor(g, 2)
assert g.edges == kf.edges
assert g.nodes == kf.nodes
@pytest.mark.parametrize("k", range(3))
def test_k_factor_grid(self, k):
g = nx.grid_2d_graph(4, 4)
kf = nx.k_factor(g, k)
assert g.nodes == kf.nodes
assert all(g.has_edge(*e) for e in kf.edges)
assert nx.is_k_regular(kf, k)
@pytest.mark.parametrize("k", range(6))
def test_k_factor_complete(self, k):
g = nx.complete_graph(6)
kf = nx.k_factor(g, k)
assert g.nodes == kf.nodes
assert all(g.has_edge(*e) for e in kf.edges)
assert nx.is_k_regular(kf, k)
def test_k_factor_degree(self):
g = nx.grid_2d_graph(4, 4)
with pytest.raises(nx.NetworkXUnfeasible, match=r"degree less than"):
nx.k_factor(g, 3)
def test_k_factor_no_matching(self):
g = nx.hexagonal_lattice_graph(4, 4)
# Perfect matching doesn't exist for 4,4 hexagonal lattice graph
with pytest.raises(nx.NetworkXUnfeasible, match=r"no perfect matching"):
nx.k_factor(g, 2)
@pytest.mark.parametrize("graph_type", [nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph])
def test_k_factor_not_implemented(self, graph_type):
with pytest.raises(nx.NetworkXNotImplemented, match=r"not implemented for"):
nx.k_factor(graph_type(), 2)
class TestIsRegular:
@pytest.mark.parametrize(
"graph,expected",
[
(nx.cycle_graph(4), True),
(nx.complete_graph(5), True),
(nx.path_graph(5), False),
(nx.lollipop_graph(5, 5), False),
(nx.cycle_graph(3, create_using=nx.DiGraph), True),
(nx.Graph([(0, 1)]), True),
(nx.DiGraph([(0, 1)]), False),
(nx.MultiGraph([(0, 1), (0, 1)]), True),
(nx.MultiDiGraph([(0, 1), (0, 1)]), False),
],
)
def test_is_regular(self, graph, expected):
assert reg.is_regular(graph) == expected
def test_is_regular_empty_graph_raises(self):
G = nx.Graph()
with pytest.raises(nx.NetworkXPointlessConcept, match="Graph has no nodes"):
nx.is_regular(G)
class TestIsKRegular:
def test_is_k_regular1(self):
g = gen.cycle_graph(4)
assert reg.is_k_regular(g, 2)
assert not reg.is_k_regular(g, 3)
def test_is_k_regular2(self):
g = gen.complete_graph(5)
assert reg.is_k_regular(g, 4)
assert not reg.is_k_regular(g, 3)
assert not reg.is_k_regular(g, 6)
def test_is_k_regular3(self):
g = gen.lollipop_graph(5, 5)
assert not reg.is_k_regular(g, 5)
assert not reg.is_k_regular(g, 6)
@@ -0,0 +1,149 @@
import pytest
import networkx as nx
def test_richclub():
G = nx.Graph([(0, 1), (0, 2), (1, 2), (1, 3), (1, 4), (4, 5)])
rc = nx.richclub.rich_club_coefficient(G, normalized=False)
assert rc == {0: 12.0 / 30, 1: 8.0 / 12}
# test single value
rc0 = nx.richclub.rich_club_coefficient(G, normalized=False)[0]
assert rc0 == 12.0 / 30.0
def test_richclub_seed():
G = nx.Graph([(0, 1), (0, 2), (1, 2), (1, 3), (1, 4), (4, 5)])
rcNorm = nx.richclub.rich_club_coefficient(G, Q=2, seed=1)
assert rcNorm == {0: 1.0, 1: 1.0}
def test_richclub_normalized():
G = nx.Graph([(0, 1), (0, 2), (1, 2), (1, 3), (1, 4), (4, 5)])
rcNorm = nx.richclub.rich_club_coefficient(G, Q=2, seed=42)
assert rcNorm == {0: 1.0, 1: 1.0}
def test_richclub2():
T = nx.balanced_tree(2, 10)
rc = nx.richclub.rich_club_coefficient(T, normalized=False)
assert rc == {
0: 4092 / (2047 * 2046.0),
1: (2044.0 / (1023 * 1022)),
2: (2040.0 / (1022 * 1021)),
}
def test_richclub3():
# tests edgecase
G = nx.karate_club_graph()
rc = nx.rich_club_coefficient(G, normalized=False)
assert rc == {
0: 156.0 / 1122,
1: 154.0 / 1056,
2: 110.0 / 462,
3: 78.0 / 240,
4: 44.0 / 90,
5: 22.0 / 42,
6: 10.0 / 20,
7: 10.0 / 20,
8: 10.0 / 20,
9: 6.0 / 12,
10: 2.0 / 6,
11: 2.0 / 6,
12: 0.0,
13: 0.0,
14: 0.0,
15: 0.0,
}
def test_richclub4():
G = nx.Graph()
G.add_edges_from(
[(0, 1), (0, 2), (0, 3), (0, 4), (4, 5), (5, 9), (6, 9), (7, 9), (8, 9)]
)
rc = nx.rich_club_coefficient(G, normalized=False)
assert rc == {0: 18 / 90.0, 1: 6 / 12.0, 2: 0.0, 3: 0.0}
def test_richclub_exception():
with pytest.raises(nx.NetworkXNotImplemented):
G = nx.DiGraph()
nx.rich_club_coefficient(G)
def test_rich_club_exception2():
with pytest.raises(nx.NetworkXNotImplemented):
G = nx.MultiGraph()
nx.rich_club_coefficient(G)
def test_rich_club_selfloop():
G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
G.add_edge(1, 1) # self loop
G.add_edge(1, 2)
with pytest.raises(
Exception,
match="rich_club_coefficient is not implemented for graphs with self loops.",
):
nx.rich_club_coefficient(G)
def test_rich_club_leq_3_nodes_unnormalized():
# edgeless graphs upto 3 nodes
G = nx.Graph()
rc = nx.rich_club_coefficient(G, normalized=False)
assert rc == {}
for i in range(3):
G.add_node(i)
rc = nx.rich_club_coefficient(G, normalized=False)
assert rc == {}
# 2 nodes, single edge
G = nx.Graph()
G.add_edge(0, 1)
rc = nx.rich_club_coefficient(G, normalized=False)
assert rc == {0: 1}
# 3 nodes, single edge
G = nx.Graph()
G.add_nodes_from([0, 1, 2])
G.add_edge(0, 1)
rc = nx.rich_club_coefficient(G, normalized=False)
assert rc == {0: 1}
# 3 nodes, 2 edges
G.add_edge(1, 2)
rc = nx.rich_club_coefficient(G, normalized=False)
assert rc == {0: 2 / 3}
# 3 nodes, 3 edges
G.add_edge(0, 2)
rc = nx.rich_club_coefficient(G, normalized=False)
assert rc == {0: 1, 1: 1}
def test_rich_club_leq_3_nodes_normalized():
G = nx.Graph()
with pytest.raises(
nx.exception.NetworkXError,
match="Graph has fewer than four nodes",
):
rc = nx.rich_club_coefficient(G, normalized=True)
for i in range(3):
G.add_node(i)
with pytest.raises(
nx.exception.NetworkXError,
match="Graph has fewer than four nodes",
):
rc = nx.rich_club_coefficient(G, normalized=True)
# def test_richclub2_normalized():
# T = nx.balanced_tree(2,10)
# rcNorm = nx.richclub.rich_club_coefficient(T,Q=2)
# assert_true(rcNorm[0] ==1.0 and rcNorm[1] < 0.9 and rcNorm[2] < 0.9)
@@ -0,0 +1,803 @@
import random
import pytest
import networkx as nx
from networkx import convert_node_labels_to_integers as cnlti
from networkx.algorithms.simple_paths import (
_bidirectional_dijkstra,
_bidirectional_shortest_path,
)
from networkx.utils import arbitrary_element, pairwise
class TestIsSimplePath:
"""Unit tests for the
:func:`networkx.algorithms.simple_paths.is_simple_path` function.
"""
def test_empty_list(self):
"""Tests that the empty list is not a valid path, since there
should be a one-to-one correspondence between paths as lists of
nodes and paths as lists of edges.
"""
G = nx.trivial_graph()
assert not nx.is_simple_path(G, [])
def test_trivial_path(self):
"""Tests that the trivial path, a path of length one, is
considered a simple path in a graph.
"""
G = nx.trivial_graph()
assert nx.is_simple_path(G, [0])
def test_trivial_nonpath(self):
"""Tests that a list whose sole element is an object not in the
graph is not considered a simple path.
"""
G = nx.trivial_graph()
assert not nx.is_simple_path(G, ["not a node"])
def test_simple_path(self):
G = nx.path_graph(2)
assert nx.is_simple_path(G, [0, 1])
def test_non_simple_path(self):
G = nx.path_graph(2)
assert not nx.is_simple_path(G, [0, 1, 0])
def test_cycle(self):
G = nx.cycle_graph(3)
assert not nx.is_simple_path(G, [0, 1, 2, 0])
def test_missing_node(self):
G = nx.path_graph(2)
assert not nx.is_simple_path(G, [0, 2])
def test_missing_starting_node(self):
G = nx.path_graph(2)
assert not nx.is_simple_path(G, [2, 0])
def test_directed_path(self):
G = nx.DiGraph([(0, 1), (1, 2)])
assert nx.is_simple_path(G, [0, 1, 2])
def test_directed_non_path(self):
G = nx.DiGraph([(0, 1), (1, 2)])
assert not nx.is_simple_path(G, [2, 1, 0])
def test_directed_cycle(self):
G = nx.DiGraph([(0, 1), (1, 2), (2, 0)])
assert not nx.is_simple_path(G, [0, 1, 2, 0])
def test_multigraph(self):
G = nx.MultiGraph([(0, 1), (0, 1)])
assert nx.is_simple_path(G, [0, 1])
def test_multidigraph(self):
G = nx.MultiDiGraph([(0, 1), (0, 1), (1, 0), (1, 0)])
assert nx.is_simple_path(G, [0, 1])
# Tests for all_simple_paths
def test_all_simple_paths():
G = nx.path_graph(4)
paths = nx.all_simple_paths(G, 0, 3)
assert {tuple(p) for p in paths} == {(0, 1, 2, 3)}
def test_all_simple_paths_with_two_targets_emits_two_paths():
G = nx.path_graph(4)
G.add_edge(2, 4)
paths = nx.all_simple_paths(G, 0, [3, 4])
assert {tuple(p) for p in paths} == {(0, 1, 2, 3), (0, 1, 2, 4)}
def test_digraph_all_simple_paths_with_two_targets_emits_two_paths():
G = nx.path_graph(4, create_using=nx.DiGraph())
G.add_edge(2, 4)
paths = nx.all_simple_paths(G, 0, [3, 4])
assert {tuple(p) for p in paths} == {(0, 1, 2, 3), (0, 1, 2, 4)}
def test_all_simple_paths_with_two_targets_cutoff():
G = nx.path_graph(4)
G.add_edge(2, 4)
paths = nx.all_simple_paths(G, 0, [3, 4], cutoff=3)
assert {tuple(p) for p in paths} == {(0, 1, 2, 3), (0, 1, 2, 4)}
def test_digraph_all_simple_paths_with_two_targets_cutoff():
G = nx.path_graph(4, create_using=nx.DiGraph())
G.add_edge(2, 4)
paths = nx.all_simple_paths(G, 0, [3, 4], cutoff=3)
assert {tuple(p) for p in paths} == {(0, 1, 2, 3), (0, 1, 2, 4)}
def test_all_simple_paths_with_two_targets_in_line_emits_two_paths():
G = nx.path_graph(4)
paths = nx.all_simple_paths(G, 0, [2, 3])
assert {tuple(p) for p in paths} == {(0, 1, 2), (0, 1, 2, 3)}
def test_all_simple_paths_ignores_cycle():
G = nx.cycle_graph(3, create_using=nx.DiGraph())
G.add_edge(1, 3)
paths = nx.all_simple_paths(G, 0, 3)
assert {tuple(p) for p in paths} == {(0, 1, 3)}
def test_all_simple_paths_with_two_targets_inside_cycle_emits_two_paths():
G = nx.cycle_graph(3, create_using=nx.DiGraph())
G.add_edge(1, 3)
paths = nx.all_simple_paths(G, 0, [2, 3])
assert {tuple(p) for p in paths} == {(0, 1, 2), (0, 1, 3)}
def test_all_simple_paths_source_target():
G = nx.path_graph(4)
assert list(nx.all_simple_paths(G, 1, 1)) == [[1]]
def test_all_simple_paths_cutoff():
G = nx.complete_graph(4)
paths = nx.all_simple_paths(G, 0, 1, cutoff=1)
assert {tuple(p) for p in paths} == {(0, 1)}
paths = nx.all_simple_paths(G, 0, 1, cutoff=2)
assert {tuple(p) for p in paths} == {(0, 1), (0, 2, 1), (0, 3, 1)}
def test_all_simple_paths_on_non_trivial_graph():
"""you may need to draw this graph to make sure it is reasonable"""
G = nx.path_graph(5, create_using=nx.DiGraph())
G.add_edges_from([(0, 5), (1, 5), (1, 3), (5, 4), (4, 2), (4, 3)])
paths = nx.all_simple_paths(G, 1, [2, 3])
assert {tuple(p) for p in paths} == {
(1, 2),
(1, 3, 4, 2),
(1, 5, 4, 2),
(1, 3),
(1, 2, 3),
(1, 5, 4, 3),
(1, 5, 4, 2, 3),
}
paths = nx.all_simple_paths(G, 1, [2, 3], cutoff=3)
assert {tuple(p) for p in paths} == {
(1, 2),
(1, 3, 4, 2),
(1, 5, 4, 2),
(1, 3),
(1, 2, 3),
(1, 5, 4, 3),
}
paths = nx.all_simple_paths(G, 1, [2, 3], cutoff=2)
assert {tuple(p) for p in paths} == {(1, 2), (1, 3), (1, 2, 3)}
def test_all_simple_paths_multigraph():
G = nx.MultiGraph([(1, 2), (1, 2)])
assert list(nx.all_simple_paths(G, 1, 1)) == [[1]]
nx.add_path(G, [3, 1, 10, 2])
paths = list(nx.all_simple_paths(G, 1, 2))
assert len(paths) == 3
assert {tuple(p) for p in paths} == {(1, 2), (1, 2), (1, 10, 2)}
def test_all_simple_paths_multigraph_with_cutoff():
G = nx.MultiGraph([(1, 2), (1, 2), (1, 10), (10, 2)])
paths = list(nx.all_simple_paths(G, 1, 2, cutoff=1))
assert len(paths) == 2
assert {tuple(p) for p in paths} == {(1, 2), (1, 2)}
# See GitHub issue #6732.
G = nx.MultiGraph([(0, 1), (0, 2)])
assert list(nx.all_simple_paths(G, 0, {1, 2}, cutoff=1)) == [[0, 1], [0, 2]]
def test_all_simple_paths_directed():
G = nx.DiGraph()
nx.add_path(G, [1, 2, 3])
nx.add_path(G, [3, 2, 1])
paths = nx.all_simple_paths(G, 1, 3)
assert {tuple(p) for p in paths} == {(1, 2, 3)}
def test_all_simple_paths_empty():
G = nx.path_graph(4)
paths = nx.all_simple_paths(G, 0, 3, cutoff=2)
assert list(paths) == []
def test_all_simple_paths_corner_cases():
assert list(nx.all_simple_paths(nx.empty_graph(2), 0, 0)) == [[0]]
assert list(nx.all_simple_paths(nx.empty_graph(2), 0, 1)) == []
assert list(nx.all_simple_paths(nx.path_graph(9), 0, 8, 0)) == []
def test_all_simple_paths_source_in_targets():
# See GitHub issue #6690.
G = nx.path_graph(3)
assert list(nx.all_simple_paths(G, 0, {0, 1, 2})) == [[0], [0, 1], [0, 1, 2]]
def hamiltonian_path(G, source):
source = arbitrary_element(G)
neighbors = set(G[source]) - {source}
n = len(G)
for target in neighbors:
for path in nx.all_simple_paths(G, source, target):
if len(path) == n:
yield path
def test_hamiltonian_path():
from itertools import permutations
G = nx.complete_graph(4)
paths = [list(p) for p in hamiltonian_path(G, 0)]
exact = [[0] + list(p) for p in permutations([1, 2, 3], 3)]
assert sorted(paths) == sorted(exact)
def test_cutoff_zero():
G = nx.complete_graph(4)
paths = nx.all_simple_paths(G, 0, 3, cutoff=0)
assert [list(p) for p in paths] == []
paths = nx.all_simple_paths(nx.MultiGraph(G), 0, 3, cutoff=0)
assert [list(p) for p in paths] == []
def test_source_missing():
with pytest.raises(nx.NodeNotFound):
G = nx.Graph()
nx.add_path(G, [1, 2, 3])
list(nx.all_simple_paths(nx.MultiGraph(G), 0, 3))
def test_target_missing():
with pytest.raises(nx.NodeNotFound):
G = nx.Graph()
nx.add_path(G, [1, 2, 3])
list(nx.all_simple_paths(nx.MultiGraph(G), 1, 4))
# Tests for all_simple_edge_paths
def test_all_simple_edge_paths():
G = nx.path_graph(4)
paths = nx.all_simple_edge_paths(G, 0, 3)
assert {tuple(p) for p in paths} == {((0, 1), (1, 2), (2, 3))}
def test_all_simple_edge_paths_empty_path():
G = nx.empty_graph(1)
assert list(nx.all_simple_edge_paths(G, 0, 0)) == [[]]
def test_all_simple_edge_paths_with_two_targets_emits_two_paths():
G = nx.path_graph(4)
G.add_edge(2, 4)
paths = nx.all_simple_edge_paths(G, 0, [3, 4])
assert {tuple(p) for p in paths} == {
((0, 1), (1, 2), (2, 3)),
((0, 1), (1, 2), (2, 4)),
}
def test_digraph_all_simple_edge_paths_with_two_targets_emits_two_paths():
G = nx.path_graph(4, create_using=nx.DiGraph())
G.add_edge(2, 4)
paths = nx.all_simple_edge_paths(G, 0, [3, 4])
assert {tuple(p) for p in paths} == {
((0, 1), (1, 2), (2, 3)),
((0, 1), (1, 2), (2, 4)),
}
def test_all_simple_edge_paths_with_two_targets_cutoff():
G = nx.path_graph(4)
G.add_edge(2, 4)
paths = nx.all_simple_edge_paths(G, 0, [3, 4], cutoff=3)
assert {tuple(p) for p in paths} == {
((0, 1), (1, 2), (2, 3)),
((0, 1), (1, 2), (2, 4)),
}
def test_digraph_all_simple_edge_paths_with_two_targets_cutoff():
G = nx.path_graph(4, create_using=nx.DiGraph())
G.add_edge(2, 4)
paths = nx.all_simple_edge_paths(G, 0, [3, 4], cutoff=3)
assert {tuple(p) for p in paths} == {
((0, 1), (1, 2), (2, 3)),
((0, 1), (1, 2), (2, 4)),
}
def test_all_simple_edge_paths_with_two_targets_in_line_emits_two_paths():
G = nx.path_graph(4)
paths = nx.all_simple_edge_paths(G, 0, [2, 3])
assert {tuple(p) for p in paths} == {((0, 1), (1, 2)), ((0, 1), (1, 2), (2, 3))}
def test_all_simple_edge_paths_ignores_cycle():
G = nx.cycle_graph(3, create_using=nx.DiGraph())
G.add_edge(1, 3)
paths = nx.all_simple_edge_paths(G, 0, 3)
assert {tuple(p) for p in paths} == {((0, 1), (1, 3))}
def test_all_simple_edge_paths_with_two_targets_inside_cycle_emits_two_paths():
G = nx.cycle_graph(3, create_using=nx.DiGraph())
G.add_edge(1, 3)
paths = nx.all_simple_edge_paths(G, 0, [2, 3])
assert {tuple(p) for p in paths} == {((0, 1), (1, 2)), ((0, 1), (1, 3))}
def test_all_simple_edge_paths_source_target():
G = nx.path_graph(4)
paths = nx.all_simple_edge_paths(G, 1, 1)
assert list(paths) == [[]]
def test_all_simple_edge_paths_cutoff():
G = nx.complete_graph(4)
paths = nx.all_simple_edge_paths(G, 0, 1, cutoff=1)
assert {tuple(p) for p in paths} == {((0, 1),)}
paths = nx.all_simple_edge_paths(G, 0, 1, cutoff=2)
assert {tuple(p) for p in paths} == {((0, 1),), ((0, 2), (2, 1)), ((0, 3), (3, 1))}
def test_all_simple_edge_paths_on_non_trivial_graph():
"""you may need to draw this graph to make sure it is reasonable"""
G = nx.path_graph(5, create_using=nx.DiGraph())
G.add_edges_from([(0, 5), (1, 5), (1, 3), (5, 4), (4, 2), (4, 3)])
paths = nx.all_simple_edge_paths(G, 1, [2, 3])
assert {tuple(p) for p in paths} == {
((1, 2),),
((1, 3), (3, 4), (4, 2)),
((1, 5), (5, 4), (4, 2)),
((1, 3),),
((1, 2), (2, 3)),
((1, 5), (5, 4), (4, 3)),
((1, 5), (5, 4), (4, 2), (2, 3)),
}
paths = nx.all_simple_edge_paths(G, 1, [2, 3], cutoff=3)
assert {tuple(p) for p in paths} == {
((1, 2),),
((1, 3), (3, 4), (4, 2)),
((1, 5), (5, 4), (4, 2)),
((1, 3),),
((1, 2), (2, 3)),
((1, 5), (5, 4), (4, 3)),
}
paths = nx.all_simple_edge_paths(G, 1, [2, 3], cutoff=2)
assert {tuple(p) for p in paths} == {((1, 2),), ((1, 3),), ((1, 2), (2, 3))}
def test_all_simple_edge_paths_multigraph():
G = nx.MultiGraph([(1, 2), (1, 2)])
paths = nx.all_simple_edge_paths(G, 1, 1)
assert list(paths) == [[]]
nx.add_path(G, [3, 1, 10, 2])
paths = list(nx.all_simple_edge_paths(G, 1, 2))
assert len(paths) == 3
assert {tuple(p) for p in paths} == {
((1, 2, 0),),
((1, 2, 1),),
((1, 10, 0), (10, 2, 0)),
}
def test_all_simple_edge_paths_multigraph_with_cutoff():
G = nx.MultiGraph([(1, 2), (1, 2), (1, 10), (10, 2)])
paths = list(nx.all_simple_edge_paths(G, 1, 2, cutoff=1))
assert len(paths) == 2
assert {tuple(p) for p in paths} == {((1, 2, 0),), ((1, 2, 1),)}
def test_all_simple_edge_paths_directed():
G = nx.DiGraph()
nx.add_path(G, [1, 2, 3])
nx.add_path(G, [3, 2, 1])
paths = nx.all_simple_edge_paths(G, 1, 3)
assert {tuple(p) for p in paths} == {((1, 2), (2, 3))}
def test_all_simple_edge_paths_empty():
G = nx.path_graph(4)
paths = nx.all_simple_edge_paths(G, 0, 3, cutoff=2)
assert list(paths) == []
def test_all_simple_edge_paths_corner_cases():
assert list(nx.all_simple_edge_paths(nx.empty_graph(2), 0, 0)) == [[]]
assert list(nx.all_simple_edge_paths(nx.empty_graph(2), 0, 1)) == []
assert list(nx.all_simple_edge_paths(nx.path_graph(9), 0, 8, 0)) == []
def test_all_simple_edge_paths_ignores_self_loop():
G = nx.Graph([(0, 0), (0, 1), (1, 1), (1, 2)])
assert list(nx.all_simple_edge_paths(G, 0, 2)) == [[(0, 1), (1, 2)]]
def hamiltonian_edge_path(G, source):
source = arbitrary_element(G)
neighbors = set(G[source]) - {source}
n = len(G)
for target in neighbors:
for path in nx.all_simple_edge_paths(G, source, target):
if len(path) == n - 1:
yield path
def test_hamiltonian__edge_path():
from itertools import permutations
G = nx.complete_graph(4)
paths = hamiltonian_edge_path(G, 0)
exact = [list(pairwise([0] + list(p))) for p in permutations([1, 2, 3], 3)]
assert sorted(exact) == sorted(paths)
def test_edge_cutoff_zero():
G = nx.complete_graph(4)
paths = nx.all_simple_edge_paths(G, 0, 3, cutoff=0)
assert [list(p) for p in paths] == []
paths = nx.all_simple_edge_paths(nx.MultiGraph(G), 0, 3, cutoff=0)
assert [list(p) for p in paths] == []
def test_edge_source_missing():
with pytest.raises(nx.NodeNotFound):
G = nx.Graph()
nx.add_path(G, [1, 2, 3])
list(nx.all_simple_edge_paths(nx.MultiGraph(G), 0, 3))
def test_edge_target_missing():
with pytest.raises(nx.NodeNotFound):
G = nx.Graph()
nx.add_path(G, [1, 2, 3])
list(nx.all_simple_edge_paths(nx.MultiGraph(G), 1, 4))
# Tests for shortest_simple_paths
def test_shortest_simple_paths():
G = cnlti(nx.grid_2d_graph(4, 4), first_label=1, ordering="sorted")
paths = nx.shortest_simple_paths(G, 1, 12)
assert next(paths) == [1, 2, 3, 4, 8, 12]
assert next(paths) == [1, 5, 6, 7, 8, 12]
assert [len(path) for path in nx.shortest_simple_paths(G, 1, 12)] == sorted(
len(path) for path in nx.all_simple_paths(G, 1, 12)
)
def test_shortest_simple_paths_singleton_path():
G = nx.empty_graph(3)
assert list(nx.shortest_simple_paths(G, 0, 0)) == [[0]]
def test_shortest_simple_paths_directed():
G = nx.cycle_graph(7, create_using=nx.DiGraph())
paths = nx.shortest_simple_paths(G, 0, 3)
assert list(paths) == [[0, 1, 2, 3]]
def test_shortest_simple_paths_directed_with_weight_function():
def cost(u, v, x):
return 1
G = cnlti(nx.grid_2d_graph(4, 4), first_label=1, ordering="sorted")
paths = nx.shortest_simple_paths(G, 1, 12)
assert next(paths) == [1, 2, 3, 4, 8, 12]
assert next(paths) == [1, 5, 6, 7, 8, 12]
assert [
len(path) for path in nx.shortest_simple_paths(G, 1, 12, weight=cost)
] == sorted(len(path) for path in nx.all_simple_paths(G, 1, 12))
def test_shortest_simple_paths_with_weight_function():
def cost(u, v, x):
return 1
G = nx.cycle_graph(7, create_using=nx.DiGraph())
paths = nx.shortest_simple_paths(G, 0, 3, weight=cost)
assert list(paths) == [[0, 1, 2, 3]]
def test_shortest_simple_paths_with_none_weight_function():
def cost(u, v, x):
delta = abs(u - v)
# ignore interior edges
return 1 if (delta == 1 or delta == 4) else None
G = nx.complete_graph(5)
paths = nx.shortest_simple_paths(G, 0, 2, weight=cost)
assert list(paths) == [[0, 1, 2], [0, 4, 3, 2]]
def test_Greg_Bernstein():
g1 = nx.Graph()
g1.add_nodes_from(["N0", "N1", "N2", "N3", "N4"])
g1.add_edge("N4", "N1", weight=10.0, capacity=50, name="L5")
g1.add_edge("N4", "N0", weight=7.0, capacity=40, name="L4")
g1.add_edge("N0", "N1", weight=10.0, capacity=45, name="L1")
g1.add_edge("N3", "N0", weight=10.0, capacity=50, name="L0")
g1.add_edge("N2", "N3", weight=12.0, capacity=30, name="L2")
g1.add_edge("N1", "N2", weight=15.0, capacity=42, name="L3")
solution = [["N1", "N0", "N3"], ["N1", "N2", "N3"], ["N1", "N4", "N0", "N3"]]
result = list(nx.shortest_simple_paths(g1, "N1", "N3", weight="weight"))
assert result == solution
def test_weighted_shortest_simple_path():
def cost_func(path):
return sum(G.adj[u][v]["weight"] for (u, v) in zip(path, path[1:]))
G = nx.complete_graph(5)
weight = {(u, v): random.randint(1, 100) for (u, v) in G.edges()}
nx.set_edge_attributes(G, weight, "weight")
cost = 0
for path in nx.shortest_simple_paths(G, 0, 3, weight="weight"):
this_cost = cost_func(path)
assert cost <= this_cost
cost = this_cost
def test_directed_weighted_shortest_simple_path():
def cost_func(path):
return sum(G.adj[u][v]["weight"] for (u, v) in zip(path, path[1:]))
G = nx.complete_graph(5)
G = G.to_directed()
weight = {(u, v): random.randint(1, 100) for (u, v) in G.edges()}
nx.set_edge_attributes(G, weight, "weight")
cost = 0
for path in nx.shortest_simple_paths(G, 0, 3, weight="weight"):
this_cost = cost_func(path)
assert cost <= this_cost
cost = this_cost
def test_weighted_shortest_simple_path_issue2427():
G = nx.Graph()
G.add_edge("IN", "OUT", weight=2)
G.add_edge("IN", "A", weight=1)
G.add_edge("IN", "B", weight=2)
G.add_edge("B", "OUT", weight=2)
assert list(nx.shortest_simple_paths(G, "IN", "OUT", weight="weight")) == [
["IN", "OUT"],
["IN", "B", "OUT"],
]
G = nx.Graph()
G.add_edge("IN", "OUT", weight=10)
G.add_edge("IN", "A", weight=1)
G.add_edge("IN", "B", weight=1)
G.add_edge("B", "OUT", weight=1)
assert list(nx.shortest_simple_paths(G, "IN", "OUT", weight="weight")) == [
["IN", "B", "OUT"],
["IN", "OUT"],
]
def test_directed_weighted_shortest_simple_path_issue2427():
G = nx.DiGraph()
G.add_edge("IN", "OUT", weight=2)
G.add_edge("IN", "A", weight=1)
G.add_edge("IN", "B", weight=2)
G.add_edge("B", "OUT", weight=2)
assert list(nx.shortest_simple_paths(G, "IN", "OUT", weight="weight")) == [
["IN", "OUT"],
["IN", "B", "OUT"],
]
G = nx.DiGraph()
G.add_edge("IN", "OUT", weight=10)
G.add_edge("IN", "A", weight=1)
G.add_edge("IN", "B", weight=1)
G.add_edge("B", "OUT", weight=1)
assert list(nx.shortest_simple_paths(G, "IN", "OUT", weight="weight")) == [
["IN", "B", "OUT"],
["IN", "OUT"],
]
def test_weight_name():
G = nx.cycle_graph(7)
nx.set_edge_attributes(G, 1, "weight")
nx.set_edge_attributes(G, 1, "foo")
G.adj[1][2]["foo"] = 7
paths = list(nx.shortest_simple_paths(G, 0, 3, weight="foo"))
solution = [[0, 6, 5, 4, 3], [0, 1, 2, 3]]
assert paths == solution
def test_ssp_source_missing():
with pytest.raises(nx.NodeNotFound):
G = nx.Graph()
nx.add_path(G, [1, 2, 3])
list(nx.shortest_simple_paths(G, 0, 3))
def test_ssp_target_missing():
with pytest.raises(nx.NodeNotFound):
G = nx.Graph()
nx.add_path(G, [1, 2, 3])
list(nx.shortest_simple_paths(G, 1, 4))
def test_ssp_multigraph():
with pytest.raises(nx.NetworkXNotImplemented):
G = nx.MultiGraph()
nx.add_path(G, [1, 2, 3])
list(nx.shortest_simple_paths(G, 1, 4))
def test_ssp_source_missing2():
with pytest.raises(nx.NetworkXNoPath):
G = nx.Graph()
nx.add_path(G, [0, 1, 2])
nx.add_path(G, [3, 4, 5])
list(nx.shortest_simple_paths(G, 0, 3))
def test_bidirectional_shortest_path_restricted_cycle():
cycle = nx.cycle_graph(7)
length, path = _bidirectional_shortest_path(cycle, 0, 3)
assert path == [0, 1, 2, 3]
length, path = _bidirectional_shortest_path(cycle, 0, 3, ignore_nodes=[1])
assert path == [0, 6, 5, 4, 3]
def test_bidirectional_shortest_path_restricted_wheel():
wheel = nx.wheel_graph(6)
length, path = _bidirectional_shortest_path(wheel, 1, 3)
assert path in [[1, 0, 3], [1, 2, 3]]
length, path = _bidirectional_shortest_path(wheel, 1, 3, ignore_nodes=[0])
assert path == [1, 2, 3]
length, path = _bidirectional_shortest_path(wheel, 1, 3, ignore_nodes=[0, 2])
assert path == [1, 5, 4, 3]
length, path = _bidirectional_shortest_path(
wheel, 1, 3, ignore_edges=[(1, 0), (5, 0), (2, 3)]
)
assert path in [[1, 2, 0, 3], [1, 5, 4, 3]]
def test_bidirectional_shortest_path_restricted_directed_cycle():
directed_cycle = nx.cycle_graph(7, create_using=nx.DiGraph())
length, path = _bidirectional_shortest_path(directed_cycle, 0, 3)
assert path == [0, 1, 2, 3]
pytest.raises(
nx.NetworkXNoPath,
_bidirectional_shortest_path,
directed_cycle,
0,
3,
ignore_nodes=[1],
)
length, path = _bidirectional_shortest_path(
directed_cycle, 0, 3, ignore_edges=[(2, 1)]
)
assert path == [0, 1, 2, 3]
pytest.raises(
nx.NetworkXNoPath,
_bidirectional_shortest_path,
directed_cycle,
0,
3,
ignore_edges=[(1, 2)],
)
def test_bidirectional_shortest_path_ignore():
G = nx.Graph()
nx.add_path(G, [1, 2])
nx.add_path(G, [1, 3])
nx.add_path(G, [1, 4])
pytest.raises(
nx.NetworkXNoPath, _bidirectional_shortest_path, G, 1, 2, ignore_nodes=[1]
)
pytest.raises(
nx.NetworkXNoPath, _bidirectional_shortest_path, G, 1, 2, ignore_nodes=[2]
)
G = nx.Graph()
nx.add_path(G, [1, 3])
nx.add_path(G, [1, 4])
nx.add_path(G, [3, 2])
pytest.raises(
nx.NetworkXNoPath, _bidirectional_shortest_path, G, 1, 2, ignore_nodes=[1, 2]
)
def validate_path(G, s, t, soln_len, path):
assert path[0] == s
assert path[-1] == t
assert soln_len == sum(
G[u][v].get("weight", 1) for u, v in zip(path[:-1], path[1:])
)
def validate_length_path(G, s, t, soln_len, length, path):
assert soln_len == length
validate_path(G, s, t, length, path)
def test_bidirectional_dijkstra_restricted():
XG = nx.DiGraph()
XG.add_weighted_edges_from(
[
("s", "u", 10),
("s", "x", 5),
("u", "v", 1),
("u", "x", 2),
("v", "y", 1),
("x", "u", 3),
("x", "v", 5),
("x", "y", 2),
("y", "s", 7),
("y", "v", 6),
]
)
XG3 = nx.Graph()
XG3.add_weighted_edges_from(
[[0, 1, 2], [1, 2, 12], [2, 3, 1], [3, 4, 5], [4, 5, 1], [5, 0, 10]]
)
validate_length_path(XG, "s", "v", 9, *_bidirectional_dijkstra(XG, "s", "v"))
validate_length_path(
XG, "s", "v", 10, *_bidirectional_dijkstra(XG, "s", "v", ignore_nodes=["u"])
)
validate_length_path(
XG,
"s",
"v",
11,
*_bidirectional_dijkstra(XG, "s", "v", ignore_edges=[("s", "x")]),
)
pytest.raises(
nx.NetworkXNoPath,
_bidirectional_dijkstra,
XG,
"s",
"v",
ignore_nodes=["u"],
ignore_edges=[("s", "x")],
)
validate_length_path(XG3, 0, 3, 15, *_bidirectional_dijkstra(XG3, 0, 3))
validate_length_path(
XG3, 0, 3, 16, *_bidirectional_dijkstra(XG3, 0, 3, ignore_nodes=[1])
)
validate_length_path(
XG3, 0, 3, 16, *_bidirectional_dijkstra(XG3, 0, 3, ignore_edges=[(2, 3)])
)
pytest.raises(
nx.NetworkXNoPath,
_bidirectional_dijkstra,
XG3,
0,
3,
ignore_nodes=[1],
ignore_edges=[(5, 4)],
)
def test_bidirectional_dijkstra_no_path():
with pytest.raises(nx.NetworkXNoPath):
G = nx.Graph()
nx.add_path(G, [1, 2, 3])
nx.add_path(G, [4, 5, 6])
_bidirectional_dijkstra(G, 1, 6)
def test_bidirectional_dijkstra_ignore():
G = nx.Graph()
nx.add_path(G, [1, 2, 10])
nx.add_path(G, [1, 3, 10])
pytest.raises(nx.NetworkXNoPath, _bidirectional_dijkstra, G, 1, 2, ignore_nodes=[1])
pytest.raises(nx.NetworkXNoPath, _bidirectional_dijkstra, G, 1, 2, ignore_nodes=[2])
pytest.raises(
nx.NetworkXNoPath, _bidirectional_dijkstra, G, 1, 2, ignore_nodes=[1, 2]
)
@@ -0,0 +1,76 @@
import pytest
pytest.importorskip("numpy")
import networkx as nx
from networkx import lattice_reference, omega, random_reference, sigma
rng = 42
def test_random_reference():
G = nx.connected_watts_strogatz_graph(50, 6, 0.1, seed=rng)
Gr = random_reference(G, niter=1, seed=rng)
C = nx.average_clustering(G)
Cr = nx.average_clustering(Gr)
assert C > Cr
with pytest.raises(nx.NetworkXError):
next(random_reference(nx.Graph()))
with pytest.raises(nx.NetworkXNotImplemented):
next(random_reference(nx.DiGraph()))
H = nx.Graph(((0, 1), (2, 3)))
Hl = random_reference(H, niter=1, seed=rng)
def test_lattice_reference():
G = nx.connected_watts_strogatz_graph(50, 6, 1, seed=rng)
Gl = lattice_reference(G, niter=1, seed=rng)
L = nx.average_shortest_path_length(G)
Ll = nx.average_shortest_path_length(Gl)
assert Ll > L
pytest.raises(nx.NetworkXError, lattice_reference, nx.Graph())
pytest.raises(nx.NetworkXNotImplemented, lattice_reference, nx.DiGraph())
H = nx.Graph(((0, 1), (2, 3)))
Hl = lattice_reference(H, niter=1)
def test_sigma():
Gs = nx.connected_watts_strogatz_graph(50, 6, 0.1, seed=rng)
Gr = nx.connected_watts_strogatz_graph(50, 6, 1, seed=rng)
sigmas = sigma(Gs, niter=1, nrand=2, seed=rng)
sigmar = sigma(Gr, niter=1, nrand=2, seed=rng)
assert sigmar < sigmas
def test_omega():
Gl = nx.connected_watts_strogatz_graph(50, 6, 0, seed=rng)
Gr = nx.connected_watts_strogatz_graph(50, 6, 1, seed=rng)
Gs = nx.connected_watts_strogatz_graph(50, 6, 0.1, seed=rng)
omegal = omega(Gl, niter=1, nrand=1, seed=rng)
omegar = omega(Gr, niter=1, nrand=1, seed=rng)
omegas = omega(Gs, niter=1, nrand=1, seed=rng)
assert omegal < omegas and omegas < omegar
# Test that omega lies within the [-1, 1] bounds
G_barbell = nx.barbell_graph(5, 1)
G_karate = nx.karate_club_graph()
omega_barbell = nx.omega(G_barbell)
omega_karate = nx.omega(G_karate, nrand=2)
omegas = (omegal, omegar, omegas, omega_barbell, omega_karate)
for o in omegas:
assert -1 <= o <= 1
@pytest.mark.parametrize("f", (nx.random_reference, nx.lattice_reference))
def test_graph_no_edges(f):
G = nx.Graph()
G.add_nodes_from([0, 1, 2, 3])
with pytest.raises(nx.NetworkXError, match="Graph has fewer that 2 edges"):
f(G)
@@ -0,0 +1,8 @@
import pytest
import networkx as nx
def test_smetric():
G = nx.Graph([(1, 2), (2, 3), (2, 4), (1, 4)])
assert nx.s_metric(G) == 19.0
@@ -0,0 +1,138 @@
"""Unit tests for the sparsifier computation functions."""
import pytest
import networkx as nx
from networkx.utils import py_random_state
_seed = 2
def _test_spanner(G, spanner, stretch, weight=None):
"""Test whether a spanner is valid.
This function tests whether the given spanner is a subgraph of the
given graph G with the same node set. It also tests for all shortest
paths whether they adhere to the given stretch.
Parameters
----------
G : NetworkX graph
The original graph for which the spanner was constructed.
spanner : NetworkX graph
The spanner to be tested.
stretch : float
The proclaimed stretch of the spanner.
weight : object
The edge attribute to use as distance.
"""
# check node set
assert set(G.nodes()) == set(spanner.nodes())
# check edge set and weights
for u, v in spanner.edges():
assert G.has_edge(u, v)
if weight:
assert spanner[u][v][weight] == G[u][v][weight]
# check connectivity and stretch
original_length = dict(nx.shortest_path_length(G, weight=weight))
spanner_length = dict(nx.shortest_path_length(spanner, weight=weight))
for u in G.nodes():
for v in G.nodes():
if u in original_length and v in original_length[u]:
assert spanner_length[u][v] <= stretch * original_length[u][v]
@py_random_state(1)
def _assign_random_weights(G, seed=None):
"""Assigns random weights to the edges of a graph.
Parameters
----------
G : NetworkX graph
The original graph for which the spanner was constructed.
seed : integer, random_state, or None (default)
Indicator of random number generation state.
See :ref:`Randomness<randomness>`.
"""
for u, v in G.edges():
G[u][v]["weight"] = seed.random()
def test_spanner_trivial():
"""Test a trivial spanner with stretch 1."""
G = nx.complete_graph(20)
spanner = nx.spanner(G, 1, seed=_seed)
for u, v in G.edges:
assert spanner.has_edge(u, v)
def test_spanner_unweighted_complete_graph():
"""Test spanner construction on a complete unweighted graph."""
G = nx.complete_graph(20)
spanner = nx.spanner(G, 4, seed=_seed)
_test_spanner(G, spanner, 4)
spanner = nx.spanner(G, 10, seed=_seed)
_test_spanner(G, spanner, 10)
def test_spanner_weighted_complete_graph():
"""Test spanner construction on a complete weighted graph."""
G = nx.complete_graph(20)
_assign_random_weights(G, seed=_seed)
spanner = nx.spanner(G, 4, weight="weight", seed=_seed)
_test_spanner(G, spanner, 4, weight="weight")
spanner = nx.spanner(G, 10, weight="weight", seed=_seed)
_test_spanner(G, spanner, 10, weight="weight")
def test_spanner_unweighted_gnp_graph():
"""Test spanner construction on an unweighted gnp graph."""
G = nx.gnp_random_graph(20, 0.4, seed=_seed)
spanner = nx.spanner(G, 4, seed=_seed)
_test_spanner(G, spanner, 4)
spanner = nx.spanner(G, 10, seed=_seed)
_test_spanner(G, spanner, 10)
def test_spanner_weighted_gnp_graph():
"""Test spanner construction on an weighted gnp graph."""
G = nx.gnp_random_graph(20, 0.4, seed=_seed)
_assign_random_weights(G, seed=_seed)
spanner = nx.spanner(G, 4, weight="weight", seed=_seed)
_test_spanner(G, spanner, 4, weight="weight")
spanner = nx.spanner(G, 10, weight="weight", seed=_seed)
_test_spanner(G, spanner, 10, weight="weight")
def test_spanner_unweighted_disconnected_graph():
"""Test spanner construction on a disconnected graph."""
G = nx.disjoint_union(nx.complete_graph(10), nx.complete_graph(10))
spanner = nx.spanner(G, 4, seed=_seed)
_test_spanner(G, spanner, 4)
spanner = nx.spanner(G, 10, seed=_seed)
_test_spanner(G, spanner, 10)
def test_spanner_invalid_stretch():
"""Check whether an invalid stretch is caught."""
with pytest.raises(ValueError):
G = nx.empty_graph()
nx.spanner(G, 0)
@@ -0,0 +1,191 @@
"""Unit tests for the :mod:`networkx.algorithms.structuralholes` module."""
import math
import pytest
import networkx as nx
from networkx.classes.tests import dispatch_interface
class TestStructuralHolesNoScipy:
"""Unit tests for computing measures of structural holes.
The expected values for these functions were originally computed using the
proprietary software `UCINET`_ and the free software `IGraph`_ , and then
computed by hand to make sure that the results are correct.
.. _UCINET: https://sites.google.com/site/ucinetsoftware/home
.. _IGraph: http://igraph.org/
"""
def setup_method(self):
self.D = nx.DiGraph()
self.D.add_edges_from([(0, 1), (0, 2), (1, 0), (2, 1)])
self.D_weights = {(0, 1): 2, (0, 2): 2, (1, 0): 1, (2, 1): 1}
# Example from http://www.analytictech.com/connections/v20(1)/holes.htm
self.G = nx.Graph()
self.G.add_edges_from(
[
("A", "B"),
("A", "F"),
("A", "G"),
("A", "E"),
("E", "G"),
("F", "G"),
("B", "G"),
("B", "D"),
("D", "G"),
("G", "C"),
]
)
self.G_weights = {
("A", "B"): 2,
("A", "F"): 3,
("A", "G"): 5,
("A", "E"): 2,
("E", "G"): 8,
("F", "G"): 3,
("B", "G"): 4,
("B", "D"): 1,
("D", "G"): 3,
("G", "C"): 10,
}
self.Dnodes = list(self.D)
self.Gnodes = list(self.G)
def test_constraint_directed(self):
constraint = nx.constraint(self.D, nodes=self.Dnodes)
assert constraint[0] == pytest.approx(1.003, abs=1e-3)
assert constraint[1] == pytest.approx(1.003, abs=1e-3)
assert constraint[2] == pytest.approx(1.389, abs=1e-3)
def test_effective_size_directed(self):
effective_size = nx.effective_size(self.D, nodes=self.Dnodes)
assert effective_size[0] == pytest.approx(1.167, abs=1e-3)
assert effective_size[1] == pytest.approx(1.167, abs=1e-3)
assert effective_size[2] == pytest.approx(1, abs=1e-3)
def test_constraint_weighted_directed(self):
D = self.D.copy()
nx.set_edge_attributes(D, self.D_weights, "weight")
constraint = nx.constraint(D, weight="weight", nodes=self.Dnodes)
assert constraint[0] == pytest.approx(0.840, abs=1e-3)
assert constraint[1] == pytest.approx(1.143, abs=1e-3)
assert constraint[2] == pytest.approx(1.378, abs=1e-3)
def test_effective_size_weighted_directed(self):
D = self.D.copy()
nx.set_edge_attributes(D, self.D_weights, "weight")
effective_size = nx.effective_size(D, weight="weight", nodes=self.Dnodes)
assert effective_size[0] == pytest.approx(1.567, abs=1e-3)
assert effective_size[1] == pytest.approx(1.083, abs=1e-3)
assert effective_size[2] == pytest.approx(1, abs=1e-3)
def test_constraint_undirected(self):
constraint = nx.constraint(self.G, nodes=self.Gnodes)
assert constraint["G"] == pytest.approx(0.400, abs=1e-3)
assert constraint["A"] == pytest.approx(0.595, abs=1e-3)
assert constraint["C"] == pytest.approx(1, abs=1e-3)
def test_effective_size_undirected_borgatti(self):
effective_size = nx.effective_size(self.G, nodes=self.Gnodes)
assert effective_size["G"] == pytest.approx(4.67, abs=1e-2)
assert effective_size["A"] == pytest.approx(2.50, abs=1e-2)
assert effective_size["C"] == pytest.approx(1, abs=1e-2)
def test_effective_size_undirected(self):
G = self.G.copy()
nx.set_edge_attributes(G, 1, "weight")
effective_size = nx.effective_size(G, weight="weight", nodes=self.Gnodes)
assert effective_size["G"] == pytest.approx(4.67, abs=1e-2)
assert effective_size["A"] == pytest.approx(2.50, abs=1e-2)
assert effective_size["C"] == pytest.approx(1, abs=1e-2)
def test_constraint_weighted_undirected(self):
G = self.G.copy()
nx.set_edge_attributes(G, self.G_weights, "weight")
constraint = nx.constraint(G, weight="weight", nodes=self.Gnodes)
assert constraint["G"] == pytest.approx(0.299, abs=1e-3)
assert constraint["A"] == pytest.approx(0.795, abs=1e-3)
assert constraint["C"] == pytest.approx(1, abs=1e-3)
def test_effective_size_weighted_undirected(self):
G = self.G.copy()
nx.set_edge_attributes(G, self.G_weights, "weight")
effective_size = nx.effective_size(G, weight="weight", nodes=self.Gnodes)
assert effective_size["G"] == pytest.approx(5.47, abs=1e-2)
assert effective_size["A"] == pytest.approx(2.47, abs=1e-2)
assert effective_size["C"] == pytest.approx(1, abs=1e-2)
def test_constraint_isolated(self):
G = self.G.copy()
G.add_node(1)
constraint = nx.constraint(G, nodes=self.Gnodes + [1])
assert math.isnan(constraint[1])
def test_effective_size_isolated(self):
G = self.G.copy()
G.add_node(1)
nx.set_edge_attributes(G, self.G_weights, "weight")
effective_size = nx.effective_size(G, weight="weight", nodes=self.Gnodes + [1])
assert math.isnan(effective_size[1])
def test_effective_size_borgatti_isolated(self):
G = self.G.copy()
G.add_node(1)
effective_size = nx.effective_size(G, nodes=self.Gnodes + [1])
assert math.isnan(effective_size[1])
class TestStructuralHoles(TestStructuralHolesNoScipy):
pytest.importorskip("scipy")
Dnodes = None
Gnodes = None
@pytest.mark.parametrize("graph", (nx.Graph, nx.DiGraph))
@pytest.mark.parametrize("nodes", (None, [0]))
def test_effective_size_isolated_node_with_selfloop(graph, nodes):
"""Behavior consistent with isolated node without self-loop. See gh-6916"""
G = graph([(0, 0)]) # Single node with one self-edge
assert math.isnan(nx.effective_size(G, nodes=nodes)[0])
@pytest.mark.parametrize("graph", (nx.Graph, nx.DiGraph))
@pytest.mark.parametrize("nodes", (None, [0]))
def test_effective_size_isolated_node_with_selfloop_weighted(graph, nodes):
"""Weighted self-loop. See gh-6916"""
G = graph()
G.add_weighted_edges_from([(0, 0, 10)])
assert math.isnan(nx.effective_size(G, nodes=nodes)[0])
@pytest.mark.parametrize("graph", (nx.Graph, nx.DiGraph))
def test_constraint_isolated_node_with_selfloop(graph):
"""Behavior consistent with isolated node without self-loop. See gh-6916"""
G = graph([(0, 0)]) # Single node with one self-edge
assert math.isnan(nx.constraint(G)[0])
@pytest.mark.parametrize("graph", (nx.Graph, nx.DiGraph))
def test_constraint_isolated_node_with_selfloop_using_nodes_kwarg(graph):
"""Behavior consistent with isolated node without self-loop. See gh-6916"""
G = graph([(0, 0)]) # Single node with one self-edge
assert nx.constraint(G, nodes=[0])[0] == 4
@pytest.mark.parametrize("graph", (nx.Graph, nx.DiGraph))
def test_constraint_isolated_node_with_selfloop_weighted(graph):
"""Weighted self-loop. See gh-6916"""
G = graph()
G.add_weighted_edges_from([(0, 0, 10)])
assert math.isnan(nx.constraint(G)[0])
@pytest.mark.parametrize("graph", (nx.Graph, nx.DiGraph))
def test_constraint_isolated_node_with_selfloop_weighted_using_nodes_kwarg(graph):
G = graph()
G.add_weighted_edges_from([(0, 0, 10)])
assert nx.constraint(G, nodes=[0])[0] == 4
@@ -0,0 +1,642 @@
"""
Unit tests for dedensification and graph summarization
"""
import pytest
import networkx as nx
class TestDirectedDedensification:
def build_original_graph(self):
original_matrix = [
("1", "BC"),
("2", "ABC"),
("3", ["A", "B", "6"]),
("4", "ABC"),
("5", "AB"),
("6", ["5"]),
("A", ["6"]),
]
graph = nx.DiGraph()
for source, targets in original_matrix:
for target in targets:
graph.add_edge(source, target)
return graph
def build_compressed_graph(self):
compressed_matrix = [
("1", "BC"),
("2", ["ABC"]),
("3", ["A", "B", "6"]),
("4", ["ABC"]),
("5", "AB"),
("6", ["5"]),
("A", ["6"]),
("ABC", "ABC"),
]
compressed_graph = nx.DiGraph()
for source, targets in compressed_matrix:
for target in targets:
compressed_graph.add_edge(source, target)
return compressed_graph
def test_empty(self):
"""
Verify that an empty directed graph results in no compressor nodes
"""
G = nx.DiGraph()
compressed_graph, c_nodes = nx.dedensify(G, threshold=2)
assert c_nodes == set()
@staticmethod
def densify(G, compressor_nodes, copy=True):
"""
Reconstructs the original graph from a dedensified, directed graph
Parameters
----------
G: dedensified graph
A networkx graph
compressor_nodes: iterable
Iterable of compressor nodes in the dedensified graph
inplace: bool, optional (default: False)
Indicates if densification should be done inplace
Returns
-------
G: graph
A densified networkx graph
"""
if copy:
G = G.copy()
for compressor_node in compressor_nodes:
all_neighbors = set(nx.all_neighbors(G, compressor_node))
out_neighbors = set(G.neighbors(compressor_node))
for out_neighbor in out_neighbors:
G.remove_edge(compressor_node, out_neighbor)
in_neighbors = all_neighbors - out_neighbors
for in_neighbor in in_neighbors:
G.remove_edge(in_neighbor, compressor_node)
for out_neighbor in out_neighbors:
G.add_edge(in_neighbor, out_neighbor)
G.remove_node(compressor_node)
return G
def setup_method(self):
self.c_nodes = ("ABC",)
def test_dedensify_edges(self):
"""
Verifies that dedensify produced the correct edges to/from compressor
nodes in a directed graph
"""
G = self.build_original_graph()
compressed_G = self.build_compressed_graph()
compressed_graph, c_nodes = nx.dedensify(G, threshold=2)
for s, t in compressed_graph.edges():
o_s = "".join(sorted(s))
o_t = "".join(sorted(t))
compressed_graph_exists = compressed_graph.has_edge(s, t)
verified_compressed_exists = compressed_G.has_edge(o_s, o_t)
assert compressed_graph_exists == verified_compressed_exists
assert len(c_nodes) == len(self.c_nodes)
def test_dedensify_edge_count(self):
"""
Verifies that dedensify produced the correct number of compressor nodes
in a directed graph
"""
G = self.build_original_graph()
original_edge_count = len(G.edges())
c_G, c_nodes = nx.dedensify(G, threshold=2)
compressed_edge_count = len(c_G.edges())
assert compressed_edge_count <= original_edge_count
compressed_G = self.build_compressed_graph()
assert compressed_edge_count == len(compressed_G.edges())
def test_densify_edges(self):
"""
Verifies that densification produces the correct edges from the
original directed graph
"""
compressed_G = self.build_compressed_graph()
original_graph = self.densify(compressed_G, self.c_nodes, copy=True)
G = self.build_original_graph()
for s, t in G.edges():
assert G.has_edge(s, t) == original_graph.has_edge(s, t)
def test_densify_edge_count(self):
"""
Verifies that densification produces the correct number of edges in the
original directed graph
"""
compressed_G = self.build_compressed_graph()
compressed_edge_count = len(compressed_G.edges())
original_graph = self.densify(compressed_G, self.c_nodes)
original_edge_count = len(original_graph.edges())
assert compressed_edge_count <= original_edge_count
G = self.build_original_graph()
assert original_edge_count == len(G.edges())
class TestUnDirectedDedensification:
def build_original_graph(self):
"""
Builds graph shown in the original research paper
"""
original_matrix = [
("1", "CB"),
("2", "ABC"),
("3", ["A", "B", "6"]),
("4", "ABC"),
("5", "AB"),
("6", ["5"]),
("A", ["6"]),
]
graph = nx.Graph()
for source, targets in original_matrix:
for target in targets:
graph.add_edge(source, target)
return graph
def test_empty(self):
"""
Verify that an empty undirected graph results in no compressor nodes
"""
G = nx.Graph()
compressed_G, c_nodes = nx.dedensify(G, threshold=2)
assert c_nodes == set()
def setup_method(self):
self.c_nodes = ("6AB", "ABC")
def build_compressed_graph(self):
compressed_matrix = [
("1", ["B", "C"]),
("2", ["ABC"]),
("3", ["6AB"]),
("4", ["ABC"]),
("5", ["6AB"]),
("6", ["6AB", "A"]),
("A", ["6AB", "ABC"]),
("B", ["ABC", "6AB"]),
("C", ["ABC"]),
]
compressed_graph = nx.Graph()
for source, targets in compressed_matrix:
for target in targets:
compressed_graph.add_edge(source, target)
return compressed_graph
def test_dedensify_edges(self):
"""
Verifies that dedensify produced correct compressor nodes and the
correct edges to/from the compressor nodes in an undirected graph
"""
G = self.build_original_graph()
c_G, c_nodes = nx.dedensify(G, threshold=2)
v_compressed_G = self.build_compressed_graph()
for s, t in c_G.edges():
o_s = "".join(sorted(s))
o_t = "".join(sorted(t))
has_compressed_edge = c_G.has_edge(s, t)
verified_has_compressed_edge = v_compressed_G.has_edge(o_s, o_t)
assert has_compressed_edge == verified_has_compressed_edge
assert len(c_nodes) == len(self.c_nodes)
def test_dedensify_edge_count(self):
"""
Verifies that dedensify produced the correct number of edges in an
undirected graph
"""
G = self.build_original_graph()
c_G, c_nodes = nx.dedensify(G, threshold=2, copy=True)
compressed_edge_count = len(c_G.edges())
verified_original_edge_count = len(G.edges())
assert compressed_edge_count <= verified_original_edge_count
verified_compressed_G = self.build_compressed_graph()
verified_compressed_edge_count = len(verified_compressed_G.edges())
assert compressed_edge_count == verified_compressed_edge_count
@pytest.mark.parametrize(
"graph_type", [nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph]
)
def test_summarization_empty(graph_type):
G = graph_type()
summary_graph = nx.snap_aggregation(G, node_attributes=("color",))
assert nx.is_isomorphic(summary_graph, G)
class AbstractSNAP:
node_attributes = ("color",)
def build_original_graph(self):
pass
def build_summary_graph(self):
pass
def test_summary_graph(self):
original_graph = self.build_original_graph()
summary_graph = self.build_summary_graph()
relationship_attributes = ("type",)
generated_summary_graph = nx.snap_aggregation(
original_graph, self.node_attributes, relationship_attributes
)
relabeled_summary_graph = self.deterministic_labels(generated_summary_graph)
assert nx.is_isomorphic(summary_graph, relabeled_summary_graph)
def deterministic_labels(self, G):
node_labels = list(G.nodes)
node_labels = sorted(node_labels, key=lambda n: sorted(G.nodes[n]["group"])[0])
node_labels.sort()
label_mapping = {}
for index, node in enumerate(node_labels):
label = f"Supernode-{index}"
label_mapping[node] = label
return nx.relabel_nodes(G, label_mapping)
class TestSNAPNoEdgeTypes(AbstractSNAP):
relationship_attributes = ()
def test_summary_graph(self):
original_graph = self.build_original_graph()
summary_graph = self.build_summary_graph()
relationship_attributes = ("type",)
generated_summary_graph = nx.snap_aggregation(
original_graph, self.node_attributes
)
relabeled_summary_graph = self.deterministic_labels(generated_summary_graph)
assert nx.is_isomorphic(summary_graph, relabeled_summary_graph)
def build_original_graph(self):
nodes = {
"A": {"color": "Red"},
"B": {"color": "Red"},
"C": {"color": "Red"},
"D": {"color": "Red"},
"E": {"color": "Blue"},
"F": {"color": "Blue"},
"G": {"color": "Blue"},
"H": {"color": "Blue"},
"I": {"color": "Yellow"},
"J": {"color": "Yellow"},
"K": {"color": "Yellow"},
"L": {"color": "Yellow"},
}
edges = [
("A", "B"),
("A", "C"),
("A", "E"),
("A", "I"),
("B", "D"),
("B", "J"),
("B", "F"),
("C", "G"),
("D", "H"),
("I", "J"),
("J", "K"),
("I", "L"),
]
G = nx.Graph()
for node in nodes:
attributes = nodes[node]
G.add_node(node, **attributes)
for source, target in edges:
G.add_edge(source, target)
return G
def build_summary_graph(self):
nodes = {
"Supernode-0": {"color": "Red"},
"Supernode-1": {"color": "Red"},
"Supernode-2": {"color": "Blue"},
"Supernode-3": {"color": "Blue"},
"Supernode-4": {"color": "Yellow"},
"Supernode-5": {"color": "Yellow"},
}
edges = [
("Supernode-0", "Supernode-0"),
("Supernode-0", "Supernode-1"),
("Supernode-0", "Supernode-2"),
("Supernode-0", "Supernode-4"),
("Supernode-1", "Supernode-3"),
("Supernode-4", "Supernode-4"),
("Supernode-4", "Supernode-5"),
]
G = nx.Graph()
for node in nodes:
attributes = nodes[node]
G.add_node(node, **attributes)
for source, target in edges:
G.add_edge(source, target)
supernodes = {
"Supernode-0": {"A", "B"},
"Supernode-1": {"C", "D"},
"Supernode-2": {"E", "F"},
"Supernode-3": {"G", "H"},
"Supernode-4": {"I", "J"},
"Supernode-5": {"K", "L"},
}
nx.set_node_attributes(G, supernodes, "group")
return G
class TestSNAPUndirected(AbstractSNAP):
def build_original_graph(self):
nodes = {
"A": {"color": "Red"},
"B": {"color": "Red"},
"C": {"color": "Red"},
"D": {"color": "Red"},
"E": {"color": "Blue"},
"F": {"color": "Blue"},
"G": {"color": "Blue"},
"H": {"color": "Blue"},
"I": {"color": "Yellow"},
"J": {"color": "Yellow"},
"K": {"color": "Yellow"},
"L": {"color": "Yellow"},
}
edges = [
("A", "B", "Strong"),
("A", "C", "Weak"),
("A", "E", "Strong"),
("A", "I", "Weak"),
("B", "D", "Weak"),
("B", "J", "Weak"),
("B", "F", "Strong"),
("C", "G", "Weak"),
("D", "H", "Weak"),
("I", "J", "Strong"),
("J", "K", "Strong"),
("I", "L", "Strong"),
]
G = nx.Graph()
for node in nodes:
attributes = nodes[node]
G.add_node(node, **attributes)
for source, target, type in edges:
G.add_edge(source, target, type=type)
return G
def build_summary_graph(self):
nodes = {
"Supernode-0": {"color": "Red"},
"Supernode-1": {"color": "Red"},
"Supernode-2": {"color": "Blue"},
"Supernode-3": {"color": "Blue"},
"Supernode-4": {"color": "Yellow"},
"Supernode-5": {"color": "Yellow"},
}
edges = [
("Supernode-0", "Supernode-0", "Strong"),
("Supernode-0", "Supernode-1", "Weak"),
("Supernode-0", "Supernode-2", "Strong"),
("Supernode-0", "Supernode-4", "Weak"),
("Supernode-1", "Supernode-3", "Weak"),
("Supernode-4", "Supernode-4", "Strong"),
("Supernode-4", "Supernode-5", "Strong"),
]
G = nx.Graph()
for node in nodes:
attributes = nodes[node]
G.add_node(node, **attributes)
for source, target, type in edges:
G.add_edge(source, target, types=[{"type": type}])
supernodes = {
"Supernode-0": {"A", "B"},
"Supernode-1": {"C", "D"},
"Supernode-2": {"E", "F"},
"Supernode-3": {"G", "H"},
"Supernode-4": {"I", "J"},
"Supernode-5": {"K", "L"},
}
nx.set_node_attributes(G, supernodes, "group")
return G
class TestSNAPDirected(AbstractSNAP):
def build_original_graph(self):
nodes = {
"A": {"color": "Red"},
"B": {"color": "Red"},
"C": {"color": "Green"},
"D": {"color": "Green"},
"E": {"color": "Blue"},
"F": {"color": "Blue"},
"G": {"color": "Yellow"},
"H": {"color": "Yellow"},
}
edges = [
("A", "C", "Strong"),
("A", "E", "Strong"),
("A", "F", "Weak"),
("B", "D", "Strong"),
("B", "E", "Weak"),
("B", "F", "Strong"),
("C", "G", "Strong"),
("C", "F", "Strong"),
("D", "E", "Strong"),
("D", "H", "Strong"),
("G", "E", "Strong"),
("H", "F", "Strong"),
]
G = nx.DiGraph()
for node in nodes:
attributes = nodes[node]
G.add_node(node, **attributes)
for source, target, type in edges:
G.add_edge(source, target, type=type)
return G
def build_summary_graph(self):
nodes = {
"Supernode-0": {"color": "Red"},
"Supernode-1": {"color": "Green"},
"Supernode-2": {"color": "Blue"},
"Supernode-3": {"color": "Yellow"},
}
edges = [
("Supernode-0", "Supernode-1", [{"type": "Strong"}]),
("Supernode-0", "Supernode-2", [{"type": "Weak"}, {"type": "Strong"}]),
("Supernode-1", "Supernode-2", [{"type": "Strong"}]),
("Supernode-1", "Supernode-3", [{"type": "Strong"}]),
("Supernode-3", "Supernode-2", [{"type": "Strong"}]),
]
G = nx.DiGraph()
for node in nodes:
attributes = nodes[node]
G.add_node(node, **attributes)
for source, target, types in edges:
G.add_edge(source, target, types=types)
supernodes = {
"Supernode-0": {"A", "B"},
"Supernode-1": {"C", "D"},
"Supernode-2": {"E", "F"},
"Supernode-3": {"G", "H"},
"Supernode-4": {"I", "J"},
"Supernode-5": {"K", "L"},
}
nx.set_node_attributes(G, supernodes, "group")
return G
class TestSNAPUndirectedMulti(AbstractSNAP):
def build_original_graph(self):
nodes = {
"A": {"color": "Red"},
"B": {"color": "Red"},
"C": {"color": "Red"},
"D": {"color": "Blue"},
"E": {"color": "Blue"},
"F": {"color": "Blue"},
"G": {"color": "Yellow"},
"H": {"color": "Yellow"},
"I": {"color": "Yellow"},
}
edges = [
("A", "D", ["Weak", "Strong"]),
("B", "E", ["Weak", "Strong"]),
("D", "I", ["Strong"]),
("E", "H", ["Strong"]),
("F", "G", ["Weak"]),
("I", "G", ["Weak", "Strong"]),
("I", "H", ["Weak", "Strong"]),
("G", "H", ["Weak", "Strong"]),
]
G = nx.MultiGraph()
for node in nodes:
attributes = nodes[node]
G.add_node(node, **attributes)
for source, target, types in edges:
for type in types:
G.add_edge(source, target, type=type)
return G
def build_summary_graph(self):
nodes = {
"Supernode-0": {"color": "Red"},
"Supernode-1": {"color": "Blue"},
"Supernode-2": {"color": "Yellow"},
"Supernode-3": {"color": "Blue"},
"Supernode-4": {"color": "Yellow"},
"Supernode-5": {"color": "Red"},
}
edges = [
("Supernode-1", "Supernode-2", [{"type": "Weak"}]),
("Supernode-2", "Supernode-4", [{"type": "Weak"}, {"type": "Strong"}]),
("Supernode-3", "Supernode-4", [{"type": "Strong"}]),
("Supernode-3", "Supernode-5", [{"type": "Weak"}, {"type": "Strong"}]),
("Supernode-4", "Supernode-4", [{"type": "Weak"}, {"type": "Strong"}]),
]
G = nx.MultiGraph()
for node in nodes:
attributes = nodes[node]
G.add_node(node, **attributes)
for source, target, types in edges:
for type in types:
G.add_edge(source, target, type=type)
supernodes = {
"Supernode-0": {"A", "B"},
"Supernode-1": {"C", "D"},
"Supernode-2": {"E", "F"},
"Supernode-3": {"G", "H"},
"Supernode-4": {"I", "J"},
"Supernode-5": {"K", "L"},
}
nx.set_node_attributes(G, supernodes, "group")
return G
class TestSNAPDirectedMulti(AbstractSNAP):
def build_original_graph(self):
nodes = {
"A": {"color": "Red"},
"B": {"color": "Red"},
"C": {"color": "Green"},
"D": {"color": "Green"},
"E": {"color": "Blue"},
"F": {"color": "Blue"},
"G": {"color": "Yellow"},
"H": {"color": "Yellow"},
}
edges = [
("A", "C", ["Weak", "Strong"]),
("A", "E", ["Strong"]),
("A", "F", ["Weak"]),
("B", "D", ["Weak", "Strong"]),
("B", "E", ["Weak"]),
("B", "F", ["Strong"]),
("C", "G", ["Weak", "Strong"]),
("C", "F", ["Strong"]),
("D", "E", ["Strong"]),
("D", "H", ["Weak", "Strong"]),
("G", "E", ["Strong"]),
("H", "F", ["Strong"]),
]
G = nx.MultiDiGraph()
for node in nodes:
attributes = nodes[node]
G.add_node(node, **attributes)
for source, target, types in edges:
for type in types:
G.add_edge(source, target, type=type)
return G
def build_summary_graph(self):
nodes = {
"Supernode-0": {"color": "Red"},
"Supernode-1": {"color": "Blue"},
"Supernode-2": {"color": "Yellow"},
"Supernode-3": {"color": "Blue"},
}
edges = [
("Supernode-0", "Supernode-1", ["Weak", "Strong"]),
("Supernode-0", "Supernode-2", ["Weak", "Strong"]),
("Supernode-1", "Supernode-2", ["Strong"]),
("Supernode-1", "Supernode-3", ["Weak", "Strong"]),
("Supernode-3", "Supernode-2", ["Strong"]),
]
G = nx.MultiDiGraph()
for node in nodes:
attributes = nodes[node]
G.add_node(node, **attributes)
for source, target, types in edges:
for type in types:
G.add_edge(source, target, type=type)
supernodes = {
"Supernode-0": {"A", "B"},
"Supernode-1": {"C", "D"},
"Supernode-2": {"E", "F"},
"Supernode-3": {"G", "H"},
}
nx.set_node_attributes(G, supernodes, "group")
return G
@@ -0,0 +1,179 @@
import pytest
import networkx as nx
cycle = nx.cycle_graph(5, create_using=nx.DiGraph)
tree = nx.DiGraph()
tree.add_edges_from(nx.random_labeled_tree(10, seed=42).edges)
path = nx.path_graph(5, create_using=nx.DiGraph)
binomial = nx.binomial_tree(3, create_using=nx.DiGraph)
HH = nx.directed_havel_hakimi_graph([1, 2, 1, 2, 2, 2], [3, 1, 0, 1, 2, 3])
balanced_tree = nx.balanced_tree(2, 3, create_using=nx.DiGraph)
@pytest.mark.parametrize("G", [path, binomial, HH, cycle, tree, balanced_tree])
def test_directed_edge_swap(G):
in_degree = set(G.in_degree)
out_degree = set(G.out_degree)
edges = set(G.edges)
nx.directed_edge_swap(G, nswap=1, max_tries=100, seed=1)
assert in_degree == set(G.in_degree)
assert out_degree == set(G.out_degree)
assert edges != set(G.edges)
assert 3 == sum(e not in edges for e in G.edges)
def test_directed_edge_swap_undo_previous_swap():
G = nx.DiGraph(nx.path_graph(4).edges) # only 1 swap possible
edges = set(G.edges)
nx.directed_edge_swap(G, nswap=2, max_tries=100)
assert edges == set(G.edges)
nx.directed_edge_swap(G, nswap=1, max_tries=100, seed=1)
assert {(0, 2), (1, 3), (2, 1)} == set(G.edges)
nx.directed_edge_swap(G, nswap=1, max_tries=100, seed=1)
assert edges == set(G.edges)
def test_edge_cases_directed_edge_swap():
# Tests cases when swaps are impossible, either too few edges exist, or self loops/cycles are unavoidable
# TODO: Rewrite function to explicitly check for impossible swaps and raise error
e = (
"Maximum number of swap attempts \\(11\\) exceeded "
"before desired swaps achieved \\(\\d\\)."
)
graph = nx.DiGraph([(0, 0), (0, 1), (1, 0), (2, 3), (3, 2)])
with pytest.raises(nx.NetworkXAlgorithmError, match=e):
nx.directed_edge_swap(graph, nswap=1, max_tries=10, seed=1)
def test_double_edge_swap():
graph = nx.barabasi_albert_graph(200, 1)
degrees = sorted(d for n, d in graph.degree())
G = nx.double_edge_swap(graph, 40)
assert degrees == sorted(d for n, d in graph.degree())
def test_double_edge_swap_seed():
graph = nx.barabasi_albert_graph(200, 1)
degrees = sorted(d for n, d in graph.degree())
G = nx.double_edge_swap(graph, 40, seed=1)
assert degrees == sorted(d for n, d in graph.degree())
def test_connected_double_edge_swap():
graph = nx.barabasi_albert_graph(200, 1)
degrees = sorted(d for n, d in graph.degree())
G = nx.connected_double_edge_swap(graph, 40, seed=1)
assert nx.is_connected(graph)
assert degrees == sorted(d for n, d in graph.degree())
def test_connected_double_edge_swap_low_window_threshold():
graph = nx.barabasi_albert_graph(200, 1)
degrees = sorted(d for n, d in graph.degree())
G = nx.connected_double_edge_swap(graph, 40, _window_threshold=0, seed=1)
assert nx.is_connected(graph)
assert degrees == sorted(d for n, d in graph.degree())
def test_connected_double_edge_swap_star():
# Testing ui==xi in connected_double_edge_swap
graph = nx.star_graph(40)
degrees = sorted(d for n, d in graph.degree())
G = nx.connected_double_edge_swap(graph, 1, seed=4)
assert nx.is_connected(graph)
assert degrees == sorted(d for n, d in graph.degree())
def test_connected_double_edge_swap_star_low_window_threshold():
# Testing ui==xi in connected_double_edge_swap with low window threshold
graph = nx.star_graph(40)
degrees = sorted(d for n, d in graph.degree())
G = nx.connected_double_edge_swap(graph, 1, _window_threshold=0, seed=4)
assert nx.is_connected(graph)
assert degrees == sorted(d for n, d in graph.degree())
def test_directed_edge_swap_small():
with pytest.raises(nx.NetworkXError):
G = nx.directed_edge_swap(nx.path_graph(3, create_using=nx.DiGraph))
def test_directed_edge_swap_tries():
with pytest.raises(nx.NetworkXError):
G = nx.directed_edge_swap(
nx.path_graph(3, create_using=nx.DiGraph), nswap=1, max_tries=0
)
def test_directed_exception_undirected():
graph = nx.Graph([(0, 1), (2, 3)])
with pytest.raises(nx.NetworkXNotImplemented):
G = nx.directed_edge_swap(graph)
def test_directed_edge_max_tries():
with pytest.raises(nx.NetworkXAlgorithmError):
G = nx.directed_edge_swap(
nx.complete_graph(4, nx.DiGraph()), nswap=1, max_tries=5
)
def test_double_edge_swap_small():
with pytest.raises(nx.NetworkXError):
G = nx.double_edge_swap(nx.path_graph(3))
def test_double_edge_swap_tries():
with pytest.raises(nx.NetworkXError):
G = nx.double_edge_swap(nx.path_graph(10), nswap=1, max_tries=0)
def test_double_edge_directed():
graph = nx.DiGraph([(0, 1), (2, 3)])
with pytest.raises(nx.NetworkXError, match="not defined for directed graphs."):
G = nx.double_edge_swap(graph)
def test_double_edge_max_tries():
with pytest.raises(nx.NetworkXAlgorithmError):
G = nx.double_edge_swap(nx.complete_graph(4), nswap=1, max_tries=5)
def test_connected_double_edge_swap_small():
with pytest.raises(nx.NetworkXError):
G = nx.connected_double_edge_swap(nx.path_graph(3))
def test_connected_double_edge_swap_not_connected():
with pytest.raises(nx.NetworkXError):
G = nx.path_graph(3)
nx.add_path(G, [10, 11, 12])
G = nx.connected_double_edge_swap(G)
def test_degree_seq_c4():
G = nx.cycle_graph(4)
degrees = sorted(d for n, d in G.degree())
G = nx.double_edge_swap(G, 1, 100)
assert degrees == sorted(d for n, d in G.degree())
def test_fewer_than_4_nodes():
G = nx.DiGraph()
G.add_nodes_from([0, 1, 2])
with pytest.raises(nx.NetworkXError, match=".*fewer than four nodes."):
nx.directed_edge_swap(G)
def test_less_than_3_edges():
G = nx.DiGraph([(0, 1), (1, 2)])
G.add_nodes_from([3, 4])
with pytest.raises(nx.NetworkXError, match=".*fewer than 3 edges"):
nx.directed_edge_swap(G)
G = nx.Graph()
G.add_nodes_from([0, 1, 2, 3])
with pytest.raises(nx.NetworkXError, match=".*fewer than 2 edges"):
nx.double_edge_swap(G)
@@ -0,0 +1,270 @@
"""
Threshold Graphs
================
"""
import pytest
import networkx as nx
import networkx.algorithms.threshold as nxt
cnlti = nx.convert_node_labels_to_integers
def test_threshold_graph_invalid_creation_sequence():
bad_creation_sequence = [2.0, 2, 1, 0] # floats are not allowed
with pytest.raises(ValueError, match="not a valid creation sequence"):
nxt.threshold_graph(bad_creation_sequence)
class TestGeneratorThreshold:
def test_threshold_sequence_graph_test(self):
G = nx.star_graph(10)
assert nxt.is_threshold_graph(G)
assert nxt.is_threshold_sequence([d for n, d in G.degree()])
G = nx.complete_graph(10)
assert nxt.is_threshold_graph(G)
assert nxt.is_threshold_sequence([d for n, d in G.degree()])
deg = [3, 2, 2, 1, 1, 1]
assert not nxt.is_threshold_sequence(deg)
deg = [3, 2, 2, 1]
assert nxt.is_threshold_sequence(deg)
G = nx.generators.havel_hakimi_graph(deg)
assert nxt.is_threshold_graph(G)
def test_creation_sequences(self):
deg = [3, 2, 2, 1]
G = nx.generators.havel_hakimi_graph(deg)
with pytest.raises(ValueError):
nxt.creation_sequence(deg, with_labels=True, compact=True)
cs0 = nxt.creation_sequence(deg)
H0 = nxt.threshold_graph(cs0)
assert "".join(cs0) == "ddid"
cs1 = nxt.creation_sequence(deg, with_labels=True)
H1 = nxt.threshold_graph(cs1)
assert cs1 == [(1, "d"), (2, "d"), (3, "i"), (0, "d")]
cs2 = nxt.creation_sequence(deg, compact=True)
H2 = nxt.threshold_graph(cs2)
assert cs2 == [2, 1, 1]
assert "".join(nxt.uncompact(cs2)) == "ddid"
assert nx.could_be_isomorphic(H0, G)
assert nx.could_be_isomorphic(H0, H1)
assert nx.could_be_isomorphic(H0, H2)
def test_make_compact(self):
assert nxt.make_compact(["d", "d", "d", "i", "d", "d"]) == [3, 1, 2]
assert nxt.make_compact([3, 1, 2]) == [3, 1, 2]
pytest.raises(TypeError, nxt.make_compact, [3.0, 1.0, 2.0])
def test_uncompact(self):
assert nxt.uncompact([3, 1, 2]) == ["d", "d", "d", "i", "d", "d"]
assert nxt.uncompact(["d", "d", "i", "d"]) == ["d", "d", "i", "d"]
assert nxt.uncompact(
nxt.uncompact([(1, "d"), (2, "d"), (3, "i"), (0, "d")])
) == nxt.uncompact([(1, "d"), (2, "d"), (3, "i"), (0, "d")])
pytest.raises(TypeError, nxt.uncompact, [3.0, 1.0, 2.0])
def test_creation_sequence_to_weights(self):
assert nxt.creation_sequence_to_weights([3, 1, 2]) == [
0.5,
0.5,
0.5,
0.25,
0.75,
0.75,
]
pytest.raises(TypeError, nxt.creation_sequence_to_weights, [3.0, 1.0, 2.0])
def test_weights_to_creation_sequence(self):
deg = [3, 2, 2, 1]
with pytest.raises(ValueError):
nxt.weights_to_creation_sequence(deg, with_labels=True, compact=True)
assert nxt.weights_to_creation_sequence(deg, with_labels=True) == [
(3, "d"),
(1, "d"),
(2, "d"),
(0, "d"),
]
assert nxt.weights_to_creation_sequence(deg, compact=True) == [4]
def test_find_alternating_4_cycle(self):
G = nx.Graph()
G.add_edge(1, 2)
assert not nxt.find_alternating_4_cycle(G)
def test_shortest_path(self):
deg = [3, 2, 2, 1]
G = nx.generators.havel_hakimi_graph(deg)
cs1 = nxt.creation_sequence(deg, with_labels=True)
for n, m in [(3, 0), (0, 3), (0, 2), (0, 1), (1, 3), (3, 1), (1, 2), (2, 3)]:
assert nxt.shortest_path(cs1, n, m) == nx.shortest_path(G, n, m)
spl = nxt.shortest_path_length(cs1, 3)
spl2 = nxt.shortest_path_length([t for v, t in cs1], 2)
assert spl == spl2
spld = {}
for j, pl in enumerate(spl):
n = cs1[j][0]
spld[n] = pl
assert spld == nx.single_source_shortest_path_length(G, 3)
assert nxt.shortest_path(["d", "d", "d", "i", "d", "d"], 1, 2) == [1, 2]
assert nxt.shortest_path([3, 1, 2], 1, 2) == [1, 2]
pytest.raises(TypeError, nxt.shortest_path, [3.0, 1.0, 2.0], 1, 2)
pytest.raises(ValueError, nxt.shortest_path, [3, 1, 2], "a", 2)
pytest.raises(ValueError, nxt.shortest_path, [3, 1, 2], 1, "b")
assert nxt.shortest_path([3, 1, 2], 1, 1) == [1]
def test_shortest_path_length(self):
assert nxt.shortest_path_length([3, 1, 2], 1) == [1, 0, 1, 2, 1, 1]
assert nxt.shortest_path_length(["d", "d", "d", "i", "d", "d"], 1) == [
1,
0,
1,
2,
1,
1,
]
assert nxt.shortest_path_length(("d", "d", "d", "i", "d", "d"), 1) == [
1,
0,
1,
2,
1,
1,
]
pytest.raises(TypeError, nxt.shortest_path, [3.0, 1.0, 2.0], 1)
def test_random_threshold_sequence(self):
assert len(nxt.random_threshold_sequence(10, 0.5)) == 10
assert nxt.random_threshold_sequence(10, 0.5, seed=42) == [
"d",
"i",
"d",
"d",
"d",
"i",
"i",
"i",
"d",
"d",
]
pytest.raises(ValueError, nxt.random_threshold_sequence, 10, 1.5)
def test_right_d_threshold_sequence(self):
assert nxt.right_d_threshold_sequence(3, 2) == ["d", "i", "d"]
pytest.raises(ValueError, nxt.right_d_threshold_sequence, 2, 3)
def test_left_d_threshold_sequence(self):
assert nxt.left_d_threshold_sequence(3, 2) == ["d", "i", "d"]
pytest.raises(ValueError, nxt.left_d_threshold_sequence, 2, 3)
def test_weights_thresholds(self):
wseq = [3, 4, 3, 3, 5, 6, 5, 4, 5, 6]
cs = nxt.weights_to_creation_sequence(wseq, threshold=10)
wseq = nxt.creation_sequence_to_weights(cs)
cs2 = nxt.weights_to_creation_sequence(wseq)
assert cs == cs2
wseq = nxt.creation_sequence_to_weights(nxt.uncompact([3, 1, 2, 3, 3, 2, 3]))
assert wseq == [
s * 0.125 for s in [4, 4, 4, 3, 5, 5, 2, 2, 2, 6, 6, 6, 1, 1, 7, 7, 7]
]
wseq = nxt.creation_sequence_to_weights([3, 1, 2, 3, 3, 2, 3])
assert wseq == [
s * 0.125 for s in [4, 4, 4, 3, 5, 5, 2, 2, 2, 6, 6, 6, 1, 1, 7, 7, 7]
]
wseq = nxt.creation_sequence_to_weights(list(enumerate("ddidiiidididi")))
assert wseq == [s * 0.1 for s in [5, 5, 4, 6, 3, 3, 3, 7, 2, 8, 1, 9, 0]]
wseq = nxt.creation_sequence_to_weights("ddidiiidididi")
assert wseq == [s * 0.1 for s in [5, 5, 4, 6, 3, 3, 3, 7, 2, 8, 1, 9, 0]]
wseq = nxt.creation_sequence_to_weights("ddidiiidididid")
ws = [s / 12 for s in [6, 6, 5, 7, 4, 4, 4, 8, 3, 9, 2, 10, 1, 11]]
assert sum(abs(c - d) for c, d in zip(wseq, ws)) < 1e-14
def test_finding_routines(self):
G = nx.Graph({1: [2], 2: [3], 3: [4], 4: [5], 5: [6]})
G.add_edge(2, 4)
G.add_edge(2, 5)
G.add_edge(2, 7)
G.add_edge(3, 6)
G.add_edge(4, 6)
# Alternating 4 cycle
assert nxt.find_alternating_4_cycle(G) == [1, 2, 3, 6]
# Threshold graph
TG = nxt.find_threshold_graph(G)
assert nxt.is_threshold_graph(TG)
assert sorted(TG.nodes()) == [1, 2, 3, 4, 5, 7]
cs = nxt.creation_sequence(dict(TG.degree()), with_labels=True)
assert nxt.find_creation_sequence(G) == cs
def test_fast_versions_properties_threshold_graphs(self):
cs = "ddiiddid"
G = nxt.threshold_graph(cs)
assert nxt.density("ddiiddid") == nx.density(G)
assert sorted(nxt.degree_sequence(cs)) == sorted(d for n, d in G.degree())
ts = nxt.triangle_sequence(cs)
assert ts == list(nx.triangles(G).values())
assert sum(ts) // 3 == nxt.triangles(cs)
c1 = nxt.cluster_sequence(cs)
c2 = list(nx.clustering(G).values())
assert sum(abs(c - d) for c, d in zip(c1, c2)) == pytest.approx(0, abs=1e-7)
b1 = nx.betweenness_centrality(G).values()
b2 = nxt.betweenness_sequence(cs)
assert sum(abs(c - d) for c, d in zip(b1, b2)) < 1e-7
assert nxt.eigenvalues(cs) == [0, 1, 3, 3, 5, 7, 7, 8]
# Degree Correlation
assert abs(nxt.degree_correlation(cs) + 0.593038821954) < 1e-12
assert nxt.degree_correlation("diiiddi") == -0.8
assert nxt.degree_correlation("did") == -1.0
assert nxt.degree_correlation("ddd") == 1.0
assert nxt.eigenvalues("dddiii") == [0, 0, 0, 0, 3, 3]
assert nxt.eigenvalues("dddiiid") == [0, 1, 1, 1, 4, 4, 7]
def test_tg_creation_routines(self):
s = nxt.left_d_threshold_sequence(5, 7)
s = nxt.right_d_threshold_sequence(5, 7)
def test_eigenvectors(self):
np = pytest.importorskip("numpy")
eigenval = np.linalg.eigvals
pytest.importorskip("scipy")
cs = "ddiiddid"
G = nxt.threshold_graph(cs)
(tgeval, tgevec) = nxt.eigenvectors(cs)
np.testing.assert_allclose([np.dot(lv, lv) for lv in tgevec], 1.0, rtol=1e-9)
lapl = nx.laplacian_matrix(G)
def test_create_using(self):
cs = "ddiiddid"
G = nxt.threshold_graph(cs)
pytest.raises(
nx.exception.NetworkXError,
nxt.threshold_graph,
cs,
create_using=nx.DiGraph(),
)
MG = nxt.threshold_graph(cs, create_using=nx.MultiGraph())
assert sorted(MG.edges()) == sorted(G.edges())
@@ -0,0 +1,431 @@
"""Unit testing for time dependent algorithms."""
from datetime import datetime, timedelta
import pytest
import networkx as nx
_delta = timedelta(days=5 * 365)
class TestCdIndex:
"""Unit testing for the cd index function."""
def test_common_graph(self):
G = nx.DiGraph()
G.add_nodes_from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
G.add_edge(4, 2)
G.add_edge(4, 0)
G.add_edge(4, 1)
G.add_edge(4, 3)
G.add_edge(5, 2)
G.add_edge(6, 2)
G.add_edge(6, 4)
G.add_edge(7, 4)
G.add_edge(8, 4)
G.add_edge(9, 4)
G.add_edge(9, 1)
G.add_edge(9, 3)
G.add_edge(10, 4)
node_attrs = {
0: {"time": datetime(1992, 1, 1)},
1: {"time": datetime(1992, 1, 1)},
2: {"time": datetime(1993, 1, 1)},
3: {"time": datetime(1993, 1, 1)},
4: {"time": datetime(1995, 1, 1)},
5: {"time": datetime(1997, 1, 1)},
6: {"time": datetime(1998, 1, 1)},
7: {"time": datetime(1999, 1, 1)},
8: {"time": datetime(1999, 1, 1)},
9: {"time": datetime(1998, 1, 1)},
10: {"time": datetime(1997, 4, 1)},
}
nx.set_node_attributes(G, node_attrs)
assert nx.cd_index(G, 4, time_delta=_delta) == 0.17
def test_common_graph_with_given_attributes(self):
G = nx.DiGraph()
G.add_nodes_from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
G.add_edge(4, 2)
G.add_edge(4, 0)
G.add_edge(4, 1)
G.add_edge(4, 3)
G.add_edge(5, 2)
G.add_edge(6, 2)
G.add_edge(6, 4)
G.add_edge(7, 4)
G.add_edge(8, 4)
G.add_edge(9, 4)
G.add_edge(9, 1)
G.add_edge(9, 3)
G.add_edge(10, 4)
node_attrs = {
0: {"date": datetime(1992, 1, 1)},
1: {"date": datetime(1992, 1, 1)},
2: {"date": datetime(1993, 1, 1)},
3: {"date": datetime(1993, 1, 1)},
4: {"date": datetime(1995, 1, 1)},
5: {"date": datetime(1997, 1, 1)},
6: {"date": datetime(1998, 1, 1)},
7: {"date": datetime(1999, 1, 1)},
8: {"date": datetime(1999, 1, 1)},
9: {"date": datetime(1998, 1, 1)},
10: {"date": datetime(1997, 4, 1)},
}
nx.set_node_attributes(G, node_attrs)
assert nx.cd_index(G, 4, time_delta=_delta, time="date") == 0.17
def test_common_graph_with_int_attributes(self):
G = nx.DiGraph()
G.add_nodes_from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
G.add_edge(4, 2)
G.add_edge(4, 0)
G.add_edge(4, 1)
G.add_edge(4, 3)
G.add_edge(5, 2)
G.add_edge(6, 2)
G.add_edge(6, 4)
G.add_edge(7, 4)
G.add_edge(8, 4)
G.add_edge(9, 4)
G.add_edge(9, 1)
G.add_edge(9, 3)
G.add_edge(10, 4)
node_attrs = {
0: {"time": 20},
1: {"time": 20},
2: {"time": 30},
3: {"time": 30},
4: {"time": 50},
5: {"time": 70},
6: {"time": 80},
7: {"time": 90},
8: {"time": 90},
9: {"time": 80},
10: {"time": 74},
}
nx.set_node_attributes(G, node_attrs)
assert nx.cd_index(G, 4, time_delta=50) == 0.17
def test_common_graph_with_float_attributes(self):
G = nx.DiGraph()
G.add_nodes_from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
G.add_edge(4, 2)
G.add_edge(4, 0)
G.add_edge(4, 1)
G.add_edge(4, 3)
G.add_edge(5, 2)
G.add_edge(6, 2)
G.add_edge(6, 4)
G.add_edge(7, 4)
G.add_edge(8, 4)
G.add_edge(9, 4)
G.add_edge(9, 1)
G.add_edge(9, 3)
G.add_edge(10, 4)
node_attrs = {
0: {"time": 20.2},
1: {"time": 20.2},
2: {"time": 30.7},
3: {"time": 30.7},
4: {"time": 50.9},
5: {"time": 70.1},
6: {"time": 80.6},
7: {"time": 90.7},
8: {"time": 90.7},
9: {"time": 80.6},
10: {"time": 74.2},
}
nx.set_node_attributes(G, node_attrs)
assert nx.cd_index(G, 4, time_delta=50) == 0.17
def test_common_graph_with_weights(self):
G = nx.DiGraph()
G.add_nodes_from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
G.add_edge(4, 2)
G.add_edge(4, 0)
G.add_edge(4, 1)
G.add_edge(4, 3)
G.add_edge(5, 2)
G.add_edge(6, 2)
G.add_edge(6, 4)
G.add_edge(7, 4)
G.add_edge(8, 4)
G.add_edge(9, 4)
G.add_edge(9, 1)
G.add_edge(9, 3)
G.add_edge(10, 4)
node_attrs = {
0: {"time": datetime(1992, 1, 1)},
1: {"time": datetime(1992, 1, 1)},
2: {"time": datetime(1993, 1, 1)},
3: {"time": datetime(1993, 1, 1)},
4: {"time": datetime(1995, 1, 1)},
5: {"time": datetime(1997, 1, 1)},
6: {"time": datetime(1998, 1, 1), "weight": 5},
7: {"time": datetime(1999, 1, 1), "weight": 2},
8: {"time": datetime(1999, 1, 1), "weight": 6},
9: {"time": datetime(1998, 1, 1), "weight": 3},
10: {"time": datetime(1997, 4, 1), "weight": 10},
}
nx.set_node_attributes(G, node_attrs)
assert nx.cd_index(G, 4, time_delta=_delta, weight="weight") == 0.04
def test_node_with_no_predecessors(self):
G = nx.DiGraph()
G.add_nodes_from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
G.add_edge(4, 2)
G.add_edge(4, 0)
G.add_edge(4, 3)
G.add_edge(5, 2)
G.add_edge(6, 2)
G.add_edge(6, 4)
G.add_edge(7, 4)
G.add_edge(8, 4)
G.add_edge(9, 4)
G.add_edge(9, 1)
G.add_edge(9, 3)
G.add_edge(10, 4)
node_attrs = {
0: {"time": datetime(1992, 1, 1)},
1: {"time": datetime(1992, 1, 1)},
2: {"time": datetime(1993, 1, 1)},
3: {"time": datetime(1993, 1, 1)},
4: {"time": datetime(1995, 1, 1)},
5: {"time": datetime(2005, 1, 1)},
6: {"time": datetime(2010, 1, 1)},
7: {"time": datetime(2001, 1, 1)},
8: {"time": datetime(2020, 1, 1)},
9: {"time": datetime(2017, 1, 1)},
10: {"time": datetime(2004, 4, 1)},
}
nx.set_node_attributes(G, node_attrs)
assert nx.cd_index(G, 4, time_delta=_delta) == 0.0
def test_node_with_no_successors(self):
G = nx.DiGraph()
G.add_nodes_from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
G.add_edge(8, 2)
G.add_edge(6, 0)
G.add_edge(6, 3)
G.add_edge(5, 2)
G.add_edge(6, 2)
G.add_edge(6, 4)
G.add_edge(7, 4)
G.add_edge(8, 4)
G.add_edge(9, 4)
G.add_edge(9, 1)
G.add_edge(9, 3)
G.add_edge(10, 4)
node_attrs = {
0: {"time": datetime(1992, 1, 1)},
1: {"time": datetime(1992, 1, 1)},
2: {"time": datetime(1993, 1, 1)},
3: {"time": datetime(1993, 1, 1)},
4: {"time": datetime(1995, 1, 1)},
5: {"time": datetime(1997, 1, 1)},
6: {"time": datetime(1998, 1, 1)},
7: {"time": datetime(1999, 1, 1)},
8: {"time": datetime(1999, 1, 1)},
9: {"time": datetime(1998, 1, 1)},
10: {"time": datetime(1997, 4, 1)},
}
nx.set_node_attributes(G, node_attrs)
assert nx.cd_index(G, 4, time_delta=_delta) == 1.0
def test_n_equals_zero(self):
G = nx.DiGraph()
G.add_nodes_from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
G.add_edge(4, 2)
G.add_edge(4, 0)
G.add_edge(4, 3)
G.add_edge(6, 4)
G.add_edge(7, 4)
G.add_edge(8, 4)
G.add_edge(9, 4)
G.add_edge(9, 1)
G.add_edge(10, 4)
node_attrs = {
0: {"time": datetime(1992, 1, 1)},
1: {"time": datetime(1992, 1, 1)},
2: {"time": datetime(1993, 1, 1)},
3: {"time": datetime(1993, 1, 1)},
4: {"time": datetime(1995, 1, 1)},
5: {"time": datetime(2005, 1, 1)},
6: {"time": datetime(2010, 1, 1)},
7: {"time": datetime(2001, 1, 1)},
8: {"time": datetime(2020, 1, 1)},
9: {"time": datetime(2017, 1, 1)},
10: {"time": datetime(2004, 4, 1)},
}
nx.set_node_attributes(G, node_attrs)
with pytest.raises(
nx.NetworkXError, match="The cd index cannot be defined."
) as ve:
nx.cd_index(G, 4, time_delta=_delta)
def test_time_timedelta_compatibility(self):
G = nx.DiGraph()
G.add_nodes_from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
G.add_edge(4, 2)
G.add_edge(4, 0)
G.add_edge(4, 3)
G.add_edge(6, 4)
G.add_edge(7, 4)
G.add_edge(8, 4)
G.add_edge(9, 4)
G.add_edge(9, 1)
G.add_edge(10, 4)
node_attrs = {
0: {"time": 20.2},
1: {"time": 20.2},
2: {"time": 30.7},
3: {"time": 30.7},
4: {"time": 50.9},
5: {"time": 70.1},
6: {"time": 80.6},
7: {"time": 90.7},
8: {"time": 90.7},
9: {"time": 80.6},
10: {"time": 74.2},
}
nx.set_node_attributes(G, node_attrs)
with pytest.raises(
nx.NetworkXError,
match="Addition and comparison are not supported between",
) as ve:
nx.cd_index(G, 4, time_delta=_delta)
def test_node_with_no_time(self):
G = nx.DiGraph()
G.add_nodes_from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
G.add_edge(8, 2)
G.add_edge(6, 0)
G.add_edge(6, 3)
G.add_edge(5, 2)
G.add_edge(6, 2)
G.add_edge(6, 4)
G.add_edge(7, 4)
G.add_edge(8, 4)
G.add_edge(9, 4)
G.add_edge(9, 1)
G.add_edge(9, 3)
G.add_edge(10, 4)
node_attrs = {
0: {"time": datetime(1992, 1, 1)},
1: {"time": datetime(1992, 1, 1)},
2: {"time": datetime(1993, 1, 1)},
3: {"time": datetime(1993, 1, 1)},
4: {"time": datetime(1995, 1, 1)},
6: {"time": datetime(1998, 1, 1)},
7: {"time": datetime(1999, 1, 1)},
8: {"time": datetime(1999, 1, 1)},
9: {"time": datetime(1998, 1, 1)},
10: {"time": datetime(1997, 4, 1)},
}
nx.set_node_attributes(G, node_attrs)
with pytest.raises(
nx.NetworkXError, match="Not all nodes have a 'time' attribute."
) as ve:
nx.cd_index(G, 4, time_delta=_delta)
def test_maximally_consolidating(self):
G = nx.DiGraph()
G.add_nodes_from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
G.add_edge(5, 1)
G.add_edge(5, 2)
G.add_edge(5, 3)
G.add_edge(5, 4)
G.add_edge(6, 1)
G.add_edge(6, 5)
G.add_edge(7, 1)
G.add_edge(7, 5)
G.add_edge(8, 2)
G.add_edge(8, 5)
G.add_edge(9, 5)
G.add_edge(9, 3)
G.add_edge(10, 5)
G.add_edge(10, 3)
G.add_edge(10, 4)
G.add_edge(11, 5)
G.add_edge(11, 4)
node_attrs = {
0: {"time": datetime(1992, 1, 1)},
1: {"time": datetime(1992, 1, 1)},
2: {"time": datetime(1993, 1, 1)},
3: {"time": datetime(1993, 1, 1)},
4: {"time": datetime(1995, 1, 1)},
5: {"time": datetime(1997, 1, 1)},
6: {"time": datetime(1998, 1, 1)},
7: {"time": datetime(1999, 1, 1)},
8: {"time": datetime(1999, 1, 1)},
9: {"time": datetime(1998, 1, 1)},
10: {"time": datetime(1997, 4, 1)},
11: {"time": datetime(1998, 5, 1)},
}
nx.set_node_attributes(G, node_attrs)
assert nx.cd_index(G, 5, time_delta=_delta) == -1
def test_maximally_destabilizing(self):
G = nx.DiGraph()
G.add_nodes_from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
G.add_edge(5, 1)
G.add_edge(5, 2)
G.add_edge(5, 3)
G.add_edge(5, 4)
G.add_edge(6, 5)
G.add_edge(7, 5)
G.add_edge(8, 5)
G.add_edge(9, 5)
G.add_edge(10, 5)
G.add_edge(11, 5)
node_attrs = {
0: {"time": datetime(1992, 1, 1)},
1: {"time": datetime(1992, 1, 1)},
2: {"time": datetime(1993, 1, 1)},
3: {"time": datetime(1993, 1, 1)},
4: {"time": datetime(1995, 1, 1)},
5: {"time": datetime(1997, 1, 1)},
6: {"time": datetime(1998, 1, 1)},
7: {"time": datetime(1999, 1, 1)},
8: {"time": datetime(1999, 1, 1)},
9: {"time": datetime(1998, 1, 1)},
10: {"time": datetime(1997, 4, 1)},
11: {"time": datetime(1998, 5, 1)},
}
nx.set_node_attributes(G, node_attrs)
assert nx.cd_index(G, 5, time_delta=_delta) == 1
@@ -0,0 +1,161 @@
"""Unit tests for the :mod:`networkx.algorithms.tournament` module."""
from itertools import combinations
import pytest
from networkx import DiGraph
from networkx.algorithms.tournament import (
hamiltonian_path,
index_satisfying,
is_reachable,
is_strongly_connected,
is_tournament,
random_tournament,
score_sequence,
tournament_matrix,
)
def test_condition_not_satisfied():
iter_in = [0]
assert index_satisfying(iter_in, lambda x: x > 0) == 1
def test_empty_iterable():
with pytest.raises(ValueError):
index_satisfying([], lambda x: x > 0)
def test_is_tournament():
G = DiGraph()
G.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 0), (1, 3), (0, 2)])
assert is_tournament(G)
def test_self_loops():
"""A tournament must have no self-loops."""
G = DiGraph()
G.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 0), (1, 3), (0, 2)])
G.add_edge(0, 0)
assert not is_tournament(G)
def test_missing_edges():
"""A tournament must not have any pair of nodes without at least
one edge joining the pair.
"""
G = DiGraph()
G.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 0), (1, 3)])
assert not is_tournament(G)
def test_bidirectional_edges():
"""A tournament must not have any pair of nodes with greater
than one edge joining the pair.
"""
G = DiGraph()
G.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 0), (1, 3), (0, 2)])
G.add_edge(1, 0)
assert not is_tournament(G)
def test_graph_is_tournament():
for _ in range(10):
G = random_tournament(5)
assert is_tournament(G)
def test_graph_is_tournament_seed():
for _ in range(10):
G = random_tournament(5, seed=1)
assert is_tournament(G)
def test_graph_is_tournament_one_node():
G = random_tournament(1)
assert is_tournament(G)
def test_graph_is_tournament_zero_node():
G = random_tournament(0)
assert is_tournament(G)
def test_hamiltonian_empty_graph():
path = hamiltonian_path(DiGraph())
assert len(path) == 0
def test_path_is_hamiltonian():
G = DiGraph()
G.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 0), (1, 3), (0, 2)])
path = hamiltonian_path(G)
assert len(path) == 4
assert all(v in G[u] for u, v in zip(path, path[1:]))
def test_hamiltonian_cycle():
"""Tests that :func:`networkx.tournament.hamiltonian_path`
returns a Hamiltonian cycle when provided a strongly connected
tournament.
"""
G = DiGraph()
G.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 0), (1, 3), (0, 2)])
path = hamiltonian_path(G)
assert len(path) == 4
assert all(v in G[u] for u, v in zip(path, path[1:]))
assert path[0] in G[path[-1]]
def test_score_sequence_edge():
G = DiGraph([(0, 1)])
assert score_sequence(G) == [0, 1]
def test_score_sequence_triangle():
G = DiGraph([(0, 1), (1, 2), (2, 0)])
assert score_sequence(G) == [1, 1, 1]
def test_tournament_matrix():
np = pytest.importorskip("numpy")
pytest.importorskip("scipy")
npt = np.testing
G = DiGraph([(0, 1)])
m = tournament_matrix(G)
npt.assert_array_equal(m.todense(), np.array([[0, 1], [-1, 0]]))
def test_reachable_pair():
"""Tests for a reachable pair of nodes."""
G = DiGraph([(0, 1), (1, 2), (2, 0)])
assert is_reachable(G, 0, 2)
def test_same_node_is_reachable():
"""Tests that a node is always reachable from it."""
# G is an arbitrary tournament on ten nodes.
G = DiGraph(sorted(p) for p in combinations(range(10), 2))
assert all(is_reachable(G, v, v) for v in G)
def test_unreachable_pair():
"""Tests for an unreachable pair of nodes."""
G = DiGraph([(0, 1), (0, 2), (1, 2)])
assert not is_reachable(G, 1, 0)
def test_is_strongly_connected():
"""Tests for a strongly connected tournament."""
G = DiGraph([(0, 1), (1, 2), (2, 0)])
assert is_strongly_connected(G)
def test_not_strongly_connected():
"""Tests for a tournament that is not strongly connected."""
G = DiGraph([(0, 1), (0, 2), (1, 2)])
assert not is_strongly_connected(G)
@@ -0,0 +1,248 @@
"""Tests for the :mod:`networkx.algorithms.triads` module."""
import itertools
from collections import defaultdict
from random import sample
import pytest
import networkx as nx
def test_triadic_census():
"""Tests the triadic_census function."""
G = nx.DiGraph()
G.add_edges_from(["01", "02", "03", "04", "05", "12", "16", "51", "56", "65"])
expected = {
"030T": 2,
"120C": 1,
"210": 0,
"120U": 0,
"012": 9,
"102": 3,
"021U": 0,
"111U": 0,
"003": 8,
"030C": 0,
"021D": 9,
"201": 0,
"111D": 1,
"300": 0,
"120D": 0,
"021C": 2,
}
actual = nx.triadic_census(G)
assert expected == actual
def test_is_triad():
"""Tests the is_triad function"""
G = nx.karate_club_graph()
G = G.to_directed()
for i in range(100):
nodes = sample(sorted(G.nodes()), 3)
G2 = G.subgraph(nodes)
assert nx.is_triad(G2)
def test_all_triads():
"""Tests the all_triads function."""
G = nx.DiGraph()
G.add_edges_from(["01", "02", "03", "04", "05", "12", "16", "51", "56", "65"])
expected = [
f"{i},{j},{k}"
for i in range(7)
for j in range(i + 1, 7)
for k in range(j + 1, 7)
]
expected = [G.subgraph(x.split(",")) for x in expected]
actual = list(nx.all_triads(G))
assert all(any(nx.is_isomorphic(G1, G2) for G1 in expected) for G2 in actual)
def test_triad_type():
"""Tests the triad_type function."""
# 0 edges (1 type)
G = nx.DiGraph({0: [], 1: [], 2: []})
assert nx.triad_type(G) == "003"
# 1 edge (1 type)
G = nx.DiGraph({0: [1], 1: [], 2: []})
assert nx.triad_type(G) == "012"
# 2 edges (4 types)
G = nx.DiGraph([(0, 1), (0, 2)])
assert nx.triad_type(G) == "021D"
G = nx.DiGraph({0: [1], 1: [0], 2: []})
assert nx.triad_type(G) == "102"
G = nx.DiGraph([(0, 1), (2, 1)])
assert nx.triad_type(G) == "021U"
G = nx.DiGraph([(0, 1), (1, 2)])
assert nx.triad_type(G) == "021C"
# 3 edges (4 types)
G = nx.DiGraph([(0, 1), (1, 0), (2, 1)])
assert nx.triad_type(G) == "111D"
G = nx.DiGraph([(0, 1), (1, 0), (1, 2)])
assert nx.triad_type(G) == "111U"
G = nx.DiGraph([(0, 1), (1, 2), (0, 2)])
assert nx.triad_type(G) == "030T"
G = nx.DiGraph([(0, 1), (1, 2), (2, 0)])
assert nx.triad_type(G) == "030C"
# 4 edges (4 types)
G = nx.DiGraph([(0, 1), (1, 0), (2, 0), (0, 2)])
assert nx.triad_type(G) == "201"
G = nx.DiGraph([(0, 1), (1, 0), (2, 0), (2, 1)])
assert nx.triad_type(G) == "120D"
G = nx.DiGraph([(0, 1), (1, 0), (0, 2), (1, 2)])
assert nx.triad_type(G) == "120U"
G = nx.DiGraph([(0, 1), (1, 0), (0, 2), (2, 1)])
assert nx.triad_type(G) == "120C"
# 5 edges (1 type)
G = nx.DiGraph([(0, 1), (1, 0), (2, 1), (1, 2), (0, 2)])
assert nx.triad_type(G) == "210"
# 6 edges (1 type)
G = nx.DiGraph([(0, 1), (1, 0), (1, 2), (2, 1), (0, 2), (2, 0)])
assert nx.triad_type(G) == "300"
def test_triads_by_type():
G = nx.DiGraph()
G.add_edges_from(["01", "02", "03", "04", "05", "12", "16", "51", "56", "65"])
all_triads = nx.all_triads(G)
expected = defaultdict(list)
for triad in all_triads:
name = nx.triad_type(triad)
expected[name].append(triad)
actual = nx.triads_by_type(G)
assert set(actual.keys()) == set(expected.keys())
for tri_type, actual_Gs in actual.items():
expected_Gs = expected[tri_type]
for a in actual_Gs:
assert any(nx.is_isomorphic(a, e) for e in expected_Gs)
def test_triadic_census_short_path_nodelist():
G = nx.path_graph("abc", create_using=nx.DiGraph)
expected = {"021C": 1}
for nl in ["a", "b", "c", "ab", "ac", "bc", "abc"]:
triad_census = nx.triadic_census(G, nodelist=nl)
assert expected == {typ: cnt for typ, cnt in triad_census.items() if cnt > 0}
def test_triadic_census_correct_nodelist_values():
G = nx.path_graph(5, create_using=nx.DiGraph)
msg = r"nodelist includes duplicate nodes or nodes not in G"
with pytest.raises(ValueError, match=msg):
nx.triadic_census(G, [1, 2, 2, 3])
with pytest.raises(ValueError, match=msg):
nx.triadic_census(G, [1, 2, "a", 3])
def test_triadic_census_tiny_graphs():
tc = nx.triadic_census(nx.empty_graph(0, create_using=nx.DiGraph))
assert {} == {typ: cnt for typ, cnt in tc.items() if cnt > 0}
tc = nx.triadic_census(nx.empty_graph(1, create_using=nx.DiGraph))
assert {} == {typ: cnt for typ, cnt in tc.items() if cnt > 0}
tc = nx.triadic_census(nx.empty_graph(2, create_using=nx.DiGraph))
assert {} == {typ: cnt for typ, cnt in tc.items() if cnt > 0}
tc = nx.triadic_census(nx.DiGraph([(1, 2)]))
assert {} == {typ: cnt for typ, cnt in tc.items() if cnt > 0}
def test_triadic_census_selfloops():
GG = nx.path_graph("abc", create_using=nx.DiGraph)
expected = {"021C": 1}
for n in GG:
G = GG.copy()
G.add_edge(n, n)
tc = nx.triadic_census(G)
assert expected == {typ: cnt for typ, cnt in tc.items() if cnt > 0}
GG = nx.path_graph("abcde", create_using=nx.DiGraph)
tbt = nx.triads_by_type(GG)
for n in GG:
GG.add_edge(n, n)
tc = nx.triadic_census(GG)
assert tc == {tt: len(tbt[tt]) for tt in tc}
def test_triadic_census_four_path():
G = nx.path_graph("abcd", create_using=nx.DiGraph)
expected = {"012": 2, "021C": 2}
triad_census = nx.triadic_census(G)
assert expected == {typ: cnt for typ, cnt in triad_census.items() if cnt > 0}
def test_triadic_census_four_path_nodelist():
G = nx.path_graph("abcd", create_using=nx.DiGraph)
expected_end = {"012": 2, "021C": 1}
expected_mid = {"012": 1, "021C": 2}
a_triad_census = nx.triadic_census(G, nodelist=["a"])
assert expected_end == {typ: cnt for typ, cnt in a_triad_census.items() if cnt > 0}
b_triad_census = nx.triadic_census(G, nodelist=["b"])
assert expected_mid == {typ: cnt for typ, cnt in b_triad_census.items() if cnt > 0}
c_triad_census = nx.triadic_census(G, nodelist=["c"])
assert expected_mid == {typ: cnt for typ, cnt in c_triad_census.items() if cnt > 0}
d_triad_census = nx.triadic_census(G, nodelist=["d"])
assert expected_end == {typ: cnt for typ, cnt in d_triad_census.items() if cnt > 0}
def test_triadic_census_nodelist():
"""Tests the triadic_census function."""
G = nx.DiGraph()
G.add_edges_from(["01", "02", "03", "04", "05", "12", "16", "51", "56", "65"])
expected = {
"030T": 2,
"120C": 1,
"210": 0,
"120U": 0,
"012": 9,
"102": 3,
"021U": 0,
"111U": 0,
"003": 8,
"030C": 0,
"021D": 9,
"201": 0,
"111D": 1,
"300": 0,
"120D": 0,
"021C": 2,
}
actual = {k: 0 for k in expected}
for node in G.nodes():
node_triad_census = nx.triadic_census(G, nodelist=[node])
for triad_key in expected:
actual[triad_key] += node_triad_census[triad_key]
# Divide all counts by 3
for k, v in actual.items():
actual[k] //= 3
assert expected == actual
@pytest.mark.parametrize("N", [5, 10])
def test_triadic_census_on_random_graph(N):
G = nx.binomial_graph(N, 0.3, directed=True, seed=42)
tc1 = nx.triadic_census(G)
tbt = nx.triads_by_type(G)
tc2 = {tt: len(tbt[tt]) for tt in tc1}
assert tc1 == tc2
for n in G:
tc1 = nx.triadic_census(G, nodelist={n})
tc2 = {tt: sum(1 for t in tbt.get(tt, []) if n in t) for tt in tc1}
assert tc1 == tc2
for ns in itertools.combinations(G, 2):
ns = set(ns)
tc1 = nx.triadic_census(G, nodelist=ns)
tc2 = {
tt: sum(1 for t in tbt.get(tt, []) if any(n in ns for n in t)) for tt in tc1
}
assert tc1 == tc2
for ns in itertools.combinations(G, 3):
ns = set(ns)
tc1 = nx.triadic_census(G, nodelist=ns)
tc2 = {
tt: sum(1 for t in tbt.get(tt, []) if any(n in ns for n in t)) for tt in tc1
}
assert tc1 == tc2
@@ -0,0 +1,41 @@
import networkx as nx
class TestClosenessVitality:
def test_unweighted(self):
G = nx.cycle_graph(3)
vitality = nx.closeness_vitality(G)
assert vitality == {0: 2, 1: 2, 2: 2}
def test_weighted(self):
G = nx.Graph()
nx.add_cycle(G, [0, 1, 2], weight=2)
vitality = nx.closeness_vitality(G, weight="weight")
assert vitality == {0: 4, 1: 4, 2: 4}
def test_unweighted_digraph(self):
G = nx.DiGraph(nx.cycle_graph(3))
vitality = nx.closeness_vitality(G)
assert vitality == {0: 4, 1: 4, 2: 4}
def test_weighted_digraph(self):
G = nx.DiGraph()
nx.add_cycle(G, [0, 1, 2], weight=2)
nx.add_cycle(G, [2, 1, 0], weight=2)
vitality = nx.closeness_vitality(G, weight="weight")
assert vitality == {0: 8, 1: 8, 2: 8}
def test_weighted_multidigraph(self):
G = nx.MultiDiGraph()
nx.add_cycle(G, [0, 1, 2], weight=2)
nx.add_cycle(G, [2, 1, 0], weight=2)
vitality = nx.closeness_vitality(G, weight="weight")
assert vitality == {0: 8, 1: 8, 2: 8}
def test_disconnecting_graph(self):
"""Tests that the closeness vitality of a node whose removal
disconnects the graph is negative infinity.
"""
G = nx.path_graph(3)
assert nx.closeness_vitality(G, node=1) == -float("inf")
@@ -0,0 +1,103 @@
import networkx as nx
from networkx.utils import pairwise
class TestVoronoiCells:
"""Unit tests for the Voronoi cells function."""
def test_isolates(self):
"""Tests that a graph with isolated nodes has all isolates in
one block of the partition.
"""
G = nx.empty_graph(5)
cells = nx.voronoi_cells(G, {0, 2, 4})
expected = {0: {0}, 2: {2}, 4: {4}, "unreachable": {1, 3}}
assert expected == cells
def test_undirected_unweighted(self):
G = nx.cycle_graph(6)
cells = nx.voronoi_cells(G, {0, 3})
expected = {0: {0, 1, 5}, 3: {2, 3, 4}}
assert expected == cells
def test_directed_unweighted(self):
# This is the singly-linked directed cycle graph on six nodes.
G = nx.DiGraph(pairwise(range(6), cyclic=True))
cells = nx.voronoi_cells(G, {0, 3})
expected = {0: {0, 1, 2}, 3: {3, 4, 5}}
assert expected == cells
def test_directed_inward(self):
"""Tests that reversing the graph gives the "inward" Voronoi
partition.
"""
# This is the singly-linked reverse directed cycle graph on six nodes.
G = nx.DiGraph(pairwise(range(6), cyclic=True))
G = G.reverse(copy=False)
cells = nx.voronoi_cells(G, {0, 3})
expected = {0: {0, 4, 5}, 3: {1, 2, 3}}
assert expected == cells
def test_undirected_weighted(self):
edges = [(0, 1, 10), (1, 2, 1), (2, 3, 1)]
G = nx.Graph()
G.add_weighted_edges_from(edges)
cells = nx.voronoi_cells(G, {0, 3})
expected = {0: {0}, 3: {1, 2, 3}}
assert expected == cells
def test_directed_weighted(self):
edges = [(0, 1, 10), (1, 2, 1), (2, 3, 1), (3, 2, 1), (2, 1, 1)]
G = nx.DiGraph()
G.add_weighted_edges_from(edges)
cells = nx.voronoi_cells(G, {0, 3})
expected = {0: {0}, 3: {1, 2, 3}}
assert expected == cells
def test_multigraph_unweighted(self):
"""Tests that the Voronoi cells for a multigraph are the same as
for a simple graph.
"""
edges = [(0, 1), (1, 2), (2, 3)]
G = nx.MultiGraph(2 * edges)
H = nx.Graph(G)
G_cells = nx.voronoi_cells(G, {0, 3})
H_cells = nx.voronoi_cells(H, {0, 3})
assert G_cells == H_cells
def test_multidigraph_unweighted(self):
# This is the twice-singly-linked directed cycle graph on six nodes.
edges = list(pairwise(range(6), cyclic=True))
G = nx.MultiDiGraph(2 * edges)
H = nx.DiGraph(G)
G_cells = nx.voronoi_cells(G, {0, 3})
H_cells = nx.voronoi_cells(H, {0, 3})
assert G_cells == H_cells
def test_multigraph_weighted(self):
edges = [(0, 1, 10), (0, 1, 10), (1, 2, 1), (1, 2, 100), (2, 3, 1), (2, 3, 100)]
G = nx.MultiGraph()
G.add_weighted_edges_from(edges)
cells = nx.voronoi_cells(G, {0, 3})
expected = {0: {0}, 3: {1, 2, 3}}
assert expected == cells
def test_multidigraph_weighted(self):
edges = [
(0, 1, 10),
(0, 1, 10),
(1, 2, 1),
(2, 3, 1),
(3, 2, 10),
(3, 2, 1),
(2, 1, 10),
(2, 1, 1),
]
G = nx.MultiDiGraph()
G.add_weighted_edges_from(edges)
cells = nx.voronoi_cells(G, {0, 3})
expected = {0: {0}, 3: {1, 2, 3}}
assert expected == cells
@@ -0,0 +1,54 @@
"""Unit tests for the :mod:`networkx.algorithms.walks` module."""
import pytest
import networkx as nx
pytest.importorskip("numpy")
pytest.importorskip("scipy")
def test_directed():
G = nx.DiGraph([(0, 1), (1, 2), (2, 0)])
num_walks = nx.number_of_walks(G, 3)
expected = {0: {0: 1, 1: 0, 2: 0}, 1: {0: 0, 1: 1, 2: 0}, 2: {0: 0, 1: 0, 2: 1}}
assert num_walks == expected
def test_undirected():
G = nx.cycle_graph(3)
num_walks = nx.number_of_walks(G, 3)
expected = {0: {0: 2, 1: 3, 2: 3}, 1: {0: 3, 1: 2, 2: 3}, 2: {0: 3, 1: 3, 2: 2}}
assert num_walks == expected
def test_non_integer_nodes():
G = nx.DiGraph([("A", "B"), ("B", "C"), ("C", "A")])
num_walks = nx.number_of_walks(G, 2)
expected = {
"A": {"A": 0, "B": 0, "C": 1},
"B": {"A": 1, "B": 0, "C": 0},
"C": {"A": 0, "B": 1, "C": 0},
}
assert num_walks == expected
def test_zero_length():
G = nx.cycle_graph(3)
num_walks = nx.number_of_walks(G, 0)
expected = {0: {0: 1, 1: 0, 2: 0}, 1: {0: 0, 1: 1, 2: 0}, 2: {0: 0, 1: 0, 2: 1}}
assert num_walks == expected
def test_negative_length_exception():
G = nx.cycle_graph(3)
with pytest.raises(ValueError):
nx.number_of_walks(G, -1)
def test_hidden_weight_attr():
G = nx.cycle_graph(3)
G.add_edge(1, 2, weight=5)
num_walks = nx.number_of_walks(G, 3)
expected = {0: {0: 2, 1: 3, 2: 3}, 1: {0: 3, 1: 2, 2: 3}, 2: {0: 3, 1: 3, 2: 2}}
assert num_walks == expected
@@ -0,0 +1,157 @@
import networkx as nx
def test_wiener_index_of_disconnected_graph():
assert nx.wiener_index(nx.empty_graph(2)) == float("inf")
def test_wiener_index_of_directed_graph():
G = nx.complete_graph(3)
H = nx.DiGraph(G)
assert (2 * nx.wiener_index(G)) == nx.wiener_index(H)
def test_wiener_index_of_complete_graph():
n = 10
G = nx.complete_graph(n)
assert nx.wiener_index(G) == (n * (n - 1) / 2)
def test_wiener_index_of_path_graph():
# In P_n, there are n - 1 pairs of vertices at distance one, n -
# 2 pairs at distance two, n - 3 at distance three, ..., 1 at
# distance n - 1, so the Wiener index should be
#
# 1 * (n - 1) + 2 * (n - 2) + ... + (n - 2) * 2 + (n - 1) * 1
#
# For example, in P_5,
#
# 1 * 4 + 2 * 3 + 3 * 2 + 4 * 1 = 2 (1 * 4 + 2 * 3)
#
# and in P_6,
#
# 1 * 5 + 2 * 4 + 3 * 3 + 4 * 2 + 5 * 1 = 2 (1 * 5 + 2 * 4) + 3 * 3
#
# assuming n is *odd*, this gives the formula
#
# 2 \sum_{i = 1}^{(n - 1) / 2} [i * (n - i)]
#
# assuming n is *even*, this gives the formula
#
# 2 \sum_{i = 1}^{n / 2} [i * (n - i)] - (n / 2) ** 2
#
n = 9
G = nx.path_graph(n)
expected = 2 * sum(i * (n - i) for i in range(1, (n // 2) + 1))
actual = nx.wiener_index(G)
assert expected == actual
def test_schultz_and_gutman_index_of_disconnected_graph():
n = 4
G = nx.Graph()
G.add_nodes_from(list(range(1, n + 1)))
expected = float("inf")
G.add_edge(1, 2)
G.add_edge(3, 4)
actual_1 = nx.schultz_index(G)
actual_2 = nx.gutman_index(G)
assert expected == actual_1
assert expected == actual_2
def test_schultz_and_gutman_index_of_complete_bipartite_graph_1():
n = 3
m = 3
cbg = nx.complete_bipartite_graph(n, m)
expected_1 = n * m * (n + m) + 2 * n * (n - 1) * m + 2 * m * (m - 1) * n
actual_1 = nx.schultz_index(cbg)
expected_2 = n * m * (n * m) + n * (n - 1) * m * m + m * (m - 1) * n * n
actual_2 = nx.gutman_index(cbg)
assert expected_1 == actual_1
assert expected_2 == actual_2
def test_schultz_and_gutman_index_of_complete_bipartite_graph_2():
n = 2
m = 5
cbg = nx.complete_bipartite_graph(n, m)
expected_1 = n * m * (n + m) + 2 * n * (n - 1) * m + 2 * m * (m - 1) * n
actual_1 = nx.schultz_index(cbg)
expected_2 = n * m * (n * m) + n * (n - 1) * m * m + m * (m - 1) * n * n
actual_2 = nx.gutman_index(cbg)
assert expected_1 == actual_1
assert expected_2 == actual_2
def test_schultz_and_gutman_index_of_complete_graph():
n = 5
cg = nx.complete_graph(n)
expected_1 = n * (n - 1) * (n - 1)
actual_1 = nx.schultz_index(cg)
assert expected_1 == actual_1
expected_2 = n * (n - 1) * (n - 1) * (n - 1) / 2
actual_2 = nx.gutman_index(cg)
assert expected_2 == actual_2
def test_schultz_and_gutman_index_of_odd_cycle_graph():
k = 5
n = 2 * k + 1
ocg = nx.cycle_graph(n)
expected_1 = 2 * n * k * (k + 1)
actual_1 = nx.schultz_index(ocg)
expected_2 = 2 * n * k * (k + 1)
actual_2 = nx.gutman_index(ocg)
assert expected_1 == actual_1
assert expected_2 == actual_2
def test_hyper_wiener_of_complete_graph():
# In a complete graph K_n, the distance is always 1.
# For K_n, this term is always (1 + 1^2) = 2.
#
# The number of ordered pairs is n * (n - 1).
# The total sum before division is (n * (n - 1)) * 2.
# The final result is therefore ((n * (n - 1)) * 2) / 2, which
# simplifies to n * (n - 1).
n = 5
G = nx.complete_graph(n)
assert nx.hyper_wiener_index(G) == n * (n - 1)
def test_hyper_wiener_of_path_graph():
G = nx.path_graph(4)
assert nx.hyper_wiener_index(G) == 30.0
def test_hyper_wiener_of_cycle_graph():
G = nx.cycle_graph(4)
assert nx.hyper_wiener_index(G) == 20.0
def test_hyper_wiener_of_disconnected_graph():
G = nx.Graph([(0, 1), (2, 3)])
assert nx.hyper_wiener_index(G) == float("inf")
def test_hyper_wiener_of_weighted_graph():
G = nx.path_graph(3)
G.edges[0, 1]["weight"] = 2
assert nx.hyper_wiener_index(G, weight="weight") == 20.0