'''
We are trying to solve an Ordinary Differential Equation
using the Euler's method. For this example we are taking
the example of:

dy/dt = -ky : IVP => y = y0 for t = 0

We will solve this without using the scipy
odeint package
'''

# import the basic libraries
import math
import numpy as np                # used for numerical analysis
import matplotlib.pyplot as plt   # used for plotting Graphs

# initialize the problem
x0 = -np.pi/2
t =0
x = x0
#k ,t , y =  1, 0.0, 5.0
#y0 = y
tt, yy = [], []
tf = 2                         # final value for the time

dt = 0.1                         # step size
#exact_sol = []
# describing the equation in function

def f(t,x):
    return -math.sin(t+x)

# now thw Euler method
exact_sol = []
while t<= tf:
    tt.append(t)
    yy.append(x)
    x = x+dt*f(t,x)
    exact = -math.asin((1-t**2)/(1+t**2)) - t
    exact_sol.append(exact)
    #exact = -math.asin((1-t**2)/(1+t**2))
    #exact_sol.append(exact)

    t += dt

# finding the Exact Result

#time
exact = -math.asin((1-t**2)/(1+t**2)) - t

#print(exact)
# now the Plotting part
print(len(tt))

plt.plot(tt,yy,'o', label="dt=%.4f"%(dt))
plt.plot(tt, exact_sol,'k', label="Exact Solution")
plt.xlabel("time")
plt.ylabel('y')
plt.legend(loc='best')

# plotting the absolute error

plt.legend(loc='best')

plt.title("ODE by Euler Method")
plt.savefig('decay-euler1.png')


"""
A Monte Carlo simulation to compute size distortion in hypothesis testing in an OLS context
"""

import numpy as np
import matplotlib as plt

# generate artificial dataset (could use real data if available)
np.random.seed(0)
n = 4  # the higher is n, the less wrong we are about using +/-1.96 as critical values,
# the closer is the empirical size to 5%
sigma_u = 30
beta = 7
X = np.random.normal(0, 10, n).reshape((n, 1))
u = np.random.normal(0, sigma_u, n).reshape((n, 1))
y = X.dot(beta) + u

# monte carlo simulation
beta_zero = 5
XX_inv = np.linalg.inv(np.transpose(X).dot(X))
A = XX_inv.dot(np.transpose(X))
b = X.dot(beta_zero)
number_of_rejections = 0
reps = 100000
for m in range(1, reps + 1):
    u_m = np.random.normal(0, sigma_u, n).reshape((n, 1))
    y_m = b + u_m
    betahat_m = A.dot(y_m)
    uhat = y_m - X.dot(betahat_m)
    s2_m = np.transpose(uhat).dot(uhat) / (n - 1)
    t_m = (betahat_m - beta_zero) / np.sqrt(s2_m * XX_inv)
    number_of_rejections += abs(t_m) > 1.96
empirical_size = number_of_rejections / reps
print('The empirical size is %.10f' % empirical_size)
print('The size distortion is |theoretical-empirical|=|5%%-%.4f%%|=%.4f' % (empirical_size * 100,
                                                                            abs(5 - empirical_size * 100)))


import sklearn.datasets as dt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
from matplotlib import pyplot as plt

def pca(data):
	"""
	Principal components analysis

	Parameters:
		data - numpy array with the data

	Return:
		proj_data - data in the new coordinates space
		eig_vals - eigen values, ordered in descending order
		eig_vecs - eigen vectors associated with the eig_vals
	"""
	#standardization
	norm = (data - np.mean(data, axis=0))/np.std(data, axis=0)

	#covariance matrix
	cov_m = np.dot(norm.transpose(),norm)/(len(norm)-1)

	eig_vals, eig_vecs = np.linalg.eig(cov_m)

	#order eig values and eig vecs in descending order
	order = np.argsort(-eig_vals)
	eig_vals = eig_vals[order]
	eig_vecs = eig_vecs[:,order]

	#project data into new coordinates system
	proj_data = np.dot(norm,eig_vecs)

	return proj_data, eig_vals, eig_vecs

def main():

	#load iris dataset
	data, target = dt.load_iris(return_X_y=True)
	data = np.asarray(data)

	#get pca of data
	proj_data, eig_vals, eig_vecs = pca(data)

	#component contribution analysis
	eig_vals_norm = eig_vals/sum(eig_vals)

	plt.bar(range(1,5), eig_vals_norm, alpha=0.5, align='center', label='Contribuição individual')
	plt.step(range(1,5), np.cumsum(eig_vals_norm), where='mid', label='Contribuição acumulada')
	plt.legend(loc='best')
	plt.xlabel("Componentes")
	plt.ylabel("Razão de variância explicada")
	plt.title("Contribuição das componentes")
	plt.tight_layout()
	plt.show()

	#plot projection with the 2 first components
	fig, ax = plt.subplots()

	color = ['#1b9e77', '#d95f02', '#7570b3']
	for idx, val in enumerate(np.unique(target)):
	    index = np.argwhere(target==val)
	    ax.scatter(proj_data[index,0], proj_data[index,1], c = color[idx], label=val)

	ax.legend()
	plt.title("Projeção sobre as duas principais componentes")
	plt.tight_layout()
	plt.show()

	#Plot projection with the 3 first components
	fig = plt.figure()
	ax = fig.add_subplot(111, projection='3d')

	color = ['#1b9e77', '#d95f02', '#7570b3']
	for idx, val in enumerate(np.unique(target)):
	    index = np.argwhere(target==val)
	    ax.scatter(proj_data[index,0], proj_data[index,2], proj_data[index,1], c = color[idx], label=val)

	ax.legend()
	plt.title("Projeção sobre as três principais componentes")
	plt.show()

if __name__ == "__main__":
    main()


import numpy as np
import matplotlib.pyplot as plt

def _marchenko_pastur(x,gamma,sigma=1.0):
    y=1/gamma
    largest_eigenval = np.power(sigma*(1 + np.sqrt(1/gamma)),2)
    smallest_eigenval= np.power(sigma*(1 - np.sqrt(1/gamma)),2)
    mp = (1/(2*np.pi*sigma*sigma*x*y))*np.sqrt((largest_eigenval - x)*(x - smallest_eigenval))*(0 if (x>largest_eigenval or x<smallest_eigenval) else 1)
    return mp

def marchenko_pastur(n,p,upper_bound=3,spacing=2000,sigma=1.0):
    x_mp_dist = np.linspace(0,upper_bound,spacing)
    y_mp_dist = [_marchenko_pastur(x,n/p,sigma=sigma) for x in x_mp_dist]

    return x_mp_dist,y_mp_dist

def eigenval_histogram(X,bins=100):
    p,n = X.shape
    S = (1./n) * np.dot(X,X.T)
    u,_ = np.linalg.eig(S)

    hist_heights, hist_bins = np.histogram(u,bins=bins,density=True)
    x = hist_bins[:-1]
    y = hist_heights
    col_widths = hist_bins[-1]/len(x)

    return x,y,col_widths

def transform_to_zero_mean_and_unit_std(X):
    m,n = X.shape
    means = np.mean(X,axis=1).reshape(m,1)
    mean_adjusted_X = np.subtract(X,means)
    stds = np.std(mean_adjusted_X,axis=1).reshape(m,1)
    mean_and_std_adjusted_X = np.divide(mean_adjusted_X,stds)
    return mean_and_std_adjusted_X

def marchenko_pastur_comparison(X,axis,histogram_bins=100,dist_upper_bound=3,dist_spacing=2000,sigma=1.0,transform=True):
    p,n = X.shape
    x_mp, y_mp = marchenko_pastur(n,p,upper_bound=dist_upper_bound,spacing=dist_spacing,sigma=sigma)

    if transform:
        X_ = transform_to_zero_mean_and_unit_std(X)
        x,y,w = eigenval_histogram(X_,bins=histogram_bins)
    else:
        x,y,w = eigenval_histogram(X,bins=histogram_bins)

    axis.bar(x,y,width=w,color='b')
    axis.plot(x_mp,y_mp,color='r')
    axis.set_xlabel(r'$\lambda$')
    axis.set_title('Marchenko-Pastur Distribution Comparison')


import numpy as np
import matplotlib.pyplot as plt
import methods as mt

#Comparaison à un pas donné des valeurs y calculées et des valeurs de la solution à ses points communs avec y. Retourne le maximum des écarts.

def step_error(y,a,b,sol):
	h=(b-a)/len(y)
	if type(y[0]) is np.ndarray:
		sol_values=np.zeros((len(y),y[0].shape[0]))
	else:
		sol_values=np.zeros(len(y))
	for i in range(len(y)):
		sol_values[i]=sol(a+i*h)
	if type(y[0]) is np.ndarray:
		diff_values=[np.linalg.norm(y[i]-sol_values[i]) for i in range(len(y))]
	else:
		diff_values=[abs(y[i]-sol_values[i]) for i in range(len(y))]
	diff=max(diff_values)
	return diff

#Tabulation des écarts maximums pour différents pas (en croissance logarithmique), sur un intervalle [a,b], construisant le tableau de valeurs avec la méthode meth. f et y0 sont nécessaires pour l'exécution de meth_n_step.

def errors_over_n(sol,f,meth,y0,a,b):
	space=[10,20,30,40,50,60,70,80,90,100,200,300,400,500,600,700,800,900,1000]
	diffs=np.zeros(len(space))
	for i in range(len(diffs)):
		h=(b-a)/space[i]
		y=mt.meth_n_step(y0,a,space[i],h,f,meth)
		diffs[i]=step_error(y,a,b,sol)
	return diffs

#Création de graphes d'erreurs pour les quatre méthodes sur le cas défini par sol, f, y0, et sur l'intervalle [a,b].

def plot_errors(sol,f,y0,a,b):
	diffs_euler=errors_over_n(sol,f,mt.step_euler,y0,a,b)
	diffs_middle=errors_over_n(sol,f,mt.step_middle,y0,a,b)
	diffs_heun=errors_over_n(sol,f,mt.step_heun,y0,a,b)
	diffs_rk4=errors_over_n(sol,f,mt.step_rk4,y0,a,b)

	plt.clf()
	X=[10,20,30,40,50,60,70,80,90,100,200,300,400,500,600,700,800,900,1000]
	plt.plot(X,diffs_euler,label="Méthode d'Euler")
	plt.plot(X,diffs_middle,label="Méthode du point milieu")
	plt.plot(X,diffs_heun,label="Méthode de Heun")
	plt.plot(X,diffs_rk4,label="Méthode de Runge-Kutta d'ordre 4")
	plt.legend()

	plt.xlabel("Pas")
	plt.ylabel("Erreur relative maximale")
	plt.title("Graphe de comparaison des erreurs entre les quatre méthodes")
	plt.xscale("log")
	plt.grid(True,which="both",linestyle='-.')
	plt.show()

if __name__=='__main__':
	f=lambda t,y: y/(1+t**2)
	sol=lambda t: np.exp(np.arctan(t))
	y0=1
	a=0
	b=5
	plot_errors(sol,f,y0,a,b)


"""Example implementation of the conjugate gradient descent algorithm using a hard coded objective function

    F(X1, X2) = 5X1**2 + X2**2 + 4X1X2 - 14X1 - 6X2 + 20

- At each step the value of X will be updated using
    X_k+1 = X_k + alpha * Pk

    Where pk is the conjugate direction and alpha is the step length
"""
import math
import logging
import numpy as np

logging.basicConfig(level=logging.DEBUG)
log = logging.getLogger(__name__)

class ConjugateGradientDescent:
    def __init__(self, max_iterations=10, hessian_matrix=None, linear_terms=None):
        self.Hessian: np.ndarray = hessian_matrix or np.array([[10, 4], [4, 2]])
        self.LinearCoefficients = linear_terms or np.array([-14, -6])
        self.ConstantTerm = 20
        self.Minimiser = np.array([0, 0])
        self.CurrentIteration = 0
        self.MaxIterations = max_iterations
        self.Epsilon = math.pow(10, -3)  # Stop if magnitude of gradient is less than this value
        self._LastConjugate = None  # Track gradient in current iteration - 1 step

    @property
    def __current_gradient_value(self):
        gradient = self.del_f(self.Minimiser)
        return np.sqrt(gradient.dot(gradient))

    def f(self, xk: np.ndarray, decimal_places=3):
        """Returns the function value calcultaed from the equivalent quadratic form"""
        squared_terms = 0.5 * xk.dot(self.Hessian).dot(xk)
        linear_terms = self.LinearCoefficients.dot(xk)
        total_cost = squared_terms + linear_terms + self.ConstantTerm
        return round(total_cost, decimal_places)

    def del_f(self, xk: np.ndarray) -> np.ndarray:
        gradient_vector = np.matmul(self.Hessian, xk)
        if isinstance(self.LinearCoefficients, np.ndarray):
            gradient_vector = np.add(gradient_vector, self.LinearCoefficients)
        return gradient_vector

    def conjugate_direction(self, gk: np.ndarray):
        if self.CurrentIteration == 0:
            self._LastConjugate = -1 * gk
            return self._LastConjugate
        gk_previous: np.ndarray = self._LastConjugate
        bk = gk.dot(gk) / gk_previous.dot(gk_previous)
        pk = (-1 * gk) + bk*gk_previous
        self._LastConjugate = pk  # We will use this on next iteration as P_k-1
        return pk

    def exact_step_length(self, gk: np.ndarray, pk: np.ndarray, decimal_places=3) -> np.float64:
        """Get the step lengh by minimising f(x + ad_k) wrt to a

        alpha(or lambda) = -gk * pk / pk * A * pk
        """
        numerator = gk.dot(pk)
        denominator = pk.dot(self.Hessian).dot(pk)
        step_length = numerator/denominator
        return round(step_length, decimal_places) * -1

    def _find_next_candidate(self):
        """Get next potential minimum using X_k+1 = X_k + alpha * pk"""
        xk = self.Minimiser
        gk = self.del_f(xk)
        pk = self.conjugate_direction(gk)
        step_length = self.exact_step_length(gk, pk)
        x_k_plus1 = xk + (step_length * pk)
        log.debug(f"X**{self.CurrentIteration} = {xk} - {step_length} * {pk}")
        return x_k_plus1

    def execute(self):
        log.info('Conjugate gradient descent iteration started')
        for k in range(self.MaxIterations):
            log.debug(f"-----------Iteration {k}--------------")
            self.CurrentIteration = k
            self.Minimiser = self._find_next_candidate()
            log.debug(f"------Minimiser = {self.Minimiser}. Gradient = {self.__current_gradient_value}--------\n")
            if self.__current_gradient_value <= self.Epsilon:
                log.warning(f"Iteration stopped at k={k}. Stopping condition reached!")
                break
        log.info(f"------Minimiser = {self.Minimiser}. Gradient = {self.__current_gradient_value}--------")
        log.info('Conjugate gradient descent completed successfully')
        return self.Minimiser, self.f(self.Minimiser), round(self.__current_gradient_value, 3)


import numpy as np
import pandas as pd
from utils import regresiveSustitution, progressiveSustitution, swapRowsSpecial,regresiveSustitutions
from utils import rowOps
from utils import getMultipliers
from utils import swapRows
from utils import isSquared

