import numpy as np
import torch
from torch.autograd import Variable

def FFT_vectorized(x):
    """A vectorized, non-recursive version of the Cooley-Tukey FFT with Numpy"""
    x = np.asarray(x, dtype=float)
    N = x.shape[0]

    if np.log2(N) % 1 > 0:
        raise ValueError("size of x must be a power of 2")

    # N_min here is equivalent to the stopping condition above, and should be a power of 2
    N_min = min(N, 32)

    # Perform an O[N^2] DFT on all length-N_min sub-problems at once
    n = np.arange(N_min)
    k = n[:, None]
    M = np.exp(-2j * np.pi * n * k / N_min)
    X = np.dot(M, x.reshape((N_min, -1)))

    # build-up each level of the recursive calculation all at once
    while X.shape[0] < N:
        X_even = X[:, :X.shape[1] // 2]
        X_odd = X[:, X.shape[1] // 2:]
        factor = np.exp(-1j * np.pi * np.arange(X.shape[0]) / X.shape[0])[:, None]
        X = np.vstack([X_even + factor * X_odd, X_even - factor * X_odd])

    return X.ravel()

def FFT_torch_simple_signal(x):
    # Enable next 2 lines for if you come in with a numpy tensor otherwise MUST be commented
    #x = np.asarray(x, dtype=float)
    #x = Variable(torch.from_numpy(x), requires_grad=True).double().cuda()
    N_samples = x.shape[0]

    if np.log2(N_samples) % 1 > 0:
        raise ValueError("size of x must be a power of 2")

    # N_min here is equivalent to the stopping condition above, and should be a power of 2
    N_min = min(N_samples, 32)

    # Perform an O[N^2] DFT on all length-N_min sub-problems at once
    n = Variable(torch.arange(N_min), requires_grad = False).double() # .view(1,N,1)
    k = n[:, None]
    M_r = torch.cos(-2 * np.pi * n * k / N_min)
    M_i = torch.sin(-2 * np.pi * n * k / N_min)

    X_r = torch.mm(M_r, x.view(N_min, -1))
    X_i = torch.mm(M_i, x.view(N_min, -1))

    # build-up each level of the recursive calculation all at once
    while X_r.shape[0] < N:
        X_r_even = X_r[:, : X_r.shape[1] // 2]
        X_r_odd = X_r[:, X_r.shape[1] // 2:]
        X_i_even = X_i[:, : X_i.shape[1] // 2]
        X_i_odd = X_i[:, X_i.shape[1] // 2 :]
        factor_r = Variable(torch.cos(-1 * np.pi * torch.arange(X_r.shape[0]).double() / X_r.shape[0]), requires_grad = False).double().view(-1,1)
        factor_i = Variable(torch.sin(-1 * np.pi * torch.arange(X_i.shape[0]).double() / X_i.shape[0]), requires_grad = False).double().view(-1,1)
        P_r = factor_r * X_r_odd - factor_i * X_i_odd
        P_i = factor_r * X_i_odd + factor_i * X_r_odd
        X_r = torch.cat([X_r_even + P_r, X_r_even - P_r])
        X_i = torch.cat([X_i_even + P_i, X_i_even - P_i])

    R = X_r.data.numpy()
    I = X_i.data.numpy()
    X_ = np.complex128(R + 1j* I)
    return X_.ravel()

def FFT_torch(x):
    '''
    FFT through pytorch cuda 9.1
    :param x: a variable Cuda with samples in time domain and shape (batch_size, feature_size, samples_size)
    :return: 2 variables Cuda with real and imaginary part of coefficients in frequency domain with same input size
    '''

    batch_size = x.shape[0]
    features = x.shape[1]
    N_samples = x.shape[2]

    if np.log2(N_samples) % 1 > 0:
        raise ValueError("size of sequence must be a power of 2")

    # N_min should be a power of 2 and is equivalent to the stopping condition above
    N_min = min(N_samples, 32)

    # Perform an O[N^2] DFT on all length-N_min sub-problems at once
    n = Variable(torch.arange(N_min), requires_grad = False).double().cuda()
    k = n[:, None]
    M_r = torch.cos(-2 * np.pi * n * k / N_min).view(N_min, -1)
    M_i = torch.sin(-2 * np.pi * n * k / N_min).view(N_min, -1)
    X_r = torch.matmul(M_r, x.view(batch_size, features, N_min, -1))
    X_i = torch.matmul(M_i, x.view(batch_size, features, N_min, -1))

    # build-up each level of the recursive calculation all at once
    while X_r.shape[2] < N_samples:
        X_r_even = X_r[:,:,:, : X_r.shape[3] // 2]
        X_r_odd =  X_r[:,:,:, X_r.shape[3] // 2:]
        X_i_even = X_i[:,:,:, : X_i.shape[3] // 2]
        X_i_odd =  X_i[:,:,:, X_i.shape[3] // 2:]
        factor_r = Variable(torch.cos(-1 * np.pi * torch.arange(X_r.shape[2]).double() / X_r.shape[2]),
                            requires_grad = False).cuda().view(-1, 1)
        factor_i = Variable(torch.sin(-1 * np.pi * torch.arange(X_i.shape[2]).double() / X_i.shape[2]),
                            requires_grad = False).cuda().view(-1, 1)
        P_r = factor_r * X_r_odd - factor_i * X_i_odd
        P_i = factor_r * X_i_odd + factor_i * X_r_odd
        X_r = torch.cat([X_r_even + P_r, X_r_even - P_r], dim=2)
        X_i = torch.cat([X_i_even + P_i, X_i_even - P_i], dim=2)

    # return the torch output tensors real and imag parts of coefficients :
    return X_r.view(batch_size,features,N_samples), X_i.view(batch_size,features,N_samples)

def IFFT_torch(x_r, x_i):

    batch_size = x_r.shape[0]
    features = x_r.shape[1]
    N_samples = x_r.shape[2]

    if np.log2(N_samples) % 1 > 0:
        raise ValueError("size of sequence must be a power of 2")

    # N_min should be a power of 2 and is equivalent to the stopping condition above
    N_min = min(N_samples, 32)

    # Perform an O[N^2] DFT on all length-N_min sub-problems at once
    n = Variable(torch.arange(N_min), requires_grad = False).double().cuda()
    k = n[:, None]
    M_r = torch.cos(2 * np.pi * n * k / N_min).view(N_min, -1)
    M_i = torch.sin(2 * np.pi * n * k / N_min).view(N_min, -1)
    X_r = torch.matmul(M_r, x_r.view(batch_size, features, N_min, -1))
    X_i = torch.matmul(M_i, x_i.view(batch_size, features, N_min, -1))
    X_r = X_r - X_i
    X_i = torch.matmul(M_i, x_r.view(batch_size, features, N_min, -1)) + torch.matmul(M_r, x_i.view(batch_size, features, N_min, -1))

    # build-up each level of the recursive calculation all at once
    while X_r.shape[2] < N_samples:
        X_r_even = X_r[:,:,:, : X_r.shape[3] // 2]
        X_r_odd =  X_r[:,:,:, X_r.shape[3] // 2:]
        X_i_even = X_i[:,:,:, : X_i.shape[3] // 2]
        X_i_odd =  X_i[:,:,:, X_i.shape[3] // 2:]
        factor_r = Variable(torch.cos(1 * np.pi * torch.arange(X_r.shape[2]).double() / X_r.shape[2]),
                            requires_grad = False).cuda().view(-1,1)
        factor_i = Variable(torch.sin(1 * np.pi * torch.arange(X_i.shape[2]).double() / X_i.shape[2]),
                            requires_grad = False).cuda().view(-1,1)
        P_r = factor_r * X_r_odd - factor_i * X_i_odd
        P_i = factor_r * X_i_odd + factor_i * X_r_odd
        X_r = torch.cat([X_r_even + P_r, X_r_even - P_r], dim=2)
        X_i = torch.cat([X_i_even + P_i, X_i_even - P_i], dim=2)

    # rescaling the samples dividing by N
    X_r /= N_samples
    X_i /= N_samples # must be very close to 0 so we can omit it
    # And return only the real value that are our original samples in time domain
    return X_r.view(batch_size,features,N_samples)


#Loading dependencies
import numpy as np

def NPV(costs_list, dt, r = 0.06):
	'''Returning the net present value of series of values in the horizon'''
	val = 0
	for i, cost in enumerate(costs_list):
		val += np.exp(-r*dt*i) * cost
	return val


import numpy as np

def S(i,j,A,B,a,b):
    ''' Recursive definition of Hermite Gaussian coefficients.
        Returns a float.

    Parameters
    ----------
    a :
        orbital exponent on Gaussian 'a' (e.g. alpha in the text)
    b :
        orbital exponent on Gaussian 'b' (e.g. beta in the text)
    i,j:
        orbital angular momentum number on Gaussian 'a' and 'b'
    t:
        number nodes in Hermite (depends on type of integral,
           e.g. always zero for overlap integrals)
        Qx: distance between origins of Gaussian 'a' and 'b'
    '''
    p = a + b
    q = (a*b)/p
    P = (1./p)*(a*A + b*B)
    PA = P-A
    PB = P-B
    AB = A-B

    if i == j == 0:
        # base case
        return np.exp(-q*AB*AB) # K_AB

    elif i<0 or j<0 :
        return 0.

    elif i == 0 and j>0:
        # decrement index j
        return  PB*S(i,j-1,A,B,a,b) +\
                1./(2*p)*i*S(i-1,j-1,A,B,a,b) +\
                1./(2*p)*(j-1)*S(i,j-2,A,B,a,b)

    else:
        # decrement index i
        return  PA*S(i-1,j,A,B,a,b) +\
                1./(2*p)*(i-1)*S(i-2,j,A,B,a,b) +\
                1./(2*p)*j*S(i-1,j-1,A,B,a,b)

def Sxyz(a,lmn1,A,b,lmn2,B):
    ''' Evaluates overlap integral between two Gaussians
        Returns a float.

    Parameters
    ----------
        a:    orbital exponent on Gaussian 'a' (e.g. alpha in the text)
        b:    orbital exponent on Gaussian 'b' (e.g. beta in the text)
        lmn1: int tuple containing orbital angular momentum (e.g. (1,0,0))
              for Gaussian 'a'
        lmn2: int tuple containing orbital angular momentum for Gaussian 'b'
        A:    list containing origin of Gaussian 'a', e.g. [1.0, 2.0, 0.0]
        B:    list containing origin of Gaussian 'b'
    '''
    i,k,m = lmn1 # shell angular momentum on Gaussian 'a'
    j,l,n = lmn2 # shell angular momentum on Gaussian 'b'
    Sx = S(i,j,A[0],B[0],a,b) # X
    Sy = S(k,l,A[1],B[1],a,b) # Y
    Sz = S(m,n,A[2],B[2],a,b) # Z
    return Sx*Sy*Sz*np.power(np.pi/(a+b),1.5)

def overlap(a,b):
    '''Evaluates overlap between two contracted Gaussians
       Returns float.
       Arguments:
       a: contracted Gaussian 'a', BasisFunction object
       b: contracted Gaussian 'b', BasisFunction object
    '''
    s = 0.0
    for ia, ca in enumerate(a.coefs):
        for ib, cb in enumerate(b.coefs):
            s += a.norm[ia]*b.norm[ib]*ca*cb*\
                     Sxyz(a.exps[ia],a.shell,a.origin,
                     b.exps[ib],b.shell,b.origin)
    return s


import aclabtools
import numpy as np
import pylab as pl

choose = aclabtools.choose

def bernstein(i, n, t):
    """ Returns the bernstein polynomial for i, n and t """
    return choose(n, i) * (t ** i) * ((1 - t) ** (n - i))

def bezier(points):
    """ Returns bezier curve coordinates from points """

    def b(t):
        out = np.array([0, 0])
        n = len(points) - 1
        for i in range(n + 1):
            point = np.multiply(points[i], bernstein(i, n, t))
            out = np.add(out, point)
        return out

    b_points = np.empty((0, 2))
    for t in np.linspace(0, 1, 101):
        b_points = np.append(b_points, np.array([b(t)]), axis=0)
    return b_points

def rational_bezier(points, weights):
    """ Draws a bezier curve from points with weights and returns coords """

    def b(t):
        numer = np.array([0, 0])
        denom = np.array([0, 0])
        n = len(points) - 1
        for i in range(n + 1):
            wbi = weights[i] * bernstein(i, n, t)
            numer = np.add(numer, np.multiply(points[i], wbi))
            denom = np.add(denom, wbi)
        return np.divide(numer, denom)

    bez = np.empty((0, 2))
    for t in np.linspace(0, 1, 101):
        bez = np.append(bez, np.array([b(t)]), axis=0)
    pl.plot(points[:, 0], points[:, 1], 'ko')
    pl.plot(points[:, 0], points[:, 1], 'k')
    pl.plot(bez[:, 0], bez[:, 1], 'r')
    return bez


# Bench mark function 11
# Griewank Function
# HW dimension: 30
# Min = 0 when X={0,0,0,....0}
# Range [-600,600]
# Reference: http://benchmarkfcns.xyz/benchmarkfcns/griewankfcn.html

import math
import numpy as np

name = "F11"
l_bound = -600
u_bound = 600
dim = 30
opt = 0

def func(X):
    agg = np.sum(X**2)/4000
    index = np.arange(1, 1+X.size)
    cos_term = np.cos(np.divide(X,index**0.5))
    multi = np.prod(cos_term)
    result = agg - multi + 1
    return result

if __name__ == '__main__':
    X = np.arange(30)
    X = X * 0.3
    print(X)
    result = func(X)
    print(result)


import numpy as np
import cv2
import argparse
import matplotlib.pyplot as plt
import math
from convolution import convolution

def dnorm(x, mu, sd):
    return 1 / (np.sqrt(2 * np.pi) * sd) * np.e ** (-np.power((x - mu) / sd, 2) / 2)

def gaussian_kernel(size, sigma, verbose=False):
    kernel_1D = np.linspace(-(size // 2), size // 2, size)
    for i in range(size):
        kernel_1D[i] = dnorm(kernel_1D[i], 0, sigma)
    kernel_2D = np.outer(kernel_1D.T, kernel_1D.T)

    kernel_2D *= 1.0 / kernel_2D.max()

    if verbose:
        plt.imshow(kernel_2D, interpolation='none', cmap='gray')
        plt.title("Kernel ( {}X{} )".format(size, size))
        plt.show()

    return kernel_2D

# sigma=math.sqrt(kernel_size)

def gaussian_blur(image, kernel_size, sigma, verbose=False):
    kernel = gaussian_kernel(
        kernel_size, sigma, verbose=verbose)
    return convolution(image, kernel, average=True, verbose=verbose)

if __name__ == '__main__':
    ap = argparse.ArgumentParser()
    ap.add_argument("-i", "--image", required=True,
                    default='B1.jpg', help="Path to the image")
    ap.add_argument("-s", "--sigma", required=True,
                    default=1, help="sigma value")

    args = vars(ap.parse_args())

    image = cv2.imread(args["image"])
    sigma = int(args["sigma"])
    kernel_size = (2*sigma + 1)

    gaussian_blur(image, kernel_size, sigma, verbose=True)


import numpy as np
import scipy.stats as ss
import matplotlib.pyplot as plt

#------------------------------------------------------------------------------------------------#
#------------------------------------------ Generals --------------------------------------------#
#------------------------------------------------------------------------------------------------#
class generals:
    """
    generals Class contain the general statistics methods to work with probabilities,
    distributions, confidence intervals and hypothesis testing.

    Methods:
    --------------------------
    get_z(x, mu ,sd, n=None)
        Function to get the Z value.
    """

    def get_z(x, mu, sd, **kwargs):
        """
        Function to get the Z value (standard deviations number under or over the average)
        correspondent of the x value (parameter) in the standard normal distribution.

        Parameters:
        --------------------------
        x : double, float
            Specific value from the random variable.

        mu : double, float
            Population average given.

        sd : double, float
            Population or sample standard deviation given.

        n : int, optional
            Sample size, if you are working with sample distributions. (default=None)

        Returns:
        --------------------------
        z : float, double
            The Z value (standard deviations number under or over the average)
            correspondent of the x value (parameter) in the standard normal distribution.
        """
        if not kwargs:
            z = ((x - mu) / sd)
            return z
        else:
            n = kwargs.get('n', None)
            if (n <= 30):
                print("The sample size must be greater than 30.")
            else:
                z = ((x - mu) / (sd / n**0.5))
                return z

    def get_z_2p(x_1, x_2, s1, s2, n1, n2):
        """

        """
        if (n1 <= 30 or n2 <= 30):
            print("The sample sizes must be greater than 30.")
        else:
            s_error = ((s1**2 / n1) + (s2**2 / n2))**0.5

            z = (x_1 - x_2) / s_error
            return z

    def get_t(x, mu, s, n):
        """
        Function to get the T value correspondent of the X value in the t-student
        distribution.

        Parameters:
        --------------------------
        x : double, float
            Specific value from the random variable.

        mu : double, float
            Population average given.

        sd : double, float
            Sample standard deviation given.

        n : int, optional
            Sample size, less than 30.

        Returns:
        --------------------------
        t : float, double
            The T value correspondent of the X value in the t-student.
        """
        if n > 30:
            print("The sample size must be less than 30.")
        else:
            t = (x - mu) / (s / (n**0.5))
            return t

    def get_t_2p(x_1, x_2, s1, s2, n1, n2, var):
        """
        """
        if (n1 > 30 or n2 > 30):
            print("The sample sizes must be less than 30.")
        else:
            if (var is True):
                df = n1 + n2 - 2
                s2p = ((s1**2 * (n1 - 1)) + ((s2**2) * (n2 - 1))) / df
                s2p_error = ((s2p/n1) + (s2p/n2))**0.5

                t = (x_1 - x_2) / s2p_error
                return t
            else:
                s2p_error = ((s1**2 / n1) + (s2**2 / n2))**0.5
                t = (x_1 - x_2) / s2p_error
                return t

    def get_z_prop(p, pi, n):
        """
        Function to get the Z value (standard deviations number under or over the average)
        correspondent of the x value (parameter) in the standard normal distribution.
        Applied in proportions.

        Parameters:
        --------------------------
        p : double, float
            "Succeses" observations sample proportion.
            Each value should be in the range [0, 1].

        pi : double, float
            Population proportion given.
            Each value should be in the range [0, 1].

        n : int
            Sample size.

        Returns:
        --------------------------
        z : float, double
            The Z value (standard deviations number under or over the average)
            correspondent of the x value (parameter) in the standard normal distribution.
        """
        error = ((pi * (1 - pi)) / n)**0.5
        z = (p - pi) / error
        return z

    def get_z_2prop(p1, p2, n1, n2):
        """
        """
        sp_error = (((p1 * (1 - p1)) / n1) + ((p2 * (1 - p2)) / n2))**0.5

        z = (p1 - p2) / sp_error
        return z

    def get_df_var(n1, n2, s1, s2):
        """
        """
        df = ((((s1**2) / n1) + ((s2**2) / n2))**2) / (((((s1**2) / n1)**2) /
                (n1 - 1)) + ((((s2**2) / n2)**2) / (n2 - 1)))
        return df

    def get_d_pair(n, cond1, cond2):
        """
        """
        d1 = [cond1[x] - cond2[x] for x in range(n)]
        d21 = [x**2 for x in d1]
        sumd1 = sum(d1)
        sumd21 = sum(d21)
        d_ = sumd1 / n
        sd = ((sumd21 - (n * (d_**2))) / (n - 1))**0.5
        sd_error = sd / (n**0.5)

        d = d_ / sd_error
        return d

#------------------------------------------------------------------------------------------------#
#------------------------------------------------------------------------------------------------#
#------------------------------------------------------------------------------------------------#

#------------------------------------------------------------------------------------------------#
#---------------------------------- Probability Distributions -----------------------------------#
#------------------------------------------------------------------------------------------------#

class distributions:

    def graficar_discreta(distribucion, title):
        """
        Función para graficar la distribución discreta recibida como parámetro
        """
        valores_x = np.arange(distribucion.ppf(0.01), distribucion.ppf(0.99))
        fmp = distribucion.pmf(valores_x)
        plt.plot(valores_x, fmp, '-')
        plt.title(title)
        plt.ylabel("Probabilidad")
        plt.xlabel("Valores")
        plt.show()

    def graficar_discreta_cdf(distribucion, a, b):
        """
        Función para graficar la sumatoria de la distribución discreta recibida como parámetro
        """
        valores_x = np.arange(distribucion.ppf(0.01), distribucion.ppf(0.99))
        x2 = np.arange(a,(b+1))
        fmp2 = distribucion.pmf(x2)
        fmp = distribucion.pmf(valores_x)
        plt.plot(valores_x, fmp)
        plt.vlines(x2, 0, fmp2, colors='b', lw=5, alpha=0.5)
        plt.title("Probabilidad encontrada")
        plt.ylabel("Probabilidad")
        plt.xlabel("Valores")
        plt.show()

    def graficar_continua(distribucion, title):
        """
        Función para graficar la distribución continua recibida como parámetro
        """
        valores_x = np.linspace(distribucion.ppf(0.01), distribucion.ppf(0.99), 100)
        fp = distribucion.pdf(valores_x)
        plt.plot(valores_x, fp, '-')
        plt.title(title)
        plt.ylabel("Probabilidad")
        plt.xlabel("Valores")
        plt.show()

    def graficar_continua_cdf(distribucion, a, b):
        """
        Función para graficar la integral de la distribución continua recibida como parámetro
        """
        valores_x = np.linspace(distribucion.ppf(0.01), distribucion.ppf(0.99), 100)
        x2 = np.linspace(a, b)
        fp2 = distribucion.pdf(x2)
        fp = distribucion.pdf(valores_x)
        plt.plot(valores_x, fp)
        plt.vlines(x2, 0, fp2, colors='b', lw=5, alpha=0.5)
        plt.title("Probabilidad encontrada")
        plt.ylabel("Probabilidad")
        plt.xlabel("Valores")
        plt.show()

    def cdf_exponential(mu, x): #mu = mu
        """
        Función para calcular la función de probabilidad acumulada de la distribución exponencial continua
        """
        if x <= 0:
            cdf = 0
        else:
            cdf = 1 - np.exp(-mu * x)
        return cdf

#------------------------------------------------------------------------------------------------#
#------------------------------------------------------------------------------------------------#
#------------------------------------------------------------------------------------------------#

#------------------------------------------------------------------------------------------------#
#------------------------------------- Confidence Intervals -------------------------------------#
#------------------------------------------------------------------------------------------------#
class intervals:
    """
    interval Class contain the methods to get the confidence intervals to
    estimate some unknown parameter.

    Methods:
    --------------------------
    norm(n, x_ ,sd, alpha)
        Function to get the confidence interval of average population in large
        samples.
    """

    def norm(n, x_, sd, alpha):
        """
        Function to get the confidence interval to estimate the average population
        when we have large samples (n >= 30).

        Parameters:
        --------------------------
        n : int
            Sample size.

        x_ : float, double
            Sample average.

        sd : float, double
            Sample or population standard deviation.

        alpha : float, double
            Confidence level to estimate our unknown parameter (the average population).
            Each value should be in the range [0, 1].

        Returns:
        --------------------------
        interval : tuple
            The confidence interval, with the minimum value at [0] and the maximum value
            at [1] for the unknown parameter (the average population).
        """
        if (n <= 30):
            print("The sample size must be greater than 30.")
        else:
            sd_error = sd / (n**0.5)
            z_value = ss.norm.interval(alpha)[1]

            minimum = x_ - (z_value * sd_error)
            maximum = x_ + (z_value * sd_error)

            return minimum, maximum

    def norm_2p(n1, n2, x_1, x_2, sd1, sd2, alpha):
        """
        Function to get the confidence interval to estimate the difference between
        2 population averages in large samples (n >= 30).

        Parameters:
        --------------------------
        n1 : int
            Sample 1 size.

        n2 : int
            Sample 2 size.

        x_1 : float, double
            Sample 1 average.

        x_2 : float, double
            Sample 2 average.

        sd1 : float, double
            Sample or population 1 standard deviation.

        sd2 : float, double
            Sample or population 2 standard deviation.

        alpha : float, double
            Confidence level to estimate our unknown parameter (the difference between
            2 population averages).
            Each value should be in the range [0, 1].

        Returns:
        --------------------------
        interval : tuple
            The confidence interval, with the minimum value at [0] and the maximum value
            at [1] for the unknown parameter (the difference between
            2 population averages).
        """
        if (n1 <= 30 or n2 <= 30):
            print("The sample sizes must be greater than 30.")
        else:
            sd_error = ((sd1**2 / n1) + (sd2**2 / n2))**0.5
            z_value = ss.norm.interval(alpha)[1]

            minimum = (x_1 - x_2) - (z_value * sd_error)
            maximum = (x_1 - x_2) + (z_value * sd_error)

            return minimum, maximum

    def t(n, x_, s, alpha):
        """
        Function to get the confidence interval to estimate the average population
        when we have small samples (n < 30).

        Parameters:
        --------------------------
        n : int
            Sample size.

        x_ : float, double
            Sample average.

        s : float, double
            Sample standard deviation.

        alpha : float, double
            Confidence level to estimate our unknown parameter (the average population).
            Each value should be in the range [0, 1].

        Returns:
        --------------------------
        interval : tuple
            The confidence interval, with the minimum value at [0] and the maximum value
            at [1] for the unknown parameter (the average population).
        """
        if (n > 30):
            print("The sample size must be less than 30.")
        else:
            df = n - 1
            s_error = s / (n**0.5)
            t_value = ss.t.interval(alpha, df)[1]

            minimum = x_ - (t_value * s_error)
            maximum = x_ + (t_value * s_error)

            return minimum, maximum

    def t_2p(n1, n2, x_1, x_2, s1, s2, alpha, var):
        """
        Function to get the confidence interval to estimate the difference between
        2 population averages in small samples (n < 30).

        Parameters:
        --------------------------
        n1 : int
            Sample 1 size.

        n2 : int
            Sample 2 size.

        x_1 : float, double
            Sample 1 average.

        x_2 : float, double
            Sample 2 average.

        s1 : float, double
            Sample 1 standard deviation.

        s2 : float, double
            Sample 2 standard deviation.

        alpha : float, double
            Confidence level to estimate our unknown parameter (the difference between
            2 population averages).
            Each value should be in the range [0, 1].

        var : bool
            Are the population variances equal?
            True if population variances are equal.
            False if population variances are different.

        Returns:
        --------------------------
        interval : tuple
            The confidence interval, with the minimum value at [0] and the maximum value
            at [1] for the unknown parameter (the difference between
            2 population averages).
        """
        if (n1 > 30 or n2 > 30):
            print("The sample sizes must be less than 30.")
        else:
            if (var is True):
                df = n1 + n2 - 2
                s2p = (((s1**2) * (n1 - 1)) + (s2**2 * (n2 - 1))) / df
                sp_error = ((s2p / n1) + (s2p / n2))**0.5
                t_value = ss.t.interval(alpha, df)[1]

                minimum = (x_1 - x_2) - (t_value * sp_error)
                maximum = (x_1 - x_2) + (t_value * sp_error)

                return minimum, maximum
            else:
                df = generals.get_df_var(n1, n2, s1, s2)

                sp_error = (((s1**2) / n1) + ((s2**2) / n2))**0.5
                t_value = ss.t.interval(alpha, df)[1]

                minimum = (x_1 - x_2) - (t_value * sp_error)
                maximum = (x_1 - x_2) + (t_value * sp_error)

                return minimum, maximum

    def prop(n, p, alpha):
        """
        Function to get the confidence interval to estimate the population proportion.

        Parameters:
        --------------------------
        n : int
            Sample size.

        p : float, double
            Sample proportion.
            Each value should be in the range [0, 1].

        alpha : float, double
            Confidence level to estimate our unknown parameter (the population proportion).
            Each value should be in the range [0, 1].

        Returns:
        --------------------------
        interval : tuple
            The confidence interval, with the minimum value at [0] and the maximum value
            at [1] for the unknown parameter (the population proportion).
        """
        sp_error = ((p * (1 - p)) / n)**0.5
        z_value = ss.norm.interval(alpha)[1]

        minimum = p - (z_value * sp_error)
        maximum = p + (z_value * sp_error)

        return minimum, maximum

    def prop_2p(n1, n2, p1, p2, alpha):
        """
        Function to get the confidence interval to estimate the difference between
        2 population proportions.

        Parameters:
        --------------------------
        n1 : int
            Sample 1 size.

        n2 : int
            Sample 2 size.

        p1 : float, double
            Sample 1 proportion.
            Each value should be in the range [0, 1].

        p2 : float, double
            Sample 2 proportion.
            Each value should be in the range [0, 1].

        alpha : float, double
            Confidence level to estimate our unknown parameter (the population proportion).
            Each value should be in the range [0, 1].

        Returns:
        --------------------------
        interval : tuple
            The confidence interval, with the minimum value at [0] and the maximum value
            at [1] for the unknown parameter (the difference between
            2 population proportions).
        """
        sp_error = (((p1 * (1 - p1)) / n1) + ((p2 * (1 - p2)) / n2))**0.5
        z_value = ss.norm.interval(alpha)[1]

        minimum = (p1 - p2) - (z_value * sp_error)
        maximum = (p1 - p2) + (z_value * sp_error)

        return minimum, maximum

    def pair_2p(n, cond1, cond2, alpha):
        """
        Function to get the confidence interval to estimate the difference between
        2 population averages in paired samples.

        Parameters:
        --------------------------
        n : int
            Sample sizes.

        cond1 : list
            Sample initial condition list.

        cond2 : list
            Sample final condition list.

        alpha : double, float
            Confidence level to estimate our unknown parameter (the difference between
            2 population averages).
            Each value should be in the range [0, 1].

        Returns:
        --------------------------
        interval : tuple
            The confidence interval, with the minimum value at [0] and the maximum value
            at [1] for the unknown parameter (the difference between
            2 population averages).

        """
        d1 = [cond1[x] - cond2[x] for x in range(n)]
        d21 = [x**2 for x in d1]
        sumd1 = sum(d1)
        sumd21 = sum(d21)
        d_ = sumd1 / n
        sd = ((sumd21 - (n * (d_**2))) / (n - 1))**0.5
        sd_error = sd / (n**0.5)

        if n <= 30:
            df = n - 1
            t_value = ss.t.interval(alpha, df)[1]

            minimum = d_ - (t_value * sd_error)
            maximum = d_ + (t_value * sd_error)

            return minimum, maximum

        else:
            z_value = ss.norm.interval(alpha)[1]

            minimum = d_ - (z_value * sd_error)
            maximum = d_ + (z_value * sd_error)

            return minimum, maximum

    def n_avg(alpha, s, error):
        """
        Function to determine the appropiate sample size to estimate the average population.

        Parameters:
        --------------------------
        alpha : float, double
            Confidence level to determine our sample size.
            Each value should be in the range [0, 1].

        s: float, double
            Pilot sample standard deviation, or population standar deviation.

        error: float, double
            Tolerable size error.

        Returns:
        --------------------------
        n : int
            The appropiate sample size to estimate the average population.
            This sample size is rounded.
        """
        z_value = ss.norm.interval(alpha)[1]

        n = ((z_value**2)*(s**2)) / (error**2)

        return round(n)

    def n_avg_2p(alpha, s1, s2, error):
        """
        Function to determine the appropiate sample size to estimate the difference
        between 2 average populations.

        Parameters:
        --------------------------
        alpha : float, double
            Confidence level to determine our sample size.
            Each value should be in the range [0, 1].

        s1 : float, double
            Pilot sample 1 standard deviation, or population standar deviation.

        s2 : float, double
            Pilot sample 2 standard deviation, or population standar deviation.

        error: float, double
            Tolerable size error.

        Returns:
        --------------------------
        n : int
            The appropiate sample size to estimate the he difference
            between 2 average populations.
            This sample size is rounded.
        """
        z_value = ss.norm.interval(alpha)[1]

        n = ((z_value**2)*(s1**2 + s2**2)) / (error**2)

        return round(n)

    def n_prop(alpha, p, error):
        """
        Function to determine the appropiate sample size to estimate the population proportion.

        Parameters:
        --------------------------
        alpha : float, double
            Confidence level to determine our sample size.
            Each value should be in the range [0, 1].

        p: float, double
            Sample proportion.
            Each value should be in the range [0, 1].

        error: float, double
            Tolerable size error.

        Returns:
        --------------------------
        n : int
            The appropiate sample size to estimate the population proportion.
            This sample size is rounded.
        """
        z_value = ss.norm.interval(alpha)[1]

        n = ((z_value**2) * (p * (1 - p))) / (error**2)

        return round(n)

    def n_prop_2p(alpha, p1, p2, error):
        """
        Function to determine the appropiate sample size to estimate the
        difference between 2 population proportions.

        Parameters:
        --------------------------
        alpha : float, double
            Confidence level to determine our sample size.
            Each value should be in the range [0, 1].

        p1: float, double
            Sample proportion 1.
            Each value should be in the range [0, 1].

        p2: float, double
            Sample proportion 2.
            Each value should be in the range [0, 1].

        error: float, double
            Tolerable size error.

        Returns:
        --------------------------
        n : int
            The appropiate sample size to estimate the difference between
            2 population proportions.
            This sample size is rounded.
        """
        z_value = ss.norm.interval(alpha)[1]

        n = ((z_value**2) * ((p1 * (1 - p1)) + ((p2 * (1 - p2))))) / (error**2)

        return round(n)

#------------------------------------------------------------------------------------------------#
#------------------------------------------------------------------------------------------------#
#------------------------------------------------------------------------------------------------#

#------------------------------------------------------------------------------------------------#
#------------------------------------- Hypothesis Testing ---------------------------------------#
#------------------------------------------------------------------------------------------------#
class hypothesis:
    """
    hypothesis Class contain the methods to work on the hypothesis testing.

    Methods:
    --------------------------
    crit_val_norm(sign, tail)
        Function to calculate the critical values in the standard normal distribution.
    """

    def crit_val_norm(sign, tail):
        """
        Function to calculate, with the standard normal distribution, the critical
        values according to the level of significance and the tail-type test.

        Parameters:
        --------------------------
        sign : float, double
            Level of significance of the test.
            Each value should be in the range [0, 1].

        tail : string
            Tail-type test, it could be 'left', 'right' or 'two'.

        Returns:
        --------------------------
        crit_val : tuple, float
            The critical values or value, according to the level of significance and
            the tail-type test.
        """
        if tail == 'two':
            alpha = 1 - (sign/2)
            crit_val = ss.norm.ppf(1 - alpha), ss.norm.ppf(alpha)
            return crit_val

        if tail == 'left':
            alpha = 1 - sign
            crit_val = ss.norm.ppf(1 - alpha)
            return crit_val

        if tail == 'right':
            alpha = 1 - sign
            crit_val = ss.norm.ppf(alpha)
            return crit_val

        print("You must input a valid tail ('two', 'left' or 'right')")

    def crit_val_t(df, sign, tail):
        """
        Function to calculate, with the t-student distribution, the critical
        values according to the level of significance and the tail-type test.

        Parameters:
        --------------------------
        df : int
            Degrees of freedom (n - 1). It's the sample size minus 1.

        sign : float, double
            Level of significance of the test.
            Each value should be in the range [0, 1].

        tail : string
            Tail-type test, it could be 'left', 'right' or 'two'.

        Returns:
        --------------------------
        crit_val : tuple, float
            The critical values or value, according to the level of significance and
            the tail-type test.
        """
        if tail == 'two':
            alpha = 1 - (sign/2)
            crit_val = ss.t.ppf(1 - alpha, df), ss.t.ppf(alpha, df)
            return crit_val

        if tail == 'left':
            alpha = 1 - sign
            crit_val = ss.t.ppf(1 - alpha, df)
            return crit_val

        if tail == 'right':
            alpha = 1 - sign
            crit_val = ss.t.ppf(alpha, df)
            return crit_val

        print("You must input a valid tail ('two', 'left' or 'right')")

    def crit_val_f(df1, df2, sign):
        """
        """
        alpha = 1 - sign
        crit_val = ss.f.ppf(alpha, df1, df2)
        return crit_val

    def reject_h0(crit_val, value, tail):
        """
        Function to determine if reject the null hypothesis or not reject it based
        on the tail-type test.

        Parameters:
        --------------------------
        crit_val : tuple, float
            Critical values to consider.

        value : float, double
            Value to compare with critical values.

        tail : string
            Tail-type test, it could be 'left', 'right' or 'two'.

        Returns:
        --------------------------
        decision : bool
            True if we should reject null hypothesis,
            False if we shouldn't reject null hypothesis.
        """
        if tail == 'two':
            return (value < crit_val[0] or value > crit_val[1])

        if tail == 'left':
            return value < crit_val

        if tail == 'right':
            return value > crit_val

        print("You must input a valid tail ('two', 'left' or 'right')")

    def get_p(z, tail):
        """
        Function to determine the p-value (minimum significance level to reject
        the null hypothesis).

        Parameters:
        --------------------------
        z : double, float
            Z-value of the test statistic.

        tail : string
            Tail-type test, it could be 'left', 'right' or 'two'.

        Returns:
        --------------------------
        p : double, float
            P-value, the minimum significance level to reject the null hypothesis.
        """
        z_area = ss.norm.cdf(abs(z))

        if tail == 'two':
            p = 2 * (1 - z_area)
            return p

        else:
            p = 1 - z_area
            return p

#------------------------------------------------------------------------------------------------#
#------------------------------------------------------------------------------------------------#
#------------------------------------------------------------------------------------------------#

#------------------------------------------------------------------------------------------------#
#------------------------------------------- ANOVA ----------------------------------------------#
#------------------------------------------------------------------------------------------------#
class anova:

    def get_sct(*args):
        """
        """
        arr_tot = np.concatenate(args)
        big_mean = np.mean(arr_tot)
        sct = sum([(x - big_mean)**2 for x in arr_tot])
        return sct

        #d1 = [cond1[x] - cond2[x] for x in range(n)]
        #d21 = [x**2 for x in d1]

    def get_sctr(*args):
        """
        """
        arr_tot = np.concatenate(args)
        big_mean = np.mean(arr_tot)
        sctr = sum([(len(x) * ((np.mean(x) - big_mean)**2)) for x in args])
        return sctr

    def get_sce(*args):
        """
        """
        sce = 0

        for x in args:
            for xj in x:
                sce += (xj - np.mean(x))**2
        return sce

    def get_sce2(*args):
        """
        """
        sct = anova.get_sct(*args)
        sctr = anova.get_sctr(*args)
        scbl = anova.get_scbl(*args)

        sce = sct - sctr - scbl
        return sce

    def get_scbl(*args):
        """
        """
        lists = args
        blocks = list(map(list, zip(*lists)))
        arr_tot = np.concatenate(blocks)
        big_mean = np.mean(arr_tot)
        scbl = sum([(len(x) * ((np.mean(x) - big_mean)**2)) for x in blocks])

        return scbl

    def get_cmt(*args):
        """
        """
        arr_tot = np.concatenate(args)
        df = len(arr_tot) - 1
        sct = anova.get_sct(*args)
        cmt = sct / df
        return cmt

    def get_cmtr(*args):
        """
        """
        sctr = anova.get_sctr(*args)
        df = len(args) - 1
        cmtr = sctr / df
        return cmtr

    def get_cme(*args):
        """
        """
        arr_tot = np.concatenate(args)
        sce = anova.get_sce(*args)
        df = len(arr_tot) - len(args)
        cme = sce / df
        return cme

    def get_cme2(*args):
        """
        """
        sce = anova.get_sce2(*args)
        df = (len(args) - 1) * (len(args[0]) - 1)
        cme = sce / df
        return cme

    def get_cmbl(*args):
        """
        """
        scbl = anova.get_scbl(*args)
        df = len(args[0]) - 1
        cmbl = scbl / df
        return cmbl

    def get_fratio(*args):
        """
        """
        cmtr = anova.get_cmtr(*args)
        cme = anova.get_cme(*args)
        fratio = cmtr / cme
        return fratio

    def get_fratio2(*args):
        """
        """
        cmtr = anova.get_cmtr(*args)
        cme = anova.get_cme2(*args)
        fratio = cmtr / cme
        return fratio

    def get_fratio2_cmbl(*args):
        """
        """
        cmbl = anova.get_cmbl(*args)
        cme = anova.get_cme2(*args)
        fratio = cmbl / cme
        return fratio

    def get_dms(*args, sign):
        """
        """
        cme = anova.get_cme(*args)

        for x in range(len(args)):
            for xj in range(x+1, len(args)):
                if(len(args[x]) == len(args[xj])):
                    fratio = ss.f.ppf((1 - sign), 1, (len(np.concatenate(args)) - len(args)))
                    dms = (((1 / len(args[x])) + (1 / len(args[xj]))) * cme * fratio)**0.5
                    x_mean = np.mean(args[x])
                    xj_mean = np.mean(args[xj])
                    x_diff = abs(x_mean - xj_mean)
                    if (x_diff > dms):
                        print("|x_{} - x_{}| = |{} - {}| = {} > {}".format(x+1, xj+1,
                              x_mean, xj_mean, x_diff, dms))
                    else:
                        print("|x_{} - x_{}| = |{} - {}| = {} <= {}".format(x+1, xj+1,
                              x_mean, xj_mean, x_diff, dms))
                else:
                    fratio = ss.f.ppf((1 - sign), (len(args) - 1), (len(np.concatenate(args)) - len(args)))
                    dms = (((1 / len(args[x])) + (1 / len(args[xj]))) * cme * fratio)**0.5
                    x_mean = np.mean(args[x])
                    xj_mean = np.mean(args[xj])
                    x_diff = abs(x_mean - xj_mean)
                    if (x_diff > dms):
                        print("|x_{} - x_{}| = |{} - {}| = {} > {}".format(x+1, xj+1,
                              x_mean, xj_mean, x_diff, dms))
                    else:
                        print("|x_{} - x_{}| = |{} - {}| = {} <= {}".format(x+1, xj+1,
                              x_mean, xj_mean, x_diff, dms))

#------------------------------------------------------------------------------------------------#
#------------------------------------------------------------------------------------------------#
#------------------------------------------------------------------------------------------------#


from math import cos, sin, pi
import numpy as np
from numpy.linalg import inv

def sph_to_cart(epsilon, alpha, r):
    """
    Transform sensor readings to Cartesian coordinates in the sensor frames.
    """
    p = np.zeros(3)  # Position vector

    # Your code here
    p[0] = r * cos(alpha) * cos(epsilon)
    p[1] = r * sin(alpha) * cos(epsilon)
    p[2] = r * sin(epsilon)

    return p

def estimate_params(P):
    """
    Estimate parameters from sensor readings in the Cartesian frame.
    Each row in the P matrix contains a single measurement.
    """
    param_est = np.zeros(3)
    A = np.ones_like(P)
    b = np.zeros((P.shape[0], 1))

      # Your code here
    for i, meas in enumerate(P):
      A[i, 1], A[i, 2], b[i] = meas

    params = inv(A.T @ A) @ A.T @ b
    param_est[0] = params[0, 0]
    param_est[1] = params[1, 0]
    param_est[2] = params[2, 0]

    return param_est

meas = np.array([[pi/3, 0, 5],
                 [pi/4, pi/4, 7],
                 [pi/6, pi/2, 4],
                 [pi/5, 3*pi/4, 6],
                 [pi/8, pi, 3]])
P = np.array([sph_to_cart(*row) for row in meas])
print(estimate_params(P))


import numpy as np
from scipy.optimize import minimize

def mse(x, beacon_locations, beacon_distances):
    calculated_distances = np.array(
        [np.linalg.norm(x - beacon_locations[i]) for i in range(len(beacon_locations))])
    return np.sum(np.power(calculated_distances - beacon_distances, 2)) / len(beacon_distances)

def trilaterate(beacon_locations, beacon_distances, initial_location=np.array([0, 0, 0])):

    result = minimize(
        mse,
        initial_location,
        args=(beacon_locations, beacon_distances),
        method='L-BFGS-B',
        options={
            'ftol': 1e-5,
            'maxiter': 1e+7
        })

    return result.x

def main():
    # Testing the code
    beacon_locations = np.array(
        [[0, 0, 0], [10, 0, 0], [10, 10, 0], [0, 10, 0]])
    # Actual location of point is at [7,4,3]
    beacon_distances = np.array([8.6, 5.83, 7.35, 9.69])
    # Result generated is [ 6.99620322  4.00005823 -2.99673958]
    print(trilaterate(beacon_locations, beacon_distances))

if __name__ == "__main__":
    main()


import numpy as np

class R2Score:
    def __call__(self, y: np.ndarray, y_pred: np.ndarray) -> np.float64:
        return self.loss(y, y_pred)

    def loss(self, y: np.ndarray, y_pred: np.ndarray) -> np.float64:
        return 1 - (np.sum(np.power(y-y_pred, 2))) / (np.sum(np.power(y-np.mean(y), 2)))


'''
Binary classification:
Spiral pattern
'''

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

num_samples = 100
np.random.seed(1)

index = np.array(range(num_samples))
r = index/num_samples * 5
t1 = 1.75 * index/num_samples * 2 * math.pi
t2 = 1.75 * index/num_samples * 2 * math.pi + math.pi

x1 = r * np.sin(t1) + np.random.uniform(-0.5, 0.5, num_samples)
y1 = r * np.cos(t1) + np.random.uniform(-0.5, 0.5, num_samples)
z1 = [0] * num_samples

x2 = r * np.sin(t2) + np.random.uniform(-0.5, 0.5, num_samples)
y2 = r * np.cos(t2) + np.random.uniform(-0.5, 0.5, num_samples)
z2 = [1] * num_samples

x = np.hstack((x1, x2))
y = np.hstack((y1, y2))
z = np.hstack((z1, z2))

x = pd.DataFrame(x, columns=['x'])
y = pd.DataFrame(y, columns=['y'])
z = pd.DataFrame(z, columns=['labels'])
data = pd.concat([x, y, z], axis=1)
data = data.sample(frac=1)
#data.to_csv('../toy_data_5.csv', sep=' ', index=False)

fig = plt.figure(figsize=(4,4), dpi=100)
plt.scatter(x['x'], y['y'], c=z['labels'], cmap=plt.cm.brg)
plt.axis('off')
plt.savefig('../images/toy_data_5.png', transparent=True)
#plt.show()


import numpy as np
from numpy.polynomial.polynomial import Polynomial

def vandermond_integration(x, y, int_start, int_end):
    # Compute vector a
    vandermonde_inv = np.linalg.inv(np.vander(x, increasing=True))
    a_coeff = vandermonde_inv @ y

    # Generate the matrix that integrates polynomials.
    integral_matrix = np.vstack([np.zeros(x.size), np.diag(1 / np.arange(1, x.size+1))])

    # Integrate the polynomial a
    a_int_coeff = integral_matrix@a_coeff
    a_int = Polynomial(a_int_coeff)

    return a_int(int_end) - a_int(int_start)

# Define vector x
x1 = np.array([-1, -0.5, 0.5, 1])
x2 = np.array([-0.2, -0.1, 0.1, 0.2])
x3 = np.array([-np.sqrt(3/7 + 2/7 * np.sqrt(6/5)), -np.sqrt(3/7 - 2/7 * np.sqrt(6/5)), np.sqrt(3/7 - 2/7 * np.sqrt(6/5)), np.sqrt(3/7 + 2/7 * np.sqrt(6/5))])

print(f"Interpolated integral with x1 is: {vandermond_integration(x1, np.exp(x1), -1, 1)}")
print(f"Interpolated integral with x2 is: {vandermond_integration(x2, np.exp(x2), -1, 1)}")
print(f"Interpolated integral with x3 is: {vandermond_integration(x3, np.exp(x3), -1, 1)}")
print(f"integral should be {np.e - 1/np.e}")


#This problem was asked by Google.

#The edit distance between two strings refers to the minimum number of character insertions, deletions, and substitutions required to change one string to the other.
#For example, the edit distance between “kitten” and “sitting” is three: substitute the “k” for “s”, substitute the “e” for “i”, and append a “g”.

#Given two strings, compute the edit distance between them.

#solution 1: By recursion
ins_cost = 1
del_cost = 1
sub_cost = 2
def edit_distance_recurse(seq1, seq2, operations=[]):
    """Returns the Edit Distance between the provided two sequences."""

    if len(seq2) == 0:
        operations = operations + ([f"Delete `{seq1}` from sequence1."] if len(seq1) else [])
        return len(seq1), operations

    if len(seq1) == 0:
        operations = operations + ([f"Insert `{seq2}` into sequence1."] if len(seq2) else [])
        return len(seq2), operations

    if seq1[0] == seq2[0]:
        operations = operations + [f"Make no change for character `{seq1[0]}`."]
        return edit_distance_recurse(seq1[1: ], seq2[1: ], operations)

    # calculate cost if insertion was made
    ins_operations = operations + [f"Insert `{seq2[0]}` in sequence1."]
    insertion, ins_operations = edit_distance_recurse(seq1, seq2[1: ], ins_operations)

    # calculate cost if deletion was done
    del_operations = operations + [f"Delete `{seq1[0]}` from sequence1."]
    deletion, del_operations = edit_distance_recurse(seq1[1: ], seq2, del_operations)

    # calculate cost if substitution was done
    sub_operations = operations + [f"Replace `{seq1[0]}` in sequence1 with `{seq2[0]}`."]
    substitution, sub_operations = edit_distance_recurse(seq1[1: ], seq2[1: ], sub_operations)

    # calculate minimum cost
    min_cost = min(insertion + ins_cost, deletion + del_cost, substitution + sub_cost)

    if min_cost == (substitution + sub_cost):
        return min_cost, sub_operations
    elif min_cost == deletion + del_cost:
        return min_cost, del_operations
    else:
        return min_cost, ins_operations
seq1 = "kitten"
seq2 = "sitting"
score, operations = edit_distance_recurse(seq1, seq2)
print(f"Edit Distance between `{seq1}` & `{seq2}` is: {score}")
print("\nOperations performed are:\n")
for operation in operations:
    print(operation)
#Edit Distance between `kitten` & `sitting` is: 5

#Operations performed are:

#Replace `k` in sequence1 with `s`.
#Make no change for character `i`.
#Make no change for character `t`.
#Make no change for character `t`.
#Replace `e` in sequence1 with `i`.
#Make no change for character `n`.
#Insert `g` into sequence1.

# - - - - - -
#Solution2 by DP:
import numpy as np
ins_cost = 1
del_cost = 1
sub_cost = 2
def min_cost_path(cost, operations):

    # operation at the last cell
    path = [operations[cost.shape[0]-1][cost.shape[1]-1]]

    # cost at the last cell
    min_cost = cost[cost.shape[0]-1][cost.shape[1]-1]

    row = cost.shape[0]-1
    col = cost.shape[1]-1

    while row >0 and col > 0:

        if cost[row-1][col-1] <= cost[row-1][col] and cost[row-1][col-1] <= cost[row][col-1]:
            path.append(operations[row-1][col-1])
            row -= 1
            col -= 1
        elif cost[row-1][col] <= cost[row-1][col-1] and cost[row-1][col] <= cost[row][col-1]:
            path.append(operations[row-1][col])
            row -= 1
        else:
            path.append(operations[row][col-1])
            col -= 1

    return "".join(path[::-1][1:])

def edit_distance_dp(seq1, seq2):

    # create an empty 2D matrix to store cost
    cost = np.zeros((len(seq1)+1, len(seq2)+1))

    # fill the first row
    cost[0] = [i for i in range(len(seq2)+1)]

    # fill the first column
    cost[:, 0] = [i for i in range(len(seq1)+1)]

    # to store the operations made
    operations = np.asarray([['-' for j in range(len(seq2)+1)] \
                                 for i in range(len(seq1)+1)])

    # fill the first row by insertion
    operations[0] = ['I' for i in range(len(seq2)+1)]

    # fill the first column by insertion operation (D)
    operations[:, 0] = ['D' for i in range(len(seq1)+1)]

    operations[0, 0] = '-'

    # now, iterate over earch row and column
    for row in range(1, len(seq1)+1):

        for col in range(1, len(seq2)+1):

            # if both the characters are same then the cost will be same as
            # the cost of the previous sub-sequence
            if seq1[row-1] == seq2[col-1]:
                cost[row][col] = cost[row-1][col-1]
            else:

                insertion_cost = cost[row][col-1] + ins_cost
                deletion_cost = cost[row-1][col] + del_cost
                substitution_cost = cost[row-1][col-1] + sub_cost

                # calculate the minimum cost
                cost[row][col] = min(insertion_cost, deletion_cost, substitution_cost)

                # get the operation
                if cost[row][col] == substitution_cost:
                    operations[row][col] = 'S'

                elif cost[row][col] == ins_cost:
                    operations[row][col] = 'I'
                else:
                    operations[row][col] = 'D'

    return cost[len(seq1), len(seq2)], min_cost_path(cost, operations)
seq1 = "kitten"
seq2 = "sitting"
score, operations = edit_distance_dp("kitten", "sitting")
print(f"Edit Distance between `{seq1}` & `{seq2}` is: {score}")
print("\nOperations performed are:\n")
for operation in operations:
    if operation == '-':
        print('No Change.')
    elif operation == 'I':
        print('Insertion')
    elif operation == 'D':
        print('Deletion')
    else:
        print('Substitution')
#Edit Distance between `kitten` & `sitting` is: 5.0

#Operations performed are:

#Substitution
#No Change.
#No Change.
#No Change.
#Substitution
#No Change.
#Deletion


import numpy as np
from scipy.linalg import eigh

def decompose_kernel(M):
    """
    Decompose a symmetric matrix into sorted eigenvalues and corresponding eigenvectors
    :param M: input square np.array
    :return vals, vecs: vector of sorted eigenvalues, matrix of corresponding eigenvectors
    """
    vals, vecs = np.linalg.eigh(M)
    vals = np.real(vals)
    vecs = np.real(vecs)
    order = np.argsort(vals)
    return vals[order], vecs[:, order]


# matplotlib inline
import numpy as np
import pandas as pd
from sklearn.metrics import r2_score
import matplotlib.pyplot as plt
from scipy import stats
import seaborn as sns

# read Data
data = pd.read_excel('fish.xlsx')
df = data[['age', 'length']]

x = df['age']
y = df['length']

# plot data
"""
plt.xlabel('Age (yr)')
plt.ylabel('Length (cm)')
plt.title('Scatter-plot of Length vs Age')
plt.scatter(x, y)
plt.show()
"""

# Distribution of data
"""
stdy = y.std()
meany = y.mean()
plt.hist(y)
plt.xlabel('Length (cm)')
plt.ylabel('Quantity')
plt.title('Length Distribution Histogram')
print("The std of y is: " + str(stdy) + " The mean of y is: " + str(meany))

stdx = x.std()
meanx = x.mean()
plt.hist(x)
plt.xlabel('Age (yr)')
plt.ylabel('Quantity')
plt.title('Age Distribution Histogram')
print("The std of x is: " + str(stdx) + " The mean of x is: " + str(meanx))
"""

""" Linear Regression """
slope, intercept, r_value, p_value, std_err = stats.linregress(x, y)
"""
z = np.polyfit(x, y, 1)
p = np.poly1d(z)

# print(p)
print('slope', slope, 'intercept', intercept)

sns.set(color_codes=True)

def lineFitLine(b):
    return intercept + slope * b

line = lineFitLine(x)
plt.scatter(x, y, color='b')
plt.xlabel('Age (yr)')
plt.ylabel('Length (cm)')
plt.title('Scatter plot of Length vs Age - Linear Regression')
plt.plot(x, line, c='g')
p = np.poly1d(np.polyfit(x, y, 2))
xp = np.linspace(1, 6, 100)
plt.plot(xp, p(xp), c='r')
plt.scatter(x, y)
plt.show()

r2_lin = r_value * r_value
print('Linear - The rsquared value is: ' + str(r2_lin))

r2 = r2_score(y, p(x))
print('Quadratic - The rsquared value is: ' + str(r2))
"""

""" Test Data """
# First, we need to divide the data into training and testing segments
# training data. used to create the model
# testing data. how well the model matches
# in this example, n1 (training data)  = 50, n2 (testing data)  = 28

shuffled = df.sample(frac=1).reset_index(drop=True)
shuffx = shuffled['age']
shuffy = shuffled['length']
trainX = shuffx[:50]
testX = shuffx[50:]
trainY = shuffy[:50]
testY = shuffy[50:]

"""
plt.scatter(trainX, trainY)
plt.xlabel('Age (yr)')
plt.ylabel('Length (cm)')
plt.title('Testing Data - Length vs Age')
axes = plt.axes()
axes.set_xlim([1, 6])
axes.set_ylim([50, 200])
plt.show()

plt.scatter(testX, testY)
plt.xlabel('Age (yr)')
plt.ylabel('Length (cm)')
plt.title('Testing Data - Length vs Age')
axes = plt.axes()
axes.set_xlim([1, 6])
axes.set_ylim([50, 200])
plt.show()
"""

# Create linear regression for the model
p1 = np.poly1d(np.polyfit(trainX, trainY, 1))
p1_1 = np.poly1d(np.polyfit(testX, testY, 1))
p2 = np.poly1d(np.polyfit(trainX, trainY, 2))
p2_2 = np.poly1d(np.polyfit(testX, testY, 2))
xp = np.linspace(0, 6, 100)
axes = plt.axes()
axes.set_xlim([1, 6])
axes.set_ylim([50, 200])

plt.subplot(221)
plt.scatter(trainX, trainY)
plt.xlabel('Age (yr)')
plt.ylabel('Length (cm)')
plt.title('Training Linear Regression')
plt.plot(xp, p1(xp), c='r')

r2_train1 = r2_score(trainY, p1(trainX))
print('Linear train', 'The rsquared value is: ' + str(r2_train1))

plt.subplot(222)
plt.scatter(testX, testY)
plt.xlabel('Age (yr)')
plt.ylabel('Length (cm)')
plt.title('Testing Linear Regression')
plt.plot(xp, p1_1(xp), c='r')

r2_test1 = r2_score(testY, p1_1(testX))
print('Linear test', 'The rsquared value is: ' + str(r2_test1))

plt.subplot(223)
plt.scatter(trainX, trainY)
plt.xlabel('Age (yr)')
plt.ylabel('Length (cm)')
plt.title('Training Quadratic Regression')
plt.plot(xp, p2(xp), c='r')

r2_train2 = r2_score(trainY, p2(trainX))
print('Quadratic train', 'The rsquared value is: ' + str(r2_train2))

plt.subplot(224)
plt.scatter(testX, testY)
plt.xlabel('Age (yr)')
plt.ylabel('Length (cm)')
plt.title('Testing Quadratic Regression')
plt.plot(xp, p2_2(xp), c='r')

r2_test2 = r2_score(testY, p2_2(testX))
print('Quadratic test', 'The rsquared value is: ' + str(r2_test2))

# plt.show()


import sympy
import unittest

import A198799

class TestA198799(unittest.TestCase):
    def setUp(self):
        self.valid_primes = [p for p in sympy.primerange(2, 400) if p % 6 == 1]

    def n_to_sig(self, n):
        sig = []
        factored = sympy.factorint(n).items()
        for p_i, e_i in factored:
            if p_i == 3: continue
            if p_i % 6 == 1:
                sig.append(e_i)
            else:
                if e_i % 2 == 1:
                    assert False, factored
                    # Maybe return None
        return sig

    def test_count_circle_with_brute(self):
        for n in range(2, 10000, 13):
            assert A198799.count_brute(n) == A198799.count_circle(n)

    def test_count_circle(self):
        assert A198799.count_circle(49) == 2
        assert A198799.count_circle(13 * 13) == 2
        assert A198799.count_circle(637) == 3
        assert A198799.count_circle(1729) == 4
        assert A198799.count_circle(8281) == 5
        assert A198799.count_circle(12103) == 6

        # test adding any number of 3 doesn't change
        assert A198799.count_circle(49 * 3) == 2
        assert A198799.count_circle(49 * 3 ** 2) == 2
        assert A198799.count_circle(49 * 3 ** 3) == 2

        # test adding even counts of 5,11,17,23 doesn't change
        assert A198799.count_circle(49 * 5) == 0
        assert A198799.count_circle(49 * 5 ** 2) == 2
        assert A198799.count_circle(49 * 5 ** 3) == 0
        assert A198799.count_circle(49 * 5 ** 4) == 2

        assert A198799.count_circle(49 * 3 * 5 ** 2 * 23 ** 2) == 2

    def test_gen_all_signatures_small(self):
        results = list(A198799.gen_all_signatures(2000, self.valid_primes))
        assert results == [
            ((1,), 7),
            ((2,), 7 ** 2),
            ((1,1), 7 * 13),
            ((3,), 7 ** 3),
            ((2,1), 7 ** 2 * 13),
            ((1,1,1), 7 * 13 * 19),
        ], results

    def test_gen_all_signatures(self):
        N = 10000
        expected = {}
        for n in range(2, N):
            factored = sympy.factorint(n)
            if all(prime in self.valid_primes for prime in factored):
                sig = tuple(sorted(factored.values(), reverse=True))
                if sig not in expected:
                    expected[sig] = n
        # Python dict is stable by insertion order
        expected = list(expected.items())

        results = list(A198799.gen_all_signatures(N, self.valid_primes))
        assert results == expected, (results, expected)

    def test_count_signature_with_brute(self):
        for sig, small in A198799.gen_all_signatures(1000, self.valid_primes):
            count_brute = A198799.count_brute(small)
            count_sig = A198799.count_signature(sig)
            assert count_brute == count_sig, (small, sig)

    def test_count_signature_with_circle(self):
        for sig, small in A198799.gen_all_signatures(100000, self.valid_primes):
            count_circle = A198799.count_circle(small)
            count_sig = A198799.count_signature(sig)
            assert count_circle == count_sig, (small, sig)

    def test_gen_small(self):
        primes = [2, 3, 5, 7, 11, 13]
        smallest = A198799.gen_small((1,1,1), count=10, primes=primes)
        assert len(smallest) == 10
        for small in smallest:
            assert sorted(sympy.factorint(small).values()) == [1,1,1], small

    def test_A198774(self):
        # All of these should return m=3
        n = [
            637, 931, 1183, 1519, 1813, 1911, 2107, 2401, 2527, 2548, 2793,
            9751, 18228, 35017, 69727, 105889, 142492, 179977,
        ]

        for an in n:
            assert A198799.count_circle(an) == 3
            sig = self.n_to_sig(an)
            assert A198799.count_signature(sig) == 3

    def test_A198775(self):
        # All of these should return m=4
        n = [
            1729, 2821, 3367, 3913, 4123, 4459, 4921, 5187, 5551, 5719, 6097,
            22204, 24583, 25753, 27937, 30121, 32116, 34333, 37387, 44548,
        ]

        for an in n:
            assert A198799.count_circle(an) == 4
            sig = self.n_to_sig(an)
            assert A198799.count_signature(sig) == 4

    def test_previous_A198799(self):
        A = [
            49, 637, 1729, 8281, 12103, 1529437, 53599, 157339, 593047,
            19882681, 375193, 68574961, 2989441, 7709611, 1983163,
            47738317081, 4877509, 21169376772835837, 18384457, 377770939,
        ]

        for m, an in enumerate(A, 2):
            if an < 1e12:
                assert A198799.count_circle(an) == m
            sig = self.n_to_sig(an)
            assert A198799.count_signature(sig) == m

if __name__ == '__main__':
    unittest.main()


import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes

# old font defaults
mpl.rcParams['mathtext.fontset'] = 'cm'
mpl.rcParams['mathtext.rm'] = 'serif'

x_smooth = np.linspace(0.0, np.pi, 500)
f_smooth = np.sin(x_smooth)

x = np.linspace(0.0, np.pi, 10)
f = np.sin(x)

i = 5
x_0 = x[i]
f_0 = f[i]

plt.plot(x_smooth, f_smooth)
plt.scatter(x, f)

plt.scatter(x_0, np.sin(x_0), color="r", zorder=10)

dx = x[1] - x[0]
x_d = np.linspace(x[i]-1.5*dx, x[i]+1.5*dx, 2)

d_exact = np.cos(x_0)

d_l = (np.sin(x[i]) - np.sin(x[i-1]))/dx
d_r = (np.sin(x[i+1]) - np.sin(x[i]))/dx
d_c = 0.5*(np.sin(x[i+1]) - np.sin(x[i-1]))/dx

# plot a line showing dx
dx = x[2] - x[1]
ann = plt.annotate('', xy=(x[1], f[1]-0.5*dx),  xycoords='data',
                   xytext=(x[2], f[1]-0.5*dx), textcoords='data',
                   arrowprops=dict(arrowstyle="<->",
                                   connectionstyle="bar,fraction=-0.4",
                                   ec="k",
                                   shrinkA=5, shrinkB=5,
                   ))

plt.text(0.5*(x[1]+x[2]), f[1]-0.8*dx, "$\Delta x$",
         horizontalalignment="center")

plt.plot([x[1], x[1]], [f[1]-0.5*dx, f[2]+0.5*dx], ls=":", color="0.5")
plt.plot([x[2], x[2]], [f[1]-0.5*dx, f[2]+0.5*dx], ls=":", color="0.5")

plt.plot(x_d, d_exact*(x_d - x_0) + f_0, color="0.5", lw=2, ls=":", label="exact")
plt.plot(x_d, d_l*(x_d - x_0) + f_0, label="left-sided")
plt.plot(x_d, d_r*(x_d - x_0) + f_0, label="right-sided")
plt.plot(x_d, d_c*(x_d - x_0) + f_0, label="centered")

ax = plt.gca()

# origin of the axes through 0
ax.spines['left'].set_position('zero')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_color('none')
ax.spines['left'].set_smart_bounds(True)
ax.spines['bottom'].set_smart_bounds(True)
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')

plt.legend(frameon=False, loc="best")

plt.ylim(-0.1, 1.1)

plt.tight_layout()

plt.savefig("derivs.pdf")


# -*- coding: utf-8 -*-
"""
Created on Sat May 13 16:48:11 2017

This script is for learning about numpy library for Data Science Projects

@author: azkei
"""
# importing NumPy module withing Python session
import numpy as np

# Using ndarray - the heart of the library

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

# Checking the newly created object thats an ndarray
type(a)

# Checking the associated data type of the ndarray
a.dtype
# Checking the axis of the ndarray
a.ndim
# Checking the array length
a.size
# Checking the shape attribute of the array
a.shape

# More Examples
b = np.array([0.234,0.1234,0.1234])

c = np.array([[0.2, 0.4, 0.3],[0.2,0.5,0.1]])

d = np.array([[3.212,5.123512,32.12351, 22.3244522,2.2345,233331,233],
              [988.2331,3231.34422334,22334.2221,233.21123],
              [23.3333,2124.2333,2123321.23333,22.2]])

# defines the size of each bytes of item in the array
d.itemsize
# Buffer containing the aactual elements of the array.
d.data

# Numpy arrays can contain a wide variety of data types
# e.g. String

stringArray = np.array([['a','b'],['c','d']],dtype = '|S1')
stringArray.dtype
stringArray.dtype.name

# The Array function does not just accept a single argument.
# You can use the dtype option to define an array with  complex values

f = np.array([[1,2,3],[4,5,6]],dtype = complex)

# Intrinsic creation of an Array
# The NumPy library provides a set of functions that generate the ndarrays with an initial content
# with values depending on the function

# Generate a 3x3 ndarry with 0 values
np.zeros((3,3))

# Generate a 3x3 ndarray with 1 values
np.ones((3,3))

# It's also possible to generate a sequence of values with precise intervals
np.arange(0,12,3)

# The third argument can even be a float
np.arange(0,6,0.3)

# So far you've only generated one dimensional arrays
# You can add reshape() to add extra dimensions
np.arange(0,6,0.5).reshape(3,4)

# Another function similar to arange() is linspace()
# This function still takes the first 2 arguments as the range
# However the third argument defines the number of elements we want to split
np.linspace(0,6,5)

# Lastly another way to fill an array with values is with numpy.random
np.random(3)

# Add extra dimensions
np.random.random((3,3))


import os
import numpy as np
from matplotlib import pyplot
from mpl_toolkits.mplot3d import Axes3D
from time import sleep

def warm_up_exercise():
    A = np.eye(5)

    return A

def plot_data(x, y):
    figure = pyplot.figure()
    pyplot.plot(x, y, 'ro', ms=10, mec='k')
    pyplot.ylabel('Profit in $10,000s')
    pyplot.xlabel('Population of City in 10,000s')

def plot_cost_hist(J_hist, rate):
    pyplot.plot(np.arange(len(J_hist)), J_hist, '-', ms=10, mec='k')
    pyplot.ylabel('Cost J')
    pyplot.xlabel('Iterations')

    pyplot.plot(np.arange(len(J_hist)), J_hist, '-')

def compute_cost(X, y, theta):
    m = y.shape[0]
    J = (1/(2*m)) * np.sum( np.square((np.dot(X, theta) - y)) )
    return J

def gradient_descent(X, y, theta, alpha, num_iters):
    m = y.shape[0]
    adjusted_theta = theta.copy()
    J_hist = []
    for i in range(0, num_iters):
        adjusted_theta -= (alpha / m) * (np.dot(X, adjusted_theta) - y).dot(X)
        J_hist.append(compute_cost(X, y, adjusted_theta))
    return J_hist, adjusted_theta

def predict(X, theta):
    pred = np.dot(X, theta)
    print("For population =", X[1]*10000, ' we predict a profit of {:.2f}\n'.format(pred*10000))

def visualize_cost(X, y, trained_theta):
    # Step over theta0 values in range -10,10 with 100 steps
    # theta0_vals => (100 x 1)
    theta0_vals = np.linspace(-10,10,100)
    # Step over theta1 values in range -4 4 with 100 steps
    # thteta1_vals => (100 x 1)
    theta1_vals = np.linspace(-1,4,100)
    # Create a matrix of costs at different values of theta0 and theta1
    # J_vals => (100 x 100)
    J_vals = np.zeros((theta0_vals.shape[0], theta1_vals.shape[0]))

    for i, theta0 in enumerate(theta0_vals):
        for j, theta1 in enumerate(theta1_vals):
            J_vals[i, j] = compute_cost(X, y, [theta0, theta1])
    J_vals = J_vals.T

    figure = pyplot.figure(figsize=(12, 5))
    # First parameter controls position in sub plot
    # projection controls 3d-ness
    axis = figure.add_subplot(121, projection='3d')
    # cmap='viridis' makes it colorful
    axis.plot_surface(theta0_vals, theta1_vals, J_vals, cmap='viridis')
    pyplot.xlabel('theta0')
    pyplot.ylabel('theta1')
    axis.set_zlabel('Cost J')
    pyplot.title('Cost at different thetas')

    axis = pyplot.subplot(122)
    # Levels controls number and positions of the contour lines, should be int or array-like object
    pyplot.contour(theta0_vals, theta1_vals, J_vals, linewidths=2, cmap='viridis', levels=np.logspace(-2, 3, 20))
    axis.set_xlabel('theta0')
    axis.set_ylabel('theta1')
    pyplot.plot(trained_theta[0], trained_theta[1], 'ro', ms=10, lw=2)
    pyplot.title('Minimum value of cost J')

def normalizeFeatures(X):
    # X => (m x n) : m = num. examples, n = num. features
    X_norm = X.copy()
    m = X_norm.shape[0]
    # X.shape => (m x n)
    # np.zeros(X.shape[1]) => np.zeros(n) => (n x 1)
    mu = np.zeros(X.shape[1])
    sigma = np.zeros(X.shape[1])

    for feature in range(X.shape[1]):
        mu[feature] = np.mean(X[:, feature])
        sigma[feature] = np.std(X[:, feature])
    X_norm = (X - mu) / sigma

    return X_norm, mu, sigma

def normal_equation(X, y):
    first = np.dot(X.T,X)
    inverse = None
    try:
        inverse = np.linalg.inv(first)
        X_terms = np.dot(inverse,X.T)
        return np.dot(X_terms, y)
    except np.linalg.LinAlgError:
        print('ERROR: Matrix is non-invertible')
        return -1

def part1():
    print('='*50)
    print('\tBegin Part 1')
    print('='*50)
    data = np.loadtxt(os.path.join('Data', 'ex1data1.txt'), delimiter=',')
    X, y = data[:, 0], data[:, 1]
    m = y.size

    plot_data(X, y)
    pyplot.show()

    X = np.stack([np.ones(m), X], axis=1)
    theta = np.array([0.0, 0.0])
    print("Compute cost for theta: ", theta)
    print(compute_cost(X, y, theta))
    print("Compute cost for theta: ", [-1,2])
    print(compute_cost(X, y, np.array([-1, 2])))

def part2():
    print('='*50)
    print('\tBegin Part 2')
    print('='*50)
    data = np.loadtxt(os.path.join('Data', 'ex1data1.txt'), delimiter=',')
    X, y = data[:, 0], data[:, 1]
    m = y.size
    iterations = 1500
    alpha = 0.01
    learning_rates = [0.01, 0.02, 0.0001, 0.0002]
    X = np.stack([np.ones(m), X], axis=1)

    str_rates = []
    figure = pyplot.figure()
    for rate in learning_rates:
        J_hist, theta = gradient_descent(X, y, [0, 0], rate, iterations)
        plot_cost_hist(J_hist, rate)
        str_rates.append("{:.4f}".format(rate))
    pyplot.legend(str_rates)
    pyplot.show()

    theta = np.array([0.0, 0.0])
    J_hist, theta = gradient_descent(X, y, theta, alpha, iterations)

    plot_data(X[:, 1], y)
    pyplot.plot(X[:, 1], np.dot(X, theta), '-')
    pyplot.legend(['Training data', 'Linear regression'])
    pyplot.show()
    predict([1, 3.5], theta)
    predict([1, 7], theta)

    visualize_cost(X, y, theta)
    pyplot.show()

def part3():
    print('='*50)
    print('\tBegin Part 3 (Optional)')
    print('='*50)
    data = np.loadtxt(os.path.join('Data', 'ex1data2.txt'), delimiter=',')
    # Grabs all rows and all columns from [start, 2) exclusive of index 2
    X = data[:, :2]
    # Grabs all rows and column at index 2
    y = data[:, 2]
    m = y.shape[0]
    print('{:>8s}{:>8s}{:>10s}'.format('X[:,0]', 'X[:,1]', 'y'))
    for i in range(10):
        print('{:8.0f}{:8.0f}{:10.0f}'.format(X[i, 0], X[i, 1], y[i]))

    X_norm, mu, sigma = normalizeFeatures(X)
    print('Computed mean:\t', mu)
    print('Computed STD:\t', sigma)
    # Concatenate a vector of 1s at the beginning of X_norm
    # Axis controls whether you concatenate by row or col, row is 0, col is 1
    X = np.concatenate([np.ones((m,1)), X_norm], axis=1)
    theta = np.array([0.0, 0.0, 0.0])
    cost = compute_cost(X, y, theta)
    print(cost)
    iterations = 400
    alpha = 0.1
    J_hist, theta = gradient_descent(X, y, theta, alpha, iterations)
    print(theta)
    cost = compute_cost(X, y, theta)
    test_case = [1, 1650, 3]
    test_case[1:3] = (test_case[1:3] - mu) / sigma
    cost = np.dot(test_case,theta)
    print(cost)
    norm_eq_theta = normal_equation(X, y)
    cost = np.dot(test_case, norm_eq_theta)
    print(cost)

def main():
    part1()
    part2()
    part3()

if __name__ == '__main__':
    main()


################################################################################
##############              21-241 Term Project                   ##############
##############                Topic: Page Rank                    ##############
##############        andrewIDs: nnishika and kratij              ##############
##############                                                    ##############
##############       Sections:                                    ##############
##############   1. Setup                                         ##############
##############   2. Method 1: Using eigenvectors                  ##############
##############   3. Method 2: Using iteration to find convergence ##############
##############   3. Helper Functions alphabetically arranged      ##############
##############   4. Testing the Program                           ##############
################################################################################

################################################################################
# The primary function which the user must call is rank(markov) which takes in a
# markov chain called 'markov' which is a list of lists (a 2D list) and returns
# a tuple of two lists with pages in descending order of 'importance'. The first
# list corresponds to the ranking from method 1 and the second to the ranking
# from method 2.
################################################################################

################################################################################
############                         Setup                        ##############
################################################################################

def rank(markov):
    # input: markov chain: a list of list that maps pages (index of inner
    # lists) to destinations (inner lists)
    # output: a tuple with two lists each of which is a list with pages in
    # descending order of 'importance'. The first list corresponds to the
    # ranking from method 1 and the second to the ranking from method 2.
    # epsilon that defines 'almost the same' to identify convergence
    epsilon = 0.000000000001
    # damp Weight
    dampWt = 0.15
    # fixing end nodes of the input markov chain
    fixEndNodes(markov)
    # method 1: using eigenvectors
    ans1 = limitToRank(limitVetor1(markov, dampWt))
    # method 2: iterative multiplication until convergence
    ans2 = limitToRank(limitVector2(markov, epsilon, dampWt))
    return ans1, ans2

def fixEndNodes(markov):
    # fixes cases when one node in the graph has no outgoing edges
    # i.e., if one column is a zero vector, we replace it with all entries
    # having equal probability. This function makes the change in the markov
    # chain itself because it is easier to check for an element of the chain
    # being an empty list than a column in a matrix being the zero vector
    for listOfDestinations in markov:
        if len(listOfDestinations) == 0:
            for page in range(len(markov)):
                listOfDestinations.append(page)

def limitToRank(limitVector):
    # given a limit vector which lists the probabilities of being
    # at any page in the form limitVector[i] is the probability of being on
    # page i, this function returns a ranking of the pages in descending order
    # of probability
    pagesWithProbability = {}
    for page in range(len(limitVector)):
        pagesWithProbability[page] = limitVector[page]
        # creates a new dictionary assignment: page --> probability
    rank = []
    while len(pagesWithProbability) > 0:
        maxPage = maxFromDictionary(pagesWithProbability)
        rank.append(maxPage)
        pagesWithProbability.pop(maxPage)
    return rank

################################################################################
############             Method 1: Using eigenvectors             ##############
################################################################################

def limitVector1(markov, dampWt):
    # this function outputs the limit vector according to method 1
    evecs = findEvecs(createPageRankMatrixWithDamping(markov, dampWt))
    for vector in evecs:
    # finds the first and only vector that can be scaled to a probability vector
        if isLimitVector(vector):
            convertToPositive(vector)
            return vector

def isLimitVector(vector):
    # function checks if given vector has all positive / all negative entries
    isPositive = None
    for entry in vector:
        if isPositive == None: # this checks if we are on the first entry
            if entry < 0:
                isPositive = False
            elif entry > 0:
                isPositive = True # sets to + or - based on the first entry
        else:
            if entry < 0 and isPositive or entry > 0 and not isPositive:
                return False # if future entry doesn't match sign of first entry
    return True

def createPageRankMatrixWithDamping(markov, dampWt):
    # adds the markov transition matrix and damping matrix according to input
    # damp weight, dampWt
    P0 = createPageRankMatrix(markov)
    B = createDampingMatrix(markov)
    # initialize a zero matrix of same dimensions as A and B
    P = [[0]*len(P0) for i in range(len(P0))]
    # taking an element wise weighted average of A and B based on dampWt
    for i in range(len(P0)):
        for j in range(len(P)):
            P[i][j] = (1 - dampWt) * P0[i][j] + dampWt * B[i][j]
    return P

def createPageRankMatrix(markov):
    # creates a markov transition matrix corresponding to the given markov chain
    m = len(markov)
    P0 = [[0]*m for index in range(m)]
    for page in range(m):
        denominator = len(markov[page])
        for destination in range(m):
            if destination in markov[page]:
                P0[destination][page] = 1/denominator
            else:
                P0[destination][page] = 0
    return P0

def createDampingMatrix(markov):
    # creates a transition matrix corresponding to a markov chain of the same
    # length as the given markov chain, where all nodes point to all nodes
    # (including self)
    m = len(markov)
    B = [[1/m]*m for index in range(m)]
    return B

################################################################################
###########      Method 2: Using iteration to find convergence       ###########
################################################################################

def limitVector2(markov, epsilon, dampWt):
    # returns the limit vector according to method 2
    markovTranspose, denominators = findMarkovTranspose(markov)
    vec0 = startVector(markov)
    vecA = findNextVector(markovTranspose, denominators, vec0, dampWt)
    vecB = findNextVector(markovTranspose, denominators, vecA, dampWt)
    while not withinEpsilon(vecA, vecB, epsilon):
        vecA = vecB
        vecB = findNextVector(markovTranspose, denominators, vecA, dampWt)
    return vecB

def findMarkovTranspose(markov):
    # given markov, returns markovTranspose as explained in the paper
    markovTranspose = [[] for i in range(len(markov))]
    denominators = []
    for j in range(len(markov)):
        for i in markov[j]:
            markovTranspose[i].append(j)
        denominators.append(len(markov[j]))
    return markovTranspose, denominators

def findNextVector(markovTranspose, denominators, currentVector, dampWt):
    # given the current vector and information derived from markov,
    # this functions returns the next vector (the probabilities at the next
    # step of the random walk)
    nextVector = []
    m = len(currentVector) # now m is the number of pages
    for i in range(m): # go through all pages in markovTranspose
        newPageProbability = 0
        for j in markovTranspose[i]:
            newPageProbability += (1 - dampWt) * currentVector[j]/denominators[j]
        # now we add the damping matrix part. The value of the damping matrix
        # times a probability vector is a constant: 1/m where m is the number
        # of pages
        newPageProbability += dampWt/m
        nextVector.append(newPageProbability)
    return nextVector

################################################################################
###############     Helper Functions alphabetically arranged     ###############
################################################################################

def convertToPositive(vector):
    # returns a vector where every element is the absolute value of the
    # corresponding element in the input vector
    for i in range(len(vector)):
        vector[i] = abs(vector[i])

import numpy
def findEvecs(matrix):
    # returns a list of eigen vectors of a matrix A
    return transpose(numpy.linalg.eig(matrix)[1].real.tolist())
    # .tolist() converts the numpy array to a 2D list and the transpose function
    # is used because the eigenvectors form the columns not the rows of the
    # returned matrix. In a 2D list representation, we have a list of the rows
    # and so to have a list of the eigenvectors it must be the case that the
    # eigenvectors are the rows and not the columns of the matrix.

def maxFromDictionary(dictionary):
    # takes in a dictionary that maps keys to values and returns the key in the
    # dictionary which is associated with the maximum value
    # this is different from a standard python max function because here we
    # want to return the key associated with the max value and not the max key
    maxKey = None
    maxValue = 0
    for key in dictionary:
        if maxKey == None or maxValue < dictionary[key]:
            maxKey = key
            maxValue = dictionary[key]
    return maxKey

def startVector(markov):
    # calculates a start vector for a given markov chain: where all pages have
    # equal probability
    denominator = len(markov)
    v = [1/denominator] * len(markov)
    return v

def transpose(matrix):
    # returns the transpose of the input matrix
    matrixTranspose = [[0]*len(matrix) for i in range(len(matrix))]
    for i in range(len(matrix)):
        for j in range(len(matrix)):
            matrixTranspose[j][i] = matrix[i][j]
    return matrixTranspose

def withinEpsilon(vec1, vec2, epsilon):
    # checks if two vectors are 'almost' equal to each other by seeing if each
    # element of vector 1 is within epsilon distance from the corresponding
    # element of vector 2 for a given epsilon
    assert(len(vec1) == len(vec2))
    for i in range(len(vec1)):
        if not -epsilon <= vec1[i] - vec2[i] <= epsilon:
            return False
    return True

################################################################################
###############              Testing the Program             ###################
################################################################################

def testPageRankProgram():
    # the results expected are in the form of a 2-tuple because we return
    # answers from both methods

    # test case 1
    markov = [[1, 3, 4], [0, 2, 3], [1, 2], [1, 3, 4], [0]]
    assert(rank(markov) == ([0, 1, 3, 2, 4], [0, 1, 3, 2, 4]))

    # test case 2
    markov = [[1], [2], [1]]
    assert(rank(markov) == ([1, 2, 0], [1, 2, 0]))

    # test case 3
    markov = [[1, 3, 4], [0, 2, 4], [3, 6], [2, 4, 6], [5, 8], [4, 6, 8],
                                    [0, 7, 9], [0, 6, 8], [2, 9], [0, 2, 8]]
    assert(rank(markov) == ([2, 6, 8, 0, 4, 3, 9, 5, 7, 1],
                                                [2, 6, 8, 0, 4, 3, 9, 5, 7, 1]))

    # test case 4
    markov = [[1, 5], [2, 5], [1, 3, 5], [4], [1, 5], [2, 6], [0, 1]]
    assert(rank(markov) == ([5, 2, 1, 6, 4, 3, 0], [5, 2, 1, 6, 4, 3, 0]))
    print('Yay!')

testPageRankProgram()

#### Credit:
#### Used numpy to find eigenvectors


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

# creating a vector from a python list

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

print 'vector: '
print vector

# creating a matrix

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

print 'matrix: '
print matrix

# El tipo de objeto de tanto de los vectores como de las matrices
# es ndarray

type(vector), type(matrix)

#Los objetos ndarray de Numpy cuentan con las propiedades shape
#y size que nos muestran sus dimensiones.

print 'vector shape:', vector.shape, 'vector size:', vector.size

print 'matrix shape:', matrix.shape, 'matrix size:', matrix.size

#ARANGE
#La funcion arange nos facilita la creación de matrices
x = np.arange(1, 11, 2) # argumentos: start, stop, step (ejemplo:
# inicia en el 1, termina en el 10, avanza de 2 en 2).

print 'arange:'
print x

#linspace
#linspace nos devuelve un vector con la cantidad de muestras
#que le ingresemos y separados uniformamente entre sí.

y = np.linspace(1, 25, 25)  # argumentos: start, stop, samples

print 'linspace:'
print y

#mgrid
#Con mgrid podemos crear arrays multimensionales.

x, y = np.mgrid[0:5, 0:5]

print 'mgrid:'
print 'x = 0:5'
print x
print 'y = 0:5'
print y

#zeros y ones
#Estas funciones nos permiten crear matrices de ceros o de unos.

zeros = np.zeros((3,3))

print 'zeros:'
print zeros

ones = np.ones((3,3))

print 'ones:'
print ones

#random.randn
#Esta funcion nos permite generar una matriz con una distribución
#estándar de números.

r = np.random.randn(5,5)

print 'random (5,5):'
print r

#diag
#Nos permite crear una matriz con la diagonal de números
#que le ingresemos.

diag = np.diag([1,1,1])

print 'diagonal:'
print diag


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

# # CE-40717: Machine Learning

# ## HW7-Regression

# ### Please fill this part
#
#
# 1.   Full Name: Amir Pourmand
# 2.   Student Number: 99210259

# In[2]:

# You are not allowed to import other packages.

import numpy as np
import pandas as pd

from matplotlib import pyplot as plt
from sklearn.metrics import mean_squared_error

# ### Part1:
# Consider the dataset which has been given to you. Split the dataset into the `training dataset` and `test dataset`. You can use `80%` of the data for training and the rest for testing.

# In[45]:

dataset = pd.read_csv("ML2021S-HW7-data.csv")

df = dataset.to_numpy()
# implement the split part
# name them: x_train, x_test, y_train, y_test

train_count = int(0.8 * len(dataset))
x_train =df[:train_count,1]
y_train = df[:train_count,-1]

x_test = df[train_count:,1]
y_test =df[train_count:,-1]

# ### Part2:
# Assume there is simple linear relationship between X and Y. Train a simple `Linear Regression` on the training dataset and report its accuracy on the training and testing dataset, separately.

# In[197]:

# here you can implement via sgd or use least square solution. we suggest the second one inv(A^T A) * A^T y.
# consider m as the returned line --> m[0] = the slope , m[1] = y intercept, also don't forget to use numpy for calculating m.

bias=np.ones((x_train.shape[0],1))
biased_train = np.append( x_train[:,None],bias,axis=1)

m = np.linalg.inv(biased_train.T @ biased_train)@ (biased_train.T @ y_train)
m

# In[116]:

predicted_train_y = m[0] * x_train + m[1]
predicted_test_y = m[0] * x_test + m[1]

train_error = mean_squared_error(predicted_train_y,y_train)
test_error = mean_squared_error(predicted_test_y,y_test)

print(f"MSE train error: {train_error}\nMSE test error: {test_error}")

# ### Part3:
# Draw the line you've found in the last part, on train and test points (each separately).

# In[117]:

plt.scatter(x_train, y_train)
plt.scatter(x_train, m[0]*x_train + m[1], s=1)
plt.title("Train dataset")

# In[118]:

plt.scatter(x_test, y_test)
plt.scatter(x_test, m[0]*x_test + m[1], s=1)
plt.title("Test dataset")

# ### Part4:
# Were the reported accuracies acceptable?
#
# What about the line you drew in the last part?
#
# Was it appropriate for the dataset?
#

# Explain what are the main problems here.

#
# No, The error is too high.
# The Line is best line that fits but we should also try other models.
# The model is clearly not suitable for this dataset.
#
# Main problem is that a single line can't fit sin or cos or anything like that.

# ### Part5:
# Now, consider the `Locally Weighted Linear Regression` approach for this problem. Do expect it performs better than simple `Linear Regression` for this dataset?

# Yes, I expect that it would work better

# ### Part6:
# Here, we want to implement the `suggested model`. You can implement any weight function you want, the below implementation is just a sample:
#

# \begin{equation*}
#     w^{(i)}=exp\left(-\frac{(\mathbf x^{(i)}-\mathbf x)^\top(\mathbf x^{(i)}-\mathbf x)}{2\tau^2}\right)
# \end{equation*}

# In[145]:

def get_weight_matrix(the_query_point, train_dataset, hyperparameter):
    l = train_dataset.shape[0]
    W = np.mat(np.eye(l))
    for i, xi in enumerate(train_dataset):
        vector = xi - the_query_point
        W[i, i] = np.exp(-1/(2*hyperparameter**2) * (vector @ vector.T))
    return W

# Now implement the `closed form solution` based on the following formula:
#

# \begin{equation*}
#     \boldsymbol\theta=(X^\top WX)^{-1}(X^\top WY)
# \end{equation*}
#

# Of course if you use another form, implement your own.

# In[163]:

def predict(train_X, train_Y, query_x, hyperparameter):
    l = train_X.shape[0]
    X = np.hstack((train_X[:,None], np.ones((l, 1))))
    qx = np.mat([query_x, 1])
    W = get_weight_matrix(qx, X, hyperparameter)

    theta = np.linalg.pinv( X.T @ W @ X) @ (X.T @ W @ train_Y).T

    return np.dot(qx, theta).item(0)

# ### Part7:
# Report accuracy of this model on the test dataset. Also, draw a plot that contains (x_test, y_test) pairs and (x_test, predicted_y) pairs that differ in color.

# In[165]:

predicted_test = [predict(x_train, y_train, query, hyperparameter=0.10) for query in x_test]
error = mean_squared_error(predicted_test,y_test)
print(f"MSE test error: {error}")

# In[166]:

plt.scatter(x_test, y_test, c="blue")
plt.scatter(x_test, predicted_test, c="yellow")

# ### Part8:
# Is the performance of the new model better than the previous one? `Explain`.

# Yes, performance is clearly better. It's actually 1/3 of error in normal linear regression. The reason is that we put weight to the input values and this way we can fit more flexible models.

# ### Part9:
# Now, we want to consider the `KNN` approach for this problem; Do you think it will perform well?

# Yes, KNN has prooven to show very good results. Why not test it?!
#
#

# ### Part10:
# Implementing the `KNN model`.

# In[176]:

class KNN:
    def __init__(self, number_neighbours, X, Y):
        self.k = number_neighbours
        self.X = X
        self.Y = Y

    def distance(self, x1, x2):
        distance = np.abs(x1-x2) # implement the distance function
        return distance

    def return_nn_ids(self, x):
        distances = self.distance(self.X,x) # find indices of k nearest neighbours to x
        indices = np.argsort(distances)[:self.k]
        return indices

    def predict(self, x):
        neareest_indices = self.return_nn_ids(x)
        predicted_y=0
        for item in neareest_indices:
            predicted_y += self.Y[item]
        predicted_y = predicted_y / self.k
        return predicted_y

# In[177]:

def evaluation(k, x_train, y_train, x_test, y_test):
    model = KNN(k, x_train, y_train)
    predicted_y = [model.predict(x) for x in x_test]
    error = mean_squared_error(predicted_y,y_test)
    return error, predicted_y

# In[178]:

k = 3
error_knn, predicted_y = evaluation(k, x_train, y_train, x_test, y_test)
print(f"MSE error of KNN for test dataset: {error_knn}")

# In[179]:

plt.scatter(x_test, y_test, c="blue")
plt.scatter(x_test, predicted_y, c="yellow")

# To find best K for this algorithm we suggest to use cross-validation.
#

# Did this model perform well (due to the plot and test accuracy)?

# Yes, It actually performs very well. The good point is that with default parameters, i.e. k=3 , it performed very well!
# we can also see that the plot shows very good fit to data.

# In[196]:

error_array = []
limit =100

min_error,min_k = None,None

for k in range(1,limit):
    error_knn, predicted_y = evaluation(k, x_train, y_train, x_test, y_test)
    error_array.append(error_knn)

    if k == 1:
        min_error = error_knn
        min_k = k

    if error_knn < min_error:
        min_error = error_knn
        min_k = k

plt.plot(np.arange(1,limit),error_array)
plt.ylim(0.096,0.12)
print('min of k would be:', min_k,'Min of error would be',min_error )

# In[ ]:

# In[ ]: