# -*- coding: utf-8 -*-
"""
Created on Wed Nov 24 17:10:18 2021

@author: pvghd
"""
import numpy as np
import pandas as pd
from datetime import date
import pandas_datareader as pdr
import matplotlib.pyplot as plt

# How does the portfolio.cov() function work?

portfolio = pd.DataFrame( [(1,3), (2,5), (3,7), (4,2), (5,4), (6,1)],
                         columns=['a', 'b'] )

covariance = portfolio.cov()
print( covariance )

# Now let us recreate the function:

a = [1,2,3,4,5,6]
b = [3,5,7,2,4,1]

"""
To calculate the pairwise covariance. You take the mean value of the array
and subtract it from each of the components. Then you multiply both arrays
component by component and sum them. You divide everything by the number
of components of the arrays (which must have the same dimension) minus one.
The minus one comes from the Bessel correction.
Just like in http://nambis.bplaced.net/nambis/SMDV/SMDV_Kapitel7.pdf p.7
(with corrections)
"""

def covariance_func(a,b):
    a_2 = a - np.mean(a)
    b_2 = b - np.mean(b)
    return np.sum( a_2 * b_2 ) /  (len(a)-1)

print()
print('variance a =', covariance_func(a, a) )
print('covariance ab =', covariance_func(a, b) )
print('covariance ab =', covariance_func(b, a) )
print('variance b =', covariance_func(b, b) )

a_2 = a - np.mean(a)
b_2 = b - np.mean(b)


import numpy as np
import matplotlib.pylab as plt

def function_1(x):
    return 0.01*x**2 + 0.1*x

x = np.arange(0.0, 20.0, 0.1) # 0에서 20까지 0.1 간격의 배열 x를 만든다.
y = function_1(x)

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

def numerical_diff(f, x):
    h = 1e-4 # 0.0001
    return (f(x+h) - f(x-h)) / (2*h)

# print(numerical_diff(function_1, 5))
#
# print(numerical_diff(function_1, 10))

# 편미분
def function_2(x):
    return x[0]**2 + x[1]**2

def function_tmp1(x0):
    return x0*x0 + 4.0**2.0

# print(numerical_diff(function_tmp1, 3.0))

def function_tmp2(x1):
    return 3.0**2.0 + x1*x1

# print(numerical_diff(function_tmp2, 4.0))

# 기울기
def numerical_gradient(f, x):
    h = 1e-4  # 0.0001
    grad = np.zeros_like(x)  # x와 형상이 같은 배열을 생성

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

        # f(x+h) 계산
        x[idx] = float(tmp_val) + h
        fxh1 = f(x)

        # f(x-h) 계산
        x[idx] = tmp_val - h
        fxh2 = f(x)

        grad[idx] = (fxh1 - fxh2) / (2 * h)
        x[idx] = tmp_val  # 값 복원

    return grad

# print(_numerical_gradient_no_batch(function_2, np.array([3.0, 4.0])))
# print(_numerical_gradient_no_batch(function_2, np.array([0.0, 2.0])))

# 경사 하강법
def gradient_descent(f, init_x, lr=0.01, step_num=100):
    x = init_x

    for i in range(step_num):
        grad = numerical_gradient(f,x)
        x -= lr *grad
    return x

init_x = np.array([-3.0, 4.0])
print(gradient_descent(function_2, init_x=init_x, lr=0.1, step_num=100))

# 학습률이 너무 큰 예 : lr=10.0
init_x = np.array([-3.0, 4.0])
print(gradient_descent(function_2, init_x=init_x, lr=10.0, step_num=100))

# 학습률이 너무 작은 예 : lr=1e-10
init_x = np.array([-3.0, 4.0])
print(gradient_descent(function_2, init_x=init_x, lr=1e-10, step_num=100))


import numpy as np
import matplotlib.pyplot as plt

def complexiter(max_iter, x, y):
    """Finds whether or not a point on the complex xy plane remains bounded
    in the absolute value of z squared after iterating the equation z = z*z +c
    or runs off to infinity within the maximum number of interations

    Parameters:
    max_iter - Integer
    x - numeric sequence of form np.linspace(start, end, number of breakpoints)
    y - numeric sequence of form np.linspace(start, end, number of breakpoints)

    Returns:
    True if iterations at that point remain bounded, False otherwise
    """

    c = x[:,np.newaxis] + y[np.newaxis,:]*1j
    #start from 0
    z = 0

    for i in range(max_iter):
        z = z*z + c
    c_set = (abs(z) < 2)
    return(c_set)

#complex plane from -2 to 2
x = np.linspace(-2, 2, 500)
y = np.linspace(-2, 2, 500)
#determining which points in the complex plane remain bounded within 200 iterations
c_set = complexiter(200, x, y)

#plotting the image
plt.imshow(c_set.T, extent=[-2, 2, -2, 2])
plt.title("Mandelbrot Set")
plt.xlabel("x")
plt.ylabel("y")
plt.gray()
plt.show()

###now zooming into a portion of the image and trying again
#complex plane from -1 to 0
x = np.linspace(-1, 0, 500)
y = np.linspace(-1, 0, 500)
#determining which points in the complex plane remain bounded within 200 iterations
c_set = complexiter(200, x, y)

#plotting the image
plt.imshow(c_set.T, extent=[-1, 0, -1, 0])
plt.title("Zoomed In Madelbrot Set")
plt.xlabel("x")
plt.ylabel("y")
plt.gray()
plt.show()


import numpy as np
import skimage
import utils
import pathlib

def otsu_thresholding(im: np.ndarray) -> int:
    """
        Otsu's thresholding algorithm that segments an image into 1 or 0 (True or False)
        The function takes in a grayscale image and outputs a boolean image

        args:
            im: np.ndarray of shape (H, W) in the range [0, 255] (dtype=np.uint8)
        return:
            (int) the computed thresholding value
    """
    assert im.dtype == np.uint8
    ### START YOUR CODE HERE ### (You can change anything inside this block)
    # You can also define other helper functions

    # Number of intensity levels
    L = 256

    # Compute normalized histogram
    p, _ = np.histogram(im, bins=L, density=True)

    # Compute the cumulative sums P_1(k) for k = 0, 1, 2,.., L-1, the cumulative means m_k for k = 0, 1, 2,.., L-1 and the global mean m_g
    P_1 = np.zeros(L)
    m = np.zeros(L)
    m_g = 0

    for k in range(L):
        # Computes the global mean
        m_g += (k * p[k])

        for i in range(k + 1):
            # Computes the cumulative sums
            P_1[k] += p[i]

            # Computes the cumulative means
            m[k] += (i * p[i])

    var = np.zeros(L)
    threshold = 128
    max_var = 0

    # Compute the between-class variance o^2 = (m_g * P_1 - m_k)^2/(P_1 * (1 - P_1)) for k = 0, 1, 2,.., L-1 and find the k value for the max variance
    for k in range(L):
        var[k] = ((m_g * P_1[k] - m[k])**2)/(P_1[k]*(1-P_1[k]))
        if (var[k] > max_var):
            max_var = var[k]
            threshold = k

    return threshold
    ### END YOUR CODE HERE ###

if __name__ == "__main__":
    # DO NOT CHANGE
    impaths_to_segment = [
        pathlib.Path("thumbprint.png"),
        pathlib.Path("polymercell.png")
    ]
    for impath in impaths_to_segment:
        im = utils.read_image(impath)
        threshold = otsu_thresholding(im)
        print("Found optimal threshold:", threshold)

        # Segment the image by threshold
        segmented_image = (im >= threshold)
        assert im.shape == segmented_image.shape, \
            "Expected image shape ({}) to be same as thresholded image shape ({})".format(
                im.shape, segmented_image.shape)
        assert segmented_image.dtype == np.bool, \
            "Expected thresholded image dtype to be np.bool. Was: {}".format(
                segmented_image.dtype)

        segmented_image = utils.to_uint8(segmented_image)

        save_path = "{}-segmented.png".format(impath.stem)
        utils.save_im(save_path, segmented_image)


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

from sal_timer import timer

def get_data():
    df = pd.read_csv(DATA_PATH)
    print(df.head(5))

    return df

def scatter_training_data(df):
    plt.plot('exam1', 'exam2', '*', c='r', data=df[df.y == 0])
    plt.plot('exam1', 'exam2', '+', c='b', data=df[df.y == 1])
    plt.legend(['Not Admitted', 'Admitted'])
    plt.xlabel('Exam 1 score')
    plt.ylabel('Exam 2 score')
    plt.title('Scatter plot of training data')
    plt.show()

def sigmoid(X: np.ndarray) -> np.ndarray:
    '''
    For large positive values of x, the sigmoid should be close to 1, while for large negative values, the sigmoid should be close to 0.
    Evaluating sigmoid(0) should give you exactly 0.5.

    this method work with vectors and matrices. For a matrix, method perform the sigmoid function on every element.

    Sigmoid function formula
    Math :
        g(z) = (1) / (1 + e^(-z))

    LaTex :
        g(z) = \frac{1}{1 + e^{-z}}
    '''
    return 1.0 / ( 1.0 + np.exp(-X))

def hypothesis(X: np.ndarray, theta: np.ndarray) -> np.ndarray:
    '''
    calculate the cost for given X and y, the following shows and example of a single dimensional X
    theta   = Vector of theta;
    X       = Row of X's np.zeros((m, j));

    where:
        m number of samples;
        j is the no. of features;

    Return ...........

    logistic regression hypothesis formula
    Math :
        1. h(x) = g(theta^T * x); g is sigmoid function.
        2. g(z) = (1) / (1 + e^(-z))

    LaTex :
        1. h_{\theta} (x) = g( \theta^{T} x)
        2. g(z) = \frac{1}{1 + e^{-z}}
    '''

    # https://www.youtube.com/watch?v=okpqeEUdEkY
    # np.dot(theta.T, X)

    return sigmoid(np.dot(X, theta))

def cost_function(theta: np.ndarray, X: np.ndarray, y: np.ndarray) -> np.ndarray:
    '''
    calculate the cost for given X and y, the following shows and example of a single dimensional X
    theta   = Vector of theta;
    X       = Row of X's np.zeros((m, j));
    y       = Actual of y's np.zeros((m, 1));

    where:
        m number of samples;
        j is the no. of features;

    Return ...........

    Cost function formula
    Math :
        J = (1 / m) * sum(-y .* log(h) - (1 - y) .* log(1 - h))
    LaTex:
        J(\theta) = \frac{1}{m} \sum_{i=1}^{m} [-y^{(i)}log(h_{\theta}(x^{(i)})) - (1 - y^{(i)})log(1 - h_{\theta}(x^{(i)}))]
    '''

    try:
        m = len(y)
        h = hypothesis(X, theta)

        # print('y : ', y.shape)
        # print('h : ', h.shape)
        # print('log(h) : ', np.log(h))
        # print(
        #     np.multiply(
        #         -y,
        #         np.log(h)
        #         ).shape
        #     )

        return (1. / m) * np.sum( np.multiply(-y, np.log(h ) - np.multiply(1 - y, np.log(1 - h)) ))
    except Exception as err:
        print('cost_function(...)  ==>  {err}.'.format(err=err))
        return 0.

def gradient_function(theta: np.ndarray, X: np.ndarray, y: np.ndarray) -> np.ndarray:
    '''
    X    = Matrix of X with added bias units;
    y    = Vector of Y;
    theta=Vector of thetas np.random.randn(j,1);

    Return ...........

    Math:
        grad = (1 / m) * sum((h - y) .* X)

    LaTex:
        \frac{\partial J(\theta)}{\partial \theta_{j}} = \frac{1}{m} \sum_{i=1}^{m} (h_{\theta}(x^{(i)}) - y^{(i)})x_{j}^{(i)}
    '''
    try:
        m = len(y)
        h = hypothesis(X, theta)
        return (1. / m) * np.sum(np.multiply(h - y, X), axis=0)
    except Exception as err:
        print('gradient_function(...)  ==>  {err}.'.format(err=err))
        return np.zeros(theta.shape)

# TODO ... https://www.youtube.com/watch?v=QOne3o-7_DQ
def gradient_descent(theta: np.ndarray, X: np.ndarray, y: np.ndarray, alpha: int, num_iterations: int) -> np.ndarray:
    '''
    X    = Matrix of X with added bias units;
    y    = Vector of Y;
    theta=Vector of thetas np.random.randn(j,1);
    learning_rate;
    iterations = no of iterations;

    Returns the final theta vector and array of cost history over no of iterations
    '''
    m = len(y)
    h = hypothesis(X, theta)
    theta = theta - alpha / m * np.dot((h - y), X)

    ####
    # cost_history = [] # plot
    # theta = theta + alpha * gradient

    ####
    # https://gist.github.com/sagarmainkar/41d135a04d7d3bc4098f0664fe20cf3c

    # m = len(y)
    # cost_history = np.zeros(iterations)
    # theta_history = np.zeros((iterations,2))
    # for it in range(iterations):

    #     prediction = np.dot(X,theta)

    #     theta = theta -(1/m)*learning_rate*( X.T.dot((prediction - y)))
    #     theta_history[it,:] =theta.T
    #     cost_history[it]  = cal_cost(theta,X,y)

    # return theta, cost_history, theta_history

def sigmoid_test():
    x = np.array([
        99999,
        100,
        10,
        4,
        2,

        0.01,
        0.1,
        0,
        -0.1,
        -0.01,

        -2,
        -4,
        -10,
        -100,
        -99999,
    ])

    print(pd.DataFrame({
        'x': x,
        'sig': sigmoid(x),
        'real': np.array([1.00,1.00,0.99,0.98,0.88,0.50,0.52,0.50,0.47,0.49,0.11,0.17,0.45,0.37,0.00])
    }))

    x = 4
    z = sigmoid(x)
    print(z)

