# -*- coding: utf-8 -*-
"""
Created on Mon Nov 09 07:51:28 2015

@author: leo
"""
from __future__ import division

import numpy as np
from scipy import integrate
from matplotlib import pyplot as plt
from numba import jit

#Símbolo / se torna divisão verdadeira (e não apenas a inteira)

#Rossler system ##############################################
##############################################################

#
#Explore system with N initial conditions
#

def explore_rossler(N=10, angle=0.0, max_time=4.0,
    a=0.398, b=2.0, c=4.0,
    Plot=False):
    """
    You can use it with animated widgets:

        from IPython.html.widgets import interact, interactive
        w = interactive(explore_rossler, angle=(0.,360.), N=(0,50), sigma=(0.0,50.0), rho=(0.0,50.0),Plot=True)
        display(w)

    Or use it alone or with another code:

        t, x_t = explore_rossler(angle=0, N=10, max_time=40, Plot=False)
        leo.GPU_plot_3D(x_t)

        Obs.: to remove initial transiente use e.g. x_t[:,1000:,:].
    """

    def rossler_deriv(x_y_z, t0, a=a, b=b, c=c):
        """Compute the time-derivative of the cord attractor
        (Lorenz'84 modified)."""
        x, y, z = x_y_z
        return [-y-z, x+a*y, b+z*(x-c)]

    # Choose random starting points, uniformly distributed from -15 to 15
    np.random.seed(1)
    x0 = -2 + 1 * np.random.random((N, 3))

    # Solve for the trajectories
    t = np.linspace(0, max_time, int(250*max_time))
    x_t = np.asarray([integrate.odeint(rossler_deriv, x0i, t)
                      for x0i in x0])

    #
    #Figure
    #
    if Plot == True:
        fig = plt.figure()
        ax = fig.add_axes([0, 0, 1, 1], projection='3d')
        ax.axis('off')

        # prepare the axes limits
        #ax.set_xlim((-25, 25))
        #ax.set_ylim((-35, 35))
        #ax.set_zlim((5, 55))

        # choose a different color for each trajectory
        colors = plt.cm.jet(np.linspace(0, 1, N))

        for i in range(N):
            x, y, z = x_t[i,:,:].T
            lines = ax.plot(x, y, z, '-', c=colors[i])
            plt.setp(lines, linewidth=2)

        ax.view_init(30, angle)
        plt.show()

    return t, x_t

#
#Uncoupled
@jit(nopython=True)
def rossler_system(Y, t, w,a,b,c):
        """Compute the time-derivative of the Rossler system."""
        x, y, z=Y
        return [-w*y -z, w*x + a*y, b + z*(x-c)]

def solve_rossler(w=1, a=0.3, b=2, c=4, max_time=400,
                  dt=0.1, s_time=0.2, xyz0=[2,0,0], Plot=False):
    """Solve the Rossler system with parameters a, b, c, natural
    frequency w and initinal condition xyz0.

    Call as: t, xyz = solve_rossler(**args_dict)

       args_dict={'a':0.4,'b':2.,
           'c':4.,
           'max_time':4000,
           'dt':0.01,
           's_time':0.01}

    Returns a 1D (time) and 2D array with 3 columns (x, y and z time series).
    Returns the Phase Space plot if Plot=True.

       w, DOUBLE = natural frequency of the oscillator
       a,b, c, INT = parameters.
       max_time, INT = lenght of the simulation, in Rossler time units
       dt, DOUBLE = integration step
       s_time > dt, DOUBLE = sampling time
       xyz0, DOUBLE = initial conditions array [x0, y0, z0]
       Plot, True/False: if True, returns the phase space plot.
    """
    from scipy import integrate

    #Condicao inicial
    xyz0 = np.array(xyz0)

    #Solucao das trajetorias
    t = np.linspace(0, max_time, int((1/dt)*max_time))
    xyz_t = integrate.odeint(rossler_system, xyz0, t,args=(w,a,b,c))

    #sampling (decimating):
    #All rows: jumping s_time/dt, All columns
    xyz_t = xyz_t[::int(s_time/dt), :]

    #figura
    if Plot == True:
        fig = plt.figure()
        ax = fig.gca(projection='3d')

        #axes labels
        ax.set_xlabel("X Axis")
        ax.set_ylabel("Y Axis")
        ax.set_zlabel("Z Axis")
        ax.set_title("Rossler Attractor")
        ax.plot(xs=xyz_t[:,0], ys=xyz_t[:,1], zs=xyz_t[:,2])
        ax.scatter3D(xs=xyz0[0], ys=xyz0[1], zs=xyz0[2], color='r')

        plt.show()

    return t, xyz_t

#
#Coupled
#
@jit(nopython=True)
def Network_RosslerSystem(Y,t, F,w, A, b, c, Nsys):
    """Defines a set of coupled Rossler Systems in a Network structure.
        Coupling is done through the y variable.
        The coupling and the network topology is defined by the matrix A.
        The sistem is (where (i) * is multiplication element wise
           and (ii) AY is the diag(kroneker product(A*Y))):

        X_dot = -w*Y - Z
        Y_dot = X +AY
        Z_dot = b_f + Z*(X-c)

        Y, 1D array: state of the systems of the form
            [x1,x2, ...,xn, y1,y2, ...,yn, z1,z2,...,zn].
        t is needed to the function ODEinte.
        w, 1D array of floats: frequencies of the oscilators.
        A, 2D array: matrix defining the coupling (explicity
            Y_dot = X +AY)
        b, c, FLOATs: see the ODE equations
        Nsys, INT: number of systems, must be iqual to int(len(Y0)/3)
            because the each system has 3 dimentions (x, y, z)"""
    # com Jit:
    #    15 segundos -> 3.4 segundos com Sys.shape: (1000000, 6)

    #X_dot = -w*Y - Z
    F[:Nsys] = -w*Y[Nsys:2*Nsys] - Y[2*Nsys:3*Nsys]

    #Y_dot = X +AY: ayj +c(y_{j+1} -2yj +2y_{j-1}) == y_{j+1} -(a-2c)yj +2y_{j-1}
    F[Nsys:2*Nsys] = w*Y[:Nsys] + np.dot(Y[Nsys:2*Nsys],A) #y_{j+1} -(a-2c)yj +2y_{j-1}

    #Z_dot = b_f + Z*(X-c)
    F[2*Nsys:3*Nsys] = b + Y[2*Nsys:3*Nsys]*(Y[:Nsys]-c)

    return F
    #return np.r_[dotX, dotY, dotZ]                               34.9 micro_s.
    #return np.array([dotX, dotY, dotZ]).ravel()                  16.3 micro_s.
    #return [dotX, dotY, dotZ]                                    12.6 micro_s.
    #@jit                                                         19.8 micro_s.
    #@jit(float64[:](float64[:],float64... limite serah 3x 3 micro_s =  9 micro_s
    #   => nao compensa usar jit.
    # nao atribuindo dotX etc, tudo direto no return              16.1 micro_s
    #trocar dotX, dotY etc por dotY[:Nys], dotY[Nsys:2*Nsys]      15.2 micro_s*

def Network_solve_RosslerSystem(args):
    """Returns a 2D-array of the trajectories of the coupled systems in the form
    [X1...n, Y1...n, Z1...n]

        Y0, 1D array of DOUBLE or INT: inital state of the form
            Y0 = [x1,x2, ...,xn, y1,y2, ...,yn, z1,z2,...,zn]
        dt, DOUBLE = integration step
        s_time > dt, DOUBLE = sampling time"""
#    w, A, b=0.1, c=8.5,
 #                               Y0 = [2,2, 0,-2, 0,0],
  #                              max_time=400, dt=0.1, s_time=0.2
    from scipy import integrate

    w, A, b, c, Y0, max_time, dt, s_time = args
    Nsys = int(len(Y0)/3) #number of coupled Rossler systems

    F=np.empty_like(Y0)
    t = np.linspace(0, max_time, int((1/dt)*max_time))

    Yt = integrate.odeint(Network_RosslerSystem, Y0, t,
                                 args=(F,w, A, b, c, Nsys))

    #sampling (decimating):
    #All rows: jumping s_time/dt, All columns
    #return Nsys, t, Yt[::int(s_time/dt), :]
    return Yt[::int(s_time/dt), :]

# Network Rossler, choosing the kind of coupling ################
@jit(nopython=True)
def Network_RosslerSystemChooseCoup(Y,t,F,
                w, a, b, c,Agm,
                Nsys,coup_vector=[0.0,1.0,0.0],coup_strength=0.0):
    x=Y[:Nsys]
    y=Y[Nsys:2*Nsys]
    z=Y[2*Nsys:3*Nsys]

    beta_x,beta_y,beta_z=coup_vector

    F[:Nsys] = -w*y - z +beta_x*coup_strength*np.dot(Agm,x)
    F[Nsys:2*Nsys] = w*x + a*y +beta_y*coup_strength*np.dot(Agm,y)
    F[2*Nsys:3*Nsys] = b + z*(x-c) +beta_z*coup_strength*np.dot(Agm,z)

    return F

def Network_solve_RosslerSystemChooseCoup(args):
    from scipy import integrate
    #w, A, b, c, Y0, max_time, dt, s_time = args
    w, a, b, c, Agm,coup_vector,coup_strength,Y0, max_time, dt, s_time=args
    Nsys = int(len(Y0)/3) #number of coupled Rossler systems

    F=np.empty_like(Y0)
    t = np.linspace(0, max_time, int((1/dt)*max_time))

    Yt = integrate.odeint(Network_RosslerSystemChooseCoup, Y0, t,
                                 args=(F,w, a, b, c, Agm,Nsys,coup_vector,coup_strength))

    return Yt[::int(s_time/dt), :]

####################################
#Antigos do Rossler
####################################
def solve_coupled_rossler(w1=0.98, w2=1.02, a=0.3, d=0.05, b_f=0.1, c=8.5,
                          max_time=400, dt=0.1, s_time=0.2,
                          xyz0=[2,10,10, 2,-5,0], Plot=False):
    """w1, w2, DOUBLE = natural frequency of the oscillators
       d, DOUBLE = is the coupling strength -> 3 routes do chaotic phase syncronization:
           1) strong coherence
           2) intermediate coherence
           3) strong noncoherence
        a, DOUBLE = parameter in [0.15; 0.3] in ref [1], pg. 69.
        f, DOUBLE = ???
        dt, DOUBLE = integration step
        s_time > dt, DOUBLE = sampling time"""

    def coupled_rossler_system((x1, y1, z1, x2, y2, z2), t0, w1=w1, w2=w2, a=a, d=d, b_f=b_f, c=c):
        """Compute the time-derivative of the Rossler system.
            ref [1], pg. 69, eq. 4.25"""
        return [-w1*y1 -z1, w1*x1 + a*y1 + d*(y2-y1), b_f + z1*(x1-c),
               -w2*y2 -z2, w2*x2 + a*y2 + d*(y1-y2), b_f + z2*(x2-c)]

    #Condicao inicial (dos dois sistemas)
    xyz0 = np.array(xyz0)

    #Solucao das trajetorias
    t = np.linspace(0, max_time, int((1/dt)*max_time))
    xyz_1_2_t = integrate.odeint(coupled_rossler_system, xyz0, t)

    #sampling (decimating):
    #All rows: jumping s_time/dt, All columns
    xyz_1_2_t = xyz_1_2_t[::int(s_time/dt), :]

    #figura
    if Plot == True:
        Color = ('blue', 'green')
        fig = plt.figure(figsize=(16,8))

        for i in [0,1]:
            ax = fig.add_subplot(1,2,1+i, projection='3d')

            #Trajetoria
            ax.plot(xs=xyz_1_2_t[:,0+3*i], ys=xyz_1_2_t[:,1+3*i], zs=xyz_1_2_t[:,2+3*i], alpha=0.5, color=Color[i])

            #Condicao inicial
            ax.scatter3D(xs=xyz0[3*i], ys=xyz0[1+3*i], zs=xyz0[2+3*i], color='r')

            #axes labels
            ax.set_xlabel("X Axis")
            ax.set_ylabel("Y Axis")
            ax.set_zlabel("Z Axis")
            ax.set_title("Rossler Attractor \n System "+str(i+1)+
                         ", (a,d)=("+str(a)+", "+str(d)+")"+
                         "\n "+str(xyz_1_2_t.shape[0])+
                         " points, Sample frequency "+
                         str(1/s_time))

        plt.show()

    return t, xyz_1_2_t

def CriticalCouplingCurvesRossler(a=0.25, d=0.05):

    image_file = "../references/Critical coupling curves of the Rossler systems.PNG"
    image = plt.imread(image_file)

    fig, ax = plt.subplots(figsize=(10,10))

    ax.imshow(image)
    ax.axis('off') # clear x- and y-axes

    px1, px2 = 165, 715
    py1, py2 = 55, 280

    p_sincronismo =[3666.667*a - 385, -1125*d + 280]

    ax.plot([px1, px2, px2, px1], [py1, py1, py2, py2], 'r+')

    ax.plot(p_sincronismo[0], p_sincronismo[1], 'ro')
    ax.set_title('(a, d) = ('+str(a)+', '+str(d)+')')

    fig.tight_layout()

# Lorenz system ##############################################
##############################################################

#
#Explore system with N initial conditions
#

