import math
import numpy as np

def gauss(A, b, pivoteo=None):
    if A.shape[0] != A.shape[1]:
        print("Error, matriz no es cuadrada")
        return

    Ab = np.zeros((len(A), len(A) + 1))
    Ab[:, :-1] = A
    Ab[:, -1] = b

    n = Ab.shape[0]
    mark = list(range(n))

    procedimiento = []

    for row in range(n):
        err = None
        if pivoteo == "parcial":
            Ab, err = pivoteo_parcial(Ab, row)
        elif pivoteo == "total":
            Ab, mark, err = pivoteo_total(Ab, row, mark)
        if err:
            print(err)

        for col in range(row + 1, n):
            M = Ab[col, row] / Ab[row, row]
            for row_i in range(row, n + 1):
                Ab[col, row_i] -= M * Ab[row, row_i]

            procedimiento.append(Ab.copy().tolist())

    x = sustitucion_regresiva(Ab)
    x = [x[mark[i]] for i in range(n)]

    error = A.dot(x) - b
    return x, Ab.tolist(), error.tolist(), procedimiento

def LU(A, b, pivoteo=None):
    n = A.shape[0]
    P = np.eye(n)
    L = np.eye(n)

    for k in range(n - 1):

        if pivoteo == "parcial":
            A, P = pivoteoLU(A, P, k)

        for i in range(k + 1, n):
            M = A[i, k] / A[k, k]
            for j in range(k, n):
                A[i, j] -= M * A[k, j]

            A[i, k] = M

    U = np.triu(A)
    L = L + np.tril(A, -1)
    B = P.dot(b)

    LB = np.zeros((len(L), len(L) + 1))
    LB[:, :-1] = L
    LB[:, -1] = b

    z = sustpro(LB)

    Uz = np.zeros((len(U), len(U) + 1))
    Uz[:, :-1] = U
    Uz[:, -1] = z

    x = sustreg(Uz)

    return x, L, U

def LUdirecto(A, met):
    n = A.shape[0]
    U = np.eye(n)
    L = np.eye(n)

    for k in range(n):
        sum1 = sum(L[k, p] * U[p, k] for p in range(k))
        if met == 0:
            U[k, k] = (A[k, k] - sum1) / L[k, k]
        elif met == 1:
            L[k, k] = (A[k, k] - sum1) / U[k, k]
        else:
            U[k, k] = np.sqrt(A[k, k] - sum1)
            L[k, k] = U[k, k]

        for i in range(k, n):
            sum2 = sum(L[i, p] * U[p, k] for p in range(k))
            L[i, k] = (A[i, k] - sum2) / U[k, k]

        for j in range(k, n):
            sum3 = sum(L[k, p] * U[p, j] for p in range(k))
            U[k, j] = (A[k, j] - sum3) / L[k, k]

    return L, U

def pivoteoLU(A, P, row):
    n = A.shape[0]

    mayor = abs(A[row, row])
    maxrow = row
    for i in range(row + 1, n):
        if abs(A[i, row]) > mayor:
            mayor = abs(A[i, row])
            maxrow = i

    if mayor == 0:
        return A, P, "El sistema no tiene solución única"

    if maxrow != row:  # Intercambio de filas
        aux = A[row, :]
        auxP = P[row, :]
        A[row, :] = A[maxrow, :]
        P[row, :] = P[maxrow, :]
        A[maxrow, :] = aux
        P[maxrow, :] = auxP

    return A, P, None

def mathJacobiSeidel(x0, A, b, tol, n_iter_max, method):

    D = np.diag(np.diag(A))
    L = -np.tril(A, -1)
    U = -np.triu(A, +1)

    message = ""
    errores = [tol + 1]
    itera = [x0]

    for _ in range(n_iter_max):
        if errores[-1] <= tol:
            message = f"{x0} es una aproximación de la solución del sistema con una tolerancia= {tol}"
            break

        if method == "jacobi":
            T = np.linalg.inv(D).dot(L + U)
            C = np.linalg.inv(D).dot(b)
            x1 = T.dot(x0) + C
        elif method == "seidel":
            T = np.linalg.inv(D - L).dot(U)
            C = np.linalg.inv(D - L).dot(b)
            x1 = T.dot(x0) + C

        # errores.append(np.linalg.norm(x1 - x0))
        itera.append(x0)
        x0 = x1
    else:
        message = f"Fracasó en {n_iter_max} iteraciones"

    return errores, x0, message, itera

def iterativos(x0, A, b, tol, n_iter_max, method):

    message = ""
    errores = [tol + 1]

    for _ in range(n_iter_max):
        if errores[-1] <= tol:
            message = f"{x0} es una aproximación de la solución del sistema con una tolerancia= {tol}"
            break

        x1 = new_jacobi(x0, A, b) if method == "jacobi" else new_seidel(x0, A, b)
        errores.append(np.linalg.norm(x1 - x0))
        x0 = x1.copy()
    else:
        message = f"Fracasó en {n_iter_max} iteraciones"

    return x0.tolist(), message, errores

def new_jacobi(x0, A, b):
    n = A.shape[0]
    x1 = x0.copy()
    for i in range(n):
        suma = sum(A[i, j] * x0[j] for j in range(n) if j != i)
        x1[i] = (b[i] - suma) / A[i, i]

    return x1

def new_seidel(x0, A, b):

    n = A.shape[0]
    x1 = x0.copy()
    for i in range(n):
        suma = sum(A[i, j] * x1[j] for j in range(n) if j != i)
        x1[i] = (b[i] - suma) / A[i, i]

    return x1

def sustitucion_regresiva(Ab):
    n = Ab.shape[0] - 1

    x = np.zeros(n + 1)
    x[n] = Ab[n, n + 1] / Ab[n, n]
    for i in range(n - 1, -1, -1):
        suma = sum(Ab[i, j] * x[j] for j in range(i + 1, n + 1))
        x[i] = (Ab[i, -1] - suma) / Ab[i, i]

    return x

def sustitucion_progresiva(Ab):
    n = Ab.shape[0] - 1
    x = np.zeros(n + 1)
    x[0] = Ab[0, n + 1] / Ab[1, 1]

    for i in range(1, n):
        suma = sum(Ab[i, j] * x[j] for j in range(i - 1))
        x[i] = (Ab[i, n + 1] - suma) / Ab[i, i]

    return x

def pivoteo_parcial(Ab, row):
    n = Ab.shape[0]

    mayor = abs(Ab[row, row])
    max_row = row
    for i in range(row + 1, n):
        if abs(Ab[i, row]) <= mayor:
            continue

        mayor = abs(Ab[i, row])
        max_row = i

    if mayor == 0:
        return Ab, "El sistema no tiene solución única"
    elif max_row != row:
        Ab[[row, max_row]] = Ab[[max_row, row]]

    return Ab, None

def pivoteo_total(Ab, row, mark):
    n = Ab.shape[0]

    mayor = 0
    max_row = row
    max_col = row
    for row_i in range(row, n):
        for col_i in range(row, n):
            if abs(Ab[row_i, col_i]) > mayor:
                mayor = abs(Ab[row_i, col_i])
                max_row = row_i
                max_col = col_i

    if mayor == 0:
        return Ab, mark, "El sistema no tiene solución única"
    if max_row != row:
        aux = Ab[row, :].copy()
        Ab[row, :] = Ab[max_row, :]
        Ab[max_row, :] = aux
    if max_col != row:
        aux = Ab[:, row].copy()
        Ab[:, row] = Ab[:, max_col]
        Ab[:, max_col] = aux
        aux2 = mark[row]
        mark[row] = mark[max_col]
        mark[max_col] = aux2

    return Ab, mark, None


# shows how linear regression analysis can be applied to moore's law
#
# notes for this course can be found at:
# https://deeplearningcourses.com/c/data-science-linear-regression-in-python
# https://www.udemy.com/data-science-linear-regression-in-python
# transistor count from: https://en.wikipedia.org/wiki/Transistor_count

from __future__ import print_function, division
from builtins import range
# Note: you may need to update your version of future
# sudo pip install -U future

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

X = []
Y = []

# some numbers show up as 1,170,000,000 (commas)
# some numbers have references in square brackets after them
non_decimal = re.compile(r'[^\d]+')

for line in open('moore.csv'):
    r = line.split('\t')

    x = int(non_decimal.sub('', r[2].split('[')[0]))
    y = int(non_decimal.sub('', r[1].split('[')[0]))
    X.append(x)
    Y.append(y)

X = np.array(X)
Y = np.array(Y)

plt.scatter(X, Y)
plt.show()

Y = np.log(Y)
plt.scatter(X, Y)
plt.show()

# copied from lr_1d.py
denominator = X.dot(X) - X.mean() * X.sum()
a = ( X.dot(Y) - Y.mean()*X.sum() ) / denominator
b = ( Y.mean() * X.dot(X) - X.mean() * X.dot(Y) ) / denominator

# let's calculate the predicted Y
Yhat = a*X + b

plt.scatter(X, Y)
plt.plot(X, Yhat)
plt.show()

# determine how good the model is by computing the r-squared
d1 = Y - Yhat
d2 = Y - Y.mean()
r2 = 1 - d1.dot(d1) / d2.dot(d2)
print("a:", a, "b:", b)
print("the r-squared is:", r2)

# how long does it take to double?
# log(transistorcount) = a*year + b
# transistorcount = exp(b) * exp(a*year)
# 2*transistorcount = 2 * exp(b) * exp(a*year) = exp(ln(2)) * exp(b) * exp(a * year) = exp(b) * exp(a * year + ln(2))
# a*year2 = a*year1 + ln2
# year2 = year1 + ln2/a
print("time to double:", np.log(2)/a, "years")


from statsmodels.nonparametric.kde import KDEUnivariate
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

my_dataset = pd.read_excel('Smith_glass_post_NYT_data.xlsx', sheet_name='Supp_traces')

x = my_dataset.Zr
x_eval = np.arange(0, 1100, 1)

fig = plt.figure()

ax1 = fig.add_subplot(2, 1, 1)
# Density Histogram
ax1.hist(x, bins='auto', density=True, label='Density Histogram', color='#c7ddf4', edgecolor='#000000')
kde = KDEUnivariate(x)
kde.fit()
my_kde = kde.evaluate(x_eval)
ax1.plot(x_eval, my_kde, linewidth=1.5, color='#ff464a', label='gaussian KDE - auto bandwidth selection')
ax1.set_xlabel('Zr [ppm]')
ax1.set_ylabel('Probability density')
ax1.legend()

ax2 = fig.add_subplot(2, 1, 2)
# Density Histogram
ax2.hist(x, bins= "auto", density = True, label='Density Histogram', color='#c7ddf4', edgecolor='#000000')

# KDE
# Effect of bandwidth
for my_bw in [10,50,100]:

    kde = KDEUnivariate(x)
    kde.fit(bw = my_bw)

    my_kde = kde.evaluate(x_eval)
    ax2.plot(x_eval, my_kde, linewidth = 1.5, label='gaussian KDE - bw: ' + str(my_bw))

ax2.set_xlabel('Zr [ppm]')
ax2.set_ylabel('Probability density')
ax2.legend()

fig.tight_layout()


import scipy.integrate as spi
import numpy as np

print('Triple integral computed by SciPy nquad')
print('Example 3-01 nquad')
print('Integral of x + yz^2 from z=1 to z=2, y=z+1 to y=z+2 and from x=y+x to x=2(y+z)')

integrand = lambda x, y, z : x + y * z ** 2

bounds_z = lambda : [1., 2.]
bounds_y = lambda z : [z+1, z+2]
bounds_x = lambda z, y : [y+z, 2 * (y+z)]
ya=lambda z: z + 1
yb=lambda z: z + 2
xa=lambda z, y : y + z
xb=lambda z, y : 2 * (y + z)

result, error = spi.nquad(integrand, [bounds_x, bounds_y, bounds_z])
print('Result is ', result, 'with error ', error)


import numpy as np
from numpy import linalg
from numpy import random
from scipy import integrate
from scipy.optimize import curve_fit

##########################################################
#gauusian_weight
#compute a gaussian weight matrix W, given a vector x
#the reference observation point i (index) and a bandwith
#tau
##########################################################

def gaussian_weight(x, i, tau):
    #compute the vector of weights
    w = np.exp(-((x-x[i])**2.)/(tau))
    #transform into a diagonal array
    W = np.diag(w)
    return W

#########################################################
#Weighted linaer regression
#perform linear regression given a matrix of inputs
#X, whose rows correspond to observations and whose columns
#correspond to features and a vector of outputs
#y
#inputs:
#X - matrix of inputs (mXn) with m features and n observations
#y - vector of ouput observations (size n)
#W - n X n weight matrix
#ouputs:
#theta - vector of best-fit parameters
#########################################################

def weighted_linear_reg(X,y,W):
    m1 = linalg.inv(np.dot(np.dot(X.T,W),X))
    m2 = np.dot(np.dot(X.T,W),y)

    theta = np.dot(m1,m2)
    return theta

#####################################################################
#Loess
#Compute a locally weighted linear regression given a vector
#of inputs x and a vector of outputs y
#Inputs
#x - vector of inputs
#y - vector of ouputs
#degree - degree of polynomial for fit (1 or 2)
#alpha - smoothing parameter. Defined as the ratio of tau**2. divided
#by the square in the difference between the two extrema in x

