import numpy as np
import csv

def input_parser(fn_csv):
    """
    Parse the input.csv into python list
    Output:
        input_lines: [[x_0, y_0], [x_1, y_1], ...]
    """
    input_lines = []
    with open(fn_csv, 'r') as f:
        lines = csv.reader(f)
        for l in lines:
            input_lines.append([float(l[0]), float(l[1])])
    return np.array(input_lines)

def dis_euclidian(input_data_points, center):
    """
    Euclidian distance
    Input:
        input_data_points [[x_0, y_0], [x_1, y_1], ...]
        center [x_c, y_c]
    Output:
        euclidien distance [d_0, d_1, ...]
    """

    return np.sqrt(np.sum(np.power((input_data_points - center), 2.0), axis=1))

def error_kmeans_centers(centers_updated, centers_old):
    """
    Calculate the convergence error
    if the centers do not change.
    Input:
        centers_updated [[x_u_0, y_u_0], [x_u_1, y_u_1], ...]
        centers_old [[x_o_0, y_o_0], [x_o_1, y_o_1], ...]
    Output:
        err_km_center
    """
    return np.sum(np.abs(centers_updated-centers_old))

def error_kmeans(input_data_points, centers, k):
    """
    Calculate the k-means error.
    The total error metric should also be accumulated
    as the sum of the distances of each point to its assigned cluster centroid.
    Input:
        input_data_points [[x_0, y_0], [x_1, y_1], ...]
        centers [[x_c_0, y_c_0], [x_c_1, x_c_1], ...]
    Output:
        err_km
    """
    distance = np.zeros((input_array.shape[0], k))
    err = np.zeros(input_data_points.shape[0])
    clusters = expectation(input_data_points, centers, distance, k)
    for i in range(k):
        err[clusters==i] = distance[clusters==i, i]
    return np.sum(err), clusters

def expectation(input_data_points, centers, distance, k):
    """
    Expectation helper function
    """
    for i in range(k):
        distance[:, i] = dis_euclidian(input_data_points, centers[i])
    clusters = np.argmin(distance, axis=1)
    return clusters

def kmeans(input_array, cluster_centers, k):
    """
    k-means EM algorithm.
    Expectation: each input data point is assigned to the closest cluster centroid measured using the Euclidian distance metric
    Maximisation: total error metric is minimised by adjusting the cluster centroids to the centre of the data points assigned to it.
    Input:
        input_array [[x_0, y_0], [x_1, y_1], ...]
        cluster_centers initialisation
    Output:
        cluster_centers
    """
    cluster_centers_old = np.zeros(cluster_centers.shape)
    distance = np.zeros((input_array.shape[0], k))

    err = 1.0
    while err != 0:
        # expectation
        clusters = expectation(input_array, cluster_centers, distance, k)

        # maximisation
        cluster_centers_old = np.copy(cluster_centers)
        for i in range(k):
            cluster_centers[i] = np.mean(input_array[clusters==i], axis=0)
        err = error_kmeans_centers(cluster_centers, cluster_centers_old)

if __name__ == "__main__":

    cluster_name = ['Adam', 'Bob', 'Charley', 'David', 'Edward']
    k = len(cluster_name) # cluster number
    cluster_centers = np.array([[-0.357, -0.253], [-0.055, 4.392], [2.674, -0.001], [1.044, -1.251], [-1.495, -0.090]])

    # parse input.csv
    input_array = input_parser('input.csv')
    # do k-means until convergence
    kmeans(input_array, cluster_centers, k)
    # calculate error metric
    err_km, clusters = error_kmeans(input_array, cluster_centers, k)
    # assign the cluster
    clusters = [cluster_name[c] for c in clusters]

    # write to OUTPUT.TXT
    with open('OUTPUT.TXT', 'w') as f:
        f.write("{:0.3f}".format(err_km)+'\n')
        for c in clusters:
            f.write(c+'\n')


#Runge kutta system of equations
import numpy as np

def dy(x,y,z):
    dyvalue = z
    return dyvalue
 #Note: change the derivative function based on question!!!!!!  Example: y-x

def dz(x,y,z):
    dyvalue = x*(z**2)-y**2
    return dyvalue
 #Note: change the derivative function based on question!!!!!!  Example: y-x

z = 0
y0 = 3 # input y for xo
x = 0
h = 0.2
y = y0
finalx = 0.4
interval = finalx - x
n = interval/h
i = 1

while i<=n:
    print("_________________" + str(i)+ "_________________"+"\n")
    k1 = dy(x,y,z) *h
    print("k1= "+str(k1))
    l1 = dz(x,y,z) *h
    print("l1= "+str(l1))
    k2 = dy(x+h*0.5,y+k1*0.5,z+l1*0.5) *h
    print("k2= "+str(k2))
    l2 = dz(x+h*0.5,y+k1*0.5, z+l1*0.5) *h
    print("l2= "+str(l2))
    k3 = dy(x+h*0.5,y+k2*0.5, z+l2*0.5) *h
    l3 = dz(x+h*0.5,y+k2*0.5, z+l2*0.5) *h
    print("l3= "+str(l3))
    print("k3= "+str(k3))
    k4 = dy(x+h,y+k3, z+l3) *h
    print("k4= "+str(k4))
    l4 = dz(x+h,y+k3, z+l3) *h
    print("l4= "+str(l4))
    y = y + (k1+2*k2+2*k3+k4)/6
    z = z + (l1+2*l2+2*l3+l4)/6
    x = x+h
    i+=1
    print("\n")
    print("y =       " + str(y))
    print("\n")
    print("z =       " + str(z))
    print("\n")


import numpy as np
import matplotlib.pyplot as plt
import scipy as sy
import scipy.fftpack as syfp
plt.close("all")
# Initial conditions
uInitial=0      # Inital displacement
udInitial=1    # Intial velocity

# Input parameters
omega=5         # Angular frequency
m=1             # Mass
t=np.arange(0.01,0.3+0.01,0.01)      # time step
dT=np.zeros(int(np.shape(t)[0]))
tArray=np.zeros(int(np.shape(t)[0]))
gamma=1/2       # for Newmark method
betaArray=np.array([0.01,1/12,1/6,1/4,1/3])
beta=1/4
#for k in range(0,len(betaArray)):
#for j in range(0,len(t)) :
    #beta=betaArray[k]
timeStep=0.1
# Exact solution
T=2*np.pi/omega # Cyclic frequency
#t=np.linspace(0,200*T,2)  # Time interval
drange=np.arange(0,5*T,timeStep)
umax=pow(pow(uInitial,2)+pow(udInitial/omega,2),0.5)
uExact=umax*np.sin(omega*drange)    # Exact solution

plt.figure(1)
plt.grid(True)                                  # Enable grids
plt.rc('grid', linestyle="--", color='black')   # Darker grids
plt.plot(drange,uExact)

# Newmark's solution

# initializing vectors
u=np.zeros(drange.size)
ud=np.zeros(drange.size)
udd=np.zeros(drange.size)
KE=np.zeros(drange.size)

# Applying initial conditions
u[0]=uInitial
ud[0]=udInitial
KE[0]=0.5*m*np.power(ud[0],2)

# python for loop does not support the use of float step-size
#drange=np.arange(0,100*T,timeStep)  # limits for the loop

# t.size-1 is used otherwise will exceed bounds limit for (i+1)
for i in range(0,drange.size-1):
    C1=u[i]+timeStep*ud[i]+0.5*np.power(timeStep,2)*(1-2*beta)*udd[i]
    C2=ud[i]+timeStep*(1-gamma)*udd[i]

    udd[i+1]=-np.power(omega,2)*C1/(1+beta*np.power(omega,2)*np.power(timeStep,2))
    ud[i+1]=ud[i]+timeStep*((1-gamma)*udd[i]+gamma*udd[i+1])
    u[i+1]=u[i]+timeStep*ud[i]+0.5*np.power(timeStep,2)*((1-2*beta)*udd[i]+2*beta*udd[i+1])
    KE[i+1]=0.5*m*np.power(ud[i+1],2)

plt.plot(drange,u)
plt.xlabel('time (seconds)')
plt.ylabel('Displacement (u)')
plt.legend(['Exact solution','Newmark\'s solution'])
plt.figure(2)
plt.plot(drange,KE)
plt.xlabel('time (seconds)')
plt.ylabel('Kinetic energy')

# Calculating the period error

freqdist = np.fft.fftfreq(int(len(u)))
#freqdist=(1/(timeStep*int(np.shape(drange)[0])))*np.arange(int(np.shape(drange)[0]))

plt.figure(3)
FFTNewmark=np.fft.fft(u)
FFTNewmark=FFTNewmark[:int(np.shape(FFTNewmark)[0]/2)]
plt.plot(abs(FFTNewmark))
maxFreqNewmarkIndex=np.argmax(abs(FFTNewmark))
maxFreqNewmark=freqdist[maxFreqNewmarkIndex]
TNewmark=abs(1/maxFreqNewmark)

FFTExact=np.fft.fft(uExact)
FFTExact=FFTExact[:int(np.shape(FFTExact)[0]/2)]
plt.plot(abs(FFTExact))
plt.legend(['Newmark\'s solution','Exact solution'])

maxFreqExactIndex=np.argmax(abs(FFTExact))
maxFreqExact=freqdist[maxFreqExactIndex]
TExact=abs(1/maxFreqExact)

cycles=drange[-1]/TNewmark
TExactEnd=abs(TExact)*cycles
#TExactEnd=abs(TExact)*cycles
deltaT=(drange[-1]-TExactEnd)
#dT[j]=deltaT/T
#dT[j]=deltaT/TExact
#tArray[j]=timeStep/T
#tArray[j]=timeStep/TExact
#print('t=',timeStep)
#print(deltaT/TExact)
rate=maxFreqExactIndex/maxFreqNewmarkIndex
TNewmark=T/rate
cycles=drange[-1]/TNewmark
TExactEnd=abs(T)*cycles
#deltaT=(drange[-1]-TExactEnd)
deltaT=(T-TNewmark)
#dT[j]=deltaT/T

plt.figure(4)
plt.plot(tArray,dT)
#plt.legend(['$\beta$=0','$\beta$=1/12','$\beta$=1/6','$\beta$=1/4','$\beta$=1/3'])

plt.grid(True)                                  # Enable grids
plt.rc('grid', linestyle="--", color='black')
plt.legend(['$\\beta$=0','$\\beta$=1/12','$\\beta$=1/6','$\\beta$=1/4','$\\beta$=1/3'])
plt.xlabel(r'$\frac{\Delta t}{T}$')
plt.ylabel('$\\frac{\Delta T}{T}$')
plt.savefig('Period error.jpg', dpi=100)


# -*- coding: utf-8 -*-
"""
Calculus with a 1D mesh function.
"""
import numpy as np

class MeshCalculus:
    def __init__(self, vectorized=True):
        self.vectorized = vectorized

    def differentiate(self, f, x):
        '''
        Computes the derivative of f by centered differences, but
        forw and back difference at the start and end, respectively.
        '''
        dx = x[1] - x[0]
        Nx = len(x) - 1     # number of spatial steps
        num_dfdx = np.zeros(Nx+1)
        # Compute approximate derivatives at end-points first
        num_dfdx[0] = (f(x[1]) - f(x[0]))/dx          # FD approx.
        num_dfdx[Nx] = (f(x[Nx]) - f(x[Nx-1]))/dx     # BD approx.
        # proceed with approximate derivatives for inner mesh points
        if self.vectorized:
            num_dfdx[1:-1] = (f(x[2:]) - f(x[:-2]))/(2*dx)
        else:   # scalar version
            for i in range(1, Nx):
                num_dfdx[i] = (f(x[i+1]) - f(x[i-1]))/(2*dx)
        return num_dfdx

    def integrate(self, f, x):
        '''
        Computes the integral of f(x) over the interval
        covered by x.
        '''
        dx = x[1] - x[0]
        F = np.zeros(len(x))
        F[0] = 0    # starting value for iterative scheme
        if self.vectorized:
            all_trapezoids = np.zeros(len(x)-1)
            all_trapezoids[:] = 0.5*(f(x[:-1]) + f(x[1:]))*dx
            F[1:] = np.cumsum(all_trapezoids)
        else:   # scalar version
            for i in range(0, len(x)-1):
                F[i+1] = F[i] + 0.5*(f(x[i]) + f(x[i+1]))*dx
        return F

def test_differentiate():
    def f(x):
        return 4*x - 2.5
    def dfdx(x):
        derivatives = np.zeros(len(x))
        derivatives[:] = 4
        return derivatives

    a = 0; b = 1; Nx = 10
    x = np.linspace(a, b, Nx+1)
    exact_dfdx = dfdx(x)        # compute exact derivatives
    # test vectorized version
    calc_v = MeshCalculus(vectorized=True)
    num_dfdx  = calc_v.differentiate(f, x)
    print np.abs(num_dfdx - exact_dfdx)
    diff = np.abs(num_dfdx - exact_dfdx).max()
    tol = 1E-14
    assert diff < tol
    # test scalar version
    calc = MeshCalculus(vectorized=False)
    num_dfdx  = calc.differentiate(f, x)
    print np.abs(num_dfdx - exact_dfdx)
    diff = np.abs(num_dfdx - exact_dfdx).max()
    assert diff < tol

def test_integrate():
    def f(x):
        return 4*x - 2.5
    a = 0; b = 1; Nx = 10
