import matplotlib
matplotlib.rcParams = matplotlib.rc_params_from_file('../../matplotlibrc')

import numpy as np
from numpy.linalg import solve
from solution import cheb
import matplotlib.pyplot as plt
import math

def deriv_matrix_exercise1():

    def f(x):
        return np.exp(x)*np.cos(6.*x)

    def fp(x):
        return np.exp(x)*(np.cos(6.*x) - 6.*np.sin(6.*x))

    N = 10
    D,x = cheb(N)
    f_exact, fp_exact = f(x), fp(x)
    xx = np.linspace(-1,1,200)
    plt.plot(xx,fp(xx),'-k') # Exact Derivative

    uu = (np.poly1d(np.polyfit(x, D.dot(f(x) ), N)))(xx)

    plt.plot(x,D.dot(f(x)),'*r')	# Approximation to derivative at grid points
    plt.plot(xx,uu,'-r')			# Approximation to derivative at other values
    plt.savefig('equally_spaced_points.pdf')
    plt.clf()
    print "max error = \n", np.max(np.abs( D.dot(f(x)) - fp(x)   ))

def exercise2():
    # Solves the Poisson boundary value problem u''(x) = f(x), u(-1) = u(1) = 0
    def f(x):
        return np.exp(2.*x) #np.exp(4.*x)

    def anal_sol(x):
        # return (np.exp(4.*x) - np.sinh(4.)*x - np.cosh(4.))/16.
        return (np.exp(2.*x) - np.sinh(2.)*x - np.cosh(2.))/4.

    N = 3
    D, x = cheb(N)

    A = np.dot(D, D)[1:N:1,1:N:1]
    u = np.zeros(x.shape)
    u[1:N] = solve(A, f(x[1:N:1]))

    xx = np.linspace(-1, 1, 50)
    uu = (np.poly1d(np.polyfit(x, u, N)))(xx)
    print "Max error is ", np.max(np.abs(uu - anal_sol(xx)))

    plt.plot(x, u, '*r')
    plt.plot(xx, uu, '-r')
    plt.plot(xx, anal_sol(xx), '-k')
    plt.axis([-1.,1.,-2.5,.5])
    plt.savefig('chebyshev_points.pdf')
    plt.clf()

def nonzeroDirichlet():
    # Solves the Poisson boundary value problem
    # u''(x) + u'(x) = f(x),
    # u(-1) = a
    # u(1) = b
    a, b = 2, -1

    def f(x):
        return -(b-a)/2.*np.ones(x.shape)+ np.exp(3.*x)

    def G(x):
        return (b-a)/2.*np.ones(x.shape) *x + (b+a)/2.*np.ones(x.shape)

    def anal_sol(x):
        # out = ( np.exp(-x)/( 12.*(-1 +np.exp(1))*(1 + np.exp(1))*np.exp(3))  *
        # (-12.*a*np.exp(x+3.)+12.*a*np.exp(4) + 12.*b*np.exp(x+5)-12*b*np.exp(4) -
        #   np.exp(4*x+3) + np.exp(4*x+5) - np.exp(x+8 )+ np.exp(x)- np.exp(1) + np.exp(7)
        #   )
        # 	  )
        N, B = np.array([[1.,np.exp(1)],[1,np.exp(-1)]]), np.array([[a-np.exp(-3)/12.], [b-np.exp(3)/12.]])
        C = solve(N,B)
        return C[0] + C[1]*np.exp(-x) + np.exp(3.*x)/12.

    N = 7
    D, x = cheb(N)

    A = np.dot(D, D)[1:N:1,1:N:1]
    u = np.zeros(x.shape)
    u[1:N] = solve(A + D[1:N:1,1:N:1], f(x[1:N:1]))

    xx = np.linspace(-1, 1, 50)
    uu = (np.poly1d(np.polyfit(x,u,N)))(xx)
    print "Max error is ", np.max(np.abs(uu + G(xx) - anal_sol(xx)))

    # plt.plot(x,u+G(x), '*k')
    # plt.plot(xx,uu+G(xx), '-r')
    plt.plot(xx, anal_sol(xx))
    plt.xlabel('$x$')
    plt.ylabel('$u$')
    # plt.axis([-1.,1.,-2.5,.5])
    plt.savefig('nonzeroDirichlet.pdf')
    plt.clf()

if __name__ == "__main__":
    deriv_matrix_exercise1()
    exercise2()
    nonzeroDirichlet()


'''
Austin Griffith
bsoptions.py

Provides an analytical solution to options via the Black Scholes methodology.
'''

import numpy as np
from scipy.stats import norm

class EuroOptions:
    '''

    The Black Scholes value of a European Call / Put option
    Payoffs are of the form :
    C = max(S - K, 0)
    P = max(K - S, 0)

    The delta value of a European Call / Put option measures sensitivity
    of option value with respect to change in underlying asset price,
    calculated from the derivative dV/dS
    Deltas are of the form :
    C = e**-qT N(d1)
    P = -e**-qT N(-d1)

    The gamma value of a European option measures sensitivity of option
    delta with respect to change in underlying asset price,
    calculated from the derivative d2C/dS2
    Gamma is of the form :
    G = e**(-qT) N'(d1) / S vol sqrt(T)

    The rho value of a European Call / Put option measures sensitivity
    of option value with respect to change in interest rate over the life
    of the option, calculated from derivative dV/dr
    Rhos are of the form :
    C = e**-rT K T N(d2)
    P = -e**-rT K T N(-d2)

    The theta value of a European Call / Put option, measures sensitivity
    of option value with respect to change in time,
    calculated from derivative dV/dT

    The vega value of a European Call / Put option measures sensitivity
    of option value with respect to change in underlying asset volatility,
    calculated from derivative dV/dσ
    Vega is of the form :
    V = S e**(-qT) N'(d1) sqrt(T)

    Parameters
    ----------
    spot : number of any type (int, float8, float64 etc.), numpy array of any type
        should the user wish to have a list of values output with varying s
        Spot value of underlying asset at current time, t
    strike : number of any type (int, float8, float64 etc.), numpy array of any type
        should the user wish to have a list of values output with varying k
        Strike value of option, determined at initiation
    riskfree : number of any type (int, float8, float64 etc.), numpy array of any type
        should the user wish to have a list of values output with varying r
        Risk free interest rate, implied constant till expiration
    tau : number of any type (int, float8, float64 etc.), numpy array of any type
        should the user wish to have a list of values output with varying T
        Time till expiration for option
    vol : number of any type (int, float8, float64 etc.), numpy array of any type
        should the user wish to have a list of values output with varying vol
        Volatility of underlying, implied constant till expiration in Black
        Scholes model
    div : number of any type (int, float8, float64 etc.) or numpy array of any type
        should the user wish to have a list of values output with varying q
        Continuous dividend payout, as a percentage

    Notes
    -----
    All parameters can be individual values.
    Only one of these parameters can be a numpy.array, otherwise there will be
    a dimension mismatch.

    '''

    def __init__(self, spot, strike, riskfree, tau, vol, div):
        self.s = spot
        self.k = strike
        self.r = riskfree
        self.T = tau
        self.vol = self.vol
        self.q = div

        self.d1, self.d2 = self._euroD()
        self.optionleg, self.strikeleg = self._euroParity()

    def EuroPrice(self):
        '''

        Returns
        -------
        [call,put] : list of pair of float or numpy.array values
            Euro call and put prices, type depends on input value.
            If all input values are individual numbers, then output will be float.
            If one input value is numpy.array, then output will be numpy.array.

        '''
        call = max(self._euroPriceCall(), 0)
        put = max(self._euroPricePut(), 0)
        return([call,put])

    def EuroCall(self):
        '''

        Returns
        -------
        data : dictionary float or numpy.array values
            Euro call price and greeks, labeled according to the respective value.
            If all input values are individual numbers, then output will be float.
            If one input value is numpy.array, then output will be numpy.array.

        '''
        price = max(self._euroPriceCall(), 0)
        delta = self._euroDeltaCall()
        gamma = self._euroGamma()
        vega = self._euroVega()
        theta = self._euroThetaCall()
        rho = self._euroRhoCall()

        eur = [price, delta, gamma, vega, theta, rho]
        label = ['price','delta','gamma','vega','theta','rho']
        data = dict(zip(label, eur))
        return(data)

    def EuroPut(self):
        '''

        Returns
        -------
        data : dictionary float or numpy.array values
            Euro put price and greeks, labeled according to the respective value.
            If all input values are individual numbers, then output will be float.
            If one input value is numpy.array, then output will be numpy.array.

        '''
        price = max(self._euroPricePut(), 0)
        delta = self._euroDeltaPut()
        gamma = self._euroGamma()
        vega = self._euroVega()
        theta = self._euroThetaPut()
        rho = self._euroRhoPut()

        eur = [price, delta, gamma, vega, theta, rho]
        label = ['price','delta','gamma','vega','theta','rho']
        data = dict(zip(label, eur))
        return(data)

    def _euroRhoCall(self):
        r = self.strikeleg*self.T*norm.cdf(self.d2)
        return(r)

    def _euroRhoPut(self):
        r = -1*self.strikeleg*self.T*norm.cdf(-1*self.d2)
        return(r)

    def _euroThetaCall(self):
        t = self._euroDerivTheta() - self.strikeleg*norm.cdf(self.d2) + \
            self.optionleg*norm.cdf(self.d1)
        return(t)

    def _euroThetaPut(self):
        t = self._euroDerivTheta() + self.strikeleg*norm.cdf(-1*self.d2) - \
            self.optionleg*norm.cdf(-1*self.d1)
        return(t)

    def _euroDerivTheta(self):
        dt = -0.5*np.exp(-1*self.q*self.T)*norm.pdf(self.d1)*self.s*self.vol / np.sqrt(self.T)
        return(dt)

    def _euroPriceCall(self):
        p = self.strikeleg*norm.cdf(-1*self.d2) - self.optionleg*norm.cdf(-1*self.d1)
        return(p)

    def _euroPricePut(self):
        p = self.optionleg*norm.cdf(self.d1) - self.strikeleg*norm.cdf(self.d2)
        return(p)

    def _euroD(self):
        d1 = (np.log(self.s / self.k) + \
              (self.r - self.q + 0.5*self.vol*self.vol)*self.T) / self.vol*np.sqrt(self.T)
        d2 = d1 - self.vol*np.sqrt(self.T)
        return(d1, d2)

    def _euroParity(self):
        o_leg = np.exp(-1*self.q*self.T)*self.s
        k_leg = np.exp(-1*self.r*self.T)*self.k
        return(o_leg, k_leg)

    def _euroGamma(self):
        g = np.exp(-1*self.q*self.T)*norm.pdf(self.d1) / (self.s*self.vol*np.sqrt(self.T))
        return(g)

    def _euroVega(self):
        v = self.s*np.exp(-1*self.q*self.T)*norm.pdf(self.d1)*np.sqrt(self.T)
        return(v)

    def _euroDeltaCall(self):
        ed = np.exp(-1*self.q*self.T)*norm.cdf(self.d1)
        return(ed)

    def _euroDeltaPut(self):
        ed = -1*np.exp(-1*self.q*self.T)*norm.cdf(-1*self.d1)
        return(ed)

