"""
Name:
Class:
MSSV:

You should understand your code

A simple implementation of 2d Perceptron
"""

import numpy as np

class LinearModel2D:

    def __init__(self, a1, a2, b):
        """

        :param a1:
        :param a2:
        :param b:
        """
        self.w = np.array([a1, a2])
        self.b = b

    def __call__(self, x):

        # The index of numpy start from 1
        f = np.dot(x, self.w) + self.b*np.ones(len(x))

        return np.where(f >= 0.5, 1, 0)

    def __str__(self):
        return f"w = {self.w}, b = {self.b}"

# Main
if __name__ == "__main__":

    X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
    y = np.array([0, 1, 1, 1])

    accuracy = []

    np.random.seed(1)
    # Q1: Update to calculate the expected accuracy in case of N(0, 1)
    for _ in range(10000):
        a1 = np.random.uniform(-1, 1)
        a2 = np.random.uniform(-1, 1)
        f = LinearModel2D(a1, a2, 1)

        output = f(X)
        accuracy += [np.mean(np.where(output == y, 1, 0))]

    print("Empirical Accuracy = ", np.mean(accuracy))


"""LogNormal distribution
"""
import numpy as np
from scipy.stats import lognorm

from xgboost_distribution.distributions.base import BaseDistribution
from xgboost_distribution.distributions.utils import check_is_gt_zero

class LogNormal(BaseDistribution):
    """LogNormal distribution with log scoring.

    Definition:

        f(x) = exp( -[ (log(x) - log(scale)) / (2 s^2) ]^2 / 2 ) / s

    with parameters (scale, s).

    We reparameterize:
        s -> log(s) = a
        scale -> log(scale) = b

    Note that b essentially becomes the 'loc' of the distribution:

        log(x/scale) / s = ( log(x) - log(scale) ) / s

    which can then be taken analogous to the normal distribution's

        (x - loc) / scale

    Hence we can re-use the computations in `distribution.normal`, exchanging:

        y -> log(y)
        scale -> s

    """

    @property
    def params(self):
        return ("scale", "s")

    def check_target(self, y):
        check_is_gt_zero(y)

    def gradient_and_hessian(self, y, params, natural_gradient=True):
        """Gradient and diagonal hessian"""

        log_y = np.log(y)

        loc, log_s = self._split_params(params)  # note loc = log(scale)
        var = np.exp(2 * log_s)

        grad = np.zeros(shape=(len(y), 2))
        grad[:, 0] = (loc - log_y) / var
        grad[:, 1] = 1 - ((loc - log_y) ** 2) / var

        if natural_gradient:
            fisher_matrix = np.zeros(shape=(len(y), 2, 2))
            fisher_matrix[:, 0, 0] = 1 / var
            fisher_matrix[:, 1, 1] = 2

            grad = np.linalg.solve(fisher_matrix, grad)

            hess = np.ones(shape=(len(y), 2))  # we set the hessian constant
        else:
            hess = np.zeros(shape=(len(y), 2))  # diagonal elements only
            hess[:, 0] = 1 / var
            hess[:, 1] = 2 * ((log_y - loc) ** 2) / var

        return grad, hess

    def loss(self, y, params):
        scale, s = self.predict(params)
        return "LogNormalError", -lognorm.logpdf(y, s=s, scale=scale).mean()

    def predict(self, params):
        log_scale, log_s = self._split_params(params)
        scale, s = np.exp(log_scale), np.exp(log_s)

        return self.Predictions(scale=scale, s=s)

    def starting_params(self, y):
        log_y = np.log(y)
        return np.mean(log_y), np.log(np.std(log_y))

    def _split_params(self, params):
        """Return log_scale (loc) and log_s from params"""
        return params[:, 0], params[:, 1]


import numpy as np

def sigmoid(z):
    return 1/(1 + np.exp(-z))

def initialize_parameters(n_x, n_h, n_y):
    np.random.seed(7)

    W1 = np.random.randn(n_h, n_x) * 0.01
    b1 = np.zeros((n_h, 1))
    W2 = np.random.randn(n_y, n_h) * 0.01
    b2 = np.zeros((n_y, 1))

    parameters = {"W1": W1,
                  "b1": b1,
                  "W2": W2,
                  "b2": b2}

    return parameters

def forward_propagate(X, parameters):
    W1 = parameters["W1"]
    b1 = parameters["b1"]
    W2 = parameters["W2"]
    b2 = parameters["b2"]

    Z1 = np.dot(W1, X) + b1
    A1 = np.tanh(Z1)
    Z2 = np.dot(W2, A1) + b2
    A2 = sigmoid(Z2)

    cache = {"Z1": Z1,
             "A1": A1,
             "Z2": Z2,
             "A2": A2}

    return A2, cache

def compute_cost(A2, Y):
    # cross entropy
    logprobs = (Y * np.log(A2)) + ((1-Y) * np.log(1-A2))
    cost = -np.average(logprobs)

    cost = np.squeeze(cost)

    return cost

def backward_propagate(parameters, cache, X, Y):
    m = X.shape[1]

    W2 = parameters["W2"]

    A1 = cache["A1"]
    A2 = cache["A2"]

    # last layer
    dZ2 = A2-Y
    dW2 = (1/m)*np.dot(dZ2, A1.T)
    db2 = (1/m)*np.sum(dZ2, axis=1, keepdims=True)

    # first layer
    dZ1 = np.multiply(np.dot(W2.T, dZ2), (1-np.power(A1,2)))
    dW1 = (1/m)*np.dot(dZ1, X.T)
    db1 = (1/m)*np.sum(dZ1, axis=1, keepdims=True)

    grads = {"dW1": dW1,
             "db1": db1,
             "dW2": dW2,
             "db2": db2}

    return grads

def update_parameters(parameters, grads, learning_rate = 1.2):
    W1 = parameters["W1"]
    b1 = parameters["b1"]
    W2 = parameters["W2"]
    b2 = parameters["b2"]

    dW1 = grads["dW1"]
    db1 = grads["db1"]
    dW2 = grads["dW2"]
    db2 = grads["db2"]

    W1 = W1 - learning_rate * dW1
    b1 = b1 - learning_rate * db1
    W2 = W2 - learning_rate * dW2
    b2 = b2 - learning_rate * db2

    parameters = {"W1": W1,
                  "b1": b1,
                  "W2": W2,
                  "b2": b2}

    return parameters


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

import numpy as np
from scipy.stats import multivariate_normal
from sklearn.mixture import GaussianMixture
from mpl_toolkits.mplot3d import Axes3D
import matplotlib as mpl
import matplotlib.pyplot as plt
from sklearn.metrics.pairwise import pairwise_distances_argmin

mpl.rcParams['font.sans-serif'] = [u'SimHei']
mpl.rcParams['axes.unicode_minus'] = False

if __name__ == '__main__':
    style = 'sklearn'

    np.random.seed(0)
    mu1_fact = (0, 0, 0)
    cov_fact = np.identity(3)
    data1 = np.random.multivariate_normal(mu1_fact, cov_fact, 400)  # 400个样本的多元正态分布
    mu2_fact = (2, 2, 1)
    cov_fact = np.identity(3)
    data2 = np.random.multivariate_normal(mu2_fact, cov_fact, 100)  # 100个样本的多元正态分布
    data = np.vstack((data1, data2))
    y = np.array([True] * 400 + [False] * 100)

    if style == 'sklearn':
        g = GaussianMixture(n_components=2, covariance_type='full', tol=1e-6, max_iter=1000)  # covariance_type 有4中类型
        g.fit(data)
        print('类别概率:\t', g.weights_[0])
        print('均值:\n', g.means_, '\n')
        print('方差:\n', g.covariances_, '\n')
        mu1, mu2 = g.means_
        sigma1, sigma2 = g.covariances_
    else:  # 自己写代码实现的GMM
        num_iter = 100
        n, d = data.shape
        # 随机指定
        # mu1 = np.random.standard_normal(d)
        # print mu1
        # mu2 = np.random.standard_normal(d)
        # print mu2
        mu1 = data.min(axis=0)
        mu2 = data.max(axis=0)
        sigma1 = np.identity(d)
        sigma2 = np.identity(d)
        pi = 0.5
        # EM
        for i in range(num_iter):
            # E Step
            norm1 = multivariate_normal(mu1, sigma1)
            norm2 = multivariate_normal(mu2, sigma2)
            tau1 = pi * norm1.pdf(data)
            tau2 = (1 - pi) * norm2.pdf(data)
            gamma = tau1 / (tau1 + tau2)

            # M Step
            mu1 = np.dot(gamma, data) / np.sum(gamma)
            mu2 = np.dot((1 - gamma), data) / np.sum((1 - gamma))
            sigma1 = np.dot(gamma * (data - mu1).T, data - mu1) / np.sum(gamma)
            sigma2 = np.dot((1 - gamma) * (data - mu2).T, data - mu2) / np.sum(1 - gamma)
            pi = np.sum(gamma) / n
            print(i, ":\t", mu1, mu2)
        print('类别概率:\t', pi)
        print('均值:\t', mu1, mu2)
        print('方差:\n', sigma1, '\n\n', sigma2, '\n')

    # 预测分类
    norm1 = multivariate_normal(mu1, sigma1)
    norm2 = multivariate_normal(mu2, sigma2)
    tau1 = norm1.pdf(data)
    tau2 = norm2.pdf(data)

    fig = plt.figure(figsize=(13, 7), facecolor='w')
    ax = fig.add_subplot(121, projection='3d')
    ax.scatter(data[:, 0], data[:, 1], data[:, 2], c='b', s=30, marker='o', depthshade=True)
    ax.set_xlabel('X')
    ax.set_ylabel('Y')
    ax.set_zlabel('Z')
    ax.set_title(u'原始数据', fontsize=18)
    ax = fig.add_subplot(122, projection='3d')
    order = pairwise_distances_argmin([mu1_fact, mu2_fact], [mu1, mu2], metric='euclidean')
    if order[0] == 0:
        c1 = tau1 > tau2
    else:
        c1 = tau1 < tau2
    c2 = ~c1
    acc = np.mean(y == c1)
    print(u'准确率：%.2f%%' % (100 * acc))
    ax.scatter(data[c1, 0], data[c1, 1], data[c1, 2], c='r', s=30, marker='o', depthshade=True)
    ax.scatter(data[c2, 0], data[c2, 1], data[c2, 2], c='g', s=30, marker='^', depthshade=True)
    ax.set_xlabel('X')
    ax.set_ylabel('Y')
    ax.set_zlabel('Z')
    ax.set_title(u'EM算法分类', fontsize=18)
    # plt.suptitle(u'EM算法的实现', fontsize=20)
    # plt.subplots_adjust(top=0.92)
    plt.tight_layout()
    plt.show()


import numpy as np

def sphere(x):
    return np.sum(np.power(x, 2))

def griewank(x):
    D = x.shape[0]
    return np.sum(np.power(x, 2) / 4000) - \
        np.prod(np.cos(x / np.sqrt(np.linspace(1, D, D)))) + 1

def rastrigin(x):
    return np.sum(np.power(x, 2) - 10 * np.cos(2 * np.pi * x) + 10)

def schwefel(x):
    D = x.shape[0]
    return 418.9829 * D - np.sum(x * np.sin(np.power(np.abs(x), 0.5)))

def rosenbrock(x):
    return np.sum(
        100 * np.power(x[:-1] **  2 - x[1:], 2) + \
        (x[:-1] - 1) ** 2)

def ackley(x):
    return -20 * np.exp(-0.2 * np.sqrt(np.mean(np.power(x, 2)))) - \
        np.exp(np.mean(np.cos(2 * np.pi * x))) + 20 + np.exp(1)

# Weierstrass function
def get_weierstrass_const(D, a=0.5, b=3, kmax=20):
    return np.sum([a ** k * np.cos(2 * np.pi * b ** k * 0.5) for k in range(kmax)])

weierstrass_const = {
    2: get_weierstrass_const(2),
    25: get_weierstrass_const(25),
    50: get_weierstrass_const(50),
}
def weierstrass(x, a=0.5, b=3, kmax=20):
    D = x.shape[0]
    return np.sum(np.stack([a ** k * np.cos(2 * np.pi * b ** k * (x + 0.5))
                            for k in range(kmax)])) - D * weierstrass_const[D]

u1 = np.full(2, 100)
l1 = np.full(2, -100)
o1 = np.full(2, 0)
def f1(x):
    return sphere(x * (u1 - l1) + l1 - o1)

u2 = np.full(2, 100)
l2 = np.full(2, -100)
o2 = np.full(2, 80)
def f2(x):
    return sphere(x * (u2 - l2) + l2 - o2)

u3 = np.full(2, 100)
l3 = np.full(2, -100)
o3 = np.full(2, -80)
def f3(x):
    return sphere(x * (u3 - l3) + l3 - o3)

u4 = np.full(25, 0.5)
l4 = np.full(25, -0.5)
o4 = np.full(25, -0.4)
def f4(x):
    return weierstrass(x[:25] * (u4 - l4) + l4 - o4)

u5 = np.full(2, 50)
l5 = np.full(2, -50)
o5 = np.full(2, 0)
def f5(x):
    return rosenbrock(x * (u5 - l5) + l5 - o5 + 1)

u6 = np.full(2, 50)
l6 = np.full(2, -50)
o6 = np.full(2, 0)
def f6(x):
    return ackley(x * (u6 - l6) + l6 - o6)

u7 = np.full(2, 0.5)
l7 = np.full(2, -0.5)
o7 = np.full(2, 0)
def f7(x):
    return weierstrass(x * (u7 - l7) + l7 - o7)

u8 = np.full(2, 500)
l8 = np.full(2, -500)
o8 = np.full(2, 420.9687)
def f8(x):
    # return schwefel(x * (u8 - l8) + l8 - o8)
    return schwefel(x * (u8 - l8) + l8)

