import numpy as np
from autodiff import jacobian, sin
def linfit(x, y, yerr=None):
    """
    Solves the linear least squares. That is solves the linear system

    A*p=y where A = [x 1]

    Using the QR-decomposition A=QR, the optimal b can be estimated as
    p = R^(-1)*Q^T*y
    """

    N = len(x)
    #If no errors specified, just set them all to 1
    if yerr is None:
        yerr = np.ones((N,1), dtype=np.float64)
    yerr = yerr.reshape(N,1)

    x = x.reshape(N,1)
    #W = np.matrix(np.diag(1/(yerr.flatten())))

    A = np.matrix(np.hstack((x,np.ones((N,1)))))
    #We want to calculate W*A, but W is just a diagonal matrix. If we just elementwise multiply the diagonal and A, this will be much faster
    #Q, R = np.linalg.qr(W*A)
    Q, R = np.linalg.qr(np.multiply(yerr,A))

    #Create these as matricies to make the multiplication prettier
    Q, R, y = np.matrix(Q), np.matrix(R), y.reshape(N,1)

    #Again, W*y is horribly inefficient. Just multiply yerr and y elementwise. Since they both are ndarrays, we can just * them to do this
    #p = R**(-1)*Q.T*W*y
    p = (R**(-1)*Q.T)*(yerr*y)

    chi2 = float(sum(np.power((y-x*p[0]+p[1]),2)/(yerr**2)))
    dof = N-1
    chi2_red = chi2/dof

    return {'p':p, 'chi2_red':chi2_red, 'f': lambda x: f(x, p)}

def nonlinfit(f, x, y, p, yerr=None, tol=1e-11, maxiter=50):
    """
    Finds the best parameters using least squares

    If J is the jacobian, W the weight, and Q*R=WJ the qr-decomposition, then the update vector p' is
    p' = R^-1 * Q^T* (y-f(x,p)).

    This is then used to update the estimate for the parameter p_i+1 = p_i + p'
    """

    if isinstance(f, str):
        f = eval('lambda x,p: '+ f)

    N = len(x)
    num_param = len(p)

    #Convert all input to numpy arrays of specific type and shape
    y = np.asarray(y, dtype=np.float64).reshape(N, 1)
    x = np.asarray(x, dtype=np.float64).reshape(N, 1)
    p = np.asarray(p, dtype=np.float64).reshape(num_param,1)

    #If no errors specified, just set them all to 1
    if yerr is None:
        yerr = np.ones((N,1), dtype=np.float64)
    yerr = yerr.reshape(N,1)

    chi2 = sum(np.power((y-f(x,p)),2)/(yerr**2))
    dof = N-num_param
    chi2_red = chi2/dof

    for _ in range(maxiter):
        delta_y = y-f(x,p)
        J = jacobian(f, x, p)

        #We want to calculate W*J, but W is just a diagonal matrix. If we just elementwise multiply the diagonal and J, this will be much faster
        #Q, R = np.linalg.qr(W*J)
        Q, R = np.linalg.qr(np.multiply(yerr,J))

        #Create these as matricies to make multiplication prettier
        Q, R, J = np.matrix(Q), np.matrix(R), np.matrix(J)

        #Again, W*delta_y is horribly inefficient. Just multiply yerr and delta_y elementwise. Since they both are ndarrays, we can just * them to do this
        #p = p + R**(-1)*Q.T*W*delta_y
        p = p + (R**(-1)*Q.T) * (yerr*delta_y)

        #p need to be in the right format
        p=np.asarray(p)

        #Check for convergence
        chi2_tmp = float(sum(np.power((y-f(x,p)),2)/(yerr**2)))
        chi2_red_tmp = chi2_tmp/(N-num_param)
        diff = abs(chi2_red-chi2_red_tmp)
        print("reduced chi2 = %0.4E   diff = %0.4E"%(chi2_red_tmp, diff))
        if diff<tol:
            print("Converged!")
            return {'p':p, 'chi2_red':chi2_red_tmp, 'f': lambda x: f(x, p)}

        #Update chi2 value, and print progress
        chi2_red = chi2_red_tmp

    print("Hit max number of iterations without converging!!")
    return {'p':p, 'chi2_red':chi2_red_tmp, 'f': lambda x: f(x, p)}

if __name__ == "__main__":
    def f(x,p):
        return p[0]*(x**2) + p[1]*x+p[2]*sin(x)

    x = np.arange(0,100,1/10)
    y = f(x,[3,5,1000]) + np.random.normal(0,1,len(x))

    pp=nonlinfit(f,x,y,[2,7,1000])


import numpy as np
import pandas as pd
from scipy.optimize import root
from scipy.integrate import odeint
from __future__ import division
from scipy import *
from pylab import *
import matplotlib.pyplot as plt
#%matplotlib inline

xo = 0.5
gamma = 0.8
yr = 0.1 #kg kgCO2 −1
TAO = 15

# en la matriz parametros se define cada conbinación (xk, A) como una fila de la matriz
parametros = np.array([[0.1, 1],[0.3, 2],[0.4, 4]])
parametros

def intervalosExtraccion(tao, xk, A):
    tao1 = (xo - xk) / (gamma * A * yr)
    tao2 = tao1 + xk / (gamma * A * yr) * np.log(xk / xo + (1 - xk / xo) * np.exp(xo / xk * A))
    zk = xk / (A * xo) * np.log((xo * np.exp(gamma * A * yr / xk * (tao - tao1)) - xk) / (xo - xk))

    return tao1, tao2, zk

def modeloLack(tao, xk, A):

    tao1, tao2, zk = intervalosExtraccion(tao, xk, A)

    if tao <= tao1 and tao < tao2:
        e = gamma * yr* tao * (1- np.exp(- A))
        print("tao < tao1 and tao < tao2")
        return e
    if tao > tao1 and tao <= tao2:
        zk = xk / (A * xo) * np.log((xo * np.exp(gamma * A * yr / xk * (tao - tao1)) - xk) / (xo - xk))
        e = gamma * yr * (tao - tao1 * np.exp(- A * (1 - zk)))
        print("tao >= tao1 and tao < tao2")
        return e
    if tao > tao2:
        e = xo - xk / A * np.log(1 + xk / xo * (np.exp(xo / xk * A) - 1) * np.exp(gamma * A * yr / xk * (tao1 - tao)))
        print("tao >= tao2")
        return e

rendimiento = [[modeloLack(tao, xk, A) for tao in np.linspace(0,TAO)] for xk, A in parametros]

def graficarLack():

	Tao = np.linspace(0,TAO)
	# plt.plot(Tao, caso1[2],label="xk=0.3")
	# plt.plot(Tao,caso2[2],label="xk=0.1")
	# plt.plot(Tao,caso3[2],label="xk=0.4")

	plt.plot(Tao, rendimiento[0],label="xk=0.3")
	plt.plot(Tao, rendimiento[1],label="xk=0.1")
	plt.plot(Tao, rendimiento[2],label="xk=0.4")
	plt.title("Modelo Lack")
	plt.xlabel(" $tao $ ")
	plt.ylabel("Rendimiento e")
	plt.legend()

	return 0


# -*- coding: UTF-8 -*-
import os
import numpy as np
def zeroMean(dataMat):
	meanVal = np.mean(dataMat, axis = 0)
	newData = dataMat - meanVal
	return newData,meanVal

def eigValPct(eigVals,percentage):
    sortArray = np.sort(eigVals)
    sortArray = sortArray[-1::-1]
    arraySum = np.sum(sortArray)
    tmpSum = 0
    num = 0
    for i in sortArray:
        tmpSum += i
        num += 1
        if tmpSum >= arraySum * percentage:
            return num

def pca(dataMat,percentage=0.95):
	meanRemoved, meanVals = zeroMean(dataMat)
	covMat = np.cov(meanRemoved,rowvar=0)
	eigVals, eigVects = np.linalg.eig(np.mat(covMat))
	k = eigValPct(eigVals,percentage)
	eigValInd = np.argsort(eigVals)
	eigValInd = eigValInd[-1:-(k+1):-1]
	redEigVects = eigVects[:,eigValInd]
	lowDDataMat = meanRemoved * redEigVects
	reconMat = (lowDDataMat * redEigVects.T) + meanVals
	return lowDDataMat, reconMat

def readData(filename):
	with open(filename, 'r') as f:
		f.readline()
		stringArr = [line.strip().split('\t')[1:] for line in f.readlines()]
		dataArr = [map(float, line) for line in stringArr]
		return np.mat(dataArr).T
def format(value):
    return "%.5f" % value

def writeData(filename, feature):
	feature = np.array(feature)
	formatted = [[format(v) for v in r] for r in feature]
	with open(filename, 'w') as f:
		for i in range(feature.shape[0]):
			for j in range(feature.shape[1]):
				f.write(formatted[i][j])
				f.write('\t')
			f.write('\n')

def check(filename):
	with open(filename, 'r') as f:
		f.readline()
		types = [line.strip().split('\t')[1] for line in f.readlines()]
	classType = list()
	for type in types:
		if type not in classType:
			classType.append(type)
	return classType

if __name__ == '__main__':
	filename = '../data/microarray.original.txt'
	#filename = '../data/finalData.txt'
	#filename = '../data/processedData_0.95.txt'
	labelfile = '../data/E-TABM-185.sdrf.txt'
	classType = check(labelfile)
	print "The number of class is {}".format(len(classType))
	print classType
	feature = readData(filename)
	print "Finish reading the data. The number is {}. The dimensions of feature are {}".format(feature.shape[0], feature.shape[1])
	filename = '../data/processedData_0.85.txt'
	new_feature, _ = pca(feature, 0.85)
	print "Feature is reduced to {} dimensions.".format(new_feature.shape[1])
	writeData(filename, new_feature)
	print "New feature has been written. The number is {}. The new dimensions are {}".format(new_feature.shape[0], new_feature.shape[1])


import numpy as np

def forrester(x: np.ndarray):
    """The forrester function.

    Parameters
    ----------
    x : np.ndarray of shape (n_samples, 1)
        The input locations.

    Returns
    -------
    np.ndarray of shape (n_samples,)
        The function values at `x`.
    """
    return ((6 * x - 2) ** 2 * np.sin(12 * x - 4)).flatten()

def bohachevsky(x: np.ndarray):
    """The bohachevsky function.

    Parameters
    ----------
    x : np.ndarray of shape (n_samples, 2)
        The input locations.

    Returns
    -------
    np.ndarray of shape (n_samples,)
        The function values at `x`.
    """
    x1 = x[:, 0]
    x2 = x[:, 1]

    return (
        x1 ** 2
        + 2 * x2 ** 2
        - 0.3 * np.cos(3 * np.pi * x1)
        - 0.4 * np.cos(4 * np.pi * x2)
        + 0.7
    )


# nelder-mead for multimodal function optimization
from scipy.optimize import minimize
from numpy.random import rand
from numpy import exp
from numpy import sqrt
from numpy import cos
from numpy import e
from numpy import pi

# objective function
def objective(v):
	x, y = v
	return -20.0 * exp(-0.2 * sqrt(0.5 * (x**2 + y**2))) - exp(0.5 * (cos(2 * pi * x) + cos(2 * pi * y))) + e + 20

# define range for input
r_min, r_max = -5.0, 5.0
# define the starting point as a random sample from the domain
pt = r_min + rand(2) * (r_max - r_min)
# perform the search
result = minimize(objective, pt, method='nelder-mead')
# summarize the result
print('Status : %s' % result['message'])
print('Total Evaluations: %d' % result['nfev'])
# evaluate solution
solution = result['x']
evaluation = objective(solution)
print('Solution: f(%s) = %.5f' % (solution, evaluation))


import sympy
from sympy.utilities import lambdify

# f(x) = (x**2 -2x)e**(3-x)

x = sympy.symbols('x')

f = (x**2 - 2*x)*sympy.exp(3 - x)

# differentiate
fp = sympy.simplify(sympy.diff(f))  # (x*(2 - x) + 2*x - 2)*exp(3 - x)

# Check differentiate mually
fp2 = (2*x - 2)*sympy.exp(3 - x) - (x**2 - 2*x)*sympy.exp(3 - x)
sympy.simplify(fp2 - fp) == 0  # True

# integrate
F = sympy.integrate(f, x)  # -x**2*exp(3 - x)

# This converts a SymPy expression to a numerical expression that uses the NumPy
# equivalents of the SymPy standard functions to evaluate the expressions numerically
lam_f = lambdify(x, f)
lam_fp = lambdify(x, fp)


# coding: utf-8
# linear_regression/regression.py
import numpy as np
import matplotlib as plt
import time

# compute execution time
def exeTime(func):
    def newFunc(*args, **args2):
        t0 = time.time()
        back = func(*args, **args2)
        return back, time.time() - t0
    return newFunc

# h(x)
def h(theta, x):
    return (theta.T*x)[0,0]

# cost function
def J(theta, X, y):
    m = len(X)
    return (X*theta-y).T*(X*theta-y)/(2*m)