def crout(A,b):

    pivots = []

    A = np.array(A).astype(float)
    b = np.array(b).astype(float)

    times = A[:, 0].size

    U = np.zeros((times, times))
    L = np.identity(times)
    cont = 0

    for d in range(0, times):
        U[d, d] = 1

    for d in range(0, times): #Etapas
        #Calculo L
        for j in range(d, times):
            sum0 = sum([L[j, s] * U[s, d] for s in range(0, j)])
            L[j, d] = A[j, d] - sum0
        #Calculo U
        for j in range(d+1, times):
            sum1 = sum([L[d, s] * U[s, j] for s in range(0, d)])
            U[d, j] = (A[d, j] - sum1) / L[d, d]
        cont = cont+1
        pivots.append({'status':'Step '+str(cont), 'matrixL': L.copy(), 'matrixU': U.copy()})

    LB = np.concatenate([L, b], axis=1)
    size = LB[:, 0].size

    pro = progressiveSustitution(LB, size)
    pro = np.array(pro).astype(float)

    UB = np.concatenate([U, pro.reshape((U.shape[0], 1))], axis=1)
    size2 = UB[:, 0].size
    reg = regresiveSustitutions(UB, size2 - 1)
    pivots.append({'status':'Results', 'reg': reg})

    return pivots

def showSteps(steps):
    for step in steps:
        try:
            print(pd.DataFrame(step).to_string(index=False, header=False)+"\n")
        except:
            print(step)

def showTable(table):
    result = ""
    if('status' in table[-1]):
        result = table[-1]
        table.pop()
    print(pd.DataFrame(table).to_string(index=False))
    print(result)

if __name__ == "__main__":
    A = [[4, -1, 0, 3],[1, 15.5, 3, 8],[0, -1.3, -4, 1.1],[14, 5, -2, 30]]
    B = [[1],[1],[1],[1]]

    results = crout(A,B)

    for r in range(0,len(results)-1):
        print(results[r]['status'])
        print("matrix L")
        showTable(results[r]['matrixL'])
        print("matrix U")
        showTable(results[r]['matrixU'])

    print("Results")
    print(results[len(results)-1]['reg'])


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Feb  7 18:59:29 2022

@author: Analabha Roy
"""

import numpy as np
from scipy.linalg import lu_factor, lu_solve
A = np.array([[25., 5., 1.],
              [64., 8., 1.],
              [144., 12., 1.]])

b = np.array([106.8,
              177.2,
              279.2])

A_fact, piv = lu_factor(A.copy())

print("Decomposed L Matrix:\n", np.tril(A_fact, k=0))
print("\n\nDecomposed U Matrix:\n", np.triu(A_fact, k=1))

x = lu_solve((A_fact.copy(), piv), b)

print("\nSolution is x =", x)
print("Solution is close?", np.allclose(A @ x, b))

id = np.eye(A.shape[0])
A_inv = np.zeros_like(A)

for i, row in enumerate(id):
    A_inv[:, i] = lu_solve((A_fact.copy(), piv), row)

print("\n\nInverse of matrix is:\n", A_inv)
print("Solution is close?", np.allclose(A @ A_inv, id))


import numpy as np

def gram_schmidt_factorization(matrix):
    matrix = np.array(matrix)
    m, n = matrix.shape
    q = np.zeros((m, n))
    r = np.zeros((n, n))
    matrix = np.transpose(matrix)
    r[0][0] = np.linalg.norm(matrix[0], 2)
    q[:, 0] = matrix[0]/r[0][0]
    for i in range(1, n):
        q[:, i] = matrix[i]
        for j in range(0, i):
            r[j][i] = np.matmul(q[:, j], q[:, i])
            q[:, i] = q[:, i] - (r[j][i] * q[:, j])
        r[i][i] = np.linalg.norm(q[:, i], 2)
        q[:, i] = q[:, i] / r[i][i]
    return q, r

def system_solver(matrix, vector):
    qr_decomposition = gram_schmidt_factorization(matrix)
    q_matrix = qr_decomposition[0]
    r_matrix = qr_decomposition[1]
    redacted_vector = np.matmul(q_matrix.transpose(), np.array(vector).transpose())
    r_matrix_inverse = np.linalg.inv(r_matrix)
    x_vector = np.matmul(r_matrix_inverse, redacted_vector)
    return x_vector

if __name__ == '__main__':
    coefficients_matrix = eval(input("enter A matrix like: [[1, 1, 1], [2, 2, 2], [3, 3, 3]\n"))
    right_hand_side_vector = eval(input("enter b vector like: [1, 1, 1]\n"))
    print(system_solver(coefficients_matrix, right_hand_side_vector))


import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from sklearn import datasets as skdata

#We will use the iris dataset
iris_dataset = skdata.load_iris()
X = iris_dataset.data # (150, 4)
y = iris_dataset.target

#Compute the mean
mu = np.mean(X, axis=0)

#Center the data
B = X - mu

#Compute the covariance matrix
C = np.matmul(B.T, B)/(B.shape[0]) #(4, 150) x (150, 4) => (4, 4)

#Eigen decomposition
S, V = np.linalg.eig(C)

#Select the top 3 dimentions
order = np.argsort(S)[::-1]
W = V[:, order][:, 0:3] #Transformation for projecting X to subspace

#Project our data
Z = np.matmul(B, W) #(150, 3)

#Let's visualize our new feature space
data_split = (Z[np.where(y==0)[0], :], Z[np.where(y==1)[0], :], Z[np.where(y==2)[0], :])
colors = ('blue', 'red', 'green')
labels = ('Setosa', 'Versicolor', 'Virginica')
markers = ('o', '^', '+')

fig = plt.figure()
fig.suptitle('Projected Iris Data')
ax = fig.add_subplot(1, 1, 1, projection='3d')
ax.set_xlabel('PC1')
ax.set_ylabel('PC2')
ax.set_zlabel('PC3')

for z, c, l, m in zip(data_split, colors, labels, markers):
    ax.scatter(z[:, 0], z[:, 1], z[:, 2], c=c, label=l, marker=m)
    ax.legend(loc='upper right')

plt.show()

#Recover our data
X_hat = np.matmul(Z, W.T)+mu
mse = np.mean((X-X_hat)**2) #0.005919048088406607

#Seems like we recovered our data pretty well
#Let's instead choose only two dimentions
W_2 = V[:, 0:2] #Transformation for projecting X to subspace

#Project our data
Z_2 = np.matmul(B, W_2)

X_hat_2 = np.matmul(Z_2, W_2.T)+mu
mse = np.mean((X-X_hat_2)**2) # 0.02534107393239825

#As we reduce more dimentions, we lose more information


import data.warmUpExercise as a
import data.computeCost as cc
import data.gradientDescent as gd
import data.plotData as pd
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm

#============Part 1=================
print('Running warmUpExercise')
print('5x5 Identity Matrix')
print(a.warm())

#==========Part 2===================
print('Plotting Data')
data = np.loadtxt('ex1data1.txt',delimiter =",")
X = data[:,0]
y = data[:,1]
m = len(y)

pd.plotData(X,y)

#================part 3====================
print('Gradient Descent')
X_padded = np.column_stack((np.ones((m,1)),X))
theta = np.zeros((2,1))

iterations = 1500
alpha = 0.01

print('testing functions')
print(cc.computeCost(X_padded,y,theta))
theta = gd.gradientDescent(X_padded,y,theta,alpha,iterations)
print("{:f}, {:f}".format(theta[0,0], theta[1,0]))

#plot linear fit
plt.plot(X,X_padded.dot(theta),'-',label = 'Linear regression')
plt.legend(loc = 'lower right')
plt.draw()

#predicts values for population size of 35000 and 70000
predict1 = np.array([1,3.5]).dot(theta)
print("For population = 35000 we predict a profit of {:f}".format(float(predict1*10000)))
predict2 = np.array([1, 7]).dot(theta)
print('For population = 70,000, we predict a profit of {:f}'.format( float(predict2*10000) ))

#================ part 4============================
print("j(theta_0,theta1)")
theta0_vals = np.linspace(-10,10,100)
theta1_vals = np.linspace(-1,4,100)

J_vals = np.zeros((len(theta0_vals),len(theta1_vals)))
for i in range(len(theta0_vals)):
    for j in range(len(theta1_vals)):
        t = [[theta0_vals[i]],[theta1_vals[j]]]
        J_vals[i,j] = cc.computeCost(X_padded,y,t)

J_vals = np.transpose(J_vals)
fig = plt.figure()
ax = fig.gca(projection='3d')
theta0_vals, theta1_vals = np.meshgrid(theta0_vals, theta1_vals) # necessary for 3D graph
surf = ax.plot_surface(theta0_vals, theta1_vals, J_vals, cmap=cm.coolwarm, rstride=2, cstride=2)
fig.colorbar(surf)
plt.xlabel('theta_0')
plt.ylabel('theta_1')

fig = plt.figure()
ax = fig.add_subplot(111)

cset = plt.contour(theta0_vals, theta1_vals, J_vals, np.logspace(-2, 3, 20), cmap=cm.coolwarm)
fig.colorbar(cset)
plt.xlabel('theta_0')
plt.ylabel('theta_1')
plt.plot(theta[0,0], theta[1,0], 'rx', markersize=10, linewidth=2)
plt.show()


# -*- coding: utf-8 -*-
"""
Created on Wed Jun 28 7:33:03 2019

@author: Parikshith.H
"""

import numpy as np

a = np.array([[1, 2],
            [3, 4]])
b = np.array([[4, 3],
            [2, 1]])

# add arrays
print ("Array sum:\n", a + b)
# =============================================================================
# #output:
# Array sum:
#  [[5 5]
#  [5 5]]
# =============================================================================

# multiply arrays (elementwise multiplication)
print ("Array multiplication:\n", a*b)
# =============================================================================
# #output:
# Array multiplication:
#  [[4 6]
#  [6 4]]
# =============================================================================

# matrix multiplication
print ("Matrix multiplication:\n", a.dot(b))
# =============================================================================
# #output:
# Matrix multiplication:
#  [[ 8  5]
#  [20 13]]
# =============================================================================


#!/usr/bin/env python
# coding: utf-8

# In[112]:

import numpy as np
import math

# In[ ]:

# # Criando Array

# In[6]:

#arrays podem ser mostrados como listas ou listas de listas e podem ser criados como listas. Quando criando um array nós
#passamos uma lista como um argumento numpy array

a = np.array(['1','2','3'])
print(a)
#pode imprimir o número de dimensões de uma lista usando o atributo "ndim"
print(a.ndim)

# In[4]:

#se inserirmos uma lista de listas, criamos então um array multi dimensional, por exemplo uma matrix
b = np.array([[1,2,3], [4,5,6]])
b
b.ndim

# In[5]:

#podemos imprimir o comprimento do array usando o atributo "shape", que retorna uma tupla, no caso = 2 linhas 3 colunas
b.shape

# In[11]:

#verificar o tipo de dados de um array "dtype"
print(a.dtype)
print(b.dtype)

# In[17]:

#floats também são aceitos em arrays numpy
c = np.array([1.1, 5, 2.8])
c.dtype.name
c
#ele converte automaticamente o 5 que era inteiro para um numero float sem perder precisão
#ele tenta manter o tipo do dado homogêneo

# In[18]:

#as vezes sabemos o formato do array que queremos criar mas não sabemos o que colocar nele, o numpy oferece
#funções para criar arrays com dados iniciais, tipo 0 e 1

#criando dois arrays com o mesmo formato potém com dados diferentes
d = np.zeros((2,3))
print(d)

e = np.ones((2,3))
print(e)

# In[21]:

#criando um array com números aleatórios
np.random.randint(2,3)

# In[24]:

#ainda da pra criar uma sequência de números com a função arange().
#o primeiro argumento é o número de partida, o segundo argumento é o número de parada e
#o terceiro argumento é a diferença entre os números, o "pulo"

#criando um array de números pares de 10 (incluso) até 50 (excluso)
f = np.arange(10, 50, 2) #começa no dez (incluso) e termina no 50 (sem contar o 50), pulando de 2 em 2
print(f)

# In[ ]:

#se quisermos criar uma sequencia de floats, podemos usar o linspace(). nessa função,
#o terceiro argumento não é a diferença entre um número e o outro (o pulo) e sim a quantidade de itens que queremos criar
np.linspace(0, 2, 15) #me gere 15 números entre 0 (incluso) e 2 (excluso)

# # Operações com arrays

# In[32]:

#da pra fazer manipulações matematicas (adição, subtração, divisão...) assim como usar arrays booleanos (true, false)
#da pra manipular matrizes como produto, transpor, inverter e tal
a = np.array([10, 20, 30, 40])
b = np.array([1, 2, 3, 4])

#a menos b
c = a - b
print(c)

#a vezes b
d = a * b
print(d)

# In[11]:

#com operações aritiméticas podemos converter o valor dos dados, tipo temperatura e distância

far = np.array([32, -16, 106, -30])

#formua de conversão
#((°F - 32) / 1.8)

cel = ((far - 32) / 1.8)
cel

# In[13]:

#usar booleano para checar se uma temperatura de um array é maior que 20°C
#vai retornar True se for verdadeiro ou False se for falso
cel > 20

# In[14]:

#usar módulo para checar se um número do array é par
cel % 2 == 0

# In[39]:

#numpy suporta manipulação de matrizes
#produto de matriz

A = np.array([[1,2], [3,4]])
B = np.array([[1,2], [3,4]])

#A = np.array([[a, b], [c, d]])
#B = np.array([[e, f], [g, h]])
# o processo é: a*e + b*g, a*f + b*h
#               c*e + d*g, c*f + d*h

soma1 = 1*1 + 2*3, 1*2 + 2*4

soma2 = 3*1 + 4*3, 3*2 + 4*4

print(A*B)
print('---')
print(A@B)
print('---')
print(soma1)
print('---')
print(soma2)

# In[40]:

#para ver o formato da matriz podemos fazer o .shape
A.shape
#(2, 2) --> duas linhas duas colunas

# In[45]:

#quando manipulando arrays de data types diferentes, ele vai colocar no final o resultado data type mais geral que existe,
#isso é chamado upcasting

#array integers
ar1 = np.array([[2, 4], [6, 8]])
#array float
ar2 = np.array([[2.5, 4.5], [6.5, 8.5]])
#somando os arrays
ar3 = ar1 + ar2
print(ar3)
print(ar3.dtype)

# In[46]:

#numpy tem funções como max, min, sum, mean
print(ar3.mean())
print(ar3.max())
print(ar3.min())
print(ar3.sum())

# In[48]:

#com arrays multidimensionais, podemos fazer a mesma coisa com cada linha e/ou coluna
#criando um array de 15 elementos de 1 a 15 com dimensão 3x5
quin = np.arange(1, 16, 1).reshape(3, 5)
quin

# # indexação, slicing e iteração

# In[49]:

#um array unidimensional funciona quase que como uma lista, para pegar um elemento usamos o [x]
ar = np.array([13,2,56,6,8,1,2])
ar[5]

# In[53]:

#já para um array multidimensional, usamos um index integer
ar2 = np.array([[2,3], [5,4], [1, 9], [2,5]])
ar2

# In[58]:

#para pegar o elemento, precisamos inserir o número da linha e o segundo o da coluna [x, y]
#lemrbando que em python começa no 0
ar2[3,0]

# In[75]:

#para pegar mais de um elemento podemos colocá-los direto numa lista usando a função doa rray
ar3 = np.array([ar2[0,0], ar2[1,1], ar2[2,0], ar2[3,1]])
print(ar3)
print('---')
#ou
print(ar2[[0,1,2,3], [0,1,0,1]]) #primeiro diz as linhas e depois quais colunas

# ## Boolean indexing

# In[78]:

#para achar elementos maiores que 3
print(ar2 > 3)
#retorna um array com verdadeiro ou falso

# ## Slicing

# In[81]:

#slicing é uma forma de criar sub-arrays com base num array original
#para um array unidimensional, ele funciona quase como uma lista
#para fazer o slice, usa-se o sinal de pois pontos : . por exemplo, se colocarmos o :5 no index, nós teriamos
# todos os elementos de 0 a 5 excluindo o 5
ar = np.array([1,2,3,4,5,6,7,8,9,10])
print(ar[:5])

# In[82]:

#daí se colocar dois números com os dois pontos, teríamos todos os elementos de x a y
#[3:8] todos os elementos de 3(incluso) a 8 (excluso)
print(ar[3:8])

# In[92]:

#agora para arrays multimensionais
ar2 = np.array([[1,2,3,4], [5,6,7,8], [9, 10, 11,12]])
print(ar2)

# In[87]:

#se adicionarmos [:2] teriamos todos os elemntos da coluna zero e da coluna um
print(ar2[:2])
#ou seja, ele tráz as linhas com todos os elementos e não os elementos individuais

# In[88]:

#se adicionar dois argumentos ar2[:2, 1:3] retorna as duas primeiras linhas e os elementos de 1 a 3 (excluso)
print(ar2[:2, 1:3])

# In[94]:

#assim, em arrays multidimensionais, o primeiro argumento é para selecionar colunas e o segundo argumento é para colunas
ar2

# In[99]:

#mudar um elemento num subarray muda ele também no array original, isso é chamado passado por referência
#então, modoificando um sub array, vai modificar o original

sub_ar2 = ar2[:2, 1:3]

print('sub_ar2 index [0,0] antes de mudar: ', sub_ar2[0,0])
sub_ar2[0,0] = 50
print('sub_ar2 [0,0] depios de mudar: ', sub_ar2[0,0])
print('array original ar2 [0,1] depois de mudar: ', ar2[0,1])
print('---')
print(ar2)

# In[105]:

sub2_ar2 = sub_ar2
sub2_ar2[0,1] = 5
print(sub2_ar2)
print('---')
print(ar2)

# # trabalhando com datasets e numpy

# # dataset de vinhos

# In[115]:

#carregabdo o arquivo csv, mas por algum acaso ele está com ponto e virgula ao invés de só a virgula separando
wines = np.genfromtxt('winequality-red.csv', delimiter = ';', skip_header=1)

# In[116]:

wines

# In[118]:

#para selecionar a coluna da acidez, a primeira coluna, nós podemos buscar inserindo o index da coluna no array
#lembrando que para arrays multidimensionais, o primeiro argumento é a linha e o segundo a coluna
# se dermos só uma argumento, ele retorna uma lista simples

#todas as linhas da primeira colun
print('um número para cortar: ',wines[:, 0]) #retorna a primeira coluna em forma de lista
#para retornarmos o valor da primeira coluna mas na forma de linhas, onde cada valor está na sua linha:
print('0 a 1 para cortar: \n', wines[:, 0:1]) #aqui retorna os valores na forma de uma única coluna

# In[121]:

#se for pra pegar o intervalo entre a primeira e a terceira coluna:
wines[:, 0:3]

# In[122]:

#e se for pra buscar um número de colunas não consecutivas, tipo 0, 2, 4: criamos um array e colocamos esse array como
#o segundo argumento da busca
wines[:,[0,2,4]]

# In[124]:

#para fazer um resumo do dataset. se quisermos saber o valor médio da qualidade do vinho vermelho,
#selecionamos a coluna de qualidade. o jeito mais apropriado de fazer isso é usando o valor -1 para buscar a última coluna
#já que números negativos quer dizer que estamos buscando pelo final da lista
wines[:, -1].mean()

# # dataset de admissão escolar

# In[153]:

graduate_admission = np.genfromtxt('Admission_Predict.csv', dtype=None, delimiter=",", skip_header=1,
                                   names=("Serial_No", 'GRE_Score', 'TOELF_Score', 'University_Rating', 'SOP',
                                         'LOR', 'CGPA', 'Research', 'Chance_of Admit'))
graduate_admission.shape

# In[154]:

#podemos retornar uma coluna do array usando apenas o nome que demos aqui em cima
graduate_admission['CGPA'][0:5]

# In[155]:

#para deixar o valor numa escala de 0 a 4, podemos dividir o valor por 10 e multiplocar por 40
graduate_admission['CGPA'] = graduate_admission['CGPA'] / 10*4

# In[156]:

graduate_admission['CGPA'][0:20]

# In[157]:

#usando boolean mask para descobrir quantos alunos tiveram experiência com pesquisa/research criando uma mascara booleana e
#passando ela para o operador index do array

len(graduate_admission[graduate_admission['Research'] == 1])

# In[159]:

#identificar quandos alunos tem maiores chances de admissão >80% dos que tem menos chance de admissão <40%
#primeiro usar a mascara booleana para pegar apenas aqueles alunos em que estamos interessados
#baseado na sua chance de admissão, daí pegamos seu CGPA Score e imprimimos o valor médio
print(graduate_admission[graduate_admission['Chance_of_Admit']> 0.8]['GRE_Score'].mean())
print(graduate_admission[graduate_admission['Chance_of_Admit']< 0.4]['GRE_Score'].mean())

# In[160]:

print(graduate_admission[graduate_admission['Chance_of_Admit']> 0.8]['CGPA'].mean())
print(graduate_admission[graduate_admission['Chance_of_Admit']< 0.4]['CGPA'].mean())


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
BE523 Biosystems Analysis & Design
HW2 - Problem 7. Bacteria growth, difference calculator
https://mathinsight.org/bacteria_growth_initial_model_exercises Exercise 5

Created on Thu Jan 21 12:11:46 2021
@author: eduardo
"""
import numpy as np
import matplotlib.pyplot as plt
import statsmodels.api as sm  # allows linear regression without intercept