def loess(x,y,degree,alpha):
    n = len(x)
    #######################################################
    #First degree polynomial regression
    #######################################################
    if degree == 1:

        #First feature in the X array is a constant
        #Second feature is the value of x
        const = np.ones(n)
        X = np.array([const,x]).T
        W = np.identity(n)
        tau = alpha * np.sqrt((x[0]-x[-1])**2.)

        #Construct the theta matrix
        Theta = np.zeros([2,n])
        #Construct the prediction vector
        yp = np.zeros(n)
        #Loop over observations
        for i in range(n):
            #compute the weight matrix
            W = gaussian_weight(x,i,tau)
           #perform linear regression
            theta = weighted_linear_reg(X,y,W)
            Theta[:,i] = theta
            yp[i] = np.dot(X[i,:],theta)
        return [Theta,yp]

    #######################################################
    #Second degree polynomial regression
    #######################################################
    if degree == 2:

        #First feature in the X array is a constant
        #Second feature is the value of x
        #Third feature is the value of x**2.
        const = np.ones(n)
        X = np.array([const,x,x**2.]).T
        W = np.identity(n)
        tau = alpha * np.sqrt((x[0]-x[-1])**2.)

        #Construct the theta matrix
        Theta = np.zeros([3,n])

        #Construct the prediction vector
        yp = np.zeros(n)
        #Loop over observations
        for i in range(n):
            #compute the weight matrix
            W = gaussian_weight(x,i,tau)
            #perform linear regression
            theta = weighted_linear_reg(X,y,W)
            Theta[:,i] = theta
            yp[i] = np.dot(X[i,:],theta)
        return [Theta,yp]

##################################
#Numerical Laplace transform
##################################
#Inputs
#t - time vector
#f - vector of discrete values of function to be transformed
#S - vector of Laplace frequencies to evaluate the transform at

def laplace(t, f, S):
    L = np.zeros(len(S))
    for i, s in enumerate(S):
        y = f*np.exp(-s*t)
        L[i] = integrate.trapz(y, t)
    return L

def get_cross_validation_score(t, y, func, p0=None):
    """ Obtain the leave-one-out cross-validation score for
    a functional model of a data set over a given fitting window.

    Parameters
    ----------
    t : 1-d array
        Length N vector  of values for the independent variable of a dataset.
    y : 1-d array
        Length N vector of values for the dependent variable of a dataset.
    func : callable function
           Model to fit the data against. Must be of the form
           `f(t, p1, p2, ..., pM)` where `t` is the independent variable and
           `p1, p2, ..., pM` is a set of M parameters to fit.

    p0 : 1-d array or list, `optional`
         Initial guesses for the M parameters to fit, [p1, p2, ..., pM]

    Returns
    -------
    cv : float
         Leave-one-out cross-validation score for the model
    """
    cv = 0.
    yskip = y
    tskip = t
    n = len(tskip)
    # Loop over each (t, y) point and perform a
    # fit to func with that point removed
    # calculate MSE for that point
    for i in range(n):
        ytest = np.delete(yskip, i)
        ttest = np.delete(tskip, i)
        try:
            paramsi = curve_fit(func, ttest, ytest, p0=p0, maxfev=10000)[0]
            yfiti = func(tskip[i], *paramsi)
            erri = (yskip[i]-yfiti)**2.
        except RuntimeError:
            erri = 1.e3
        cv = cv + erri
    # Average cv scores
    cv = cv/np.float(n)

    # If fit is not found for this window, penalize strongly
    try:
        paramsi = curve_fit(func, t, y, p0=p0, maxfev=10000)[0]
    except RuntimeError:
        cv = 1.e6
    return cv

def minimize_cv_error(t, y, twindows, func, p0=None):
    """ Find the fitting interval that minimizes the cross-validation
    error for a model fitted to a sub-interval of a dataset,
    given a set of possible intervals in the independent variable
    over which to perform the fit.

    Parameters
    ----------
    t : 1-d array
        Length N vector  of values for the independent variable of a dataset.
    y : 1-d array
        Length N vector of values for the dependent variable of a dataset.
    twindows : List of len 2 lists
               List of the form [[t0, tend1], [t0, tend2], ...]
               where [t0, tendi] represents the `ith` closed interval over which to
               fit the data set and find the cross-validation score.
    func : callable function
           Model to fit the data against. Must be of the form
           `f(t, p1, p2, ..., pM)` where `t` is the independent variable and
           `p1, p2, ..., pM` is a set of M parameters to fit.

    p0 : 1-d array or list, `optional`
         Initial guesses for the M parameters to fit, [p1, p2, ..., pM]

    Returns
    -------
    twindow_min : List
                  List of the form `[t0, tend]' where t0 and tend are the
                  beginning and end of the closed interval in ``t`` that
                  provides the lowest cross-validation score for the fitted
                  model
    pmin : list
           Optimal parameters for fitting the model ``func`` to the data
           over the sub-interval ``twindow_min``
    CV_min : float
             Leave-one-out cross-validation error for the model ``func``
             over the interval ``twindow_min``
    """
    CVs = []
    params = []
    for twindow in twindows:
        tinds = [np.argmin(np.abs(t-twindow[0])),
                 np.argmin(np.abs(t-twindow[1]))]
        tfit = t[tinds[0]: tinds[1]+1]
        yfit = y[tinds[0]: tinds[1]+1]
        CVs.append(get_cross_validation_score(tfit, yfit, func, p0))
        try:
            paramsi = curve_fit(func, tfit, yfit, p0=p0, maxfev=100000)[0]
            params.append(paramsi)
        except RuntimeError:
            params.append(None)
    CV_argmin = np.argmin(CVs)
    CV_min = CVs[CV_argmin]
    twindow_min = twindows[CV_argmin]
    pmin = params[CV_argmin]
    return [twindow_min, pmin, CV_min]

def laplace_merge(omega, G1, G2, G1_Fdirect, G2_Fdirect):
    """ Find the interval where the Laplace transform is valid over
    the frequency space and replace the Fourier transform data with
    the Laplace transform modulus.

    Parameters
    ----------
    omega : 1-d array
            Vector of angular frequencies in units of rad/s
    G1 : 1-d array
         Storage modulus at angular frequencies ``omega`` in units of Pa
         calculated using Fourier transform
    G2 : 1-d array
         Loss modulus at angular frequencies ``omega`` in units of Pa
         calculated using Fourier transform
    G1_Fdirect: 1-d array
                Storage modulus at angular frequencies ``omega`` in units
                of Pa calculated using direct Laplace transform
    G2_Fdirect: 1-d array
                Loss modulus at angular frequencies ``omega`` in units
                of Pa calculated using direct Laplace transform

    Returns
    -------
    G1_plot : 1-d array
              Loss modulus at angular frequencies ``omega`` in units of Pa
              after merging Laplace and Fourier transform moduli
    G2_plot : 1-d array
              Storage  modulus at angular frequencies ``omega`` in units of
              Pa after merging Laplace and Fourier transform moduli
    """
    G1_lower = 0
    G2_lower = 0
    G1_upper = len(omega) - 1
    G2_upper = len(omega) - 1
    for n in range(len(omega)-1):
        if (G1[n]-G1_Fdirect[n])/np.abs(G1[n]-G1_Fdirect[n]) != (G1[n+1]-G1_Fdirect[n+1])/np.abs(G1[n+1]-G1_Fdirect[n+1]):
            G1_lower = n
            break
    for n in range(len(omega)-1):
        if (G2[n]-G2_Fdirect[n])/np.abs(G2[n]-G2_Fdirect[n]) != (G2[n+1]-G2_Fdirect[n+1])/np.abs(G2[n+1]-G2_Fdirect[n+1]):
            G2_lower = n
            break
    for n in range(len(omega)-1):
        if (G1[len(omega)-1-n]-G1_Fdirect[len(omega)-1-n])/np.abs(G1[len(omega)-1-n]-G1_Fdirect[len(omega)-1-n]) != (G1[len(omega)-2-n]-G1_Fdirect[len(omega)-2-n])/np.abs(G1[len(omega)-2-n]-G1_Fdirect[len(omega)-2-n]):
            G1_upper = len(omega)-1-n
            break
    for n in range(len(omega)-1):
        if (G2[len(omega)-1-n]-G2_Fdirect[len(omega)-1-n])/np.abs(G2[len(omega)-1-n]-G2_Fdirect[len(omega)-1-n]) != (G2[len(omega)-2-n]-G2_Fdirect[len(omega)-2-n])/np.abs(G2[len(omega)-2-n]-G2_Fdirect[len(omega)-2-n]):
            G2_upper = len(omega)-1-n
            break

    G1_plot = np.zeros_like(G1)
    G2_plot = np.zeros_like(G2)
    G1_plot[0:G1_lower] = G1[0:G1_lower]
    G1_plot[G1_lower:G1_upper] = G1_Fdirect[G1_lower:G1_upper]
    G1_plot[G1_upper:] = G1[G1_upper:]
    G2_plot[0:G2_lower] = G2[0:G2_lower]
    G2_plot[G2_lower:G2_upper] = G2_Fdirect[G2_lower:G2_upper]
    G2_plot[G2_upper:] = G2[G2_upper:]
    return G1_plot, G2_plot


import numpy as np
class loss_Function():
    def __init__(self,loss_type):
        self.loss_type=loss_type
        self.Lambda=0
        #self.pred
        #self.labels

    def log(self, X): #replace log(0)=-9999999 instead of -inf
        with np.errstate(divide='ignore'):
            res = np.log(X)
        res[np.isneginf(res)]=-9999999
        return res

    def getLambda(self):
        return self.Lambda
    def setLambda(self,Lambda):
        self.Lambda= Lambda

    def forward(self,pred,labels,weights_sum):
        self.m=labels.shape[1]
        self.pred=pred
        self.labels=labels
        self.weights_sum=weights_sum
        self.regularization_cost=((self.Lambda)/(2*self.m))*(self.weights_sum)

        if self.loss_type=="LOG":
            return (((1/self.m)*np.sum(-self.log(np.absolute((labels/2)-0.5+pred))))+self.regularization_cost)

        elif self.loss_type=="MEAN":
            return ((np.sum(np.power(pred-labels,2))/(2*self.m))+self.regularization_cost)

        elif self.loss_type=="CROSSENTROPY":
            return (((-1/self.m)*np.sum(np.multiply(labels, self.log(pred))+np.multiply(1-labels, self.log(1-pred))))+self.regularization_cost)

        elif self.loss_type=="SoftmaxCrossEntropy":
            exp = np.exp(pred) # exp of all output nodes
            sums = np.sum(exp, axis=0) # sum of exponentials of each example
            softmax = exp/sums # softmax output for all nodes
            self.softmax = softmax # cache softmax
            L = -1*self.log(softmax[labels==1]) # individual losses for each example
            return (((1/self.m)*np.sum(L))+self.regularization_cost)

    def backward(self):
        if self.loss_type=="LOG":
            return (((-1/self.pred)*((1+self.labels)/2)) + ((1/(1-self.pred))*((1-self.labels)/2)))

        elif self.loss_type=="MEAN":
            return (self.pred-self.labels)

        elif self.loss_type=="CROSSENTROPY":
            return ((-self.labels/self.pred)+(1-self.labels/1-self.pred))

        elif self.loss_type=="SoftmaxCrossEntropy":
            return (self.softmax - self.labels)

#lossfn=Loss_Functions('MEAN')
#pred=np.array([[1,1,1]])
#labels=np.array([[1,1,-1]])

#loss=lossfn.forward(pred, labels)
#grad=lossfn.backward()
#print(loss)
#print(grad)


import survey
import numpy as np
from collections import defaultdict
import operator
import matplotlib.pyplot as plt
import pandas as pd
import random

def chapter_1(df):
	'''
	ThinkStats Chapter 1 solutions
	'''
	# task 1: number of pregnancies
	print('Number of pregnancies: {0}'.format(len(df)))

	# task 2: total of live births
	print('Number of live births: {0}'.format(sum(df[df['outcome'] == 1]['outcome'])))

	# task 3: total of live births splitted for first babies and other babies
	df_first = df[df['birthord'] == 1]
	df_other = df[df['birthord'] != 1]
	print('Number of live births: {0} (1st baby), {1} (non-1st baby)'.format(
		sum(df_first[df_first['outcome'] == 1]['outcome']),
		sum(df_other[df_other['outcome'] == 1]['outcome'])))

	# task 4: avg pregnancy length for first babies and other babies
	prglength_first = np.mean(df_first[df_first['outcome'] == 1]['prglength'])
	prglength_other = np.mean(df_other[df_other['outcome'] == 1]['prglength'])
	print('Average pregnancy length (in weeks): {0} (1st baby) {2} {1} (non-1st baby)'.format(
		prglength_first, prglength_other, '>' if prglength_first > prglength_other else '<'))
	delta = abs(prglength_first - prglength_other)
	print('Delta: {0} (weeks), {1} (hours)'.format(delta, delta * (24 * 7)))