# gradient descent optimizer function
@exeTime
def gradientDescentOptimizer(rate, maxLoop, epsilon, X, y):
    """
    Args:
    rate: learning rate
    maxLoop: maximum iteration number
    epsilon: precision

    Returns:
        (theta, errors, thetas), timeConsumed
    """
    m,n = X.shape
    # initialize theta
    theta = np.zeros((n,1))
    count = 0
    converged = False
    error = float('inf')
    errors = []
    thetas = {}
    for j in range(n):
        thetas[j] = [theta[j,0]]
    while count<=maxLoop:
        if(converged):
            break
        count = count + 1
        for j in range(n):
            ############################################
            # YOUR CODE HERE!
            # deriv = ??
            deriv = -(X*theta-y).T*X[:, j]/m
            ############################################
            theta[j,0] = theta[j,0]+rate*deriv
            thetas[j].append(theta[j,0])
        error = J(theta, X, y)
        errors.append(error[0,0])
        if(error < epsilon):
            converged = True
    return theta,errors,thetas

# standarize function
def standarize(X):
    m, n = X.shape
    # normalize each feature
    for j in range(n):
        features = X[:,j]
        meanVal = features.mean(axis=0)
        std = features.std(axis=0)
        if std != 0:
            X[:, j] = (features-meanVal)/std
        else:
            X[:, j] = 0
    return X


from statistics import mean
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import style
import random

style.use('ggplot')

# XS = np.array([1, 2, 3, 4, 5, 6, 1, 4], dtype=np.float64)
# YS = np.array([5, 4, 6, 5, 6, 7, 1, 4], dtype=np.float64)

# noinspection PyPep8Naming,PyShadowingNames
def best_fit_slope_finder(features, labels):
    m = (((mean(XS) * mean(YS)) - mean(XS * YS)) /
         ((mean(XS) ** 2) - (mean(XS * XS))))
    return m

def y_intercept_finder(features, labels):
    b = (mean(YS) - m * mean(XS))
    return b

def squared_error(point, line):
    return sum((line - point) ** 2)

def coefficient_of_determination(point, line):
    global y_mean_line
    y_mean_line = [mean(point) for i in point]
    squared_error_regression = squared_error(point, line)
    squared_error_y_mean = squared_error(point, y_mean_line)
    return 1 - (squared_error_regression / squared_error_y_mean)

# noinspection PyPep8Naming,PyPep8Naming
def create_dataSet(hm, variance, step=2, correlation=False):
    val = 1
    # noinspection PyPep8Naming
    YS = []
    for i in range(hm):
        data = val + random.randrange(-variance, variance)
        YS.append(data)
        if correlation and correlation == 'pos':
            val += step
        elif correlation and correlation == 'neg':
            val -= step
    XS = [i for i in range(len(YS))]
    return np.array(XS, dtype=np.float64), np.array(YS, dtype=np.float64)

XS, YS = create_dataSet(1000, 500, 7, correlation='pos')

m = best_fit_slope_finder(XS, YS)

b = y_intercept_finder(XS, YS)

# Gets coordinates for best fit line plot
regression_line = []
for i in XS:
    ordinate = (m * i + b)
    regression_line.append(ordinate)

# Predictions
predict_x = np.array([120, 30, 92], dtype=np.float64)
predictions = []
for j in predict_x:
    predict_ordinate = (m * predict_x + b)
    predictions.append(predict_ordinate)

r_squared = coefficient_of_determination(YS, regression_line)

# Output:
print('Slope = ', m, 'Y intercept = ', b, 'Ordinates =', regression_line, 'Predict_Ordinates = ',
      predictions, 'R^2 = ', r_squared, sep='\n')

plt.scatter(XS, YS, color='b', label='data')
plt.scatter(predict_x, predict_ordinate, color='g', label='prediction')
plt.plot(XS, regression_line, label='regression_line')
plt.plot(YS, y_mean_line, label='y_mean_line')
plt.legend(loc=4)
plt.show()


import math
import numpy as np

from awesomediff.core import variable

def sin(x):
    """
        Helper function that calculates the sin of a variable or number.

        INPUTS:
            x : awesomediff.variable object or a number.

        OUTPUT:
            awesomediff.variable
    """

    try:
        # Assume object is a variable:
        val = x.val
        der = x.der
    except:
        # If not, treat it as a constant.
        try:
            float(x)
        except:
            raise ValueError("{} is not a number.".format(x))
        val = x
        der = 0  # Derivative of a constant is zero.
    # Calculate new value an derivative:
    new_val = np.sin(val)
    new_der = np.cos(val)*der
    # Return variable with new value an derivative:
    return variable(val=new_val,der=new_der)

def cos(x):
    """
        Helper function that calculates the sin of a variable or number.

        INPUTS:
            x : awesomediff.variable object or a number.

        OUTPUT:
            awesomediff.variable
    """

    try:
        # Assume object is a variable:
        val = x.val
        der = x.der
    except:
        # If not, treat it as a constant.
        try:
            float(x)
        except:
            raise ValueError("{} is not a number.".format(x))
        val = x
        der = 0  # Derivative of a constant is zero.
    # Calculate new value an derivative:
    new_val = np.cos(val)
    new_der = -np.sin(val)*der
    # Return variable with new value an derivative:
    return variable(val=new_val,der=new_der)

def tan(x):
    """
        Helper function that calculates the sin of a variable or number.

        INPUTS:
            x : awesomediff.variable object or a number.

        OUTPUT:
            awesomediff.variable
    """

    try:
        # Assume object is a variable:
        val = x.val
        der = x.der
    except:
        # If not, treat it as a constant.
        try:
            float(x)
        except:
            raise ValueError("{} is not a number.".format(x))
        val = x
        der = 0  # Derivative of a constant is zero.
    # Calculate new value an derivative:
    new_val = np.tan(val)
    new_der = ((1/np.cos(val))**2)*der
    # Return variable with new value an derivative:
    return variable(val=new_val,der=new_der)

def arcsin(x):
    """
        Helper function that calculates the arcsine of a variable or number.

        INPUTS:
            x : awesomediff.variable object or a number.

        OUTPUT:
            awesomediff.variable
    """
    try:
        # Assume object is a variable:
        val = x.val
        der = x.der
    except:
        # If not, treat it as a constant.
        try:
            float(x)
        except:
            raise ValueError("{} is not a number.".format(x))
        val = x
        der = 0  # Derivative of a constant is zero.
    # Calculate new value an derivative:
    new_val = np.arcsin(val)
    if(np.isnan(new_val)):
        raise ValueError("Please enter a number on the unit circle.")

    # if (np.sqrt(1-val**2)==0):
    #     new_der = 0
    # else:
    #     new_der = 1/np.sqrt(1-val**2)*der
    # print("new val", new_val)
    new_der = 1/np.sqrt(1-val**2)*der
    # Return variable with new value an derivative:
    return variable(val=new_val,der=new_der)

def arccos(x):
    """
        Helper function that calculates the arccos of a variable or number.

        INPUTS:
            x : awesomediff.variable object or a number.

        OUTPUT:
            awesomediff.variable
    """
    try:
        # Assume object is a variable:
        val = x.val
        der = x.der
    except:
        # If not, treat it as a constant.
        try:
            float(x)
        except:
            raise ValueError("{} is not a number.".format(x))
        val = x
        der = 0  # Derivative of a constant is zero.
    # Calculate new value an derivative:
    new_val = np.arccos(val)
    # print("new val", new_val)
    if(np.isnan(new_val)):
        raise ValueError("Please enter a number on the unit circle.")

    # if (np.sqrt(1-val**2)==0):
    #     new_der = 0
    # else:
    #     new_der = -1/np.sqrt(1-val**2)*der

    new_der = -1/np.sqrt(1-val**2)*der
    # Return variable with new value an derivative:
    return variable(val=new_val,der=new_der)

def arctan(x):
    """
        Helper function that calculates the arctan of a variable or number.

        INPUTS:
            x : awesomediff.variable object or a number.

        OUTPUT:
            awesomediff.variable
    """
    try:
        # Assume object is a variable:
        val = x.val
        der = x.der
    except:
        # If not, treat it as a constant.
        try:
            float(x)
        except:
            raise ValueError("{} is not a number.".format(x))
        val = x
        der = 0  # Derivative of a constant is zero.
    # Calculate new value an derivative:
    new_val = np.arctan(val)
    if(np.isnan(new_val)):
        raise ValueError("Please enter a number on the unit circle.")
    new_der = 1/(1+val**2)*der
    # Return variable with new value an derivative:
    return variable(val=new_val,der=new_der)

def log(x):
    """
        Helper function that calculates the natural log of a variable or number.

        INPUTS:
            x : AutoDiff.variable object or a number.

        OUTPUT:
            AutoDiff.variable
    """
    try:
        # x.val = np.log(x.val)
        # x.der = (1/x.val)*x.der
        new_val = np.log(x.val)
        new_der = (1/x.val)*x.der
        # return x
        return variable(val=new_val,der=new_der)
    except:
        new_val = np.log(x)
        new_der = 0
        return variable(val=new_val,der=new_der)

def logb(x, b):
    """
        Helper function that calculates the log of a variable or number with any base.

        INPUTS:
            x : AutoDiff.variable object or a number.
            b: a number

        OUTPUT:
            AutoDiff.variable
    """
    try:
        new_val = np.log(x.val)/np.log(b)
        new_der = (1/(np.log(b)*x.val))*x.der
        return variable(val=new_val,der=new_der)
    except:
        new_val = np.log(x)/np.log(b)
        new_der = 0
        return variable(val=new_val,der=new_der)

def sqrt(x):
    """
        Helper function that calculates the square root of a variable or number.

        INPUTS:
            x : AutoDiff.variable object or a number.

        OUTPUT:
            AutoDiff.variable
    """
    try:
        new_val = np.sqrt(x.val)
        new_der = (0.5/np.sqrt(x.val))*x.der
        return variable(val=new_val,der=new_der)
    except:
        new_val = np.sqrt(x)
        new_der = 0
        return variable(val=new_val,der=new_der)

def exp(x):
    """
        Helper function that calculates the exponential of a variable or number.

        INPUTS:
            x : AutoDiff.variable object or a number.

        OUTPUT:
            AutoDiff.variable
    """
    try:
        new_val = np.exp(x.val)
        new_der = np.exp(x.val)*x.der
        return variable(val=new_val,der=new_der)
    except:
        new_val = np.exp(x)
        new_der = 0
        return variable(val=new_val,der=new_der)

def sinh(x):
    """
        Helper function that calculates hyperbolic sine of a variable or number

        INPUTS:
            x : AutoDiff.variable object or a number.

        OUTPUT:
            AutoDiff.variable
    """
    return (exp(x) - exp(-x)) / 2

def cosh(x):
    """
        Helper function that calculates hyperbolic cosine of a variable or number

        INPUTS:
            x : AutoDiff.variable object or a number.

        OUTPUT:
            AutoDiff.variable
    """
    return (exp(x) + exp(-x)) / 2

def tanh(x):
    """
        Helper function that calculates hyperbolic tangent of a variable or number

        INPUTS:
            x : AutoDiff.variable object or a number.

        OUTPUT:
            AutoDiff.variable
    """
    return sinh(x) / cosh(x)

def logistic(x, L=1, k=1, x0=1):
    """
        Helper function that calculates the logistic of a variable or number.

        INPUTS:
            x : AutoDiff.variable object or a number.

        OUTPUT:
            AutoDiff.variable
    """
    try:
        val = x.val
        der = x.der
        logistic = L/(1+np.exp(-k*(val-x0)))
        new_val = logistic
        new_der = logistic*(1-logistic)*der
        return variable(val=new_val,der=new_der)
    except:
        new_val = L/(1+np.exp(-k*(x-x0)))
        new_der = 0
        return variable(val=new_val,der=new_der)


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

# # **IDS575: Machine Learning and Statistical Methods**
# ## [Linear Regression and Gradient Descent (PA)]
#
#

# ## Import Libraries
# * See various conventions and acronyms.

# In[1]:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_boston

# ## Load the data
# * Verify Python type for the dataset.

# In[2]:

HousingDataset = load_boston()
print(type(HousingDataset))
print(HousingDataset.keys())

# ## Verify basic data statistics
# * Count the number of features. (i.e., attributes)
# * Count the number of examples. (i.e., instances and labels)
# * Print out the description of each feature.

# In[14]:

def printBasicStats(dataset):
    print(dataset['feature_names'])
    print(len(dataset['feature_names']), type(dataset['feature_names']))
    print(dataset['data'].shape, dataset['target'].shape)
    print(dataset['DESCR'])

printBasicStats(HousingDataset)

# ## Convert the dataset to a DataFrame
# *   Not necessarily useful. (scikit-learn works well with default libraries such as list, numpy array, and scipy's sparse matrix)
# *   But using pandas provides more intuitive excel or R-like views.

# In[15]:

def getDataFrame(dataset):
    featureColumns = pd.DataFrame(dataset.data, columns=dataset.feature_names)
    targetColumn = pd.DataFrame(dataset.target, columns=['Target'])
    return featureColumns.join(targetColumn)

DataFrame = getDataFrame(HousingDataset)
print(DataFrame)

# ## Data inspection
# * See correlations between features.
# * Check the quantiles with the highest-correlated feature.
#

# In[16]:

print(DataFrame.corr())
DataFrame[['RM', 'Target']].describe()

# ## Data cleaning
# * Target could have some outliers because the maximum price is almost doubled to 50.0 though 75% of the data less than 25.0.
# * We can remove excessively expensive houses.

# In[17]:

Df = DataFrame[DataFrame.Target < 22.5328 + 2*9.1971]
Df[['RM', 'Target']].describe()

# * Rescale the data (different from Gaussian regularization).
#

# In[18]:

def rescaleVector(x):
    min = x.min()
    max = x.max()
    return pd.Series([(element - min)/(max - min) for element in x])

x_rescale = rescaleVector(Df.RM)
y_rescale = rescaleVector(Df.Target)
print(x_rescale.min(), x_rescale.max())
print(y_rescale.min(), x_rescale.max())

# * Plot the correlation between RM and Target.
# * Observe the linear relationship (excluding some outliers).
#
#

# In[19]:

def drawScatterAndLines(x, y, lines=[], titles={'main':None, 'x':None, 'y':None}):
    plt.figure(figsize=(20, 5))
    plt.rcParams['figure.dpi'] = 200
    plt.style.use('seaborn-whitegrid')
    plt.scatter(x, y, label='Data', c='blue', s=6)
    for (x_line, y_line) in lines:
        plt.plot(x_line, y_line, c='red', lw=3, label='Regression')
    plt.title(titles['main'], fontSize=14)
    plt.xlabel(titles['x'], fontSize=11)
    plt.ylabel(titles['y'], fontSize=11)
    plt.legend(frameon=True, loc=1, fontsize=10, borderpad=.6)
    plt.tick_params(direction='out', length=6, color='black', width=1, grid_alpha=.6)
    plt.show()

drawScatterAndLines(x_rescale, y_rescale, titles={'main':'correlation', 'x':'Avg # of Rooms', 'y':'Hosue Price'})

# ## Toy Linear Regression
# * Use only a single feature RM to fit house price.
# * This could be called Simple Linear Regression.
# * Plot the regression line.
#

# In[20]:

from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
def toyLinearRegression(df, feature_name, target_name):
  # This function performs a simple linear regression.
  # With a single feature (given by feature_name)
  # With a rescaling (for stability of test)
    x = rescaleVector(df[feature_name])
    y = rescaleVector(df[target_name])
    x_train = x.values.reshape(-1, 1)
    y_train = y.values.reshape(-1, 1)
    lr = LinearRegression()
    lr.fit(x_train, y_train)
    y_train_pred = lr.predict(x_train)

  # Return training error and (x_train, y_train, y_train_pred)
    return mean_squared_error(y_train, y_train_pred), (x_train, y_train, y_train_pred)

ToyTrainingError, (x_rescale_train, y_rescale_train, y_rescale_train_pred) = toyLinearRegression(Df, 'RM', 'Target')
print('training error = %.4f' % ToyTrainingError)
drawScatterAndLines(x_rescale_train, y_rescale_train, lines=[(x_rescale_train, y_rescale_train_pred)], titles={'main':'correlation', 'x':'RM', 'y':'Target'})

# ## Main Linear Regression
# * Use all of multi-variate features to fit house price.
# * This could be called Multiple Linear Regression.

# In[21]:

from sklearn.model_selection import train_test_split
def splitTrainTest(df, size):
    X, y = df.drop('Target', axis=1), df.Target
    X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=size, test_size=X.shape[0] - size, random_state=0)
    return (X_train, y_train), (X_test, y_test)

(X_train, y_train), (X_test, y_test) = splitTrainTest(Df, 350)
LR = LinearRegression()
LR.fit(X_train, y_train)
print(LR.coef_)
y_train_pred = LR.predict(X_train)
y_test_pred = LR.predict(X_test)

# ## Measure training and test accuracy
# * Use Mean Squared Error.

# In[13]:

from sklearn.metrics import mean_squared_error
print('Training error = %.4f' % mean_squared_error(y_train, y_train_pred))
print('Test error = %.4f' % mean_squared_error(y_test, y_test_pred))

# # Programming Assignment (PA)
# * Implement predict().
# * Implement batchGradientDescent().
# * Implement stocGradientDescent().
# * Implement normalEquation().
# * Play with testYourCode() that compares your implementations against scikit-learn's results. **Do not change alpha and epoch options separately provided to bgd and sgd for single-feature simple linear regression.**
# * Once everything is done, then compare your implementations against scikit-learn's results using the entire features. **Now play with different alpha and epoch values, reporting your comparative impressions among bgd, sgd, and normal equations.**

# In[26]:

class MyLinearRegression:
    theta = None

    def fit(self, X, y, option, alpha, epoch):
        X = np.concatenate((np.array(X), np.ones((X.shape[0], 1), dtype=np.float64)), axis=1)
        y = np.array(y)
        if option.lower() in ['bgd', 'gd']:
            # Run batch gradient descent.
            self.theta = self.batchGradientDescent(X, y, alpha, epoch)
        elif option.lower() in ['sgd']:
      # Run stochastic gradient descent.
            self.theta = self.stocGradientDescent(X, y, alpha, epoch)
        else:
      # Run solving the normal equation.
            self.theta = self.normalEquation(X, y)

    def predict(self, X):
        X = np.concatenate((np.array(X), np.ones((X.shape[0], 1), dtype=np.float64)), axis=1)
        y_pred = np.array([])
        if isinstance(self.theta, np.ndarray):
            theta = self.theta
            for x in X:
        # TO-DO: #############################################
                y_pred = np.dot(X, self.theta)
      ######################################################
            return y_pred
        return None

    def batchGradientDescent(self, X, y, alpha=0.00001, epoch=100000):
        (m, n) = X.shape
        theta = np.zeros((n, 1), dtype=np.float64)
        for iter in range(epoch):
            if (iter % 1000) == 0:
                print('- currently at %d epoch...' % iter)
        y_size = y.size
        for j in range(n):
         # TO-DO: #############################################
            theta[j] = theta[j] - (alpha * (sum([(np.dot(X[i], theta) - y[i]) * X[i][j] for i in range(m)])[0]) / m)
        ######################################################
        return theta

    def stocGradientDescent(self, X, y, alpha=0.000001, epoch=10000):
        (m, n) = X.shape
        theta = np.zeros((n, 1), dtype=np.float64)
        for iter in range(epoch):
            if (iter % 100) == 0:
                print('- currently at %d epoch...' % iter)
        for i in range(m):
            for j in range(n):
            # TO-DO: #############################################
                theta[j] = theta[j] - alpha * (np.dot(X[i], theta) - y[i]) * X[i][j]
          ######################################################
        return theta

    def normalEquation(self, X, y):
        # TO-DO: #############################################
        theta = np.dot(np.linalg.inv(np.dot(np.transpose(X), X)), np.dot(np.transpose(X), y))

    ######################################################
        return theta

    @staticmethod
    def toyLinearRegression(df, feature_name, target_name, option, alpha, epoch):
        # This function performs a simple linear regression.
    # With a single feature (given by feature_name)
    # With a rescaling (for stability of test)
        x = rescaleVector(df[feature_name])
        y = rescaleVector(df[target_name])
        x_train = x.values.reshape(-1, 1)
        y_train = y.values.reshape(-1, 1)

    # Perform linear regression.
        lr = MyLinearRegression()
        lr.fit(x_train, y_train, option, alpha, epoch)
        y_train_pred = lr.predict(x_train)

    # Return training error and (x_train, y_train, y_train_pred)
        return mean_squared_error(y_train, y_train_pred), (x_train, y_train, y_train_pred)

# In[27]:

def testYourCode(df, feature_name, target_name, option, alpha, epoch):
    trainError0, (x_train0, y_train0, y_train_pred0) = toyLinearRegression(df, feature_name, target_name)
    trainError1, (x_train1, y_train1, y_train_pred1) = MyLinearRegression.toyLinearRegression(df, feature_name, target_name, option, alpha, epoch)
    drawScatterAndLines(x_train0, y_train0, lines=[(x_train0, y_train_pred0)], titles={'main':'Linear Regression', 'x':feature_name, 'y':target_name})
    drawScatterAndLines(x_train1, y_train1, lines=[(x_train1, y_train_pred1)], titles={'main':'Linear Regression', 'x':feature_name, 'y':target_name})
    return trainError0, trainError1

TrainError0, TrainError1 = testYourCode(Df, 'DIS', 'Target', option='sgd', alpha=0.001, epoch=500)
print("Scikit's training error = %.6f / My training error = %.6f --> Difference = %.4f" % (TrainError0, TrainError1, np.abs(TrainError0 - TrainError1)))
TrainError0, TrainError1 = testYourCode(Df, 'RM', 'Target', option='bgd', alpha=0.1, epoch=5000)
print("Scikit's training error = %.6f / My training error = %.6f --> Difference = %.4f" % (TrainError0, TrainError1, np.abs(TrainError0 - TrainError1)))

# In[28]:

MyLR = MyLinearRegression()
MyLR.fit(X_train, y_train.values.reshape(-1, 1), option='sgd', alpha=0.000001, epoch=10000)
print(MyLR.theta)
y_train_pred = MyLR.predict(X_train)
y_test_pred = MyLR.predict(X_test)

print('Training error = %.4f' % mean_squared_error(y_train, y_train_pred))
print('Test error = %.4f' % mean_squared_error(y_test, y_test_pred))

# In[ ]:

Linear Regression and Gradient Descent.py


# coding: utf-8

# In[ ]:

get_ipython().run_line_magic('matplotlib', 'inline')
import numpy as np
import matplotlib.pyplot as plt

#Defining the function to integrate
def func(x):
    return (np.exp(-2*x))*(np.cos(10*x))

#Defining the integral
def func_integral(x):
    return (np.exp(-2*x))*(5*np.sin(10*x) - np.cos(10*x))/(52)

# TRAPEZOID METHOD

#This is the core program of the Trapezoid Method
def trapezoid_core(f,x,h):
    return 0.5*h*(f(x+h) + f(x))

#Wrapper function for the Trapezoid Method
def trapezoid_method(f,a,b,N):
    #f == function to integrate
    #a == lower limit of integration
    #b == upper limit of integration
    #N == number of function evaluations to use

    #define x values to perform trapezoid rule
    x = np.linspace(a,b,N)
    h = x[1]-x[0]

    #define the value of the integral
    Fint = 0.0

    #perform the integral using the trapezoid method
    for i in range(0, len(x)-1,1):
        Fint += trapezoid_core(f,x[i],h)

    #return the answer
    return Fint

#SIMPSON'S METHOD

#This is the core program of Simpson's Method
def simpson_core(f,x,h):
    return h*( f(x) + 4*f(x+h) + f(x+2*h))/3.

#Wrapper function for Simpson's Method
def simpsons_method(f,a,b,N):
    #f == function to integrate
    #a == lower limit of integration
    #b == upper limit of integration
    #N == number of function evaluations to use

    '''Note the number of chunks will be N-1
        so if N is odd, then we don't need to
        adjust the last segment'''

    #Define x values to perform simpson's rule
    x = np.linspace(a,b,N)
    h = x[1]-x[0]

    #define the value of the integral
    Fint = 0.0

    #perform the integral using simpson's method
    for i in range(0,len(x)-2,2):
        Fint += simpson_core(f,x[i],h)

    #apply simpson's rule over the last interval
    #if N is even
    if((N%2)==0):
        Fint += simpson_core(f,x[-2],0.5*h)

    return Fint

# ROMBERG INTEGRATION

#This is the Romberg Core
def romberg_core(f,a,b,i):

    #we need the difference b-a
    h = b-a

    #and the increment between new func evals
    dh = h/2.**(i)

    #we need the cofactor
    K = h/2.**(i+1)

    #and the function evaluations
    M = 0.0
    for j in range(2**i):
        M += f(a + 0.5*dh + j*dh)

    #return the answer
    return K*M

#Wrapper function for Romberg
def romberg_integration(f,a,b,tol):

    #define an iteration variable
    i = 0

    #define a maximum number of iterations
    imax = 1000

    #define an error estimate, set to a large value
    delta = 100.0*np.fabs(tol)

    #set an array of integral answers
    I = np.zeros(imax,dtype=float)

    #get the zeroth romberg iteration
    I[0] = 0.5*(b-a)*(f(a) + f(b))

    #iterate by 1
    i += 1

    while(delta>tol):

        #find this romberg iteration
        I[i] = 0.5*I[i-1] + romberg_core(f,a,b,i)

        #compute the new fractional error estimate
        delta = np.fabs( (I[i]-I[i-1])/I[i])

        print(i,I[i],I[i-1],delta)

        if(delta>tol):

            #iterate
            i+=1

            #if we've reached the maximum iterations
            if(i>imax):
                print("Max iterations reached.")
                raise StopIteration('Stopping iterations after ',i)

    #return the answer
    return I[i]

#Print Answers:

print("Answer:")
Answer = func_integral(np.pi)-func_integral(0)
print(Answer)
print("Trapezoid:")
print(trapezoid_method(func,0,1,10))
print("Simpson's Method:")
print(simpsons_method(func,0,1,10))
print("Romberg:")
tolerance = 1.0e-6
RI = romberg_integration(func,0,1,tolerance)
print(RI, (RI-Answer)/Answer, tolerance)


"""
Author: michealowen
Last edited: 2019.11.14,Thursday
计算距离
"""
#encoding=UTF-8

import numpy as np

def euclidean_distance(x_1,x_2):
    '''
    计算x_1和x_2中的向量的欧几里得(L2)距离
    compute the L2-distances of the vectors in x_1,x_2

    Params:
        x_1:array-like, shape: (n_samples_X, n_features)
        x_2:array-like, shape: (n_samples_X, n_features)
    Returns:
        distance:array-like, shape: (n_samples_X,1)
    '''
    x_1 = check_array(x_1)
    x_2 = check_array(x_2)
    return np.sum(np.power(x_1-x_2,2),axis=1).reshape(-1,1)