u9 = np.full(2, 100)
l9 = np.full(2, -100)
# o9 = np.concatenate([np.full(1, -80), np.full(1, 80)])
o9 = np.full(2, 0)
def f9(x):
    return griewank(x * (u9 - l9) + l9 - o9)

u10 = np.full(2, 5)
l10 = np.full(2, -5)
# o10 = np.concatenate([np.full(1, 40), np.full(1, -40)])
o10 = np.full(2, 0)
def f10(x):
    return rastrigin(x * (u10 - l10) + l10 - o10)

from pylab import meshgrid,cm,imshow,contour,clabel,colorbar,axis,title,show
from matplotlib.ticker import LinearLocator, FormatStrFormatter
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm

def f_wrap(f, X, Y):
    Z = np.zeros_like(X)
    for i in range(len(X)):
        for j in range(len(Y)):
            Z[i, j] = f(np.array([X[i,j], Y[i,j]]))
    return Z

def plot(f):
    x = np.arange(0, 1, 0.01)
    y = np.arange(0, 1, 0.01)
    X, Y = meshgrid(x, y) # grid of point
    Z = f_wrap(f, X, Y) # evaluation of the function on the grid

    fig = plt.figure()
    ax = fig.gca(projection='3d')
    surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1,
                          cmap=cm.RdBu,linewidth=0, antialiased=False)

    ax.zaxis.set_major_locator(LinearLocator(10))
    ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))

    fig.colorbar(surf, shrink=0.5, aspect=5)
    plt.tight_layout()
    plt.show()

def main():
    plot(f7)

if __name__ == '__main__':
    main()


import numpy as np

def distance(point1: np.array, point2: np.array):
    """
    Distance of a point to another one in 3d space
    :param point1: a 3d point
    :param point2: another 3d point
    :return: a float value
    """
    return np.linalg.norm(point2 - point1)

def angle_difference(b1: float, b2: float):
    """
    To compute the difference between the two angles
    :param b1: first angle
    :param b2: second angle
    :return: difference with direction (sign)
    """
    r = (b2 - b1) % 360.0
    # Python modulus has same sign as divisor, which is positive here,
    # so no need to consider negative case
    if r >= 180.0:
        r -= 360.0
    return r

def find_inflection(stations: list):
    """
    Process the stations and check if the middle station is an inflection point
    :type stations: 3 survey stations
    :return: a boolean
    """
    if len(stations) != 3:
        raise Exception(f"Only 3 self.stations are accepted, {len(stations)} are given")

    st1, st2, st3 = stations
    inc_diff_12 = angle_difference(st1.inc, st2.inc)
    inc_diff_23 = angle_difference(st2.inc, st3.inc)
    azi_diff_12 = angle_difference(st1.azi, st2.azi)
    azi_diff_23 = angle_difference(st2.azi, st3.azi)

    if inc_diff_12 * inc_diff_23 < 0 or azi_diff_12 * azi_diff_23 < 0:
        return True

    if st2.equal_inc_azi(st3):
        return True

    return False


# Starting code for HW 2. Add to this!
import numpy as np
from matplotlib import pyplot as plt

def go(f, fprime):
    tol = 1e-12 # tolerance

    xk = 0 # Initial guess x_k

    residuals_newton = [np.abs(f(xk))]

    itnum_newton = 0 # iteration counter
    success = True # Assume success unless we fail!

    while np.abs(f(xk))>tol:
        xk = xk-f(xk)/fprime(xk)

        itnum_newton += 1 # Clean version of itnum = itnum + 1. Works with subtraction/multiplication too!
        residuals_newton.append(np.abs(f(xk)))

        if itnum_newton>10000:
            print('Something is wrong, too many iterations, let\'s break out of this loop...!')
            success = False
            break

    xk = 0.1 # Initial guess x_k
    xkm1 = 0 # x_{k-1}

    residuals_secant = [np.abs(f(xkm1)), np.abs(f(xk))]

    itnum_secant = 1 # iteration counter
    success = True # Assume success unless we fail!

    while np.abs(f(xk))>tol:
        old_xk = xk
        xk -= f(xk) * (xk - xkm1) / (f(xk) - f(xkm1))
        xkm1 = old_xk

        itnum_secant += 1 # Clean version of itnum = itnum + 1. Works with subtraction/multiplication too!
        residuals_secant.append(np.abs(f(xk)))

        if itnum_secant>10000:
            print('Something is wrong, too many iterations, let\'s break out of this loop...!')
            success = False
            break

    if success:
        # Plotting using matplotlib, imported at the top of the code.
        plt.plot(np.arange(itnum_newton+1),residuals_newton,'o', label='Newton')
        plt.plot(np.arange(itnum_secant+1),residuals_secant,'o', label='Secant')
        plt.xlabel('$k$')
        plt.ylabel('$r_k := |f(x_k)|$')
        plt.yscale("log")
        plt.title('Root-finding comparison')
        plt.legend()
        plt.show()

go(lambda x: x-np.cos(x), lambda x: 1 + np.sin(x))


import numpy as np
from scipy.special import factorial2
from scipy.special import factorial

def kfn(theta0):
    return np.sin(np.float64(theta0) / 2)

def periodTerm(n, k):
    numerator = np.float64((factorial2(2 * n - 1)) ** 2)
    denominator = np.float64((2 ** n) * factorial(n) * factorial2(2 * n))
    return (numerator / denominator) * (k ** (2 * n)) * (2 * np.pi)

def periodTermNextStep(n, k, prevTerm):
    nextTerm = ((2 * n - 1) ** 2) * prevTerm / (4 * n ** 2) * k ** 2
    return nextTerm

def periodPendulum(theta0, tol, maxN, printFlag=0):
    periodApprox = 2 * np.pi  # n = 0 term
    k = kfn(theta0)
    for n in (np.arange(maxN) + 1):  # n = 1, 2, ..., maxN
        if n < 3:
            nextTerm = periodTerm(n, k)
        else:
            # nextTermOldFormula = periodTerm(n, k)
            nextTerm = periodTermNextStep(n, k, prevTerm)
            # if (np.abs(nextTermOldFormula - nextTerm) > 10**(-10)):
            #    print "discrepancy at n = %f, k = %f: prev. formula said nextTerm = %f, new formula says nextTerm = %f" % (n, k, nextTermOldFormula, nextTerm)
        if nextTerm < 0:
            print("nextTerm < 0: %f at n = %f, k = %f" % (nextTerm, n, k))
        if nextTerm > tol:  # nextTerm gives sense of error (really, lower bound, since all terms are non-neg)
            periodApprox += nextTerm
        else:
            if printFlag:
                print("reached tol (lower bound on error) after n = %d" % (n - 1))
            break
        prevTerm = nextTerm.copy()
    return periodApprox

def FindTheta0(theta, thetadot):
    # print "find theta0 for theta = %f and thetadot = %f" % (theta, thetadot)
    potential = (1.0 / 2.0) * (thetadot ** 2) - np.cos(theta) + 1  # H
    # E = 1 - np.cos(theta0)
    # domain for real #s for arrcos is [-1,1]
    # so want 1-potential in [-1, 1]
    # so want potential in [0, 2]
    if ((potential < 0) or (potential > 2)):
        # TODO: do something smarter here
        potential = 0
    theta0 = np.arccos(1 - potential)
    return theta0

# def f(t, x):
#    return [x[1], -np.sin(x[0])]

def FindOmega(point, tol=10 ** (-7), maxTerms=100):
    theta0 = FindTheta0(point[0], point[1])
    period = periodPendulum(theta0, tol, maxTerms)
    omega = (2 * np.pi) / period
    # eigenval = np.exp(omega*1j*deltat)
    return omega

def AddFrequency(prefix, suffix, tol=10 ** (-7), maxTerms=100):
    fname = prefix + suffix
    print("loading %s" % fname)
    data = np.loadtxt(fname, delimiter=',')
    # each row is an example, we add extra column
    data_freq = np.zeros((data.shape[0], data.shape[1] + 1), dtype=np.float32)
    data_freq[:, 0:2] = data.copy()
    for j in range(data.shape[0]):
        data_freq[j, 2] = FindOmega(data[j, 0:2])
    newfname = prefix + 'Freq' + suffix
    print("saving %s" % newfname)
    np.savetxt(newfname, data_freq, delimiter=',')


import numpy as np
"""
    Stochastic bisection algorithm,
    as described in
    "Probabilistic bisection converges almost
     as quickly as stochastic approximation,
     Peter I. Frazier, Shane G. Henderson, Rolf Waeber"
"""
TEST_VARIABLE = "TESTVARIABLE"
def stochastic_bisection(measure,gamma=0.9,maxiter=100,maxdrift=500,tol=1e-3,
                         verbose=0):
    """
        measure : function that takes a scalar as value and returns
                  a noisy measurement of some 1d function f:[0,1] -> R
        gamma : gamma factor for drift test, as described in the article
        maxiter : maximum number of iterations of algorithm
        maxdrift : maximum number of iterations for each drift test
        verbose : frequency of printings of x_m
        tol : tolerance (NOT IMPLEMENTED YET)
    """
    pc = 1.0 - gamma/2
    p0 = pc-1e-2
    points = [0.0,1.0]
    values = [0.0,1.0]
    x_m = 0.5
    x_r0 = x_m
    running_alpha = 0.1
    if verbose == 0:
        verbose = maxiter+1
    for n in range(maxiter):
        sign_func = lambda : np.sign(measure(x_m))
        z_m = _drift_test(sign_func,gamma,maxdrift)
        if z_m == -1:
            p_update = p0
        elif z_m == 1:
            p_update = 1-p0
        else:
            continue
        points,values = _update_cdf(x_m,p_update,points,values)
        x_m = _get_median(points,values)
        x_r = x_r0 + running_alpha*(x_m-x_r0)
        if n >= 10 and np.abs(x_r-x_r0) <= tol:
            break
        else:
            x_r0 = x_r
        if (n+1)%verbose == 0:
            print(x_r,x_m)
    print("Finished")
    return x_r

def _drift_test(sign_func,gamma,maxiter):
    s0 = 0.0
    m = 1
    while True:
        k = np.sqrt(2*m*np.log(m+1)-np.log(gamma))
        s0 += sign_func()
        if s0 >= k:
            return 1
        elif s0 <= -k:
            return -1
        elif m >= maxiter:
            return 0
        m += 1

def _location_ordered_list(x,L):
    #L : [p0,p1,p2,...,pN], where it's ordered
    #returns : i, where i is the index where p[i-1] <= x < p[i]
    #          if empty list, return 0.
    #          if x < p[0], return 0. x >= p[-1], return len(p)
    N = len(L)
    if N == 0:
        return 0
    if N == 1:
        return 1 if x >= L[0] else 0
    if x >= L[-1]:
        return N
    elif x < L[0]:
        return 0
    else:
        i = (N-1)//2
        if L[i+1] <= x:
            return i+1 + _location_ordered_list(x,L[i+1:])
        else:
            if L[i] <= x:
                return i+1
            else:
                return _location_ordered_list(x,L[:i+1])

def _update_cdf(x,p,points,values):
    #x \in (0,1). F_n(x) = 1/2
    #p \in (0,1)
    #points : [x0=0,x1,x2,...,xN=1]
    #values : [y0=0,y1,y2,...,yN=1]
    #Those represent the CDF of an uniform by parts. Let the PDF of if be f_N
    #returns: CDF of the distribution with density f_{N+1}(y) = p*f_N(y) if y < x else (1-p)*f_N(y)
    q = 1-p
    p_ = 2*p
    q_ = 2*q
    ind = _location_ordered_list(x,points)
    points_low = points[:ind]
    points_high = points[ind:]
    values_low = values[:ind]
    values_high = values[ind:]
    values_low = list(p_*np.array(values_low))
    values_high = list((p_-q_)/2+q_*np.array(values_high))
    points_new = points_low + [x] + points_high
    values_new = values_low + [p_/2] + values_high
    return points_new,values_new

def _get_median(points,values):
    #get the median of a CDF defined by points and values
    #points : [x0=0,x1,x2,...,xN=1]
    #values : [y0=0,y1,y2,...,yN=1]
    y_median = 0.5
    i = _location_ordered_list(y_median,values)
    xlow,xhigh,ylow,yhigh = points[i-1],points[i],values[i-1],values[i]
    x_median = ((xhigh-xlow)*y_median-(ylow*xhigh-yhigh*xlow))/(yhigh-ylow)
    return x_median


# -*- coding:utf-8 -*-

import numpy as np

def merge_sort(a):
    merge_sort_call(a, 0, len(a) - 1)

def merge_sort_call(a, sp, ep):
    if sp >= ep:
        return
    mp = sp + ((ep - sp) >> 2)
    merge_sort_call(a, sp, mp)
    merge_sort_call(a, mp + 1, ep)
    merge(a, sp, mp, ep)

def merge(a, sp, mp, ep):
    i, j = sp , mp + 1
    tmp = []
    while i <= mp and j <= ep:
        if a[i] < a[j]:
            tmp.append(a[i])
            i += 1
        else:
            tmp.append(a[j])
            j += 1
    s, e = i, mp
    if j <= ep:
        s, e = j, ep
    while s <= e :
        tmp.append(a[s])
        s += 1
    for i in range(ep - sp + 1):
        a[i + sp] = tmp[i]

def qsort(a):
    qsort_call(a, 0, len(a) - 1)

def qsort_call(a, sp, ep):
    if sp >= ep:
        return
    pivot = partition(a, sp, ep)
    qsort_call(a, sp, pivot - 1)
    qsort_call(a, pivot + 1, ep)

def partition(a, sp, ep):
    pivot_val = a[ep]
    i = j = sp
    while j < ep:
        if a[j] < pivot_val:
            a[i], a[j] = a[j], a[i]
            i += 1
        j += 1
    a[i], a[ep] = a[ep], a[i]
    return i