#    a = 2.5/4; b = 10; Nx = 2
    x = np.linspace(a, b, Nx+1)
    # The exact integral amounts to the total area of two triangles
    I_exact = 0.5*abs(2.5/4 - a)*f(a) + 0.5*abs(b - 2.5/4)*f(b)
    # test vectorized version
    calc_v = MeshCalculus(vectorized=True)
    F = calc_v.integrate(f, x)
    print F, I_exact
    diff = np.abs(F[-1] - I_exact)
    print diff
    tol = 1E-14
    assert diff < tol
    # test scalar version
    calc = MeshCalculus(vectorized=False)
    F = calc.integrate(f, x)
    print F, I_exact
    diff = np.abs(F[-1] - I_exact)
    print diff
    assert diff < tol

if __name__ == '__main__':
    test_differentiate()
    test_integrate()


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

# # Differentiation and Searching
# [Numerical differentiation](https://personal.math.ubc.ca/~pwalls/math-python/differentiation/differentiation/) in really straightforward, where we will evaluate the forward, central, and extrapolated-difference methods.  Another tool is trial-and-error searching which can be used in root-finding, minimization, and is widely used to solve problems where analytic solutions do not exist or are impractical.
#
# ## Numerical differentiation
# Before implementing a numerical method for differentiation, let's review some basics from Introductory Calculus.  The elementery definition was
#
# $\frac{dy(t)}{dt} = \displaystyle \lim_{h\rightarrow 0}\frac{y(t+h)-y(t)}{h}$,
#
# but this is impractical to implement in a computer.  The denominator is approaching zero and the finite machine prescision causes oscillations in the solution.  Therefore we implement a series of approximations to obtain a practical solution.
#
# ### Forward difference
# The most direct method for numerical differentiaion starts by expanding a function in a Taylor series to obtain its value a small step *h* away:
#
# $y(t+h) = y(t) + h\frac{dy(t)}{dt} + \frac{h^2}{2!}\frac{d^2y(t)}{dt^2} + \frac{h^3}{3!}\frac{d^3y(t)}{dt^3} + \cdots$.
#
# We obtain a *forward-difference* derivative by using the approximation in the elementary definition for $y^\prime(t)$:
#
# $\frac{dy(t)}{dt}\biggr\rvert_{fd} = \frac{y(t+h)-y(t)}{h}$.
#
# An approximation for the error follows from substituting the Taylor series:
#
# $\frac{dy(t)}{dt}\biggr\rvert_{fd} \simeq \frac{dy(t)}{dt} + \frac{h}{2}\frac{d^2y(t)}{dt^2} + \cdots$.
#
# You can think of this approximation as using two points to represent the function by a straight line between $x$ and $x+h$.  The approximation has an error proportional to $h$ (the slope of the line).  Therefore, we can make the approximation error smaller by taking smaller steps (decreasing $h$), yet there will be some round-off error that is present from the subtraction in the *forward-differenece* derivative.  To see how this algorithm works, let $y(t) = a + bt^2$, where the exact derivative is $y^\prime = 2bt$ and the computed derivative is:
#
# $\frac{dy(t)}{dt}\biggr\rvert_{fd} \simeq \frac{y(t+h)-y(t)}{h} = \frac{a+b(t+h)^2 - a - bt^2}{h} = \frac{2bth + bh^2}{h} = 2bt + bh$.
#
# This approximation is only good for small *h*, where the $bh \ll 1$.  Implementing the *forward-difference* algorithm in python is super easy.  Simply use the *diff* function from **numpy**.  Suppose you have an array of *N* values in *y*, then *np.diff(y)* returns an array with length *N-1* containing the consecutive differences (i.e., y[1]-y[0], y[2]-y[1], ...).  If the values are sampled at regular intervals in the *t* array, then $y^\prime(t)$ = np.diff(y)/(t[1]-t[0]).
#
#

# ### Central difference
# An improved approximation uses the principles from the Taylor series (i.e., expand relative to a central point).  Now, rather than making a single step of *h* forward, we form a *central difference* by stepping forward a half-step and backward a half-step:
#
# $\frac{dy(t)}{dt}\biggr\rvert_{cd} = \frac{y(t+h/2)-y(t-h/2)}{h}$.
#
# We estimate the error by substituting the Taylor series for $y(t\pm h/2)$.  **Do you think this will result in a better approximation? Why?** By substitution, we find:
#
# $y(t+\frac{h}{2})-y(t-\frac{h}{2}) \simeq \left[y(t) + \frac{h}{2}y^\prime(t) + \frac{h^2}{8}y^{\prime\prime}(t) + \frac{h^3}{48}y^{\prime\prime\prime}(t) + O(h^4)\right] - \left[y(t) - \frac{h}{2}y^\prime(t) + \frac{h^2}{8}y^{\prime\prime}(t) - \frac{h^3}{48}y^{\prime\prime\prime}(t) + O(h^4)\right]$
#
# and through many cancellations, we find:
#
# $y(t+\frac{h}{2})-y(t-\frac{h}{2}) \simeq hy^\prime(t) + \frac{h^3}{24}y^{\prime\prime\prime}(t) + O(h^5)$,
#
# and the *central-difference* derivative is:
#
# $\frac{dy(t)}{dt}\biggr\rvert_{cd} = y^\prime(t) + \frac{h^2}{24}y^{\prime\prime\prime}(t) + O(h^4)$.
#
# The important difference between the *forward-difference* and *central-difference* algorithms is that the *central-difference* algorithm cancels the terms with an even power of *h* in from each of the Taylor series.  This makes the leading-order error of *central-difference* accurate to $h^2$, while the *forward-difference* is only accurate to order *h*.  Returning to our parabola example ($y(t) = a + bt^2$), we see that the *central-difference* gives the exact derivative independent of $h$:
#
# $\frac{dy(t)}{dt}\biggr\rvert_{cd} \simeq \frac{y(t+h/2)-y(t-h/2)}{h} = \frac{a+b(t+h/2)^2 - a - b(t-h/2)^2}{h} = \frac{2bth}{h} = 2bt$.
#
# The **numpy** module has a special function called [gradient](https://numpy.org/doc/stable/reference/generated/numpy.gradient.html) that performs a second order central differences method on the input array and requires the stepsize *h* as an argument.  From an array with *N* elements $y(t)$, it returns an array of *N* elements for $y^\prime(t)$.

# ### Extrapolated difference
# You may have noticed that different differentiation rules will keep a different number of terms in a Taylor series expansion, which also providesan expression for the error.  We can reduce the error further by combining several algorithims together so that the summed errors extrapolate to zero.  For example, we could employ two *central-difference* algorithms as a linear combination, where the first algorithm takes half-steps and the second takes quarter-steps.  Let's look at the quarter-step approximation ($h\rightarrow h/2$):
#
# $\frac{dy(t)}{dt}\biggr\rvert_{cd}^{h/4} = \frac{y(t+h/4)-y(t-h/4)}{h/2} = y^\prime(t) + \frac{h^2}{96}y^{\prime\prime\prime}(t) + O(h^4)$.
#
# The quarter-step derivative produces a factor of 1/96 instead of the 1/24 on the second order term.  If we want to cancel this term, we realize that 96 = 4*24 and so we perform the following subtraction to get:
#
# $4\frac{dy(t)}{dt}\biggr\rvert_{cd}^{h/4} - \frac{dy(t)}{dt}\biggr\rvert_{cd}^{h/2} = 4y^\prime(t) + \frac{h^2}{24}y^{\prime\prime\prime}(t) - y^\prime(t) - \frac{h^2}{24}y^{\prime\prime\prime}(t) = 3 y^\prime(t) + O(h^4)$
#
# Thus, we find for the *extrapolated-difference* the following:
#
# $\frac{dy(t)}{dt}\biggr\rvert_{ed} = \frac{4}{3}\frac{dy(t)}{dt}\biggr\rvert_{cd}^{h/4} - \frac{1}{3}\frac{dy(t)}{dt}\biggr\rvert_{cd}^{h/2} \simeq y^\prime(t) + O(h^4)$

# ## Second Derivatives
# Many problems in physics involve a second derivative, or acceleration.  One of the most important equations is Newton's second law that tells us that a force $F$ and acceleration $a$ are linearly related:
#
# $F = ma = m\frac{d^2y}{dt^2}$,
#
# where $m$ is a particle's mass and the acceleration can be expressed as the second derivative of a function $y(t)$.  If we can determin the acceleration, then we can determine the force.  Using the *central-difference* method for the first derivative, we have:
#
# $\frac{dy(t)}(dt) \simeq \frac{y(t+h/2)-y(t-h/2)}{h}$,
#
# which gives the derivative at t by moving forward and backward from t by a half-step $h/2$.  We take the second derivative by applying the same operation again as follows:
#
# $\frac{d^2y(t)}{dt^2} \simeq \frac{y^\prime(t+h/2)-y^\prime(t-h/2)}{h} \simeq \frac{[y(t+h)-y(t)]-[y(t)-y(t-h)]}{h^2} = \frac{y(t+h)-2y(t)+y(t-h)}{h^2}$
#

# ## Root Via Bisection
# Trial-and-error root finding looks for a vlaue of $x$ at which $f(x)=0$, where the $f(x)$ can be polynomials or a trancendental function (e.g., $10\sin x = 3x^3 \rightarrow 10\sin x - 3x^3 = 0$).  The search procedure starts with a guess for $x$, substitutes that guess into $f(x)$ (i.e., the trial), and sees how far from zero the evaluated function is (i.e., the error).  the program then revises the guess $x$ based on the error and then substitutes the revised guess into $f(x)$ again.  The procedure continues until $f(x)\simeq 0$ or to some desired level of precision.  Sometimes the procedure is stopped once it appears progress is not being made.
#
# The most elementary trial-and-error technique is the *bisection algorithm*.  It is reliable, but slow.  If you know some interval in which $f(x)$ changes sign, then the [bisection algorithm](https://personal.math.ubc.ca/~pwalls/math-python/roots-optimization/bisection/) is the way to go.  The basis of the bisection algorithm is that we start with two values of $x$ between which a zero occurs (i.e., the bounds).  Let us say that $f(x)$ is negative at $a$ and positive at $b$.  Thus we start with the interval $a\geq x \geq b$ within which we know a zero occurs.  Then we evaluate each of these tasks for a given iteration:
#
# 1. Calculate $c$, the midpoint of the interval, $c = \frac{a+b}{2}$.
# 2. Evaluate the function at the midpoint, $f(c)$.
# 3. If convergence is satisfactory (i.e., $|f(c)|\simeq 0$), return c and stop iterating.
# 4. Examine the sign of $f(c)f(a)$.
#     - If negative then set $b = c$ and return to step 1.
#     - Else set $a = c$ and return to step 1.
#
# Let's turn these steps into code:

# In[1]:

def bisection(f,a,b,N):
    '''Approximate solution of f(x)=0 on interval [a,b] by bisection method.
    f : function
    a,b : The interval in which to search for a solution.
    N : The number of iterations to implement.'''
    if f(a)*f(b) >= 0: #checking that a zero exist in the interval [a,b]
        print("Bisection method fails.")
        return None
    #initial interval
    a_n = a
    b_n = b
    for n in range(1,N+1):
        c = (a_n + b_n)/2 #calculate midpoint
        f_c = f(c) #evaluate function at midpoint
        if f(a_n)*f_c < 0: #evaluate sign
            b_n = c
        elif f(b_n)*f_c < 0: #evaluate sign
            a_n = c
        elif f_c == 0:
            print("Found exact solution.")
            return m_n
        else:
            print("Bisection method fails.")
            return None
    return (a_n + b_n)/2

# Now let's try our function on a problem from quantum mechanics.  The most standard problem is to solve for the energies of a particle of mass $m$ bound within a 1-D square well of radius $a$.  The potential $V(x) = -V_o$ if $|x|\leq a$, otherwised it is zero.  From quantum mechanics textbooks we know there is a [solution](https://ocw.mit.edu/courses/physics/8-04-quantum-physics-i-spring-2016/lecture-notes/MIT8_04S16_LecNotes11.pdf) for the energies of the bound states $E_B$:
#
# $\sqrt{V_o - E_B}\tan(\sqrt{V_o - E_B}) = \sqrt{E_B}$ (even),
#
# and
#
# $\sqrt{V_o - E_B}\cot(\sqrt{V_o - E_B}) = \sqrt{E_B}$ (odd).
#
# Here we have chosen units such that $\hbar=1$, $2m=1$, and $a=1$.  Now we want to find several of the bound state energies for the even wave functions.  First we have to find a reasonable interval and then apply our algorithm.

# In[29]:

import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import bisect
V_o = 10 #define the potential
def even_func(E_B):
    #E_B is a guess at the ground state energy
    if (V_o - E_B)>= 0:
        temp = np.sqrt(V_o - E_B)
        return temp*np.tan(temp) - np.sqrt(E_B)
    else:
        return np.nan

fig = plt.figure(1,figsize=(8,8))
ax = fig.add_subplot(111)

x_rng = np.arange(0,15,0.1)

for x in x_rng:
    y = even_func(x)
    ax.plot(x,y,'k.',ms=10)
#root_EB = bisection(even_func,8,10,50)
#scipy_root = bisect(even_func,8,10)
#print("our method   = ",root_EB)
#print("scipy method = ",scipy_root)
#ax.plot(root_EB,even_func(root_EB),'r.',ms=10)

ax.grid(True)
ax.set_ylim(-30,30)
ax.set_xlabel("Bound State Energy$ E_B$",fontsize=20)
ax.set_ylabel("1D finite well solution",fontsize=20)

# ## Newton-Raphson searching
# The Newton-Raphson (NR) algorithm is another way to find roots, but it is much quicker than the bisection method.  The NR method is equivalent to drawing a straight line $f(x)\simeq mx + b$ to a curve at an $x$ value for which $f(x) \simeq 0$.  Then it uses the intercept of the line with the $x$ axis at $-b/m$ as an imporved guess for the root.  If the "curve" were a straight line, then the answer would be exact.  Otherwise, it is a good approximation if the guess is close enough to the root for $f(x) to be nearly linear.
#
# As an [iteration](https://personal.math.ubc.ca/~pwalls/math-python/roots-optimization/newton/) scheme, we need to start with a guess $x_o$, then find a correction to the guess $\Delta x$, and finally formulate a new guess $x = x_o + \Delta x$.  Recall that the equation of a line can be formulated in terms of a Taylor series, keeping on the first two terms:
#
# $f(x) \simeq f(x_o) + \frac{df}{dx}\biggr\rvert_{x_o}\Delta x$.
#
# We then determine the correction $\Delta x$ by calculating the point at which this linear appromation to $f(x)$ would cross the $x$ axis:
#
# $f(x_o) + \frac{df}{dx}\biggr\rvert_{x_o}\Delta x = 0$
#
# $\Delta x = -\frac{f(x_o)}{df/dx\rvert_{x_o}}$.
#
# Notice that the NR method requires the calculation of the first derivative $df/dx$ at each guess.  In many cases you may have an analytic expression for the derivative and can build it into the algorithm.  However, it is simpler to use a numerical *forward-difference* approximation to the derivative.  While a central-difference approximation would be more accurate, it would require addition function evaluations and the overall process would take longer to run.

# ## Problems
# -   Complete the following problems in a Jupyter notebook, where you will save your results as an external file (*.png) as needed.
# -   Create a LaTex document with:
#     -   an abstract summary
#     -   sections for each problem that state the problem, summarize what you did, and display the results
#     -   include a reference for each solution (this can be textbooks)
#
# 1.  Write a program to calculate sin(x) and cos(x).  Calculate and plot the numerical derivatives using a) forward-difference, b) central-difference, and c) the analytical derivative.
#
# 2.  Write a function to implement the Newton-Raphson method.  Apply it to the 1D Quantum well example and compare it to the bisection method.  Also compare your implementation to the function from *scipy.optimize*.
#


#!/usr/bin/env python3
"""
In casino games, the gambler often has the intention to play the game repeatedly until he either increases
his bankroll to a predetermined level or goes broke. Imagine a gambler who starts with an initial bankroll
of a dollars and then on each successive gamble either wins one dollar or loses one dollar with
probabilities p and q = 1 – p respectively. The gambler stops playing after having raised his bankroll
to a + b dollars or running out of money, whichever happens first. Here a and b are positive integers.
What is the gambler’s probability of reaching his desired goal before going broke?
This is commonly known as the gambler’s ruin problem. The progress of the gambler’s bankroll forms a random
walk in which there is a probability p of moving one unit in the positive direction and a probability q of
moving one unit in the other direction. The random walk eventually reaches one of the absorbing states
0 or a + b and then stops.

Tijms, Henk (2012-06-14). Understanding Probability (p. 93). Cambridge University Press. Kindle Edition.
"""

import argparse
import numpy as np
import matplotlib.pyplot as plt
import sys

def simulate(p, a, b, x, verbose):
    """(real, int, int, int, bool): int
        p is the probability of moving to the right, or winning, and q = 1 - p is of losing
        a is the initial bankroll, or starting point in the X coordinate for a random walk
        b + a is the desired exit winning bankroll, or the rightmost absorbing state
        x is the leftmost exit losing bankroll, typically 0, that signals end of game

        n is number of moves needed to reach a + b or x
        q is bankroll at end
        k is amount of times moves returned to a
        v is amount of stations between x and a + b that have never been visited
        m is maximum bankroll
        Returns [n, q, k , v]
    """

    segment     = np.zeros(shape=(1+a+b-x),dtype=np.int16)
    segment[a]  = 1                                                 # visited from start...
    n           = 0                                                 # initialize total movements
    k           = 0                                                 # initialize return to a to zero
    q           = a                                                 # bankroll to start
    m           = a                                                 # max wins
    z           = a                                                 # min wins
    history     = [q]

    while q > x and q < a + b:
        move         = np.random.choice([ -1, 1],p=[1 - p, p])       # first move, either left or right...
        q            += move
        history.append(q)
        if q > m:    m = q
        if q < z:    z = q
        segment[q-x] += 1
        n            += 1
        if q == a: k += 1
        if verbose: print('{} '.format(q),end='');
        if verbose and n % 5 == 0 :
            print(' *** ',end='');
            sys.stdout.flush()

    v  = np.sum((segment == 0))
    if verbose: print('exit with n,q,k,v,m,z {},{},{},{},{}'.format(n,q,k,v,m,z))
    return [n,q,k,v,m,z,history]

def main():
    np.random.seed()
    # Get command-line arguments
    parser = argparse.ArgumentParser(description='simulate the Gamblers Ruin Random Walk Problem')
    parser.add_argument('--p', default=0.5, type=float, metavar='float',
                        help='winning probability')
    parser.add_argument('--a', default=10, type=int, metavar='int',
                        help='initial bankroll')
    parser.add_argument('--x', default=0, type=int, metavar='int',
                        help='exit level of money in bankroll')
    parser.add_argument('--b', default=5, type=int, metavar='int',
                        help='desired pot')
    parser.add_argument('--trials', default=10, type=int, metavar='int',
                        help='number of trials to perform')
    parser.add_argument('--xmx', default=10000, type=int, metavar='int',
                        help='plot coordinate max X')
    parser.add_argument('--verbose', default=False, action='store_true',
                        help='display the results of each trial')
    args = parser.parse_args()

    print('Simulating Gamblers Ruin starting with {} and desire to win {}. Winning prob={:.2f} exit game level {} --- {:,} trials...\n'.format(args.a, args.b, args.p, args.x, args.trials))
    xmx = args.xmx
    x = np.arange(0,xmx) # Generate x-axis values
    plt.figure(figsize=(10, 5), dpi=240)
    plt.title('Gamblers Ruin', fontname='Times New Roman',fontweight='bold')
    plt.xlabel('games')
    plt.ylabel('Bankroll')
    plt.axhline(y=args.a, xmin=0, xmax=xmx, c='green',linewidth=0.5, ls='dashed')
    plt.axhline(y=args.a+args.b, xmin=0, xmax=xmx, c='blue',linewidth=0.5, ls='dashed')

    plt.text(10, args.x+5 , 'p={:.2f} Initial={} Win={}'.format(args.p,args.a,args.a+args.b))

    # Carry out the trials
    for i in range(args.trials):
        (n, q, k, v, m, z, y) = simulate(args.p, args.a, args.b, args.x, args.verbose)
        if q == args.x:
            print('BROKEN ',end='')
        else:
            print('WINNER ',end='')
        print('Total games played: {:,}; Final bankroll: {} ({} times going back to original amount; money max: {:,} min: {:,}; never reached {})\n'.format(n,q,k,m,z,v))
        y.extend([q] * xmx)
        del y[xmx:]
        plt.plot(x, y, linewidth=0.5)

    print('Done\n')

    plt.show()
    fig = plt.gcf()
    ax  = plt.gca()

    #fig.set_size_inches(10,5)
    fig.savefig('tijms.ch00.GamblersRuin.png')

if __name__ == '__main__':
    main()


from scipy.stats import norm

def pvalue (p, q, N) :
    theta   = abs(p-q)
    var     = p*(1-p)
    bn      = (2*N)**0.5 * theta / var**0.5
    ret     = (1 - norm.cdf(bn))*2
    return ret

def pvalue_N (p, q, alpha) :
    theta   = abs(p-q)
    var     = p*(1-p)
    rev     = abs(norm.ppf (alpha/2))
    N       = 2 * (rev * var**0.5 / theta)** 2
    return int(N+1)

def alphatable (ps, dps, alpha) :
    values = []
    for p in ps :
        row=[]
        for dp in dps :
            q = p+dp
            r = pvalue_N (p,q,alpha) if 1 >= q >= 0 else -1
            row.append (r)
        values.append (row)
    return values

def pprint(ps,dps,table, format, fileFormat = "latex") :
    text = []
    if fileFormat == "latex" :
        cc = "r" + ("|r" * len(dps))
        text.append("\\begin{tabular}{" + cc + "}")
        row  = [""] + [ r"\textbf{%1.3f}" % _ for _ in dps ]
        text.append("&".join(row) + r"\\\hline")
        for p,line in zip(ps,table) :
            row  = ["\\textbf{%1.2f}" % p] + \
                   [ format % _ if _ != -1 else ""  for _ in line ]
            text.append("&".join(row) + r"\\\hline")
        text.append("\\end{tabular}")
        return "\n".join(text)
    else :
        # TSV
        row  = [""] + [ r"%1.3f" % _ for _ in dps ]
        text.append("\t".join(row) )
        for p,line in zip(ps,table) :
            row  = ["%1.2f" % p] + \
                   [ format % _ if _ != -1 else ""  for _ in line ]
            text.append("\t".join(row) )
        return "\n".join(text)

if __name__ == "__main__" :
    print ("norm.ppf(0.025)",norm.ppf (0.025)) # -1.9599639845400545
    ps  = [0.001, 0.002] + [ 0.05*i for i in range (1,20) ]
    dps = [ -0.2, -0.1,  -0.02, -0.01, -0.002, -0.001,
             0.2,  0.1,   0.02,  0.01,  0.002,  0.001, ]
    dps.sort()
    t = alphatable (ps, dps, 0.05)
    print (pprint (ps, dps, t, "%d", "latex"))
    print (pprint (ps, dps, t, "%d", "html"))


import numpy as np

a = np.array([[0, 1, 2, 3],
             [4, 5, 6, 7],
             [8, 9, 10, 11]])

print(np.cumsum(a)) # 1 次元 array に変換してから累積和を計算

'''
[ 0  1  3  6 10 15 21 28 36 45 55 66]
'''

print(np.cumsum(a, axis=0)) # 縦方向に累積和を計算

'''
[[ 0  1  2  3]
 [ 4  6  8 10]
 [12 15 18 21]]
'''

print(np.cumsum(a, axis=1)) # 横方向に累積和を計算

'''
[[ 0  1  3  6]
 [ 4  9 15 22]
 [ 8 17 27 38]]
'''


import numpy as np
import matplotlib.pyplot as plt
from scipy.special import comb

def zernike_map(rho, theta, index):
    """Generate a map of the Zernike polynomial, normalised over the unit disk.
    If index is a 1D array, the linear indexing is used.
    If index is a 2D array, the (n, m) indexing is used."""
    # Get (n, m) indices
    if np.size(index) == 1:
        n, m = zernike_lin_to_nm(index)
    elif np.size(index) == 2:
        n = index[0]
        m = index[1]
        # Check for correct index
        if np.any(n < 0):
            raise ValueError('n should be positive')
        if np.mod(n,2) != np.mod(m, 2):
            raise ValueError('n and m should have the same parity')
        if np.abs(m) > n:
            raise ValueError('|m| should be smaller than n')
    else:
        raise ValueError('index should have at most 2 elements')
    # Check input size
    if rho.shape != theta.shape:
        raise ValueError('rho ans theta do not have the same shape')
    # Compute radial polynomial
    R = np.zeros_like(rho)
    for k in np.arange((n-np.abs(m))/2+1):
        R += (-1)**k*comb(n-k, k)*comb(n-2*k, (n-np.abs(m))/2-k)*rho**(n-2*k)
    # Get the full wavefront map
    wave_map = np.sqrt(2*(n+1)/(1+(m==0)))*R*np.cos(np.abs(m)*theta - (m<0)*np.pi/2)
    wave_map[rho>1]=0
    return wave_map

def zernike_nm_to_lin(n, m):
    """Computes linear index from (n, m) indices"""
    if np.any(n < 0):
        raise ValueError('n should be positive')
    if np.any(np.mod(n,2) != np.mod(m, 2)):
        raise ValueError('n and m should have the same parity')
    if np.any(np.abs(m) > n):
        raise ValueError('|m| should be smaller than n')
    return n*(n+1)/2 + 1 + np.abs(m) - np.mod(m, 2) - np.abs(np.sign(m))*(1 + np.sign(m) - 2*np.mod(n, 2))/2

def zernike_lin_to_nm(j):
    """Computes (n, m) indices from linear index"""
    if np.any(j < 1):
        raise ValueError('j should be strictly positive')
    j -= 1
    n = np.floor((-1 + np.sqrt(1+8*j))/2)
    l = j - (n*(n+1)/2)
    m = (-1)**(np.mod(l, 2) + np.mod(n, 2) + 1)*(l + np.mod(np.mod(l, 2) + np.mod(n, 2), 2))
    return n, m

def zernike_project(wf_map, N_max = 66):
    """
    Project the wavefront map on the Zernike polynomials
    """
    n2,n1 = wf_map.shape
    x = np.linspace(-1,1,num=n1)
    y = np.linspace(-1,1,num=n2)
    X, Y = np.meshgrid(x,y)
    rho, theta = cart2pol(X,Y)
    list_z = np.zeros(N_max)
    for k in range(0,N_max):
        z = zernike_map(rho, theta, k+1)
        list_z[k] = np.sum(wf_map*z)/np.sum(z*z)
    return list_z

def zernike_list_to_map(zernike_list, rho, theta):
    """
    Generate the wavefront map from the Zernike polynomials
    """
    n = zernike_list.size
    wmap = np.zeros_like(rho)
    for k in range(n):
        wmap += zernike_list[k]*zernike_map(rho, theta, k+1)
    return wmap

def cart2pol(x,y):
    """Convert cartesian to polar coordinates"""
    return np.abs(x+1j*y), np.angle(x+1j*y)


__author__ = 'ferrard'

import scipy as sp
import scipy.linalg as la

