""" Tests for VF2 isomorphism algorithm. """ import os import struct import random from nose.tools import assert_true, assert_equal from nose import SkipTest import networkx as nx from networkx.algorithms import isomorphism as iso class TestWikipediaExample(object): # Source: https://en.wikipedia.org/wiki/Graph_isomorphism # Nodes 'a', 'b', 'c' and 'd' form a column. # Nodes 'g', 'h', 'i' and 'j' form a column. g1edges = [['a', 'g'], ['a', 'h'], ['a', 'i'], ['b', 'g'], ['b', 'h'], ['b', 'j'], ['c', 'g'], ['c', 'i'], ['c', 'j'], ['d', 'h'], ['d', 'i'], ['d', 'j']] # Nodes 1,2,3,4 form the clockwise corners of a large square. # Nodes 5,6,7,8 form the clockwise corners of a small square g2edges = [[1, 2], [2, 3], [3, 4], [4, 1], [5, 6], [6, 7], [7, 8], [8, 5], [1, 5], [2, 6], [3, 7], [4, 8]] def test_graph(self): g1 = nx.Graph() g2 = nx.Graph() g1.add_edges_from(self.g1edges) g2.add_edges_from(self.g2edges) gm = iso.GraphMatcher(g1, g2) assert_true(gm.is_isomorphic()) mapping = sorted(gm.mapping.items()) # this mapping is only one of the possibilies # so this test needs to be reconsidered # isomap = [('a', 1), ('b', 6), ('c', 3), ('d', 8), # ('g', 2), ('h', 5), ('i', 4), ('j', 7)] # assert_equal(mapping, isomap) def test_subgraph(self): g1 = nx.Graph() g2 = nx.Graph() g1.add_edges_from(self.g1edges) g2.add_edges_from(self.g2edges) g3 = g2.subgraph([1, 2, 3, 4]) gm = iso.GraphMatcher(g1, g3) assert_true(gm.subgraph_is_isomorphic()) class TestVF2GraphDB(object): # http://amalfi.dis.unina.it/graph/db/ @staticmethod def create_graph(filename): """Creates a Graph instance from the filename.""" # The file is assumed to be in the format from the VF2 graph database. # Each file is composed of 16-bit numbers (unsigned short int). # So we will want to read 2 bytes at a time. # We can read the number as follows: # number = struct.unpack('<H', file.read(2)) # This says, expect the data in little-endian encoding # as an unsigned short int and unpack 2 bytes from the file. fh = open(filename, mode='rb') # Grab the number of nodes. # Node numeration is 0-based, so the first node has index 0. nodes = struct.unpack('<H', fh.read(2))[0] graph = nx.Graph() for from_node in range(nodes): # Get the number of edges. edges = struct.unpack('<H', fh.read(2))[0] for edge in range(edges): # Get the terminal node. to_node = struct.unpack('<H', fh.read(2))[0] graph.add_edge(from_node, to_node) fh.close() return graph def test_graph(self): head, tail = os.path.split(__file__) g1 = self.create_graph(os.path.join(head, 'iso_r01_s80.A99')) g2 = self.create_graph(os.path.join(head, 'iso_r01_s80.B99')) gm = iso.GraphMatcher(g1, g2) assert_true(gm.is_isomorphic()) def test_subgraph(self): # A is the subgraph # B is the full graph head, tail = os.path.split(__file__) subgraph = self.create_graph(os.path.join(head, 'si2_b06_m200.A99')) graph = self.create_graph(os.path.join(head, 'si2_b06_m200.B99')) gm = iso.GraphMatcher(graph, subgraph) assert_true(gm.subgraph_is_isomorphic()) class TestAtlas(object): @classmethod def setupClass(cls): global atlas import platform if platform.python_implementation() == 'Jython': raise SkipTest('graph atlas not available under Jython.') import networkx.generators.atlas as atlas def setUp(self): self.GAG = atlas.graph_atlas_g() def test_graph_atlas(self): # Atlas = nx.graph_atlas_g()[0:208] # 208, 6 nodes or less Atlas = self.GAG[0:100] alphabet = list(range(26)) for graph in Atlas: nlist = list(graph) labels = alphabet[:len(nlist)] for s in range(10): random.shuffle(labels) d = dict(zip(nlist, labels)) relabel = nx.relabel_nodes(graph, d) gm = iso.GraphMatcher(graph, relabel) assert_true(gm.is_isomorphic()) def test_multiedge(): # Simple test for multigraphs # Need something much more rigorous edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10), (10, 11), (10, 11), (11, 12), (11, 12), (12, 13), (12, 13), (13, 14), (13, 14), (14, 15), (14, 15), (15, 16), (15, 16), (16, 17), (16, 17), (17, 18), (17, 18), (18, 19), (18, 19), (19, 0), (19, 0)] nodes = list(range(20)) for g1 in [nx.MultiGraph(), nx.MultiDiGraph()]: g1.add_edges_from(edges) for _ in range(10): new_nodes = list(nodes) random.shuffle(new_nodes) d = dict(zip(nodes, new_nodes)) g2 = nx.relabel_nodes(g1, d) if not g1.is_directed(): gm = iso.GraphMatcher(g1, g2) else: gm = iso.DiGraphMatcher(g1, g2) assert_true(gm.is_isomorphic()) def test_selfloop(): # Simple test for graphs with selfloops edges = [(0, 1), (0, 2), (1, 2), (1, 3), (2, 2), (2, 4), (3, 1), (3, 2), (4, 2), (4, 5), (5, 4)] nodes = list(range(6)) for g1 in [nx.Graph(), nx.DiGraph()]: g1.add_edges_from(edges) for _ in range(100): new_nodes = list(nodes) random.shuffle(new_nodes) d = dict(zip(nodes, new_nodes)) g2 = nx.relabel_nodes(g1, d) if not g1.is_directed(): gm = iso.GraphMatcher(g1, g2) else: gm = iso.DiGraphMatcher(g1, g2) assert_true(gm.is_isomorphic()) def test_isomorphism_iter1(): # As described in: # http://groups.google.com/group/networkx-discuss/browse_thread/thread/2ff65c67f5e3b99f/d674544ebea359bb?fwc=1 g1 = nx.DiGraph() g2 = nx.DiGraph() g3 = nx.DiGraph() g1.add_edge('A', 'B') g1.add_edge('B', 'C') g2.add_edge('Y', 'Z') g3.add_edge('Z', 'Y') gm12 = iso.DiGraphMatcher(g1, g2) gm13 = iso.DiGraphMatcher(g1, g3) x = list(gm12.subgraph_isomorphisms_iter()) y = list(gm13.subgraph_isomorphisms_iter()) assert_true({'A': 'Y', 'B': 'Z'} in x) assert_true({'B': 'Y', 'C': 'Z'} in x) assert_true({'A': 'Z', 'B': 'Y'} in y) assert_true({'B': 'Z', 'C': 'Y'} in y) assert_equal(len(x), len(y)) assert_equal(len(x), 2) def test_isomorphism_iter2(): # Path for L in range(2, 10): g1 = nx.path_graph(L) gm = iso.GraphMatcher(g1, g1) s = len(list(gm.isomorphisms_iter())) assert_equal(s, 2) # Cycle for L in range(3, 10): g1 = nx.cycle_graph(L) gm = iso.GraphMatcher(g1, g1) s = len(list(gm.isomorphisms_iter())) assert_equal(s, 2 * L) def test_multiple(): # Verify that we can use the graph matcher multiple times edges = [('A', 'B'), ('B', 'A'), ('B', 'C')] for g1, g2 in [(nx.Graph(), nx.Graph()), (nx.DiGraph(), nx.DiGraph())]: g1.add_edges_from(edges) g2.add_edges_from(edges) g3 = nx.subgraph(g2, ['A', 'B']) if not g1.is_directed(): gmA = iso.GraphMatcher(g1, g2) gmB = iso.GraphMatcher(g1, g3) else: gmA = iso.DiGraphMatcher(g1, g2) gmB = iso.DiGraphMatcher(g1, g3) assert_true(gmA.is_isomorphic()) g2.remove_node('C') assert_true(gmA.subgraph_is_isomorphic()) assert_true(gmB.subgraph_is_isomorphic()) # for m in [gmB.mapping, gmB.mapping]: # assert_true(m['A'] == 'A') # assert_true(m['B'] == 'B') # assert_true('C' not in m)