class TestGraphAddEdgeExceptionWarning(UnittestPythonCompatibility): """ Test logged warnings and raised Exceptions by Graph add_edge. Same as for add_nodes """ def setUp(self): """ Build empty graph to add a node to and test default state """ self.graph = Graph() def test_add_edge_node_not_exist(self): """ Test adding edges for nodes that do not exist """ self.assertRaises(GraphitException, self.graph.add_edge, 1, 2) def test_add_edge_exist(self): """ Test adding edges that already exist. A warning is logged but edge ID is returned """ self.graph.add_nodes([1, 2]) self.graph.add_edge(1, 2) eid = self.graph.add_edge(1, 2) self.assertTrue(eid, (1, 2)) def test_remove_edge_exist(self): """ Test removal of edges that do not exist should only log a warning """ self.graph.add_nodes([1, 2]) try: self.graph.remove_edge(1, 2) except GraphitException: self.fail('remove_edge raised GraphitException unexpectedly')
class TestGraphRemoveEdges(UnittestPythonCompatibility): """ Test removal of edges in directed and undirected way """ def setUp(self): """ Build Graph with nodes and edges """ self.graph = Graph() self.graph.add_edges([(1, 2), (2, 3), (3, 4), (3, 5), (4, 5)], node_from_edge=True) self.assertTrue(len(self.graph) == 5) self.assertTrue(len(self.graph.nodes) == 5) self.assertTrue(len(self.graph.edges) == 10) self.assertTrue(len(self.graph.adjacency) == 5) def tearDown(self): """ Test state after edge removal """ if self.edges: # If undirected, add reversed edges if not self.graph.directed: self.edges.extend([e[::-1] for e in self.edges if e[::-1] not in self.edges]) for edge in self.edges: # Edge should be removed self.assertTrue(edge not in self.graph.edges) # Nodes connected should still be there self.assertTrue(all([node in self.graph.nodes for node in edge])) # Adjacency should be corrected self.assertTrue(edge[1] not in self.graph.adjacency[edge[0]]) # If directional, reverse edge still in graph if self.graph.directed: rev_edge = edge[::-1] if rev_edge not in self.edges: self.assertTrue(rev_edge in self.graph.edges) # filled after addition self.assertTrue(len(self.graph) == 5) self.assertTrue(len(self.graph.nodes) == 5) self.assertTrue(len(self.graph.edges) == 10 - len(self.edges)) self.assertTrue(len(self.graph.adjacency) == 5) def test_remove_edge_single_undirected(self): """ Test removal of single undirected edge """ self.edges = [(1, 2)] self.graph.remove_edge(*self.edges[0]) def test_remove_edge_single_directed(self): """ Test removal of single directed edge """ self.graph.directed = True self.edges = [(1, 2)] self.graph.remove_edge(*self.edges[0]) def test_remove_edge_multiple_undirected(self): """ Test removal of multiple undirected edges """ self.edges = [(1, 2), (2, 3), (4, 5)] self.graph.remove_edges(self.edges) def test_remove_edge_multiple_directed(self): """ Test removal of multiple directed edges """ self.graph.directed = True self.edges = [(1, 2), (2, 3), (4, 5)] self.graph.remove_edges(self.edges) def test_remove_edge_single_mixed(self): """ Test removal of a single directed edge in a global undirected graph using local override of directionality """ self.edges = [(1, 2)] self.graph.remove_edge(*self.edges[0], directed=True) self.graph.directed = True def test_remove_edge_multiple_mixed(self): """ Test removal of multiple directed edges in a global undirected graph using local override of directionality """ self.edges = [(1, 2), (2, 3), (4, 5)] self.graph.remove_edges(self.edges, directed=True) self.graph.directed = True def test_graph_clear(self): """ Test clear method to removal all nodes and edges """ self.edges = [] self.graph.clear() self.assertTrue(len(self.graph) == 0) self.assertTrue(len(self.graph.nodes) == 0) self.assertTrue(len(self.graph.edges) == 0) self.assertTrue(len(self.graph.adjacency) == 0)
class TestGraphCombinatorialSetlike(UnittestPythonCompatibility): def setUp(self): """ Setup two graphs for combinatorial tests """ self.graph1 = Graph(auto_nid=False) self.graph1.add_nodes(range(1, 11)) self.graph1.add_edges([(1, 2), (2, 3), (3, 4), (3, 5), (5, 6), (4, 7), (6, 8), (7, 8), (8, 9), (9, 10)]) self.graph2 = Graph(auto_nid=False) self.graph2.add_nodes(range(6, 16)) self.graph2.add_edges([(6, 8), (7, 8), (8, 9), (9, 10), (10, 11), (10, 12), (12, 13), (11, 14), (13, 15), (14, 15)]) def test_combinatorial_intersection(self): """ Test intersection between two graphs """ intr = graph_intersection(self.graph1, self.graph2) self.assertItemsEqual(intr.nodes.keys(), range(6, 11)) self.assertItemsEqual(intr.edges.keys(), [(8, 9), (6, 8), (9, 8), (9, 10), (8, 7), (8, 6), (7, 8), (10, 9)]) self.assertFalse(intr.nodes.is_view) self.assertFalse(intr.edges.is_view) self.assertEqual(intr, intr.origin) def test_combinatorial_intersection_edgediff(self): """ Test intersection between two graphs with a different edge population """ self.graph2.remove_edge(8, 9) intr = graph_intersection(self.graph1, self.graph2) self.assertItemsEqual(intr.nodes.keys(), range(6, 11)) self.assertItemsEqual(intr.edges.keys(), [(6, 8), (9, 10), (8, 7), (8, 6), (7, 8), (10, 9)]) self.assertFalse(intr.nodes.is_view) self.assertFalse(intr.edges.is_view) self.assertEqual(intr, intr.origin) def test_combinatorial_difference(self): """ Test difference between two graphs """ diff = graph_difference(self.graph1, self.graph2) self.assertItemsEqual(diff.nodes.keys(), range(1, 6)) self.assertItemsEqual(diff.edges.keys(), [(1, 2), (3, 2), (2, 1), (2, 3), (4, 3), (5, 3), (3, 4), (3, 5)]) self.assertFalse(diff.nodes.is_view) self.assertFalse(diff.edges.is_view) self.assertEqual(diff, diff.origin) diff = graph_difference(self.graph2, self.graph1) self.assertItemsEqual(diff.nodes.keys(), range(11, 16)) self.assertItemsEqual(diff.edges.keys(), [(14, 11), (13, 12), (15, 13), (12, 13), (13, 15), (14, 15), (11, 14), (15, 14)]) self.assertFalse(diff.nodes.is_view) self.assertFalse(diff.edges.is_view) self.assertEqual(diff, diff.origin) def test_combinatorial_difference_edgediff(self): """ Test difference between two graphs using edge oriented difference. """ diff = graph_difference(self.graph1, self.graph2, edge_diff=True) self.assertItemsEqual(diff.nodes.keys(), range(1, 8)) self.assertItemsEqual(diff.edges.keys(), [(1, 2), (3, 2), (2, 1), (2, 3), (4, 3), (5, 3), (3, 4), (3, 5), (5, 6), (6, 5), (4, 7), (7, 4)]) self.assertFalse(diff.nodes.is_view) self.assertFalse(diff.edges.is_view) self.assertEqual(diff, diff.origin) diff = graph_difference(self.graph2, self.graph1, edge_diff=True) self.assertItemsEqual(diff.nodes.keys(), range(10, 16)) self.assertItemsEqual(diff.edges.keys(), [(14, 11), (13, 12), (15, 13), (12, 13), (13, 15), (14, 15), (11, 14), (15, 14), (10, 11), (11, 10), (10, 12), (12, 10)]) self.assertFalse(diff.nodes.is_view) self.assertFalse(diff.edges.is_view) self.assertEqual(diff, diff.origin) def test_combinatorial_symmetric_difference(self): """ Test symmetric difference between two graphs using edge oriented difference. Returns a new graph """ diff = graph_symmetric_difference(self.graph1, self.graph2, edge_diff=True) self.assertItemsEqual(diff.nodes.keys(), [1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15]) self.assertItemsEqual(diff.edges.keys(), [(1, 2), (2, 1), (2, 3), (3, 2), (3, 4), (4, 3), (3, 5), (5, 3), (11, 14), (14, 11), (12, 13), (13, 12), (14, 15), (15, 14), (13, 15), (15, 13), (4, 7), (7, 4), (5, 6), (6, 5), (10, 11), (11, 10), (10, 12), (12, 10)]) self.assertFalse(diff.nodes.is_view) self.assertFalse(diff.edges.is_view) self.assertEqual(diff, diff.origin) def test_combinatorial_symmetric_difference_edgediff(self): """ Test symmetric difference between two graphs. Returns a new graph """ diff = graph_symmetric_difference(self.graph1, self.graph2) self.assertItemsEqual(diff.nodes.keys(), [1, 2, 3, 4, 5, 11, 12, 13, 14, 15]) self.assertItemsEqual(diff.edges.keys(), [(1, 2), (2, 1), (2, 3), (3, 2), (3, 4), (4, 3), (3, 5), (5, 3), (11, 14), (14, 11), (12, 13), (13, 12), (14, 15), (15, 14), (13, 15), (15, 13)]) self.assertFalse(diff.nodes.is_view) self.assertFalse(diff.edges.is_view) self.assertEqual(diff, diff.origin) def test_combinatorial_union(self): """ Test union between two graphs. Returns a new graph """ union = graph_union(self.graph1, self.graph2) self.assertItemsEqual(union.nodes.keys(), range(1, 16)) self.assertItemsEqual(union.edges.keys(), [(1, 2), (2, 1), (2, 3), (3, 2), (3, 4), (4, 3), (3, 5), (5, 3), (5, 6), (6, 5), (4, 7), (7, 4), (6, 8), (8, 6), (7, 8), (8, 7), (8, 9), (9, 8), (9, 10), (10, 9), (10, 11), (11, 10), (12, 10), (10, 12), (12, 13), (13, 12), (11, 14), (14, 11), (13, 15), (15, 13), (14, 15), (15, 14)]) self.assertFalse(union.nodes.is_view) self.assertFalse(union.edges.is_view) self.assertEqual(union, union.origin) def test_combinatorial_issubset(self): """ Test graph 1 issubset of graph 2 """ graph2 = Graph(auto_nid=False) graph2.add_nodes(range(7, 11)) graph2.add_edges([(7, 8), (8, 9), (9, 10)]) self.assertTrue(graph_issubset(graph2, self.graph1)) self.assertFalse(graph_issubset(self.graph1, graph2)) def test_combinatorial_issuperset(self): """ Test graph 1 issuperset of graph 2 """ graph2 = Graph(auto_nid=False) graph2.add_nodes(range(7, 11)) graph2.add_edges([(7, 8), (8, 9), (9, 10)]) self.assertFalse(graph_issuperset(graph2, self.graph1)) self.assertTrue(graph_issuperset(self.graph1, graph2))
class TestGraphAlgorithms(UnittestPythonCompatibility): def setUp(self): edges = { (1, 2): { 'weight': 1.0 }, (2, 3): { 'weight': 1.0 }, (2, 4): { 'weight': 1.0 }, (3, 5): { 'weight': 1.0 }, (4, 5): { 'weight': 1.0 }, (4, 7): { 'weight': 1.0 }, (5, 7): { 'weight': 0.75 }, (3, 14): { 'weight': 2.0 }, (14, 15): { 'weight': 2.0 }, (14, 16): { 'weight': 1.0 }, (15, 12): { 'weight': 2.0 }, (22, 24): { 'weight': 1.0 }, (12, 13): { 'weight': 2.0 }, (13, 28): { 'weight': 1.0 }, (5, 8): { 'weight': 1.0 }, (8, 9): { 'weight': 0.5 }, (8, 10): { 'weight': 3.0 }, (9, 12): { 'weight': 0.5 }, (10, 11): { 'weight': 3.0 }, (11, 13): { 'weight': 1.0 }, (7, 25): { 'weight': 1.0 }, (25, 26): { 'weight': 1.0 }, (26, 27): { 'weight': 1.0 }, (11, 26): { 'weight': 1.0 }, (7, 17): { 'weight': 1.0 }, (17, 18): { 'weight': 1.0 }, (18, 19): { 'weight': 2.0 }, (18, 20): { 'weight': 1.0 }, (20, 21): { 'weight': 1.0 }, (21, 22): { 'weight': 1.0 }, (22, 23): { 'weight': 1.0 } } # Regular graphit Graph self.graph = Graph(auto_nid=False) self.graph.directed = True # NetworkX graphit Graph self.gn = NetworkXGraph(auto_nid=False) self.gn.directed = True # NetworkX graph #self.nx = networkx.DiGraph() for eid in edges: self.graph.add_edge(node_from_edge=True, *eid, **edges[eid]) self.gn.add_edge(node_from_edge=True, *eid, **edges[eid]) #self.nx.add_edge(*eid, **edges[eid]) def test_algorithm_degree(self): """ Test degree method part of the Adjacency view """ # Degree self.assertDictEqual( self.graph.adjacency.degree(), { 1: 1, 2: 3, 3: 3, 4: 3, 5: 4, 7: 4, 14: 3, 15: 2, 16: 1, 12: 3, 22: 3, 24: 1, 13: 3, 28: 1, 8: 3, 9: 2, 10: 2, 11: 3, 25: 2, 26: 3, 27: 1, 17: 2, 18: 3, 19: 1, 20: 2, 21: 2, 23: 1 }) # Outdegree self.assertDictEqual( self.graph.adjacency.degree(method='outdegree'), { 1: 1, 2: 2, 3: 2, 4: 2, 5: 2, 7: 2, 14: 2, 15: 1, 16: 0, 12: 1, 22: 2, 24: 0, 13: 1, 28: 0, 8: 2, 9: 1, 10: 1, 11: 2, 25: 1, 26: 1, 27: 0, 17: 1, 18: 2, 19: 0, 20: 1, 21: 1, 23: 0 }) # Indegree self.assertDictEqual( self.graph.adjacency.degree(method='indegree'), { 1: 0, 2: 1, 3: 1, 4: 1, 5: 2, 7: 2, 14: 1, 15: 1, 16: 1, 12: 2, 22: 1, 24: 1, 13: 2, 28: 1, 8: 1, 9: 1, 10: 1, 11: 1, 25: 1, 26: 2, 27: 1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 23: 1 }) # Weighted degree self.assertDictEqual( self.graph.adjacency.degree(weight='weight'), { 1: 1.0, 2: 3.0, 3: 4.0, 4: 3.0, 5: 3.75, 7: 3.75, 14: 5.0, 15: 4.0, 16: 1.0, 12: 4.5, 22: 3.0, 24: 1.0, 13: 4.0, 28: 1.0, 8: 4.5, 9: 1.0, 10: 6.0, 11: 5.0, 25: 2.0, 26: 3.0, 27: 1.0, 17: 2.0, 18: 4.0, 19: 2.0, 20: 2.0, 21: 2.0, 23: 1.0 }) def test_algorithm_dijkstra_shortest_path(self): """ Test Dijkstra shortest path method, weighted and non-weighted. """ # Shortest path in fully directed graph self.assertListEqual(dijkstra_shortest_path(self.graph, 1, 28), [1, 2, 3, 14, 15, 12, 13, 28]) # Shortest path in fully directed graph considering weights self.assertListEqual( dijkstra_shortest_path(self.graph, 1, 28, weight='weight'), [1, 2, 3, 5, 8, 9, 12, 13, 28]) # Reverse directionality of edge (14, 15) self.graph.add_edge(15, 14) self.graph.remove_edge(14, 15) self.assertListEqual(dijkstra_shortest_path(self.graph, 1, 28), [1, 2, 3, 5, 8, 10, 11, 13, 28]) def test_algorithm_dfs_paths(self): """ Test depth-first search of all paths between two nodes """ self.assertListEqual( list(dfs_paths(self.graph, 2, 26)), [[2, 4, 7, 25, 26], [2, 4, 5, 7, 25, 26], [2, 4, 5, 8, 10, 11, 26], [2, 3, 5, 7, 25, 26], [2, 3, 5, 8, 10, 11, 26]]) # Nodes 13 and 26 not connected via directional path self.assertListEqual(list(dfs_paths(self.graph, 13, 26)), []) # Switch to breath-first search self.assertListEqual( list(dfs_paths(self.graph, 2, 26, method='bfs')), [[2, 4, 7, 25, 26], [2, 3, 5, 7, 25, 26], [2, 4, 5, 7, 25, 26], [2, 3, 5, 8, 10, 11, 26], [2, 4, 5, 8, 10, 11, 26]]) # dfs_path with path length cutoff self.assertListEqual( list(dfs_paths(self.graph, 2, 26, cutoff=5)), [[2, 4, 7, 25, 26], [2, 4, 5, 7, 25, 26], [2, 3, 5, 7, 25, 26]]) def test_algorithm_dfs_edges(self): """ Test graph dfs_edges method in depth-first-search (dfs) and breath-first-search (bfs) mode. """ self.assertListEqual(list(dfs_edges(self.graph, 5)), [(5, 7), (7, 17), (17, 18), (18, 19), (18, 20), (20, 21), (21, 22), (22, 23), (22, 24), (7, 25), (25, 26), (26, 27), (5, 8), (8, 9), (9, 12), (12, 13), (13, 28), (8, 10), (10, 11)]) self.assertListEqual(list(dfs_edges(self.graph, 8)), [(8, 9), (9, 12), (12, 13), (13, 28), (8, 10), (10, 11), (11, 26), (26, 27)]) # Breath-first search self.assertListEqual(list(dfs_edges(self.graph, 8, method='bfs')), [(8, 9), (8, 10), (9, 12), (10, 11), (12, 13), (11, 26), (13, 28), (26, 27)]) # With depth limit self.assertListEqual(list(dfs_edges(self.graph, 5, max_depth=2)), [(5, 7), (7, 17), (7, 25), (5, 8), (8, 9), (8, 10)]) def test_algorithm_dfs_edges__edge_based(self): """ Test graph dfs_edges in True edge traversal mode """ # Use true edge oriented DFS method self.assertListEqual(list(dfs_edges(self.graph, 8, edge_based=True)), [(8, 9), (9, 12), (12, 13), (13, 28), (8, 10), (10, 11), (11, 13), (11, 26), (26, 27)]) def test_algorithm_dfs_nodes(self): """ Test graph dfs_nodes method in depth-first-search (dfs) and breath-first-search (bfs) mode """ # Connectivity information using Depth First Search / Breath first search self.assertListEqual(list(dfs_nodes(self.graph, 8)), [8, 9, 12, 13, 28, 10, 11, 26, 27]) self.assertListEqual(list(dfs_nodes(self.graph, 8, method='bfs')), [8, 9, 10, 12, 11, 13, 26, 28, 27]) def test_algorithm_is_reachable(self): """ Test is_reachable method to test connectivity between two nodes """ self.assertTrue(is_reachable(self.graph, 3, 21)) # Reverse directionality of edge (20, 21) self.graph.add_edge(21, 20) self.graph.remove_edge(20, 21) self.assertFalse(is_reachable(self.graph, 7, 23)) def test_algorithm_brandes_betweenness_centrality(self): """ Test graph Brandes betweenness centrality measure """ # Non-weighted Brandes betweenness centrality self.assertDictEqual( brandes_betweenness_centrality(self.graph), { 1: 0.0, 2: 0.038461538461538464, 3: 0.026153846153846153, 4: 0.04461538461538461, 5: 0.047692307692307694, 7: 0.08461538461538462, 8: 0.03230769230769231, 9: 0.00923076923076923, 10: 0.016923076923076923, 11: 0.013846153846153847, 12: 0.023076923076923078, 13: 0.01846153846153846, 14: 0.023076923076923078, 15: 0.01846153846153846, 16: 0.0, 17: 0.06461538461538462, 18: 0.06461538461538462, 19: 0.0, 20: 0.04923076923076923, 21: 0.04153846153846154, 22: 0.03076923076923077, 23: 0.0, 24: 0.0, 25: 0.01846153846153846, 26: 0.015384615384615385, 27: 0.0, 28: 0.0 }) # Weighted Brandes betweenness centrality self.assertDictEqual( brandes_betweenness_centrality(self.graph, weight='weight'), { 1: 0.0, 2: 0.038461538461538464, 3: 0.021538461538461538, 4: 0.04923076923076923, 5: 0.06153846153846154, 7: 0.08461538461538462, 8: 0.046153846153846156, 9: 0.027692307692307693, 10: 0.012307692307692308, 11: 0.00923076923076923, 12: 0.027692307692307693, 13: 0.01846153846153846, 14: 0.00923076923076923, 15: 0.004615384615384615, 16: 0.0, 17: 0.06461538461538462, 18: 0.06461538461538462, 19: 0.0, 20: 0.04923076923076923, 21: 0.04153846153846154, 22: 0.03076923076923077, 23: 0.0, 24: 0.0, 25: 0.01846153846153846, 26: 0.015384615384615385, 27: 0.0, 28: 0.0 }) # Non-Normalized Brandes betweenness centrality self.assertDictEqual( brandes_betweenness_centrality(self.graph, normalized=False), { 1: 0.0, 2: 25.0, 3: 17.0, 4: 29.0, 5: 31.0, 7: 55.0, 8: 21.0, 9: 6.0, 10: 11.0, 11: 9.0, 12: 15.0, 13: 12.0, 14: 15.0, 15: 12.0, 16: 0.0, 17: 42.0, 18: 42.0, 19: 0.0, 20: 32.0, 21: 27.0, 22: 20.0, 23: 0.0, 24: 0.0, 25: 12.0, 26: 10.0, 27: 0.0, 28: 0.0 }) def test_algorithm_eigenvector_centrality(self): """ Test graph node eigenvector centrality """ # Default eigenvector centrality self.assertDictAlmostEqual(eigenvector_centrality(self.graph, max_iter=1000), { 1: 4.625586668162422e-22, 2: 2.585702947502789e-19, 3: 7.21415747939946e-17, 4: 7.21415747939946e-17, 5: 2.6788916354749308e-14, 7: 3.737126037589252e-12, 8: 3.723731579643154e-12, 9: 4.133449353873311e-10, 10: 4.133449353873311e-10, 11: 3.816675918922932e-08, 12: 3.8373431692993267e-08, 13: 6.049668071167027e-06, 14: 1.3394458408653937e-14, 15: 1.861865919106721e-12, 16: 1.861865919106721e-12, 17: 4.152068010478676e-10, 18: 3.837343162085218e-08, 19: 3.0343757153351047e-06, 20: 3.0343757153351047e-06, 21: 0.0002095723846317974, 22: 0.012842890187130716, 23: 0.7070483707650801, 24: 0.7070483707650801, 25: 4.152068010478676e-10, 26: 3.0536657740585734e-06, 27: 0.00021109911510684646, 28: 0.0004176371258851023 }, places=14) # Weighted eigenvector centrality self.assertDictAlmostEqual(eigenvector_centrality(self.graph, max_iter=1000, weight='weight'), { 1: 8.688902566026301e-23, 2: 5.899764842331867e-20, 3: 2.0000289704530646e-17, 4: 2.0000289704530646e-17, 5: 9.026876112472413e-15, 7: 1.148684996586023e-12, 8: 1.5255620780686783e-12, 9: 1.029772476508652e-10, 10: 6.178634859047565e-10, 11: 2.0822457823635333e-07, 12: 6.607834043233963e-09, 13: 2.131713811356956e-05, 14: 9.026876112472416e-15, 15: 3.051124156050468e-12, 16: 1.5255620780686783e-12, 17: 1.5522865250051764e-10, 18: 1.745502542904305e-08, 19: 3.359775472482072e-06, 20: 1.679887736241036e-06, 21: 0.00014125541592609745, 22: 0.010542254842300024, 23: 0.7070653405331172, 24: 0.7070653405331172, 25: 1.5522865250051764e-10, 26: 2.0037291488250496e-05, 27: 0.0016833983234686718, 28: 0.0017929426478924984 }, places=14) # Non-convergence exception self.assertRaises(GraphitAlgorithmError, eigenvector_centrality, self.graph, max_iter=100)
class TestGraphUndirectional(UnittestPythonCompatibility): """ Test graph with undirected edges """ def setUp(self): """ Build undirected Graph """ self.graph = Graph() self.graph.add_edges([(1, 2), (2, 3), (2, 4), (4, 5), (4, 3), (3, 5), (3, 6)], node_from_edge=True, arg1=1.22, arg2=False) def test_graph_is_undirected(self): self.assertFalse(self.graph.directed) self.assertEqual(graph_directionality(self.graph), 'undirectional') self.assertTrue( all([not edge.is_directed for edge in self.graph.iteredges()])) self.assertTrue(len(self.graph.edges) == 14) # 2 * 7 def test_graph_contains(self): """ Test if pair of directed edges is contained in undirected edge """ for edge in self.graph.edges: self.assertTrue(edge in self.graph.edges) self.assertTrue(tuple(reversed(edge)) in self.graph.edges) def test_graph_adjacency(self): """ Node adjacency in a undirected graph reflects the pairs of directed edges that exists between nodes. This is also seen in the adjacency 'link count' and 'degree' metrics for nodes. """ # Number of edges equals the link count of the full node adjacency self.assertEqual(len(self.graph.edges), self.graph.adjacency.link_count()) # Undirected degree is bidirectional. It equals the number of # connected edges to a node degree = self.graph.adjacency.degree() for node in self.graph: self.assertEqual(degree[node.nid], len(node.connected_edges())) def test_graph_degree(self): """ Total degree equals sum of equal inwards and outwards degree """ degree = self.graph.adjacency.degree() indegree = self.graph.adjacency.degree(method='indegree') outdegree = self.graph.adjacency.degree(method='outdegree') self.assertDictEqual(indegree, outdegree) self.assertEqual(sum(indegree.values()) * 2, sum(degree.values())) def test_graph_edge_removal_undirected(self): """ Undirected edge removal removes pair of directed edges """ self.graph.remove_edge(2, 3) self.assertFalse((2, 3) in self.graph.edges) self.assertFalse((3, 2) in self.graph.edges) self.assertEqual(len(self.graph.edges), 12) def test_graph_edge_removal_directed(self): """ Directed removal in a undirected graph is supported """ self.graph.remove_edge(2, 3, directed=True) self.assertFalse((2, 3) in self.graph.edges) self.assertTrue((3, 2) in self.graph.edges) self.assertEqual(len(self.graph.edges), 13) # globally still marked as undirected but of mixed type self.assertFalse(self.graph.directed) self.assertEqual(graph_directionality(self.graph), 'mixed') def test_graph_edge_removal_directed_data_reference(self): """ Test resolving data references after directed edge removal in a undirected graph to prevent orphan pointers """ # Before removal values are referenced self.assertDictEqual(self.graph.edges[(2, 3)], self.graph.edges[(2, 3)]) self.assertEqual(id(self.graph.edges[(2, 3)]), id(self.graph.edges[(2, 3)])) # Remove parent edge copies data to referencing edge self.graph.remove_edge(2, 3, directed=True) self.assertTrue( self.graph.edges._data_pointer_key not in self.graph.edges[(3, 2)]) self.assertDictEqual(self.graph.edges[(3, 2)], { 'arg1': 1.22, 'arg2': False }) # Remove referencing edge does not change anything self.graph.remove_edge(4, 2, directed=True) self.assertTrue( self.graph.edges._data_pointer_key not in self.graph.edges[(2, 4)]) self.assertDictEqual(self.graph.edges[(2, 4)], { 'arg1': 1.22, 'arg2': False }) def test_graph_undirectional_to_directional(self): """ Test conversion of a undirectional to directional graph Conversion essentially breaks all linked edge pairs by removing the data reference pointer. """ directional = graph_undirectional_to_directional(self.graph) # directed attribute changed to True self.assertTrue(directional.directed) self.assertNotEqual(id(directional), id(self.graph)) # directional graph still contains same nodes and edges self.assertEqual(directional, self.graph) # data reference pointers determine directionality self.assertEqual(graph_directionality(directional), 'directional') self.assertEqual( graph_directionality(directional, has_data_reference=False), 'undirectional') # directional edge pair no longer point to same value directional_checked = [] for edge in directional.edges: directional_checked.append( id(directional.edges[edge]) != id(directional.edges[( edge[1], edge[0])])) self.assertTrue(all(directional_checked)) def test_graph_undirected_linked_values(self): """ Test setting and getting linked edge data """ self.graph.edges[(2, 3)]['test'] = True # In storage backend only one edge of the pair has the data self.assertTrue('test' in self.graph.edges._storage[(2, 3)]) self.assertFalse('test' in self.graph.edges._storage[(3, 2)]) # transparent getting and setting of linked data self.assertTrue(self.graph.edges[(2, 3)]['test']) self.assertTrue(self.graph.edges[(3, 2)]['test']) self.graph.edges[(3, 2)]['test'] = False self.assertFalse(self.graph.edges[(2, 3)]['test']) self.assertFalse(self.graph.edges[(3, 2)]['test'])
class TestGraphAlgorithms(UnittestPythonCompatibility): def setUp(self): edges = { (5, 4): { 'type': 'universal' }, (5, 6): { 'type': 'universal' }, (11, 9): { 'type': 'universal' }, (3, 2): { 'type': 'universal' }, (2, 1): { 'type': 'monotone' }, (9, 10): { 'type': 'universal' }, (2, 3): { 'type': 'universal' }, (9, 6): { 'type': 'universal' }, (6, 5): { 'type': 'universal' }, (1, 2): { 'type': 'monotone' }, ('object', 12): { 'type': 'universal' }, (6, 9): { 'type': 'universal' }, (6, 7): { 'type': 'universal' }, (12, 13): { 'type': 'monotone' }, (7, 8): {}, (7, 6): { 'type': 'universal' }, (13, 12): { 'type': 'monotone' }, (3, 8): { 'type': 'universal' }, (4, 5): { 'type': 'universal' }, (12, 'object'): { 'type': 'universal' }, (9, 11): { 'type': 'universal' }, (4, 3): { 'type': 'universal' }, (8, 3): { 'type': 'universal' }, (3, 4): { 'type': 'universal' }, (10, 9): { 'type': 'universal' } } self.graph = Graph(auto_nid=False) self.graph.directed = True self.gn = NetworkXGraph() self.gn.directed = True self.nx = networkx.DiGraph() weight = 0 for node in range(1, 14): self.graph.add_node(node, weight=weight) self.gn.add_node(node, weight=weight) self.nx.add_node(node, _id=node, key=node, weight=weight) weight += 1 self.graph.add_node('object') self.gn.add_node('object') self.nx.add_node('object', _id=node + 1, key='object') weight = 0 for eid in sorted(edges.keys(), key=lambda x: str(x[0])): self.graph.add_edge(*eid, weight=weight) self.gn.add_edge(*eid, weight=weight) self.nx.add_edge(*eid, weight=weight) weight += 0.05 def test_graph_shortest_path_method(self): """ Test Dijkstra shortest path method """ from networkx.algorithms.shortest_paths.generic import shortest_path from networkx.algorithms.traversal.depth_first_search import dfs_preorder_nodes print(shortest_path(self.nx, 8, 10)) print(list(dfs_preorder_nodes(self.nx, 8))) # In a mixed directed graph where 7 connects to 8 but not 8 to 7 self.assertEqual(dijkstra_shortest_path(self.graph, 8, 10), [8, 3, 4, 5, 6, 9, 10]) self.assertEqual(list(dfs_paths(self.graph, 8, 10)), [[8, 3, 4, 5, 6, 9, 10]]) self.assertEqual(list(dfs_paths(self.graph, 8, 10, method='bfs')), [[8, 3, 4, 5, 6, 9, 10]]) # Fully connect 7 and 8 self.graph.add_edge(8, 7, directed=True) self.assertEqual(dijkstra_shortest_path(self.graph, 8, 10), [8, 7, 6, 9, 10]) self.assertEqual(list(dfs_paths(self.graph, 8, 10)), [[8, 7, 6, 9, 10], [8, 3, 4, 5, 6, 9, 10]]) self.assertEqual(list(dfs_paths(self.graph, 8, 10, method='bfs')), [[8, 7, 6, 9, 10], [8, 3, 4, 5, 6, 9, 10]]) def test_graph_dfs_method(self): """ Test graph depth-first-search and breath-first-search """ # Connectivity information using Depth First Search / Breath first search self.assertListEqual(dfs(self.graph, 8), [8, 3, 4, 5, 6, 9, 11, 10, 7, 2, 1]) self.assertListEqual(dfs(self.graph, 8, method='bfs'), [8, 3, 2, 4, 1, 5, 6, 7, 9, 10, 11]) def test_graph_node_reachability_methods(self): """ Test graph algorithms """ # Test if node is reachable from other node (uses dfs internally) self.assertTrue(is_reachable(self.graph, 8, 10)) self.assertFalse(is_reachable(self.graph, 8, 12)) def test_graph_centrality_method(self): """ Test graph Brandes betweenness centrality measure """ # Return Brandes betweenness centrality self.assertDictEqual( brandes_betweenness_centrality(self.graph), { 1: 0.0, 2: 0.11538461538461538, 3: 0.26282051282051283, 4: 0.21474358974358973, 5: 0.22756410256410256, 6: 0.3205128205128205, 7: 0.0673076923076923, 8: 0.060897435897435896, 9: 0.21794871794871795, 10: 0.0, 11: 0.0, 12: 0.01282051282051282, 13: 0.0, u'object': 0.0 }) print(brandes_betweenness_centrality(self.graph, weight='weight')) print(brandes_betweenness_centrality(self.graph, normalized=False)) # Test against NetworkX if possible if self.nx is not None: from networkx.algorithms.centrality.betweenness import betweenness_centrality # Regular Brandes betweenness centrality nx_between = betweenness_centrality(self.nx) gn_between = brandes_betweenness_centrality(self.graph) self.assertDictEqual(gn_between, nx_between) # Weighted Brandes betweenness centrality nx_between = betweenness_centrality(self.nx, weight='weight') gn_between = brandes_betweenness_centrality(self.graph, weight='weight') self.assertDictEqual(gn_between, nx_between) # Normalized Brandes betweenness centrality nx_between = betweenness_centrality(self.nx, normalized=False) gn_between = brandes_betweenness_centrality(self.graph, normalized=False) self.assertDictEqual(gn_between, nx_between) def test_graph_nodes_are_interconnected(self): """ Test if all nodes directly connected with one another """ nodes = [1, 2, 3, 4, 5, 6] self.graph = Graph() self.graph.add_nodes(nodes) for edge in itertools.combinations(nodes, 2): self.graph.add_edge(*edge) self.graph.remove_edge(5, 6) self.assertTrue(nodes_are_interconnected(self.graph, [1, 2, 4])) self.assertFalse(nodes_are_interconnected(self.graph, [3, 5, 6])) def test_graph_degree(self): """ Test (weighted) degree method """ self.assertDictEqual(degree(self.graph, [1, 3, 12]), { 1: 1, 3: 3, 12: 2 }) # Directed graphs behave the same as undirected self.graph.directed = False self.assertDictEqual(degree(self.graph, [1, 3, 12]), { 1: 1, 3: 3, 12: 2 }) self.assertDictEqual(degree(self.graph, [1, 3, 12], weight='weight'), { 1: 0, 3: 1.3499999999999999, 12: 0.35000000000000003 }) # Loops counted twice self.graph.add_edge(12, 12) self.assertDictEqual(degree(self.graph, [1, 3, 12]), { 1: 1, 3: 3, 12: 4 }) self.assertDictEqual(degree(self.graph, [1, 3, 12], weight='weight'), { 1: 0, 3: 1.3499999999999999, 12: 2.3499999999999996 })