# ---------------------------------------------------------------
# Main
# ---------------------------------------------------------------

def main():
    # define matrices
    m_a = sp.matrix(
        '1 5 8;'
        '0 -1 4'
    )
    print(type(m_a))

    m_b = sp.matrix(
        '1 4;'
        '-2 0;'
        '-3 1'
    )

    m_c = sp.matrix(
        '1 2 4;'
        '3 8 14;'
        '2 6 13'
    )

    m_d = sp.matrix(
        '0 1 -1;'
        '5 7 9;'
        '-3 0 0'
    )

    # print them
    print("Matrix A is: \n" + str(m_a))
    print("Matrix B is: \n" + str(m_b))
    print("Matrix C is: \n" + str(m_c))
    print()

    # their shape
    print("Shape of A is: " + str(m_a.shape))
    print("Shape of B is: " + str(m_b.shape))
    print("Shape of C is: " + str(m_c.shape))
    print()

    # add/multiply
    m_add = m_c + m_d
    print("C+D = \n" + str(m_add))

    m_mult = m_a * m_b
    print("A*B = \n" + str(m_mult))

    # inverse
    m_inv = la.inv(m_c)
    print("Inverse of C is: \n" + str(m_inv))

    # determinant
    det = la.det(m_c)
    print("Determinant of C: " + str(det))

    # eigen-decomposition
    eig_vals_vectors = la.eig(m_c)
    print("Eigenvalues of C: \n" + str(eig_vals_vectors[0]))
    print("Eigenvectors (unit) of C (vectors are in columns): \n" + str(eig_vals_vectors[1]))
    # let's verify
    u_1 = eig_vals_vectors[1][:, 0]
    u_1 = u_1.reshape(u_1.size, 1)
    print("C*u_1: \n" + str(m_c*u_1))
    print("lambda_1*u_1: \n" + str(eig_vals_vectors[0][0]*u_1))

    # LU-decomposition
    m_p, m_l, m_u = la.lu(m_c)
    print("LU decomposition of C:\n"
          "P: \n" + str(m_p) + "\n"
          "L: \n" + str(m_l) + "\n"
          "U: \n" + str(m_u) + "\n")

    # solve a system
    b = sp.array([1, 5, 6])
    print("b is: \n" + str(b))
    x = la.solve(m_c, b)
    print("Solution to Cx = b: " + str(x))

if __name__ == "__main__":
    main()


# regressaõ Linear Scikit-learn
import matplotlib.pyplot as plt
import numpy as np

# Dados

x = [-1., -0.77777778, -0.55555556, -0.33333333, -0.11111111,
      0.11111111, 0.33333333, 0.55555556, 0.77777778, 1.]

y = [-1.13956201, -0.57177999, -0.21697033, 0.5425699, 0.49406657,
      1.14972239, 1.64228553, 2.1749824, 2.64773614, 2.95684202]
# plot dos dados

plt.figure(figsize=(10, 5))
plt.plot(x, y, 'o', label='dados originais')
plt.legend()
plt.xlabel('x')
plt.ylabel('y')
plt.grid()
plt.show()

# transforma em numpy array
x, y = np.array(x).reshape(-1, 1), np.array(y).reshape(-1, 1)

# modelo
from sklearn.linear_model import LinearRegression

# treinando o modelo: y = a*x +b, valores reais (a, b) = (2, 1)
reg = LinearRegression()
reg.fit(x, y)

# coeficiente a, b estimados:
# valores estimados usando numpy diretamente
# a estimado no numpy: 2.05414951
# bestimado no numpy: 0.96798926
print('A estimado: ', reg.coef_.ravel()[0])
print('B estimado: ',reg.intercept_[0])

# predição do modelo
y_pred = reg.predict(x)

# socre do modelo
score = reg.score(x, y)
print('score', score)

# plot dos dado
plt.figure(figsize=(10, 5))
plt.plot(x, y, 'o', label='dados originais')
plt.plot(x, y_pred, label='regressão linear(R2: {:.3f})'.format(score))
plt.legend()
plt.xlabel('x')
plt.ylabel('y')
plt.title("Regressão Linear Scikit-Learn")
plt.grid()
plt.show()

# plot dos dado para exemplificar o R2
plt.figure(figsize=(10, 5))
plt.plot(x, y, 'o', label='dados originais')
plt.plot(x, y_pred, label='regressão linear(R2: {:.3f})'.format(score))
plt.hlines(y=y.mean(), xmin=x.min(), xmax=x.max(), linestyle='dashed',
            label='Modelo De Referencia do $R^2$')
plt.legend()
plt.xlabel('x')
plt.ylabel('y')
plt.title("Regressão Linear Scikit-Learn")
plt.grid()
plt.show()

# função para calculo do MSE
def mse (y_true, y_pred, is_ref = False):
    # mse modelo
    if is_ref:
        mse = ((y_true - y_true.mean())**2).mean()
    else:
        mse = ((y_true - y_pred)**2).mean()
    return mse

# função para calculço do coeficiente de determinação R2
def r2(mse_reg, mse_ref):
    return 1 - mse_reg/mse_ref

# visulizando  y e y_pred
print('y_true:', y.ravel())
print('y_pred:', y_pred.ravel())

# Calculando o mse dos modelos
mse_reg = mse (y_true=y, y_pred=y_pred)
print("MSE do modelo de regressão:",mse_reg)
mse_ref = mse(y_true=y, y_pred=y_pred, is_ref=True)
print("MSE do modelo de Referencia: ", mse_ref)

# calculando o R2 score
r2_score = r2(mse_reg=mse_reg, mse_ref=mse_ref)
print('coeficiente R2 do modelo implementado (calculado: ',r2_score)

# score retornado pelo scikit-learn
r2_score_skl = reg.score(x, y)


# -*- coding: utf8 -*-

"""
Integer functions
"""

import sympy

from mathics.builtin.base import Builtin
from mathics.core.expression import Integer

class Floor(Builtin):
    """

'Floor[$x$]'

gives the smallest integer less than or equal to $x$.

'Floor[$x$, $a$]'

gives the smallest multiple of $a$ less than or equal to $x$.

    >> Floor[10.4]
     = 10
    >> Floor[10/3]
     = 3
    >> Floor[10]
     = 10
    >> Floor[21, 2]
     = 20
    >> Floor[2.6, 0.5]
     = 2.5
    >> Floor[-10.4]
     = -11

    For negative $a$, the smallest multiple of $a$ greater than or equal to $x$
    is returned.
    >> Floor[10.4, -1]
     = 11
    >> Floor[-10.4, -1]
     = -10
    """

    rules = {
        'Floor[x_, a_]': 'Floor[x / a] * a',
        'Floor[z_Complex]': 'Complex[Floor[Re[z]], Floor[Im[z]]]',
    }

    def apply_real(self, x, evaluation):
        'Floor[x_?RealNumberQ]'

        x = x.value
        if x < 0:
            floor = - sympy.Integer(abs(x))
            if x != floor:
                floor -= 1
        else:
            floor = sympy.Integer(x)
        return Integer(floor)

class IntegerLength(Builtin):
    """
    >> IntegerLength[123456]
     = 6
    >> IntegerLength[10^10000]
     = 10001
    >> IntegerLength[-10^1000]
     = 1001
    'IntegerLength' with base 2:
    >> IntegerLength[8, 2]
     = 4
    Check that 'IntegerLength' is correct for the first 100 powers of 10:
    >> IntegerLength /@ (10 ^ Range[100]) == Range[2, 101]
     = True
    The base must be greater than 1:
    >> IntegerLength[3, -2]
     : Base -2 is not an integer greater than 1.
     = IntegerLength[3, -2]
    """

    rules = {
        'IntegerLength[n_]': 'IntegerLength[n, 10]',
    }

    messages = {
        'base': "Base `1` is not an integer greater than 1.",
    }

    def apply(self, n, b, evaluation):
        'IntegerLength[n_, b_]'

        # Use interval arithmetic to account for "right" rounding

        n, b = n.get_int_value(), b.get_int_value()
        if n is None or b is None:
            evaluation.message('IntegerLength', 'int')
            return
        if b <= 1:
            evaluation.message('IntegerLength', 'base', b)
            return

        result = sympy.Integer(sympy.log(abs(n), b)) + 1
        return Integer(result)


import numpy as np

def calculate(numbers: list):
    """
    Receives a list of numbers and convert the list
    into a 3 x 3 Numpy array. Then, return a dictionary
    containing the mean, variance, standard deviation,
    max, min, and sum along both axes and for the
    flattened matrix.
    """

    if len(numbers) == 9:
        # convert list into numpy array
        matrix = np.array([
            numbers[:3],
            numbers[3:6],
            numbers[6:]
        ])

        # initialize dictionary
        calculations = dict()

        calculations['mean'] = [
            np.mean(matrix, axis=0).tolist(),
            np.mean(matrix, axis=1).tolist(),
            np.mean(matrix)
        ]

        calculations['variance'] = [
            np.var(matrix, axis=0).tolist(),
            np.var(matrix, axis=1).tolist(),
            np.var(matrix)
        ]

        calculations['standard deviation'] = [
            np.std(matrix, axis=0).tolist(),
            np.std(matrix, axis=1).tolist(),
            np.std(matrix)
        ]

        calculations['max'] = [
            np.max(matrix, axis=0).tolist(),
            np.max(matrix, axis=1).tolist(),
            np.max(matrix)
        ]

        calculations['min'] = [
            np.min(matrix, axis=0).tolist(),
            np.min(matrix, axis=1).tolist(),
            np.min(matrix)
        ]

        calculations['sum'] = [
            np.sum(matrix, axis=0).tolist(),
            np.sum(matrix, axis=1).tolist(),
            np.sum(matrix)
        ]

        return calculations
    else:
        raise ValueError('List must contain nine numbers.')

if __name__ == '__main__':
    listinha = [0, 1, 2, 3, 4, 5, 6, 7, 8]
    print(calculate(listinha))


# Copyright (c) Facebook, Inc. and its affiliates
# Copyright (c) MTRF authors

import numpy as np

def circle_distance(euler1, euler2):
    """Always <= np.pi radians"""
    euler1 = np.mod(euler1, 2 * np.pi)
    euler2 = np.mod(euler2, 2 * np.pi)
    abs_diff = np.abs(euler1 - euler2)
    return np.minimum(abs_diff, 2 * np.pi - abs_diff)

def circle_distance_mod(euler1, euler2, mod=2*np.pi/3):
    euler1 = np.mod(euler1, mod)
    euler2 = np.mod(euler2, mod)
    abs_diff = np.abs(euler1 - euler2)
    return np.minimum(abs_diff, mod - abs_diff)

def quat_distance(quat1, quat2):
    """ Maps to [0, 1].
        Implements Phi_4 from http://www.cs.cmu.edu/~cga/dynopt/readings/Rmetric.pdf"""
    return 1 - np.abs(np.dot(quat1, quat2))


import numpy as np

A = np.array([[ 1.0, 2.0, 3.0],[ 4.0, 5.0, 2.0],[ 2.0, 8.0, 5.0]]) #3x3 coefficient array
b = np.array([5.0,10.0,15.0])
c = np.array([1.0,1.0,1.0])

def myGE(A,b):

    if A.shape[0]!=A.shape[1]:
        print ("Matrix cannot solved because it's not in correct form ")
        return

    if np.linalg.det(A)==0:
        print ("Matrix is not linearly independent. Solving not possible")
        return

    stepcounter = 1
    lenght = len(A)
    print("\nFirst Step")
    print("\n",np.matrix(A),np.matrix(b).T,"\n")

    for g in range(lenght):
        for p in range(g,lenght):
            if abs(A[g][p]) > abs(A[g][g]):
                A[[g,p]] = A[[p,g]]
                b[[g,p]] = b[[p,g]]
                print("Pivoting done:R%d <---> R%d"%(g,p))
            else : pass

    print("\n",np.matrix(A),np.matrix(b),"\n")
    x=0
    y=0

    # if diagonal is not 0
    #it does  A[x][y]-= A[x][y]*c
    if x < A.shape[0]-1:
        glork =  A.shape[0]-1 -x
        for r in range(glork):
            if glork==0:
                pass
            print("Step : ", stepcounter)
            for ner in range(A.shape[0]-x-1):
                if A[x][y]!=0: #if Akj=0 gauss it
                    c = A[x+1+ner][y]/A[x][y] #to find if c of equation Akj += c*Aij
                    A[x+1+ner]=A[x+1+ner]-c*A[x]
                    b[x+1+ner]=b[x+1+ner]-c*b[x]
                    print("R%s = R%s - R%s*%s"%(x+2+ner,x+2+ner,x+1,c))

            print("\n",np.matrix(A),np.matrix(b),"\n")
            y=y+1
            stepcounter=stepcounter+1
            x=x+1

    else : pass

    print("“Back Substitution Started.”")
    x3=b[2]/A[2][2]
    x2=(b[1]-A[1][2]*x3)/A[1][1]
    x1=(b[0]-A[0][1]*x2-A[0][2]*x3)/A[0][0]
    print("x1:",x1,"\n")
    print("x2:",x2,"\n")
    print("x3:",x3,"\n")
    b[0]=x1
    b[1]=x2
    b[2]=x3

    print("--result vector x.--")
    print(np.matrix(b))

def main():
    myGE(A,b)

main()


import numpy as np

# Katherine Wang and Jenny Fish
# 21241 Final Project 2018
# Topic: Markov Chains/PageRank

