Fixed database typo and removed unnecessary class identifier.
This commit is contained in:
parent
00ad49a143
commit
45fb349a7d
5098 changed files with 952558 additions and 85 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,451 @@
|
|||
import pytest
|
||||
|
||||
np = pytest.importorskip("numpy")
|
||||
|
||||
import networkx as nx
|
||||
|
||||
|
||||
from networkx.algorithms.tree import branchings
|
||||
from networkx.algorithms.tree import recognition
|
||||
|
||||
#
|
||||
# Explicitly discussed examples from Edmonds paper.
|
||||
#
|
||||
|
||||
# Used in Figures A-F.
|
||||
#
|
||||
# fmt: off
|
||||
G_array = np.array([
|
||||
# 0 1 2 3 4 5 6 7 8
|
||||
[0, 0, 12, 0, 12, 0, 0, 0, 0], # 0
|
||||
[4, 0, 0, 0, 0, 13, 0, 0, 0], # 1
|
||||
[0, 17, 0, 21, 0, 12, 0, 0, 0], # 2
|
||||
[5, 0, 0, 0, 17, 0, 18, 0, 0], # 3
|
||||
[0, 0, 0, 0, 0, 0, 0, 12, 0], # 4
|
||||
[0, 0, 0, 0, 0, 0, 14, 0, 12], # 5
|
||||
[0, 0, 21, 0, 0, 0, 0, 0, 15], # 6
|
||||
[0, 0, 0, 19, 0, 0, 15, 0, 0], # 7
|
||||
[0, 0, 0, 0, 0, 0, 0, 18, 0], # 8
|
||||
], dtype=int)
|
||||
# fmt: on
|
||||
|
||||
|
||||
def G1():
|
||||
G = nx.from_numpy_array(G_array, create_using=nx.MultiDiGraph)
|
||||
return G
|
||||
|
||||
|
||||
def G2():
|
||||
# Now we shift all the weights by -10.
|
||||
# Should not affect optimal arborescence, but does affect optimal branching.
|
||||
Garr = G_array.copy()
|
||||
Garr[np.nonzero(Garr)] -= 10
|
||||
G = nx.from_numpy_array(Garr, create_using=nx.MultiDiGraph)
|
||||
return G
|
||||
|
||||
|
||||
# An optimal branching for G1 that is also a spanning arborescence. So it is
|
||||
# also an optimal spanning arborescence.
|
||||
#
|
||||
optimal_arborescence_1 = [
|
||||
(0, 2, 12),
|
||||
(2, 1, 17),
|
||||
(2, 3, 21),
|
||||
(1, 5, 13),
|
||||
(3, 4, 17),
|
||||
(3, 6, 18),
|
||||
(6, 8, 15),
|
||||
(8, 7, 18),
|
||||
]
|
||||
|
||||
# For G2, the optimal branching of G1 (with shifted weights) is no longer
|
||||
# an optimal branching, but it is still an optimal spanning arborescence
|
||||
# (just with shifted weights). An optimal branching for G2 is similar to what
|
||||
# appears in figure G (this is greedy_subopt_branching_1a below), but with the
|
||||
# edge (3, 0, 5), which is now (3, 0, -5), removed. Thus, the optimal branching
|
||||
# is not a spanning arborescence. The code finds optimal_branching_2a.
|
||||
# An alternative and equivalent branching is optimal_branching_2b. We would
|
||||
# need to modify the code to iterate through all equivalent optimal branchings.
|
||||
#
|
||||
# These are maximal branchings or arborescences.
|
||||
optimal_branching_2a = [
|
||||
(5, 6, 4),
|
||||
(6, 2, 11),
|
||||
(6, 8, 5),
|
||||
(8, 7, 8),
|
||||
(2, 1, 7),
|
||||
(2, 3, 11),
|
||||
(3, 4, 7),
|
||||
]
|
||||
optimal_branching_2b = [
|
||||
(8, 7, 8),
|
||||
(7, 3, 9),
|
||||
(3, 4, 7),
|
||||
(3, 6, 8),
|
||||
(6, 2, 11),
|
||||
(2, 1, 7),
|
||||
(1, 5, 3),
|
||||
]
|
||||
optimal_arborescence_2 = [
|
||||
(0, 2, 2),
|
||||
(2, 1, 7),
|
||||
(2, 3, 11),
|
||||
(1, 5, 3),
|
||||
(3, 4, 7),
|
||||
(3, 6, 8),
|
||||
(6, 8, 5),
|
||||
(8, 7, 8),
|
||||
]
|
||||
|
||||
# Two suboptimal maximal branchings on G1 obtained from a greedy algorithm.
|
||||
# 1a matches what is shown in Figure G in Edmonds's paper.
|
||||
greedy_subopt_branching_1a = [
|
||||
(5, 6, 14),
|
||||
(6, 2, 21),
|
||||
(6, 8, 15),
|
||||
(8, 7, 18),
|
||||
(2, 1, 17),
|
||||
(2, 3, 21),
|
||||
(3, 0, 5),
|
||||
(3, 4, 17),
|
||||
]
|
||||
greedy_subopt_branching_1b = [
|
||||
(8, 7, 18),
|
||||
(7, 6, 15),
|
||||
(6, 2, 21),
|
||||
(2, 1, 17),
|
||||
(2, 3, 21),
|
||||
(1, 5, 13),
|
||||
(3, 0, 5),
|
||||
(3, 4, 17),
|
||||
]
|
||||
|
||||
|
||||
def build_branching(edges):
|
||||
G = nx.DiGraph()
|
||||
for u, v, weight in edges:
|
||||
G.add_edge(u, v, weight=weight)
|
||||
return G
|
||||
|
||||
|
||||
def sorted_edges(G, attr="weight", default=1):
|
||||
edges = [(u, v, data.get(attr, default)) for (u, v, data) in G.edges(data=True)]
|
||||
edges = sorted(edges, key=lambda x: (x[2], x[1], x[0]))
|
||||
return edges
|
||||
|
||||
|
||||
def assert_equal_branchings(G1, G2, attr="weight", default=1):
|
||||
edges1 = list(G1.edges(data=True))
|
||||
edges2 = list(G2.edges(data=True))
|
||||
assert len(edges1) == len(edges2)
|
||||
|
||||
# Grab the weights only.
|
||||
e1 = sorted_edges(G1, attr, default)
|
||||
e2 = sorted_edges(G2, attr, default)
|
||||
|
||||
# If we have an exception, let's see the edges.
|
||||
print(e1)
|
||||
print(e2)
|
||||
print
|
||||
|
||||
for a, b in zip(e1, e2):
|
||||
assert a[:2] == b[:2]
|
||||
np.testing.assert_almost_equal(a[2], b[2])
|
||||
|
||||
|
||||
################
|
||||
|
||||
|
||||
def test_optimal_branching1():
|
||||
G = build_branching(optimal_arborescence_1)
|
||||
assert recognition.is_arborescence(G), True
|
||||
assert branchings.branching_weight(G) == 131
|
||||
|
||||
|
||||
def test_optimal_branching2a():
|
||||
G = build_branching(optimal_branching_2a)
|
||||
assert recognition.is_arborescence(G), True
|
||||
assert branchings.branching_weight(G) == 53
|
||||
|
||||
|
||||
def test_optimal_branching2b():
|
||||
G = build_branching(optimal_branching_2b)
|
||||
assert recognition.is_arborescence(G), True
|
||||
assert branchings.branching_weight(G) == 53
|
||||
|
||||
|
||||
def test_optimal_arborescence2():
|
||||
G = build_branching(optimal_arborescence_2)
|
||||
assert recognition.is_arborescence(G), True
|
||||
assert branchings.branching_weight(G) == 51
|
||||
|
||||
|
||||
def test_greedy_suboptimal_branching1a():
|
||||
G = build_branching(greedy_subopt_branching_1a)
|
||||
assert recognition.is_arborescence(G), True
|
||||
assert branchings.branching_weight(G) == 128
|
||||
|
||||
|
||||
def test_greedy_suboptimal_branching1b():
|
||||
G = build_branching(greedy_subopt_branching_1b)
|
||||
assert recognition.is_arborescence(G), True
|
||||
assert branchings.branching_weight(G) == 127
|
||||
|
||||
|
||||
def test_greedy_max1():
|
||||
# Standard test.
|
||||
#
|
||||
G = G1()
|
||||
B = branchings.greedy_branching(G)
|
||||
# There are only two possible greedy branchings. The sorting is such
|
||||
# that it should equal the second suboptimal branching: 1b.
|
||||
B_ = build_branching(greedy_subopt_branching_1b)
|
||||
assert_equal_branchings(B, B_)
|
||||
|
||||
|
||||
def test_greedy_max2():
|
||||
# Different default weight.
|
||||
#
|
||||
G = G1()
|
||||
del G[1][0][0]["weight"]
|
||||
B = branchings.greedy_branching(G, default=6)
|
||||
# Chosen so that edge (3,0,5) is not selected and (1,0,6) is instead.
|
||||
|
||||
edges = [
|
||||
(1, 0, 6),
|
||||
(1, 5, 13),
|
||||
(7, 6, 15),
|
||||
(2, 1, 17),
|
||||
(3, 4, 17),
|
||||
(8, 7, 18),
|
||||
(2, 3, 21),
|
||||
(6, 2, 21),
|
||||
]
|
||||
B_ = build_branching(edges)
|
||||
assert_equal_branchings(B, B_)
|
||||
|
||||
|
||||
def test_greedy_max3():
|
||||
# All equal weights.
|
||||
#
|
||||
G = G1()
|
||||
B = branchings.greedy_branching(G, attr=None)
|
||||
|
||||
# This is mostly arbitrary...the output was generated by running the algo.
|
||||
edges = [
|
||||
(2, 1, 1),
|
||||
(3, 0, 1),
|
||||
(3, 4, 1),
|
||||
(5, 8, 1),
|
||||
(6, 2, 1),
|
||||
(7, 3, 1),
|
||||
(7, 6, 1),
|
||||
(8, 7, 1),
|
||||
]
|
||||
B_ = build_branching(edges)
|
||||
assert_equal_branchings(B, B_, default=1)
|
||||
|
||||
|
||||
def test_greedy_min():
|
||||
G = G1()
|
||||
B = branchings.greedy_branching(G, kind="min")
|
||||
|
||||
edges = [
|
||||
(1, 0, 4),
|
||||
(0, 2, 12),
|
||||
(0, 4, 12),
|
||||
(2, 5, 12),
|
||||
(4, 7, 12),
|
||||
(5, 8, 12),
|
||||
(5, 6, 14),
|
||||
(7, 3, 19),
|
||||
]
|
||||
B_ = build_branching(edges)
|
||||
assert_equal_branchings(B, B_)
|
||||
|
||||
|
||||
def test_edmonds1_maxbranch():
|
||||
G = G1()
|
||||
x = branchings.maximum_branching(G)
|
||||
x_ = build_branching(optimal_arborescence_1)
|
||||
assert_equal_branchings(x, x_)
|
||||
|
||||
|
||||
def test_edmonds1_maxarbor():
|
||||
G = G1()
|
||||
x = branchings.maximum_spanning_arborescence(G)
|
||||
x_ = build_branching(optimal_arborescence_1)
|
||||
assert_equal_branchings(x, x_)
|
||||
|
||||
|
||||
def test_edmonds2_maxbranch():
|
||||
G = G2()
|
||||
x = branchings.maximum_branching(G)
|
||||
x_ = build_branching(optimal_branching_2a)
|
||||
assert_equal_branchings(x, x_)
|
||||
|
||||
|
||||
def test_edmonds2_maxarbor():
|
||||
G = G2()
|
||||
x = branchings.maximum_spanning_arborescence(G)
|
||||
x_ = build_branching(optimal_arborescence_2)
|
||||
assert_equal_branchings(x, x_)
|
||||
|
||||
|
||||
def test_edmonds2_minarbor():
|
||||
G = G1()
|
||||
x = branchings.minimum_spanning_arborescence(G)
|
||||
# This was obtained from algorithm. Need to verify it independently.
|
||||
# Branch weight is: 96
|
||||
edges = [
|
||||
(3, 0, 5),
|
||||
(0, 2, 12),
|
||||
(0, 4, 12),
|
||||
(2, 5, 12),
|
||||
(4, 7, 12),
|
||||
(5, 8, 12),
|
||||
(5, 6, 14),
|
||||
(2, 1, 17),
|
||||
]
|
||||
x_ = build_branching(edges)
|
||||
assert_equal_branchings(x, x_)
|
||||
|
||||
|
||||
def test_edmonds3_minbranch1():
|
||||
G = G1()
|
||||
x = branchings.minimum_branching(G)
|
||||
edges = []
|
||||
x_ = build_branching(edges)
|
||||
assert_equal_branchings(x, x_)
|
||||
|
||||
|
||||
def test_edmonds3_minbranch2():
|
||||
G = G1()
|
||||
G.add_edge(8, 9, weight=-10)
|
||||
x = branchings.minimum_branching(G)
|
||||
edges = [(8, 9, -10)]
|
||||
x_ = build_branching(edges)
|
||||
assert_equal_branchings(x, x_)
|
||||
|
||||
|
||||
# Need more tests
|
||||
|
||||
|
||||
def test_mst():
|
||||
# Make sure we get the same results for undirected graphs.
|
||||
# Example from: https://en.wikipedia.org/wiki/Kruskal's_algorithm
|
||||
G = nx.Graph()
|
||||
edgelist = [
|
||||
(0, 3, [("weight", 5)]),
|
||||
(0, 1, [("weight", 7)]),
|
||||
(1, 3, [("weight", 9)]),
|
||||
(1, 2, [("weight", 8)]),
|
||||
(1, 4, [("weight", 7)]),
|
||||
(3, 4, [("weight", 15)]),
|
||||
(3, 5, [("weight", 6)]),
|
||||
(2, 4, [("weight", 5)]),
|
||||
(4, 5, [("weight", 8)]),
|
||||
(4, 6, [("weight", 9)]),
|
||||
(5, 6, [("weight", 11)]),
|
||||
]
|
||||
G.add_edges_from(edgelist)
|
||||
G = G.to_directed()
|
||||
x = branchings.minimum_spanning_arborescence(G)
|
||||
|
||||
edges = [
|
||||
({0, 1}, 7),
|
||||
({0, 3}, 5),
|
||||
({3, 5}, 6),
|
||||
({1, 4}, 7),
|
||||
({4, 2}, 5),
|
||||
({4, 6}, 9),
|
||||
]
|
||||
|
||||
assert x.number_of_edges() == len(edges)
|
||||
for u, v, d in x.edges(data=True):
|
||||
assert ({u, v}, d["weight"]) in edges
|
||||
|
||||
|
||||
def test_mixed_nodetypes():
|
||||
# Smoke test to make sure no TypeError is raised for mixed node types.
|
||||
G = nx.Graph()
|
||||
edgelist = [(0, 3, [("weight", 5)]), (0, "1", [("weight", 5)])]
|
||||
G.add_edges_from(edgelist)
|
||||
G = G.to_directed()
|
||||
x = branchings.minimum_spanning_arborescence(G)
|
||||
|
||||
|
||||
def test_edmonds1_minbranch():
|
||||
# Using -G_array and min should give the same as optimal_arborescence_1,
|
||||
# but with all edges negative.
|
||||
edges = [(u, v, -w) for (u, v, w) in optimal_arborescence_1]
|
||||
|
||||
G = nx.from_numpy_array(-G_array, create_using=nx.DiGraph)
|
||||
|
||||
# Quickly make sure max branching is empty.
|
||||
x = branchings.maximum_branching(G)
|
||||
x_ = build_branching([])
|
||||
assert_equal_branchings(x, x_)
|
||||
|
||||
# Now test the min branching.
|
||||
x = branchings.minimum_branching(G)
|
||||
x_ = build_branching(edges)
|
||||
assert_equal_branchings(x, x_)
|
||||
|
||||
|
||||
def test_edge_attribute_preservation_normal_graph():
|
||||
# Test that edge attributes are preserved when finding an optimum graph
|
||||
# using the Edmonds class for normal graphs.
|
||||
G = nx.Graph()
|
||||
|
||||
edgelist = [
|
||||
(0, 1, [("weight", 5), ("otherattr", 1), ("otherattr2", 3)]),
|
||||
(0, 2, [("weight", 5), ("otherattr", 2), ("otherattr2", 2)]),
|
||||
(1, 2, [("weight", 6), ("otherattr", 3), ("otherattr2", 1)]),
|
||||
]
|
||||
G.add_edges_from(edgelist)
|
||||
|
||||
ed = branchings.Edmonds(G)
|
||||
B = ed.find_optimum("weight", preserve_attrs=True, seed=1)
|
||||
|
||||
assert B[0][1]["otherattr"] == 1
|
||||
assert B[0][1]["otherattr2"] == 3
|
||||
|
||||
|
||||
def test_edge_attribute_preservation_multigraph():
|
||||
|
||||
# Test that edge attributes are preserved when finding an optimum graph
|
||||
# using the Edmonds class for multigraphs.
|
||||
G = nx.MultiGraph()
|
||||
|
||||
edgelist = [
|
||||
(0, 1, [("weight", 5), ("otherattr", 1), ("otherattr2", 3)]),
|
||||
(0, 2, [("weight", 5), ("otherattr", 2), ("otherattr2", 2)]),
|
||||
(1, 2, [("weight", 6), ("otherattr", 3), ("otherattr2", 1)]),
|
||||
]
|
||||
G.add_edges_from(edgelist * 2) # Make sure we have duplicate edge paths
|
||||
|
||||
ed = branchings.Edmonds(G)
|
||||
B = ed.find_optimum("weight", preserve_attrs=True)
|
||||
|
||||
assert B[0][1][0]["otherattr"] == 1
|
||||
assert B[0][1][0]["otherattr2"] == 3
|
||||
|
||||
|
||||
def test_edge_attribute_discard():
|
||||
# Test that edge attributes are discarded if we do not specify to keep them
|
||||
G = nx.Graph()
|
||||
|
||||
edgelist = [
|
||||
(0, 1, [("weight", 5), ("otherattr", 1), ("otherattr2", 3)]),
|
||||
(0, 2, [("weight", 5), ("otherattr", 2), ("otherattr2", 2)]),
|
||||
(1, 2, [("weight", 6), ("otherattr", 3), ("otherattr2", 1)]),
|
||||
]
|
||||
G.add_edges_from(edgelist)
|
||||
|
||||
ed = branchings.Edmonds(G)
|
||||
B = ed.find_optimum("weight", preserve_attrs=False)
|
||||
|
||||
edge_dict = B[0][1]
|
||||
with pytest.raises(KeyError):
|
||||
_ = edge_dict["otherattr"]
|
|
@ -0,0 +1,117 @@
|
|||
"""Unit tests for the :mod:`~networkx.algorithms.tree.coding` module."""
|
||||
from itertools import product
|
||||
|
||||
import pytest
|
||||
import networkx as nx
|
||||
from networkx.testing import assert_nodes_equal
|
||||
from networkx.testing import assert_edges_equal
|
||||
|
||||
|
||||
class TestPruferSequence:
|
||||
"""Unit tests for the Prüfer sequence encoding and decoding
|
||||
functions.
|
||||
|
||||
"""
|
||||
|
||||
def test_nontree(self):
|
||||
with pytest.raises(nx.NotATree):
|
||||
G = nx.cycle_graph(3)
|
||||
nx.to_prufer_sequence(G)
|
||||
|
||||
def test_null_graph(self):
|
||||
with pytest.raises(nx.NetworkXPointlessConcept):
|
||||
nx.to_prufer_sequence(nx.null_graph())
|
||||
|
||||
def test_trivial_graph(self):
|
||||
with pytest.raises(nx.NetworkXPointlessConcept):
|
||||
nx.to_prufer_sequence(nx.trivial_graph())
|
||||
|
||||
def test_bad_integer_labels(self):
|
||||
with pytest.raises(KeyError):
|
||||
T = nx.Graph(nx.utils.pairwise("abc"))
|
||||
nx.to_prufer_sequence(T)
|
||||
|
||||
def test_encoding(self):
|
||||
"""Tests for encoding a tree as a Prüfer sequence using the
|
||||
iterative strategy.
|
||||
|
||||
"""
|
||||
# Example from Wikipedia.
|
||||
tree = nx.Graph([(0, 3), (1, 3), (2, 3), (3, 4), (4, 5)])
|
||||
sequence = nx.to_prufer_sequence(tree)
|
||||
assert sequence == [3, 3, 3, 4]
|
||||
|
||||
def test_decoding(self):
|
||||
"""Tests for decoding a tree from a Prüfer sequence."""
|
||||
# Example from Wikipedia.
|
||||
sequence = [3, 3, 3, 4]
|
||||
tree = nx.from_prufer_sequence(sequence)
|
||||
assert_nodes_equal(list(tree), list(range(6)))
|
||||
edges = [(0, 3), (1, 3), (2, 3), (3, 4), (4, 5)]
|
||||
assert_edges_equal(list(tree.edges()), edges)
|
||||
|
||||
def test_decoding2(self):
|
||||
# Example from "An Optimal Algorithm for Prufer Codes".
|
||||
sequence = [2, 4, 0, 1, 3, 3]
|
||||
tree = nx.from_prufer_sequence(sequence)
|
||||
assert_nodes_equal(list(tree), list(range(8)))
|
||||
edges = [(0, 1), (0, 4), (1, 3), (2, 4), (2, 5), (3, 6), (3, 7)]
|
||||
assert_edges_equal(list(tree.edges()), edges)
|
||||
|
||||
def test_inverse(self):
|
||||
"""Tests that the encoding and decoding functions are inverses.
|
||||
|
||||
"""
|
||||
for T in nx.nonisomorphic_trees(4):
|
||||
T2 = nx.from_prufer_sequence(nx.to_prufer_sequence(T))
|
||||
assert_nodes_equal(list(T), list(T2))
|
||||
assert_edges_equal(list(T.edges()), list(T2.edges()))
|
||||
|
||||
for seq in product(range(4), repeat=2):
|
||||
seq2 = nx.to_prufer_sequence(nx.from_prufer_sequence(seq))
|
||||
assert list(seq) == seq2
|
||||
|
||||
|
||||
class TestNestedTuple:
|
||||
"""Unit tests for the nested tuple encoding and decoding functions.
|
||||
|
||||
"""
|
||||
|
||||
def test_nontree(self):
|
||||
with pytest.raises(nx.NotATree):
|
||||
G = nx.cycle_graph(3)
|
||||
nx.to_nested_tuple(G, 0)
|
||||
|
||||
def test_unknown_root(self):
|
||||
with pytest.raises(nx.NodeNotFound):
|
||||
G = nx.path_graph(2)
|
||||
nx.to_nested_tuple(G, "bogus")
|
||||
|
||||
def test_encoding(self):
|
||||
T = nx.full_rary_tree(2, 2 ** 3 - 1)
|
||||
expected = (((), ()), ((), ()))
|
||||
actual = nx.to_nested_tuple(T, 0)
|
||||
assert_nodes_equal(expected, actual)
|
||||
|
||||
def test_canonical_form(self):
|
||||
T = nx.Graph()
|
||||
T.add_edges_from([(0, 1), (0, 2), (0, 3)])
|
||||
T.add_edges_from([(1, 4), (1, 5)])
|
||||
T.add_edges_from([(3, 6), (3, 7)])
|
||||
root = 0
|
||||
actual = nx.to_nested_tuple(T, root, canonical_form=True)
|
||||
expected = ((), ((), ()), ((), ()))
|
||||
assert actual == expected
|
||||
|
||||
def test_decoding(self):
|
||||
balanced = (((), ()), ((), ()))
|
||||
expected = nx.full_rary_tree(2, 2 ** 3 - 1)
|
||||
actual = nx.from_nested_tuple(balanced)
|
||||
assert nx.is_isomorphic(expected, actual)
|
||||
|
||||
def test_sensible_relabeling(self):
|
||||
balanced = (((), ()), ((), ()))
|
||||
T = nx.from_nested_tuple(balanced, sensible_relabeling=True)
|
||||
edges = [(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]
|
||||
assert_nodes_equal(list(T), list(range(2 ** 3 - 1)))
|
||||
assert_edges_equal(list(T.edges()), edges)
|
|
@ -0,0 +1,79 @@
|
|||
import networkx as nx
|
||||
from networkx.algorithms.tree.decomposition import junction_tree
|
||||
|
||||
|
||||
def test_junction_tree_directed_confounders():
|
||||
B = nx.DiGraph()
|
||||
B.add_edges_from([("A", "C"), ("B", "C"), ("C", "D"), ("C", "E")])
|
||||
|
||||
G = junction_tree(B)
|
||||
J = nx.Graph()
|
||||
J.add_edges_from(
|
||||
[
|
||||
(("C", "E"), ("C",)),
|
||||
(("C",), ("A", "B", "C")),
|
||||
(("A", "B", "C"), ("C",)),
|
||||
(("C",), ("C", "D")),
|
||||
]
|
||||
)
|
||||
|
||||
assert nx.is_isomorphic(G, J)
|
||||
|
||||
|
||||
def test_junction_tree_directed_unconnected_nodes():
|
||||
B = nx.DiGraph()
|
||||
B.add_nodes_from([("A", "B", "C", "D")])
|
||||
G = junction_tree(B)
|
||||
|
||||
J = nx.Graph()
|
||||
J.add_nodes_from([("A", "B", "C", "D")])
|
||||
|
||||
assert nx.is_isomorphic(G, J)
|
||||
|
||||
|
||||
def test_junction_tree_directed_cascade():
|
||||
B = nx.DiGraph()
|
||||
B.add_edges_from([("A", "B"), ("B", "C"), ("C", "D")])
|
||||
G = junction_tree(B)
|
||||
|
||||
J = nx.Graph()
|
||||
J.add_edges_from(
|
||||
[
|
||||
(("A", "B"), ("B",)),
|
||||
(("B",), ("B", "C")),
|
||||
(("B", "C"), ("C",)),
|
||||
(("C",), ("C", "D")),
|
||||
]
|
||||
)
|
||||
assert nx.is_isomorphic(G, J)
|
||||
|
||||
|
||||
def test_junction_tree_directed_unconnected_edges():
|
||||
B = nx.DiGraph()
|
||||
B.add_edges_from([("A", "B"), ("C", "D"), ("E", "F")])
|
||||
G = junction_tree(B)
|
||||
|
||||
J = nx.Graph()
|
||||
J.add_nodes_from([("A", "B"), ("C", "D"), ("E", "F")])
|
||||
|
||||
assert nx.is_isomorphic(G, J)
|
||||
|
||||
|
||||
def test_junction_tree_undirected():
|
||||
B = nx.Graph()
|
||||
B.add_edges_from([("A", "C"), ("A", "D"), ("B", "C"), ("C", "E")])
|
||||
G = junction_tree(B)
|
||||
|
||||
J = nx.Graph()
|
||||
J.add_edges_from(
|
||||
[
|
||||
(("A", "D"), ("A",)),
|
||||
(("A",), ("A", "C")),
|
||||
(("A", "C"), ("C",)),
|
||||
(("C",), ("B", "C")),
|
||||
(("B", "C"), ("C",)),
|
||||
(("C",), ("C", "E")),
|
||||
]
|
||||
)
|
||||
|
||||
assert nx.is_isomorphic(G, J)
|
|
@ -0,0 +1,285 @@
|
|||
"""Unit tests for the :mod:`networkx.algorithms.tree.mst` module."""
|
||||
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
from networkx.testing import assert_nodes_equal, assert_edges_equal
|
||||
|
||||
|
||||
def test_unknown_algorithm():
|
||||
with pytest.raises(ValueError):
|
||||
nx.minimum_spanning_tree(nx.Graph(), algorithm="random")
|
||||
|
||||
|
||||
class MinimumSpanningTreeTestBase:
|
||||
"""Base class for test classes for minimum spanning tree algorithms.
|
||||
|
||||
This class contains some common tests that will be inherited by
|
||||
subclasses. Each subclass must have a class attribute
|
||||
:data:`algorithm` that is a string representing the algorithm to
|
||||
run, as described under the ``algorithm`` keyword argument for the
|
||||
:func:`networkx.minimum_spanning_edges` function. Subclasses can
|
||||
then implement any algorithm-specific tests.
|
||||
|
||||
"""
|
||||
|
||||
def setup_method(self, method):
|
||||
"""Creates an example graph and stores the expected minimum and
|
||||
maximum spanning tree edges.
|
||||
|
||||
"""
|
||||
# This stores the class attribute `algorithm` in an instance attribute.
|
||||
self.algo = self.algorithm
|
||||
# This example graph comes from Wikipedia:
|
||||
# https://en.wikipedia.org/wiki/Kruskal's_algorithm
|
||||
edges = [
|
||||
(0, 1, 7),
|
||||
(0, 3, 5),
|
||||
(1, 2, 8),
|
||||
(1, 3, 9),
|
||||
(1, 4, 7),
|
||||
(2, 4, 5),
|
||||
(3, 4, 15),
|
||||
(3, 5, 6),
|
||||
(4, 5, 8),
|
||||
(4, 6, 9),
|
||||
(5, 6, 11),
|
||||
]
|
||||
self.G = nx.Graph()
|
||||
self.G.add_weighted_edges_from(edges)
|
||||
self.minimum_spanning_edgelist = [
|
||||
(0, 1, {"weight": 7}),
|
||||
(0, 3, {"weight": 5}),
|
||||
(1, 4, {"weight": 7}),
|
||||
(2, 4, {"weight": 5}),
|
||||
(3, 5, {"weight": 6}),
|
||||
(4, 6, {"weight": 9}),
|
||||
]
|
||||
self.maximum_spanning_edgelist = [
|
||||
(0, 1, {"weight": 7}),
|
||||
(1, 2, {"weight": 8}),
|
||||
(1, 3, {"weight": 9}),
|
||||
(3, 4, {"weight": 15}),
|
||||
(4, 6, {"weight": 9}),
|
||||
(5, 6, {"weight": 11}),
|
||||
]
|
||||
|
||||
def test_minimum_edges(self):
|
||||
edges = nx.minimum_spanning_edges(self.G, algorithm=self.algo)
|
||||
# Edges from the spanning edges functions don't come in sorted
|
||||
# orientation, so we need to sort each edge individually.
|
||||
actual = sorted((min(u, v), max(u, v), d) for u, v, d in edges)
|
||||
assert_edges_equal(actual, self.minimum_spanning_edgelist)
|
||||
|
||||
def test_maximum_edges(self):
|
||||
edges = nx.maximum_spanning_edges(self.G, algorithm=self.algo)
|
||||
# Edges from the spanning edges functions don't come in sorted
|
||||
# orientation, so we need to sort each edge individually.
|
||||
actual = sorted((min(u, v), max(u, v), d) for u, v, d in edges)
|
||||
assert_edges_equal(actual, self.maximum_spanning_edgelist)
|
||||
|
||||
def test_without_data(self):
|
||||
edges = nx.minimum_spanning_edges(self.G, algorithm=self.algo, data=False)
|
||||
# Edges from the spanning edges functions don't come in sorted
|
||||
# orientation, so we need to sort each edge individually.
|
||||
actual = sorted((min(u, v), max(u, v)) for u, v in edges)
|
||||
expected = [(u, v) for u, v, d in self.minimum_spanning_edgelist]
|
||||
assert_edges_equal(actual, expected)
|
||||
|
||||
def test_nan_weights(self):
|
||||
# Edge weights NaN never appear in the spanning tree. see #2164
|
||||
G = self.G
|
||||
G.add_edge(0, 12, weight=float("nan"))
|
||||
edges = nx.minimum_spanning_edges(
|
||||
G, algorithm=self.algo, data=False, ignore_nan=True
|
||||
)
|
||||
actual = sorted((min(u, v), max(u, v)) for u, v in edges)
|
||||
expected = [(u, v) for u, v, d in self.minimum_spanning_edgelist]
|
||||
assert_edges_equal(actual, expected)
|
||||
# Now test for raising exception
|
||||
edges = nx.minimum_spanning_edges(
|
||||
G, algorithm=self.algo, data=False, ignore_nan=False
|
||||
)
|
||||
with pytest.raises(ValueError):
|
||||
list(edges)
|
||||
# test default for ignore_nan as False
|
||||
edges = nx.minimum_spanning_edges(G, algorithm=self.algo, data=False)
|
||||
with pytest.raises(ValueError):
|
||||
list(edges)
|
||||
|
||||
def test_nan_weights_order(self):
|
||||
# now try again with a nan edge at the beginning of G.nodes
|
||||
edges = [
|
||||
(0, 1, 7),
|
||||
(0, 3, 5),
|
||||
(1, 2, 8),
|
||||
(1, 3, 9),
|
||||
(1, 4, 7),
|
||||
(2, 4, 5),
|
||||
(3, 4, 15),
|
||||
(3, 5, 6),
|
||||
(4, 5, 8),
|
||||
(4, 6, 9),
|
||||
(5, 6, 11),
|
||||
]
|
||||
G = nx.Graph()
|
||||
G.add_weighted_edges_from([(u + 1, v + 1, wt) for u, v, wt in edges])
|
||||
G.add_edge(0, 7, weight=float("nan"))
|
||||
edges = nx.minimum_spanning_edges(
|
||||
G, algorithm=self.algo, data=False, ignore_nan=True
|
||||
)
|
||||
actual = sorted((min(u, v), max(u, v)) for u, v in edges)
|
||||
shift = [(u + 1, v + 1) for u, v, d in self.minimum_spanning_edgelist]
|
||||
assert_edges_equal(actual, shift)
|
||||
|
||||
def test_isolated_node(self):
|
||||
# now try again with an isolated node
|
||||
edges = [
|
||||
(0, 1, 7),
|
||||
(0, 3, 5),
|
||||
(1, 2, 8),
|
||||
(1, 3, 9),
|
||||
(1, 4, 7),
|
||||
(2, 4, 5),
|
||||
(3, 4, 15),
|
||||
(3, 5, 6),
|
||||
(4, 5, 8),
|
||||
(4, 6, 9),
|
||||
(5, 6, 11),
|
||||
]
|
||||
G = nx.Graph()
|
||||
G.add_weighted_edges_from([(u + 1, v + 1, wt) for u, v, wt in edges])
|
||||
G.add_node(0)
|
||||
edges = nx.minimum_spanning_edges(
|
||||
G, algorithm=self.algo, data=False, ignore_nan=True
|
||||
)
|
||||
actual = sorted((min(u, v), max(u, v)) for u, v in edges)
|
||||
shift = [(u + 1, v + 1) for u, v, d in self.minimum_spanning_edgelist]
|
||||
assert_edges_equal(actual, shift)
|
||||
|
||||
def test_minimum_tree(self):
|
||||
T = nx.minimum_spanning_tree(self.G, algorithm=self.algo)
|
||||
actual = sorted(T.edges(data=True))
|
||||
assert_edges_equal(actual, self.minimum_spanning_edgelist)
|
||||
|
||||
def test_maximum_tree(self):
|
||||
T = nx.maximum_spanning_tree(self.G, algorithm=self.algo)
|
||||
actual = sorted(T.edges(data=True))
|
||||
assert_edges_equal(actual, self.maximum_spanning_edgelist)
|
||||
|
||||
def test_disconnected(self):
|
||||
G = nx.Graph([(0, 1, dict(weight=1)), (2, 3, dict(weight=2))])
|
||||
T = nx.minimum_spanning_tree(G, algorithm=self.algo)
|
||||
assert_nodes_equal(list(T), list(range(4)))
|
||||
assert_edges_equal(list(T.edges()), [(0, 1), (2, 3)])
|
||||
|
||||
def test_empty_graph(self):
|
||||
G = nx.empty_graph(3)
|
||||
T = nx.minimum_spanning_tree(G, algorithm=self.algo)
|
||||
assert_nodes_equal(sorted(T), list(range(3)))
|
||||
assert T.number_of_edges() == 0
|
||||
|
||||
def test_attributes(self):
|
||||
G = nx.Graph()
|
||||
G.add_edge(1, 2, weight=1, color="red", distance=7)
|
||||
G.add_edge(2, 3, weight=1, color="green", distance=2)
|
||||
G.add_edge(1, 3, weight=10, color="blue", distance=1)
|
||||
G.graph["foo"] = "bar"
|
||||
T = nx.minimum_spanning_tree(G, algorithm=self.algo)
|
||||
assert T.graph == G.graph
|
||||
assert_nodes_equal(T, G)
|
||||
for u, v in T.edges():
|
||||
assert T.adj[u][v] == G.adj[u][v]
|
||||
|
||||
def test_weight_attribute(self):
|
||||
G = nx.Graph()
|
||||
G.add_edge(0, 1, weight=1, distance=7)
|
||||
G.add_edge(0, 2, weight=30, distance=1)
|
||||
G.add_edge(1, 2, weight=1, distance=1)
|
||||
G.add_node(3)
|
||||
T = nx.minimum_spanning_tree(G, algorithm=self.algo, weight="distance")
|
||||
assert_nodes_equal(sorted(T), list(range(4)))
|
||||
assert_edges_equal(sorted(T.edges()), [(0, 2), (1, 2)])
|
||||
T = nx.maximum_spanning_tree(G, algorithm=self.algo, weight="distance")
|
||||
assert_nodes_equal(sorted(T), list(range(4)))
|
||||
assert_edges_equal(sorted(T.edges()), [(0, 1), (0, 2)])
|
||||
|
||||
|
||||
class TestBoruvka(MinimumSpanningTreeTestBase):
|
||||
"""Unit tests for computing a minimum (or maximum) spanning tree
|
||||
using Borůvka's algorithm.
|
||||
|
||||
"""
|
||||
|
||||
algorithm = "boruvka"
|
||||
|
||||
def test_unicode_name(self):
|
||||
"""Tests that using a Unicode string can correctly indicate
|
||||
Borůvka's algorithm.
|
||||
|
||||
"""
|
||||
edges = nx.minimum_spanning_edges(self.G, algorithm="borůvka")
|
||||
# Edges from the spanning edges functions don't come in sorted
|
||||
# orientation, so we need to sort each edge individually.
|
||||
actual = sorted((min(u, v), max(u, v), d) for u, v, d in edges)
|
||||
assert_edges_equal(actual, self.minimum_spanning_edgelist)
|
||||
|
||||
|
||||
class MultigraphMSTTestBase(MinimumSpanningTreeTestBase):
|
||||
# Abstract class
|
||||
|
||||
def test_multigraph_keys_min(self):
|
||||
"""Tests that the minimum spanning edges of a multigraph
|
||||
preserves edge keys.
|
||||
|
||||
"""
|
||||
G = nx.MultiGraph()
|
||||
G.add_edge(0, 1, key="a", weight=2)
|
||||
G.add_edge(0, 1, key="b", weight=1)
|
||||
min_edges = nx.minimum_spanning_edges
|
||||
mst_edges = min_edges(G, algorithm=self.algo, data=False)
|
||||
assert_edges_equal([(0, 1, "b")], list(mst_edges))
|
||||
|
||||
def test_multigraph_keys_max(self):
|
||||
"""Tests that the maximum spanning edges of a multigraph
|
||||
preserves edge keys.
|
||||
|
||||
"""
|
||||
G = nx.MultiGraph()
|
||||
G.add_edge(0, 1, key="a", weight=2)
|
||||
G.add_edge(0, 1, key="b", weight=1)
|
||||
max_edges = nx.maximum_spanning_edges
|
||||
mst_edges = max_edges(G, algorithm=self.algo, data=False)
|
||||
assert_edges_equal([(0, 1, "a")], list(mst_edges))
|
||||
|
||||
|
||||
class TestKruskal(MultigraphMSTTestBase):
|
||||
"""Unit tests for computing a minimum (or maximum) spanning tree
|
||||
using Kruskal's algorithm.
|
||||
|
||||
"""
|
||||
|
||||
algorithm = "kruskal"
|
||||
|
||||
|
||||
class TestPrim(MultigraphMSTTestBase):
|
||||
"""Unit tests for computing a minimum (or maximum) spanning tree
|
||||
using Prim's algorithm.
|
||||
|
||||
"""
|
||||
|
||||
algorithm = "prim"
|
||||
|
||||
def test_multigraph_keys_tree(self):
|
||||
G = nx.MultiGraph()
|
||||
G.add_edge(0, 1, key="a", weight=2)
|
||||
G.add_edge(0, 1, key="b", weight=1)
|
||||
T = nx.minimum_spanning_tree(G)
|
||||
assert_edges_equal([(0, 1, 1)], list(T.edges(data="weight")))
|
||||
|
||||
def test_multigraph_keys_tree_max(self):
|
||||
G = nx.MultiGraph()
|
||||
G.add_edge(0, 1, key="a", weight=2)
|
||||
G.add_edge(0, 1, key="b", weight=1)
|
||||
T = nx.maximum_spanning_tree(G)
|
||||
assert_edges_equal([(0, 1, 2)], list(T.edges(data="weight")))
|
|
@ -0,0 +1,38 @@
|
|||
"""Unit tests for the :mod:`networkx.algorithms.tree.operations` module.
|
||||
|
||||
"""
|
||||
|
||||
import networkx as nx
|
||||
from networkx.testing import assert_nodes_equal
|
||||
from networkx.testing import assert_edges_equal
|
||||
|
||||
|
||||
class TestJoin:
|
||||
"""Unit tests for the :func:`networkx.tree.join` function."""
|
||||
|
||||
def test_empty_sequence(self):
|
||||
"""Tests that joining the empty sequence results in the tree
|
||||
with one node.
|
||||
|
||||
"""
|
||||
T = nx.join([])
|
||||
assert len(T) == 1
|
||||
assert T.number_of_edges() == 0
|
||||
|
||||
def test_single(self):
|
||||
"""Tests that joining just one tree yields a tree with one more
|
||||
node.
|
||||
|
||||
"""
|
||||
T = nx.empty_graph(1)
|
||||
actual = nx.join([(T, 0)])
|
||||
expected = nx.path_graph(2)
|
||||
assert_nodes_equal(list(expected), list(actual))
|
||||
assert_edges_equal(list(expected.edges()), list(actual.edges()))
|
||||
|
||||
def test_basic(self):
|
||||
"""Tests for joining multiple subtrees at a root node."""
|
||||
trees = [(nx.full_rary_tree(2, 2 ** 2 - 1), 0) for i in range(2)]
|
||||
actual = nx.join(trees)
|
||||
expected = nx.full_rary_tree(2, 2 ** 3 - 1)
|
||||
assert nx.is_isomorphic(actual, expected)
|
|
@ -0,0 +1,163 @@
|
|||
import pytest
|
||||
import networkx as nx
|
||||
|
||||
|
||||
class TestTreeRecognition:
|
||||
|
||||
graph = nx.Graph
|
||||
multigraph = nx.MultiGraph
|
||||
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
|
||||
cls.T1 = cls.graph()
|
||||
|
||||
cls.T2 = cls.graph()
|
||||
cls.T2.add_node(1)
|
||||
|
||||
cls.T3 = cls.graph()
|
||||
cls.T3.add_nodes_from(range(5))
|
||||
edges = [(i, i + 1) for i in range(4)]
|
||||
cls.T3.add_edges_from(edges)
|
||||
|
||||
cls.T5 = cls.multigraph()
|
||||
cls.T5.add_nodes_from(range(5))
|
||||
edges = [(i, i + 1) for i in range(4)]
|
||||
cls.T5.add_edges_from(edges)
|
||||
|
||||
cls.T6 = cls.graph()
|
||||
cls.T6.add_nodes_from([6, 7])
|
||||
cls.T6.add_edge(6, 7)
|
||||
|
||||
cls.F1 = nx.compose(cls.T6, cls.T3)
|
||||
|
||||
cls.N4 = cls.graph()
|
||||
cls.N4.add_node(1)
|
||||
cls.N4.add_edge(1, 1)
|
||||
|
||||
cls.N5 = cls.graph()
|
||||
cls.N5.add_nodes_from(range(5))
|
||||
|
||||
cls.N6 = cls.graph()
|
||||
cls.N6.add_nodes_from(range(3))
|
||||
cls.N6.add_edges_from([(0, 1), (1, 2), (2, 0)])
|
||||
|
||||
cls.NF1 = nx.compose(cls.T6, cls.N6)
|
||||
|
||||
def test_null_tree(self):
|
||||
with pytest.raises(nx.NetworkXPointlessConcept):
|
||||
nx.is_tree(self.graph())
|
||||
|
||||
def test_null_tree2(self):
|
||||
with pytest.raises(nx.NetworkXPointlessConcept):
|
||||
nx.is_tree(self.multigraph())
|
||||
|
||||
def test_null_forest(self):
|
||||
with pytest.raises(nx.NetworkXPointlessConcept):
|
||||
nx.is_forest(self.graph())
|
||||
|
||||
def test_null_forest2(self):
|
||||
with pytest.raises(nx.NetworkXPointlessConcept):
|
||||
nx.is_forest(self.multigraph())
|
||||
|
||||
def test_is_tree(self):
|
||||
assert nx.is_tree(self.T2)
|
||||
assert nx.is_tree(self.T3)
|
||||
assert nx.is_tree(self.T5)
|
||||
|
||||
def test_is_not_tree(self):
|
||||
assert not nx.is_tree(self.N4)
|
||||
assert not nx.is_tree(self.N5)
|
||||
assert not nx.is_tree(self.N6)
|
||||
|
||||
def test_is_forest(self):
|
||||
assert nx.is_forest(self.T2)
|
||||
assert nx.is_forest(self.T3)
|
||||
assert nx.is_forest(self.T5)
|
||||
assert nx.is_forest(self.F1)
|
||||
assert nx.is_forest(self.N5)
|
||||
|
||||
def test_is_not_forest(self):
|
||||
assert not nx.is_forest(self.N4)
|
||||
assert not nx.is_forest(self.N6)
|
||||
assert not nx.is_forest(self.NF1)
|
||||
|
||||
|
||||
class TestDirectedTreeRecognition(TestTreeRecognition):
|
||||
graph = nx.DiGraph
|
||||
multigraph = nx.MultiDiGraph
|
||||
|
||||
|
||||
def test_disconnected_graph():
|
||||
# https://github.com/networkx/networkx/issues/1144
|
||||
G = nx.Graph()
|
||||
G.add_edges_from([(0, 1), (1, 2), (2, 0), (3, 4)])
|
||||
assert not nx.is_tree(G)
|
||||
|
||||
G = nx.DiGraph()
|
||||
G.add_edges_from([(0, 1), (1, 2), (2, 0), (3, 4)])
|
||||
assert not nx.is_tree(G)
|
||||
|
||||
|
||||
def test_dag_nontree():
|
||||
G = nx.DiGraph()
|
||||
G.add_edges_from([(0, 1), (0, 2), (1, 2)])
|
||||
assert not nx.is_tree(G)
|
||||
assert nx.is_directed_acyclic_graph(G)
|
||||
|
||||
|
||||
def test_multicycle():
|
||||
G = nx.MultiDiGraph()
|
||||
G.add_edges_from([(0, 1), (0, 1)])
|
||||
assert not nx.is_tree(G)
|
||||
assert nx.is_directed_acyclic_graph(G)
|
||||
|
||||
|
||||
def test_emptybranch():
|
||||
G = nx.DiGraph()
|
||||
G.add_nodes_from(range(10))
|
||||
assert nx.is_branching(G)
|
||||
assert not nx.is_arborescence(G)
|
||||
|
||||
|
||||
def test_path():
|
||||
G = nx.DiGraph()
|
||||
nx.add_path(G, range(5))
|
||||
assert nx.is_branching(G)
|
||||
assert nx.is_arborescence(G)
|
||||
|
||||
|
||||
def test_notbranching1():
|
||||
# Acyclic violation.
|
||||
G = nx.MultiDiGraph()
|
||||
G.add_nodes_from(range(10))
|
||||
G.add_edges_from([(0, 1), (1, 0)])
|
||||
assert not nx.is_branching(G)
|
||||
assert not nx.is_arborescence(G)
|
||||
|
||||
|
||||
def test_notbranching2():
|
||||
# In-degree violation.
|
||||
G = nx.MultiDiGraph()
|
||||
G.add_nodes_from(range(10))
|
||||
G.add_edges_from([(0, 1), (0, 2), (3, 2)])
|
||||
assert not nx.is_branching(G)
|
||||
assert not nx.is_arborescence(G)
|
||||
|
||||
|
||||
def test_notarborescence1():
|
||||
# Not an arborescence due to not spanning.
|
||||
G = nx.MultiDiGraph()
|
||||
G.add_nodes_from(range(10))
|
||||
G.add_edges_from([(0, 1), (0, 2), (1, 3), (5, 6)])
|
||||
assert nx.is_branching(G)
|
||||
assert not nx.is_arborescence(G)
|
||||
|
||||
|
||||
def test_notarborescence2():
|
||||
# Not an arborescence due to in-degree violation.
|
||||
G = nx.MultiDiGraph()
|
||||
nx.add_path(G, range(5))
|
||||
G.add_edge(6, 4)
|
||||
assert not nx.is_branching(G)
|
||||
assert not nx.is_arborescence(G)
|
Loading…
Add table
Add a link
Reference in a new issue