def bubble_sort(a):
    for i in range(len(a)):
        is_sort = True
        for j in range(len(a) - i - 1):
            if a[j] > a[j + 1]:
                a[j], a[j + 1] = a[j + 1], a[j]
                is_sort = False
        if is_sort:
            return

def inserting_sort(a):
    for i in range(len(a)):
        pos = i
        val = a[i]
        for j in range(i, 0, -1):
            if a[j - 1] > val:
                a[j] = a[j - 1]
                pos = j - 1
            else:
                break
        a[pos] = val

def selective_sort(a):
    for i in range(len(a)):
        min_pos = i
        min_val = a[i]
        for j in range(i, len(a)):
            if a[j] < min_val:
                min_val = a[j]
                min_pos = j
        a[i], a[min_pos] = a[min_pos], a[i]

if __name__ == '__main__':
    data = np.random.randint(0, 100, 50)
    print(data)
    selective_sort(data)
    print(data)


from psecas.grids.grid import Grid

class LegendreExtremaGrid(Grid):
    """
    This grid uses the Legendre extrema and endpoints grid on
    z ∈ [zmin, zmax] to discretize the system. This grid is also known as the
    Gauss-Lobatto grid. Implementation follows Boyd Appendix F.10 on page 572.

    N: The number of grid points
    zmin: The z value at the lower boundary
    zmax: The z value at the upper boundary

    Optional:
    z: a string which can be set to e.g. 'x' if x is used as the
       coordinate in your linearized equations.

    """

    def __init__(self, N, zmin, zmax, z="z"):
        super().__init__(N, zmin, zmax, z=z)

    def make_grid(self):
        import numpy as np
        from numpy.polynomial.legendre import legder, legroots, legval

        N = self._N
        self.NN = N + 1
        L = self.L

        factor = L / 2

        d1 = np.zeros((N + 1, N + 1))

        cp = legder([0] * N + [1])
        zg = np.hstack([-1.0, legroots(cp), 1.0])

        P_N = legval(zg, [0] * N + [1])

        with np.errstate(divide='ignore'):
            d1 = P_N[:, None] / (P_N[None, :] * (zg[:, None] - zg[None, :]))

        d1[np.diag_indices(N+1)] = 0.0
        d1[0, 0] = -N * (N + 1) / 4
        d1[N, N] = +N * (N + 1) / 4

        d2 = np.dot(d1, d1)

        self.zg = (zg + 1) * L / 2 + self.zmin
        self.d0 = np.eye(self.NN)
        self.d1 = d1 / factor
        self.d2 = d2 / factor ** 2

        # Call other objects that depend on the grid
        for callback in self._observers:
            callback()

    def to_coefficients(self, f):
        from numpy.polynomial.legendre import legfit

        c, res = legfit(self.zg, f, deg=self.N, full=True)

        return c

    def interpolate(self, z, f):
        from numpy.polynomial.legendre import legval
        import numpy as np

        msg = "Can't interpolate outside grid domain"
        assert np.array([z]).min() >= self.zmin, msg
        assert np.array([z]).max() <= self.zmax, msg

        # Get coefficients for Legendre polynomials
        c = self.to_coefficients(f)

        return legval(z, c)


# -*- coding: utf-8 -*-
"""1. numpy linearRegression.ipynb

Automatically generated by Colaboratory.

Original file is located at
    https://colab.research.google.com/drive/1wXwuRkGK8fH29hoSa5k9u_crhfFi06MN
"""

import numpy as np
import matplotlib.pyplot as plt

# Linear regression is essentially fitting the gradient and offset of the line eqn: y = mx + c
# We seek to minimise the error by generating a bets fit to the data...

# Nice guide a: https://glowingpython.blogspot.com/2012/03/linear-regression-with-numpy.html
# Nice guide b: https://machinelearningmastery.com/implement-simple-linear-regression-scratch-python

# So, ahem, borrowing some code/data

x = np.arange(0, 9)
y = [19, 20, 20.5, 21.5, 22, 23, 23, 25.5, 24]

# Algebra, see wikipedia: https://en.wikipedia.org/wiki/Simple_linear_regression
# (its a standard sln)
#
# m = sum((x(i) - mean(x)) * (y(i) - mean(y))) / sum( (x(i) - mean(x))^2 )
# c = mean(y) - m * mean(x)

def mean(lst):
    return np.mean(lst)

def sum(lst):
    return np.sum(lst)

def m(x, y):
    return sum((x - mean(y)) * (y - mean(y))) / sum(np.power(x - mean(x), 2))

def c(x, y, m_val):
    return mean(y) - (m_val * mean(x))

m_final = m(x, y)
c_final = c(x, y, m_final)

print(str(m_final) + " " + str(c_final))

_ = plt.plot(x, y, '-', np.arange(0, 9), m_final * np.arange(9) + c_final, '-')

# and: Lo we have a a shiny best fit line

# But... this isnt using a cost function or gradient descent.


import numpy as np

def getpowerlevel(x, y, serialnumber):
	rack_id = x+ 10
	current = rack_id
	current *= y
	current += serialnumber
	current *= rack_id
	str_current = str(current)
	if len(str_current)<3:
		current = 0
	else:
		current = int(str_current[-3])
	ans = current - 5
	return ans

def solve_naive(serialnumber):
	biggest_sum = 0
	big_x = 0
	big_y = 0
	for x in range(1, 298):
		for y in range(1,298):
			current_sum = sum([getpowerlevel(i+x, j+y, serialnumber) for i in range(3) for j in range(3)])
			if current_sum>biggest_sum:
				biggest_sum = current_sum
				big_x = x
				big_y = y

	return biggest_sum, big_x, big_y

def build_lookup_grid(serialnumber):
	power_grid = np.zeros(shape=(301,301), dtype=np.int)
	for x in range(1,301):
		for y in range(1, 301):
			power_grid[x, y] = getpowerlevel(x, y, serialnumber)

	lookup_grid = np.zeros(shape=(301,301), dtype=np.int)
	for x in range(1,301):
		for y in range(1, 301):
			lookup_grid[x,y] = power_grid[:x,:y].sum()
	return lookup_grid

def lookup_area(grid, x,y,size):#add back the diagonal because it was removed twice by the subtractions
	return grid[x,y]-grid[x-size,y]-grid[x,y-size]+grid[x-size, y-size]

def solve(grid, size):
	biggest_sum = 0
	big_x,big_y = 0, 0
	for x in range(size+1, 300):
		for y in range(size+1, 300):
			current_sum = lookup_area(grid,x,y,size)
			if current_sum>biggest_sum:
				biggest_sum = current_sum
				big_x, big_y = x, y

	return biggest_sum, big_x-size, big_y-size, size

def solvepart1(serialnumber):
	grid = build_lookup_grid(serialnumber)
	return solve(grid, 3)

def solvepart2(serialnumber):
	grid = build_lookup_grid(serialnumber)

	biggest_sum, big_x, big_y, size = 0,0,0,0
	for s in range(1,300):
		cur = solve(grid, s)
		if cur[0]>biggest_sum:
			biggest_sum, big_x, big_y, size = cur
	return biggest_sum, big_x, big_y, size

if __name__=='__main__':

	#print solve_naive(6042)
	#print solvepart1(6042)
	#print solve(6042, 3)
	print solvepart2(6042)


import numpy as np
import sys

#Naive_Gauss Pseudocode For Python
def Naive_Gauss(A, B):
    n = len(B)
    # Elimination phase
    for k in range(0, n-1):
        for i in range(k+1, n):
            if A[i,k] != 0.0:
                lam = A[i,k]/A[k,k]
                A[i,k+1:n] = A[i,k+1:n] - lam*A[k,k+1:n]
                B[i] = B[i] - lam*B[k]
    # Back substitution
    for k in range(n-1, -1, -1):
        B[k] = (B[k] - np.dot(A[k,k+1:n],B[k+1:n]))/A[k,k]
    return B

#Example
A = np.array([[2, 1, 1],
              [-1, 1, -1],
              [1, 2, 3]], dtype = float)

B = np.array([2, 3, -10], dtype = float)
X = Naive_Gauss(A, B)
print(X)
#answer [3,1,-5]

#Example 2
A = np.array([[2, 1, 1, 2],
              [-1, 1, -1, 3],
              [1, 2, 3, -10]], dtype = float)
rows = np.shape(A)[0]
cols = np.shape(A)[1]
print(rows)
print(cols)

#Example 3
m = 10
#A = np.array(zeros_like)
#B = np.array(zeros_like)
A = np.arange(m*m,dtype=float).reshape(m, m)   #, dtype=np.int32
B = np.arange(10,dtype=float)
print(A)
print(B)
for n in range(4,m):
    for i in range(1,n):
        for j in range(1,n):
            A[i][j] = (i+1)**(j-1)
        B[i] = (((i+1)**n)-1)/i
print("\n",A,"\n",B)
X = Naive_Gauss(A,B)
print("\n",X)

#Example 4
A = np.array([[1.0, 0, 3],
              [0, 1, -3],
              [3, -3, 0],
              [0, 2, 4]])
B = np.array([0.0, 0, 6, -6])
print(A, "\n")
print(B, "\n")

X = Naive_Gauss(A,B)
print(X,"\n")


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

"""
Statistical tests to check whether a distribution is normal.
"""
def dagostino_test(data):

    alpha = 0.05
    _, p = stats.normaltest(data)
    result = 'Not normal' if p < alpha else 'Normal'

    return result

def kurtosis_test(data):

    alpha = 0.05
    _, p = stats.kurtosistest(data)
    result = 'Not normal' if p < alpha else 'Normal'

    return f'{result}, {round(p, 4)}'

def skewness_test(data):

    alpha = 0.05
    _, p = stats.skewtest(data)
    result = 'Not normal' if p < alpha else 'Normal'

    return f'{result}, {round(p, 4)}'

def normal_tests(data):

    yj = stats.yeojohnson(data)[0]
    sq = np.sqrt(data)
    cb = np.cbrt(data)
    log = np.log(data)

    return pd.DataFrame({
        'Skewness': [stats.skew(data), stats.skew(sq), stats.skew(cb), stats.skew(log), stats.skew(yj)],
        'Skewness Test': [skewness_test(data), skewness_test(sq), skewness_test(cb), skewness_test(log), skewness_test(yj)],
        'Kurtosis': [stats.kurtosis(data), stats.kurtosis(sq), stats.kurtosis(cb), stats.kurtosis(log), stats.kurtosis(yj)],
        'Kurtosis Test': [kurtosis_test(data), kurtosis_test(sq), kurtosis_test(cb), kurtosis_test(log), kurtosis_test(yj)],
        'Normal Test': [dagostino_test(data), dagostino_test(sq), dagostino_test(cb), dagostino_test(log), dagostino_test(yj)]},
        index=['default', 'sqrt', 'cuberoot', 'log', 'yeojohnson'])


import numpy as np
from scipy.stats import norm as norm_dist

X = np.array([-1,0,4,5,6])
p1 = 0.5
p2 = 0.5
m1 = 6
m2 = 7
sd1 = 1
sd2 = 2

x_from_1 = norm_dist.pdf(X, loc=m1, scale=sd1)
x_from_2 = norm_dist.pdf(X, loc=m2, scale=sd2)
x_likelihood = np.array([x_from_1, x_from_2])
weights = np.array([p1, p2])

likelihoods = np.matmul(x_likelihood.transpose(), weights)
log_likelihoods = np.log(likelihoods)
log_likelihood = np.sum(log_likelihoods)

print("log likelihood : " + str(log_likelihood))

gaussian_likelihood_1 = p1*norm_dist.pdf(X, loc=m1, scale=sd1)/(p1*norm_dist.pdf(X, loc=m1, scale=sd1)+p2*norm_dist.pdf(X, loc=m2, scale=sd2))
print("likelihood for gaussian 1: \n" + str(gaussian_likelihood_1))

gaussian_likelihood_2 = p2*norm_dist.pdf(X, loc=m2, scale=sd2)/(p1*norm_dist.pdf(X, loc=m1, scale=sd1)+p2*norm_dist.pdf(X, loc=m2, scale=sd2))
print("likelihood for gaussian 1: \n" + str(gaussian_likelihood_2))

x_1 = np.array([5,6])
x_2 = np.array([-1,0,4])

gammas_1 = np.array([0.6667,0.6938])
gammas_2 = np.array([0.99999986, 0.99998608, 0.54533833])

m1 = np.matmul(x_1, gammas_1.transpose())/np.sum(gammas_1)
m2 = np.matmul(x_2, gammas_2.transpose())/np.sum(gammas_2)
print("new m1 : " + str(m1) + " and m2 : " + str(m2))

v1 = np.matmul((x_1-m1)**2, gammas_1.transpose())/np.sum(gammas_1)
v2 = np.matmul((x_2-m2)**2, gammas_2.transpose())/np.sum(gammas_2)
new_sd1 = v1**(0.5)
new_sd2 = v2**(0.5)
print("new sd1 grew by : " + str(new_sd1/sd1) + " and sd2 by : " + str(new_sd2/sd2))


import numpy as np
from scipy.integrate import solve_ivp
import matplotlib.pyplot as plt

def SIR_continuous_mask(b,k,time,ii,q):
    """
    Simulates continuous SIR model
    ii = initial percentage of infected
    time = Days of simulation
    b = probability that people getting infectious
    k = probability that people getting recovered
    q = people wearing masks to slow down the parameter b (0 < q < 1)

    returns sol from solve_ivp
    """
    def SIR(t, X):

        #The main set of equations
        Y = np.zeros((3))
        Y[0] = -b * q * X[0] * X[2]
        Y[1] = k * X[2]
        Y[2] = b * q * X[0] * X[2] - (k * X[2])
        return Y
    t_eval = np.linspace(0, time, 1000)
    sol1 = solve_ivp(SIR, [0, time], [1-ii, 0, ii], method='RK45', t_eval=t_eval)    # solve the equation
    return sol1