# checks that a matrix P is a markov transition matrix
# takes in a matrix represented as a list of lists
# 1. checks that matrix is square
# 2. checks that elements of P are nonnegative and not greater than 1
# 3. checks that the sum the elements in each column
#    is equal to 1, or 0 if there are no paths from that node
# 3.1 if the sum of the elements of a column is 0,
#    modified the elements of that
#    column to have value 1/N
# 4. checks that P has 1 as an eigenvalue
def is_markov_transition_matrix(P):
    # if P is not a list, raise Exception
    if (not isinstance(P, list)):
        raise Exception("Error: P is not a matrix")

    # if an element of P is not a list, raise Exception
    for i in range(len(P)):
        if (not isinstance(P[i], list)):
            raise Exception("Error: P is not a matrix")

    dim = len(P) # dimension of P

    # if P is not square, raise Exception
    for i in range(dim):
        if (len(P[i]) != dim):
            raise Exception("Error: P is not square")

    # if P contains a negative value or a value greater than 1, raise Exception
    for i in range(dim):
        for j in range(dim):
            if (P[i][j] < 0 or 1 < P[i][j]):
                raise Exception("Error: P contains a negative value or a value greater than 1")

    # if elements of each column of P do not add up to 1, raise Exception
    for i in range(dim):
        sum = 0
        for j in range(dim):
            sum += P[i][j]
        # if the sum of the elements of a column is 0, make each element 1/N to correct P
        if (sum == 0):
            for j in range(dim):
                P[i][j] = 1/dim
        if (sum != 1):
            raise Exception("Error: the elements in a column of P do not add up to 1")

    # if 1 is not an eigenvalue of P, raise Exception
    eig = np.linalg.eig(np.array(P))[0]
    tolerance = 0.1             # tolerance for float comparison
    eig1_found = False          # eigenvalue 1 originally not found
    for i in range(len(eig)):
        if (abs(eig[i] - 1) < tolerance):
            eig1_found = True   # if found, set eig1_found to True
    if (eig1_found == False):
        raise Exception("Error: 1 is not an eigenvalue of P")

    # all checks passed without issue and P is a valid markov transition matrix
    return True

# helper function that helps to calculate the rank of the elements of the original list
# takes in a probability matrix A
# outputs the ranks of the elements of the original list in order of importance
# Ex: might take the form ranks=[2, 1, 3, 0], indicating that the most
# important node is 2, and the least important is 0
def calculate_rank(A):
    A = np.array(A).T

    # find the eigenvectors of A and take the eigenvector corresponding to the 1 eigenvalue
    # x is the pagerank vector
    evects = np.linalg.eig(A)[1].T
    x = evects[0]

    # use this to calculate the ranks of the elements in the original list
    rank = []
    for i in range(len(x)):
        max_index = 0
        max = 0
        for j in range(len(x)):
            if (abs(x[j]) > max):
                if (j in rank):
                    continue
                max_index = j
                max = abs(x[j])
        rank.append(max_index)

    return rank

# main function
# takes in a markov chain, "links", and d a damping factor between 0 and 1
# outputs a set of rankings
def rank(links, d=.85):
    # count number of nodes
    num_nodes = len(links)

    # initialize markov transition matrix with 0s
    A = [[0 for i in range(num_nodes)] for j in range(num_nodes)]

    # modify the markov transition matrix based on the paths from each element
    # to other elements
    for i in range(num_nodes):
        # count number of edges for each node
        num_edges = len(links[i])
        for j in range(len(links[i])):
            # add the probabilities for each "path" from i to its neighbors
            neighbor = links[i][j]
            A[i][neighbor] = 1/num_edges

    # checking that A is a valid markov transition matrix
    print("Verifying markov transition matrix...")
    if (not is_markov_transition_matrix(A)):
        raise Exception("Error: rank verification failed")

    # create modified matrix M like so:
    # M = dA + (1-d)/num_nodes * all_ones
    all_ones = [[0 for i in range(num_nodes)] for j in range(num_nodes)]
    all_ones = np.array(all_ones)

    M = np.add(np.multiply(d,A), np.multiply((1-d)/num_nodes, all_ones))

    # analyzing rank using eigenvectors of A
    print("Analyzing rank...")
    result = calculate_rank(M)

    return result


import sympy as sy
from sympy import Matrix, Symbol, sin, cos, latex, simplify
from IPython.display import display, Math
from sympy.physics.mechanics import dynamicsymbols, init_vprinting
from copy import copy

def rad(degrees):
    """Convert degrees to radians."""
    return degrees/180 * sy.pi

def homo_transf(alpha, a, d, theta):
    """Returns the homogeneous transform matrix. Input are Denavit-Hartenberg parameters of one link.
    All angles are in degrees.

    Parameters
    ----------
    alpha: float
        The angle from Zi to Zi+1 measured about Xi
    a: float
        The distance from Zi to Zi+1 measured along Xi
    d: float
        The distance from Xi-1 to Xi measured along Zi
    theta: float
        The angle from Xi-1 to Xi measured about Zi
    Returns
    -------
    sympy.Matrix
    """
    alpha = rad(alpha)
    if isinstance(theta, str):
        theta = Symbol(theta)
    else:
        theta = rad(theta)
    if isinstance(d, str):
        d = Symbol(d)

    transf = Matrix(
        [
            [cos(theta), -sin(theta), 0, a],
            [sin(theta)*cos(alpha), cos(theta)*cos(alpha), -sin(alpha), -sin(alpha)*d],
            [sin(theta)*sin(alpha), cos(theta)*sin(alpha), cos(alpha), cos(alpha)*d],
            [0, 0, 0, 1]
        ]
    )
    return simplify(transf)

def homo_transpose(transform, simple=True):

    R = transform[:3, :3].T
    transform_T = copy(transform.as_mutable())
    transform_T[:3, :3] = R
    transform_T[:3, 3] = -R @ transform[:3, 3]
    if simple:
        transform_T = sy.simplify(sy.expand(transform_T))
    return transform_T

def build_transf(dh_params, verbose=True):
    """Computes the homogeneous transforms for all links.

    Parameters
    ----------
    dh_params: list of lists
        Each sublist are the D-H-parameters for a given link (alpha, a, d, theta).
        Example: [[0, 1, 0, sympy.Symbol("theta_1")], [0, 2, 2, sympy.Symbol("theta_2")]]
    verbose: bool
        Wether to print the outputs.

    Returns
    -------
        List of transform matrices.
    """
    transforms = [homo_transf(*dhp) for dhp in dh_params]

    if verbose:
        for i, transf in enumerate(transforms):
            index = "{}" + f"^{i}_{i + 1}"
            display(Math(f"{index}T = {latex(transf)}"))

    return transforms

def full_homo_transf(transforms, verbose=True, simple=True):
    """Multiplies out homogeneous transforms from the left.

    Parameters
    ----------
    transforms: List of homogeneous transforms.
    verbose: Whether to print the intermediary and final outputs.
    simple: Whether to simplify the results.

    Returns
    -------
        sympy.Matrix
    """
    left_mat = transforms[0]
    for i in range(1, len(transforms)):
        left_mat = left_mat @ transforms[i]
        if verbose:
            index = "{}" + f"^0_{i + 1}"
            if simple:
                left_mat = simplify(left_mat)
            display(Math(f"{index}T = {latex(left_mat)}"))

    return left_mat

def prop_velo(dh_params, joint_points, verbose=True, simple=True):
    """Propagate velocities from the base to the end-effector of the manipulator.

    Parameters
    ----------
    dh_params: list of lists with D-H-parameters (alpha, a, d, theta)
    joint_points: list of sympy.Matrix vectors
        The joint points measured in the coordinate frame of the previous joint. Ordered from base to end-effector.
    verbose: Whether to print the intermediary results.
    simple: Whether to simplify results.

    Returns
    -------
        Final linear and angular velocity vectors
        joint_params: list of derivatives of joint parameters
    """
    transforms = [homo_transf(*dhp) for dhp in dh_params]
    rot_mats = [t[:3, :3] for t in transforms]

    joint_params = []
    for joint in dh_params:
        for param in joint:
            if "d" in str(param):
                joint_params.append(Symbol(fr"\dot {param}"))
            elif "theta" in str(param):
                joint_params.append(Symbol(fr"\dot\{param}"))

    # base is fixed
    omega = Matrix([0, 0, 0])
    v = Matrix([0, 0, 0])
    for i in range(len(rot_mats)):
        # If joint is neither revolute not prismatic
        if i >= len(joint_params):
            # Transpose inverts rotation
            v = rot_mats[i].T @ (v + omega.cross(joint_points[i]))
            omega = rot_mats[i].T @ omega
        # Revolute
        elif "theta" in str(joint_params[i]):
            v = rot_mats[i].T @ (v + omega.cross(joint_points[i]))
            omega = rot_mats[i].T @ omega + joint_params[i] * Matrix([0, 0, 1])
        # Prismatic
        elif "d" in str(joint_params[i]):
            v = rot_mats[i].T @ (v + omega.cross(joint_points[i])) + joint_params[i] * Matrix([0, 0, 1])
            omega = rot_mats[i].T @ omega

        if verbose:
            if simple:
                omega = simplify(omega)
                v = simplify(v)
            display(Math("{}" f"^{i+1}{latex(Symbol('omega'))}_{i+1} = {latex(omega)}"))
            display(Math("{}" f"^{i + 1}v_{i + 1} = {latex(v)}"))

    return v, omega, joint_params

def comp_jacobian(dh_params, joint_points, verbose=True, simple=True):
    """Computes the Jacobian matrix.

    Parameters
    ----------
    dh_params: list of lists with D-H-parameters (alpha, a, d, theta)
    joint_points: list of sympy.Matrix vectors
        The joint points measured in the coordinate frame of the previous joint. Ordered from base to end-effector.
    verbose: Whether to print the intermediary results.
    simple: Whether to simplify results.

    Returns
    -------
        sympy.Matrix
        The Jacobian matrix of shape (6, number of joint parameters).
    """

    v, omega, joint_params = prop_velo(dh_params, joint_points, verbose=verbose)

    jacobian_rows = []
    for vec in (v, omega):
        for i in range(3):
            # Collect factors; expanding and not simplifying makes this more robust
            expr = sy.collect(sy.expand(vec[i, 0]), joint_params)
            coeff = sy.Matrix([expr.coeff(v) for v in joint_params]).T
            jacobian_rows.append(coeff)

    jacobian = Matrix.vstack(*jacobian_rows)
    if simple:
        jacobian = sy.simplify(jacobian)
    display(Math("{}" f"^{len(dh_params)}J = {latex(jacobian)}"))
    return jacobian

def prop_force_torque(dh_params, joint_points, end_force_torque, verbose=True, simple=True):
    """Propagates static forces and torques from the end-effector to the base of the manipulator.

    Parameters
    ----------
    dh_params: list of lists with D-H-parameters (alpha, a, d, theta)
    joint_points: list of sympy.Matrix vectors
        The joint points measured in the coordinate frame of the previous joint. Ordered from base to end-effector.
    end_force_torque: sympy.Matrix of shape 6x1
        The force-torque vector at the end-effector/ last link.
    verbose: Whether to print the intermediary results.
    simple: Whether to simplify results.

    Returns
    -------
        None
    """
    transforms = [homo_transf(*dhp) for dhp in dh_params]
    rot_mats = [t[:3, :3] for t in transforms]

    force = end_force_torque[:3, :]
    torque = end_force_torque[3:, :]

    if verbose:
        n = len(rot_mats)
        display(Math("{}" f"^{n}f_{n} = {latex(force)}"))
        display(Math("{}" f"^{n}n_{n} = {latex(torque)}"))

    for i in reversed(range(1, len(rot_mats))):

        force = rot_mats[i] @ force
        torque = rot_mats[i] @ torque + joint_points[i].cross(force)

        if verbose:
            if simple:
                force = simplify(force)
                torque = simplify(torque)
            display(Math("{}" f"^{i}f_{i} = {latex(force)}"))
            display(Math("{}" f"^{i}n_{i} = {latex(torque)}"))

