Download .venv/Lib/site-packages/networkx/algorithms/tests/test_cycles.py from DrDavis/PythonProject1: direct link, hf CLI and curl.
- Browser
- Download file 34.4 kB
-
https://huggingface.co/DrDavis/PythonProject1/resolve/main/.venv/Lib/site-packages/networkx/algorithms/tests/test_cycles.py
- Command line
-
hf download hf://DrDavis/PythonProject1/.venv/Lib/site-packages/networkx/algorithms/tests/test_cycles.py
-
curl -L -o test_cycles.py https://huggingface.co/DrDavis/PythonProject1/resolve/main/.venv/Lib/site-packages/networkx/algorithms/tests/test_cycles.py
34.4 kB
| 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: | |
| 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: | |
| def K(n): | |
| return nx.complete_graph(n) | |
| def D(n): | |
| return nx.complete_graph(n).to_directed() | |
| 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: | |
| 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: | |
| 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: | |
| def test_girth(self, G, expected): | |
| assert nx.girth(G) == expected | |