I am done

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

View File

@ -0,0 +1,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)

View File

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

View File

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

View File

@ -0,0 +1,82 @@
"""Unit tests for the broadcasting module."""
import math
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
def test_path_broadcast():
for i in range(2, 12):
G = nx.path_graph(i)
b_T, b_C = nx.tree_broadcast_center(G)
assert b_T == math.ceil(i / 2)
assert b_C == {
math.ceil(i / 2),
math.floor(i / 2),
math.ceil(i / 2 - 1),
math.floor(i / 2 - 1),
}
assert nx.tree_broadcast_time(G) == i - 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
def test_star_broadcast():
for i in range(4, 12):
G = nx.star_graph(i)
b_T, b_C = nx.tree_broadcast_center(G)
assert b_T == i
assert b_C == set(G.nodes())
assert nx.tree_broadcast_time(G) == b_T
def test_binomial_tree_broadcast():
for i in range(2, 8):
G = nx.binomial_tree(i)
b_T, b_C = nx.tree_broadcast_center(G)
assert b_T == i
assert b_C == {0, 2 ** (i - 1)}
assert nx.tree_broadcast_time(G) == 2 * i - 1

View File

@ -0,0 +1,141 @@
"""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)
# This chain decomposition isn't unique
# for chain in chains:
# print(chain)
# self.assertContainsChain(chain, 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)

View File

@ -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()

View File

@ -0,0 +1,291 @@
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_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))

View File

@ -0,0 +1,549 @@
import pytest
import networkx as nx
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
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)"""
G = nx.Graph([(1, 2), (1, 3), (2, 4), (3, 4), (3, 5), (3, 6)])
assert nx.square_clustering(G, [1])[1] == 1 / 3
def test_self_loops_square_clustering(self):
G = nx.path_graph(5)
assert nx.square_clustering(G) == {0: 0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0}
G.add_edges_from([(0, 0), (1, 1), (2, 2)])
assert nx.square_clustering(G) == {0: 1, 1: 0.5, 2: 0.2, 3: 0.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},
}

View File

@ -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)

View File

@ -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.deprecated_call():
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.deprecated_call():
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.deprecated_call():
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.deprecated_call():
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)

View File

@ -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)})

View File

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

View File

@ -0,0 +1,974 @@
from itertools import chain, islice, tee
from math import inf
from random import shuffle
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)))
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_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

View File

@ -0,0 +1,348 @@
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
def test_deprecations():
G = nx.DiGraph([(0, 1), (1, 2)])
with pytest.deprecated_call():
nx.d_separated(G, 0, 2, {1})
with pytest.deprecated_call():
z = nx.minimal_d_separator(G, 0, 2)

View File

@ -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)
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)
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)
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)
# 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)
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.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)
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.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)
assert edges_equal(sorted(nx.transitive_closure(G, False).edges()), soln)
assert edges_equal(sorted(nx.transitive_closure(G, None).edges()), solution)
assert edges_equal(sorted(nx.transitive_closure(G, True).edges()), soln)
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)
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)
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)
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)
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)
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)
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_undirected_raises():
G = nx.Graph()
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_rary_tree():
G = nx.full_rary_tree(3, 27, create_using=nx.DiGraph())
assert not nx.is_aperiodic(G)
def test_is_aperiodic_disconnected():
# disconnected graph
G = nx.DiGraph()
nx.add_cycle(G, [1, 2, 3, 4])
nx.add_cycle(G, [5, 6, 7, 8])
assert not nx.is_aperiodic(G)
G.add_edge(1, 3)
G.add_edge(5, 7)
assert nx.is_aperiodic(G)
def test_is_aperiodic_disconnected2():
G = nx.DiGraph()
nx.add_cycle(G, [0, 1, 2])
G.add_edge(3, 3)
assert not 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_compute_v_structures_raise():
G = nx.Graph()
with pytest.raises(nx.NetworkXNotImplemented, match="for undirected type"):
nx.compute_v_structures(G)
def test_compute_v_structures():
edges = [(0, 1), (0, 2), (3, 2)]
G = nx.DiGraph(edges)
v_structs = set(nx.compute_v_structures(G))
assert len(v_structs) == 1
assert (0, 2, 3) in v_structs
edges = [("A", "B"), ("C", "B"), ("B", "D"), ("D", "E"), ("G", "E")]
G = nx.DiGraph(edges)
v_structs = set(nx.compute_v_structures(G))
assert len(v_structs) == 2
def test_compute_v_structures_deprecated():
G = nx.DiGraph()
with pytest.deprecated_call():
nx.compute_v_structures(G)
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

View File

@ -0,0 +1,774 @@
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):
G = cnlti(nx.grid_2d_graph(4, 4), first_label=1, ordering="sorted")
self.G = G
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 abs(nx.harmonic_diameter(self.G) - 2.0477815699658715) < 1e-12
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_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(self):
assert set(nx.center(self.G)) == {6, 7, 10, 11}
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 type(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 type(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 type(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)

View File

@ -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)

View File

@ -0,0 +1,286 @@
import pytest
import networkx as nx
class TestImmediateDominators:
def test_exceptions(self):
G = nx.Graph()
G.add_node(0)
pytest.raises(nx.NetworkXNotImplemented, nx.immediate_dominators, G, 0)
G = nx.MultiGraph(G)
pytest.raises(nx.NetworkXNotImplemented, nx.immediate_dominators, G, 0)
G = nx.DiGraph([[0, 0]])
pytest.raises(nx.NetworkXError, nx.immediate_dominators, G, 1)
def test_singleton(self):
G = nx.DiGraph()
G.add_node(0)
assert nx.immediate_dominators(G, 0) == {0: 0}
G.add_edge(0, 0)
assert nx.immediate_dominators(G, 0) == {0: 0}
def test_path(self):
n = 5
G = nx.path_graph(n, create_using=nx.DiGraph())
assert nx.immediate_dominators(G, 0) == {i: max(i - 1, 0) for i in range(n)}
def test_cycle(self):
n = 5
G = nx.cycle_graph(n, create_using=nx.DiGraph())
assert nx.immediate_dominators(G, 0) == {i: max(i - 1, 0) for i in range(n)}
def test_unreachable(self):
n = 5
assert n > 1
G = nx.path_graph(n, create_using=nx.DiGraph())
assert nx.immediate_dominators(G, n // 2) == {
i: max(i - 1, n // 2) for i in range(n // 2, n)
}
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.immediate_dominators(G, 5) == {i: 5 for i in range(1, 6)}
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)
result = nx.immediate_dominators(G, 6)
assert result == {i: 6 for i in range(1, 7)}
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 == {1: 1, 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, 6: 6}
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 == {0: 0, 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, 7: 7}
class TestDominanceFrontiers:
def test_exceptions(self):
G = nx.Graph()
G.add_node(0)
pytest.raises(nx.NetworkXNotImplemented, nx.dominance_frontiers, G, 0)
G = nx.MultiGraph(G)
pytest.raises(nx.NetworkXNotImplemented, nx.dominance_frontiers, G, 0)
G = nx.DiGraph([[0, 0]])
pytest.raises(nx.NetworkXError, 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: set()}
def test_path(self):
n = 5
G = nx.path_graph(n, create_using=nx.DiGraph())
assert nx.dominance_frontiers(G, 0) == {i: set() for i in range(n)}
def test_cycle(self):
n = 5
G = nx.cycle_graph(n, create_using=nx.DiGraph())
assert nx.dominance_frontiers(G, 0) == {i: set() for i in range(n)}
def test_unreachable(self):
n = 5
assert n > 1
G = nx.path_graph(n, create_using=nx.DiGraph())
assert nx.dominance_frontiers(G, n // 2) == {i: set() for i in range(n // 2, n)}
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 dict(nx.dominance_frontiers(G, 5).items()) == {
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": set(), "b": set(), "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])

View File

@ -0,0 +1,46 @@
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})

