123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- """Test trophic levels, trophic differences and trophic coherence
- """
- import pytest
- np = pytest.importorskip("numpy")
- pytest.importorskip("scipy")
- import networkx as nx
- def test_trophic_levels():
- """Trivial example"""
- G = nx.DiGraph()
- G.add_edge("a", "b")
- G.add_edge("b", "c")
- d = nx.trophic_levels(G)
- assert d == {"a": 1, "b": 2, "c": 3}
- def test_trophic_levels_levine():
- """Example from Figure 5 in Stephen Levine (1980) J. theor. Biol. 83,
- 195-207
- """
- S = nx.DiGraph()
- S.add_edge(1, 2, weight=1.0)
- S.add_edge(1, 3, weight=0.2)
- S.add_edge(1, 4, weight=0.8)
- S.add_edge(2, 3, weight=0.2)
- S.add_edge(2, 5, weight=0.3)
- S.add_edge(4, 3, weight=0.6)
- S.add_edge(4, 5, weight=0.7)
- S.add_edge(5, 4, weight=0.2)
- # save copy for later, test intermediate implementation details first
- S2 = S.copy()
- # drop nodes of in-degree zero
- z = [nid for nid, d in S.in_degree if d == 0]
- for nid in z:
- S.remove_node(nid)
- # find adjacency matrix
- q = nx.linalg.graphmatrix.adjacency_matrix(S).T
- # fmt: off
- expected_q = np.array([
- [0, 0, 0., 0],
- [0.2, 0, 0.6, 0],
- [0, 0, 0, 0.2],
- [0.3, 0, 0.7, 0]
- ])
- # fmt: on
- assert np.array_equal(q.todense(), expected_q)
- # must be square, size of number of nodes
- assert len(q.shape) == 2
- assert q.shape[0] == q.shape[1]
- assert q.shape[0] == len(S)
- nn = q.shape[0]
- i = np.eye(nn)
- n = np.linalg.inv(i - q)
- y = np.asarray(n) @ np.ones(nn)
- expected_y = np.array([1, 2.07906977, 1.46511628, 2.3255814])
- assert np.allclose(y, expected_y)
- expected_d = {1: 1, 2: 2, 3: 3.07906977, 4: 2.46511628, 5: 3.3255814}
- d = nx.trophic_levels(S2)
- for nid, level in d.items():
- expected_level = expected_d[nid]
- assert expected_level == pytest.approx(level, abs=1e-7)
- def test_trophic_levels_simple():
- matrix_a = np.array([[0, 0], [1, 0]])
- G = nx.from_numpy_array(matrix_a, create_using=nx.DiGraph)
- d = nx.trophic_levels(G)
- assert d[0] == pytest.approx(2, abs=1e-7)
- assert d[1] == pytest.approx(1, abs=1e-7)
- def test_trophic_levels_more_complex():
- # fmt: off
- matrix = np.array([
- [0, 1, 0, 0],
- [0, 0, 1, 0],
- [0, 0, 0, 1],
- [0, 0, 0, 0]
- ])
- # fmt: on
- G = nx.from_numpy_array(matrix, create_using=nx.DiGraph)
- d = nx.trophic_levels(G)
- expected_result = [1, 2, 3, 4]
- for ind in range(4):
- assert d[ind] == pytest.approx(expected_result[ind], abs=1e-7)
- # fmt: off
- matrix = np.array([
- [0, 1, 1, 0],
- [0, 0, 1, 1],
- [0, 0, 0, 1],
- [0, 0, 0, 0]
- ])
- # fmt: on
- G = nx.from_numpy_array(matrix, create_using=nx.DiGraph)
- d = nx.trophic_levels(G)
- expected_result = [1, 2, 2.5, 3.25]
- print("Calculated result: ", d)
- print("Expected Result: ", expected_result)
- for ind in range(4):
- assert d[ind] == pytest.approx(expected_result[ind], abs=1e-7)
- def test_trophic_levels_even_more_complex():
- # fmt: off
- # Another, bigger matrix
- matrix = np.array([
- [0, 0, 0, 0, 0],
- [0, 1, 0, 1, 0],
- [1, 0, 0, 0, 0],
- [0, 1, 0, 0, 0],
- [0, 0, 0, 1, 0]
- ])
- # Generated this linear system using pen and paper:
- K = np.array([
- [1, 0, -1, 0, 0],
- [0, 0.5, 0, -0.5, 0],
- [0, 0, 1, 0, 0],
- [0, -0.5, 0, 1, -0.5],
- [0, 0, 0, 0, 1],
- ])
- # fmt: on
- result_1 = np.ravel(np.linalg.inv(K) @ np.ones(5))
- G = nx.from_numpy_array(matrix, create_using=nx.DiGraph)
- result_2 = nx.trophic_levels(G)
- for ind in range(5):
- assert result_1[ind] == pytest.approx(result_2[ind], abs=1e-7)
- def test_trophic_levels_singular_matrix():
- """Should raise an error with graphs with only non-basal nodes"""
- matrix = np.identity(4)
- G = nx.from_numpy_array(matrix, create_using=nx.DiGraph)
- with pytest.raises(nx.NetworkXError) as e:
- nx.trophic_levels(G)
- msg = (
- "Trophic levels are only defined for graphs where every node "
- + "has a path from a basal node (basal nodes are nodes with no "
- + "incoming edges)."
- )
- assert msg in str(e.value)
- def test_trophic_levels_singular_with_basal():
- """Should fail to compute if there are any parts of the graph which are not
- reachable from any basal node (with in-degree zero).
- """
- G = nx.DiGraph()
- # a has in-degree zero
- G.add_edge("a", "b")
- # b is one level above a, c and d
- G.add_edge("c", "b")
- G.add_edge("d", "b")
- # c and d form a loop, neither are reachable from a
- G.add_edge("c", "d")
- G.add_edge("d", "c")
- with pytest.raises(nx.NetworkXError) as e:
- nx.trophic_levels(G)
- msg = (
- "Trophic levels are only defined for graphs where every node "
- + "has a path from a basal node (basal nodes are nodes with no "
- + "incoming edges)."
- )
- assert msg in str(e.value)
- # if self-loops are allowed, smaller example:
- G = nx.DiGraph()
- G.add_edge("a", "b") # a has in-degree zero
- G.add_edge("c", "b") # b is one level above a and c
- G.add_edge("c", "c") # c has a self-loop
- with pytest.raises(nx.NetworkXError) as e:
- nx.trophic_levels(G)
- msg = (
- "Trophic levels are only defined for graphs where every node "
- + "has a path from a basal node (basal nodes are nodes with no "
- + "incoming edges)."
- )
- assert msg in str(e.value)
- def test_trophic_differences():
- matrix_a = np.array([[0, 1], [0, 0]])
- G = nx.from_numpy_array(matrix_a, create_using=nx.DiGraph)
- diffs = nx.trophic_differences(G)
- assert diffs[(0, 1)] == pytest.approx(1, abs=1e-7)
- # fmt: off
- matrix_b = np.array([
- [0, 1, 1, 0],
- [0, 0, 1, 1],
- [0, 0, 0, 1],
- [0, 0, 0, 0]
- ])
- # fmt: on
- G = nx.from_numpy_array(matrix_b, create_using=nx.DiGraph)
- diffs = nx.trophic_differences(G)
- assert diffs[(0, 1)] == pytest.approx(1, abs=1e-7)
- assert diffs[(0, 2)] == pytest.approx(1.5, abs=1e-7)
- assert diffs[(1, 2)] == pytest.approx(0.5, abs=1e-7)
- assert diffs[(1, 3)] == pytest.approx(1.25, abs=1e-7)
- assert diffs[(2, 3)] == pytest.approx(0.75, abs=1e-7)
- def test_trophic_incoherence_parameter_no_cannibalism():
- matrix_a = np.array([[0, 1], [0, 0]])
- G = nx.from_numpy_array(matrix_a, create_using=nx.DiGraph)
- q = nx.trophic_incoherence_parameter(G, cannibalism=False)
- assert q == pytest.approx(0, abs=1e-7)
- # fmt: off
- matrix_b = np.array([
- [0, 1, 1, 0],
- [0, 0, 1, 1],
- [0, 0, 0, 1],
- [0, 0, 0, 0]
- ])
- # fmt: on
- G = nx.from_numpy_array(matrix_b, create_using=nx.DiGraph)
- q = nx.trophic_incoherence_parameter(G, cannibalism=False)
- assert q == pytest.approx(np.std([1, 1.5, 0.5, 0.75, 1.25]), abs=1e-7)
- # fmt: off
- matrix_c = np.array([
- [0, 1, 1, 0],
- [0, 1, 1, 1],
- [0, 0, 0, 1],
- [0, 0, 0, 1]
- ])
- # fmt: on
- G = nx.from_numpy_array(matrix_c, create_using=nx.DiGraph)
- q = nx.trophic_incoherence_parameter(G, cannibalism=False)
- # Ignore the -link
- assert q == pytest.approx(np.std([1, 1.5, 0.5, 0.75, 1.25]), abs=1e-7)
- # no self-loops case
- # fmt: off
- matrix_d = np.array([
- [0, 1, 1, 0],
- [0, 0, 1, 1],
- [0, 0, 0, 1],
- [0, 0, 0, 0]
- ])
- # fmt: on
- G = nx.from_numpy_array(matrix_d, create_using=nx.DiGraph)
- q = nx.trophic_incoherence_parameter(G, cannibalism=False)
- # Ignore the -link
- assert q == pytest.approx(np.std([1, 1.5, 0.5, 0.75, 1.25]), abs=1e-7)
- def test_trophic_incoherence_parameter_cannibalism():
- matrix_a = np.array([[0, 1], [0, 0]])
- G = nx.from_numpy_array(matrix_a, create_using=nx.DiGraph)
- q = nx.trophic_incoherence_parameter(G, cannibalism=True)
- assert q == pytest.approx(0, abs=1e-7)
- # fmt: off
- matrix_b = np.array([
- [0, 0, 0, 0, 0],
- [0, 1, 0, 1, 0],
- [1, 0, 0, 0, 0],
- [0, 1, 0, 0, 0],
- [0, 0, 0, 1, 0]
- ])
- # fmt: on
- G = nx.from_numpy_array(matrix_b, create_using=nx.DiGraph)
- q = nx.trophic_incoherence_parameter(G, cannibalism=True)
- assert q == pytest.approx(2, abs=1e-7)
- # fmt: off
- matrix_c = np.array([
- [0, 1, 1, 0],
- [0, 0, 1, 1],
- [0, 0, 0, 1],
- [0, 0, 0, 0]
- ])
- # fmt: on
- G = nx.from_numpy_array(matrix_c, create_using=nx.DiGraph)
- q = nx.trophic_incoherence_parameter(G, cannibalism=True)
- # Ignore the -link
- assert q == pytest.approx(np.std([1, 1.5, 0.5, 0.75, 1.25]), abs=1e-7)
|