from typing import final
import numpy as np
import matplotlib.pyplot as plt

initial_speed = 2 * np.pi
final_speed = 0.5
x_goal = 15

def generate_curve(x_values: np.ndarray, k: float, offset: float, x_goal: float)->np.ndarray:
    x_shift = generate_x_shift(x_goal, k, offset)
    out = initial_speed/(1+np.exp(k*(x_values-x_shift)))
    return out

def generate_x_shift(x_goal: float, k: float, offset: float):
    out = (x_goal+offset) - (1/k)*np.log(initial_speed/final_speed - 1)
    return out

# Generate x values for plotting
num_pts = 10000
x_values = np.linspace(0,25,num_pts)

# Select ks and offsets
ks = [0.1, 2.5, 20]
offsets = [0,5.5]

# Generate curve based on those xs, ks, and offsets
curves = []
for k in ks:
    curves_by_k = []
    for offset in offsets:
        curve = generate_curve(x_values, k, offset, x_goal)
        curves_by_k.append(curve)
    curves.append(curves_by_k)

# Plot those curves accordingly
fig, axs = plt.subplots(len(ks), len(offsets), figsize=(6,6), dpi=100)

y_low = -0.2
y_high = 2*np.pi+0.2

for ax_row, curves_by_k in zip(axs, curves):
    for ax, curve in zip(ax_row, curves_by_k):
        ax.plot(x_values, curve, label="Speed")
        ax.plot([x_goal, x_goal], [y_low, y_high], ":", label="Island Left Edge")

# Set title
# fig.suptitle("Speed Functions")

# set labels
plt.setp(axs[-1, :], xlabel='x Position (BL)')
plt.setp(axs[:, 0], ylabel='Speed (rad/s)')

# set legend
lines, labels = axs[-1][-1].get_legend_handles_labels()
fig.legend(lines, labels, loc = 'upper right')

plt.tight_layout()
plt.show()


"""Demonstration of a logistic regression and naive gaussian classifier.

It uses the spambase dataset.
"""

import numpy as np
import argparse
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
from sklearn.linear_model import LogisticRegression

# Avoids division by zero.
EPSILON = 0.001

def confusion_matrix_metrics(test_classes, predicted_class):
    """
    Prints a confusion matrix and its metrics for 2 classes.
    """
    cm = confusion_matrix(test_classes, predicted_class)
    print(cm)
    tn, fp, fn, tp = cm.ravel()
    print(
        "Accuracy",
        (tp + tn) / (tn + tp + fp + fn),
        "precision",
        tp / (tp + fp),
        "recall",
        tp / (tp + fn),
    )

def logistic_regresssion(training_features, test_features, training_classes):
    """Wrapper function for Scikit's Logistic Regression"""
    model = LogisticRegression(solver="liblinear", multi_class="ovr")
    model.fit(training_features, training_classes)

    return model.predict(test_features)

def gaussian_stats_by_class(featuresList, classList, class_types):
    """Returns stdev and mean of a given class only."""
    # Get locations of features based on the class_types.
    indices_class = np.nonzero(classList != class_types)[0]
    # Remove the non desired classes from features.
    result = np.delete(featuresList, indices_class, axis=0)

    return result.std(axis=0) + EPSILON, result.mean(axis=0)

def naive_gaussian_classifier(training_features, test_features, training_classes, probability_class):
    """Based on naive gaussian probability, return predictions."""
    predicted_class = []
    # Get gaussian probability statistics for each class.
    std_spam, mean_spam = gaussian_stats_by_class(training_features, training_classes, 1)
    std_not_spam, mean_not_spam = gaussian_stats_by_class(training_features, training_classes, 0)

    # For each feature calculate its probability based on the gaussian formulas.
    for i in range(len(test_features)):
        probability_spam = np.log10(probability_class[0]) + np.sum(
            np.log10(
                (1 / (np.sqrt(2 * np.pi) * std_spam))
                * np.exp(
                    -(np.power(test_features[i] - mean_spam, 2) / (2 * np.power(std_spam, 2)))
                )
            )
        )
        probability_not_spam = np.log10(probability_class[0]) + np.sum(
            np.log10(
                (1 / (np.sqrt(2 * np.pi) * std_not_spam))
                * np.exp(
                    -(np.power(test_features[i] - mean_not_spam, 2) / (2 * np.power(std_not_spam, 2)))
                )
            )
        )
        # Choose the result based on which probability is greater.
        pred = 1 if probability_spam > probability_not_spam else 0
        predicted_class.append(pred)

    return np.array(predicted_class)

def prior_probability(classes, class_types):
    """Computes prior probabilities."""
    class_list = classes == class_types
    return np.count_nonzero(class_list) / len(classes)

def main(data_file):
    df = pd.read_csv(data_file, index_col=None, header=None)
    features = df.iloc[:, :57].values
    class_type = df.iloc[:, -1].values
    # use Scikit's built in data splitter.
    training_features, test_features, training_classes, test_classes, = train_test_split(
        features, class_type, test_size=0.5, random_state=2814
    )

    training_probability_spam = prior_probability(training_classes, 1)
    training_probability_not_spam = prior_probability(training_classes, 0)
    print("Naive Gaussian Classifier Result")
    predicted_class = naive_gaussian_classifier(
        training_features,
        test_features,
        training_classes,
        [training_probability_not_spam, training_probability_spam],
    )
    print("Confusion Matrix")
    confusion_matrix_metrics(test_classes, predicted_class)

    print("Logistic Regression Classifier Result")
    predicted_class = logistic_regresssion(
        training_features, test_features, training_classes
    )
    print("Confusion Matrix")
    confusion_matrix_metrics(test_classes, predicted_class)

if __name__ == "__main__":
    np.random.seed(2814)

    parser = argparse.ArgumentParser()
    parser.add_argument("-data", dest="data", help="spambase dataset", default="spambase.csv")
    args = parser.parse_args()
    main(args.data)


import numpy as np

def sphere(p):
    # Sphere函数
    out_put = 0
    for i in p:
        out_put += i ** 2
    return out_put

def schaffer(p):
    '''
    This function has plenty of local minimum, with strong shocks
    global minimum at (0,0) with value 0
    https://en.wikipedia.org/wiki/Test_functions_for_optimization
    '''
    x1, x2 = p
    part1 = np.square(x1) - np.square(x2)
    part2 = np.square(x1) + np.square(x2)
    return 0.5 + (np.square(np.sin(part1)) - 0.5) / np.square(1 + 0.001 * part2)

def myfunc(p):
    x = p
    return np.sin(x) + np.square(x)


import numpy as np
from matplotlib import pyplot as plt
from PIL import Image
import imageio

#----------Question 1----------
def getHistogram(inp_img, name):
	m, n = inp_img.shape

	x_hist = range(0, 256)
	y_hist = [0 for i in x_hist]

	for y in range(0, m):
		for x in range(0, n):
			y_hist[int(inp_img[y][x])] += 1

	fig, axes = plt.subplots(1, 2)
	plt.gray()
	axes[0].imshow(inp_img)
	axes[0].set_title(name + " Image")
	axes[1].bar(x_hist, y_hist)
	axes[1].set_title("Corresponding Histogram")
	plt.show()

	return np.array(y_hist)/(m*n)

def getCDF(pdf):
	CDF = []
	pSum = 0
	for p in pdf:
		pSum += p
		CDF.append(pSum)
	return np.array(CDF)

def q1():
	L = 256

	inp_img = imageio.imread("Yash_Kalyani_2017273_cameraman.tif")
	m, n = inp_img.shape
	P_r = getHistogram(inp_img, "Input")

	spec_img = imageio.imread("Yash_Kalyani_2017273_specified image.jpg")
	P_z = getHistogram(spec_img, "Specified")

	T_r = (L-1) * getCDF(P_r)
	G_z = (L-1) * getCDF(P_z)

	out_img = np.zeros((m, n))

	matching = {}
	idx = 0
	for r in T_r:
		matching[idx] = (np.abs(G_z - r)).argmin()
		idx += 1

	# print("Mapping is: ")
	# print(matching)

	for y in range(m):
		for x in range(n):
			out_img[y][x] = matching[inp_img[y][x]]
	getHistogram(out_img, "Output")

#----------Question 3a---------
def getTransposedFilter(kernel):
	m, n = kernel.shape
	img_kernel = np.zeros((m, n))

	for i in range(0, m):
		for j in range(0, n):
			img_kernel[m-i-1][n-j-1] = kernel[i][j]

	return img_kernel

def convolve(inp_img, kernel):
	m1, n1 = inp_img.shape
	m2, n2 = kernel.shape

	padded_inp = np.zeros((m1+m2-1, n1+n2-1))
	padded_inp[m2//2:m1+(m2//2), n2//2:n1+(n2//2)] = inp_img

	final_out = np.zeros((m1, n1))

	for y in range(m1):
		for x in range(n1):
			kersum = 0
			for i in range(m2):
				for j in range(n2):
					kersum += (padded_inp[y+i][x+j] * kernel[i][j])
			final_out[y][x] = kersum

	return final_out

def q3a():
	kernel = np.array([[0,1,0],[1,-4,1],[0,1,0]])
	inp = np.array([[6,7],[8,9]])
	print("Convolution result for Q3a is: ")
	print(convolve(inp, getTransposedFilter(kernel)))

#----------Question 4----------
def getWKernel():
	# laplacian_filter = np.array([[0,1,0],[1,-4,1],[0,1,0]])
	# m, n = laplacian_filter.shape

	# delta_xy = np.zeros(laplacian_filter.shape)
	# delta_xy[m//2][n//2] = 1

	# w_xy = np.zeros(laplacian_filter.shape)
	# if laplacian_filter[m//2][n//2] < 0:
	# 	w_xy = delta_xy - laplacian_filter
	# else:
	# 	w_xy = delta_xy + laplacian_filter

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

	return w_xy_calc

def sharpen(inp_img):
	out_img = convolve(inp_img, getWKernel())

	fig, axes = plt.subplots(1, 2)
	plt.gray()
	axes[0].imshow(inp_img, vmin=0, vmax=255)
	axes[0].set_title("Original Image")
	axes[1].imshow(out_img, vmin=0, vmax=255)
	axes[1].set_title("Sharpened Image")
	plt.show()

def q4():
	inp_img = imageio.imread("Yash_Kalyani_2017273_chandrayaan.jpg")
	print("w(x, y) kernel is: ")
	print(getWKernel())
	sharpen(inp_img)

if __name__ == '__main__':

	q1()
	q3a()
	q4()


"""
Author : Achintya Gupta
Date Created : 22-09-2020
"""

"""
Problem Statement
------------------------------------------------
Q)  Starting in the top left corner of a 2×2 grid, and only being able to move to the right and down, there are exactly 6 routes to the bottom right corner.
    How many such routes are there through a 20×20 grid?
"""
from utils import timing_decorator, get_factorial
import numpy as np

@timing_decorator
def num_routes(N=20):
    no_paths = get_factorial(N*2)/get_factorial(N)/get_factorial(N)
    print(f'Number of possible paths in {N}x{N} grid : {int(no_paths)}')

num_routes()


import numpy as np

activation_function = {
    'lineal': (lambda x: x, lambda x: 1),
    'hard_sigmoid': (lambda x: np.max(0, np.min(1, (x + 1) / 2)), lambda x: 0 if x < -2.5 or x > 2.5 else 0.2),
    'sigmoid': (lambda x: 1 / (1 + np.exp(-x)), lambda x: (1 / (1 + np.exp(-x))) * (1 - (1 / (1 + np.exp(-x))))),
    'tanh': (lambda x: (np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x)), lambda x: 1 - ((np.exp(x) - np.exp(-x))**2 / (np.exp(x) + np.exp(-x))**2)),
    'hard_tanh': (lambda x: -1 if x < -1 else (x if -1 <= x <= 1 else 1), lambda x: 1 if -1 <= x <= 1 else 0),
    'ReLU': (lambda x: (x > 0) * x, lambda x: (x > 0) * 1),
    'leaky_ReLU': (lambda x: 0.01 * x if x < 0 else x, lambda x: 0.01 if x < 0 else 1),
    'pReLU': (lambda x, alpha: alpha * x if x <= 0 else x, lambda x, alpha: alpha if x <= 0 else 1),
    'rRelu': (lambda x: x if x >= 0 else np.random.uniform(0.8, 0.3) * x, lambda x: 1 if x >= 0 else np.random.uniform(0.8, 0.3) * 1),
    'elu': (lambda x: x if x > 0 else 0.01 * (np.exp(x) - 1), lambda x: 1 if x > 0 else 0.01 * np.exp(x)),
    'swish': (lambda x: x * (1 / (1 + np.exp(-x))), None),
    'softsign': (lambda x: x / (1 + np.abs(x)), lambda x: 1 / (1 + np.abs(x))**2),
    'softplus': (lambda x: np.log(1 + np.exp(x)), lambda x: 1 / (1 + np.exp(-x)))
}

def get_activation(name_function):
    if name_function in activation_function.keys():
        activation_f, _ = activation_function[name_function]
        return activation_f

def get_derivative_activation(name_function):
    if name_function in activation_function.keys():
        _, derivative_activation_f = activation_function[name_function]
        return derivative_activation_f


# Import Libraries
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

#import dataset from working directory
dataset= pd.read_csv('50_Startups.csv')
x= dataset.iloc[:,:-1].values
y= dataset.iloc[:,4].values

#Encode categorical data
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
labelencoder_x=LabelEncoder()
x[:,3]= labelencoder_x.fit_transform(x[:, 3])
onehotencoder = OneHotEncoder(categorical_features=[3])
x = onehotencoder.fit_transform(x).toarray()

