Graph
=====

Unit tests for Graph Class
--------------------------

>>> from networkx import *
>>> import networkx
>>> is_isomorphic=networkx.algorithms.isomorphism.isomorph.graph_could_be_isomorphic
>>> from networkx import convert_node_labels_to_integers as cnlti

Some small graphs
-----------------

>>> null=null_graph()
>>> P1=cnlti(path_graph(1),first_label=1)
>>> P3=cnlti(path_graph(3),first_label=1)
>>> P10=cnlti(path_graph(10),first_label=1)
>>> K1=cnlti(complete_graph(1),first_label=1)
>>> K3=cnlti(complete_graph(3),first_label=1)
>>> K4=cnlti(complete_graph(4),first_label=1)
>>> K5=cnlti(complete_graph(5),first_label=1)
>>> K10=cnlti(complete_graph(10),first_label=1)

Name
----

>>> G = Graph(name="test")
>>> print G    # test of __str__
test
>>> print G.name
test
>>> H=Graph()
>>> print H.name
<BLANKLINE>

>>> G2=Graph(data={1:[2],2:[1]}, name="test")
>>> print G2.edges()
[(1, 2)]
>>> print G2.name
test


Nodes
-----

>>> G.add_node('A')
>>> G.has_node('A')
True

Test if a non-hashable object is in the Graph.  A python dict will
raise a TypeError, but for a Graph class a simple  False should be
returned (see Graph __contains__). If it cannot be a node then it is
not a node.

>>> G.has_node(['A'])
False

>>> G.has_node({'A':1})
False

>>> G.remove_node('A')
>>> G.has_node('A')
False
>>> G.add_nodes_from(list("ABCDEFGHIJKL"))
>>> G.has_node("L")
True
>>> G.remove_nodes_from(['H','I','J','K','L'])
>>> G.add_nodes_from([1,2,3,4])
>>> sorted(G.nodes(),key=str)
[1, 2, 3, 4, 'A', 'B', 'C', 'D', 'E', 'F', 'G']
>>> sorted(G,key=str)   # test __iter__
[1, 2, 3, 4, 'A', 'B', 'C', 'D', 'E', 'F', 'G']


test __contains__

>>> 'A' in G   
True
>>> [] in G  # never raise a Key or TypeError in this test
False
>>> {1:1} in G
False


test __len__

>>> len(G) 
11

test node portion of clear()

>>> G.clear()   
>>> G.nodes()
[]

Test add_node and remove_node acting for various nbunch

>>> G.add_node('m')
>>> G.has_node('m')
True
>>> G.add_node('m')   # no complaints
>>> G.remove_node('j') # NetworkXError
Traceback (most recent call last):
...
NetworkXError: The node j is not in the graph.
>>> G.remove_node('m')
>>> G.nodes()
[]

nbunch is a list.

>>> G.add_nodes_from(list("ABCD")) 
>>> G.add_nodes_from(P3) # add nbunch of nodes (nbunch=Graph)
>>> sorted(G.nodes(),key=str)
[1, 2, 3, 'A', 'B', 'C', 'D']
>>> G.remove_nodes_from(P3) # remove nbunch of nodes (nbunch=Graph)
>>> sorted(G.nodes(),key=str)
['A', 'B', 'C', 'D']

nbunch is a set

>>> nbunch=set("ABCDEFGHIJKL")
>>> G.add_nodes_from(nbunch)
>>> G.has_node("L")
True

nbunch is a dict with nodes as keys

>>> nbunch={'I':"foo",'J':2,'K':True,'L':"spam"}
>>> G.remove_nodes_from(nbunch)
>>> sorted(G.nodes(),key=str)
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']

nbunch is an iterator