class ExoticOptions:

    @staticmethod
    def AsianGeometric(s, k, r, T, vol, q):
        '''

        Calculate the Black Scholes value of Geometric Average Asian Call / Put
        option with a fixed strike
        Payoffs are of the form :
        C = max(AVG_geo - K, 0)
        P = max(K - AVG_geo, 0)

        Parameters
        ----------
        s : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying s
            Spot value of underlying asset at current time, t
        k : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying k
            Strike value of option, determined at initiation
        r : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying r
            Risk free interest rate, implied constant till expiration
        T : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying T
            Time till expiration for option
        vol : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying vol
            Volatility of underlying, implied constant till expiration in Black
            Scholes model
        q : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying q
            Continuous dividend payout, as a percentage

        Notes
        -----
        All parameters can be individual values.
        Only one of these parameters can be a numpy.array, otherwise there will be
        a dimension mismatch.

        Returns
        -------
        [call,put] : list of pair of float or numpy.array values
            Asian call and put values, type depends on input value.
            If all input values are individual numbers, then output will be float.
            If one input value is numpy.array, then output will be numpy.array.

        '''
        a = 0.5*(r - q - vol*vol/6)
        volG = vol/np.sqrt(3)

        d1 = (np.log(s/k) + (a + 0.5*volG*volG)*T) / (volG*np.sqrt(T))
        d2 = d1 - volG*np.sqrt(T)

        option = s*np.exp((a - r)*T)
        strike = k*np.exp(-r*T)

        put = max(strike*norm.cdf(-d2) - option*norm.cdf(-d1), 0)
        call = max(option*norm.cdf(d1) - strike*norm.cdf(d2), 0)
        return([call,put])

    @staticmethod
    def AsianArithmetic(s, k, r, T, vol, q):
        '''

        Calculate the Black Scholes value of Arithmetic Average Asian Call / Put
        option with a fixed strike
        Payoffs are of the form :
        C = max(AVG_arithmetic - K, 0)
        P = max(K - AVG_arithmetic, 0)

        Parameters
        ----------
        s : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying s
            Spot value of underlying asset at current time, t
        k : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying k
            Strike value of option, determined at initiation
        r : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying r
            Risk free interest rate, implied constant till expiration
        T : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying T
            Time till expiration for option
        vol : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying vol
            Volatility of underlying, implied constant till expiration in Black
            Scholes model
        q : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying q
            Continuous dividend payout, as a percentage

        Notes
        -----
        All parameters can be individual values.
        Only one of these parameters can be a numpy.array, otherwise there will be
        a dimension mismatch.

        * want r > q, else the natural logarithm has a chance of breaking

        Returns
        -------
        [call,put] : list of pair of float or numpy.array values
            Asian call and put values, type depends on input value.
            If all input values are individual numbers, then output will be float.
            If one input value is numpy.array, then output will be numpy.array.

        '''
        m1 = s*(np.exp((r - q)*T) - 1) / ((r - q)*T)
        m2l = 2*s*s*np.exp((2*r - 2*q + vol*vol)*T) / ((r - q +vol*vol)*T*T*(2*r - 2*q + vol*vol))
        m2r = (2*s*s / ((r-q)*T*T))*((1/(2*(r-q) + vol*vol)) -
            np.exp((r-q)*T)/(r - q - vol*vol))
        m2 = m2l + m2r

        volA = np.sqrt(np.log(m2/(m1*m1)) / T)

        d1 = (np.log(m1/k) + 0.5*volA*volA*T) / (volA*np.sqrt(T))
        d2 = d1 - volA*np.sqrt(T)

        call = np.exp(-r*T)*(m1*norm.cdf(d1) - k*norm.cdf(d2))
        put = np.exp(-r*T)*(k*norm.cdf(-d2) - m1*norm.cdf(-d1))
        return([call,put])

    @staticmethod
    def Power(s, k, r, T, vol, q, n):
        '''

        Calculate the Black Scholes value of a traditional Power Call / Put option
        with a fixed strike
        Payoffs are of the form :
        C = max(S**n - K, 0)
        P = max(K - S**n, 0)

        Parameters
        ----------
        s : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying s
            Spot value of underlying asset at current time, t
        k : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying k
            Strike value of option, determined at initiation
        r : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying r
            Risk free interest rate, implied constant till expiration
        T : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying T
            Time till expiration for option
        vol : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying vol
            Volatility of underlying, implied constant till expiration in Black
            Scholes model
        q : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying q
            Continuous dividend payout, as a percentage
        n : number of any type (int, float8, float64 etc.)
            Power to which the underlying spot is raised at payoff

        Notes
        -----
        All parameters can be individual values.
        Only one of these parameters can be a numpy.array (not including 'n'),
        otherwise there will be a dimension mismatch.

        Returns
        -------
        [call,put] : list of pair of float or numpy.array values
            Power call and put values, type depends on input value.
            If all input values are individual numbers, then output will be float.
            If one input value, other than 'n', is numpy.array, then output
            will be numpy.array.

        '''
        d1 = (np.log(s/np.power(k,1/n)) + (r - q + vol*vol*(n - 0.5))*T) / (vol*np.sqrt(T))
        d2 = d1 - n*vol*np.sqrt(T)

        option = np.exp(T*(n-1)*(r + 0.5*n*vol*vol))*np.power(s,n)
        strike = k*np.exp(-r*T)

        put = strike*norm.cdf(-d2) - option*norm.cdf(-d1)
        call = option*norm.cdf(d1) - strike*norm.cdf(d2)
        return([call,put])

    @staticmethod
    def PowerStrike(s, k, r, T, vol, q, n):
        '''

        Calculate the Black Scholes value of Power Call / Put option with a
        fixed strike to the power n
        Payoffs are of the form :
        C = max(S**n - K**n, 0)
        P = max(K**n - S**n, 0)

        Parameters
        ----------
        s : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying s
            Spot value of underlying asset at current time, t
        k : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying k
            Strike value of option, determined at initiation
        r : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying r
            Risk free interest rate, implied constant till expiration
        T : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying T
            Time till expiration for option
        vol : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying vol
            Volatility of underlying, implied constant till expiration in Black
            Scholes model
        q : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying q
            Continuous dividend payout, as a percentage
        n : number of any type (int, float8, float64 etc.)
            Power to which the underlying spot is raised at payoff

        Notes
        -----
        All parameters can be individual values.
        Only one of these parameters can be a numpy.array (not including 'n'),
        otherwise there will be a dimension mismatch.

        Returns
        -------
        [call,put] : list of pair of float or numpy.array values
            Power call and put values, type depends on input value.
            If all input values are individual numbers, then output will be float.
            If one input value, other than 'n', is numpy.array, then output
            will be numpy.array.

        '''
        d1 = (np.log(s/np.power(k,1/n)) + (r - q + vol*vol*(n - 0.5))*T) / (vol*np.sqrt(T))
        d2 = d1 - n*vol*np.sqrt(T)

        option = np.exp(T*(n-1)*(r + 0.5*n*vol*vol))*np.power(s,n)
        strike = np.power(k,n)*np.exp(-r*T)

        put = strike*norm.cdf(-d2) - option*norm.cdf(-d1)
        call = option*norm.cdf(d1) - strike*norm.cdf(d2)
        return([call,put])

    @staticmethod
    def Margrabe(s, s2, T, vol, vol2, q, q2, corr):
        '''

        Calculate the Black Scholes value of the Margrabe Option
        Payoff is of the form :
        O = max(S_1 - S_2, 0)

        Parameters
        ----------
        s1 and s2 : number of any type (int, float8, float64 etc.), numpy array of
            any type should the user wish to have a list of values output with
            varying s
            Spot value of underlying assets 1 and 2 at current time, t
        T : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying T
            Time till expiration for option
        vol1 and vol2 : number of any type (int, float8, float64 etc.), numpy array
            of any type should the user wish to have a list of values output with
            varying vol
            Volatility of underlying for assets 1 and 2, implied constant till
            expiration in Black Scholes model
        q1 and q2 : number of any type (int, float8, float64 etc.), numpy array of
            any type should the user wish to have a list of values output with
            varying q
            Continuous dividend payout for assets 1 and 2, as a percentage
        corr : number of any type (int, float8, float64 etc.), numpy array of any
            type should the user wish to have a list of values output with varying
            corr
            Correlation between the motion of the underlying (relationship between
            the Weiner process of asset 1 and 2)

        Notes
        -----
        All parameters can be individual values.
        At most, only one pair of these parameters can be a numpy.array,
        otherwise there will be a dimension mismatch.

        Returns
        -------
        price : float or numpy.array value
            Margrabe price, type depends on input value.
            If all input values are individual numbers, then output will be float.
            If one pair of input values are a numpy.array,
            then output will be numpy.array.

        '''
        volMix = np.sqrt(vol*vol + vol2*vol2 - vol*vol2*corr)
        d1 = (np.log(s/s2) + (q2 - q + 0.5*(volMix**2))*T) / (volMix*np.sqrt(T))
        d2 = d1 - volMix*np.sqrt(T)

        option = np.exp(-q*T)*s*norm.cdf(d1)
        option2 = np.exp(-q2*T)*s2*norm.cdf(d2)

        price = option - option2
        return(price)

    @staticmethod
    def Lookback(s, M, r, T, vol, q):
        '''

        Calculate the Black Scholes value of floating strike
        Lookback Call / Put option
        Payoffs are of the form :
        C = S_T - min(m,m_T)
        P = max(M,M_T) - S_T
        where 'm' is the current minimum, or starting strike at initiation, and
        'm_T' is the minimum over the remaining life of the option
        similarly, 'M' is the current maximum, or starting strike at initiation, and
        'M_T' is the maximum over the remaining life of the option

        Parameters
        ----------
        s : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying s
            Spot value of underlying asset at current time, t
        M : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying k
            Strike value of option, determined by minimum value of underlying
            over the life of the option
        r : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying r
            Risk free interest rate, implied constant till expiration
        T : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying T
            Time till expiration for option
        vol : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying vol
            Volatility of underlying, implied constant till expiration in Black
            Scholes model
        q : number of any type (int, float8, float64 etc.), numpy array of any type
            should the user wish to have a list of values output with varying q
            Continuous dividend payout, as a percentage

        Notes
        -----
        All parameters can be individual values.
        Only one of these parameters can be a numpy.array, otherwise there will be
        a dimension mismatch.

        Returns
        -------
        [call,put] : list of pair of float or numpy.array values
            Lookback call and put values, type depends on input value.
            If all input values are individual numbers, then output will be float.
            If one input value is numpy.array, then output will be numpy.array.

        '''
        B = 2*(r - q) / (vol*vol)
        x = (np.log(s/M) + (r - q - 0.5*vol*vol)*T) / (vol*np.sqrt(T))
        y = (-np.log(s/M) - (r - q + 0.5*vol*vol)*T) / (vol*np.sqrt(T))

        option = s*np.exp(-q*T)
        minimum = M*np.exp(-r*T)
        left = np.exp(-r*T)*np.power(s/M,-B)
        right = np.exp(-q*T)

        call = (option*norm.cdf(x + vol*np.sqrt(T)) -
            minimum*norm.cdf(x) +
            (s/B)*(left*norm.cdf(y + B*vol*np.sqrt(T)) -
                right*norm.cdf(y)))
        put = (-option*norm.cdf(-x - vol*np.sqrt(T)) +
            minimum*norm.cdf(-x) -
            (s/B)*(left*norm.cdf(-y - B*vol*np.sqrt(T)) -
                right*norm.cdf(-y)))
        return([call,put])