#Avoid Dummy variable trap by deleting 1 coloums
x=x[:, 1:]

#splitting training and test data
from sklearn.cross_validation import train_test_split
x_train,x_test,y_train,y_test = train_test_split(x,y, test_size =0.2, random_state=0)

#add 1 coloum at the end of your dataset fro backward elimination.
import statsmodels.formula.api as sm
x = np.append(arr= np.ones((50,1)).astype(int), values = x, axis = 1)
x_opt = x[:, [0, 1, 2, 3, 4, 5]]
regressor_OLS = sm.OLS(endog = y , exog = x_opt).fit()
regressor_OLS.summary()

# removing coloum 2 since it's p>sl (sl=0.05)
x_opt = x[:, [0, 1, 3, 4, 5]]
regressor_OLS = sm.OLS(endog = y , exog = x_opt).fit()
regressor_OLS.summary()

# removing coloum 1 since it's p>sl (sl=0.05)
x_opt = x[:, [0, 3, 4, 5]]
regressor_OLS = sm.OLS(endog = y , exog = x_opt).fit()
regressor_OLS.summary()

# removing coloum 4 since it's p>sl (sl=0.05)
x_opt = x[:, [0, 3, 5]]
regressor_OLS = sm.OLS(endog = y , exog = x_opt).fit()
regressor_OLS.summary()

# removing coloum 5 since it's p>sl (sl=0.05)
x_opt = x[:, [0, 3]]
regressor_OLS = sm.OLS(endog = y , exog = x_opt).fit()
regressor_OLS.summary()

# Now we know there is a strong co-relation between coloum 3 which is R&D and Profit.

#Find linear regression between R&D and Profit
# Convert integer to matrix since that is needed for regressor to fit to models.
x=x[:, 3:4]
from sklearn.cross_validation import train_test_split
x_train,x_test,y_train,y_test = train_test_split(x,y, test_size =0.2, random_state=0)

from sklearn.linear_model import LinearRegression
regressor = LinearRegression()
regressor.fit(x_train,y_train)

#Prediction
y_pred = regressor.predict(x_test)

# Plot predicted and actual values
plt.scatter(x_test,y_test,color='red')
plt.plot(x_test,y_pred,color='blue')
plt.title('Actual vs Predicted for R&D vs Profit')
plt.xlabel('R&D spend')
plt.ylabel('Profit')
plt.show()

Machine Learning/Part 2 - Regression/Section 5 - Multiple Linear Regression/Multiple_Linear_Regression/multiple_linear_regression_backward_elimination.py


import math
import numpy as np

class VectorMath(object):
    """
    Encasuplation class for vector operations
    """

    def __init__(self):
        pass

    @staticmethod
    def get_magnitude(vector_one, vector_two):
        """
        Returns the distance between two vectors
        """
        if len(vector_one) != len(vector_two):
            raise ValueError("Point dimension mismatch")

        diff = np.subtract(vector_one, vector_two)

        return VectorMath.get_vector_magnitude(diff)

    @staticmethod
    def get_unit_vector_from_two_vectors(vector_one, vector_two):
        """
        Returns the unit vector of two vectors
        """
        unit_vector = np.subtract(vector_two, vector_one) / VectorMath.get_magnitude(vector_one, vector_two)

        return unit_vector

    @staticmethod
    def get_single_unit_vector(vector):
        """
        Returns the unit vector of a vector
        """
        unit_vector = vector / VectorMath.get_vector_magnitude(vector)

        return unit_vector

    @staticmethod
    def get_vector_magnitude(vector):
        """
        Returns the magnitude of the vector
        """
        return np.linalg.norm(vector)

    @staticmethod
    def get_vector_projection(vector_one, vector_two):
        """
        Returns the vector projection between two vectors
        """
        dot_prod = np.dot(vector_one, vector_two)
        mag = VectorMath.get_vector_magnitude(vector_two)

        return vector_two * (dot_prod / (mag**2))

    @staticmethod
    def get_vector_rejection(vector_one, vector_two):
        """
        Returns the vector rejection between two vectors
        """
        projection_vector = VectorMath.get_vector_projection(vector_one, vector_two)

        return np.subtract(vector_one, projection_vector)


import numpy as np

def PointCloud(A, B):
    '''
    Calculates the least-squares best-fit transform between corresponding 3D points A->B
    Input:
      A: Nx3 numpy array of corresponding 3D points
      B: Nx3 numpy array of corresponding 3D points
    Returns:
      T: 4x4 homogeneous transformation matrix
      R: 3x3 rotation matrix
      t: 3x1 column vector
    '''

    assert len(A) == len(B)

    # translate points to their centroids
    centroid_A = np.mean(A, axis=0)
    centroid_B = np.mean(B, axis=0)
    AA = A - centroid_A
    BB = B - centroid_B

    # rotation matrix
    H = np.dot(AA.T, BB)
    U, S, Vt = np.linalg.svd(H)
    R = np.dot(Vt.T, U.T)

    # special reflection case
    if np.linalg.det(R) < 0:
        Vt[2, :] *= -1
        R = np.dot(Vt.T, U.T)

    # translation
    t = centroid_B.T - np.dot(R, centroid_A.T)

    # homogeneous transformation
    T = np.identity(4)
    T[0:3, 0:3] = R
    T[0:3, 3] = t

    return T, R, t

def frameInv(T):
    # output -
    """[summary]

    Args:
        T ([T, numpy array 4x4, ]): [homogeneous frame transformation]

    Returns:
        Tinv, inverse of the frame
        Rinv, rotation matrix
        pinv - translation vector
    """
    T = np.array(T)

    R = T[0:3, 0:3]
    P = T[0:3, 3]

    # Rinv = np.transpose(R)
    Rinv = np.linalg.inv(R)
    Pinv = -np.matmul(Rinv, P)

    Tinv = np.identity(4)
    Tinv[0:3, 0:3] = Rinv
    Tinv[0:3, 3] = Pinv

    return Tinv, Rinv, Pinv


from numpy import array,zeros,copy
from numpy.linalg import det

def cramer(a,b):
    n = len(b)
    xs = zeros(n)
    for i in range(n):
        cram = copy(a)
        cram[:,i] = b[:]
        xs[i] = det(cram)/det(a)
    return xs

def newt2print(a,xi):
    print('f(x) = %8.4f + %8.4f (x - %8.4f) + %8.4f (x - %8.4f )(x - %8.4f )' %
         (a[0],a[1],xi[0],a[2],xi[0],xi[1]))
    return

def coeffs(xi):
    cf0 = xi[1]-xi[0]
    cf1 = xi[2]-xi[0]
    cf2 = cf1*(xi[2]-xi[1])
    c00 = c10 = c20 = 1.0
    c01 = c02 = 0.0
    c11 = cf0
    c12 = 0.0
    c21 = cf1
    c22 = cf2
    ci = array([[c00, c01, c02],[c10, c11, c12],[c20,c21,c22]])
    return ci

def newt2poly(xt,fxt):
    k = 0
    for i in range(n):
        xi = xt[k:k+3]
        fx = fxt[k:k+3]
        k = i + 2
        ci = coeffs(xi)
        a = cramer(ci,fx)
        newt2print(a,xi)

x = array([1.0, 3.0, 4.0, 6.0, 9.0])
y = array([4.0, 3.5, 2.9, 2.5, 2.75])
n = int((len(x)-1)/2)


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans

dataset = pd.read_csv('cancer_data.csv')
X = dataset.iloc[:, [1, 30]].values

wcss = []
for k in range(1, 11):
    kmeans = KMeans(n_clusters=k, init='k-means++', random_state=42)
    kmeans.fit(X)
    wcss.append(kmeans.inertia_)
plt.plot(range(1, 11), wcss)
plt.xlabel('No of clusters (k)')
plt.xlabel('WCSS - Within-Cluster Sum of Square')
plt.title('The elbow method')
plt.show()


# -*- coding: utf-8 -*-
"""
Created on Thu Sep 16 20:23:07 2021

@author: gansa001
"""
from sympy import *
import math

def delta(j,a,x,N):
    '''
    Parameters
    ----------
    j : int.
        the jth lagrange polynomial
    a : sympy symbol
        the dependent term in the lagrange polynomial
    x : list
        x values from the original function
    N : int

    Returns
    -------
    The lagrange polynomial for any given j

    '''
    num=1
    den=1
    for i in range(N):
        if i!=j:
            num*=(a-x[i])
            den*=(x[j]-x[i])
    return (num/den)

def Lagrange(x,y):
    '''
    Parameters
    ----------
    x : list
        dependent variable values
    y : list
        independent variable values

    Returns
    -------
    The lagrangian approximation of the function

    '''
    N=len(x)
    a=Symbol('a')
    f=0
    for j in range(len(x)):
        answer= (delta(j,a,x,N))
        f+=y[j]*answer
    return (simplify(f))

#test
'''
enter corresponding x and y values of the function to approximate.
In this case, the exponential function from -3 to 3
'''
x=[-3,-2,-1,0,1,2,3]
y=[math.exp(i) for i in x]

print(Lagrange(x,y))


from factorization.matrixtools import Mattools
import numpy as np
import copy

def gram_schmidt(matrix):
    '''
    QR for Gram_Schmidt_orthogonalization

    Examples
    ========

    >>>from factorization.qr_factorization import gram_schmidt
    >>>import numpy as np
    >>>a = np.array([[0,-20,-14],[3,27,-4],[4,11,-2]])
    >>>gram_schmidt(a)

    array([[ 0.  , -0.8 , -0.6 ],
        [ 0.6 ,  0.48, -0.64],
        [ 0.8 , -0.36,  0.48]]), array([[ 5., 25., -4.],
        [ 0., 25., 10.],
        [ 0.,  0., 10.]])

    '''
    mat = copy.deepcopy(matrix)
    nrow, ncol = mat.shape
    R = np.zeros([ncol, ncol])
    Q = np.zeros(mat.shape)
    try:
        for i in range(ncol):
            a = mat[:, i]
            if i == 0:
                v = np.sqrt(np.sum(a ** 2))
                R[i, i] = v
                q = a / v
                Q[:, i] = q
            else:
                fourier = np.zeros([nrow])
                for j in range(i):
                    R[j, i] = np.dot(Q[:, j].T, a)
                    fourier += R[j, i] * Q[:, j]
                q = a - fourier
                v = np.sqrt(np.sum(q ** 2))
                R[i, i] = v
                q = q / v
                Q[:, i] = q
        return Q, R
    except:
        print("Some errors happend,Please check the matrix to ensure its column independent")

def get_reflector(submatix):
    '''
    For reflector of a matrix (line)
    '''
    mat = copy.deepcopy(submatix)
    col = mat.size
    e = np.zeros(col)
    e[0] = 1
    u = submatix - np.sqrt(np.sum(submatix ** 2)) * e
    if np.all(u == 0):
        return np.eye(col)
    else:
        u = u.reshape(col, 1)
        R = np.eye(col) - 2 * (u @ u.T) / (u.T @ u)
        return R

def householder(matrix):
    '''
    For Householder_reduction using reflectors

    :return P: orthogonal matrix
            R: upper triangular matrix

    Example
    ========

    >>>from factorization.qr_factorization import householder
    >>>import numpy as np
    >>>a = np.array([[0,-20,-14],[3,27,-4],[4,11,-2]])
    >>>householder(a)

    array([[ 0.  ,  0.6 ,  0.8 ],
        [-0.8 ,  0.48, -0.36],
        [-0.6 , -0.64,  0.48]]),
     array([[ 5.00000000e+00,  2.50000000e+01, -4.00000000e+00],
            [ 0.00000000e+00,  2.50000000e+01,  1.00000000e+01],
            [ 0.00000000e+00, -1.33226763e-15,  1.00000000e+01]])

    '''
    mat = copy.deepcopy(matrix)
    nrow, ncol = mat.shape
    P = np.eye(nrow)
    submat = copy.deepcopy(mat)
    if nrow > ncol:
        for i in range(min(nrow, ncol)):
            submat_this = submat[i:, i:]
            R_this = np.eye(nrow)
            if np.all(submat_this[:, 0] == 0):
                continue
            else:
                sub_R_this = get_reflector(submat_this[:, 0])
            R_this[i:, i:] = sub_R_this
            submat = R_this @ submat
            P = R_this @ P
        return P, submat
    else:
        for i in range(min(nrow, ncol) - 1):
            submat_this = submat[i:, i:]
            R_this = np.eye(nrow)
            if np.all(submat_this[:, 0] == 0):
                continue
            else:
                sub_R_this = get_reflector(submat_this[:, 0])
            R_this[i:, i:] = sub_R_this
            submat = R_this @ submat
            P = R_this @ P
        return P, submat

def get_rotation(submatrix):
    '''
    For rotation of a matrix (line)
    '''
    mat = copy.deepcopy(submatrix)
    ncol = mat.size
    P = np.eye(ncol)
    for i in range(1, ncol):
        P_this = np.eye(ncol)
        noml = np.sqrt(mat[0] ** 2 + mat[i] ** 2)
        if noml != 0:
            c = mat[0] / noml
            s = mat[i] / noml
            P_this[0, 0] = c
            P_this[0, i] = s
            P_this[i, 0] = -s
            P_this[i, i] = c
            mat = P_this @ mat
            P = P_this @ P
        else:
            continue
    return P