>>> n_iter=P3.nodes_iter()
>>> G.add_nodes_from(n_iter)
>>> sorted(G.nodes(),key=str)
[1, 2, 3, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
>>> n_iter=P3.nodes_iter() # rebuild same iterator
>>> G.remove_nodes_from(n_iter) # remove nbunch of nodes (nbunch=iterator)
>>> sorted(G.nodes(),key=str)
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']

nbunch is a graph

>>> nbunch=K3
>>> G.add_nodes_from(nbunch)
>>> sorted(G.nodes(),key=str)
[1, 2, 3, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']


Edges
-----

>>> G.add_edge('A')
Traceback (most recent call last):
...
TypeError: add_edge() takes at least 3 arguments (2 given)


>>> G.add_edge('A','B')     # testing add_edge()
>>> G.add_edge('A','B') # should fail silently
>>> G.has_edge('A','B')     # testing has_edge()
True
>>> G.has_edge('A','C')
False
>>> G.has_edge( *('A','B') )
True
>>> G.has_edge('B','A') # G is undirected, so B->A is an edge
True

>>> G.add_edge('A','C')  # test directedness
>>> G.add_edge('C','A')
>>> G.remove_edge('C','A')
>>> G.has_edge('A','C') # G is undirected
False
>>> G.has_edge('C','A') 
False


>>> G.add_edge('A','A') # test self loops
>>> G.has_edge('A','A') # should have edge
True
>>> G.remove_edge('A','A')

>>> G.add_edge('X','X')
>>> G.has_node('X')  # added node but not self loop
True
>>> G.remove_node('X')


>>> G.add_edge('A','Z') # should add the node silently
>>> G.has_node('Z')
True

>>> G.add_edges_from([('B','C')])   # test add_edges_from()
>>> G.has_edge('B','C')
True
>>> G.has_edge('C','B')  # undirected
True
>>> G.add_edges_from([('D','F'),('B','D')])
>>> G.has_edge('D','F')
True
>>> G.has_edge('B','D')
True
>>> G.has_edge('D','B')  # undirected
True
>>> G.add_edges_from([tuple('IJ'),list('KK'),tuple('JK')])  # after failing silently, should add 2nd edge
>>> G.has_edge(*('I','J'))
True
>>> G.has_edge(*('K','K'))
True
>>> G.has_edge(*('J','K'))
True
>>> G.has_edge(*('K','J'))  # undirected
True

>>> G.add_edges_from(zip(list('ACD'),list('CDE'))) 
>>> G.has_edge('D','E')
True
>>> G.has_edge('E','C')
False
>>> G.add_edges_from(zip(list('MNOP'),list('NOPM')))
>>> G.has_edge('O','P')
True
>>> G.has_edge('P','M')
True
>>> G.remove_node('P')    # tests remove_node()'s handling of edges.
>>> G.has_edge('P','M')
False


>>> G.remove_edge('M')       # test remove_edge()
Traceback (most recent call last):
...
TypeError: remove_edge() takes exactly 3 arguments (2 given)

>>> G.add_edge('N','M')  
>>> G.has_edge('M','N')
True
>>> G.remove_edge('M','N')
>>> G.has_edge('M','N')
False
>>> G.has_edge('N','M')  # undirected
False
>>> G.remove_edges_from([list('HI'),list('DF'),tuple('KK'),tuple('JK')]) # self loop fails silently
>>> G.has_edge('H','I')
False
>>> G.has_edge('J','K')
False
>>> G.remove_edges_from([list('IJ'),list('KK'),list('JK')])
>>> G.has_edge('I','J')
False
>>> G.remove_nodes_from(set('ZEFHIMNO'))
>>> sorted(G.nodes(),key=str)
[1, 2, 3, 'A', 'B', 'C', 'D', 'G', 'J', 'K']
>>> G.remove_nodes_from([1,2,3])
>>> sorted(G.nodes())
['A', 'B', 'C', 'D', 'G', 'J', 'K']
>>> sorted(G.edges())
[('A', 'B'), ('A', 'C'), ('B', 'D'), ('C', 'B'), ('C', 'D')]

Test G.edges(nbunch) with various forms of nbunch

node not in nbunch should be quietly ignored

>>> sorted(G.edges(6))    # non-iterable non-node
Traceback (most recent call last):
...
NetworkXError: nbunch is not a node or a sequence of nodes.


>>> sorted(G.edges('Z'))  # iterable non-node
[]

nbunch can be an empty list

>>> sorted(G.edges([])) 
[]

nbunch can be a list

>>> sorted(G.edges(['A','B']))
[('A', 'B'), ('A', 'C'), ('B', 'C'), ('B', 'D')]

nbunch can be a set

>>> sorted(G.edges(set(['A','B'])))
[('A', 'B'), ('A', 'C'), ('B', 'C'), ('B', 'D')]

nbunch can be a graph

>>> G1=Graph()
>>> G1.add_nodes_from('AB')
>>> sorted(G.edges(G1)) # nbunch is a graph
[('A', 'B'), ('A', 'C'), ('B', 'C'), ('B', 'D')]

nbunch can be a dict with nodes as keys

>>> ndict={'A': "thing1", 'B': "thing2"}
>>> sorted(G.edges(ndict))
[('A', 'B'), ('A', 'C'), ('B', 'C'), ('B', 'D')]

nbunch can be a single node

>>> sorted(G.edges('A'))
[('A', 'B'), ('A', 'C')]


Test G.edges_iter(nbunch) with various forms of nbunch

node not in nbunch should be quietly ignored

>>> sorted(G.edges_iter('Z'))
[]

nbunch can be an empty list

>>> sorted(G.edges_iter([])) 
[]

nbunch can be a list

>>> sorted(G.edges_iter(['A','B']))
[('A', 'B'), ('A', 'C'), ('B', 'C'), ('B', 'D')]

nbunch can be a set

>>> sorted(G.edges_iter(set(['A','B'])))
[('A', 'B'), ('A', 'C'), ('B', 'C'), ('B', 'D')]

nbunch can be a graph

>>> G1=Graph()
>>> G1.add_nodes_from(['A','B'])
>>> sorted(G.edges_iter(G1)) # nbunch is a graph
[('A', 'B'), ('A', 'C'), ('B', 'C'), ('B', 'D')]

nbunch can be a dict with nodes as keys

>>> ndict={'A': "thing1", 'B': "thing2"}
>>> sorted(G.edges_iter(ndict))
[('A', 'B'), ('A', 'C'), ('B', 'C'), ('B', 'D')]

nbunch can be a single node

>>> sorted(G.edges_iter('A'))
[('A', 'B'), ('A', 'C')]

nbunch can be nothing (whole graph)

>>> sorted(G.edges_iter())
[('A', 'B'), ('A', 'C'), ('B', 'D'), ('C', 'B'), ('C', 'D')]
>>> sorted(G.nodes_iter())
['A', 'B', 'C', 'D', 'G', 'J', 'K']



Properties
----------

degree of single node must return single int

>>> G.degree('A')
2

degree of single node in iterable container must return dict

>>> G.degree(['A']).values()
[2]

>>> G.degree(['A'])
{'A': 2}

>>> G.degree(['A','B']).values()
[2, 3]
>>> G.degree(['A','B'])
{'A': 2, 'B': 3}

>>> sorted(G.degree().values())
[0, 0, 0, 2, 2, 3, 3]

sorted(list(G.degree_iter()))
[0, 0, 0, 2, 2, 3, 3]

>>> H=Graph()
>>> H.add_edges_from([(1,24),(1,2)])
>>> sorted(H.degree([1,24]).values())
[1, 2]

>>> P3=path_graph(3)
>>> P5=path_graph(5)
>>> P3.degree(['A','B']) # silently ignore nodes not in P3
{}
>>> sorted(P5.degree(P3).values()) # nbunch can be a graph
[1, 2, 2]
>>> sorted(P3.degree(P5).values()) # nbunch can be a graph thats way to big
[1, 1, 2]
>>> P5.degree([])
{}
>>> list(P5.degree_iter([]))
[]
>>> dict( P5.degree_iter([]) )
{}
>>> dict(P5.degree_iter([]))
{}

>>> null=null_graph()
>>> null.degree()
{}
>>> list(null.degree_iter())
[]
>>> dict(null.degree_iter())
{}

>>> G.order()
7
>>> G.size()
5
>>> G.number_of_edges()
5
>>> G.number_of_edges('A','B')
1
>>> G.number_of_edges('A','D')
0

Operations
-----------

copy
~~~~

>>> H=G.copy()      # copy
>>> H.adj==G.adj
True
>>> H.name==G.name
True
>>> H==G
False

subgraph

>>> SG=G.subgraph(['A','B','D']) 
>>> sorted(SG.nodes())
['A', 'B', 'D']
>>> sorted(SG.edges())
[('A', 'B'), ('B', 'D')]

to_directed

DG=G.to_directed()
DG==G
False
DG.is_directed()
True
G.is_directed()
False
DG.name==G.name
True
DG.adj==G.adj
True
sorted(DG.out_edges(list('AB')))
[('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('B', 'D')]
DG.remove_edge('A','B')
DG.has_edge('B','A') # this removes B-A but not  A-B
True
DG.has_edge('A','B')
False

  # to_undirected


Neighbors
---------

>>> sorted(G['A'])
['B', 'C']
>>> sorted(G.neighbors('A'))
['B', 'C']
>>> sorted(G.neighbors_iter('A'))
['B', 'C']

>>> sorted(G.neighbors('G'))
[]
>>> sorted(G.neighbors('j'))
Traceback (most recent call last):
...
NetworkXError: The node j is not in the graph.


Iterators
---------

>>> sorted(G.nodes_iter())
['A', 'B', 'C', 'D', 'G', 'J', 'K']
>>> sorted(G.edges_iter())
[('A', 'B'), ('A', 'C'), ('B', 'D'), ('C', 'B'), ('C', 'D')]

sorted(G.degree_iter())
[0, 0, 0, 2, 2, 3, 3]

>>> sorted(G.degree_iter(),key=str)
[('A', 2), ('B', 3), ('C', 3), ('D', 2), ('G', 0), ('J', 0), ('K', 0)]
>>> sorted(G.neighbors_iter('A'))
['B', 'C']
>>> sorted(G.neighbors_iter('X'))
Traceback (most recent call last):
...
NetworkXError: The node X is not in the graph.

>>> G.clear()
>>> number_of_nodes(G)
0
>>> number_of_edges(G)
0

Subgraph
--------

Subgraph of a null graph is a null graph

>>> nullgraph=null_graph()
>>> G=null_graph()
>>> H=G.subgraph([])
>>> is_isomorphic(H,nullgraph)
True

Subgraph of an empty graph is an empty graph. test 1 

>>> E5=empty_graph(5)
>>> E10=empty_graph(10)
>>> H=E10.subgraph([])
>>> is_isomorphic(H,nullgraph)
True

Subgraph of an  empty graph is an empty graph. test 2

>>> H=E10.subgraph([1,2,3,4,5])
>>> is_isomorphic(H,E5)
True

Subgraph of a complete graph is a complete graph

>>> K1=complete_graph(1)
>>> K3=complete_graph(3)
>>> K5=complete_graph(5)
>>> H=K5.subgraph([1,2,3])
>>> is_isomorphic(H,K3)
True

Test G.subgraph(nbunch), where nbunch is a single node

>>> H=K5.subgraph(1)
>>> is_isomorphic(H,K1)
True

Test G.subgraph(nbunch), where nbunch is a set

>>> H=K5.subgraph(set([1]))
>>> is_isomorphic(H,K1)
True

Test G.subgraph(nbunch), where nbunch is an iterator

>>> H=K5.subgraph(iter(K3))
>>> is_isomorphic(H,K3)
True

Test G.subgraph(nbunch), where nbunch is another graph

>>> H=K5.subgraph(K3)
>>> is_isomorphic(H,K3)
True


Test for no error when nbunch has node not in G.nodes()

>>> H=K5.subgraph([9])
>>> is_isomorphic(H,null_graph())
True

Test error handling of tuple as a node
>>> H.remove_node((1,2))
Traceback (most recent call last):
...
NetworkXError: The node (1, 2) is not in the graph.

>>> H.remove_nodes_from([(1,2)])

was
Traceback (most recent call last):
...
NetworkXError: The node (1, 2) is not in the graph.

>>> H.neighbors((1,2))
Traceback (most recent call last):
...
NetworkXError: The node (1, 2) is not in the graph.