def manhattan_distance(x_1,x_2):
    '''
    计算x_1和x_2中的向量的曼哈顿距离(L1)距离
    compute the L1-distances of the vectors in x_1,x_2

    Params:
        x_1:array-like, shape: (n_samples_X, n_features)
        x_2:array-like, shape: (n_samples_X, n_features)
    Returns:
        distance:array-like, shape: (n_samples_X,1)
    '''
    x_1 = check_array(x_1)
    x_2 = check_array(x_2)
    return np.sum(np.abs(x_1-x_2,2),axis=1).reshape(-1,1)

def check_array(x):
    '''
    to change the type of x to np.ndarray if isinstance(x,np.ndarray) != True
    '''
    if not isinstance(x,np.ndarray):
        x = np.array(x)
    return x

if __name__ == '__main__':
    print(manhattan_distance([1,1],[2,2]))


# ###################################
# author: Gonzalo Salazar
# course: Python for Data Science and Machine Learning Bootcamp
# purpose: lecture notes
# description: Section 22 - Principal Component Analysis
# datasets: breast cancer diagnoses from sklearn
# ###################################

## PCA (aka general factor analysis) ###
# An unsupervised statistical technique used to examine the interrelations among a set of
# variables in order to identify the underlying structure of those variables. Where regression
# determines a line of best fit to a data set, factor analysis determines several orthogonal
# lines of best fit to the data set.
# The components are a linear transformation that chooses a variable system for the data set
# such that the greatest variance of the data set comes to lie on the first axis, the second
# greatest variance on the second axis, and so on ... This process allows us to reduce the number
# of variables used in an analysis.
#
# Note that components are uncorrelated, since in the sample space they are orthogonal to each other!
#
# PROS:
#   When we have a large number of variables in a data set, we can compress the amount of explained
#   variation to just a few components
#
# CONS:
#   Interpreting the components

# %%
#import os
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import load_breast_cancer
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
#%matplotlib inline

# %%
# Loading and visualizing data
cancer = load_breast_cancer()
cancer.keys()
print(cancer['DESCR'])

df = pd.DataFrame(cancer['data'],columns = cancer['feature_names'])
df.head()

# %%
# Scaling everything to have a single unit variance
scaler = StandardScaler()
scaler.fit(df)
scaled_data = scaler.transform(df)

# %%
# Performing the PCA
pca = PCA(n_components = 2)  # number of component we want to keep are two
pca.fit(scaled_data)
x_pca = pca.transform(scaled_data)  # transforms the data to its first principle component

# Checking dimensions before and after transformation
print('Data dimensions')
print('Scaled data: ',str(scaled_data.shape))
print('Transformed data: ', str(x_pca.shape))

# %%
# Checking how the data looks like in 2D
plt.figure(figsize=(8,6))
plt.scatter(x_pca[:,0],x_pca[:,1],c=cancer['target'],cmap='plasma')
plt.xlabel('First Principle Component')
plt.ylabel('Second Principle Component')
plt.show()

# %%
# Showing how each variable was combined to get a component, where each row represents a component
# and each column represents the weigth of each column
pca.components_

df_comp = pd.DataFrame(pca.components_, columns=cancer['feature_names'])
sns.set_style('darkgrid')
plt.figure(figsize=(12,6))
sns.heatmap(df_comp,cmap='Greens')

# We can now use the x_pca to fit a classification algorithm. So we can do something like a logistic
# regression on x_pca instead of doing a logistic regression on the entire data frame of features.
# Since data seems to be very well separated, SVMs may actually be a good choice for this.


from aerosandbox.numpy.array import array, length
import numpy as _onp

def finite_difference_coefficients(
        x: _onp.ndarray,
        x0: float = 0,
        derivative_degree: int = 1,
) -> _onp.ndarray:
    """
    Computes the weights (coefficients) in compact finite differece formulas for any order of derivative
    and to any order of accuracy on one-dimensional grids with arbitrary spacing.

    (Wording above is taken from the paper below, as are docstrings for parameters.)

    Modified from an implementation of:

        Fornberg, Bengt, "Generation of Finite Difference Formulas on Arbitrarily Spaced Grids". Oct. 1988.
        Mathematics of Computation, Volume 51, Number 184, pages 699-706.

        PDF: https://www.ams.org/journals/mcom/1988-51-184/S0025-5718-1988-0935077-0/S0025-5718-1988-0935077-0.pdf

        More detail: https://en.wikipedia.org/wiki/Finite_difference_coefficient

    Args:

        derivative_degree: The degree of the derivative that you are interested in obtaining. (denoted "M" in the
        paper)

        x: The grid points (not necessarily uniform or in order) that you want to obtain weights for. You must
        provide at least as many grid points as the degree of the derivative that you're interested in, plus 1.

            The order of accuracy of your derivative depends in part on the number of grid points that you provide.
            Specifically:

                order_of_accuracy = n_grid_points - derivative_degree

            (This is in general; can be higher in special cases.)

            For example, if you're evaluating a second derivative and you provide three grid points, you'll have a
            first-order-accurate answer.

            (x is denoted "alpha" in the paper)

        x0: The location that you are interested in obtaining a derivative at. This need not be on a grid point.

    Complexity is O(derivative_degree * len(x) ^ 2)

    Returns: A 1D ndarray corresponding to the coefficients that should be placed on each grid point. In other words,
    the approximate derivative at `x0` is the dot product of `coefficients` and the function values at each of the
    grid points `x`.

    """
    ### Check inputs
    if derivative_degree < 1:
        return ValueError("The parameter derivative_degree must be an integer >= 1.")
    expected_order_of_accuracy = length(x) - derivative_degree
    if expected_order_of_accuracy < 1:
        return ValueError("You need to provide at least (derivative_degree+1) grid points in the x vector.")

    ### Implement algorithm; notation from paper in docstring.
    N = length(x) - 1

    delta = _onp.zeros(
        shape=(
            derivative_degree + 1,
            N + 1,
            N + 1
        ),
        dtype="O"
    )

    delta[0, 0, 0] = 1
    c1 = 1
    for n in range(1,
                   N + 1):  # TODO make this algorithm more efficient; we only need to store a fraction of this data.
        c2 = 1
        for v in range(n):
            c3 = x[n] - x[v]
            c2 = c2 * c3
            # if n <= M: # Omitted because d is initialized to zero.
            #     d[n, n - 1, v] = 0
            for m in range(min(n, derivative_degree) + 1):
                delta[m, n, v] = (
                                         (x[n] - x0) * delta[m, n - 1, v] - m * delta[m - 1, n - 1, v]
                                 ) / c3
        for m in range(min(n, derivative_degree) + 1):
            delta[m, n, n] = (
                    c1 / c2 * (
                    m * delta[m - 1, n - 1, n - 1] - (x[n - 1] - x0) * delta[m, n - 1, n - 1]
            )
            )
        c1 = c2

    coefficients_object_array = delta[derivative_degree, -1, :]

    coefficients = array([*coefficients_object_array])  # Reconstructs using aerosandbox.numpy to intelligently type

    return coefficients


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Jan 21 15:36:06 2022

