133 lines
4.2 KiB
Python
133 lines
4.2 KiB
Python
|
# test_chains.py - unit tests for the chains module
|
||
|
#
|
||
|
# Copyright 2004-2019 NetworkX developers.
|
||
|
#
|
||
|
# This file is part of NetworkX.
|
||
|
#
|
||
|
# NetworkX is distributed under a BSD license; see LICENSE.txt for more
|
||
|
# information.
|
||
|
"""Unit tests for the chain decomposition functions."""
|
||
|
from itertools import cycle
|
||
|
from itertools import islice
|
||
|
from unittest import TestCase
|
||
|
|
||
|
import networkx as nx
|
||
|
|
||
|
|
||
|
def cycles(seq):
|
||
|
"""Yields cyclic permutations of the given sequence.
|
||
|
|
||
|
For example::
|
||
|
|
||
|
>>> list(cycles('abc'))
|
||
|
[('a', 'b', 'c'), ('b', 'c', 'a'), ('c', 'a', 'b')]
|
||
|
|
||
|
"""
|
||
|
n = len(seq)
|
||
|
cycled_seq = cycle(seq)
|
||
|
for x in seq:
|
||
|
yield tuple(islice(cycled_seq, n))
|
||
|
next(cycled_seq)
|
||
|
|
||
|
|
||
|
def cyclic_equals(seq1, seq2):
|
||
|
"""Decide whether two sequences are equal up to cyclic permutations.
|
||
|
|
||
|
For example::
|
||
|
|
||
|
>>> cyclic_equals('xyz', 'zxy')
|
||
|
True
|
||
|
>>> cyclic_equals('xyz', 'zyx')
|
||
|
False
|
||
|
|
||
|
"""
|
||
|
# Cast seq2 to a tuple since `cycles()` yields tuples.
|
||
|
seq2 = tuple(seq2)
|
||
|
return any(x == tuple(seq2) for x in cycles(seq1))
|
||
|
|
||
|
|
||
|
class TestChainDecomposition(TestCase):
|
||
|
"""Unit tests for the chain decomposition function."""
|
||
|
|
||
|
def assertContainsChain(self, chain, expected):
|
||
|
# A cycle could be expressed in two different orientations, one
|
||
|
# forward and one backward, so we need to check for cyclic
|
||
|
# equality in both orientations.
|
||
|
reversed_chain = list(reversed([tuple(reversed(e)) for e in chain]))
|
||
|
for candidate in expected:
|
||
|
if cyclic_equals(chain, candidate):
|
||
|
break
|
||
|
if cyclic_equals(reversed_chain, candidate):
|
||
|
break
|
||
|
else:
|
||
|
self.fail('chain not found')
|
||
|
|
||
|
def test_decomposition(self):
|
||
|
edges = [
|
||
|
# DFS tree edges.
|
||
|
(1, 2), (2, 3), (3, 4), (3, 5), (5, 6), (6, 7), (7, 8), (5, 9),
|
||
|
(9, 10),
|
||
|
# Nontree edges.
|
||
|
(1, 3), (1, 4), (2, 5), (5, 10), (6, 8)
|
||
|
]
|
||
|
G = nx.Graph(edges)
|
||
|
expected = [
|
||
|
[(1, 3), (3, 2), (2, 1)],
|
||
|
[(1, 4), (4, 3)],
|
||
|
[(2, 5), (5, 3)],
|
||
|
[(5, 10), (10, 9), (9, 5)],
|
||
|
[(6, 8), (8, 7), (7, 6)],
|
||
|
]
|
||
|
chains = list(nx.chain_decomposition(G, root=1))
|
||
|
self.assertEqual(len(chains), len(expected))
|
||
|
# This chain decomposition isn't unique
|
||
|
# for chain in chains:
|
||
|
# print(chain)
|
||
|
# self.assertContainsChain(chain, expected)
|
||
|
|
||
|
def test_barbell_graph(self):
|
||
|
# The (3, 0) barbell graph has two triangles joined by a single edge.
|
||
|
G = nx.barbell_graph(3, 0)
|
||
|
chains = list(nx.chain_decomposition(G, root=0))
|
||
|
expected = [
|
||
|
[(0, 1), (1, 2), (2, 0)],
|
||
|
[(3, 4), (4, 5), (5, 3)],
|
||
|
]
|
||
|
self.assertEqual(len(chains), len(expected))
|
||
|
for chain in chains:
|
||
|
self.assertContainsChain(chain, expected)
|
||
|
|
||
|
def test_disconnected_graph(self):
|
||
|
"""Test for a graph with multiple connected components."""
|
||
|
G = nx.barbell_graph(3, 0)
|
||
|
H = nx.barbell_graph(3, 0)
|
||
|
mapping = dict(zip(range(6), 'abcdef'))
|
||
|
nx.relabel_nodes(H, mapping, copy=False)
|
||
|
G = nx.union(G, H)
|
||
|
chains = list(nx.chain_decomposition(G))
|
||
|
expected = [
|
||
|
[(0, 1), (1, 2), (2, 0)],
|
||
|
[(3, 4), (4, 5), (5, 3)],
|
||
|
[('a', 'b'), ('b', 'c'), ('c', 'a')],
|
||
|
[('d', 'e'), ('e', 'f'), ('f', 'd')],
|
||
|
]
|
||
|
self.assertEqual(len(chains), len(expected))
|
||
|
for chain in chains:
|
||
|
self.assertContainsChain(chain, expected)
|
||
|
|
||
|
def test_disconnected_graph_root_node(self):
|
||
|
"""Test for a single component of a disconnected graph."""
|
||
|
G = nx.barbell_graph(3, 0)
|
||
|
H = nx.barbell_graph(3, 0)
|
||
|
mapping = dict(zip(range(6), 'abcdef'))
|
||
|
nx.relabel_nodes(H, mapping, copy=False)
|
||
|
G = nx.union(G, H)
|
||
|
chains = list(nx.chain_decomposition(G, root='a'))
|
||
|
expected = [
|
||
|
[('a', 'b'), ('b', 'c'), ('c', 'a')],
|
||
|
[('d', 'e'), ('e', 'f'), ('f', 'd')],
|
||
|
]
|
||
|
self.assertEqual(len(chains), len(expected))
|
||
|
for chain in chains:
|
||
|
self.assertContainsChain(chain, expected)
|