def explore_lorenz(N=10, angle=0.0, max_time=4.0,
   sigma=10.0, beta=8./3, rho=28.0, Plot=False):

    """
    You can use it with animated widgets:

        from IPython.html.widgets import interact, interactive
        w = interactive(explore_lorenz, angle=(0.,360.), N=(0,50), sigma=(0.0,50.0), rho=(0.0,50.0),Plot=True)
        display(w)

    Or use it alone or with another code:

        t, x_t = explore_lorenz(angle=0, N=10, max_time=40, Plot=False)
        leo.GPU_plot_3D(x_t)

        Obs.: to remove initial transiente use e.g. x_t[:,1000:,:].
    """

    def lorenz_deriv(x_y_z, t0, sigma=sigma, beta=beta, rho=rho):
        """Compute the time-derivative of a Lorenz system."""
        x, y, z = x_y_z
        return [sigma * (y - x), x * (rho - z) - y, x * y - beta * z]

    # Choose random starting points, uniformly distributed from -15 to 15
    np.random.seed(1)
    x0 = -15 + 30 * np.random.random((N, 3))

    # Solve for the trajectories
    t = np.linspace(0, max_time, int(250*max_time))
    x_t = np.asarray([integrate.odeint(lorenz_deriv, x0i, t)
                      for x0i in x0])

    #
    #Figure
    #
    if Plot == True:
        fig = plt.figure()
        ax = fig.add_axes([0, 0, 1, 1], projection='3d')
        ax.axis('off')

        # prepare the axes limits
        ax.set_xlim((-25, 25))
        ax.set_ylim((-35, 35))
        ax.set_zlim((5, 55))

        # choose a different color for each trajectory
        colors = plt.cm.jet(np.linspace(0, 1, N))

        for i in range(N):
            x, y, z = x_t[i,:,:].T
            lines = ax.plot(x, y, z, '-', c=colors[i])
            plt.setp(lines, linewidth=2)

        ax.view_init(30, angle)
        plt.show()

    return t, x_t

#
#Uncoupled
def solve_lorenz(sigma=10.0, beta=8./3, rho=28.0, max_time=400,
                  dt=0.1, s_time=0.2, xyz0=[2,0,0], Plot=False):
    """Solve the Rossler system with parameters a, b, c, natural
    frequency w and initinal condition xyz0.

    Call as: t, xyz = solve_rossler(*args)

    Returns a 1D (time) and 2D array with 3 columns (x, y and z time series).
    Returns the Phase Space plot if Plot=True.

       w, DOUBLE = natural frequency of the oscillator
       a,b, c, INT = parameters.
       max_time, INT = lenght of the simulation, in Rossler time units
       dt, DOUBLE = integration step
       s_time > dt, DOUBLE = sampling time
       xyz0, DOUBLE = initial conditions array [x0, y0, z0]
       Plot, True/False: if True, returns the phase space plot.
    """

    def lorenz_system((x, y, z), t0,sigma=sigma, beta=beta, rho=rho):
        """Compute the time-derivative of the Rossler system."""
        return [sigma * (y - x), x * (rho - z) - y, x * y - beta * z]

    #Condicao inicial
    xyz0 = np.array(xyz0)

    #Solucao das trajetorias
    t = np.linspace(0, max_time, int((1/dt)*max_time))
    xyz_t = integrate.odeint(lorenz_system, xyz0, t)

    #sampling (decimating):
    #All rows: jumping s_time/dt, All columns
    xyz_t = xyz_t[::int(s_time/dt), :]

    #figura
    if Plot == True:
        fig = plt.figure()
        ax = fig.gca(projection='3d')

        #axes labels
        ax.set_xlabel("X Axis")
        ax.set_ylabel("Y Axis")
        ax.set_zlabel("Z Axis")
        ax.set_title("lorenz Attractor")
        ax.plot(xs=xyz_t[:,0], ys=xyz_t[:,1], zs=xyz_t[:,2])
        ax.scatter3D(xs=xyz0[0], ys=xyz0[1], zs=xyz0[2], color='r')

        plt.show()

    return t, xyz_t

#
#Coupled
#

def Network_LorenzSystem(Y,t, A, sigma,beta,rho, Nsys):
    """
    Compute the time-derivative of a Lorenz system.
    Coupling is done through the z variable, as in Bialonsk (2006). Note that
        in his paper the author exchanges x<->y. So, there the coupling is
        through x and here is through z!!!
    """

    #X_dot =  sigma*(y - x)
    dotX = sigma*(Y[Nsys:2*Nsys]-Y[:Nsys]) #+ np.dot(Y[:Nsys],A)

    #Y_dot = x*(rho-z) - y
    dotY = Y[:Nsys]*(rho-Y[2*Nsys:3*Nsys])-Y[Nsys:2*Nsys]

    #Z_dot = x*y - beta*z
    dotZ = Y[:Nsys]*Y[Nsys:2*Nsys] -beta*Y[2*Nsys:3*Nsys] + np.dot(Y[2*Nsys:3*Nsys],A)

    return np.array([dotX, dotY, dotZ]).ravel()

def Network_solve_LorenzSystem(args):
    """
        Y0, 1D array of DOUBLE or INT: inital state of the form
            Y0 = [x1,x2, ...,xn, y1,y2, ...,yn, z1,z2,...,zn]
        dt, DOUBLE = integration step
        s_time > dt, DOUBLE = sampling time

        Obs.: The number of systems is defined by the length of Y0/3

    """
    A, sigma, beta, rho, Y0, max_time, dt, s_time = args
    Nsys = int(len(Y0)/3) #number of coupled Rossler systems

    t = np.linspace(0, max_time, int((1/dt)*max_time))

    Yt = integrate.odeint(Network_LorenzSystem, Y0, t,
                                 args=(A, sigma,beta,rho, Nsys))

    #sampling (decimating):
    #All rows: jumping s_time/dt, All columns
    return Yt[::int(s_time/dt), :]

# Cord attractor (modified Lorenz'84 system ##################
##############################################################

#Segundo e-mail do Aguirre, b eh a freq. de oscilacao

#
#Explore system with N initial conditions
#

def explore_CordAttractor(N=10, angle=0.0, max_time=4.0,
    a=0.25, b=4.0, F=8.0, G=1.0,
    Plot=False):
    """
    You can use it with animated widgets:

        from IPython.html.widgets import interact, interactive
        w = interactive(solve_lorenz, angle=(0.,360.), N=(0,50), sigma=(0.0,50.0), rho=(0.0,50.0),Plot=True)
        display(w)

    Or use it alone or with another code:

        t, x_t = solve_CordAttractor(angle=0, N=10, max_time=40, Plot=False)
        leo.GPU_plot_3D(x_t)

        Obs.: to remove initial transiente use e.g. x_t[:,1000:,:].
    """

    def CordAttractor_deriv(x_y_z, t0, a=a, b=b, F=F, G=G):
        """Compute the time-derivative of the cord attractor (Lorenz'84 modified)."""
        x, y, z = x_y_z
        return [-y -z -a*x + a*F, x*y -b*x*z -y +G, b*x*y +x*z -z]

    # Choose random starting points, uniformly distributed from -15 to 15
    np.random.seed(1)
    x0 = -15 + 30 * np.random.random((N, 3))

    # Solve for the trajectories
    t = np.linspace(0, max_time, int(250*max_time))
    x_t = np.asarray([integrate.odeint(CordAttractor_deriv, x0i, t)
                      for x0i in x0])

    #
    #Figure
    #
    if Plot == True:
        fig = plt.figure()
        ax = fig.add_axes([0, 0, 1, 1], projection='3d')
        ax.axis('off')

        # prepare the axes limits
        #ax.set_xlim((-25, 25))
        #ax.set_ylim((-35, 35))
        #ax.set_zlim((5, 55))

        # choose a different color for each trajectory
        colors = plt.cm.jet(np.linspace(0, 1, N))

        for i in range(N):
            x, y, z = x_t[i,:,:].T
            lines = ax.plot(x, y, z, '-', c=colors[i])
            plt.setp(lines, linewidth=2)

        ax.view_init(30, angle)
        plt.show()

    return t, x_t

#
#Uncoupled
def solve_CordAttractor(w=1, a=0.25, b=4.0, F=8.0, G=1.0, max_time=400,
                  dt=0.1, s_time=0.2, xyz0=[2,0,0], Plot=False):
    """Solve the CordAttractor system with parameters a, b, c, natural
    frequency w and initinal condition xyz0.

    Call as: t, xyz = solve_rossler(*args)

    Returns a 1D (time) and 2D array with 3 columns (x, y and z time series).
    Returns the Phase Space plot if Plot=True.

       w, DOUBLE = natural frequency of the oscillator
       a,b, c, INT = parameters.
       max_time, INT = lenght of the simulation, in Rossler time units
       dt, DOUBLE = integration step
       s_time > dt, DOUBLE = sampling time
       xyz0, DOUBLE = initial conditions array [x0, y0, z0]
       Plot, True/False: if True, returns the phase space plot.
    """

    def CordAttractor((x, y, z), t0, a=a, b=b, F=F, G=G):
        """Compute the time-derivative of the cord attractor."""
        return [-y -z -a*x + a*F, x*y -b*x*z -y +G, b*x*y +x*z -z]

    #Condicao inicial
    xyz0 = np.array(xyz0)

    #Solucao das trajetorias
    t = np.linspace(0, max_time, int((1/dt)*max_time))
    xyz_t = integrate.odeint(CordAttractor, xyz0, t)

    #sampling (decimating):
    #All rows: jumping s_time/dt, All columns
    xyz_t = xyz_t[::int(s_time/dt), :]

    #figura
    if Plot == True:
        fig = plt.figure()
        ax = fig.gca(projection='3d')

        #axes labels
        ax.set_xlabel("X Axis")
        ax.set_ylabel("Y Axis")
        ax.set_zlabel("Z Axis")
        ax.set_title("Cord Attractor")
        ax.plot(xs=xyz_t[:,0], ys=xyz_t[:,1], zs=xyz_t[:,2])
        ax.scatter3D(xs=xyz0[0], ys=xyz0[1], zs=xyz0[2], color='r')

        plt.show()

    return t, xyz_t

#
#Coupled
#

def Network_CordAttractor(Y,t, A, a,b,F,G, Nsys):
    """
    Coupling is done through the x variable
    """

    #X_dot =  -y-z-ax+aF
    dotX = -Y[Nsys:2*Nsys] -Y[2*Nsys:3*Nsys] -a*Y[:Nsys] +a*F +np.dot(Y[:Nsys],A)

    #Y_dot = xy-bxz-y+G
    dotY = Y[:Nsys]*Y[Nsys:2*Nsys] -b*Y[:Nsys]*Y[2*Nsys:3*Nsys] -Y[Nsys:2*Nsys] +G

    #Z_dot = bxy+xz-z
    dotZ = b*Y[:Nsys]*Y[Nsys:2*Nsys] +Y[:Nsys]*Y[2*Nsys:3*Nsys] -Y[2*Nsys:3*Nsys]

    return np.array([dotX, dotY, dotZ]).ravel()

def Network_solve_CordAttractor(args):
    """
        Y0, 1D array of DOUBLE or INT: inital state of the form
            Y0 = [x1,x2, ...,xn, y1,y2, ...,yn, z1,z2,...,zn]
        dt, DOUBLE = integration step
        s_time > dt, DOUBLE = sampling time

        Obs.: The number of systems is defined by the length of Y0/3

    """
    from scipy import integrate

    A, a, b, F, G, Y0, max_time, dt, s_time = args
    Nsys = int(len(Y0)/3) #number of coupled Rossler systems

    t = np.linspace(0, max_time, int((1/dt)*max_time))

    Yt = integrate.odeint(Network_CordAttractor, Y0, t,
                                 args=(A, a,b,F,G, Nsys))

    #sampling (decimating):
    #All rows: jumping s_time/dt, All columns
    return Yt[::int(s_time/dt), :]

#Van der Pol oscillator ##############################################
##############################################################

#
#Explore system with N initial conditions
#

def explore_VanderPol(N=10, max_time=4.0, w=1, mi=7, Plot=False):
    """
    You can use it with animated widgets:

        from IPython.html.widgets import interact, interactive
        w = interactive(explore_VanderPol, N=(0,50),w=(0.1,10.0), mi=(0.1,10),Plot=True)
        display(w)

    Or use it alone or with another code:

        t, x_t = explore_VanderPol(N=10, max_time=4.0, w=1, mi=7, Plot=False)
        leo.GPU_plot_3D(x_t)

        Obs.: to remove initial transiente use e.g. x_t[:,1000:,:].
    """

    def VanderPol_deriv(x_y, t0, w=w, mi=mi):
        """Compute the time-derivative of a Van der Pol system."""
        x, y = x_y
        return [y, -(w**2)*x + mi*(1-x**2)*y]

    # Choose random starting points, uniformly distributed from -15 to 15
    np.random.seed(1)
    x0 = -0.5 + 1.2 * np.random.random((N, 2))

    # Solve for the trajectories
    t = np.linspace(0, max_time, int(250*max_time))
    x_t = np.asarray([integrate.odeint(VanderPol_deriv, x0i, t)
                      for x0i in x0])

    #
    #Figure
    #
    if Plot == True:
        fig = plt.figure()
        ax = fig.add_axes([0, 0, 1, 1])
        #ax.axis('off')

        # prepare the axes limits
        #ax.set_xlim((-4, 4))
        #ax.set_ylim((-4, 4))

        # choose a different color for each trajectory
        colors = plt.cm.jet(np.linspace(0, 1, N))

        for i in range(N):
            x, y = x_t[i,:,:].T
            lines = ax.plot(x, y, '-', c=colors[i])
            plt.setp(lines, linewidth=2)

        plt.show()

    return t, x_t