def hypothesis_test():
    X = np.array([34.62365962451697, 78.0246928153624])
    theta = np.array([1, 1])
    y = np.array([0])
    # yy = hypothesis(X, theta)
    # print(yy)

    # ...
    X = np.array([7, 2])
    theta = np.array([3, 4])
    y = hypothesis(X, theta)
    print('X : ', X.shape)
    print('theta : ', theta.shape)
    print('y : ', y.shape)
    print(y)

    # ...
    X = np.array([1, 2])
    theta = np.array([3, 4]).T
    y = hypothesis(X, theta)
    print('X : ', X.shape)
    print('theta : ', theta.shape)
    print('y : ', y.shape)
    print(y)

    # ...
    X = np.array([1, 2]).T
    theta = np.array([3, 4])
    y = hypothesis(X, theta)
    print('X : ', X.shape)
    print('theta : ', theta.shape)
    print('y : ', y.shape)
    print(y)

    # ...
    X = np.array([[1, 2], [1, 2], [1, 2]])
    theta = np.array([3, 4])
    y = hypothesis(X, theta)
    print('X : ', X.shape)
    print('theta : ', theta.shape)
    print('y : ', y.shape)
    print(y)

    # ... ERROR
    # X = np.array([[1, 2], [1, 2], [1, 2]]).T
    # theta = np.array([3, 4])
    # y = hypothesis(X, theta)
    # print('X : ', X.shape)
    # print('theta : ', theta.shape)
    print('y : ', y.shape)
    # print(y)

    # ...
    X = np.array([[1, 2], [1, 2], [1, 2]])
    theta = np.array([3, 4]).T
    y = hypothesis(X, theta)
    print('X : ', X.shape)
    print('theta : ', theta.shape)
    print('y : ', y.shape)
    print(y)

    # ...
    X = np.array([[1, 2], [1, 2], [1, 2]]).T
    theta = np.array([3, 4]).T
    y = hypothesis(theta, X)  # <<===== replace in space !!!
    print('X : ', X.shape)
    print('theta : ', theta.shape)
    print('y : ', y.shape)
    print(y)

def cost_function_test():
    # ...
    X = np.array([[1, 2], [1, 2], [1, 2]])
    m, n = X.shape
    theta = np.array([[3], [4]])
    # y = np.array([1., 1., 0.])
    y = np.array([[1.], [1.], [0.]])

    # ...
    J = cost_function(theta, X, y)
    print(J)

def gradient_function_test():
    # ...
    X = np.array([[1, 2], [1, 2], [1, 2]])
    theta = np.array([[3], [4]])
    # y = np.array([1., 1., 0.])
    y = np.array([[1.], [1.], [0.]])

    # ...
    grad = gradient_function(theta, X, y)
    print(grad)

def test_1():
    # ...
    df = get_data()
    X, y = df.iloc[:, :-1].values, df.iloc[:, -1].values
    m, n = X.shape
    X = np.hstack([np.ones([m, 1]), X])
    initial_theta = np.zeros([n + 1, 1])
    y = np.resize(y, (len(y), 1))
    # y = y.reshape((len(y), 1))

    # ...
    print('X : ', X.shape)
    print('Theta : ', initial_theta.shape)
    print('y : ', y.shape)

    # ...
    cost = cost_function(initial_theta, X, y)
    print('\n'*2)
    print('Cost at initial theta (zeros): ', cost)
    print('Expected cost (approx): 0.693')

    grad = gradient_function(initial_theta, X, y)
    print('Gradient at initial theta:\t\t', grad)
    print('Expected gradients (approx):\t\t[-0.1000 -12.0092 -11.2628]')

    test_theta = np.array([[-24], [0.2], [0.2]])

    cost = cost_function(test_theta, X, y)
    print('\n'*2)
    print('Cost at test theta: ', cost)
    print('Expected cost (approx): 0.218')

    grad = gradient_function(test_theta, X, y)
    print('Gradient at test theta:\t\t', grad)
    print('Expected gradients (approx): \t\t[0.043 2.566 2.647]')

@timer
def main():
    df = get_data()
    # scatter_training_data(df)

    # X, y = df.iloc[:, :-1].values, df.iloc[-1].values
    # print(X[:3])
    # print(y[:3])

@timer
def test():
    # sigmoid_test()
    # hypothesis_test()
    # cost_function_test()
    # gradient_function_test()
    test_1()

DATA_PATH = './data/ex2data1.csv'

if __name__ == '__main__':
    print('========================================== START ==========================================')
    #...
    test()
    # main()
    print('========================================== END ============================================')


import numpy as np
import matplotlib.pyplot as plt

# Soluciones a los ejercicios de la seccion 6.2.5
# del libro A Survey of Computational Physics Introductory Computational Science
# de Landau, Paez, Bordeianu (Python Multimodal eTextBook Beta4.0)

#1. Write a double-precision program to integrate an arbitrary function numerically
# using the trapezoid rule, the Simpson rule, and Gaussian quadrature.
def integral(f, a, b, n_points=10, metodo="trapecio"):
    # Genera siempre un numero impar de puntos
    if n_points%2 == 0:
        n_points = n_points + 1

    if metodo=="trapecio":
        x = np.linspace(a, b, n_points)
        h = x[1] - x[0]
        w = np.ones(n_points) * h
        w[0] = h/2
        w[-1] = h/2
    elif metodo=="simpson":
        x = np.linspace(a, b, n_points)
        h = x[1] - x[0]
        w = np.ones(n_points)
        ii = np.arange(n_points)
        w[ii%2!=0] = 4.0*h/3.0
        w[ii%2==0] = 2.0*h/3.0
        w[0] = h/3
        w[-1] = h/3
    elif metodo=="cuadratura":
        y, wprime = np.polynomial.legendre.leggauss(n_points)
        x = 0.5*(b+a) + 0.5*(b-a)*y
        w = 0.5*(b-a)*wprime
    else:
        print('metodo no implementado')
        x = np.zeros(n_points)
        y = np.zeros(n_points)

    return np.sum(f(x)*w)

def func(x):
    return np.cos(x)

def error(x):
    return np.abs(1-x)

# 2 Compute the relative error (epsilon=abs(numerical-exact)/exact) in each case.
# Present your data in tabular form for N=2,10,20,40,80,160

def integra():
    N = [2,10,20,40,80,160]
    print("Primera Parte")
    out = open("/tmp/tabla_resultados.dat", "w")
    print("# N\t e_T\t e_S \t e_G")
    for n_points in N:
        a = integral(func, 0, np.pi/2, n_points=n_points, metodo="trapecio")
        b = integral(func, 0, np.pi/2, n_points=n_points, metodo="simpson")
        c = integral(func, 0, np.pi/2, n_points=n_points, metodo="cuadratura")
        print("{:d}\t {:.1e} {:.1e} {:.1e}".format(n_points, error(a), error(b), error(c)))
        out.write("{:d}\t {:.1e} {:.1e} {:.1e}\n".format(n_points, error(a), error(b), error(c)))
    out.close()
    print("")

    # 3 Make a log-log plot of relative error versus N
    data = np.loadtxt("/tmp/tabla_resultados.dat")
    plt.figure()
    plt.plot(data[:,0], data[:,1], label="Trapecio")
    plt.plot(data[:,0], data[:,2], label="Simpson")
    plt.plot(data[:,0], data[:,3], label="Cuadratura")

    plt.xlabel('N')
    plt.ylabel('|error|')
    plt.loglog()
    plt.legend()
    plt.savefig("error_loglogplot.png")

    # 4. Use your plot or table to estimate the power-law dependence of the error on N and
# to determine the nuber of decimal places of precision.

    for i,m in zip([1,2,3],["Trapecio", "Simpson", "Cuadratura"]):
        power_law = (np.log(data[2,i]) - np.log(data[0,i]))/(np.log(data[2,0]) - np.log(data[0,0]))
        decimal_places = -np.log10(data[-1,i])
        print("Metodo {}".format(m))
        print("\t Power Law: {:.1f}".format(power_law))
        print("\t Decimal Places: {:d}".format(int(decimal_places)))


import numpy as np
import matplotlib.pyplot as plt
import ipywidgets as widgets

def solow_equation(k,alpha,delta,s):

    saving = s*k**alpha
    depreciation = delta*k
    k_plus = k + saving - depreciation
    return k_plus, saving, depreciation

def simulate_solow_model(k0,alpha,delta,s,T):

    k_path = [k0]
    saving_path = []
    depreciation_path = []

    for t in range(1,T):

        k_plus,saving,depreciation = solow_equation(k_path[t-1],alpha,delta,s)

        k_path.append(k_plus)
        saving_path.append(saving)
        depreciation_path.append(depreciation)

    return k_path,saving_path,depreciation_path

def simulate(k_max=10,T=150):

   widgets.interact(simulate_,
                    k0=widgets.FloatSlider(description='$k_0$',min=0, max=k_max, step=0.05, value=2),
                    alpha=widgets.FloatSlider(description='$\\alpha$',min=0.01, max=0.99, step=0.01, value=0.3),
                    delta=widgets.FloatSlider(description='$\\delta$',min=0.01, max=0.50, step=0.01, value=0.1),
                    s=widgets.FloatSlider(description='$s$',min=0.01, max=0.99, step=0.01, value=0.3),
                    T=widgets.fixed(T),
                    k_max=widgets.fixed(k_max))

def simulate_(k0,alpha,delta,s,T,k_max):

    k_path,saving_path,depreciation_path = simulate_solow_model(k0,alpha,delta,s,T*5)

    fig = plt.figure(figsize=(16,6),dpi=100)
    ax1 = fig.add_subplot(1,2,1)

    ax1.plot(k_path[:T],lw=2)
    ax1.grid(ls='--',lw=1)
    ax1.set_title('Capital ($k_t$)')
    ax1.set_xlim([0,T])
    ax1.set_ylim([0,k_max])

    k_path = np.array(k_path)
    I = np.abs(np.log(k_path) - np.log(k_path[-1])) < 0.01
    t_converge = T*5 - k_path[I].size
    ax1.plot([t_converge,t_converge],[0,k_max],ls='--',color='black',label='$|\\log(k_t)-\\log(k^\\ast)| < 0.01$')

    legend = ax1.legend(loc='lower right', shadow=True)
    frame = legend.get_frame()
    frame.set_facecolor('0.90')

    ax2 = fig.add_subplot(1,2,2)
    ax2.plot(saving_path[:T],label='saving')
    ax2.plot(depreciation_path[:T],label='depreciation')
    ax2.grid(ls='--',lw=1)
    ax2.set_title('Saving ($sk^\\alpha$) and depreciation ($\\delta k_t$)')
    ax2.set_xlim([0,T])
    ax2.set_ylim([0,0.5*k_max])

    legend = ax2.legend(loc='upper left', shadow=True)
    frame = legend.get_frame()
    frame.set_facecolor('0.90')


import numpy as np
import copy
from copy import deepcopy

#normalize the list into range [-1,1]
def linear_normalization(data):
    normalization = deepcopy(data)
    for index, datai in enumerate(normalization):
        datai = float(datai)
        normalization[index] = datai
    max = np.max(normalization)
    min = np.min(normalization)
    for index, datai in enumerate(data):
        datai = 2*(datai-min)/(max-min)-1
        normalization[index] = datai
    return normalization

#normalize the list by substracting mean and divided by standard deviation (zscore)
def z_score(data):
    normalization = deepcopy(data)
    average = np.mean(normalization)
    std = np.std(normalization)
    for index, datai in enumerate(normalization):
        datai = (datai-average)/std
        normalization[index] = datai
    return normalization

samplelist = [1, 2, 3, 4, 5]
linearsample = linear_normalization(samplelist)
zscoresample = z_score(samplelist)


import numpy as np
import matplotlib.pyplot as plt

def fowardDif(func,x,h):
    return (func(x+h)-func(x))/h

def backwardDif(func,x,h):
    return (func(x)-func(x-h))/h

def centralDif(func,x,h):
    return (func(x+h)-func(x-h))/(2*h)

def getError(approx,truth):
    return np.abs(truth-approx)/np.abs(truth)

def partOne(f,dT,h,x):
    # Setup

    # Part 1
    # fowardErr = []
    # backErr = []
    centralErr = []
    for i in h:
        # fowardErr.append(getError(fowardDif(f,x,i),dT))
        # backErr.append(getError(backwardDif(f,x,i),dT))
        centralErr.append(getError(centralDif(f,x,i),dT))

    # plt.loglog(h, fowardErr, 'ro-')
    # plt.grid()
    # plt.xlabel('h')
    # plt.ylabel("Relative Error")
    # plt.title("Forward Difference Error")
    # plt.show()

    # plt.loglog(h, backErr, 'ro-')
    # plt.xlabel('h')
    # plt.grid()
    # plt.ylabel("Relative Error")
    # plt.title("Backward Difference Error")
    # plt.show()

    plt.loglog(h, centralErr, 'ro-')
    plt.xlabel('h')
    plt.grid()
    plt.ylabel("Relative Error")
    plt.title("Central Difference Error")
    plt.show()

def partTwo(f,x,h,dT):
    approxDerivErr = []
    for i in h:
        approxDerivErr.append(getError(approxDeriv(f,x,i),dT))

    plt.loglog(h, approxDerivErr, 'ro-')
    plt.xlabel('h')
    plt.grid()
    plt.ylabel("Relative Error")
    plt.title("Relative Error for an Approximated Derivative")
    plt.show()

def helper():
    xT = 0.2
    deriFunc = lambda x: np.cos(4.8*np.pi*x)*4.8*np.pi
    deriTruth = deriFunc(xT)
    h = [np.power(2.,-i) for i in range(5,31)]
    h.reverse()
    func = lambda x: np.sin(4.8*np.pi*x)
    arrErr = []
    for i in h:
        # arrErr.append(getError(centralDif(func,xT,i),deriTruth))
        arrErr.append(getError(approxDeriv(func,xT,i),deriTruth))
    temp = np.log(arrErr[16])-np.log(arrErr[len(arrErr)-2])
    temp2 = np.log(h[16])-np.log(h[len(arrErr)-2])
    print(temp/temp2)

def approxDeriv(func,x,h):
    return (2*func(x+h) + 3*func(x) - 6*func(x-h) + func(x-2*h))/(6*h)