def make_hist(lst, trim=False):

	def reject_outliers(data, m=2):
	    return data[abs(data - np.mean(data)) < m * np.std(data)]

	if trim:
		lst = reject_outliers(lst)
	hist = defaultdict(int)
	for item in lst:
		hist[item] += 1
	return hist

def mode(hist):
	return max(hist.items(), key=operator.itemgetter(1))[0]

def all_modes(hist):
	return sorted(hist.items(), key=operator.itemgetter(1), reverse=True)

def chapter_2(df):
	'''
	ThinkStats Chapter 2 solutions
	'''
	df_first = df[df['birthord'] == 1]
	df_other = df[df['birthord'] != 1]
	print('Gestation time (mean, std deviation): \n{0} (first baby), \n{1} (non-1st baby)'.format(
		(np.mean(df_first[df_first['outcome'] == 1]['prglength']), df_first[df_first['outcome'] == 1]['prglength'].std()),
		(np.mean(df_other[df_other['outcome'] == 1]['prglength']), df_other[df_other['outcome'] == 1]['prglength'].std())))

	# show histogram of all prglengths for 2 df
	hist = make_hist(df_first[df_first['outcome'] == 1]['prglength'], True)
	print(mode(hist))
	print(all_modes(hist))

	hist_other = make_hist(df_other[df_other['outcome'] == 1]['prglength'], True)
	plt.bar(np.fromiter(hist.keys(), dtype=float) - 0.2, list(hist.values()), width=0.2, align='center')
	plt.bar(np.fromiter(hist_other.keys(), dtype=float) + 0.2, list(hist_other.values()), width=0.2, align='center')
	plt.show()

def make_cdf(hist):
	cdf = defaultdict(int)
	total = 0
	for k in sorted(hist.keys()):
		total += hist[k]
		cdf[k] = total
	return cdf

def chapter_4(df):
	'''
	ThinkStats chapter 4 solutions
	'''
	df_first = df[df['birthord'] == 1]
	df_other = df[df['birthord'] != 1]
	wgt_first = df_first[df_first['totalwgt_oz'] != 'NA']['totalwgt_oz']
	wgt_other = df_other[df_other['totalwgt_oz'] != 'NA']['totalwgt_oz']

	hist_first = make_hist(wgt_first)
	hist_other = make_hist(wgt_other)

	# plt.bar(np.fromiter(hist_first.keys(), dtype=float) - 0.2, list(hist_first.values()), width=0.4, align='center')
	# plt.bar(np.fromiter(hist_other.keys(), dtype=float) + 0.2, list(hist_other.values()), width=0.2, align='center')
	# plt.show()

	cdf_first = make_cdf(hist_first)
	cdf_other = make_cdf(hist_other)

	plt.plot(np.fromiter(cdf_first.keys(), dtype=float) - 0.2, list(cdf_first.values()))
	plt.plot(np.fromiter(cdf_other.keys(), dtype=float) + 0.2, list(cdf_other.values()))
	plt.show()

def random_study():
	n = 1000
	sample = [random.random() for _ in range(n)]

	hist = make_hist(sample)
	cdf = make_cdf(hist)
	plt.plot(np.fromiter(hist.keys(), dtype=float), list(hist.values()))
	# plt.plot(np.fromiter(cdf.keys(), dtype=float), list(cdf.values()))
	plt.show()

if __name__ == '__main__':
	table = survey.Pregnancies()
	table.ReadRecords('./data')
	df = table.ConvertToDataFrame()
	random_study()


import numpy as np

def describe_error_disparity(diff_vector, absolute=None, badn_error=[.5, 1, 2, 3, 4, 5]):

    metrics = describe_error_depth(diff_vector, absolute=absolute)

    abs_diff_vector = np.abs(diff_vector)
    metrics['EPE'] = np.mean(abs_diff_vector)
    for n in badn_error:
        metrics['Bad'+str(n)] = (np.sum(abs_diff_vector>n)/len(abs_diff_vector))*100
    return metrics

def describe_error_depth(diff_vector, absolute=None):
    metrics={}
    metrics['RMS'] = np.sqrt(np.sum((diff_vector)**2)/len(diff_vector))
    if (absolute is None) or (absolute == True):
        abs_diff_vector = np.abs(diff_vector)
        metrics['Mean (Absolute)'] = np.mean(abs_diff_vector)
        metrics['Median (Absolute)'] = np.median(abs_diff_vector)
        metrics['Std (Absolute)'] = np.std(abs_diff_vector)
        metrics['Max (Absolute)'] = np.amax(abs_diff_vector)
        metrics['Min (Absolute)'] = np.amin(abs_diff_vector)
        metrics['Q1 (Absolute)'] = np.percentile(abs_diff_vector,25)
        metrics['Q3 (Absolute)'] = np.percentile(abs_diff_vector,75)

    if (absolute is None) or (absolute == False):
        metrics['Mean'] = np.mean(diff_vector)
        metrics['Median'] = np.median(diff_vector)
        metrics['Std'] = np.std(diff_vector)
        metrics['Max'] = np.amax(diff_vector)
        metrics['Min'] = np.amin(diff_vector)
        metrics['Q1'] = np.percentile(diff_vector,25)
        metrics['Q3'] = np.percentile(diff_vector,75)
    return metrics


# -*- coding: utf-8 -*-
"""
Created on 21/05/2021
@author: Paschalis Giakoumoglou 10054
@author: Christos Kyratsous 10105
"""

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

def f(x):
    return math.exp(3*x)*math.sin(2*x)

def rectLeft(f, a, b, N):
    h = (b-a)/N
    tot = 0
    for i in range(N):
        tot += f(a+i*h)
    return tot*h

def rectRight(f, a, b, N):
    h = (b-a)/N
    tot = 0
    for i in range(N):
        tot += f(a+i*h+h)
    return tot*h

def trapezoid(f, a, b, N):
    h = (b - a) / N
    tot = f(a)+f(b)
    for i in range(1, N):
        tot += 2 * f(a + i * h)
    return (h / 2) * tot

def simp(f, a,b,N):
    h = (b-a)/N
    tot = f(a) + f(b)
    for i in range(1,N,1):
        tot += f(a+i*h)*(2*(i%2 == 0) + 4*(i%2 == 1))
    return tot*h/3

def calcError(f, a, b, maxErrors, realResult):
    val = np.arange(10,1010,10)
    val = val.astype(np.int64)
    exponents = np.array([1,1,2,4])
    x = np.zeros((100,1), np.double)
    y = np.zeros((100,1), np.double)
    z = np.zeros((100,1), np.double)
    name = ["Left Rectangle", "Right Rectangle", "Trapezoid", "Simpson"]
    for counter, fun in enumerate([rectLeft, rectRight, trapezoid, simp]):
        for i, N in enumerate(val):
            y[i] = math.log10(((abs(fun(f ,a, b, N)-realResult))))
            x[i] = math.log10((b-a)/N)
            z[i] = math.log10(maxErrors[counter]/val[i]**exponents[counter])

        plt.xlabel("Values of h")
        plt.ylabel("Absolute Error")
        plt.plot(x, y, marker = 'o')
        plt.plot(x, z, marker = 'o')
        plt.title(name[counter])
        plt.show()

realResult = 3/13*math.exp(3*math.pi/4)+2/13
a = 0
b = math.pi/4
maxErrors = np.array([(((b-a)**2)/2)*3*math.exp(3*math.pi/4),
                      (((b-a)**2)/2)*3*math.exp(3*math.pi/4),
                      ((b-a)**3)/12 * 5 * math.exp(3*math.pi/4),
                      ((b-a)**5/180) * 119 * math.exp(3*math.pi/4)])
calcError(f, a, b, maxErrors, realResult)


import numpy as np
import scipy as sp

def E_sq(z, om0):
    """
    A function giving Hubble's law for flat cosmology

    :param z: redshift value
    :param om0: matter density
    :return: A value for the Hubble parameter
    """
    return om0 * (1 + z) ** 3 + 1 - om0

def f_integrand(z, om0):
    """
    A function for the redshift integrand in the intrinsic alignment calculation

    :param z: redshift value
    :param om0: matter density
    :return: redshift integrand
    """
    return (z + 1) / (E_sq(z, om0)) ** 1.5

def D_single(z, om0):
    """
    Provides the normalised linear growth factor

    :param z: single redshift value
    :param om0: matter density
    :return: normalised linear growth factor
    """
    first_integral = sp.integrate.quad(f_integrand, z, np.inf, args=(om0))[0]
    second_integral = sp.integrate.quad(f_integrand, 0, np.inf, args=(om0))[0]

    return (E_sq(z, om0) ** 0.5) * first_integral / second_integral

def D_1(z, om0):
    """
    Normalised linear growth factor (D_plus)

    :param z: single redshift value or array values
    :param om0: matter density
    :return: normalised linear growth factor
    """

    if (isinstance(z, float)) or (isinstance(z, int)):
        D_values = D_single(z, om0)
    else:
        z = list(z)
        D_values = [D_single(z[i], om0) for i in range(len(z))]
        D_values = np.array(D_values)

    return D_values

def F_nla(z, om0, A_ia, rho_c1, eta=0., z0=0., lbar=0., l0=1e-9, beta=0.):
    """
    NLA intrinsic alignment amplitude

    :param z: redshift value
    :param om0: matter density
    :param A_ia: amplitude parameter
    :param rho_c1: rho_crit x C1 (C1 approx 1.508e+27 cm3 / g)
    :param eta: redshift dependence
    :param z0: arbitrary redshift pivot parameter
    :param lbar: average luminosity of source galaxy population
    :param l0: arbitrary luminosity pivot parameter
    :param beta: luminosity dependence
    :return: NLA F(z) amplitude
    """

    prefactor = - A_ia * rho_c1 * om0
    inverse_linear_growth = 1. / D_1(z, om0)
    redshift_dependence = ((1+z)/(1+z0))**eta
    luminosity_dependence = (lbar/l0)**beta

    return prefactor * inverse_linear_growth * redshift_dependence * luminosity_dependence


# numpy <- it is useful to calculate array and matrix
import numpy as np

x = np.array([1.0, 2.0, 3.0])
print(x / 2.0)
# numpy.ndarray
print(type(x))

# calculate array <- it is necessary to be element-wise
y = np.array([2.0, 4.0, 6.0])
print(x + y)
print(x - y)
print(x * y)
print(x / y)

A = np.array([[1, 2], [3, 4]])
print(A)
# 2 × 2
print(A.shape)
# int64
print(A.dtype)

# calculate matrix
B = np.array([[3, 0], [0, 6]])
print(A + B)
print(A * 10)

# numpy can create N array
# in math, we call
# 1 array to vector
# 2 array to array
# more then 3 array to tensor

# in calculate A * 10, 10 becomes 2 × 2
# it is called by broadcast
# so we can calculate different size array

C = np.array([[1, 2], [3, 4]])
D = np.array([10, 20])
print(C * D)

# to access ndarray
X = np.array([[51, 55], [14, 19], [0, 4]])
print(X)
print(X[0])
print(X[0][1])

for row in X:
    print(row)

# n array to vector
X = X.flatten()
print(X)
# to get index number 0, 2, 4
print(X[np.array([0, 2, 4])])
# so we can get element by condition
# X > 15 will be the result array [True, True, False, True, False, False, dtype=bool]
print(X > 15)
print(X[X > 15])


# Code made for : Sergio Andrés Díaz Ariza
# Specialization--> Statistics in Python Coursera
# 23 April, 2021
# License MIT

import numpy as np
import pandas as pd
from scipy.stats import t

pd.set_option('display.max_columns', 30)  # set so can see all columns of the DataFrame

# Import the data
df = pd.read_csv("nap_no_nap.csv")

# First, look at the DataFrame to get a sense of the data
print(df.head())

############################################################################
# Average bedtime confidence interval for napping and non napping toddlers #
# Create two 95% confidence intervals for the average bedtime, one for     #
# toddler who nap and one for toddlers who don't.                          #
# ##########################################################################

# First, isolate the column 'night bedtime' for those who nap into a new variable, and those who didn't nap
# into another new variable.

df["bedtime_nap"] = df["night bedtime"].where(df["napping"] == 1)
df["bedtime_no_nap"] = df["night bedtime"].where(df["napping"] == 0)
dx = df[["bedtime_nap", "bedtime_no_nap"]]  # replace(np.nan, 0)  # fillna(0)

# Now find the sample mean bedtime for nap and no_nap.

# Group by the Data
nap_mean_bedtime = dx.bedtime_nap.mean()
no_nap_mean_bedtime = dx.bedtime_no_nap.mean()
print("Mean of the toddlers nap:\t{}".format(nap_mean_bedtime))
print("Mean of the toddlers no_nap:\t{}".format(no_nap_mean_bedtime))

# Find the t_stars for the 95% confidence intervals

# Careful the t-distribution has two tails, then you dont do (1 - 0.05)= 0.95 instead (1 - 0.05 / 2)
nap_t_star = t.ppf(1 - 0.05 / 2, dx.bedtime_nap.count()-1)
no_nap_t_star = t.ppf(1 - 0.05 / 2, dx.bedtime_no_nap.count()-1)

print("t* of the interval toddlers nap:\t{}".format(nap_t_star))
print("t* of the interval toddlers no_nap:\t{}".format(no_nap_t_star))

