Fixed database typo and removed unnecessary class identifier.

This commit is contained in:
Batuhan Berk Başoğlu 2020-10-14 10:10:37 -04:00
parent 00ad49a143
commit 45fb349a7d
5098 changed files with 952558 additions and 85 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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