def main():
    xT = 0.2
    h = [np.power(2.,-i) for i in range(5,31)]
    h.reverse()
    func = lambda x: np.sin(4.8*np.pi*x)
    deriFunc = lambda x: np.cos(4.8*np.pi*x)*4.8*np.pi
    deriTruth = deriFunc(xT)

    partOne(func,deriTruth,h,xT)
    # partTwo(func,xT,h,deriTruth)

helper()
# main()


import numpy as np
import matplotlib.pyplot as plt

# generate an array of floats between 0 and 1
number_of_features = 10
x = np.random.uniform(low=0, high=1, size=(number_of_features,))  # Write your code here
y = 1 + 2 * x
# plot data
# plt.plot(x,y,'b')
# plt.show()

# # Step 2: Generate noisy data
# generate random values using normal distribution with mean = 0 and standard deviation = 0.1
mu, sigma = 0, 0.1
noise = np.random.normal(mu, sigma, number_of_features)  # Write your code here
y_noise = y + noise
#
plt.plot(x, y, 'b', x, y_noise, 'rx')
plt.show()

#
## Step 3: Find parameters
# Here you have to use the provided formulas to calculate the parameters theta_0 ($\theta_0$) and theta_1 ($\theta_1$)
def get_theta_1(x1, y):
    X = np.stack((x1, y), axis=0)
    cov = np.cov(X)[0][1]
    var = np.var(x1)
    return cov / var

def get_theta_2(x1, y, theta_1):
    return np.mean(y) - theta_1 * np.mean(x1)

# Write your code below
theta_1 = get_theta_1(x, y_noise)
theta_0 = get_theta_2(x, y_noise, theta_1)
#
z = theta_0 + theta_1 * x
#
# plot the original line and the fitting linear line here on the same graph
plt.xlabel('x')
plt.ylabel('y')
plt.plot(x,y,'b',x,y_noise,'rx',x,z,'g')
plt.legend(('Original Data', 'Noisy Data', 'Linear Fit'), loc='lower right')
plt.show()
#
## Step 4: Fit multiple lines
plt.subplot(111)
num_sigma = 6
y_noise = np.zeros((num_sigma,len(x)))
z = np.zeros((num_sigma,len(x)))
for indx,sigma in enumerate(np.linspace(0,1,num=num_sigma)):
    # generate the noise
    noise = np.random.normal(mu, sigma, number_of_features)
    y_noise[indx,:] = noise + y
    # calculate theta_1 and theta_0
    theta_1 = get_theta_1(x, y_noise[indx,:])
    theta_0 = get_theta_2(x,y_noise[indx,:],theta_1)
    z[indx,:] = theta_0 + theta_1*x
    # plot the fitting line
    plt.plot(x,z[indx,:])
plt.xlabel('x')
plt.ylabel('y')
plt.legend(('0.0','0.2','0.4','0.6','0.8','1.0'), loc='lower right')
#
# reset the color cycle, so that the same color will be used for dots
plt.gca().set_prop_cycle(None)
# plot dots in the same figure
for indx in range(num_sigma):
    plt.plot(x,y_noise[indx,:],'.')
plt.show()
#
## Step 5: Explore the statistical properties of simple linear regression
mu = 0
sigma = 0.1
n = 1000
params = np.zeros((n,2))
for i in range(n):
    # genrate the noise
    noise = np.random.normal(mu, sigma, number_of_features)
    y_noise = y + noise
    # your code goes here
    theta_1 = get_theta_1(x, y_noise)
    theta_0 = get_theta_2(x,y_noise,theta_1)
    params[i,0] = theta_0
    params[i,1] = theta_1
theta_bar = np.mean(params,axis=0)
theta_sd = np.std(params,axis=0)
print("Mean Theta 0: ",theta_bar[0])
print("Mean Theta 1: ",theta_bar[1])
print("STD 0: ",theta_sd[0])
print("STD 1: ",theta_sd[1])
f, axarr = plt.subplots(2, sharex=True)
axarr[0].hist(params[:,0],bins=30)
axarr[0].set_title('Theta 0')
axarr[1].hist(params[:,1],bins=30)
axarr[1].set_title('Theta 1')
plt.show()

1.- simple linear regression/solution/simple_linear_regression.py


# #from numpy import array, zeros
# from array import *
import numpy as np
#from numpy import fabs

'''
a=np.array([[25,5,1],
        [64,8,1],
        [144,12,1]],float)
b=[106.8,177,279.2]
n= len(b)
x= np.zeros(n,float)

# Elimination
for k in range(n - 1):
    for i in range(k + 1, n):
        if a[i, k] == 0:
            continue
        factor = a[k, k] / a[i, k]
        for j in range(k, n):
            a[i, j] = a[k, j] - a[i, j] * factor
        b[i] = b[k] - b[i] * factor
print(a)

print(b)

# Back-Substitution
x[n - 1] = b[n - 1] / a[n - 1, n - 1]
for i in range(n - 2, -1, -1):
    sum_ax = 0
    for j in range(i + 1, n):
        sum_ax += a[i, j] * x[j]
    x[i] = (b[i] - sum_ax) / a[i, i]

print(x)
'''

### Gauss Elimination Method with Partial Pivoting
##3 defining array
## Coefficient Matrix
'''
a= np.array([[0,7,-1,3,1],
             [0,3,4,1,7],
             [6,2,0,2,-1],
             [2,1,2,0,2],
             [3,4,1,-2,1]],float)
## Constant vector
b = np.array([5,7,2,3,4],float)
'''
'''
a=np.array([[25,5,1],
        [64,8,1],
        [144,12,1]],float)
b=np.array([106.8,177,279.2],float)
'''
a=np.array([[20,15,10],
            [-3,-2.249,7],
            [5,1,3]],float)
b=np.array([45,1.751,9],float)

## length of the vector
n= len(b)
# defining zeros to fill the entries of x
x= np.zeros(n,float)
##

## --------------Partial Pivoting---------------
## if there is zero on the main diagonal the we have to interchange the row with another row which has greater value then zero
for k in range(n-1):
    if abs(a[k,k])<1.0e-10:
        for i in range(k+1, n):  ### I have to check here what if i take n instead of (n-1), can we interchange the pivot row with the last row.
            if abs(a[i,k])> abs(a[k,k]): #we can also write (1.0e-10) instead of a([k,k])### it doesn't matter whether we take n or n-1in the previous range.
                a[[i,k]]=a[[k,i]]
                b[[k,i]]=b[[i,k]]
                break

    # Elimination---->

    #for k in range(n-1):
    for i in range(k+1,n):
        if a[i,k]==0:
            continue
        factor= a[k,k]/a[i,k]
        for j in range(k,n):
            a[i,j]= a[k,j]- a[i,j]*factor
        b[i]=b[k]-b[i]*factor
print("the upper triangular matrix ")
U=a
print(np.round(U,1))
## back-Substitution---->

x[n-1]= b[n-1]/a[n-1,n-1]
for i in range(n-2,-1,-1):
    sum_ax=0
    for j in range(i+1,n):
        sum_ax+=a[i,j]*x[j]
    x[i]= (b[i]-sum_ax)/a[i,i]

print("The solution vector is: ")
print(np.round(x,1))


def cells():
    # setup SymPy
    from sympy import *
    init_printing()
    x, y, z, t = symbols('x y z t')
    alpha, beta = symbols('alpha beta')

    '''
    '''

    '''
    # Linearity
    '''

    '''
    '''

    b, m = symbols('b m')

    def f(x):
        return m*x

    '''
    '''

    f(1)

    '''
    '''

    f(2)

    '''
    '''

    f(1+2)

    '''
    '''

    f(1) + f(2)

    '''
    '''

    expand(f(x+y)) ==  f(x) + f(y)

    '''
    '''

    '''
    ## What about vector inputs?
    '''

    '''
    '''

    m_1, m_2 = symbols('m_1 m_2')

    def T(vec):
        """A function that takes a 2D vector and returns a number."""
        return m_1*vec[0] + m_2*vec[1]

    '''
    '''

    u_1, u_2 = symbols('u_1 u_2')
    u = Matrix([u_1,u_2])
    v_1, v_2 = symbols('v_1 v_2')
    v = Matrix([v_1,v_2])

    '''
    '''

    T(u)

    '''
    '''

    T(v)

    '''
    '''

    T(u) + T(v)

    '''
    '''

    expand( T(u+v) )

    '''
    '''

    simplify( T(alpha*u + beta*v) - alpha*T(u) - beta*T(v) )

    '''
    '''

    '''
    # Linear transformations
    '''

    '''
    '''

    '''
    A linear transformation is function that takes vectors as inputs, and produces vectors as outputs:

    $$
       T: \mathbb{R}^n \to \mathbb{R}^m.
    $$

    see page 116 in book
    '''

    '''
    '''

    m_11, m_12, m_21, m_22 = symbols('m_11 m_12 m_21 m_22')

    def T(vec):
        """A linear transformations R^2 --> R^2."""
        out_1 = m_11*vec[0] + m_12*vec[1]
        out_2 = m_21*vec[0] + m_22*vec[1]
        return Matrix([out_1, out_2])

    '''
    '''

    T(u)

    '''
    '''

    T(v)

    '''
    '''

    T(u+v)

    '''
    '''

    '''
    ## Linear transformations as matrix-vector products
    '''

    '''
    '''

    '''
    see page 113
    '''

    '''
    '''

    def T_impl(vec):
        """A linear transformations implemented as matrix-vector product."""
        M_T = Matrix([[m_11, m_12],
                      [m_21, m_22]])
        return M_T*vec

    '''
    '''

    T_impl(u)

    '''
    '''

    '''
    '''


# make a cobweb plot
import numpy as np
#from numpy import cos
import matplotlib.pyplot as plt

# here is the function we want to iterate
# mu is a possible parameter
def func(x,mu,c4):
	return x*(2*np.pi/mu) + c4#mu*np.sin(x*(1.0*np.pi)) #mu*x*(1-x)

# return f^n(x)
def func_n(x,mu,c4,n):
	for i in range(-n,n):
		x = func(x,mu,c4)

	return x
# here is "plotting graphical" or "cobweb" for an interated map
# connect up (x, f^1(x)), (f^1(x),f^1(x)), (f^1(x), f^2(x)), (f^2(x),f^2(x))
#  ... (f^i(x), f^(i+1)(x)),(f^(i+1),f^(i+1)) to i=n
# initial x0, mu is a parameter to pass to function
# connect up points n times, this is 2n pairs of points
def plot_graphical(x0,mu,c4, n):
	xv = np.linspace(0.0,1.0,2*n)  # create array for points xvalue
	yv = np.linspace(0.0,1.0,2*n)  # create array for points yvalue
	x =x0
	for i in range(0,n):  #iterate
		xv[2*i] = x  # first point is (x,f(x))
		x = func(x,mu,c4)
		yv[2*i] = x
		xv[2*i+1] = x #second point is (f(x),f(x))
		yv[2*i+1] = x
	plt.plot(xv,yv,'b')  # connect up all these points blue

plt.figure()
plt.xlabel('r')
plt.ylabel('P(r)')
plt.xticks([-np.pi, -.5*np.pi,0., .5*np.pi, np.pi,],[r"$-\pi$",r"$\frac{-1}{2}\pi$","$0$", r"$\frac{1}{2}\pi$",
                     r"$\pi$"])

fac=1.01
xmax = np.pi
xmin =-np.pi
#ymax = 1.01
#ymin =0.01
#plt.axis([xmin*fac,xmax*fac,ymin*fac,ymax*fac])
xcon = np.arange(xmin, xmax, 0.01)   # to plot function
plt.plot(xcon,xcon, 'g',label=r'P(r) = r')             #y=x plotted green

mu=10
c4= .5
ycon = func(xcon,mu,c4)                 # function computed
plt.plot(xcon,ycon, 'r', label="P(r)")             # function plotted red
plot_graphical(xmin,mu,c4,10)            # cobweb plot, 0.3 is initial condition
plot_graphical(xmax,mu,c4,10)            # cobweb plot, 0.3 is initial condition
plt.title('Strogatz Figure 8.7.3')
plt.grid()
plt.legend()
plt.show()


from package import redact_ex

from package import \
    euler_differentiate, \
    range_kutta_differentiate

import numpy as np

EXERCISE_02i = """\
Make a program that is able to graphically solve the equation
d\u00B2x/dt\u00B2 + b dx/dt + \u03C9_0\u00B2 x = 0 using the Euler method.\
"""

redact_ex(EXERCISE_02i, '2i')

# dx/dt = y
# dy/dt = -b*y - omega

b = 1/2; omega_0 = 2

def inct(dt, *o):
    return dt

def incx(dt, t, x, y):
    return dt * y

def incy(dt, t, x, y, b = b, omega_0 = omega_0):
    return dt * (-b*y - omega_0**2*x)

print("Computing Euler method...", end='\n\n')
euler_differentiate([inct, incx, incy], bounds = [0, 1, -1],
    delta = 1e-2, itern = 1e4, graph = [1, 2],
    title = r"Euler method for function"
        + r"$\:\:\frac{d^2x}{dt^2} + b \frac{dx}{dt} + \omega_0^2 x = 0$")

EXERCISE_02 = """\
Make a program that is able to graphically solve the equation
d\u00B2x/dt\u00B2 + b dx/dt + \u03C9_0\u00B2 x = F cos(x) using:
+ the Euler method
+ the Range-Kutta method of order 2
+ the Range-Kutta method of order 4
Comment on the effects of \u0394t over the obtained solutions
(analyze the methods' stability with regards to the \u0394t used).\
"""

redact_ex(EXERCISE_02, 2)

# dx/dt = y
# dy/dt = -b*y - omega_0**2*x + f*np.cos(omega*t)

b = 1/2; omega_0 = 4; f = 1; omega = 2

def inct(dt, *o):
    return dt

def incx(dt, t, x, y):
    return dt * y

def incy(dt, t, x, y, b = b, omega_0 = omega_0, f = f, omega = omega):
    return dt * (-b*y - omega_0**2*x + f*np.cos(omega*t))