def givens(matrix):
    '''
    For Givens_reduction using reflectors

    :return P: orthogonal matrix
            R: upper triangular matrix

    Example
    ========

    >>>from factorization.qr_factorization import householder
    >>>import numpy as np
    >>>a = np.array([[0,-20,-14],[3,27,-4],[4,11,-2]])
    >>>householder(a)

    array([[ 0.  ,  0.6 ,  0.8 ],
        [-0.8 ,  0.48, -0.36],
        [-0.6 , -0.64,  0.48]]),
     array([[ 5.00000000e+00,  2.50000000e+01, -4.00000000e+00],
            [ 2.66453526e-16,  2.50000000e+01,  1.00000000e+01],
            [-3.55271368e-16, -3.10862447e-16,  1.00000000e+01]])
    '''
    mat = copy.deepcopy(matrix)
    nrow, ncol = mat.shape
    P = np.eye(nrow)
    submat = copy.deepcopy(mat)
    for i in range(min(nrow, ncol)):
        submat_this = submat[i:, i:]
        P_this = np.eye(nrow)
        sub_P_this = get_rotation(submat_this[:, 0])
        P_this[i:, i:] = sub_P_this
        submat = P_this @ submat
        P = P_this @ P
    return P, submat

class QR(Mattools):
    def __init__(self, mat):
        super().__init__(mat)

    def Gram_Schmidt_orthogonalization(self):
        '''

        Example
        ========

        >>>from factorization import Matrix
        >>>import numpy as np
        >>>a = np.array([[0,-20,-14],[3,27,-4],[4,11,-2]])
        >>>test_object = Matrix(a)
        >>>test_object.Gram_Schmidt_orthogonalization()

        array([[ 0.  , -0.8 , -0.6 ],
        [ 0.6 ,  0.48, -0.64],
        [ 0.8 , -0.36,  0.48]]), array([[ 5., 25., -4.],
        [ 0., 25., 10.],
        [ 0.,  0., 10.]])

        See alse
        ========

        def gram_schmidt(matrix)
        '''
        return gram_schmidt(self.mat)

    def Householder_reduction(self):
        '''

        Example
        ========

        >>>from factorization import Matrix
        >>>import numpy as np
        >>>a = np.array([[0,-20,-14],[3,27,-4],[4,11,-2]])
        >>>test_object = Matrix(a)
        >>>test_object.Householder_reduction()

        array([[ 0.  ,  0.6 ,  0.8 ],
        [-0.8 ,  0.48, -0.36],
        [-0.6 , -0.64,  0.48]]),
         array([[ 5.00000000e+00,  2.50000000e+01, -4.00000000e+00],
                [ 0.00000000e+00,  2.50000000e+01,  1.00000000e+01],
                [ 0.00000000e+00, -1.33226763e-15,  1.00000000e+01]])

        See alse
        ========

        def gram_schmidt(matrix)
        '''

        return householder(self.mat)

    def Givens_reduction(self):
        '''

        Example
        ========

        >>>from factorization import Matrix
        >>>import numpy as np
        >>>a = np.array([[0,-20,-14],[3,27,-4],[4,11,-2]])
        >>>test_object = Matrix(a)
        >>>test_object.Givens_reduction()

        array([[ 0.  ,  0.6 ,  0.8 ],
        [-0.8 ,  0.48, -0.36],
        [-0.6 , -0.64,  0.48]]),
         array([[ 5.00000000e+00,  2.50000000e+01, -4.00000000e+00],
                [ 0.00000000e+00,  2.50000000e+01,  1.00000000e+01],
                [ 0.00000000e+00, -1.33226763e-15,  1.00000000e+01]])

        See alse
        ========

        def gram_schmidt(matrix)
        '''

        return givens(self.mat)

if __name__ == "__main__":
    pass


# measurements.py
"""
Functions to calculate distance between points using different metrics.
"""
import numpy as np

def crow(pointA, pointB):
	"""
	Distance between points A and B "as the crow flies."
		pointA = (x1, y1)
		pointB = (x2, y2)
	returns sqrt( (x2-x1)**2 + (y2-y1)**2 )
	"""
	interval = np.sqrt( (pointA[0] - pointB[0])**2 + \
						(pointA[1] - pointB[1])**2 )
	return interval

def taxicab(pointA, pointB):
	"""
	Distance between points A and B "as the cab drives."
		pointA = (x1, y1)
		pointB = (x2, y2)
	returns |x2-x1| + |y2-y1|
	"""
	interval =	abs(pointB[0] - pointA[0]) + \
				abs(pointB[1] - pointA[1])
	return interval

def distance(pointA, pointB=(0,0), metric='taxi'):
	"""
	Return distance between points A and B. If metric is 'taxi', use taxicab
	metric. Otherwise, use Euclidean distance.
		pointA = (x1, y1)
		pointB = (x2, y2)
	"""
	if metric == 'taxi':
		return taxicab(pointA, pointB)
	else:
		return crow(pointA, pointB)


# Optimización sin restricciones 1

import scipy.optimize

f = lambda x : x**2 - 2*x + 5*np.sin(2*x)
df = lambda x : 2*x - 2 + 10*np.cos(2*x)

fig, ax = plt.subplots(1, figsize=(7, 2), tight_layout=True, sharex=True)
x_plot = np.linspace(-5, 8, num=100)
ax.plot(x_plot, f(x_plot))

for x0 in [-4, 0, 4, 6]:
    res = scipy.optimize.minimize(f, x0, jac=df,
                                  options={'disp':False})

    print(x0, res.x[0], res.fun, res.message)
    ax.scatter(res.x, f(res.x), s=100, label=f'$x_0$={x0}')
ax.legend();

# Optimización sin restricciones 2

def fun(x):
    return dromedario(x[0], x[1])

def dromedario(x, y):
    return (4 -2.1*x**2+x**4/3)*x**2 +x*y + 4*y**2*(y**2 -1)

x = np.linspace(-2, 2, num=20)
y = np.linspace(-1, 1, num=20)
X, Y = np.meshgrid(x, y)
F = dromedario(X, Y)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, F, cmap=plt.cm.Blues);
ax.set_xlabel('x')
ax.set_ylabel('y')

x0 = [-1.0, -1.0]
for x0 in [[-1, -1], [1, 1]]:
    res = scipy.optimize.minimize(fun, x0)
    print(x0, res.x, res.fun, res.message)
    ax.scatter(res.x[0], res.x[1], fun(res.x), s=200, label=f'$x_0$:{x0}');
ax.legend();

# Optimización con restricciones

def f(z):
    return -(2*z[0]*z[1] + 2*z[0] - z[0]**2 - 2*z[1]**2)

def df(z):
    return np.array([-2*z[1] - 2 + 2*z[0],
                     -2*z[0] + 4*z[1]])

cons = ({'type': 'eq',
         'fun': lambda z: z[0]**3 - z[1],
         'jac': lambda z: np.array([3*z[0]**2, -1.])
        },
        {'type': 'ineq',
         'fun': lambda z: z[1] - (z[0]-1)**4 - 2,
         'jac': lambda z: np.array([-4*(z[0]-1)**3, 1.])
        })

bnds = ((0.5, 1.5), (1.5, 2.5))

x0 = np.array([0, 1])

res1 = scipy.optimize.minimize(f, x0, jac=df)
res2 = scipy.optimize.minimize(f, x0, jac=df,
                               method='L-BFGS-B', bounds=bnds)
res3 = scipy.optimize.minimize(f, x0, jac=df,
                               method='SLSQP', bounds=bnds, constraints=cons)

display(res1.x, res2.x, res3.x)

x = np.linspace(0, 3, 100)
y = np.linspace(0, 3, 100)
A, B = np.meshgrid(x, y)
C = f(np.vstack([A.ravel(), B.ravel()])).reshape((100,100))
fig, ax = plt.subplots(figsize=(5, 3))
ax.contourf(A, B, C, cmap=plt.cm.Blues);
ax.plot(x, x**3, 'k--', lw=2)
ax.plot(x, 2+(x-1)**4, 'k:', lw=2)
ax.fill([0.5, 0.5, 1.5, 1.5], [2.5, 1.5, 1.5, 2.5], alpha=0.3)
ax.axis([0, 3, 0, 3])
ax.scatter(res1.x[0], res1.x[1], s=20, c='b', label='BFGS');
ax.scatter(res2.x[0], res2.x[1], s=20, c='g', label='L-BFGS-B');
ax.scatter(res3.x[0], res3.x[1], s=20, c='r', label='SLSQP');
plt.legend();


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

# q3. a
# (1)
def cal_log(x, y, sigma):
    return (-1/(np.pi * np.power(sigma, 4)))\
           * (1-(np.power(x, 2)+np.power(y, 2))/(2*np.power(sigma, 2)))\
           * np.exp(-(np.power(x, 2)+np.power(y, 2))/(2*np.power(sigma, 2)))

def generate_log_kernel(sigma, tp):
    max = cal_log(0, 0, sigma)
    threshold = np.abs(max * tp)
    y = 0
    x = 0

    while np.abs(cal_log(x, y, sigma)) > threshold:
        x += 1
    print(x)
    height, width = 2*x-1, 2*x-1
    kernel = np.zeros((height, width))
    for i in range(x):
        for j in range(x):
            entry_val = cal_log(i, j, sigma)
            kernel[-i+x-1, -j+x-1] = entry_val
            kernel[i+x-1, -j+x-1] = entry_val
            kernel[-i+x-1, j+x-1] = entry_val
            kernel[i+x-1, j+x-1] = entry_val
    return kernel

def svd(M):
    u, s, v = np.linalg.svd(M)
    s = s.tolist()
    if(np.nonzero(s)) != 1:
        return False
    return True

# q3. b
# (2)
def cal_gaussian(mean, sigma, x):
    result = np.divide(1, np.sqrt(2 * np.pi * np.power(sigma, 2))) *  \
    np.exp(- np.divide(np.power((x-mean), 2), (2 * np.power(sigma, 2))))
    return result

def generate_gaussian_kernel(sigma, k, increment):
    x_list = np.arange(-k, k, increment)
    mean = np.mean(x_list)
    y_list = []
    for x in x_list:
        y_list.append(cal_gaussian(mean, sigma, x))
    return y_list

def cal_1d_log(sigma, x):
    result = (np.divide(np.power(x, 2), np.power(sigma, 4))- np.divide(1, np.power(sigma, 2))) * \
             np.exp(- np.divide(np.power(x, 2), (2 * np.power(sigma, 2)))) * \
             np.divide(1, np.sqrt(2 * np.pi * np.power(sigma, 2)))
    return result

def generate_1d_log_kernel(sigma, k, increment):
    x_list = np.arange(-k, k, increment)
    y_list = []
    for x in x_list:
        y_list.append(cal_1d_log(sigma, x))
    return y_list, x_list

def cal_dog(g1, g2):
    return np.array(g1)-np.array(g2)

# cite: http://kestrel.nmt.edu/~raymond/software/python_notes/paper004.html
def plot(dog, log, x_list, i):
    x = x_list
    a = dog
    b = log
    plt.plot(x, a, 'b-', label='DOG')
    plt.plot(x, b, 'r--', label='LOG')
    plt.legend(loc='upper left')
    plt.xlabel('X')
    plt.ylabel('Value')
    plt.title("Scale(increasing) {}".format(i))

def plot_multiples(scales, initial_sigma, k, increment):
    plt.figure(figsize=(20, 10))
    i = 0
    for scale in scales:
        print("Iteration: {}".format(i+1))
        gaussian_kernel_2 = generate_gaussian_kernel(initial_sigma, k, increment)
        print("Gaussian kernel 2's length is: {}".format(len(gaussian_kernel_2)))
        gaussian_kernel_1 = generate_gaussian_kernel(scale*initial_sigma, k, increment)
        print("Gaussian kernel 1's length is: {}".format(len(gaussian_kernel_1)))
        log_1d_kernel, x_list = generate_1d_log_kernel(scale*initial_sigma, k, increment)
        print("1d log kernel's length is: {}".format(len(log_1d_kernel)))
        dog = cal_dog(gaussian_kernel_1, gaussian_kernel_2)
        plt.subplot(np.ceil(np.sqrt(len(scales))), np.floor(np.sqrt(len(scales))), i + 1)
        plot(dog, log_1d_kernel, x_list, i + 1)
        i += 1

if __name__ == '__main__':
    start = datetime.now()
    kernel = generate_log_kernel(1.4, 0.02)
    print(kernel)
    print("Can be svd? {}".format(svd(kernel)))
    scales = np.arange(1, 3, 0.2)
    plot_multiples(scales, 1, 15, 0.1)
    plt.title("Initial sigma = 1")
    plot_multiples(scales, 2, 15, 0.1)
    plt.title("Initial sigma = 5")
    plt.tight_layout()
    plt.show()
    print(datetime.now() - start)


import numpy as np

# Note: please don't import any new package. You should solve this problem using only the package(s) above.
#-------------------------------------------------------------------------

'''
    Problem 2: Support Vector Machine (with Linear Kernel)
    In this problem, you will implement the SVM classification method.  We will optimize the parameters using gradient descent method.
    A list of all variables being used in this problem is provided at the end of this file.
'''

#---------------------------------------------------
'''
    compute the f(x) of the linear model on one data instance x.
    Inputs:
        * x: the feature vector of one training data sample, a numpy vector of length p.
        * w: the weights of the SVM model, a numpy float vector of length p.
        * b: the bias of the SVM model, a float scalar.
    Outputs:
        * fx: f(x), the output of linear model on the data instance (x), a float scalar.
    --------------------------------------
    Example:
        suppose we have a two dimensional feature space (p=2).
        If x is a data sample with 2 features:
        x = [1, 2]
        So now we want to compute the f(x) = w*x +b in the linear model.
        Suppose w = [0.1, 0.2] and b = -0.5
        The result f(x) = w*x + b =  0.1*1 + 0.2*2 - 0.5 = 0
    --------------------------------------
    Hints:
        * This problem can be solved using 1 line(s) of code.
'''
#---------------------
def compute_fx(x, w, b):
    #########################################
    ## INSERT YOUR CODE HERE
    fx = sum(a * b for [a, b] in zip(x, w)) + b
    #########################################
    return fx
