# Graph_Functions.py
# A program of basic functions for directed and undirected graphs.
# There are two graph formats used in this program:

# (1) Adjacency Matrix: A = NxN matrix with a 1 in position (A)ij if there is a connection from node i to node j, and 0 otherwise.
# (2) List form: L is a list of the N nodes. The kth entry in the list corresponds to the kth node and is itself a list of connections
#                from node k to other nodes.

# Note: The formats themselves are the same for directed and undirected graphs. If the graph is undirected the matrix must be symmetric
# as must the Node List. However, the functions used to manipulate these graphs will not check for symmetry. If you want to use an undirected graph
# algorithm, you must make sure your input graph G is undirected otherwise errors may arise.All directed graph algorithms should work on undirected graphs.

# There are 5 functions:

#(1) Convert_List_to_Adjacency_Matrix(L)
#(2) Convert_Adjacency_Matrix_to_List(A)
#(3) Find_Connected_Components(L)
#(4) Find_Strongly_Connected_Components(L)
#(5) Construct_Component_Graph(L)

#(1) Define Convert_List_to_Adjacency_Matrix(L)
# Input: a graph L (directed or undirected) in list format. Output: a copy of the graph L, in adjacency matrix format. We call the copy A.
def Convert_List_to_Adjacency_Matrix(L):
    N = len(L)
    A = zeros((N,N))
    for i in xrange(N):
        for j in xrange(N):

            connections = set(L[i])
            if j in connections: A[i][j] = 1

    print 'A =', A
    return A


#(2) Define Convert_Adjacency_Matrix_to_List(A)
# Input: a graph A (directed or undirected) in adjacency matrix format. Output: a copy of the graph A, in list format. We call the copy L. 
def Convert_Adjacency_Matrix_to_List(A):
    N = len(A)
    L = []
    
    for i in xrange(N):
        connections = []
        
        for j in xrange(N):
            if A[i][j] == 1: connections.append(j)

        L.append(connections)
        

    print 'L =', L
    return L
            
#(3) Define Find_Connected_Components(L)
# Input: a undirected graph L in list format. Output: a list of the connected components of L.
# i.e. connected_components = [ [1,2,4], [0,3], [5,6,7] ] means there are 3 connected components nodes 1,2,4, nodes 0,3, and nodes 5,6,7
def Find_Connected_Components(L):
    connected_components = []
    N = len(L)
    remaining_states = set(range(N))
    next_base_state = 0
    go = 1
    
    while go == 1:
        new_component = [next_base_state]
        continue_component = 1

        while continue_component == 1:
            new_component_prime = new_component
            
            for x in new_component:
                new_component_prime = new_component_prime + L[x]

            if set(new_component) == set(new_component_prime):
                continue_component = 0
                connected_components.append(set(list(new_component)))
                remaining_states = remaining_states - set(new_component)
                if remaining_states == set([]): go = 0
                if remaining_states != set([]): next_base_state = min(remaining_states)

            if set(new_component) != set(new_component_prime):
                new_component = list(set(new_component_prime))

    print 'connected_components =', connected_components
    return connected_components
                

#(4) Define Find_Strongly_Connected_Components(L):
# Input: a directed graph L in list format. Output: a list of the strongly connected components of L.
# i.e. strongly_connected_components = [ [1,2,4], [0,3], [5,6,7] ] means there are 3 strongly connected components nodes 1,2,4, nodes 0,3, and nodes 5,6,7
def Find_Strongly_Connected_Components(L):
    strongly_connected_components = []
    n_components = []
    N = len(L)

    # find the states accesible from each node n called n_component
    for n in xrange(N):
        n_component = [n]
        continue_component = 1
    
        while continue_component == 1:
            n_component_prime = n_component
            
            for x in n_component:
                n_component_prime = n_component_prime + L[x]

            if set(n_component) == set(n_component_prime):
                continue_component = 0

            if set(n_component) != set(n_component_prime):
                n_component = list(set(n_component_prime))
                
        n_components.append(n_component)
        
        #print 'n_components = ....'
        #for x in n_components: print x
        #print ''

    # determine which groups of nodes are strongly connected based on this list n_components
    remaining_states = set(range(N))
    next_base_state = 0
    go = 1

    while go == 1:
        new_component = [next_base_state]
        for x in n_components[next_base_state]:
            if next_base_state in n_components[x]: new_component.append(x)
        new_component = list(set(new_component))
        strongly_connected_components.append(new_component)
        remaining_states = remaining_states - set(new_component)
        if remaining_states == set([]): go = 0
        if remaining_states != set([]): next_base_state = min(remaining_states)

    print 'strongly_connected_components =', strongly_connected_components
    return strongly_connected_components
        