print("Computing Euler method...", end='\n\n')
euler_differentiate([inct, incx, incy], bounds = [0, 0, 1],
    delta = 1e-2, itern = 1e4, graph = [1, 2],
    title = r"Euler method for function"
        + r"$\:\:\frac{d^2x}{dt^2} + b \frac{dx}{dt} + \omega_0^2 x = F \cos(\omega t)$")

print("Computing Range-Kutta method of order 2...", end='\n\n')
range_kutta_differentiate([inct, incx, incy], bounds = [0, 0, 1],
    delta = 1e-2, order = 2, itern = 1e4, graph = [1, 2],
    title = r"Range-Kutta method of order 2 for function"
        + r"$\:\:\frac{d^2x}{dt^2} + b \frac{dx}{dt} + \omega_0^2 x = F \cos(\omega t)$")

print("Computing Range-Kutta method of order 4...", end='\n\n')
range_kutta_differentiate([inct, incx, incy], bounds = [0, 0, 1],
    delta = 1e-2, order = 4, itern = 1e4, graph = [1, 2],
    title = r"Range-Kutta method of order 4 for function"
        + r"$\:\:\frac{d^2x}{dt^2} + b \frac{dx}{dt} + \omega_0^2 x = F \cos(\omega t)$")

omega = omega_0
def incy(dt, t, x, y, b = b, omega_0 = omega_0, f = f, omega = omega):
    return dt * (-b*y - omega_0**2*x + f*np.cos(omega*t))

euler_differentiate([inct, incx, incy], bounds = [0, 0, 1],
    delta = 1e-2, itern = 1e4, graph = [1, 2],
    title = r"Euler method for function"
        + r"$\:\:\frac{d^2x}{dt^2} + b \frac{dx}{dt} + \omega_0^2 x = F \cos(\omega_0 t)$")

# When omega_0 == omega, movement is a single sinusoidal wave since the start
# rather than a combination of two, as we can see in the early stages
# of the plots where omega_0 != omega.


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

class fakeRule:
    f = None
    xl = 0
    xu = 0
    mini = 0
    maxi = 0
    table = [["i","xl","xu","xi","f(xl)","f(xu)","f(xi)"]]

    def __init__(self, f, mini=0, maxi= 0):
        self.f = f
        self.maxi = maxi
        self.mini = mini

    def display(self):
        xvalues = np.arange(self.mini,self.maxi,.1)
        plt.plot(xvalues ,[self.f(x) for x in xvalues])
        plt.plot([self.mini,self.maxi],[0,0],"b")
        plt.plot([0,0],[self.f(self.mini),self.f(self.maxi)],"b")
        plt.xlabel("x")
        plt.ylabel("f(x)")
        plt.show()

    def getXi(self, xl, xu):
        a = xu*(self.f(xl)) - xl*(self.f(xu))
        b = self.f(xl) - self.f(xu)
        return a/b

    def isInside(self,error,xi):
        return abs(self.f(xi)) < error

    def findRoot(self, xl,xu, error):
        self.table = [["i","xl","xu","xi","f(xl)","f(xu)","f(xi)"]]
        xi = self.getXi(xl,xu)
        it = 0

        while not self.isInside(error,xi):
            self.table.append([it,xl,xu,xi,self.f(xl),self.f(xu),self.f(xi)])
            xu = {True:xi, False:xu}[self.f(xi)*self.f(xu)>0]
            xl = {True:xi, False:xl}[self.f(xi)*self.f(xl)>0]
            xi = self.getXi(xl,xu)
            it += 1
        self.table.append([it,xl,xu,xi,self.f(xl),self.f(xu),self.f(xi)])
        print('\n'.join(['\t'.join([str(cell) for cell in row]) for row in self.table]))
        return (xi)

# How to use it
# We need to define a function, the one that we need to solve
fun = lambda x : math.sqrt(x) - (2*math.cos(math.radians(x+1)))
#we create an object that receives the function and the range that it needs to be graphed
root_fun = fakeRule(fun, 0,50)
#the display method allows us to see the function graphically
root_fun.display()
#seeing this graph, we can find points in the function where the f(x) changes it's sign
#the closer we get to these points the faster we will find a solution
#Solve uses tree arguments, the lower limit, the higher limit and a degree of error that we will allow
print("The value x | f(x) -> 0 is: " + str(root_fun.findRoot(0,10,.0001)))


import numpy as np

def angular_dist(a1, d1, a2, d2):
  a1 = np.radians(a1)
  d1 = np.radians(d1)
  a2 = np.radians(a2)
  d2 = np.radians(d2)

  p1 = np.sin(abs(d1-d2)/2)**2
  p2 = np.cos(d1)*np.cos(d2)*np.sin(abs(a1-a2)/2)**2
  p3 = 2*np.arcsin(np.sqrt(p1+p2))
  return np.degrees(p3)

# You can use this to test your function.
# Any code inside this `if` statement will be ignored by the automarker.
if __name__ == '__main__':
  # Run your function with the first example in the question.
  print(angular_dist(21.07, 0.1, 21.15, 8.2))

  # Run your function with the second example in the question
  print(angular_dist(10.3, -3, 24.3, -29))


# example of calculating the js divergence between two mass functions
# forked from https://machinelearningmastery.com/divergence-between-probability-distributions/#:~:text=KL%20divergence%20can%20be%20calculated,of%20the%20event%20in%20P.&text=The%20value%20within%20the%20sum%20is%20the%20divergence%20for%20a%20given%20event.
#HINT:
# # calculate the jensen-shannon distance metric
# from scipy.spatial.distance import jensenshannon
# from numpy import asarray
# # define distributions
# p = asarray([0.10, 0.40, 0.50])
# q = asarray([0.80, 0.15, 0.05])
# # calculate JS(P || Q)
# js_pq = jensenshannon(p, q, base=2)
# print('JS(P || Q) Distance: %.3f' % js_pq)
# # calculate JS(Q || P)
# js_qp = jensenshannon(q, p, base=2)
# print('JS(Q || P) Distance: %.3f' % js_qp)
#
#
from math import log2
from math import sqrt
from numpy import asarray

# calculate the kl divergence
def kl_divergence(p, q):
	return sum(p[i] * log2(p[i]/q[i]) for i in range(len(p)))

# calculate the js divergence
def js_divergence(p, q):
	m = 0.5 * (p + q)
	return 0.5 * kl_divergence(p, m) + 0.5 * kl_divergence(q, m)

if __name__ is "__main__":
	# define distributions
	p = asarray([0.10, 0.40, 0.50])
	q = asarray([0.80, 0.15, 0.05])
	# calculate JS(P || Q)
	js_pq = js_divergence(p, q)
	print('JS(P || Q) divergence: %.3f bits' % js_pq)
	print('JS(P || Q) distance: %.3f' % sqrt(js_pq))
	# calculate JS(Q || P)
	js_qp = js_divergence(q, p)
	print('JS(Q || P) divergence: %.3f bits' % js_qp)
	print('JS(Q || P) distance: %.3f' % sqrt(js_qp))

	# calculate the jensen-shannon distance metric
	from scipy.spatial.distance import jensenshannon
	from numpy import asarray
	# define distributions
	p = asarray([0.10, 0.40, 0.50])
	q = asarray([0.80, 0.15, 0.05])
	# calculate JS(P || Q)
	js_pq = jensenshannon(p, q, base=2)
	print('JS(P || Q) Distance: %.3f' % js_pq)
	# calculate JS(Q || P)
	js_qp = jensenshannon(q, p, base=2)
	print('JS(Q || P) Distance: %.3f' % js_qp)


"""
Author: phunc20

haar1D and haar2D are slightly modified version of the functions carrying the same names in the notebook.

"""
import math
import numpy as np

def haar1D(n, SIZE):
    # check power of two
    if math.floor(math.log(SIZE) / math.log(2)) != math.log(SIZE) / math.log(2):
        print("Haar defined only for lengths that are a power of two")
        return None
    if n >= SIZE or n < 0:
        print("invalid Haar index")
        return None

    # zero basis vector
    if n == 0:
        return np.ones(SIZE)

    # express n >= 1 as 2^p + q with p as large as possible;
    # then k = SIZE/2^p is the length of the support
    # and s = qk is the shift
    p = math.floor(math.log(n) / math.log(2))
    pp = int(pow(2, p))
    k = SIZE / pp
    s = (n - pp) * k

    h = np.zeros(SIZE)
    h[int(s):int(s+k/2)] = 1
    h[int(s+k/2):int(s+k)] = -1
    # these are not normalized
    return h

def haar2D(n, SIZE=8):
    # get horizontal and vertical indices
    hr = haar1D(n % SIZE, SIZE)
    hv = haar1D(int(n / SIZE), SIZE)
    # 2D Haar basis matrix is separable, so we can
    # just take the column-row product
    H = np.outer(hr, hv)
    H = H / math.sqrt(np.sum(H * H))
    # the previous line just divides H by its Frobenius norm
    # so that the returned value of haar2D() has norm 1.
    return H