# Bacteria density data
# B = [0.028, 0.047, 0.082, 0.141, 0.240, 0.381]  # Exercise 5
B = np.array([0.022, 0.036, 0.060, 0.101, 0.169, 0.266])  # From problem 2

steps = len(B)  # Adjust the length of the vectors with the number of steps
dB = np.zeros(steps)
dt = 16  # time interval

t = np.linspace(0, (steps-1)*dt, steps)  # actual time vector

for i in range(1, steps):
    dB[i] = B[i] - B[i-1]  # compute the increment between time steps

# Perform a linear regression with B and dB, then plot (dB vs B)
model = sm.OLS(dB, B)  # No intercept by default, force through the origin
results = model.fit()
slope = results.params[0]  # grow rate of population
print("slope=", slope)

# Figure 1, plotting dB vs B
plt.figure(1)
plt.plot(B, dB, 'kx', B, slope*B, 'b-')
plt.legend(['data', 'linear regression $R^2$=%.3f' % results.rsquared], loc='best')
plt.xlabel('B')
plt.ylabel('dB')
plt.savefig('p7_bacteria_linear2.png', dpi=300, bbox_inches='tight')

# Generate an exponential equation ('exact solution')
tdouble = np.log(2)/np.log(1+slope)*dt
print('tdouble =', tdouble)
K = np.log(2)/tdouble
Bexp = B[0] * np.exp(K*t)

# Make 'predictions' using the analytical solution to the linear dynamical system,
# (also an exponential equation) in the form B(t) = B[0]*R^t with R>1
# we don't need to know the previous value, each calculation is only dependant of the time 't'
Bmodel = B[0]*pow(slope+1, t/dt)
print("The population after %d steps is: %.3f" % (steps, Bmodel[-1]))

# Figure 2, plotting P (from data and model) vs t
plt.figure(2)
plt.plot(t, B, 'bx', t, Bmodel, 'r-', t, Bexp, 'k+')
plt.legend(['data',
            'numerical B=%g$\cdot$(1+%.4f)$^t$' % (B[0], slope),
            'exact B=%g$\cdot$exp(%.4f$\cdot$t)' % (B[0], K)],
           loc='best')
plt.xlabel('Time (minutes)')
plt.ylabel('Bacteria population')
plt.savefig('p7_bacteria_%dsteps2.png' % steps, dpi=300, bbox_inches='tight')


#Code for creating halton sampling in low n-dimensions
# references : - https://gist.github.com/tupui/cea0a91cc127ea3890ac0f002f887bae
#              - https://www.w3resource.com/python-exercises/list/python-data-type-list-exercise-34.php

import numpy as np

def primes (n):
    #Defining prime numbers for base using sieve of erasthostenes
    not_prime = []
    prime = []
    for i in range(2, n+1):
        if i not in not_prime:
            prime.append(i)
            for j in range(i*i, n+1, i):
                not_prime.append(j)
    return prime

def vandercorput(n_sample,base=2):
    #generate sample using van der corput sequence per dimension
    sequence=[]
    for i in range(0,n_sample):
        f=1. ;   r=0.
        while i > 0:
            i, remainder = divmod(i, base)
            f = f/base
            r = r+f*remainder
        sequence.append(r)
    return sequence

def halton (dimension,n_sample):
    # halton sequence general form of van der corput sequence in n-dimensions
    big_number = 1000       # just an input for base, as long as dim <= len(base) the program won't error
    base = primes(big_number)[:dimension]
    #print("base = ",base)             # for debugging
    sample = [vandercorput(n_sample + 1, dim) for dim in base] # looping van der corput for each dimension
    sample = np.stack(sample, axis= -1)[1:]     #arrange the array
    sample[1:n_sample,:] = sample[0:n_sample - 1,:]
    sample[0,:] = 0
    return sample


import numpy as np
import matplotlib.pyplot as plt

def squareDistance(u, v):
    diff = u - v
    return diff.dot(diff)

# We want to minimize intra-cluster variance (squard error)
def objective(X, R, M):
    cost = 0
    for k in range(len(M)):
        for n in range(len(X)):
            cost += R[n,k] * squareDistance(M[k], X[n])

    return cost

def expDistance(p1, p2, beta):
    return np.exp(-beta * squareDistance(p1, p2))

def plot_k_means(X, K, max_iter=20, beta=1.0):
    N, D = X.shape

    M = np.array(
        X[np.random.choice(N, K, replace=False), :]
    )

    R = np.zeros((N, K))

    costs = np.zeros(max_iter)
    for i in range(max_iter):
        # calculate responsibility for each point for each cluster
        for k in range(K):
            for n in range(N):
                sumMeanDistances = np.array([
                    expDistance(M[j], X[n], beta) for j in range(K)
                ]).sum()

                meanDistance = expDistance(M[k], X[n], beta)

                softMax = meanDistance / sumMeanDistances

                R[n,k] = softMax

        # calculate the means for each cluster
        for k in range(K):
            M[k] = R[:,k].dot(X) / R[:,k].sum()

        # calculate cost and break if it hasn't changed much
        costs[i] = objective(X, R, M)
        if i > 0 and np.abs(costs[i] - costs[i - 1]) < 0.1:
            break

    # pick K random RGB values to represent clusters
    random_colors = np.random.random((K, 3))
    # weight the color of a point based on its responsibility to each cluster.
    colors = R.dot(random_colors)

    plt.scatter(X[:,0], X[:,1], c=colors)
    plt.show()

def generate_samples():
    D = 2
    s = 4 # distance between means

    mean1 = np.array([0, 0])
    mean2 = np.array([s, s])
    mean3 = np.array([0, s])

    samples = 900
    data = np.zeros((samples, D))

    # generate the dataset
    data[:300, :] = np.random.randn(300, D) + mean1
    data[300:600, :] = np.random.randn(300, D) + mean2
    data[600:, :] = np.random.randn(300, D) + mean3

    return data

def main():
    data = generate_samples()

    clusters = 3
    plot_k_means(data, clusters)

    # clusters = 5
    # plot_k_means(data, clusters, max_iter=30)

    # clusters = 5
    # plot_k_means(data, clusters, max_iter=30, beta=0.3)

if __name__ == '__main__':
    main()


# reference: Liu, Y. and Durlofsky, L.J., 2020. 3D CNN-PCA: A Deep-Learning-Based. Parameterization for Complex Geomodels. arXiv preprint arXiv:2007.08478
import numpy as np
import matplotlib.pyplot as plt

class PCA(object):
    def __init__(self, nc=1, nr=1, l=1):
        self.l = l
        self.nc = nc
        self.nr = nr
        self.xm = np.zeros((nc, 1))
        self.usig = np.zeros((nc, l))
        self.data_matrix = None
        self.sig = None
        self.u = None

    def construct_pca(self, x):
        assert x.shape == (self.nc, self.nr)
        self.data_matrix = x
        self.xm = np.mean(x, axis=1)[:, None]
        y = 1. / (np.sqrt(float(self.nr - 1.))) * (x - self.xm)
        self.u, self.sig, _ = np.linalg.svd(y, full_matrices=False)
        self.u = self.u[:, :self.l]
        self.sig = self.sig[:self.l, None]
        self.usig = np.dot(self.u, np.diag(self.sig[:, 0]))

    def generate_pca_realization(self, xi, dim=None):
        if dim is None:
            assert xi.shape[0] == self.l
            if xi.shape == (self.l, ):
                xi = xi[:, None]
            return self.usig.dot(xi) + self.xm
        else:
            assert xi.shape[0] == dim
            if xi.shape == (dim, ):
                xi = xi[:, None]
            return self.usig[:, :dim].dot(xi) + self.xm

    def get_xi(self, m, dim=None):
        assert self.u is not None, "Input or calculate U matrix to obtain reconstructed xi"
        assert m.shape[0] == self.nc

        if m.shape == (self.nc, ):
            m = m[:, None]
        if dim is None:
            xi = self.u.T.dot(m - self.xm) / self.sig
        else:
            xi = self.u[:, :dim].T.dot(m - self.xm) / self.sig[:dim]
        return xi

    def energy_plot(self, rel_energy, truncate_point):
        plt.figure(figsize=(8, 4))
        plt.subplot(1, 2, 1)
        plt.plot(rel_energy)
        plt.ylabel('Relative Energy', fontsize=12)
        plt.xlabel('Number of principal components', fontsize=12)
        plt.subplot(1, 2, 2)
        plt.plot(rel_energy[:truncate_point])
        plt.ylabel('Relative Energy', fontsize=12)
        plt.xlabel('Number of principal components', fontsize=12)
        plt.tight_layout()
        plt.show()

    def princ_component(self, tol=0.9):
        cum_energy = np.cumsum(self.sig**2)
        rel_energy = cum_energy / cum_energy[-1]
        truncate_point = np.argmin(np.abs(rel_energy - tol)) + 1
        print('Principle components: ', truncate_point)
        return truncate_point, rel_energy


from sys import *
from sympy import *
import numpy as np
#from sympy import Symbol, solve
from sympy import init_printing
from sympy.solvers.solveset import linsolve
from sympy.polys.polyfuncs import horner

