import numpy as np
import matplotlib.pyplot as plt

#The key to solving this problem is to turn it into a 3D-problem, with radius being the z axis
#Then draw a plane between the 2 datasets

class donut:
    def create_data(self, N = 1000, D = 2):
        #Gonna have 2 radia:
        R_inner = 5
        R_outer = 10

        #The // means that the answer will be an integer and not a float
        R1 = np.random.randn(N//2) + R_inner
        theta = 2*np.pi*np.random.random(N//2)
        X_inner = np.concatenate([[R1 * np.cos(theta)], [R1 * np.sin(theta)]]).T #So N goes along the rows

        R2 = np.random.randn(N//2) + R_outer
        theta = 2*np.pi*np.random.random(N//2)
        X_outer = np.concatenate([[R2 * np.cos(theta)], [R2 * np.sin(theta)]]).T

        X = np.concatenate([X_inner, X_outer])
        Y = np.array([0] * (N//2) + [1] * (N//2))

        #Plot the data to showcase the problem
        #plt.scatter(X[:,0], X[:,1], c = Y, s = 100, alpha = 0.5)
        #plt.show()

        #Add the bias term:
        ones = np.ones((N, 1))

        #The key to solving this problem is creating a column that represents the radius of the point
        r = np.zeros((N, 1)) #This allows us to linearly separate the datapoints
        for i in range(N):
            #Calculates the radius
            r[i] = np.sqrt(X[i,:].dot(X[i,:]))

        X_bias = np.concatenate((ones, r, X), axis = 1)

        return X_bias, Y

    def sigmoid(self, z):
        return 1 / (1 + np.exp(-z))

    def cross_entropy_error(self, Y, Y_hat):
        return -np.mean(Y * np.log(Y_hat) + (1 - Y) * np.log(1 - Y_hat))

    def gradient_decent(self, learning_rate = 0.0001):
        X_bias, Y = DP.create_data()

        N, D = X_bias.shape
        w = np.random.randn(D)

        #L2 Regularization
        l2 = 0.1

        error = []
        for i in range(10000):
            z = X_bias.dot(w)
            Y_hat = DP.sigmoid(z)
            cost = DP.cross_entropy_error(Y, Y_hat)
            if i % 100 == 0:
                print(i, cost)

            error.append(cost)

            #gradient decent:
            w += learning_rate * (X_bias.T.dot(Y - Y_hat) - l2 * w)

        plt.plot(error)
        plt.title('Cross-Entropy Error')
        plt.show()

        print("Final w: ", w)
        print("Final classification rates: ", 1 - np.abs(Y - np.round(Y_hat)).sum()/N)

if __name__ == "__main__":

    DP = donut()
    DP.gradient_decent()


from scipy.special import beta, betainc

from scipy.integrate import solve_ivp, cumtrapz

from numpy import sign, absolute, arange, meshgrid

from matplotlib import pyplot as plt

"""
================================================================================
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                        TWO-PARAMETER (twopara) CLASS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
================================================================================

The two-parameter (twopara) class defines the periodic differential system of
autonomous expressions which are specific power-law based generalizations of
the sine and cosine relationships often referred to as Generalized Trigonometric
functions, Ateb functions, or Lindqvist functions. This class models the
autonomous system of two first-order differential equations

                    y' = a*sign(x)*|x|^(p-1),   x(0) = x_0              (1)
                    x' = -b*sign(y)*|y|^(q-1),   y(0) = y_0

for p,q > 1 and a,b > 0.

    twopara( p , q , initial_conditions , coef=None )

    exponents:

            The exponents are given individually for each of p and q described
            in the differential system (1).

    initial_conditions:

        The initial conditions argument is a tuple which provides the initial
        conditions x_0 and y_0 necessary to solve the system of two first-order
        equations (1). It is required in order to define the class.

                initial_conditions = (x_0, y_0)

    coef:

        The coef argument is a tuple which provides the coefficients a and b in
        the differential system (1). It is not a required argument. If coef is
        not provided it is assumed that a=b=1.

                coef = (a, b)   or  (1, 1)

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                    FUNCTIONS OF THE TWO-PARAMETER CLASS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The two-parameter class contains several functions which performs operations
with respect to the system in (1); namely, its inherent behaviors, qualities,
and representation.

    s_max()

        This function returns the maximum/minimum extrema of the generalized
        sine function - i.e.,

                s_max = ((q / a) * (b * |x_0|^p / p + a * |y_0|^q / q))^(1 / q)

    c_max()

        This function returns the maximum/minimum extrema of the generalized
        cosine function - i.e.,

                c_max = ((p / b) * (b * |x_0|^p / p + a * |y_0|^q / q))^(1 / p)

    sum_constant()

        This function returns the total constant equivalent of the generalized
        ellipse - i.e.,

                c = a * |x_0|^p / p + b * |y_0|^q / q

    frequency()

        This function returns the frequency of the parameterized functions
        described by the system (1). The frequency is a function of all given
        parameters of the system.

                f = (p^(1 - 1/p) / q^(1/q)) * (|x_0|^p / p + |y_0|^q / q)^(1 - 1/p - 1/q)

    period()

        This function returns the period of the parameterized functions
        described by the system (1). The period changes depending on the choice
        of initial conditions, except in the case of Holder equality systems
        when the period is invariant, such as the degenerate case of the
        classical trigonometric functions when the period is 2pi. The period
        functions uses the Beta function and the inherent symmetry of the
        behaviors of the system between the four quadrants to return the value
        of the period

                P = 4 * Beta(1/p,1/q) / (q * f)

    phase()

        This function returns the phase shift of the parameterized functions
        described by the system (1) for some set of initial conditions.

    func()

        This function defines the differential system (1).

    para_func( tspan = None, max_step = None, solver = None )

        This functions solves the differential system (1) using solve_ivp and
        returns the two parametrized functions x(t) and y(t) which can be
        thought of as the generalized cosine and generalized sine functions,
        respectively. This function may take one argument for the tspan value
        necessary in solve_ivp. If no value is given, the default value shall be
        one full period of the system as calculated by period().

                                                    default:

                tspan =     USER_DEFINED        or      period()
                max_step =  USER_DEFINED        or      tspan / 100
                solver =    USER_DEFINED        or      'RK45'

================================================================================
================================================================================
"""

class twopara:

    #The indexed ordering of all values is intended to preserve that of traditional
    #spatial coordinates, i.e.
    #
    #          self.i_c = [self.i_c[0], self.i_c[1]] = (x_0, y_0)
    #   or
    #          y[0] -> generalized cosine, which traditionaly corresponds to x
    #          y[1] -> generalized sine, which traditionaly corresponds to y
    #

    def __init__(self, p, q, i_c, coef = None):

        self.p = p

        self.q = q

        self.i_c = i_c

        self.coef = coef or [1,1]

    #For reference, this class concerns the power-law based system of two
    #first order autonomous equations
    #
    #   dy[0] = self.coef[1]*sign(y[1])*|y[1]|^(self.p - 1),   y[1](0) = self.i_c[0]
    #   dy[1] = -self.coef[0]*sign(y[0])*|y[0]|^(self.q - 1),   y[0](0) = self.i_c[1]
    #

    #This function returns the maximum value of the generalized sine function y[0]
    def s_max(self):

        return ((self.q / self.coef[1]) * (self.coef[0] * absolute(self.i_c[0])**self.p / self.p + self.coef[1] * absolute(self.i_c[1])**self.q / self.q))**(1 / self.q)

    #This function returns the maximum value of the generalized cosine function y[1]
    def c_max(self):

        return ((self.p / self.coef[0]) * (self.coef[0] * absolute(self.i_c[0])**self.p / self.p + self.coef[1] * absolute(self.i_c[1])**self.q / self.q))**(1 / self.p)

    def sum_constant(self):

        return self.coef[0] * absolute(self.i_c[0])**self.p / self.p + self.coef[1] * absolute(self.i_c[1])**self.q / self.q

    #This function returns the frequency of the system
    def frequency(self):

        return (self.coef[0] * (self.p / self.coef[0])**(1 - 1/self.p) / (self.q / self.coef[1])**(1/self.q)) * ((self.coef[0] / self.p) * absolute(self.i_c[0])**self.p + (self.coef[1] / self.q) * absolute(self.i_c[1])**self.q)**(1 - 1/self.p - 1/self.q)

    #This function returns the period of system using the Euler Beta function
    def period(self):

        return 4 * beta(1/self.p, 1/self.q) / (self.q * self.frequency())

    #This function returns the phase shift using the incomplete Beta function
    def phase(self):

        if self.i_c[0] == 0:

            return 0.0

        else:

            UPPER_BOUND = self.i_c[1] / ((self.coef[0] / self.p) * absolute(self.i_c[0])**self.p + (self.coef[1] / self.q) * absolute(self.i_c[1])**self.q)**(1 - 1/self.p)

            return betainc(UPPER_BOUND, 1/self.p, 1/self.q) / (self.q * self.frequency())

    #This function returns the expressions of the derivatives dy[i] of each
    #respective function y[i] as defined by y[j]
    def func(self, t, y):

        return [ sign(y[1]) * absolute(y[1])**(self.p - 1), -sign(y[0]) * absolute(y[0])**(self.q - 1) ]

    #This function returns the solution to the system using solve_ivp. Because
    #we adhere to the spatial coordinate ordering, self.i_c = (x_0, y_0), we
    #must reverse this order in solve_ivp because it indexes by the given
    #ordering of dy. It returns the solution set such that it respects the
    #traditional Cartesian ordering, i.e. - t, x(t), y(t)
    def para_func(self, tspan = None, max_step = None, solver = None):

        tspan = tspan or self.period()

        max_step = max_step or tspan / 100

        solver = solver or 'RK45'

        SOLUTION = solve_ivp(self.func, [0, tspan], [self.i_c[1], self.i_c[0]], max_step = max_step, method = solver)

        return SOLUTION.t, SOLUTION.y[1], SOLUTION.y[0]

"""
================================================================================
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                                PARAPLOT CLASS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
================================================================================

The paraplot class plots and organizes the various output from the twopara
class.

    paraplot( twopara )

    twopara:

        The twopara argument is an object from the twopara class which caries
        all of the associated values for displaying the appropriate plots.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                    FUNCTIONS OF THE PARAPLOT CLASS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    gen_ellipse()

        This function returns a plot of the generalized ellipse with respect to
        the system described by (1). The generalized ellipse is the phase
        portrait representation of the system for some set of parameters.

            a * |x|^p / p + b * |y|^q / q = a * |x_0|^p / p + b * |y_0|^q / q

    para_plot( single_plot = None )

        This functions returns a plot of the generalized trigonometric
        functions with respect to solving (1). The default is to plot both
        parameterized functions on the same graph, but the user may select only
        one plot or the other by passing the argument 's' for the generalized
        sine or 'c' for the generalized cosine using the single_plot parameter

            single_plot = s        or      c       or      None for BOTH

================================================================================
================================================================================
"""

class paraplot:

    def __init__(self, twopara_object):

        self.twopara_object = twopara_object

    #This function plots the generalized ellipse which defines the phase
    #portrait for the specific set of initial conditions, which are plotted as
    #a phase shifted line along the circumference.
    def gen_ellipse(self, grid_step = None):

        grid_step = grid_step or [self.twopara_object.c_max() / 1000, self.twopara_object.s_max() / 1000]

        x = arange(-1.15*self.twopara_object.c_max(), 1.15*self.twopara_object.c_max(), grid_step[0])

        y = arange(-1.15*self.twopara_object.s_max(), 1.15*self.twopara_object.s_max(), grid_step[1])

        X, Y = meshgrid(x, y)

        Z = self.twopara_object.coef[0] * absolute(X)**self.twopara_object.p / self.twopara_object.p + self.twopara_object.coef[1] * absolute(Y)**self.twopara_object.q / self.twopara_object.q

        level = self.twopara_object.coef[0] * absolute(self.twopara_object.i_c[0])**self.twopara_object.p / self.twopara_object.p + self.twopara_object.coef[1] * absolute(self.twopara_object.i_c[1])**self.twopara_object.q / self.twopara_object.q

        plt.contour(X, Y, Z, colors='black', levels = [level])
        plt.plot([0,self.twopara_object.i_c[0]], [0,self.twopara_object.i_c[1]], 'k:', self.twopara_object.i_c[0], self.twopara_object.i_c[1], 'ko')
        plt.title('p = ' + str(self.twopara_object.p) + ', q = ' + str(self.twopara_object.q) + '\ncoefficients a = ' + str(self.twopara_object.coef[0]) + ' and b = ' + str(self.twopara_object.coef[1]) + ', initial conditions = ' + '(' + str(self.twopara_object.i_c[0]) + ',' + str(self.twopara_object.i_c[1]) + ')')
        plt.text(1.1*self.twopara_object.i_c[0], 1.1*self.twopara_object.i_c[1], '(' + str(self.twopara_object.i_c[0]) + ',' + str(self.twopara_object.i_c[1]) + ')')
        plt.xlabel('x')
        plt.ylabel('y')
        plt.show()

    #This function plots the generalized sine and cosine values of the specified
    #system. Default plots both. Changing single_plot to 's' or 'c' plots each
    #individually.
    def para_plot(self, tspan = None, max_step = None, single_plot = None):

        t, c, s = self.twopara_object.para_func(tspan, max_step)

        if single_plot == 's':

            plt.plot(t, s, 'k')
            plt.title('Generalized Sine function')
            plt.ylabel('y')

        elif single_plot == 'c':

            plt.plot(t, c, 'k--')
            plt.title('Generalized Cosine function')
            plt.ylabel('x')

        else:

            plt.plot(t, s, 'k', t, c, 'k--')
            plt.title('Generalized Sine and Cosine functions')
            plt.legend(['gen sine', 'gen cosine'], loc=3)

        plt.xlabel('t')
        plt.show()


from __future__ import division
import scipy.sparse as sp
import numpy as np

from domain import IrregularGrid, RegularGrid, OneDimensionalGrid

def compute_finite_difference_scheme_coeffs(evaluation_points, derivative_order):
    """
    computes the coefficients for a Finite Difference schemes to compute the d-th derivative based on evaluation points
    at u(x+p[i]*h). The resulting scheme has the form $\sum\limits_i coeff_i * u(x+p_i*h) / h^d$ with h defining the
    stepwidth, p_i defining the evaluation points and coeff_i being the returned FD coefficients.
    See http://web.media.mit.edu/~crtaylor/calculator.html
    :param evaluation_points: vector providing evaluation points
    :param derivative_order: derivative
    :return: coefficients corresponding to evaluation points
    """

    n = evaluation_points.shape[0]
    A = np.zeros([n,n])
    b = np.zeros(n)
    for i in range(n):
        for j in range(n):
            A[i,j] = evaluation_points[j] ** i
        b[i] = np.math.factorial(i)*int(i == derivative_order)

    return np.linalg.solve(A, b)

def second_derivative_matrix(grid, **kwargs):
    """
    creates the matrix operator u_xx = Au + R with Dirichlet or Neumann boundary conditions.
    Used e.g. for the 1D heat transport equation: u_t = c * u_xx
    u = [u_0, ... ,u_N]
    :param grid: grid where we create the finite difference operator for u_xx
    :type grid: OneDimensionalGrid
    :param kwargs: boundary conditions neumann/dirichlet_l/r correspond to neumann/dirichlet boundary conditions at left/right boundary
    :return: A,R
    """

    if type(grid) is RegularGrid:
        return regular_matrix_operator(grid.h, grid.N_gridpoints, **kwargs)
    elif type(grid) is IrregularGrid:
        return irregular_matrix_operator(grid.x, **kwargs)
    else:
        print("in second_derivative_matrix(...) unsupported grid type!")
        quit()

def irregular_matrix_operator(x, **kwargs):
    """
    creates the matrix operator u_xx = Au + R with Dirichlet or Neumann boundary conditions.
    Used e.g. for the 1D heat transport equation: u_t = c * u_xx
    u = [u_0, ... ,u_N]
    :param x: arbitrary 1D mesh
    :param kwargs: boundary conditions neumann/dirichlet_l/r correspond to neumann/dirichlet boundary conditions at left/right boundary
    :return: A,R
    """

    N = x.shape[0]
    l = np.zeros(N-1) # lower diagonal
    d = np.zeros(N) # diagonal
    u = np.zeros(N-1) # upper diagonal
    for i in range(1,N-1):  # iterate over inner nodes
        stencil = np.array([x[i-1]-x[i],x[i]-x[i],x[i+1]-x[i]])  # create three point stencil
        l[i-1], d[i], u[i] = compute_finite_difference_scheme_coeffs(stencil, 2)  # compute coefficients for three point stencil

    A = (sp.diags(l,-1) + sp.diags(d) + sp.diags(u,+1)).tolil()
    R = np.zeros(N)

    # add boundary conditions
    if 'neumann_l' in kwargs:
        # modify row and column corresponding to neumann BC at u_0
        h = x[1]-x[0]  # use h of leftmost cell
        A[0,0] = -2.0/h**2
        A[0,1] = +2.0/h**2
        R[0] = -2.0/h * kwargs.get('neumann_l')
    elif 'dirichlet_l' in kwargs:
        # remove row and column corresponding to dirichlet BC at u_0
        h = x[1]-x[0]  # use h of leftmost cell
        A[0,:] = 0
        A[:,0] = 0
        # manually add it to R
        R[1] = 1.0/h**2 * kwargs.get('dirichlet_l')
    else:
        print("in irregular_matrix_operator(...): insufficient boundary conditions at left boundary!")
        quit()

    if 'neumann_r' in kwargs:
        # modify row and column corresponding to neumann BC at u_N
        h = x[-2]-x[-1]  # use h of rightmost cell
        A[-1,-1] = -2.0/h**2
        A[-1,-2] = +2.0/h**2
        R[-1] = 2.0/h * kwargs.get('neumann_r')
    elif 'dirichlet_r' in kwargs:
        # remove row and column corresponding to dirichlet BC at u_N
        h = x[-2]-x[-1]  # use h of rightmost cell
        A[-1,:] = 0
        A[:,-1] = 0
        # manually add it to R
        R[-2] = 1.0/h**2 * kwargs.get('dirichlet_r')
    else:
        print("in irregular_matrix_operator(...): insufficient boundary conditions at right boundary!")
        quit()

    return A, R

def regular_matrix_operator(h, N, **kwargs):
    """
    creates the matrix operator u_xx = Au + R with Dirichlet or Neumann boundary conditions.
    Used e.g. for the 1D heat transport equation: u_t = c * u_xx
    u = [u_0, ... ,u_N]
    :param h: meshwidth
    :param N: number of gridpoints - 1 (including boundary condition gridpoints)
    :param kwargs: boundary conditions neumann/dirichlet_l/r correspond to neumann/dirichlet boundary conditions at left/right boundary
    :return: A,R
    """

    # second derivative u_xx
    A = 1.0/h**2 * (sp.eye(N, N, -1) - 2 * sp.eye(N, N) + sp.eye(N, N, 1)).tolil()
    R = 2.0/h*np.zeros(N)

    # add boundary conditions
    if 'neumann_l' in kwargs:
        # modify row and column corresponding to neumann BC at u_0
        A[0,0] = -2.0/h**2
        A[0,1] = +2.0/h**2
        R[0] = -2.0/h * kwargs.get('neumann_l')
    elif 'dirichlet_l' in kwargs:
        # remove row and column corresponding to dirichlet BC at u_0
        A[0,:] = 0
        A[:,0] = 0
        # manually add it to R
        R[1] = 1.0/h**2 * kwargs.get('dirichlet_l')
    else:
        print("in regular_matrix_operator(...): insufficient boundary conditions at left boundary!")
        quit()

    if 'neumann_r' in kwargs:
        # modify row and column corresponding to neumann BC at u_N
        A[-1,-1] = -2.0/h**2
        A[-1,-2] = +2.0/h**2
        R[-1] = 2.0/h * kwargs.get('neumann_r')
    elif 'dirichlet_r' in kwargs:
        # remove row and column corresponding to dirichlet BC at u_N
        A[-1,:] = 0
        A[:,-1] = 0
        # manually add it to R
        R[-2] = 1.0/h**2 * kwargs.get('dirichlet_r')
    else:
        print("in regular_matrix_operator(...): insufficient boundary conditions at right boundary!")
        quit()

    return A, R


# THIS IS USING CUBIC SPIRAL FOR TURNING
# Using quintic spiral for turning results in almost the same turning trajectory
import numpy as np

v = 1
dt = 0.1
num_st_pts = int(v/dt)
num_pts = 50

def cubic_spiral(theta_i, theta_f, n=50):
    x = np.linspace(0, 1, num=n)
    #-2*x**3 + 3*x**2
    # return (theta_f-theta_i)*(6*x**5 - 15*x**4 + 10*x**3) + theta_i   # Use this for quintic plot if needed
    return (theta_f-theta_i)*(-2*x**3 + 3*x**2) + theta_i

def straight(dist, curr_pose, n=num_st_pts):
    # dist is the straight distance to be travelled
    # the straight-line may be along x or y axis
    # curr_theta will determine the orientation
    # print(n)
    x0, y0, t0 = curr_pose
    xf, yf = x0 + dist*np.cos(t0), y0 + dist*np.sin(t0)
    x = (xf - x0) * np.linspace(0, 1, n) + x0
    y = (yf - y0) * np.linspace(0, 1, n) + y0
    return x, y, t0*np.ones_like(x)

def turn(change, curr_pose, n=num_pts):
     # adjust scaling constant for desired turn radius
     # change is the angle by which we want to turn, like 90 or -90
    #  print(n)
     x0, y0, t0 = curr_pose
     theta = cubic_spiral(t0, t0 + np.deg2rad(change), n)
     # cumsum() is basically integration
     x= x0 + np.cumsum(v*np.cos(theta)*dt)
     y= y0 + np.cumsum(v*np.sin(theta)*dt)
     return x, y, theta

def generate_trajectory(route, init_pose = (0, 0,np.pi/2)):
    curr_pose = init_pose
    func = {'straight': straight, 'turn': turn}
    x, y, t = np.array([]), np.array([]),np.array([])
    for manoeuvre, command in route:
        px, py, pt = func[manoeuvre](command, curr_pose)
        curr_pose = px[-1],py[-1],pt[-1]  # Pull last element of px, py, pt to get latest orientation
        # print(np.round(px[-1],2),np.round(py[-1],2),np.round(pt[-1],2))   # This is the final position after every route iteration
        # Add latest pose to array
        x = np.concatenate([x, px])
        y = np.concatenate([y, py])
        t = np.concatenate([t, pt])
    # print(x[-1], y[-1], t[-1])   # This is the final position
    return np.vstack([x, y, t])


import numpy as np
from numpy.linalg import inv

def polyfit2d(x, y, z, order):
    if order == 1: # Linear
        G = np.vstack([np.ones(len(x)), x, y]).T
    if order == 2: # BiLinear
        G = np.vstack([np.ones(len(x)), x, y, x*y]).T
    if order == 3: #BiQuadratic6
        G = np.vstack([np.ones(len(x)), x, y, x*y, x**2, y**2]).T
    if order == 4: #BiQuadratic9
        G = np.vstack([np.ones(len(x)), x, y, x*y, x**2, y**2, x**2 * y**2, x**2 * y, y**2 * x]).T
    if order == 5: #BiCubic
        G = np.vstack([np.ones(len(x)), x, y, x*y, x**2, y**2, x**3, y**3, x**2 * y, y**2 * x, x**3 * y, y**3 * x, x**2 * y**2, y**3 * x**3, y**3 * x**2, y**2 * x**3]).T

    m = np.matmul(inv(G), z)
    return m

def polyval2d(x, y, m):

    if len(m) == 3:
        z = np.dot(np.array([1, x, y]),m)
    if len(m) == 4:
        z = np.dot(np.array([1, x, y, x*y]),m)
    if len(m) == 6:
        z = np.dot(np.array([1, x, y, x*y, x**2, y**2]),m)
    if len(m) == 9:
        z = np.dot(np.array([1, x, y, x*y, x**2, y**2, x**2 * y**2, x**2 * y, y**2 * x]),m)
    if len(m) == 16:
        z = np.dot(np.array([1, x, y, x*y, x**2, y**2, x**3, y**3, x**2 * y, y**2 * x, x**3 * y, y**3 * x, x**2 * y**2, y**3 * x**3, y**3 * x**2, y**2 * x**3]),m)

    return z


# 逻辑斯谛回归模型
# 2020/09/04
import numpy as np

def sigmoid(z):
    if z >= 0:
        return 1.0 / (1 + np.exp(-z))
    else:
        return np.exp(z) / (1 + np.exp(z))

def logistic_regression(trainDataArr, trainLabelArr, max_iter=500):
    # 梯度步长
    h = 0.001

    w = np.zeros(trainDataArr.shape[1])
    # 迭代
    for epoch in range(max_iter):
        for i in range(len(trainDataArr)):
            xi = trainDataArr[i]
            yi = trainLabelArr[i]
            wx = np.dot(xi, w)
            w += h * (yi - sigmoid(wx)) * xi

        if epoch % 100 == 0:
            print(f"epoch = {epoch }\t w = {w}")
    return w

def score(X_test, y_test, w):
    errorCnt = 0
    for i in range(len(X_test)):
        xi = X_test[i]
        y = sigmoid(np.dot(xi, w))
        yi = y_test[i]
        if (y >= 0.5 and yi == 0) or (y < 0.5 and yi == 1):
            errorCnt += 1
    return 1 - errorCnt / len(X_test)

if __name__ == '__main__':
    from sklearn.datasets import load_breast_cancer
    from sklearn.model_selection import train_test_split
    # from sklearn.linear_model import LogisticRegression

    X, y = load_breast_cancer(return_X_y=True)
    X = X.tolist()
    for x in X:
        x.append(1)
    X = np.array(X)
    X_train, X_test, y_train, y_test = train_test_split(X, y)

    w = logistic_regression(X_train, y_train)
    print(f"准确率为: {score(X_test, y_test, w)}")


import numpy as np
import matplotlib.pyplot as plt
from math import *

# The Function we Solve
def f(x):
    y = x**4 + 2*x**3 - 5*x**2 - 7*x - 5
    return y

# (a,b): the interval we suspect to find the root
# N: number of repetitions
def RegulaFalsi(a, b, N):

    for i in range(N):
        y = (f(b)*a-f(a)*b)/(f(b)-f(a))
        if np.sign(f(a))*np.sign(f(y)) == -1: b = y
        else: a = y

    solution = y

    return solution

if __name__ == '__main__':

    # Visualize the Function
    x = np.linspace(1,3,100)
    y = f(x)

    plt.plot(x,y)
    plt.xlabel("x")
    plt.ylabel("y")
    plt.tight_layout
    plt.show()

    print(RegulaFalsi(1.5,3,30))


import numpy as np

def softmax(x):
    """Compute softmax values for each sets of scores in x."""
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum()

class SoftmaxLayer(object):

    def __init__(self, n_in, n_out):
        self.n_in = n_in
        self.n_out = n_out
        # Initialize weights and biases
        self.weights = np.zeros((n_in, n_out))
        self.biases = np.zeros(n_out)

    def calculate(self, input_images):
        self.input_images = input_images
        out = [(np.dot(im,self.weights) + self.biases) for im in input_images]
        self.out = [softmax(im) for im in out]
        return self.out

    def BP(self,eta, RefResults, previous_layer_out):
        # return delta using log-likehood cost function
        # and update weights / biases
        # 'next_layer_delta' value is not used, just to keep the same API for different layers
        self.delta = [(a-y) for a,y in zip(self.out,RefResults)]
        nabla_w = [np.dot(a.reshape(len(a),1),d.reshape(1,len(d))) for d,a in zip(self.delta,previous_layer_out)]
        nabla_w = np.sum(nabla_w,axis=0) / len(nabla_w)
        nabla_b = np.sum(self.delta,axis=0) / len(self.delta)

        # update network parameters
        self.weights = self.weights - eta * nabla_w
        self.biases = self.biases - eta * nabla_b

        return self.delta


import ode
import numpy as np
import matplotlib.pyplot as plt

def fun(x,y):
    ydot = y - x*x + 1.0
    return ydot

def main():
    xn   = np.linspace( 0.0, 5.0, 20 )     # Grid
    y0   = np.array( [ 0.5 ] )            # Initial condition
    y_ef = ode.euler( fun, xn, y0 )       # Forward Euler
    y_mp = ode.midpoint( fun, xn, y0 )    # Explicit Midpoint
    y_rk = ode.rk4( fun, xn, y0 )         # Runge-Kutta 4
    y_an = xn**2 + 2.0*xn + 1.0 - 0.5*np.exp(xn) # Analytical

    for i in range(0,xn.size):
        print xn[i], y_an[i], y_ef[i,0], y_mp[i,0], y_rk[i,0]

    plt.figure(1)
    plt.plot( xn, y_ef, 'ro-', label='Forward Euler (1st)' )
    plt.plot( xn, y_mp, 'go-', label='Explicit Mid-Point (2nd)' )
    plt.plot( xn, y_rk, 'bx-', label='Runge-Kutta (4th)' )
    plt.plot( xn, y_an, 'k-',  label='Analytical Solution' )
    plt.xlabel('x')
    plt.ylabel('y')
    plt.legend(loc=3)
    plt.savefig('ex02_ode_solution.png')
    plt.show()

if __name__ == '__main__':
   main()


import time

import numpy as np
from scipy.optimize import curve_fit

import matplotlib.pyplot as plt
import seaborn as sns
seapal = sns.color_palette()

from IPython import embed

def merge_and_count_split_inversions(lst, c=None):

    if len(lst) == 1:
        return lst, c

    if c is None:
        c = 0

    idx = len(lst)//2

    sub1, c = merge_and_count_split_inversions(lst[:idx], c)
    sub2, c = merge_and_count_split_inversions(lst[idx:], c)

    i = j = 0

    output = [None]*len(lst)
    for k in range(len(lst)):

        if i == len(sub1):
            output[k:] = sub2[j:]
            break

        if j == len(sub2):
            output[k:] = sub1[i:]
            break

        if sub1[i] <= sub2[j]:
            output[k] = sub1[i]
            i += 1
        else:
            output[k] = sub2[j]
            j += 1
            c += len(sub1) - i

    return output, c

def complexity(n, p):
    return p*n*(np.log2(n) + 1)

if __name__ == "__main__":

    timings = 50        # repetitions
    max_npower = 15     # max size of array: 2**max_npower

    n_list = np.logspace(2, max_npower, 20, dtype=int, base=2)
    meantimes = []
    stdtimes = []

    loop_time = time.time()
    for n in n_list:

        times = []

        for t in range(timings):
            lst = np.random.randint(0,n,n)
            start = time.time()
            sorted_list, c = merge_and_count_split_inversions(lst)
            times.append(time.time()-start)

        meantimes.append(np.mean(times))
        stdtimes.append(np.std(times))

    print("Looping took {:g} s".format(time.time()-loop_time))

    # Fit theoretical complexity:
    p = curve_fit(complexity, n_list, meantimes, sigma=stdtimes)[0][0]
    x = np.linspace(0, 2**max_npower, 100)
    y = complexity(x, p)

    fig, ax = plt.subplots(figsize=(7,4))

    # Plot measurements:
    ax.errorbar(n_list, meantimes, stdtimes, fmt='none', ecolor=seapal[0])
    ax.plot(n_list, meantimes, ls='', marker="o", ms=6,
                label='Measurements')

    # Plot complexity:
    ax.plot(x, y, alpha=.7, label='Theoretical complexity')

    ax.set_title(("Merge sort and counting split inversion running time ({} "
                  "repetitions)").format(timings))
    ax.set_xlabel("Array size")
    ax.set_ylabel("Running time")
    ax.set_xscale('log', basex=2)

    plt.legend(loc='upper left')

    plt.tight_layout()
    #plt.show(block=False)
    plt.savefig('../../figures/week1/inversioncount.svg')

    #embed()


import numpy as np
import time

def simulateCircleTask(iterations):
    """
        Simulates the following:
        Sample three points uniformly on a circle. Connect all three points to a triangle.
        What is the likelihhod that the center of the circle is covered in the area of the triangle?

        Args:
        iterations: Number of experiements

        Returns:
        Prints the occupied time, as well as the approximated probability of above question.

    """
    start_time = time.time()
    start = 0
    ende = 2*np.pi
    inside = 0
    outside = 0
    for k in range(0, iterations):
        a = np.random.uniform(start, ende)
        b = np.random.uniform(start, ende)
        c = np.mod(np.random.uniform(start, ende) + np.pi, ende)
        if(np.abs(a - b) <= np.pi):
            q1 = np.minimum(a, b)
            q2 = np.maximum(a, b)
            q3 = 0
            q4 = 0
        else:
            q1 = np.maximum(a, b)
            q2 = ende
            q3 = start
            q4 = np.minimum(a, b)
        if((c >= q1 and c <= q2) or (c >= q3 and c <= q4)):
            inside += 1
        else:
            outside += 1
    end_time = time.time()
    print("Elapsed Time: " + str(end_time - start_time) + " seconds")
    print("Approximated Probability: " + str(float(inside) / float(iterations)))

simulateCircleTask(100000)


import numpy as np
from sklearn.decomposition import PCA

numbers = 4
dimension = 3
X_origin = np.random.rand(numbers, dimension)
X_new = X - np.mean(X_origin, axis=0)
varify = np.allclose(X_new.mean(axis=0), np.zeros(X_new.shape[1]))

C = np.matmul(X_new.T, X_new) / (X_new.shape[0] - 1)
eig_vals, eig_vecs = np.linalg.eig(C)
X_pca = np.matmul(X_new, eig_vecs)

U, S, Vh = np.linalg.svd(X_new, full_matrices=False, compute_uv=True)
varify = np.allclose(eig_vals, np.square(S) / (X_new.shape[0] - 1))
X_pca_svd = np.matmul(X_new, Vh.T)

pca = PCA(3)
X_pca_skl = pca.fit_transform(X)

print('origin')
print(X_new)

print('manual PCA')
print(X_pca)

print('SVD PCA')
print(X_pca_svd)

print('sklern PCA')
print(X_pca_skl)


# Create 2 new lists height and weight
height = [1.87,  1.87, 1.82, 1.91, 1.90, 1.85]
weight = [81.65, 97.52, 95.25, 92.98, 86.18, 88.45]

import numpy as np

# Create 2 numpy arrays from height and weight
np_height = np.array(height)
np_weight = np.array(weight)

print(type(np_height))

# Calculate bmi
bmi = np_weight / np_height ** 2

# Print the result
print(bmi)

# For a boolean response
bmi > 23

# Print only those observations above 23
bmi[bmi > 23]

# exercise
# using numpy to convert kg in lbs
weight_kg = [81.65, 97.52, 95.25, 92.98, 86.18, 88.45]

# creating a numpy array
np_weight_kg = np.array(weight_kg)

# creating a new variable to store the values of kg converted to lbs
np_weight_lbs = np_weight_kg * 2.2

# printing the value in lbs
print("Value in LBS: ",np_weight_lbs)


import numpy as np

# fitSolution {1,2,3,4}
fitSolution = 1

# how many times of experiment to conduct
EXTIMES = 1

# two rate (parameter)
CROSSOVER_RATE = 0.5
MUTATION_RATE = 0.08

# to record every max fitness in every iteration
maxFitnessIter = []

# in the crossover_rate experiment, record every max fitness
maxFitnessCrs = []

# in the mutation_rate experiment, record every max fitness
maxFitnessMut = []

# size of DNA (1111)
M = 4
# size of  population
P = 100
# number of generations
N = 100

# domain of x1,x2,x3
xDomain = [0,15]

def getFitness(pop):
    x1, x2, x3 = encodeDNA(pop)
    ans = compFitness(x1, x2, x3)

    # Fitness solution1: move linearly
    if (fitSolution == 1):
        return ans + 1e-3 - np.min(ans)

    # Fitness solution2: square
    if (fitSolution ==2):
        return ans ** 2

    # Fitness solution3: root + move linearly
    if (fitSolution ==3):
        return (ans + 1e-3 - np.min(ans)) ** 0.5

    # Fitness solution4: minimal
    if (fitSolution ==4):
        return -(ans - np.max(ans) - 1e-3)

def compFitness(x1, x2, x3):
    return (2*x1*x1 - 3*x2*x2 - 4*x1 + 5*x2 + x3)

# binary to decimal
def encodeDNA(pop):
    #cut a line into 3 pieces, x1,x2,x3
    x1Pop = pop[:, 0:M]
    x2Pop = pop[:, M:M * 2]
    x3Pop = pop[:, M * 2:]

    # pop:(P,M)*(M,1) --> (P,1)   2 to 10
    x1 = x1Pop.dot(2 ** np.arange(M)[::-1]) / float(2 ** M - 1) * (xDomain[1] - xDomain[0]) + xDomain[0]
    x2 = x2Pop.dot(2 ** np.arange(M)[::-1]) / float(2 ** M - 1) * (xDomain[1] - xDomain[0]) + xDomain[0]
    x3 = x3Pop.dot(2 ** np.arange(M)[::-1]) / float(2 ** M - 1) * (xDomain[1] - xDomain[0]) + xDomain[0]
    return x1,x2,x3

def mutation(child, MUTATION_RATE):
    if np.random.rand() < MUTATION_RATE:
        # decide which point to be mutated by randomly pick up
        mutate_point = np.random.randint(0, M * 3)
        # reverse binaryly
        child[mutate_point] = child[mutate_point] ^ 1

def crossover_and_mutation(pop, CROSSOVER_RATE):
    new_pop = []
    # pick a father and a mother to corssover
    for father in pop:
        child = father
        # crossover happens under CROSS_RATE
        if np.random.rand() < CROSSOVER_RATE:
            mother = pop[np.random.randint(P)]
            # select the crossover point randomly
            cross_points = np.random.randint(low=0, high=M * 3)
            # the latter part is from mother
            child[cross_points:] = mother[cross_points:]
        # every child has possbility to mutate
        mutation(child,MUTATION_RATE)
        # new population
        new_pop.append(child)

    return new_pop

def roulette(pop, fitness):
    # roulette by pick index with weight(fitness/fitness.sum())
    idx = np.random.choice(np.arange(P), size=P, replace=True,
                           p=fitness/fitness.sum())
    return pop[idx]

    # sumFits = sum(fitness)
    # # generate a random number
    # rndPoint = np.random.uniform(0, sumFits)
    # # calculate the index: O(N)
    # accumulator = 0.0
    # inx = []
    # for ind, val in enumerate(fitness):
    #     accumulator += val
    #     if accumulator >= rndPoint:
    #     	inx.append(ind)
    #     	return pop[inx]
    #     else:
    #     	inx.append(ind)

def printPop(pop):
    fitness = getFitness(pop)
    max_fitness_index = np.argmax(fitness)
    x1, x2, x3 = encodeDNA(pop)
    x1=x1[max_fitness_index]
    x2=x2[max_fitness_index]
    x3=x3[max_fitness_index]

    print(pop)
    print("x1: ",x1)
    print("x2: ",x2)
    print("x3: ",x3)
    print("Max Fitness: ", compFitness(x1,x2,x3))

if __name__ == "__main__":

    for _ in range(EXTIMES):
        # generate population matrix with random number
        pop = np.random.randint(0, 2, size=(P, M * 3))
        # run iterations
        for i in range(N):
            pop = np.array(crossover_and_mutation(pop, CROSSOVER_RATE))
            fitness = getFitness(pop)
            pop = roulette(pop, fitness)

            if EXTIMES==1:
                max_fitness_index = np.argmax(fitness)
                x1, x2, x3 = encodeDNA(pop)
                x1=x1[max_fitness_index]
                x2=x2[max_fitness_index]
                x3=x3[max_fitness_index]
                maxFitnessIter.append(compFitness(x1,x2,x3))

        if EXTIMES==1:
            print(maxFitnessIter)
        else:
            fitness = getFitness(pop)
            max_fitness_index = np.argmax(fitness)
            x1, x2, x3 = encodeDNA(pop)
            x1=x1[max_fitness_index]
            x2=x2[max_fitness_index]
            x3=x3[max_fitness_index]
            maxFitnessCrs.append(compFitness(x1,x2,x3))
    if EXTIMES!=1:
        print(maxFitnessCrs)


import csv
import numpy as np

from numpy import sqrt, exp, pi

from scipy.special import erfcinv, betaincinv
from scipy.stats.distributions import  t

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

path = '/home/renato/Dropbox/Machine Learning/PLD/VaR/'

#################################################################
#################################################################
def csv_to_array(filename, type_):
	with open(filename, 'r') as f:
		next(f)
		reader = csv.reader(f)
		list_ = list(reader)
	f.close()

	return np.array(list_).astype(type_)

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

def gaussian(x, K, x0, sigma):
    return K * exp(-(x-x0)**2/(2*sigma**2))

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

def VaR_gaussian(quantity, mean, stddev, alpha, log_returns = True):
    var_log = mean - sqrt(2) * stddev * erfcinv(2 * alpha)

    if log_returns == False:
        var = var_log
    else:
        var = quantity * (exp(var_log) -1)

    return var

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

def CVaR_gaussian(quantity, mean, stddev, alpha, log_returns = True):

    K = 2 * mean - 2 * alpha * mean + sqrt(2 / pi) * stddev * exp(erfcinv(2 * alpha)**2)
    N = 2 - 2 * alpha

    cvar_log = K / N

    if log_returns == False:
        cvar = cvar_log
    else:
        cvar = quantity * (exp(cvar_log) - 1)

    return cvar

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

def student_t(x, K, mean, stddev, nu):
    return K * t.pdf(x, nu, mean, stddev)

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

def VaR_T(mean, stddev, nu, alpha):
    var = mean - stddev * sqrt(nu) * sqrt(-1 + 1/betaincinv(nu / 2, 1/2, 2 * alpha))
    return var

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


# Example of polynomial regression on synthetic dataset

import numpy as np
import pandas as pd
import seaborn as sn
import matplotlib.pyplot as plt

from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.linear_model import Ridge
from sklearn.preprocessing import MinMaxScaler
from sklearn.linear_model import Lasso
from sklearn.preprocessing import PolynomialFeatures

# synthetic dataset for more complex regression
from sklearn.datasets import make_friedman1
plt.figure()
plt.title('Complex regression problem with one input variable')
X_F1, y_F1 = make_friedman1(n_samples = 100, n_features = 7, random_state=0)
plt.scatter(X_F1[:, 2], y_F1, marker= 'o', s=50)
plt.show()

# make train test split
X_train, X_test, y_train, y_test = train_test_split(X_F1, y_F1, random_state = 0)

# standard linear regression
linreg = LinearRegression().fit(X_train, y_train)

print('linear model coeff (w): {}'.format(linreg.coef_))
print('linear model intercept (b): {:.3f}'.format(linreg.intercept_))
print('R-squared score (training): {:.3f}'.format(linreg.score(X_train, y_train)))
print('R-squared score (test): {:.3f}'.format(linreg.score(X_test, y_test)))

# add polynomial features to dataset and do polynomial regression
print('\nNow we transform the original input data to add\n\
polynomial features up to degree 2 (quadratic)\n')
poly = PolynomialFeatures(degree=2)
X_F1_poly = poly.fit_transform(X_F1)

# do polynomial regression
X_train, X_test, y_train, y_test = train_test_split(X_F1_poly, y_F1, random_state = 0)
linreg = LinearRegression().fit(X_train, y_train)

print('(poly deg 2) linear model coeff (w):\n{}'.format(linreg.coef_))
print('(poly deg 2) linear model intercept (b): {:.3f}'.format(linreg.intercept_))
print('(poly deg 2) R-squared score (training): {:.3f}'.format(linreg.score(X_train, y_train)))
print('(poly deg 2) R-squared score (test): {:.3f}\n'.format(linreg.score(X_test, y_test)))

# polynomial regression with ridge regularization
print('\nAddition of many polynomial features often leads to\n\
overfitting, so we often use polynomial features in combination\n\
with regression that has a regularization penalty, like ridge\n\
regression.\n')

X_train, X_test, y_train, y_test = train_test_split(X_F1_poly, y_F1, random_state = 0)
linreg = Ridge().fit(X_train, y_train)

print('(poly deg 2 + ridge) linear model coeff (w):\n{}'.format(linreg.coef_))
print('(poly deg 2 + ridge) linear model intercept (b): {:.3f}'.format(linreg.intercept_))
print('(poly deg 2 + ridge) R-squared score (training): {:.3f}'.format(linreg.score(X_train, y_train)))
print('(poly deg 2 + ridge) R-squared score (test): {:.3f}'.format(linreg.score(X_test, y_test)))


# Bellman-Ford Algorithm
import numpy as np
import math

def Path_Relaxation(u, v, w, d, parent):
	# u and v are positions in d and w that represent nodes.
	# d is a list distances and each node is represented by its index in d
	# w is a matrix of weights
	if d[v] > d[u] + w[u,v]:
		d[v] = d[u] + w[u,v]
		parent[v] = u

	return d, parent

def Bellman_Ford(adjacency_list, source, out = None):
	w = adjacency_list
	if out is None:
		print('Distance, Parent')
	parent = []
	d = []
	neg = True
	for i in range(len(w[0,:])):
		parent.append(None)
		d.append(math.inf)
	d[source] = 0
	for i in range(len(d)):
		for u in range(len(d)):
			for v in range(len(d)):
				d, parent = Path_Relaxation(u,v,w,d, parent)
				if out is None:
					print(d, parent)
	for u in range(len(d)):
		for v in range(len(d)):
			if d[v] <= d[u] + w[u,v]:
				neg = False
	return neg, d, parent

def All_Paths_Bellman_Ford(adjacency_list):
	d_lists = []
	p_lists = []
	temp_d = []
	temp_p = []
	f = True
	for i in range(len(adjacency_list)):
		flag, temp_d, temp_p = Bellman_Ford(adjacency_list, i, out= False)
		d_lists.append(np.array(temp_d))
		p_lists.append(np.array(temp_p))
		if flag == False:
			f = False
	return f, np.array(d_lists), np.array(p_lists)

w1 = np.array([[0,-2,3,math.inf],[math.inf,0,-10,-1], [-4,-4,0,-4], [11, math.inf, math.inf, 0]])
w2 = np.array([[0,-2,3,math.inf, math.inf],[math.inf,0,-10,-1,math.inf], [-4,-4,0,-4, 5], [11, math.inf, math.inf, 0, 5], [-1,-1,-1,-1,0]])

print('Shortest path from Node 0')
flag, d, parent = Bellman_Ford(w1,0)
print(d)
print(parent)
print(flag)
print('Shortest path from Node 0')
flag, d, parent = Bellman_Ford(w2, 0)
print(d)
print(parent)
print(flag)
print('All Paths for the first list')
flag, d, parent = All_Paths_Bellman_Ford(w1)
print(d)
print(flag)
print('All paths for the second list')
flag, d, parent = All_Paths_Bellman_Ford(w2)
print(d)
print(flag)


# coding: utf-8
"""Sample Variance Example

@author: Liz
@modified: 11-20-2016

"""

import numpy as np

data1 = np.array([1, 5, 18])
data2 = np.array([2, 4, 6, 9, 12, 15, 20])

data1_ssd = sum(i ** 2 for i in (data1 - np.ones_like(data1) * data1.mean()))
data2_ssd = sum(i ** 2 for i in (data2 - np.ones_like(data2) * data2.mean()))

data1_sd = (data1_ssd / (len(data1) - 1)) ** 0.5
data2_sd = (data2_ssd / (len(data2) - 1)) ** 0.5

print("standard deviation 1: {}".format(data1_sd))
print("standard deviation 2: {}".format(data2_sd))


import numpy as _np
from math import pi as _pi
from fractions import Fraction as _Fraction

def generate_pi_axis(start=0, end=2 * _pi, jumps=5):
    """
    Generates an evenly spaced scale, in Pi multiplicities.
    :param start: A number to start the scale.
    :param end: A number to end the scale.
    :param jumps: Number of slices of the scale.
    :return: A tuple made of two lists, one for actual numbers and one for nicely formatted LaTeX scale.
    """

    jump_size = (end - start) / (jumps - 1)
    y_ticks = []
    y_ticks_labels = []
    for y_tick in range(0, jumps):
        cur_tick = start + jump_size * y_tick
        y_ticks.append(cur_tick)
        cur_tick = _Fraction(cur_tick / _pi).limit_denominator(max_denominator=10)
        if cur_tick.numerator == 0:
            y_ticks_labels.append("0")
        elif cur_tick.denominator == 1:
            y_ticks_labels.append("$%s\\pi$" % (str(cur_tick.numerator)))
        elif cur_tick.numerator < 0:
            y_ticks_labels.append("$-\\frac{%s}{%s}\\pi$" % (str(-cur_tick.numerator), str(cur_tick.denominator)))
        else:
            y_ticks_labels.append("$\\frac{%s}{%s}\\pi$" % (str(cur_tick.numerator), str(cur_tick.denominator)))
    return y_ticks, y_ticks_labels

def expand_linspace(lin_min, lin_max, chunks):
    """
    Uniformly expands a numpy linspace by 10%.
    :param lin_min: A number to start the scale.
    :param lin_max: A number to end the scale.
    :param chunks: Number of slices of the scale.
    :return: Expanded numpy linspace.
    """
    return _np.linspace(lin_min - _np.abs(lin_min * 0.1), lin_max + _np.abs(lin_max * 0.1), chunks)


"""Code for KMeans Clustering Algorithm -> WIP"""
from sklearn import datasets
import random

from typing import List, Callable
import numpy as np
import math
import collections

random.seed(45)

class KMeansClassifier:
	def __init__(self, k, m_iter=50):
		self.k = k
		self.m_iter = m_iter

	def fit(self, data, init_method: str = "random") -> int:
		""" given a chunk of data first we classify/fit all points """
		initialization_function = self._validate_init_method(init_method.lower())
		cluster_locs = initialization_function(self.k, data)
		# print("starting locations", cluster_locs)

		for itr in range(self.m_iter):
			assigned_clusters = [self._get_cluster(point, cluster_locs) for point in data]

			reassigned_cluster_centriods = self._get_cluster_centriods(assigned_clusters, data)

			if all([np.array_equal(x,y) for x,y in zip(reassigned_cluster_centriods, cluster_locs)]):
				print(f"Exit Criterion Met, Exiting at Iter: {itr}")
				self.fit_data, self.centriods = assigned_clusters, cluster_locs
				return 1

			cluster_locs = reassigned_cluster_centriods

		self.fit_data, self.centriods = assigned_clusters, cluster_locs
		return 1

	def _get_cluster_centriods(self, assigned_clusters, data):
		new_clust_locs = []

		for i in range(self.k):
			subset_cluster = np.array([data_i for point, data_i in zip(assigned_clusters, data) if point==i])

			if subset_cluster.size:
				new_clust_locs.append(np.mean(subset_cluster, axis=0))
			else:
				new_clust_locs.append([0.0]*data.shape[1])

		return new_clust_locs

	def _get_cluster(self, point, cluster_locations):
		clulster_distances = []
		for cluster_loc in cluster_locations:
			clulster_distances.append(KMeansClassifier.euclidean_distance(point, cluster_loc))

		return np.argmin(clulster_distances)

	def _validate_init_method(self, method: str):
		if method in VALID_METHODS:
			return VALID_METHODS[method]
		else:
			raise ValueError(f"Invalid method provided: {method}")

	@classmethod
	def euclidean_distance(self, point_a, point_b):
		return math.sqrt(sum([(a-b)**2 for a, b in zip(point_a, point_b)]))

	@classmethod
	def _random_points(cls, k, data) -> List[int]:
		#max_, min_ = np.max(data[:,0]) + 1, np.min(data[:,0])
		min_, max_ = 0, 10
		depth = data.shape[1]
		return [np.array([random.randint(int(min_), int(max_)) for i_d in range(depth)]) for ki in range(k)]

VALID_METHODS = {"random": KMeansClassifier._random_points}

# Import some data to play with
iris = datasets.load_iris()
x = iris.data  # we only take the first two features.
y = iris.target

classifier = KMeansClassifier(len(collections.Counter(y)))
classifier.fit(x, "random")

# Labels
print(list(y))
# Predicted
print(classifier.fit_data)


# from __future__ import division

import os, sys
import numpy as np
import math
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from matplotlib.colors import LogNorm
import matplotlib as mpl

from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib.legend_handler import HandlerLine2D

from scipy.constants import pi
from scipy.constants import speed_of_light as clight
from scipy.constants import epsilon_0 as eps0
from scipy.constants import mu_0 as mu0
from scipy.constants import elementary_charge as qe
from scipy.constants import electron_mass as me
from scipy.constants import proton_mass as mp
from scipy.constants import Boltzmann as kB

import scipy.integrate as integrate
from scipy.integrate import quad, nquad, dblquad

#
# 1D integrals (function integrate.quad)
#

#
#  1) int_0^inf exp(-x^2):
#
def integrand1(x):
   return np.exp(-x**2)

result1=2./np.sqrt(pi)*quad(integrand1,0,np.inf)[0]
print 'result1=%e' % result1

# result1=1.00000+00, i.e.OK!

result2=2./np.sqrt(pi)*quad(lambda x: integrand1(x), 0, np.inf)[0]
print 'result2=%e' % result2

# result2=1.00000+00, i.e.OK!

#
#  2) sqrt(pi)/2.*(int_0^inf exp(-x^2)*erf(x)):
#
def integrand3(x):
   return np.exp(-x**2)*math.erf(x)

result3=np.sqrt(pi)/2.*quad(integrand3,0,np.inf)[0]
print 'result3=%e' % result3

# result3=3.926991-01, i.e. OK (see example 2) for 2D integation)

#
# 1D integral with parameter (function quad)
#
#  3) sqrt(pi/a)/2.*(int_0^inf exp(-x^2)*erf(sqrt(a)*x)):
#     if a=1 example moves to examples 2) for 1D):
#

# Repetition without parameter, but using quad inside def (works!):
#
# def integrand3a(x):
#    return np.exp(-x**2)*math.erf(x)
#
# def int_integrand3a():
#    return quad(integrand3a,0,np.inf)[0]
#
# result3a=np.sqrt(pi)/2.*int_integrand3a()
# print 'result3a=%e' % result3a

# Now with parameter:
#
def integrand3a(x,a):
   return np.exp(-x**2)*math.erf(np.sqrt(a)*x)

def int_integrand3a(a):
   intgrl=quad(lambda x: integrand3a(x,a), 0, np.inf)[0]
   return np.sqrt(pi/a)/2.*intgrl

for p in (0.5,1.0,1.5):
   result3a=int_integrand3a(p)
   print 'result3a=%e' % result3a

#
# 2D integrals (function integrate.dblquad)
#

#
#  1) int_0^inf exp(-x^2-y^2):
#
def integrand4(x,y):
   return np.exp(-x**2-y**2)

result4=4./pi*dblquad(lambda x,y: integrand4(x,y), 0, np.inf, \
                      lambda x: 0, lambda x: np.inf)[0]
print 'result4=%e' % result4

#
#  2) int_x_0^inf int_y_0^x exp(-x^2-y^2)
#     (= sqrt(pi)/2*(int_0^inf exp(-x^2)*erf(x))):
#
timeStart=os.times()
for i in range(10):
   result5=dblquad(lambda x,y: integrand4(x,y), 0, np.inf, \
                   lambda x: 0, lambda x: x)[0]
timeEnd=os.times()
print 'result5=%e' % result5
#
# result5=3.926991-01, i.e. OK (see example 2) for 1D integation)
#

# "time games": os.times() returns tuple with CPU time, system time,
# children's CPU time, children's system time, and elapsed time (from
# year's begin  ?); all times in seconds

print 'timeStart=',timeStart
print 'timeEnd=',timeEnd
delta1=float(timeEnd[0])-float(timeStart[0])   # CPU time
delta2=float(timeEnd[4])-float(timeStart[4])   # elapsed real time
print 'delta1=%e, delta2=%e' % (delta1, delta2)
#
#delta1=1.121000e+01, delta2=1.119000e+01 (for 1000 cycles)
#

#
# 2D integrals (function integrate.nquad)
#

#
#  3) int_x_0^inf int_y_0^x exp(-x^2-y^2)
#     (= sqrt(pi)/2*(int_0^inf exp(-x^2)*erf(x))):
#
def integrand6(x,y):
   return np.exp(-x**2-y**2)

def bounds_x():
   return [0,np.inf]

def bounds_y(x):
   return [0,x]

timeStart=os.times()
for i in range(10):
   result6=nquad(integrand6,[bounds_y,bounds_x])[0]
timeEnd=os.times()
print 'result6=%e' % result6
#
# result6=3.926991-01, i.e. OK (see example 2) for 1D integation)
#
print 'timeStart=',timeStart
print 'timeEnd=',timeEnd
delta3=float(timeEnd[0])-float(timeStart[0])   # CPU time
delta4=float(timeEnd[4])-float(timeStart[4])   # elapsed real time
print 'delta3=%e, delta4=%e' % (delta3, delta4)
#
#delta3=1.149000e+01, delta4=1.149000e+01 (for 1000 cycles)
#
#--------------------------------------------
#  So, nquad some slower than dblquad!!!
#--------------------------------------------

#
# 2D integrals with parameter (function integrate.dblquad)
#

#
#  1) int_x_0^inf int_y_0^x exp(-x^2-a*y^2)
#     (= sqrt(pi/a)/2*(int_0^inf exp(-x^2)*erf(sqrt(a)*x));
#     if a=1 example moves to examples 2),3) for 2D):
#

sys.exit()


from __future__ import division
from scipy.misc import factorial#, floor, sqrt
from numpy import arange, floor, sqrt, round

__all__ = ['wigner3j', 'wigner6j']

def TriaCoeff(a,b,c):
    # Calculating the triangle coefficient
    return factorial(a+b-c)*factorial(a-b+c)*factorial(-a+b+c)/(factorial(a+b+c+1))

def wigner3j(j1,j2,j3,m1,m2,m3):
    """Return the value of the 3-j symbol for the given values of j and m using the Racah formula [1].
        / j1 j2 j3 \
        |          |
        \ m1 m2 m3 /
        Reference: Wigner 3j-Symbol entry of Eric Weinstein's Mathworld:
        http://mathworld.wolfram.com/Wigner3j-Symbol.html

        Usage
        wigner = Wigner3j(j1,j2,j3,m1,m2,m3)

    Args:
        j1 (float): j1
        j2 (float): j2
        j3 (float): j3
        m1 (float): m1
        m2 (float): m2
        m3 (float): m3

    Returns:
        float: value of the 3-j symbol
    """
    # Error checking
    if ( ( 2*j1 != floor(2*j1) ) | ( 2*j2 != floor(2*j2) ) | ( 2*j3 != floor(2*j3) ) | ( 2*m1 != floor(2*m1) ) | ( 2*m2 != floor(2*m2) ) | ( 2*m3 != floor(2*m3) ) ):
        print('All arguments must be integers or half-integers.')
        return -1

    # Additional check if the sum of the second row equals zero
    if ( m1+m2+m3 != 0 ):
        print('3j-Symbol unphysical')
        return 0

    if ( j1 - m1 != floor ( j1 - m1 ) ):
        print('2*j1 and 2*m1 must have the same parity')
        return 0

    if ( j2 - m2 != floor ( j2 - m2 ) ):
        print('2*j2 and 2*m2 must have the same parity')
        return; 0

    if ( j3 - m3 != floor ( j3 - m3 ) ):
        print('2*j3 and 2*m3 must have the same parity')
        return 0

    if ( j3 > j1 + j2)  | ( j3 < abs(j1 - j2) ):
        print('j3 is out of bounds.')
        return 0

    if abs(m1) > j1:
        print('m1 is out of bounds.')
        return 0

    if abs(m2) > j2:
        print('m2 is out of bounds.')
        return 0

    if abs(m3) > j3:
        print('m3 is out of bounds.')
        return 0

    t1 = j2 - m1 - j3
    t2 = j1 + m2 - j3
    t3 = j1 + j2 - j3
    t4 = j1 - m1
    t5 = j2 + m2

    tmin = max( 0, max( t1, t2 ) )
    tmax = min( t3, min( t4, t5 ) )
    tvec = arange(tmin, tmax+1, 1)

    wigner = 0

    for t in tvec:
        wigner += (-1)**t / ( factorial(t) * factorial(t-t1) * factorial(t-t2) * factorial(t3-t) * factorial(t4-t) * factorial(t5-t) )

    return wigner * (-1)**(j1-j2-m3) * sqrt( factorial(j1+j2-j3) * factorial(j1-j2+j3) * factorial(-j1+j2+j3) / factorial(j1+j2+j3+1) * factorial(j1+m1) * factorial(j1-m1) * factorial(j2+m2) * factorial(j2-m2) * factorial(j3+m3) * factorial(j3-m3) )

def wigner6j(j1,j2,j3,J1,J2,J3):
    """Return the value of the 6-j symbol for the given values of j and m using the Racah formula.
        / j1 j2 j3 \
        <          >
        \ J1 J2 J3 /
        Based upon Wigner3j.m from David Terr, Raytheon
        Reference: http://mathworld.wolfram.com/Wigner6j-Symbol.html

        Usage
        wigner = Wigner6j(j1,j2,j3,J1,J2,J3)

    Args:
        j1 (float): j1
        j2 (float): j2
        j3 (float): j3
        J1 (float): J1
        J2 (float): J2
        J3 (float): J3

    Returns:
        float: value of the 6-j symbol
    """
    # Check that the js and Js are only integer or half integer
    if ( ( 2*j1 != round(2*j1) ) | ( 2*j2 != round(2*j2) ) | ( 2*j2 != round(2*j2) ) | ( 2*J1 != round(2*J1) ) | ( 2*J2 != round(2*J2) ) | ( 2*J3 != round(2*J3) ) ):
        print('All arguments must be integers or half-integers.')
        return -1

# Check if the 4 triads ( (j1 j2 j3), (j1 J2 J3), (J1 j2 J3), (J1 J2 j3) ) satisfy the triangular inequalities
    if ( ( abs(j1-j2) > j3 ) | ( j1+j2 < j3 ) | ( abs(j1-J2) > J3 ) | ( j1+J2 < J3 ) | ( abs(J1-j2) > J3 ) | ( J1+j2 < J3 ) | ( abs(J1-J2) > j3 ) | ( J1+J2 < j3 ) ):
        print('6j-Symbol is not triangular!')
        return 0

    # Check if the sum of the elements of each traid is an integer
    if ( ( 2*(j1+j2+j3) != round(2*(j1+j2+j3)) ) | ( 2*(j1+J2+J3) != round(2*(j1+J2+J3)) ) | ( 2*(J1+j2+J3) != round(2*(J1+j2+J3)) ) | ( 2*(J1+J2+j3) != round(2*(J1+J2+j3)) ) ):
        print('6j-Symbol is not triangular!')
        return 0

    # Arguments for the factorials
    t1 = j1+j2+j3
    t2 = j1+J2+J3
    t3 = J1+j2+J3
    t4 = J1+J2+j3
    t5 = j1+j2+J1+J2
    t6 = j2+j3+J2+J3
    t7 = j1+j3+J1+J3

    # Finding summation borders
    tmin = max(0, max(t1, max(t2, max(t3,t4))))
    tmax = min(t5, min(t6,t7))
    tvec = arange(tmin,tmax+1,1)

    # Calculation the sum part of the 6j-Symbol
    WignerReturn = 0
    for t in tvec:
        WignerReturn += (-1)**t*factorial(t+1)/( factorial(t-t1)*factorial(t-t2)*factorial(t-t3)*factorial(t-t4)*factorial(t5-t)*factorial(t6-t)*factorial(t7-t) )

    # Calculation of the 6j-Symbol
    return WignerReturn*sqrt( TriaCoeff(j1,j2,j3)*TriaCoeff(j1,J2,J3)*TriaCoeff(J1,j2,J3)*TriaCoeff(J1,J2,j3) )


import math
import numpy as np
import re
import entropy_time_bin

def rand_entropy(sequence):
    """
    Compute the "random entropy", that is, the entropy of a uniform distribution.
    Equation:
        S_{rand} = \log_{2}(n), where n is the number of unique symbols in the input sequence.
    Args:
        sequence: 1-D array-like sequence of symbols.
    Returns:
        A float representing the random entropy of the input sequence.
    Reference:
        Limits of Predictability in Human Mobility. Chaoming Song, Zehui Qu,
        Nicholas Blumm1, Albert-László Barabási. Vol. 327, Issue 5968, pp. 1018-1021.
        DOI: 10.1126/science.1177170
    """
    alphabet_size = len(np.unique(sequence))
    return np.log2(alphabet_size)

def unc_entropy(sequence):
    """
    Compute temporal-uncorrelated entropy (Shannon entropy).
    Equation:
    S_{unc} = - \sum p(i) \log_2{p(i)}, for each symbol i in the input sequence.
    Args:
        sequence: the input sequence of symbols.
    Returns:
        temporal-uncorrelated entropy of the input sequence.
    Reference:
        Limits of Predictability in Human Mobility. Chaoming Song, Zehui Qu,
        Nicholas Blumm1, Albert-László Barabási. Vol. 327, Issue 5968, pp. 1018-1021.
        DOI: 10.1126/science.1177170
    """
    _, counts = np.unique(sequence, return_counts=True)
    probabilities = counts / counts.sum()
    return -np.sum(probabilities * np.log2(probabilities))

def lambdas_naive(sequence):
    """
    Compute the lambdas in the following equation:

    Equation:
        S_{real} = \left( \frac{1}{n} \sum \Lambda_{i} \right)^{-1}\log_{2}(n)

    Args:
        sequence: the input sequence of symbols.
    Returns:
        The sum of the average length of sub-sequences that
        (up to a certain point) do not appear in the original sequence.
    Reference:
        Kontoyiannis, I., Algoet, P. H., Suhov, Y. M., & Wyner, A. J. (1998).
        Nonparametric entropy estimation for stationary processes and random
        fields, with applications to English text. IEEE Transactions on Information
        Theory, 44(3), 1319-1327.
    """
    lambdas = 0
    for i in range(len(sequence)):
        current_sequence = ','.join(sequence[0:i])
        match = True
        k = i
        while match and k < len(sequence):
            k += 1
            match = ','.join(sequence[i:k]) in current_sequence
        lambdas += (k - i)
    if lambdas==0:
        lambdas=1
    return lambdas

def real_entropy(lambdas, n):
    """
    Estimate the entropy rate of the symbols encoded in the input sequence.

    Equation:
        S_{real} = \left( \frac{1}{n} \sum \Lambda_{i} \right)^{-1}\log_{2}(n)

    Args:
        sequence: the input sequence of symbols.
    Returns:
        A float representing the entropy rate of the input sequence.
    Reference:
        Kontoyiannis, I., Algoet, P. H., Suhov, Y. M., & Wyner, A. J. (1998).
        Nonparametric entropy estimation for stationary processes and random
        fields, with applications to English text. IEEE Transactions on Information
        Theory, 44(3), 1319-1327.
    """
    return (1.0 * n / lambdas) * np.log(n)

def compute_f(p,S,N):
    if p<=0 or p>=1 :
        print(p)
    h = -p * np.log2(p) - (1 - p) * np.log2(1 - p)
    pi_max = h + (1 - p) * np.log2(N - 1) - S
    return pi_max

def getapproximation(p,S,N) :
    f= compute_f(p,S,N)
    d1 = np.log2(1-p) - np.log2(p) - np.log2(N-1)
    d2 = 1 / ((p-1)*p)
    return f/(d1-f*d2/(2*d1))

def max_predictability(S, N):
    """
    Estimate the maximum predictability of a sequence with
    entropy S and alphabet size N.
    Equation:
    $S = - H(\Pi) + (1 - \Pi)\log(N - 1),$
        where $H(\Pi)$ is given by
    $H(\Pi) = \Pi \log_2(\Pi) + (1 - \Pi) \log_2(1 - \Pi)$
    Args:
        S: the entropy of the input sequence of symbols.
        N: the size of the alphabet (number of unique symbols)
    Returns:
        the maximum predictability of the sequence.
    Reference:
        Limits of Predictability in Human Mobility. Chaoming Song, Zehui Qu,
        Nicholas Blumm1, Albert-László Barabási. Vol. 327, Issue 5968, pp. 1018-1021.
        DOI: 10.1126/science.1177170
    """
    if S>np.log2(N) :
        return 0
    if S<=0.01 :
        return 0.999
    p = (N+1)/(2*N)
    while(abs(compute_f(p,S,N))>0.0000001):
        p = p - 0.8*getapproximation(p,S,N)
    return p

def regularity(sequence):
    """
    Compute the regularity of a sequence.
    The regularity basically measures what percentage of a user's
    visits are to a previously visited place.
    Parameters
    ----------
    sequence : list
        A list of symbols.
    Returns
    -------
    float
        100 minus the percentage of the symbols in the sequence that are unique.
    """
    if len(set(sequence)) <= 1:
        return 100.0

    if len(set(sequence)) == len(sequence):
        return .0

    return round(100.0 - len(set(sequence)) * 100 / len(sequence), 2)

def stationarity(sequence):
    """
    Compute the stationarity of a sequence.
    A stationary transition is one whose source and destination symbols
    are the same. The stationarity measures the percentage of transitions
    to the same location.
    Parameters
    ----------
    sequence : list
        A list of symbols.
    Returns
    -------
    float
        Percentage of the sequence that is stationary.
    """
    if len(sequence) <= 1:
        return 100.0

    if len(sequence) == len(set(sequence)):
        return .0

    stationary_transitions = 0
    for i in range(1, len(sequence)):
        if sequence[i - 1] == sequence[i]:
            stationary_transitions += 1
    return round(stationary_transitions * 100 / (len(sequence) - 1), 2)

def diversity(locations, user_home):
    """
    Compute the of trajectories of a user's mobility trace.
    The diversity of trajectories is the ratio of unique home-home
    trajectories and their sizes compared to the total length of
    the trace.
    Parameters
    ----------
    locations : list
        A list of locations that a user visited.
    user_home : str
        A string representing the ID of the location of the user's home.
    Returns
    -------
    float
        The percentage of the overall trajectories that is accounted for
        by the unique trajectories.
    """
    if not locations:
        return .0

    # We assume that the user's trajectory starts and ends at their home location.
    if not locations[0].startswith(user_home):
        locations = [user_home] + locations
    if not locations[-1].startswith(user_home):
        locations.append(user_home)

    # Split home-to-home trajectories into groups.
    trajectories = re.split('(' + str(user_home) + ')', ''.join(str(loc) for loc in locations))

    # Counts home-to-home trajectories.
    trajectories = [traj for traj in trajectories if traj != '' and traj != user_home]

    if len(set(trajectories)) <= 1:
        return .0

    if len(set(trajectories)) == len(trajectories):
        return 100.0

    # Compute the diversity of trajectories: number of unique trajectories
    # times average size of unique trajectories as a percentage of the
    # total number of trajectories.
    if trajectories:
        unique_trajectories = len(set(trajectories))
        mean_unique_trajectory_size = np.mean([max(1, len(traj) // len(str(user_home))) for traj in set(trajectories)])
        return (unique_trajectories * mean_unique_trajectory_size) * 100 / len(locations)

    return .0

if __name__ == '__main__':
    b= [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
    a= [str(num) for num in b]
    lam = lambdas_naive(a)
    print(lam)
    print(real_entropy(lam,len(a)),unc_entropy(a))
    print(max_predictability(real_entropy(lam,len(a)),len(np.unique(a))))


from beacon.tensor import Tensor
from beacon.tensor import functions as fn
import numpy as np

########################
### Activation functions
########################

def sigmoid(t: Tensor):
    """
    Sigmoid activation function.

    ## Parameters
    t: `Tensor` - tensor on which to apply activation function

    ## Example usage
    ```python
    from beacon.tensor import Tensor
    from beacon.functional import functions as F

    t = Tensor([[1, 2], [3, 4], [5, 6]], requires_grad=True)
    a = F.sigmoid(t)
    ```
    """
    return 1/ (1 + fn.exp(-t))

def softplus(t: Tensor):
    """
    Softplus activation function.

    ## Parameters
    t: `Tensor` - tensor on which to apply activation function

    ## Example usage
    ```python
    from beacon.tensor import Tensor
    from beacon.functional import functions as F

    t = Tensor([[1, 2], [3, 4], [5, 6]], requires_grad=True)
    a = F.softplus(t)
    ```
    """
    return fn.log(1 + fn.exp(t))

def softsign(t: Tensor):
    """
    Softsign activation function.

    ## Parameters
    t: `Tensor` - tensor on which to apply activation function

    ## Example usage
    ```python
    from beacon.tensor import Tensor
    from beacon.functional import functions as F

    t = Tensor([[1, 2], [3, 4], [5, 6]], requires_grad=True)
    a = F.softign(t)
    ```
    """
    return t / (1 + fn.abs(t))

def hard_sigmoid(t: Tensor):
    """
    Hard sigmoid activation function.

    ## Parameters
    t: `Tensor` - tensor on which to apply activation function

    ## Example usage
    ```python
    from beacon.tensor import Tensor
    from beacon.functional import functions as F

    t = Tensor([[1, 2], [3, 4], [5, 6]], requires_grad=True)
    a = F.hard_sigmoid(t)
    ```
    """
    value = 0.2 * t + 0.5
    return fn.clip(value, 0.0, 1.0)

def relu(t: Tensor, alpha=None):
    """
    ReLU activation function.

    ## Parameters
    t: `Tensor` - tensor on which to apply activation function

    alpha: `float` - scale factor, defaults to None (0.0)

    ## Example usage
    ```python
    from beacon.tensor import Tensor
    from beacon.functional import functions as F

    t = Tensor([[1, 2], [3, 4], [5, 6]], requires_grad=True)
    a = F.relu(t)
    ```
    """
    return t * (t >= 0.0) + alpha * t * (t < 0.0) if alpha else t * (t >= 0.0)

def leaky_relu(t: Tensor, alpha = 0.01):
    """
    Leaky ReLU activation function.

    ## Parameters
    t: `Tensor` - tensor on which to apply activation function

    alpha: `float` - scale factor, defaults to 0.01

    ## Example usage
    ```python
    from beacon.tensor import Tensor
    from beacon.functional import functions as F

    t = Tensor([[1, 2], [3, 4], [5, 6]], requires_grad=True)
    a = F.leaky_relu(t)
    ```
    """
    return relu(t, alpha=alpha)

def softmax(t: Tensor):
    """
    Softmax activation function.

    ## Parameters
    t: `Tensor` - tensor on which to apply activation function

    ## Example usage
    ```python
    from beacon.tensor import Tensor
    from beacon.functional import functions as F

    t = Tensor([[1, 2], [3, 4], [5, 6]], requires_grad=True)
    a = F.softmax(t)
    ```
    """
    y = fn.exp(t - fn.max(t, axis=-1, keepdims=True))
    return y / fn.sum(y, axis=-1, keepdims=True)

def elu(t: Tensor, alpha=1.0):
    """
    ELU activation function.

    ## Parameters
    t: `Tensor` - tensor on which to apply activation function

    alpha: `float` - scale factor, defaults to 1.0

    ## Example usage
    ```python
    from beacon.tensor import Tensor
    from beacon.functional import functions as F

    t = Tensor([[1, 2], [3, 4], [5, 6]], requires_grad=True)
    a = F.elu(t)
    ```
    """
    return t * (t >= 0.0) + alpha * (fn.exp(t) - 1.0) * (t < 0.0)

def tanh(t: Tensor):
    """
    Tanh activation function.

    ## Parameters
    t: `Tensor` - tensor on which to apply activation function

    ## Example usage
    ```python
    from beacon.tensor import Tensor
    from beacon.functional import functions as F

    t = Tensor([[1, 2], [3, 4], [5, 6]], requires_grad=True)
    a = F.tanh(t)
    ```
    """
    return fn.tanh(t)

##############
### Reductions
##############

def sum_over_batch_size(loss_function):
    """
    Decorator which reduces loss function over batch size.

    ## Parameters
    loss_function: `callable` - loss function

    ## Example usage
    ```python
    from beacon.tensor import Tensor
    from beacon.tensor import functions as fn
    from beacon.functional import functions as F

    @F.sum_over_batch_size
    def my_loss(output: Tensor, target: Tensor):
        return fn.sin(output - target)
    ```
    """
    def wrapper(*args, **kwargs):
        batch_loss = fn.sum(loss_function(*args, **kwargs))
        batch_loss.data /= np.size(args[0].data)
        return batch_loss
    return wrapper

########################
### Loss functions
########################

@sum_over_batch_size
def mean_squared_error(output: Tensor, target: Tensor):
    """
    Mean squared error loss function.

    ## Parameters
    output: `Tensor` - model's prediction

    target: `Target` - training sample targets

    ## Example usage
    ```python
    from beacon.tensor import Tensor
    from beacon.functional import functions as F

    output = Tensor([[0.2, 0.7, 0.1], [0.4, 0.45, 0.15]], requires_grad=True)
    target = Tensor([[0, 1, 0], [1, 0, 0]], requires_grad=True)
    loss = F.mean_squared_error(output, target)
    ```
    """
    output, target = fn.to_tensor(output), fn.to_tensor(target)
    return fn.mean(fn.square(output - target), axis=-1)

@sum_over_batch_size
def mean_absolute_error(output: Tensor, target: Tensor):
    """
    Mean absolute error loss function.

    ## Parameters
    output: `Tensor` - model's prediction

    target: `Target` - training sample targets

    ## Example usage
    ```python
    from beacon.tensor import Tensor
    from beacon.functional import functions as F

    output = Tensor([[0.2, 0.7, 0.1], [0.4, 0.45, 0.15]], requires_grad=True)
    target = Tensor([[0, 1, 0], [1, 0, 0]], requires_grad=True)
    loss = F.mean_absolute_error(output, target)
    ```
    """
    output, target = fn.to_tensor(output), fn.to_tensor(target)
    return fn.mean(fn.abs(output - target), axis=-1)

@sum_over_batch_size
def categorical_crossentropy(output: Tensor, target: Tensor):
    """
    Cross entropy loss function.

    ## Parameters
    output: `Tensor` - model's prediction

    target: `Target` - training sample targets

    ## Example usage
    ```python
    from beacon.tensor import Tensor
    from beacon.functional import functions as F

    output = Tensor([[0.2, 0.7, 0.1], [0.4, 0.45, 0.15]], requires_grad=True)
    target = Tensor([[0, 1, 0], [1, 0, 0]], requires_grad=True)
    loss = F.categorical_crossentropy(output, target)
    ```
    """
    output, target = fn.to_tensor(output), fn.to_tensor(target)
    output = fn.clip(output, 1e-7, 1 - 1e-7)
    return target * -fn.log(output)

@sum_over_batch_size
def binary_crossentropy(output: Tensor, target: Tensor):
    """
    Binary cross entropy loss function.

    ## Parameters
    output: `Tensor` - model's prediction

    target: `Target` - training sample targets

    ## Example usage
    ```python
    from beacon.tensor import Tensor
    from beacon.functional import functions as F

    output = Tensor([[0.89], [0.76], [0.1]], requires_grad=True)
    target = Tensor([[1], [1], [0]], requires_grad=True)
    loss = F.binary_crossentropy(output, target)
    ```
    """
    output, target = fn.to_tensor(output), fn.to_tensor(target)
    output = fn.clip(output, 1e-7, 1 - 1e-7)
    return (target * -fn.log(sigmoid(output)) + (1 - target) * -fn.log(1 - sigmoid(output)))

@sum_over_batch_size
def nll_loss(output: Tensor, target: Tensor):
    """
    Negative log likelihood loss function.

    ## Parameters
    output: `Tensor` - model's prediction

    target: `Target` - training sample targets

    ## Example usage
    ```python
    from beacon.tensor import Tensor
    from beacon.functional import functions as F

    output = Tensor([[0.2, 0.7, 0.1], [0.4, 0.45, 0.15]], requires_grad=True)
    target = Tensor([[0, 1, 0], [1, 0, 0]], requires_grad=True)
    loss = F.nll_loss(output, target)
    ```
    """
    output, target = fn.to_tensor(output), fn.to_tensor(target)
    output = fn.clip(output, 1e-7, 1 - 1e-7)
    return -target * fn.log(output)

@sum_over_batch_size
def quadratic(output: Tensor, target: Tensor):
    """
    Quadratic loss function.

    ## Parameters
    output: `Tensor` - model's prediction

    target: `Target` - training sample targets

    ## Example usage
    ```python
    from beacon.tensor import Tensor
    from beacon.functional import functions as F

    output = Tensor([[0.2, 0.7, 0.1], [0.4, 0.45, 0.15]], requires_grad=True)
    target = Tensor([[0, 1, 0], [1, 0, 0]], requires_grad=True)
    loss = F.quadratic(output, target)
    ```
    """
    output, target = fn.to_tensor(output), fn.to_tensor(target)
    return fn.square(output - target)

@sum_over_batch_size
def half_quadratic(output: Tensor, target: Tensor):
    """
    Half quadratic loss function.

    ## Parameters
    output: `Tensor` - model's prediction

    target: `Target` - training sample targets

    ## Example usage
    ```python
    from beacon.tensor import Tensor
    from beacon.functional import functions as F

    output = Tensor([[0.2, 0.7, 0.1], [0.4, 0.45, 0.15]], requires_grad=True)
    target = Tensor([[0, 1, 0], [1, 0, 0]], requires_grad=True)
    loss = F.half_quadratic(output, target)
    ```
    """
    output, target = fn.to_tensor(output), fn.to_tensor(target)
    return 0.5 * fn.square(output - target)

#####################
### Pooling functions
#####################

def max_pool(x: Tensor, kernel_size: tuple, stride: tuple=None, padding: tuple=(0, 0)):
    """
    Performs max pooling.

    ## Parameters
    x: `Tensor` - input tensor

    kernel_size: `tuple` - shape of the kernel

    stride: `tuple` - stride, if not set default shape is same as kernel_size

    padding: `tuple` - padding, defaults to (0, 0)

    ## Example usage
    ```python
    from beacon.tensor import Tensor
    from beacon.functional import functions as F
    import numpy as np

    x = Tensor(data=np.random.rand(32, 10, 24, 24), requires_grad=True)
    sample = F.max_pool(x, kernel_size=(2, 2))
    output = F.max_pool(x, 2)
    ```
    """
    kernel_size = kernel_size if isinstance(kernel_size, tuple) else (kernel_size, kernel_size)
    stride = stride if stride else kernel_size
    return fn.max_pool(x, kernel_size, stride, padding)

def average_pool(x: Tensor, kernel_size: tuple, stride: tuple=None, padding: tuple=(0,0)):
    """
    Performs average pooling.

    ## Parameters
    x: `Tensor` - input tensor

    kernel_size: `tuple` - shape of the kernel

    stride: `tuple` - stride, if not set default shape is same as kernel_size

    padding: `tuple` - padding, defaults to (0, 0)

    ## Example usage
    ```python
    from beacon.tensor import Tensor
    from beacon.functional import functions as F
    import numpy as np

    x = Tensor(data=np.random.rand(32, 10, 24, 24), requires_grad=True)
    sample = F.average_pool(x, kernel_size=(2, 2))
    output = F.average_pool(x, 2)
    ```
    """
    kernel_size = kernel_size if isinstance(kernel_size, tuple) else (kernel_size, kernel_size)
    stride = stride if stride else kernel_size
    return fn.average_pool(x, kernel_size, stride, padding)


import numpy as np
import matplotlib.pyplot as plt

from copy import deepcopy
from numpy import linalg

## initialisation algorithm
def func_Kmeans(X, ncls, init='k-means++', max_iter=300, bfig=False):

    def plot(X, centroids, icls, ncls):
        plt.figure(10)
        plt.subplot(1, ncls, icls + 1)

        plt.scatter(X[:, 0], X[:, 1], marker='.',
                    color='gray', label='data points')
        plt.scatter(centroids[:-1, 0], centroids[:-1, 1],
                    color='black', label='previously selected centroids')
        plt.scatter(centroids[-1, 0], centroids[-1, 1],
                    color='red', label='next centroid')
        plt.title('Select % d th centroid' % (centroids.shape[0]))

        plt.legend()
        # plt.xlim(-5, 12)
        # plt.ylim(-10, 15)
        # plt.show()

    def func_Kmeanspp(X, ncls):
        '''
        intialized the centroids for K-means++
        '''

        # The number of training data
        ndata = X.shape[0]

        # The number of features in the data
        nfeature = X.shape[1]

        ## initialize the centroids list and add
        ## a randomly selected data point to the list
        centroids = np.zeros((ncls, nfeature))
        centroids[0, :] = X[np.random.randint(ndata), :]

        if bfig:
            plot(X, centroids[:0+1, :], 0, ncls)

        ## compute remaining k - 1 centroids
        for icls in range(1, ncls):

            distances = np.zeros((icls, ndata))
            dist_min = np.zeros(ndata)

            ## initialize a list to store distances of data
            ## points from nearest centroid
            for icls_pre in range(icls):
                distances[icls_pre, :] = linalg.norm(X - centroids[icls_pre, :], axis=1)

            idx_min = np.argmin(distances, axis=0)

            for imin in range(len(idx_min)):
                dist_min[imin] = distances[idx_min[imin], imin]

            imax = np.argmax(dist_min) % ndata
            centroids[icls, :] = X[imax, :]

            if bfig:
                plot(X, centroids[:icls+1, :], icls, ncls)

        return centroids

    # The number of training data
    ndata = X.shape[0]

    # The number of features in the data
    nfeature = X.shape[1]

    if init == 'ramdom':
        mean = np.mean(X, axis=0)
        std = np.std(X, axis=0)
        centers_init = np.random.randn(ncls, nfeature)*std + mean
    else:
        centers_init = func_Kmeanspp(X, ncls)

    # Store new centers
    centers = deepcopy(centers_init)

    clusters = np.zeros(ndata)
    distances = np.zeros((ndata, ncls))

    # When, after an update, the estimate of that center stays the same, exit loop
    for i in range(max_iter):
    # while error != 0:
        # Measure the distance to every center
        for icls in range(ncls):
            distances[:, icls] = linalg.norm(X - centers[icls], axis=1)

        # Assign all training data to closest center
        clusters = np.argmin(distances, axis=1)

        centers_pre = deepcopy(centers)

        # Calculate mean for every cluster and update the center
        for icls in range(ncls):
            if (clusters == icls).any():
                centers[icls, :] = np.mean(X[clusters == icls], axis=0)

        error = linalg.norm(centers - centers_pre)

        if error == 0:
            break

    return clusters, centers


import numpy as np
import math, time, sys

__author__ = 'Lujing Cen'

EPSILON = sys.float_info.epsilon

def fabrik(p, d, t, tolerance, step=False):
    if step:
        ret = []

    # The distance between root and target.
    dist = np.linalg.norm(t - p[0])

    # Check if target is within reach.
    if dist > np.sum(d):
        # Target is unreachable.

        for i in range(len(p) - 1):
            # Find the distance r, between the target t and the joint position p_i.
            r = np.linalg.norm(p[i] - t)
            delta = d[i] / r

            # Find the new joint positions p_i.
            p[i+1] = (1-delta)*p[i] + delta*t

        return None
    else:
        # The target is reachable; thus, set as b the initial position of the joint p_i.
        b = p[0].copy()

        # Check whether the distance between the end effector pn and the target t is greater than a tolerance.
        dif = np.linalg.norm(t - p[-1])

        while dif > tolerance:
            # STAGE 1: FORWARD REACHING.
            # Set the end effector p_n as target t.
            p[-1] = t

            if step:
                ret.append(p.copy())

            for i in reversed(range(len(p) - 1)):
                # Find the distance ri between the new joint position p_i+1 and the joint p_i.
                r = np.linalg.norm(p[i+1] - p[i])
                delta = d[i] / r

                # Find the new joint position p_i.
                p[i] = (1-delta)*p[i+1] + delta*p[i]
                if step:
                    ret.append(p.copy())

            # STAGE 2: BACKWARD REACHING.
            # Set the root p_1 its initial position.
            p[0] = b
            if step:
                ret.append(p.copy())

            for i in range(len(p) - 1):
                # Find the distance ri between the new joint position p_i and the joint p_i+1.
                r = np.linalg.norm(p[i+1] - p[i])
                delta = d[i] / r

                # Find the new joint positions p_i.
                p[i+1] = (1-delta)*p[i] + delta*p[i+1]
                if step:
                    ret.append(p.copy())

            dif = np.linalg.norm(t - p[-1])
    if step:
        return ret
    else:
        return p


import numpy as np

def coor_trans(point, theta):
	"""
		coordinate transformation (坐标转换)

		theta方向：以顺时针旋转为正
	"""
	point = np.transpose(point)
	k = np.array([[np.cos(theta), np.sin(theta)],
				  [-np.sin(theta), np.cos(theta)]])
	print(point)
	# return np.dot(k, point)
	return np.round(np.dot(k, point),6)

if __name__ == "__main__":
	point = np.array([[1,0],[1,2],[2,0],[1,0]])
	print(point)
	point_trans = coor_trans(point, np.pi / 2)
	print(point_trans)


import random as rn

import numba
import numpy as np

def log_transform(x, inverse=False, inplace=True):
    """

    Parameters
    ----------
    x : numpy.ndarray
        An array which
    inverse : bool
        inverse transform

    inplace : bool

    Returns
    -------

    References
    ----------
    https://docs.scipy.org/doc/numpy/reference/generated/numpy.where.html
    https://stackoverflow.com/questions/21610198/runtimewarning-divide-by-zero-encountered-in-log
    """
    if inplace:
        # method 1: use boolean mask
        if inverse:
            mask = (x >= 0)
            x[mask] = np.power(10, x[mask]) - 1
            x[~mask] = -np.power(10, -x[~mask]) + 1
        else:
            mask = (x >= 0)
            x[mask] = np.log10(x[mask] + 1)
            x[~mask] = -np.log10(np.abs(x[~mask] - 1))

        # method 2: use index
        # ge0 = np.where(x >= 0)  # greater equal 0
        # lt0 = np.where(x < 0)  # less than 0
        # ge0 = np.asarray(x >= 0).nonzero()
        # lt0 = np.asarray(x < 0).nonzero()
        # x[ge0] = np.log10(x[ge0] + 1)
        # x[lt0] = -np.log10(np.abs(x[lt0] - 1))

        # method 3: use numpy.where(condition[, x, y])
        # An array with elements from x where condition is True, and elements from y elsewhere.
        # Note: numpy.log10(prob) is being evaluated before the numpy.where is being evaluated.
        # x = np.where(x >= 0, np.log10(x + 1), -np.log10(np.abs(x - 1)))
        return
    else:
        new_x = x.copy()
        if inverse:
            mask = (new_x >= 0)
            new_x[mask] = np.power(10, new_x[mask]) - 1
            new_x[~mask] = -np.power(10, -new_x[~mask]) + 1
        else:
            mask = (new_x >= 0)
            new_x[mask] = np.log10(new_x[mask] + 1)
            new_x[~mask] = -np.log10(np.abs(new_x[~mask] - 1))
        return new_x

@numba.njit()
def add_noise(x, ratio=0.05):
    """Add noise to each element of the array by a certain percentage.

    In order to handle large arrays under memory constraints, this function uses in-place mode.

    Parameters
    ----------
    x : numpy.ndarray
        Array that you wanted to add noise.
    ratio : float, default 0.05
        Noise added to element is proportional to this value.

    Returns
    -------
    None

    References
    ----------
    https://stackoverflow.com/questions/44257931/fastest-way-to-add-noise-to-a-numpy-array
    """
    x = x.reshape(-1)  # flat view
    for i in range(len(x)):
        x[i] += x[i] * rn.uniform(-1, 1) * ratio


#from __future__ import division
import matplotlib.pyplot as plt
import numpy as np

def outlierDetection(Q1,Q3,list_of_numbers):
    #this function takes a list of numbers and prints out those numbers that are considered outliers
    IQR= float(Q3-Q1)
    print ("IQR= ",IQR)
    lowerthreshold=Q1-(IQR*1.5)
    print ("Lowerthreshold= ",lowerthreshold)
    upperthreshold=Q3+(IQR*1.5)
    print ("Upperthreshold= ",upperthreshold)
    for i in list_of_numbers:
        lowflag=0
        highflag=0
        if (i <lowerthreshold):
            lowflag=1
        if (i>upperthreshold):
            highflag=1
        if (lowflag):
            print (i , "is an outlier because it is lower than than the lowerthreshold")
        if (highflag):
            print (i , "is an outlier because it is higher than than the upperthreshold")

# different lists
list_of_numbers=[53,79,80,82,87,91,93,98]
#list_of_numbers=[53,79,80,82,87,91,93,98,57,79,99,66]
#list_of_numbers=[1,1,4,4,9,11]

#the numbers need to be sorted first
list_of_numbers.sort()
print ("The observations after sorting: ", list_of_numbers,'\n')

# printing the number of observations
print  ("Number of observations =  ",len(list_of_numbers),'\n')

# getting the minimum number of the list
Min=list_of_numbers[0]

#getting the maximum number of the list
Max=list_of_numbers[-1]

# getting the Q2 of the list
median = np.median(list_of_numbers)
# getting the Q3 of the list
upper_quartile = np.percentile(list_of_numbers, 75)
# getting the Q1 of the list
lower_quartile = np.percentile(list_of_numbers, 25)

# printing the five number summary
print ("Five Number Summary ",Min, lower_quartile, median, upper_quartile, Max,'\n')

# getting the outliers
outlierDetection(lower_quartile, upper_quartile,list_of_numbers)

# plotting the BOXPLOT verticaly with outliers as  diamond shape
plt.figure()
#plt.boxplot(list_of_numbers, 0, 'gD',0.50)
# plotting the BOXPLOT horizontal
plt.boxplot(list_of_numbers, 0, 'rs',0)
'''
# plotting the BOXPLOT with an outliers as a +
plt.boxplot(list_of_numbers)
# don't show outlier points
plt.figure()
plt.boxplot(list_of_numbers, 0, '')

# horizontal boxes
plt.figure()
plt.boxplot(list_of_numbers, 0, 'rs', 0)
'''

plt.show()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#活性化関数

import numpy as np
import matplotlib.pylab as plt

def step_function(x):
    return np.array(x > 0, dtype=np.int)

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

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

def main():

    x = np.arange(-5.0, 5.0, 0.1)

    step_y = step_function(x)
    sigmoid_y = sigmoid_function(x)
    relu_y = relu_function(x)

    plt.plot(x, step_y, linestyle="dashed", label="$step$")
    plt.plot(x, sigmoid_y, 'r-', label="$sigmoid$")
    plt.plot(x, relu_y, 'g-', label="$relu")
    plt.xlim(-6.0, 6.0)
    plt.ylim(-0.1, 1.1) #y range
    plt.legend()
    plt.grid()
    plt.show()

if __name__ == '__main__':
    main()


from unittest import TestCase

import numpy as np
from numpy.testing import assert_allclose
from pytest import mark

from fastats.linear_algebra import inv

class MatrixInverseValidator:

    A, A_inv = None, None

    def setUp(self):
        self._A = np.array(self.A)

    def test_inv_outputs_numpy(self):
        A_inv = inv(self._A)
        assert A_inv.tolist() == self.A_inv

        I = np.eye(self._A.shape[0])
        assert_allclose(self.A @ A_inv, I, atol=1e-10)

class MathwordsInv2x2Test(MatrixInverseValidator, TestCase):
    """
    This test is an example 2x2 matrix inverse from
    http://www.mathwords.com/i/inverse_of_a_matrix.htm
    """
    A = [[4, 3],
         [3, 2]]

    A_inv = [[-2, 3],
             [3, -4]]

class MathwordsInv3x3Test(MatrixInverseValidator, TestCase):
    """
    This test is an example 3x3 matrix inverse from
    http://www.mathwords.com/i/inverse_of_a_matrix.htm
    """
    A = [[1, 2, 3],
         [0, 4, 5],
         [1, 0, 6]]

    A_inv = (np.array([[24, -12, -2],
                       [5, 3, -5],
                       [-4, 2, 4]]) * 1 / 22).tolist()

class MathscentreInv3x3Test(MatrixInverseValidator, TestCase):
    """
    This test is an example 3x3 matrix inverse from
    http://www.mathcentre.ac.uk/resources/uploaded/sigma-matrices11-2009-1.pdf
    """
    A = [[7, 2, 1],
         [0, 3, -1],
         [-3, 4, -2]]

    A_inv = [[-2, 8, -5],
             [3, -11, 7],
             [9, -34, 21]]

class ImperialInv3x3Test(MatrixInverseValidator, TestCase):
    """
    This test is an example 3x3 matrix inverse from
    http://wwwf.imperial.ac.uk/metric/metric_public/matrices/inverses/inverses2.html
    """
    A = [[0, -3, -2],
         [1, -4, -2],
         [-3, 4, 1]]

    A_inv = [[4, -5, -2],
             [5, -6, -2],
             [-8, 9, 3]]

def test_hilbert_inv_5x5():
    """
    This test is an example 5x5 Hilbert matrix inverse from
    http://mathfaculty.fullerton.edu/mathews/n2003/Web/InverseMatrixMod/Links/MatrixInverseMod_lnk_2.html
    """
    hilbert = np.empty((5, 5))

    for i in range(5):
        for j in range(5):
            hilbert[i][j] = 1 / (1 + i + j)

    hilbert_inv = np.array([[25, -300, 1050, -1400, 630],
                            [-300, 4800, -18900, 26880, -12600],
                            [1050, -18900, 79380, -117600, 56700],
                            [-1400, 26880, -117600, 179200, -88200],
                            [630, -12600, 56700, -88200, 44100]])

    output = inv(hilbert)
    assert_allclose(hilbert_inv, output)
    assert_allclose(hilbert @ output, np.eye(5), atol=1e-8)

def test_inv_5x5():
    A = np.array([[3, 13, 14, 10, 12],
                  [8, 15, 4, 16, 5],
                  [6, 11, 7, 9, 17],
                  [18, 19, 2, 20, 21],
                  [22, 23, 24, 25, 26]])

    A_inv = np.linalg.inv(A)

    output = inv(A)
    assert_allclose(A_inv, output)
    assert_allclose(A @ output, np.eye(5), atol=1e-10)

@mark.parametrize('n', range(2, 9))
def test_inv_basic_sanity(n):
    """
    Note the degradation in run times for n > 5
    """
    scalar = 4
    A = np.eye(n) * scalar
    A_inv = np.eye(n) * 1 / scalar

    output = inv(A)
    assert_allclose(A_inv, output)
    assert_allclose(A @ output, np.eye(n))

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


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon May 17 20:08:43 2021

@author: leslie
"""
# Importing the libraries
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

# Importing the dataset
dataset = pd.read_csv('Position_Salaries.csv')
X = dataset.iloc[:,1:2].values # "Level" is our independent variable
"""Since we want our independent variable to be matrix all the time in machine learning,
   here we executed [:,1:2] instead of [:,1] as vector."""
y = dataset.iloc[:,2].values # "Salary" is our dependent variable

# Fitting Linear Regression to the dataset
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(X, y)

# Fitting Polynomial Regression to the dataset
from sklearn.preprocessing import PolynomialFeatures
poly_reg = PolynomialFeatures(degree = 4)
X_poly = poly_reg.fit_transform(X) # Adding additional polynomial matrix of x

lin_reg2 = LinearRegression()
lin_reg2.fit(X_poly, y)

# Visualising the Linear Regression results
plt.scatter(X, y, color = 'red') #Real observation"""
plt.plot(X, lin_reg.predict(X), color = 'blue') #Prediction
plt.title('Truth or Bluff (Linear Regression)')
plt.xlabel('Position Level')
plt.ylabel('Salary')
plt.show()

# Visualising the Polynomial Regression results
import numpy as np
X_grid = np.arange(min(X), max(X), 0.1) # from level 1-10 (Position level), incremented by 0.1
X_grid = X_grid.reshape((len(X_grid)), 1)

plt.scatter(X, y, color = 'red')
plt.plot(X_grid, lin_reg2.predict(poly_reg.fit_transform(X_grid)), color = 'blue') # Here we don't use X_poly directly because we don't want to add another matrix
plt.title('Truth or Bluff (Polynomial Regression)')
plt.xlabel('Position Level')
plt.ylabel('Salary')
plt.show()

# Predicting a new result with Linear Regression
lin_reg.predict(6.5)

# Predicting a new result with Polynomial Regression
lin_reg2.predict(poly_reg.fit_transform(6.5))