def newton_euler(dh_params, joint_points, m_center_points, v_dot_0, link_m, link_I,
                 end_force_torque=sy.Matrix([0, 0, 0, 0, 0, 0]), verbose=True, simple=True):
    """Compute joint torques/ forces via the Newton-Euler-Method.

    Parameters
    ----------
    dh_params: list of lists with D-H-parameters (alpha, a, d, theta)
    joint_points: list of sympy.Matrix vectors
        The joint points measured in the coordinate frame of the previous joint. Ordered from base to end-effector.
    end_force_torque: sympy.Matrix of shape 6x1
        The force-torque vector at the end-effector/ last link.
    verbose: Whether to print the intermediary results.
    simple: Whether to simplify results.
    m_center_points: list of sympy.Matrix
        The center of mass of link i in the coordinate frame of link i.
    v_dot_0: sympy.Matrix (3 x 1 vector)
        The acceleration of the base frame. Usually set to -G.
    link_m: list
        The mass of each link.
    link_I: list of sympy.Matrix (3 x 3 matrices)
        The inertia tensor of each link.

    Returns
    -------
        sympy.Matrix of shape (number of joints, 1)
        The joint torques/ forces.
    """

    transforms = [homo_transf(*dhp) for dhp in dh_params]
    rot_mats = [t[:3, :3] for t in transforms]

    joint_params_fo = []            # first-order derivative
    joint_params_so = []            # second-order derivative
    for joint in dh_params:
        for param in joint:
            if "d" in str(param):
                joint_params_fo.append(Symbol(r"\dot{" + param + "}"))
                joint_params_so.append(Symbol(r"\ddot{" + param + "}"))
            elif "theta" in str(param):
                joint_params_fo.append(Symbol(fr"\dot\{param}"))
                joint_params_so.append(Symbol(fr"\ddot\{param}"))

    # base is fixed
    omega = Matrix([0, 0, 0])
    omega_dot = Matrix([0, 0, 0])
    v_dot = v_dot_0
    # Forces and torques acting on the centre of mass of each link
    link_F = []
    link_N = []

    if verbose:
        print("Forward phase:")

    # forward phase
    for i in range(len(rot_mats)):
        # If the joint is neither revolute not prismatic
        if i >= len(joint_params_fo):
            # Transpose inverts rotation
            v_dot = rot_mats[i].T @ (omega.cross(joint_points[i]) + v_dot)
            omega_dot = rot_mats[i].T @ omega_dot
            omega = rot_mats[i].T @ omega
        # Revolute
        elif "theta" in str(joint_params_fo[i]):
            v_dot = rot_mats[i].T @ (omega_dot.cross(joint_points[i]) + omega.cross(omega.cross(joint_points[i])) +
                                     v_dot)
            omega_dot = rot_mats[i].T @ omega_dot + \
                        rot_mats[i].T @ omega.cross(joint_params_fo[i] * Matrix([0, 0, 1])) + \
                        joint_params_so[i] * Matrix([0, 0, 1])
            omega = rot_mats[i].T @ omega + joint_params_fo[i] * Matrix([0, 0, 1])
        # Prismatic
        elif "d" in str(joint_params_fo[i]):
            v_dot = rot_mats[i].T @ (omega_dot.cross(joint_points[i]) + omega.cross(omega.cross(joint_points[i])) +
                                     v_dot)
            omega_dot = rot_mats[i].T @ omega_dot
            omega = rot_mats[i].T @ omega
            v_dot += 2 * omega.cross(joint_params_fo[i] * Matrix([0, 0, 1])) + joint_params_so[i] * Matrix([0, 0, 1])

        # calculate velocity of center of mass and forces and torques acting on link i+1

        v_c_dot = omega_dot.cross(m_center_points[i]) + omega.cross(omega.cross(m_center_points[i])) + v_dot
        F = link_m[i] * v_c_dot
        N = link_I[i] @ omega_dot + omega.cross(link_I[i] @ omega)
        link_F.append(F)
        link_N.append(N)

        if verbose:
            if simple:
                omega = simplify(omega)
                omega_dot = simplify(omega_dot)
                v_dot = simplify(v_dot)
            print(f"i = {i}:")
            display(Math("{}" f"^{i + 1}" + r"\dot{v}_" + f"{i + 1} = {latex(v_dot)}"))
            display(Math("{}" f"^{i+1}{latex(Symbol('omega'))}_{i+1} = {latex(omega)}"))
            display(Math("{}" f"^{i + 1}" + r"\dot{" + latex(Symbol("omega")) + "}_" + f"{i + 1} = {latex(omega_dot)}"))
            display(Math("{}" f"^{i + 1}" + r"\dot{v}_{C_" + f"{i + 1}" + "}" + f" = {latex(v_dot)}"))
            display(Math("{}" f"^{i+1}F_{i+1} = {latex(F)}"))
            display(Math("{}" f"^{i+1}N_{i+1} = {latex(N)}"))

    if verbose:
        print("Backwards phase:")

    # Backwards
    force = end_force_torque[:3, :]
    torque = end_force_torque[3:, :]
    forces = []
    torques = []

    for i in reversed(range(0, len(rot_mats))):

        torque = link_N[i] + rot_mats[i] @ torque + m_center_points[i].cross(link_F[i]) + \
                 joint_points[i].cross(rot_mats[i] @ force)
        force = rot_mats[i] @ force + link_F[i]

        if simple:
            force = simplify(force)
            torque = simplify(torque)

        forces.append(force)
        torques.append(torque)

        if verbose:
            print(f"i = {i+1}:")
            display(Math("{}" f"^{i+1}f_{i+1} = {latex(force)}"))
            display(Math("{}" f"^{i+1}n_{i+1} = {latex(torque)}"))

    print("Joint torques/ forces:")

    tau = []
    for i in range(len(joint_params_fo)):
        # Check for theta first, because e.g. "\dot\theta_1" also contains "d"
        if "theta" in str(joint_params_fo[i]):
            tau.append(torques[-i-1][2, 0])
        elif "d" in str(joint_params_fo[i]):
            tau.append(forces[-i-1][2, 0])

    tau = sy.Matrix(tau)

    display(Math(r"\tau = " + latex(sy.Matrix([sy.Symbol(f"tau_{i}") for i in range(1, len(tau)+1)])) + f" = {latex(tau)}"))

    return tau

def lagrange(dh_params, m_center_points_0, link_m, link_I, verbose=True, simple=True):

    # TODO

    # Convert thetas and ds in dh_params to dynamic symbols which allow autodiff
    joint_params = []
    dh_rows = []
    for joint in dh_params:
        row = []
        for param in joint:
            if "d" in str(param) or "theta" in str(param):
                dyn_sym = dynamicsymbols(str(param))
                row.append(dyn_sym)
                joint_params.append(dyn_sym)
            elif isinstance(param, str):
                row.append(Symbol(param))
            else:
                row.append(param)
        dh_rows.append(row)
    dh_params = dh_rows

    transforms = [homo_transf(*dhp) for dhp in dh_params]
    rot_mats = [t[:3, :3] for t in transforms]

    return dh_params, joint_params


from typing import Callable, Tuple, Union

import numpy as np

from .utils import isType

def exponential(x: Union[int, float], n: int) -> float:
    """
    Computes an approximation of e^x

    :param x: x
    :param k: k

    :return: the approximated value
    """

    acc = 0

    try:
        if isType(x, [int, float]) and isType(n, int) and n >= 0:
            for i in range(0, n):
                acc += pow(x, i) / np.math.factorial(i)

            return acc
        else:
            return np.nan
    except OverflowError:
        return acc

def coskx1(k: int, x: Union[int, float]) -> float:
    """
    Computes an approximation of cos(kx) using the formula:

    .. math::
        cos((m+1)x) = 2cos(x) * cos(mx) - cos((m-1)x)

    :param x: x
    :param k: k

    :return: the approximated value
    """

    try:
        if isType(k, int) and k >= 0 and isType(x, [int, float]):
            m1 = k - 1
            m2 = m1 - 1

            if m1 >= 1:
                if m1 == 1:
                    e1 = np.math.cos(x)
                else:
                    e1 = coskx1(m1, x)
            else:
                e1 = 1

            if m2 >= 1:
                if m2 == 1:
                    e2 = np.math.cos(x)
                else:
                    e2 = coskx1(m2, x)
            else:
                e2 = 1

            if k == 0:
                return np.math.cos(0)
            elif k == 1:
                return np.math.cos(x)
            else:
                return 2 * np.math.cos(x) * e1 - e2
        else:
            return np.nan
    except:
        return np.nan

def cossinkx2(k: int, x: Union[int, float]) -> Tuple[float, float]:
    """
    Computes an approximation of cos(kx) & sin(kx) using the formulas:

    .. math::
        cos(mx) = cosx \\cdot cos(m-1)x - sinx \\cdot sin(m-1)x
    .. math::
        sin(mx) = sinx \\cdot cos(m-1)x + cosx \\cdot sin(m-1)x

    :param x: x
    :param k: k

    :return: approximated cos(kx) and sin(kx) values
    """

    try:
        if isType(k, int) and k >= 0 and isType(x, [int, float]):
            if k == 0:
                return (np.math.cos(0), np.math.sin(0))
            elif k == 1:
                return (np.math.cos(x), np.math.sin(x))
            else:
                (cosKLess1, sinKLess1) = cossinkx2((k - 1), x)
                return (np.math.cos(x) * cosKLess1 - np.math.sin(x) * sinKLess1, np.math.sin(x) * cosKLess1 + np.math.cos(x) * sinKLess1)
        else:
            return np.nan, np.nan
    except:
        return np.nan, np.nan

def pi(n: int) -> float:
    """Computes an approximate value of Pi, using the formula:

    .. math::
        \\sum_{n=1}^{\\infty} \\cfrac{1}{n^2} = \\cfrac{1}{6} \\pi^2

    :param n: the amount of sum elements

    :return: the approximated value of pi
    """

    if isType(n, int) and n > 0:
        acc = 0

        for i in range(1, n + 1):
            acc += 1 / pow(i, 2)

        return np.math.sqrt(acc * 6)
    else:
        return np.nan

def approxZeroBisection(a: Union[int, float], b: Union[int, float], f: Callable[[float], float], epsilon: float, iteration: int) -> Tuple[float, int]:
    """
    Approximates the solution to f(x) = 0 in range [a, b] using bisection method

    :param a: left range bound
    :param b: right range bound
    :param f: callable function of variable x
    :param epsilon: the desired precision (stop condition)
    :param iteration: maximum iterations count limit (to prevent an infinite loop)

    :raises ValueError: when input types are wrong or f has same signs on both range bounds

    :return: a tuple containing (in order): an approximate solution x, iterations made count
    """

    if not isType(a, [int, float]) or not isType(b, [int, float]) or not isType(f, Callable) or not isType(epsilon, float) or not isType(iteration, int) or epsilon < 0 or iteration < 0 or b < a:
        raise ValueError("Wrong input types")

    if np.sign(f(a)) * np.sign(f(b)) >= 0:
        raise ValueError(
            "Function f has to be of different signs at the edges of the range [a, b]")

    i = 0
    c = (a + b) / 2
    while np.abs(f(c)) > epsilon and i < iteration:
        c = (a + b) / 2

        if np.sign(f(a)) != np.sign(f(c)):
            b = c
        else:
            a = c

        i += 1

    return c, i - 1

def approxZeroSecant(a: float, b: float, f: Callable[[float], float], epsilon: float, iteration: int) -> Tuple[float, int]:
    """
    Approximates the solution to f(x) = 0 in range [a, b] using secant method

    :param a: left range bound
    :param b: right range bound
    :param f: callable function of variable x
    :param epsilon: the desired precision (stop condition)
    :param iteration: maximum iterations count limit (to prevent an infinite loop)

    :raises ValueError: when input types are wrong or f has same signs on both range bounds

    :return: a tuple containing (in order): an approximate solution x, iterations made count
    """

    if not isType(a, [int, float]) or not isType(b, [int, float]) or not isType(f, Callable) or not isType(epsilon, float) or not isType(iteration, int) or epsilon < 0 or iteration < 0 or b < a or np.sign(f(a)) * np.sign(f(b)) >= 0:
        raise ValueError("Wrong input types")

    if np.sign(f(a)) * np.sign(f(b)) >= 0:
        raise ValueError(
            "Function f has to be of different signs at the edges of the range [a, b]")

    f_a = f(a)
    f_b = f(b)
    x0 = 0
    i = 0
    while iteration > -1 and (abs(a - b) > epsilon):
        i += 1

        if abs(f_a - f_b) < epsilon:
            break

        x0 = a - f(a) * (a - b) / (f(a) - f(b))
        f0 = f(x0)

        if abs(f0) < epsilon:
            break

        if f(a) * f0 < 0:
            b = x0

        elif f(b) * f0:
            a = x0

        elif f0 == 0:
            return x0, iteration

        iteration -= 1

    return x0, i - 1

def approxZeroNewton(f: Callable[[float], float], df: Callable[[float], float], ddf: Callable[[float], float], a: Union[int, float], b: Union[int, float], epsilon: float, iteration: int) -> Tuple[float, int]:
    """
    Approximates the solution to f(x) = 0 in range [a, b] using Newton's method

    :param f: callable function of variable x
    :param df: callable derivate of f (f')
    :param ddf: callable second-order derivate of f (f'')
    :param a: left range bound
    :param b: right range bound
    :param epsilon: the desired precision (stop condition)
    :param iteration: maximum iterations count limit (to prevent an infinite loop)

    :raises ValueError: when input types are wrong or either df or dff has same different on both range bounds

    :return: a tuple containing (in order): an approximate solution x, iterations made count
    """

    if not isType(a, [int, float]) or not isType(b, [int, float]) or not isType(f, Callable) or not isType(df, typing.Callable) \
            or not isType(ddf, Callable) or not isType(epsilon, float) or not isType(iteration, int) or epsilon < 0 or iteration < 0 or b < a or np.sign(f(a)) * np.sign(f(b)) >= 0:
        raise ValueError("Wrong input types")

    if np.sign(df(a)) * np.sign(df(b)) <= 0:
        raise ValueError(
            "Df has to be of different signs at the edges of range [a, b]")

    if np.sign(ddf(a)) * np.sign(ddf(b)) <= 0:
        raise ValueError(
            "Ddf has to be of different signs at the edges of range [a, b]")

    x0 = a
    for x0 in [a, b]:
        if(f(x0) * ddf(x0) > 0):
            # initial condition found
            break

    i = 0
    while np.abs(f(x0)) > epsilon and i < iteration:
        x0 = x0 - f(x0) / df(x0)

        i += 1

    return x0, i


"""
Created on Thu Apr  1 01:12:40 2021
"""

import numpy as np

def cartesian2spherical(rectangular_coordinates):
    """
    Converts Cartesian coordinates to spherical coordinates.

    Parameters
    ----------
    rectangular_coordinates:
        A list of coordinates of the point in Cartesian space.

    Returns
    -------
    list:
        Spherical coordinates of the input Cartesian coordinates.
    """

    # initialize the coordinates
    x, y, z = rectangular_coordinates

    # calculate rho
    rho = np.sqrt(x**2 + y**2 + z**2)

    # calculate theta, the reason for using arctan2 is that the range of theta
    # parameter is -180 < theta < 180.
    theta = np.arctan2(y, x)

    # calculate phi
    phi = np.arccos(z / rho)

    return rho, theta, phi


"""Algorithms ranking nodes in a graph."""

import numpy as np
import pandas as pd