def symbolCIP(nOrder):
    # Simple check
    if nOrder % 2 == 0:
        sys.exit("Order might be odd more than 3,\n"
                 "for issue 3, 5, 7, 9, etc.")

    # Set values
    coeff = symbols('a0:%d' % (nOrder + 1))
    xi, h = symbols('xi, h')
    ucval = symbols('uC:%d' % (nOrder - 1))
    urval = symbols('uR:%d' % (nOrder - 1))
    ulval = symbols('uL:%d' % (nOrder - 1))

    # Set interpolation polynomials
    fpoly = sum(coeff[k] * xi ** k for k in range(nOrder+1))
    dfpoly = []
    for k in range(np.int((nOrder + 1)/2)):
        dfpoly.append(fpoly.diff(xi, k))

    # Set constrains
    eqs_left = []
    eqs_right = []
    for k in range(np.int((nOrder + 1)/2)):
        eqs_right.append(dfpoly[k].subs(xi, 0))
        eqs_right.append(dfpoly[k].subs(xi,-h))
        eqs_left.append(dfpoly[k].subs(xi, 0))
        eqs_left.append(dfpoly[k].subs(xi, h))

    # Solve system under constrains
    # Right wave
    vals_right = ucval + ulval
    sys_eqns_right = [eqs_right[k] - vals_right[k] for k in range((nOrder+1))]
    sol_right = linsolve(sys_eqns_right, coeff)
    sol_right_list = list(sol_right)
    print('Right wave:\n', sol_right_list[0])
    # Left wave
    vals_left = ucval + urval
    sys_eqns_left = [eqs_left[k] - vals_left[k] for k in range((nOrder+1))]
    sol_left = linsolve(sys_eqns_left, coeff)
    sol_left_list = list(sol_left)
    print('Left wave:\n', sol_left_list[0])
    # print('LaTeX form:\n', latex(sol_left_list[0]))
    # print('Mathematica form:\n', mathematica_code(sol_left_list[0]))
    # print('C form:\n', ccode(sol_left_list[0]))
    # print('Octave form:\n', octave_code(sol_left_list[0]))

def symboldeltaP3():
    x, a, h, t, nu, c1, c2, c3, c4, ui, ui1, ri, ri1, uxx, u3x, rx, rxx, _uni \
        = symbols('x, a, h, t, nu, c1, c2, c3, c4, ui, ui1, ri, ri1, uxx, u3x, rx, rxx, _uni')
    # Set interpolants
    u = c1 + c2 * x + c3 * x ** 2 + c4 * x ** 3
    du = u.diff(x)
    print(du)
    # Set constrains
    eq01 = u.subs(x, 0)
    eq02 = du.subs(x, 0)
    eq03 = u.subs(x, -h)
    eq04 = du.subs(x, -h)
    # Solve system under constrains
    sol = linsolve([eq01 - ui, eq02 - ri, eq03 - ui1, eq04 - ri1], (c1, c2, c3, c4))
    # Get coefficients
    sol_get = next(iter(sol))
    c1_expr = sol_get[0].subs(ri, ri / h)
    c2_expr = sol_get[1].subs(ri, ri / h)
    c3_expr = sol_get[2].subs(ri, ri / h).subs(ri1, ri1 / h)
    c4_expr = sol_get[3].subs(ri, ri / h).subs(ri1, ri1 / h)
    print(("c1 = {0}".format(c1_expr)))
    print(("c2 = {0}".format(c2_expr)))
    print(("c3 = {0}".format(c3_expr)))
    print(("c4 = {0}".format(c4_expr)))

    # print("u = {0}".format(u))
    # nu = a * t / h
    # Set constrains
    ui = u.subs(x, 0)
    ui1 = u.subs(x, -h)

    ux = u.diff(x)
    uxi = ux.subs(x, 0)

    uxx = ux.diff(x)
    uxxi = uxx.subs(x, 0)

    u3x = uxx.diff(x)
    u3xi = u3x.subs(x, 0)

    r = h * u.diff(x)
    ri = r.subs(x, 0)

    rx = r.diff(x)
    rxi = rx.subs(x, 0)

    rxx = rx.diff(x)
    rxxi = rxx.subs(x, 0)

    # uxx = ux.diff(x)
    # u3x = uxx.diff(x)

    # print("ui = {0}, ui1 = {1}, ri = {2}, ri1 = {3}".format(simplify(ui), simplify(ui1), ri, ri1))
    #
    # c1 = ui
    # c2 = ri / h
    #
    # c3 = (3 * (ui1 - ui) + 2 * ri + ri1) / h**2
    # c4 = (2 * (ui1 - ui) + ri + ri1) / h**3

    # uxx = 2 * c3
    # u3x = 6 * c4
    #
    # rx = h * 2 * c3
    # rxx = h * 6 * c4

    uni = ui - a * t * uxi + a**2 * t**2 / 2 * uxxi - a**3 * t**3 / 6 * u3xi
    # uni = simplify(uni)
    _uni = uni.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr).subs(c4, c4_expr).subs(a * t / h, nu)
    _uni = collect(expand(_uni), nu)
    print(_uni)

    rni = simplify(ri - a * t * rxi + a**2 * t**2 / 2 * rxxi)
    _rni = rni.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr).subs(c4, c4_expr).subs(a * t / h, nu)
    print(collect(expand(_rni), nu))

def symbolDeltaP():
    # # Simple check
    # if nOrder % 2 == 0:
    #     sys.exit("Order might be odd more than 3,\n"
    #              "for issue 3, 5, 7, 9, etc.")
    nOrder = 3
    # Set values
    coeff = symbols('с1:%d' % (nOrder + 2))
    print(coeff)
    x, a, h, t, nu, c1, c2, c3, c4, ui, ui1, ri, ri1, uxx, u3x, rx, rxx, _uni \
        = symbols('x, a, h, t, nu, c1, c2, c3, c4, ui, ui1, ri, ri1, uxx, u3x, rx, rxx, _uni')
    # Set interpolants
    u = c1 + c2 * x + c3 * x ** 2 + c4 * x ** 3
    print(u)
    u = sum(coeff[k] * x ** k for k in range(nOrder + 1))
    du = u.diff(x)
    print(u)
    # Set constrains
    eq01 = u.subs(x, 0)
    eq02 = du.subs(x, 0)
    eq03 = u.subs(x, -h)
    eq04 = du.subs(x, -h)
    # Solve system under constrains
    sol = linsolve([eq01 - ui, eq02 - ri, eq03 - ui1, eq04 - ri1], (c1, c2, c3, c4))
    # Get coefficients
    sol_get = next(iter(sol))
    c1_expr = sol_get[0].subs(ri, ri / h)
    c2_expr = sol_get[1].subs(ri, ri / h)
    c3_expr = sol_get[2].subs(ri, ri / h).subs(ri1, ri1 / h)
    c4_expr = sol_get[3].subs(ri, ri / h).subs(ri1, ri1 / h)
    print(("c1 = {0}".format(c1_expr)))
    print(("c2 = {0}".format(c2_expr)))
    print(("c3 = {0}".format(c3_expr)))
    print(("c4 = {0}".format(c4_expr)))

    # print("u = {0}".format(u))
    # nu = a * t / h
    # Set constrains
    ui = u.subs(x, 0)
    ui1 = u.subs(x, -h)

    ux = u.diff(x)
    uxi = ux.subs(x, 0)

    uxx = ux.diff(x)
    uxxi = uxx.subs(x, 0)

    u3x = uxx.diff(x)
    u3xi = u3x.subs(x, 0)

    r = h * u.diff(x)
    ri = r.subs(x, 0)

    rx = r.diff(x)
    rxi = rx.subs(x, 0)

    rxx = rx.diff(x)
    rxxi = rxx.subs(x, 0)

    # uxx = ux.diff(x)
    # u3x = uxx.diff(x)

    # print("ui = {0}, ui1 = {1}, ri = {2}, ri1 = {3}".format(simplify(ui), simplify(ui1), ri, ri1))
    #
    # c1 = ui
    # c2 = ri / h
    #
    # c3 = (3 * (ui1 - ui) + 2 * ri + ri1) / h**2
    # c4 = (2 * (ui1 - ui) + ri + ri1) / h**3

    # uxx = 2 * c3
    # u3x = 6 * c4
    #
    # rx = h * 2 * c3
    # rxx = h * 6 * c4

    uni = ui - a * t * uxi + a**2 * t**2 / 2 * uxxi - a**3 * t**3 / 6 * u3xi
    # uni = simplify(uni)
    _uni = uni.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr).subs(c4, c4_expr).subs(a * t / h, nu)
    _uni = collect(expand(_uni), nu)
    print(_uni)

    rni = simplify(ri - a * t * rxi + a**2 * t**2 / 2 * rxxi)
    _rni = rni.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr).subs(c4, c4_expr).subs(a * t / h, nu)
    print(collect(expand(_rni), nu))

# def symboldeltaP5():
#     x, a, h, tau, nu, ui, ui1, ri, ri1, u3x, u4x, u5x, rxi, rxi1, rxx, r3x, r4x \
#         = symbols('x, a, h, tau, nu, ui, ui1, ri, ri1, u3x, u4x, u5x, rxi, rxi1, rxx, r3x, r4x')
#
#     c1, c2, c3, c4, c5, c6 = symbols('c1, c2, c3, c4, c5, c6')
#
#     _uxi, _uxxi, _ux1i, _uxx1i = symbols('_uxi, _uxxi, _ux1i, _uxx1i')
#
#     u = c1 + c2 * x + c3 * x ** 2 + c4 * x ** 3 + c5 * x ** 4 + c6 * x ** 5
#
#     du = u.diff(x)
#     ddu = du.diff(x)
#     eq01 = u.subs(x, 0)
#     eq02 = du.subs(x, 0)
#     eq03 = u.subs(x, -h)
#     eq04 = du.subs(x, -h)
#     eq05 = ddu.subs(x, 0)
#     eq06 = ddu.subs(x, -h)
#     # Solve system under constrains
#     sol = linsolve([eq01 - ui, eq02 - _uxi, eq03 - ui1, eq04 - _ux1i, eq05 - _uxxi, eq06 - _uxx1i],
#                    (c1, c2, c3, c4, c5, c6))
#
#     sol_get = next(iter(sol))
#     # c1_expr = sol_get[0].subs(ri, ri / h).subs(ri1, ri1 / h).subs(rxi, rxi / h**2).subs(rxi1, rxi1 / h**2)
#     # c2_expr = sol_get[1].subs(ri, ri / h).subs(ri1, ri1 / h).subs(rxi, rxi / h**2).subs(rxi1, rxi1 / h**2)
#     # c3_expr = sol_get[2].subs(ri, ri / h).subs(ri1, ri1 / h).subs(rxi, rxi / h**2).subs(rxi1, rxi1 / h**2)
#     # c4_expr = sol_get[3].subs(ri, ri / h).subs(ri1, ri1 / h).subs(rxi, rxi / h**2).subs(rxi1, rxi1 / h**2)
#     # c5_expr = sol_get[4].subs(ri, ri / h).subs(ri1, ri1 / h).subs(rxi, rxi / h**2).subs(rxi1, rxi1 / h**2)
#     # c6_expr = sol_get[5].subs(ri, ri / h).subs(ri1, ri1 / h).subs(rxi, rxi / h**2).subs(rxi1, rxi1 / h**2)
#     c1_expr = sol_get[0]
#     c2_expr = sol_get[1].subs(_uxi, ri / h)
#     c3_expr = sol_get[2].subs(_uxxi, rxi / h ** 2)
#     c4_expr = sol_get[3].subs(h * _uxi, ri).subs(h * _ux1i, ri1).subs(h ** 2 * _uxxi, rxi).subs(h ** 2 * _uxx1i, rxi1)
#     c5_expr = sol_get[4].subs(h * _uxi, ri).subs(h * _ux1i, ri1).subs(h ** 2 * _uxxi, rxi).subs(h ** 2 * _uxx1i, rxi1)
#     c6_expr = sol_get[5].subs(h * _uxi, ri).subs(h * _ux1i, ri1).subs(h ** 2 * _uxxi, rxi).subs(h ** 2 * _uxx1i, rxi1)
#     print("c1 = {0}".format(c1_expr))
#     print("c2 = {0}".format(c2_expr))
#     print("c3 = {0}".format(c3_expr))
#     print("c4 = {0}".format(c4_expr))
#     print("c5 = {0}".format(c5_expr))
#     print("c6 = {0}".format(c6_expr))
#
#     # print("u = {0}".format(u))
#     # Set constrains
#     ui = u.subs(x, 0)
#
#     ux = u.diff(x)
#     uxi = ux.subs(x, 0)
#
#     uxx = ux.diff(x)
#     uxxi = uxx.subs(x, 0)
#
#     u3x = uxx.diff(x)
#     u3xi = u3x.subs(x, 0)
#
#     u4x = u3x.diff(x)
#     u4xi = u4x.subs(x, 0)
#
#     u5x = u4x.diff(x)
#     u5xi = u5x.subs(x, 0)
#
#     r = h * ux
#     ri = r.subs(x, 0)
#
#     rx = r.diff(x)
#     rxi = rx.subs(x, 0)
#
#     rxx = rx.diff(x)
#     rxxi = rxx.subs(x, 0)
#
#     r3x = rxx.diff(x)
#     r3xi = r3x.subs(x, 0)
#
#     r4x = r3x.diff(x)
#     r4xi = r4x.subs(x, 0)
#
#     # uxx = ux.diff(x)
#     # u3x = uxx.diff(x)
#
#     # print("ui = {0}, ui1 = {1}, ri = {2}, ri1 = {3}".format(simplify(ui), simplify(ui1), ri, ri1))
#     #
#     # c1 = ui
#     # c2 = ri / h
#     #
#     # c3 = (3 * (ui1 - ui) + 2 * ri + ri1) / h**2
#     # c4 = (2 * (ui1 - ui) + ri + ri1) / h**3
#
#     # uxx = 2 * c3
#     # u3x = 6 * c4
#     #
#     # rx = h * 2 * c3
#     # rxx = h * 6 * c4
#
#     uni = (ui - a * tau * uxi + a**2 * tau**2 / 2 * uxxi - a**3 * tau**3 / 6
#            * u3xi + a**4 * tau**4 / 24 * u4xi - a ** 5 * tau ** 5 / 120 * u5xi)
#
#     _uni = uni.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr)\
#         .subs(c4, c4_expr).subs(c5, c5_expr).subs(c6, c6_expr).subs(a * tau / h, nu)
#
#     rni = ri - a * tau * rxi + a**2 * tau**2 / 2 * rxxi - a**3 * tau**3 / 6 * r3xi + a**4 * tau**4 / 24 * r4xi
#
#     _rni = rni.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr)\
#         .subs(c4, c4_expr).subs(c5, c5_expr).subs(c6, c6_expr).subs(a * tau / h, nu)
#
#     rxni = rxi - a * tau * rxxi + a**2 * tau**2 / 2 * r3xi - a**3 * tau**3 / 6 * r4xi
#
#     _rxni = rxni.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr)\
#         .subs(c4, c4_expr).subs(c5, c5_expr).subs(c6, c6_expr).subs(a * tau / h, nu) * h
#
#     print(simplify(_uni))
#     print(simplify(_rni))
#     print(simplify(_rxni))