#(5) Define Construct_Component_Graph(L)
# Input: A directed graph L in list form. Output a new directed graph LL (in list form) whose vertices are the strongly connected components of L. There is an edge from vertex i to vertex j
# of the component graph LL if there is an edge from any node of L in i to and node of L in j. The oupput graph is given two LL explicitly list states as groups of nodes of
# the original graph L. LL_fixed renames these states with single numbers.


def Construct_Component_Graph(L):
    N = len(L)
    strongly_connected_components = Find_Strongly_Connected_Components(L)
    
    # build table of components by node (i.e a list of the component each node is in, list has N entries 1 for each node)
    component_list = []
    for n in xrange(N):
        go = 1
        test_component = 0
        
        while go == 1:
            if n in strongly_connected_components[test_component]:
                component_list.append(strongly_connected_components[test_component])
                go = 0
            if n not in strongly_connected_components[test_component]: test_component = test_component + 1

    print 'component list =',
    for x in component_list:print x

    # build the component graph using explicit lists of L nodes for nodes of LL        
    LL = []

    for component in strongly_connected_components:
        connections = [component]

        for x in component:
            for y in L[x]:
                if component_list[y] not in connections: connections.append(component_list[y])

        LL.append([component,connections])

    # rename the the nodes of the component graph with single numbers
    LL_fixed = []
    NN = len(strongly_connected_components)
        
    for n in xrange(NN):
        connections = []
        
        for link in LL[n][1]:
            for y in xrange(NN):
                if strongly_connected_components[y] == link: connections.append(y)

        LL_fixed.append(connections)

    return LL, LL_fixed
                

    



# Begin Main Program to Test Functions
from numpy import *

#(1) Test Convert_List_to_Adjacncy_Matrix(L)
#L = [ [0,1,5], [2,3], [], [1,4], [1], [0] ]
#L = [ [1,2], [2,3], [4], [1,4], [1] ]
#A = Convert_List_to_Adjacency_Matrix(L)
#print 'A =', A

#(2) Test Convert_List_to_Adjacency_Matrix
#A = array([ [0,1,0,0,1], [0,1,0,1,0], [0,1,1,0,1], [0,1,1,1,1], [0,1,0,0,1] ])
#A = array([ [0,1,0,1], [1,1,1,0], [0,0,0,0], [0,1,0,1] ])
#L = Convert_Adjacency_Matrix_to_List(A)
#print 'L =', L

#(3) Test Find_Connected_Components(L)
#L = [ [0,2], [2], [0,1], [3,4], [3], [6,7], [5,6,7], [5,6] ]
#L = [ [0], [3], [2,5], [1,4], [3], [2] ]
#L = [ [6], [1,2], [1,2], [4,5], [3], [3], [0] ]
#L = [[0,1,2], [0,2,3], [0,1,3], [1,2,3] ]
#connected_components = Find_Connected_Components(L)
#print 'connected_components =', connected_components


#(4) Test Find_Strongly_Connected_Components(L)
#L = [ [1], [2,3], [1,2], [4], [5], [6], [4] ]
#L = [ [1], [2], [3,4], [2,0], [5], [6], [5], [8], [9], [7] ]
#L = [ [2], [3], [8], [1,0], [7], [4], [5], [], [1] ]
#L = [ [2,3], [1,2], [0,1], [0,4], [4,6], [6,7], [5], [3] ] 
#strongly_connected_components = Find_Strongly_Connected_Components(L)
#print 'strongly_connected_components =', strongly_connected_components

#(5) Test Construct_Component_Graph(L):
#L = [ [1,2], [0], [3,4], [2], [5,7], [6], [5], [8], [], [7,8] ]
#L = [ [1,2], [2,3], [0], [4,5], [10,12], [6], [5,7,8], [], [9], [8], [11], [10], [] ]
#L = [ [1,2], [0], [3], [2,4], [5], [4,6], [7], [8,1], [6] ]
#L = [ [1], [2,3], [1], [4,5], [6,7,8], [9,10], [], [], [], [], [] ]

#LL, LL_fixed = Construct_Component_Graph(L)
#print 'LL ='
#for x in LL: print x
#print ''
#print 'LL_fixed ='
#for x in LL_fixed: print x