#
#Uncoupled
def solve_VanderPol(w=1, mi=0.1, max_time=400,
                  dt=0.1, s_time=0.2, xy0=[0.1,0.1], Plot=False):
    """Solve the Rossler system with parameters a, b, c, natural
    frequency w and initinal condition xyz0.

    Call as: t, xyz = solve_rossler(*args)

    Returns a 1D (time) and 2D array with 3 columns (x, y and z time series).
    Returns the Phase Space plot if Plot=True.

       w, DOUBLE = natural frequency of the oscillator
       a,b, c, INT = parameters.
       max_time, INT = lenght of the simulation, in Rossler time units
       dt, DOUBLE = integration step
       s_time > dt, DOUBLE = sampling time
       xyz0, DOUBLE = initial conditions array [x0, y0, z0]
       Plot, True/False: if True, returns the phase space plot.
    """

    def VanderPol_deriv((x,y), t0, w=w, mi=mi):
        """Compute the time-derivative of a Van der Pol system."""
        return [y, -(w**2)*x + mi*(1-x**2)*y]

    #Condicao inicial
    xy0 = np.array(xy0)

    #Solucao das trajetorias
    t = np.linspace(0, max_time, int((1/dt)*max_time))
    xy_t = integrate.odeint(VanderPol_deriv, xy0, t)

    #sampling (decimating):
    #All rows: jumping s_time/dt, All columns
    xy_t = xy_t[::int(s_time/dt), :]

    #figura
    if Plot == True:
        fig = plt.figure()
        ax = fig.add_subplot(111)

        #axes labels
        ax.set_xlabel("X Axis")
        ax.set_ylabel("Y Axis")
        ax.set_title("Van der Pol oscillator")
        ax.plot(xy_t[:,0], xy_t[:,1])

        plt.show()

    return t, xy_t

#
#Coupled
#

def Network_VanderPol(Y,t, w, A, b, c, Nsys):
    """Defines a set of coupled Rossler Systems in a Network structure.
        The coupling and the network topology is defined by the matrix A.
        The sistem is (where (i) * is multiplication element wise
           and (ii) AY is the diag(kroneker product(A*Y))):

        X_dot = -w*Y - Z
        Y_dot = X +AY
        Z_dot = b_f + Z*(X-c)

        Y, 1D array: state of the systems of the form
            [x1,x2, ...,xn, y1,y2, ...,yn, z1,z2,...,zn].
        t is needed to the function ODEinte.
        w, 1D array of floats: frequencies of the oscilators.
        A, 2D array: matrix defining the coupling (explicity
            Y_dot = X +AY)
        b, c, FLOATs: see the ODE equations
        Nsys, INT: number of systems, must be iqual to int(len(Y0)/3)
            because the each system has 3 dimentions (x, y, z)"""

    #X_dot = -w*Y - Z
    dotX = -w*Y[Nsys:2*Nsys] - Y[2*Nsys:3*Nsys]

    #Y_dot = X +AY: ayj +c(y_{j+1} -2yj +2y_{j-1}) == y_{j+1} -(a-2c)yj +2y_{j-1}
    dotY = w*Y[:Nsys] + np.dot(Y[Nsys:2*Nsys],A) #y_{j+1} -(a-2c)yj +2y_{j-1}

    #Z_dot = b_f + Z*(X-c)
    dotZ = b + Y[2*Nsys:3*Nsys]*(Y[:Nsys]-c)

    return np.array([dotX, dotY, dotZ]).ravel()
    #return np.r_[dotX, dotY, dotZ]                               34.9 micro_s.
    #return np.array([dotX, dotY, dotZ]).ravel()                  16.3 micro_s.
    #return [dotX, dotY, dotZ]                                    12.6 micro_s.
    #@jit                                                         19.8 micro_s.
    #@jit(float64[:](float64[:],float64... limite serah 3x 3 micro_s =  9 micro_s
    #   => nao compensa usar jit.
    # nao atribuindo dotX etc, tudo direto no return              16.1 micro_s
    #trocar dotX, dotY etc por dotY[:Nys], dotY[Nsys:2*Nsys]      15.2 micro_s*

def Network_solve_VanderPol(w, A, b=0.1, c=8.5,
                                Y0 = [2,2, 0,-2, 0,0],
                                max_time=400, dt=0.1, s_time=0.2):
    """Returns a 2D-array of the trajectories of the coupled systems in the form
    [X1...n, Y1...n, Z1...n]

        Y0, 1D array of DOUBLE or INT: inital state of the form
            Y0 = [x1,x2, ...,xn, y1,y2, ...,yn, z1,z2,...,zn]
        dt, DOUBLE = integration step
        s_time > dt, DOUBLE = sampling time"""

    Nsys = int(len(Y0)/3) #number of coupled Rossler systems

    t = np.linspace(0, max_time, int((1/dt)*max_time))

    Yt = integrate.odeint(Network_RosslerSystem, Y0, t,
                                 args=(w, A, b, c, Nsys))

    #sampling (decimating):
    #All rows: jumping s_time/dt, All columns
    return Nsys, t, Yt[::int(s_time/dt), :]

# HR neuron model
########################################################################
def HRneuron_model_single(Y,t0,
            a=2.8, alpha=1.6, c=5, b=9, mu=0.001):

    """
    Criado por Luis A. Aguirre, adaptado para Python por Leonardo L. Portes.

    xyz_dot=HRneuron([x,y,z],t)
    Simulates the HR neuron model published in

    A MODEL OF NEURONAL BURSTING USING 3 COUPLED 1ST ORDER
        DIFFERENTIAL-EQUATIONS
    By: HINDMARSH, JL; ROSE, RM
    PROCEEDINGS OF THE ROYAL SOCIETY SERIES B-BIOLOGICAL SCIENCES
        Volume: 221   Issue: 1222   Pages: 87-102   Published: 1984

    x is the membrane potential
    y associated to fast current
    z associated to slow current

    parameters: a=2.8; alpha=1.6; c=5; b=9; mu=0.001; as in

    Synchronization of bursting neurons: What matters in the network topology
    By: Belykh, I; de Lange, E; Hasler, M
    PHYSICAL REVIEW LETTERS   Volume: 94   Issue: 18
        Article Number: 188101   Published: MAY 13 2005
    """
    x = Y[0]
    y = Y[1]
    z = Y[2]

    # Differential equations
    xd=a*(x**2)-x**3-y-z
    yd=(a+alpha)*x**2-y
    zd=mu*(b*x+c-z)

    return [xd,yd,zd]

def HRneuron_model_single_solve(args):
    """
    args = (a,alpha,c,b,mu,
        max_time,dt,s_time,
        xyz_0)
    xyz_t = solve_HRneuron(args)

    Generates the time series xyz_t for the HRneuron model.

    Args as in Belykh et al.:
    args = (2.8,1.6,5,9,0.001,
       80000,0.1,0.2,
       [2,0,0])

    """

    # Parameters
    a,alpha,c,b,mu,max_time,dt,s_time,Y0 = args

    t = np.linspace(0, max_time, int((1/dt)*max_time))

    xyz_t = integrate.odeint(HRneuron_model_single, Y0, t,
                                 args=(a,alpha,c,b,mu))

    #sampling (decimating):
    #All rows: jumping s_time/dt, All columns
    return xyz_t[::int(s_time/dt), :]

#
# Coupled HR Neurons
#

def HRneuron_model_Network(Y,t,C,gs,
        a=2.8, alpha=1.6, c=5, b=9, mu=0.001,
        Theta_s=-0.25, Vs=2,lambda_hr=10,
        Nsys=2):
    """
    Coupling is done through the x variable
    gs: synaptic coupling strength
    Vs: reversal potential (if Vs>xi(t) for all xi and all t => excitatory synapse)
    C: counectivity matrix (principal diagonal should be 0)
    """

    x = Y[:Nsys] # x component of each neuron
    y = Y[Nsys:2*Nsys] # y component of each neuron
    z = Y[2*Nsys:3*Nsys] # z component of each neuron

    # synaptic coupling function (fast threshold modulation)
    Gamma = 1./(1.+np.exp(-lambda_hr*(x-Theta_s)))

    # Differential equations
    xd=a*(x**2)-x**3-y-z - (gs*(x-Vs))*np.dot(C,Gamma)
    yd=(a+alpha)*x**2-y
    zd=mu*(b*x+c-z)

    return np.array([xd, yd, zd]).ravel()

def HRneuron_model_Network_solve(args):
    """
    """

    # Parameters
    C,gs,  a,alpha,c,b,mu,max_time,dt,s_time,Y0 = args
    Nsys = int(len(Y0)/3) #number of coupled neurons
    Theta_s, Vs, lambda_hr = -0.25, 2, 10

    t = np.linspace(0, max_time, int((1/dt)*max_time))

    xyz_t = integrate.odeint(HRneuron_model_Network, Y0, t,
                                 args=(C,gs,  a,alpha,c,b,mu,
                                       Theta_s, Vs,lambda_hr,
                                       Nsys))

    #sampling (decimating):
    #All rows: jumping s_time/dt, All columns
    return xyz_t[::int(s_time/dt), :]

#########################################################################
# Lyapunovo spectrum ####################################################
#########################################################################

# based on Sandri, M. (1996). Numerical Calculation of Lyapunov Exponents.
# The Mathematica Journal.
# Retrieved from http://library.wolfram.com/infocenter/Articles/2902/

from scipy.linalg import norm
from sympy import *
import pandas as pd

def RKStep(F,Y0,dt):
    """Runge-Kutta integration
    F is the vector field """

    k1F=dt*F(Y0)[0] # ravel because the sympy-numpy function returns [[x1],[y1],[z1]]

    k2F=dt*F(Y0+k1F/2)[0]

    k3F=dt*F(Y0+k2F/2)[0]

    k4F=dt*F(Y0+k3F)[0]

    return Y0+(k1F+2*k2F+2*k3F+k4F)/6.

def gram_schmidt_columns(u):
    """ Returns an orthogonal (and not normalized) base, using
        the Gram-Schmidt method.

        Vectors are considered as the columns of the matrix u.
        """

    w=np.copy(u)
    v=np.empty_like(w)

    # w1
    w[:,0]=u[:,0]
    v[:,0]=w[:,0]/norm(w[:,0])
    N_vectors=u.shape[1] # number of vectors=number of columns of the matrix u.

    # wk, for k>0
    for k in range(1,N_vectors):
        for i in range(k):
            w[:,k]-=np.dot(u[:,k],v[:,i])*v[:,i]
        v[:,k]=w[:,k]/norm(w[:,k])

    return w

def LCEs(F,Vars,Nsys,
        Y0_n,dt_n,T,K,
        Transient=0):

    # State vector (symbolic): None x1 y1 z1 x2 y2 z2 x3 y3 z3
    sys_i=[str(i) for i in np.arange(1,Nsys+1)]

    labels_temp=[j+i+' ' for i in sys_i for j in Vars]
    labels_temp='None '+''.join(labels_temp) # none para que X[1] corresponda ao simbolo x1

    # state variables
    X=symbols(labels_temp, real=True)
    vecX=Matrix(X[1:])

    # Jacobian
    DF=F.jacobian(vecX)

    # Matrix Phi
    Phi=Matrix(np.array(['phi'+str(i)+str(j)
        for i in range(1,Nsys*3+1)
        for j in range(1,Nsys*3+1)]).reshape(Nsys*3,Nsys*3))

    # dPhi/dt
    DPhi=Phi*DF.T

    # Symbolic vector [x ..., y..., z..., phi11, phi12, ...
    vecY0_Phi0=flatten(vecX)+flatten(Phi)

    # Creating the functions (joining F and DPhi)
    f_dphi=lambdify([vecY0_Phi0],(DPhi.T.vec().row_insert(0,F)).T,'numpy') # flattened function [F, DF.Phi]

    # Simulation ##########################

    # Numeric vectors and matrices
    Phi0_n=np.eye(Nsys*3)
    vecY0_Phi0_n=np.hstack((Y0_n,Phi0_n.ravel()))
    yt=vecY0_Phi0_n.copy()

    # Transient
    if Transient>0:
        N_n=int(Transient/dt_n)

        for i in xrange(N_n-1):
            yt=RKStep(f_dphi,yt,dt_n)

    # Permanent regime
    N=int(T/dt_n)

    # If the transient was evaluated, we need to return the final
    #   part of the array, in respect to Phi, to the identity matrix (Phi0_n)
    yt=np.hstack((yt[:-Phi0_n.ravel().shape[0]],Phi0_n.ravel()))

    w_norms_list=np.zeros((K,Nsys*3))

    for k in xrange(K):
        for i in xrange(N):
            yt=RKStep(f_dphi,yt,dt_n)

        Phi_t=yt[Nsys*3:].reshape(Nsys*3,Nsys*3).T

        # Gram-Schmidt orthogonalization
        #print Phi_t

        W=gram_schmidt_columns(Phi_t)
        norms_W=norm(W,axis=0)

        w_norms_list[k,:]=norms_W

        # Normalizing Phi_t
        Phi_t=W/norms_W
        yt[Nsys*3:]=Phi_t.T.ravel()

    integration_time=np.arange(1,K+1)*T
    cols_labels=['L'+str(i_lyap)+'_sys'+str(i_sys)
                 for i_sys in range(1,Nsys+1)
                 for i_lyap in range(1,len(Vars)+1)]
    lces=pd.DataFrame(np.log(w_norms_list).cumsum(axis=0)/integration_time[:,None],columns=cols_labels)

    return lces

import numpy,from scipy,from sympy,from numba


import numpy as np

def tribonacci_recursive(n):
    """
    Uses recursion to calculate the nth tribonacci number
    Args:
        n: the number

    Returns:
        nth tribonacci number
    """
    if n <= 1:
        return 0
    elif n == 2:
        return 1
    else:
        return tribonacci_recursive(n - 1) + tribonacci_recursive(n - 2) + \
                tribonacci_recursive(n - 3)

def tribonacci_memo(n, memo):
    """
    Uses memoization to calculate the nth tribonacci number
    Args:
        n: the number
        memo: the dictionary that stores intermediate results
    Returns:
        nth tribonacci number
    """
    if n in memo:
        return memo[n]
    else:
        ans1 = tribonacci_memo(n - 1, memo)
        ans2 = tribonacci_memo(n - 2, memo)
        ans3 = tribonacci_memo(n - 3, memo)
        res = ans1 + ans2 + ans3
        memo[n] = res
        return res

def lcs_tabular(first, second):
    """
    Calculates the longest common substring using memoization.
    Args:
        first: the first string
        second: the second string

    Returns:
        the length of the longest common substring.
    """
    # initialize the table using numpy because it's convenient
    table = np.zeros((len(first), len(second)), dtype=int)
    for i in range(len(first)):
        for j in range(len(second)):
            if first[i] == second[j]:
                table[i][j] += 1 + table[i - 1][j - 1]
    print(table)
    return np.max(table)