# def symboldeltaP7():
#     x, a, h, tau, nu, c1, c2, c3, c4, c5, c6, c7, c8 = symbols('x, a, h, tau, nu, c1, c2, c3, c4, c5, c6, c7, c8')
#
#     ui, ui1, uxi, uxi1, uxxi, uxxi1, u3xi, u3xi1 = symbols('ui, ui1, ri, ri1, uxxi, uxx1i, u3xi, u3x1i')
#
#     ri, ri1, rxi, rxi1, rxxi, rxxi1 = symbols('ri, ri1, rxi, rxi1, rxxi, rxxi1')
#
#     u = c1 + c2 * x + c3 * x ** 2 + c4 * x ** 3 + c5 * x ** 4 + c6 * x ** 5 + c7 * x ** 6 + c8 * x ** 7
#
#     du = u.diff(x)
#     ddu = du.diff(x)
#     dddu = ddu.diff(x)
#     eq01 = u.subs(x, 0)
#     eq02 = du.subs(x, 0)
#     eq03 = u.subs(x, -h)
#     eq04 = du.subs(x, -h)
#     eq05 = ddu.subs(x, 0)
#     eq06 = ddu.subs(x, -h)
#     eq07 = dddu.subs(x, 0)
#     eq08 = dddu.subs(x, -h)
#     # Solve system under constrains
#     sol = linsolve([eq01 - ui, eq02 - uxi, eq03 - ui1, eq04 - uxi1, eq05 - uxxi, eq06 - uxxi1, eq07 - u3xi, eq08 - u3xi1],
#                    (c1, c2, c3, c4, c5, c6, c7, c8))
#     sol_get = next(iter(sol))
#     c1_expr = sol_get[0]
#     c2_expr = sol_get[1].subs(uxi, ri / h)
#     c3_expr = sol_get[2].subs(uxxi, rxi / h ** 2)
#     c4_expr = sol_get[3].subs(u3xi, rxxi / h ** 3)
#     c5_expr = sol_get[4].subs(h * uxi, ri).subs(h * uxi1, ri1).subs(h ** 2 * uxxi, rxi)\
#         .subs(h ** 2 * uxxi1, rxi1).subs(h ** 3 * u3xi, rxxi).subs(h ** 3 * u3xi1, rxxi1)
#     c6_expr = sol_get[5].subs(h * uxi, ri).subs(h * uxi1, ri1).subs(h ** 2 * uxxi, rxi)\
#         .subs(h ** 2 * uxxi1, rxi1).subs(h ** 3 * u3xi, rxxi).subs(h ** 3 * u3xi1, rxxi1)
#     c7_expr = sol_get[6].subs(h * uxi, ri).subs(h * uxi1, ri1).subs(h ** 2 * uxxi, rxi)\
#         .subs(h ** 2 * uxxi1, rxi1).subs(h ** 3 * u3xi, rxxi).subs(h ** 3 * u3xi1, rxxi1)
#     c8_expr = sol_get[7].subs(h * uxi, ri).subs(h * uxi1, ri1).subs(h ** 2 * uxxi, rxi)\
#         .subs(h ** 2 * uxxi1, rxi1).subs(h ** 3 * u3xi, rxxi).subs(h ** 3 * u3xi1, rxxi1)
#
#     print("c1 = {0}".format(c1_expr))
#     print("c2 = {0}".format(c2_expr))
#     print("c3 = {0}".format(c3_expr))
#     print("c4 = {0}".format(c4_expr))
#     print("c5 = {0}".format(c5_expr))
#     print("c6 = {0}".format(c6_expr))
#     print("c7 = {0}".format(c7_expr))
#     print("c8 = {0}".format(c8_expr))
#
#
#     # Set constrains
#     ui = u.subs(x, 0)
#
#     ux = u.diff(x)
#     uxi = ux.subs(x, 0)
#
#     uxx = ux.diff(x)
#     uxxi = uxx.subs(x, 0)
#
#     u3x = uxx.diff(x)
#     u3xi = u3x.subs(x, 0)
#
#     u4x = u3x.diff(x)
#     u4xi = u4x.subs(x, 0)
#
#     u5x = u4x.diff(x)
#     u5xi = u5x.subs(x, 0)
#
#     u6x = u5x.diff(x)
#     u6xi = u6x.subs(x, 0)
#
#     u7x = u6x.diff(x)
#     u7xi = u7x.subs(x, 0)
#
#     r = h * u.diff(x)
#     ri = r.subs(x, 0)
#
#     rx = r.diff(x)
#     rxi = rx.subs(x, 0)
#
#     rxx = rx.diff(x)
#     rxxi = rxx.subs(x, 0)
#
#     r3x = rxx.diff(x)
#     r3xi = r3x.subs(x, 0)
#
#     r4x = r3x.diff(x)
#     r4xi = r4x.subs(x, 0)
#
#     r5x = r4x.diff(x)
#     r5xi = r5x.subs(x, 0)
#
#     r6x = r5x.diff(x)
#     r6xi = r6x.subs(x, 0)
#
#     # uxx = ux.diff(x)
#     # u3x = uxx.diff(x)
#
#     # print("ui = {0}, ui1 = {1}, ri = {2}, ri1 = {3}".format(simplify(ui), simplify(ui1), ri, ri1))
#     #
#     # c1 = ui
#     # c2 = ri / h
#     #
#     # c3 = (3 * (ui1 - ui) + 2 * ri + ri1) / h**2
#     # c4 = (2 * (ui1 - ui) + ri + ri1) / h**3
#
#     # uxx = 2 * c3
#     # u3x = 6 * c4
#     #
#     # rx = h * 2 * c3
#     # rxx = h * 6 * c4
#
#     uni = ui - a * tau * uxi + a**2 * tau**2 / 2 * uxxi - a**3 * tau**3 / 6 * u3xi + a**4 * tau**4 / 24 * u4xi \
#           - a ** 5 * tau ** 5 / 120 * u5xi + a ** 6 * tau ** 6 / 720 * u6xi - a ** 7 * tau ** 7 / 5040 * u7xi
#
#     rni = ri - a * tau * rxi + a**2 * tau**2 / 2 * rxxi - a**3 * tau**3 / 6 * r3xi + a**4 * tau**4 / 24 * r4xi \
#           - a ** 5 * tau ** 5 / 120 * r5xi + a ** 6 * tau ** 6 / 720 * r6xi
#
#     rxni = rxi - a * tau * rxxi + a**2 * tau**2 / 2 * r3xi - a**3 * tau**3 / 6 * r4xi \
#            + a**4 * tau**4 / 24 * r5xi - a ** 5 * tau ** 5 / 120 * r6xi
#
#     rxxni = rxxi - a * tau * r3xi + a**2 * tau**2 / 2 * r4xi - a**3 * tau**3 / 6 * r5xi \
#             + a**4 * tau**4 / 24 * r6xi
#
#     uni = uni.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr)\
#         .subs(c4, c4_expr).subs(c5, c5_expr).subs(c6, c6_expr).subs(c7, c7_expr).subs(c8, c8_expr).subs(a * tau / h, nu)
#
#     rni = rni.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr) \
#         .subs(c4, c4_expr).subs(c5, c5_expr).subs(c6, c6_expr).subs(c7, c7_expr).subs(c8, c8_expr).subs(a * tau / h, nu)
#
#     rxni = rxni.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr) \
#         .subs(c4, c4_expr).subs(c5, c5_expr).subs(c6, c6_expr).subs(c7, c7_expr).subs(c8, c8_expr)\
#         .subs(a * tau / h, nu) * h
#
#     rxxni = rxxni.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr) \
#         .subs(c4, c4_expr).subs(c5, c5_expr).subs(c6, c6_expr).subs(c7, c7_expr).subs(c8, c8_expr)\
#         .subs(a * tau / h, nu) * h ** 2
#
#     print("uni = {0}".format(simplify(uni)))
#     print("rni = {0}".format(simplify(rni)))
#     print("rxni = {0}".format(simplify(rxni)))
#     print("rxxni = {0}".format(simplify(rxxni)))
#
#     # print("rx = {0}, с2 = {1}, с3 = {2}, с4 = {3}, с5 = {4}".format(simplify(rx), simplify(c2), c3, c4, c5))

# def symboldeltaP9():
#     x, a, h, tau, nu, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10 \
#         = symbols('x, a, h, tau, nu, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10')
#
#     ui, ui1, uxi, uxi1, uxxi, uxxi1, u3xi, u3xi1, u4xi, u4xi1 \
#         = symbols('ui, ui1, ri, ri1, uxxi, uxx1i, u3xi, u3x1i, u4xi, u4xi1')
#
#     ri, ri1, rxi, rxi1, rxxi, rxxi1, r3xi, r3xi1 = symbols('ri, ri1, rxi, rxi1, rxxi, rxxi1, r3xi, r3xi1')
#
#     u = c1 + c2 * x + c3 * x ** 2 + c4 * x ** 3 + c5 * x ** 4 + c6 * x ** 5 + c7 * x ** 6 + c8 * x ** 7 \
#         + c9 * x ** 8 + c10 * x ** 9
#
#     du = u.diff(x)
#     ddu = du.diff(x)
#     d3u = ddu.diff(x)
#     d4u = d3u.diff(x)
#
#     eq01 = u.subs(x, 0)
#     eq02 = du.subs(x, 0)
#     eq03 = u.subs(x, -h)
#     eq04 = du.subs(x, -h)
#     eq05 = ddu.subs(x, 0)
#     eq06 = ddu.subs(x, -h)
#     eq07 = d3u.subs(x, 0)
#     eq08 = d3u.subs(x, -h)
#     eq09 = d4u.subs(x, 0)
#     eq10 = d4u.subs(x, -h)
#
#     # Solve system under constrains
#
#     sol = linsolve([eq01 - ui, eq02 - uxi, eq03 - ui1, eq04 - uxi1, eq05 - uxxi, eq06 - uxxi1, eq07 - u3xi,
#                     eq08 - u3xi1, eq09 - u4xi, eq10 - u4xi1], (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10))
#
#     sol_get = next(iter(sol))
#
#     c1_expr = sol_get[0]
#     c2_expr = sol_get[1].subs(uxi, ri / h)
#     c3_expr = sol_get[2].subs(uxxi, rxi / h ** 2)
#     c4_expr = sol_get[3].subs(u3xi, rxxi / h ** 3)
#     c5_expr = sol_get[4].subs(u4xi, r3xi / h ** 4)
#     c6_expr = sol_get[5].subs(h * uxi, ri).subs(h * uxi1, ri1).subs(h ** 2 * uxxi, rxi) \
#         .subs(h ** 2 * uxxi1, rxi1).subs(h ** 3 * u3xi, rxxi).subs(h ** 3 * u3xi1, rxxi1).subs(h ** 4 * u4xi, r3xi)\
#         .subs(h ** 4 * u4xi1, r3xi1)
#     c7_expr = sol_get[6].subs(h * uxi, ri).subs(h * uxi1, ri1).subs(h ** 2 * uxxi, rxi) \
#         .subs(h ** 2 * uxxi1, rxi1).subs(h ** 3 * u3xi, rxxi).subs(h ** 3 * u3xi1, rxxi1).subs(h ** 4 * u4xi, r3xi)\
#         .subs(h ** 4 * u4xi1, r3xi1)
#     c8_expr = sol_get[7].subs(h * uxi, ri).subs(h * uxi1, ri1).subs(h ** 2 * uxxi, rxi) \
#         .subs(h ** 2 * uxxi1, rxi1).subs(h ** 3 * u3xi, rxxi).subs(h ** 3 * u3xi1, rxxi1).subs(h ** 4 * u4xi, r3xi)\
#         .subs(h ** 4 * u4xi1, r3xi1)
#     c9_expr = sol_get[8].subs(h * uxi, ri).subs(h * uxi1, ri1).subs(h ** 2 * uxxi, rxi) \
#         .subs(h ** 2 * uxxi1, rxi1).subs(h ** 3 * u3xi, rxxi).subs(h ** 3 * u3xi1, rxxi1).subs(h ** 4 * u4xi, r3xi)\
#         .subs(h ** 4 * u4xi1, r3xi1)
#     c10_expr = sol_get[9].subs(h * uxi, ri).subs(h * uxi1, ri1).subs(h ** 2 * uxxi, rxi) \
#         .subs(h ** 2 * uxxi1, rxi1).subs(h ** 3 * u3xi, rxxi).subs(h ** 3 * u3xi1, rxxi1).subs(h ** 4 * u4xi, r3xi)\
#         .subs(h ** 4 * u4xi1, r3xi1)
#     print("c1 = {0}".format(c1_expr))
#     print("c2 = {0}".format(c2_expr))
#     print("c3 = {0}".format(c3_expr))
#     print("c4 = {0}".format(c4_expr))
#     print("c5 = {0}".format(c5_expr))
#     print("c6 = {0}".format(c6_expr))
#     print("c7 = {0}".format(c7_expr))
#     print("c8 = {0}".format(c8_expr))
#     print("c9 = {0}".format(c9_expr))
#     print("c10 = {0}\n".format(c10_expr))
#
#     # Set constrains
#     ui = u.subs(x, 0)
#
#     ux = u.diff(x)
#     uxi = ux.subs(x, 0)
#
#     uxx = ux.diff(x)
#     uxxi = uxx.subs(x, 0)
#
#     u3x = uxx.diff(x)
#     u3xi = u3x.subs(x, 0)
#
#     u4x = u3x.diff(x)
#     u4xi = u4x.subs(x, 0)
#
#     u5x = u4x.diff(x)
#     u5xi = u5x.subs(x, 0)
#
#     u6x = u5x.diff(x)
#     u6xi = u6x.subs(x, 0)
#
#     u7x = u6x.diff(x)
#     u7xi = u7x.subs(x, 0)
#
#     u8x = u7x.diff(x)
#     u8xi = u8x.subs(x, 0)
#
#     u9x = u8x.diff(x)
#     u9xi = u9x.subs(x, 0)
#
#     r = h * u.diff(x)
#     ri = r.subs(x, 0)
#
#     rx = r.diff(x)
#     rxi = rx.subs(x, 0)
#
#     rxx = rx.diff(x)
#     rxxi = rxx.subs(x, 0)
#
#     r3x = rxx.diff(x)
#     r3xi = r3x.subs(x, 0)
#
#     r4x = r3x.diff(x)
#     r4xi = r4x.subs(x, 0)
#
#     r5x = r4x.diff(x)
#     r5xi = r5x.subs(x, 0)
#
#     r6x = r5x.diff(x)
#     r6xi = r6x.subs(x, 0)
#
#     r7x = r6x.diff(x)
#     r7xi = r7x.subs(x, 0)
#
#     r8x = r7x.diff(x)
#     r8xi = r8x.subs(x, 0)
#
#     uni = ui - a * tau * uxi + a ** 2 * tau ** 2 / 2 * uxxi - a ** 3 * tau ** 3 / 6 * u3xi + a ** 4 * tau ** 4 / 24 * u4xi \
#           - a ** 5 * tau ** 5 / 120 * u5xi + a ** 6 * tau ** 6 / 720 * u6xi - a ** 7 * tau ** 7 / 5040 * u7xi \
#           + a ** 8 * tau ** 8 / 40320 * u8xi - a ** 9 * tau ** 9 / 362880 * u9xi
#
#     rni = ri - a * tau * rxi + a ** 2 * tau ** 2 / 2 * rxxi - a ** 3 * tau ** 3 / 6 * r3xi + a ** 4 * tau ** 4 / 24 * r4xi \
#           - a ** 5 * tau ** 5 / 120 * r5xi + a ** 6 * tau ** 6 / 720 * r6xi - a ** 7 * tau ** 7 / 5040 * r7xi \
#           + a ** 8 * tau ** 8 / 40320 * r8xi
#
#     rxni = rxi - a * tau * rxxi + a ** 2 * tau ** 2 / 2 * r3xi - a ** 3 * tau ** 3 / 6 * r4xi \
#            + a ** 4 * tau ** 4 / 24 * r5xi - a ** 5 * tau ** 5 / 120 * r6xi + a ** 6 * tau ** 6 / 720 * r7xi \
#            - a ** 7 * tau ** 7 / 5040 * r8xi
#
#     rxxni = rxxi - a * tau * r3xi + a ** 2 * tau ** 2 / 2 * r4xi - a ** 3 * tau ** 3 / 6 * r5xi \
#             + a ** 4 * tau ** 4 / 24 * r6xi - a ** 5 * tau ** 5 / 120 * r7xi + a ** 6 * tau ** 6 / 720 * r8xi
#
#     r3xni = r3xi - a * tau * r4xi + a ** 2 * tau ** 2 / 2 * r5xi - a ** 3 * tau ** 3 / 6 * r6xi \
#             + a ** 4 * tau ** 4 / 24 * r7xi - a ** 5 * tau ** 5 / 120 * r8xi
#
#     uni = uni.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr) \
#         .subs(c4, c4_expr).subs(c5, c5_expr).subs(c6, c6_expr).subs(c7, c7_expr).subs(c8, c8_expr).subs(c9, c9_expr)\
#         .subs(c10, c10_expr).subs(a * tau / h, nu)
#
#     rni = rni.subs(c2, c2_expr).subs(c3, c3_expr) \
#         .subs(c4, c4_expr).subs(c5, c5_expr).subs(c6, c6_expr).subs(c7, c7_expr).subs(c8, c8_expr).subs(c9, c9_expr)\
#         .subs(c10, c10_expr).subs(a * tau / h, nu)
#
#     rxni = rxni.subs(c2, c2_expr).subs(c3, c3_expr) \
#         .subs(c4, c4_expr).subs(c5, c5_expr).subs(c6, c6_expr).subs(c7, c7_expr).subs(c8, c8_expr).subs(c9, c9_expr)\
#         .subs(c10, c10_expr).subs(a * tau / h, nu) * h
#
#     rxxni = rxxni.subs(c2, c2_expr).subs(c3, c3_expr) \
#         .subs(c4, c4_expr).subs(c5, c5_expr).subs(c6, c6_expr).subs(c7, c7_expr).subs(c8, c8_expr).subs(c9, c9_expr)\
#         .subs(c10, c10_expr).subs(a * tau / h, nu) * h ** 2
#
#     r3xni = r3xni.subs(c2, c2_expr).subs(c3, c3_expr) \
#         .subs(c4, c4_expr).subs(c5, c5_expr).subs(c6, c6_expr).subs(c7, c7_expr).subs(c8, c8_expr).subs(c9, c9_expr)\
#         .subs(c10, c10_expr).subs(a * tau / h, nu) * h ** 3
#
#     print("uni = {0}".format(simplify(uni)))
#     print("rni = {0}".format(simplify(rni)))
#     print("rxni = {0}".format(simplify(rxni)))
#     print("rxxni = {0}".format(simplify(rxxni)))
#     print("r3xni = {0}".format(simplify(r3xni)))