# Now to create our confidence intervals. For the average bedtime for nap and no nap,
# find the upper and lower bounds for the respective 95% confidence intervals.

# Standard Error for each Population of the mean
sd_nap = dx["bedtime_nap"].std()  # standard Deviation
sd_no_nap = dx["bedtime_no_nap"].std()
se_nap = sd_nap / np.sqrt(dx.bedtime_nap.count())    # Is of the sample not of the populaton
se_no_nap = sd_no_nap / np.sqrt(dx.bedtime_no_nap.count())

print("Standard Error of the Napping Group:\t{}".format(se_nap))
print("Standard Error of the No-Napping Group:\t{}".format(se_no_nap))

# Confidence Intervals
lcb_nap = nap_mean_bedtime - nap_t_star * se_nap
ucb_nap = nap_mean_bedtime + nap_t_star * se_nap
lcb_no_nap = no_nap_mean_bedtime - no_nap_t_star * se_no_nap
ucb_no_nap = no_nap_mean_bedtime + no_nap_t_star * se_no_nap
print("\nConfidence Interval Napping:\t({}, {})".format(lcb_nap, ucb_nap))
print("\nConfidence Interval No-Napping:\t({}, {})".format(lcb_no_nap, ucb_no_nap))

Inferential Satatistics Analysis/Introduction Confidence Intervals/Assesment/Assesment_confidence_intervals.py


"""
A module for generating parametrized utility and production functions.

"""

from numba import njit

def utility_function_factory(ν):
    """
    Create a CRRA utility function for consumtpion with degree of relative
    risk aversion `ν`.

    Parameters
    ----------
    ν : scalar(float)
        Degree of relative risk aversion

    Returns
    ----------
    utility_function : callable
        A JIT-compiled utility function that takes consumption `c` as an input
        and returns the corresponding CRRA utility.

    References
    ----------

    .. [1] https://en.wikipedia.org/wiki/Isoelastic_utility

    """

    if not ν > 1:
        raise ValueError('ν must be greater than 1.')

    @njit
    def utility_function(c):
        return c ** (1 - ν) / (1 - ν)

    return utility_function

def production_function_factory(A, σ_1, σ_2):
    """
    Create a Cobb-Douglas production function with capital, labor and
    intermediary good inputs and its corresponding derivatives.

    Parameters
    ----------
    A : scalar(float)
        Scale of production parameter.

    σ_1 : scalar(float)
        Capital share of production.

    σ_2 : scalar(float)
        Labor share of production.

    Returns
    ----------
    F : callable
        A JIT-compiled utility function that takes capital `K`, labor `L` and
        intermediate goods `M` as inputs and returns the corresponding output.

    F_K : callable
        The JIT-compiled derivative function of `F` with respect to `K`.

    F_L : callable
        The JIT-compiled derivative function of `F` with respect to `L`.

    F_M : callable
        The JIT-compiled derivative function of `F` with respect to `M`.

    References
    ----------

    .. [1] https://en.wikipedia.org/wiki/Cobb%E2%80%93Douglas_production_function

    """

    if not A > 0:
        raise ValueError("A must be strictly greater than 0.")

    if not (σ_1 > 0 and σ_1 < 1):
        raise ValueError('σ_1 must be in (0, 1).')

    if not (σ_2 > 0 and σ_2 < 1):
        raise ValueError('σ_2 must be in (0, 1).')

    @njit
    def F(K, L, M):
        return A * K ** σ_1 * L ** σ_2 * M ** (1 - σ_1 - σ_2)

    @njit
    def F_K(K, L, M):
        return A * σ_1 * K ** (σ_1 - 1) * L ** σ_2 * M ** (1 - σ_1 - σ_2)

    @njit
    def F_L(K, L, M):
        return A * σ_2 * K ** σ_1 * L ** (σ_2 - 1) * M ** (1 - σ_1 - σ_2)

    @njit
    def F_M(K, L, M):
        return A * (1 - σ_1 - σ_2) * K ** σ_1 * L ** σ_2 * M ** (-σ_1 - σ_2)

    return F, F_K, F_L, F_M


import math
from math import pi

import numpy as np

def calc_full_radial_coord(nx):
    """Calculates the radial coordinates for a single full spoke.

    Args:
        nx (int): Size of image along one side.
    """
    ind = math.floor(nx / 2)  # End point
    r = np.linspace(-ind, ind, num=nx, dtype=np.float32)
    return r

def calc_half_radial_coord(nx, ramp=False, device=-1):
    """Calculates the radial coordinates for a single half spoke.

    Args:
        nx (int): Size of image along one side.
        ramp (bool): Whether ramp sampling is included.
    """
    ind = math.floor(nx / 2)  # End point
    num_points = nx / 2
    r = np.linspace(0, ind, num=num_points, dtype=np.float32)
    # TODO: Implement ramping and ramp sampling
    return r

def unif_radial_3d(nx, num_theta):
    """Uniform 3D radial trajectory.

    `theta` is the polar angle (rotates away from z axis).
    `phi` is azimuthal angle (rotates in xy-plane)

    Args:
        nx (int): Size of image along one side.
        num_theta (int): Number of polar positions theta.

    Returns:
        traj (array): Trajectory. Shape (na, ns, 3).
    """
    r = calc_full_radial_coord(nx)

    # Calculate polar coordinates
    thetas, dtheta = np.linspace(0, pi, num=num_theta,
                                 endpoint=False, retstep=True)

    area = dtheta * dtheta
    # This is roughly the area that one sample should occupy
    # (divided by radius)

    traj = []

    for theta in thetas:
        if theta == 0:
            dphi = 2 * pi
        else:
            dphi = area / dtheta / np.sin(theta)

        num_phi = math.ceil(2 * pi / dphi / 2)

        phis = np.linspace(0, pi, num=num_phi, endpoint=False)
        # Only 0 to pi because full spoke (other half is sampled)

        for phi in phis:
            z = r * np.cos(theta)
            y = r * np.sin(theta) * np.sin(phi)
            x = r * np.sin(theta) * np.cos(phi)

            traj.append(np.stack((x, y, z), 1))

    traj = np.stack(traj, axis=0)
    return traj

def ga_radial_2d(nx, na):
    """Golden angle 2D radial trajectory.

    Args:
        nx (int): Size of image along one side.
        na (int): Number of spokes.

    Returns:
        traj (array): Trajectory. Shape (na, ns, 2).
    """
    tau = (np.sqrt(5) + 1) / 2
    golden_angle = 180 / tau
    theta_init = 90
    angles = np.linspace(theta_init, theta_init + golden_angle*(na - 1), na)

    r = calc_full_radial_coord(nx)

    traj = np.zeros((na, nx, 2), dtype=np.float32)
    thetas = np.expand_dims(np.radians(angles), axis=1)
    traj[:, :, 0] = np.cos(thetas) * r
    traj[:, :, 1] = np.sin(thetas) * r
    return traj


import numpy as np

def rsiFunc(prices, n=14):
    deltas = np.diff(prices)
    seed = deltas[:n+1]
    up = seed[seed>=0].sum()/n
    down = -seed[seed<0].sum()/n
    rs = up/down
    rsi = np.zeros_like(prices)
    rsi[:n] = 100. - 100./(1.+rs)

    for i in range(n, len(prices)):
        delta = deltas[i-1] # cause the diff is 1 shorter

        if delta>0:
            upval = delta
            downval = 0.
        else:
            upval = 0.
            downval = -delta

        up = (up*(n-1) + upval)/n
        down = (down*(n-1) + downval)/n

        rs = up/down
        rsi[i] = 100. - 100./(1.+rs)

    return rsi


import numba as nb

@nb.njit
def extgcd_recurse(a: int, b: int) -> tuple[int, int, int]:
    if not b:
        return a, 1, 0
    g, s, t = extgcd_recurse(b, a % b)
    return g, t, s - a // b * t

@nb.njit
def extgcd(a: int, b: int) -> tuple[int, int, int]:
    x0, x1, x2, x3 = 1, 0, 0, 1

    while b:
        q, r = divmod(a, b)
        x0, x1 = x1, x0 - x1 * q
        x2, x3 = x3, x2 - x3 * q
        a, b = b, r
    return a, x0, x2

@nb.njit
def gcd(a: int, b: int) -> int:
    while b:
        a, b = b, a % b
    return a

@nb.njit
def lcm(a: int, b: int) -> int:
    return a // gcd(a, b) * b


import numpy as np

def latlonstospace(lats,lons):
	lats = np.deg2rad(90 - lats)
	lons = np.deg2rad(lons)
	return np.array([np.sin(lats)*np.cos(lons), np.sin(lats)*np.sin(lons), np.cos(lats)]).T


from sympy import symbols, simplify, pprint

x = symbols('x')
y = symbols('y')

rosenbrock = (1 - x)**2 + 10*(y - x**2)**2
print('rosenbrock:')
pprint(rosenbrock)
print('d_rosenbrock_dx:')
pprint(simplify(rosenbrock.diff(x)))
print('d_rosenbrock_dy:')
pprint(simplify(rosenbrock.diff(y)))
print('d2_rosenbrock_dx2:')
pprint(simplify(rosenbrock.diff(x).diff(x)))
print('d2_rosenbrock_dxy:')
pprint(simplify(rosenbrock.diff(x).diff(y)))
print('d2_rosenbrock_dy2:')
pprint(simplify(rosenbrock.diff(y).diff(y)))


import numpy as np

class LinearRegressionNumpy:
    """Linear Regresion using gradient descent algorithm.
    Implemented in numpy.
    Related equations:
    # hypothesis function: y_pred = xw + b
    # Loss function: J = (1/2m) sum_i^m (h(xi)-yi)^2
    # Take derivative of the loss function w.r.t. the parameters: dw/dJ = (1/m) sum_i^m (h(xi)-yi)x
    """

    def __init__(self):
        self.w_ = None
        self.intercept_ = None
        self.coef_ = None

    def fit(self, x, y, learning_rate=0.5, n_iterations=1000):
        """Fit the training data using Gradient Descent
        Args:
            x (np.array): Training samples, shape = [n_samples, n_features]
            y (np.array): Target samples, shape = [n_samples, n_target_values]
            learning_rate (float, optional): Learning rate. Defaults to 0.5.
            n_iterations (int, optional): Number of iterations i.e., num. of epochs. Defaults to 1000.
        """

        self.cost_ = []

        # Create weight matrix including the bias term
        np.random.seed(0)
        self.w_ = np.random.rand(x.shape[1]+1, y.shape[1])*0.01

        # Add 1 to the beginning of input vectors for the bias.
        m = x.shape[0]
        x = np.hstack((np.ones((m,1)), x))

        # Shuffle the training data.
        indices = np.arange(0, m)
        np.random.shuffle(indices)
        x = x[indices]
        y = y[indices]

        # Start of training.
        for _ in range(n_iterations):

            # hypothesis function
            y_pred = np.dot(x, self.w_)

            # residuals, i.e. errors
            residuals = y_pred - y

            # Derivatives of the loss function w.r.t weights, dw/dJ:
            gradient_vector = np.dot(x.T, residuals)

            # Update weights
            self.w_ -= (learning_rate / m) * gradient_vector

            # Save loss function. Might be useful.
            cost = np.sum((residuals ** 2)) / (2 * m)
            self.cost_.append(cost)

        # Intercept and slope (coefficients).
        self.intercept_ = self.w_[0]
        self.coef_ = self.w_[1:]

    def predict(self, x):
        """Predicts for test samples.
        Args:
            x (np.array): Test samples, shape = [n_samples, n_features]

        Returns:
            np.array: Predicted value, shape = [n_samples, n_target_values]
        """

        # Add 1 vector to the inputs for bias.
        m = x.shape[0]
        x = np.hstack((np.ones((m,1)), x))

        # Return hypothesis
        return np.dot(x, self.w_)


import numpy as np

# l1 (Manhattan) norm
def manhattan(s, t):
    return np.linalg.norm(s-t, 1)

# l2 (Euclidian) norm
def euclidian(s, t):
    return np.linalg.norm(s-t, 2)

# linf norm
def linf(s, t):
    return np.linalg.norm(s-t, np.inf)


import numpy as np

g1 = np.array([1.5, 0.5])
g2 = np.array([1, 2])

G = np.array([[1.5, 1],
              [0.5, 2]])
print("斜角坐标系到笛卡尔坐标系的变换矩阵(列是斜角坐标系的两个基向量笛卡尔读数)\n", G)
metricsTensor = np.array([[g1 @ g1, g1 @ g2],
                          [g2 @ g1, g2 @ g2]])

print("度量张量的第一种求法：基向量点乘\n", metricsTensor)
metricsTensor = G.T @ G
print("度量张量的第二种求法：G的转置矩阵乘以G\n", metricsTensor)
print("G的转置\n", G.T)

Va = np.array([1, 1])
print("向量V的斜角坐标读数Va\n", Va)
Ve = G @ Va
print("已知斜角读数和转换矩阵G，求迪卡尔读数Ve,需要用G@Va, 或者Va@G.T, "
      "用Va@G是不可以的\n",
      G @ Va, Va @ G.T, Va @ G)
