class TestGraphIteration(UnittestPythonCompatibility): """ Test methods for iteration over nodes and edges in a graph """ def setUp(self): """ Build default Graph with node and edge attributes """ self.graph = Graph() self.graph.add_nodes([('g', {'weight': 1.0, 'value': 'gr'}), ('r', {'weight': 1.5, 'value': 'ra'}), ('a', {'weight': 2.0, 'value': 'ap'}), ('p', {'weight': 2.5, 'value': 'ph'}), ('h', {'weight': 3.0})]) self.graph.add_edges([(1, 2), (2, 3), (3, 4), (3, 5), (4, 5)], value=True, weight=43.2, key='edge') def test_iterators_isgenerator(self): """ Node and edge iterators return a generator """ self.assertTrue(isinstance(self.graph.iternodes(), types.GeneratorType)) self.assertTrue(isinstance(self.graph.iteredges(), types.GeneratorType)) def test_iterators_iternodes(self): """ Iternodes returns single node graphs based on sorted node ID which in case of auto_nid returns the nodes in the order they where added. """ for i, n in enumerate(self.graph.iternodes(), start=1): self.assertIsInstance(n, Graph) self.assertEqual(n.nid, i) # The Graph '__iter__' magic method points to iternodes for i, n in enumerate(self.graph, start=1): self.assertIsInstance(n, Graph) self.assertEqual(n.nid, i) def test_iterators_iternodes_reversed(self): """ Iterate over nodes in reversed order based on node ID """ self.assertListEqual([n.nid for n in self.graph.iternodes(reverse=True)], [5, 4, 3, 2, 1]) def test_iterators_iternodes_subgraph(self): """ Iternodes on a subgraph will only iterate over the nodes in the subgraph """ sub = self.graph.getnodes([1, 3, 4]) self.assertEqual(len(sub), 3) self.assertEqual([n.nid for n in sub.iternodes()], [1, 3, 4]) self.assertEqual([n.nid for n in sub], [1, 3, 4]) def test_iterators_iteredges(self): """ Iteredges returns single edge graphs based on sorted edge ID. """ edges = [] for e in self.graph.iteredges(): self.assertIsInstance(e, Graph) edges.append(e.nid) self.assertListEqual(edges, [(1, 2), (2, 1), (2, 3), (3, 2), (3, 4), (3, 5), (4, 3), (4, 5), (5, 3), (5, 4)]) def test_iterators_iteredges_reversed(self): """ Iterate over edges in reversed order based on edge ID """ self.assertListEqual([e.nid for e in self.graph.iteredges(reverse=True)], [(5, 4), (5, 3), (4, 5), (4, 3), (3, 5), (3, 4), (3, 2), (2, 3), (2, 1), (1, 2)])
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 TestGraphMixed(UnittestPythonCompatibility): """ Test graph with mixed directed and undirected edges 1 - 2 - 3 - 6 | / | 4 - 5 """ def setUp(self): """ Build mixed directed and undirected Graph """ self.graph = Graph(directed=True) self.graph.add_edges([(1, 2), (2, 3), (3, 2), (3, 6), (3, 5), (5, 4), (4, 5), (4, 2)], node_from_edge=True, arg1=1.22, arg2=False) self.graph.add_edge(3, 4, directed=False, node_from_edge=True, arg1=1.22, arg2=False) for i, edge in enumerate(self.graph.iteredges()): edge['nd'] = i def test_graph_is_mixed(self): self.assertTrue(self.graph.directed) self.assertEqual(graph_directionality(self.graph), 'mixed') self.assertEqual( sum([1 for edge in self.graph.iteredges() if edge.is_directed]), 4) self.assertEqual( sum([1 for edge in self.graph.iteredges() if not edge.is_directed]), 6) self.assertTrue(len(self.graph.edges) == 10) def test_graph_contains(self): """ Test a mix of directed and undirected (pairs) edges """ directed = [edge.is_directed for edge in self.graph.iteredges()] self.assertEqual( directed, [True, False, False, False, True, True, True, False, False, False]) def test_graph_directional_to_undirectional(self): """ Test conversion of a directional to undirectional graph """ undirectional = graph_directional_to_undirectional(self.graph) # directed attribute changed to True self.assertFalse(undirectional.directed) self.assertNotEqual(undirectional, self.graph) # data reference pointers determine directionality self.assertEqual(graph_directionality(undirectional), 'undirectional') self.assertEqual( graph_directionality(undirectional, has_data_reference=False), 'undirectional') # all edges exist in pairs resulting duplicated values values = undirectional.edges(data='nd').values() self.assertEqual(len(values), len(set(values)) * 2)
class TestGraphDirectional(UnittestPythonCompatibility): """ Test graph with directed edges 1 - 2 - 3 - 6 | / | 4 - 5 """ def setUp(self): """ Build directed Graph """ self.graph = Graph(directed=True) self.graph.add_edges([(1, 2), (2, 3), (3, 6), (3, 5), (5, 4), (4, 3), (4, 2)], node_from_edge=True, arg1=1.22, arg2=False) self.graph.edges[(3, 6)]['arg1'] = 2.44 self.graph.edges[(5, 4)]['arg3'] = 'test' def test_graph_is_directed(self): self.assertTrue(self.graph.directed) self.assertEqual(graph_directionality(self.graph), 'directional') self.assertTrue( all([edge.is_directed for edge in self.graph.iteredges()])) self.assertTrue(len(self.graph.edges) == 7) def test_graph_contains(self): """ Only forward edge present """ for edge in self.graph.edges: self.assertTrue(edge in self.graph.edges) self.assertFalse(tuple(reversed(edge)) in self.graph.edges) def test_graph_adjacency(self): """ Node adjacency in a directed graph reflects the presence of only a forward edge connecting 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()) # Directed degree is unidirectional. 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): """ The total degree equals the sum of inwards and outwards degrees but the latter two are not equals """ degree = self.graph.adjacency.degree() indegree = self.graph.adjacency.degree(method='indegree') outdegree = self.graph.adjacency.degree(method='outdegree') self.assertEqual(sum(degree.values()), sum(indegree.values()) + sum(outdegree.values())) self.assertNotEqual(indegree, outdegree) def test_graph_directional_to_undirectional(self): """ Test conversion of a directional to undirectional graph """ undirectional = graph_directional_to_undirectional(self.graph) # directed attribute changed to True self.assertFalse(undirectional.directed) self.assertNotEqual(undirectional, self.graph) # data reference pointers determine directionality self.assertEqual(graph_directionality(undirectional), 'undirectional') self.assertEqual( graph_directionality(undirectional, has_data_reference=False), 'undirectional') # directional edge pair point to same value undirectional_checked = [] for edge in undirectional.edges: undirectional_checked.append( id(undirectional.edges[edge]) == id(undirectional.edges[( edge[1], edge[0])])) self.assertTrue(all(undirectional_checked)) # edge argument equality self.assertDictEqual(undirectional.edges[(3, 6)], { 'arg1': 2.44, 'arg2': False }) self.assertDictEqual(undirectional.edges[(6, 3)], { 'arg1': 2.44, 'arg2': False }) self.assertDictEqual(undirectional.edges[(5, 4)], { 'arg1': 1.22, 'arg2': False, 'arg3': 'test' }) self.assertDictEqual(undirectional.edges[(4, 5)], { 'arg1': 1.22, 'arg2': False, 'arg3': 'test' })