# def get_derivative():
#     t, x, y = symbols('t, x, y')
#
#     p = cos(2 * sqrt(2) * pi * t) * sin(2 * pi * x) * sin(2 * pi * y)
#
#     u = -1 / sqrt(2) * sin(2 * sqrt(2) * pi * t) * cos(2 * pi * x) * sin(2 * pi * y)
#
#     v = -1 / sqrt(2) * sin(2 * sqrt(2) * pi * t) * sin(2 * pi * x) * cos(2 * pi * y)
#
#     p_exp = exp(-(10 * (x ** 2 + (y + 0.7) ** 2)))
#
#     p_sin = 1 / pi * sin(pi * x) ** 2 * sin(pi * y) ** 2
#
#     rp = diff(p, x)
#     ru = diff(u, x)
#     rv = diff(v, x)
#
#     sp = diff(p, y)
#     su = diff(u, y)
#     sv = diff(v, y)
#
#     rp_exp = diff(p_exp, x)
#     sp_exp = diff(p_exp, y)
#
#     rp_sin = diff(p_sin, x)
#     sp_sin = diff(p_sin, y)
#
#     print("rp = {0}\n".format(rp))
#     print("ru = {0}\n".format(ru))
#     print("rv = {0}\n".format(rv))
#
#     print("sp = {0}\n".format(sp))
#     print("su = {0}\n".format(su))
#     print("sv = {0}\n".format(sv))
#
#     print("rp_exp = {0}\n".format(rp_exp))
#     print("sp_exp = {0}\n".format(sp_exp))
#
#     print("rp_sin = {0}\n".format(rp_sin))
#     print("sp_sin = {0}\n".format(sp_sin))

# def two_dim_deltaP3():
#     x, y, a, h, tau, nu, ui, ui1, ri, ri1, uxx, u3x, rx, rxx \
#         = symbols('x, y, a, h, tau, nu, ui, ui1, ri, ri1, uxx, u3x, rx, rxx')
#
#     c1, c2, c3, c4, c5, c6, c7, c8, c9, c10 = \
#         symbols('c1, c2, c3, c4, c5, c6, c7, c8, c9, c10')
#
#     phi_i_j1, phi_i1_j1, phi_i1_j, phi_i_j, r_i_j1, r_i_j, r_i1_j, s_i_j1, s_i_j, s_i1_j = \
#         symbols('phi_i_j1, phi_i1_j1, phi_i1_j, phi_i_j, r_i_j1, r_i_j, r_i1_j, s_i_j1, s_i_j, s_i1_j')
#
#     # p, u, v, rp, ru, rv, sp, su, sv = symbols('p, u, v, rp, ru, rv, sp, su, sv')
#
#     p = c1 + c2 * x + c3 * y + c4 * x * y + c5 * x ** 2 + c6 * y ** 2 + c7 * x ** 2 * y + c8 * x * y ** 2 \
#         + c9 * x ** 3 + c10 * y ** 3
#
#     u = c1 + c2 * x + c3 * y + c4 * x * y + c5 * x ** 2 + c6 * y ** 2 + c7 * x ** 2 * y + c8 * x * y ** 2 \
#         + c9 * x ** 3 + c10 * y ** 3
#
#     v = c1 + c2 * x + c3 * y + c4 * x * y + c5 * x ** 2 + c6 * y ** 2 + c7 * x ** 2 * y + c8 * x * y ** 2 \
#         + c9 * x ** 3 + c10 * y ** 3
#
#     # linear system of 10 equations
#
#     # eq01 = p.subs(x, 0).subs(y, h)
#     # eq02 = p.subs(x, h).subs(y, h)
#     # eq03 = p.subs(x, h).subs(y, 0)
#     # eq04 = p.subs(x, 0).subs(y, 0)
#     #
#     # eq05 = p.diff(x).subs(x, 0).subs(y, h)
#     # eq06 = p.diff(x).subs(x, h).subs(y, h)
#     # eq07 = p.diff(x).subs(x, h).subs(y, 0)
#     #
#     # eq08 = p.diff(y).subs(x, 0).subs(y, h)
#     # eq09 = p.diff(y).subs(x, h).subs(y, h)
#     # eq10 = p.diff(y).subs(x, h).subs(y, 0)
#
#     eq01 = p.subs(x, -h).subs(y, 0)
#     eq02 = p.subs(x, 0).subs(y, 0)
#     eq03 = p.subs(x, 0).subs(y, -h)
#     eq04 = p.subs(x, -h).subs(y, -h)
#
#     eq05 = p.diff(x).subs(x, -h).subs(y, 0)
#     eq06 = p.diff(x).subs(x, 0).subs(y, 0)
#     eq07 = p.diff(x).subs(x, 0).subs(y, -h)
#
#     eq08 = p.diff(y).subs(x, -h).subs(y, 0)
#     eq09 = p.diff(y).subs(x, 0).subs(y, 0)
#     eq10 = p.diff(y).subs(x, 0).subs(y, -h)
#
#     sol = linsolve([eq01 - phi_i1_j, eq02 - phi_i_j, eq03 - phi_i_j1, eq04 - phi_i1_j1, eq05 - r_i1_j, eq06 - r_i_j,
#                     eq07 - r_i_j1, eq08 - s_i1_j, eq09 - s_i_j, eq10 - s_i_j1],
#                    (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10))
#
#     sol_get = next(iter(sol))
#
#     c1_expr = sol_get[0] #.subs(r_i_j1, r_i_j1 / h).subs(r_i1_j1, r_i1_j1 / h).subs(r_i1_j, r_i1_j / h)\
#        # .subs(s_i_j1, s_i_j1 / h).subs(s_i1_j1, s_i1_j1 / h).subs(s_i1_j, s_i1_j / h)
#
#     c2_expr = sol_get[1] #.subs(r_i_j1, r_i_j1 / h).subs(r_i1_j1, r_i1_j1 / h).subs(r_i1_j, r_i1_j / h)\
#        # .subs(s_i_j1, s_i_j1 / h).subs(s_i1_j1, s_i1_j1 / h).subs(s_i1_j, s_i1_j / h)
#
#     c3_expr = sol_get[2] #.subs(r_i_j1, r_i_j1 / h).subs(r_i1_j1, r_i1_j1 / h).subs(r_i1_j, r_i1_j / h)\
#        # .subs(s_i_j1, s_i_j1 / h).subs(s_i1_j1, s_i1_j1 / h).subs(s_i1_j, s_i1_j / h)
#
#     c4_expr = sol_get[3] #.subs(r_i_j1, r_i_j1 / h).subs(r_i1_j1, r_i1_j1 / h).subs(r_i1_j, r_i1_j / h)\
#        # .subs(s_i_j1, s_i_j1 / h).subs(s_i1_j1, s_i1_j1 / h).subs(s_i1_j, s_i1_j / h)
#
#     c5_expr = sol_get[4] #.subs(r_i_j1, r_i_j1 / h).subs(r_i1_j1, r_i1_j1 / h).subs(r_i1_j, r_i1_j / h)\
#        # .subs(s_i_j1, s_i_j1 / h).subs(s_i1_j1, s_i1_j1 / h).subs(s_i1_j, s_i1_j / h)
#
#     c6_expr = sol_get[5] #.subs(r_i_j1, r_i_j1 / h).subs(r_i1_j1, r_i1_j1 / h).subs(r_i1_j, r_i1_j / h)\
#         #.subs(s_i_j1, s_i_j1 / h).subs(s_i1_j1, s_i1_j1 / h).subs(s_i1_j, s_i1_j / h)
#
#     c7_expr = sol_get[6] #.subs(r_i_j1, r_i_j1 / h).subs(r_i1_j1, r_i1_j1 / h).subs(r_i1_j, r_i1_j / h)\
#        # .subs(s_i_j1, s_i_j1 / h).subs(s_i1_j1, s_i1_j1 / h).subs(s_i1_j, s_i1_j / h)
#
#     c8_expr = sol_get[7] #.subs(r_i_j1, r_i_j1 / h).subs(r_i1_j1, r_i1_j1 / h).subs(r_i1_j, r_i1_j / h)\
#         #.subs(s_i_j1, s_i_j1 / h).subs(s_i1_j1, s_i1_j1 / h).subs(s_i1_j, s_i1_j / h)
#
#     c9_expr = sol_get[8] #.subs(r_i_j1, r_i_j1 / h).subs(r_i1_j1, r_i1_j1 / h).subs(r_i1_j, r_i1_j / h)\
#         #.subs(s_i_j1, s_i_j1 / h).subs(s_i1_j1, s_i1_j1 / h).subs(s_i1_j, s_i1_j / h)
#
#     c10_expr = sol_get[9] #.subs(r_i_j1, r_i_j1 / h).subs(r_i1_j1, r_i1_j1 / h).subs(r_i1_j, r_i1_j / h)\
#         #.subs(s_i_j1, s_i_j1 / h).subs(s_i1_j1, s_i1_j1 / h).subs(s_i1_j, s_i1_j / h)
#
#     print("c1 = {0}".format(c1_expr))
#     print("c2 = {0}".format(c2_expr))
#     print("c3 = {0}".format(c3_expr))
#     print("c4 = {0}".format(c4_expr))
#     print("c5 = {0}".format(c5_expr))
#     print("c6 = {0}".format(c6_expr))
#     print("c7 = {0}".format(c7_expr))
#     print("c8 = {0}".format(c8_expr))
#     print("c9 = {0}".format(c9_expr))
#     print("c10 = {0}\n".format(c10_expr))
#
#     # first derivative in the x-direction
#
#     rp = p.diff(x)
#     ru = u.diff(x)
#     rv = v.diff(x)
#
#     # first derivative in the y-direction
#
#     sp = p.diff(y)
#     su = u.diff(y)
#     sv = v.diff(y)
#
#     # second and third derivative for p
#
#     p_xx = rp.diff(x)
#     p_xy = rp.diff(y)
#     p_yy = sp.diff(y)
#
#     p_3x = p_xx.diff(x)
#     p_xxy = p_xx.diff(y)
#     p_xyy = p_xy.diff(y)
#     p_3y = p_yy.diff(y)
#
#     # second and third derivative for u
#
#     u_xx = ru.diff(x)
#     u_xy = ru.diff(y)
#     u_yy = su.diff(y)
#
#     u_3x = u_xx.diff(x)
#     u_xxy = u_xx.diff(y)
#     u_xyy = u_xy.diff(y)
#     u_3y = u_yy.diff(y)
#
#     # second and third derivative for v
#
#     v_xx = rv.diff(x)
#     v_xy = rv.diff(y)
#     v_yy = sv.diff(y)
#
#     v_3x = v_xx.diff(x)
#     v_xxy = v_xx.diff(y)
#     v_xyy = v_xy.diff(y)
#     v_3y = v_yy.diff(y)
#
#     # parameters values at the discrete point i
#
#     pi = p.subs(x, 0).subs(y, 0)
#     ui = u.subs(x, 0).subs(y, 0)
#     vi = v.subs(x, 0).subs(y, 0)
#
#     rpi = rp.subs(x, 0).subs(y, 0)  # first derivatives in the x-direction
#     rui = ru.subs(x, 0).subs(y, 0)
#     rvi = rv.subs(x, 0).subs(y, 0)
#
#     spi = sp.subs(x, 0).subs(y, 0)  # first derivatives in the y-direction
#     sui = su.subs(x, 0).subs(y, 0)
#     svi = sv.subs(x, 0).subs(y, 0)
#
#     p_xxi = p_xx.subs(x, 0).subs(y, 0)  # second derivatives
#     print("p_xx = {0}\n".format(p_xxi.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr).subs(c4, c4_expr).subs(c5, c5_expr)\
#        .subs(c6, c6_expr).subs(c7, c7_expr).subs(c8, c8_expr).subs(c9, c9_expr).subs(c10, c10_expr)))
#     p_xyi = p_xy.subs(x, 0).subs(y, 0)
#     print("p_xy = {0}\n".format(
#         p_xyi.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr).subs(c4, c4_expr).subs(c5, c5_expr) \
#         .subs(c6, c6_expr).subs(c7, c7_expr).subs(c8, c8_expr).subs(c9, c9_expr).subs(c10, c10_expr)))
#     p_yyi = p_yy.subs(x, 0).subs(y, 0)
#
#     u_xxi = u_xx.subs(x, 0).subs(y, 0)
#     u_xyi = u_xy.subs(x, 0).subs(y, 0)
#     u_yyi = u_yy.subs(x, 0).subs(y, 0)
#
#     v_xxi = v_xx.subs(x, 0).subs(y, 0)
#     v_xyi = v_xy.subs(x, 0).subs(y, 0)
#     v_yyi = v_yy.subs(x, 0).subs(y, 0)
#
#     p_3xi = p_3x.subs(x, 0).subs(y, 0)  # third derivatives
#
#     p_xxyi = p_xxy.subs(x, 0).subs(y, 0)
#     p_xyyi = p_xyy.subs(x, 0).subs(y, 0)
#     p_3yi = p_3y.subs(x, 0).subs(y, 0)
#
#     u_3xi = u_3x.subs(x, 0).subs(y, 0)
#     u_xxyi = u_xxy.subs(x, 0).subs(y, 0)
#     u_xyyi = u_xyy.subs(x, 0).subs(y, 0)
#     u_3yi = u_3y.subs(x, 0).subs(y, 0)
#
#     v_3xi = v_3x.subs(x, 0).subs(y, 0)
#     v_xxyi = v_xxy.subs(x, 0).subs(y, 0)
#     v_xyyi = v_xyy.subs(x, 0).subs(y, 0)
#     v_3yi = v_3y.subs(x, 0).subs(y, 0)
#
#     pni = pi - tau * (rui + svi) + tau**2 / 2 * (p_xxi + p_yyi) - tau**3 / 6 * (u_3xi + u_xyyi + v_xxyi + v_3yi)
#
#     uni = ui - tau * rpi + tau**2 / 2 * (u_xxi + v_xyi) - tau**3 / 6 * (p_3xi + p_xyyi)
#
#     vni = vi - tau * spi + tau**2 / 2 * (u_xyi + v_yyi) - tau**3 / 6 * (p_xxyi + p_3yi)
#
#     # derivatives at the x-direction
#
#     rpni = rpi - tau * (u_xxi + v_xyi) + tau**2 / 2 * (p_3xi + p_xyyi)
#
#     runi = rui - tau * p_xxi + tau ** 2 / 2 * (u_3xi + v_xxyi)
#
#     rvni = rvi - tau * p_xyi + tau**2 / 2 * (u_xxyi + v_xyyi)
#
#     # derivatives at the y-direction
#
#     spni = spi - tau * (u_xyi + v_yyi) + tau**2 / 2 * (p_xxyi + p_3yi)
#
#     suni = sui - tau * p_xyi + tau**2 / 2 * (u_xxyi + v_xyyi)
#
#     svni = svi - tau * p_yyi + tau**2 / 2 * (u_xyyi + v_3yi)
#
#     _pni = pni.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr).subs(c4, c4_expr).subs(c5, c5_expr)\
#        .subs(c6, c6_expr).subs(c7, c7_expr).subs(c8, c8_expr).subs(c9, c9_expr).subs(c10, c10_expr)
#
#     _uni = uni.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr).subs(c4, c4_expr).subs(c5, c5_expr)\
#        .subs(c6, c6_expr).subs(c7, c7_expr).subs(c8, c8_expr).subs(c9, c9_expr).subs(c10, c10_expr).subs(tau / h, nu)
#
#     _vni = vni.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr).subs(c4, c4_expr).subs(c5, c5_expr)\
#         .subs(c6, c6_expr).subs(c7, c7_expr).subs(c8, c8_expr).subs(c9, c9_expr).subs(c10, c10_expr).subs(tau / h, nu)
#
#     _rpni = rpni.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr).subs(c4, c4_expr).subs(c5, c5_expr)\
#        .subs(c6, c6_expr).subs(c7, c7_expr).subs(c8, c8_expr).subs(c9, c9_expr).subs(c10, c10_expr).subs(tau / h, nu)
#
#     _runi = runi.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr).subs(c4, c4_expr).subs(c5, c5_expr)\
#        .subs(c6, c6_expr).subs(c7, c7_expr).subs(c8, c8_expr).subs(c9, c9_expr).subs(c10, c10_expr).subs(tau / h, nu)
#
#     _rvni = rvni.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr).subs(c4, c4_expr).subs(c5, c5_expr)\
#         .subs(c6, c6_expr).subs(c7, c7_expr).subs(c8, c8_expr).subs(c9, c9_expr).subs(c10, c10_expr).subs(tau / h, nu)
#
#     _spni = spni.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr).subs(c4, c4_expr).subs(c5, c5_expr)\
#        .subs(c6, c6_expr).subs(c7, c7_expr).subs(c8, c8_expr).subs(c9, c9_expr).subs(c10, c10_expr).subs(tau / h, nu)
#
#     _suni = suni.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr).subs(c4, c4_expr).subs(c5, c5_expr)\
#        .subs(c6, c6_expr).subs(c7, c7_expr).subs(c8, c8_expr).subs(c9, c9_expr).subs(c10, c10_expr).subs(tau / h, nu)
#
#     _svni = svni.subs(c1, c1_expr).subs(c2, c2_expr).subs(c3, c3_expr).subs(c4, c4_expr).subs(c5, c5_expr)\
#        .subs(c6, c6_expr).subs(c7, c7_expr).subs(c8, c8_expr).subs(c9, c9_expr).subs(c10, c10_expr).subs(tau / h, nu)
#
#     print(simplify(_pni))
#     # print(simplify(_uni))
#     # print(simplify(_vni))
#     print(simplify(_rpni))
#     # print(simplify(_runi))
#     # print(simplify(_rvni))
#     print(simplify(_spni))
#     # print(simplify(_suni))
#     # print(simplify(_svni))