View File

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

View File

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

View File

@ -0,0 +1,686 @@
import pytest
import networkx as nx
from networkx.generators import directed
# 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
# Unit tests for the :func:`~networkx.weisfeiler_lehman_hash_subgraphs` 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
list_digest_sizes_correct = lambda l: 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

View File

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

View File

@ -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)

View File

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

View File

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

View File

@ -0,0 +1,586 @@
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 = 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):
assert pytest.raises(
nx.NetworkXNotImplemented, self.func, graph_type([(0, 1), (1, 2)]), [(0, 2)]
)
def test_node_not_found(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (2, 3)])
assert 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 = 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):
assert pytest.raises(
nx.NetworkXNotImplemented, self.func, graph_type([(0, 1), (1, 2)]), [(0, 2)]
)
def test_node_not_found(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (2, 3)])
assert 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 = 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):
assert pytest.raises(
nx.NetworkXNotImplemented, self.func, graph_type([(0, 1), (1, 2)]), [(0, 2)]
)
def test_node_not_found(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (2, 3)])
assert 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 = 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):
assert pytest.raises(
nx.NetworkXNotImplemented, self.func, graph_type([(0, 1), (1, 2)]), [(0, 2)]
)
def test_node_u_not_found(self):
G = nx.Graph()
G.add_edges_from([(1, 3), (2, 3)])
assert 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)])
assert 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)
assert pytest.raises(nx.NetworkXAlgorithmError, self.test, G, [(0, 0)], [])
def test_equal_nodes_with_alpha_one_raises_error(self):
G = nx.complete_graph(4)
assert 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 = 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):
assert pytest.raises(
nx.NetworkXNotImplemented, self.func, graph_type([(0, 1), (1, 2)]), [(0, 2)]
)
def test_node_not_found(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (2, 3)])
assert 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 = 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)
assert 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
assert 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)
assert 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
assert 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 = 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)
assert 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
assert 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)
assert 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
assert 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 = 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)
assert 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
assert 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)
assert 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
assert 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)
assert pytest.raises(nx.NetworkXAlgorithmError, self.func, G, [(0, 1)], 0)
assert 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)])

View File

@ -0,0 +1,427 @@
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) == 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)

View File