#---------------------
    '''
        TEST: Now you can test the correctness of your code above by typing the following in the terminal:
        ---------------------------------------------------
        nosetests -v test2.py:test_compute_fx
        ---------------------------------------------------
    '''

#---------------------------------------------------
'''
    compute the g(x) of the linear model on one data instance x.
    Inputs:
        * x: the feature vector of one training data sample, a numpy vector of length p.
        * w: the weights of the SVM model, a numpy float vector of length p.
        * b: the bias of the SVM model, a float scalar.
    Outputs:
        * gx: g(x), the predicted label (-1 or 1) of SVM model on the data instance (x), a float scalar.
    --------------------------------------
    Example:
        suppose we have a two dimensional feature space (p=2).
        If x is a data sample with 2 features:
        x = [1, 2]
        So now we want to compute the f(x) = w*x +b in the linear model.
        Suppose w = [0.1, 0.2] and b = -0.4
        f(x) = w*x + b =  0.1*1 + 0.2*2 - 0.4 = 0.1
        The result g(x) = sign(f(x))= sign(0.1) = 1
    --------------------------------------
    Hints:
        * This problem can be solved using 1 line(s) of code.
'''
#---------------------
def compute_gx(x, w, b):
    #########################################
    ## INSERT YOUR CODE HERE
    gx = 1 if (compute_fx(x, w, b) >= 0) else -1
    #########################################
    return gx
#---------------------
    '''
        TEST: Now you can test the correctness of your code above by typing the following in the terminal:
        ---------------------------------------------------
        nosetests -v test2.py:test_compute_gx
        ---------------------------------------------------
    '''

#---------------------------------------------------
'''
    Compute the gradient of loss function of SVM w.r.t. w and b (on one training instance).
    Inputs:
        * x: the feature vector of one training data sample, a numpy vector of length p.
        * y: the label of one training data sample, a float scalar (1 or -1).
        * w: the weights of the SVM model, a numpy float vector of length p.
        * b: the bias of the SVM model, a float scalar.
        * l: short for lambda = 1/ (n C), which is the weight of the L2 regularization term.
    Outputs:
        * dL_dw: the gradient of the weights, a numpy float vector of length p. The i-th element is  d L / d w[i].
        * dL_db: the gradient of the bias, a float scalar.
    Hints:
        * This problem can be solved using 6 line(s) of code.
'''
#---------------------
def compute_gradient(x, y, w, b, l=0.01):
    #########################################
    ## INSERT YOUR CODE HERE
    if 1 - y * compute_fx(x, w, b) > 0:
        dL_dw = l * w - y * x
        dL_db = -y
    else:
        dL_dw = l * w
        dL_db = 0
    #########################################
    return dL_dw, dL_db
#---------------------
    '''
        TEST: Now you can test the correctness of your code above by typing the following in the terminal:
        ---------------------------------------------------
        nosetests -v test2.py:test_compute_gradient
        ---------------------------------------------------
    '''

#---------------------------------------------------
'''
    Update the parameter w using the gradient descent.
    Inputs:
        * w: the weights of the SVM model, a numpy float vector of length p.
        * dL_dw: the gradient of the weights, a numpy float vector of length p. The i-th element is  d L / d w[i].
        * lr: the learning rate, a float scalar, controlling the speed of gradient descent.
    Outputs:
        * w: the weights of the SVM model, a numpy float vector of length p.
    Hints:
        * This problem can be solved using 1 line(s) of code.
'''
#---------------------
def update_w(w, dL_dw, lr=0.01):
    #########################################
    ## INSERT YOUR CODE HERE
    w = w - lr * dL_dw
    #########################################
    return w
#---------------------
    '''
        TEST: Now you can test the correctness of your code above by typing the following in the terminal:
        ---------------------------------------------------
        nosetests -v test2.py:test_update_w
        ---------------------------------------------------
    '''

#---------------------------------------------------
'''
    Update the parameter b using the gradient descent.
    Inputs:
        * b: the bias of the SVM model, a float scalar.
        * dL_db: the gradient of the bias, a float scalar.
        * lr: the learning rate, a float scalar, controlling the speed of gradient descent.
    Outputs:
        * b: the bias of the SVM model, a float scalar.
    Hints:
        * This problem can be solved using 1 line(s) of code.
'''
#---------------------
def update_b(b, dL_db, lr=0.01):
    #########################################
    ## INSERT YOUR CODE HERE
    b = b - lr * dL_db
    #########################################
    return b
#---------------------
    '''
        TEST: Now you can test the correctness of your code above by typing the following in the terminal:
        ---------------------------------------------------
        nosetests -v test2.py:test_update_b
        ---------------------------------------------------
    '''

#---------------------------------------------------
'''
    Train the SVM model using Stochastic Gradient Descent (SGD).
    Inputs:
        * X: the feature matrix of all data samples, a numpy matrix of shape n by p.
        * Y: the labels of all data samples, a numpy vector of length n. If the i-th instance is positive labeled, Y[i]= 1, otherwise -1.
        * lr: the learning rate, a float scalar, controlling the speed of gradient descent.
        * C: the weight of the hinge loss.
        * n_epoch: the number of rounds to iterate through all training examples.
    Outputs:
        * w: the weights of the SVM model, a numpy float vector of length p.
        * b: the bias of the SVM model, a float scalar.
    Hints:
        * You could use the functions that you have implemented above to build the solution.
        * This problem can be solved using 3 line(s) of code.
'''
#---------------------
def train(X, Y, lr=0.01, C=1.0, n_epoch=10):
    n,p = X.shape
    l = 1./(n * C) #l is the weight of the L2 regularization term.
    w,b = np.random.rand(p), 0. # initialize the weight with a random vector and bias with 0
    for _ in range(n_epoch): # iterate through the dataset n_epoch times
        indices = np.random.permutation(n) # shuffle the indices of all instances
        for i in indices: # iterate through each random instance (x,y)
            x=X[i] # the feature vector of the i-th random instance
            y=Y[i] # the label of the i-th random instance
            #########################################
            ## INSERT YOUR CODE HERE
            dL_dw, dL_db = compute_gradient(x, y, w, b, l)
            w = update_w(w, dL_dw, lr)
            b = update_b(b, dL_db, lr)
            #########################################
    return w, b
#---------------------
    '''
        TEST: Now you can test the correctness of your code above by typing the following in the terminal:
        ---------------------------------------------------
        nosetests -v test2.py:test_train
        ---------------------------------------------------
    '''

#---------------------------------------------------
'''
    Predict the labels of a set of data instances. Suppose the weights w and bias b of the SVM are given, use the model to predict the labels of all the instances in matrix X.
    Inputs:
        * X: the feature matrix of all data samples, a numpy matrix of shape n by p.
        * w: the weights of the SVM model, a numpy float vector of length p.
        * b: the bias of the SVM model, a float scalar.
    Outputs:
        * Y: the labels of all data samples, a numpy vector of length n. If the i-th instance is positive labeled, Y[i]= 1, otherwise -1.
    Hints:
        * You could use np.where() to simplify the code.
        * This problem can be solved using 1 line(s) of code.
'''
#---------------------
def predict(X, w, b):
    #########################################
    ## INSERT YOUR CODE HERE
    Y = np.where(np.dot(X, w) + b > 0, 1, -1)
    #########################################
    return Y
#---------------------
    '''
        TEST: Now you can test the correctness of your code above by typing the following in the terminal:
        ---------------------------------------------------
        nosetests -v test2.py:test_predict
        ---------------------------------------------------
    '''
    '''
        TEST: Now you can test the correctness of your code above by typing the following in the terminal:
        ---------------------------------------------------
        nosetests -v test2.py:test_svm
        ---------------------------------------------------
    '''

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

'''
    TEST problem 2:
        Now you can test the correctness of all the above functions by typing the following in the terminal:
        ---------------------------------------------------
        nosetests -v test2.py
        ---------------------------------------------------

        If your code passed all the tests, you will see the following message in the terminal:
        ----------- Problem 2 (30 points in total)--------------------- ... ok
        * (1 point) compute_fx ... ok
        * (1 points) compute_gx ... ok
        * (5 points) compute_gradient ... ok
        * (5 points) update_w ... ok
        * (5 points) update_b ... ok
        * (5 point) train ... ok
        * (3 points) predict ... ok
        * (5 point) SVM ... ok
        ----------------------------------------------------------------------
        Ran 8 tests in 0.959s

        OK
'''

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

#--------------------------------------------
'''
    List of All Variables

* p:  the number of dimensions in the feature space.
* n:  the number of data instances.
* X:  the feature matrix of all data samples, a numpy matrix of shape n by p.
* Y:  the labels of all data samples, a numpy vector of length n. If the i-th instance is positive labeled, Y[i]= 1, otherwise -1.
* w:  the weights of the SVM model, a numpy float vector of length p.
* b:  the bias of the SVM model, a float scalar.
* x:  the feature vector of one training data sample, a numpy vector of length p.
* y:  the label of one training data sample, a float scalar (1 or -1).
* fx:  f(x), the output of linear model on the data instance (x), a float scalar.
* gx:  g(x), the predicted label (-1 or 1) of SVM model on the data instance (x), a float scalar.
* C:  the weight of the hinge loss.
* l:  short for lambda = 1/ (n C), which is the weight of the L2 regularization term.
* dL_dw:  the gradient of the weights, a numpy float vector of length p. The i-th element is  d L / d w[i].
* dL_db:  the gradient of the bias, a float scalar.
* lr:  the learning rate, a float scalar, controlling the speed of gradient descent.
* n_epoch:  the number of rounds to iterate through all training examples.

'''
#--------------------------------------------


# -*- coding: utf-8 -*-
"""
Two Masses on a String -- N-D Newton Raphson Solver

Created on Fri Oct  4 10:12:18 2019

@author: gafeiden
"""
import numpy as np
import numpy.linalg as la

def makeJacobian(Ls, X):
    """ Create the Jacobian for the system

    INPUT
        L1 = Length of string connecting mass 1 to ceiling
        L2 = Length of string connecting masses 1 and 2
        L3 = Length of string connecting mass 2 to ceiling
        X  = [sin01, cos01, sin02, cos02, sin03, cos03, T1, T2, T3]

    OUTPUT
        J  = Jacobian of system

    """
    J = np.zeros((9, 9))

    J[0, 1] = J[1, 0] = Ls[0]
    J[0, 3] = J[1, 2] = Ls[1]
    J[0, 5] = Ls[2]
    J[1, 4] = -Ls[2]

    J[2, 0] = 2*X[0]
    J[2, 1] = 2*X[1]
    J[3, 2] = 2*X[2]
    J[3, 3] = 2*X[3]
    J[4, 4] = 2*X[4]
    J[4, 5] = 2*X[5]

    J[5, 0] = J[6, 1] = X[6]
    J[5, 2] = J[6, 3] = -X[7]
    J[5, 6] = X[0]
    J[5, 7] = -X[2]
    J[6, 6] = X[1]
    J[6, 7] = -X[3]

    J[7, 2] = J[8, 3] = X[7]
    J[7, 4] = X[8]
    J[7, 5] = -X[8]

    J[7, 7] = X[2]
    J[7, 8] = X[4]
    J[8, 7] = X[3]
    J[8, 8] = -X[5]

    return J

def makeFunction(L, Ls, W, X):
    """ Populate array with function values """
    f = np.empty((9, 1))

    f[0] = Ls[0]*X[1] + Ls[1]*X[3] + Ls[2]*X[5] - L
    f[1] = Ls[0]*X[0] + Ls[1]*X[2] - Ls[2]*X[4]
    f[2] = X[0]**2 + X[1]**2 - 1.0
    f[3] = X[2]**2 + X[3]**2 - 1.0
    f[4] = X[4]**2 + X[5]**2 - 1.0
    f[5] = X[6]*X[0] - X[7]*X[2] - W[0]
    f[6] = X[6]*X[1] - X[7]*X[3]
    f[7] = X[7]*X[2] + X[8]*X[4] - W[1]
    f[8] = X[7]*X[3] - X[8]*X[5]

    return f

def calcAngles(X):
    """ """
    angles = np.zeros((3, 1))

    angles[0] = np.arcsin(X[0])*180./np.pi
    angles[1] = np.arcsin(X[2])*180./np.pi
    angles[2] = np.arcsin(X[4])*180./np.pi

    return angles

def checkChange(dX):
    """ """
    tol = 1.0e-8
    if abs(dX[0]) < tol and abs(dX[1]) < tol and abs(dX[2]) < tol and \
       abs(dX[3]) < tol and abs(dX[4]) < tol and abs(dX[5]) < tol and \
       abs(dX[6]) < tol and abs(dX[7]) < tol and abs(dX[8]) < tol:
        small_change = True
    else:
        small_change = False

    return small_change

def setInitialX():
    """ Set initial guess for X """
    return np.zeros((9, 1)) + 1.0

# SOLVE THE SYSTEM OF EQUATIONS USING NEWTON-RAPHSON #

# set initial conditions:
X  = setInitialX()    # initial guess on X
W  = [10.0, 20.0]     # individual weights [W1, W2]
Ls = [3.0, 4.0, 4.0]  # length of strings [L1, L2, L3]
L  = 8.0              # total length between end points, L

for n in range(100):
    # compute values of functions
    f  = makeFunction(L, Ls, W, X)

    # compute Jacobian
    J  = makeJacobian(Ls, X)

    # calculate changes to guess
    dX = np.dot(-1.0*la.inv(J), f)

    # modify current guess
    X  += dX

    # check that all elements in dX are within the tolerance
    solved = checkChange(dX)
    if solved:
        break
    else:
        continue

print("Changes after {:03.0f} iterations:".format(n))
print(dX)
print("Final Parameters:")
print(X)
print("Angles:")
print(calcAngles(X))