# symbolCIP(3)
symbolDeltaP()


#!/usr/bin/env python

import numpy as np
from scipy.integrate import ode

#
# f[0] = d theta1/dt = omega1
# f[1] = d theata2/dt = omega2
# f[2] = d omega1/dt = -(g/l1)*theta1 - k*(theta1 - theta2)
# f[3] = d omega2/dt = -(g/l2)*theta2 - k*(theta2 - theta1)
#
# y[0] = theta1
# y[1] = theta2
# y[2] = omega1
# y[3] = omega2
#
def functions(t, y, l1, l2, k):
    g = 9.81
    return [
        y[2],
        y[3],
        -(g/l1)*y[0] - k*(y[0] - y[1]),
        -(g/l2)*y[1] - k*(y[1] - y[0])
    ]

def jacobian(t, y, l1, l2, k):
    g = 9.81
    return [
        [0.0,                  0.0,  1.0,   0.0],
        [0.0,                  0.0,  0.0,   1.0],
        [-(g/l1) - k,            k,  0.0,   0.0],
        [k,            -(g/l2) - k,  0.0,   0.0]
    ]

def init_integrator(theta0_1, theta0_2, t0=0.0, l1=1.0, l2=1.0, k=0.1):
    integrator = ode(functions, jacobian).set_integrator('dopri5',
                                                         atol=1.0e-6,
                                                         rtol=0.0)
    integrator.set_initial_value([theta0_1, theta0_2, 0.0, 0.0], t0)
    integrator.set_f_params(l1, l2, k)
    integrator.set_jac_params(l1, l2, k)
    return integrator

if __name__ == '__main__':
    from argparse import ArgumentParser
    arg_parser = ArgumentParser(description='solved coupled pendulums')
    arg_parser.add_argument('--theta0_1', type=float, default=0.0,
                            help='initial theta of first pendulum')
    arg_parser.add_argument('--theta0_2', type=float, default=0.0,
                            help='initial theta of second pendulum')
    arg_parser.add_argument('--l1', type=float, default=1.0,
                            help='length of first pendulum')
    arg_parser.add_argument('--l2', type=float, default=1.0,
                            help='length of second pendulum')
    arg_parser.add_argument('--k', type=float, default=0.5,
                            help="Hooke's constant")
    arg_parser.add_argument('--t_max', type=float, default=10*2*np.pi,
                            help='maximum time')
    arg_parser.add_argument('--delta_t', type=float, default=0.01,
                            help='delta t')
    options = arg_parser.parse_args()
    integrator = init_integrator(options.theta0_1, options.theta0_2, t0=0.0,
                                 l1=options.l1, l2=options.l2, k=options.k)
    while integrator.successful() and integrator.t < options.t_max:
        integrator.integrate(integrator.t + options.delta_t)
        print('{0:.3f}\t{1:.5f}\t{2:.5f}'.format(integrator.t,
                                                 integrator.y[0],
                                                 integrator.y[1]))


import numpy as np
import matplotlib.pyplot as plt

w = np.array([0.15, 0.6])
eta = 0.01

print(w, eta)

all_weights = []
energy = []

while (w[0]+w[1]) < 1 and w[0] > 0 and w[1] > 0 :
    f_w = - np.log(1 - w[0] - w[1]) - np.log(w[0]) - np.log(w[1])
    energy.append(f_w)

    all_weights.append(w)

    grad = np.array([(1 / (1-w[0]-w[1])) - (1 / w[0]), (1 / (1-w[0]-w[1])) - (1 / w[1])])

    delta_w = eta * grad
    new_w = w - delta_w

    if np.linalg.norm(w - new_w) < 0.001:
        break
    else:
        w = new_w

all_weights = np.array(all_weights)

plt.plot(all_weights[:, 0], all_weights[:, 1], '-o')
plt.xlabel("X-axis")
plt.ylabel("Y-axis")
plt.title("Gradient Descent Weight Updates")
plt.show()

plt.plot(range(len(energy)), energy)
plt.xlabel("Number of Iterations")
plt.ylabel("Energy f(w)")
plt.title("Changes in Energy")
plt.show()

w = np.array([0.15, 0.6])
eta = 0.01

print(f"Initial Weight is: {w}\nLearning Rate is: {eta}")

all_weights = []
energy = []

H = np.empty((2, 2))

while (w[0]+w[1]) < 1 and w[0] > 0 and w[1] > 0 :
    f_w = - np.log(1 - w[0] - w[1]) - np.log(w[0]) - np.log(w[1])
    energy.append(f_w)

    all_weights.append(w)

    grad = np.array([(1 / (1-w[0]-w[1])) - (1 / w[0]), (1 / (1-w[0]-w[1])) - (1 / w[1])])

    H[0,:] = [(1 / (1-w[0]-w[1])**2) + (1 / w[0]**2),
              (1 / (1-w[0]-w[1])**2)]
    H[1,:] = [(1 / (1-w[0]-w[1])**2),
              (1 / (1-w[0]-w[1])**2) + (1 / w[1]**2)]

    delta_w = eta * np.matmul(np.linalg.inv(H), grad)
    new_w = w - delta_w

    if np.linalg.norm(w - new_w) < 0.001:
        break
    else:
        w = new_w

all_weights = np.array(all_weights)
plt.plot(all_weights[:, 0], all_weights[:, 1], '-o')
plt.xlabel("X-axis")
plt.ylabel("Y-axis")
plt.title("Newton's Method Weight Updates")
plt.show()

plt.plot(range(len(energy)), energy)
plt.xlabel("Number of Iterations")
plt.ylabel("Energy f(w)")
plt.title("Changes in Energy for Newton's Method")
plt.show()


# -*- coding: utf-8 -*-
import numpy as np

def h(x, theta):
    return 1 / (1 + np.exp(-np.dot(x, theta)))

def cost(x, y, theta):
    _h = h(x, theta)
    if y == 1:
        return -np.log(_h)
    else:
        return -np.log(1 - _h)

def J(X, Y, theta):
    m, n = np.shape(X)
    loss = 0
    for k in range(m):
        loss += cost(X[k], Y[k], theta)
    return loss / m

def logistic_gd(X, Y, alpha=0.01, epsilon=1e-6, trace=True):
    m = len(X)
    _X = np.column_stack((np.ones(m), X))
    m, n = np.shape(_X)
    theta, j1, cnt = np.ones(n), 0, 0
    Xt = _X.T

    while True:
        loss = h(_X, theta) - Y
        gradient = np.dot(Xt, loss) / m
        theta -= alpha * gradient

        j = J(_X, Y, theta)

        if trace:
            print("[ Epoch {0} ] theta = {1}, loss = {2}, error = {3})".format(
                cnt, theta, loss, j))

        if abs(j - j1) < epsilon:
            break
        else:
            j1 = j

        cnt += 1
    return theta


import numpy
import scipy.stats
import pandas

def compare_averages(filename):
    """
    Performs a t-test on two sets of baseball data (left-handed and right-handed hitters).

    You will be given a csv file that has three columns.  A player's
    name, handedness (L for lefthanded or R for righthanded) and their
    career batting average (called 'avg'). You can look at the csv
    file by downloading the baseball_stats file from Downloadables below.

    Write a function that will read that the csv file into a pandas data frame,
    and run Welch's t-test on the two cohorts defined by handedness.

    One cohort should be a data frame of right-handed batters. And the other
    cohort should be a data frame of left-handed batters.

    We have included the scipy.stats library to help you write
    or implement Welch's t-test:
    http://docs.scipy.org/doc/scipy/reference/stats.html

    With a significance level of 95%, if there is no difference
    between the two cohorts, return a tuple consisting of
    True, and then the tuple returned by scipy.stats.ttest.

    If there is a difference, return a tuple consisting of
    False, and then the tuple returned by scipy.stats.ttest.

    For example, the tuple that you return may look like:
    (True, (9.93570222, 0.000023))
    """
    dataframe = pandas.read_csv(filename)
    dataframe_l = dataframe[dataframe['handedness'] == 'L']
    dataframe_r = dataframe[dataframe['handedness'] == 'R']
    result_df = scipy.stats.ttest_ind(dataframe_l['avg'], dataframe_r['avg'], equal_var=False)
    if result_df[1] <= 0.05:
        return(False, result_df)
    return(True, result_df)


import matplotlib.pyplot as plt
import numpy as np
import numpy.linalg as LA

def lin_lsq(x,y):
    """
    This function returns the coefficients of the linear regression AND the corresponding linear polyonomial
    of the given input data, x and y, in row or column vector form. It implements the linear version of the Method
    of Least Squares. It also displays a table where each row contains the x and y coordinates of the data, the
    linear function of the method of LSQ evaluated at that point and the absolute error of the best-fit linear
    function and the data. We also display the total squared error at the end.
    """
    n = np.prod(x.shape)
    x = x.reshape(n,1) #if given otherwise, we turn x and y vectors to column vectors
    y = y.reshape(n,1)
    s_x = np.sum(x); s_xx = np.sum(x**2); s_y = np.sum(y); s_xy = np.sum(x*y)
    S = np.array([[s_xx, s_x], [s_x, 4]], float)
    d = np.array([s_xy, s_y], float)
    try:
        S_inv = LA.inv(S) # if LA.det(S)=0 then a LinAlgError exception will be raised
    except LinAlgError:
        print("""With the given data, the system of normal equations of the Method of LSQ, does not have or
                 has infinite solutions because the coefficient matrix, S, is singular, i.e. it doesnt have an inverse.""")
        return None
    else:
        sol = LA.solve(S,d)
        a = sol[0]; b = sol[1]
        g = np.poly1d([a,b])
        g_x = np.polyval(g,x).reshape(n,1)
        err = y-g_x
        print('|    x    |    y    |   g(x)  |   y-g(x) | \n ----------------------------------------')
        table = np.concatenate((x, y, g_x, err), axis=1)
        for (x_i, y_i, g_xi, err_i) in table:
            print(f'|  {x_i:5.2f}  |  {y_i:5.2f}  |  {g_xi:5.2f}  |  {err_i:6.2f}  |')
        print(f"Also the total squared error is {sum(err**2)[0]:.2f} \n")
        return (a,b), g