Vb = metricsTensor @ Va
print("向量V的对偶坐标读数Vb=metricsTensor@Va\n", Vb)
print("------------------------------\n")
print("度规张量的最原始解释: 将向量的斜角坐标转换成笛卡尔坐标再运算，"
      "(Va @ G.T) @ (G @ Va)\n",
      (Va @ G.T) @ (G @ Va))

# print("度规张量的最原始解释: 将向量的斜角坐标转换成笛卡尔坐标再运算，"
#       "(Va @ G.T) @ (G @ Va)\n",
#       np.matmul( np.matmul(Va, G.T), np.matmul(G, Va)))

print("------------------------------\n")
print("笛卡尔点乘Ve@Ve\n", Ve @ Ve)

print("对偶点乘Vb@Va Va@Vb\n", Vb @ Va, Va @ Vb)
print("用度量张量求长度\n", Va @ metricsTensor @ Va)


import numpy as np

def R(angle):
    rad_angle = (angle)*np.pi/180
    return np.array([[np.cos(rad_angle), -np.sin(rad_angle)],
                     [np.sin(rad_angle), np.cos(rad_angle)]])

if __name__ == "__main__":
    a = np.array([0, 1])
    print(np.dot(R(180), a))


from scipy.optimize import minimize

def residual(x):
    return (peak_infections(x) - 0.1) ** 2

x0 = 0.5
res = minimize(residual, x0, method="Nelder-Mead", options={'fatol':1e-04})
print(res)

## Edit

import numpy as np
from scipy.integrate import odeint
from  scipy.optimize import minimize
import pandas as pd

d = {'Week': [1, 2,3,4,5,6,7,8,9,10,11], 'incidence': [206.1705794,2813.420201,11827.9453,30497.58655,10757.66954,7071.878779,3046.752723,1314.222882,765.9763902,201.3800578,109.8982006]}
df = pd.DataFrame(data=d)

def peak_infections(beta, df):

    # Weeks for which the ODE system will be solved
    weeks = df.Week.to_numpy()

    # Total population, N.
    N = 1000
    # Initial number of infected and recovered individuals, I0 and R0.
    I0, R0 = 10, 0
    # Everyone else, S0, is susceptible to infection initially.
    S0 = N - I0 - R0
    J0 = I0
    # Contact rate, beta, and mean recovery rate, gamma, (in 1/days).
    gamma = 1/7 * 7 #rate should be in weeks now
    # A grid of time points (in days)
    t = np.linspace(0, weeks[-1], weeks[-1] + 1)

    # The SIR model differential equations.
    def deriv(y, t, N, beta, gamma):
        S, I, R, J = y
        dS = ((-beta * S * I) / N)
        dI = ((beta * S * I) / N) - (gamma * I)
        dR = (gamma * I)
        dJ = ((beta * S * I) / N)
        return dS, dI, dR, dJ

    # Initial conditions are S0, I0, R0
    # Integrate the SIR equations over the time grid, t.
    solve = odeint(deriv, (S0, I0, R0, J0), t, args=(N, beta, gamma))
    S, I, R, J = solve.T

    return I/N

def residual(x, df):

    # Total population, N.
    N = 1000
    incidence = df.incidence.to_numpy()/N
    return np.sum((peak_infections(x, df)[1:] - incidence) ** 2)

x0 = 0.5
res = minimize(residual, x0, args=(df), method="Nelder-Mead", options={'fatol':1e-04})
print(res)

8 - Minimising root function through scipy.optimize/code.py


import numpy as np
from matplotlib import pyplot as plt

def plotData(x, y, h):
    plt.subplots()
    plt.plot(x, h)
    plt.scatter(x, y, c='red')
    plt.grid(color='gray', linestyle='--', linewidth=.6, axis='both', which='both', alpha=.4)
    plt.show()

def compute_error(b, m, data):
    total_error = 0
    for i in range(0,len(data)):
        x = data[i,0]
        y = data[i,1]
        total_error += (y - (m * x + b)) ** 2
    return total_error/float(len(data))

def step_gradient(b_current, m_current, data, learning_rate):
    b_gradient = 0
    m_gradient = 0
    N = float(len(data))
    for i in range(0, len(data)):
        x = data[i,0]
        y = data[i,1]
        b_gradient += -(2/N) * (y - ((m_current * x) + b_current))
        m_gradient += -(2/N) * x * (y - ((m_current * x) + b_current))
    new_b = b_current - (learning_rate * b_gradient)
    new_m = m_current - (learning_rate * m_gradient)
    return [new_b, new_m]

def gradient_descent_runner(data, initial_b, initial_m, learning_rate,  num_iterations):
    b = initial_b
    m = initial_m
    for i in range(num_iterations):
        b, m = step_gradient(b,m, np.array(data), learning_rate)
    return [b,m]

def run():
    # hyperparaters/learning rate/alpha
    learning_rate = 0.0001
    # y = mx + b (slope formula)
    initial_b = 0 # initial y-intercept guess
    initial_m = 0 # initial slope guess
    num_iterations = 1000
    data = np.genfromtxt('../../data/test_scores.csv', delimiter=',')
    cost = compute_error(initial_b, initial_m, data)
    print("Slope : {}, Y Intercept : {}, Cost : {}".format(initial_b,initial_m, cost))
    [b, m] = gradient_descent_runner(data, initial_b, initial_m, learning_rate,  num_iterations)
    print("Slope : {}, Y Intercept : {}, Cost : {}".format(m,b, compute_error(b, m, data)))
    predictions = data[:,0] * m + b
    plotData(data[:,0], data[:,1],predictions)

run()


import numpy as np
import logging
from tqdm import tqdm
class Perceptron:
  def __init__(self,lr,epochs):
    self.weights=np.random.randn(3)*1e-4
    logging.info(f'initial weights before training{self.weights}')
    self.lr=lr
    self.epochs=epochs

  def activationFunction(self,inputs,weights):
    z=np.dot(inputs,weights)
    return np.where(z>0,1,0)
  def fit(self,X,y):
    self.X=X
    self.y=y

    X_with_bias=np.c_[self.X,-np.ones((len(self.X),1))] #bias concates with the inputs
    logging.info(f'X with bias vals:\n{X_with_bias}')

    for epoch in tqdm(range(self.epochs),total=self.epochs,desc="training the model"):
        logging.info('--'*10)
        logging.info(f'for epochs: {epoch}')
        y_hat=self.activationFunction(X_with_bias,self.weights)
        logging.info(f'predicted value after forward pass: {y_hat}')
        self.error=self.y-y_hat
        logging.info(f'error: \n {self.error}')

        self.weights=self.weights + self.lr* np.dot(X_with_bias.T,self.error)
        logging.info(f'Updated weights {self.weights} after epoch {epoch}')
        logging.info("####"*10)

  def predict(self,X):
    X_with_bias=np.c_[X,-np.ones((len(X),1))]
    return self.activationFunction(X_with_bias,self.weights)
  def total_loss(self):
    total_loss1=np.sum(self.error)
    logging.info(f'total loss: {total_loss1}')
    return total_loss1


import numpy as np

def road_length(road_points):
    dxs = road_points[1:, 0] - road_points[0:-1, 0]
    dys = road_points[1:, 1] - road_points[0:-1, 1]
    delta_ss = np.sqrt(dxs * dxs + dys * dys)
    return np.sum(delta_ss)

def normalized_distance(distance_function, road_points1, road_points2):
    length1 = road_length(road_points1)
    length2 = road_length(road_points2)
    return distance_function(road_points1, road_points2) / np.array([length1, length2]).max()

def normalized_distance_value(distance_value, road_points1, road_points2):
    length1 = road_length(road_points1)
    length2 = road_length(road_points2)
    return distance_value / np.array([length1, length2]).max()

# print(normalized_distance(simple_frechet, road_points1, road_points2))
# print(normalized_distance(relative_angle_distance, road_points1, road_points2))


import numpy as np

def fdr(pvalues, alpha=0.05):
    """
    Calculate the p-value cut-off to control for
    the false discovery rate (FDR) for multiple testing.

    If by controlling for FDR, all of n null hypotheses
    are rejected, the conservative Bonferroni bound (alpha/n)
    is returned instead.

    Arguments
    ---------
    pvalues     : array
                  (n, ), p values for n multiple tests.
    alpha       : float, optional
                  Significance level. Default is 0.05.

    Returns
    -------
                : float
                  Adjusted criterion for rejecting the null hypothesis.
                  If by controlling for FDR, all of n null hypotheses
                  are rejected, the conservative Bonferroni bound (alpha/n)
                  is returned.

    Notes
    -----

    For technical details see :cite:`Benjamini:2001` and
    :cite:`Castro:2006tz`.

    Examples
    --------
    >>> import pysal.lib
    >>> import numpy as np
    >>> np.random.seed(10)
    >>> w = pysal.lib.io.open(pysal.lib.examples.get_path("stl.gal")).read()
    >>> f = pysal.lib.io.open(pysal.lib.examples.get_path("stl_hom.txt"))
    >>> y = np.array(f.by_col['HR8893'])
    >>> from pysal.explore.esda.moran import Moran_Local
    >>> from pysal.explore.esda import fdr
    >>> lm = Moran_Local(y, w, transformation = "r", permutations = 999)
    >>> fdr(lm.p_sim, 0.1)
    0.002564102564102564
    >>> fdr(lm.p_sim, 0.05) #return the conservative Bonferroni bound
    0.000641025641025641

    """

    n = len(pvalues)
    p_sort = np.sort(pvalues)[::-1]
    index = np.arange(n, 0, -1)
    p_fdr = index * alpha / n
    search = p_sort < p_fdr
    sig_all = np.where(search)[0]
    if len(sig_all) == 0:
        return alpha/n
    else:
        return p_fdr[sig_all[0]]


import numpy as np
import matplotlib.pyplot as plt
import cvxpy as cp

P = np.array([  [1, -1/2],
                [-1/2, 2]])

q = np.array([-1, 0])

x = cp.Variable(2)

obj = cp.quad_form(x,P) + q.T @ x
u = cp.Parameter(2)
A = np.array([  [1,2],
                [1,-4],
                [1,1]])

constraints = [
    A[0] @ x <= u[0],
    A[1] @ x <= u[1],
    A[2] @ x >= -5
]

prob = cp.Problem(cp.Minimize(obj), constraints=constraints)

# (a)
u.value = np.array([-2,-3])
prob.solve()
print('status:', prob.status)
print('prob value:', prob.value)
lS = np.array([c.dual_value for c in constraints])
xS = np.array(x.value)
print('primal optimal', xS)
print('dual optimal:', lS)

# Check KKT
print('Checking KKT')
# primal feasible
assert np.array([c.value() for c in constraints]).all()
# dual feasible
assert (lS >= 0).all()
# comp. slackness
f = [c.expr.value for c in constraints]
assert np.isclose(lS @ f , 0)
# grad_x L(xS,lS) = 0
L_grad = 2*(P @ xS) + q + A.T @ lS
print('L grad', L_grad)
assert np.isclose(L_grad, 0).all()
print('KKT ok')

print('x2:', round(xS[1], 2))
print('lambda3:', round(lS[2],2))

# (b)
print('Pertubation table')

delta = [0, -0.1 ,0.1]
p_org = prob.value
dV = []
for d1 in delta:
    for d2 in delta:
        u.value = np.array([-2 + d1, -3 + d2])
        p_exact = prob.solve()
        assert prob.status == cp.OPTIMAL
        p_pred = p_org - lS.T @ [d1,d2,0]
        assert p_pred <= p_exact
        print(f'd1 {d1} d2 {d2} p_pred {p_pred} p_exact {p_exact} '
                f'exact - pred:{p_exact - p_pred}'
            )
        dV.append((p_exact - p_pred))

print('diff argsort', np.argsort(dV))


#NumberTh functions with SymPy package
#-------------------------------------

from sympy import ntheory
from sympy import sieve
from sympy import generate

sieve._reset() # An infinite list of prime numbers, implemented as a dynamically growing sieve of Eratosthenes.#When a lookup is
# requested involving an odd number that has not been sieved,the sieve is automatically extended up to that number.

print(13 in sieve)  # prints 'True' if the input is prime,

sieve.extend(40) #for input n grows the sieve to contain all primes <= n

print(sieve[10])  # returns n th prime

sieve.extend_to_no(15)  # extends upto n th prime

# mobiusrange(a,b) genarates Mobius numbers for the range [a,b), means output will be a list.
Mob_func = sieve.mobiusrange(7,12)
print('\n Mobius function outputs in the given range:',[i for i in Mob_func])

# primerange(a,b) generates all prime numbers in the range [a,b)
Prime_list = sieve.primerange(1,5)
print('\n Primes in the given range are :',[i for i in Prime_list])

#search(n) return the indices i, j of the primes that bound n. If n is prime then i == j. Although n can be an expression,
# if ceiling cannot convert it to an integer then an error will be raised.
x,y = sieve.search(25)
print('\n The given input is in between ','(',x,',',y,')','th Primes \n')

#totientrange(a, b) generates all totient numbers for the range [a, b). Simply it is a list of outputs
Totient_range = sieve.totientrange(1,10)
print(' Values of Totient(n):' ,[i for i in Totient_range])