class BarrierOptions:
    '''

    Provides an analytical solution to barrier options using
    the Black Scholes methodology. Due to the underlying calculus'
    assumptions, the risk-free rate and implied volatility of the
    underlying are held constant from t0 to t1.

    f(u) : the density function of the natural logarithm of
        the risk-neutral underlying asset return
    g(u) : the density function of the natural logarithm of
        the risk-neutral underlying asset return when the
        underlying asset price starts above/below the barrier
        crosses the barrier but ends up above/below the barrier
        at expiration

    The _I functions are set values used as the analytical
    solution to the barrier options. Since a barrier option
    is marked by a set of logic conditions being met, these
    can be manipulated via an alpha and beta scalar to account
    for each barrier option's requirements. Each barrier is
    a linear combination of this set of analytical solutions.

    I1 - I2 : call payoff integrated over f(u) between the
        strike and barrier
    I1 - I3 : call payoff integrated over the probability density
        of the terminal asset price conditional on NOT crossing
        the barrier
    I2 - I4 :
    I3 : call payoff integrated over g(u) conditional on crossing
        the barrier
    I4 : call payoff integrated over the density function of
        the natural logarithm under risk-neutral assumptions
        between the barrier and infinity
    I5 : rebate for "In" options
    I6 : reabte for "Out" options

    Parameters
    ----------
    spot : number of any type (int, float8, float64 etc.)
        Spot value of underlying asset at current time, t
    strike : number of any type (int, float8, float64 etc.)
        Strike value of option, determined at initiation
    riskfree : number of any type (int, float8, float64 etc.)
        Risk free interest rate, implied constant till expiration
    barrier : number of any type (int, float8, float64 etc.)
        Barrier value of option, determined at initiation
    tau : number of any type (int, float8, float64 etc.)
        Time till expiration for option, can be interpreted as 'T - t' should
        the option already be initiated, and be 't' time from time = 0
    vol : number of any type (int, float8, float64 etc.)
        Volatility of underlying, implied constant till expiration in Black
        Scholes model
    div : number of any type (int, float8, float64 etc.)
        Continuous dividend payout, as a percentage
    rebate : number of any type (int, float8, float64 etc.)
        Rebate of barrier option, if there is no rebate provision, set = 0
        Default value is 0

    '''
    def __init__(self, spot, strike, riskfree, barrier, tau, vol, div, rebate=0):
        self.s = spot
        self.k = strike
        self.r = riskfree
        self.Z = barrier
        self.T = tau
        self.vol = vol
        self.q = div
        self.R = rebate

        self.L = self._lambdaDrift()

    def _lambdaDrift(self):
        '''

        Lambda constant calculated from the risk-netural
        drift of the underlying

        Returns
        -------
        l : float
            The lambda constant used in each of barrier option
            analytical solutions

        '''
        m = self._mu()
        l = 1 + (m / (self.vol*self.vol))
        return(l)

    def _mu(self):
        '''

        The underlying's risk-netural drift term,
        referred to as 'Mu'

        Returns
        -------
        mu : float
            The drift term used in the barrier option
            analytical solutions

        '''
        mu = (self.r - self.q - self.vol*self.vol*0.5)
        return(mu)

    def _x1val(self):
        x = np.log(self.s / self.Z) / (self.vol*np.sqrt(self.T)) + self.L*self.vol*np.sqrt(self.T)
        return(x)

    def _xval(self):
        x = np.log(self.s / self.k) / (self.vol*np.sqrt(self.T)) + self.L*self.vol*np.sqrt(self.T)
        return(x)

    def _y1val(self):
        y = np.log(self.Z / self.s) / (self.vol*np.sqrt(self.T)) + self.L*self.vol*np.sqrt(self.T)
        return(y)

    def _yval(self):
        y = np.log(np.square(self.Z) / (self.s*self.k)) / (self.vol*np.sqrt(self.T)) + self.L*self.vol*np.sqrt(self.T)
        return(y)

    def _zval(self):
        z = np.log(self.Z / self.s) / (self.vol*np.sqrt(self.T)) + self._bval()*self.vol*np.sqrt(self.T)
        return(z)

    def _aval(self):
        a = self._mu() / (self.vol*self.vol)
        return(a)

    def _bval(self):
        b = np.sqrt(self._mu()**2 + 2*self.r*self.vol*self.vol) / (self.vol*self.vol)
        return(b)

    def _I1(self, alpha : int, beta : int):
        '''

        Parameters
        ----------
        alpha : int
            Scalar to represent either a call or put
        beta : int
            Scalar to represent whether the asset price
            starts above or below the barrier

        Returns
        -------
        partial : float
            The I1 partial analytical solution for the barrier option

        '''
        xval = self._xval()
        partial = alpha*self.s*norm.cdf(alpha*xval) - alpha*self.k*np.exp(-1*self.r*self.T)*norm.cdf(alpha*xval - alpha*self.vol*np.sqrt(self.T))
        return(partial)

    def _I2(self, alpha : int, beta : int):
        '''

        Parameters
        ----------
        alpha : int
            Scalar to represent either a call or put
        beta : int
            Scalar to represent whether the asset price
            starts above or below the barrier

        Returns
        -------
        partial : float
            The I2 partial analytical solution for the barrier option

        '''
        xval = self._x1val()
        partial = alpha*self.s*norm.cdf(alpha*xval) - alpha*self.k*np.exp(-1*self.r*self.T)*norm.cdf(alpha*xval - alpha*self.vol*np.sqrt(self.T))
        return(partial)

    def _I3(self, alpha : int, beta : int):
        '''

        Parameters
        ----------
        alpha : int
            Scalar to represent either a call or put
        beta : int
            Scalar to represent whether the asset price
            starts above or below the barrier

        Returns
        -------
        partial : float
            The I3 partial analytical solution for the barrier option

        '''
        yval = self._yval()
        partial = alpha*self.s*np.power(self.Z / self.s, 2*self.L)*norm.cdf(beta*yval) - \
            alpha*self.k*np.exp(-1*self.r*self.T)*np.power(self.Z / self.s, 2*self.L - 2)*norm.cdf(beta*yval - beta*self.vol*np.sqrt(self.T))
        return(partial)

    def _I4(self, alpha : int, beta : int):
        '''

        Parameters
        ----------
        alpha : int
            Scalar to represent either a call or put
        beta : int
            Scalar to represent whether the asset price
            starts above or below the barrier

        Returns
        -------
        partial : float
            The I4 partial analytical solution for the barrier option

        '''
        yval = self._y1val()
        partial = alpha*self.s*np.power(self.Z / self.s, 2*self.L)*norm.cdf(beta*yval) - \
            alpha*self.k*np.exp(-1*self.r*self.T)*np.power(self.Z / self.s, 2*self.L - 2)*norm.cdf(beta*yval - beta*self.vol*np.sqrt(self.T))
        return(partial)

    def _I5(self, beta : int):
        x = self._x1val()
        y = self._y1val()
        partial = self.R*np.exp(-1*self.r*self.T) * \
            (norm.cdf(beta*x - beta*self.vol*np.sqrt(self.T)) - \
             np.power(self.Z / self.s, 2*self.L - 2)*norm.cdf(beta*y - beta*self.vol*np.sqrt(self.T)))
        return(partial)

    def _I6(self, beta : int):
        a = self._aval()
        b = self._bval()
        z = self._zval()
        partial = self.R * (np.power(self.Z / self.s, a - b)*norm.cdf(beta*z) - \
                            np.power(self.Z / self.s, a - b)*norm.cdf(beta*z - 2*beta*b*self.vol*np.sqrt(self.T)))
        return(partial)

    def DownOutPut(self):
        '''

        Calculate the Down-and-Out PUT option

        Returns
        -------
        price : float
            Price value of barrier option.

        '''
        a = -1
        b = 1

        if self.k > self.Z and self.s >= self.Z:
            price = self._I1(a,b) - self._I2(a,b) + self._I3(a,b) - self._I4(a,b) + self._I6(b)
        elif self.k < self.Z and self.s >= self.Z:
            price = self._I6(b)
        else:
            price = 0.0
        return(max(price, 0.0))

    def DownOutCall(self):
        '''

        Calculate the Down-and-Out CALL option, for any barrier

        Returns
        -------
        price : float
            Price value of barrier option.

        '''
        a = 1
        b = 1

        if self.k > self.Z and self.s >= self.Z:
            price = self._I1(a,b) - self._I3(a,b) + self._I6(b)
        elif self.k < self.Z and self.s >= self.Z:
            price = self._I2(a,b) - self._I4(a,b) + self._I6(b)
        else:
            price = 0.0
        return(max(price, 0.0))

    def UpOutCall(self):
        '''

        Calculate the Up-and-Out CALL option

        Returns
        -------
        price : float
            Price value of barrier option.

        '''
        a = 1
        b = -1

        if self.k > self.Z and self.s <= self.Z:
            price = self._I1(a,b) - self._I2(a,b) + self._I3(a,b) - self._I4(a,b) + self._I6(b)
        elif self.k < self.Z and self.s <= self.Z:
            price = self._I6(b)
        else:
            price = 0.0
        return(max(price, 0.0))

    def UpOutPut(self):
        '''

        Calculate the Up-and-Out PUT option

        Returns
        -------
        price : float
            Price value of barrier option.

        '''
        a = -1
        b = -1

        if self.k < self.Z and self.s <= self.Z:
            price = self._I1(a,b) - self._I3(a,b) + self._I6(b)
        elif self.k > self.Z and self.s <= self.Z:
            price = self._I2(a,b) - self._I4(a,b) + self._I6(b)
        else:
            price = 0.0
        return(max(price, 0.0))

    def DownInCall(self):
        '''

        Calculate the Down-and-In CALL option

        Returns
        -------
        price : float
            Price value of barrier option.

        '''
        a = 1
        b = 1

        if self.k > self.Z:
            price = self._I3(a,b) + self._I5(b)
        elif self.k < self.Z:
            price = self._I1(a,b) - self._I2(a,b) + self._I4(a,b) + self._I5(b)
        else:
            price = 0.0
        return(max(price, 0.0))

    def DownInPut(self):
        '''

        Calculate the Down-and-In PUT option

        Returns
        -------
        price : float
            Price value of barrier option.

        '''
        a = -1
        b = 1

        if self.k > self.Z:
            price = self._I2(a,b) - self._I3(a,b) + self._I4(a,b) + self._I5(b)
        elif self.k < self.Z:
            price = self._I1(a,b) + self._I5(b)
        else:
            price = 0.0
        return(max(price, 0.0))

    def UpInCall(self):
        '''

        Calculate the Up-and-In Call option

        Returns
        -------
        price : float
            Price value of barrier option.

        '''
        a = 1
        b = -1

        if self.k > self.Z:
            price = self._I1(a,b) + self._I5(b)
        elif self.k < self.Z:
            price = self._I2(a,b) - self._I3(a,b) + self._I4(a,b) + self._I5(b)
        else:
            price = 0.0
        return(max(price, 0.0))

    def UpInPut(self):
        '''

        Calculate the Up-and-In Put option

        Returns
        -------
        price : float
            Price value of barrier option.

        '''
        a = -1
        b = -1

        if self.k > self.Z:
            price = self._I1(a,b) - self._I2(a,b) + self._I4(a,b) + self._I5(b)
        elif self.k < self.Z:
            price = self._I3(a,b) + self._I5(b)
        else:
            price = 0.0
        return(max(price, 0.0))

if __name__ == '__main__':
    opt1 = [1, 2, 0.02, 1.5, 5, 0.05, 0.01, 0.01]
    bro = BarrierOptions(*opt1)
    a = bro.DownInCall()


import numpy as np
from scipy.stats import entropy

__all__ = [
  'mutual_information',
  'binarize',
  'split'
]

def mutual_information(hist):
  """
  Mutual Information is used as a measure of dependency between variables.

  However, it is inherently difficult to reliably compute MI, thus, don't trust
  this function too much. Also, it is generally not a good idea to use it for
  anything beyond 2 dimensions.

  :param hist: 2D histogram for X and Y random variables.
  :return: rough estimation of mutual information coefficient between X and Y.
  """
  pmf = hist / np.sum(hist, dtype='float64')
  marginal_x, marginal_y = np.sum(pmf, axis=1), np.sum(pmf, axis=0)

  H_xy = entropy(pmf.reshape(-1))

  ### to avoid warnings...
  pmf = np.where(hist > 0, pmf, 1)

  H_x = -np.sum(
    np.where(hist > 0, pmf * (np.log(pmf) - np.log(marginal_x)[:, None]), 0)
  )

  H_y = -np.sum(
    np.where(hist > 0, pmf * (np.log(pmf) - np.log(marginal_y)[None, :]), 0)
  )

  MI = H_xy - H_x - H_y

  return MI

def binarize(xs, n_bins=10):
  if xs.dtype.kind == 'i':
    n = np.max(xs) - np.min(xs) + 1
    indx = xs - np.min(xs)
    return indx, np.arange(n + 1) - 0.5
  else:
    qs = np.linspace(0, 1, num=n_bins + 1)[1:-1]
    bins = np.quantile(xs, qs)
    indx = np.searchsorted(bins, xs)

    return indx, np.concatenate(([np.min(xs)], bins, [np.max(xs)]), axis=0)

def split(*data, split_ratios=0.8, seed=None):
  if len(data) == 0:
    return tuple()

  try:
    iter(split_ratios)
  except TypeError:
    split_ratios = (split_ratios, 1 - split_ratios)

  assert all([r >= 0 for r in split_ratios])

  size = len(data[0])

  split_ratios = np.array(split_ratios)
  split_sizes = np.ceil((split_ratios * size) / np.sum(split_ratios)).astype('int64')
  split_bins = np.cumsum([0] + list(split_sizes))
  split_bins[-1] = size

  if seed is not None:
    state = np.random.get_state()
    np.random.seed(seed)
  else:
    state = None

  r = np.random.permutation(size)

  if state is not None:
    np.random.set_state(state)

  result = list()

  for i, _ in enumerate(split_ratios):
    from_indx = split_bins[i]
    to_indx = split_bins[i + 1]
    indx = r[from_indx:to_indx]

    for d in data:
      if isinstance(d, np.ndarray):
        result.append(d[indx])
      else:
        result.append([ d[i] for i in indx ])

  return tuple(result)


from math import *  # pylint: disable=unused-wildcard-import disable=wildcard-import
from math import log as ln  # pylint: disable=unused-import
from math import log10 as log  # pylint: disable=unused-import
from tkinter import Label, Toplevel
from typing import Union
import scipy.integrate
import numpy as np