def hubs_and_authorities(matrix, limit=25, disp_iterations=False):
    """Hubs and authorities algorithm.
    Also known as Hyperlink-Induced Topic Search.

    Gives each node two scores:
    Hub score is based on how many other nodes the node links to. It is
    calculated as a sum of authority scores of nodes the node points to.
    Authority score is based on how many other nodes link to the node. It is
    calculated as a sum of hub scores of nodes pointing to the node.

    After each iteration the scores are normalized.

    Full description at:
    https://en.wikipedia.org/wiki/HITS_algorithm

    Arguments:
        matrix {DataFrame} -- Pandas dataframe containing square adjacency matrix.

    Keyword Arguments:
        limit {int} -- Number of iterations. (default: {25})
        disp_iterations {bool} -- Print status every iteration. (default: {False})

    Returns:
        DataFrame -- Results.
    """

    matrix_values = matrix.values
    rows, columns = matrix_values.shape

    assert rows == columns, ("Number of rows and columns in matrix does not "
                             "match")

    # Initialize values
    init_value = 1.0 / np.sqrt(rows)
    hubs = np.ones((rows, 1)) * (init_value)
    authorities = np.ones((rows, 1)) * (init_value)

    # Main iteration loop
    for counter in range(limit):

        h_old = hubs.copy()
        a_old = authorities.copy()

        for node in range(rows):
            # Nodes the current node points to
            a_mask = matrix_values[node, :] > 0
            # Nodes pointing to the current node
            h_mask = matrix_values[:, node] > 0

            hubs[node] = authorities[a_mask].sum()
            authorities[node] = hubs[h_mask].sum()

        # Normalize scores
        hubs = hubs / np.linalg.norm(hubs)
        authorities = authorities / np.linalg.norm(authorities)

        if (counter == limit-1) or disp_iterations:
            print(f"Iteration: {counter+1}")
            print("Change: {}, {}".format(
                np.sqrt(np.sum((authorities - a_old) ** 2)),
                np.sqrt(np.sum((hubs - h_old) ** 2))
            ))

    df_out = pd.DataFrame(index=matrix.index)
    df_out['Hubs'] = hubs
    df_out['Authorities'] = authorities
    df_out['Initial'] = init_value

    return df_out

def page_rank(matrix, epsilon=0.001, limit=50):
    """PageRank algorithm.

    Ranks nodes in a manner that gives high score to nodes which are linked to
    by other nodes with high scores.
    Final ranks are equivalent to probability distribution of random traveller
    arraiving to specific node.

    The algorithm is implemented as follows:
    First, the matrix F is calculated. Its elements are defined as:
    F_st = (1 - eps) / deg(t) + eps / N, if t links to s,
         = 1 / N                       , if t doesn't link anywhere,
         = eps / N                     , otherwise.
    Here, deg(t) is the number of links from t, N is the total number of nodes
    and eps is some small constant.
    Second, the final ranks r_n are computed iteratively r_i = F * r_i-1 until
    iteration limit i=n is reached.

    Full description at:
    https://en.wikipedia.org/wiki/PageRank

    Arguments:
        matrix {DataFrame} -- Pandas dataframe containing square adjacency matrix.

    Keyword Arguments:
        epsilon {float} -- Constant value. (default: {0.001})
        limit {int} -- Number of iterations. (default: {50})

    Returns:
        DataFrame -- Results.
    """

    matrix_values = matrix.values
    rows, columns = matrix_values.shape

    ranks = np.zeros((rows, columns))

    for s in range(rows):
        for t in range(columns):
            t_n_links = matrix_values[t, :].sum()
            # If t is connected to s:
            if matrix_values[t, s] == 1:
                ranks[s, t] = epsilon / float(rows)
                assert not np.isclose(t_n_links, 0)
                ranks[s, t] += (1.0 - epsilon) / t_n_links
            elif np.isclose(t_n_links, 0):
                ranks[s, t] = 1.0 / float(rows)
            else:
                ranks[s, t] = epsilon / float(rows)

    relevance = np.ones((rows, 1)) * (1.0 / float(rows))
    old_relevance = relevance.copy()

    final_ranks = np.zeros((rows, 1))

    for i in range(limit):
        for x in range(rows):
            row_count = np.sum(ranks[x, :] * old_relevance.ravel())
            relevance[x] = row_count
            if i == limit - 1:
                final_ranks[x] = relevance[x]
        old_relevance = relevance.copy()

    df_out = pd.DataFrame(final_ranks, index=matrix.index, columns=['Rank'])

    return df_out


import numpy as np
import math
import csv
import matplotlib.pyplot as plt

## Reading the csv values into arrays:

data_x0 = []
data_x1 = []
y = []

# Using CSV functions to read the values row-wise
with open("/home/vedant/Downloads/COL774/Ass1/data/q3/logisticX.csv") as f:
	reader = csv.reader(f)
	for row in reader:
		data_x0.append(row[0])
		data_x1.append(row[1])

with open("/home/vedant/Downloads/COL774/Ass1/data/q3/logisticY.csv") as f:
	reader = csv.reader(f)
	for row in reader:
		y.append(row[0])

# Converting the values in float
data_x0 = np.asarray(data_x0).astype(np.float64)
data_x1 = np.asarray(data_x1).astype(np.float64)
y = np.asarray(y).astype(np.float64)

## Normalization step:
data_x0 = (data_x0-(np.mean(data_x0)))/(math.sqrt(np.var(data_x0)))
data_x1 = (data_x1-(np.mean(data_x1)))/(math.sqrt(np.var(data_x1)))

### Part (A) : Implementing Newton's method

# Initializing the thetha vector
thetha = np.array([0,0,0])
m = len(y)

## Defining the functions to calculate h(x), gradient, and Hessian

# Getting the h(x)
def h(data_x0,data_x1,thetha):
	z = thetha[0] + thetha[1]*data_x0 + thetha[2]*data_x1
	return (1/(1 + math.exp(-z)))

# Getting the gradient
def grad(data_x0,data_x1,y,thetha,m):
	output = np.array([0,0,0])
	for j in range(3):
		sumVal = 0
		for i in range(m):
			if (j==0):
				xj=1
			elif (j==1):
				xj = data_x0[i]
			else:
				xj = data_x1[i]

			partial_grad = (y[i] - h(data_x0[i],data_x1[i],thetha))*xj
			sumVal = sumVal + partial_grad

		output[j] = sumVal

	return output

# Getting the Hessian
def hessian(data_x0,data_x1,y,thetha,m):
	a = [[0,0,0],[0,0,0],[0,0,0]]
	output = np.array(a)

	# Obtaining H(j,k)
	for j in range(3):
		for k in range(3):
			sumVal=0
			for i in range(m):

				# xj value
				if (j==0):
					xj=1
				elif (j==1):
					xj = data_x0[i]
				else:
					xj = data_x1[i]

				# xk value
				if (k==0):
					xk=1
				elif (k==1):
					xk = data_x0[i]
				else:
					xk = data_x1[i]

				sumVal = sumVal - (h(data_x0[i],data_x1[i],thetha)) * (1-h(data_x0[i],data_x1[i],thetha))*xj*xk

			output[j][k] = sumVal

	return output

## The newton's method

thetha_old = thetha

# Running the algorithm for at max 1000 steps and breaking when the consequtive thethas are very close to each other, which will be a stopping condition.
for i in range(8):
	thetha = thetha - np.dot((np.linalg.inv(hessian(data_x0,data_x1,y,thetha,m))),(grad(data_x0,data_x1,y,thetha,m)))

	diff = np.linalg.norm(thetha - thetha_old)
	# The comparision between the consequtive values of the parameters
	if (diff < 10**-10):
		break
	thetha_old = thetha

print(thetha)

### Part (B): Plots

# Plotting the scatters
for i in range(m):
	if (y[i]==0):
		plt.scatter(data_x0[i],data_x1[i],color = 'red')
	else:
		plt.scatter(data_x0[i],data_x1[i],color = 'blue')

# Getting the labels for the individual points by taking known points from the CSV files
plt.scatter(data_x0[0],data_x1[0],color = 'red', label = 'Points with y=0')
plt.scatter(data_x0[51],data_x1[51],color = 'blue', label = 'Points with y=1')

# Plotting the line: (thetha)' * X = 0
x = np.linspace(-3,3,1000)
plt.plot(x,-(thetha[0]/thetha[2]) - (thetha[1]/thetha[2])*x, color='black', label = 'Boundary line')

# Setting the Graph
plt.legend()
plt.xlabel('x1 axis')
plt.ylabel('x2 axis')
plt.savefig('Q3_plot.png', dpi=1000, bbox_inches='tight')


"""
inner

The inner tool returns the inner product of two arrays.

import numpy

A = numpy.array([0, 1])
B = numpy.array([3, 4])

print numpy.inner(A, B)     #Output : 4
outer

The outer tool returns the outer product of two arrays.

import numpy

A = numpy.array([0, 1])
B = numpy.array([3, 4])

print numpy.outer(A, B)     #Output : [[0 0]
                            #          [3 4]]
Task

You are given two arrays:  and .
Your task is to compute their inner and outer product.

Input Format

The first line contains the space separated elements of array .
The second line contains the space separated elements of array .

Output Format

First, print the inner product.
Second, print the outer product.

Sample Input

0 1
2 3
Sample Output

3
[[0 0]
 [2 3]]
"""

import numpy as np

np.set_printoptions(legacy="1.13")

arr1 = np.array(list(map(int, input().split())), int)
arr2 = np.array(list(map(int, input().split())), int)
print(np.inner(arr1, arr2))
print(np.outer(arr1, arr2))


# -*- coding: utf-8 -*-
# gradient descent without tuning, no epsilon in the gamma = float(np.matmul((x-xprev).T,(d-dprev)))/den**2
# gamma = float(np.matmul((x-xprev).T,(d-dprev)))/den**2 + epsilon; if we add epsilon then it will tune
# Gradient descent for minimizing a function - Barzilai-Borwein method
import numpy as np

def Rosenbrock(x):
    a = 2
    b = 100
    y = b*(x[1]-x[0]**2)**2+(x[0]-a)**2

    return y

def gradf(f,x,n,epsilon):
    y = np.zeros((n,1))
    for i in range (0,n):
        xperturbed = np.copy(x)
        xperturbed[i] = x[i] + epsilon
        y1 = f(xperturbed)
        xperturbed[i] = x[i] - epsilon
        y2 = f(xperturbed)
        y[i] = (y1-y2)/(2*epsilon)
    return y

def Gradient_Descent_BB(f,n):
    x = np.zeros((n,1))
    x[0] = float(1)
    x[1] = float(2)
    dprev = np.zeros((n,1))
    xprev = np.zeros((n,1))
    tol = float(1e-9)
    epsilon = float(1e-8)

    kmax = int(1000)
    k = int(1)

    d = gradf(f,x,n,epsilon)
    f_x = f(x)
    s = np.linalg.norm(d)

    while (s > tol*(1+ np.absolute(f_x)) and k < kmax):
        if (k == 1):
            gamma = float(np.matmul(x.T,d))/s**2 + epsilon;
        else:
            den = np.linalg.norm(d-dprev)
            gamma = float(np.matmul((x-xprev).T,(d-dprev)))/den**2
        dprev = d
        xprev = x
        x = x - gamma*d
        f_x = f(x)
        d = gradf(f,x,n,epsilon)
        k = k+1
        s = np.linalg.norm(d)

    f_min = f_x;

    return x, f_min;

def main():
    n = 2
    x , f_min = Gradient_Descent_BB(Rosenbrock, n)
    print(x)

if __name__ == "__main__":  ## This command executes the main function
    main()


# A rectangular tank with a square base, an open top, and a volume of 500 ft cubed is to be constructed of sheet steel.
# Find the dimensions of the tank that has the minimum surface area.

from sympy import symbols, solve, diff, pprint

volume = 4000

s, h = symbols( 's, h' )
A = s**2 + 4*s*h
V = s**2 * h

H = solve( V - volume, h )[ 0 ]

pprint( H )

Ah = A.subs( { h: H } ).evalf()

dA_ds = diff( Ah, s )

min_side = solve( dA_ds, s )[ 0 ]
min_height = H.subs( { s: min_side } ).evalf()

if __name__ == '__main__':
	print( 'The dimensions of the tank with the minimum surface area are: ' )
	print( 'Min side: {0:.0f} ft x {0:.0f} ft x {1:.0f} ft'.format( min_side, min_height ) )
	print( '{0:.0f}, {0:.0f}, {1:.0f}'.format( min_side, min_height ) )


import numpy as np
from scipy.linalg import lu_factor, lu_solve, expm
import matplotlib.pyplot as plt

def eig_qr(A, max_n = 100, tol=1e-10):
    d = A.shape[0]
    n = 0
    M = A.copy()

    while n<max_n and np.linalg.norm(np.tril(M, -1))**2/A.size>tol:
        ew,ev = np.linalg.eig(M[-2:,-2:])
        k = np.argmin(ew-M[-1,-1])
        shift_value = ew[k]
        Q, R = np.linalg.qr(M-shift_value*np.eye(d))
        M = np.matmul(Q.T, np.matmul(M, Q))
        n+= 1

    return M.diagonal()

def largest_eval(A, n_max = 500):
    z = np.random.rand((A.shape[0]))
    z /= np.linalg.norm(z)
    n = 0

    while n<n_max:
        w = np.matmul(A, z)
        z = w/np.linalg.norm(w)
        n+=1

    return z, np.dot(np.conj(z), np.matmul(A, z))