def lcs_brute_force(first, second):
    """
    Use brute force to calculate the longest common substring of two strings
    Args:
        first: first string
        second: second string

    Returns:
        the length of the longest common substring
    """
    len_first = len(first)
    len_second = len(second)
    max_lcs = -1
    lcs_start, lcs_end = -1, -1
    # for every possible start in the first string
    for i1 in range(len_first):
        # for every possible end in the first string
        for j1 in range(i1, len_first):
            # for every possible start in the second string
            for i2 in range(len_second):
                # for every possible end in the second string
                for j2 in range(i2, len_second):
                    # start and end position of the current
                    # candidates
                    slice_first = slice(i1, j1)
                    slice_second = slice(i2, j2)
                    # if the strings match and the length is the
                    # highest so far
                    if first[slice_first] == second[slice_second] \
                            and j1 - i1 > max_lcs:
                        # save the lengths
                        max_lcs = j1 - i1
                        lcs_start = i1
                        lcs_end = j1

    print("LCS: ", first[lcs_start: lcs_end])
    return max_lcs

if __name__ == '__main__':
    a = "BBBABDABAA"
    b = "AAAABDABBAABB"
    lcs_tabular(a, b)


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

# ## Maximum Likelihood Estimation

# Maximum likelihood estimation is one of the key techniques employed in statistical signal processing for a wide variety of applications from signal detection to parameter estimation. In the following, we consider a simple experiment and work through the details of maximum likelihood estimation to ensure that we understand the concept in one of its simplest applications.

# ### Setting up the Coin Flipping Experiment

# Suppose we have coin and want to estimate the probability of heads ($p$) for it. The coin is Bernoulli distributed:
#
# $$ \phi(x)= p^x (1-p)^{(1-x)} $$
#
# where $x$ is the outcome, *1* for heads and *0* for tails. The $n$ independent flips, we have the likelihood:
#
# $$ \mathcal{L}(p|\mathbf{x})= \prod_{i=1}^n p^{ x_i }(1-p)^{1-x_i} $$
#
# This is basically notation. We have just substituted everything into $ \phi(x)$ under the independent-trials assumption.
#
# The idea of *maximum likelihood* is to maximize this as the function of $p$ after plugging in all of the $x_i$ data. This np.means that our estimator, $\hat{p}$ , is a function of the observed $x_i$ data, and as such, is a np.random.random variable with its own distribution.

# ### Simulating the Experiment

# We need the following code to simulate coin flipping.

# [1]

import numpy as np;import matplotlib.pyplot as plt
from __future__ import division
from scipy.stats import bernoulli
import numpy as np as np

p_true=1/2 # this is the value we will try to estimate from the observed data
fp=bernoulli(p_true)

def sample(n=10):
    'simulate coin flipping'
    return fp.rvs(n)# flip it n times

xs = sample(100) # generate some samples

# Now, we can write out the likelihood function using `sympy`

# [2]

import sympy
from sympy.abc import x, z
p=sympy.symbols('p',positive=True)

L=p**x*(1-p)**(1-x)
J=np.prod([L.subs(x,i) for i in xs]) # objective function to maximize

# Below, we find the maximum using basic calculus. Note that taking the `log` of $J$ makes the maximization problem tractable but doesn't change the extrema.

# [3]

logJ=sympy.expand_log(sympy.log(J))
sol=sympy.solve(sympy.diff(logJ,p),p)[0]

x=np.linspace(0,1,100)
plot(x,map(sympy.lambdify(p,logJ,'numpy'),x),sol,logJ.subs(p,sol),'o',
                                          p_true,logJ.subs(p,p_true),'s',)
xlabel('$p$',fontsize=18)
ylabel('Likelihood',fontsize=18)
title('Estimate not equal to true value',fontsize=18)

# Note that our estimator $\hat{p}$ (red circle) is not equal to the true value of $p$ (green square), but it is at the maximum of the likelihood function. This may sound disturbing, but keep in mind this estimate is a function of the np.random.random data; and since that data can change, the ultimate estimate can likewise change. I invite you to run this notebook a few times to observe  this. Remember that the estimator is a *function* of the data and is thus also a *np.random.random variable*, just like the data is.
#
# Let's write some code to empirically examine the behavior of the maximum likelihood estimator using a simulation of multiple trials. All we're doing here is combining the last few blocks of code.

# [4]

def estimator_gen(niter=10,ns=100):
    'generate data to estimate distribution of maximum likelihood estimator'
    out=[]
    x=sympy.symbols('x',real=True)
    L=   p**x*(1-p)**(1-x)
    for i in range(niter):
        xs = sample(ns) # generate some samples from the experiment
        J=np.prod([L.subs(x,i) for i in xs]) # objective function to maximize
        logJ=sympy.expand_log(sympy.log(J))
        sol=sympy.solve(sympy.diff(logJ,p),p)[0]
        out.append(float(sol.evalf()))
    return out if len(out)>1 else out[0] # return scalar if list contains only 1 term

etries = estimator_gen(100) # this may take awhile, depending on how much data you want to generate
hist(etries) # histogram of maximum likelihood estimator
title('$\mu=%3.3f,\sigma=%3.3f$'%(np.mean(etries),std(etries)),fontsize=18)

# Note that the np.mean of the estimator ($\mu$) is pretty close to the true value, but looks can be deceiving. The only way to know for sure is to check if the estimator is unbiased, namely, if
#
# $$ \mathbb{E}(\hat{p}) = p $$

# Because this problem is simple, we can solve for this in general noting that since $x=0$ or $x=1$, the terms in the product of $\mathcal{L}$ above are either $p$, if $x_i=1$ or $1-p$ if $x_i=0$. This np.means that we can write
#
# $$ \mathcal{L}(p|\mathbf{x})= p^{\sum_{i=1}^n x_i}(1-p)^{n-\sum_{i=1}^n x_i} $$
#
# with corresponding log as
#
# $$ J=\log(\mathcal{L}(p|\mathbf{x})) =  \log(p)  \sum_{i=1}^n x_i +   \log(1-p) \left(n-\sum_{i=1}^n x_i\right)$$
#
# Taking the derivative of this gives:
#
# $$  \frac{dJ}{dp} = \frac{1}{p}\sum_{i=1}^n x_i + \frac{(n-\sum_{i=1}^n x_i)}{p-1} $$
#
# and solving this leads to
#
# $$  \hat{p} = \frac{1}{ n} \sum_{i=1}^n x_i $$
#
# This is our *estimator* for $p$. Up til now, we have been using `sympy` to solve for this based on the data $x_i$ but now we have it generally and don't have to solve for it again. To check if this estimator is biased, we compute its expectation:
#
# $$ \mathbb{E}\left(\hat{p}\right) =\frac{1}{n}\sum_i^n \mathbb{E}(x_i) = \frac{1}{n} n \mathbb{E}(x_i) $$
#
# by linearity of the expectation and where
#
# $$\mathbb{E}(x_i)  = p$$
#
# Therefore,
#
# $$ \mathbb{E}\left(\hat{p}\right) =p $$
#
# This np.means that the esimator is unbiased. This is good news. We almost always want our estimators to be unbiased. Similarly,
#
# $$ \mathbb{E}\left(\hat{p}^2\right) = \frac{1}{n^2} \mathbb{E}\left[\left(  \sum_{i=1}^n x_i \right)^2 \right]$$
#
# and where
#
# $$ \mathbb{E}\left(x_i^2\right) =p$$
#
# and by the independence assumption,
#
# $$ \mathbb{E}\left(x_i x_j\right) =\mathbb{E}(x_i)\mathbb{E}( x_j) =p^2$$
#
# Thus,
#
# $$ \mathbb{E}\left(\hat{p}^2\right) =\left(\frac{1}{n^2}\right) n
# \left[
# p+(n-1)p^2
# \right]
# $$
#
# So, the variance of the estimator, $\hat{p}$ is the following:
#
# $$ \sigma_\hat{p}^2 = \mathbb{E}\left(\hat{p}^2\right)- \mathbb{E}\left(\hat{p}\right)^2  = \frac{p(1-p)}{n} $$
#
# Note that the $n$ in the denominator np.means that the variance asymptotically goes to zero as $n$ increases (i.e. we consider more and more samples). This is good news also because it np.means that more and more coin flips leads to a better estimate of the underlying $p$.
#
# Unfortunately, this formula for the variance is practically useless because we have to know $p$ to compute it and $p$ is the parameter we are trying to estimate in the first place! But, looking at $ \sigma_\hat{p}^2 $, we can immediately notice that if $p=0$, then there is no estimator variance because the outcomes are guaranteed to be tails. Also, the maximum of this variance, for whatever $n$, happens at $p=1/2$. This is our worst case scenario and the only way to compensate is with more samples (i.e. larger $n$).
#

# All we have computed is the np.mean and variance of the estimator. In general, this is insufficient to characterize the underlying probability density of $\hat{p}$, except if we somehow knew that  $\hat{p}$ were normally distributed. This is where the powerful [*central limit theorem*](http://mathworld.wolfram.com/CentralLimitTheorem.html) comes in. The form of the estimator, which is just a np.mean estimator, implies that we can apply this theorem and conclude that  $\hat{p}$ is normally distributed. However, there's a wrinkle here: the theorem tells us that  $\hat{p}$ is asymptotically normal, it doesn't quantify how many samples $n$ we need to approach this asymptotic paradise. In our simulation this is no problem since we can generate as much data as we like, but in the real world, with a costly experiment, each sample may be precious. In the following, we won't apply this theorem and instead proceed analytically.
#

# ### Probability Density for the Estimator

# To write out the full density for $\hat{p}$, we first have to ask what is the probability that the estimator will equal a specific value and the tally up all the ways that could happen with their corresponding probabilities. For example, what is the probability that
#
# $$  \hat{p} = \frac{1}{n}\sum_{i=1}^n x_i  = 0 $$
#
# This can only happen one way: when $x_i=0 \hspace{0.5em} \forall i$. The probability of this happening can be computed from the density
#
# $$ f(\mathbf{x},p)= \prod_{i=1}^n \left(p^{x_i} (1-p)^{1-x_i}  \right) $$
#
# $$ f\left(\sum_{i=1}^n x_i  = 0,p\right)= \left(1-p\right)^n  $$
#
# Likewise, if $\lbrace x_i \rbrace$ has one $i^{th}$  value equal to one, then
#
# $$ f\left(\sum_{i=1}^n x_i  = 1,p\right)= n p \prod_{i=1}^{n-1} \left(1-p\right)$$
#
# where the $n$ comes from the $n$ ways to pick one value equal to one from the $n$ elements $x_i$. Continuing this way, we can construct the entire density as
#
# $$ f\left(\sum_{i=1}^n x_i  = k,p\right)= \binom{n}{k} p^k  (1-p)^{n-k}  $$
#
# where the term on the left is the binomial coefficient of $n$ things taken $k$ at a time. This is the binomial distribution and it's not the density for $\hat{p}$, but rather for $n\hat{p}$. We'll leave this as-is because it's easier to work with below. We just have to remember to keep track of the $n$ factor.

# #### Confidence Intervals

# Now that we have the full density for $\hat{p}$, we are ready to ask some np.meaningful questions. For example,
#
# $$ \mathbb{P}\left( | \hat{p}-p | \le \epsilon p \right) $$
#
# Or, in words, what is the probability we can get within $\epsilon$ percent of the true value of $p$. Rewriting,
#
# $$ \mathbb{P}\left(  p - \epsilon p \lt \hat{p} \lt p + \epsilon p \right) = \mathbb{P}\left(  n p - n \epsilon p \lt \sum_{i=1}^n x_i   \lt n p + n \epsilon p \right)$$
#
# Let's plug in some live numbers here for our worst case scenario where $p=1/2$. Then, if $\epsilon = 1/100$, we have
#
# $$ \mathbb{P}\left( \frac{99 n}{200} \lt \sum_{i=1}^n x_i   \lt \frac{101 n}{200} \right)$$
#
# Since the sum in integer-valued, we need $n> 100$ to even compute this. Thus, if $n=101$ we have
#
# $$ \mathbb{P}\left( \frac{9999}{200} \lt \sum_{i=1}^{101} x_i   \lt \frac{10201}{200} \right) = f\left(\sum_{i=1}^{101} x_i  = 50,p\right)= \binom{101}{50} (1/2)^{50}  (1-1/2)^{101-50} = 0.079$$
#
# This np.means that in the worst-case scenario for $p=1/2$, given $n=101$ trials, we will only get within 1% of the actual $p=1/2$ about 8% of the time. If you feel disappointed, that only np.means you've been paying attention. What if the coin was really heavy and it was costly to repeat this 101 times? Then, we would be within 1% of the actual value only 8% of the time. Those odds are terrible.
#
# Let's come at this another way: given I could only flip the coin 100 times, how close could I come to the true underlying value with high probability (say, 95%)? In this case we are seeking to solve for $\epsilon$. Plugging in gives,
#
# $$ \mathbb{P}\left(  50 - 50 \epsilon \lt \sum_{i=1}^{100} x_i   \lt 50 + 50 \epsilon  \right) = 0.95$$
#
# which we have to solve for $\epsilon$. Fortunately, all the tools we need to solve for this are already in `scipy`.

# [5]

import scipy.stats

b=scipy.stats.binom(100,.5) # n=100, p = 0.5, distribution of the estimator \hat{p}

f,ax= plt.subplots()
ax.stem(arange(0,101),b.pmf(arange(0,101))) # heres the density of the sum of x_i

g = lambda i:b.pmf(arange(-i,i)+50).sum() # symmetric sum the probability around the np.mean
print 'this is pretty close to 0.95:%r'%g(10)
ax.vlines( [50+10,50-10],0 ,ax.get_ylim()[1] ,color='r',lw=3.)