class IntegralWindow(Toplevel):
    '''Creates Toplevel window for integral and sum solutions'''
    def __init__(self, startrange: float, endrange: float, n: int, fx: str) -> None:
        Toplevel.__init__(self)
        self.title("Integral")
        self.geometry("300x180")
        self.startrange = startrange
        self.endrange = endrange
        self.n = n
        self.fx = fx
        self.integrate_label = Label(self, text="Integral:")
        self.integrate_answer = Label(self)

        self.width = (self.endrange - self.startrange)/n
        # left riemann sum
        self.lr_sum_label = Label(self, text="Left Riemann Sum:")
        self.lr_sum = Label(self)
        # right riemann sum
        self.rr_sum = Label(self)
        self.rr_sum_label = Label(self, text="Right Riemann Sum")
        # middle riemann sum
        self.mr_sum_label = Label(self, text="Middle Riemann Sum")
        self.mr_sum = Label(self)
        # Trapezoid sum
        self.trpz_sum_label = Label(self, text="Trapezoidal Sum")
        self.trpz_sum = Label(self, text=self.trapezoid(self.fx))

        self.integrate(startrange, endrange, n, fx)
        self.grid_layout()

    def grid_layout(self) -> None:
        '''Grids labels'''
        self.integrate_label.grid(row=0, column=0)
        self.integrate_answer.grid(row=0, column=1)
        self.lr_sum_label.grid(row=1, column=0)
        self.lr_sum.grid(row=1, column=1)
        self.rr_sum_label.grid(row=2, column=0)
        self.rr_sum.grid(row=2, column=1)
        self.mr_sum_label.grid(row=3, column=0)
        self.mr_sum.grid(row=3, column=1)
        self.trpz_sum_label.grid(row=4, column=0)
        self.trpz_sum.grid(row=4, column=1)

    def integrate(self, startrange: float, endrange: float, n: int, fx: str) -> None:
        '''Calls riemann and trapezoid and does integration'''
        self.function = eval(f"lambda x:{fx}")
        self.i = scipy.integrate.quad(
            self.function, self.startrange, self.endrange)
        self.integrate_answer['text'] = str(self.i[0])
        # left riemann sum
        self.lr_sum['text'] = self.riemann(
            self.startrange, self.endrange, self.n, self.fx, self.width)
        # right riemann sum
        self.rr_sum['text'] = self.riemann(
            endrange, startrange, n, fx, self.width)
        # middle riemann sum
        self.mr_sum['text'] = self.riemann(
            startrange+self.width/2, (self.endrange+self.width/2), n, fx, self.width)

    def func(self, x: float, fx: str) -> Union[int, float]:
        '''evaluates function at the given x value'''
        return eval(fx)

    def riemann(self, startrange: float, endrange: float, N: int, fx: str, width: float):
        '''returns sum of a given riemann given parameters'''
        points = np.linspace(startrange, endrange, num=N, endpoint=False)
        func_points = []
        for i in points:
            func_points.append(self.func(i, fx))
        func_points = np.asarray(func_points)
        indv_area = func_points*width
        return np.sum(indv_area)

    def trapezoid(self, fx):
        '''returns sum of area using trapezoids'''
        points = np.arange(self.startrange, self.endrange, self.width)
        points = np.append(points, self.endrange)
        func_points = []
        for i in points:
            func_points.append(self.func(i, fx))
        ans = np.trapz(func_points, dx=self.width)
        return ans

if __name__ == "__main__":
    pass


import numpy as np
import codecs

#计算欧氏距离
def distance(x1,x2):
    return np.sqrt(sum(np.power(x1-x2,2)))

#对一个样本找到与该样本距离最近的聚类中心
def nearest(point, cluster_centers):
    min_dist = np.inf
    m = np.shape(cluster_centers)[0]  # 当前已经初始化的聚类中心的个数
    for i in range(m):
        # 计算point与每个聚类中心之间的距离
        d = distance(point, cluster_centers[i, ])
        # 选择最短距离
        if min_dist > d:
            min_dist = d
    return min_dist
#选择尽可能相距较远的类中心
def get_centroids(dataset, k):
    m, n = np.shape(dataset)
    cluster_centers = np.zeros((k , n))
    index = np.random.randint(0, m)
    cluster_centers[0,] = dataset[index, ]
    # 2、初始化一个距离的序列
    d = [0.0 for _ in range(m)]
    for i in range(1, k):
        sum_all = 0
        for j in range(m):
            # 3、对每一个样本找到最近的聚类中心点
            d[j] = nearest(dataset[j, ], cluster_centers[0:i, ])
            # 4、将所有的最短距离相加
            sum_all += d[j]
        # 5、取得sum_all之间的随机值
        sum_all *= np.random.rand()
        # 6、获得距离最远的样本点作为聚类中心点
        for j, di in enumerate(d):
            sum_all=sum_all - di
            if sum_all > 0:
                continue
            cluster_centers[i,] = dataset[j, ]
            break
    return cluster_centers

#主程序
def Kmeans(dataset,k):
    row_m=np.shape(dataset)[0]
    cluster_assign=np.zeros((row_m,2))
    center=get_centroids(dataset,k)
    change=True
    while change:
        change=False
        for i in range(row_m):
            mindist=np.inf
            min_index=-1
            for j in range(k):
                distance1=distance(center[j,:],dataset[i,:])
                if distance1<mindist:
                    mindist=distance1
                    min_index=j
            if cluster_assign[i,0] != min_index:
                change=True
            cluster_assign[i,:]=min_index,mindist**2
        for cen in range(k):
            cluster_data=dataset[np.nonzero(cluster_assign[:,0]==cen)]
            center[cen,:]=np.mean(cluster_data,0)
    return center ,cluster_assign
#cluster_center,cluster_assign=Kmeans(datas,3)

def kmeanspp_select(gradient, k):
    center, cluster_assign = Kmeans(gradient, k)
    keep_idx = []
    tot = cluster_assign.shape[0]
    mapp = {}
    for i in range(tot):
        cid = cluster_assign[i][0]
        dist = cluster_assign[i][1]
        if not cid in mapp:
            mapp[cid] = (dist, i)
        else:
            if dist < mapp[cid][0]:
                mapp[cid] = (dist, i)
    for cid in mapp:
        keep_idx.append(mapp[cid][1])
    return keep_idx


# Chapter 5 Compressing Data via Dimensionality Reduction
# Principal Components Analysis
# Total and explained variance
'''
First, we will start by loading the Wine dataset that we have been working with
in Chapter 4'''
import pandas as pd
df_wine = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data', header=None)

'''
Next, we will process the Wine data into separate training and test sets using 70
percent and 30 percent of the data, respectively - and standardize it to unit variance'''
from sklearn.cross_validation import train_test_split
from sklearn.preprocessing import StandardScaler
X, y = df_wine.iloc[:, 1:].values, df_wine.iloc[:, 0].values
X_train, X_test, y_train, y_test = \
train_test_split(X, y, test_size=0.3, random_state=0)
sc = StandardScaler()
X_train_std = sc.fit_transform(X_train)
X_test_std = sc.transform(X_test)

'''
We will use the linalg.eig function from Numpy to obtain the eigenpairs of the Wine
covariance matrix'''
import numpy as np
cov_mat = np.cov(X_train_std.T)
eigen_vals, eigen_vecs = np.linalg.eig(cov_mat)
print '\nEigenvalues \n%s' % eigen_vals
'''
Using the numpy.cov function, we computed the covariance matrix of the
standardized training dataset. Using the linalg.eig function, we performed the
eigendecomposition that yielded a vector (eigen_vals) consisting of 13 eigenvalues
 and the corresponding eigenvectors stored as columns in a 13x13 -dimensional
 matrix (eigen_vecs).
'''
'''
Since we want to reduce the dimensionality of our dataset by compressing it onto
a new feature subspace, we only select the subset of the eigenvectors(principle
components) that contains most of the information (variance). Since the eigenvalues
define the magitude of the eigenvectors, we have to sort the eigenvalues by
decreasing magnitude; we are interested in the top k eigenvectors based on the
values of their corresponding eigenvalues.
But before we collect those k most informative eigenvectors, let's plot the variance
explained ratios of the eigenvalues

'''
'''
Using the NumPy cumsum function, we can then calculate the cumulative  sum of
explained variances, which we will plot via matplotlib's step function: '''
tot = sum(eigen_vals)
var_exp = [(i / tot) for i in sorted(eigen_vals, reverse=True)]
cum_var_exp = np.cumsum(var_exp)

import matplotlib.pyplot as plt
plt.bar(range(1, 14), var_exp, alpha=0.5, align='center', label='individual explained variance')
plt.step(range(1,14), cum_var_exp, where='mid', label='cumulative explained variance')
plt.ylabel('Explained variance ratio')
plt.xlabel('Principal components')
plt.legend(loc='best')
plt.show()

'''
The resulting plot indicates that the first principal component alone accounts for
40 percent of the variance. Also, we can see that the first two principal components
combined explain almost 60 percent of the variance in the data:
'''
# Feature transformation
# We start by sorting the eigenpairs by decreasing order of the eigenvalues:
eigen_pairs = [(np.abs(eigen_vals[i]), eigen_vecs[:, i]) for i in range(len(eigen_vals))]
eigen_pairs.sort(reverse=True)

w = np.hstack((eigen_pairs[0][1][:, np.newaxis],   # eigen_pairs[0][1].shape =(13,)
                       eigen_pairs[1][1][:, np.newaxis]))  # eigen_pairs[0][1][:, np.newaxis].shape= (13, 1)
print 'Matrix W: \n', w

'''
By executing the preceding code, we have created a 13x2-dimensional projection
matrix w from the top two eigenvectors. Using the projection matrix, we can now
transform a sample x (represented as 1x13-dimensional row vector) onto the PCA
subspace obraining x', a now two-dimensional sample vector consisting of two new
features:       x' = xW
'''
X_train_std[0].dot(w) # 1x13 13x2 = 1x2
# similarly, we can transform the entire 124 x 13-dimensional training dataset onto the two principal components
# by calculating the matrix dot product:
X_train_pca = X_train_std.dot(w) # 124x13 13x2 = 124x2

'''
Lastly, let's visualize the transformed Wine training set, now stored as an
124 x 2-dimensional matrix, in a two-dimensional scatterplot:
'''
colors = ['r', 'b', 'g']
markers = ['s', 'x', 'o']
for l, c, m in zip(np.unique(y_train), colors, markers):
    plt.scatter(X_train_pca[y_train==l, 0], X_train_pca[y_train==l, 1],c=c, label=1, marker = m)

plt.xlabel('PC 1')
plt.ylabel('PC 2')
plt.legend(loc='lower left')
plt.show()

# Principal component analysis in scikit-learn
from Plot_Decision_Regions import plot_decision_regions
from sklearn.linear_model import LogisticRegression
from sklearn.decomposition import PCA
pca = PCA(n_components=2) # Number of components to keep. if n_components is not set all components are kept
lr = LogisticRegression()
X_train_pca = pca.fit_transform(X_train_std)
X_test_pca = pca.transform(X_test_std)
lr.fit(X_train_pca, y_train)
plot_decision_regions(X_train_pca, y_train, classifier=lr)
plt.xlabel('PC1')
plt.ylabel('PC2')
plt.legend(loc='lower left')
plt.show()

'''
Let's plot the decision regions of the logistic regression on the transformed test
dataset to see if it can separate the classes well.
'''
plot_decision_regions(X_test_pca, y_test, classifier=lr)
plt.xlabel('PC1')
plt.ylabel('PC2')
plt.legend(loc='lower left')
plt.show()

'''
If we are interested in the explained variance ratios of the different principal
components, we can simply intialize the PCA class with the n_components parameter
set to None, so all principal components are kept and the explained variance ratio can
then be accessed via the explained _variance_ratio_ attribute
'''
pca = PCA(n_components=None)
X_train_pca = pca.fit_transform(X_train_std)
pca.explained_variance_ratio_

'''
From Wikipedia:
The eigendecomposition of a symmetric positive semidefinite (PSD) matrix yields an
orthogonal basis of eigenvectors, each of which has a nonnegative eigenvalue. The
orthogonal decomposition of a PSD matrix is used in multivariate analysis, where the
sample covariance matrices are PSD. This orthogonal decomposition is called principal
components analysis (PCA) in statistics. PCA studies linear relations among variables.
PCA is performed on the covariance matrix or the correlation matrix (in which each
variable is scaled to have its sample variance equal to one). For the covariance or
correlation matrix, the eigenvectors correspond to principal components and the
eigenvalues to the variance explained by the principal components. Principal
component analysis of the correlation matrix provides an orthonormal eigen-basis for
the space of the observed data: In this basis, the largest eigenvalues correspond to
the principal components that are associated with most of the covariability among a
number of observed data.

'''