#sympy.ntheory.generate.prime(nth)  return the n th prime number. Logarithmic integral of x is a pretty nice approximation
# for number of primes <= x, i.e. li(x) ~ pi(x)
print('\n','See this! The 1000000th prime: ', generate.prime(1000000))

#sympy.ntheory.generate.primepi(n) returns the value of the prime counting function pi(n) = the number of prime numbers
# less than or equal to n.
print('\n','There are ',generate.primepi(40),' primes < or = the given input')

#sympy.ntheory.generate.nextprime(n, ith=1) return the ith prime greater than n
print('\n','Next 5th prime after 2 is ',generate.nextprime(2, ith=5))

#sympy.ntheory.generate.prevprime(n) return the largest prime smaller than n
print('\n','Largest previous prime of given input: ', generate.prevprime(8))


from math import sqrt
from numpy import median

def distance(p0, p1):
    return sqrt((p0[0] - p1[0])**2 + (p0[1] - p1[1])**2)

def avg_distance(point, points):
    return median([distance(point, other_point) for other_point in points])


# author: Krzysztof Sopyła (krzysztofsopyla@gmail.com)
# Twitter: ksopyla
# Blog: http://ksopyla.com

# If you want to use this material in your own trainning please let me know.

import numpy as np
import matplotlib.pyplot as plt

# Compute the x and y coordinates for points on a sine curve
x = np.arange(0, 3 * np.pi, 0.1)
y = np.sin(x)

# Plot the points using matplotlib
plt.plot(x, y)
plt.show()  # You must call plt.show() to make graphics appear.

# Compute the x and y coordinates for points on sine and cosine curves
x = np.arange(0, 3 * np.pi, 0.1)
y_sin = np.sin(x)
y_cos = np.cos(x)

# Plot the points using matplotlib
plt.plot(x, y_sin)
plt.plot(x, y_cos)
plt.xlabel('x axis label')
plt.ylabel('y axis label')
plt.title('Sine and Cosine')
plt.legend(['Sine', 'Cosine'])
plt.show()

# Compute the x and y coordinates for points on sine and cosine curves
x = np.arange(0, 3 * np.pi, 0.1)
y_sin = np.sin(x)
y_cos = np.cos(x)

# Set up a subplot grid that has height 2 and width 1,
# and set the first such subplot as active.
plt.subplot(2, 1, 1)

# Make the first plot
plt.plot(x, y_sin)
plt.title('Sine')

# Set the second subplot as active, and make the second plot.
plt.subplot(2, 1, 2)
plt.plot(x, y_cos)
plt.title('Cosine')

# Show the figure.
plt.show()

#chooses 5 plots from gallery
#http://matplotlib.org/gallery.html

#scatter plots

import matplotlib.pyplot as plt
from numpy.random import rand

for color in ['red', 'green', 'blue']:
    n = 150
    x, y = rand(2, n)
    scale = 200.0 * rand(n)
    plt.scatter(x, y, c=color, s=scale, label=color,
                alpha=0.4, edgecolors='none')

plt.legend()
plt.grid(True)

plt.show()


#!/usr/bin/python

import numpy as np
from scipy import stats

'''Leave one out k nearest neighbours regression
This finds the k nearest neighbours and estimates a function
from the average of the values from those of its neighbours

We then get a residual for each sample.
This is the difference between the true function value and its estimate.

The final step is to calculate the mean square error (MSE) which is just the
mean squared length of the residuals.
MSE is always non-negative and close to 0 means a good estimation.

This can work with real valued or vector valued functions'''

def find_neighbours(number_neighbours, sample_index, dists):
    #output the indices of the nearest number_neighbours
    s = dists[sample_index]
    sort_index = np.argsort(s)
    return([x for x in sort_index[1:number_neighbours+1] if x != sample_index])

def Regression(number_neighbours, sample_index, dists, Function):
    s = dists[sample_index]
    sort_index = np.argsort(s)
    nearest_neighbours = [x for x in sort_index[1:number_neighbours+1] if x!=sample_index]
    neighbour_values = [Function[x] for x in nearest_neighbours]
    return(np.mean(neighbour_values, axis = 0))

def Mean_Square_estimator(number_neighbours, dists, Function):
    n = dists.shape[0]
#    Estimate = [Regression]
    Residuals = [(Function[i]-Regression(number_neighbours, i, dists, Function)) for i in range(n)]
    Square_errors = [np.dot(Residuals[i],Residuals[i]) for i in range(n)]
    return(np.mean(Square_errors, axis = 0))

if __name__ == '__main__':
    '''Example'''
    number_neighbours=5
    Y= np.array([(0,10,2,3),(10,0,2,2),(2,2,0,6),(3,2,6,0)])
    C1=[2,3,-1,4]
    C2=[[10,30],[0,0],[3,10],[10,20]]
    Regression(3,0,Y,C1)
    Regression(3,1,Y,C1)
    Regression(2,3,Y,C1)
    mse=Mean_Square_estimator(number_neighbours, Y,C1)
    print(mse)
    mse=Mean_Square_estimator(number_neighbours, Y,C2)
    print(mse)
    print(find_neighbours(2,0,Y))
    print(find_neighbours(2,1,Y))
    print(find_neighbours(2,2,Y))


'''
=== Power spectral density estimation ====
>The power spectrum of a time series x(t) describes the distribution of power
into frequency components composing that signal. The frequency spectrum X(f) of the
time series x(t) is calculated by Fourier Transform.

> A 'Fast Fourier Transform' (FFT) is a
relatively fast algorithm for calculating
the discrete Fourier transform (DFT)
of a continuous, discrete-sampled, time series signal.
Periodogram and Welch's methods are also spectral analysis tools commonly used.

> Welch's method improves upon the periodogram method by reducing noise in
exchange for reducing the frequency resolution.

>Here are various examples of estimating the power spectral density of a time-
domain signal.

----------
KTR 2016

'''
#===========================================================
import matplotlib.pyplot as plt
import scipy.signal as signal
import numpy as np
import math

plt.close("all")

def nextpow2(x):
    '''
    calculate the next power of 2 of x
    '''
    return (x-1).bit_length()

def time_signal(f0, amp,t):
    '''
    calculate time domain signal w\ and w\o noise
    '''
    # non noisey sigal y
    y = amp*np.sin(2 * np.pi * f0 * t) # fundamental component
    y = amp/2*np.sin(2 * np.pi * f0/2 * t) + y # subharmonic component
    # add noise to signal ynoise
    noise = np.random.normal(0,.01,len(t))
    ynoise = y + noise
    # plot signal
    fig = plt.figure(1)
    plt.subplot(2,2,1)
    plt.plot(t, y,'b-',linewidth=1.2)
    plt.plot(t, ynoise,'r--',linewidth=1.2)
    plt.xlabel('time')
    plt.ylabel('amp')
    plt.title('Time-domain signal')
    plt.grid()
    plt.show()
    return y, ynoise

def freq_spec(y,ynoise,t,Fs):
    '''
    Calculate fft/freqeuncy spectrum
    '''
    freq  = np.linspace(0,Fs,len(t)) # freq vec for non-zero pad signal
    freqn = np.linspace(0,Fs, 2**nextpow2(len(ynoise)))
    # fft of non-noisey signal
    Y = np.fft.fft(y) # fft of y (non-noisey signal)
    Y = np.abs(Y)**2 #
    Y = (Y/max(Y))# normalize and log scale
    # fft of noisey signal
    Y2 = np.fft.fft(ynoise,2**nextpow2(len(ynoise))) # fft with zero padding to length Nzero
    Y2 = np.abs(Y2)
    Y2 = Y2/max(Y2) # normalize and log scale
    # plot --------------------
    fig = plt.figure(1)
    plt.subplot(2,2,2)
    plt.plot(freq, Y, 'b-')
    plt.plot(freqn, Y2, 'r--',linewidth=1.2)
    plt.ylabel('Amplitude (norm)')
    plt.xlabel('frequency (Hz)')
    plt.title('Frequency Spectrum')
    plt.grid()
    plt.show()
    plt.xlim([0,Fs/2])

def periodogram_spec(y, ynoise, Fs):
    '''
    calculate PSD using method of periodograms
    '''
    # signal w\o noise
    f, Pper_spec = signal.periodogram(y, Fs, 'flattop', scaling='spectrum')
    Pper_spec = Pper_spec/max(Pper_spec) # normalize re max
    # signal w\ noise
    f, Pper_spec2 = signal.periodogram(ynoise, Fs, 'flattop', scaling='spectrum')
    Pper_spec2 = Pper_spec2/max(Pper_spec2) # normalize re max
    # plot ------------
    fig = plt.figure(1)
    plt.subplot(2,2,3)
    plt.semilogy(f, Pper_spec,'b-')
    plt.semilogy(f, Pper_spec2,'r-',linewidth=1.2)
    plt.xlabel('frequency [Hz]')
    plt.ylabel('PSD')
    plt.grid()
    plt.show()
    plt.title('Method of periodograms')
def Welch_spec(y, ynoise, Fs):
    '''
    calculate PSD using Welchs method
    '''
    # signal w\ noise
    f, Pwelch_spec2 = signal.welch(ynoise, Fs, scaling='spectrum')
    Pwelch_spec2 = Pwelch_spec2/max(Pwelch_spec2)
    # signal w\o noise
    f, Pwelch_spec = signal.welch(y, Fs, scaling='spectrum')
    Pwelch_spec = Pwelch_spec/max(Pwelch_spec)
    # plot
    fig = plt.figure(1)
    plt.subplot(2,2,4)
    plt.semilogy(f, Pwelch_spec2/max(Pwelch_spec2),'r-',linewidth=1.2)
    plt.semilogy(f, Pwelch_spec/max(Pwelch_spec)  ,'b-')
    plt.xlabel('frequency [Hz]')
    plt.ylabel('PSD')
    plt.title('Welchs method ')
    plt.grid()
    plt.show()

def PSDexample():
    '''
    main
    '''
    f0 = float(input('Enter center frequency (f0): '))
    amp = float(input('Enter amplitude of f0: '))
    # paramters
    Fs = 10*f0 # make sure we dont alias
    t = np.arange(0,float(10*f0/2),float(1/Fs))

    # make signal
    y, ynoise = time_signal(f0, amp,t)
    # take FFT
    freq_spec(y,ynoise,t,Fs)
    # method of periodogram
    periodogram_spec(y, ynoise, Fs)
    # welchs method
    Welch_spec(y, ynoise, Fs)

PSDexample()


import numpy as np
import seaborn as sns
import scipy.stats as stats
import matplotlib.pyplot as plt
import matplotlib.animation as animation

sns.set_theme()

class Estimate:
    """

    This class estimates using Monte Carlo Simulation in Question 1 and 2.
    It will also animate and save a GiF along with a confidence interval graph.

    Inputs:

    low, high -- Xlimit and Ylimit of the graph.
    l -- Number of points to take e.g. 1, 2, 10, 100, ....
    area_func -- Function to generate the points
    mask_func -- Function to mask the generate points to select the area in
                 which "useful" points will lie
    estimate_func -- Function to estimate the value from the masked points.

    E.g.

    Q1

    area_func - generated points in a 2x2 Square.
    mask_func - mask points where x^2 + y^2 <= 1 (radius of circle)
    estimate_func - pi = masked points / total points

    Q2

    area_func - generated points in 1x1 square.
    mask_func - mask points where for a point (x, y) f(x) is greater than equals y
    estimate_func - integral = masked points / total points

    """

    def __init__(self, low, high, l, area, mask, estimate, true_val):
        self.fig, self.ax = plt.subplots(1, 2, figsize=(6.4, 3.2))
        self.low = low
        self.high = high
        self.l = l  # points to take e.g. 100, 400 etc....
        self.time_steps = len(self.l)
        self.estimates = []  # estimates we make
        self.area_func = area  # generate the points in area
        self.mask_func = mask  # mask the area appropriately
        self.estimate_func = estimate  # func for estimating from mask
        self.true_val = true_val

    def init_func(self):
        for ax_ in self.ax.flatten():
            ax_.clear()

    def plotScatter(self, arr, ax, c="r"):
        ax.scatter(arr[:, 0], arr[:, 1], c=c)

    def __call__(self, i):
        self.init_func()

        points = self.l[i]
        estimates = []
        for _ in range(100):
            # generate 100 times to make sure we calculate the Confidence Interval
            area = self.area_func(self.low, self.high, points)
            mask = self.mask_func(area)
            masked = area[mask]
            estimate = self.estimate_func(self.high, self.low, masked, points)
            estimates.append(estimate)
        self.estimates.append(estimates)

        estimate = np.mean(estimates)

        self.ax[1].plot(self.l[: i + 1], np.mean(self.estimates, axis=1))
        self.ax[1].axhline(self.true_val, c="r")

        self.fig.suptitle(f"Points : {points} | Estimation = {estimate:.5}")
        self.plotScatter(area, self.ax[0])
        self.plotScatter(masked, self.ax[0], c="b")
        self.fig.tight_layout()

    def animate(self, name):
        ani = animation.FuncAnimation(
            fig=self.fig,
            func=self,
            init_func=self.init_func,
            interval=150,
            frames=self.time_steps,
        )
        ani.save(name, writer="ffmpeg")
        plt.close()

    def ci_graph(self, name, title=""):
        x = np.array(self.estimates)
        points = np.sqrt(self.l)
        mu = x.mean(axis=1)
        sd = np.sqrt(x.var(axis=1))
        ci = (1.645 * sd) / points  # calculate the confidence intervals
        plt.plot(self.l, mu)
        plt.title(title)
        plt.xlabel("Points")
        plt.ylabel("Estimate")
        plt.fill_between(self.l, (mu - ci), (mu + ci), alpha=0.5)
        plt.savefig(name)
        plt.close()