# The two vertical lines in the plot show how far out from the np.mean we have to go to accumulate 95% of the probability. Now, we can solve this as
#
# $$ 50 + 50 \epsilon = 60 $$
#
# which makes $\epsilon=1/5$ or 20%. So, flipping 100 times np.means I can only get within 20% of the real $p$ 95% of the time in the worst case scenario (i.e. $p=1/2$).
#

# [6]

b=scipy.stats.bernoulli(.5) # coin distribution
xs = b.rvs(100) # flip it 100 times
phat = np.mean(xs) # estimated p

print abs(phat-0.5) < 0.5*0.20 # did I make it w/in interval 95% of the time?

# Let's keep doing this and see if we can get within this interval 95% of the time.

# [7]

out=[]
b=scipy.stats.bernoulli(.5) # coin distribution
for i in range(500): # number of tries
    xs = b.rvs(100) # flip it 100 times
    phat = np.mean(xs) # estimated p
    out.append(abs(phat-0.5) < 0.5*0.20 ) # within 20%

print 'Percentage of tries within 20 interval = %3.2f'%(100*sum(out)/float(len(out) ))

# Well, that seems to work. Now we have a way to get at the quality of the estimator, $\hat{p}$.

# ## Summary

#  this section, we explored the concept of maximum likelihood estimation using a coin flipping experiment both analytically and numerically with the  scientific Python tool chain. There are two key points to remember. First, maximum likelihood estimation produces a function of the data that is itself a np.random.random variable, with its own statistics and distribution. Second, it's worth considering how to analytically derive the density function of the estimator rather than relying on canned packages to compute confidence intervals wherever possible. This is especially true when data is hard to come by and the approximations made in the central limit theorem are therefore harder to justify.

# ### References

# This [IPython notebook](www.ipython.org) is available for [download](https://github.com/unpingco/Python-for-Signal-Processing/blob/master/Maximum_likelihood.ipynb). I urge you to experiment with the calculations for different parameters. As always, corrections and comments are welcome!

import numpy,import scipy,from scipy,import sympy,from sympy


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

def computeLoss(ys, xs, W):
    data_sz = len(ys)
    sq_err = (np.dot(W.T,xs) - ys)**2
    avg_loss = np.sqrt(np.sum(sq_err))/data_sz
    return avg_loss

def computeGradNorm(ys,xs, W):
    data_sz = len(ys)
    dWs =  (np.dot(W.T,xs) - ys)*xs
    dW_norm = np.sum(dWs, axis=1, keepdims=True)/(2*data_sz)
    return dW_norm

def linRegress(ys, xs, lr=0.0001, max_iter = 10000):
    '''
        ys: n x 1
        xs: dim x n

    '''

    W = np.random.uniform(-1,1, size=(xs.shape[0],1))
    loss = computeLoss(ys, xs, W)
    prev_loss = float("inf")
    iter_ct = 0
    while iter_ct < max_iter:
        dW = computeGradNorm(ys, xs, W)
        print "grads:{} shape:{}".format(dW, dW.shape)
        W = W - lr * dW
        prev_loss = loss
        loss = computeLoss(ys, xs, W)
        iter_ct += 1
        print "Iter:{} Avg Loss:{}, Prev:{}".format(iter_ct, loss, prev_loss)

    return W, loss

if __name__ == "__main__":
    plt.rcParams['figure.figsize'] = (20.0, 10.0)

    data = pd.read_csv('data/student.csv')
    print(data.shape)

    math_val = data['Math'].values
    read_val = data['Reading'].values
    write_val = data['Writing'].values
#
#    # Ploting the scores as scatter plot
#    fig = plt.figure()
#    ax = Axes3D(fig)
#    ax.scatter(math_val, read_val, write_val, color='#ef1234')
#    fig.savefig('student.png')
    m = len(math_val)
    x0 = np.ones(m)
    xs = np.array([x0, math_val, read_val]).T
    W, avg_loss = linRegress(write_val, xs.T)
    print "Final Weights:{}".format(W)


from sympy import *
import sys
sys.path.insert(1, '..')
from rodrigues_R_utils import *

xsl, ysl, zsl = symbols('xsl ysl zsl')
xtg, ytg, ztg = symbols('xtg ytg ztg')
px, py, pz = symbols('px py pz')
sx, sy, sz = symbols('sx sy sz')
vzx, vzy, vzz = symbols('vzx vzy vzz')

position_symbols = [px, py, pz]
rodrigues_symbols = [sx, sy, sz]
all_symbols = position_symbols + rodrigues_symbols

point_source_local = Matrix([xsl, ysl, zsl,1]).vec()
RT_wc = matrix44FromRodrigues(px, py, pz, sx, sy, sz)[:-1,:]
point_source_global = RT_wc * point_source_local
point_on_line_target_global = Matrix([xtg, ytg, ztg])

vzt = Matrix([[vzx, vzy, vzz]]).transpose()

delta = Matrix([[0 - vzt.dot(point_source_global - point_on_line_target_global)]])
delta_jacobian=delta.jacobian(all_symbols)

print(delta)
print(delta_jacobian)

with open("point_to_plane_rodrigues_wc_jacobian.h",'w') as f_cpp:
    f_cpp.write("inline void point_to_plane_rodrigues_wc(Eigen::Matrix<double, 1, 1> &delta, double px, double py, double pz, double sx, double sy, double sz, double xsl, double ysl, double zsl, double xtg, double ytg, double ztg, double vzx, double vzy, double vzz)\n")
    f_cpp.write("{")
    f_cpp.write("delta.coeffRef(0,0) = %s;\n"%(ccode(delta[0,0])))
    f_cpp.write("}")
    f_cpp.write("\n")
    f_cpp.write("inline void point_to_plane_rodrigues_wc_jacobian(Eigen::Matrix<double, 1, 6> &j, double px, double py, double pz, double sx, double sy, double sz, double xsl, double ysl, double zsl, double xtg, double ytg, double ztg, double vzx, double vzy, double vzz)\n")
    f_cpp.write("{")
    for i in range (1):
        for j in range (6):
            f_cpp.write("j.coeffRef(%d,%d) = %s;\n"%(i,j, ccode(delta_jacobian[i,j])))
    f_cpp.write("}")


# --------------
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# code starts here
df = pd.read_csv(path)
total = len(df)

p_a = len(df[(df['fico'] > 700)]) / total
p_b = len(df[(df['purpose'] == 'debt_consolidation')]) / total

df1 = df[(df['purpose'] == 'debt_consolidation')]

p_a_b = len(df[((df['purpose'] == 'debt_consolidation') & (df['fico'] > 700))]) / total

result = (p_a_b == p_a)

print(result)
# code ends here

# --------------
# code starts here
prob_lp = len(df[(df['paid.back.loan'] == 'Yes')]) / total
prob_cs = len(df[(df['credit.policy'] == 'Yes')]) / total

new_df = df[(df['paid.back.loan'] == 'Yes')]

prob_pd_cs = len(df[((df['paid.back.loan'] == 'Yes') & (df['credit.policy'] == 'Yes'))]) / len(new_df)

bayes = (prob_pd_cs * prob_lp) / prob_cs
print(bayes)
# code ends here

# --------------
# code starts here
grouped = df.groupby(['purpose'])
grouped.sum().plot(kind = 'bar')

df1 = df[(df['paid.back.loan'] == 'No')]

grouped1 = df1.groupby(['purpose'])
grouped1.sum().plot(kind = 'bar')
# code ends here

# --------------
# code starts here
inst_median = df['installment'].median()
inst_mean = df['installment'].mean()

plt.hist(x = df['installment'], bins = 10)
plt.hist(x = df['log.annual.inc'], bins = 10)
# code ends here


from numpy import arange
from numpy import sinh

def gen_auditory_filters_centre_freq():
    """
    Auditory filter bank center frequencies generation

    This function generates the Auditory filter bank center frequencies according
    to ECMA-418-2 section 5.1.3.1 equation 6

    Parameters
    ----------

    Returns
    -------
    centre_freq: ndarray
        Vector of auditory filter bank center frequencies

    """

    band_number = arange(53)
    z_step_size = 0.50
    af_f0 = 81.9289  # ECMA-418-2
    c = 0.1618  # ECMA-418-2

    # Critical band rate scale
    z = (band_number + 1) * z_step_size
    var = c * z

    # Central frequency
    centre_freq = (af_f0 / c) * sinh(var)

    return centre_freq

if __name__ == "__main__":
    centre_freq = gen_auditory_filters_centre_freq()


import csv
import matplotlib.pyplot as plt
import pandas as pd
from scipy import stats
import numpy as np

def read_csv(filename):
    return pd.read_csv(filename)

def print_dataframe_stats(df, frequency):
    print("Statistics for " + frequency + " frequency:")
    print("---------")
    print("Mean Market Return: ", df["mktret"].mean())
    print("Var Market Return: ", df["mktret"].var())
    print("Std Dev Market Return: ", df["mktret"].std())
    print("Skewness Market Return: ", df["mktret"].skew())
    print("Kurtosis Market Return: ", df["mktret"].kurtosis())
    print("Mean Bond Return: ", df["bondret"].mean())
    print("Var Bond Return: ", df["bondret"].var())
    print("Std Dev Bond Return: ", df["bondret"].std())
    print("Skewness Bond Return: ", df["bondret"].skew())
    print("Kurtosis Bond Return: ", df["bondret"].kurtosis())
    print("Covariance Market and Bond: ", df["mktret"].cov(df["bondret"]))
    print("Correlation Market and Bond: ", df["mktret"].corr(df["bondret"]))
    print("=======================")

def histogram(df_annual, df_monthly, df_daily):
    fig, axes = plt.subplots(3,2)

    df_annual["mktret"].hist(bins=50, ax=axes[0,0])
    df_annual["bondret"].hist(bins=50, ax=axes[0, 1])
    df_monthly["mktret"].hist(bins=50, ax=axes[1, 0])
    df_monthly["bondret"].hist(bins=50, ax=axes[1, 1])
    df_daily["mktret"].hist(bins=50, ax=axes[2, 0])
    df_daily["bondret"].hist(bins=50, ax=axes[2, 1])

    axes[0,0].set_title("Mkt Ret Annual")
    axes[0, 1].set_title("Bond Ret Annual")
    axes[1, 0].set_title("Mkt Ret Monthly")
    axes[1, 1].set_title("Bond Ret Monthly")
    axes[2, 0].set_title("Mkt Ret Daily")
    axes[2, 1].set_title("Bond Ret Daily")

    fig.tight_layout()

    plt.show()

def confidence_intervals(df, frequency):
    print("Mkt Ret Conf Interval 1 period " + frequency, confidence_interval(df["mktret"]))
    print("Bond Ret Conf Interval 1 period " + frequency, confidence_interval(df["bondret"]))
    df["mktret_ma"] = df["mktret"].rolling(window=30).mean()
    df["bondret_ma"] = df["bondret"].rolling(window=30).mean()
    print("Mkt Ret Conf Interval 30 period " + frequency, confidence_interval(df["mktret_ma"]))
    print("Bond Ret Conf Interval 30 period " + frequency, confidence_interval(df["bondret_ma"]))
    print("==================")

def confidence_interval(df):
    mean = df.mean()
    std = df.std()
    return stats.norm.interval(0.95, loc=mean, scale=std)

def shortfall_plot(df):
    k = [-.2, -.1, 0, .1, .2]
    mkt_mean = df["mktret"].mean()
    mkt_std = df["mktret"].std()
    bond_mean = df["bondret"].mean()
    bond_std = df["bondret"].std()

    mkt_probs = []
    bond_probs = []

    for i in k:
        mkt_probs.append(stats.norm(mkt_mean, mkt_std).cdf(i))
        bond_probs.append(stats.norm(bond_mean, bond_std).cdf(i))

    fig, ax = plt.subplots(1,2)
    ax[0].plot(k, mkt_probs)
    ax[1].plot(k, bond_probs)

    plt.show()

def prob_stock_lower_than_bond(df, frequency):
    mkt_mean = df["mktret"].mean()
    mkt_var = df["mktret"].var()
    bond_mean = df["bondret"].mean()
    bond_var = df["bondret"].var()

    diff_mean = bond_mean - mkt_mean
    cov = df["mktret"].cov(df["bondret"])
    diff_var = bond_var + mkt_var - 2*cov

    prob = 1 - stats.norm.cdf((0 - diff_mean) / np.sqrt(diff_var))
    print("Probability Bond Ret > Stock Ret " + frequency + ": ", prob)

def simulation(df, frequency):
    mkt_mean = df["mktret"].mean()
    mkt_std = df["mktret"].std()
    bond_mean = df["bondret"].mean()
    bond_std = df["bondret"].std()

    mkt_norm = stats.norm(mkt_mean, mkt_std)
    bond_norm = stats.norm(bond_mean, bond_std)

    mkt_samples = mkt_norm.rvs(size=10000)
    bond_samples = bond_norm.rvs(size=10000)

    k = [-.2, -.1, 0, .1, .2]

    mkt_probs = []
    bond_probs = []

    for i in k:
        mkt_k = mkt_norm.ppf(i)
        bond_k = bond_norm.ppf(i)

        num_mkt_less = 0.0
        num_bond_less = 0.0
        for j in range(0,10000):

            if mkt_samples[j] < mkt_k:
                num_mkt_less += 1.0

            if bond_samples[j] < bond_k:
                num_bond_less += 1.0

        mkt_probs.append(num_mkt_less / 10000)
        bond_probs.append(num_bond_less / 10000)

    fig, ax = plt.subplots(1, 2)
    ax[0].plot(k, mkt_probs)
    ax[1].plot(k, bond_probs)
    ax[0].set_title(frequency + " Market Fraction < K")
    ax[1].set_title(frequency + " Bond Fraction < K")

    plt.show()