self_practice/Chapter 5 Principal Component Analysis.py


#--------------------------------
#SIMPLE UNIVARIABLE REGRESSION EXAMPLE
#--------------------------------

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from   scipy.optimize import curve_fit

#------------------------
#GENERATE TOY DATA
#------------------------

data_type="E"; #L=linear  E=exp   Q=quadratic
model_type="E" #L=linear  E=exp   Q=quadratic

#GROUND TRUTH DATA GENERATING FUNCTION
def G(x):
	if(data_type=="L"): out=50*x+3
	if(data_type=="Q"): out=100*x**2+50*x+3
	if(data_type=="E"): out=50*np.exp(-0.1*(x-2))+3
	return out

#NOISY DATA
N=200; xmin=-10; xmax=10
x = np.linspace(xmin,xmax,N)
y = G(x)  #PRISTINE DATA
# noise=np.random.normal(loc=0.0, scale=0.05*(max(y)-min(y)),size=len(x))
noise=0.15*(max(y)-min(y))*np.random.uniform(-1,1,size=len(x))
yn = y+ noise

#GROUND TRUTH DATA
xe = np.linspace(xmin,2*xmax,int(2*N))
ye = G(xe)

#NORMALIZE
xe=(xe-np.mean(x))/np.std(x)
ye=(ye-np.mean(y))/np.std(y)
yn=(yn-np.mean(y))/np.std(y)

x=(x-np.mean(x))/np.std(x)
y=(y-np.mean(y))/np.std(y)

#------------------------
#TRAINING
#------------------------

##FITTING MODEL
def model(x,p0,p1,p2,p3):
	if(model_type=="L"): out=p1*x+p0
	if(model_type=="Q"): out=p2*x**2+p1*x+p0
	if(model_type=="E"): out=p3*np.exp(p2*(x-p1))+p0
	return out

#INITIAL GUESS FOR PARAMETERS p0,p1,p2,p3
po=[0.1,0.1,0.1,0.1]

#FIT MODEL
popt, pcov = curve_fit(model, x, yn,po) #,[0.1,0.1,0.1])

#------------------------
#PLOT
#------------------------

fig, ax = plt.subplots()
ax.plot(x, yn, 'o', label='Data')
ax.plot(xe, ye, '-', label='Ground-Truth')
ax.plot(xe, model(xe, *popt), 'r-', label="Model")

ax.legend()
FS=18   #FONT SIZE
plt.xlabel('X', fontsize=FS)
plt.ylabel('Y', fontsize=FS)

plt.show()


# Copyright 2020, Battelle Energy Alliance, LLC
# ALL RIGHTS RESERVED
import math
from scipy.integrate import quad
import time

def timeDepLambda(t,a,b):
  exponent = a+t*b
  return 10**exponent

def pdfFailure(t,a,b):
  first  = timeDepLambda(t,a,b)
  second = math.exp(-quad(timeDepLambda, 0, t, args=(a,b))[0])
  return first*second

t = 3700.
a_V2 = -4.
b_V2 = 1.E-4

start_time = time.clock()
probability1 = quad(pdfFailure, 0, t, args=(a_V2,b_V2))
print(probability1, time.clock() - start_time)

start_time = time.clock()
probability2 = 1. - math.exp(-quad(timeDepLambda, 0, t, args=(a_V2,b_V2))[0])
print(probability2, time.clock() - start_time)


"""
Plots spectral density for AR(1) X' = phi X + epsilon
"""
import numpy as np
import matplotlib.pyplot as plt

def ar1_sd(phi, omega):
    return 1 / (1 - 2 * phi * np.cos(omega) + phi**2)

omegas = np.linspace(0, np.pi, 180)
num_rows, num_cols = 2, 1
fig, axes = plt.subplots(num_rows, num_cols, figsize=(10, 8))
plt.subplots_adjust(hspace=0.4)

# Autocovariance when phi = 0.8
temp = r'spectral density, $\phi = {0:.2}$'
for i, phi in enumerate((0.8, -0.8)):
    ax = axes[i]
    sd = ar1_sd(phi, omegas)
    ax.plot(omegas, sd, 'b-', alpha=0.6, lw=2, label=temp.format(phi))
    ax.legend(loc='upper center')
    ax.set_xlabel('frequency')
    ax.set_xlim((0, np.pi))
plt.show()


import numpy as np

def gram_schmidt(A):
    U = np.zeros(A.shape)
    U[:, 0] = A[:, 0] / np.linalg.norm(A[:, 0])
    for i in range(1, A.shape[1]):
        U[:, i] = A[:, i]
        for j in range(i-1):
            U[:, i] = U[:, i] - (U[:, j].T @ U[:, i]) / (np.linalg.norm(U[:, j]))**2 * U[:, j]
        U[:, i] = U[:, i] / np.linalg.norm(U[:, i])

    return U

A = np.array([[1, 1, 1],
              [1/1000, 1/1000, 0],
              [1/1000, 0, 1/1000]])

U = gram_schmidt(A)

print(U)

# Orthogonality test
u1_u2_dot = U[:, 0].T @ U[:, 1]
print(f'u1.T @ u2 = {u1_u2_dot}')

u1_u3_dot = U[:, 0].T @ U[:, 2]
print(f'u1.T @ u3 = {u1_u3_dot}')


from numpy import loadtxt, zeros, e, array, log, ones, where

def feature_normalize(input):
    mu = input.mean(axis=0)
    sd = input.std(axis=0)
    input_norm = (input - mu) / sd

    return input_norm

def sigmoid(X):
    return 1.0 / (1.0 + e ** (-1.0 * X))

def cost_function(theta, x, y):
    m = y.size
    h = sigmoid(x.dot(theta))
    J = (1.0 / m) * ((-y.T.dot(log(h))) - ((1.0 - y.T).dot(log(1.0 - h))))
    return - 1 * J.sum()

def gradient_descent(x, y, theta, alpha = 0.01, num_iters = 1):
    m = x.shape[0]
    theta_size = theta.size
    for i in range(num_iters):

        predictions = x.dot(theta)

        for it in range(theta_size):
            temp = x[:, it]
            temp.shape = (m, 1)

            errors_x1 = (predictions - y) * temp

            theta[it][0] = theta[it][0] - alpha * (1.0 / m) * errors_x1.sum()

        cost_function(theta, x, y)

    return theta

def predict(theta, x):
    m, n = x.shape
    p = zeros(shape=(m, 1))

    h = sigmoid(x.dot(theta))

    for it in range(h.shape[0]):
        if h[it] >= 0.5:
            p[it, 0] = 1
        else:
            p[it, 0] = 0

    return p

data = loadtxt('ex2data1.txt', delimiter=',')
x = data[:, :-1]
y = data[:, -1]
m, n = x.shape
y.shape = (m, 1)

x_norm = feature_normalize(x)
#theta0, theta1, thetaM
x = ones(shape=(m, 3))
x[:, 1:3] = x_norm

theta = zeros(shape=(n+1, 1))

theta = gradient_descent(x, y, theta)
print theta

p = predict(array(theta), x)
print 'Accuracy: %f' % ((y[where(p == y)].size / float(y.size)) * 100.0)


import numpy as np

'''
The following code is to help you play with Numpy, which is a library
that provides functions that are especially useful when you have to
work with large arrays and matrices of numeric data, like doing
matrix matrix multiplications. Also, Numpy is battle tested and
optimized so that it runs fast, much faster than if you were working
with Python lists directly.
'''

'''
The array object class is the foundation of Numpy, and Numpy arrays are like
lists in Python, except that every thing inside an array must be of the
same type, like int or float.
'''
# Change False to True to see Numpy arrays in action
if False:
    array = np.array([1, 4, 5, 8], float)
    print array
    print ""
    array = np.array([[1, 2, 3], [4, 5, 6]], float)  # a 2D array/Matrix
    print array

'''
You can index, slice, and manipulate a Numpy array much like you would with a
a Python list.
'''
# Change False to True to see array indexing and slicing in action
if False:
    array = np.array([1, 4, 5, 8], float)
    print array
    print ""
    print array[1]
    print ""
    print array[:2]
    print ""
    array[1] = 5.0
    print array[1]

# Change False to True to see Matrix indexing and slicing in action
if False:
    two_D_array = np.array([[1, 2, 3], [4, 5, 6]], float)
    print two_D_array
    print ""
    print two_D_array[1][1]
    print ""
    print two_D_array[0, :]
    print ""
    print two_D_array[1, :]

'''
Here are some arithmetic operations that you can do with Numpy arrays
'''
# Change False to True to see Array arithmetics in action
if False:
    array_1 = np.array([1, 2, 3], float)
    array_2 = np.array([5, 2, 6], float)
    print array_1 + array_2
    print ""
    print array_1 - array_2
    print ""
    print array_1 * array_2

# Change False to True to see Matrix arithmetics in action
if False:
    array_1 = np.array([[1, 2], [3, 4]], float)
    array_2 = np.array([[5, 6], [7, 8]], float)
    print array_1 + array_2
    print ""
    print array_1 - array_2
    print ""
    print array_1 * array_2

'''
In addition to the standard arthimetic operations, Numpy also has a range of
other mathematical operations that you can apply to Numpy arrays, such as
mean and dot product.

Both of these functions will be useful in later programming quizzes.
'''
if True:
    array_1 = np.array([1, 2, 3], float)
    array_2 = np.array([[6], [7], [8]], float)
    print np.mean(array_1)
    print np.mean(array_2)
    print ""
    print np.dot(array_1, array_2)


import myutil as mu
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import TensorDataset  # 텐서데이터셋
from torch.utils.data import DataLoader  # 데이터로더
from torch.utils.data import Dataset

import numpy as np  # 넘파이 사용

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

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

torch.manual_seed(1)

x_data = [[1, 2], [2, 3], [3, 1], [4, 3], [5, 3], [6, 2]]
y_data = [[0], [0], [0], [1], [1], [1]]
x_train = torch.FloatTensor(x_data)
y_train = torch.FloatTensor(y_data)

W = torch.zeros((2, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
hypothesis = 1 / (1 + torch.exp(-(x_train.matmul(W) + b)))
mu.log("hypothesis", hypothesis)
mu.log("y_train", y_train)

hypothesis = torch.sigmoid(x_train.matmul(W) + b)
mu.log("hypothesis", hypothesis)
mu.log("y_train", y_train)

losses = -(y_train * torch.log(hypothesis)) + (1 - y_train) * torch.log(1 - hypothesis)
cost = losses.mean()
mu.log("losses", losses)
mu.log("cost", cost)

loss = F.binary_cross_entropy(hypothesis, y_train)
mu.log("loss.item()", loss.item())

x_data = [[1, 2], [2, 3], [3, 1], [4, 3], [5, 3], [6, 2]]
y_data = [[0], [0], [0], [1], [1], [1]]
x_train = torch.FloatTensor(x_data)
y_train = torch.FloatTensor(y_data)

W = torch.zeros((2, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
optimizer = optim.SGD([W, b], lr=1)
nb_epoches = 1000
mu.plt_init()

'''inserted code'''
import sys
sys.path.append("/data")
from scripts.utils.torch_utils import TorchScheduler
scheduler = TorchScheduler(name="PyTorchDeepLearningStart.0401_logistic_regression")
'''inserted code'''

while True:
    hypothesis = torch.sigmoid(x_train.matmul(W) + b)
    cost = -(y_train * torch.log(hypothesis) + (1 - y_train) * torch.log(1 - hypothesis)).mean()
    accuracy = mu.get_regression_accuracy(hypothesis, y_train)
    optimizer.zero_grad()
    cost.backward()
    optimizer.step()

    '''inserted code'''
    scheduler.loss_checker(cost)
    scheduler.check_time()
    '''inserted code'''

mu.plt_show()
mu.log("W", W)
mu.log("b", b)

prediction = hypothesis >= torch.FloatTensor([0.5])
mu.log("prediction", prediction)
mu.log("y_data", y_data)


import numpy as np

def accuracy(confusion_matrix: np.ndarray):
    """
    Computes accuracy of the classifier based on confusion matrix.

    Parameters
    ----------
    confusion_matrix: ArrayLike
        The Numpy nxn array or nxn list representing confusion matrix

    Returns
    -------
    float : accuracy value
    """
    return np.trace(confusion_matrix) / np.sum(confusion_matrix)

def mean_precision(confusion_matrix: np.ndarray):
    """
    Computes mean precision for all classes in the confusion matrix.

    Parameters
    ----------
    confusion_matrix: ArrayLike
        The Numpy nxn array or nxn list representing confusion matrix

    Returns
    -------
    float : mean precision value
    """
    return np.mean(
        confusion_matrix.diagonal() /
        np.sum(confusion_matrix, axis=1)
    )

def mean_sensitivity(confusion_matrix: np.ndarray):
    """
    Computes mean sensitivity for all classes in the confusion matrix.

    Parameters
    ----------
    confusion_matrix: ArrayLike
        The Numpy nxn array or nxn list representing confusion matrix

    Returns
    -------
    float : Mean sensitivity
    """
    return np.mean(
        confusion_matrix.diagonal() /
        np.sum(confusion_matrix, axis=0)
    )

def g_mean(confusion_matrix: np.ndarray):
    """
    Computes g-mean metric for the confusion matrix.

    Parameters
    ----------
    confusion_matrix: ArrayLike
        The Numpy nxn array or nxn list representing confusion matrix

    Returns
    -------
    float : G-Mean value
    """
    return np.float_power(np.prod(
        np.array(confusion_matrix).diagonal() /
        np.sum(confusion_matrix, axis=0)
    ), 1.0 / np.array(confusion_matrix).shape[0])


#!/bin/python3
import numpy as np

def find_closest_fibonacci_number(x):
    a = 1
    b = 1
    while (a + b) / 2 < x:
        a, b = b, a + b
    return a

def find_distance_closest_fibonacci_number(x):
    closest_fib = find_closest_fibonacci_number(x)
    return abs(closest_fib - x)

input_array = [1, 3, 4, 6, 7, 11, 17, 63, 101, 377, 467, 500, 1399]
closest_fibonacci_numbers = [1, 3, 3, 5, 8, 13, 13, 55, 89, 377, 377, 610, 1597]

test_result = [find_closest_fibonacci_number(x) for x in input_array]
print(np.array_equal(test_result, closest_fibonacci_numbers))


# Generate trigonometric patterns.

import numpy as np

def trigonometric_1(n, m, k=1, w=0, b=0):
    """
    Generate a trigonometric pattern.

    Args:
      * n: number of points to generate the pattern with
      * m: number of points multiplier

    Kwargs:
      * k: frequency adjustment for pattern
      * w: phase adjustment for pattern
      * b: offset adjustment for pattern

    """
    t = np.linspace(-m*np.pi, m*np.pi, n)
    r = t % n
    x = r * np.sin(k*t + w) + b
    y = r * np.cos(k*t + w) + b
    return x, y

def trigonometric_2(n, m, k=1, w=0, b=0):
    """
    Generate a trigonometric pattern.

    Args:
      * n: number of points to generate the pattern with
      * m: number of points multiplier

    Kwargs:
      * k: frequency adjustment for pattern
      * w: phase adjustment for pattern
      * b: offset adjustment for pattern

    """
    t = np.linspace(-m*n, m*n, n)
    r = t // n
    x = r * np.sin(k*t + w) + b
    y = r * np.cos(k*t + w) + b
    return x, y

trigonometric = trigonometric_1

def spirograph(n, m,
               rnx=1, rny=1,
               rmx=1, rmy=1,
               kn=1, km=1,
               wn=0, wm=0):
    """
    Generate a spirograph pattern by extruding circles around a central ring.

    Args:
      * n: number of points to generate the central ring with
      * m: number of points to generate each circle with

    Kwargs:
      * rnx: amplitude of central ring x-component
      * rny: amplitude of central ring y-component
      * rmx: amplitude of circles' x-component
      * rmy: amplitude of circles' y-component
      * kn: frequency adjustment of central ring (x and y)
      * km: frequency adjustment of circles (x and y)
      * wn: phase adjustment of central ring (x and y)
      * wm: phase adjustment of circles (x and y)

    Args must be supplied as a single integer value. Kwargs can be supplied either
    as a single numeric value or an array that can be broadcast to the shape (n, m).

    """
    t_ring = np.linspace(0, 2*np.pi, n*m)
    t_circle = np.linspace(0, 2*np.pi, m)

    x_ring = rnx * np.sin(t_ring*kn).reshape(n, m)
    y_ring = rny * np.cos(t_ring*kn).reshape(n, m)
    xp = rmx * np.sin(t_circle*km) + x_ring
    yp = rmy * np.cos(t_circle*km) + y_ring

    return xp.reshape(-1), yp.reshape(-1)


from sympy import *

def bernstein_series(N):
  # FIXME: check if a normalization constant is common in the definition
  # advantage is that the basis is always positive
  psi = []
  for k in range(0,N+1):
    psi_k = x**k*(1-x)**(N-k)
    psi.append(psi_k)
  return psi

def sin_series(N):
  # FIXME: do not satisfy bc
  psi = []
  for k in range(1,N):
    psi_k = sin(pi*k*x)
    psi.append(psi_k)
  return psi

def taylor_series(N):
  # FIXME: do not satisfy bc
  psi = []
  for k in range(1,N):
    psi_k = x**k
    psi.append(psi_k)
  return psi

def series(series_type, N):
  if series_type=="Taylor" : return taylor_series(N)
  elif series_type=="sin"  : return sin_series(N)
  elif series_type=="bernstein"  : return bernstein_series(N)
  else: print("series type unknown ") # sys.exit(0)

N = 10
M = 8
series_type = "bernstein"
Omega = [0, 1]
x = Symbol("x")

psi = series(series_type, N)
eta = series(series_type, M)

A = zeros((N+1+M+1), (N+1+M+1))
b = zeros((N+1+M+1))
f = 0
g = 0

for i in range(0, N+1):
  for j in range(0, N+1):
    integrand = psi[i]*psi[j]
    A[i,j] = integrate(integrand, (x,Omega[0], Omega[1]))

for i in range(0, N+1):
  for j in range(0, M+1):
    integrand = diff(psi[i],x)*eta[j]
    A[i,N+1+j] = integrate(integrand, (x,Omega[0], Omega[1]))
    A[N+1+j,i] = A[i,N+1+j]

for i in range(0, N+1):
  integrand = f*psi[i]
  print(integrand)
  b[i,0] = integrate(integrand, (x, Omega[0], Omega[1]))

for i in range(0, M+1):
  integrand = g*eta[i]
  print(integrand)
  b[N+1+i,0] = integrate(integrand, (x, Omega[0], Omega[1]))

# bc
A[0,:] = zeros(1,N+1+M+1)
A[0,0] = 1
b[0] = 1

A[N,:] = zeros(1,N+1+M+1)
A[N,N] = 1
b[N] = 0

print(A)

#c = A.LUsolve(b)
# FIXME: pinvsolve does not work and there is no info about this function
c = A.pinvsolve(b)
print(c)
u = sum(c[r,0]*psi[r] for r in range(N+1))

print(u)

import numpy, pylab
U = lambdify([x], u)
x = numpy.arange(Omega[0], Omega[1], 1/((N+1)*10.0))
UU = U(x)
pylab.plot(UU)
pylab.show()


import numpy as np

def Entropy(class1, class2=[], sample_weight=[]):
    ## entropy is measure of randomness, attribute with highest entropy is splitted at each node. attribute-
    ## with 0 entropy will be a leaf node, entropy>0 needs further splitting.
    ## class1 is usually the target class
    if(len(class2)==0):
        unique_counts1 = np.unique(class1, return_counts=True)
        if(len(sample_weight)==0):
            size = len(class1)
            prob = unique_counts1[1]/size
            return -np.sum(prob*np.log2(prob))
        else:
            entropy = 0
            total_weights = np.sum(sample_weight)
            for j in range(len(unique_counts1[0])):
                ind1 = np.where(class1==unique_counts1[0][j])[0]
                p = np.sum(sample_weight[ind1])/total_weights
                entropy+=(-p*np.log2(p))
            return entropy
    else:   ##groupby class2 and find entropy of each group
        unique_counts = np.unique(class2, return_counts=True)
        size = len(class2)
        prob = unique_counts[1]/size
        entropy=0
        classes = np.asarray(unique_counts[0])
        if(len(sample_weight)!=0):
            total_weights = np.sum(sample_weight)
        for i in range(len(classes)):  ##for each class
            ind = np.where(class2==classes[i])[0]
            if len(sample_weight)!=0:
                p = Entropy(class1[ind], sample_weight = sample_weight[ind])
                #print(p, (np.sum(sample_weight[ind])/(total_weights)))
                entropy+=(np.sum(sample_weight[ind])/(total_weights))*p
                #print(entropy)
            else:
                p = Entropy(class1[ind])
                entropy+= prob[i]*p
        return entropy

def Information_Gain(class1, class2, sample_weight=[]):       ## change in entropy before and after split
    return (Entropy(class1, sample_weight=sample_weight) - Entropy(class1, class2, sample_weight=sample_weight))

def Gini_index(class1, class2, sample_weight=[]):
    ## higher the value of gini index higher the homogeneity.
    unique_counts = np.unique(class2, return_counts=True)
    size = len(class2)
    prob = unique_counts[1]/size
    if len(sample_weight)!=0:
        total_weight = np.sum(sample_weight)
    gini=0
    classes = np.asarray(unique_counts[0])
    for i in range(len(classes)):  ##for each class
        ind = np.where(class2==classes[i])[0]
        p1 = np.unique(class1[ind], return_counts=True)
        if len(sample_weight)==0:
            tot = np.sum(p1[1])
            p1 = p1[1]/tot
            p1 = 1-np.sum(np.power(p1,2))
            gini+=p1*(tot)/size
        else:
            p2 = 0
            total_weight1 = np.sum(sample_weight[ind])
            for j in range(len(p1[0])):
                ind1 = ind[class1[ind]==p1[0][j]]
                p3 = np.sum(sample_weight[ind1])/total_weight1
                p3 = np.power(p3,2)
                p2+=p3
            p2 = (total_weight1/total_weight)*(1-p2)
            gini+=p2
    return gini

def Chi_square(class1, class2):
    class_values = np.unique(class2, return_counts=True)
    classes = np.unique(class1, return_counts=True)
    expected = classes[1]/np.sum(classes[1])
    chaid = 0
    for i in range(len(class_values[0])):  ##for each class
        ind = np.where(class2==class_values[0][i])[0]
        p1 = np.unique(class1[ind], return_counts=True)
        p2 = np.power(np.divide(np.power((p1[1]-expected*np.sum(p1[1])),2),expected*np.sum(p1[1])), 0.5)
        #print(p1, p2)
        chaid +=np.sum(p2)
    return chaid

#def Variance_reduction():


import numpy as np

import thalesians.tsa.numpyutils as npu
import thalesians.tsa.utils as utils

def make_cov_2d(sd1, sd2, cor):
    offdiag = cor*sd1*sd2
    return np.array([[sd1*sd1, offdiag], [offdiag, sd2*sd2]])

def make_vol_2d(sd1, sd2, cor):
    return np.array([[sd1, 0.], [cor*sd2, np.sqrt(1. - cor*cor)*sd2]])

def cov_to_vol(cov):
    cov = npu.to_ndim_2(cov, ndim_1_to_col=True, copy=False)
    return np.linalg.cholesky(cov)

def cor_to_cov(cors, vars=None, sds=None, copy=True):  # @ReservedAssignment
    assert (vars is None and sds is not None) or (vars is not None and sds is None)
    sds = np.sqrt(vars) if vars is not None else sds
    if isinstance(cors, (utils.DiagonalArray, utils.SubdiagonalArray)):
        cors = cors.tonumpyarray()
    cors = npu.to_ndim_2(cors, copy=copy)
    dim = len(vars)
    assert dim == np.shape(cors)[0] and dim == np.shape(cors)[1]
    np.fill_diagonal(cors, 1.)
    for i in range(dim):
        cors[i,:] = sds[i] * cors[i,:]
        cors[:,i] = sds[i] * cors[:,i]
    npu.lower_to_symmetric(cors, copy=False)
    return cors

def vol_to_cov(vol):
    vol = npu.to_ndim_2(vol, ndim_1_to_col=True, copy=False)
    return np.dot(vol, vol.T)

def cholesky_sqrt_2d(sd1, sd2, cor):
    return np.array(((sd1, 0.), (sd2 * cor, sd2 * np.sqrt(1. - cor * cor))))

class OnlineStatsCalculator(object):
    def __init__(self, zero=0.0):
        self.reset(zero)

    def reset(self, zero):
        self.__n = 0
        self.__sum = zero
        self.__mean = zero
        self.__mean_sq = zero
        self.__M2 = zero

    @property
    def count(self):
        return self.__n

    @property
    def sum(self):
        return self.__sum

    @property
    def mean(self):
        return self.__mean

    @property
    def mean_sq(self):
        return self.__mean_sq

    @property
    def rms(self):
        return np.sqrt(self.mean_sq)

    @property
    def var_n(self):
        return self.__M2 / self.__n

    @property
    def var(self):
        return self.__M2 / (self.__n - 1)

    @property
    def sd(self):
        return np.sqrt(self.var)

    @property
    def sd_n(self):
        return np.sqrt(self.var_n)

    def add(self, x):
        self.__n += 1
        self.__sum += x
        delta = x - self.__mean
        self.__mean += delta / self.__n
        deltasq = x * x - self.__mean_sq
        self.__mean_sq += deltasq / self.__n
        if self.__n > 1:
            self.__M2 += delta * (x - self.__mean)

    def add_all(self, xs):
        for x in xs: self.add(x)


import numpy as np
from random import seed
from succinctly.datasets import linearly_separable, get_dataset
from succinctly.algorithms.smo_algorithm import SmoAlgorithm

def linear_kernel(x1, x2):
    return np.dot(x1, x2)

def compute_w(multipliers, X, y):
    return np.sum(multipliers[i] * y[i] * X[i] for i in range(len(y)))

if __name__ == '__main__':

    seed(5) # to have reproducible results

    X_data, y_data = get_dataset(linearly_separable.get_training_examples)

    smo = SmoAlgorithm(X_data, y_data, C=10, tol=0.001, kernel=linear_kernel, use_linear_optim=True)

    smo.main_routine()

    w = compute_w(smo.alphas, X_data, y_data)

    print('w = {}'.format(w))

    # -smo.b because Platt uses the convention w.x-b=0
    print('b = {}'.format(-smo.b))

    # w = [0.4443664  1.1105648]
    # b = -9.66268641132


import numpy as np

from landlab.utils import get_watershed_mask

def hypsometric_integral(grid, outlet_id):
    """Calculate the hypsometric integral for the model grid.

    The hypsometric integral :math:`I` is defined as

    .. math::

        I = \\frac{\\frac{1}{N} \\sum_{i=0}^{N}
        \\left( z - \\min \\left( z\\right) \\right)}
        {\\max \\left(z\\right) - \\min \\left( z \\right)}

    Where :math:`z` is the set of elevation values, and :math:`N` is the number
    of elevation values.

    Parameters
    ----------
    grid : Landlab model grid
    outlet_id : int
        Outlet id of the watershed.

    Returns
    -------
    I : float
        The hypsometric integral.

    Examples
    --------
    First an example that only uses the ``hypsometric_integral`` function.

    >>> from landlab import RasterModelGrid
    >>> from landlab.components import FlowAccumulator
    >>> from umami.calculations import hypsometric_integral
    >>> grid = RasterModelGrid((10, 10))
    >>> z = grid.add_zeros("node", "topographic__elevation")
    >>> z += grid.x_of_node + grid.y_of_node
    >>> fa = FlowAccumulator(grid)
    >>> fa.run_one_step()
    >>> hypsometric_integral(grid, 1)
    0.5

    Next, the same calculations are shown as part of an umami ``Metric``.

    >>> from io import StringIO
    >>> from umami import Metric
    >>> file_like=StringIO('''
    ... hi:
    ...     _func: hypsometric_integral
    ...     outlet_id: 1
    ... ''')
    >>> metric = Metric(grid)
    >>> metric.add_from_file(file_like)
    >>> metric.names
    ['hi']
    >>> metric.calculate()
    >>> metric.values
    [0.5]
    """
    # Get just those elevation values that are within the watershed
    mask = get_watershed_mask(grid, outlet_id)
    vals = grid.at_node["topographic__elevation"][mask]

    # Get min and max
    min_val = np.amin(vals)
    max_val = np.amax(vals)

    # Calc and return the hypsometric_integral
    return np.mean(vals - min_val) / (max_val - min_val)


import numpy as np

#note that the datatype of the matrix should be float
#or the element during GE may be wrong

#original
A=np.float64([[1,-1,1]
             ,[-1,1,-1]
             ,[4,2,0]
             ,[0,2,5]])

B=np.float64([[0]
             ,[0]
             ,[8]
             ,[9]])

#if there is any row filling with 0
#delete it
def del_zero(A,B):
    zero_row=[]
    for i in range(0,len(A)):
        flag=0
        for j in range(0,np.shape(A)[1]):
            if A[i,j]==0:
                flag+=1
        if flag==np.shape(A)[1]:
            zero_row.append(i)
    A=np.delete(A,zero_row,axis=0)
    B=np.delete(B,zero_row,axis=0)
    return A,B

#to deal with the condition that A[0,0]==0
def change_row(A,B):
    if A[0,0]==0:
        temp=A[0,:]
        temp1=B[0]
        A=np.delete(A,0,axis=0)
        A=np.insert(A,[1],temp,axis=0)
        B=np.delete(B,0,axis=0)
        B=np.insert(B,[1],temp1,axis=0)
    return A,B

#Gaussian Elimination
def GE(A,B):
    for i in range(0,len(A)):
        for j in range(i+1,len(A)):
            if A[j,i]!=0:
                lam=A[j,i]/A[i,i]
                A[j,:]=A[j,:]-lam*A[i,:]
                B[j]=B[j]-lam*B[i]
                A,B=del_zero(A,B)
                break
        #break the outer for loop
        else:
            continue
        break

    print(np.c_[A,B],"\n")
    return A,B

#back substitution
def BS(A,B):
    sol=np.zeros((len(A)))
    sol[len(A)-1]=B[len(A)-1]/A[len(A)-1,len(A)-1]

    for i in range(len(A)-2,-1,-1):
        sol[i]=(B[i]-np.dot(A[i,i+1:len(A)],sol[i+1:len(A)]))/A[i,i]
    # sol[1]=(B[1]-A[1,2]*sol[2])/A[1,1]
    # sol[0]=(B[0]-A[0,2]*sol[2]-A[0,1]*sol[1])/A[0,0]
    for j in range(0,len(A)):
        print("i_",j+1,"=",sol[j],end="   ")
    print("\n")

#avoid the original A matrix has invalid value
A,B=del_zero(A,B)

#keep doing GE until the matrix are all the same
def GE_BS(A,B):
    #check if A[0,0]==0 if yew -> change the row
    A,B=change_row(A,B)
    for _ in range(1000):
        A,B=GE(A,B)
        C=np.c_[A,B]
        A,B=GE(A,B)
        D=np.c_[A,B]
        if np.all((C==D)):
            break
    BS(A,B)

GE_BS(A,B)
#result:
# [[ 1. -1.  1.  0.]
#  [ 4.  2.  0.  8.]
#  [ 0.  2.  5.  9.]]

# [[ 1. -1.  1.  0.]
#  [ 0.  6. -4.  8.]
#  [ 0.  2.  5.  9.]]

# [[ 1.         -1.          1.          0.        ]
#  [ 0.          6.         -4.          8.        ]
#  [ 0.          0.          6.33333333  6.33333333]]

# [[ 1.         -1.          1.          0.        ]
#  [ 0.          6.         -4.          8.        ]
#  [ 0.          0.          6.33333333  6.33333333]]

# i_ 1 = 0.9999999999999998   i_ 2 = 2.0   i_ 3 = 1.0000000000000002

#note that there might be a little bit wrong to the right answer
#because lambda can't be divided

#now we change the sequence of the original array test whether the solution are the same

#original
# A=np.float64([[1,-1,1]
#              ,[-1,1,-1]
#              ,[4,2,0]
#              ,[0,2,5]])

# B=np.float64([[0]
#              ,[0]
#              ,[8]
#              ,[9]])

#testing matrix change the 3 and 4 row can it get the same value?
C=np.float64([[1,-1,1]
             ,[-1,1,-1]
             ,[0,2,5]
             ,[4,2,0]])

D=np.float64([[0]
             ,[0]
             ,[9]
             ,[8]])

GE_BS(C,D)
#result:
# [[ 1. -1.  1.  0.]
#  [ 0.  2.  5.  9.]
#  [ 4.  2.  0.  8.]]

# [[ 1. -1.  1.  0.]
#  [ 0.  2.  5.  9.]
#  [ 0.  6. -4.  8.]]

# [[  1.  -1.   1.   0.]
#  [  0.   2.   5.   9.]
#  [  0.   0. -19. -19.]]

# [[  1.  -1.   1.   0.]
#  [  0.   2.   5.   9.]
#  [  0.   0. -19. -19.]]

# i_ 1 = 1.0   i_ 2 = 2.0   i_ 3 = 1.0

#testing matrix change the 2 and 3 row can it get the same value?
#original
# A=np.float64([[1,-1,1]
#              ,[-1,1,-1]
#              ,[4,2,0]
#              ,[0,2,5]])

# B=np.float64([[0]
#              ,[0]
#              ,[8]
#              ,[9]])

E=np.float64([[1,-1,1]
             ,[4,2,0]
             ,[-1,1,-1]
             ,[0,2,5]])

F=np.float64([[0]
             ,[8]
             ,[0]
             ,[9]])

GE_BS(E,F)
#results
# [[ 1. -1.  1.  0.]
#  [ 0.  6. -4.  8.]
#  [-1.  1. -1.  0.]
#  [ 0.  2.  5.  9.]]

# [[ 1. -1.  1.  0.]
#  [ 0.  6. -4.  8.]
#  [ 0.  2.  5.  9.]]

# [[ 1.         -1.          1.          0.        ]
#  [ 0.          6.         -4.          8.        ]
#  [ 0.          0.          6.33333333  6.33333333]]

# [[ 1.         -1.          1.          0.        ]
#  [ 0.          6.         -4.          8.        ]
#  [ 0.          0.          6.33333333  6.33333333]]

# i_ 1 = 0.9999999999999998   i_ 2 = 2.0   i_ 3 = 1.0000000000000002

#test if A[0,0]==0, will the change_row function run?
#original
# A=np.float64([[1,-1,1]
#              ,[-1,1,-1]
#              ,[4,2,0]
#              ,[0,2,5]])

# B=np.float64([[0]
#              ,[0]
#              ,[8]
#              ,[9]])

#change the 1 and 4 row
G=np.float64([[0,2,5]
             ,[-1,1,-1]
             ,[4,2,0]
             ,[1,-1,1]])

H=np.float64([[9]
             ,[0]
             ,[8]
             ,[0]])

GE_BS(G,H)
#results:

#we can observe that here change the row 1 and 2
# [[-1.  1. -1.  0.]
#  [ 0.  2.  5.  9.]
#  [ 0.  6. -4.  8.]
#  [ 1. -1.  1.  0.]]

# [[-1.  1. -1.  0.]
#  [ 0.  2.  5.  9.]
#  [ 0.  6. -4.  8.]]

# [[ -1.   1.  -1.   0.]
#  [  0.   2.   5.   9.]
#  [  0.   0. -19. -19.]]

# [[ -1.   1.  -1.   0.]
#  [  0.   2.   5.   9.]
#  [  0.   0. -19. -19.]]

# i_ 1 = 1.0   i_ 2 = 2.0   i_ 3 = 1.0


"""Small script that calculates the inertia matrix for common shapes. See
`this wiki <https://en.wikipedia.org/wiki/List_of_moments_of_inertia>`_ for more
information.
"""
import math

import numpy as np

# Script settings
CUBE_WIDTH = 0.02
CUBE_DEPTH = 0.032
CUBE_HEIGHT = 0.064

CYLINDER_RADIUS = 0.025
CYLINDER_HEIGHT = 0.02

MATERIAL_DENSITY = 2000

def get_cylinder_mass(radius, height, rho):
    """Calculate the mass of a cylinder.

    Args:
        radius (float): The radius of the cylinder [m].
        height (float): The height of the cylinder [m].
        rho (float): The material density [kg/m^3].

    Returns:
        float: The mass [kg].
    """
    return rho * math.pi * radius ** 2 * height

def get_cube_inertia(height, width, depth, mass):
    """Calculates the moment of inertia matrix for a cube.

    Args:
        height (float): The height of the cube [m].
        width (float): The width of the cube [m].
        depth (float): The length of the cube [m].
        mass (float): The mass of the cube [kg].

    Returns:
        numpy.ndarray: The inertia matrix [kg*m^2].
    """

    return np.array(
        [
            [(1 / 12) * mass * (width ** 2 + depth ** 2), 0, 0],
            [0, (1 / 12) * mass * (depth ** 2 + height ** 2), 0],
            [0, 0, (1 / 12) * mass * (width ** 2 + height ** 2)],
        ]
    )

def get_cube_mass(height, width, depth, rho):
    """Calculate the mass of a cube.

    Args:
        radius (float): The radius of the cylinder [m].
        height (float): The height of the cylinder [m].
        depth (float): The depth of the cylinder [m].
        rho (float): The material density [kg/m^3].

    Returns:
        float: The mass [kg].
    """
    return rho * height * width * depth

def get_cylinder_inertia(radius, height, mass):
    """Calculates the moment of inertia matrix for a cube.

    Args:
        radius (float): The radius of the cylinder [m].
        height (float): The height of the cylinder [m].
        mass (float): The mass of the cylinder [kg].

    Returns:
        numpy.ndarray: The inertia matrix [kg*m^2].
    """

    return np.array(
        [
            [(1 / 12) * mass * (3 * radius ** 2 + height ** 2), 0, 0],
            [0, (1 / 12) * mass * (3 * radius ** 2 + height ** 2), 0],
            [0, 0, (1 / 2) * mass * radius ** 2],
        ]
    )

if __name__ == "__main__":

    # Cue calculations
    print("==Cube==")
    cube_mass = get_cube_mass(
        height=CUBE_HEIGHT, width=CUBE_WIDTH, depth=CUBE_DEPTH, rho=MATERIAL_DENSITY
    )
    I_cube = get_cube_inertia(
        height=CUBE_HEIGHT, width=CUBE_WIDTH, depth=CUBE_DEPTH, mass=cube_mass
    )
    print(f"mass = {cube_mass}")
    print("inertia =")
    print(I_cube)
    print("\n")

    # Cylinder calculations
    print("==Cylinder==")
    cylinder_mass = get_cylinder_mass(
        radius=CYLINDER_RADIUS, height=CYLINDER_HEIGHT, rho=MATERIAL_DENSITY
    )
    I_cylinder = get_cylinder_inertia(
        radius=CYLINDER_RADIUS, height=CYLINDER_HEIGHT, mass=cylinder_mass
    )
    print(f"mass = {cylinder_mass}")
    print("inertia =")
    print(I_cylinder)


import numpy as np
import matplotlib.pyplot as plt

class Diff:
    def diff1(self,f,x):
        h = 1e-4
        return (f(x+h)-f(x-h)) / (2*h)

    def gradient(self,f,x):
        h = 1e-4
        grad = np.zeros_like(x)

        for idx in range(x.size):
            tmpVal = x[idx]

            x[idx] = tmpVal + h
            fxh1 = f(x)

            x[idx] = tmpVal - h
            fxh2 = f(x)

            grad[idx] = (fxh1 - fxh2) / (2*h)
            x[idx] = tmpVal
        return grad

    def diff_Line(self,a,x0,y0,x):
        return a*(x - x0)+y0

if __name__ == '__main__':
    df = Diff()
    def f(x):
        return 0.1*x**2 + 3*x + 9.0

    def f2(x):
        return x[0]**2 + x[1]**2

    ## 1D Diffrential
    x = np.arange(-10,10,0.1)
    y = f(x)

    x0 = x[50]
    y0 = y[50]

    diffline = df.diff_Line( df.diff1(f,x0) , x0,y0, x )

    plt.xlabel("x")
    plt.ylabel("y")
    plt.plot(x,y,'r')
    plt.plot(x0,y0,'g+')
    plt.plot(x,diffline,'b')
    #plt.show()

    ## Partial
    def ftmp1(x0):
        return x0**2 + 4.0**2.0

    res = df.diff1(ftmp1 , 3 )
    print(res)

    grad1 = df.gradient(f2 , np.array([3.0,4.0]))
    grad2 = df.gradient(f2 , np.array([0.0,4.0]))
    print(grad1)
    print(grad2)


import math
import numpy as np

def get_clip_mean(x, err=None, mask=None, high=3, low=3, maxiter=5):
    """Get the mean value of an input array using the sigma-clipping method

    Args:
        x (:class:`numpy.ndarray`): The input array.
        err (:class:`numpy.ndarray`): Errors of the input array.
        mask (:class:`numpy.ndarray`): Initial mask of the input array.
        high (float): Upper rejection threshold.
        low (float): Loweer rejection threshold.
        maxiter (int): Maximum number of iterations.

    Returns:
        tuple: A tuple containing:

            * **mean** (*float*) – Mean value after the sigma-clipping.
            * **std** (*float*) – Standard deviation after the sigma-clipping.
            * **mask** (:class:`numpy.ndarray`) – Mask of accepted values in the
              input array.
    """
    x = np.array(x)
    if mask is None:
        mask = np.zeros_like(x)<1

    niter = 0
    while(True):

        niter += 1
        if err is None:
            mean = x[mask].mean()
            std  = x[mask].std()
        else:
            mean = (x/err*mask).sum()/((1./err*mask).sum())
            std = math.sqrt(((x - mean)**2/err*mask).sum()/((1./err*mask).sum()))

        if maxiter==0 or niter>maxiter:
            # return without new mask
            break

        # calculate new mask
        new_mask = mask * (x < mean + high*std) * (x > mean - low*std)

        if mask.sum() == new_mask.sum():
            break
        else:
            mask = new_mask

    return mean, std, new_mask

def linear_fit(x,y,yerr=None,maxiter=10,high=3.0,low=3.0,full=False):
    """Fit the input arrays using a linear function.

    Args:
        x (:class:`numpy.ndarray`): The input X values.
        y (:class:`numpy.ndarray`): The input Y values.
        yerr (:class:`numpy.ndarray`): Errors of the Y array.
        maxiter (int): Maximum number of iterations.
        high (float): Upper rejection threshold.
        low (float): Lower rejection threshold.
        full (bool): `True` if return all information.

    Returns:
        tuple: A tuple containing:

            * **p** (:class:`numpy.ndarray`) – Parameters of the fitting.
            * **std** (*float*) – Standard deviation of the fitting.
            * **mask** (:class:`numpy.ndarray`) – Mask of the accepted values in
              the input array.
            * **func** (*func*) – Function.
            * **r2** (*float*) – *R*:sup:`2` value.
            * **p_std** (*tuple*) – Standar deviations of the parameters.
    """
    x = np.array(x)
    y = np.array(y)
    if yerr is not None:
        yerr = np.array(yerr)
    mask = np.ones_like(x)>0
    niter = 0
    func = lambda p, x: p[0] + p[1]*np.array(x)
    while(True):
        niter += 1
        if yerr == None:
            xm = x[mask].mean()
            ym = y[mask].mean()
            a = ((x[mask]-xm)*(y[mask]-ym)).sum()/((x[mask]-xm)**2).sum()
            b = ym - a*xm
            p = np.array([b,a])
            vy = func(p,x)
            std = (y[mask]-vy[mask]).std()
        else:
            w = 1./yerr[mask]**2
            w /= w.sum()
            b = (x[mask]*y[mask]*w).sum()
            c = (x[mask]*w).sum()
            d = (y[mask]*w).sum()
            e = (x[mask]**2*w).sum()
            f = e-c**2
            p = np.array([(e*d-c*b)/f, (b-c*d)/f])
            vy = func(p,x)
            std = math.sqrt(((y[mask]-vy[mask])**2*w).sum())

        if maxiter==0 or niter>maxiter:
            # return without new mask
            break

        # calculate new mask
        m1 = y > vy - low*std
        m2 = y < vy + high*std
        new_mask = np.logical_and(m1, m2)

        if mask.sum()==new_mask.sum() or new_mask.sum()<=2:
            break
        else:
            mask = new_mask
    if full:
        xm = x[mask].mean()
        ym = y[mask].mean()
        n  = mask.sum()
        lxx = (x[mask]**2).sum() - n*xm**2
        lyy = (y[mask]**2).sum() - n*ym**2
        lxy = (x[mask]*y[mask]).sum() - n*xm*ym
        r2 = lxy**2/lxx/lyy
        s = math.sqrt((lyy-lxy**2/lxx)/(n-2))
        sa = s/math.sqrt(lxx)
        sb = s*math.sqrt(1./n + xm**2/lxx)
        p_std = np.array([sb,sa])
        return p, std, mask, func, r2, p_std
    else:
        return p, std, mask, func

def iterative_polyfit(x, y, yerr=None, deg=3, mask=None, maxiter=10,
    upper_clip=None, lower_clip=None):
    """Fit data with polynomial iteratively. This is a wrap of numpy.polyfit
    function.

    Args:
        x (:class:`numpy.ndarray`): Input X values.
        y (:class:`numpy.ndarray`): Input Y values.
        yerr (:class:`numpy.ndarray`): Errors of **y**.
        deg (int): Degree of polynomial.
        mask (:class:`numpy.ndarray`): Input mask.
        maxiter (int): Maximum number of iterations.
        lower_clip (float): Lower sigma-clipping value.
        upper_clip (float): Upper sigma-clipping value.

    Returns:
        tuple: A tuple containing:

            * **coeff** (:class:`numpy.ndarray`) – Coefficients of polynomial.
            * **yfit** (:class:`numpy.ndarray`) – Fitted y values.
            * **yres** (:class:`numpy.ndarray`) – Residuals of y values.
            * **mask** (:class:`numpy.ndarray`) – Mask of y values.
            * **std** (float) – Standard deviation.
    """

    x = np.array(x)
    y = np.array(y)
    if yerr is None:
        pass
    else:
        yerr = np.array(yerr)
    if mask is None:
        mask = np.ones_like(x, dtype=np.bool)

    for ite in range(maxiter):
        coeff = np.polyfit(x[mask], y[mask], deg=deg)
        yfit = np.polyval(coeff, x)
        yres = y - yfit
        std = yres[mask].std(ddof=1)

        # replace np.nan by np.inf to avoid runtime warning
        yres[np.isnan(yres)] = np.inf

        # generate new mask
        # make a copy of existing mask
        new_mask = mask * np.ones_like(mask, dtype=np.bool)
        # give new mask with lower and upper clipping value
        if lower_clip is not None:
            new_mask *= (yres > -lower_clip * std)
        if upper_clip is not None:
            new_mask *= (yres < upper_clip * std)

        if new_mask.sum() == mask.sum():
            break
        mask = new_mask

    return coeff, yfit, yres, mask, std


from math import sqrt, exp, log, sin, pi
from sympy import Symbol, Limit, Derivative, solve, sympify, S, simplify, lambdify, pprint, init_printing, symbols
from fractions import Fraction

init_printing( order = 'rev-lex', use_unicode = True )

# 4.3.2

# Find the derivitave: f(x) =(2x)^3 * (3x)^2

Fx = ( 2*x )**3 * ( 3*x )**2
x = Symbol( 'x' )
Derivative( Fx, x ).doit()

# 4.3.3

# Find the derivitave: f(x) = 7 * [ x ^ 7/3 + 11/7*x^7/5 + 13x^7/7 ] ^ 4/3

Fx = 7 * ( x ** 7/3 + 11/7*x**7/5 + 13*x**7/7 ) ** 4/3
x = Symbol( 'x' )
Derivative( Fx, x ).doit()

# 4.3.9

# A particle's position is given by the function: x(t) = ( 4 - t ) ^ 3.
# What is the value of dx/dt when t = 3 ?

Ft = ( 4 - t ) ** 3
t = Symbol( 't' )
Dt = Derivative( Ft, t ).doit()
pprint( Dt )
Dt.subs( { t: 3 } )


import numpy as np

def loss_function(name, y, y_hat, derivative=False):
  """
  Computes the loss and its derivative

  Parameters
  ----------
  name: str
      Type of loss function.
      Options:
          mse ( Mean squared error )
          ce ( Cross entropy )

  y: list
      numpy array ( target )

  y_hat: list
      numpy array ( output )

  derivative: bool
      If True, returns the derivative of loss.
      Default: False

  Returns
  -------
  numpy array
  """

  # y - target, y_hat - output
  # Mean Squared Error
  if name == "mse":
      if derivative:
          return (y_hat - y)
      else:
          return np.mean((y - y_hat)**2)
  # Log-likelihood
  elif name == "ll":
      if derivative:
          return - (1 / y_hat)
      else:
          return -1 * np.log(y_hat)
  # y - target prob distro, y_hat - output prob distro
  # Cross Entropy
  elif name == "ce":
      if derivative:
          # if activation fn is sigmoid/softmax
          return (y_hat - y)
      else:
          return np.sum(np.nan_to_num(-y*np.log(y_hat)-(1-y)*np.log(1-y_hat)))

Activation functions and Loss functions/loss_functions.py