def smallest_eval(A, n_max = 500):
    LUP = lu_factor(A)
    n = 0
    z = np.random.rand((A.shape[0]))
    z /= np.linalg.norm(z)

    while n<n_max:
        w = lu_solve(LUP, z)
        z = w/np.linalg.norm(w)
        n += 1

    return z, np.dot(np.conj(z), np.matmul(A, z))

def closest_eval(A, ev, n_max = 500,):
    z, e = smallest_eval(A-ev*np.eye(A.shape[0]), n_max)
    return z, ev+e

def closest_eval_shifted(A, ev, n_max = 500, k = 10):
    n = 0
    z = np.random.rand((A.shape[0]))
    z /= np.linalg.norm(z)
    while n<n_max:
        if n%k == 0:
            if n != 0:
                ev = np.dot(np.conj(z), np.matmul(A, z))
                M = A-ev*np.eye(A.shape[0])
            else:
                M = A-ev*np.eye(A.shape[0])

            LUP = lu_factor(M)

        w = lu_solve(LUP, z)
        z = w/np.linalg.norm(w)
        n += 1

    return z, np.dot(np.conj(z), np.matmul(A, z))

def arnoldi(A, v0, k):
    v = v0/np.linalg.norm(v0)
    V = np.zeros((v0.size, k+1))
    V[:, 0] = v
    v_bar = v
    H = np.zeros((k+1, k))

    for l in range(k):
        v_bar = np.matmul(A, V[:,l])
        for i in range(l+1):
            h = np.dot(V[:, i], v_bar)
            v_bar -= h*V[:, i]
            H[i, l] = h
        h = np.linalg.norm(v_bar)
        H[l+1, l] = h
        V[:, l+1] = v_bar/h

    return H, V

def lanczos(A, v0, k):
    v = v0/np.linalg.norm(v0)
    V = np.zeros((v0.size, k+1))
    V[:, 0] = v
    v_bar = v
    H = np.zeros((k+1, k))

    for l in range(k):
        v_bar = np.matmul(A, V[:,l])
        for i in range(l-1, l+1):
            h = np.dot(V[:, i], v_bar)
            v_bar -= h*V[:, i]
            H[i, l] = h
        h = np.linalg.norm(v_bar)
        H[l+1, l] = h
        V[:, l+1] = v_bar/h

    return H, V


# 21 February 2018 Miroslav Gasparek
# Practice with NumPy

import numpy as np

# Practice 1
# Generate array of 0 to 10
my_ar1 = np.arange(0,11,dtype='float')
print(my_ar1)

my_ar2 = np.linspace(0,10,11,dtype='float')
print(my_ar2)

# Practice 2
# Load in data
xa_high = np.loadtxt('data/xa_high_food.csv',comments='#')
xa_low = np.loadtxt('data/xa_low_food.csv',comments='#')

def xa_to_diameter(xa):
    """ Convert an array of cross-sectional areas to diameters with
    commensurate units."""

    # Compute diameter from area
    diameter = np.sqrt((4*xa)/np.pi)

    return diameter

# Practice 3
# Create matrix A
A = np.array([[6.7, 1.3, 0.6, 0.7],
              [0.1, 5.5, 0.4, 2.4],
              [1.1, 0.8, 4.5, 1.7],
              [0.0, 1.5, 3.4, 7.5]])

# Create vector b
b = np.array([1.1, 2.3, 3.3, 3.9])

# 1. Print row 1 (remember, indexing starts at zero) of A.
print(A[0,:])
# 2. Print columns 1 and 3 of A.
print(A[:,(0,2)])
# 3. Print the values of every entry in A that is greater than 2.
print(A[A > 2])
# 4. Print the diagonal of A. using the np.diag() function.
print(np.diag(A))

# 1. First, we'll solve the linear system  A⋅x=bA⋅x=b .
# Try it out: use np.linalg.solve().
# Store your answer in the Numpy array x.
x = np.linalg.solve(A,b)
print('Solution of A*x = b is x = ',x)
# 2. Now do np.dot(A, x) to verify that  A⋅x=bA⋅x=b .
b1 = np.dot(A,x)
print(np.isclose(b1,b))
# 3. Use np.transpose() to compute the transpose of A.
AT = np.transpose(A)
print('Transpose of A is AT = \n',AT)
# 4. Use np.linalg.inv() to compute the inverse of A.
AInv = np.linalg.inv(A)
print('Inverse of A is AInv = \n',AInv)

# 1. See what happens when you do B = np.ravel(A).
B = np.ravel(A)
print(B)
# 2. Look of the documentation for np.reshape(). Then, reshape B to make it look like A again.
C  = B.reshape((4,4))
print(C)


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

# # Due Dec 5, 11:59 PM
# Programming Assignment
# 15 points
# Shovon Bhowmik Nov 17 (Edited Nov 17)
# Problems that has to be solved using python or matlab:
#
# 1.
#  * Make an excel sheet of the datas given for the correlation example in class.
#  * Calculate correlation coefficient and find the relationship between the datas.
#  * Also compare the result with the defined function used by python or matlab for establishing correlation between datas.
#  * Also visualize the graphical representation of the datas.
#
#
#
# 2.
#  * Collect any dataset with two variables x and y from any site. **The dataset must be atleast of 100 items.**
#  * Find the predicted y value based on x using **simple linear regression equation.**
#
#  Then find
#
#  1. SSR,
#  2. SSE
#  and
#  3. Coefficient of determination by calculating difference between the actual y value and the predicted y value.
#  4. Finally show the regression line based on your result graphically and find the accuracy of your model's performance.
#
# N. B:
#
# 1) You all are requested to submit the problems in the following link:
# https://drive.google.com/drive/folders/1qreLOzd0iN3ye4JfZ9S_O9O5qzTMnD-N?usp=sharing
# Upload your zip file which will be named after your roll number and there will be two files inside the zip file. Both files will be in .py or .mat format
#
# 2) If I find same code, the code that will come second or next will be not accepted and he or she will get no marks in the assignment.
#
# Please knock me if there is any question regarding the assignment.
# Thank you.

# In[68]:

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

# DATASET
# > https://archive.ics.uci.edu/ml/datasets/student+performance

# In[69]:

df = pd.read_csv('student-mat.csv', delimiter=';')

# In[70]:

df.head()

# In[71]:

mydf = df.iloc[ : , -3:-1]

# In[72]:

# G1 = Grade1
# G2 = Grade2
#
mydf.head()

# In[73]:

dataX = mydf.iloc[ : , 0:1]
dataY = mydf.iloc[ : , 1:]

# In[74]:

dataX.head()

# In[75]:

dataY.head()

# In[ ]:

# In[ ]:

# In[ ]:

# In[ ]:

# \begin{equation} r = ( sum((Xi - meanX) * (Yi - meanY) ) / sqrt( sum( squre( Xi - meanX) * squre(Yi - meanY) ) )) \end{equation}

# In[76]:

r = ( np.sum( (dataX.values - dataX.values.mean()) * (dataY.values - dataY.values.mean()) ) / np.sqrt( np.sum( np.square(dataX.values - dataX.values.mean()) ) * np.sum( np.square(dataY.values - dataY.values.mean()) ) ) )

# In[77]:

r

# In[78]:

meanX = dataX.values.mean()

# In[79]:

meanY = dataY.values.mean()

# In[80]:

sumOfMultiplyOfMeanXY = np.sum((dataX.values - meanX) * (dataY.values-meanY))

# In[81]:

meanX

# In[82]:

meanY

# In[83]:

sumOfMultiplyOfMeanXY

# In[84]:

np.sum( (dataX.values - dataX.values.mean()) * (dataY.values - dataY.values.mean()))

# In[85]:

np.sqrt( np.sum( np.square(dataX.values - dataX.values.mean()) ) * np.sum( np.square(dataY.values - dataY.values.mean()) ) )

# In[86]:

sumOfSqureOfX = np.sum(np.square(dataX.values - dataX.values.mean()))

# In[87]:

sumOfSqureOfY = np.sum(np.square(dataY.values - dataY.values.mean()))

# In[88]:

sqrtOfSSXY = np.sqrt( sumOfSqureOfX * sumOfSqureOfY )

# In[89]:

sqrtOfSSXY

# In[90]:

check_r = sumOfMultiplyOfMeanXY / sqrtOfSSXY

# In[91]:

check_r

# # $$y = a + bx$$
#
#
# ## $$b = r * (sdY/sdX)$$
#
# ## $$a = meanY - b * meanX$$
#

# In[92]:

b = r * (np.std(dataY.values) / dataX.values.std())

# In[93]:

b

# In[94]:

np.std(dataY.values)

# In[95]:

dataY.values.std()

# In[96]:

a = dataY.values.mean() - ( b * dataX.values.mean())

# In[97]:

a

# In[98]:

def Yprediction(x):
    return (a + (b * x))

# In[99]:

# Calculating Predicted Y
predictedY = dataX.apply(Yprediction)

# print(predictedY.head())?
# print(dataY.head())

# In[ ]:

# In[ ]:

# In[100]:

r == check_r

# In[101]:

print(type(dataY), type(predictedY))

# In[102]:

# >>> d = {'col1': [1, 2], 'col2': [3, 4]}
# >>> df = pd.DataFrame(data=d)
# >>> df

testdf = dataY
testdf['G2_Predicted'] = predictedY

# In[103]:

testdf.head()

# In[ ]:

# In[ ]:

# In[104]:

# type(testdf['G2_Predicted'].values)

# In[105]:

# SSR = np.sum( testdf['G2_Predicted'].values, testdf['G2'].values.mean() )
# testdf['G2'].values.mean()
# testdf['G2_Predicted'].values
# np.sum( np.square( testdf['G2_Predicted'].values, testdf['G2'].values.mean() ) )

# np.sum(np.square(testdf['G2_Predicted'].values - testdf['G2'].values.mean()))

# In[106]:

SSR = np.sum(np.square( np.subtract(testdf['G2_Predicted'].values , testdf['G2'].values.mean()) ))

# In[107]:

SSE = np.sum(np.square( np.subtract(testdf['G2'].values , testdf['G2_Predicted'].values.mean()) ))

# In[108]:

CoeffOfDetermination = (SSR / SSE)

# In[109]:

CoeffOfDetermination

# In[110]:

np.sqrt(CoeffOfDetermination)

# In[111]:

r

# In[ ]:

# In[ ]:

# In[ ]:

# In[112]:

plt.scatter(df.G1, df.G2)
plt.plot(df.G1, testdf[['G2_Predicted']])
plt.show()

# In[113]:

from sklearn import linear_model

reg = linear_model.LinearRegression()

# In[114]:

reg.fit(df[['G1']], df.G2)

# In[115]:

y_cap = reg.predict(df[['G1']])
reg.coef_

# In[116]:

# %matplotlib inline

plt.scatter(df.G1,df.G2,color='red', marker='o')
plt.plot(df.G1,y_cap)
plt.show()

# In[ ]:

# # Mean absolute error(MAE)
#
# $$MAE = \frac{1}{n} \sum_{}\left\lvert{y- \hat{y}}\right\rvert$$
#
# # Mean Squared Error
#
# $$MSE = \frac{1}{n} \sum_{}{(y- \hat{y})^2}$$
#
# # Root Mean Squared Error
#
# $$RMSE = \sqrt{ \frac{1}{n} \sum_{}{(y- \hat{y})^2}}$$
#

# In[117]:

# testdf.G2 - testdf.G2_Predicted

# In[118]:

MAE = ( (1/len(testdf.G2)) * np.sum(abs(np.subtract(testdf.G2 , testdf.G2_Predicted))) )

# In[119]:

MAE

# In[120]:

YY = testdf.G2
YY.head()
YYpredict = testdf.G2_Predicted
YYpredict.head()
# ( (1 / len(YY)) * ( np.sum(np.abs(np.subtract(YY,YYp)))) )
MSE = ( (1 / len(YY)) * ( np.sum(np.square(np.subtract(YY,YYpredict)))) )

# In[121]:

MSE

# In[122]:

RMSE = np.sqrt(MSE)

# In[123]:

RMSE

# In[124]:

from sklearn.metrics import mean_squared_error

from math import sqrt

rmse = sqrt(mean_squared_error(YY, YYpredict))

print(rmse)

# In[ ]:

# In[ ]:

# In[ ]:

# In[ ]:

# In[ ]:


import numpy as np
x=np.array([100,200,300,400]) #each member as a
y=np.array([400,500,600,700]) #each member as b
condition=np.array([True,True,False,False]) #each member cond
#if true take x else y
z= [a if cond else b for a,cond, b in zip(x,condition,y)]
print(z)

#same opeartion usiing numpy
#np.where(#condition,#value for yes,#value for no)
z2=np.where(condition,x,y)

z3=np.where(x>200,0,1) #if x is > 200 replace it by 0 else 1
print(z3)

#sum
print(x.sum())

n=np.array([[10,20,30],[40,50,60],[70,80,90]])
print("N=",n)
#colomun sum
print("Sum of Coloums=",n.sum(0))
#row sum
print("Sum of Rows=",n.sum(1))

#mean
mean=x.mean()
print("Mean=",mean)
#std deviation
std_dev=x.std()
print("Standard Deviation=",std_dev)

#varinace
var=x.var()
print("Variane=",var)

#logical operations

condition2=np.array([True,False,True])
print("OR operator",condition2.any())
print("AND operator",condition2.all())

#sorting in numpy arrays
unsorted=np.array([2,5,9,4,-6,3,1])
print("Unsorted Array=",unsorted)
unsorted.sort()
print("Sorted Array=",unsorted)

s=np.array(["Solid","Solid","Liquid","Liquid","Liquid","Gas","Gas"])
n=np.array([1,2,3,1,2,4,4,8,9,9,3,4,4])
print(np.unique(s))
print(np.unique(n))

print(np.in1d([2,6,9],n))