332 lines
12 KiB
Python
332 lines
12 KiB
Python
# test_minors.py - unit tests for the minors module
|
|
#
|
|
# Copyright 2015 Jeffrey Finkelstein <jeffrey.finkelstein@gmail.com>.
|
|
#
|
|
# This file is part of NetworkX.
|
|
#
|
|
# NetworkX is distributed under a BSD license; see LICENSE.txt for more
|
|
# information.
|
|
"""Unit tests for the :mod:`networkx.algorithms.minors` module."""
|
|
import pytest
|
|
|
|
import networkx as nx
|
|
from networkx.testing.utils import *
|
|
from networkx.utils import arbitrary_element
|
|
|
|
|
|
class TestQuotient(object):
|
|
"""Unit tests for computing quotient graphs."""
|
|
|
|
def test_quotient_graph_complete_multipartite(self):
|
|
"""Tests that the quotient graph of the complete *n*-partite graph
|
|
under the "same neighbors" node relation is the complete graph on *n*
|
|
nodes.
|
|
|
|
"""
|
|
G = nx.complete_multipartite_graph(2, 3, 4)
|
|
# Two nodes are equivalent if they are not adjacent but have the same
|
|
# neighbor set.
|
|
|
|
def same_neighbors(u, v):
|
|
return (u not in G[v] and v not in G[u] and G[u] == G[v])
|
|
|
|
expected = nx.complete_graph(3)
|
|
actual = nx.quotient_graph(G, same_neighbors)
|
|
# It won't take too long to run a graph isomorphism algorithm on such
|
|
# small graphs.
|
|
assert nx.is_isomorphic(expected, actual)
|
|
|
|
def test_quotient_graph_complete_bipartite(self):
|
|
"""Tests that the quotient graph of the complete bipartite graph under
|
|
the "same neighbors" node relation is `K_2`.
|
|
|
|
"""
|
|
G = nx.complete_bipartite_graph(2, 3)
|
|
# Two nodes are equivalent if they are not adjacent but have the same
|
|
# neighbor set.
|
|
|
|
def same_neighbors(u, v):
|
|
return (u not in G[v] and v not in G[u] and G[u] == G[v])
|
|
|
|
expected = nx.complete_graph(2)
|
|
actual = nx.quotient_graph(G, same_neighbors)
|
|
# It won't take too long to run a graph isomorphism algorithm on such
|
|
# small graphs.
|
|
assert nx.is_isomorphic(expected, actual)
|
|
|
|
def test_quotient_graph_edge_relation(self):
|
|
"""Tests for specifying an alternate edge relation for the quotient
|
|
graph.
|
|
|
|
"""
|
|
G = nx.path_graph(5)
|
|
|
|
def identity(u, v):
|
|
return u == v
|
|
|
|
def same_parity(b, c):
|
|
return (arbitrary_element(b) % 2 == arbitrary_element(c) % 2)
|
|
|
|
actual = nx.quotient_graph(G, identity, same_parity)
|
|
expected = nx.Graph()
|
|
expected.add_edges_from([(0, 2), (0, 4), (2, 4)])
|
|
expected.add_edge(1, 3)
|
|
assert nx.is_isomorphic(actual, expected)
|
|
|
|
def test_condensation_as_quotient(self):
|
|
"""This tests that the condensation of a graph can be viewed as the
|
|
quotient graph under the "in the same connected component" equivalence
|
|
relation.
|
|
|
|
"""
|
|
# This example graph comes from the file `test_strongly_connected.py`.
|
|
G = nx.DiGraph()
|
|
G.add_edges_from([(1, 2), (2, 3), (2, 11), (2, 12), (3, 4), (4, 3),
|
|
(4, 5), (5, 6), (6, 5), (6, 7), (7, 8), (7, 9),
|
|
(7, 10), (8, 9), (9, 7), (10, 6), (11, 2), (11, 4),
|
|
(11, 6), (12, 6), (12, 11)])
|
|
scc = list(nx.strongly_connected_components(G))
|
|
C = nx.condensation(G, scc)
|
|
component_of = C.graph['mapping']
|
|
# Two nodes are equivalent if they are in the same connected component.
|
|
|
|
def same_component(u, v):
|
|
return component_of[u] == component_of[v]
|
|
|
|
Q = nx.quotient_graph(G, same_component)
|
|
assert nx.is_isomorphic(C, Q)
|
|
|
|
def test_path(self):
|
|
G = nx.path_graph(6)
|
|
partition = [{0, 1}, {2, 3}, {4, 5}]
|
|
M = nx.quotient_graph(G, partition, relabel=True)
|
|
assert_nodes_equal(M, [0, 1, 2])
|
|
assert_edges_equal(M.edges(), [(0, 1), (1, 2)])
|
|
for n in M:
|
|
assert M.nodes[n]['nedges'] == 1
|
|
assert M.nodes[n]['nnodes'] == 2
|
|
assert M.nodes[n]['density'] == 1
|
|
|
|
def test_multigraph_path(self):
|
|
G = nx.MultiGraph(nx.path_graph(6))
|
|
partition = [{0, 1}, {2, 3}, {4, 5}]
|
|
M = nx.quotient_graph(G, partition, relabel=True)
|
|
assert_nodes_equal(M, [0, 1, 2])
|
|
assert_edges_equal(M.edges(), [(0, 1), (1, 2)])
|
|
for n in M:
|
|
assert M.nodes[n]['nedges'] == 1
|
|
assert M.nodes[n]['nnodes'] == 2
|
|
assert M.nodes[n]['density'] == 1
|
|
|
|
def test_directed_path(self):
|
|
G = nx.DiGraph()
|
|
nx.add_path(G, range(6))
|
|
partition = [{0, 1}, {2, 3}, {4, 5}]
|
|
M = nx.quotient_graph(G, partition, relabel=True)
|
|
assert_nodes_equal(M, [0, 1, 2])
|
|
assert_edges_equal(M.edges(), [(0, 1), (1, 2)])
|
|
for n in M:
|
|
assert M.nodes[n]['nedges'] == 1
|
|
assert M.nodes[n]['nnodes'] == 2
|
|
assert M.nodes[n]['density'] == 0.5
|
|
|
|
def test_directed_multigraph_path(self):
|
|
G = nx.MultiDiGraph()
|
|
nx.add_path(G, range(6))
|
|
partition = [{0, 1}, {2, 3}, {4, 5}]
|
|
M = nx.quotient_graph(G, partition, relabel=True)
|
|
assert_nodes_equal(M, [0, 1, 2])
|
|
assert_edges_equal(M.edges(), [(0, 1), (1, 2)])
|
|
for n in M:
|
|
assert M.nodes[n]['nedges'] == 1
|
|
assert M.nodes[n]['nnodes'] == 2
|
|
assert M.nodes[n]['density'] == 0.5
|
|
|
|
def test_overlapping_blocks(self):
|
|
with pytest.raises(nx.NetworkXException):
|
|
G = nx.path_graph(6)
|
|
partition = [{0, 1, 2}, {2, 3}, {4, 5}]
|
|
nx.quotient_graph(G, partition)
|
|
|
|
def test_weighted_path(self):
|
|
G = nx.path_graph(6)
|
|
for i in range(5):
|
|
G[i][i + 1]['weight'] = i + 1
|
|
partition = [{0, 1}, {2, 3}, {4, 5}]
|
|
M = nx.quotient_graph(G, partition, relabel=True)
|
|
assert_nodes_equal(M, [0, 1, 2])
|
|
assert_edges_equal(M.edges(), [(0, 1), (1, 2)])
|
|
assert M[0][1]['weight'] == 2
|
|
assert M[1][2]['weight'] == 4
|
|
for n in M:
|
|
assert M.nodes[n]['nedges'] == 1
|
|
assert M.nodes[n]['nnodes'] == 2
|
|
assert M.nodes[n]['density'] == 1
|
|
|
|
def test_barbell(self):
|
|
G = nx.barbell_graph(3, 0)
|
|
partition = [{0, 1, 2}, {3, 4, 5}]
|
|
M = nx.quotient_graph(G, partition, relabel=True)
|
|
assert_nodes_equal(M, [0, 1])
|
|
assert_edges_equal(M.edges(), [(0, 1)])
|
|
for n in M:
|
|
assert M.nodes[n]['nedges'] == 3
|
|
assert M.nodes[n]['nnodes'] == 3
|
|
assert M.nodes[n]['density'] == 1
|
|
|
|
def test_barbell_plus(self):
|
|
G = nx.barbell_graph(3, 0)
|
|
# Add an extra edge joining the bells.
|
|
G.add_edge(0, 5)
|
|
partition = [{0, 1, 2}, {3, 4, 5}]
|
|
M = nx.quotient_graph(G, partition, relabel=True)
|
|
assert_nodes_equal(M, [0, 1])
|
|
assert_edges_equal(M.edges(), [(0, 1)])
|
|
assert M[0][1]['weight'] == 2
|
|
for n in M:
|
|
assert M.nodes[n]['nedges'] == 3
|
|
assert M.nodes[n]['nnodes'] == 3
|
|
assert M.nodes[n]['density'] == 1
|
|
|
|
def test_blockmodel(self):
|
|
G = nx.path_graph(6)
|
|
partition = [[0, 1], [2, 3], [4, 5]]
|
|
M = nx.quotient_graph(G, partition, relabel=True)
|
|
assert_nodes_equal(M.nodes(), [0, 1, 2])
|
|
assert_edges_equal(M.edges(), [(0, 1), (1, 2)])
|
|
for n in M.nodes():
|
|
assert M.nodes[n]['nedges'] == 1
|
|
assert M.nodes[n]['nnodes'] == 2
|
|
assert M.nodes[n]['density'] == 1.0
|
|
|
|
def test_multigraph_blockmodel(self):
|
|
G = nx.MultiGraph(nx.path_graph(6))
|
|
partition = [[0, 1], [2, 3], [4, 5]]
|
|
M = nx.quotient_graph(G, partition,
|
|
create_using=nx.MultiGraph(), relabel=True)
|
|
assert_nodes_equal(M.nodes(), [0, 1, 2])
|
|
assert_edges_equal(M.edges(), [(0, 1), (1, 2)])
|
|
for n in M.nodes():
|
|
assert M.nodes[n]['nedges'] == 1
|
|
assert M.nodes[n]['nnodes'] == 2
|
|
assert M.nodes[n]['density'] == 1.0
|
|
|
|
def test_quotient_graph_incomplete_partition(self):
|
|
G = nx.path_graph(6)
|
|
partition = []
|
|
H = nx.quotient_graph(G, partition, relabel=True)
|
|
assert_nodes_equal(H.nodes(), [])
|
|
assert_edges_equal(H.edges(), [])
|
|
|
|
partition = [[0, 1], [2, 3], [5]]
|
|
H = nx.quotient_graph(G, partition, relabel=True)
|
|
assert_nodes_equal(H.nodes(), [0, 1, 2])
|
|
assert_edges_equal(H.edges(), [(0, 1)])
|
|
|
|
|
|
class TestContraction(object):
|
|
"""Unit tests for node and edge contraction functions."""
|
|
|
|
def test_undirected_node_contraction(self):
|
|
"""Tests for node contraction in an undirected graph."""
|
|
G = nx.cycle_graph(4)
|
|
actual = nx.contracted_nodes(G, 0, 1)
|
|
expected = nx.complete_graph(3)
|
|
expected.add_edge(0, 0)
|
|
assert nx.is_isomorphic(actual, expected)
|
|
|
|
def test_directed_node_contraction(self):
|
|
"""Tests for node contraction in a directed graph."""
|
|
G = nx.DiGraph(nx.cycle_graph(4))
|
|
actual = nx.contracted_nodes(G, 0, 1)
|
|
expected = nx.DiGraph(nx.complete_graph(3))
|
|
expected.add_edge(0, 0)
|
|
expected.add_edge(0, 0)
|
|
assert nx.is_isomorphic(actual, expected)
|
|
|
|
def test_create_multigraph(self):
|
|
"""Tests that using a MultiGraph creates multiple edges."""
|
|
G = nx.path_graph(3, create_using=nx.MultiGraph())
|
|
G.add_edge(0, 1)
|
|
G.add_edge(0, 0)
|
|
G.add_edge(0, 2)
|
|
actual = nx.contracted_nodes(G, 0, 2)
|
|
expected = nx.MultiGraph()
|
|
expected.add_edge(0, 1)
|
|
expected.add_edge(0, 1)
|
|
expected.add_edge(0, 1)
|
|
expected.add_edge(0, 0)
|
|
expected.add_edge(0, 0)
|
|
assert_edges_equal(actual.edges, expected.edges)
|
|
|
|
def test_multigraph_keys(self):
|
|
"""Tests that multiedge keys are reset in new graph."""
|
|
G = nx.path_graph(3, create_using=nx.MultiGraph())
|
|
G.add_edge(0, 1, 5)
|
|
G.add_edge(0, 0, 0)
|
|
G.add_edge(0, 2, 5)
|
|
actual = nx.contracted_nodes(G, 0, 2)
|
|
expected = nx.MultiGraph()
|
|
expected.add_edge(0, 1, 0)
|
|
expected.add_edge(0, 1, 5)
|
|
expected.add_edge(0, 1, 2) # keyed as 2 b/c 2 edges already in G
|
|
expected.add_edge(0, 0, 0)
|
|
expected.add_edge(0, 0, 1) # this comes from (0, 2, 5)
|
|
assert_edges_equal(actual.edges, expected.edges)
|
|
|
|
def test_node_attributes(self):
|
|
"""Tests that node contraction preserves node attributes."""
|
|
G = nx.cycle_graph(4)
|
|
# Add some data to the two nodes being contracted.
|
|
G.nodes[0]['foo'] = 'bar'
|
|
G.nodes[1]['baz'] = 'xyzzy'
|
|
actual = nx.contracted_nodes(G, 0, 1)
|
|
# We expect that contracting the nodes 0 and 1 in C_4 yields K_3, but
|
|
# with nodes labeled 0, 2, and 3, and with a self-loop on 0.
|
|
expected = nx.complete_graph(3)
|
|
expected = nx.relabel_nodes(expected, {1: 2, 2: 3})
|
|
expected.add_edge(0, 0)
|
|
cdict = {1: {'baz': 'xyzzy'}}
|
|
expected.nodes[0].update(dict(foo='bar', contraction=cdict))
|
|
assert nx.is_isomorphic(actual, expected)
|
|
assert actual.nodes == expected.nodes
|
|
|
|
def test_without_self_loops(self):
|
|
"""Tests for node contraction without preserving self-loops."""
|
|
G = nx.cycle_graph(4)
|
|
actual = nx.contracted_nodes(G, 0, 1, self_loops=False)
|
|
expected = nx.complete_graph(3)
|
|
assert nx.is_isomorphic(actual, expected)
|
|
|
|
def test_contract_selfloop_graph(self):
|
|
"""Tests for node contraction when nodes have selfloops."""
|
|
G = nx.cycle_graph(4)
|
|
G.add_edge(0, 0)
|
|
actual = nx.contracted_nodes(G, 0, 1)
|
|
expected = nx.complete_graph([0, 2, 3])
|
|
expected.add_edge(0, 0)
|
|
expected.add_edge(0, 0)
|
|
assert_edges_equal(actual.edges, expected.edges)
|
|
actual = nx.contracted_nodes(G, 1, 0)
|
|
expected = nx.complete_graph([1, 2, 3])
|
|
expected.add_edge(1, 1)
|
|
expected.add_edge(1, 1)
|
|
assert_edges_equal(actual.edges, expected.edges)
|
|
|
|
def test_undirected_edge_contraction(self):
|
|
"""Tests for edge contraction in an undirected graph."""
|
|
G = nx.cycle_graph(4)
|
|
actual = nx.contracted_edge(G, (0, 1))
|
|
expected = nx.complete_graph(3)
|
|
expected.add_edge(0, 0)
|
|
assert nx.is_isomorphic(actual, expected)
|
|
|
|
def test_nonexistent_edge(self):
|
|
"""Tests that attempting to contract a non-existent edge raises an
|
|
exception.
|
|
|
|
"""
|
|
with pytest.raises(ValueError):
|
|
G = nx.cycle_graph(4)
|
|
nx.contracted_edge(G, (0, 2))
|