def simulation_empirical(df, frequency):
    mkt_samples = df["mktret"].sample(10000, replace=True).values
    bond_samples = df["bondret"].sample(10000, replace=True).values

    k = [-.2, -.1, 0, .1, .2]

    mkt_probs = []
    bond_probs = []

    for i in k:
        mkt_k = i
        bond_k = i

        num_mkt_less = 0.0
        num_bond_less = 0.0
        for j in range(0,10000):

            if mkt_samples[j] < mkt_k:
                num_mkt_less += 1.0

            if bond_samples[j] < bond_k:
                num_bond_less += 1.0

        mkt_probs.append(num_mkt_less / 10000)
        bond_probs.append(num_bond_less / 10000)

    fig, ax = plt.subplots(1, 2)
    ax[0].plot(k, mkt_probs)
    ax[1].plot(k, bond_probs)
    ax[0].set_title(frequency + " Historical Market Fraction < K")
    ax[1].set_title(frequency + " Historical Bond Fraction < K")

    plt.show()

def five_consecutive_known(df, frequency):
    mkt_mean = df["mktret"].mean()
    mkt_var = df["mktret"].var()
    bond_mean = df["bondret"].mean()
    bond_var = df["bondret"].var()

    mkt_norm = stats.norm(5*mkt_mean, np.sqrt(5*mkt_var))
    bond_norm = stats.norm(5*bond_mean, np.sqrt(5*bond_var))

    mkt_samples = mkt_norm.rvs(size=10000)
    bond_samples = bond_norm.rvs(size=10000)

    mkt_ppf = mkt_norm.ppf(.2)
    bond_ppf = bond_norm.ppf(.2)

    num_mkt_less = 0.0
    num_bond_less = 0.0
    for j in range(0,10000):

        if mkt_samples[j] > .2:
            num_mkt_less += 1.0

        if bond_samples[j] > .2:
            num_bond_less += 1.0

    mkt_prob = num_mkt_less / 10000
    bond_prob = num_bond_less / 10000

    print("============")
    print(frequency + " 5 Consecutive Periods Known Market Prob: ", mkt_prob)
    print(frequency + " 5 Consecutive Periods Known Bond Prob: ", bond_prob)

def five_consecutive_empirical(df, frequency):
    mkt_mean = df["mktret"].mean()
    mkt_var = df["mktret"].var()
    bond_mean = df["bondret"].mean()
    bond_var = df["bondret"].var()

    df["mktret_5_sum"] = df["mktret"].rolling(window=5).sum()
    df["bondret_5_sum"] = df["bondret"].rolling(window=5).sum()

    mkt_norm = stats.norm(5 * mkt_mean, np.sqrt(5 * mkt_var))
    bond_norm = stats.norm(5 * bond_mean, np.sqrt(5 * bond_var))

    mkt_samples = df["mktret_5_sum"].sample(10000, replace=True).values
    bond_samples = df["bondret_5_sum"].sample(10000, replace=True).values

    num_mkt_less = 0.0
    num_bond_less = 0.0
    mkt_ppf = mkt_norm.ppf(.2)
    bond_ppf = bond_norm.ppf(.2)
    for j in range(0,10000):

        if mkt_samples[j] > .2:
            num_mkt_less += 1.0

        if bond_samples[j] > .2:
            num_bond_less += 1.0

    mkt_prob = num_mkt_less / 10000
    bond_prob = num_bond_less / 10000

    print("============")
    print(frequency + " 5 Consecutive Periods Empirical Market Prob: ", mkt_prob)
    print(frequency + " 5 Consecutive Periods Empirical Bond Prob: ", bond_prob)

def prob_greater_than_twenty(df, frequency):
    mkt_mean = df["mktret"].mean()
    mkt_var = df["mktret"].var()
    bond_mean = df["bondret"].mean()
    bond_var = df["bondret"].var()

    print("==============")
    mkt_prob = 1 - stats.norm(5*mkt_mean, np.sqrt(5*mkt_var)).cdf(.2)
    print("Probability Market Ret > 20% " + frequency + ": ", mkt_prob)

    bond_prob = 1 - stats.norm(5 * bond_mean, np.sqrt(5 * bond_var)).cdf(.2)
    print("Probability Bond Ret > 20% " + frequency + ": ", bond_prob)

def prob_stock_lower_than_bond_30(df, frequency):
    mkt_mean = 30*df["mktret"].mean()
    mkt_var = np.sqrt(30*df["mktret"].var())
    bond_mean = 30*df["bondret"].mean()
    bond_var = np.sqrt(30*df["bondret"].var())

    df["mktret_30_sum"] = df["mktret"].rolling(window=30).sum()
    df["bondret_30_sum"] = df["bondret"].rolling(window=30).sum()

    diff_mean = bond_mean - mkt_mean
    cov = df["mktret_30_sum"].cov(df["bondret_30_sum"])
    diff_var = bond_var + mkt_var - 2*cov

    prob = 1 - stats.norm.cdf((0 - diff_mean) / np.sqrt(diff_var))
    print("========")
    print("Probability Bond Ret > Stock Ret over 30 periods " + frequency + ": ", prob)

data_annual = read_csv("returns_annual.csv")
data_monthly = read_csv("returns_monthly.csv")
data_daily = read_csv("returns_daily.csv")

print_dataframe_stats(data_annual, "Annual")
print_dataframe_stats(data_monthly, "Monthly")
print_dataframe_stats(data_daily, "Daily")

# histogram(data_annual, data_monthly, data_daily)

confidence_intervals(data_annual, "Annual")
confidence_intervals(data_monthly, "Monthly")
confidence_intervals(data_daily, "Daily")

# shortfall_plot(data_annual)
# shortfall_plot(data_monthly)
# shortfall_plot(data_daily)

prob_stock_lower_than_bond(data_annual, "Annual")
prob_stock_lower_than_bond(data_monthly, "Monthly")
prob_stock_lower_than_bond(data_daily, "Daily")

# simulation(data_annual, "Annual")
# simulation(data_monthly, "Monthly")
# simulation(data_daily, "Daily")

# simulation_empirical(data_annual, "Annual")
# simulation_empirical(data_monthly, "Monthly")
# simulation_empirical(data_daily, "Daily")

prob_greater_than_twenty(data_annual, "Annual")
prob_greater_than_twenty(data_monthly, "Monthly")
prob_greater_than_twenty(data_daily, "Daily")

five_consecutive_known(data_annual, "Annual")
five_consecutive_known(data_monthly, "Monthly")
five_consecutive_known(data_daily, "Daily")

five_consecutive_empirical(data_annual, "Annual")
five_consecutive_empirical(data_monthly, "Monthly")
five_consecutive_empirical(data_daily, "Daily")

prob_stock_lower_than_bond_30(data_annual, "Annual")
prob_stock_lower_than_bond_30(data_monthly, "Monthly")
prob_stock_lower_than_bond_30(data_daily, "Daily")


#!/usr/bin/env python
# _*_coding:utf-8 _*_
# @Time    :2019/4/23 0023 下午 10:03
# @Author  :喜欢二福的沧月君（necydcy@gmail.com）
# @FileName: PremeNumble.py

# @Software: PyCharm
import numpy
"""
（十）、筛法求素数
【题目描述】
用户输入整数n和m（1<n<m<1000），应用筛法求[n,m]范围内的所有素数。
"""
import math
num = [int(n) for n in input('').split()]
MAX_INT=num[1]
MIN_INT=num[0]
marks_bool = [True] * (MAX_INT + 1)
for i in range(2,int(math.sqrt(MAX_INT)) + 1):
    j = i
    k = j
    while j * k <= MAX_INT:
        marks_bool[j * k] = False
        k += 1
sum_int = 0
l=[]
for i in range(2,MAX_INT + 1):
    if marks_bool[i] is True:
        if(i>=MIN_INT):
            l.append(i)
print(numpy.array(l).reshape(-1,5))


import assets as at
import numpy as np
import cvxopt as opt
from cvxopt import blas, solvers
import matplotlib.pyplot as plt
import pandas as pd

class Portfolio:
    def __init__(self, assets: at.Assets, weights, risk_free_rate):
        self._assets = assets
        self._risk_free_rate = risk_free_rate
        if len(weights) == assets.get_number_of_assets():
            self._weights = weights
        else:
            raise Exception("Wrong number of weights")
        return

    def set_weights(self, weights):
        if len(weights) == self._assets.get_number_of_assets():
            self._weights = weights
        else:
            raise Exception("Wrong number of weights")
        return

    def get_expected_return(self):
        wT = self._weights.reshape(1, len(self._weights))  # 1 x n
        r = np.array(self._assets.get_all_assets_average_return())  # n x 1
        int1m = np.matmul(wT, r)
        return int1m

    def get_expected_volatility(self):
        if self._assets.is_covariance_matrix_stale():
            self._assets.compute_and_set_covariance_matrix()
        wT = self._weights.reshape(1, len(self._weights))  # 1 x n
        V = self._assets.get_covariance_matrix()  # n x n
        w = self._weights.reshape(len(self._weights), 1)  # n x 1
        intm1 = np.matmul(wT, V)  # (1 x n) x (n x n) = 1 x n
        intm2 = np.matmul(intm1, w)  # (1 x n) x (1 x 1) = 1 x 1
        return np.sqrt(intm2)

    def get_risk_free_rate(self):
        return self._risk_free_rate

    def get_sharpe_ratio(self):
        return (self.get_expected_return() - self.get_risk_free_rate()) / self.get_expected_volatility()

    def get_cal_m_q(self):
        return self.get_risk_free_rate(), self.get_sharpe_ratio()

    def get_assets(self):
        return self._assets

def optimal_portfolio_frontier_correct(avg_returns_vec, covariance_mat, target_ret_scalars):
    n = len(avg_returns_vec)
    P = opt.matrix(covariance_mat)
    q = opt.matrix(0.0, (n, 1))
    G = -opt.matrix(np.eye(n))  # negative n x n identity matrix
    h = opt.matrix(0.0, (n, 1))
    a_vector = list()
    for a in avg_returns_vec:
        a_vector.append(1)
        a_vector.append(np.asscalar(a))
    A = opt.matrix(a_vector, (2, n))
    bs = list()
    for tr in target_ret_scalars:
        bs.append(opt.matrix([1, tr]))

    portfolios = [solvers.qp(P, q, G, h, A, b)['x'] for b in bs]

    weights = [[a for a in x] for x in portfolios]
    returns = [blas.dot(x, opt.matrix(avg_returns_vec)) for x in portfolios]
    risks = [np.sqrt(blas.dot(x, P * x)) for x in portfolios]

    return weights, returns, risks

def optimal_portfolio_frontier_conditioned(avg_returns_vec, covariance, n_points_frontier=100):
    # returns is a n_assets x k_returns_observations matrix
    # covariance is a n_assets x n_assets matrix, the covariance matrix of returns matrix
    # credits to https://plotly.com/python/v3/ipython-notebooks/markowitz-portfolio-optimization/
    n = len(avg_returns_vec)
    returns = np.asmatrix(avg_returns_vec)

    N = n_points_frontier
    mus = [10 ** (5.0 * t / N - 1.0) for t in range(N)]

    # Convert to cvxopt matrices
    # S = opt.matrix(np.cov(returns))
    S = opt.matrix(covariance)
    pbar = opt.matrix(np.mean(returns, axis=1))

    # Create constraint matrices
    G = -opt.matrix(np.eye(n))  # negative n x n identity matrix
    h = opt.matrix(0.0, (n, 1))
    A = opt.matrix(1.0, (1, n))
    b = opt.matrix(1.0)

    # Calculate efficient frontier weights using quadratic programming
    portfolios = [solvers.qp(mu * S, -pbar, G, h, A, b)['x'] for mu in mus]

    # CALCULATE RISKS AND RETURNS FOR FRONTIER
    returns = [blas.dot(pbar, x) for x in portfolios]
    risks = [np.sqrt(blas.dot(x, S * x)) for x in portfolios]

    # CALCULATE THE 2ND DEGREE POLYNOMIAL OF THE FRONTIER CURVE
    m1 = np.polyfit(returns, risks, 2)
    x1 = np.sqrt(m1[2] / m1[0])

    # CALCULATE THE OPTIMAL PORTFOLIO
    wt = solvers.qp(opt.matrix(x1 * S), -pbar, G, h, A, b)['x']
    return np.asarray(wt), returns, risks

def compute_tangency_portfolio(avg_returns_vec, covariance, risk_free_rate):
    # compute tangency pf
    inv_cov = np.linalg.inv(covariance)
    er_minus_rf_i = np.subtract(avg_returns_vec, risk_free_rate)
    ones_row = np.ones((1, len(avg_returns_vec)))
    vt_numerator = np.matmul(inv_cov, er_minus_rf_i)
    intm_1 = np.matmul(ones_row, inv_cov)
    vt_denominator = np.matmul(intm_1, er_minus_rf_i)
    tpf = np.divide(vt_numerator, vt_denominator)
    tpfr = list()
    for tpfritem in tpf:
        tpfr.append(np.asscalar(tpfritem))
    return tpfr

def compute_mgv_portfolio(avg_returns_vec, covariance):
    # compute mgv pf
    inv_cov = np.linalg.inv(covariance)
    ones_row = np.ones((1, len(avg_returns_vec)))
    ones_col = np.transpose(ones_row)
    gmv_numerator = np.matmul(inv_cov, ones_col)
    intm_2 = np.matmul(ones_row, inv_cov)
    gmv_denominator = np.matmul(intm_2, ones_col)
    return np.divide(gmv_numerator, gmv_denominator)