# -*- coding: utf-8 -*-
"""
ELS 1-asset by Monte Carlo simulation

@author: Minhyun Yoo
"""
import time
import numpy as np
from math import exp, sqrt, ceil

S0 = 100.0; # underlying price
T = 3.0; # maturity
r = 0.03; # riskless interest rate
discr = 0.03; # discount rate
sig = 0.3; # volatility
ns = 10000;  # # of simulations
dateConv = 360; # 1 year
nStep = int(dateConv * T); # # of time steps

Kib = 60.0; # knock in barrier
cpy = 0.03; # coupon per year
dummy = cpy*T; # dummy
face = 10000.0; # face value
B = [90.0, 90.0, 85.0, 85.0, 80.0, 80.0]; # early redemption barrier
numExercise = len(B);
obsDate = np.array([ceil(nStep / numExercise), ceil(2.0*nStep/numExercise), ceil(3.0*nStep/numExercise),
           ceil(4.0*nStep / numExercise), ceil(5.0*nStep / numExercise), ceil(nStep)]); # observation dates
payment = face*(1.0+np.array([cpy*0.5, cpy*1.0, cpy*1.5, cpy*2.0, cpy*2.5, cpy*3.0])); # payment

dt = T / nStep; # time step
t0 = time.clock();

sumPayoff = 0.0;
idx = np.zeros(numExercise);

drift = (r - 0.5*sig**2)*dt;
sigsqdt = sig*sqrt(dt);

# simulation loop
for i in xrange(ns):
    s = S0; kievent = False; tag = False;
    cnt1 = 0; cnt2 = 0;
    payoff = 0.0; idx[:] = 0.0;

    # random number
    z = np.random.normal(size = [nStep]);
    # generating path
    for j in xrange(nStep):
        s = s * exp(drift + sigsqdt*z[j]);

        # check hitting knock in barrier
        kievent = True if s < Kib else kievent;

        # save the underlying price at observation dates
        if ((j+1) == obsDate[cnt1]):
            idx[cnt1] = s;
            cnt1 += 1;

    # check for early redemption
    for k in xrange(numExercise):
        if (idx[k] >= B[k]):
            payoff = payment[k];
            tag = True;
            cnt2 = k;
            break;

    if (tag == False):
        payoff = 100.0 * s; # loss
        if (kievent == False):
            if (s >= Kib):
                payoff = face * (1.0 + dummy); # dummy
    sumPayoff += payoff * exp(-discr * obsDate[cnt2] / dateConv);

# mean
els1 = sumPayoff / ns;
t1 = time.clock();
del z;

print 'Monte Carlo Call Price : %.5f' % els1
print 'CPU time in Python(sec) : %.4f' % (t1-t0)


"""
A gambler has the opportunity to make bets on the outcomes of a sequence of coin flips.
If the coin comes up heads, he wins as many dollars as he has staked on that flip;
if it is tails, he loses his stake.

The game ends when the gambler wins by reaching his goal of $100, or loses by running out of money.
On each flip, the gambler must decide what portion of his capital to stake, in integer numbers of dollars.
This problem can be formulated as an undiscounted, episodic, finite MDP.
The state is the gambler’s capital,  s belongs {1, 2, . . . , 99} and the actions
are stakes, a belongs {0, 1, . . . , min(s, 100− s)}.
The reward is zero on all transitions except those on which the gambler reaches his goal, when it is +1.

The state-value function then gives the probability of winning from each state. A policy is a mapping from
levels of capital to stakes. The optimal policy maximizes the probability of reaching the goal.
Let ph denote the probability of the coin coming up heads. If ph is known, then the entire problem is known
and it can be solved, for instance, by value iteration.

Figure 4.3 shows the change in the value function over successive sweeps of value iteration,
and the final policy found, for the case of ph = 0.4. This policy is optimal, but not unique.
Infact, there is a whole family of optimal policies, all corresponding to ties for the argmax action
selection with respect to the optimal value function. Can you guess what the entire family looks like?

https://github.com/dennybritz/reinforcement-learning/blob/master/DP/Gamblers%20Problem%20Solution.ipynb

"""

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

# state  0~ 99
# actions = 0 ~ min(s, 100-s)
# reward 0 except when gambler reaches his goal +1

# state-value function : the probability of winning from each state
# policy is a mapping levels of capital to stake
# optimal policy --> maximizes the probability of reaching the goal
# ph --> probability of the coin coming up heads!!

gamma = 1.0
ph = 0.4
#small threshold value for comparing between estimated and real value function
theta = 0.00001

n_observation_space = 100

reward = [0 for _ in range(n_observation_space+1)]
reward[n_observation_space] = 1

value = [0 for _ in range(n_observation_space+1)]
policy = [0 for _ in range(n_observation_space+1)]

def rollout():
    delta = 1

    while delta > theta:
        delta = 0
        "Looping over all the states i.e the money in hand for a current episode"
        for i in range(1, n_observation_space):
            oldvalue = value[i]
            bellmanoptimalequation(i)
            diff = abs(oldvalue - value[i])
            delta = max(delta, diff)
        print(value)
        print(policy)
    plt.plot(policy)
    plt.show()
    plt.plot(value)
    plt.show()

def bellmanoptimalequation(num):
    optimalvalue = 0

    "The range of number of bets"
    for stake in range(1, min(num, 100-num)+1):
        "Amount after winning and loosing"
        win = num + stake
        loss = num - stake
        "calculate the average of possible states for an action"
        "In this case it would be Head or Tails"
        sum = ph * (reward[win] + gamma * value[win]) + (1 - ph) * (reward[loss] + gamma * value[loss])

        "Choose the action that gives the max reward and update the policy and value for that"
        if sum > optimalvalue:
            optimalvalue = sum
            value[num] = sum
            policy[num] = stake

if __name__=="__main__":
    rollout()


"""
Generalized functions used for the formulation of the problem. The notation generally follows that used in the
assignment: https://byu.instructure.com/courses/14234/assignments/545719
"""
import numpy as np

fx = 1
x_min, x_max = 0, 1
x_range = x_max - x_min

def formulate(num_points, gamma):
    """Formulate the A matrix and b vector"""
    A = A_matrix(num_points, gamma)
    b = b_vector(num_points)
    return A, b

def A_matrix(num_points, gamma):
    """Generate the A matrix"""
    dx = x_range / num_points
    A = np.zeros((num_points, num_points))
    for i, row in enumerate(A):
        # Create the diagonal terms
        row[i] = 2 / dx
        if i != 0:
            # Create the lower-diagonal terms
            row[i - 1] = -(1 / dx) - (gamma / 2)
        if i != num_points - 1:
            # Create the upper-diagonal terms
            row[i + 1] = -(1 / dx) + (gamma / 2)
            pass
    return A

def A1_inv(num_points):
    """Generate the A matrix"""
    dx = x_range / num_points
    A = np.zeros((num_points, num_points))
    for i, row in enumerate(A):
        # Create the diagonal terms
        row[i] = 2 / dx
        if i != 0:
            # Create the lower-diagonal terms
            row[i - 1] = -(1 / dx)
        if i != num_points - 1:
            # Create the upper-diagonal terms
            row[i + 1] = -(1 / dx)
            pass
    return np.linalg.inv(A)

def b_vector(num_points):
    """Generate the b vector"""
    dx = x_range / num_points
    b = np.ones((num_points, 1)) * dx
    return b


#===================================================================================
#
#   Code cho phương pháp Dây cung
#       * Input: f(x) trong pt f(x) = 0; khoảng cách li ban đầu (a, b); sai số epsilon
#       * Output: Nghiệm PT f(x) = 0;
#
#===================================================================================
from math import *
from sympy import *
import sys

#===================================================================================
# Phần thuật toán chính
class daycung_oop:
#{
    def __init__(self, a_0, b_0, eps, expr):
    #{
        x = symbols("x");
        self.func = sympify(expr);
        self.a_0 = a_0;
        self.b_0 = b_0;
        self.eps = eps;
        f=self.func
        self.sym_df = [
            f,
            diff(f, x),
            diff(f, x, 2)
        ];

        self.df = [
            lambdify(x, self.sym_df[0], "math"),
            lambdify(x, self.sym_df[1], "math"),
            lambdify(x, self.sym_df[2], "math"),
        ];
        # print(self.sym_df, file=sys.stderr);
    #}

    # Tìm max/min của f
    def __Kiem_tra_don_dieu(self):
    #{
        x = symbols("x");
        a = self.a_0;
        b = self.b_0;
        sol_set = solveset(diff(self.func, x ), x, Interval(a, b))
        sol_set = Union(sol_set, solveset(diff(self.func, x, 2), x, Interval(a, b)));
        sol_set = Union(sol_set, solveset(diff(self.func, x , 3), x, Interval(a, b)));
        if(sol_set.is_empty):
            for i in sol_set:
                print(i)
            return 1
        else:
            return 0
    #}

    # Chốt 141
    def __checkInputValidity(self):
    #{
        a = self.a_0;
        b = self.b_0;
        f = self.df[0];

        # Check if f(a) * f(b) < 0
        if(f(a) * f(b) >= 0):
        #{
            print(f(a)," ",f(b))
            print(f"Khoảng cách ly [{a}, {b}] không hợp lệ");
            return 0;
        #}

        if(self.__Kiem_tra_don_dieu()==0):
        #{
            print("Khoảng đã cho không hợp lệ");
            return 0;
        #}
    #}

    def __Daycung(self):
    #{

        eps = self.eps
        a = self.a_0
        b = self.b_0

        f = self.df[0]
        f1 = self.df[1]
        f2 = self.df[2]

        if ((f1(a)>0 and f2(a)>0) or (f1(a)<0 and f2(a)<0)):
            d=b
            x=a
        else:
            d=a
            x=b
        x_pre=-1000

        while (x-x_pre > eps):
            x_pre=x
            x=x_pre - (d-x_pre)/(f(d)-f(x_pre))*f(x_pre)
        return x

    def Solve(self):
        if(self.__checkInputValidity() == 0):
            print("Vui lòng nhập lại dữ liệu", file=sys.stderr);
            exit(0)
        return self.__Daycung();

#===================================================================================
# Chương trình chính

expr = "x^4+0.5"
L = 0.1
R = 1
eps = 1e-6

uu = daycung_oop(L, R, eps, expr);
print(f"Nghiệm của phương trình {expr} trên khoảng [{L}, {R}] là: {uu.Solve()}");

Topic 1 - Find solution for single-variable functions/PP_Day_cung/day_cung.py


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline
from sklearn.linear_model import LinearRegression
from sklearn import preprocessing
from scipy.optimize import curve_fit

np.seterr(all='raise')
factor = 2.0

def func(x, a, b, c):
    return a * np.sin(b * x) + c

def run_exp(train_path, sine=False, ks=[1, 2, 3, 5, 10, 20], filename='plot.png'):
    model = pd.read_csv(train_path)
    train_x, train_y = model['x'].values.reshape(-1, 1), model['y'].values.reshape(-1, 1)
    plot_x = np.linspace(-factor*np.pi, factor*np.pi, 1000).reshape(-1, 1)
    plt.figure()
    plt.scatter(train_x, train_y)
    scaler = preprocessing.StandardScaler()
    for k in ks:
        '''
        Our objective is to train models and perform predictions on plot_x data
        '''
        if sine == False:
            polyreg_scaled = make_pipeline(PolynomialFeatures(k), scaler, LinearRegression(fit_intercept=True))
            polyreg_scaled.fit(train_x, train_y)
            plot_y = polyreg_scaled.predict(plot_x)
        else:
            popt, _ = curve_fit(func, train_x.ravel(), train_y.ravel())
            plot_y = func(plot_x, *popt)
        '''
        Here plot_y are the predictions of the linear model on the plot_x data
        '''
        plt.ylim(-2, 2)
        plt.plot(plot_x, plot_y, label='k=%d' % k)

    plt.legend()
    plt.savefig(filename)
    plt.clf()

def main(train_path, val_path):
    run_exp(train_path, sine=True, filename='vscikit_train_sine.png')
    run_exp(train_path, sine=False, filename='vscikit_train_poly.png')
    run_exp(val_path, sine=True, filename='vscikit_eval_sine.png')
    run_exp(val_path, sine=False, filename='vscikit_eval_poly.png')

if __name__ == '__main__':
    main(train_path='train.csv', val_path='valid.csv')


import numpy as np

# Recursive
def LCS_rec(str1, str2, l1, l2):
	if l1==0 or l2==0:
		return 0
	elif str1[l1-1] == str2[l2-1]:
		return 1 + LCS_rec(str1, str2, l1-1, l2-1)
	else:
		return max(LCS_rec(str1, str2, l1, l2-1), LCS_rec(str1, str2, l1-1, l2))

# Optimal
def LCS_opt(str1, str2, l1, l2):
	L = np.zeros((l1+1, l2+1), dtype=np.int)
	match = []
	for i in range(l1+1):
		for j in range(l2+1):
			if i==0 or j==0:
				L[i][j] = 0
			elif str1[i-1] == str2[j-1]:
				L[i][j] = 1 + L[i-1][j-1]
			else:
				L[i][j] = max(L[i-1][j], L[i][j-1])
	print(L)

	match = []
	i,j = l1-1,l2-1
	while i!=0 and j!=0:
		if str1[i] != str2[j]:
			if L[i-1][j] > L[i][j-1]:
				i=i-1
			else:
				j=j-1
		else:
			match.append(str1[i])
			i=i-1
			j=j-1
	match.append(str1[0])
	return L[l1][l2], match[::-1]

str1 = "ABSJABHWQ"
str2 = "ABHWQAAA"
l1 = len(str1)
l2 = len(str2)

length1 = LCS_rec(str1, str2, l1, l2)
length2, match = LCS_opt(str1, str2, l1, l2)

print(length2)
print(''.join(match))