DiGraph
=======

>>> from networkx import *
>>> from networkx.isomorph import graph_could_be_isomorphic
>>> is_isomorphic=graph_could_be_isomorphic


Unit tests for DiGraph Class in base.py
---------------------------------------


In addition to the usual suspects (P1, P2, P3, K1, K2, null, etc.)
we use: P1di, P2di, etc. where Xdi=to_directed(X)
 
     G -- named "test"
       -- grown and deleted, with nodes A,B,C,...
 
     H -- copy of G with extra int nodes from P3 and K3
 

>>> from networkx import *
>>> from networkx.isomorph import graph_could_be_isomorphic
>>> is_isomorphic=graph_could_be_isomorphic


Some small Graphs
-----------------

>>> null=null_graph()
>>> P1=path_graph(1)
>>> P3=path_graph(3)
>>> P10=path_graph(10)
>>> K1=complete_graph(1)
>>> K3=complete_graph(3)
>>> K5=complete_graph(5)

Some small digraphs

>>> nulldi=null.to_directed()
>>> P1di=P1.to_directed()
>>> P3di=P3.to_directed()
>>> P10di=P10.to_directed()
>>> K1di=K1.to_directed()
>>> K3di=K3.to_directed()
>>> K5di=K5.to_directed()




Name
----

>>> G = DiGraph(name="test")
>>> print G    # test of __str__
test
>>> print G.name
test
>>> G.print_dna()
datastructure  : vdict_of_dicts

>>> H= DiGraph()
>>> print H.name
No Name


Nodes
-----

>>> G.add_node('A')
>>> G.has_node('A')
True
>>> G.delete_node('A')
>>> G.has_node('A')
False
>>> G.add_nodes_from(list("ABCDEFGHIJKL"))
>>> G.has_node("L")
True
>>> G.delete_nodes_from(['H','I','J','K','L'])
>>> G.add_nodes_from([1,2,3,4])
>>> sorted(G.nodes())
[1, 2, 3, 4, 'A', 'B', 'C', 'D', 'E', 'F', 'G']
>>> sorted(G)   # test __iter__
[1, 2, 3, 4, 'A', 'B', 'C', 'D', 'E', 'F', 'G']
>>> 'A' in G    # test __contains__
True
>>> len(G)      # test __len__
11

>>> G.clear()     # test node portion of clear()
>>> G.nodes()
[]

Test add_node and delete_node acting for various nbunch