# Q1
# 3.14
low = -1.0
high = 1.0
l = list(range(1, 3001, 10))

def area(low, high, points):
    return np.random.default_rng().uniform(low, high, (points, 2))

def mask(area):
    return np.sum(area ** 2, axis=1) <= 1

def estimate(high, low, masked, points):
    return ((high - low) ** 2) * (masked.shape[0] / points)

sim = Estimate(low, high, l, area, mask, estimate, 3.14159)

sim.animate("1.mp4")
sim.ci_graph("1_ci.png", "Monte Carlo Estimates")

# --------------------------------------------------------------
# Q2
# 0.4815990593768372
low = 0.0
high = 1.0
l = list(range(1, 2001, 10))

def area(low, high, points):
    return np.random.default_rng().uniform(low, high, (points, 2))

def mask(area):
    f = (
        lambda x: (
            (-65536 * (x ** 8))
            + (262144 * (x ** 7))
            - (409600 * (x ** 6))
            + (311296 * (x ** 5))
            - (114688 * (x ** 4))
            + (16384 * (x ** 3))
        )
        / 27
    )
    val = f(area[:, 0])
    out = np.less_equal(val, area[:, 1])
    return (val >= 0) & ~out  # mask True where --- f(x) >= y and x >= 0

def estimate(high, low, masked, points):
    return ((high - low) ** 2) * (masked.shape[0] / points)

sim = Estimate(low, high, l, area, mask, estimate, 0.48159)
sim.animate("2.mp4")
sim.ci_graph("2_ci.png", "Monte Carlo Estimates")

# References

# https://stackoverflow.com/questions/63453435/animate-scatter-plot-with-colorbar-using-matplotlib


from scipy.special import roots_jacobi, jacobi, gamma
import numpy as np
import torch

# GAUSS-LOBATTO-JACOBI QUADRATURE RULE
##################################################

def jacobi_evaluation(n, alpha, beta, x):
    '''Recursive generation of the Jacobi polynomial of order n.

    Args:
        n (int): Order of the Jacobi Polynomial, :math:`\ge 0`.
        alpha (float): Alpha parameter of the Jacobi polynomial, :math:`> -1`.
        beta (float): Beta parameter of the Jacobi polynomial, :math:`> -1`.
        x (np.ndarray): Array to evaluate.

    Returns:
        y (np.ndarray): A numpy array of the Jacobi polynomial evaluated at x.

    Example:
        >>> n, alpha, beta = 1, 0, 0
        >>> x = np.linspace(-1., 1., 5)
        >>> y = jacobi_evaluation(n, alpha, beta, x)
        >>> print(y)
        [-1.  -0.5  0.   0.5  1. ]

    '''
    x = np.array(x)
    return jacobi(n, alpha, beta)(x)

def gauss_lobatto_jacobi_weights(q, alpha=0, beta=0):
    '''Returns the roots and weights of the Gauss-Lobatto-Jacobi quadrature rule.

    Args:
        q (int): Number of roots for the quadrature rule, :math:`\ge 1`.
        alpha (float, optional): Alpha parameter of the Jacobi polynomial, :math:`> -1`. Defaults to 0.
        beta (float, optional): Beta parameter of the Jacobi polynomial, :math:`> -1`. Defaults to 0.

    Returns:
        roots (np.ndarray): roots of the Jacobi polynomial.
        weights (np.ndarray): weights of the quadrature rule.

    Example:

        >>> roots, weights = gauss_lobatto_jacobi_weights(5, 0, 0)
        >>> print(roots, weights)
        [-1.         -0.65465367  0.          0.65465367  1.        ] [0.1        0.54444444 0.71111111 0.54444444 0.1       ]

    '''
    w = []
    x = roots_jacobi(q - 2, alpha + 1, beta + 1)[0]
    if alpha == 0 and beta == 0:
        w = 2 / ((q - 1) * (q) * (jacobi_evaluation(q - 1, 0, 0, x) ** 2))
        wl = 2 / ((q - 1) * (q) * (jacobi_evaluation(q - 1, 0, 0, -1) ** 2))
        wr = 2 / ((q - 1) * (q) * (jacobi_evaluation(q - 1, 0, 0, 1) ** 2))
    else:
        w = 2 ** (alpha + beta + 1) * gamma(alpha + q) * gamma(beta + q) / ((q - 1) * gamma(q)
                                                                            * gamma(alpha + beta + q + 1) * (
                                                                                jacobi_evaluation(q - 1, alpha, beta,
                                                                                x) ** 2))
        wl = (beta + 1) * 2 ** (alpha + beta + 1) * gamma(alpha + q) * gamma(beta + q) / ((q - 1)
                                                                                * gamma(q) * gamma(
                    alpha + beta + q + 1) * (jacobi_evaluation(q - 1, alpha, beta, -1) ** 2))
        wr = (alpha + 1) * 2 ** (alpha + beta + 1) * gamma(alpha + q) * gamma(beta + q) / ((q - 1)
                                                                                * gamma(q) * gamma(
                    alpha + beta + q + 1) * (jacobi_evaluation(q - 1, alpha, beta, 1) ** 2))
    w = np.append(w, wr)
    w = np.append(wl, w)
    x = np.append(x, 1)
    x = np.append(-1, x)
    return [x, w]

def gauss_lobatto_jacobi_quadrature1D(num_points, a, b, alpha=0, beta=0):
    """Gives the 1D Gauss-Lobatto-Jacobi quadrature rule.

    Returns the nodes and weights :math:`\{x_i,w_i\}_{i=1}^Q`, such that:

    .. math::
        \int_a^b f(x) dx \\approx \sum_{i=1}^Q w_i f(x_i)

    The nodes and weights are given by the Gauss-Lobatto-Jacobi quadrature rule.

    Args:
        num_points (int): Number of nodes of the quadrature rule.
        a (float, torch.Tensor): Start of the integration interval.
        b (float, torch.Tensor): End of the integration interval.
        alpha (float, optional): Alpha parameter of the Jacobi polynomial, :math:`> -1`. Defaults to 0.
        beta (float, optional): Beta parameter of the Jacobi polynomial, :math:`> -1`. Defaults to 0.

    Returns:
        roots (torch.Tensor: roots of the Jacobi polynomial.
        weights (torch.Tensor): weights of the quadrature rule.

    Example:

        >>> roots, weights = gauss_lobatto_jacobi_quadrature1D(5, -1, 2)
        >>> print(roots, weights)
        [-1.         -0.48198051  0.5         1.48198051  2.        ] [0.1        0.54444444 0.71111111 0.54444444 0.1       ]

    """

    roots, weights = gauss_lobatto_jacobi_weights(num_points, alpha, beta)
    X = (b - a) / 2 * (roots + 1) + a

    if type(X).__name__ != 'Tensor':
        X = torch.tensor(X)

    return X, torch.tensor(weights)

def gauss_lobatto_jacobi_quadrature2D(num_points_per_dim, a, b, c, d,
                                      alpha_x=0, beta_x=0, alpha_y=0, beta_y=0):
    """Gives the 1D Gauss-Lobatto-Jacobi quadrature rule.

    Returns the nodes and weights :math:`\{x_i, w_i\}_{i=1}^Q`,
    and :math:`\{y_i, \\tilde{w}_i\}_{i=1}^Q`, such that:

    .. math::
        \int_a^b \int_c^d f(x,y) dxdy \\approx \sum_{i,j=1}^Q w_i  \\tilde{w}_j f(x_i, y_j)

    The nodes and weights are given by the Gauss-Lobatto-Jacobi quadrature rule.

    Args:
        num_points_per_dim (int): Number of nodes of the quadrature rule (per dimension).
        a (float): Start of the first integration interval.
        b (float): End of the first integration interval.
        c (float): Start of the second integration interval.
        d (float): End of the second integration interval.
        alpha_x (float, optional): Alpha parameter of the first Jacobi polynomial, :math:`> -1`. Defaults to 0.
        beta_x (float, optional): Beta parameter of the first Jacobi polynomial, :math:`> -1`. Defaults to 0.
        alpha_y (float, optional): Alpha parameter of the second Jacobi polynomial, :math:`> -1`. Defaults to 0.
        beta_y (float, optional): Beta parameter of the second Jacobi polynomial, :math:`> -1`. Defaults to 0.

    Returns:
        X (np.ndarray): roots of the Jacobi polynomial for the first dimension.
        Y (np.ndarray): roots of the Jacobi polynomial for the second dimension.
        Weights_X (np.ndarray): weights of the quadrature rule for the first dimension.
        Weights_y (np.ndarray): weights of the quadrature rule for the second dimension.

    Example:

        >>> X, Y, weights_X, weights_Y = gauss_lobatto_jacobi_quadrature2D(3, -1, 2, 0, 1)
        >>> print(X, "\\n", Y, "\\n", weights_X, "\\n", weights_Y)
        [[-1.   0.5  2. ]
         [-1.   0.5  2. ]
         [-1.   0.5  2. ]]
         [[0.  0.  0. ]
         [0.5 0.5 0.5]
         [1.  1.  1. ]]
         [[0.33333333 1.33333333 0.33333333]
         [0.33333333 1.33333333 0.33333333]
         [0.33333333 1.33333333 0.33333333]]
         [[0.33333333 0.33333333 0.33333333]
         [1.33333333 1.33333333 1.33333333]
         [0.33333333 0.33333333 0.33333333]]

    """

    roots_X, weights_X = gauss_lobatto_jacobi_quadrature1D(
        num_points_per_dim, a, b, alpha_x, beta_x)
    roots_Y, weights_Y = gauss_lobatto_jacobi_quadrature1D(
        num_points_per_dim, c, d, alpha_y, beta_y)

    X, Y = np.meshgrid(roots_X, roots_Y)
    weights_X, weights_Y = np.meshgrid(weights_X, weights_Y)

    return X, Y, weights_X, weights_Y

# INTEGRATION ALGORITHMS
##################################################

def integrate_1d(f, a, b, weights, X=None):
    '''Integrates a function :math:`f(x)` over :math:`[a, b]` using the given roots and weights.

    Note that if X is None, then f must be a np.ndarray or torch.Tensor containing the values
    of the function at the nodes.

    Args:
        f (Iterable or function handle): if f is an array of the same size as weights,
                                         the integral is directly computed using summation.
                                         Otherwise f is evaluated on the roots before hand.
        a (float): start of the interval.
        b (float): end of the interval.
        weights (Iterable): weights of the quadrature.
        X (Iterable, optional): where the function needs to be evaluated (as accorded to the quadrature rule).  Defaults to None.

    Returns:
        integral (float) Value of the integral.

    Example:
        >>> a, b = -1, 2
        >>> roots, weights = gauss_lobatto_jacobi_quadrature1D(5, a, b)
        >>> f = lambda x: x**2
        >>> integral = integrate_1d(f, a, b, weights, roots)
        >>> print(integral)
        3.0

    '''

    if weights is None:
        raise ValueError("No quadrature weights provided (weights = None)")

    if type(weights).__name__ != "ndarray" and type(weights).__name__ != "Tensor":
        weights = torch.Tensor(weights)

    if X is not None and type(X).__name__ != "ndarray" and type(X).__name__ != "Tensor":
        X = torch.Tensor(X)

    if type(f).__name__ == 'function':
        if X is None:
            raise ValueError(
                "f is a function handle but no values were provided for evaluation (roots = None)")
        values = f(X)
    elif type(f).__name__ != "ndarray" and type(f).__name__ != "Tensor":
        values = torch.Tensor(f)
    else:
        values = f

    if type(values).__name__ == 'Tensor':
        integral = (b - a) * torch.sum(values * weights) / 2
    else:
        integral = (b - a) * np.sum(values * weights) / 2

    return integral

