# --------------
# Code starts here

import numpy as np

# Code starts here

# Adjacency matrix
adj_mat = np.array([[0,0,0,0,0,0,1/3,0],
                   [1/2,0,1/2,1/3,0,0,0,0],
                   [1/2,0,0,0,0,0,0,0],
                   [0,1,0,0,0,0,0,0],
                  [0,0,1/2,1/3,0,0,1/3,0],
                   [0,0,0,1/3,1/3,0,0,1/2],
                   [0,0,0,0,1/3,0,0,1/2],
                   [0,0,0,0,1/3,1,1/3,0]])

# Compute eigenvalues and eigencevectrs
eigenvalues, eigenvectors=np.linalg.eig(adj_mat)

# Eigen vector corresponding to 1
abs(eigenvectors[:,0])
eigen_1 = abs(eigenvectors[:,0])/np.linalg.norm(eigenvectors[:,0],1)

# most important page
page =  np.where(np.max(eigen_1) == eigen_1)[0][0] + 1

# Code ends here
print(page)

# --------------
# Code starts here

# Initialize stationary vector I
init_I = np.array([1,0,0,0,0,0,0,0])

# Perform iterations for power method
for _ in range(10):
    init_I = np.dot(adj_mat,init_I)
    init_I = init_I/np.linalg.norm(init_I,1)

print(init_I)
power_page = np.where(np.max(init_I) == init_I)[0][0]+1
print(power_page)
# Code ends here

# --------------
new_adj_mat = np.array([[0,0,0,0,0,0,0,0],
                   [1/2,0,1/2,1/3,0,0,0,0],
                  [1/2,0,0,0,0,0,0,0],
                   [0,1,0,0,0,0,0,0],
                   [0,0,1/2,1/3,0,0,1/2,0],
                   [0,0,0,1/3,1/3,0,0,1/2],
                   [0,0,0,0,1/3,0,0,1/2],
                   [0,0,0,0,1/3,1,1/2,0]])

new_init_I = np.array([1,0,0,0,0,0,0,0])
for _ in range(10):
    new_init_I  = np.dot(new_adj_mat, new_init_I)
    new_init_I /= np.linalg.norm(new_init_I,1)

print(new_init_I)

# --------------
# Alpha value
alpha = 0.85

# Code starts here

new_adj_mat = np.array([[0,0,0,0,0,0,0,0],
                   [1/2,0,1/2,1/3,0,0,0,0],
                  [1/2,0,0,0,0,0,0,0],
                   [0,1,0,0,0,0,0,0],
                   [0,0,1/2,1/3,0,0,1/2,0],
                   [0,0,0,1/3,1/3,0,0,1/2],
                   [0,0,0,0,1/3,0,0,1/2],
                   [0,0,0,0,1/3,1,1/2,0]])

# Modified adjancency matrix
G = (alpha*new_adj_mat) + (1-alpha)*(1 / len(new_adj_mat))*np.ones(new_adj_mat.shape)

# Initialize stationary vector I
final_init_I = np.array([1,0,0,0,0,0,0,0])

# Perform iterations for power method
for _ in range(1000):
    final_init_I = np.dot(new_adj_mat, final_init_I)
    final_init_I /= np.linalg.norm(final_init_I, 1)

print(final_init_I)
# Code ends here


#Import stuff
import numpy as np
import matplotlib.pyplot as pp

a = 0.5 #A parameter defined in the problem statement

def initial_guess(x): #A function defining our initial guess
    return 1 + 2*x

def phi(x):  #A function defined in the problem statement
    return 20*np.pi*x*x*x

def phiprime(x):  #The derivative of the above function
    return 60*np.pi*x*x

def phidoubleprime(x):  #The derivative of the derivative of the two-above function
    return 120*np.pi*x

def f(x):  #u''(x) = f(x), which is this f
    return -20. + a*phidoubleprime(x)*np.cos(phi(x)) - a*(phiprime(x))**2 *np.sin(phi(x))

def true_solution(x):  #The analytic solution
    return 1 + 12*x - 10*x*x + a*np.sin(phi(x))

n_grid = 255 #Number of interior grid points
h = 1./float(n_grid+1) #size of grid spacing
u_0 = 1. #Left boundary condition
u_1 = 3. #Right boundary condition
epsilon = 1e-3  #The error tolerance I want to be within the true value before saying I've converged
number_of_iterations_per_level = 100 #Number of iterations per multigrid level

#In what follows, we are trying to solve the matrix equation A u = b

A_diagonal = -2./(h*h)  #The diagonol elements of my A matrix
A_offdiagonal = 1./(h*h) #The off-diagonol elements of my tridiagonol A matrix

u = np.zeros(n_grid) #the solution vector

x_binedges = [] #This list will store the x-values of the edges of the cells
for i in range(n_grid):
    x_binedges.append(float(i+1)/float(n_grid+1))

#initialize the solution vector:
for i in range(n_grid):
    u[i] = initial_guess(x_binedges[i])

b = np.zeros(n_grid)  #The RHS vector

for i in range(n_grid): #Initialize this
    b[i] = f(x_binedges[i])

b[0] = b[0] - u_0/(h*h) #Add LHS boundary condition to b
b[n_grid-1] = b[n_grid-1] - u_1/(h*h) #Add RHS boundary condition to b

#Next two lines is to plot the true solution
x_toplot = np.linspace(0,1,200)
pp.plot(x_toplot,true_solution(x_toplot),label="True Solution",lw=4)