x = np.array([1, 3, 5, 7], int)
y = np.array([2.5, 3.5, 6.35, 8.1], float)
(a,b), g = lin_lsq(x,y)
print(f"The coefficients of the linear polyonomial of the Method of LSQ are: a = {a:.2f} and b = {b:.2f}")
print(g)
t = np.linspace(0,10, num=1000)
g_t = g(t)
print(g_t)

# Creating the plot and editing some of its attributes for clarity. We will just copy and paste them for future use.
# We also assign every modification to our plot to a dummy/garbage collecting variable; '_' to prevent unwanted outputs

plt.figure(figsize=(10,5))
plt.scatter(x, y , marker='*', c='red', s=80, label='Our Data')
plt.plot(t, g_t, c='blue', linewidth='2.0', label=r'$g(x)=ax+b$')
plt.xlabel('x', fontsize=14)
plt.ylabel('y', fontsize=14)
plt.grid(True)
axes = plt.gca() #gca stands for get current axes
axes.set_xlim([-0.5,10])
axes.set_ylim([-0.5,10])
plt.rcParams['xtick.labelsize']=18
plt.rcParams['ytick.labelsize']=18
plt.legend(loc='best', fontsize=14) #Sets the legend box at the best location
plt.axhline(0, color='black', lw=2)
plt.axvline(0, color='black', lw=2)

plt.show()


# --------------
# 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,eigencevectors=np.linalg.eig(adj_mat)

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

print(eigen_1)
# most important page
page=int(np.where(eigen_1 == eigen_1.max())[0])+1
#page=eigen_1.where()
print(page)
# Code ends here

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

# Initialize stationary vector I
init_I=np.array([1,0,0,0,0,0,0,0]);
print(init_I)
for i in range(10):
    init_I=abs(np.dot(adj_mat, 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)
# Perform iterations for power method

# Code ends here

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

# New Adjancency matrix
# New Adjancency matrix
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]])

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

# Perform iterations for power method
for i in range(10):
    new_init_I=abs(np.dot(new_adj_mat, new_init_I))/(np.linalg.norm(new_init_I,1))

print(new_init_I)

# Code ends here

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

# Code starts here

# Modified adjancency matrix
n=len(new_adj_mat)
l=np.ones(new_adj_mat.shape).astype(float)
G=np.dot(alpha,new_adj_mat)+np.dot((1-alpha)*(1/n),l)
# Initialize stationary vector I
final_init_I=np.array([1,0,0,0,0,0,0,0])
# Perform iterations for power method
for i in range(1000):
    final_init_I=abs(np.dot(G, final_init_I))/(np.linalg.norm(final_init_I,1))
print(final_init_I)

# Code ends here


def edit_distance(s1, s2, verbose=False):
    import numpy as np
    n1 = len(s1)+1
    n2 = len(s2)+1
    dp_matrix = np.zeros([n1, n2])
    for i in range(n1):
        dp_matrix[i,0] = i
    for j in range(n2):
        dp_matrix[0,j] = j

    for i in range(1,n1):
        for j in range(1,n2):
            delta = 1
            if (s1[i-1] == s2[j-1]):
                delta = 0
            dp_matrix[i,j] = min(dp_matrix[i-1,j-1]+delta,
                                 dp_matrix[i-1,j]+1,
                                 dp_matrix[i,j-1]+1)
    if (verbose):
        print dp_matrix
    return int(dp_matrix[n1-1,n2-1])

if (__name__ == "__main__"):
    import sys
    s1 = sys.argv[1]
    s2 = sys.argv[2]
    verbose=False
    ED = edit_distance(s1, s2, verbose)
    print("{0}\t{1}\t{2}".format(s1, s2, ED))


# Implementaion of k-Means Clustering algo for credit card fraud detection
# https://github.com/llSourcell/k_means_clustering
# Marker info - https://matplotlib.org/api/markers_api.html

import sys
from pprint import pprint
import numpy as np
import matplotlib.pyplot as plt

#np.random.seed(1)
datapath = 'k_means_clustering-master/durudataset.txt'
no_of_clusters = 2

def data_process(datapath):
    # preprocess the data
    data = np.loadtxt(datapath)
    num_rows, num_features = data.shape

    return data, num_rows, num_features

def plot(centroids, cluster_points, test_cluster={}):
    # Plot the clustered datapoints and their centroid
    colors = ['r', 'g', 'm', 'c', 'y', 'w']
    fig, ax = plt.subplots()

    for key in cluster_points.keys():
        for point in cluster_points[key]:
            ax.plot(point[0], point[1], (colors[key] + 'o'))

    if test_cluster:
        for key in test_cluster.keys():
            for point in test_cluster[key]:
                ax.plot(point[0], point[1], (colors[key] + 's'))

    for centroid in centroids:
        ax.plot(centroid[0], centroid[1], 'ko')     # black dot
    plt.show()

def euclidean(v):
    # returns norm of a vector/matrix
    return np.linalg.norm(v)

def clustering(data, belongs_to, k):
    # returns k clusters of data points
    cluster_points = {}
    for i in range(k):
        cluster_points[i] = []
    for i, value in enumerate(belongs_to):
        cluster_points[value].append(data[i])

    return cluster_points

def kmeans(data, num_rows, num_features, k):
    # k-Means algorithm
    rand_no = np.random.choice(num_rows, k)
    new_centroids = [data[i] for i in rand_no]
    belongs_to = list(np.zeros(num_rows))
    error = 1
    iterator = 0

    while error > 0:
        centroids = new_centroids
        for i, row in enumerate(data):
            distance = []
            for centroid in centroids:
                distance.append(euclidean(row - centroid))
            centroid_no = np.argmin(distance)
            belongs_to[i] = centroid_no

        # calculate no. of datapoints in each cluster
        # by calculating the frequency of items in belongs_to list
        d = {x: belongs_to.count(x) for x in belongs_to}
        cluster_counter = d.values()

        tmp_centroids = np.zeros((k, num_features))
        for i, row in enumerate(data):
            tmp_centroids[belongs_to[i]] += row
        tmp_centroids = [tmp_centroids[i]/float(cluster_counter[i]) for i in range(k)]

        new_centroids = tmp_centroids
        sub = np.subtract(new_centroids, centroids)
        error = euclidean(sub)
        iterator += 1
        new_centroids = [np.ndarray.tolist(i) for i in new_centroids]

    cluster_points = clustering(data, belongs_to, k)

    return new_centroids, cluster_points, iterator

def inference(test_data, centroids):
    # cluster new data points
    belongs_to = list(np.zeros(test_data.shape[0]))

    for i, data in enumerate(test_data):
        distance = []
        for centroid in centroids:
            distance.append(euclidean(data - centroid))
        belongs_to[i] = np.argmin(distance)

    return belongs_to

def main():
    data, num_rows, num_features = data_process(datapath)
    centroids, cluster_points, iterations = kmeans(data, num_rows, num_features, no_of_clusters)

    # Testing
    test_data = np.array([[1.8,1.8], [0.2,0.2], [0.25,1.5]])
    test_belongs = inference(test_data, centroids)
    test_cluster = clustering(test_data, test_belongs, no_of_clusters)

    # Graph plot
    print "Centroid of clusters: "
    pprint(centroids)
    print "No. of iterations: ", iterations
    plot(centroids, cluster_points, test_cluster)

if __name__ == '__main__':
    main()

'''
Result-
Centroid of clusters:
[[1.5805824656617171, 1.5689741160857174],
 [0.2233106749885314, 0.28960446247509586]]
No. of iterations:  3
'''


"""
                        Eigendecomposition
Eigenvalues and eigenvectors are easy to find with Python and NumPy. Remember,
an eigenvector of a square matrix  A  is a nozero vector  v  such that multiplication by  A
alters only the scale of  v
                                Av=λv
The scalar  λ  is known as the eigenvalue corresponding to this eigenvector.
 """

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.cm as cm

# find the eigenvalues and eigenvectors for a simple square matrix
A = np.diag(np.arange(1, 4))
"""
[[1 0 0]
 [0 2 0]
 [0 0 3]]
"""

eigenvalues, eigenvectors = np.linalg.eig(A)
"""
Eigenvalue's: [1. 2. 3.]
Eigenvectors:[[1. 0. 0.]
              [0. 1. 0.]
              [0. 0. 1.]]
"""

# the eigenvalue w[i] corresponds to the eigenvector v[:, i]
print('Eigenvalue: {}'.format(eigenvalues[1]))
print('Eigenvector: {}'.format(eigenvectors[:, 1]))
"""
Eigenvalue: 2.0
Eigenvector: [0. 1. 0.]
"""

# verify eigendecomposition - should return original matrix
matrix = np.matmul(np.diag(eigenvalues), np.linalg.inv(eigenvectors))
output = np.matmul(eigenvectors, matrix).astype(np.int)
"""
[[1 0 0]
 [0 2 0]
 [0 0 3]]
"""

# plot the eigenvectors
origin = [0,0,0]

fig = plt.figure(figsize=(18,10))
fig.suptitle('Effects of Eigenvalues and Eigenvectors')
ax1 = fig.add_subplot(121, projection='3d')

ax1.quiver(origin, origin, origin, eigenvectors[0, :], eigenvectors[1, :], eigenvectors[2, :], color = 'k')
ax1.set_xlim([-3, 3])
ax1.set_ylim([-3, 3])
ax1.set_zlim([-3, 3])
ax1.set_xlabel('X axis')
ax1.set_ylabel('Y axis')
ax1.set_zlabel('Z axis')
ax1.view_init(15, 30)
ax1.set_title("Before Multiplication")

# multiply original matrix by eigenvectors
new_eig = np.matmul(A, eigenvectors)
ax2 = plt.subplot(122, projection='3d')

# plot the new vectors
ax2.quiver(origin, origin, origin, new_eig[0, :], new_eig[1, :], new_eig[2, :], color = 'k')

# plot the eigenvalues for each vector (the amount the vector should be scaled by)
ax2.plot((eigenvalues[0]*eigenvectors[0]), (eigenvalues[1]*eigenvectors[1]), (eigenvalues[2]*eigenvectors[2]), 'rX')
ax2.set_title("After Multiplication")
ax2.set_xlim([-3, 3])
ax2.set_ylim([-3, 3])
ax2.set_zlim([-3, 3])
ax2.set_xlabel('X axis')
ax2.set_ylabel('Y axis')
ax2.set_zlabel('Z axis')
ax2.view_init(15, 30)

# check the png file for plot
plt.savefig('eigen_vectors.png')
plt.close(fig)


import numpy as np

#Importing the data
data = np.loadtxt('sample.txt', delimiter=",")
class LinearRegression(object):
    #Setting some of our initial parameters
    def __init__(self, alpha, theta):
        self.alpha = alpha
        self.theta = theta
    #Cleaning the data to make it ready for the training
    def Cleaningdata(self, data):
        ones = np.ones((len(data),1))
        #This needs to be modified before calibrated the method
        x = np.concatenate((ones, data[:,0].reshape(-1,1)), axis=1)
        y = data[:,1].reshape(-1,1)
        return (x, y)
    #Training the model using gradient descent
    def train(self,x , y, m):
        error = y.T - (self.theta @ x.T)
        J = m * (error @ error.T)
        dj = (m*-2) * ((error) @ x)
        self.theta = self.theta - self.alpha * dj
    #We can also train our model using the normal equation
    def normalEquation(self, x, y):
        self.theta = np.linalg.inv(x.T @ x) @ (x.T @ y)
    #We can query a specific answer
    def query(self, x):
        return self.theta @ x

#Example of a model
model = LinearRegression(0.00000001, [10,10])
x, y = model.Cleaningdata(data)

#Setting our training loop
iteration = 0
m = 1/len(data)
while iteration < 100000:
    iteration += 1
    model.train(x,y,m)
#Asking the model to answer a specific question
answer = model.query([100,23])

#Example of model traning using the normal equation
model.normalEquation(x,y)


import math
import sklearn.metrics
import pandas as pd
import numpy as  np
import matplotlib.pyplot as plt

def residuals(actual, predicted):
    return actual - predicted

def sse(actual, predicted):
    return (residuals(actual, predicted) **2).sum()

def mse(actual, predicted):
    n = actual.shape[0]
    return sse(actual, predicted) / n

def rmse(actual, predicted):
    return math.sqrt(mse(actual, predicted))

def ess(actual, predicted):
    return ((predicted - actual.mean()) ** 2).sum()

def tss(actual):
    return ((actual - actual.mean()) ** 2).sum()

def regression_errors(actual, predicted):
    return pd.Series({
        'sse': sse(actual, predicted),
        'ess': ess(actual, predicted),
        'tss': tss(actual),
        'mse': mse(actual, predicted),
        'rmse': rmse(actual, predicted),
    })

def baseline_mean_errors(actual):
    predicted = actual.mean()
    return {
        'sse': sse(actual, predicted),
        'mse': mse(actual, predicted),
        'rmse': rmse(actual, predicted),
    }

def better_than_baseline(actual, predicted):
    rmse_baseline = rmse(actual, actual.mean())
    rmse_model = rmse(actual, predicted)
    return rmse_model < rmse_baseline

def model_significance(ols_model):
    return {
        'r^2 -- variance explained': ols_model.rsquared,
        'p-value -- P(data|model == baseline)': ols_model.f_pvalue,
    }
def plot_residuals(actual, predicted):
    residuals = actual - predicted
    plt.hlines(0, actual.min(), actual.max(), ls=':')
    plt.scatter(actual, residuals)
    plt.ylabel('residual ($y - \hat{y}$)')
    plt.xlabel('actual value ($y$)')
    plt.title('Actual vs Residual')
    plt.show()


from statistics import mean
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import style

style.use('fivethirtyeight')

xs = np.array([1, 2, 3, 4, 5, 6], dtype=np.float64)
ys = np.array([5, 4, 6, 5, 6, 7], dtype=np.float64)

# plt.scatter(xs, ys)
# plt.show()

def best_fit_slope_and_intercept(xs, ys):
    # least square method
    m = ((mean(xs) * mean(ys)) - mean(xs*ys)) / (mean(xs)**2 - mean(xs**2))
    b = mean(ys) - m*mean(xs)
    return m, b

def squared_error(ys_orig, ys_line):
    # calculate squared error
    return sum((ys_line-ys_orig)**2)

def coefficient_of_determination(ys_orig, ys_line):
    y_mean_line = [mean(ys_orig) for y in ys_orig]
    squared_error_regr = squared_error(ys_orig, ys_line)
    squared_error_y_mean = squared_error(ys_orig, y_mean_line)
    return 1 - (squared_error_regr / squared_error_y_mean)

m, b = best_fit_slope_and_intercept(xs, ys)

# print (m, b)
regression_line = [(m*x)+b for x in xs]
# The line above is equal
# for x in xs:
#     regression_line.append((m*x)+b)

predict_x = 8
predict_y = (m*predict_x)+b

r_squared = coefficient_of_determination(ys, regression_line)
print (r_squared)

plt.scatter(xs, ys)
plt.scatter(predict_x, predict_y, color='g')
plt.plot(xs, regression_line)
plt.show()