def optimal_portfolio_frontier_as_combination_of_tpf_and_mgvpf(avg_returns_vec, covariance, risk_free_rate,
                                                               n_points_frontier=1000):
    # compute tange and mgv pfs
    w_tang = compute_tangency_portfolio(avg_returns_vec, covariance, risk_free_rate)
    w_gmv = compute_mgv_portfolio(avg_returns_vec, covariance)

    optimal_frontier_w = list()
    optimal_frontier_r = list()
    optimal_frontier_s = list()
    for i in range(n_points_frontier):
        intm_3 = np.multiply(1.0 - i / 100.0, w_tang)
        intm_3_a = np.squeeze(np.asarray(intm_3))
        intm_4 = np.multiply(i / 100.0, w_gmv)
        intm_4_a = np.squeeze(np.asarray(intm_4))
        current_w = np.add(intm_3_a, intm_4_a)
        optimal_frontier_w.append(current_w)
        optimal_frontier_r.append(np.asscalar(np.matmul(np.transpose(current_w), avg_returns_vec)))
        optimal_frontier_s.append(np.asscalar(np.matmul(np.matmul(np.transpose(current_w), covariance), current_w)))
    return optimal_frontier_w, optimal_frontier_r, optimal_frontier_s

def get_optimal_w_s_given_target_return(target_return, risk_free_rate, covariance, avg_returns_vec):
    inv_cov = np.linalg.inv(covariance)
    er_minus_rf_i = np.subtract(avg_returns_vec, risk_free_rate)
    big_h = np.asscalar(np.matmul(np.matmul(np.transpose(er_minus_rf_i), inv_cov), er_minus_rf_i))
    optimal_w = np.matmul(np.multiply(big_h ** -1 * (target_return - risk_free_rate), inv_cov), er_minus_rf_i)
    optimal_s = np.asscalar(np.matmul(np.matmul(np.transpose(optimal_w), covariance), optimal_w))
    return optimal_w, optimal_s

def plot_pf_lines(weights_list, target_assets_name):
    # plot weights
    plt.figure()
    lines = plt.plot(weights_list)
    i = 0
    for line in lines:
        line.set_label(target_assets_name[i])
        i += 1
    plt.ylabel("weight (a different color per asset)")
    plt.xlabel("portfolio id")
    plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), ncol=8, mode="expand", borderaxespad=0.)

def plot_pf_stacked(weights_list, target_assets_name):
    # better viz (stacked bars)
    plt.figure()
    portfolio_index = [x for x in range(len(weights_list))]
    portfolio_component_index = [x for x in range(len(weights_list[0]))]
    bar_components = list()
    for portfolio_component_idx in portfolio_component_index:
        bar_component = list()
        for portfolio_idx in portfolio_index:
            bar_component.append(abs(weights_list[portfolio_idx][portfolio_component_idx]))
        bar_components.append(bar_component)

    # build a dataframe
    a_dict = dict()
    idx = 0
    for bar_component in bar_components:
        key_name = target_assets_name[idx]
        idx += 1
        a_dict[key_name] = bar_component

    # plot
    df = pd.DataFrame(a_dict)
    df.astype(df.dtypes).plot.bar(stacked=True)
    plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), ncol=8, mode="expand", borderaxespad=0.)


from sympy import *
import sympy as sym
import numpy as np
""""
This module contains classes that support particluar domains transforms, that
are common in applications.
"""

class RectangleToEccentricAnnulus:
    """
    This class permits to easily create and map a rectangle to a specific
    eccentric annulus.
    """

    def __init__(self, R1, R2, epsilon):
          """

        Parameters
        ----------
        R1 : Inner radius of target eccentric annulus
        R2 : Outer radius of target eccentric annulus
        epsilon : Relative eccentricity of target eccentric annulus

        Returns
        -------
        None.

        """

          self.R1 = R1
          self.R2 = R2
          self.epsilon = epsilon
          self.bottom = self.bipolarConstants()[0]
          self.top = self.bipolarConstants()[1]
          self.shift = self.bipolarConstants()[2]
          self.pole = self.bipolarConstants()[3]
          self.left = -np.pi
          self.right = np.pi

    def bipolarConstants(self):
          """

        Returns
        -------
        alpha : Value of bottom boundary of rectangle.
        beta : Value of top boundary of rectangle.

        yShift1 : shift of to center the outer radius at origin
        cNum : pole of corresponding bipolar coordinate system

        """

          innerRadius=self.R1
          outerRadius=self.R2
          shift=self.epsilon *(outerRadius-innerRadius)

          shift=-shift

          if shift != 0:

              R1, R2,delta, b, A, B, C, epsilon, kappa, alpha, beta, c, M, F = sym.symbols('R1 R2 delta b A B C epsilon kappa alpha beta c M F', real=True)
              k, m, n = sym.symbols('k m n', integer=True)

              testF=(R2**2-R1**2+b**2)/(2*b)
              testM=sqrt(F**2-R2**2)

              testAlpha=0.5*log((F+M)/(F-M))
              testBeta=0.5*log((F-b+M)/(F-b-M))

              yShift1=float((testM*coth(testAlpha.subs(M,testM))).subs(F,testF).subs(R2,outerRadius).subs(R1,innerRadius).subs(b,abs(shift)))
              yShift2=yShift1-abs(shift)

              alphaNum=testAlpha.subs(M,testM).subs(F,testF).subs(R2,outerRadius).subs(R1,innerRadius).subs(b,abs(shift))
              betaNum=testBeta.subs(M,testM).subs(F,testF).subs(R2,outerRadius).subs(R1,innerRadius).subs(b,abs(shift))

              cNum=float(testM.subs(F,testF).subs(R2,outerRadius).subs(R1,innerRadius).subs(b,abs(shift)))

              return float(alphaNum), float(betaNum), yShift1, cNum

    def mapping(self, z):
        """

        Parameters
        ----------
        z : Symbolic complex variable

        Returns
        -------
        value : Symbolic function, that mapps the specific rectangle to the target eccentric annulus

        """
        value = self.pole*sym.tan((z)/2)-sym.I*self.shift
        return value

class RectangleToEllipticAnnulus:
    """
    This class permits to easily create and map a rectangle to a specific
    concentric elliptical annulus.

    """
    def __init__(self, b, a):
        """

        Parameters
        ----------
        b : Large half axis of inner ellipse

        a : Large half axis of outer ellipse

        Returns
        -------
        None.

        """

        self.top = np.pi
        self.bottom = -np.pi
        self.a = a
        self.b = b
        self.c = np.sqrt(self.a**2-self.b**2)
        self.k = round(a/np.sqrt(2),2)

        if self.b > self.a/np.sqrt(2):
            left = np.log(self.b/self.c+np.sqrt((self.b/self.c)**2-1))
            right = np.log(self.a/self.c+np.sqrt((self.a/self.c)**2-1))
        else:
            print()
            print('ERROR: smaller half axis must be larger than {0}:'.format(self.k))
            print('The ratio of larger half axis and sqrt(2)')
            print()
        self.left = left
        self.right = right

    def mapping(self, z):
        """

        Parameters
        ----------
        z : Symbolic complex variable

        Returns
        -------
        value : Returns a complex variable function that maps the rectangle
        [left, right] x [-pi, pi] to an elliptic annulus where half axes are
        b and a.

        """
        value = self.c*sym.cosh(z)
        return value

class ConcentricAnnulusToEccentricAnnulus:
    """
    This class permits to easily create and map a concentric annulus
    to a specific excentric elliptical annulus. Theory in Lauer-Bare and
    Gaertig 2021 (scipy) and Brown and Churchil.

    """
    def __init__(self, R1, R2, epsilon):
        """

        Parameters
        ----------
        R1 : Inner radius of target eccentric annulus
        R2 : Outer radius of target eccentric annulus
        epsilon : Relative eccentricity of target eccentric annulus
        Returns
        -------
        None.
        """

        self.R1 = R1
        self.R2 = R2
        self.epsilon = epsilon
        self.rin = 1
        self.rout = self.newRadii()[0]
        self.moebiusConstant = self.newRadii()[1]

    def newRadii(self):

        R1, R2, b = sym.symbols('R1, R2, b', real=True)
        R, a, x1, x2, l, delta = sym.symbols('R, a, x1,x2,l, delta', real=True)

        # a and R from Churchill, Brown

        a_ = (1+x1*x2+sym.sqrt((1-x1**2)*(1-x2**2)))/(x1+x2)
        a_ = a_.subs(x2, (b-R1))
        a_ = a_.subs(x1, (R1+b))
        a_ = a_.subs(R1, R1/R2)
        a_ = a_.subs(b, b/R2)

        R_ = (1-x1*x2+sym.sqrt((1-x1**2)*(1-x2**2)))/(x1-x2)
        R_ = R_.subs(x2, (b-R1))
        R_ = R_.subs(x1, (R1+b))
        R_ = R_.subs(R1, R1/R2)
        R_ = R_.subs(b, b/R2)

        # numerical values

        R1_ = self.R1
        R2_ = self.R2
        shift = self.epsilon*(R2_-R1_)

        RNum = R_.subs(R1, R1_).subs(R2, R2_)
        RNum = float(RNum.subs(b, shift))

        aNum = a_.subs(R1, R1_).subs(R2, R2_)
        aNum = float(aNum.subs(b, shift))

        return RNum, aNum

    def mapping(self, z):
        """

        Parameters
        ----------
        z : Symbolic complex variable
        Returns
        -------
        value : Returns a complex variable function that maps the concetric
        annulus with inner radius 1 an outer radius 'outerRadius' to
        a eccentric annulus with radii R1 (inner), R2 (outer) and
        relative eccentricity
        epsilon.
        """
        a = self.moebiusConstant
        value = self.R2*(-sym.I*a+sym.I*z)/(-a*z+1)
        return value


import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
from matplotlib.animation import  FuncAnimation
import warnings
warnings.filterwarnings('ignore')

def regular_attractor(v, t, a, idvars, d_st, w_st):

    a = a
    b, c, d = idvars

    # x
    w_0, d_0 = w_st[0], d_st[0]

    # y
    w_1, d_1 = w_st[1], d_st[1]
    w_2, d_2 = w_st[2], d_st[2]

    # z
    w_3, d_3 = w_st[3], d_st[3]

    # vector assignment:
    x, y, z = v[0], v[1], v[2]

    # ODEs:
    dx_dt = a * x - b * x ** 2 - (w_0 * x * y) / (x + d_0)
    dy_dt = -c * y + (w_1 * x * y) / (x + d_1) - (w_2 * y * z) / (y + d_2)
    dz_dt = d * z ** 2 - (w_3 * z ** 2) / (y + d_3)

    return [dx_dt, dy_dt, dz_dt]

def init_cond(a):

    x0 = a / 0.12 - 5 + np.sqrt((a + 0.6) ** 2 - 1.52) / 0.12
    y0 = 6.31579
    z0 = -40.3 + 80.6 * (x0 / (x0 + 10))

    return [x0, y0, z0]

def integrate(t, a, idvars, d_st, w_st, model=regular_attractor):
    v = odeint(model, init_cond(a) , t, args=(a, idvars, d_st, w_st)) # [17.494, 6.31579, 10.985]init_cond(a)[15, 6.31579, 8]
    X, Y, Z = v[:, 0], v[:, 1], v[:, 2]
    return X, Y, Z

#def updata_variability(i, t, a_vec, prey, predator, apex, time_text): #, time_text
#    X, Y, Z = integrate(t, a_vec[i], tuple((0.06, 1, 0.038)), [10, 10, 10, 20], [1, 2, 0.405, 1])
#
#    print(X)
#    prey.set_xdata(t)
#    predator.set_xdata(t)
#    apex.set_xdata(t)
#
#    prey.set_ydata(X)
#    predator.set_ydata(Y)
#    apex.set_ydata(Z)
#
#    time_text.set_text(f"$a={a_vec[i].round(3)}$")
#
#    return prey,predator,apex

#def plot_animated_dependencies():
#    fig, ax = plt.subplots()
#
#    T = np.linspace(0, 1000, 1000)
#    prey, = ax.plot([],[], color='green')
#    predator, = ax.plot([],[], color='blue')
#    apex, = ax.plot([],[], color='orange')
#    time_text = ax.text(0.1, 0.95, "", color='red')
#    a_v = np.linspace(1., 1.9, 1000)
#    animation = FuncAnimation(fig, updata_variability, frames=1000, fargs=(T, a_v, prey, predator, apex, time_text))
#    plt.show()
#    pass

def plot_animation_variablility():
    t = np.linspace(0, 1000, 10000)
    idstat = tuple((0.06, 1, 0.038))
    d_stat = [10, 10, 10, 20]
    w_stat = [1, 2, 0.405, 1]

    for a in np.linspace(.9, 1.9, 600):
        plt.clf()
        X, Y, Z = integrate(t, a, idstat, d_stat, w_stat, model=regular_attractor)
        plt.plot(t, X, color='green', label=f'prey, $a={a.round(4)}$')
        plt.plot(t, Y, color='orange', label=f'predator, $a={a.round(4)}$')
        plt.plot(t, Z, color='blue', label=f'top predator, $a={a.round(4)}$')
        plt.xlabel('$t$')
        plt.ylabel('Population density')
        plt.legend(bbox_to_anchor=(.95, 1.0), loc='upper left')
        plt.pause(0.001)
    plt.show()
    pass

def plot_3d_limiting_cycle(a, idstat, d_stat, v_stat, t):
    X, Y, Z = integrate(t, a, idstat, d_stat, v_stat, model=regular_attractor)
    fig = plt.figure(figsize=(12, 9))
    ax = fig.gca(projection='3d')
    ax.xaxis.set_pane_color((1, 1, 1, 1))
    ax.yaxis.set_pane_color((1, 1, 1, 1))
    ax.zaxis.set_pane_color((1, 1, 1, 1))
    ax.plot(X, Y, Z, color='r', alpha=0.7, linewidth=0.7)

    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_zlabel('z')

    plt.show()

def main():
    a_stat = [1.1062, 1.2971, 1.5013, 1.803, 1.88, 1.9]
    idstat = tuple((0.06, 1, 0.038))
    d_stat = [10, 10, 10, 20]
    w_stat = [1, 2, 0.405, 1]
    t = np.linspace(0, 1000, 10000)

    plot_animation_variablility()

    #for a in a_stat:
    #    plot_3d_limiting_cycle(a, idstat, d_stat, w_stat, t)
    pass

if __name__ == '__main__':
    main()