@author: bobrokerson
"""
# part of code from task #1

from math import sin, exp
import numpy as np
import matplotlib.pyplot as plt

def func(x):
    return sin(x / 5.) * exp(x / 10.) + 5. * exp(-x/ 2.)

xarr = np.arange(1., 31.)
print(xarr)
print("x:", xarr.shape)
yarr = np.array([func(x) for x in xarr])
print(yarr)
print("y:", yarr.shape)

plt.plot(xarr, yarr)
plt.grid(True)
plt.axis([0, 30, -15, 5])
plt.show()

# 1.Теперь рассмотрим функцию h(x) = int(f(x)) на том же отрезке [1, 30], т.е. теперь каждое значение f(x) приводится к типу int и функция принимает только целые значения.
# 2.Такая функция будет негладкой и даже разрывной, а ее график будет иметь ступенчатый вид. Убедитесь в этом, построив график h(x) с помощью matplotlib.
# 3.Попробуйте найти минимум функции h(x) с помощью BFGS, взяв в качестве начального приближения x=30. Получившееся значение функции – ваш первый ответ в этой задаче.
# 4.Теперь попробуйте найти минимум h(x) на отрезке [1, 30] с помощью дифференциальной эволюции. Значение функции h(x) в точке минимума – это ваш второй ответ в этом задании. Запишите его через пробел после предыдущего.
# 5.Обратите внимание на то, что полученные ответы различаются. Это ожидаемый результат, ведь BFGS использует градиент (в одномерном случае – производную) и явно не пригоден для минимизации рассмотренной нами разрывной функции. Попробуйте понять, почему минимум, найденный BFGS, именно такой (возможно в этом вам поможет выбор разных начальных приближений).
# 6.Выполнив это задание, вы увидели на практике, чем поиск минимума функции отличается от глобальной оптимизации, и когда может быть полезно применить вместо градиентного метода оптимизации метод, не использующий градиент. Кроме того, вы попрактиковались в использовании библиотеки SciPy для решения оптимизационных задач, и теперь знаете, насколько это просто и удобно.

from scipy.optimize import minimize
from scipy.optimize import differential_evolution

def funcnew(x):
    return int(func(x))

xarrnew = np.arange(1., 31., 0.01)
print(xarrnew)
print("x:", xarrnew.shape)
yarrnew = np.array([funcnew(x) for x in xarrnew])
print(yarrnew)
print("y:", yarrnew.shape)

# create plot
plt.plot(xarrnew, yarrnew)
plt.grid(True)
plt.axis([0, 30, -15, 5])
plt.show()

minFuncnewVal1 = minimize(funcnew, 30, method = 'BFGS')
print("Min f(x) BFGS method: ", round(minFuncnewVal1.fun, 3), "for x = ", minFuncnewVal1.x)
print("Number: ", minFuncnewVal1.nit)

minValR2 = np.zeros( (2) )
minValR2 [0] = round(minFuncnewVal1.fun, 2)
print(minValR2)

# searching min h(x)

bounds = [(1, 30)]
minFuncnewVal2 = differential_evolution(funcnew, bounds)
print("Min f(x) BFGS method: ", round(minFuncnewVal2.fun, 3), "for x = ", minFuncnewVal2.x)
print("Number: ", minFuncnewVal2.nit)

minValR2[1] = round(minFuncnewVal2.fun, 2)
print (minValR2)

with open("docvalue3.txt", "w") as file:
    for item in minValR2:
        file.write(str(item) + ' ')
        S


# Question 3 Algorithm 2 (efficient) Lab Assignment 03
# @AB Satyaprakash, 180123062

# imports
from math import exp, sqrt
import numpy as np
import time

# Initialising a dictionary -- C++ equivalent to map<>
Map = {}
# functions ---------------------------------------------------------------------------

def recursion(n, K, S, T, r, sig, M, u, d, p, q, dt, upCnt):
    if (n, upCnt) in Map:
        return Map[(n, upCnt)]

    if n == M:
        Map[(n, upCnt)] = max(S - K, 0)
        return max(S - K, 0)

    up = recursion(n + 1, K, S * u, T, r, sig, M, u, d, p, q, dt, upCnt+1)
    dn = recursion(n + 1, K, S * d, T, r, sig, M, u, d, p, q, dt, upCnt)
    pc = (exp(-r * dt)) * (p * up + q * dn)
    Map[(n, upCnt)] = pc
    return pc

# -------------------------------------------------------------------------------------
# Given Initial Values -- and taking K = 100
S0, K, T, r, sig = 100, 100, 1, 0.08, 0.2
# Can handle values of M as much as 500 ..
Mlist = [5, 10, 25, 50, 100, 500]

for M in Mlist:
    dt = T/M

    u = exp(sig*sqrt(dt)+(r-sig*sig/2)*dt)
    d = exp(-sig*sqrt(dt)+(r-sig*sig/2)*dt)
    p = (exp(r*dt)-d)/(u-d)
    q = 1-p
    Map.clear()
    loopbackOptionPrice = recursion(0, S0, S0, T, r, sig, M, u, d, p, q, dt, 0)
    print('The initial european call option price for M = {} is {}'.format(M, loopbackOptionPrice))

# Test computation time for M = 15: -----------------------------------------------------
dt = T/M
u = exp(sig*sqrt(dt)+(r-sig*sig/2)*dt)
d = exp(-sig*sqrt(dt)+(r-sig*sig/2)*dt)
p = (exp(r*dt)-d)/(u-d)
q = 1-p
Map.clear()
start = time.time()
timeTempPrice = recursion(0, S0, S0, T, r, sig, M, u, d, p, q, dt, 0)
end = time.time()

print('Computational time for M = 15 is {}s'.format(end-start))

# Question 3 algorithm 2 ends -------------------------------------------------------------

Semester 6/MA 374 (Financial Engg. Lab)/Lab 3/180123062_AB_q3_2.py


import numpy as np
import matplotlib.pyplot as plt
from scipy import special as sp

# https://mail.python.org/pipermail/scipy-dev/2016-February/021252.html
def qfunc(arg):
    return 0.5 - 0.5 * sp.erf(arg / 1.414)

samples = 2 * (np.random.randn(1, 10000)[0]) + 2
mean_samples = np.mean(samples)
std_samples = np.std(samples)
z1 = (2 - mean_samples) / std_samples
z2 = (6 - mean_samples) / std_samples
probability = qfunc(z1) - qfunc(z2)
print(probability)

probability_less_than_a = 1 - qfunc(z1)
print("P(X<a):", probability_less_than_a)

probability_higher_than_b = qfunc(z2)
print("P(X>b):", probability_higher_than_b)

probability_total = probability + probability_higher_than_b + probability_less_than_a
print("P(X<a) + P(a<X<b) + P(X>b):", probability_total)


import numpy as np
import sympy as sp

def eigenstuff(R):
    # Calculate their eigenvalues, eigenvectors, and
    #  conditioning numbers

    eigenvalues, eigenvectors = np.linalg.eig(R)
    conditioning_number = max(eigenvalues) / min(eigenvalues)

    # Setando a precisão para 3 pro print ficar mais bonito.
    np.set_printoptions(precision=3)

    print(f'A matriz R:')
    print(R)
    print('\n')
    print(f'Autovalores de R: {eigenvalues}')
    print(f'Autovetores de R:')
    print(eigenvectors)
    print(f'Número de condicionamento de R: {conditioning_number:.3f}')

def symbolic_eigevalues(R):

    print('\n')
    print(f'Autovalores e suas multiplicidades:')
    print(R.eigenvals())

def symbolic_eigenstuff(R):
    print('\n')
    print('Autovalores, multiplicidade e autoveores:')
    print(R.eigenvects())

def symbolic_diagonalize(R):
    P, D = R.diagonalize()
    print('\n \n')
    print('Diagonalização da matriz R:')
    print(P)
    print(D)
    print(P**-1)


"""
The prime HP reached starting from a number , concatenating its prime factors, and repeating until a prime is reached.
If you have doubts, refer the article here [http://mathworld.wolfram.com/HomePrime.html]

write a function to calculate the HP of a given number.

Also write a function to compute the Euclid-Mullin sequence [http://mathworld.wolfram.com/Euclid-MullinSequence.html].
"""

import numpy as np

def is_prime(n):
    """https://stackoverflow.com/questions/15285534/isprime-function-for-python-language"""
    if n == 2 or n == 3: return True
    if n < 2 or n%2 == 0: return False
    if n < 9: return True
    if n%3 == 0: return False
    r = int(n**0.5)
    f = 5
    while f <= r:
        if n%f == 0: return False
        if n%(f+2) == 0: return False
        f +=6
    return True

def factorize(n):
    active_num = n

    factors = []
    n = 2
    while active_num != 1:
        if active_num % n == 0:
            active_num /= n
            factors.append(str(n))
        else:
            n += 1
    return factors

def home_prime(n):
    while not is_prime(n):
        n = int(''.join(factorize(n)))
    return n

def euclid_mullin(n):
    e_m = []
    for i in range(n):
        e_m.append(int(factorize(np.prod(e_m)+1)[0]))
        print(e_m)
    return e_m

if __name__ == '__main__':
    print(home_prime(5))
    euclid_mullin(9)


"""
Created on Fri Feb 18 09:50:30 2022

@author: udaytalwar
"""

'''
References:

ASX Portfolio on YouTube
Paul Wilmott's "The Mathematics of Financial Derivatives: A Student Introduction"

'''

import scipy.stats as stats
import math as m
import numpy as np

#calculating option price using Black-Scholes

def d(S, K, sigma, r, t):

    '''
    S = Current Price
    K = Strike Price
    sigma = Volatility
    r = annualized risk-free rate
    t = time to expiration

    returns d1, d2 for option price calculation using Black Scholes
    '''
    d1 = (m.log(S/K) + (r + (sigma**2/2))*t) * (1/(sigma*m.sqrt(t)))

    d2 = d1 - sigma*m.sqrt(t)

    return d1, d2

def option_price(S, K, sigma, r, t, flag, d1 = 0, d2 = 0):

    '''
    S = Current Price
    K = Strike Price
    sigma = Volatility
    r = annualized risk-free rate
    t = time to expiration
    flag = 'Call' or 'Put'

    returns option price according to Black Scholes
    '''

    if d1 == 0 and d2 == 0:

        d1, d2 = d(S, K, sigma, r, t)

        if flag == 'Call':

            price = stats.norm.cdf(d1)*S - stats.norm.cdf(d2)*K*m.exp(-r*t)

        elif flag == 'Put':

            price = stats.norm.cdf(-d2)*K*m.exp(-r*t) - stats.norm.cdf(-d1)*S

        return price

    else:

        if flag == 'Call':

            price = stats.norm.cdf(d1)*S - stats.norm.cdf(d2)*K*m.exp(-r*t)

        elif flag == 'Put':

            price = stats.norm.cdf(-d2)*K*m.exp(-r*t) - stats.norm.cdf(-d1)*S

        return price

#calculating option price using Monte Carlo Approximation

def option_price_MC(S, K, sigma, r, t, flag, steps, paths, error = False):

    '''
    S = Current Price
    K = Strike Price
    sigma = Volatility
    r = annualized risk-free rate
    t = time to expiration
    flag = 'Call' or 'Put'
    steps = number of steps per path
    paths = number of paths to simulate
    error = False, if true returns (price, approximation error)

    returns expected option price from simulation

    '''

    timestep = t/steps

    nudt = (r - 0.5*sigma**2)*timestep

    volsdt = vol*m.sqrt(timestep)

    lnS = m.log(S)

    random_matr = np.random.normal(size=(steps,paths))

    delta_lnSt = nudt + volsdt*random_matr

    lnSt = lnS + np.cumsum(delta_lnSt, axis = 0)

    lnSt = np.concatenate( (np.full(shape = (1,paths), fill_value = lnS), lnSt))

    ST = np.exp(lnSt)

    if flag == 'Call':
        price = np.maximum(0, ST - K)

    elif flag == 'Put':
        price = np.maximum(0, K - ST)

    initial_price = np.exp(-r*t)*np.sum(price[-1])/paths

    err = np.sqrt(np.sum((price[-1]-initial_price)**2)/(paths-1))

    SE = err/m.sqrt(paths)

    if error == True:

        return initial_price, SE

    else:
        return initial_price

#calculating option price using Binomial Trees

def option_price_BT(S, K, sigma, r, t, flag, steps):

    '''
    S = Current Price
    K = Strike Price
    sigma = Volatility
    r = annualized risk-free rate
    t = time to expiration
    flag = 'Call' or 'Put'
    steps = number of steps per path

    returns option price using Binomial Trees
    '''

    timestep = t/steps #equivalent to dt, a discrete time step to iterate through

    #the formulas for A, d, u and p are described in Wilmott's book

    A = (1.0/2.0)*(m.exp(-r*timestep)+m.exp((r+sigma**2)*timestep))

    d = A - m.sqrt(A**2 - 1)

    u = A + m.sqrt(A**2 - 1)

    p = (m.exp(r*timestep)-d)/(u-d)

    #discount function to discount the final value back to our initial value ie current option price
    discount = m.exp(-r*timestep)

    #price at node (i,j) at timestep i is = start price * d^(i-j)*u^(i) where j is the number of the node at the
    # given time step, where we count from the bottom of the tree up to the top

    price = S * d ** (np.arange(steps,-1,-1)) * u **(np.arange(0, steps+1, 1))

        #price - K for Call, K - price for put
    if flag == 'Call':

        price = np.maximum(price - K, np.zeros(steps+1))

    elif flag == 'Put':

        price = np.maximum(K - price, np.zeros(steps+1))

        #apply the discount function to iteratively arrive at initial value

    for j in np.arange(steps, 0, -1):

        # formula below also given in Wilmott's book

        price = discount * (p * price[1:j+1] + (1-p) * price[0:j])

    return price[0]

t = 30.0/365.0
S0 = 100
K = 100
flag = 'Put'
vol = (0.4/30.0)*np.sqrt(365)
r = 0.015

steps = 20
paths = 10000
steps_bt = 500

print('Price according to B-S = ',str(round(option_price(S0, K, vol, r, t, flag), 3)))

print('Price according to Monte Carlo = ',str(round(option_price_MC(S0, K, vol, r, t, flag, steps, paths), 3)))

print('Price according to Binomial Tree= ',str(round(option_price_BT(S0, K, vol, r, t, flag, steps_bt), 3)))


# Fast Fourier Transform is an algorithm to efficiently multiply polynomials among many other applications
# Fast Fourier Transform is a divide and conquer that splits the X(n) sequence of values recursively
# performs DFT on pairs of elements and then combines the results

import numpy as np

# O(N^2) DFT calculation
def dft(x):
    x = np.asarray(x, dtype=float)
    N = x.shape[0]
    n = np.arange(N)
    k = n.reshape((N, 1))
    M = np.exp(-2j * np.pi * k * n / N)
    return np.dot(M, x)

# D&C FFT: You can think of FFT as a method to apply DFT recursively by split domain space
# that utilizes specific value points that achieve further reduction in computations needed.
def fft(x):
    x = np.asarray(x, dtype=float)
    N = x.shape[0]
    if N % 2 > 0:
        raise ValueError("must be a power of 2")
    elif N <= 2:
        return dft(x)
    else:
        X_even = fft(x[::2])
        X_odd = fft(x[1::2])
        terms = np.exp(-2j * np.pi * np.arange(N) / N)
        return np.concatenate([X_even + terms[:int(N / 2)] * X_odd,
                               X_even + terms[int(N / 2):] * X_odd])

# Another function to compute the Fourier Transform.
# This time, we make use of vector operations instead of recursion.

def fft_v(x):
    x = np.asarray(x, dtype=float)
    N = x.shape[0]
    if np.log2(N) % 1 > 0:
        raise ValueError("must be a power of 2")

    N_min = min(N, 2)

    n = np.arange(N_min)
    k = n[:, None]
    M = np.exp(-2j * np.pi * n * k / N_min)
    X = np.dot(M, x.reshape((N_min, -1)))

    while X.shape[0] < N:
        X_even = X[:, :int(X.shape[1] / 2)]
        X_odd = X[:, int(X.shape[1] / 2):]
        terms = np.exp(-1j * np.pi * np.arange(X.shape[0])
                       / X.shape[0])[:, None]
        X = np.vstack([X_even + terms * X_odd,
                       X_even - terms * X_odd])
    return X.ravel()

import time

def main():
    # Validate results
    x = np.random.random(1024)
    np.allclose(dft(x), np.fft.fft(x))

    # timeit

    t0 = time.time()
    dft(x)
    t1 = time.time()
    print(t1 - t0)

    t0 = time.time()
    np.fft.fft(x)
    t1 = time.time()
    print(t1 - t0)

    # Validate results
    x = np.random.random(1024)
    np.allclose(fft(x), np.fft.fft(x))

    # timeit
    t0 = time.time()
    dft(x)
    t1 = time.time()
    print(t1 - t0)

    t0 = time.time()
    fft(x)
    t1 = time.time()
    print(t1 - t0)

    t0 = time.time()
    np.fft.fft(x)
    t1 = time.time()
    print(t1 - t0)

    # Validate results
    x = np.random.random(1024)
    np.allclose(fft_v(x), np.fft.fft(x))

    # timeit
    t0 = time.time()
    fft(x)
    t1 = time.time()
    print(t1 - t0)

    t0 = time.time()
    fft_v(x)
    t1 = time.time()
    print(t1 - t0)

    t0 = time.time()
    np.fft.fft(x)
    t1 = time.time()
    print(t1 - t0)

if __name__ == '__main__':
    main()


import matplotlib.pyplot as plt
import numpy

def calc_mean(list_values):
	pre_mean = 0
	for x in list_values:
		pre_mean = pre_mean + x

	return (pre_mean / len(list_values))

def sum_of_squared_errors(list_values, mean):
	sse = 0
	for x in list_values:
		sse = sse + (x-mean)**2

	return sse

def sum_of_multiplied_xy(list_values_x, list_values_y, x_mean, y_mean):
	smxy = 0
	for i in range(0, len(list_values_x)):
		smxy = smxy + (list_values_x[i] - x_mean)*(list_values_y[i] - y_mean)

	return smxy

def cov(x, y):
    x_mean = calc_mean(x)
    y_mean = calc_mean(y)
    data = [(x[i] - x_mean) * (y[i] - y_mean)
            for i in range(len(x))]
    return sum(data) / (len(data) - 1)

def var(values, average):
    variance = 0
    for number in values:
        variance += (average - number) ** 2
    return variance / len(values)

if __name__ == "__main__":
	x_values = [1, 4, 4, 5]
	y_values = [3, 6, 8, 2]
	x_mean = calc_mean(x_values)
	y_mean = calc_mean(y_values)

	SSE = sum_of_squared_errors(y_values, y_mean) #changes made here
	SMXY = sum_of_multiplied_xy(x_values, y_values, x_mean, y_mean)

	slope = SSE / SMXY
	slope_2 = cov(x_values, y_values) / var(x_values, x_mean)

	b = y_mean - (slope_2 * x_mean)

	print("m = %.2f\nb = %.2f" % (slope_2, b))

	plt.plot(x_values, y_values, "ro")
	x = numpy.array(x_values)
	y = slope_2*x+b
	plt.plot(x, y)
	#plt.axis([0, 6, 0, 5])
	plt.show()


import numpy as np
import matplotlib.pyplot as plt

# 42. Ten pregnant women were given an injection of pitocin to induce labor. Their
# systolic blood pressures immediately before and after the injection were:

before = [134, 122, 132, 130, 128, 140, 118, 127, 125, 142]
after = [140, 130, 135, 126, 134, 138, 124, 126, 132, 144]

# vamos a implementar un metodo de shuffling. para hacer esto la hipotesis nula
# es que ambas series de datos vienen de la misma distribucion.
# la estadistica que vamos a calcular es el promedio de las diferencias.

n_mc = 10000
n = len(before)
mu_mc = np.zeros(n_mc)
datos = np.array(before+after)
for i in range(n_mc):
    np.random.shuffle(datos)
    new_before = datos[:n]
    new_after = datos[n:]
    mu_mc[i] = np.mean(new_before - new_after)

mu = np.mean(np.array(before) - np.array(after))
p_value = np.count_nonzero(np.abs(mu_mc)>np.abs(mu))/n_mc
if p_value<0.05:
    H = 'SI'
else:
    H = 'NO'
print('Problema 42. p-value: {:.2f}. La droga {} tiene un efecto en la presion'.format(p_value, H))

plt.figure()
plt.hist(mu_mc, bins=20)
plt.axvline(mu, color='red')
plt.savefig("tmp_42.png")

# 43. A question of medical importance is whether jogging leads to a
#reduction in one’s pulse rate. To test this hypothesis, 8 nonjogging
#volunteers agreed to begin a 1-month jogging program. After the month
#their pulse rates were determined and compared with their earlier
#values. If the data are as follows, can we conclude that jogging has
#had an effect on the pulse rates?

before = [74,86,98,102,78,84,79,70]
after = [70,85,90,110,71,80,69,74]

# vamos a implementar un metodo de shuffling. para hacer esto la hipotesis nula
# es que ambas series de datos vienen de la misma distribucion.
# la estadistica que vamos a calcular es el promedio de las diferencias

n_mc = 10000
n = len(before)
mu_mc = np.zeros(n_mc)
datos = np.array(before+after)
for i in range(n_mc):
    np.random.shuffle(datos)
    new_before = datos[:n]
    new_after = datos[n:]
    mu_mc[i] = np.mean(new_before - new_after)

mu = np.mean(np.array(before) - np.array(after))
p_value = np.count_nonzero(np.abs(mu_mc)>np.abs(mu))/n_mc
if p_value<0.05:
    H = 'SI'
else:
    H = 'NO'
print('Problema 43. p-value: {:.2f}. Correr {} tiene un efecto en el pulso'.format(p_value, H))

plt.figure()
plt.hist(mu_mc, bins=20)
plt.axvline(mu, color='red')
plt.savefig("tmp_43.png")

#67. In the
#nineteenseventies,theU.S.VeteransAdministration(Murphy,1977)con-
#ducted an experiment comparing coronary artery bypass surgery with
#medical drug therapy as treatments for coronary artery disease. The
#experiment involved 596 patients, of whom 286 were randomly assigned
#to receive surgery, with the remaining 310 assigned to drug
#therapy. A total of 252 of those receiving surgery, and a total of
#270 of those receiving drug therapy were still alive three years
#after  Use these data to test the hypothesis that the survival
#probabilities are equal.

N_cirugia = 286
N_droga = 310
N_sobrevive_cirugia = 252
N_sobrevive_droga = 270

# Vamos a tomar como la probabilidad de supervivencia global
# p_sobre = (N_sobrevive_cirugia+N_sobreviev_droga)/(N_cirugia+N_droga).
# Si las probabilidades de supervivencia son iguales, entonce la
# p_sobrevive para cualquiera de los casos (cirugia, droga) debe ser
# consistente con p_sobre.
# Vamos a generar entonces numeros aleatorios con la probabilidad
# p_sobre y compararlo con el N_sobrevive_cirugia observado.

n_mc  = 10000
p_sobre = (N_sobrevive_cirugia + N_sobrevive_droga)/(N_cirugia + N_droga)

n_sobre_mc = np.zeros(n_mc)
for i in range(n_mc):
    r = np.random.random(N_cirugia)
    n_sobre_mc[i] = np.count_nonzero(r<p_sobre)

delta = np.abs(np.mean(n_sobre_mc) - N_sobrevive_cirugia)
p_value = np.count_nonzero(np.abs(n_sobre_mc - np.mean(n_sobre_mc))>delta)/n_mc

if p_value<0.05:
    H = 'NO'
else:
    H = 'SI'
print('Problema 67. p-value: {:.2f}. Las probabilidades de supervivencia {} son iguales.'.format(p_value, H))

plt.figure()
plt.hist(n_sobre_mc, bins=20)
plt.axvline(N_sobrevive_cirugia, color='red')
plt.savefig("tmp_67.png")

#69 The following table gives the number
#of fatal accidents o fU.S.commercialairline carriers in the 16 years from
#1980 to 1995. Do these data disprove, at the 5 percent level of
#significance, the hypothesis that the mean number of accidents in a
#year is greater than or equal to 4.5? What is the p-value? (Hint:
#First formulate a model for the number of accidents.)

accidentes = [0, 4, 4, 4, 1, 4, 2, 4, 3, 11, 6, 4, 4, 1, 4, 2]

# Se espera que el numero de accidentes anuales siga una distribucion
# possoniana de parametro lambda. Dado que par esta distribucion el
# valor medio es lambda, entonces vamos a comparar la suma total del
# numero de accidentes observados con el numero total de accidentes
# esperados si lambda fuera 4.5.

n_mc = 10000
n_acc = len(accidentes)
n_tot_acc_mc = np.zeros(n_mc)

for i in range(n_mc):
    n_tot_acc_mc[i] = np.sum(np.random.poisson(lam=4.5, size=n_acc))

p_1 = np.count_nonzero(n_tot_acc_mc<np.sum(accidentes))/n_mc
p_2 = np.count_nonzero(n_tot_acc_mc>np.sum(accidentes))/n_mc
p_value = 2.0*np.min([p_1, p_2])

if p_value<0.05:
    H = 'NO'
else:
    H = 'SI'
print('Problema 69. p-value: {:.2f}. El numero promedio de accidentes {} es mayor o igual que 4.5.'.format(p_value, H))

plt.figure()
plt.hist(n_tot_acc_mc, bins=20)
plt.axvline(np.sum(accidentes), color='red')
plt.savefig("tmp_69.png")


# you should fill in the functions in this file,
# do NOT change the name, input and output of these functions

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

# first function to fill, compute distance matrix using loops
def compute_distance_naive(X):
    N = X.shape[0]      # num of rows
    D = X[0].shape[0]   # num of cols

    M = np.zeros([N,N])
    for i in range(N):
        for j in range(N):
            xi = X[i,:]    # take ith row
            xj = X[j,:]    # take jth row

            dist = np.sqrt(np.sum((xi-xj)*(xi-xj)))  # a placetaker line

            M[i,j] = dist

    return M

# second function to fill, compute distance matrix without loops
def compute_distance_smart(X):
    N = X.shape[0]  # num of rows
    D = X[0].shape[0]  # num of cols

    # use X to create M
    M = np.zeros([N, N])
    A = np.zeros((N, N))
    B = np.zeros((N, N))
    C = np.zeros((N, N))

    xiSum = np.sum(np.multiply(X,X), axis=1)   # row vector of length N. Sum over rows

    A = np.transpose((xiSum, ) * N)   # clone vector xisum to a matrix of size NxN
    #A = (xiSum * np.ones([N,1]))

    #Xtr = np.transpose(X)

    #B = np.dot(X, Xtr)
    B = np.inner(X,X)
    C = np.transpose(A)

    Sum = np.add(np.add(A, -2 * B), C)

    M = np.sqrt(abs(Sum))   # M = sqrt(A - 2*B + C)

    return M

# third function to fill, compute correlation matrix using loops
def compute_correlation_naive(X):
    N = X.shape[0]  # num of rows
    D = X[0].shape[0]  # num of cols

    # use X to create M
    M = np.zeros([D, D])

    for i in range(D):
        for j in range(D):
            xi = X[:, i]   # take ith column
            xj = X[:, j]   # take jth column
            xih = xi - np.sum(xi).astype(float) / N   # xi - mui
            xjh = xj - np.sum(xj).astype(float) / N   # xj - muj
            sij = (np.dot(xih, xjh)).astype(float) / (N-1)    # covariance at i row j column

            stdi = np.sqrt(np.dot(xih, xih).astype(float) / (N - 1))  # sigmai
            stdj = np.sqrt(np.dot(xjh, xjh).astype(float) / (N - 1))  # sigmaj
            corr = sij.astype(float) / stdi / stdj # a placetaker line,
                    #  you have to change it to correlation between xi and xj
            M[i, j] = corr

    return M

# fourth function to fill, compute correlation matrix without loops
def compute_correlation_smart(X):
    N = X.shape[0]  # num of rows
    D = X[0].shape[0]  # num of cols

    # use X to create M
    M = np.zeros([D, D])

    mu = (np.sum(X, axis=0)).astype(float) / N    #mean vector. column sum of column vectors

    MatrixMu = mu * np.ones([N,1])    #matrix with  mu

    Xmu = X - MatrixMu

    S = (np.dot(np.transpose(Xmu),Xmu)).astype(float) / (N-1)     #covariance matrix

    varvector = (np.sum(np.multiply(Xmu, Xmu),axis=0)).astype(float) / (N-1)     #variance vector

    std = np.sqrt(varvector)    #std vector

    Denom = np.outer(std, std)     #denominator matrix

    M = np.multiply(S, np.power(Denom, -1))

    return M

def main():
    print 'starting comparing distance computation .....'
    np.random.seed(100)
    params = range(10,141,10)   # different param setting
    nparams = len(params)       # number of different parameters

    perf_dist_loop = np.zeros([10,nparams])  # 10 trials = 10 rows, each parameter is a column
    perf_dist_cool = np.zeros([10,nparams])
    perf_corr_loop = np.zeros([10,nparams])  # 10 trials = 10 rows, each parameter is a column
    perf_corr_cool = np.zeros([10,nparams])

    counter = 0

    for ncols in params:
        nrows = ncols * 10

        print "matrix dimensions: ", nrows, ncols

        for i in range(10):
            X = np.random.rand(nrows, ncols)   # random matrix

            # compute distance matrices
            st = time.time()
            dist_loop = compute_distance_naive(X)
            et = time.time()
            perf_dist_loop[i,counter] = et - st              # time difference

            st = time.time()
            dist_cool = compute_distance_smart(X)
            et = time.time()
            perf_dist_cool[i,counter] = et - st

            assert np.allclose(dist_loop, dist_cool, atol=1e-06) # check if the two computed matrices are identical all the time

            # compute correlation matrices
            st = time.time()
            corr_loop = compute_correlation_naive(X)
            et = time.time()
            perf_corr_loop[i,counter] = et - st              # time difference

            st = time.time()
            corr_cool = compute_correlation_smart(X)
            et = time.time()
            perf_corr_cool[i,counter] = et - st

            assert np.allclose(corr_loop, corr_cool, atol=1e-06) # check if the two computed matrices are identical all the time

        counter = counter + 1

    mean_dist_loop = np.mean(perf_dist_loop, axis = 0)    # mean time for each parameter setting (over 10 trials)
    mean_dist_cool = np.mean(perf_dist_cool, axis = 0)
    std_dist_loop = np.std(perf_dist_loop, axis = 0)      # standard deviation
    std_dist_cool = np.std(perf_dist_cool, axis = 0)

    plt.figure(1)
    plt.errorbar(params, mean_dist_loop[0:nparams], yerr=std_dist_loop[0:nparams], color='red',label = 'Loop Solution for Distance Comp')
    plt.errorbar(params, mean_dist_cool[0:nparams], yerr=std_dist_cool[0:nparams], color='blue', label = 'Matrix Solution for Distance Comp')
    plt.xlabel('Number of Cols of the Matrix')
    plt.ylabel('Running Time (Seconds)')
    plt.title('Comparing Distance Computation Methods')
    plt.legend()
    plt.savefig('CompareDistanceCompFig.pdf')
    # plt.show()    # uncomment this if you want to see it right way
    print "result is written to CompareDistanceCompFig.pdf"

    mean_corr_loop = np.mean(perf_corr_loop, axis = 0)    # mean time for each parameter setting (over 10 trials)
    mean_corr_cool = np.mean(perf_corr_cool, axis = 0)
    std_corr_loop = np.std(perf_corr_loop, axis = 0)      # standard deviation
    std_corr_cool = np.std(perf_corr_cool, axis = 0)

    plt.figure(2)
    plt.errorbar(params, mean_corr_loop[0:nparams], yerr=std_corr_loop[0:nparams], color='red',label = 'Loop Solution for Correlation Comp')
    plt.errorbar(params, mean_corr_cool[0:nparams], yerr=std_corr_cool[0:nparams], color='blue', label = 'Matrix Solution for Correlation Comp')
    plt.xlabel('Number of Cols of the Matrix')
    plt.ylabel('Running Time (Seconds)')
    plt.title('Comparing Correlation Computation Methods')
    plt.legend()
    plt.savefig('CompareCorrelationCompFig.pdf')
    # plt.show()    # uncomment this if you want to see it right way
    print "result is written to CompareCorrelationCompFig.pdf"

if __name__ == "__main__": main()


from __future__ import division  # floating point division
import math
import numpy as np

def mean(numbers):
    return sum(numbers)/float(len(numbers))

def stdev(numbers):
    avg = mean(numbers)
    variance = sum([pow(x-avg,2) for x in numbers])/float(len(numbers)-1)
    return math.sqrt(variance)

def sigmoid(xvec):
    """ Compute the sigmoid function """
    # Cap -xvec, to avoid overflow
    # Undeflow is okay, since it get set to zero
    xvec[xvec < -100] = -100

    vecsig = 1.0 / (1.0 + np.exp(np.negative(xvec)))

    return vecsig

def dsigmoid(xvec):
    """ Gradient of standard sigmoid 1/(1+e^-x) """
    vecsig = sigmoid(xvec)
    return vecsig * (1 - vecsig)

def l2(vec):
    """ l2 norm on a vector """
    return np.linalg.norm(vec)

def dl2(vec):
    """ Gradient of l2 norm on a vector """
    return vec

def l1(vec):
    """ l1 norm on a vector """
    return np.linalg.norm(vec, ord=1)

def threshold_probs(probs):
    """ Converts probabilities to hard classification """
    classes = np.ones(len(probs),)
    classes[probs < 0.5] = 0
    return classes

def logsumexp(a):
    """
    Compute the log of the sum of exponentials of input elements.
    Modified scipys logsumpexp implemenation for this specific situation
    """

    awithzero = np.hstack((a, np.zeros((len(a),1))))
    maxvals = np.amax(awithzero, axis=1)
    aminusmax = np.exp((awithzero.transpose() - maxvals).transpose())

    # suppress warnings about log of zero
    with np.errstate(divide='ignore'):
        out = np.log(np.sum(aminusmax, axis=1))

    out = np.add(out,maxvals)

    return out

def update_dictionary_items(dict1, dict2):
    """ Replace any common dictionary items in dict1 with the values in dict2
    There are more complicated and efficient ways to perform this task,
    but we will always have small dictionaries, so for our use case, this simple
    implementation is acceptable.
    """
    for k in dict1:
        if k in dict2:
            dict1[k]=dict2[k]


import numpy as np
from scipy import linalg
import matplotlib.pyplot as plt

def auto_corr(x, nlags, demean=True):
    """
    autocorrelation like statsmodels
    https://stackoverflow.com/a/51168178
    """

    var=np.var(x)

    if demean:
        x -= np.mean(x)

    corr = np.full(nlags+1, np.nan, np.float64)
    corr[0] = 1.

    for l in range(1, nlags+1):
        corr[l] = np.sum(x[l:]*x[:-l])/len(x)/var

    return corr

def pac_yw(x, nlags):
    """partial autocorrelation according to ywunbiased method"""

    pac = np.full(nlags+1, fill_value=np.nan, dtype=np.float64)
    pac[0] = 1.

    for l in range(1, nlags+1):
        pac[l] = ar_yw(x, l)[-1]

    return pac

def ar_yw(x, order=1, adj_needed=True, demean=True):
    """Performs autoregressor using Yule-Walker method.
    Returns:
        rho : np array
        coefficients of AR
    """
    x = np.array(x, dtype=np.float64)

    if demean:
        x -= x.mean()

    n = len(x)
    r = np.zeros(order+1, np.float64)
    r[0] = (x ** 2).sum() / n
    for k in range(1, order+1):
        r[k] = (x[0:-k] * x[k:]).sum() / (n - k * adj_needed)
    R = linalg.toeplitz(r[:-1])

    rho = np.linalg.solve(R, r[1:])
    return rho

def plot_autocorr(
        x,
        axis=None,
        plot_marker=True,
        show=True,
        legend=None,
        title=None,
        xlabel=None,
        vlines_colors=None,
        hline_color=None,
        marker_color=None,
        legend_fs=None
):

    if not axis:
        _, axis = plt.subplots()

    if plot_marker:
        axis.plot(x, 'o', color=marker_color, label=legend)
        if legend:
            axis.legend(fontsize=legend_fs)
    axis.vlines(range(len(x)), [0], x, colors=vlines_colors)
    axis.axhline(color=hline_color)

    if title:
        axis.set_title(title)
    if xlabel:
        axis.set_xlabel("Lags")

    if show:
        plt.show()

    return axis

def ccovf_np(x, y, unbiased=True, demean=True):
    n = len(x)
    if demean:
        xo = x - x.mean()
        yo = y - y.mean()
    else:
        xo = x
        yo = y
    if unbiased:
        xi = np.ones(n)
        d = np.correlate(xi, xi, 'full')
    else:
        d = n
    return (np.correlate(xo, yo, 'full') / d)[n - 1:]

def ccf_np(x, y, unbiased=True):
    """cross correlation between two time series
    # https://stackoverflow.com/a/24617594
    """
    cvf = ccovf_np(x, y, unbiased=unbiased, demean=True)
    return cvf / (np.std(x) * np.std(y))


import numpy as np

a = np.arange(24)

print(a)
# [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23]

print(a.shape)
# (24,)

print(a.ndim)
# 1

a_4_6 = a.reshape([4, 6])

print(a_4_6)
# [[ 0  1  2  3  4  5]
#  [ 6  7  8  9 10 11]
#  [12 13 14 15 16 17]
#  [18 19 20 21 22 23]]

print(a_4_6.shape)
# (4, 6)

print(a_4_6.ndim)
# 2

a_2_3_4 = a.reshape([2, 3, 4])

print(a_2_3_4)
# [[[ 0  1  2  3]
#   [ 4  5  6  7]
#   [ 8  9 10 11]]
#
#  [[12 13 14 15]
#   [16 17 18 19]
#   [20 21 22 23]]]

print(a_2_3_4.shape)
# (2, 3, 4)

print(a_2_3_4.ndim)
# 3

# a_5_6 = a.reshape([5, 6])
# ValueError: cannot reshape array of size 24 into shape (5,6)

print(a.reshape(4, 6))
# [[ 0  1  2  3  4  5]
#  [ 6  7  8  9 10 11]
#  [12 13 14 15 16 17]
#  [18 19 20 21 22 23]]

print(a.reshape(2, 3, 4))
# [[[ 0  1  2  3]
#   [ 4  5  6  7]
#   [ 8  9 10 11]]
#
#  [[12 13 14 15]
#   [16 17 18 19]
#   [20 21 22 23]]]

print(np.reshape(a, [4, 6]))
# [[ 0  1  2  3  4  5]
#  [ 6  7  8  9 10 11]
#  [12 13 14 15 16 17]
#  [18 19 20 21 22 23]]

print(np.reshape(a, [2, 3, 4]))
# [[[ 0  1  2  3]
#   [ 4  5  6  7]
#   [ 8  9 10 11]]
#
#  [[12 13 14 15]
#   [16 17 18 19]
#   [20 21 22 23]]]

# print(np.reshape(a, [5, 6]))
# ValueError: cannot reshape array of size 24 into shape (5,6)

print(a.reshape(4, 6))
# [[ 0  1  2  3  4  5]
#  [ 6  7  8  9 10 11]
#  [12 13 14 15 16 17]
#  [18 19 20 21 22 23]]

# print(np.reshape(a, 4, 6))
# ValueError: cannot reshape array of size 24 into shape (4,)

print(a.reshape([4, 6], order='C'))
# [[ 0  1  2  3  4  5]
#  [ 6  7  8  9 10 11]
#  [12 13 14 15 16 17]
#  [18 19 20 21 22 23]]

print(a.reshape([4, 6], order='F'))
# [[ 0  4  8 12 16 20]
#  [ 1  5  9 13 17 21]
#  [ 2  6 10 14 18 22]
#  [ 3  7 11 15 19 23]]

print(a.reshape([2, 3, 4], order='C'))
# [[[ 0  1  2  3]
#   [ 4  5  6  7]
#   [ 8  9 10 11]]
#
#  [[12 13 14 15]
#   [16 17 18 19]
#   [20 21 22 23]]]

print(a.reshape([2, 3, 4], order='F'))
# [[[ 0  6 12 18]
#   [ 2  8 14 20]
#   [ 4 10 16 22]]
#
#  [[ 1  7 13 19]
#   [ 3  9 15 21]
#   [ 5 11 17 23]]]

print(np.reshape(a, [4, 6], order='F'))
# [[ 0  4  8 12 16 20]
#  [ 1  5  9 13 17 21]
#  [ 2  6 10 14 18 22]
#  [ 3  7 11 15 19 23]]

# print(a.reshape([4, 6], 'F'))
# TypeError: 'list' object cannot be interpreted as an integer

print(np.reshape(a, [4, 6], 'F'))
# [[ 0  4  8 12 16 20]
#  [ 1  5  9 13 17 21]
#  [ 2  6 10 14 18 22]
#  [ 3  7 11 15 19 23]]

print(a.reshape([4, -1]))
# [[ 0  1  2  3  4  5]
#  [ 6  7  8  9 10 11]
#  [12 13 14 15 16 17]
#  [18 19 20 21 22 23]]

print(a.reshape([2, -1, 4]))
# [[[ 0  1  2  3]
#   [ 4  5  6  7]
#   [ 8  9 10 11]]
#
#  [[12 13 14 15]
#   [16 17 18 19]
#   [20 21 22 23]]]

# print(a.reshape([2, -1, -1]))
# ValueError: can only specify one unknown dimension

# print(a.reshape([2, -1, 5]))
# ValueError: cannot reshape array of size 24 into shape (2,newaxis,5)

a = np.arange(8)
print(a)
# [0 1 2 3 4 5 6 7]

a_2_4 = a.reshape([2, 4])
print(a_2_4)
# [[0 1 2 3]
#  [4 5 6 7]]

print(np.shares_memory(a, a_2_4))
# True

a[0] = 100
print(a)
# [100   1   2   3   4   5   6   7]

print(a_2_4)
# [[100   1   2   3]
#  [  4   5   6   7]]

a_2_4[0, 0] = 0
print(a_2_4)
# [[0 1 2 3]
#  [4 5 6 7]]

print(a)
# [0 1 2 3 4 5 6 7]

a_2_4_copy = a.reshape([2, 4]).copy()
print(a_2_4_copy)
# [[0 1 2 3]
#  [4 5 6 7]]

print(np.shares_memory(a, a_2_4_copy))
# False

a[0] = 100
print(a)
# [100   1   2   3   4   5   6   7]

print(a_2_4_copy)
# [[0 1 2 3]
#  [4 5 6 7]]

a_2_4_copy[0, 0] = 200
print(a_2_4_copy)
# [[200   1   2   3]
#  [  4   5   6   7]]

print(a)
# [100   1   2   3   4   5   6   7]

a = np.arange(6).reshape(2, 3)
print(a)
# [[0 1 2]
#  [3 4 5]]

a_step = a[:, ::2]
print(a_step)
# [[0 2]
#  [3 5]]

print(a_step.reshape(-1))
# [0 2 3 5]

print(np.shares_memory(a_step, a_step.reshape(-1)))
# False

np.info(a)
# class:  ndarray
# shape:  (2, 3)
# strides:  (24, 8)
# itemsize:  8
# aligned:  True
# contiguous:  True
# fortran:  False
# data pointer: 0x7fb49bf71950
# byteorder:  little
# byteswap:  False
# type: int64

np.info(a_step)
# class:  ndarray
# shape:  (2, 2)
# strides:  (24, 16)
# itemsize:  8
# aligned:  True
# contiguous:  False
# fortran:  False
# data pointer: 0x7fb49bf71950
# byteorder:  little
# byteswap:  False
# type: int64

np.info(a_step.reshape(-1))
# class:  ndarray
# shape:  (4,)
# strides:  (8,)
# itemsize:  8
# aligned:  True
# contiguous:  True
# fortran:  True
# data pointer: 0x7fb49e162210
# byteorder:  little
# byteswap:  False
# type: int64

a = np.arange(8).reshape(2, 4)
print(a)
# [[0 1 2 3]
#  [4 5 6 7]]

a_step = a[:, ::2]
print(a_step)
# [[0 2]
#  [4 6]]

print(a_step.reshape(-1))
# [0 2 4 6]

print(np.shares_memory(a_step, a_step.reshape(-1)))
# True

np.info(a)
# class:  ndarray
# shape:  (2, 4)
# strides:  (32, 8)
# itemsize:  8
# aligned:  True
# contiguous:  True
# fortran:  False
# data pointer: 0x7fb49e0e1c40
# byteorder:  little
# byteswap:  False
# type: int64

np.info(a_step)
# class:  ndarray
# shape:  (2, 2)
# strides:  (32, 16)
# itemsize:  8
# aligned:  True
# contiguous:  False
# fortran:  False
# data pointer: 0x7fb49e0e1c40
# byteorder:  little
# byteswap:  False
# type: int64

np.info(a_step.reshape(-1))
# class:  ndarray
# shape:  (4,)
# strides:  (16,)
# itemsize:  8
# aligned:  True
# contiguous:  False
# fortran:  False
# data pointer: 0x7fb49e0e1c40
# byteorder:  little
# byteswap:  False
# type: int64


import scipy.optimize

def newton(f, fp, x0, delta=1e-4):
	cnt = 0
	x = x0
	print('x0 = %.10lf' % x0)
	while abs(f(x)) > delta or abs(x - x0) > delta:
		s = f(x) / fp(x)
		x0, x = x, x - s
		lbd = 1
		while abs(f(x)) >= abs(f(x0)):
			x = x0 - lbd * s
			lbd /= 2
		cnt += 1
		print('x%d = %.10lf, lambda = %.10lf' % (cnt, x, lbd))
	return x

def gen22():
	def func1(x):
		return x**3 - x - 1
	def func1p(x):
		return 3*x*x - 1
	def func2(x):
		return -x**3 + 5 * x
	def func2p(x):
		return -3*x*x + 5

	print('t22 result'.center(30, '-'))
	x = newton(func1, func1p, 0.6)
	print('final result: x = %.10lf, f(x) = %.10lf' % (x, func1(x)))
	print('scipy gives: %.10lf' % (scipy.optimize.fsolve(func1, 0.6)))
	x = newton(func2, func2p, 1.35)
	print('final result: x = %.10lf, f(x) = %.10lf' % (x, func2(x)))
	print('scipy gives: %.10lf' % (scipy.optimize.fsolve(func2, 1.35)))
	print('end t22'.center(30, '-')+'\n')

if __name__ == '__main__':
	gen22()


###____________________ Interpolation & Curve Fitting ______________________###

# Based on: pybonacci.org/2013/08/15/ajuste-e-interpolacion-unidimensionales-basicos-en-python-con-scipy/

import numpy as np
import matplotlib.pyplot as plt
# plt.ion()

###____________________________ Interpolation  _____________________________###
#_______________________ Airplane Polar Data _______________________#
# Supongamos que tenemos una serie de puntos que representan los datos de un
# cierto experimento. Como ejemplo, vamos a cargar los datos de la polar de un
# avión que están en el archivo polar.dat.

datos = np.loadtxt("../data/polar.dat")
C_L = datos[0]
C_D = datos[1]

# Representamos los datos # (pista: usar `mew=2`, "marker edge width 2", para que las cruces se vean mejor):
plt.plot(C_D, C_L, '^', mew=2, label="Datos reales")
plt.xlabel("$C_D$")
plt.ylabel("$C_L$")
plt.legend()
plt.show()

# Identify the stall region # Hallando el índice del máximo valor de 𝐶𝐿 podemos descartar los datos fuera de la región de entrada en pérdida, y para eso necesitamos la función np.argmax
    # tip: argmax function: finds index of max value of a set of data
idx_stall = np.argmax(C_L)
# Identify the C_L_MAX
print((C_L[idx_stall]))

# Representamos los datos dentro y fuera del modelo
plt.plot(C_D[:idx_stall + 1], C_L[:idx_stall + 1], '^', mew=2, label="Datos reales")
plt.plot(C_D[idx_stall + 1:], C_L[idx_stall + 1:], 'x', mfc='none', label="Fuera del modelo")
plt.xlabel("$C_D$")
plt.ylabel("$C_L$")
plt.legend(loc=4)
plt.show()

# Hay dos cosas que nos pueden interesar:
    # Como solo tenemos puntos intermedios, no tenemos posibilidad de evaluar,
    # por ejemplo, CL para un CD que no esté en los datos. Si interpolamos la
    # curva ya podemos hacerlo. Sabemos que, fuera de la región de entrada en
    # pérdida, la polar tiene forma parabólica. Si ajustamos la curva podemos hallar
    # el CD0 y el k

### Interpolation ###
from scipy import interpolate
# ## easy example with sin(x) ##
x_i = [0.0, 0.9, 1.8, 2.7, 3.6, 4.4, 5.3, 6.2, 7.1, 8.0]
y_i = [0.0, 0.8, 1.0, 0.5, -0.4, -1.0, -0.8, -0.1, 0.7, 1.0]
plt.plot(x_i, y_i, 'd', mew=2)
plt.show()

# # Para crear una función interpolante utilizaremos el objeto InterpolatedUnivariateSpline
# # del paquete interpolate. A este objeto solo hay que pasarle los puntos de
# # interpolación y el grado, y generará un spline.
f_interp = interpolate.InterpolatedUnivariateSpline(x_i, y_i, k=2)
# # ¿Cómo obtengo los puntos desde aquí? El resultado que hemos obtenido es una
# # función y admite como argumento la x.
print(f_interp(np.pi / 2))

x = np.linspace(0, 8)
y_interp = f_interp(x)

plt.plot(x_i, y_i, 'x', mew=2)
plt.plot(x, y_interp)
plt.show()

## Exercise: Crear una función interpolante 𝐶𝐷=𝑓(𝐶𝐿) usando splines de grado 2
# y representarla. Utiliza solo los datos que resultan de haber eliminado la región
# de entrada en pérdida. y ten en cuenta que la 𝑥 y la 𝑦 para este caso están
# cambiadas de sitio. ##
# 1. Crea un polinomio interpolante usando los valores que encajan en el modelo parabólico.
# 2. Crea un dominio de CL entre C_L.min() y C_L.max().
# 3. Halla los valores interpolados de CD en ese dominio.
# 4. Representa la función y los puntos.
f_C_D = interpolate.InterpolatedUnivariateSpline(C_L[:idx_stall + 1], C_D[:idx_stall + 1], k=2) # I put CL first because if im taking a parabolic I am acc seeing the graph rotated 90 deg
C_L_domain = np.linspace(C_L.min(), C_L.max())
C_D_domain = f_C_D(C_L_domain)

with plt.style.context('seaborn-notebook'):
    plt.title('Interpolated Polar Data', fontsize = 25)
    plt.plot(C_D[:idx_stall + 1], C_L[:idx_stall + 1], '^', mew=2, label="Datos reales")
    # plt.plot(C_D[idx_stall + 1:], C_L[idx_stall + 1:], 'x', mfc='none', label="Fuera del modelo")
    plt.plot(C_D_domain, C_L_domain, label="Interpolated data", color="purple")
    plt.xlabel("$C_D$")
    plt.ylabel("$C_L$")
    plt.legend(loc=4)
    plt.grid()
    plt.show()

#_______________________ Runge Phenomenon _______________________#
def runge(x):
    return 1 / (1 + x ** 2)

# Número de nodos
N = 11  # Nodos de interpolación

# Seleccionamos los nodos
xp = np.linspace(-5, 5, N)   # -5, -4, -3, ..., 3, 4, 5
fp = runge(xp)

# Seleccionamos la x para interpolar
x = np.linspace(-5, 5, 200)
# Calculamos el pol interp de Lagrange
lag_pol = interpolate.lagrange(xp, fp)
y = lag_pol(x)

with plt.style.context('seaborn-notebook'):
    plt.plot(x, y, label='interpolation')
    plt.plot(xp, fp, 'o', label='samples')
    plt.plot(x, runge(x), label='real')
    plt.legend(loc='upper center')
    plt.show()

# importamos el polinomio de chebychev #
from numpy.polynomial import chebyshev
N = 11  # Nodos de interpolación

coeffs_cheb = [0] * N + [1]  # Solo queremos el elemento 11 de la serie

T11 = chebyshev.Chebyshev(coeffs_cheb, [-5, 5])
xp_ch = T11.roots()

fp = runge(xp_ch)

x = np.linspace(-5, 5, 200)

lag_pol = interpolate.lagrange(xp_ch, fp)
# lag_pol = interpolate.InterpolatedUnivariateSpline(xp_ch, fp, k=2) # Just to check

y = lag_pol(x)
with plt.style.context('seaborn-notebook'):
    plt.plot(x, y, label='interpolation')
    plt.plot(xp_ch, fp, 'o', label='samples')
    plt.plot(x, runge(x), label='real')
    plt.legend()
    plt.show()

###_______________________________ Fitting _________________________________###
 # El ajuste funciona de manera totalmente distinta: obtendremos una curva que no
 # tiene por qué pasar por ninguno de los puntos originales, pero que a cambio
 # tendrá una expresión analítica simple.
from scipy.optimize import curve_fit
# # ## easy example with random quadratic function ##
#  # Vamos otra vez a generar unos datos para ver cómo funcionaría, del tipo:
#  # $$y(x) = x^2 - x + 1 + \text{Ruido}$$
x_i = np.linspace(-2, 3, num=10)
y_i = x_i ** 2 - x_i + 1 + 0.5 * np.random.randn(10) # np.random.randn(10) Creates an array with 10 components that are random numbers
plt.plot(x_i, y_i, 'x', mew=2)
plt.show()

#  # Vamos a utilizar la función polynomial.polyfit, que recibe los puntos de
#  # interpolación y el grado del polinomio. El resultado serán los coeficientes
#  # del mismo, en orden de potencias crecientes.
def poldeg2(x, a, b, c):
    return a * x**2 + b * x + c

val, cov = curve_fit(poldeg2, x_i, y_i)
a, b, c = val

x = np.linspace(-2, 3)

y_fit = poldeg2(x, a, b, c)

with plt.style.context('seaborn-notebook'):
    l, = plt.plot(x, y_fit)
    plt.plot(x_i, y_i, 'x', mew=2, c=l.get_color())
    plt.grid()
    plt.show()

## Exercise: Si modelizamos la polar como: CD=CD0+k*CL**2,
# hallar los coeficientes CD0 y k ##
def model(x, A, C):
    return A*x**2 + C

popt, lqdata = curve_fit(model, C_L[:idx_stall+1], C_D[:idx_stall+1])
A, B = popt
# To compute one standard deviation errors on the parameters use:
perr = np.sqrt(np.diag(lqdata))

x = np.linspace(-1.5, 1.5, 50)
y = model(x, A, B)

with plt.style.context('seaborn-notebook'):
    plt.plot(y, x)
    plt.plot(C_D[:idx_stall + 1], C_L[:idx_stall + 1], 'x', mew=2, label="Datos reales")
    plt.plot(C_D[idx_stall + 1:], C_L[idx_stall + 1:], 'o', mfc='none', label="Fuera del modelo")
    plt.xlabel("$C_D$")
    plt.ylabel("$C_L$")
    plt.legend(loc=4)
    plt.grid()
    plt.show()


'''
k-Means python implementation
'''

import pandas as pd
import numpy as np
import random as rd
import matplotlib.pyplot as plt
from sklearn import datasets
from collections import defaultdict
import seaborn as sns

def euclidean_distance(center,point):
    dist = np.linalg.norm(center-point)
    return dist

### K-Means Algorithm

def kmeans(data,k,iters) :

    ##Step 1: Choose number K of clusters
    K = k

    ##Step 2: Select K random points from the data as centroids

    random_indices = np.random.choice(len(data), size=K, replace=False)
    centroids = data[random_indices,:]

    ##Step 3: Assign all points to the closest cluster centroid

    counter_1 = 0 #count number of iters where centroids don't change
    iteration = 0
    while True:

        # for every point calculate distance from every centroid
        distances = defaultdict(dict)

        for i,center in enumerate(centroids):
            for j,point in enumerate(data):
                distances[j][i] = euclidean_distance(center,point)

        # assign every point to the closest centroid
        points = {}
        for i in range(len(data)):
            points[i] = list({k: v for k, v in sorted(distances[i].items(), key=lambda item: item[1])}.keys())[0]

        # group points of every centroid
        cluster_points = {}
        for i in range(K):
             cluster_points[i] = list({k for (k,v) in points.items() if v==i})

        ##Step 4: Recompute the centroids of newly transformed clusters as the mean of their points
        centroids_cached = [tuple(c) for c in centroids]
        centroids = []
        for i in range(K):
            centroids.append(np.array([data[i] for i in cluster_points[i]]).transpose().mean(axis=1))

        ##Step 5: Repeat steps 3,4 until a stopping criterion is met (max iterations or centroids remain the same for 3 iterations)

        if ([tuple(c) for c in centroids] == centroids_cached and counter_1==3):
            break
            return centroids,cluster_points
        elif ([tuple(c) for c in centroids] == centroids_cached and counter_1<3):
            counter_1 +=1

        if iteration == iters:
            break
        else:
            iteration += 1

    points = np.array([tup[1] for tup in points.items()]) #return cluster indices per point

    return centroids, cluster_points, points

### Find K using Elbow Method

def inertia (centroids,cluster_points,data):
    cost=0
    for i,centroid in enumerate(centroids):
        for point in cluster_points[i]:
            cost += euclidean_distance(centroid,data[point])
    return cost

def elbow_method(data,k):

    cost_list = []
    for k in range(1, k+1):

        centroids, cluster_points,points = kmeans(data, k,iters = 20)
        cost = inertia(centroids, cluster_points,data)
        cost_list.append(cost)

    sns.lineplot(x=range(1,k+1), y=cost_list, marker='o')
    plt.xlabel('k')
    plt.ylabel('WCSS')
    plt.show()

if __name__ == "__main__":

    X, y = datasets.make_blobs(n_samples=100, centers=3, n_features=3,random_state=0)
    K=3
    iters = 20
    centroids, cluster_points,points = kmeans(X,K,iters)


# quadrature.py
# -------------------------------------------------------------------------
# Integrate two functions using quad.
# -------------------------------------------------------------------------
import numpy as np
from scipy.integrate import quad

#%% Integrate a built-in function.
upper_limit = np.linspace(0,3*np.pi,16)
cos_integral = np.zeros(upper_limit.size)
for i in range(upper_limit.size):
    cos_integral[i], error = quad(np.cos, 0, upper_limit[i])

#%% Now integrate a user-defined function.
def integrand(x): return np.exp(-x**2/2)

upper_limit = np.linspace(0, 5, 51)
gauss_integral = np.zeros(upper_limit.size)
for i in range(upper_limit.size):
    gauss_integral[i], error = quad(integrand, 0, upper_limit[i])