@ -0,0 +1,605 @@
import math
from itertools import permutations
from pytest import raises
import networkx as nx
from networkx.algorithms.matching import matching_dict_to_set
from networkx.utils import edges_equal
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), matching_dict_to_set({0: 1, 1: 0})
)
assert edges_equal(
nx.min_weight_matching(G), matching_dict_to_set({0: 1, 1: 0})
)
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),
matching_dict_to_set({"three": "two", "two": "three"}),
)
assert edges_equal(
nx.min_weight_matching(G),
matching_dict_to_set({"one": "two", "two": "one"}),
)
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), matching_dict_to_set({2: 3, 3: 2})
)
assert edges_equal(
nx.max_weight_matching(G, 1), matching_dict_to_set({1: 2, 2: 1, 3: 4, 4: 3})
)
assert edges_equal(
nx.min_weight_matching(G), matching_dict_to_set({1: 2, 3: 4})
)
assert edges_equal(
nx.min_weight_matching(G, 1), matching_dict_to_set({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), matching_dict_to_set({1: 2, 3: 4})
)
assert edges_equal(
nx.min_weight_matching(G), matching_dict_to_set({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"),
matching_dict_to_set({"one": "two", "two": "one"}),
)
assert edges_equal(
nx.min_weight_matching(G, weight="abcd"),
matching_dict_to_set({"three": "two"}),
)
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), matching_dict_to_set({1: 4, 2: 3, 3: 2, 4: 1})
)
assert edges_equal(
nx.min_weight_matching(G), matching_dict_to_set({1: 4, 2: 3, 3: 2, 4: 1})
)
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), matching_dict_to_set({1: 2, 2: 1})
)
assert edges_equal(
nx.max_weight_matching(G, maxcardinality=True),
matching_dict_to_set({1: 3, 2: 4, 3: 1, 4: 2}),
)
assert edges_equal(
nx.min_weight_matching(G), matching_dict_to_set({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 = matching_dict_to_set({1: 2, 2: 1, 3: 4, 4: 3})
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 = matching_dict_to_set({1: 6, 2: 3, 3: 2, 4: 5, 5: 4, 6: 1})
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 = matching_dict_to_set({1: 6, 2: 3, 3: 2, 4: 5, 5: 4, 6: 1})
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 = matching_dict_to_set({1: 2, 2: 1, 3: 6, 4: 5, 5: 4, 6: 3})
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),
]
)
dict_format = {1: 3, 2: 4, 3: 1, 4: 2, 5: 6, 6: 5}
expected = {frozenset(e) for e in matching_dict_to_set(dict_format)}
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 = matching_dict_to_set({1: 2, 2: 1, 3: 4, 4: 3, 5: 6, 6: 5, 7: 8, 8: 7})
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 = matching_dict_to_set({1: 2, 2: 1, 3: 5, 4: 6, 5: 3, 6: 4, 7: 8, 8: 7})
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 = matching_dict_to_set({1: 6, 2: 3, 3: 2, 4: 8, 5: 7, 6: 1, 7: 5, 8: 4})
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 = matching_dict_to_set({1: 8, 2: 3, 3: 2, 4: 7, 5: 6, 6: 5, 7: 4, 8: 1})
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),
]
)
ansdict = {1: 6, 2: 3, 3: 2, 4: 8, 5: 7, 6: 1, 7: 5, 8: 4, 9: 10, 10: 9}
answer = matching_dict_to_set(ansdict)
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),
]
)
ans = {1: 6, 2: 3, 3: 2, 4: 8, 5: 7, 6: 1, 7: 5, 8: 4, 9: 10, 10: 9}
answer = matching_dict_to_set(ans)
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),
]
)
ans = {1: 6, 2: 3, 3: 2, 4: 8, 5: 7, 6: 1, 7: 5, 8: 4, 9: 10, 10: 9}
answer = matching_dict_to_set(ans)
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),
]
)
ans = {
1: 8,
2: 3,
3: 2,
4: 6,
5: 9,
6: 4,
7: 10,
8: 1,
9: 5,
10: 7,
11: 12,
12: 11,
}
answer = matching_dict_to_set(ans)
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),
]
)
ans = {1: 2, 2: 1, 3: 5, 4: 9, 5: 3, 6: 7, 7: 6, 8: 10, 9: 4, 10: 8}
answer = matching_dict_to_set(ans)
assert edges_equal(nx.max_weight_matching(G), answer)
assert edges_equal(nx.min_weight_matching(G), answer)
def test_wrong_graph_type(self):
error = nx.NetworkXNotImplemented
raises(error, nx.max_weight_matching, nx.MultiGraph())
raises(error, nx.max_weight_matching, nx.MultiDiGraph())
raises(error, nx.max_weight_matching, nx.DiGraph())
raises(error, nx.min_weight_matching, nx.DiGraph())
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_invalid_input(self):
error = nx.NetworkXError
G = nx.path_graph(4)
# edge to node not in G
raises(error, nx.is_matching, G, {(0, 5), (2, 3)})
# edge not a 2-tuple
raises(error, nx.is_matching, G, {(0, 1, 2), (2, 3)})
raises(error, nx.is_matching, G, {(0,), (2, 3)})
def test_selfloops(self):
error = nx.NetworkXError
G = nx.path_graph(4)
# selfloop for node not in G
raises(error, nx.is_matching, G, {(5, 5), (2, 3)})
# 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)})
raises(nx.NetworkXError, nx.is_matching, G, {(0, 55)})
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_invalid_input(self):
error = nx.NetworkXError
G = nx.path_graph(4)
# edge to node not in G
raises(error, nx.is_maximal_matching, G, {(0, 5)})
raises(error, nx.is_maximal_matching, G, {(5, 0)})
# edge not a 2-tuple
raises(error, nx.is_maximal_matching, G, {(0, 1, 2), (2, 3)})
raises(error, nx.is_maximal_matching, G, {(0,), (2, 3)})
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_invalid_input(self):
error = nx.NetworkXError
G = nx.path_graph(4)
# edge to node not in G
raises(error, nx.is_perfect_matching, G, {(0, 5)})
raises(error, nx.is_perfect_matching, G, {(5, 0)})
# edge not a 2-tuple
raises(error, nx.is_perfect_matching, G, {(0, 1, 2), (2, 3)})
raises(error, nx.is_perfect_matching, G, {(0,), (2, 3)})
def test_selfloops(self):
error = nx.NetworkXError
G = nx.path_graph(4)
# selfloop for node not in G
raises(error, nx.is_perfect_matching, G, {(5, 5), (2, 3)})
# 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)
def test_wrong_graph_type(self):
error = nx.NetworkXNotImplemented
raises(error, nx.maximal_matching, nx.MultiGraph())
raises(error, nx.maximal_matching, nx.MultiDiGraph())
raises(error, nx.maximal_matching, nx.DiGraph())

View File

@ -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),
}

View File

@ -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))

View File

@ -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)

View File

@ -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]

View File

@ -0,0 +1,42 @@
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)

View File

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

View File

@ -0,0 +1,535 @@
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)
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

View File

@ -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)

View File

@ -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)

View File

@ -0,0 +1,92 @@
import pytest
import networkx
import networkx as nx
import networkx.algorithms.regular as reg
import networkx.generators as gen
class TestKFactor:
def test_k_factor_trivial(self):
g = gen.cycle_graph(4)
f = reg.k_factor(g, 2)
assert g.edges == f.edges
def test_k_factor1(self):
g = gen.grid_2d_graph(4, 4)
g_kf = reg.k_factor(g, 2)
for edge in g_kf.edges():
assert g.has_edge(edge[0], edge[1])
for _, degree in g_kf.degree():
assert degree == 2
def test_k_factor2(self):
g = gen.complete_graph(6)
g_kf = reg.k_factor(g, 3)
for edge in g_kf.edges():
assert g.has_edge(edge[0], edge[1])
for _, degree in g_kf.degree():
assert degree == 3
def test_k_factor3(self):
g = gen.grid_2d_graph(4, 4)
with pytest.raises(nx.NetworkXUnfeasible):
reg.k_factor(g, 3)
def test_k_factor4(self):
g = gen.lattice.hexagonal_lattice_graph(4, 4)
# Perfect matching doesn't exist for 4,4 hexagonal lattice graph
with pytest.raises(nx.NetworkXUnfeasible):
reg.k_factor(g, 2)
def test_k_factor5(self):
g = gen.complete_graph(6)
# small k to exercise SmallKGadget
g_kf = reg.k_factor(g, 2)
for edge in g_kf.edges():
assert g.has_edge(edge[0], edge[1])
for _, degree in g_kf.degree():
assert degree == 2
class TestIsRegular:
def test_is_regular1(self):
g = gen.cycle_graph(4)
assert reg.is_regular(g)
def test_is_regular2(self):
g = gen.complete_graph(5)
assert reg.is_regular(g)
def test_is_regular3(self):
g = gen.lollipop_graph(5, 5)
assert not reg.is_regular(g)
def test_is_regular4(self):
g = nx.DiGraph()
g.add_edges_from([(0, 1), (1, 2), (2, 0)])
assert reg.is_regular(g)
def test_is_regular_empty_graph_raises():
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)

View File

@ -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)

View File