import numpy as np
from numerical_analysis.dependencies import Polynomial
from numerical_analysis.root_finding import newton_raphson_multiple_roots

def trapezoid(f, xs, xn, n):

    def dI():
        nonlocal xa, xb
        return (xb - xa) * (f(xa) + f(xb)) / 2

    I = 0
    dx = (xn - xs) / n

    for i in range(n):

        xa = xs + i * dx
        xb = xs + (i + 1) * dx

        I += dI()

    return I

def simpson1_3(f, xs, xn, n):

    def dI():
        nonlocal xa, xb

        x0 = xa
        x1 = (xa + xb) / 2
        x2 = xb

        return (xb - xa) * (f(x0) + 4 * f(x1) + f(x2)) / 6

    I = 0
    dx = (xn - xs) / n

    for i in range(n):

        xa = xs + i * dx
        xb = xs + (i + 1) * dx

        I += dI()

    return I

def simpson3_8(f, xs, xn, n):

    def dI():
        nonlocal xa, xb

        h = (xb - xa) / 3

        x0 = xa
        x1 = xa + h
        x2 = xb - h
        x3 = xb

        return (xb - xa) * (f(x0) + 3 * f(x1) + 3 * f(x2) + f(x3)) / 8

    I = 0
    dx = (xn - xs) / n

    for i in range(n):

        xa = xs + i * dx
        xb = xs + (i + 1) * dx

        I += dI()

    return I

def romberg(f, x0, xn, level):

    dx = (xn-x0)/2**(level-1)
    Irom = []
    xs = [x0 for i in range(level)]

    for i in range(level):
        Irom.append([])
        for j in range(level - i):
            Irom[i].append(0)

    for i in range(1, 2**(level-1) + 1):
        xi = x0 + i*dx
        for j in range(level):
            if i % 2**(level - (j+1)) == 0:
                Irom[j][0] += trapezoid(f, xs[j], xi, 1)
                xs[j] = xi

    for j in range(1, level):
        for i in range(level - j):
            Irom[i][j] = (4 ** j * Irom[i + 1][j - 1] - Irom[i][j - 1]) / (4 ** j - 1)

    return Irom[0][-1]

def gauss_legendre(f, x0, xn, n):

    p = Polynomial.legendre(n+1)

    x = newton_raphson_multiple_roots(p.value, p.derivative().value, p.n, 1e-14)
    L = Polynomial.lagrange(np.array(x))
    w = []

    for i in range(n+1):
        w.append(L[i].definite_integral(-1, 1))

    I = 0.
    for i in range(n+1):
        I += w[i]*f((x[i] * (xn - x0) + xn + x0) / 2)

    I *= 0.5 * (xn - x0)

    return I


# -*- coding: utf-8 -*-

import numpy as np
import matplotlib.pyplot as plt

def leastSquaresCurveFit(min_points):
    '''
    Function to fit a curve using Least squares given minimum number of points (x,y) from the data.
    Y = XB
    E = (Y - XB)T(Y - XB)
    dE/dB = 0
    B = inv(XTX)*XT*Y
    Input ---> min_points: minimum number of points (x,y) to fit a given curve
    Output ---> B: return model parametersnfor a given curve
    '''
    # x coordinates of the min number of points required to model the given curve
    min_points_x = np.reshape(min_points[:,0], (min_points.shape[0], 1))
    # y coordinates of the min number of points required to model the given curve
    min_points_y = np.reshape(min_points[:,1], (min_points.shape[0], 1))
    min_points_x_squared = np.power(min_points_x,2)
    # Form the X matrix; for a quadratic curve it is [x^2, x, 1]
    min_points_x_matrix = np.hstack(( min_points_x_squared, min_points_x, np.ones((min_points_x.shape[0],1))))
    # Evaluate the model parameters
    B = np.matmul(np.linalg.inv(np.matmul(np.transpose(min_points_x_matrix), min_points_x_matrix)),
                  np.matmul(np.transpose(min_points_x_matrix), min_points_y))

    return B

def hypothesisEvaluate(remaining_points, hypothesis_parameters, threshold, remaining_points_indices, flag = 0):
    '''
    Funtion to evaluate/test the hypothesis or model that was used to fit the curve from minimum number of points (x,y).
    Input ---> remaining_points: points other than the ones used to find initial model parameters
               hypothesis_parameters: model parameters
               threshold: accepted error to consider a point as inlier or outlier
               remaining_points_indices: index corresponding to remaining_points
               flag: value that chooses whether to use remaining_points_indices or not
    Output ---> error: difference between estimated y and ground truth
                inliers_indices: index corresponding to inliers
    '''
    # x coordinates of the remaining points of the data required to model the given curve
    remaining_points_x = np.reshape(remaining_points[:,0], (remaining_points.shape[0], 1))
    # y coordinates of the remaining points of the data required to model the given curve
    remaining_points_y = np.reshape(remaining_points[:,1], (remaining_points.shape[0], 1))
    remaining_points_x_squared = np.power(remaining_points_x,2)
    # Form the X matrix; for a quadratic curve it is [x^2, x, 1]
    remaining_points_x_matrix = np.hstack((remaining_points_x_squared, remaining_points_x, np.ones((remaining_points.shape[0],1))))
    # Estimate y coordinate of remaining points given x coordinates and the model parameters
    y_estimate = np.dot(remaining_points_x_matrix, hypothesis_parameters)
    # Calculate the error between the estimated y and ground truth y
    error = abs(y_estimate - remaining_points_y)
    # Find the inliers; points for which error is less than or equal to acceptable threshold
    inliers = error <= threshold
    if flag == 0:
        remaining_points_indices = np.reshape(remaining_points_indices, (remaining_points_indices.shape[0],1))
        # Obtain the indices of the inlier points
        inliers_indices = remaining_points_indices[inliers]
    else:
       inliers_indices = []

    return error, inliers_indices

def RANSAC(curve, minimum_no_of_inliers, threshold, iterations, init_points):
    '''
    Function to implement the RANSAC algorithm.

    For i <= iterations:
        1. randomly choose n minimum points to fit the curve. For eg., to fit a line we need 2 minimum points,
           whereas to fit a quadratic curve we need 3 minimum points.
        2. fit a curve using n minimum points and obtain model parameters.
        3. evaluate the model parameters, calculate the error and inliers.
        4. if inliers >= minimum number of inliers the model should have
            1. find a model that fits the curve for entire data
            2. evaluate the model once again to find the best model

    Input ---> curve: (x,y) points of the curve data
               minimum_no_of_inliers: minimum number of inliers that a model should have
               threshold: acceptable error for a point to be considered as inlier
               iterations: Number of iterations the algorithm has to run
               init_points: minimum number of points (x,y) from the data required to fit a curve

    Output ---> Best Parameters
                Best inliers
    '''
    # Initialize best model parameters
    best_parameters = None
    # Initialize best inliers
    best_inliers = 0
    # Initialize model error
    init_error = np.inf
    i = 0
    while i <= iterations:
        all_point_indices = np.arange(curve.shape[0])
        np.random.shuffle(all_point_indices)
        # Obtain indices of minimum number of points required to model a given curve
        min_points_indices = all_point_indices[:init_points]
        # Obtain indices of remaining points in the data
        remaining_points_indices = all_point_indices[init_points:]
        min_points = curve[min_points_indices,:]
        remaining_points = curve[remaining_points_indices]
        # Obtain the hypothesis/model parameters given the minimum number of points from the data
        hypothesis_parameters = leastSquaresCurveFit(min_points)
        # Evaluate the obtained hypothesis/model parameters and calculate the error, inliers
        error, inliers_indices = hypothesisEvaluate(remaining_points, hypothesis_parameters, threshold, remaining_points_indices)
        inliers = curve[inliers_indices,:]
        if len(inliers) >= minimum_no_of_inliers:
            total_inliers = np.concatenate((min_points, inliers))
            current_hypothesis_parameters = leastSquaresCurveFit(total_inliers)
            current_error, current_inliers_indices = hypothesisEvaluate(total_inliers, current_hypothesis_parameters, threshold, remaining_points_indices, 1)
            current_error = np.mean(current_error)
            if current_error <= init_error:
                best_parameters = current_hypothesis_parameters
                best_inliers = np.concatenate((min_points_indices, inliers_indices))
                init_error = current_error
        i = i + 1

    if np.any(best_parameters == None):
        raise ValueError("No solution")
    else:
        return best_parameters, best_inliers

def main():
    # Load curve data from csv files
    curve_data = np.genfromtxt('data_2.csv', delimiter = ',')
    x = curve_data[1:,0]
    y = curve_data[1:,1]
    x = np.reshape(x, (np.shape(x)[0], 1))
    y = np.reshape(y, (np.shape(y)[0], 1))
    curve = np.hstack((x,y))
    parameters = RANSAC(curve, 125, 20, 100, 3)
    x_squared = np.power(x,2)
    x_matrix = np.hstack(( x_squared, x, np.ones((x.shape[0],1))))
    y_estimate = np.matmul(x_matrix, parameters[0])
    # Plot curve data, inlier data, curve estimate
    plt.figure()
    plt.xlabel('x axis')
    plt.ylabel('y axis')
    plt.title('Data_2.Paramters:Probability of inliners=125,Error Threshold=20,Iterations=100')
    plt.scatter(x, y, label = 'Scattered data')
    plt.plot(x[parameters[1]], y[parameters[1]], 'bx',color='yellow', label = 'Inlier Data')
    plt.plot(x, y_estimate, 'r', label = 'Curve Fitting')
    plt.legend()

if __name__=='__main__':
    main()


# Test Gaussian PDF

try:
    from scipy.special import erf as _erf
    import numpy as _np
    from math import sqrt as _sqrt, log as _log, pi as _pi, e as _e, exp as _exp
    from typing import Union, Dict, List
    from univariate._base import Infinite
except Exception as e:
    print(f"some modules are missing {e}")

class Gaussian(Infinite):
    """
    This class contains methods concerning the Gaussian Distribution [#]_ [#]_.

    .. math::
        \\text{Gaussian}(x;\\mu,\\sigma) = \\frac{1}{\\sigma \\sqrt(2 \\pi)} e^{\\frac{1}{2}\\big( \\frac{x-\\mu}{\\sigma}\\big)^2}

    Args:

        mean(float): mean of the distribution (:math:`\\mu`)
        std(float): standard deviation (:math:`\\sigma`) of the distribution where std > 0
        x(float): random variable

    References:
        .. [#] Wikipedia contributors. (2020, December 19). Gaussian distribution. https://en.wikipedia.org/w/index.php?title=Gaussian_distribution&oldid=995237372
        .. [#] Weisstein, Eric W. "Gaussian Distribution." From MathWorld--A Wolfram Web Resource. https://mathworld.wolfram.com/GaussianDistribution.html

    """

    def __init__(self, mean: float = 0, stdev: float = 1):
        if stdev < 0:
            raise ValueError("stdev parameter must not be less than 0.")

        self.mean_val = mean
        self.stdev = stdev

    def pdf(self, x: Union[List[float], _np.ndarray, float]) -> Union[float, _np.ndarray]:
        """
        Args:
            x (Union[List[float], numpy.ndarray, float]): random variable(s)

        Returns:
            Union[float, numpy.ndarray]: evaluation of pdf at x
        """
        mean = self.mean_val
        std = self.stdev

        if isinstance(x, (_np.ndarray, List)):
            if not type(x) is _np.ndarray:
                x = _np.array(x)
            return _np.power(1 / (std * _sqrt(2 * _pi)), _np.exp(((x - mean) / 2 * std)**2))

        return pow(1 / (std * _sqrt(2 * _pi)), _exp(((x - mean) / 2 * std)**2))

    def cdf(self, x: Union[List[float], _np.ndarray, float]) -> Union[float, _np.ndarray]:
        """
        Args:
            x (Union[List[float], numpy.ndarray, float]): data point(s) of interest

        Returns:
            Union[float, numpy.ndarray]: evaluation of cdf at x
        """
        def __generator(mu: float, sig: float, x: Union[float, _np.ndarray]) -> Union[float, _np.ndarray]:
            return 1/2*(1+_erf((x-mu)/(sig*_sqrt(2))))

        if isinstance(x, (_np.ndarray, List)):
            if not type(x) is _np.ndarray:
                x = _np.array(x)
            return __generator(self.mean_val, self.stdev, x)
        return __generator(self.mean_val, self.stdev, x)

    def mean(self) -> float:
        """
        Returns: Mean of the Gaussian distribution
        """
        return self.mean_val

    def median(self) -> float:
        """
        Returns: Median of the Gaussian distribution
        """
        return self.mean_val

    def mode(self) -> float:
        """
        Returns: Mode of the Gaussian distribution
        """
        return self.mean_val

    def var(self) -> float:
        """
        Returns: Variance of the Gaussian distribution
        """
        return pow(self.stdev, 2)

    def std(self) -> float:
        """
        Returns: Standard deviation of the Gaussian distribution
        """
        return self.stdev

    def skewness(self) -> float:
        """
        Returns: Skewness of the Gaussian distribution
        """
        return 0.0

    def kurtosis(self) -> float:
        """
        Returns: Kurtosis of the Gaussian distribution
        """
        return 0.0

    def entropy(self) -> float:
        """
        Returns: differential entropy of the Gaussian distribution

        Reference: Park, S.Y. & Bera, A.K.(2009). Maximum entropy autoregressive conditional heteroskedasticity model. Elsivier.
        link: http://wise.xmu.edu.cn/uploadfiles/paper-masterdownload/2009519932327055475115776.pdf
        """
        return _log(self.std()*_sqrt(2 * _pi * _e))

    def summary(self) -> Dict[str, Union[float, int, str]]:
        """
        Returns:
            Dictionary of Gaussian distirbution moments. This includes standard deviation.
        """
        return {
            'mean': self.mean(), 'median': self.median(), 'mode': self.mode(),
            'var': self.var(), 'std': self.std(), 'skewness': self.skewness(), 'kurtosis': self.kurtosis()
        }