def decomposition_step(array):
    h = len(array)
    exponent = math.log(h, 2)
    if math.floor(exponent) != exponent:
        print("Haar defined only for lengths that are a power of two")
        return None
    # =============================================
    #  Implementation 01:
    #  Same as in the paper, only diff being
    #  1-based to 0-based.
    # =============================================
    #array2 = np.empty_like(array)
    array2 = [0]*h
    for i in range(h//2):
        array2[i] = (array[2*i] + array[2*i+1]) / math.sqrt(2)
        array2[h//2 + i] = (array[2*i] - array[2*i+1]) / math.sqrt(2)
    # =============================================
    #  Implementation 02:
    #  Try to code using matrix operation instead
    # =============================================
    #if not isinstance(array):
    #    array = np.array(array)
    return array2

if __name__ == "__main__":
    print(f"decomposition_step([9,7,3,5]) = {decomposition_step([9,7,3,5])}")


# Question: https://projecteuler.net/problem=158
# General idea:
#     - Pick an decreasing sequence S, e.g. (n,n-1,...,1). There are C(26, n) ways of choosing S.
#     - Assume the sequence we're going to construct consists of the left seq and the right seq, which is a partition of S.
#     - Let the "pivot" be the character that comes lexicographically after its neighbour on the left, i.e. the first character of the right seq.
#     - For each possible length k of the left seq,
#         * There are C(n, k) ways of choosing the left seq.
#         * However, among those C(n,k) seqs, there is always one seq such that all characters on the right seq are lexicographically after all characters on the left. In other words, left = (n, n-1, ..., n-k+1), right = (n-k, n-k-1, ..., 1).
#         * For each of the C(n,k)-1 seqs on the left, there is always only one possible of choosing the right seq. This is because the right seq must be a decreasing seq.
#     - So, p(n) = C(26, n) * sum_{k=1}^(k=n}[C(n, k) - 1]
#     - We have that sum_{k=0}^{k=n}[C(n,k)]   = 2^n,
#                 so sum_{k=1}^{k=n}[C(n,k)]   = 2^n - 1,
#                 so sum_{k=1)^{k=n}[C(n,k)-1] = 2^n - 1 - n
#     - So, p(n) = C(26, n) * (2^n - n - 1)

from scipy.special import comb # comb(n,k) = n choose k

M = 26 # number of characters in the alphabet

def C(n,k):
    return comb(n, k, exact = True)

def sum_from_a_to_b(a, b):
    return (b-a+1)*(a+b)//2

def p(n):
    return C(26, n) * (2**n - n - 1)
if __name__ == "__main__":
    ans = 0

    for n in range(1, M+1):
        p_n = p(n)
        ans = max(ans, p_n)

    print(ans)


import pandas as pd
from datascience import *
from sympy import *
import matplotlib.pyplot as plt
import numpy as np
import plotly.graph_objs as go
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
from ipywidgets import interact, interactive, fixed, interact_manual
from IPython.display import display, HTML

def cobb_douglas(A, K, L, alpha, beta = None):
    if beta is None:
        return A * K ** alpha * L ** (1 - alpha)
    else:
        return A * K ** alpha * L ** (beta)

def cobb_douglas_plotter_K(A, L_bar, alpha, y):
    plt.plot(np.arange(0, 1.01, 0.01), y)
    plt.title(fr"Cobb-Douglas with $\bar L$ = {L_bar}, $A$ = {A} and $\alpha$ = {alpha}", size=16)
    plt.xlabel("Capital Stock", size=16)
    plt.ylabel("Output", size=16);

def cobb_douglas_plotter_L(A, K_bar, alpha):
    L_s = np.arange(0, 1.01, 0.01)
    V_3 = cobb_douglas(A, K_bar, L_s, alpha)
    plt.plot(L_s, V_3)
    plt.title(fr"Cobb-Douglas with $\bar K$ = {K_bar}, $A$ = {A} and $\alpha$ = {alpha}", size=16)
    plt.xlabel("Labor Force", size=16)
    plt.ylabel("Output", size=16);

def plot_cobb_douglas(V, orig_V, t0label="trace 0", t1label="trace 1", filename=None):
    data = [go.Surface(z = V, contours = go.surface.Contours(z = go.surface.contours.Z(show = False, project = dict(z = True))),
                      colorscale = "Electric", showscale = False, name=t0label),
           go.Surface(z = orig_V, contours = go.surface.Contours(z = go.surface.contours.Z(show = False, project = dict(z = True))),
                     colorscale = "Viridis", showscale = False, name=t1label)]
    layout = go.Layout(title = "Cobb-Douglas Production Function", autosize=False, width=500, height=500, margin = dict(l = 65, r = 50, b = 65, t = 90),
                       scene = dict(xaxis = dict(title = 'K'), yaxis = dict(title = 'L'), zaxis = dict(title = 'Y')))
    fig = go.Figure(data = data, layout = layout)
    if filename:
        plot(fig, filename=filename, auto_open=False, include_mathjax='cdn')
    else:
        iplot(fig)

def orig_cobb_douglas():
    L_s = np.arange(0, 10.11, 0.1)
    K_s = np.arange(0, 10.11, 0.1)
    A = 1
    alpha = 0.5
    xx, yy = np.meshgrid(K_s, L_s)
    curr_V = cobb_douglas(A, xx, yy, alpha)
    return curr_V

def change_A(A, filename=None):
    L_s = np.arange(0, 10.11, 0.1)
    K_s = np.arange(0, 10.11, 0.1)
    alpha = 0.5
    xx, yy = np.meshgrid(K_s, L_s)
    curr_V = cobb_douglas(A, xx, yy, alpha)
    plot_cobb_douglas(curr_V, orig_cobb_douglas(), fr"A = {A}", r"A = 1", filename=filename)

def change_alpha(alpha, filename=None):
    L_s = np.arange(0, 10.11, 0.1)
    K_s = np.arange(0, 10.11, 0.1)
    A = 1
    xx, yy = np.meshgrid(K_s, L_s)
    curr_V = cobb_douglas(A, xx, yy, alpha)
    plot_cobb_douglas(curr_V, orig_cobb_douglas(), fr"alpha = {alpha}", f"alpha = 0.5", filename=filename)

def change_alpha_beta(alpha_beta_sum, filename=None):
    L_s = np.arange(0, 10.11, 0.1)
    K_s = np.arange(0, 10.11, 0.1)
    A = 1
    alpha = alpha_beta_sum / 2
    beta = alpha_beta_sum / 2
    xx, yy = np.meshgrid(K_s, L_s)
    curr_V = cobb_douglas(A, xx, yy, alpha, beta)
    plot_cobb_douglas(curr_V, orig_cobb_douglas(), f"alpha + beta = {alpha_beta_sum}", f"alpha + beta = 1", filename=filename)

def MPK(A, K, L, alpha):
    return A * alpha * (K ** (alpha - 1)) * (L ** (1 - alpha))

def MPL(A, K, L, alpha):
    return A * (1 - alpha) * (K / L) ** alpha


import math
import numpy as np
from matplotlib import pyplot as plt
from scipy import optimize
from methods import *
from pprint import pprint

#-------F(x)--------------
def F(x):
    n = len(x)

    piatoria = 1
    exp_power = 0

    for el in x:
        piatoria*= (math.cos(el))
        exp_power-= (el-math.pi)**2

    return -1  *  ((-1)**n) * (piatoria) * math.exp(exp_power)

#--------For x_i <= 500 restrictions-------------
def g_i_1(x_i : float):
    return x_i - 2*math.pi

def g_i_1_plus(x_i : float):
    return max(0,x_i -2* math.pi)
#------For x_i>= -500 restrictions---------------
def g_i_2(x_i : float):
    return -x_i-2*math.pi

def g_i_2_plus(x_i : float):
    return max(0,-x_i-2*math.pi)

#------Q(x,c)-for penalization method----------------

def Q(c):
    def Q_call(x):
        f_x_eval = F(x)

        g_i_1_sum = 0
        for variable in x:
            g_i_1_sum +=g_i_1_plus(variable)

        g_i_2_sum = 0
        for variable in x:
            g_i_2_sum +=g_i_2_plus(variable)

        return f_x_eval + c * g_i_1_sum + c * g_i_2_sum

    return Q_call

#------R(x,miu)- for barrier method-------------------
def R(miu):
    def R_call(x):
        f_x_eval = F(x)

        g_i_1_sum = 0
        for variable in x:
            g_i_1_sum -=1/g_i_1(variable)

        g_i_2_sum = 0
        for variable in x:
            g_i_2_sum -=1/g_i_2(variable)

        return f_x_eval + miu * g_i_1_sum + miu * g_i_2_sum

    return R_call

#------Omega( chequear si un vector x cumple con las restricciones)-------
def omega(x):
    # print(x)
    for element in x:
        # print(element)
        if element<-2*math.pi or element>2*math.pi:
            return False
    return True

def Bounds(n):
    return [(-2 * math.pi, 2 * math.pi) for _ in range(n)]

def plot():
    x = np.arange(-2 * math.pi, 2 * math.pi, 0.1)
    y = [F([i]) for i in x]

    plt.plot(x, y)
    plt.show()

#testing-------------------
if __name__ == '__main__':
    # plot()

    # print("Penalization Method")
    # answer = Penalization_method("BFGS", Q, omega, x0=np.array([2, 2]), c0=1, alpha=1.5, epsilon=0.001, k_max=500)
    # pprint(answer)
    # print(F(answer["1.Result"]))
    # print()
    print("Barrier Method")
    answer = Barrier_method("BFGS", R, x0=np.array([2,2]), miu_0=1, alpha=0.5, epsilon=0.001, k_max=500)
    pprint(answer)
    print(F(answer["1.Result"]))
    # print()
    # print("SQP Method")
    # print(SQP_method(F, np.array([3,3]), Bounds(2), k_max=500))


# Question 05, Lab 07
# AB Satyapraksh, 180123062

# imports
import numpy as np
import pandas as pd

# functions

def f(t, y):
    return y - t**2 + 1

def F(t):
    return (t+1)**2 - 0.5*np.exp(t)

def AdamsBashforth(t, y, h):
    return y[-1] + h*(55*f(t[-1], y[-1]) - 59*f(t[-2], y[-2]) + 37*f(t[-3], y[-3]) - 9*f(t[-4], y[-4]))/24

def AdasmMoulton(t, y, h):
    t1 = t[-1]+h
    y1 = AdamsBashforth(t, y, h)
    return y[-1] + h*(9*f(t1, y1) + 19*f(t[-1], y[-1]) - 5*f(t[-2], y[-2]) + f(t[-3], y[-3]))/24

# program body
# part (a)
t, y1, h = [0], [0.5], 0.2
for i in range(3):
    t.append(round(t[-1]+h, 1))
    y1.append(F(t[-1]))

while t[-1] < 2:
    t.append(round(t[-1]+h, 1))
    y1.append(AdamsBashforth(t, y1, h))

# part (b)
t, y2, h = [0], [0.5], 0.2
for i in range(3):
    t.append(round(t[-1]+h, 1))
    y2.append(F(t[-1]))

while t[-1] < 2:
    t.append(round(t[-1]+h, 1))
    y2.append(AdasmMoulton(t, y2, h))

y3 = []
for T in t:
    y3.append(F(T))

#  tabulate and print the results
df = pd.DataFrame()
df['Adams-Bashforth'] = pd.Series(y1)
df['Adams-Moulton'] = pd.Series(y2)
df['Actual Value'] = pd.Series(y3)
df.set_index(pd.Series(t), inplace=True)
print(df)

Semester 6/MA 322 (Scientific Computing Theory)/Labs/Lab 7/Code/q5.py


import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from mpl_toolkits.mplot3d import Axes3D

import numpy as np
from numpy.linalg import svd
import scipy.io as sio
import itertools

from common_functions import feature_normalize, matrix_args, matrix_args_array_only
from ex7 import kmeans_init_centroids, run_kmeans

@matrix_args
def pca(X):
    m = len(X)

    u, s, v = svd(X.T*X/m)

    return u, s

@matrix_args_array_only
def project_data(X, U, K):
    return X*U[:, :K]

@matrix_args_array_only
def recover_data(Z, U, K):
    return (Z*(U[:, :K]).T).A

def display_data(X, title):
    fig = plt.figure(figsize=(8, 8))  # figure size in inches
    fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.05, wspace=0.05)
    plt.suptitle(title, fontsize=18, color='r')

    for i, x in enumerate(X):
        ax = fig.add_subplot(10, 10, i + 1, xticks=[], yticks=[])
        ax.imshow(x.reshape(32, 32).T, cmap=plt.cm.Greys_r, interpolation='nearest')

    plt.show()

if __name__ == '__main__':

    data = sio.loadmat('ex7data1.mat')
    X = data['X']
    x1, x2 = X.T

    plt.plot(x1, x2, 'bo')
    plt.show()

    mu, sigma, X_norm = feature_normalize(X)

    U, S = pca(X_norm)

    print('Top eigenvector: ');
    print(' U[:,0] = %s' % U[:, 0])
    print('(you should expect to see -0.707107 -0.707107)')

    K = 1
    Z = project_data(X_norm, U, K)
    print('Projection of the first example: %s' % Z[0])
    print('(this value should be about 1.49631261)')

    X_rec = recover_data(Z, U, K)
    print('Approximation of the first example: %s' % X_rec[0])
    print('(this value should be about  -1.05805279 -1.05805279)')

    x1, x2 = X_norm.T
    plt.plot(x1, x2, 'bo')
    x1, x2 = X_rec.T
    plt.plot(x1, x2, 'ro')
    plt.axis('equal')

    for x_rec, x_norm in zip(X_rec, X_norm):
        plt.plot((x_rec[0], x_norm[0]), (x_rec[1], x_norm[1]), 'k-')
    plt.show()

    #PCA on Face Data: Eigenfaces
    data = sio.loadmat('ex7faces.mat')
    X = data['X']

    display_data(X[:100, :], 'Faces dataset')

    mu, sigma, X_norm = feature_normalize(X)

    U, S = pca(X_norm)

    display_data(U[:, :36].T, 'Principal components on the face dataset')

    K = 100
    Z = project_data(X_norm, U, K)
    X_rec = recover_data(Z, U, K)

    display_data(X_norm[:100, :], 'Original images of faces')
    display_data(X_rec[:100, :], 'Reconstructed from only the top 100 principal components')

    #PCA for Visualization
    img=mpimg.imread('bird_small.png')
    img_size = img.shape

    X = img.reshape(img_size[0]*img_size[1], 3)

    K = 16
    max_iters = 10
    initial_centroids = kmeans_init_centroids(X, K)
    idx, centroids = run_kmeans(X, initial_centroids, max_iters)

    to_plot = range(len(X))
    np.random.shuffle(to_plot)
    to_plot = to_plot[:1000]

    X_plot = X[to_plot, :]
    idx_plot = idx[to_plot]
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')
    for k, color in zip(xrange(K), itertools.cycle(['r', 'b', 'g', 'k', 'm'])):

        x, y, z = X_plot[idx_plot == k, :].T
        ax.scatter(x, y, z, c=color)
    plt.title('Pixel dataset plotted in 3D. Color shows centroid memberships')
    plt.show()

    mu, sigma, X_norm = feature_normalize(X)
    U, S = pca(X_norm)
    Z = project_data(X_norm, U, 2)

    Z_plot = Z[to_plot, :]
    for k, color in zip(xrange(K), itertools.cycle(['r', 'b', 'g', 'k', 'm'])):

        x1, x2 = Z_plot[idx_plot == k, :].T
        plt.plot(x1, x2, color+'o')
    plt.title('Pixel dataset plotted in 2D, using PCA for dimensionality reduction')
    plt.show()


from sympy import ( symbols, solve, diff, integrate, exp, sqrt, lambdify, pprint, Integral, simplify )
import matplotlib.pyplot as plt
import numpy as np

x = symbols( 'x' )

# A construction company has an expenditure rate of
dE = exp( 0.12 * x )
E = integrate( dE, x )

#  dollars per day on a particular paving job and an income rate
dI = 120.8 - exp( 0.12 * x )
I = integrate( dI, x )

# dollars per day on the same job, where x is the number of days from the start of the job.

# The company's profit on that job will equal total income less total expenditures.
#
# Profit will be maximized if the job ends at the optimum time, which is the point where the two curves meet.

P = I - E

# What is the graph doing?
g_xlim = [ 1, 40 ]

lam_e = lambdify( x, dE, np )
x_vals = np.linspace( g_xlim[0], g_xlim[1], 1000, endpoint=True )
y_vals = lam_e( x_vals )
plt.plot( x_vals, y_vals, label = "Expense" )

lam_i = lambdify( x, dI, np )
x_vals = np.linspace( g_xlim[0], g_xlim[1], 1000, endpoint=True )
y_vals = lam_i( x_vals )
plt.plot( x_vals, y_vals, label = "Income" )

days = solve( dI - dE, x )[ 0 ]

lam_p = lambdify( x, P, np )
x_vals = np.linspace( g_xlim[0], g_xlim[1], 1000, endpoint=True )
y_vals = lam_p( x_vals )
plt.plot( x_vals, y_vals, label = "Profit" )

plt.vlines( x = days, ymin = 0, ymax = P.subs( { x: days } ), color = 'Red', zorder = 1 )

income = round( I.subs( { x: days } ), 2 )
expenses = round( E.subs( { x: days } ), 2 )
max_profit = round( P.subs( { x: days } ), 2 )

days = round( days )

plt.title( 'Max Profit at {0} days, ${1}'.format( days, max_profit ) )
plt.show()

if __name__ == '__main__':
	print( 'Optimal Days: {0}'.format( days ) )
	print( 'Total Income: {0}'.format( income ) )
	print( 'Total Expenditures: {0}'.format( expenses ) )
	print( 'Max Profit: {0}'.format( max_profit ) )


from sympy import *

# The average monthly rent for a 1000-sq-ft apartment in a major metropolitan area from
# 1998 through 2005 can be approximated by the function below where t is the time in years since
# the beginning of 1998. Find the value of t when rents were increasing most rapidly.
# Approximately when did this occur?

t = symbols( 't', positive = True ) # 0 <= x
C = 1.6434 * t**4 - 21.052 * t**3 + 62.98 * t**2 + 6.1157 * t + 1005

# The rents were increasing most rapidly when
dC = diff( C, t, 1 )
ddC = diff( dC, t, 1 )

critical_values = solve( ddC )

cv_1 = dC.subs( { t : critical_values[ 0 ] } )
cv_2 = dC.subs( { t : critical_values[ 1 ] } )

if cv_1 > cv_2:
	cv = critical_values[ 0 ]
	print( 'Critical Value {0} is higher [ {1} ]'.format( round( cv, 3 ), cv_1 ) )
else:
	cv = critical_values[ 1 ]
	print( 'Critical Value {0} is higher [ {1} ]'.format( round( cv, 3 ), cv_2 ) )

print( 'Year: {0}'.format( round( 1998 + cv ) ) )


# Adding vectors using Python
import numpy as np

vector1 = np.array([[10],[3],[21]])
vector2 = np.array([[3],[2],[4]])

output = vector1 + vector2
print(output)

"""

gives a result:

[[13]
 [ 5]
 [25]]

which is the component-wise sum of the elements of each vector

"""


import numpy as np

def mse(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Mean squared error

    Args:
        y_true (np.ndarray): ground-truth values
        y_pred (np.ndarray): y_predicted values
    Returns:
        float: metric value

    """
    result = np.mean((y_true - y_pred) ** 2)

    return result

def rmse(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Root mean squared error

    Args:
        y_true (np.ndarray): ground-truth values
        y_pred (np.ndarray): y_predicted values
    Returns:
        float: metric value

    """
    result = np.sqrt(np.mean((y_true - y_pred) ** 2))

    return result

def r2(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Coefficient of determination

    Args:
        y_true (np.ndarray): ground-truth values
        y_pred (np.ndarray): y_predicted values
    Returns:
        float: metric value

    """
    result = 1 - np.sum((y_true - y_pred) ** 2) / np.sum((y_true - np.mean(y_true)) ** 2) # noqa

    return result


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

#
# @Author: Jxtopher
# @License: CC-BY-NC-SA
# @Date: 2019-04
# @Version: 1
# @Purpose: approximation de pi par la méthode de Monte-Carlo
#           see https://fr.wikipedia.org/wiki/Méthode_de_Monte-Carlo#Détermination_de_la_valeur_de_π
#

import numpy as np

from itertools import cycle
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

lines = ["-"]#,"--","-.",":"]
markers = ["o", "v", "^", "^", ">", "1", "2", "3", "4", "8", "s", "p", "*", "h", "H", "+", "<", "D"]
colors = ["black", "blue", "green", "red", "brown", "magenta", "silver", "pink"]
linecycler = cycle(lines)
markercycler = cycle(markers)
colorcycler = cycle(colors)

def approximationPI():
    result_x = []
    result_y = []

    inTheCircle = 0
    outsideTheCircle = 0
    step = 0
    while (step < 5010):

        if (np.sqrt(pow(np.random.uniform(), 2) + pow(np.random.uniform(), 2))  <= 1):
            inTheCircle += 1
        else:
            outsideTheCircle += 1

        x = inTheCircle / float(inTheCircle + outsideTheCircle)
        step +=1
        result_x += [step]
        result_y += [x*4]
    return result_x, result_y

if __name__ == '__main__':
    x, y = approximationPI()

    plt.plot(x,
            [3.141592653589793]*len(x),
            linestyle=next(linecycler),
            marker=None,
            label=ur"π",
            linewidth=1,
            markersize=2,
            markeredgewidth=1,
            color=next(colorcycler))

    plt.plot(x,
            y,
            linestyle=next(linecycler),
            marker=None,#next(markercycler),
            label=ur"Approximation of π",
            linewidth=1,
            markersize=2,
            markeredgewidth=1,
            color=next(colorcycler))

    size = 10
    plt.xticks(np.arange(0, np.max(x), step=500))
    plt.grid(True)
    plt.ylabel("Value", fontsize=size)
    plt.xlabel("Steps", fontsize=size)
    plt.legend(loc=4, bbox_to_anchor=(1, 0.2),prop={'size':size}, fancybox=False) #(loc='upper center', ncol=3, fancybox=True)
    plt.savefig("approximation-of-PI.pdf", bbox_inches='tight')
    plt.savefig("approximation-of-PI.svg", bbox_inches='tight')


import math
import matplotlib
import numpy as np

def dist(x1, y1, x2, y2):
    return math.sqrt((x2-x1)**2 + (y2-y1)**2)

def intersect(x1, y1, x2, y2, x3, y3, x4, y4):
    # unchecked implementation that appears to work
    # I can implement my own using the cross product vector based approach
    # where p * tr = q * us
    def ccw(x1, y1, x2, y2, x3, y3):
        return (y3-y1) * (x2-x1) > (y2-y1) * (x3-x1)
    return (ccw(x1,y1, x3,y3, x4,y4) != ccw(x2,y2, x3,y3, x4,y4) and
           ccw(x1,y1, x2,y2, x3,y3) != ccw(x1,y1, x2,y2, x4,y4))

def my_intersect(x1, y1, x2, y2, x3, y3, x4, y4):
    # based on https://stackoverflow.com/a/565282
    p = (x1, y1)
    r = (x2-x1, y2-y1)
    q = (x3, y3)
    s = (x4-x3, y4-y3)
    def calc(q, p, s, r):
        t = (q[0]-p[0], q[1]-p[1])
        t = (t[0]*s[1]) - (t[1]*s[0])
        t /= (r[0]*s[1]) - (r[1]*s[0])
        return t
    rs = (r[0]*s[1]) - (r[1]*s[0])
    if rs == 0:
        return False
    t = calc(q,p,s,r)
    u = calc(p,q,r,s)
    # print("t: {}".format(p[0]+ r[0]*t, p[1]+r[1]*t))
    # print("u: {}".format(q[0]+ s[0]*u, q[1]+s[1]*u))
    if t >= 0 and t <= 1 and u >= 0 and u <= 1:
        return (p[0]+ r[0]*t, p[1]+r[1]*t)

def has_intersection(polygon1, polygon2):
    # Check if there is any intersection between pairs of edges of polygons
    for i in range(len(polygon1)-1):
        x1, y1 = polygon1[i]
        x2, y2 = polygon1[i+1]
        for j in range(len(polygon2)-1):
            x3, y3 = polygon2[j]
            x4, y4 = polygon2[j+1]
            if intersect(x1, y1, x2, y2, x3, y3, x4, y4):
                return True
    return False

def point_inside_polygon(x, y, polygon):
    # source: https://stackoverflow.com/a/23453678
    test_polygon = [point for point in polygon]
    bb_path = matplotlib.path.Path(np.array(test_polygon))
    return bb_path.contains_point((x, y))

def is_inside(polygon1, polygon2):
    #print("Checking if poylgon1: \n {}".format(polygon1))
    #print("is inside polygon2: \n {}".format(polygon2))
    if has_intersection(polygon1, polygon2):
        return False

    # If no intersection is found, we just need to check that
    # at least 1 point of pol1 is within pol2
    for x, y in polygon1:
        if point_inside_polygon(x, y, polygon2):
            return True

    return False

def get_boundingbox(polygon):
    x, y = polygon[0]
    min_x, min_y, max_x, max_y = x, y, x, y
    for x, y in polygon:
        min_x = x if x < min_x else min_x
        min_y = y if y < min_y else min_y
        max_x = x if x > max_x else max_x
        max_y = y if y > max_y else max_y
    return min_x, min_y, max_x, max_y

def get_parallel_points(x1, y1, x2, y2, u, v, d):
    return x1 + d*u, y1 + d*v, x2 + d*u, y2 + d*v

def get_unit_vector(a, b):
    import math
    l = 1 / math.sqrt(a**2 + b**2)
    u = l * a
    v = l * b
    return u, v

def get_line_equation(x1, y1, x2, y2):
    a = y1 - y2
    b = x2 - x1
    c = (-(y1 - y2))*x1 + (x1 - x2)*y1
    return a, b, c

def get_pont_in_line(x1,y1,x2,y2,dist):
    d = math.sqrt((x2-x1)**2 + (y2-y1)**2)
    x3 = (dist*(x2-x1))/d + x1
    y3 = (dist*(y2-y1))/d + y1
    return x3, y3

def extend_line(x1,y1,x2,y2,ext=0.1):
    len_p1_p2 = dist(x1,y1,x2,y2)
    x3 = x2 + (x2 - x1) / len_p1_p2 * (ext*len_p1_p2)
    y3 = y2 + (y2 - y1) / len_p1_p2 * (ext*len_p1_p2)
    return x3, y3

def get_angle(lat1, long1, lat2, long2):
    import math
    dLon = (long2 - long1)

    y = math.sin(dLon) * math.cos(lat2)
    x = math.cos(lat1) * math.sin(lat2) - math.sin(lat1) * math.cos(lat2) * math.cos(dLon)

    brng = math.atan2(y, x)

    brng = math.degrees(brng)
    brng = (brng + 360) % 360
    brng = 360 - brng # count degrees clockwise - remove to make counter-clockwise

    return brng

# playing and understanding the code
def line_manipulation_demo():

    # points in a line
    x1, y1 = 4, 5
    x2, y2 = 7, 9
    print(x1, y1, x2, y2)

    # a, b, c terms that define the line
    a, b, c = get_line_equation(x1, y1, x2, y2)
    print(a, b, c)

    # unit vector of a perpendicular vector to the line given by a, b
    u, v = get_unit_vector(a, b)
    print(u, v)

    # calculate p3 and p4 given a multiple of the perpendicular unit vector
    d = 5
    x3, y3, x4, y4 = get_parallel_points(x1, y1, x2, y2, u, v, d)
    print(x3, y3, x4, y4)

    d = 1
    x3, y3, x4, y4 = get_parallel_points(x1, y1, x2, y2, u, v, d)
    print(x3, y3, x4, y4)


#!/usr/bin/env python3
import sys
import numpy as np

def inside_circle(total_count):

    x = np.random.uniform(size=total_count)
    y = np.random.uniform(size=total_count)

    radii_square = x**2 + y**2

    count = (radii_square<=1.0).sum()

    return count

def estimate_pi(n_samples):

    return (4.0 * inside_circle(n_samples) / n_samples)

if __name__=='__main__':

    n_samples = 10000
    if len(sys.argv) > 1:
        n_samples = int(sys.argv[1])

    my_pi = estimate_pi(n_samples)
    sizeof = np.dtype(np.float64).itemsize

    print("required memory {:.3f} MB".format(n_samples*sizeof*3/(1024*1024)))
    print("pi is {} from {} samples".format(my_pi,n_samples))
    print("error is {:.3e}".format(abs(my_pi - np.pi)))


import numpy as np
from math import *

# Global variables
NMAX = 100
TOLERANCE = 0.00001
OP = 1

def fe(a, b, op, f):
    if op == 1:
        return abs(f(a))
    elif op == 2:
        return abs(b - a)

    elif op == 3:
        return abs(b - a) / b

def bisection(lower_end, upper_end,  test, f):
    a = lower_end
    b = upper_end
    niter = 0
    m = lower_end + (upper_end - lower_end) / 2

    fa = f(lower_end)
    fb = f(upper_end)
    fm = f(m)
    ERROR = 100
    if not test:
        print("# iter\t\t a \t\t f(a) \t\t b \t\t f(b) \t\t m \t\t f(m)  \t\t ERROR")
        print(
            "{0} \t\t {1:6.4f} \t {2:6.4f} \t {3:6.4f} \t {4:6.4f} \t {5:6.4f} \t {6:6.4f} \t {7:6.9f}".format(
                niter, lower_end, fa, upper_end, fb, m, fm, ERROR
            )
        )

    while ERROR > TOLERANCE and niter < NMAX:
        m = lower_end + (upper_end - lower_end) / 2
        if np.sign(fa) == np.sign(fm):
            lower_end = m
            fa = f(lower_end)
        else:
            upper_end = m
            fb = f(upper_end)
        m = lower_end + (upper_end - lower_end) / 2
        fm = f(m)
        ERROR = fe(lower_end, upper_end, OP, f)

        niter += 1
        if not test:
            print(
                "{0} \t\t {1:6.4f} \t {2:6.4f} \t {3:6.4f} \t {4:6.4f} \t {5:6.4f} \t {6:6.4f} \t {7:6.9f}".format(
                    niter, lower_end, fa, upper_end, fb, m, fm, ERROR
                )
            )
    if not test:
        print(
            "The root of the function between [{0:6.4f}, {1:6.4f}] is: {2:6.4f}".format(
                a, b, m
            )
        )
        print("The estimated ERROR is: {0:6.8f}".format(ERROR))
    else:
        return m

def f_test_1(x):
    return (3 * x**3) - (2 * x) - 5

if __name__ == "__main__":
    lower_end = 0
    upper_end = 2
    bisection(lower_end, upper_end, False, f_test_1)

# Tests
def test_1():
    lower_end = 0
    upper_end = 2
    assert bisection(lower_end, upper_end,
                     True, f_test_1) == 1.3717398643493652


# Practicing Dynamic Programming (DP) with the log cutting problem given on the practice midterm.
# this file contains the solution from the given exam solutions.
import numpy as np

################################################################################
# Top Down Algorithm (Solution)
################################################################################

def top_down_woody(d):
    i = 0
    j = len(d) - 1
    c = np.ones([j+1, j+1])*np.inf
    return top_down_woody_aux(i, j, d, c)

def top_down_woody_aux(i, j, d, c):
    if j == i+1:
        return 0
    if c[i, j] == np.inf:
        for k in range(i+1, j):
            c[i, j] = min(c[i, j], d[j] - d[i] + top_down_woody_aux(i, k, d, c) + top_down_woody_aux(k, j, d, c))
    return c[i, j]

################################################################################
# Bottom Up Algorithm (solution)
################################################################################

def bottom_up_woody(d):
    n = len(d) - 2
    c = np.zeros([n+2, n+2])
    for m in range(2, n+2):
        for i in range(0, n-m+2):
            j = i + m
            c[i, j] = np.inf
            for k in range(i+1, j+1):
                c[i, j] = min(c[i, j], d[j] - d[i] + c[i, k] + c[k, j])
    return c[0, n+1]

################################################################################
# Main
################################################################################
if __name__ == '__main__':
    dist = np.array([0, 3, 8, 10])
    print("min cost (top down sol) = $" + str(top_down_woody(dist)))
    print("min cost (bottom up sol) = $" + str(bottom_up_woody(dist)))


import math

import numpy as np

from typing import Tuple, Callable, Union

INV_PHI = (math.sqrt(5) - 1) / 2
INV_PHI_SQUARED = (3 - math.sqrt(5)) / 2

def golden_section_search(func: Callable, low: float, up: float, tolerance: float = 1e-5,
                          return_mean: bool = True) -> Union[Tuple[float, float], float]:
    """Golden Section Search Algorithm - Taken directly from Wikipedia.

    Given a function f with a single local minimum in
    the interval [a,b], gss returns a subset interval
    [c,d] that contains the minimum with d-c <= tol.

    This implementation
    reuses function evaluations, saving 1/2 of the evaluations per
    iteration, and returns a bounding interval if return_mean=False, else it takes the mean of the
    upper and lower bound of the interval.

    Example:
        f = lambda x: (x-2)**2
        a = 1
        b = 5
        tol = 1e-5
        (c,d) = golden_section_search(f, a, b, tol)
        print(c, d)
        1.9999959837979107 2.0000050911830893
    """
    (low, up) = (min(low, up), max(low, up))
    h = up - low
    if h <= tolerance:
        if return_mean:
            return float(np.mean([low, up]))
        else:
            return low, up

    # Required steps to achieve tolerance
    n = int(math.ceil(math.log(tolerance / h) / math.log(INV_PHI)))

    c = low + INV_PHI_SQUARED * h
    d = low + INV_PHI * h
    yc = func(c)
    yd = func(d)

    for k in range(n - 1):
        if yc < yd:
            up = d
            d = c
            yd = yc
            h = INV_PHI * h
            c = low + INV_PHI_SQUARED * h
            yc = func(c)
        else:
            low = c
            c = d
            yc = yd
            h = INV_PHI * h
            d = low + INV_PHI * h
            yd = func(d)

    if yc < yd:
        if return_mean:
            return float(np.mean([low, d]))
        else:
            return low, d
    else:
        if return_mean:
            return float(np.mean([c, up]))
        else:
            return c, up


"""
9.4-2. The Wirehouse Lumber Company will soon begin logging eight groves of trees in the same general area. Therefore,
it must develop a system of dirt roads that makes each grove accessible from every other grove. The distance (in
miles) between every pair of groves is as follows:

                grove_1   grove_2     grove_3     grove_4     grove_5     grove_6     grove_7     grove_8
    grove_1     --        1.3         2.1         0.9         0.7         1.8         2.0         1.5
    grove_2     1.3       --          0.9         1.8         1.2         2.6         2.3         1.1
    grove_3     2.1       0.9         --          2.6         1.7         2.5         1.9         1.0
    grove_4     0.9       1.8         2.6         --          0.7         1.6         1.5         0.9
    grove_5     0.7       1.2         1.7         0.7         --          0.9         1.1         0.8
    grove_6     1.8       2.6         2.5         1.6         0.9         --          0.6         1.0
    grove_7     2.0       2.3         1.9         1.5         1.1         0.6         --          0.5
    grove_8     1.5       1.1         1.0         0.9         0.8         1.0         0.5         --

Management now wishes to determine between which pairs of groves the roads should be constructed to connect all
groves with a minimum total length of road.<br>

(a) Describe how this problem fits the network description of the minimum spanning tree problem.
(b) Use the algorithm described in Sec. 9.4 to solve the problem.

Copyright @author: S. Lei
"""

import pandas as pd
import matplotlib.pyplot as plt
import networkx as nx
from helper_functions.util import export_soln_to_csv
from networkx.algorithms import tree

# model name
m = '9.4-2_v2'

# read in data
df = pd.read_csv('data_arc.csv')
df = df[df['distance'] != 0]

# create a Graph object
arcs = list((t[1], t[2]) for t in df.itertuples())
distance_dict = dict([((t[1], t[2]), t.distance) for t in df.itertuples()])

G = nx.Graph()

for x in arcs:
    i = x[0]
    j = x[1]
    weight = distance_dict[i,j]
    G.add_edge(i,j,weight = weight)

# solve for MSP
mst = tree.minimum_spanning_edges(G, algorithm='prim', data=False)
edges = list(mst)
edges

# print results
distance_lst = []
for x in edges:
    i = x[0]
    j = x[1]
    weight = distance_dict[i,j]
    distance_lst.append(weight)
total_distance = sum(distance_lst)
print(total_distance)
print(edges)

pos = nx.spring_layout(G)
nx.draw_networkx_nodes(G, pos, node_size=400)
nx.draw_networkx_edges(G, pos, edgelist=edges, width=1)
nx.draw_networkx_labels(G, pos, font_size=15, font_family='sans-serif')

plt.axis('off')
plt.show()

# export solution to csv
node_1 = [i[0] for i in edges]
node_2 = [i[1] for i in edges]

output_df = pd.DataFrame(
    {'node_1': node_1,
     'node_2': node_2,
     'distance': distance_lst
     })

export_soln_to_csv(output_df, m)

9.4 (The Minimum Spanning Tree Problem)/prob_2/mod_v2.py


from numba import jit
import numpy as np
import math
import time

@jit('f4[:,:](i2,f4[:,:])')
def ra_numba(doy, lat):

    M, N = lat.shape

    ra = np.zeros_like(lat)
    Gsc = 0.0820

    # math.pi doesnt work?
    # NumbaError: 11:31: Binary operations mul on values typed object_ and object_ not (yet) supported)
    pi = math.pi
    #pi = 3.1415926535897932384626433832795

    dr = 1 + 0.033 * math.cos( 2 * pi / 365 * doy)
    decl = 0.409 * math.sin( 2 * pi / 365 * doy - 1.39 )

    for i in range(M):
        for j in range(N):

            # it crashes without the float() wrapped around the array slicing?!
            ws = math.acos(-1 * math.tan(float(lat[i,j])) * math.tan(decl))
            ra[i,j] = 24 * 60 / pi * Gsc * dr * ( ws * math.sin(float(lat[i,j])) * math.sin(decl) + math.cos(float(lat[i,j])) * math.cos(decl) * math.sin(ws)) * 11.6

    return ra

def ra_numpy(doy, lat):

    Gsc = 0.0820

    pi = math.pi

    dr = 1 + 0.033 * np.cos( 2 * pi / 365 * doy)
    decl = 0.409 * np.sin( 2 * pi / 365 * doy - 1.39 )
    ws = np.arccos(-np.tan(lat) * np.tan(decl))

    ra = 24 * 60 / pi * Gsc * dr * ( ws * np.sin(lat) * np.sin(decl) + np.cos(lat) * np.cos(decl) * np.sin(ws)) * 11.6

    return ra

ra_python = ra_numba.py_func

doy = 120 # day of year

py = []
nump = []
numb = []
dims = []

for dim in [25,50,100,200,400,800,1600]:

    dims.append(dim)

    lat = np.deg2rad(np.ones((dim,dim), dtype=np.float32) * 45.) # array of 45 degrees latitude converted to rad

    tic = time.clock()
    ra_nb = ra_numba(doy, lat)
    numb.append(time.clock() - tic)

    tic = time.clock()
    ra_np = ra_numpy(doy, lat)
    nump.append(time.clock() - tic)

    tic = time.clock()
    ra_py = ra_python(doy, lat)
    py.append(time.clock() - tic)

dims = np.array(dims)**2
py = np.array(py)
numb = np.array(numb)
nump = np.array(nump)


import numpy as np

#training set
#x: {n features, m samples as n*m array}
#x =  np.array([ […] ... ])
#for e.g., a 3 * 4 array for 3 fearures, 4 samples

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

#calculate u (mean) and var (variance)
u = np.mean(x,axis=1,keepdims=True)
var = np.mean((x - u)**2, axis=1, keepdims=True)

#gaussian probability (vectorised implementation)
# x {n features, m samples as n*m array}
# u = mean of each feature as n * 1 array
# var = variance of each feature as n* 1 array
def prob_gaussian(x,u,var):
 p1 = 1/np.sqrt(2*np.pi*var)
 p2 = np.exp(- ((x-u)**2) / (2*var))
 return p1*p2

#epsilon - anomaly threshold, to be tuned based on performance on anomalous vs normal samples
epsilon = 0.02

#flags anomaly if prob_prod<epsilon
# x_test {n features, m samples as n*m array}
# u = mean of each feature as n * 1 array
# var = variance of each feature as n* 1 array
# epsilon = anomaly threshold
def anomaly(x_test, u, var, epsilon):
 probs = prob_gaussian(x_test, u, var)
 prob_prod = np.prod(probs,axis=0)
 return prob_prod, prob_prod<epsilon

#test with some examples

x_test = [[3],
          [2],
          [4]]

anomaly(x_test, u, var, epsilon)
#(array([0.03351253]), array([False]))

x_test = [[0],
          [5],
          [6]]

#(array([9.39835262e-05]), array([ True]))


# calcuation.py
# calculate linear regession line and correlation coefficient

import numpy as np
import copy

# funtion to inverse the 2x2 matrix
def inverse_matrix(normal_matrix):

    scalar = 1 / ((normal_matrix[0][0] * normal_matrix[1][1]) - (normal_matrix[0][1] * normal_matrix[1][0]))
    # scalar = np.linalg.det(normal_matrix))    # the code above could be replace by this line, and this is also applicable for different size of matrix

    inversed_matrix = copy.deepcopy(normal_matrix)  # deepcopy the normal_matrixto inversed_matrix to store in another memory

    # inverse the matrix manually
    inversed_matrix[0][0] = normal_matrix[1][1]
    inversed_matrix[0][1] = -normal_matrix[1][0]
    inversed_matrix[1][0] = -normal_matrix[0][1]
    inversed_matrix[1][1] = normal_matrix[0][0]

    # iterate through rows and columns of the matrix
    for row in range(len(inversed_matrix)):
        for column in range(len(inversed_matrix[row])):
            # replace the value in the inversed_matrix after scalar multplication
            inversed_matrix[row][column] = inversed_matrix[row][column] * scalar

    return inversed_matrix  # return inversed matrix (a nested list)

# function to find the linear regression line
def find_linear_regress(x_values, y_values):

    # create nested array with ones and insert x_values as colomn and update x_values
    x_values = np.insert(np.ones((x_values.size, 1)), 1, x_values, axis=1)
    x_T_x = x_values.transpose().dot(x_values)
    x_T_y = np.dot(x_values.transpose(), y_values)

    x_T_x_inv = np.array(inverse_matrix(x_T_x.tolist()))    # convert x_T_x to list, and call function to find inversed matrix, then convert the return value to numpy array

    b_m = x_T_x_inv.dot(x_T_y)  # multiply both matrix to find A, the values for b and m

    verify_b_m = np.linalg.inv(x_values.transpose().dot(x_values)).dot(x_values.T).dot(y_values)  # the code above could be replace by this one line by using the function from numpy

    return b_m, verify_b_m  # return both variables in numpy array form

# function to find the correlation coefficient
def find_corr_coeff(x_values, y_values, b_m):

    e_values = np.array([y_values[i] - (b_m[1] * x_values[i] + b_m[0]) for i in range(len(x_values))])  # find matrix E
    sse = e_values.transpose().dot(e_values)    # find the sum of squares error

    sst = np.sum((y_values - np.mean(y_values)) ** 2)   # find the sum of squares total
    ssr = sst - sse # find the sum of squares regression
    r_2 = ssr / sst # find the r^2 value

    verify_r_2 = (np.corrcoef(x_values, y_values)[0,1])**2  # the code above could be replace by this one line by using the function from numpy

    return r_2, verify_r_2  # return both variables


# # Big O Notation

# The goal of this lesson is to develop your ability to look at some code and indentify its time complexity, using Big O notation.

# Comparison computacional complexity
import matplotlib.pyplot as plt
from scipy.special import gamma
import math
import numpy as np
n = np.linspace(1,101,100)
O1 = gamma(n)
O2 = 2**n
O3 = n**2
O4 = n*np.log(n) / np.log(2)
O5 = n
O6 = np.sqrt(n)
O7 = np.log(n) / np.log(2)
plt.plot(n, O1, '--k', label='n!')
plt.plot(n, O2, '--r', label='2^n')
plt.plot(n, O3, '--g', label='n^2')
plt.plot(n, O4, 'y', label='nlog(n)')
plt.plot(n, O5, 'c', label='n')
plt.plot(n, O6, '--m', label='sqrt(n)')
plt.plot(n, O7, 'b', label='log(n)')
axes = plt.gca()
axes.set(xlim=(0, 100), ylim=(0, 100))
leg = axes.legend()
plt.show()

# O(N!)
# This is the Heap's algorithm, which is used for generating all possible permutation of n objects
# Another example could be the Travelling Salesman Problem
def Permutation(data, n):
    if n == 1:
        print(data)
        return
    for i in range(n):
        Permutation(data, n - 1)
        if n % 2 == 0:
            data[i], data[n-1] = data[n-1], data[i]
        else:
            data[0], data[n-1] = data[n-1], data[0]
data = [1, 2]
Permutation(data,len(data))
get_ipython().run_line_magic('time', '')

# O(2^n)
# Recursive calculation of Fibonacci numbers
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(20))
get_ipython().run_line_magic('time', '')

# O(N^2)
# Print pair of numbers in the data

def Print_Pair(some_list):
    for i in some_list:
        for j in some_list:

            print("Items: {}, {}".format(i,j))
Print_Pair([1, 2, 3, 4])
get_ipython().run_line_magic('time', '')

# O(nlog(n))
# Mergesort algorithm
def Merge_Sort(data):
    if len(data) <= 1:
        return

    mid = len(data) // 2
    left_data = data[:mid]
    right_data = data[mid:]

    Merge_Sort(left_data)
    Merge_Sort(right_data)

    left_index = 0
    right_index = 0
    data_index = 0

    while left_index < len(left_data) and right_index < len(right_data):
        if left_data[left_index] < right_data[right_index]:
            data[data_index] = left_data[left_index]
            left_index += 1
        else:
            data[data_index] = right_data[right_index]
            right_index += 1
        data_index += 1

    if left_index < len(left_data):
        del data[data_index:]
        data += left_data[left_index:]
    elif right_index < len(right_data):
        del data[data_index:]
        data += right_data[right_index:]

data = [9, 0, 8, 6, 2, 5, 7, 3, 4, 1]
Merge_Sort(data)
print(data)
get_ipython().run_line_magic('time', '')

# O(n)
# Just print some itens

def Print_Item(data):
    for i in data:
        print(i)

Print_Item([1, 2, 3, 4])
get_ipython().run_line_magic('time', '')

# Linear search
def Linear_Search(data, value):
    for index in range(len(data)):
        if value == data[index]:
            return index
    raise ValueError('Value not found in the list')
data = [1, 3, 7, 4, 5, 9, 0, 11]
print(Linear_Search(data,9))
get_ipython().run_line_magic('time', '')

# O(log(n))
# This algorithms with logarithmic time complexity are commonly found on binary trees
for idx in range(0, len(data), 3):
    print(data[idx])
get_ipython().run_line_magic('time', '')

# Binary search
def binary_search(data, value):
    n = len(data)
    left = 0
    right = n - 1
    while left <= right:
        middle = (left + right) // 2
        if value < data[middle]:
            right = middle - 1
        elif value > data[middle]:
            left = middle + 1
        else:
            return middle
    raise ValueError('Value is not in the list')

data = [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(binary_search(data, 8))

# O(0n + 1)

def First_Idx(data):
    return data[0]

data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(First_Idx(data))
get_ipython().run_line_magic('time', '')


# solutions.py
"""Volume I: Monte Carlo Integration
Solutions file. Written by Tanner Christensen
"""

import numpy as np
import scipy.stats as stats

def mc_int(f, mins, maxs, numPoints=500, numIters=100):
    """Use Monte-Carlo integration to approximate the integral of f
    on the box defined by mins and maxs.

    Inputs:
        f (function) - The function to integrate. This function should
            accept a 1-D NumPy array as input.
        mins (1-D np.ndarray) - Minimum bounds on integration.
        maxs (1-D np.ndarray) - Maximum bounds on integration.
        numPoints (int, optional) - The number of points to sample in
            the Monte-Carlo method. Defaults to 500.
        numIters (int, optional) - An integer specifying the number of
            times to run the Monte Carlo algorithm. Defaults to 100.

    Returns:
        estimate (int) - The average of 'numIters' runs of the
            Monte-Carlo algorithm.

    Example:
        >>> f = lambda x: np.hypot(x[0], x[1]) <= 1
        >>> # Integral over the square [-1,1] x [-1,1]. Should be pi.
        >>> mc_int(f, np.array([-1,-1]), np.array([1,1]))
        3.1290400000000007
    """
    if len(mins) != len(maxs):
        raise ValueError("Dimension of mins and maxs must be the same")

    results = []
    for i in xrange(numIters):
        # create points
        dim = len(mins)
        side_lengths = maxs-mins
        points = np.random.rand(numPoints,dim)
        points = side_lengths*points + mins

        # calculate Volume
        V = 1
        for i in xrange(dim):
            V *= maxs[i] - mins[i]

        # apply the function f along axis=1 and sum all the results
        total = np.sum(np.apply_along_axis(f,1,points))
        results.append((V/float(numPoints))*total)
    estimate = np.average(results)
    return estimate

def joint_normal(mins, maxs):
    """Caluclate the integral of the joint normal distribution using SciPy and
    Monte Carlo integration.

    Inputs:
        mins (1-D np.ndarray) - Minimum bounds of integration.
        maxs (1-D np.ndarray) - Maximum bounds of integration.

    Returns:
        value (int) - result from intregration using SciPy
        estimate (1-D np.ndarray) - result of Monte Carlo integration
            using 'numPoints' = {10,100,1000,10000}
    """
    # define means and covs
    means = np.zeros(len(mins))
    covs = np.eye(len(mins))

    #calculate integral using SciPy
    value, inform = stats.mvn.mvnun(mins, maxs, means, covs)

    f = lambda x: (1./np.sqrt((2*np.pi)**(len(x))))*np.exp(-x.dot(x)/2)
    estimates = []
    for n in xrange(1,5):
        estimates.append(mc_int(f,mins,maxs,numPoints=10**n))
    estimates = np.array(estimates)

    return value, estimates


# Dependencies
import matplotlib.pyplot as plt
import numpy as np
from scipy.fft import fft,ifft
from module_1 import *

# Sampling
# Length of the signal
L = 1
# Sampling frequency
sf = 12
# Sampling points
sp = sf*L
# Time partition
t_part = np.linspace(0,L,sp,endpoint=False) #[s]
# Frequency partition
f_part = np.arange(0,sp/L,1/L)

# Sinewave signal parameters
freq = 1 # frequency of the signal in Hz (1/period)
A = 1. # Amplitude of the signal [V]
phi = 0. # phase shift of the signal [degree]
# Sinewave signal function
omega = 2*np.pi*freq
func = A*np.sin(omega*t_part + degree_to_rad(phi))

# fourier transform of func
ft = np.empty(sp, dtype = np.complex_)
ft = fft(func) # backward normalization (i.e, no normalisation for fft, 1/sp for ifft)

# inverse fourier transform of ft
ift = np.empty(sp, dtype = np.complex_)   # *1/sp
ift = ifft(ft)

# modulus and phase spectrum
modulus = np.abs(ft[0:int(sp/2)])
phase = np.arctan2(ft.imag[0:int(sp/2)],ft.real[0:int(sp/2)])

# Index associated with the maximum Fourier's coefficient
max_mod = np.max(modulus)
index = np.where(modulus==max_mod)

# Amplitute of the signal through maximum fourier coefficient
A_max = max_mod/(sp/2)
phase_max = phase[index]

# PRINTING AMPLIUDE AND PHASE OF THE SIN FUNCTION
print("PRINTING THE VALUES OF THE AMPLITUDE AND THE PHASE OF THE SINEWAVE CALCULATED FROM THE FOURIER'S COEFFICIENTS")
print("----------------------------------------------------------------------------------------------------------")
print("AMPLITUTE:")
print("Expected value = %1.8f"%A)
print("Calculated from the maximum Fourier coefficient = %1.8f"%A_max)
print("-----------------------------------------------")
print("PHASE")
print("Expected value = %1.8f"%phi)
print("Calculated from the maximum Fourier coefficients = %1.8f"%(90+rad_to_degree(phase_max)))

# Creating visualization
fig, ax = plt.subplots(3)

# plotting the sin function and the inverse fourier transform
ax[0].plot(t_part,func,"-",color = "red",label=r"$sin(\omega t)$")
ax[0].plot(t_part,ift.real,".",color = "blue",label =r"$F^{-1}[F[\nu]]$")
ax[0].legend()
ax[0].set_xlabel("Time (s)")
ax[0].set_ylabel("Signal [a.u]")

# plotting the modulus of the fourier transform
ax[1].plot(f_part[0:int(sp/2)],modulus[0:int(sp/2)], marker='o',markerfacecolor="red")
ax[1].set_xlabel("Frequency [Hz]")
ax[1].set_ylabel("Amplitude [a.u]")

# plotting the fourier transform (imaginary part)
ax[2].plot(f_part[0:int(sp/2)],rad_to_degree(phase[0:int(sp/2)])+90, marker='o',markerfacecolor="red")
ax[2].set_xlabel("Frequency [Hz]")
ax[2].set_ylabel("Phase [degree]")

plt.show()


import numpy as np
import matplotlib.pyplot as plt

def plotData(x, y):
    """ Plots the data points x and y into a new figure;
        plotData(x, y) plots the data points and gives the figure axes labels of population and profit.
    """
    plt.figure()
    plt.plot(X, y, 'rx')
    plt.xlabel('Profit in $10,000s')
    plt.ylabel('Population of City in 10,000s')

# load comma separated txt file
data = np.loadtxt('ex1data1.txt', delimiter=',')
X = data[:, 0][:, np.newaxis]
y = data[:, 1][:, np.newaxis]
plotData(X, y)

def computeCost(X, y, theta):
    """ computeCost(X, y, theta) computes the cost of using theta as the
   parameter for linear regression to fit the data points in X and y
    """

    m = len(y)
    J = 0.5 / m * np.sum((np.dot(X, theta)-y)**2)

    return J

def gradientDescent(X, y, theta, alpha, num_iters):
    """ gradientDescent(X, y, theta, alpha, num_iters) updates theta by
    taking num_iters gradient steps with learning rate alpha
    """
    m = len(y)
    J_history = np.zeros((num_iters, 1))
    print(J_history.shape)

    for iter in range(num_iters):

        # update theta
        theta = theta - alpha / m * np.dot(X.T, (np.dot(X, theta)-y))
        J_history[iter, 0] = computeCost(X, y, theta)

    return theta, J_history

m = len(y)
# X with bias items
X_wb = np.append(np.ones((m, 1)), X, axis=1)
theta0 = np.zeros((2, 1))

# gradient descent settings
iterations = 1500
alpha = 0.01

# test cost function
J = computeCost(X_wb, y, theta0)
print('Expected 32.07 here.')
print('Actual result is {:.2f}.'.format(J))

J = computeCost(X_wb, y, np.array([[-1], [2]]))
print('Expected 54.24 here.')
print('Actual result is {:.2f}.'.format(J))

# run gradient descent
theta, _ = gradientDescent(X_wb, y, theta0, alpha, iterations)

# test theta
print('Expected \n-3.6303 \n1.1664\n')
print('Actual result is \n{:.4f} \n{:.4f}'.format(theta[0, 0], theta[1, 0]))

# Plot the result
plotData(X, y)
plt.plot(X, np.dot(X_wb, theta))
plt.legend(['Training data', 'Linear regression'])

# Create theta grid
theta0_vals = np.linspace(-10, 10, 100)
theta1_vals = np.linspace(-1, 4, 100)

# initialize J_vals
J_vals = np.zeros((len(theta0_vals), len(theta1_vals)))

# Fill out J_vals
for i in range(len(theta0_vals)):
    for j in range(len(theta1_vals)):
        t = np.array([[theta0_vals[i]], [theta1_vals[j]]])
        J_vals[i, j] = computeCost(X_wb, y, t)

J_valsT = J_vals.T

# # Contour plot
# plt.contour(theta0_vals, theta1_vals, J_valsT, np.logspace(-2, 3, 20))
# plt.plot(theta[0], theta[1], 'rx')

# Surface plot
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
Xt, Yt = np.meshgrid(theta0_vals, theta1_vals)
surf = ax.plot_surface(Xt, Yt, J_valsT)
ax.view_init(30, -125)

plt.show()


"""
Animated example of Linear Regression with Gradient Descent

MIT License

Copyright (c) 2021 Luiz Gustavo da Rocha Charamba

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation

# cost function method
def chi_square(m, c, X, Y):
    error = 0
    n = len(X)
    for xi, yi in zip(X,Y):
        error += (yi - (m*xi + c))*(yi - (m*xi + c))

    return error/n

# partial derivative of chi square for m line parameter
def dChi_dm(m, c, X, Y):
    m_grad = 0
    n = len(X)
    for xi, yi in zip(X,Y):
        m_grad += xi*(yi - (m*xi + c))

    return (-2.0/n)*m_grad

# partial derivative of chi square for c line parameter
def dChi_dc(m, c, X, Y):
    c_grad = 0
    n = len(X)
    for xi, yi in zip(X,Y):
        c_grad += (yi - (m*xi + c))

    return (-2.0/n)*c_grad

# gradient descent updates line parameters
def grad_descent(m, c, lr, X, Y):
    m_ = m - lr*dChi_dm(m, c, X, Y)
    c_ = c - lr*dChi_dc(m, c, X, Y)
    return m_, c_

def calc_line(m, c, X):
    Y = []
    for xi in X:
        Y.append(m*xi + c)
    return Y

def generate_noisydata(m, c, X):
    noise = np.random.randint(40, size=(50))
    noise = noise - 20
    noise = 0.1*noise
    Y = calc_line(m, c, X)
    X_data = X
    Y_data = Y + noise
    return X_data, Y_data

# original line
m = 0.0
c = 0.0

fig = plt.figure()
ax = plt.axes(xlim=(0, 50), ylim=(0, 30))
data_points, = ax.plot([],[], 'o', color='orange')
line_fitted, = ax.plot([],[])
text = ax.text(0.5, 0.5, '', fontsize=12)

X_data, Y_data = generate_noisydata(0.5, 10, range(0,50))
X_line = X_data
iterations = 5000 #10000
lr = 0.001 # learning rate

# Training
M, C = [m],[c]
for it in range(iterations):
    m,c = grad_descent(m, c, lr, X_data, Y_data)
    M.append(m)
    C.append(c)

def init_animation():
    line_fitted.set_data([], [])
    data_points.set_data(X_data, Y_data)
    return data_points, line_fitted, text

font0 = {'family': 'serif',
        'color':  'black',
        'weight': 'normal',
        'size': 16,
        }

font1 = {'family': 'serif',
        'color':  'darkred',
        'weight': 'normal',
        'size': 16,
        }

font2 = {'family': 'serif',
        'color':  'blue',
        'weight': 'normal',
        'size': 14,
        }

def animate(i):
    plt.title('Linear Regression with Gradient Descent optimization in MSE', fontdict=font1)
    mserror = chi_square(M[i], C[i], X_data, Y_data)
    text0 = ax.text(0.5, 28, "Iteration: " + str(i+1), fontdict=font0)
    text1 = ax.text(0.5, 26, "MSE = " + "{:.4f}".format(mserror), fontdict=font1)
    text2 = ax.text(0.5, 23, "Line Parameters: \n" + "(m = " + "{:.4f}".format(M[i]) + ", c = " + "{:.4f}".format(C[i]) + ")", fontdict=font2)

    Y_line = calc_line(M[i], C[i], X_line)
    line_fitted.set_data(X_line, Y_line)
    data_points.set_data(X_data, Y_data)

    return data_points, line_fitted, text0, text1, text2

print("Line Parameters:\n(m = " + str(m) + ", c = " + str(c) + ")")

# Animation
anim = animation.FuncAnimation(fig, animate, init_func=init_animation,
                               frames=iterations, interval=2, blit=True, repeat=False)

plt.show()