>>> G.add_node('m')
>>> G.has_node('m')
True
>>> G.add_node('m')   # no complaints
>>> G.delete_node('j') # NetworkXError
Traceback (most recent call last):
...
NetworkXError: node j not in graph
>>> G.delete_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())
[1, 2, 3, 'A', 'B', 'C', 'D']
>>> G.delete_nodes_from(P3) # delete nbunch of nodes (nbunch=Graph)
>>> sorted(G.nodes())
['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.delete_nodes_from(nbunch)
>>> sorted(G.nodes())
['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())
[1, 2, 3, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
>>> n_iter=P3.nodes_iter() # rebuild same iterator
>>> G.delete_nodes_from(n_iter) # delete nbunch of nodes (nbunch=iterator)
>>> sorted(G.nodes())
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']

nbunch is a graph

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

Edges
-----

>>> G.add_edge('A')
Traceback (most recent call last):
...
ValueError: need more than 1 value to unpack

>>> 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 directed, so B->A is not an edge
False
>>> G.has_neighbor('A','C')  # same as has_edge
False
>>> G.has_neighbor('A','B')  
True

>>> G.add_edge('A','C')  # test directedness
>>> G.add_edge('C','A')
>>> G.delete_edge('C','A')
>>> G.has_edge('A','C') # G is directed
True
>>> G.has_edge('C','A') 
False

>>> G.add_edge('A','A') # test self loops
>>> G.has_edge('A','A')
False

>>> G.add_edge('X','X')
>>> G.has_node('X')  # added node but not self loop
True
>>> G.delete_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')  # directed
False
>>> G.add_edges_from([('D','F'),('B','D')])   # test add_edges_from()
>>> G.has_edge('D','F')
True
>>> G.has_edge('B','D')
True
>>> G.has_edge('D','B')  # directed
False
>>> G.add_edges_from([tuple('IJ'),list('KK'),tuple('JK')])  # after failing silently, should add 3rd edge
>>> G.has_edge(('I','J'))
True
>>> G.has_edge(('K','K'))
False
>>> G.has_edge(('J','K'))
True
>>> G.has_edge(('K','J'))  # directed
False

>>> G.add_path(list('ACDE'))      # test add_path() and add_cycle()
>>> G.has_edge('D','E')
True
>>> G.has_edge('E','C')
False
>>> G.add_cycle(list('MNOP'))
>>> G.has_edge('O','P')
True
>>> G.has_edge('P','M')
True
>>> G.delete_node('P')    # tests delete_node()'s handling of edges.
>>> G.has_edge('P','M')
False


>>> G.delete_edge('M')       # test delete_edge()
Traceback (most recent call last):
...
ValueError: need more than 1 value to unpack

>>> G.add_edge('N','M')  
>>> G.has_edge('M','N')
True
>>> G.delete_edge('M','N')
>>> G.has_edge('M','N')
False
>>> G.has_edge('N','M')  # directed
True
>>> G.delete_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.delete_nodes_from(set('ZEFHIMNO'))
>>> sorted(G.nodes())
[1, 2, 3, 'A', 'B', 'C', 'D', 'G', 'J', 'K']
>>> G.delete_nodes_from([1,2,3])
>>> sorted(G.nodes())
['A', 'B', 'C', 'D', 'G', 'J', 'K']
>>> sorted(G.edges())
[('A', 'B'), ('A', 'C'), ('B', 'C'), ('B', 'D'), ('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
[]

>>> 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)) 
[('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')]

G.edges() does not support with_labels=True option

>>> sorted(G.edges(with_labels=True))
Traceback (most recent call last):
...
NetworkXError: with_labels=True option not supported. Rather use neighbors()


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')]

G.edges_iter() does not support with_labels=True option

>>> sorted(G.edges_iter(with_labels=True))
Traceback (most recent call last):
...
NetworkXError: with_labels=True option not supported. Rather use neighbors()

nbunch can be nothing (whole graph)

>>> sorted(G.edges_iter())
[('A', 'B'), ('A', 'C'), ('B', 'C'), ('B', 'D'), ('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 list

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

with_labels=True always return a dict

>>> G.degree('A',with_labels=True)
{'A': 2}

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

>>> sorted(G.in_degree())
[0, 0, 0, 0, 1, 2, 2]
>>> G.in_degree(with_labels=True)
{'A': 0, 'C': 2, 'B': 1, 'D': 2, 'G': 0, 'K': 0, 'J': 0}
>>> sorted(G.out_degree())
[0, 0, 0, 0, 1, 2, 2]
>>> G.out_degree(with_labels=True)
{'A': 2, 'C': 1, 'B': 2, 'D': 0, 'G': 0, 'K': 0, 'J': 0}
>>> sorted(G.degree())
[0, 0, 0, 2, 2, 3, 3]
>>> sorted(list(G.in_degree_iter()))
[0, 0, 0, 0, 1, 2, 2]
>>> dict(G.in_degree_iter(with_labels=True))
{'A': 0, 'C': 2, 'B': 1, 'D': 2, 'G': 0, 'K': 0, 'J': 0}
>>> sorted(list(G.out_degree_iter()))
[0, 0, 0, 0, 1, 2, 2]
>>> dict(G.out_degree_iter(with_labels=True))
{'A': 2, 'C': 1, 'B': 2, 'D': 0, 'G': 0, 'K': 0, 'J': 0}
>>> sorted(list(G.degree_iter()))
[0, 0, 0, 2, 2, 3, 3]

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

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

>>> G.order()
7
>>> G.size()
5


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

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

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

>>> UG=G.to_undirected()       # to_undirected
>>> UG==G
False
>>> UG.is_directed()
False
>>> G.is_directed()
True
>>> UG.dna==G.dna
True
>>> UG.name==G.name
True
>>> UG.adj==G.adj
False
>>> sorted(UG.edges(list('AB')))
[('A', 'B'), ('A', 'C'), ('B', 'C'), ('B', 'D')]
>>> sorted(UG.edges(['A','B']))
[('A', 'B'), ('A', 'C'), ('B', 'C'), ('B', 'D')]
>>> UG.delete_edge('A','B')
>>> UG.has_edge('B','A')
False
>>> UG.has_edge('A','B')
False

  # to_directed
  # to_weightedgraph
  # to_pseudograph


Neighbors, Predecessors and Successors
--------------------------------------

>>> sorted(G.neighbors('C'))
['A', 'B', 'D']
>>> sorted(G['C'])
['A', 'B', 'D']
>>> sorted(G.neighbors('A'))
['B', 'C']
>>> sorted(G.neighbors('A',with_labels=True))
['B', 'C']
>>> G.neighbors('A',with_labels=True).values()
[1, 1]
>>> sorted(G.neighbors_iter('A',with_labels=True))
[('B', 1), ('C', 1)]
>>> sorted(G.neighbors_iter('A'))
['B', 'C']
>>> sorted(G.neighbors_iter('C'))
['A', 'B', 'D']
>>> sorted(G.neighbors_iter('C',with_labels=True))
[('A', 1), ('B', 1), ('D', 1)]

>>> sorted(G.successors('A'))
['B', 'C']
>>> sorted(G.successors_iter('A'))
['B', 'C']
>>> G.successors('A',with_labels=True)
{'C': 1, 'B': 1}
>>> sorted(G.successors_iter('A',with_labels=True))
[('B', 1), ('C', 1)]
>>> sorted(G.predecessors('C'))
['A', 'B']
>>> sorted(G.predecessors_iter('C'))
['A', 'B']
>>> G.predecessors('C',with_labels=True)
{'A': 1, 'B': 1}
>>> sorted(G.predecessors_iter('C',with_labels=True))
[('A', 1), ('B', 1)]

>>> sorted(G.successors('G'))   # no edges
[]
>>> sorted(G.predecessors('G'))  
[]
>>> sorted(G.predecessors('A'))     # some edges but wrong direction
[]
>>> sorted(G.successors('D'))  
[]
>>> sorted(G.successors_iter('G'))   # no edges
[]
>>> sorted(G.predecessors_iter('G'))  
[]
>>> sorted(G.predecessors_iter('A'))     # some edges but wrong direction
[]
>>> sorted(G.successors_iter('D'))  
[]
>>> sorted(G.neighbors('j'))  
Traceback (most recent call last):
...
NetworkXError: node j not in graph
>>> sorted(G.predecessors('j'))  
Traceback (most recent call last):
...
NetworkXError: node j not in graph
>>> sorted(G.successors('j'))  
Traceback (most recent call last):
...
NetworkXError: node j not in graph
>>> sorted(G.neighbors_iter('j'))  
Traceback (most recent call last):
...
NetworkXError: node j not in graph
>>> sorted(G.predecessors_iter('j'))  
Traceback (most recent call last):
...
NetworkXError: node j not in graph
>>> sorted(G.successors_iter('j'))  
Traceback (most recent call last):
...
NetworkXError: node j not in graph


Functional interface
--------------------

>>> sorted(nodes(G))
['A', 'B', 'C', 'D', 'G', 'J', 'K']
>>> sorted(nodes_iter(G))
['A', 'B', 'C', 'D', 'G', 'J', 'K']
>>> sorted(edges(G))
[('A', 'B'), ('A', 'C'), ('B', 'C'), ('B', 'D'), ('C', 'D')]
>>> sorted(edges_iter(G))
[('A', 'B'), ('A', 'C'), ('B', 'C'), ('B', 'D'), ('C', 'D')]
>>> sorted(degree(G))
[0, 0, 0, 2, 2, 3, 3]
>>> sorted(neighbors(G,'A'))
['B', 'C']
>>> neighbors(G,'A',with_labels=True)
{'C': 1, 'B': 1}
>>> number_of_nodes(G)
7
>>> number_of_edges(G)
5
>>> density(G)==5/(7*(7-1)*0.5)
True
>>> degree_histogram(G)
[3, 0, 2, 2]


Iterators
---------

>>> sorted(G.nodes_iter())
['A', 'B', 'C', 'D', 'G', 'J', 'K']
>>> sorted(G.edges_iter())
[('A', 'B'), ('A', 'C'), ('B', 'C'), ('B', 'D'), ('C', 'D')]
>>> sorted(G.degree_iter())
[0, 0, 0, 2, 2, 3, 3]
>>> sorted(G.degree_iter(with_labels=True))
[('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: node X not in 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
>>> J5=K5.copy()
>>> H=J5.subgraph(1,inplace=True)
>>> is_isomorphic(H,K1)
True
>>> is_isomorphic(J5,K1)
True

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

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

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

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

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

>>> H=K5.subgraph(K3)
>>> is_isomorphic(H,K3)
True
>>> J5=K5.copy()
>>> H=J5.subgraph(K3,inplace=True)
>>> is_isomorphic(H,K3)
True
>>> is_isomorphic(J5,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 reverse

>>> G=complete_graph(10)
>>> H=G.to_directed()
>>> HR=H.reverse()
>>> is_isomorphic(H,HR)
True
>>> sorted(H.edges())==sorted(HR.edges())
True

>>> H=DiGraph()
>>> foo=[H.add_edge(u,u+1) for u in range(0,5)]
>>> HR=H.reverse()
>>> [HR.has_edge(u+1,u) for u in range(0,5)]
[True, True, True, True, True]

>>> H=DiGraph()
>>> H.add_nodes_from([1,2,3,4])
>>> HR=H.reverse()
>>> sorted(HR.nodes())
[1, 2, 3, 4]