def integrate_2d(f, a, b, c, d, weights_X,
                 weights_Y, X=None, Y=None):
    '''
    Integrates a function :math:`f(x,y)` over :math:`[a, b] \\times [c,d]` using the given roots and weights.

    Args:
        f (Iterable or function handle): if f is an array of the same size as weights,
                                         the integral is directly computed using summation.
                                         Otherwise, f is evaluated on the roots beforehand.
        a (float): start of the interval on x.
        b (float): end of the interval on x.
        c (float): start of the interval on y.
        d (float): end of the interval on y.
        weights_X (Iterable): weights for the quadrature.
        weights_Y (Iterable): weights for the quadrature.
        X (Iterable, optional): where the function needs to be evaluated (as accorded to the quadrature rule).  Defaults to None.
        Y (Iterable, optional): where the function needs to be evaluated (as accorded to the quadrature rule). Defaults to None.

    Example:
        >>> a, b, c, d = -1, 2, 0, 1
        >>> X, Y, weights_X, weights_Y = gauss_lobatto_jacobi_quadrature2D(3, a, b, c, d)
        >>> f = lambda x, y: x**2 * y**2
        >>> integral = integrate_2d(f, a, b, c, d, weights_X, weights_Y, X, Y)
        >>> print(integral)
        1.0

    '''

    if weights_X is None or weights_Y is None:
        raise ValueError("No quadrature weights provided (weights = None)")

    # First we convert everything to numpy arrays:
    if X is not None and type(X).__name__ != "ndarray" and type(X).__name__ != "Tensor":
        X = torch.Tensor(X)
    if Y is not None and type(Y).__name__ != "ndarray" and type(Y).__name != "Tensor":
        Y = torch.Tensor(Y)
    if type(weights_X).__name__ != "ndarray" and type(weights_X).__name__ != "Tensor":
        weights_X = torch.Tensor(weights_X)
    if type(weights_Y).__name__ != "ndarray" and type(weights_Y).__name__ != "Tensor":
        weights_Y = torch.Tensor(weights_Y)

    if type(f).__name__ == 'function':
        if X is None or Y is None:
            raise ValueError(
                "f is a function handle but no values were provided for evaluation (roots = None)")
        values = f(X, Y)
    elif type(f).__name__ != "ndarray" and type(f).__name__ != "Tensor":
        values = torch.Tensor(f)
    else:
        values = f

    if type(values).__name__ == 'Tensor':
        integral = (b - a) * (d - c) * \
                   torch.sum(values * weights_X * weights_Y) / 4
    else:
        integral = (b - a) * (d - c) * np.sum(values * weights_X * weights_Y) / 4

    return integral

if __name__ == "__main__":
    import doctest
    doctest.testmod()


# -*- coding: utf-8 -*-
"""
Created on Mon Sep 10 14:50:53 2018

@author: Thanh Tung Khuat

Helper function for matrix handling
"""

from sklearn.decomposition import PCA
import numpy as np

def pca_transform(A, n):
    """
    Perform Principal Component Analysis for the input maxtrix to reduce the current dimensions into
    new dimensions, n

    INPUT:
        A   Input maxtrix
        n   new dimensions

    OUTPUT:
        New n-dimensional matrix
    """
    pca = PCA(n_components = n)
    principalComponents = pca.fit_transform(A)

    return principalComponents

def delete_const_dims(Xl, Xu = None):
    """
    delete any constant dimensions (the same value for all samples) within both lower and upper bounds

          Xl, Xu = delete_const_dims(Xl, Xu)

    INPUT:
        Xl      Lower bounds matrix (rows: objects, cols: features)
        Xu      Upper bounds matrix (rows: objects, cols: features)

    OUTPUT:
        Xl, Xu after deleting all constant dimensions

    If input is only one matrix, the operation is performed for that matrix
    """
    numDims = Xl.shape[1]
    colMask = np.array([True] * numDims)

    for i in range(numDims):
        if (Xl[:, i] == Xl[0, i]).all() == True and ((Xu is not None and (Xu[:, i] == Xu[0, i]).all() == True) or (Xu is None)):
            colMask[i] = False

    Xl_n = Xl[:, colMask]

    if Xu is not None:
        Xu_n = Xu[:, colMask]

        return (Xl_n, Xu_n)
    else:
        return Xl_n


#!/usr/bin/env python

import numpy as np

p = np.pi

def DH_to_T(DH):
  """!
  Computes the transformation matrices given the DH table of the serial link.
  @param DH: devavitt-hartemberg parameters.
  @return T: transformation matrices of a joint with respect to previous joint.
  """
  # Get the number of rows, to know how many T matrices should create.
  rows = len(DH)

  T = []

  for i in range(rows):
    Tmp = np.array([[np.cos(DH[i,3]), -np.sin(DH[i,3]), 0, DH[i,1]],
            [np.sin(DH[i,3])*np.cos(DH[i,0]), np.cos(DH[i,3])*np.cos(DH[i,0]), -np.sin(DH[i,0]), -DH[i,2]*np.sin(DH[i,0])],
            [np.sin(DH[i,3])*np.sin(DH[i,0]), np.cos(DH[i,3])*np.sin(DH[i,0]), np.cos(DH[i,0]), DH[i,2]*np.cos(DH[i,0])],
            [0, 0, 0, 1]])
    T.append(Tmp)

  return T

def transformations(T_rel_ini, q, info):
  """!
  Computes tranformations given T_relatives, q's and the info.
  @param T_rel_ini: the ones computed with DH_to_T.
  @param q: current configuration of baxter's arm.
  @param info: 1->revolute, 0->prismatic.
  @return T: transformation matrices of a joint with respect to previous joint in
  the new configuration.
  """
  row_q = q.size
  row_info = info.size

  T = []

  if row_q != row_info:
    print("Warning. q and info must have same size.")
    return

  for i in range(row_q):
    if info[i] == 1:
      Tel = np.array([[np.cos(q[i]), -np.sin(q[i]), 0 , 0],
                      [np.sin(q[i]), np.cos(q[i]), 0 , 0],
                      [0, 0, 1, 0],
                      [0, 0, 0, 1]])
  #  else:
      # Case in which there are prismatic joints.
##      Tel = np.array([[1, 0, 0, 0],
##                      [0, 1, 0, 0],
##                      [0, 0, 0, q[i]]
##                      [0, 0, 0, 1]])

    Tmp = np.dot(T_rel_ini[i], Tel)
    T.append(Tmp)

  # Last matrix is constant in time. T_7,e.e
  T.append(T_rel_ini[row_q])

  return T

def abs_trans(T_rel):
  """!
  Computes trasformations matrices w.r.t. 0 frame.
  @param T_rel: trasformation matrices of a joint with respect to previous one.
  @return T: absolute transformation matrices.
  """
  T = []
  # First is the same.
  T.append(T_rel[0])

  for i in range(1, len(T_rel)):
    Tmp = np.dot(T[i-1], T_rel[i])
    T.append(Tmp)

  return T


import numpy as np

def rms(e):
    ''' Calculate root mean square of a vector of data. '''
    return np.sqrt(np.mean(np.square(e)))

def bound_array(a, lb, ub):
    ''' Elementwise bound array above and below. '''
    return np.minimum(np.maximum(a, lb), ub)

def right_pseudoinverse(J):
    JJT = J.dot(J.T)
    return J.T.dot(np.linalg.inv(JJT))

def rotation_matrix(θ):
    ''' 2D rotation matrix: rotates points counter-clockwise. '''
    return np.array([[np.cos(θ), -np.sin(θ)],
                     [np.sin(θ),  np.cos(θ)]])

def rotation_jacobian(θ):
    ''' Derivative of rotation matrix (above) w.r.t θ. '''
    return np.array([[-np.sin(θ), -np.cos(θ)],
                     [ np.cos(θ), -np.sin(θ)]])

def dist_to_line_segment(p, p1, p2):
    ''' Calculate distance and closest point of p to the line segment with end
        points p1 and p2. '''
    v = (p2 - p1)
    length2 = np.dot(v, v)
    P = (np.eye(2) - np.outer(v, v) / length2)
    a = p1 - p

    # closest point
    pc = np.dot(P, a) + p

    # squared distance between closest point and end points of the line
    d1_2 = np.dot(p1 - pc, p1 - pc)
    d2_2 = np.dot(p2 - pc, p2 - pc)

    # check if closest point falls outside the line segment
    if max(d1_2, d2_2) > length2:
        if d1_2 >= d2_2:
            pc = p2
        else:
            pc = p1

    d2 = np.dot(p - pc, p - pc)
    return pc, d2


# coding: utf-8

# In[33]:

get_ipython().run_cell_magic('javascript', '', '\nIPython.OutputArea.prototype._should_scroll = function(lines) {\n    return false;\n}')

# In[34]:

get_ipython().run_line_magic('config', "InlineBackend.figure_format = 'retina'")

from __future__ import division
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# ## Sample dataset
# We will sample the dataset using normal distribution µ =1 and σ = 0.05.

# y is generated using 1.3x2 + 4.8x + 8 + e

# In[35]:

"""Sample features for the dataset"""
x = np.random.uniform(0.913397, 1.08660, size = (100, 1))

"""Generate target. Here target is given by 1.3x^2 + 4.8x + 8 + e"""
x = sorted(x)
func = np.vectorize(lambda x: (1.3 * (x * x)) + (4.8 * x) + 8)
y = func(x)
e = np.random.rand(100, 1)
y = y + e

# In[36]:

from sklearn.model_selection import train_test_split

xTrain, xTest, yTrain, yTest = train_test_split(x, y,
                                                test_size=0.2)

print("Train set size " +str(len(xTrain)))
print("Test set size " +str(len(xTest)))

# In[37]:

from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import PolynomialFeatures
from sklearn.metrics import mean_squared_error
trainingPipeline = Pipeline([
    ("polynomialFeatures", PolynomialFeatures()),
    ("featureScaling", StandardScaler()),
    ("regression", LinearRegression())
])

pipelineParameters = {"polynomialFeatures__include_bias":False}

# In[38]:

degrees = [1, 2, 7, 10, 16, 100]
fig, axs = plt.subplots(6, 2, figsize=(20, 40))
axs = axs.flatten()
index = 0

mseNoReg = []
fig.suptitle("Actual vs prediction", fontsize = 20)

for degree in degrees:
    pipelineParameters['polynomialFeatures__degree'] = degree
    trainingPipeline.set_params(** pipelineParameters)

    trainingPipeline.fit(x, y)
    yPrediction = trainingPipeline.predict(x)

    #axs[index].plot(yPrediction,y)
    #axs[index].plot(yPrediction, "ro", label = "Prediction")
    #axs[index].plot(y, "gx", label = "Actual")
    #axs[index].legend()

    for i in range(len(yPrediction)):
        axs[index].plot([i,i],[y[i], yPrediction[i]], c = "k", linewidth=0.5)

    axs[index].plot(yPrediction, 'o', label='Prediction', color = 'g')
    axs[index].plot(y,'^' , label='Ground Truth', color = 'r' )
    axs[index].legend(prop={'size': 15})

    axs[index + 1].plot(x, yPrediction, label = "Polynomial curve with degree "+str(degree))
    axs[index + 1].plot(x, y, "go", label = "Ground truth")
    axs[index+1].legend(prop={'size': 15})
    index = index + 2
    mseNoReg.append(mean_squared_error(y, yPrediction))
plt.show()
plt.close()

fig, axs = plt.subplots(1, 1, figsize=(25, 10))

axs.plot(mseNoReg)
index = 0
for data, label in zip(mseNoReg, degrees):
    axs.plot(index, data, "o", label = "Degree-" + str(label))
    index = index + 1
axs.legend(prop={'size': 15})
plt.show()

# As we make model more complex the error is decreasing as it better fits the training set. It is evident from the above graph. However it is not a good practice to train more complex model as it may overfit. To get a best model that performs good on train and test set, regularization should be used. Furthermore we can see as the degree of the polynomial increases the graph becomes complex.

# In[39]:

from sklearn.linear_model import Ridge

trainingPipeline = Pipeline([
    ("polynomialFeatures", PolynomialFeatures()),
    ("featureScaling", StandardScaler()),
    ("regression", Ridge())
])

pipelineParameters = {"polynomialFeatures__include_bias":False}

# In[40]:

degree = 10
regParameters = [0, 1e-10, 1e-8, 1e-6, 1e-4, 1]

fig, axs = plt.subplots(6, 2, figsize=(20, 40))
axs = axs.flatten()
index = 0

mseReg = []
fig.suptitle("Actual vs prediction", fontsize = 20)

for regParameter in regParameters:
    pipelineParameters['polynomialFeatures__degree'] = degree
    pipelineParameters['regression__alpha'] = regParameter

    trainingPipeline.set_params(** pipelineParameters)

    trainingPipeline.fit(x, y)
    yPrediction = trainingPipeline.predict(x)

    for i in range(len(yPrediction)):
        axs[index].plot([i,i],[y[i], yPrediction[i]], c = "k", linewidth=0.5)

    axs[index].plot(yPrediction, 'o', label='Prediction', color = 'g')
    axs[index].plot(y,'^' , label='Ground Truth', color = 'r' )
    axs[index].legend(prop={'size': 15})

    axs[index + 1].plot(x, yPrediction,
                        label = "Ridge regression with regulizarationParameter "+str(regParameter))

    axs[index + 1].plot(x, y, "go",
                        label = "ground truth")

    axs[index + 1].legend(prop={'size': 15})
    index = index + 2
    mseReg.append(mean_squared_error(y, yPrediction))
plt.show()
plt.close()

fig, axs = plt.subplots(1, 1, figsize=(25, 10))

axs.plot(mseReg)
index = 0
for data, label in zip(mseReg, regParameters):
    axs.plot(index, data, "o", label = "Regulizaration Parameter-" + str(label))
    index = index + 1
axs.legend(prop={'size': 15})
plt.show()

# We can see that the model performs poorly as the regularization parameter value is increased. This is expected behaviour. The idea behind regularization is that we want to avoid overfitting on training set so that the model can perform good on test set. Furthermore we can see that the curve simplifies as the regularization parameter value increases.