#Begin the iteration
for k255 in range(number_of_iterations_per_level):
  for i in range(len(u)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * u[i+1]
    elif i== (len(u)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * u[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * u[i+1] + A_offdiagonal * u[i-1]
    u[i] = 1./A_diagonal * (b[i] - summation) #Update u based on Jacobi algorithm

#Calculate the residual
A = np.zeros( (n_grid,n_grid) )
for i in range(len(A)):
    for j in range(len(A[i])):
        if i == j:
            A[i][j] = A_diagonal
        elif j == (i-1) or j == (i+1):
            A[i][j] = A_offdiagonal
tau255 = np.add(b, -1.*np.dot(A,u) )

#############################################
#Now to iterate on the residual in the multigrid fashion
#First restrict to 2 times smaller than original grid
tau127 = np.zeros( (n_grid+1)/2 -1)  #The coarser tau
for i in range(len(tau127)):
    tau127[i] = tau255[i*2 + 1]  #Copy values at corresponding cell edges to coarsen

#b127 = np.zeros( (ngrid+1)/2)
#for i in range(len(b127)):
#    b127[i] = b[i*2]
A_diagonal = A_diagonal/4.
A_offdiagonal = A_offdiagonal/4.

error127 = np.zeros( (n_grid+1)/2 -1) #the error vector, set initally to zero

for k127 in range(number_of_iterations_per_level): #Solve A * error127 = tau127
  for i in range(len(error127)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * error127[i+1]
    elif i== (len(error127)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * error127[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * error127[i+1] + A_offdiagonal * error127[i-1]
    error127[i] = 1./A_diagonal * (tau127[i] - summation) #Update error127 based on Jacobi algorithm

A127 = np.zeros( ((n_grid+1)/2-1,(n_grid+1)/2-1) ) #Make a matrix A for the tau prime calculation
for i in range(len(A127)):
    for j in range(len(A127[i])):
        if i == j:
            A127[i][j] = A_diagonal
        elif j == (i-1) or j == (i+1):
            A127[i][j] = A_offdiagonal

tau127_prime = np.add(tau127, -1.*np.dot(A127,error127)) #Calculate a tau prime to communicate to
                                                           #Next restriction

#############################################
#Now restrict to 4 times smaller than original grid
tau63 = np.zeros( (n_grid+1)/4 -1)  #The coarser tau
for i in range(len(tau63)):
    tau63[i] = tau127_prime[i*2 + 1]  #Copy values at corresponding cell edges to coarsen

A_diagonal = A_diagonal/4.
A_offdiagonal = A_offdiagonal/4.

error63 = np.zeros( (n_grid+1)/4 -1) #the error vector, set initally to zero

for k63 in range(number_of_iterations_per_level): #Solve A * error63 = tau63
  for i in range(len(error63)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * error63[i+1]
    elif i== (len(error63)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * error63[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * error63[i+1] + A_offdiagonal * error63[i-1]
    error63[i] = 1./A_diagonal * (tau63[i] - summation) #Update error63 based on Jacobi algorithm

A63 = np.zeros( ((n_grid+1)/4 -1,(n_grid+1)/4 -1) ) #Make a matrix A for the tau prime calculation
for i in range(len(A63)):
    for j in range(len(A63[i])):
        if i == j:
            A63[i][j] = A_diagonal
        elif j == (i-1) or j == (i+1):
            A63[i][j] = A_offdiagonal

tau63_prime = np.add(tau63, -1.*np.dot(A63,error63)) #Calculate a tau prime to communicate to
                                                           #Next restriction

#############################################
#Now restrict to 8 times smaller than original grid
tau31 = np.zeros( (n_grid+1)/8 -1)  #The coarser tau
for i in range(len(tau31)):
    tau31[i] = tau63_prime[i*2 + 1]  #Copy values at corresponding cell edges to coarsen

A_diagonal = A_diagonal/4.
A_offdiagonal = A_offdiagonal/4.

error31 = np.zeros( (n_grid+1)/8 -1) #the error vector, set initally to zero

for k31 in range(number_of_iterations_per_level): #Solve A * error31 = tau31
  for i in range(len(error31)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * error31[i+1]
    elif i== (len(error31)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * error31[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * error31[i+1] + A_offdiagonal * error31[i-1]
    error31[i] = 1./A_diagonal * (tau31[i] - summation) #Update error31 based on Jacobi algorithm

A31 = np.zeros( ((n_grid+1)/8 -1,(n_grid+1)/8 -1) ) #Make a matrix A for the tau prime calculation
for i in range(len(A31)):
    for j in range(len(A31[i])):
        if i == j:
            A31[i][j] = A_diagonal
        elif j == (i-1) or j == (i+1):
            A31[i][j] = A_offdiagonal

tau31_prime = np.add(tau31, -1.*np.dot(A31,error31)) #Calculate a tau prime to communicate to
                                                           #Next restriction

#############################################
#Now restrict to 16 times smaller than original grid
tau15 = np.zeros( (n_grid+1)/16 -1)  #The coarser tau
for i in range(len(tau15)):
    tau15[i] = tau31_prime[i*2 + 1]  #Copy values at corresponding cell edges to coarsen

A_diagonal = A_diagonal/4.
A_offdiagonal = A_offdiagonal/4.

error15 = np.zeros( (n_grid+1)/16 -1) #the error vector, set initally to zero

for k15 in range(number_of_iterations_per_level): #Solve A * error15 = tau15
  for i in range(len(error15)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * error15[i+1]
    elif i== (len(error15)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * error15[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * error15[i+1] + A_offdiagonal * error15[i-1]
    error15[i] = 1./A_diagonal * (tau15[i] - summation) #Update error15 based on Jacobi algorithm

A15 = np.zeros( ((n_grid+1)/16 -1,(n_grid+1)/16 -1) ) #Make a matrix A for the tau prime calculation
for i in range(len(A15)):
    for j in range(len(A15[i])):
        if i == j:
            A15[i][j] = A_diagonal
        elif j == (i-1) or j == (i+1):
            A15[i][j] = A_offdiagonal

tau15_prime = np.add(tau15, -1.*np.dot(A15,error15)) #Calculate a tau prime to communicate to
                                                           #Next restriction

#############################################
#Now restrict to 32 times smaller than original grid
tau7 = np.zeros( (n_grid+1)/32 -1)  #The coarser tau
for i in range(len(tau7)):
    tau7[i] = tau15_prime[i*2 + 1]  #Copy values at corresponding cell edges to coarsen

A_diagonal = A_diagonal/4.
A_offdiagonal = A_offdiagonal/4.

error7 = np.zeros( (n_grid+1)/32 -1) #the error vector, set initally to zero

for k7 in range(number_of_iterations_per_level): #Solve A * error7 = tau7
  for i in range(len(error7)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * error7[i+1]
    elif i== (len(error7)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * error7[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * error7[i+1] + A_offdiagonal * error7[i-1]
    error7[i] = 1./A_diagonal * (tau7[i] - summation) #Update error7 based on Jacobi algorithm

A7 = np.zeros( ((n_grid+1)/32 -1,(n_grid+1)/32 -1) ) #Make a matrix A for the tau prime calculation
for i in range(len(A7)):
    for j in range(len(A7[i])):
        if i == j:
            A7[i][j] = A_diagonal
        elif j == (i-1) or j == (i+1):
            A7[i][j] = A_offdiagonal

tau7_prime = np.add(tau7, -1.*np.dot(A7,error7)) #Calculate a tau prime to communicate to
                                                           #Next restriction

#############################################
#Now restrict to 64 times smaller than original grid
tau3 = np.zeros( (n_grid+1)/64 -1)  #The coarser tau
for i in range(len(tau3)):
    tau3[i] = tau7_prime[i*2 + 1]  #Copy values at corresponding cell edges to coarsen

A_diagonal = A_diagonal/4.
A_offdiagonal = A_offdiagonal/4.

error3 = np.zeros( (n_grid+1)/64 -1) #the error vector, set initally to zero

for k3 in range(number_of_iterations_per_level): #Solve A * error3 = tau3
  for i in range(len(error3)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * error3[i+1]
    elif i== (len(error3)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * error3[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * error3[i+1] + A_offdiagonal * error3[i-1]
    error3[i] = 1./A_diagonal * (tau3[i] - summation) #Update error3 based on Jacobi algorithm

A3 = np.zeros( ((n_grid+1)/64 -1,(n_grid+1)/64 -1) ) #Make a matrix A for the tau prime calculation
for i in range(len(A3)):
    for j in range(len(A3[i])):
        if i == j:
            A3[i][j] = A_diagonal
        elif j == (i-1) or j == (i+1):
            A3[i][j] = A_offdiagonal

tau3_prime = np.add(tau3, -1.*np.dot(A3,error3)) #Calculate a tau prime to communicate to
                                                           #Next restriction

#####################################################################################
#####################################################################################
#####################################################################################

#### Now work our way back "up" the V

####################################
###Moving from 3 grids to 7 grids

error3_prolongated = np.zeros( (n_grid+1)/32 -1)  #Create the prolongated error vector for moving to finer grid
for i in range(len(error3_prolongated)):  #Do simple interpolation to prolongate the error vector
    if i == 0:
        error3_prolongated[0] = .5*(u_0 + error3[0])
    elif i == (len(error3_prolongated) -1):
        error3_prolongated[-1] = .5*(u_1 + error3[-1])
    elif i%2 == 1:
        error3_prolongated[i] = error3[(i-1)/2]
    else:
        error3_prolongated[i] = .5*(error3[i/2] + error3[i/2-1])

A_diagonal = A_diagonal*4.
A_offdiagonal = A_offdiagonal*4.

error7 = np.add(error7 , error3_prolongated)  #Update error7 based on error3_prolongated vector

for k7 in range(number_of_iterations_per_level): #Solve A * error7 = tau7
  for i in range(len(error7)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * error7[i+1]
    elif i== (len(error7)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * error7[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * error7[i+1] + A_offdiagonal * error7[i-1]
    error7[i] = 1./A_diagonal * (tau7[i] - summation) #Update error7 based on Jacobi algorithm

####################################
###Moving from 7 grids to 15 grids

error7_prolongated = np.zeros( (n_grid+1)/16 -1)  #Create the prolongated error vector for moving to finer grid
for i in range(len(error7_prolongated)):  #Do simple interpolation to prolongate the error vector
    if i == 0:
        error7_prolongated[0] = .5*(u_0 + error7[0])
    elif i == (len(error7_prolongated) -1):
        error7_prolongated[-1] = .5*(u_1 + error7[-1])
    elif i%2 == 1:
        error7_prolongated[i] = error7[(i-1)/2]
    else:
        error7_prolongated[i] = .5*(error7[i/2] + error7[i/2-1])

A_diagonal = A_diagonal*4.
A_offdiagonal = A_offdiagonal*4.

error15 = np.add(error15 , error7_prolongated)  #Update error15 based on error7_prolongated vector

for k15 in range(number_of_iterations_per_level): #Solve A * error15 = tau15
  for i in range(len(error15)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * error15[i+1]
    elif i== (len(error15)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * error15[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * error15[i+1] + A_offdiagonal * error15[i-1]
    error15[i] = 1./A_diagonal * (tau15[i] - summation) #Update error15 based on Jacobi algorithm

####################################
###Moving from 15 grids to 31 grids

error15_prolongated = np.zeros( (n_grid+1)/8 -1)  #Create the prolongated error vector for moving to finer grid
for i in range(len(error15_prolongated)):  #Do simple interpolation to prolongate the error vector
    if i == 0:
        error15_prolongated[0] = .5*(u_0 + error15[0])
    elif i == (len(error15_prolongated) -1):
        error15_prolongated[-1] = .5*(u_1 + error15[-1])
    elif i%2 == 1:
        error15_prolongated[i] = error15[(i-1)/2]
    else:
        error15_prolongated[i] = .5*(error15[i/2] + error15[i/2-1])

A_diagonal = A_diagonal*4.
A_offdiagonal = A_offdiagonal*4.

error31 = np.add(error31 , error15_prolongated)  #Update error31 based on error15_prolongated vector

for k31 in range(number_of_iterations_per_level): #Solve A * error31 = tau31
  for i in range(len(error31)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * error31[i+1]
    elif i== (len(error31)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * error31[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * error31[i+1] + A_offdiagonal * error31[i-1]
    error31[i] = 1./A_diagonal * (tau31[i] - summation) #Update error31 based on Jacobi algorithm

####################################
###Moving from 31 grids to 63 grids

error31_prolongated = np.zeros( (n_grid+1)/4 -1)  #Create the prolongated error vector for moving to finer grid
for i in range(len(error31_prolongated)):  #Do simple interpolation to prolongate the error vector
    if i == 0:
        error31_prolongated[0] = .5*(u_0 + error31[0])
    elif i == (len(error31_prolongated) -1):
        error31_prolongated[-1] = .5*(u_1 + error31[-1])
    elif i%2 == 1:
        error31_prolongated[i] = error31[(i-1)/2]
    else:
        error31_prolongated[i] = .5*(error31[i/2] + error31[i/2-1])

A_diagonal = A_diagonal*4.
A_offdiagonal = A_offdiagonal*4.

error63 = np.add(error63 , error31_prolongated)  #Update error63 based on error31_prolongated vector

for k63 in range(number_of_iterations_per_level): #Solve A * error63 = tau63
  for i in range(len(error63)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * error63[i+1]
    elif i== (len(error63)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * error63[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * error63[i+1] + A_offdiagonal * error63[i-1]
    error63[i] = 1./A_diagonal * (tau63[i] - summation) #Update error63 based on Jacobi algorithm

####################################
###Moving from 63 grids to 127 grids

error63_prolongated = np.zeros( (n_grid+1)/2 -1)  #Create the prolongated error vector for moving to finer grid
for i in range(len(error63_prolongated)):  #Do simple interpolation to prolongate the error vector
    if i == 0:
        error63_prolongated[0] = .5*(u_0 + error63[0])
    elif i == (len(error63_prolongated) -1):
        error63_prolongated[-1] = .5*(u_1 + error63[-1])
    elif i%2 == 1:
        error63_prolongated[i] = error63[(i-1)/2]
    else:
        error63_prolongated[i] = .5*(error63[i/2] + error63[i/2-1])

A_diagonal = A_diagonal*4.
A_offdiagonal = A_offdiagonal*4.

error127 = np.add(error127 , error63_prolongated)  #Update error127 based on error63_prolongated vector

for k127 in range(number_of_iterations_per_level): #Solve A * error127 = tau127
  for i in range(len(error127)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * error127[i+1]
    elif i== (len(error127)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * error127[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * error127[i+1] + A_offdiagonal * error127[i-1]
    error127[i] = 1./A_diagonal * (tau127[i] - summation) #Update error127 based on Jacobi algorithm

####################################
###Moving from 127 grids to 255 grids

error127_prolongated = np.zeros( (n_grid+1) -1)  #Create the prolongated error vector for moving to finer grid
for i in range(len(error127_prolongated)):  #Do simple interpolation to prolongate the error vector
    if i == 0:
        error127_prolongated[0] = .5*(u_0 + error127[0])
    elif i == (len(error127_prolongated) -1):
        error127_prolongated[-1] = .5*(u_1 + error127[-1])
    elif i%2 == 1:
        error127_prolongated[i] = error127[(i-1)/2]
    else:
        error127_prolongated[i] = .5*(error127[i/2] + error127[i/2-1])

A_diagonal = A_diagonal*4.
A_offdiagonal = A_offdiagonal*4.

print A_diagonal*h*h
print A_offdiagonal*h*h

####Whew! Enough already!  Now for the final solution

u = np.add(u, error127_prolongated)  #Update u based on error255_prolongated vector

for k in range(number_of_iterations_per_level): #Solve A * u = b
  for i in range(len(u)):
    if i==0:  #If we're on the first row, only add A[i][i+1]
        summation = A_offdiagonal * u[i+1]
    elif i== (len(u)-1): #If we're on the last row, only add A[i][i-1]
        summation = A_offdiagonal * u[i-1]
    else:  #Only need to add two elements together
        summation = A_offdiagonal * u[i+1] + A_offdiagonal * u[i-1]
    u[i] = 1./A_diagonal * (b[i] - summation) #Update u based on Jacobi algorithm

#Plot the final solution and save the figure
pp.plot(x_binedges,u,label='final iteration', lw=2.5)
pp.xlabel("x")
pp.ylabel("u(x)")
pp.legend(loc='best')
pp.xlim(0,1)
#pp.ylim(0,5.5)
pp.savefig('problem1b.pdf')


import numpy as np
import matplotlib.pylab as plt

def step(x):
    # y = x > 0
    # return y.astype(np.int)
    return np.array(x > 0, dtype=np.int)

def sigmoid(x):
    return 1/(1+np.exp(-x))

def relu(x):
    return np.maximum(x,0)

if __name__ == '__main__':
    x = np.arange(-5.0, 5.0, 0.1)
    # y = step(x) #階層函式
    # y = sigmoid(x) #sigmoid
    y = relu(x) #ReLU
    plt.plot(x, y)
    plt.ylim(-0.1, 1.1)
    plt.show()


# -*- coding: utf-8 -*-
"""
Author: Paschalis Giakoumoglou
Date: Fri Jun 18 00:42:40 2021
"""
import numpy as np
import math

def euler(f, x0, t0, t1, dt):
    print("============================== Euler's Method ============================== ")
    print()
    t = np.arange(t0, t1 + dt, dt)
    x = np.zeros((len(t), len(x0)), np.float64)
    x[0, :] = x0
    for n in range(len(t) - 1):
        x[n + 1, :] = x[n, :] + dt * f(t[n], x[n, :])
        print(f"x[{n+1}] = x[{n}] + dt*f(t[{n}], x[{n}]) = {x[n, :]} + {dt}*{f(t[n], x[n, :])} = {x[n, :]} + {dt}*{f(t[n], x[n, :])} = {x[n + 1, :]}")
    print()
    print('Summing up:')
    for n in range(len(t)):
        print(f"t[{n}] = {t[n]} -> x[{n}] = {x[n,:]}")
    print()
    #return t, x

def improved_euler(f, x0, t0, t1, dt): #not for systems
    print("==============================  Improved Euler's Method ============================== ")
    print()
    t = np.arange(t0, t1 + dt, dt)
    x = np.zeros((len(t), len(x0)), np.float64)
    x[0, :] = x0
    for n in range(len(t) - 1):
        k0 = dt * f(t[n], x[n])
        k1 = dt * f(t[n+1], x[n]+k0)
        x[n+1, :] = x[n, :] + 1/2*(k0 + k1)
        print(f"k0 = dt * f(t[n], x[n]) = {dt} * {f(t[n], x[n])} = {k0}")
        print(f"k1 = dt * f(t[n+1], x[n]+k0) = {dt} * {f(t[n+1], x[n]+k0)} = {k1}")
        print(f"x[{n+1}] = x[{n}] + 1/2*(k0+k1) = {x[n, :]} + 1/2*({k0}+{k1}) = {x[n, :] + 1/2*(k0 + k1)}")
        print()
    print()
    print('Summing up:')
    for n in range(len(t)):
        print(f"t[{n}] = {t[n]} -> x[{n}] = {x[n,:]}")
    print()
    #return t, x

def mid_point(f, x0, t0, t1, dt):
    print("============================== Mid Point Method ==============================")
    print()
    t = np.arange(t0, t1 + dt, dt)
    x = np.zeros((len(t), len(x0)), np.float64)
    x[0, :] = x0
    for n in range(len(t) - 1):
        k0 = dt *f(t[n], x[n, :])
        k1 = f(t[n]+1/2*dt, x[n, :]+1/2*k0)
        x[n+1, :] = x[n, :] + dt*k1
        print(f"k0 = dt *f(t[n], x[n, :]) ={dt}*{f(t[n], x[n])} = {k0}")
        print(f"k1 = f(t[n]+1/2*dt, x[n, :]+1/2*k0) = {dt}*f({t[n]+1/2*dt},{x[n]+1/2*k0}) = {dt}*{f(t[n]+1/2*dt, x[n]+1/2*k0)} = {k1}")
        print(f"x[{n + 1}] = x[{n}] + {dt}*{k1} = {x[n + 1]} ")
        print()
    print()
    print('Summing up:')
    for n in range(len(t)):
        print(f"t[{n}] = {t[n]} -> x[{n}] = {x[n,:]}")
    print()
    #return t, x

def runge_kutta(f, x0, t0, t1, dt): #not for systems
    print("============================== Runge Kutta Method ==============================")
    print()
    t = np.arange(t0, t1 + dt, dt)
    x = np.zeros((len(t), len(x0)), np.float64)
    x[0, :] = x0
    for n in range(len(t) - 1):
        k0 = dt*f(t[n], x[n, :])
        k1 = dt*f(t[n]+1/2*dt, x[n, :]+1/2*k0)
        k2 = dt*f(t[n]+1/2*dt, x[n, :]+1/2*k1)
        k3 = dt*f(t[n+1], x[n, :]+k2)
        x[n+1, :] = x[n, :] + 1/6*(k0 + 2*k1 + 2*k2 + k3)
        print(f"k0 = {dt}*f({t[n]}, {x[n]}) = {dt}*{f(t[n], x[n])} = {dt*f(t[n], x[n])}")
        print(f"k1 = {dt}*f({t[n]+1/2*dt}, {x[n]+1/2*k0}) = {dt}*{f(t[n]+1/2*dt, x[n, :]+1/2*k0)} ={k1}")
        print(f"k2 = {dt}*f({t[n]+1/2*dt}, {x[n]+1/2*k1}) = {dt}*{f(t[n]+1/2*dt, x[n, :]+1/2*k1)} ={k2}")
        print(f"k3 = {dt}*f({t[n+1]}, {x[n]+k2}) = {dt}*{f(t[n+1], x[n, :]+k2)} ={k3}")
        print(f"x[{n + 1}] = x[{n}] + 1/6*(k0 + 2*k1 + 2*k2 + k3) = {x[n, :]} + 1/6*({k0} + 2*{k1} + 2*{k2} + {k3}) = {x[n+1, :]}")
        print()
    print()
    print('Summing up:')
    for n in range(len(t)):
        print(f"t[{n}] = {t[n]} -> x[{n}] = {x[n,:]}")
    print()
    #return t, x

def mult_steps(f, x0, t0, t1, dt):
    print("============================== Multi Step Method ==============================")
    print()
    x=5

if __name__ == "__main__":

    # Example 1
    def f(t, x):
        return -x+t+1

    x0 = np.array([1])
    t0 = 0
    t1 = 1
    dt = 0.1 # = (t0-t1)/N

    euler(f, x0, t0, t1, dt)
    improved_euler(f, x0, t0, t1, dt)
    mid_point(f, x0, t0, t1, dt)
    runge_kutta(f, x0, t0, t1, dt)

    # Example 2: System
    def F(t, x):
        return np.array([1.1 * x[0] - 0.4 * x[0] * x[1], 0.4 * x[0] * x[1] - 0.1 * x[1]])

    x0 = np.array([1,2])
    t0 = 0
    t1 = 1
    dt = 0.1 # = (t0-t1)/N

    euler(F, x0, t0, t1, dt)
    improved_euler(F, x0, t0, t1, dt)
    mid_point(F, x0, t0, t1, dt)
    runge_kutta(F, x0, t0, t1, dt)

    #Example 2018
    def G(t, x):
        return np.array([-3*x[0]+2*x[1], 3*x[0]-4*x[1]])

    x0 = np.array([0, 0.5])
    t0 = 0
    t1 = 0.4
    dt = 0.2
    runge_kutta(G, x0, t0, t1, dt)

    #Example 2019
    def f(t, x):
        return t**2-math.exp(x)*math.sin(t)

    x0 = np.array([1])
    t0 = 0
    t1 = 0.5
    dt = 0.25
    runge_kutta(f, x0, t0, t1, dt)

    #Example 2019
    def f(t, x):
        return t**3 - 4*x
    x0 = np.array([1])
    t0  = 0
    t1 = 0.4
    dt = 0.2
    improved_euler(f, x0, t0, t1, dt)
    mid_point(f, x0, t0, t1, dt)


import time
import random

from numba import jit
from contextlib import contextmanager

@jit(nopython=True)
def monte_carlo_pi(nsamples):
    acc = 0
    for i in range(nsamples):
        x = random.random()
        y = random.random()
        if (x ** 2 + y ** 2) < 1.0:
            acc += 1
    return 4.0 * acc / nsamples

@contextmanager
def timing(description: str) -> None:
    start = time.perf_counter()
    yield
    end = time.perf_counter()

    print(f"{description}: {end-start:.6f}")

if __name__=="__main__":

    with timing("First iteration "):
        pi = monte_carlo_pi(1_000_000)

    with timing("Second iteration"):
        pi = monte_carlo_pi(1_000_000)


"""

Friedman function (5 random inputs, scalar output)
======================================================================

In this example, PCE is used to generate a surrogate model for a given set of 5D data.

Friedman function
----------------------------------------

.. math:: f(x) = 10 sin(\pi x_1x_2) + 20(x_3 - 0.5)^2 + 10x_4 + 5x_5

**Description:**  Dimensions: 5

**Input Domain:**  This function is evaluated on the hypercube :math:`x_i \in [0,1]` for all i = 1, …, 5.

**Reference:**  Friedman, J. H. (1991). Multivariate adaptive regression splines. The annals of statistics, 19(1), 1-67.
"""

# %% md
#
# Import necessary libraries.

# %%

import numpy as np
import matplotlib.pyplot as plt
from UQpy.distributions import Uniform, JointIndependent
from UQpy.surrogates import *

# %% md
#
# Define the function.

# %%

def function(x):
    return 10*np.sin(np.pi*x[:,0]*x[:,1]) + 20*(x[:,2]-0.5)**2 + 10*x[:,3] + 5*x[:,4]

# %% md
#
# Create a distribution object, generate samples and evaluate the function at the samples.

# %%

np.random.seed(1)

dist = Uniform(loc=0, scale=1)

marg = [dist]*5
joint = JointIndependent(marginals=marg)

n_samples = 200
x = joint.rvs(n_samples)
y = function(x)

# %% md
#
# Create an object from the PCE class. Then compute PCE coefficients using least squares regression.

# %%

max_degree = 3

polynomial_basis = TotalDegreeBasis(joint, max_degree)
least_squares = LeastSquareRegression()
pce = PolynomialChaosExpansion(polynomial_basis=polynomial_basis, regression_method=least_squares)
pce.fit(x,y)

# %% md
#
# Compute PCE coefficients using Lasso regression.

# %%

polynomial_basis = TotalDegreeBasis(joint, max_degree)
lasso = LassoRegression()
pce2 = PolynomialChaosExpansion(polynomial_basis=polynomial_basis, regression_method=lasso)
pce2.fit(x,y)

# %% md
#
# Compute PCE coefficients using Ridge regression.

# %%

polynomial_basis = TotalDegreeBasis(joint, max_degree)
ridge = RidgeRegression()
pce3 = PolynomialChaosExpansion(polynomial_basis=polynomial_basis, regression_method=ridge)
pce3.fit(x,y)

# %% md
# Error Estimation
# -----------------
# Validation error.

# %%

n_samples = 100
x_val = joint.rvs(n_samples)
y_val = function(x_val)

y_pce = pce.predict(x_val).flatten()
y_pce2 = pce2.predict(x_val).flatten()
y_pce3 = pce3.predict(x_val).flatten()

error = np.sum(np.abs(y_pce - y_val)/np.abs(y_val))/n_samples
error2 = np.sum(np.abs(y_pce2 - y_val)/np.abs(y_val))/n_samples
error3 = np.sum(np.abs(y_pce3 - y_val)/np.abs(y_val))/n_samples

print('Validation error, LSTSQ-PCE:', error)
print('Validation error, LASSO-PCE:', error2)
print('Validation error, Ridge-PCE:', error3)

# %% md
# Moment Estimation
# -----------------
# Returns mean and variance of the PCE surrogate.

# %%

n_mc = 1000000
x_mc = joint.rvs(n_mc)
y_mc = function(x_mc)
mean_mc = np.mean(y_mc)
var_mc = np.var(y_mc)

print('Moments from least squares regression :', pce.get_moments())
print('Moments from LASSO regression :', pce2.get_moments())
print('Moments from Ridge regression :', pce3.get_moments())
print('Moments from MC integration: ', mean_mc, var_mc)


import math
import numba

import numpy as np
# import autograd.numpy as np
# from autograd import grad

@numba.jit
def to_rad(deg):
    return np.pi * deg / 180.0

@numba.jit
def iiwa_forward_kinematics(joints, out='xyz'):
    ''' Direct Kinematics from iiwa structure. Using its dimensions and angles.
    '''
    #joints = [0,0,0,0,0,0,0]
    DH=np.array([[joints[0], 0.34, 0, -90],
                 [joints[1], 0.0, 0, 90],
                 [joints[2], 0.4, 0, 90],
                 [joints[3], 0.0, 0, -90],
                 [joints[4], 0.4, 0, -90],
                 [joints[5], 0.0, 0, 90],
                 [joints[6], 0.126, 0, 0]])
    Tr = np.eye(4)

    for i in range(0, len(DH)):
        t = DH[i, 0]
        d = DH[i, 1]
        a = DH[i, 2]
        al= DH[i, 3]
        # rad_al = math.radians(al)
        rad_al = to_rad(al)
        T = np.array([[np.cos(t), -np.sin(t)*np.cos(rad_al), np.sin(t)*np.sin(rad_al), a*np.cos(t)],
              [np.sin(t), np.cos(t)*np.cos(rad_al), -np.cos(t)*np.sin(rad_al), a*np.sin(t)],
              [0, np.sin(rad_al), np.cos(rad_al), d],
              [0, 0, 0, 1]])
        Tr = np.matmul(Tr, T)
    #pp = [Tr[0,3], Tr[1,3], T[2,3]]
    #pp
    if out=='xyz':
        return Tr[0:3,3]
        # return [Tr[0,3], Tr[1,3], Tr[2,3]]
    if out=='matrix':
        return Tr
    return None

@numba.jit
def iiwa_jacobian(state):
    fun = iiwa_forward_kinematics
    eps = 0.001
    jacobian = np.zeros((3,7))

    inp = np.array(state)
    selector = np.array([0,1,2,3,4,5,6])

    for i in selector:
        jacobian[:,i] = (fun(inp + eps* (selector == i)) - fun(inp - eps* (selector == i))) / (2*eps)

    # print(jacobian)
    return jacobian

# def jacobian_test():
#     J = iiwa_jacobian(7*[0.5])
#     eef_vel = np.dot(J, np.array(7*[0.1]))
#
# import timeit
#
#
# print(timeit.timeit(jacobian_test, number=1000))
#
# jacobian_test()
# jacobian_test()
# jacobian_test()
#
# print('timeit')
#
# print(timeit.timeit(jacobian_test, number=1000))
#
# print(timeit.timeit(jacobian_test, number=1000))

# jacobian_test()

# grad_iiwa_fk = grad(iiwa_forward_kinematics)
# print(grad_iiwa_fk(7*[0.01]))

# print(iiwa_forward_kinematics(7*[0.01]))


# -*- coding: utf-8 -*-
#%% NumPyの読み込み
import numpy as np
#   SciPyのstatsモジュールの読み込み
import scipy.stats as st
#   MatplotlibのPyplotモジュールの読み込み
import matplotlib.pyplot as plt
#   日本語フォントの設定
from matplotlib.font_manager import FontProperties
import sys
if sys.platform.startswith('win'):
    FontPath = 'C:\\Windows\\Fonts\\meiryo.ttc'
elif sys.platform.startswith('darwin'):
    FontPath = '/System/Library/Fonts/ヒラギノ角ゴシック W4.ttc'
elif sys.platform.startswith('linux'):
    FontPath = '/usr/share/fonts/truetype/takao-gothic/TakaoPGothic.ttf'
else:
    print('このPythonコードが対応していないOSを使用しています．')
    sys.exit()
jpfont = FontProperties(fname=FontPath)
#%% ベルヌーイ分布の成功確率qの事前分布
fig1 = plt.figure(num=1, facecolor='w')
q = np.linspace(0, 1, 250)
plt.plot(q, st.uniform.pdf(q), 'k-')
plt.plot(q, st.beta.pdf(q, 4, 6), 'k--')
plt.xlim(0, 1)
plt.ylim(0, 2.8)
plt.legend(['(A) 一様分布 ($\\alpha$ = 1, $\\beta$ = 1)',
            '(B) ベータ分布 ($\\alpha$ = 4, $\\beta$ = 6)'],
            loc='best', frameon=False, prop=jpfont)
plt.xlabel('成功確率 q', fontproperties=jpfont)
plt.ylabel('確率密度', fontproperties=jpfont)
plt.savefig('pybayes_fig_beta_prior.png', dpi=300)
plt.show()


import numpy as np
from matplotlib import pyplot as plt
import cv2
import math
import pandas as pd

def histogram_features(img_hist):
    gray_level = [0] * 256

    for i in img_hist:
        gray_level[i] += 1
    Pg = [x / len(img_hist) for x in gray_level]
    mean = sum(img_hist)/len(img_hist)

    var = 0
    for i in range(len(gray_level)):
        var += ((gray_level[i] - mean)**2)*Pg[i]
    std = math.sqrt(var)

    skew = 0
    for i in range(len(gray_level)):
        skew += ((gray_level[i] - mean)**3)*Pg[i]
    skew = skew/(std**3)

    energy = 0
    for i in Pg:
        energy += i**2

    entropy = 0

    for i in Pg:
        try:
            entropy += i * math.log(i,2)
        except:
            continue
    entropy = entropy * -1
    return mean,std,skew,energy,entropy

img2 = cv2.imread("../input/cv-image/image.png")
img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
img_hist2 = np.ravel(img2)
img3 = cv2.imread('../input/cv-image/image (1).png')
img3 = cv2.cvtColor(img3, cv2.COLOR_BGR2GRAY)
img_hist3 = np.ravel(img3)

plt.figure(figsize=(20,15))
plt.subplot(2,2,1),plt.imshow(img2,cmap = 'gray');
plt.title('High Contrast')
plt.subplot(2,2,2),plt.imshow(img3,cmap = 'gray');
plt.title('Low Contrast')
plt.subplot(2,2,3),plt.hist(img_hist2,255);
plt.subplot(2,2,4),plt.hist(img_hist3,255);

mean1, std1, skew1,energy1,ent1 = histogram_features(img_hist2)
mean2, std2, skew2,energy2,ent2 = histogram_features(img_hist3)

mean = [mean1,mean2]
std = [std1,std2]
skew = [skew1,skew2]
energy = [energy1,energy2]
entropy = [ent1,ent2]
img_names = ['Baboon High Contrast','Baboon Low Contrast']
df = pd.DataFrame(list(zip(img_names, mean,std,skew,energy,entropy)), columns =['Img Name', 'Mean','Std','Skew','Energy','Entropy'])
print(df)


import numpy as np
import matplotlib.pyplot as plt

def vandermonde(x, d):
  # Expanding x in Vandermonde Matrix: f(x) = 1 + x + x^2 ... + x^d
  X = np.matrix(np.polynomial.polynomial.polyvander(x, d))
  return X

def polynomial_regression(x, y, d):
  # Expanding x in Vandermonde Matrix
  X = vandermonde(x, d)
  # Calculating model weights: W = Inv(X.T * X) * X.T * Y
  #w = np.linalg.inv(np.transpose(X) * X) * np.transpose(X) * np.transpose(np.matrix(y))
  w = np.linalg.lstsq(X, y)[0]
  return np.transpose(np.matrix(w))

def bayesian_regression(x, y, d, var_o):
  # Expanding x in Vandermonde Matrix
  X = vandermonde(x, d)
  # Calculating y variance
  var_y = np.var(y)
  # Calculating model weights: W = Inv(X.T * X) * X.T * Y
  w = np.linalg.inv(np.transpose(X) * X + var_y / var_o * np.identity(X.shape[1])) * np.transpose(X) * np.transpose(np.matrix(y))
  return w

# Importing dataset
## Dataset path
dataset_path = 'data/whData.dat'
## Column names
dt = np.dtype([('w', np.float), ('h', np.float), ('g', np.str_, 1)])
## Loading the dataset
dataset = np.loadtxt(dataset_path, dtype=dt, comments='#', delimiter=None)
## Loading dataset without outliers
dataset_without_outliers = dataset[np.where(dataset['w'] > 0)]
## Loading dataset outliers (w < 0)
dataset_outliers = dataset[np.where(dataset['w'] < 0)]

# Heights from the training data
train_data = dataset_without_outliers['h']
# Weights from the training data
train_labels = dataset_without_outliers['w']
# Heights to predict their corresponding weights
test_data = dataset_outliers['h']

# Regression line X points to draw the model fitted line
regressionX = np.linspace(np.amin(train_data)-5, np.amax(train_data)+5, 1000)

# Regression for d = 5
d = 5
# Expand regression line X points in Vandermonde Matrix
X_regression = vandermonde(regressionX, d)
# Expand outlier height points in Vandermonde Matrix
X_test = vandermonde(test_data, d)

# Polynomial Regression
## Calculating the coefficients (weights) of the polynomial regression fitted line
Wp = polynomial_regression(train_data, train_labels, d)
# Calculating the fitted line Y points
yp = X_regression * Wp
# Calculating the corresponding weights (prediction values)
predictions = X_test * Wp

## Calculating the coefficients (weights) of the bayesian regression fitted line
var_o = 3.0
Wb = bayesian_regression(train_data, train_labels, d, var_o)
# Calculating the fitted line Y points
yb = X_regression * Wb
# Calculating the corresponding weights (prediction values)
predictionsb = X_test * Wb

# Plot the results
## Plot the data
plt.plot(train_data, train_labels, 'k.', label='Data')
## Plot the polynomial fitted line
plt.plot(regressionX, yp, 'b-', label='Polynomial Fit')
## plot the bayesian fitted line
plt.plot(regressionX, yb, 'g-', label='Bayesian Regression')
## Setting axes limits
plt.xlim(np.amin(train_data)-5, np.amax(train_data)+5)
plt.ylim(np.amin(train_labels)-5, np.amax(train_labels)+5)
plt.legend(loc='lower right')
plt.savefig("out/task23/bayesian_regression.png", bbox_inches="tight", pad_inches=0)
plt.show()


import sys
import csv
import numpy
import getopt
import math

def usage():
    print '$> python generaterawdata.py <required args> [optional args]\n' + \
        '\t-c <#>\t\tNumber of clusters to generate\n' + \
        '\t-p <#>\t\tNumber of points per cluster\n' + \
        '\t-o <file>\tFilename for the output of the raw data\n' + \
        '\t-v [#]\t\tMaximum coordinate value for points\n'

def euclideanDistance(p1, p2):
    '''
    Takes two 2-D points and computes the Euclidean distance between them.
    '''
    return math.sqrt(math.pow((p2[0] - p1[0]), 2) + \
                    math.pow((p2[1] - p1[1]), 2))

def tooClose(point, points, minDist):
    '''
    Computes the euclidean distance between the point and all points
    in the list, and if any points in the list are closer than minDist,
    this method returns true.
    '''
    for pair in points:
        if euclideanDistance(point, pair) < minDist:
                return True

    return False

def handleArgs(args):
    # set up return values
    numClusters = -1
    numPoints = -1
    output = None
    maxValue = 10

    try:
        optlist, args = getopt.getopt(args[1:], 'c:p:v:o:')
    except getopt.GetoptError, err:
        print str(err)
        usage()
        sys.exit(2)

    for key, val in optlist:
        # first, the required arguments
        if   key == '-c':
            numClusters = int(val)
        elif key == '-p':
            numPoints = int(val)
        elif key == '-o':
            output = val
        # now, the optional argument
        elif key == '-v':
            maxValue = float(val)

    # check required arguments were inputted
    if numClusters < 0 or numPoints < 0 or \
            maxValue < 1 or \
            output is None:
        usage()
        sys.exit()
    return (numClusters, numPoints, output, \
            maxValue)

def drawOrigin(maxValue):
    return numpy.random.uniform(0, maxValue, 2)

# start by reading the command line
numClusters, \
numPoints, \
output, \
maxValue = handleArgs(sys.argv)

writer = csv.writer(open(output, "w"))

# step 1: generate each 2D centroid
centroids_radii = []
minDistance = 0
for i in range(0, numClusters):
    centroid_radius = drawOrigin(maxValue)
    # is it far enough from the others?
    while (tooClose(centroid_radius, centroids_radii, minDistance)):
        centroid_radius = drawOrigin(maxValue)
    centroids_radii.append(centroid_radius)

# step 2: generate the points for each centroid
points = []
minClusterVar = 0
maxClusterVar = 0.5
for i in range(0, numClusters):
    # compute the variance for this cluster
    variance = numpy.random.uniform(minClusterVar, maxClusterVar)
    cluster = centroids_radii[i]
    for j in range(0, numPoints):
        # generate a 2D point with specified variance
        # point is normally-distributed around centroids[i]
        x, y = numpy.random.normal(cluster, variance)
        # write the points out
        writer.writerow([x, y])


import numpy as np

#SE(2) functions
def matrix_log_SO2(SO2_mat) :
  #ln(R) in SO(3) = theta
  return np.arctan2(SO2_mat[1,0], SO2_mat[0, 0])

def matrix_log_SE2(SE2_mat) :
  theta = matrix_log_SO2(SE2_mat[0:2, 0:2])
  if (theta < 1e-6) :
    A = 1
    B = 0
  else :
    A = np.sin(theta)/theta
    B = (1-np.cos(theta))/theta

  v_inv = 1/(A**2 + B**2) * np.array([[A, B], [-B, A]])
  mat_log = np.array(np.matmul(v_inv, SE2_mat[0:2, 2]))
  mat_log = np.append(mat_log, theta)

  return mat_log

def matrix_exp_so2(theta) :
  #reconstruct R.
  return np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]])

def matrix_exp_se2(twist) :
  theta = twist[-1]
  R = matrix_exp_so2(theta)

  #V converges to I2
  if (theta < 1e-6) :
    V = np.eye(2)
  else:
    V = 1/theta * np.array([[np.sin(theta), -(1 - np.cos(theta))], [(1-np.cos(theta)), np.sin(theta)]])

  mat_exp = np.zeros((3,3))
  mat_exp[0:2, 0:2] = R
  mat_exp[0:2, 2] = np.matmul(V, twist[0:2])
  mat_exp[2, 2] = 1

  return mat_exp

def get_twist_SE2(Xstart, pos_end, pos_future=None) :

  Xend = np.zeros((3,3))

  Xend[-1,-1] = 1
  Xend[0:2, 2] = pos_end

  #compute end direction (face in direction of future step i.e. end+1)
  if not pos_future is None:
    next_displacement = pos_future - pos_end
    next_theta = np.arctan2(next_displacement[1], next_displacement[0])
    Xend[0:2, 0:2] = np.array([[np.cos(next_theta), -np.sin(next_theta)], [np.sin(next_theta), np.cos(next_theta)]])
  else :
    Xend[0:2, 0:2] = Xstart[0:2, 0:2]

  # set_trace()
  twist_SE2 = matrix_log_SE2(np.matmul(np.linalg.inv(Xstart), Xend))

  return twist_SE2, Xend

def twist_motion(Xstart, twist, s=1) :
  return np.matmul(Xstart, s * matrix_exp_se2(twist))


import numpy as np
from enum import Enum

""" All the activation functions available (feel free to add more) """

def identity(val):
    return val

def binarystep(val):
    return (0 if val < 0 else 1)

def sigmoid(val, activationResponse=1/4.924273):
    return 1 / (1 + np.exp(-(val/activationResponse)))

def tanh(val):
    return np.tanh(val)

def relu(val):
    return np.maximum(0, val)

def leakyrelu(val):
    return (val*0.01 if val < 0 else val)

def absval(val):
    return abs(val)

def gaussian(val):
    return np.exp(-(val**2))

def arctan(val):
    return np.arctan(val)

def sine(val):
    return np.sin(val)

def cosine(val):
    return np.cos(val)

class ActivationFunctions(Enum):
    IDENTITY = identity
    BINARYSTEP = binarystep
    SIGMOID = sigmoid
    TANH = tanh
    RELU = relu
    LEAKYRELU = leakyrelu
    ABSVAL = absval
    GAUSSIAN = gaussian
    ARCTAN = arctan
    SINE = sine
    COSINE = cosine


# K=funLtoK(L)
# The function funLtoK(L) converts a kernel L matrix into  a (normalized)
# kernel K matrix. The K matrix has to be semi-positive definite.

import numpy as np  # NumPy package for arrays, random number generation, etc

##TEMP: Testing
#B=np.array([[3, 2, 1], [4, 5,6], [9, 8,7]]);
#
#L=np.matmul(B.transpose(),B);
#
#

def funLtoK(L):
    #METHOD 1 -- using eigen decomposition.
    #This method doesn't need inverse calculating and seems to more stable.
    eigenValL, eigenVectLK = np.linalg.eig(L)  # eigen decomposition
    eigenValK = eigenValL/(1+eigenValL)  # eigenvalues of K
    eigenValK = np.diagflat(eigenValK)  # eigenvalues of L as diagonal matrix
    # recombine from eigen components
    K = np.matmul(np.matmul(eigenVectLK, eigenValK), eigenVectLK.transpose())
    K = np.real(K)  # make sure all values are real
    return K

#K=funLtoK(L)
# K=
#array([[ 0.76022099,  0.33480663, -0.09060773],
#       [ 0.33480663,  0.3320442 ,  0.32928177],
#       [-0.09060773,  0.32928177,  0.74917127]])

# #METHOD 2 -- standard approach.
# #Slightly faster, seems to be less stable.
# K=np.matmul(L,np.linalg.inv(L+np.eye(L.shape[0])));


#%% setup
import numpy as np
from matplotlib import pyplot as plt

def euler(f, y_0, start, stop, step):
    w_i = y_0
    w = [w_i]
    t = np.arange(start, stop+step, step)  # include endpoint, therefore add step
    for t_i in t[:-1]:
        w_i += step*f(t_i, w_i)
        w.append(w_i)

    return t, w

def y_a(t):
    return 0.5*t*t + 1

def y_b(t):
    return np.e ** (t*t*t/3)

def dydt_a(t, y):
    return t

def dydt_b(t, y):
    return t*t*y

#%% run
h = 0.1
a, b = 0., 1.
y_0 = 1.
t, w_a = euler(dydt_a, y_0, a, b, h)
_, w_b = euler(dydt_b, y_0, a, b, h)

#%% print a
print("t:       ", '|', "w:       ", '|', "error:  ")
for i in range(len(t)):
    print(f'{t[i]:+6f} | {w_a[i]:+6f} | {y_a(t[i]) - w_a[i]:+6f}')

#%% print b ØVING
print("t:       ", '|', "w:       ", '|', "error:  ")
for i in range(len(t)):
    print(f'{t[i]:+6f} | {w_b[i]:+6f} | {y_b(t[i]) - w_b[i]:+6f}')

#%% plot a
plt.plot(t, w_a, 'r')
plt.scatter(t, w_a, c='r', s=15)
plt.plot(t, y_a(t), 'b')
plt.show()

#%% plot b
plt.plot(t, w_b, 'r')
plt.scatter(t, w_b, c='r', s=15)
plt.plot(t, y_b(t), 'b')
plt.show()


from numpy import mgrid, sum

def moments2e(image):
	"""
	for testing purposes only
	for 'production' use, please use cv2 or other implemented moments

	SOURCE: https://github.com/shackenberg/Image-Moments-in-Python/blob/master/moments.py
	This function calculates the raw, centered and normalized moments
	for any image passed as a numpy array.

	Further reading:
	https://en.wikipedia.org/wiki/Image_moment
	https://en.wikipedia.org/wiki/Central_moment
	https://en.wikipedia.org/wiki/Moment_(mathematics)
	https://en.wikipedia.org/wiki/Standardized_moment
	http://opencv.willowgarage.com/documentation/cpp/structural_analysis_and_shape_descriptors.html#cv-moments

	compare with:
	import cv2
	cv2.moments(image)

	"""
	assert len(image.shape) == 2 # only for grayscale images
	x, y = mgrid[:image.shape[0],:image.shape[1]]
	moments = {}
	moments['mean_x'] = sum(x*image)/sum(image)
	moments['mean_y'] = sum(y*image)/sum(image)

	# raw or spatial moments
	moments['m00'] = sum(image)
	moments['m01'] = sum(x*image)
	moments['m10'] = sum(y*image)
	moments['m11'] = sum(y*x*image)
	moments['m02'] = sum(x**2*image)
	moments['m20'] = sum(y**2*image)
	moments['m12'] = sum(x*y**2*image)
	moments['m21'] = sum(x**2*y*image)
	moments['m03'] = sum(x**3*image)
	moments['m30'] = sum(y**3*image)

	# central moments
	# moments['mu01']= sum((y-moments['mean_y'])*image) # should be 0
	# moments['mu10']= sum((x-moments['mean_x'])*image) # should be 0
	moments['mu11'] = sum((x-moments['mean_x'])*(y-moments['mean_y'])*image)
	moments['mu02'] = sum((y-moments['mean_y'])**2*image) # variance
	moments['mu20'] = sum((x-moments['mean_x'])**2*image) # variance
	moments['mu12'] = sum((x-moments['mean_x'])*(y-moments['mean_y'])**2*image)
	moments['mu21'] = sum((x-moments['mean_x'])**2*(y-moments['mean_y'])*image)
	moments['mu03'] = sum((y-moments['mean_y'])**3*image)
	moments['mu30'] = sum((x-moments['mean_x'])**3*image)

	# opencv versions
	#moments['mu02'] = sum(image*(x-m01/m00)**2)
	#moments['mu02'] = sum(image*(x-y)**2)

	# wiki variations
	#moments['mu02'] = m20 - mean_y*m10
	#moments['mu20'] = m02 - mean_x*m01

	# central standardized or normalized or scale invariant moments
	moments['nu11'] = moments['mu11'] / sum(image)**(2/2+1)
	moments['nu12'] = moments['mu12'] / sum(image)**(3/2+1)
	moments['nu21'] = moments['mu21'] / sum(image)**(3/2+1)
	moments['nu20'] = moments['mu20'] / sum(image)**(2/2+1)
	moments['nu03'] = moments['mu03'] / sum(image)**(3/2+1) # skewness
	moments['nu30'] = moments['mu30'] / sum(image)**(3/2+1) # skewness
	return moments


import numpy as np

class SecantMethod:
    def __init__(self, f, e, max_itterations):
        self.name              = 'Secant'
        self.f                 = f
        self.epsilon           = e
        self.itterations       = 0
        self.max_itterations   = max_itterations

    def method(self, xn_2, xn_1):
        return ( xn_2 * self.f(xn_1) - xn_1 * self.f(xn_2) ) / ( self.f(xn_1) - self.f(xn_2) )

    def find(self, x1, x2):
        if np.absolute(self.f(x2)) < self.epsilon or self.itterations == self.max_itterations:
            return x2
        if np.absolute(self.f(x1)) < self.epsilon:
            return x1
        self.itterations += 1
        return self.find(x2, self.method(x1, x2))


import sympy.physics.mechanics as me
import sympy as sm
import math as m
import numpy as np

x, y = me.dynamicsymbols('x y')
xd, yd = me.dynamicsymbols('x y', 1)
e = sm.cos(x)+sm.sin(x)+sm.tan(x)+sm.cosh(x)+sm.sinh(x)+sm.tanh(x)+sm.acos(x)+sm.asin(x)+sm.atan(x)+sm.log(x)+sm.exp(x)+sm.sqrt(x)+sm.factorial(x)+sm.ceiling(x)+sm.floor(x)+sm.sign(x)
e = (x)**2+sm.log(x, 10)
a = sm.Abs(-1*1)+int(1.5)+round(1.9)
e1 = 2*x+3*y
e2 = x+y
am = sm.Matrix([e1.expand().coeff(x), e1.expand().coeff(y), e2.expand().coeff(x), e2.expand().coeff(y)]).reshape(2, 2)
b = (e1).expand().coeff(x)
c = (e2).expand().coeff(y)
d1 = (e1).collect(x).coeff(x,0)
d2 = (e1).collect(x).coeff(x,1)
fm = sm.Matrix([i.collect(x)for i in sm.Matrix([e1,e2]).reshape(1, 2)]).reshape((sm.Matrix([e1,e2]).reshape(1, 2)).shape[0], (sm.Matrix([e1,e2]).reshape(1, 2)).shape[1])
f = (e1).collect(y)
g = (e1).subs({x:2*x})
gm = sm.Matrix([i.subs({x:3}) for i in sm.Matrix([e1,e2]).reshape(2, 1)]).reshape((sm.Matrix([e1,e2]).reshape(2, 1)).shape[0], (sm.Matrix([e1,e2]).reshape(2, 1)).shape[1])
frame_a = me.ReferenceFrame('a')
frame_b = me.ReferenceFrame('b')
theta = me.dynamicsymbols('theta')
frame_b.orient(frame_a, 'Axis', [theta, frame_a.z])
v1=2*frame_a.x-3*frame_a.y+frame_a.z
v2=frame_b.x+frame_b.y+frame_b.z
a = me.dot(v1, v2)
bm = sm.Matrix([me.dot(v1, v2),me.dot(v1, 2*v2)]).reshape(2, 1)
c=me.cross(v1, v2)
d = 2*v1.magnitude()+3*v1.magnitude()
dyadic=me.outer(3*frame_a.x, frame_a.x)+me.outer(frame_a.y, frame_a.y)+me.outer(2*frame_a.z, frame_a.z)
am = (dyadic).to_matrix(frame_b)
m = sm.Matrix([1,2,3]).reshape(3, 1)
v=m[0]*frame_a.x +m[1]*frame_a.y +m[2]*frame_a.z


"""Linear Classification Models
Author: Rajan Subramanian
Created: May 25, 2020
"""

import numpy as np
from scipy.optimize import minimize
from learning.base import LinearBase
from typing import Union, Dict
from numpy.linalg import norm

class LogisticRegression(LinearBase):
    """
    Implements Logistic Regression via nls
    Args:
    fit_intercept: indicates if intercept is needed or not

    Attributes:
    theta:          Coefficient Weights after fitting
    predictions:    Predicted Values from fitting
    residuals:      Number of incorrect Predictions

    Notes:
    Class uses two estimation methods to estimate the parameters
    of logistic regression
    - A implemention using Newton's Method is given
    - A implemention using Stochastic Gradient Descent is given
    """
    def __init__(self, fit_intercept: bool = True, degree: int = 1):
        self.fit_intercept = fit_intercept
        self.degree = degree
        self.run = False

    def sigm(self, z: np.ndarray) -> np.ndarray:
        """Computes the sigmoid function

        Args:
            z (np.ndarray): input value from linear transformation

        Returns:
            np.ndarray: sigmoid function value
        """
        return 1.0 / (1 + np.exp(-z))

    def sigm_prime(self, z: np.ndarray) -> np.ndarray:
        """computes the first derivative of sigmoid function

        Args:
            z (np.ndarray): input value from linear transformation

        Returns:
            np.ndarray: the first derivative of sigmoid function
        """
        return self.sigm(z) * (1 - self.sigm(z))

    def _jacobian(self,
                  guess: np.ndarray,
                  X: np.ndarray,
                  y: np.ndarray
                  ):
        """Computes the jacobian of likelihood function

        Args:
            guess (np.ndarray): the initial guess for optimizer
            X (np.ndarray): design matrix
                            shape = (n_samples, n_features)
            y (np.ndarray): response variable
                            shape = (n_samples,)

        Returns:
            [np.ndarray]: first partial derivatives wrt weights
        """
        predictions = self.predict(X, guess)
        return -1*(X.T @ (y - predictions))

    def _hessian(self,
                 guess: np.ndarray,
                 X: np.ndarray,
                 y: np.ndarray
                 ):
        """computes the hessian wrt weights

        Args:
            guess (np.ndarray): initial guess for optimizer
            X (np.ndarray): design matrix
                            shape = (n_samples, n_features)
            y (np.ndarray): response variable
                            shape = (n_samples,)

        Returns:
            np.ndarray: second partial derivatives wrt weights
        """
        z = self.net_input(X, thetas=guess)
        prob = self.sigm_prime(z)
        n = prob.shape[0]
        W = np.zeros((n, n))
        np.fill_diagonal(W, prob)
        return X.T @ W @ X

    def _loglikelihood(self, y, z):
        """returns the loglikelihood function of logistic regression

        Args:
            y (np.ndarray): response variable
                            shape = (n_samples,)
            z (np.ndarray): result of net input function
                            shape = (n_samples,)

        Returns:
            np.ndarray: loglikelihood function
        """
        return y @ z - np.log(1 + np.exp(z)).sum()

    def _objective_func(self, guess: np.ndarray, X: np.ndarray, y: np.ndarray):
        """the objective function to be minimized

        Args:
            guess (np.ndarray): initial guess for optimization
            X (np.ndarray): design matrix
                            shape = {n_samples, p_features}
            y (np.ndarray): the response variable
                            shape = {n_samples,}

        Returns:
            float: value from loglikelihood function
        """
        # z = X @ theta
        z = self.net_input(X, thetas=guess)
        f = self._loglikelihood(y, z)
        return -f

    def fit(self,
            X: np.ndarray,
            y: np.ndarray,
            ) -> 'LogisticRegression':
        """fits model to training data and returns regression coefficients
        Args:
        X:
            shape = (n_samples, p_features)
            n_samples is number of instances i.e rows
            p_features is number of features i.e columns
        y:
            shape = (n_samples,)
            Target values

        Returns:
        object
        """
        X = self.make_polynomial(X)
        # generate random guess
        guess_params = np.mean(X, axis=0)
        self.theta = minimize(self._objective_func,
                              guess_params,
                              jac=self._jacobian,
                              hess=self._hessian,
                              method='BFGS',
                              options={'disp': True},
                              args=(X, y))['x']
        return self

    def newton_system(self, X, y, func, jac, x, tol_approx=10E-9, tol_consec=10E-6):
        """Solves N-dimensional newton's problem for F(x) = 0

        Args:
            func (function): given function evaluated at x
            jac (function): jacobian of func as a function of x
            x (np.ndarray): initial guess
            tol_approx (float, optional): largest admissible value of ||F(x)||
                                          when solution is found
                                          Defaults to 10E-9.
            tol_consec (float, optional): largest admissible distance between
                                          two consecutive approximations when
                                          solution is found
                                          Defaults to 10E-6.
        """
        xnew, xold = x, x - 1
        count = 0
        while norm(xnew - xold, ord=2) > tol_consec or count > niter:
            xold = xnew
            z = self.net_input(X, thetas=xold)
            predictions = self.predict(X, xold)
            prob = self.sigm_prime(z)
            n = prob.shape[0]
            W = np.zeros((n, n))
            Winv = np.zeros((n, n))
            np.fill_diagonal(W, prob)
            np.fill_diagonal(Winv, 1/prob)
            zbar = X @ xold - Winv @ (y-predictions)
            xnew = np.linalg.inv(X.T @ W @ X) @ X @ W @ zbar
            count += 1
        return xnew

    def net_input(self, X: np.ndarray, thetas: np.ndarray) -> np.ndarray:
        """Computes linear transformation X*theta

        Args:
            X (np.ndarray): design matrix,
            shape = {n_samples, p_features}
            thetas (np.ndarray): weights of logistic function
            shape = {p_features + intercept}

        Returns:
            np.ndarray: linear transformation
        """
        return X @ thetas

    def predict(self,
                X: np.ndarray,
                thetas: np.ndarray = None,
                ) -> Union[np.ndarray, Dict]:
        """Makes predictions of probabilities

        Args:
            X (np.ndarray): design matrix
            shape = {n_samples, p_features}
            thetas (np.ndarray, optional): estimated weights from fitting
            Defaults to None.
            shape = {p_features + intercept,}

        Returns:
            Union[np.ndarray, Dict]: predicted probabilities
            shape = {n_samples,}
        """
        if thetas is None:
            if isinstance(self.theta, np.ndarray):
                return self.sigm(self.net_input(X, self.theta))
            else:
                return self.sigm(self.net_input(X, self.theta['x']))
        return self.sigm(self.net_input(X, thetas))

class LogisticRegressionGD(LinearBase):
    """Implements Logistic Regression via Gradient Descent

       Args:
       eta:             Learning rate (between 0.0 and 1.0)
       n_iter:          passees over the training set
       random_state:    Random Number Generator seed
                        for random weight initilization

       Attributes:
       theta:           Weights after fitting
       residuals:       Number of incorrect predictions
    """
    def __init__(self,
                 eta: float = 0.001,
                 n_iter: int = 20,
                 random_state: int = 1,
                 fit_intercept: bool = True,
                 degree: int = 1):
        self.eta = eta
        self.n_iter = n_iter
        self.random_state = random_state
        self.fit_intercept = fit_intercept
        self.degree = degree
        self.run = False

    def fit(self, X: np.ndarray, y: np.ndarray) -> 'LogisticRegressionGD':
        """fits training data
        Args:
        X: shape = {n_samples, p_features}
                    n_samples is number of instances i.e rows
                    p_features is number of features (the dimension of dataset)

        y: shape = {n_samples,}
                    Target values
        Returns:
        object
        """
        n_samples, p_features = X.shape[0], X.shape[1]
        self.theta = np.zeros(shape=1 + p_features)
        self.cost = []
        X = self.make_polynomial(X)

        for _ in range(self.n_iter):
            z = self.net_input(X, self.theta)
            self.theta += self.eta * self._jacobian(self.theta, X, y)
            self.cost.append(self._cost(y, z) / n_samples)
        self.run = True
        return self

    def _jacobian(self,
                  guess: np.ndarray,
                  X: np.ndarray,
                  y: np.ndarray
                  ):
        """Computes the jacobian of likelihood function

        Args:
            guess (np.ndarray): the initial guess for optimizer
            X (np.ndarray): design matrix
                            shape = (n_samples, n_features)
            y (np.ndarray): response variable
                            shape = (n_samples,)

        Returns:
            [np.ndarray]: first partial derivatives wrt weights
        """
        predictions = self.predict(X, guess)
        return (X.T @ (y - predictions))

    def _cost(self, y, z):
        """computes cost of likelihood function

        Args:
            y (np.ndarray): response variable
                            shape = (n_samples,)
            z (np.ndarray): result of net input function
                            shape = (n_samples,)

        Returns:
            np.ndarray: loglikelihood function
        """
        # direction is reversed since we are minimizng cost
        return -1 * (y @ z - np.log(1 + np.exp(z)).sum())

    def net_input(self, X: np.ndarray, thetas: np.ndarray) -> np.ndarray:
        """Computes linear transformation X*theta

        Args:
            X (np.ndarray): design matrix,
            shape = {n_samples, p_features}
            thetas (np.ndarray): weights of logistic function
            shape = {p_features + intercept}

        Returns:
            np.ndarray: linear transformation
        """
        return X @ thetas

    def sigm(self, z: np.ndarray) -> np.ndarray:
        """Computes the sigmoid function

        Args:
            z (np.ndarray): input value from linear transformation

        Returns:
            np.ndarray: sigmoid function value
        """
        return 1.0 / (1 + np.exp(-z))

    def predict(self,
                X: np.ndarray,
                thetas: np.ndarray = None,
                ) -> Union[np.ndarray, Dict]:
        """Makes predictions of probabilities

        Args:
            X (np.ndarray): design matrix
            shape = {n_samples, p_features}
            thetas (np.ndarray, optional): estimated weights from fitting
            Defaults to None.
            shape = {p_features + intercept,}

        Returns:
            Union[np.ndarray, Dict]: predicted probabilities
            shape = {n_samples,}
        """
        if thetas is None:
            if isinstance(self.theta, np.ndarray):
                return self.sigm(self.net_input(X, self.theta))
        return self.sigm(self.net_input(X, thetas))


from scipy.stats import rankdata
list = [0, 2, 3, 2]
print(f'list : {list}')

print(f"rankdata : {rankdata(list)}")
print(f"rankdata method='min' : {rankdata(list,method='min')}")
print(f"rankdata method='max' : {rankdata(list,method='max')}")
print(f"rankdata method='dense' : {rankdata(list,method='dense')}")
print(f"rankdata method='ordinal' : {rankdata(list,method='ordinal')}")


#!/usr/bin/env python3

import numpy as np

def lowPassFIR(fc, b = 0.08):
  #fc: Cutoff frequency as a fraction of the sampling rate (in (0, 0.5)).
  #b: Transition band, as a fraction of the sampling rate (in (0, 0.5)).

  N = int(np.ceil((4 / b)))
  if not N % 2: N += 1  # Make sure that N is odd.
  n = np.arange(N)

  # Compute sinc filter.
  h = np.sinc(2 * fc * (n - (N - 1) / 2))

  # Compute Blackman window.
  w = 0.42 - 0.5 * np.cos(2 * np.pi * n / (N - 1)) + \
      0.08 * np.cos(4 * np.pi * n / (N - 1))

  # Multiply sinc filter by window.
  h = h * w

  # Normalize to get unity gain.
  h = h / np.sum(h)

  return N, h

#-------------------------------------------------------------------------------

import matplotlib.pyplot as plt

fs = 1000 # sampling frequency

# generate properly the time vector
t = np.arange(1000)/fs
sga = np.sin(2*np.pi*2*t)   # signal with f = 2
sgb = np.sin(2*np.pi*10*t)  # signal with f = 10
sgc = sga + sgb

N, h = lowPassFIR(fc = 6/fs, b = 2/fs)
sgf = np.convolve(sgc, h)

plt.plot(t, sgc, label = 'original')
plt.plot(t, sgf[int(N/2):1000 + int(N/2)], label = 'low-pass')
plt.plot(t, sga, label = 'f = 2')
plt.legend()
plt.show()


import numpy as np
import pandas as pd
from pytest import approx, mark

from fastats.maths.correlation import spearman, spearman_pairwise
from tests.data.datasets import SKLearnDataSets

def test_spearman_basic_sanity():
    """
    Tests the Spearman rank correlation using
    the example on wikipedia:
    https://en.wikipedia.org/wiki/Spearman%27s_rank_correlation_coefficient
    """
    iq = np.array([86, 97, 99, 100, 101, 103, 106, 110, 112, 113])
    tv = np.array([0, 20, 28, 27, 50, 29, 7, 17, 6, 12])

    result = spearman(iq, tv)
    assert result == approx(-0.1757575, abs=1e-7)

    A = np.stack([iq, tv]).T
    assert spearman_pairwise(A).diagonal(1)[0] == approx(-0.1757575, abs=1e-7)

def test_spearman_nan_result():
    """
    Confirms that any nan value on the way in
    will result in a nan value returned.
    """
    x = np.array([1, 2, 3, 4], dtype='float')
    y = np.array([2, 3, 4, 3], dtype='float')
    assert spearman(x, y) == approx(0.8)

    x[0] = np.nan
    assert spearman(x, y) == approx(0.4)

    x[0] = 1
    y[0] = np.nan
    assert spearman(x, y) == approx(-0.4)

    y[0] = 2
    assert spearman(x, y) == approx(0.8)

def test_spearman_rgs():
    """
    Tests the Spearman rank correlation using
    the Royal Geographical Society example pdf in
    literature/OASpearmansRankExcelGuidePDF.pdf
    """
    width = np.array([0, 50, 150, 200, 250, 300, 350, 400, 450, 500])
    depth = np.array([0, 10, 28, 42, 59, 51, 73, 85, 104, 96])

    assert spearman(width, depth) == approx(0.9757, abs=1e-4)

    A = np.stack([width, depth]).T
    assert spearman_pairwise(A).diagonal(1) == approx(0.9757, abs=1e-4)

@mark.parametrize('A', SKLearnDataSets)
def test_spearman_pairwise_versus_pandas(A):
    """
    This is a check of the pairwise Spearman correlation against
    pandas DataFrame corr for an input dataset A.
    """
    data = A.value
    expected = pd.DataFrame(data).corr(method='spearman').values
    output = spearman_pairwise(data)
    assert np.allclose(expected, output)

if __name__ == '__main__':
    import pytest
    pytest.main([__file__])


import numpy as np

def normalize_array(arr: np.ndarray) -> np.ndarray:
    """Normalizes an array to the range [0, 1].

    The maximal element will be 1 and the minimal 0, while the
    scale remains the same.

    An array containing only identical values will be returned as a zeroed array.

    Paramaters
    ----------
    arr : np.ndarray
        Input array to normalize

    Returns
    -------
    np.ndarray
        Normalized array
    """
    arr = np.nan_to_num(arr)
    arr -= arr.min()
    max_ = arr.max()
    return arr if np.isclose(max_, 0.) else arr / max_


# Author: Eyad Sibai
import numpy as np
from scipy.stats import iqr

def histogram_square_formula(values):
    """ reference https://www.kdnuggets.com/2018/02/histogram-tips-tricks.html
    :param values: the values of the histogram data
    :return: number of bins and width of the bin
    """
    bins = np.sqrt(len(values))
    bin_width = (np.max(values) - np.min(values)) / bins
    return bins, bin_width

def histogram_sturges_formula(values):
    """
    :param values:
    :return:
    """
    bins = np.ceil(np.log2(len(values))) + 1
    bin_width = (np.max(values) - np.min(values)) / bins
    return bins, bin_width

def histogram_rice_formula(values):
    """
    :param values:
    :return:
    """
    bins = 2 * np.power(len(values), 1. / 3)
    bin_width = (np.max(values) - np.min(values)) / bins
    return bins, bin_width

def histogram_scott_formula(values):
    """
    :param values:
    :return:
    """
    bin_width = 3.5 * np.std(values) / np.power(len(values), 1 / 3)
    bins = (np.max(values) - np.min(values)) / bin_width
    return bins, bin_width

def histogram_freedman_diaconis_formula(values):
    """
    :param values:
    :return:
    """
    bin_width = 2 * iqr(values) / np.power(len(values), 1 / 3)
    bins = (np.max(values) - np.min(values)) / bin_width
    return bins, bin_width

# Author: Andreas Syrén
def bin_it(values, formula='freedman_diaconis'):
    values = [v for v in values if np.isfinite(v)]
    if formula == 'square':
        bins, bin_width = histogram_square_formula(values)
    elif formula == 'sturges':
        bins, bin_width = histogram_sturges_formula(values)
    elif formula == 'rice':
        bins, bin_width = histogram_rice_formula(values)
    elif formula == 'scott':
        bins, bin_width = histogram_scott_formula(values)
    elif ('freedman' in formula) or ('diaconis' in formula):
        bins, bin_width = histogram_freedman_diaconis_formula(values)
    else:
        raise ValueError("\"%s\" not in implemented formulas." % str(formula))
    return np.linspace(np.min(values), np.max(values), min(500, bins))


from sympy import ( symbols, solve, diff, integrate, exp, sqrt, lambdify, Integral, ln, pprint, oo )
# It is found that the time (in minutes) required by a predator to find a prey is a random variable that is exponentially distributed with

mu = 27

t = symbols( 't' )
a = 1/mu
pdf = a * exp( -a*t )

# According to this distribution, what is the longest time within which the predator will be 85% certain of finding a prey?

pct = .85
x = symbols( 'x' )

prob = solve( Integral( pdf, ( t, 0, x ) ).doit() - pct, x )[ 0 ]
round( prob, 1 )

#  probability that the predator will have to spend more than 1 hour looking for a prey

prob = integrate( pdf, ( t, 60, oo ) ).doit()
round( prob, 4 )


import numpy as np

def mse(targets, outputs):
    return np.mean(np.power(targets - outputs, 2))

def mse_prime(targets, outputs):
    return 2 * (outputs - targets) / targets.size


import networkx as nx
import pandas as pd
import numpy as np
import scipy.integrate

class SI_Model():

    def __init__(self, poblacion, infectados, beta=None):

        self.transitions = nx.MultiDiGraph()
        self.transitions.add_edge('S', 'I', agent='I', rate=beta)
        self.S0 = poblacion-infectados
        self.I0 = infectados
        self.poblacion = poblacion
        self.dataframe = None

    def _new_cases(self, population, time, pos):

        diff = np.zeros(len(pos))
        N = np.sum(population)

        for edge in self.transitions.edges(data=True):
            source = edge[0]
            target = edge[1]
            trans = edge[2]

            rate = trans['rate']*population[pos[source]]

            if 'agent' in trans:
                agent = trans['agent']
                rate *= population[pos[agent]]/N

            diff[pos[source]] -= rate
            diff[pos[target]] += rate

        return diff

    def integrate(self, timesteps):

        pos = {'S':0,
               'I':1}
        population=np.zeros(2)
        population[0] = self.S0
        population[1] = self.I0

        time = np.arange(1, timesteps, 1)

        self.dataframe = pd.DataFrame(scipy.integrate.odeint(self._new_cases, population, time, args=(pos,)), columns=pos.keys(), index=time)
        self.dataframe['tiempo']=time

class SIR_Model():

    def __init__(self, poblacion, infectados, recuperados=0, beta=None, mu=None):

        self.transitions = nx.MultiDiGraph()
        self.transitions.add_edge('S', 'I', agent='I', rate=beta)
        self.transitions.add_edge('I', 'R', rate=mu)
        self.S0 = poblacion-infectados
        self.I0 = infectados
        self.R0 = recuperados
        self.poblacion = poblacion
        self.dataframe = None

    def _new_cases(self, population, time, pos):

        diff = np.zeros(len(pos))
        N = np.sum(population)

        for edge in self.transitions.edges(data=True):
            source = edge[0]
            target = edge[1]
            trans = edge[2]

            rate = trans['rate']*population[pos[source]]

            if 'agent' in trans:
                agent = trans['agent']
                rate *= population[pos[agent]]/N

            diff[pos[source]] -= rate
            diff[pos[target]] += rate

        return diff

    def integrate(self, timesteps):

        pos = {'S':0,
               'I':1,
               'R':2}
        population=np.zeros(3)
        population[0] = self.S0
        population[1] = self.I0
        population[2] = self.R0

        time = np.arange(1, timesteps, 1)

        self.dataframe = pd.DataFrame(scipy.integrate.odeint(self._new_cases, population, time, args=(pos,)), columns=pos.keys(), index=time)
        self.dataframe['tiempo']=time

import numpy,import scipy,import networkx


# -*- coding: utf-8 -*-
"""
Created on Wed Feb 22 16:01:40 2017

@author: picku
"""
"""Linear regression via gradient descent"""

import matplotlib.pyplot as plt
from matplotlib import style
import numpy as np
style.use('ggplot')

def total_squared_error(data, m, b):
    """Total squared error between the data points and line
    y = mx + b SE_line = (y_1-(m*x_1+b))^2 + (y_2-(m*x_2)+b)^2 + ...+
    (y_n - (m*x_n + b))^2"""

    total_SE = 0
    for data_point in data:
        SE = (data_point[1] - (m * data_point[0] + b)) ** 2
        total_SE += SE

    return total_SE / len(data)

def partial_difference_quotient(f, points, v, i, h):
    """compute the ith partial difference quotient of function f w.r.t to ith position of v"""

    #add h to just the ith element of v
    w = [v_j + (h if j == i else 0) for j, v_j in enumerate(v)]

    return (f(points, w[0], w[1]) - f(points, v[0], v[1])) / h

def estimate_gradient(points, current_m, current_b):
    """estimate partial derivatives w.r.t. m and b using difference quotients"""
    m = current_m
    b = current_b

    partial_wrt_m = partial_difference_quotient(total_squared_error, points, [m, b], i = 0, h = 0.0001)
    partial_wrt_b = partial_difference_quotient(total_squared_error, points, [m, b], i = 1, h = 0.0001)

    return partial_wrt_m, partial_wrt_b

def step(points, current_m, current_b, learningRate):
    """take step 'downhill' in gradient descent process"""

    #estimate gradient w.r.t. m and w.r.t. b
    partial_wrt_m, partial_wrt_b = estimate_gradient(points, current_m, current_b)

    #update m and b
    next_m = current_m - (learningRate * partial_wrt_m)
    next_b = current_b - (learningRate * partial_wrt_b)

    #update tolerance levels for m and b
    t_val = [next_m - current_m, next_b - current_b]

    return next_m, next_b, t_val

def gradient_descent(points, start_m, start_b, learningRate, numIterations, tolerance):
    """perform gradient descent for specified number of iterations or when specified tolerance
    levels are reached for both m and b"""

    #initialize search
    next_m = start_m
    next_b = start_b

    #keeping track of m and b steps for each iteration
    m_steps = []
    b_steps = []
    total_SE = []

    for i in range(numIterations):

        numIters = 0
        [next_m, next_b, t_val] = step(points, next_m, next_b, learningRate)

        #take step if m and b tolerance are greater than given tolerance value
        if abs(t_val[0]) >= tolerance and abs(t_val[1]) >= tolerance:
            m_steps.append(next_m)
            b_steps.append(next_b)
            total_SE.append(total_squared_error(points, next_m, next_b)) #track total squared error
            numIters = i + 1
        else:
            break

    return next_m, next_b, m_steps, b_steps, total_SE, numIters

def create_line(m, b, x):
    """create line with slope m and y-intercept b using x data points"""
    return m * x + b

def run():

    data = np.load('sampleData.npy')
    x = [row[0] for row in data]
    y = [row[1] for row in data]

    m, b, m_steps, b_steps, SE, numIters = gradient_descent(data, start_m = 0, start_b = 0, learningRate = 0.001, numIterations = 1000, tolerance = 0.0001)

    print('m = {:.2f}, b = {:.2f}'.format(m,b))

    #plot sample data points and line of best fit
    plt.figure(1)
    line = [create_line(m, b, i) for i in range(min(x)-1, max(x)+1)]
    plt.plot(line, label = 'y = {:.2f}x + {:.2f}'.format(m, b))
    plt.scatter(x, y)
    plt.xlabel('x')
    plt.ylabel('y')
    plt.show()
    plt.legend()

    #m and b step progression
    fig = plt.figure(2)
    plt.subplot(311)
    plt.plot(m_steps, b_steps)
    plt.xlabel('m')
    plt.ylabel('b')
    fig.subplots_adjust(bottom = 0.2)

    #Lines created using m and b steps throughout gradient descent procedure
    plt.subplot(312)
    plt.scatter(x, y)
    for i, j in zip(m_steps, b_steps):
        line = [create_line(i, j, k) for k in range(int(round(min(x))-1), int(round(max(x)+1)))]
        plt.plot(line)
    plt.xlabel('x')
    plt.ylabel('y')

    #Total squared error vs. number of iterations
    plt.subplot(313)
    plt.plot(SE)
    plt.ylabel('Total Squared Error')
    plt.xlabel('Number of Iterations')

    #Minimum error found on m,b surface plot
    fig = plt.figure(3)
    ax = fig.gca(projection = '3d')

    m_plot = np.arange(m - 10, m + 10, 0.5)
    b_plot = np.arange(b - 10, b + 10, 0.5)
    m_plot, b_plot = np.meshgrid(m_plot, b_plot)
    SE = np.array(total_squared_error(data, m_plot, b_plot))

    #Set axis limits
    ax.set_zlim(np.min(SE), np.max(SE))
    ax.set_xlim(np.min(m_plot), np.max(m_plot))
    ax.set_ylim(np.min(b_plot), np.max(b_plot))

    #Set axis labels
    ax.set_xlabel('m (slope)')
    ax.set_ylabel('b (y-intercept')
    ax.set_zlabel('SE (Total Squared Error')

    ax.plot_wireframe(m_plot, b_plot, SE, color = 'grey')
    ax.scatter(m, b, total_squared_error(data, m, b), s = 200, c = 'r', marker = '*') #, label = 'm = {}, b = {}'.format(m,b))
    ax.text(m + 0.25, b + 0.25, total_squared_error(data, m, b), '(m = {:.2f}, b = {:.2f})'.format(m,b), size = 12, bbox=dict(facecolor='yellow', alpha=0.5))

    plt.show()

if __name__ == '__main__':
    run()


import numpy as np

##### Base Gratient Descent ####
class GradientDescentRegressor:

    def __init__(self,iterations=100,learning_rate=0.01,weights=np.nan,verbose=False,random_state=100):
        '''
            """ Vanilla Gradient Descent Algorithm

            Read more in the :ref: https://github.com/beyond-papers/beyondpapers

            Parameters
            ----------
            iterations : int , default 100
                Set number of iterations to run.
            learning_rate : float , classifier function, default=0.01
                Set learning rate  of the model.
            weights : array , default np.nan
                weights make gradient descent learn incrementally. Calling fit on the model will reuse the weights.
            random_state : int , default 100
                random state can be change for different random weight initialisation.

            """
        '''
        self.iterations,self.learning_rate,self.weights,self.verbose,self.random_state =\
        iterations,learning_rate,weights,verbose , random_state

    def fit(self,X,y):
        np.random.seed(self.random_state)
        num_features = X.shape[1]
        #Initialisations
        iterations,learning_rate,weights=self.iterations,self.learning_rate,self.weights

        ### Intialise Weights if not exist
        if weights is np.nan:
            bias=np.array(0.0)
            feature_weights=np.random.rand(num_features)
            weights = np.hstack([bias,feature_weights]).T
        ### Transfer Weights
        else:
            weights = self.weights

        ### Add bias col for bias
        bias_col = np.ones(X.shape[0]).reshape(X.shape[0],1)
        X = np.hstack([bias_col,X]).T

        for i in range(iterations):
            ypred=np.dot(weights,X)

            error = ypred - y

            sum_of_squared_error = sum(error**2)
            delta_gradient = learning_rate*np.dot(X,error)/X.shape[1]

            weights =weights-delta_gradient

            self.weights = weights

            if self.verbose == True:
                print('Iterarion error {}: {} '.format(i,sum_of_squared_error))

    def predict(self,X):
        bias_col = np.ones(X.shape[0]).reshape(X.shape[0],1)
        X = np.hstack([bias_col,X]).T
        ypred=np.dot(self.weights,X)
        return ypred


# -*- coding: utf-8 -*-
import numpy as np
from batch_gradient_descent import bgd

# define least square estimation function: Y = aX + b
def lse(X, Y):
    cov_mat = np.cov(X, Y, ddof=0)
    a = cov_mat[0][1] / cov_mat[0][0]
    Y_mean = Y.mean()
    X_mean = X.mean()
    b = Y_mean - a * X_mean

    Y_pred = a * X + b
    Rsquare = np.mean((Y_pred - Y_mean) ** 2) / np.var(Y)

    return a, b, Rsquare

# define gradient descent function: Y = aX + b
def gd(X, Y, alpha=0.01, epsilon=1e-32):
    m = len(X)
    a, b, sse2 = 0, 0, 0
    while True:
        loss = a * X + b - Y
        lossT = loss.T
        sse = np.dot(lossT, loss) / (2 * m)
        if abs(sse2 - sse) < epsilon:
            break
        else:
            sse2 = sse

        grad_a = np.dot(lossT, X) / m
        grad_b = np.dot(lossT, np.ones(m)) / m
        a -= alpha * grad_a
        b -= alpha * grad_b
    return a, b

X = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
Y = np.array([0.199, 0.389, 0.580, 0.783, 0.980, 1.177, 1.380, 1.575, 1.771])

print(lse(X, Y))
print(gd(X, Y, 0.05))
print(bgd(X, Y, 0.05, 1e-32, False))


from numba import njit
import numpy as np

def det1(A):
    """Compute the determinants of a series of 1x1 matrices.

    Parameters
    ----------
    A : nx1x1 array_like
        Arrays to compute determinants of

    Returns
    -------
    dets : array of length n
        Matrix determinants
    """
    return A.flatten()

@njit(cache=True)
def det2(A):
    """Compute the determinants of a series of 2x2 matrices.

    Parameters
    ----------
    A : nx2x2 array_like
        Arrays to compute determinants of

    Returns
    -------
    dets : array of length n
        Matrix determinants
    """
    n = np.shape(A)[0]
    dets = np.zeros(n)
    for i in range(n):
        dets[i] = A[i, 0, 0] * A[i, 1, 1] - A[i, 0, 1] * A[i, 1, 0]
    return dets

@njit(cache=True)
def det3(A):
    """Compute the determinants of a series of 3x3 matrices.

    Parameters
    ----------
    A : nx3x3 array_like
        Arrays to compute determinants of

    Returns
    -------
    dets : array of length n
        Matrix determinants
    """
    n = np.shape(A)[0]
    dets = np.zeros(n)
    for i in range(n):
        dets[i] = (
            A[i, 0, 0] * (A[i, 1, 1] * A[i, 2, 2] - A[i, 1, 2] * A[i, 2, 1])
            - A[i, 0, 1] * (A[i, 1, 0] * A[i, 2, 2] - A[i, 1, 2] * A[i, 2, 0])
            + A[i, 0, 2] * (A[i, 1, 0] * A[i, 2, 1] - A[i, 1, 1] * A[i, 2, 0])
        )
    return dets

def inv1(A):
    """Compute the inverses of a series of 1x1 matrices.

    Parameters
    ----------
    A : nx1x1 array_like
        Arrays to compute inverses of

    Returns
    -------
    dets : nx1x1 array
        Matrix inverses
    """
    return 1.0 / A

@njit(cache=True)
def inv2(A):
    """Compute the inverses of a series of 2x2 matrices.

    Parameters
    ----------
    A : nx2x2 array_like
        Arrays to compute inverses of

    Returns
    -------
    dets : nx2x2 array
        Matrix inverses
    """
    invdets = 1.0 / det2(A)
    n = len(invdets)
    invs = np.zeros((n, 2, 2))
    for i in range(n):
        invs[i, 0, 0] = invdets[i] * A[i, 1, 1]
        invs[i, 1, 1] = invdets[i] * A[i, 0, 0]
        invs[i, 0, 1] = -invdets[i] * A[i, 0, 1]
        invs[i, 1, 0] = -invdets[i] * A[i, 1, 0]
    return invs

@njit(cache=True)
def inv3(A):
    """Compute the inverses of a series of 3x3 matrices.

    Parameters
    ----------
    A : nx3x3 array_like
        Arrays to compute inverses of

    Returns
    -------
    dets : nx3x3 array
        Matrix inverses
    """
    invdets = 1.0 / det3(A)
    n = len(invdets)
    invs = np.zeros((n, 3, 3))
    for i in range(n):
        invs[i, 0, 0] = invdets[i] * (A[i, 1, 1] * A[i, 2, 2] - A[i, 1, 2] * A[i, 2, 1])
        invs[i, 0, 1] = -invdets[i] * (A[i, 0, 1] * A[i, 2, 2] - A[i, 0, 2] * A[i, 2, 1])
        invs[i, 0, 2] = invdets[i] * (A[i, 0, 1] * A[i, 1, 2] - A[i, 0, 2] * A[i, 1, 1])
        invs[i, 1, 0] = -invdets[i] * (A[i, 1, 0] * A[i, 2, 2] - A[i, 1, 2] * A[i, 2, 0])
        invs[i, 1, 1] = invdets[i] * (A[i, 0, 0] * A[i, 2, 2] - A[i, 0, 2] * A[i, 2, 0])
        invs[i, 1, 2] = -invdets[i] * (A[i, 0, 0] * A[i, 1, 2] - A[i, 0, 2] * A[i, 1, 0])
        invs[i, 2, 0] = invdets[i] * (A[i, 1, 0] * A[i, 2, 1] - A[i, 1, 1] * A[i, 2, 0])
        invs[i, 2, 1] = -invdets[i] * (A[i, 0, 0] * A[i, 2, 1] - A[i, 0, 1] * A[i, 2, 0])
        invs[i, 2, 2] = invdets[i] * (A[i, 0, 0] * A[i, 1, 1] - A[i, 0, 1] * A[i, 1, 0])
    return invs

if __name__ == "__main__":
    np.random.seed(0)
    A2 = np.random.rand(1000, 2, 2)
    A3 = np.random.rand(1000, 3, 3)
    dets2 = det2(A2)
    dets3 = det3(A3)
    invs2 = inv2(A2)
    invs3 = inv3(A3)
    # Check error between this implementation and numpy, use hybrid error measure (f_ref - f_test)/(f_ref + 1) which
    # measures the relative error for large numbers and absolute error for small numbers
    errors = {}
    errors["det2"] = np.linalg.norm((dets2 - np.linalg.det(A2)) / (dets2 + 1.0))
    errors["det3"] = np.linalg.norm((dets3 - np.linalg.det(A3)) / (dets3 + 1.0))
    errors["inv2"] = np.linalg.norm((invs2 - np.linalg.inv(A2)) / (invs2 + 1.0))
    errors["inv3"] = np.linalg.norm((invs3 - np.linalg.inv(A3)) / (invs3 + 1.0))
    for name, error in errors.items():
        print(f"Error norm in {name} = {error:.03e}")


# ========================================
# [] File Name : map.py
#
# [] Creation Date : February 2018
#
# [] Created By : Ali Gholami (aligholami7596@gmail.com)
# ========================================

"""
    Implementation of a linear transformation on a multivariate Gaussian distribution.
"""
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from scipy.stats import multivariate_normal

color_map=[cm.winter,cm.autumn]

MEAN_VECTOR = np.array([0,0])

COV_MATRIX = np.array([
    [1, 0],
    [0, 1]
])

# Transformation configuration
A_T = np.array([
    [-5, 5],
    [1, 1]
])

B_T = np.array([0.5, 1])

# Linear transformation
def linear_map(transformation_matrix, bias_matrix, data):
    '''
        Performs a matrix multiplication as a linear map
        also translates the new mean vector and covariance matrix
    '''
    Y = np.matmul(transformation_matrix, np.transpose(data)) + bias_matrix

    return Y

# Finds the new mean and covariance matrix
def transform_cov_mean(prev_mean, prev_cov, transformation_matrix, bias_matrix):
    '''
        Find the new mean as A*prev_mean + B
        Find the new cov as A^(T) prev_cov A
    '''
    return np.transpose(np.matmul(transformation_matrix, np.transpose(prev_mean))) + bias_matrix, np.matmul(np.matmul(np.transpose(transformation_matrix), prev_cov), transformation_matrix)

# Function to plot each distribution
def plot_dists(samples, mean_vec, cov_mat, i):
    '''
        This will plot normal surface for each sample set
    '''

    x = samples[:, 0]
    y = samples[:, 1]
    x, y = np.meshgrid(x, y)
    xy = np.column_stack([x.flat, y.flat])

    # Use multivariate normal as the result for Z
    z = multivariate_normal.pdf(xy, mean=mean_vec.tolist(), cov=cov_mat.tolist())
    z = z.reshape(x.shape)

    # Plot
    FIG = plt.figure()
    AX = Axes3D(FIG)

    SURF = AX.plot_surface(x, y, z, cmap=color_map[i])
    FIG.colorbar(SURF, shrink=0.5, aspect=5)

    plt.show()

# Generate Gaussian samples and plot the results
GAUSSIAN_SAMPLES = np.random.multivariate_normal(MEAN_VECTOR, COV_MATRIX, size=500)
plot_dists(GAUSSIAN_SAMPLES, MEAN_VECTOR, COV_MATRIX, 1)

# Transform the Gaussian samples
TRANSFORMED_SAMPLES = np.array([])
for point in GAUSSIAN_SAMPLES:
    TRANSFORMED_SAMPLES = np.append(TRANSFORMED_SAMPLES, linear_map(data=point, transformation_matrix=A_T, bias_matrix=B_T))

# Reshape the transformed samples like GAUSSIAN SAMPLES
TRANSFORMED_SAMPLES = TRANSFORMED_SAMPLES.reshape((500, 2))

# Plot the transformed samples
new_mean, new_cov = transform_cov_mean(MEAN_VECTOR, COV_MATRIX, A_T, B_T)
plot_dists(TRANSFORMED_SAMPLES, new_mean, new_cov, 0)


# SmaEmaHelperFunctions.py

#################################################################

## Libraries

import matplotlib.pyplot as plt
import numpy as np

################################################################

## Exponential Moving Average (EMA) Functions

# Calculate the alpha value for a desired period.
def calculateAlpha(ema_period):
    alpha = 2.0 / (ema_period + 1)
    return alpha

# Returns the denominator
def getDenominator(number_of_terms):
    # bottom = 1 + (1-a) + (1-a)^2 + (1-a)^3 + ...
    a = calculateAlpha(number_of_terms)
    i = 0
    total = 0
    while i < number_of_terms:
        term = (1-a)**i
        total = total + term
        i = i + 1
    return total

# Returns the numerator
def getNumerator(price_data, price_data_index, number_of_terms):
    # top = p1 + (1-a)*p2 + (1-a)^2*p3 + (1-a)^3*p4 + ...
    a = calculateAlpha(number_of_terms)
    i = 0
    total = 0
    while i < number_of_terms:
        price = price_data[price_data_index - i]
        cof = (1-a)**i
        term = price * cof
        total = total + term
        i = i + 1
    return total

# Returns a single Exponential Moving Average value.
def getEMA(price_data, price_data_index, number_of_terms):
    if (number_of_terms - price_data_index) > 1:
        # There are too many terms for the given index.
        return 0
    else:
        top = getNumerator(price_data, price_data_index, number_of_terms)
        bottom = getDenominator(number_of_terms)
        EMA = np.array([top / bottom])
        return EMA

# Returns a list of all EMA values.
def getEMAdataset(price_data, number_of_terms):
    ema_data = np.zeros(np.size(price_data))
    i = 0
    while i < np.size(price_data):
        datum = getEMA(price_data, i, number_of_terms)
        ema_data[i] = datum
        i = i + 1
    return ema_data

################################################################

# SMA Functions

# Calculates the average given a list of numbers.
def getAvg(data):
    total = 0
    i = 0
    while i < len(data):
        total = total + data[i]
        i = i + 1
    # Catch divide by zero errors.
    if i == 0:
        return 0
    else:
        return total / i

# Produces a subset list given a list of numbers, the final
#desired index, and the desired subset length.
def getMovingList(data, index, length):
    listEnd = index + 1
    listStart = index - length + 1
    result = data[listStart:listEnd]
    # Note that when index - length < -1, then the result will be [] (empty)
    return result

# Calculates a single average.
def getSingleSMA(data, index, sma_period):
    movingList = getMovingList(data, index, sma_period)
    average = getAvg(movingList)
    return average

# Calculates the entire list of averages.
def getSMAlist(data, sma_period):
    smaList = []
    i = 0
    while i < len(data):
        value = getSingleSMA(data, i, sma_period)
        smaList.append(value)
        i = i + 1
    return smaList

################################################################

# Plotting Functions

# Generates a sine wave.
def generateSineWave(period, amplitude, sigma, end):
    # Equations
    alpha = amplitude / 2.0
    beta = 2.0 * np.pi / period
    frequency = 1.0 / period
    x = np.arange(end + 1)

    # Formula
    y = alpha * np.sin(beta * x) + sigma
    return y


import numpy as np

from numpy import ndarray as Array

def MSE(error: Array) -> float:
    return np.sum(error**2) / (2 * len(error))


import sys
import numpy as np
from matplotlib import cm
"""
A file for all common functions used in project 1
"""

def FrankeFunction(x,y):
	term1 = 0.75*np.exp(-(0.25*(9*x-2)**2) - 0.25*((9*y-2)**2))
	term2 = 0.75*np.exp(-((9*x+1)**2)/49.0 - 0.1*(9*y+1))
	term3 = 0.5*np.exp(-(9*x-7)**2/4.0 - 0.25*((9*y-3)**2))
	term4 = -0.2*np.exp(-(9*x-4)**2 - (9*y-7)**2)
	return term1 + term2 + term3 + term4

def MSE(y, y_tilde):
	"""
	Function for computing mean squared error.
	Input is y: analytical solution, y_tilde: computed solution.
	"""
	return np.sum((y-y_tilde)**2)/y.size

def R2_Score(y, y_tilde):
	"""
	Function for computing the R2 score.
	Input is y: analytical solution, y_tilde: computed solution.
	"""

	return 1 - np.sum((y[:-2]-y_tilde[:-2])**2)/np.sum((y[:-2]-np.average(y))**2)

def create_X(x, y, n = 5):
	"""
	Function for creating a X-matrix with rows [1, x, y, x^2, xy, xy^2 , etc.]
	Input is x and y mesh or raveled mesh, keyword agruments n is the degree of the polinomial you want to fit.
	"""
	if len(x.shape) > 1:
		x = np.ravel(x)
		y = np.ravel(y)

	N = len(x)
	l = int((n+1)*(n+2)/2)		# Number of elements in beta
	X = np.ones((N,l))

	for i in range(1,n+1):
		q = int((i)*(i+1)/2)
		for k in range(i+1):
			X[:,q+k] = (x**(i-k))*(y**k)

	return X

def plot_surface(x, y, z, title = "", show = False, cmap=cm.coolwarm, figsize = None):
	"""
	Function to plot surfaces of z, given an x and y.
	Input: x, y, z (NxN'Modeler' matrices), and a title (string)
	"""

	from mpl_toolkits.mplot3d import Axes3D
	import matplotlib.pyplot as plt
	from matplotlib.ticker import LinearLocator, FormatStrFormatter

	if figsize:
		fig = plt.figure(figsize = figsize)
	else:
		fig = plt.figure()

	ax = fig.gca(projection='3d')

	# Plot the surface.of the best fit

	surf = ax.plot_surface(x, y, z, cmap=cmap,
				   linewidth=0, antialiased=False)

	# Customize the z axis automatically
	z_min = np.min(z)
	z_min = z_min*1.01 if z_min < 0 else z_min*.99
	z_max = np.max(z)
	z_max = z_max*1.01 if z_max > 0 else z_max*.99

	ax.set_zlim(z_min, z_max)
	ax.zaxis.set_major_locator(LinearLocator(10))
	ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
	ax.view_init(azim=20,elev=45)
	# Add a color bar which maps values to colors.
	fig.colorbar(surf, shrink=0.5, aspect=5)
	ax.set_title(title)

	if show:
		plt.show()

	return fig, ax ,surf

def train_test_data(x_,y_,z_,i):
	"""
	Takes in x,y and z arrays, and a array with random indesies iself.
	returns learning arrays for x, y and z with (N-len(i)) dimetions
	and test data with length (len(i))
	"""
	x_learn=np.delete(x_,i)
	y_learn=np.delete(y_,i)
	z_learn=np.delete(z_,i)
	x_test=np.take(x_,i)
	y_test=np.take(y_,i)
	z_test=np.take(z_,i)

	return x_learn,y_learn,z_learn,x_test,y_test,z_test

def K_fold(x,y,z,k,alpha,model,m=5, ret_std = False):
	"""Function to who calculate the average MSE and R2 using k-fold.
	Takes in x,y and z varibles for a dataset, k number of folds, alpha and which method beta shall use. (OLS,Ridge or Lasso)
	Returns average MSE and average R2"""
	print(m)
	if len(x.shape) > 1:
		x = np.ravel(x)
		y = np.ravel(y)
		z = np.ravel(z)
	n=len(x)
	n_k=int(n/k)
	if n_k*k!=n:
		print("k needs to be a multiple of ", n,k)
	i=np.arange(n)
	np.random.shuffle(i)

	MSE_=0
	R2_=0
	Variance_=0
	Bias_=0
	betas = np.zeros((k,int((m+1)*(m+2)/2)))
	for t in range(k):
		x_,y_,z_,x_test,y_test,z_test=train_test_data(x,y,z,i[t*n_k:(t+1)*n_k])
		X= create_X(x_,y_,n=m)
		X_test= create_X(x_test,y_test,n=m)

		model.fit(X,z_)
		betas[t] = model.beta
		z_predict=model.predict(X_test)

		MSE_+=MSE(z_test,z_predict)
		R2_+=R2_Score(z_test,z_predict)
		Bias_+=bias(z_test,z_predict)
		Variance_+=variance(z_predict)

	return (MSE_/k, R2_/k, Bias_/k, Variance_/k, np.std(betas, axis = 0), np.mean(betas, axis = 0))

def variance(y_tilde):
	"""
	Calculates the variance of the predicted values y_tilde.
	"""
	return np.sum((y_tilde - np.mean(y_tilde))**2)/np.size(y_tilde)

def bias(y, y_tilde):
	"""
	Calculates the bias of the predicted values y_tilde compared to
	the actual data y.
	"""
	return np.sum((y - np.mean(y_tilde))**2)/np.size(y_tilde)

def update_progress(job_title, progress):
	"""
	Shows the progress of an for-loop.
	"""
	length = 20 # modify this to change the length
	block = int(round(length*progress))
	msg = "\r{0}: [{1}] {2}%".format(job_title, "#"*block + "-"*(length-block), round(progress*100, 2))
	if progress >= 1: msg += " DONE\r\n"
	sys.stdout.write(msg)
	sys.stdout.flush()

def savefigure(name, figure = "gcf"):
	"""
	Function for saving figures as a .tex-file for easier integration with latex.
	"""
	try:
		from matplotlib2tikz import save as tikz_save
		tikz_save(name.replace(" ", "_") + ".tex", figure = figure, figureheight='\\figureheight', figurewidth='\\figurewidth')
	except ImportError:
		print("Please install matplotlib2tikz to save figure as a .tex-file.")
		import matplotlib.pyplot as plt
		if figure == "gcf":
			plt.savefig(name+".pdf")
		else:
			fig.savefig(name+".pdf")