@ -0,0 +1,946 @@
import pytest
import networkx as nx
from networkx.algorithms.similarity import (
graph_edit_distance,
optimal_edit_paths,
optimize_graph_edit_distance,
)
from networkx.generators.classic import (
circular_ladder_graph,
cycle_graph,
path_graph,
wheel_graph,
)
def nmatch(n1, n2):
return n1 == n2
def ematch(e1, e2):
return e1 == e2
def getCanonical():
G = nx.Graph()
G.add_node("A", label="A")
G.add_node("B", label="B")
G.add_node("C", label="C")
G.add_node("D", label="D")
G.add_edge("A", "B", label="a-b")
G.add_edge("B", "C", label="b-c")
G.add_edge("B", "D", label="b-d")
return G
class TestSimilarity:
@classmethod
def setup_class(cls):
global np
np = pytest.importorskip("numpy")
pytest.importorskip("scipy")
def test_graph_edit_distance_roots_and_timeout(self):
G0 = nx.star_graph(5)
G1 = G0.copy()
pytest.raises(ValueError, graph_edit_distance, G0, G1, roots=[2])
pytest.raises(ValueError, graph_edit_distance, G0, G1, roots=[2, 3, 4])
pytest.raises(nx.NodeNotFound, graph_edit_distance, G0, G1, roots=(9, 3))
pytest.raises(nx.NodeNotFound, graph_edit_distance, G0, G1, roots=(3, 9))
pytest.raises(nx.NodeNotFound, graph_edit_distance, G0, G1, roots=(9, 9))
assert graph_edit_distance(G0, G1, roots=(1, 2)) == 0
assert graph_edit_distance(G0, G1, roots=(0, 1)) == 8
assert graph_edit_distance(G0, G1, roots=(1, 2), timeout=5) == 0
assert graph_edit_distance(G0, G1, roots=(0, 1), timeout=5) == 8
assert graph_edit_distance(G0, G1, roots=(0, 1), timeout=0.0001) is None
# test raise on 0 timeout
pytest.raises(nx.NetworkXError, graph_edit_distance, G0, G1, timeout=0)
def test_graph_edit_distance(self):
G0 = nx.Graph()
G1 = path_graph(6)
G2 = cycle_graph(6)
G3 = wheel_graph(7)
assert graph_edit_distance(G0, G0) == 0
assert graph_edit_distance(G0, G1) == 11
assert graph_edit_distance(G1, G0) == 11
assert graph_edit_distance(G0, G2) == 12
assert graph_edit_distance(G2, G0) == 12
assert graph_edit_distance(G0, G3) == 19
assert graph_edit_distance(G3, G0) == 19
assert graph_edit_distance(G1, G1) == 0
assert graph_edit_distance(G1, G2) == 1
assert graph_edit_distance(G2, G1) == 1
assert graph_edit_distance(G1, G3) == 8
assert graph_edit_distance(G3, G1) == 8
assert graph_edit_distance(G2, G2) == 0
assert graph_edit_distance(G2, G3) == 7
assert graph_edit_distance(G3, G2) == 7
assert graph_edit_distance(G3, G3) == 0
def test_graph_edit_distance_node_match(self):
G1 = cycle_graph(5)
G2 = cycle_graph(5)
for n, attr in G1.nodes.items():
attr["color"] = "red" if n % 2 == 0 else "blue"
for n, attr in G2.nodes.items():
attr["color"] = "red" if n % 2 == 1 else "blue"
assert graph_edit_distance(G1, G2) == 0
assert (
graph_edit_distance(
G1, G2, node_match=lambda n1, n2: n1["color"] == n2["color"]
)
== 1
)
def test_graph_edit_distance_edge_match(self):
G1 = path_graph(6)
G2 = path_graph(6)
for e, attr in G1.edges.items():
attr["color"] = "red" if min(e) % 2 == 0 else "blue"
for e, attr in G2.edges.items():
attr["color"] = "red" if min(e) // 3 == 0 else "blue"
assert graph_edit_distance(G1, G2) == 0
assert (
graph_edit_distance(
G1, G2, edge_match=lambda e1, e2: e1["color"] == e2["color"]
)
== 2
)
def test_graph_edit_distance_node_cost(self):
G1 = path_graph(6)
G2 = path_graph(6)
for n, attr in G1.nodes.items():
attr["color"] = "red" if n % 2 == 0 else "blue"
for n, attr in G2.nodes.items():
attr["color"] = "red" if n % 2 == 1 else "blue"
def node_subst_cost(uattr, vattr):
if uattr["color"] == vattr["color"]:
return 1
else:
return 10
def node_del_cost(attr):
if attr["color"] == "blue":
return 20
else:
return 50
def node_ins_cost(attr):
if attr["color"] == "blue":
return 40
else:
return 100
assert (
graph_edit_distance(
G1,
G2,
node_subst_cost=node_subst_cost,
node_del_cost=node_del_cost,
node_ins_cost=node_ins_cost,
)
== 6
)
def test_graph_edit_distance_edge_cost(self):
G1 = path_graph(6)
G2 = path_graph(6)
for e, attr in G1.edges.items():
attr["color"] = "red" if min(e) % 2 == 0 else "blue"
for e, attr in G2.edges.items():
attr["color"] = "red" if min(e) // 3 == 0 else "blue"
def edge_subst_cost(gattr, hattr):
if gattr["color"] == hattr["color"]:
return 0.01
else:
return 0.1
def edge_del_cost(attr):
if attr["color"] == "blue":
return 0.2
else:
return 0.5
def edge_ins_cost(attr):
if attr["color"] == "blue":
return 0.4
else:
return 1.0
assert (
graph_edit_distance(
G1,
G2,
edge_subst_cost=edge_subst_cost,
edge_del_cost=edge_del_cost,
edge_ins_cost=edge_ins_cost,
)
== 0.23
)
def test_graph_edit_distance_upper_bound(self):
G1 = circular_ladder_graph(2)
G2 = circular_ladder_graph(6)
assert graph_edit_distance(G1, G2, upper_bound=5) is None
assert graph_edit_distance(G1, G2, upper_bound=24) == 22
assert graph_edit_distance(G1, G2) == 22
def test_optimal_edit_paths(self):
G1 = path_graph(3)
G2 = cycle_graph(3)
paths, cost = optimal_edit_paths(G1, G2)
assert cost == 1
assert len(paths) == 6
def canonical(vertex_path, edge_path):
return (
tuple(sorted(vertex_path)),
tuple(sorted(edge_path, key=lambda x: (None in x, x))),
)
expected_paths = [
(
[(0, 0), (1, 1), (2, 2)],
[((0, 1), (0, 1)), ((1, 2), (1, 2)), (None, (0, 2))],
),
(
[(0, 0), (1, 2), (2, 1)],
[((0, 1), (0, 2)), ((1, 2), (1, 2)), (None, (0, 1))],
),
(
[(0, 1), (1, 0), (2, 2)],
[((0, 1), (0, 1)), ((1, 2), (0, 2)), (None, (1, 2))],
),
(
[(0, 1), (1, 2), (2, 0)],
[((0, 1), (1, 2)), ((1, 2), (0, 2)), (None, (0, 1))],
),
(
[(0, 2), (1, 0), (2, 1)],
[((0, 1), (0, 2)), ((1, 2), (0, 1)), (None, (1, 2))],
),
(
[(0, 2), (1, 1), (2, 0)],
[((0, 1), (1, 2)), ((1, 2), (0, 1)), (None, (0, 2))],
),
]
assert {canonical(*p) for p in paths} == {canonical(*p) for p in expected_paths}
def test_optimize_graph_edit_distance(self):
G1 = circular_ladder_graph(2)
G2 = circular_ladder_graph(6)
bestcost = 1000
for cost in optimize_graph_edit_distance(G1, G2):
assert cost < bestcost
bestcost = cost
assert bestcost == 22
# def test_graph_edit_distance_bigger(self):
# G1 = circular_ladder_graph(12)
# G2 = circular_ladder_graph(16)
# assert_equal(graph_edit_distance(G1, G2), 22)
def test_selfloops(self):
G0 = nx.Graph()
G1 = nx.Graph()
G1.add_edges_from((("A", "A"), ("A", "B")))
G2 = nx.Graph()
G2.add_edges_from((("A", "B"), ("B", "B")))
G3 = nx.Graph()
G3.add_edges_from((("A", "A"), ("A", "B"), ("B", "B")))
assert graph_edit_distance(G0, G0) == 0
assert graph_edit_distance(G0, G1) == 4
assert graph_edit_distance(G1, G0) == 4
assert graph_edit_distance(G0, G2) == 4
assert graph_edit_distance(G2, G0) == 4
assert graph_edit_distance(G0, G3) == 5
assert graph_edit_distance(G3, G0) == 5
assert graph_edit_distance(G1, G1) == 0
assert graph_edit_distance(G1, G2) == 0
assert graph_edit_distance(G2, G1) == 0
assert graph_edit_distance(G1, G3) == 1
assert graph_edit_distance(G3, G1) == 1
assert graph_edit_distance(G2, G2) == 0
assert graph_edit_distance(G2, G3) == 1
assert graph_edit_distance(G3, G2) == 1
assert graph_edit_distance(G3, G3) == 0
def test_digraph(self):
G0 = nx.DiGraph()
G1 = nx.DiGraph()
G1.add_edges_from((("A", "B"), ("B", "C"), ("C", "D"), ("D", "A")))
G2 = nx.DiGraph()
G2.add_edges_from((("A", "B"), ("B", "C"), ("C", "D"), ("A", "D")))
G3 = nx.DiGraph()
G3.add_edges_from((("A", "B"), ("A", "C"), ("B", "D"), ("C", "D")))
assert graph_edit_distance(G0, G0) == 0
assert graph_edit_distance(G0, G1) == 8
assert graph_edit_distance(G1, G0) == 8
assert graph_edit_distance(G0, G2) == 8
assert graph_edit_distance(G2, G0) == 8
assert graph_edit_distance(G0, G3) == 8
assert graph_edit_distance(G3, G0) == 8
assert graph_edit_distance(G1, G1) == 0
assert graph_edit_distance(G1, G2) == 2
assert graph_edit_distance(G2, G1) == 2
assert graph_edit_distance(G1, G3) == 4
assert graph_edit_distance(G3, G1) == 4
assert graph_edit_distance(G2, G2) == 0
assert graph_edit_distance(G2, G3) == 2
assert graph_edit_distance(G3, G2) == 2
assert graph_edit_distance(G3, G3) == 0
def test_multigraph(self):
G0 = nx.MultiGraph()
G1 = nx.MultiGraph()
G1.add_edges_from((("A", "B"), ("B", "C"), ("A", "C")))
G2 = nx.MultiGraph()
G2.add_edges_from((("A", "B"), ("B", "C"), ("B", "C"), ("A", "C")))
G3 = nx.MultiGraph()
G3.add_edges_from((("A", "B"), ("B", "C"), ("A", "C"), ("A", "C"), ("A", "C")))
assert graph_edit_distance(G0, G0) == 0
assert graph_edit_distance(G0, G1) == 6
assert graph_edit_distance(G1, G0) == 6
assert graph_edit_distance(G0, G2) == 7
assert graph_edit_distance(G2, G0) == 7
assert graph_edit_distance(G0, G3) == 8
assert graph_edit_distance(G3, G0) == 8
assert graph_edit_distance(G1, G1) == 0
assert graph_edit_distance(G1, G2) == 1
assert graph_edit_distance(G2, G1) == 1
assert graph_edit_distance(G1, G3) == 2
assert graph_edit_distance(G3, G1) == 2
assert graph_edit_distance(G2, G2) == 0
assert graph_edit_distance(G2, G3) == 1
assert graph_edit_distance(G3, G2) == 1
assert graph_edit_distance(G3, G3) == 0
def test_multidigraph(self):
G1 = nx.MultiDiGraph()
G1.add_edges_from(
(
("hardware", "kernel"),
("kernel", "hardware"),
("kernel", "userspace"),
("userspace", "kernel"),
)
)
G2 = nx.MultiDiGraph()
G2.add_edges_from(
(
("winter", "spring"),
("spring", "summer"),
("summer", "autumn"),
("autumn", "winter"),
)
)
assert graph_edit_distance(G1, G2) == 5
assert graph_edit_distance(G2, G1) == 5
# by https://github.com/jfbeaumont
def testCopy(self):
G = nx.Graph()
G.add_node("A", label="A")
G.add_node("B", label="B")
G.add_edge("A", "B", label="a-b")
assert (
graph_edit_distance(G, G.copy(), node_match=nmatch, edge_match=ematch) == 0
)
def testSame(self):
G1 = nx.Graph()
G1.add_node("A", label="A")
G1.add_node("B", label="B")
G1.add_edge("A", "B", label="a-b")
G2 = nx.Graph()
G2.add_node("A", label="A")
G2.add_node("B", label="B")
G2.add_edge("A", "B", label="a-b")
assert graph_edit_distance(G1, G2, node_match=nmatch, edge_match=ematch) == 0
def testOneEdgeLabelDiff(self):
G1 = nx.Graph()
G1.add_node("A", label="A")
G1.add_node("B", label="B")
G1.add_edge("A", "B", label="a-b")
G2 = nx.Graph()
G2.add_node("A", label="A")
G2.add_node("B", label="B")
G2.add_edge("A", "B", label="bad")
assert graph_edit_distance(G1, G2, node_match=nmatch, edge_match=ematch) == 1
def testOneNodeLabelDiff(self):
G1 = nx.Graph()
G1.add_node("A", label="A")
G1.add_node("B", label="B")
G1.add_edge("A", "B", label="a-b")
G2 = nx.Graph()
G2.add_node("A", label="Z")
G2.add_node("B", label="B")
G2.add_edge("A", "B", label="a-b")
assert graph_edit_distance(G1, G2, node_match=nmatch, edge_match=ematch) == 1
def testOneExtraNode(self):
G1 = nx.Graph()
G1.add_node("A", label="A")
G1.add_node("B", label="B")
G1.add_edge("A", "B", label="a-b")
G2 = nx.Graph()
G2.add_node("A", label="A")
G2.add_node("B", label="B")
G2.add_edge("A", "B", label="a-b")
G2.add_node("C", label="C")
assert graph_edit_distance(G1, G2, node_match=nmatch, edge_match=ematch) == 1
def testOneExtraEdge(self):
G1 = nx.Graph()
G1.add_node("A", label="A")
G1.add_node("B", label="B")
G1.add_node("C", label="C")
G1.add_node("C", label="C")
G1.add_edge("A", "B", label="a-b")
G2 = nx.Graph()
G2.add_node("A", label="A")
G2.add_node("B", label="B")
G2.add_node("C", label="C")
G2.add_edge("A", "B", label="a-b")
G2.add_edge("A", "C", label="a-c")
assert graph_edit_distance(G1, G2, node_match=nmatch, edge_match=ematch) == 1
def testOneExtraNodeAndEdge(self):
G1 = nx.Graph()
G1.add_node("A", label="A")
G1.add_node("B", label="B")
G1.add_edge("A", "B", label="a-b")
G2 = nx.Graph()
G2.add_node("A", label="A")
G2.add_node("B", label="B")
G2.add_node("C", label="C")
G2.add_edge("A", "B", label="a-b")
G2.add_edge("A", "C", label="a-c")
assert graph_edit_distance(G1, G2, node_match=nmatch, edge_match=ematch) == 2
def testGraph1(self):
G1 = getCanonical()
G2 = nx.Graph()
G2.add_node("A", label="A")
G2.add_node("B", label="B")
G2.add_node("D", label="D")
G2.add_node("E", label="E")
G2.add_edge("A", "B", label="a-b")
G2.add_edge("B", "D", label="b-d")
G2.add_edge("D", "E", label="d-e")
assert graph_edit_distance(G1, G2, node_match=nmatch, edge_match=ematch) == 3
def testGraph2(self):
G1 = getCanonical()
G2 = nx.Graph()
G2.add_node("A", label="A")
G2.add_node("B", label="B")
G2.add_node("C", label="C")
G2.add_node("D", label="D")
G2.add_node("E", label="E")
G2.add_edge("A", "B", label="a-b")
G2.add_edge("B", "C", label="b-c")
G2.add_edge("C", "D", label="c-d")
G2.add_edge("C", "E", label="c-e")
assert graph_edit_distance(G1, G2, node_match=nmatch, edge_match=ematch) == 4
def testGraph3(self):
G1 = getCanonical()
G2 = nx.Graph()
G2.add_node("A", label="A")
G2.add_node("B", label="B")
G2.add_node("C", label="C")
G2.add_node("D", label="D")
G2.add_node("E", label="E")
G2.add_node("F", label="F")
G2.add_node("G", label="G")
G2.add_edge("A", "C", label="a-c")
G2.add_edge("A", "D", label="a-d")
G2.add_edge("D", "E", label="d-e")
G2.add_edge("D", "F", label="d-f")
G2.add_edge("D", "G", label="d-g")
G2.add_edge("E", "B", label="e-b")
assert graph_edit_distance(G1, G2, node_match=nmatch, edge_match=ematch) == 12
def testGraph4(self):
G1 = getCanonical()
G2 = nx.Graph()
G2.add_node("A", label="A")
G2.add_node("B", label="B")
G2.add_node("C", label="C")
G2.add_node("D", label="D")
G2.add_edge("A", "B", label="a-b")
G2.add_edge("B", "C", label="b-c")
G2.add_edge("C", "D", label="c-d")
assert graph_edit_distance(G1, G2, node_match=nmatch, edge_match=ematch) == 2
def testGraph4_a(self):
G1 = getCanonical()
G2 = nx.Graph()
G2.add_node("A", label="A")
G2.add_node("B", label="B")
G2.add_node("C", label="C")
G2.add_node("D", label="D")
G2.add_edge("A", "B", label="a-b")
G2.add_edge("B", "C", label="b-c")
G2.add_edge("A", "D", label="a-d")
assert graph_edit_distance(G1, G2, node_match=nmatch, edge_match=ematch) == 2
def testGraph4_b(self):
G1 = getCanonical()
G2 = nx.Graph()
G2.add_node("A", label="A")
G2.add_node("B", label="B")
G2.add_node("C", label="C")
G2.add_node("D", label="D")
G2.add_edge("A", "B", label="a-b")
G2.add_edge("B", "C", label="b-c")
G2.add_edge("B", "D", label="bad")
assert graph_edit_distance(G1, G2, node_match=nmatch, edge_match=ematch) == 1
# note: nx.simrank_similarity_numpy not included because returns np.array
simrank_algs = [
nx.simrank_similarity,
nx.algorithms.similarity._simrank_similarity_python,
]
@pytest.mark.parametrize("simrank_similarity", simrank_algs)
def test_simrank_no_source_no_target(self, simrank_similarity):
G = nx.cycle_graph(5)
expected = {
0: {
0: 1,
1: 0.3951219505902448,
2: 0.5707317069281646,
3: 0.5707317069281646,
4: 0.3951219505902449,
},
1: {
0: 0.3951219505902448,
1: 1,
2: 0.3951219505902449,
3: 0.5707317069281646,
4: 0.5707317069281646,
},
2: {
0: 0.5707317069281646,
1: 0.3951219505902449,
2: 1,
3: 0.3951219505902449,
4: 0.5707317069281646,
},
3: {
0: 0.5707317069281646,
1: 0.5707317069281646,
2: 0.3951219505902449,
3: 1,
4: 0.3951219505902449,
},
4: {
0: 0.3951219505902449,
1: 0.5707317069281646,
2: 0.5707317069281646,
3: 0.3951219505902449,
4: 1,
},
}
actual = simrank_similarity(G)
for k, v in expected.items():
assert v == pytest.approx(actual[k], abs=1e-2)
# For a DiGraph test, use the first graph from the paper cited in
# the docs: https://dl.acm.org/doi/pdf/10.1145/775047.775126
G = nx.DiGraph()
G.add_node(0, label="Univ")
G.add_node(1, label="ProfA")
G.add_node(2, label="ProfB")
G.add_node(3, label="StudentA")
G.add_node(4, label="StudentB")
G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 4), (4, 2), (3, 0)])
expected = {
0: {0: 1, 1: 0.0, 2: 0.1323363991265798, 3: 0.0, 4: 0.03387811817640443},
1: {0: 0.0, 1: 1, 2: 0.4135512472705618, 3: 0.0, 4: 0.10586911930126384},
2: {
0: 0.1323363991265798,
1: 0.4135512472705618,
2: 1,
3: 0.04234764772050554,
4: 0.08822426608438655,
},
3: {0: 0.0, 1: 0.0, 2: 0.04234764772050554, 3: 1, 4: 0.3308409978164495},
4: {
0: 0.03387811817640443,
1: 0.10586911930126384,
2: 0.08822426608438655,
3: 0.3308409978164495,
4: 1,
},
}
# Use the importance_factor from the paper to get the same numbers.
actual = simrank_similarity(G, importance_factor=0.8)
for k, v in expected.items():
assert v == pytest.approx(actual[k], abs=1e-2)
@pytest.mark.parametrize("simrank_similarity", simrank_algs)
def test_simrank_source_no_target(self, simrank_similarity):
G = nx.cycle_graph(5)
expected = {
0: 1,
1: 0.3951219505902448,
2: 0.5707317069281646,
3: 0.5707317069281646,
4: 0.3951219505902449,
}
actual = simrank_similarity(G, source=0)
assert expected == pytest.approx(actual, abs=1e-2)
# For a DiGraph test, use the first graph from the paper cited in
# the docs: https://dl.acm.org/doi/pdf/10.1145/775047.775126
G = nx.DiGraph()
G.add_node(0, label="Univ")
G.add_node(1, label="ProfA")
G.add_node(2, label="ProfB")
G.add_node(3, label="StudentA")
G.add_node(4, label="StudentB")
G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 4), (4, 2), (3, 0)])
expected = {0: 1, 1: 0.0, 2: 0.1323363991265798, 3: 0.0, 4: 0.03387811817640443}
# Use the importance_factor from the paper to get the same numbers.
actual = simrank_similarity(G, importance_factor=0.8, source=0)
assert expected == pytest.approx(actual, abs=1e-2)
@pytest.mark.parametrize("simrank_similarity", simrank_algs)
def test_simrank_noninteger_nodes(self, simrank_similarity):
G = nx.cycle_graph(5)
G = nx.relabel_nodes(G, dict(enumerate("abcde")))
expected = {
"a": 1,
"b": 0.3951219505902448,
"c": 0.5707317069281646,
"d": 0.5707317069281646,
"e": 0.3951219505902449,
}
actual = simrank_similarity(G, source="a")
assert expected == pytest.approx(actual, abs=1e-2)
# For a DiGraph test, use the first graph from the paper cited in
# the docs: https://dl.acm.org/doi/pdf/10.1145/775047.775126
G = nx.DiGraph()
G.add_node(0, label="Univ")
G.add_node(1, label="ProfA")
G.add_node(2, label="ProfB")
G.add_node(3, label="StudentA")
G.add_node(4, label="StudentB")
G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 4), (4, 2), (3, 0)])
node_labels = dict(enumerate(nx.get_node_attributes(G, "label").values()))
G = nx.relabel_nodes(G, node_labels)
expected = {
"Univ": 1,
"ProfA": 0.0,
"ProfB": 0.1323363991265798,
"StudentA": 0.0,
"StudentB": 0.03387811817640443,
}
# Use the importance_factor from the paper to get the same numbers.
actual = simrank_similarity(G, importance_factor=0.8, source="Univ")
assert expected == pytest.approx(actual, abs=1e-2)
@pytest.mark.parametrize("simrank_similarity", simrank_algs)
def test_simrank_source_and_target(self, simrank_similarity):
G = nx.cycle_graph(5)
expected = 1
actual = simrank_similarity(G, source=0, target=0)
assert expected == pytest.approx(actual, abs=1e-2)
# For a DiGraph test, use the first graph from the paper cited in
# the docs: https://dl.acm.org/doi/pdf/10.1145/775047.775126
G = nx.DiGraph()
G.add_node(0, label="Univ")
G.add_node(1, label="ProfA")
G.add_node(2, label="ProfB")
G.add_node(3, label="StudentA")
G.add_node(4, label="StudentB")
G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 4), (4, 2), (3, 0)])
expected = 0.1323363991265798
# Use the importance_factor from the paper to get the same numbers.
# Use the pair (0,2) because (0,0) and (0,1) have trivial results.
actual = simrank_similarity(G, importance_factor=0.8, source=0, target=2)
assert expected == pytest.approx(actual, abs=1e-5)
@pytest.mark.parametrize("alg", simrank_algs)
def test_simrank_max_iterations(self, alg):
G = nx.cycle_graph(5)
pytest.raises(nx.ExceededMaxIterations, alg, G, max_iterations=10)
def test_simrank_source_not_found(self):
G = nx.cycle_graph(5)
with pytest.raises(nx.NodeNotFound, match="Source node 10 not in G"):
nx.simrank_similarity(G, source=10)
def test_simrank_target_not_found(self):
G = nx.cycle_graph(5)
with pytest.raises(nx.NodeNotFound, match="Target node 10 not in G"):
nx.simrank_similarity(G, target=10)
def test_simrank_between_versions(self):
G = nx.cycle_graph(5)
# _python tolerance 1e-4
expected_python_tol4 = {
0: 1,
1: 0.394512499239852,
2: 0.5703550452791322,
3: 0.5703550452791323,
4: 0.394512499239852,
}
# _numpy tolerance 1e-4
expected_numpy_tol4 = {
0: 1.0,
1: 0.3947180735764555,
2: 0.570482097206368,
3: 0.570482097206368,
4: 0.3947180735764555,
}
actual = nx.simrank_similarity(G, source=0)
assert expected_numpy_tol4 == pytest.approx(actual, abs=1e-7)
# versions differ at 1e-4 level but equal at 1e-3
assert expected_python_tol4 != pytest.approx(actual, abs=1e-4)
assert expected_python_tol4 == pytest.approx(actual, abs=1e-3)
actual = nx.similarity._simrank_similarity_python(G, source=0)
assert expected_python_tol4 == pytest.approx(actual, abs=1e-7)
# versions differ at 1e-4 level but equal at 1e-3
assert expected_numpy_tol4 != pytest.approx(actual, abs=1e-4)
assert expected_numpy_tol4 == pytest.approx(actual, abs=1e-3)
def test_simrank_numpy_no_source_no_target(self):
G = nx.cycle_graph(5)
expected = np.array(
[
[
1.0,
0.3947180735764555,
0.570482097206368,
0.570482097206368,
0.3947180735764555,
],
[
0.3947180735764555,
1.0,
0.3947180735764555,
0.570482097206368,
0.570482097206368,
],
[
0.570482097206368,
0.3947180735764555,
1.0,
0.3947180735764555,
0.570482097206368,
],
[
0.570482097206368,
0.570482097206368,
0.3947180735764555,
1.0,
0.3947180735764555,
],
[
0.3947180735764555,
0.570482097206368,
0.570482097206368,
0.3947180735764555,
1.0,
],
]
)
actual = nx.similarity._simrank_similarity_numpy(G)
np.testing.assert_allclose(expected, actual, atol=1e-7)
def test_simrank_numpy_source_no_target(self):
G = nx.cycle_graph(5)
expected = np.array(
[
1.0,
0.3947180735764555,
0.570482097206368,
0.570482097206368,
0.3947180735764555,
]
)
actual = nx.similarity._simrank_similarity_numpy(G, source=0)
np.testing.assert_allclose(expected, actual, atol=1e-7)
def test_simrank_numpy_source_and_target(self):
G = nx.cycle_graph(5)
expected = 1.0
actual = nx.similarity._simrank_similarity_numpy(G, source=0, target=0)
np.testing.assert_allclose(expected, actual, atol=1e-7)
def test_panther_similarity_unweighted(self):
np.random.seed(42)
G = nx.Graph()
G.add_edge(0, 1)
G.add_edge(0, 2)
G.add_edge(0, 3)
G.add_edge(1, 2)
G.add_edge(2, 4)
expected = {3: 0.5, 2: 0.5, 1: 0.5, 4: 0.125}
sim = nx.panther_similarity(G, 0, path_length=2)
assert sim == expected
def test_panther_similarity_weighted(self):
np.random.seed(42)
G = nx.Graph()
G.add_edge("v1", "v2", w=5)
G.add_edge("v1", "v3", w=1)
G.add_edge("v1", "v4", w=2)
G.add_edge("v2", "v3", w=0.1)
G.add_edge("v3", "v5", w=1)
expected = {"v3": 0.75, "v4": 0.5, "v2": 0.5, "v5": 0.25}
sim = nx.panther_similarity(G, "v1", path_length=2, weight="w")
assert sim == expected
def test_panther_similarity_source_not_found(self):
G = nx.Graph()
G.add_edges_from([(0, 1), (0, 2), (0, 3), (1, 2), (2, 4)])
with pytest.raises(nx.NodeNotFound, match="Source node 10 not in G"):
nx.panther_similarity(G, source=10)
def test_panther_similarity_isolated(self):
G = nx.Graph()
G.add_nodes_from(range(5))
with pytest.raises(
nx.NetworkXUnfeasible,
match="Panther similarity is not defined for the isolated source node 1.",
):
nx.panther_similarity(G, source=1)
def test_generate_random_paths_unweighted(self):
index_map = {}
num_paths = 10
path_length = 2
G = nx.Graph()
G.add_edge(0, 1)
G.add_edge(0, 2)
G.add_edge(0, 3)
G.add_edge(1, 2)
G.add_edge(2, 4)
paths = nx.generate_random_paths(
G, num_paths, path_length=path_length, index_map=index_map, seed=42
)
expected_paths = [
[3, 0, 3],
[4, 2, 1],
[2, 1, 0],
[2, 0, 3],
[3, 0, 1],
[3, 0, 1],
[4, 2, 0],
[2, 1, 0],
[3, 0, 2],
[2, 1, 2],
]
expected_map = {
0: {0, 2, 3, 4, 5, 6, 7, 8},
1: {1, 2, 4, 5, 7, 9},
2: {1, 2, 3, 6, 7, 8, 9},
3: {0, 3, 4, 5, 8},
4: {1, 6},
}
assert expected_paths == list(paths)
assert expected_map == index_map
def test_generate_random_paths_weighted(self):
np.random.seed(42)
index_map = {}
num_paths = 10
path_length = 6
G = nx.Graph()
G.add_edge("a", "b", weight=0.6)
G.add_edge("a", "c", weight=0.2)
G.add_edge("c", "d", weight=0.1)
G.add_edge("c", "e", weight=0.7)
G.add_edge("c", "f", weight=0.9)
G.add_edge("a", "d", weight=0.3)
paths = nx.generate_random_paths(
G, num_paths, path_length=path_length, index_map=index_map
)
expected_paths = [
["d", "c", "f", "c", "d", "a", "b"],
["e", "c", "f", "c", "f", "c", "e"],
["d", "a", "b", "a", "b", "a", "c"],
["b", "a", "d", "a", "b", "a", "b"],
["d", "a", "b", "a", "b", "a", "d"],
["d", "a", "b", "a", "b", "a", "c"],
["d", "a", "b", "a", "b", "a", "b"],
["f", "c", "f", "c", "f", "c", "e"],
["d", "a", "d", "a", "b", "a", "b"],
["e", "c", "f", "c", "e", "c", "d"],
]
expected_map = {
"d": {0, 2, 3, 4, 5, 6, 8, 9},
"c": {0, 1, 2, 5, 7, 9},
"f": {0, 1, 9, 7},
"a": {0, 2, 3, 4, 5, 6, 8},
"b": {0, 2, 3, 4, 5, 6, 8},
"e": {1, 9, 7},
}
assert expected_paths == list(paths)
assert expected_map == index_map
def test_symmetry_with_custom_matching(self):
print("G2 is edge (a,b) and G3 is edge (a,a)")
print("but node order for G2 is (a,b) while for G3 it is (b,a)")
a, b = "A", "B"
G2 = nx.Graph()
G2.add_nodes_from((a, b))
G2.add_edges_from([(a, b)])
G3 = nx.Graph()
G3.add_nodes_from((b, a))
G3.add_edges_from([(a, a)])
for G in (G2, G3):
for n in G:
G.nodes[n]["attr"] = n
for e in G.edges:
G.edges[e]["attr"] = e
match = lambda x, y: x == y
print("Starting G2 to G3 GED calculation")
assert nx.graph_edit_distance(G2, G3, node_match=match, edge_match=match) == 1
print("Starting G3 to G2 GED calculation")
assert nx.graph_edit_distance(G3, G2, node_match=match, edge_match=match) == 1

View File

@ -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]
)

Some files were not shown because too many files have changed in this diff Show More