import numpy as np
import scipy
import scipy.stats as stats
import scipy.linalg
from scipy.misc import factorial
from scipy.special import erf
from sigvisa.models import Distribution

class Gamma(Distribution):
    def __init__(self, alpha, beta):
        self.alpha = alpha
        self.beta = beta

    def log_p(self, x, **kwargs):
        alpha = self.alpha
        beta = self.beta

        if x < 0.0: return np.log(1e-300)
        # the special case of an exponential distribution is defined even when x==0
        if alpha == 1: return np.log(beta) - beta*x
        if x == 0.0: return np.log(1e-300)
        lp = alpha*np.log(beta) - scipy.special.gammaln(alpha) + (alpha-1)*np.log(x) - beta*x
        if np.isnan(lp):
            lp = np.float("-inf")
        return lp

    def deriv_log_p(self, x):
        alpha = self.alpha
        beta = self.beta
        if x == 0.0: return 1000.0
        return (alpha-1)/x - beta

    def predict(self, **kwargs):
        return self.alpha/self.beta

    def sample(self, **kwargs):
        return stats.gamma.rvs(self.alpha, scale=1.0/self.beta, loc=0., n=1)

class InvGamma(Distribution):
    # mean: beta/(alpha-1)
    # mode: beta/(alpha+1)
    # variance: beta^2 / ( (alpha-1)^2 (alpha-2) )

    def __init__(self, alpha, beta):
        self.alpha = alpha
        self.beta = beta

    def predict(self):
        return self.beta / (self.alpha+1) # return the mode, since the mean isn't always defined

    def log_p(self, x, **kwargs):
        alpha = self.alpha
        beta = self.beta
        if alpha <= 0 or beta <= 0:
            return np.float("-inf")
        if x == 0.0: return np.log(1e-300)
        lp = alpha*np.log(beta) - scipy.special.gammaln(alpha) - (alpha+1)*np.log(x) - beta/x
        if np.isnan(lp):
            lp = np.float("-inf")

        return lp

    def deriv_log_p(self, x):
        alpha = self.alpha
        beta = self.beta
        if x == 0.0: return 1000.0
        return beta/(x**2) - (alpha+1)/x

    def sample(self):
        s = scipy.stats.invgamma.rvs(a=self.alpha, size=1)
        return s * self.beta

    def variance(self):
        assert (self.alpha > 2)
        return self.beta**2 / ((self.alpha-1)**2 * (self.alpha - 2))

class LogNormal(Distribution):
    def __init__(self, mu, sigma):
        self.mu = mu
        self.sigma = sigma

    def log_p(self, x, **kwargs):
        mu = self.mu
        sigma = self.sigma
        if x == 0.0: return np.log(1e-300)
        lp = -1 * np.log(x) - .5 * np.log(2*np.pi) - np.log(sigma) - .5 * (np.log(x) - mu)**2 / sigma**2
        if np.isnan(lp):
            lp = np.float("-inf")
        return lp

    def deriv_log_p(self, x):
        mu = self.mu
        sigma = self.sigma
        if x == 0.0: return 1000.0
        return (-1 -(np.log(x) - mu)/(sigma**2)) / x

    def sample(self):
        return np.exp(np.random.randn() * self.sigma + self.mu)

    def variance(self):
        return (np.exp(self.sigma**2) - 1 ) * ( np.exp(2*self.mu + self.sigma**2))

    def predict(self):
        return np.exp(self.mu)

class Uniform(Distribution):
    def __init__(self, lbound, rbound):
        assert ( lbound < rbound)
        self.lbound = lbound
        self.rbound = rbound

    def log_p(self, x,  **kwargs):
        if self.lbound <= x <= self.rbound:
            return -np.log(self.rbound - self.lbound)
        else:
            return np.float("-inf")

    def predict(self, **kwargs):
        return self.lbound + (self.rbound - self.lbound) / 2

    def sample(self, **kwargs):
        return self.lbound + np.random.rand() * (self.rbound - self.lbound)

class Gaussian(Distribution):
    def __init__(self, mean, std):
        self.mean = mean
        self.std = std
        self.var = std**2

    def log_p(self, x,  **kwargs):
        mu = self.mean
        sigma2 = self.var
        lp = -.5 * np.log(2*np.pi*sigma2) - .5 * (x - mu)**2 / sigma2
        if sigma2==0:
            lp = np.float("-inf")
        return lp

    def deriv_log_p(self, x, **kwargs):
        return -(x - self.mean)/self.var

    def entropy(self):
        return .5 * np.log(2*np.pi*np.e*self.var)

    def predict(self, *args, **kwargs):
        return self.mean

    def variance(self, *args, **kwargs):
        return self.var

    def sample(self, *args, **kwargs):
        return self.mean + np.random.randn() * self.std

    def product(self, other):
        # given two Gaussian distributions, return
        # the Gaussian resulting from normalizing the product
        # N(x; u1, v1) N(x; u2, v2).
        # This corresponds to computing a Bayesian posterior
        # given a Gaussian prior and Gaussian likelihood.

        prec1 = 1.0/self.var
        prec2 = 1.0/other.var
        prec = prec1+prec2

        mean = (prec1*self.mean + prec2*other.mean)/prec
        return Gaussian(mean, 1.0/np.sqrt(prec))

    def __str__(self):
        return "Gaussian(mean=%f, std=%f)" % (self.mean, self.std)

class TruncatedGaussian(Distribution):
    def __init__(self, mean, std, a=-np.inf, b=np.inf, eps=1e-2):
        self.mean = mean
        self.std = std
        self.a=a
        self.b=b
        self.eps = eps

        self.Z = .5 * (erf((self.b-mean)/(std*np.sqrt(2))) -  erf((self.a-mean)/(std*np.sqrt(2))))
        self.logZ = np.log(self.Z)

    def log_p(self, x,  **kwargs):
        eps = self.eps
        if x <= self.a-eps or x >= self.b +eps:
            return -np.inf

        mu = self.mean
        sigma = self.std
        lp = -.5 * np.log(2*np.pi*sigma*sigma) - .5 * (x - mu)**2 / sigma**2 - self.logZ
        if np.isnan(lp):
            lp = np.float("-inf")

        if x < self.a:

            d = ( (self.a-x)/eps ) ** (1/1024.0)
            penalty =  d / (1 - d )
            return lp - penalty
        elif x > self.b:
            # at x=b, we have 0
            # at x=b+eps
            d = ( (x-self.b)/eps ) ** (1/1024.0)
            penalty =  d / (1 - d )
            return lp - penalty

        return lp

    def deriv_log_p(self, x, **kwargs):
        eps=self.eps
        if x <= self.a-eps or x >= self.b +eps:
            return 0.0

        d_lp = -(x - self.mean)/(self.std**2)

        if x < self.a:
            d = ( (self.a-x)/eps ) ** (1/1024.0)
            dd_dx = (1/1024.0) * ( (self.a-x)/eps )**(1/1024.0 - 1.0) * -1.0/eps
            penalty =  d / (1 - d )
            dpenalty_dd = 1.0/(1-d)**2
            dpenalty_dx = dpenalty_dd * dd_dx
            return d_lp - dpenalty_dx
        elif x > self.b:
            # at x=b, we have 0
            # at x=b+eps
            d = ( (x-self.b)/eps ) ** (1/1024.0)
            dd_dx = (1/1024.0) * ( (x-self.b)/eps )**(1/1024.0 - 1.0) * 1.0/eps
            penalty =  d / (1 - d )
            dpenalty_dd = 1.0/(1-d)**2
            dpenalty_dx = dpenalty_dd * dd_dx
            return d_lp - dpenalty_dx

        return d_lp

    def predict(self, **kwargs):
        return self.mean

    def sample(self, **kwargs):

        # just do rejection sampling since in practice I only plan to
        # cut off very unlikely values.
        sample = self.a
        while sample <= self.a or sample >= self.b:
            sample = self.mean + np.random.randn() * self.std
        return sample

    def variance(self, **kwargs):
        return self.std**2

class MultiGaussian(Distribution):
    def __init__(self, mean, cov, pre_inv=False):
        self.mean = mean
        self.n_d = len(mean)
        self.cov = cov
        self.L = scipy.linalg.cholesky(cov, True)

        if pre_inv:
            self.invL = np.linalg.inv(self.L)
        else:
            self.invL = None

    def predict(self, **kwargs):
        return self.mean.copy()

    def log_p(self, x, **kwargs):
        assert(len(x) == self.n_d)
        r = x-self.mean

        if self.invL is not None:
            l = np.dot(self.invL, r)
            rr = np.dot(l.T, l)
        else:
            tmp = scipy.linalg.cho_solve((self.L, True), r)
            rr = np.dot(r.T, tmp)

        logdet2 = np.log(np.diag(self.L)).sum()
        ll =  -.5 * (rr + len(self.mean) * np.log(2*np.pi)) - logdet2
        return ll

    def sample(self):
        n = len(self.mean)
        z = np.random.randn(n)
        return np.dot(self.L, z) + self.mean

class Laplacian(Distribution):
    def __init__(self, center, scale):
        self.center = center
        self.scale = scale

    def log_p(self, x,  **kwargs):
        center = self.center
        scale = self.scale
        lp = -np.log(2*scale) - np.abs(x-center)/scale
        if np.isnan(lp):
            lp = np.float("-inf")
        return lp

    def predict(self, **kwargs):
        return self.center

    def sample(self, **kwargs):
        u = np.random.rand() - .5
        return self.center - self.scale * np.sign(u) * np.log(1-2*np.abs(u))

    def variance(self, **kwargs):
        return 2*self.scale**2

class Exponential(Distribution):
    def __init__(self, rate, min_value=0.0):
        self.rate = float(rate)
        self.min_value = min_value

    def log_p(self, x,  **kwargs):
        rate = self.rate
        x = x - self.min_value
        if x < 0:
            lp = np.float("-inf")
        else:
            lp = np.log(rate) - rate * x

        return lp

    def predict(self, **kwargs):
        return 1.0/self.rate + self.min_value

    def sample(self, **kwargs):
        u = np.random.rand()
        return -np.log(u) / self.rate + self.min_value

class Poisson(Distribution):
    def __init__(self, mu):
        self.mu = mu

    def log_p(self, x, **kwargs):
        return -self.mu  + x * np.log(self.mu) - scipy.special.gammaln(x+1)

    def predict(self, **kwargs):
        return self.mu

    def sample(self, **kwargs):
        return stats.poisson.rvs(self.mu)

class Beta(Distribution):
    def __init__(self, alpha, beta):
        assert(alpha >= 0 and beta >= 0)
        self.alpha = float(alpha)
        self.beta = float(beta)

    def log_p(self, x, **kwargs):
        if x <= 0 or x >= 1:
            return -np.inf
        alpha , beta = self.alpha, self.beta

        lp = (alpha-1)*np.log(x) + (beta-1)*np.log(1-x) - scipy.special.betaln(alpha, beta)
        return lp

    def deriv_log_p(self, x, *args, **kwargs):
        return (self.alpha-1.) / x + (self.beta-1.) / (x-1.)

    def entropy(self):
        alpha, beta = self.alpha, self.beta

        B = scipy.special.betaln(alpha, beta)
        dga = scipy.special.digamma(alpha)
        dgb = scipy.special.digamma(beta)
        dgab = scipy.special.digamma(alpha+beta)

        entropy = B - (alpha-1)*dga - (beta-1)*dgb - (alpha+beta-2)*dgab
        return entropy

    def predict(self, *args, **kwargs):
        return self.alpha/(self.alpha+self.beta)

    def sample(self, *args, **kwargs):
        return scipy.stats.beta(self.alpha, self.beta, loc=0, scale=1).rvs(1)[0]

    def variance(self, **kwargs):
        alpha, beta = self.alpha, self.beta
        return (alpha*beta)/((alpha+beta)**2 * (alpha+beta+1))

class Bernoulli(Distribution):

    def __init__(self, p):
        self.p = p

    def log_p(self, x, **kwargs):
        if x:
            return np.log(self.p)
        else:
            return np.log(1-self.p)

    def predict(self, **kwargs):
        return self.p >= .5

    def sample(self, **kwargs):
        u = np.random.rand()
        return u < self.p

class Negate(Distribution):
    def __init__(self, dist):
        self.dist = dist

    def dim(self, *args, **kwargs):
        return self.dist.dim( *args, **kwargs)

    def predict(self, *args, **kwargs):
        return -1 * self.dist.predict(*args, **kwargs)

    def sample(self, *args, **kwargs):
        return -1 * self.dist.sample(*args, **kwargs)

    def log_p(self, x, *args, **kwargs):
        return self.dist.log_p(-x, *args, **kwargs)

    def deriv_log_p(self, x, *args, **kwargs):
        return self.dist.deriv_log_p(-x, *args, **kwargs)

class PiecewiseLinear(Distribution):
    def __init__(self, xs, lps, mix_dist=None, mix_weight=0.0):
        self.mix_dist = mix_dist
        self.mix_weight = mix_weight
        self.ps = np.exp(lps - np.max(lps))
        self.xs = xs
        self._normalize()

    def _normalize(self):
        pts = len(self.xs)
        areas = np.empty((pts-1,))
        for i in range(pts-1):
            gap = self.xs[i+1]-self.xs[i]
            areas[i] = gap * (self.ps[i+1]+self.ps[i])/2.0
        Z = np.sum(areas)

        if Z == 0 or not np.isfinite(Z):
            import pdb; pdb.set_trace()

        self.areas = areas / Z
        self.ps /= Z

    def log_p(self, x, *args, **kwargs):
        idx = np.searchsorted(self.xs, x)
        if idx == 0:
            p = 0
        elif idx==len(self.xs):
            p = 0
        else:
            p1 = self.ps[idx-1]
            p2 = self.ps[idx]
            z = x-self.xs[idx-1]
            gap = self.xs[idx]-self.xs[idx-1]

            p = (1.0 - z/gap) * p1 + (z/gap)*p2

        if self.mix_weight > 0:
            mix_p = np.exp(self.mix_dist.log_p(x))
            p = p*(1.0-self.mix_weight) + self.mix_weight * mix_p

        lp = np.log(p)
        #assert(np.isfinite(lp))
        return lp

    def sample(self, *args, **kwargs):
        if self.mix_weight > 0:
            u = np.random.rand()
            if u < self.mix_weight:
                return self.mix_dist.sample(*args, **kwargs)

        pts = len(self.xs)
        segment = np.random.choice(np.arange((pts-1),), p=self.areas)
        area = self.areas[segment]
        gap = self.xs[segment+1] - self.xs[segment]
        p1 = self.ps[segment]
        p2 = self.ps[segment+1]

        u = np.random.rand() * area
        if np.abs(p1-p2) < 1e-10:
            z = (u/area) * gap
        else:
            z = (np.sqrt(p1**2 * gap**2 + 2*(p2-p1)*u*gap) - p1*gap) / (p2-p1)

        if np.isnan(z):
            import pdb; pdb.set_trace()

        if z > gap:
            import pdb; pdb.set_trace()

        x =  self.xs[segment] + z
        return x


import os
import numpy as np

# import jax.numpy as jnp
from sklearn.decomposition import TruncatedSVD

def Temporal_basis_POD(K, SAVE_T_POD=False, FOLDER_OUT='./',n_Modes=10):
    """
    This method computes the POD basis. For some theoretical insights, you can find
    the theoretical background of the proper orthogonal decomposition in a nutshell here:

    https://youtu.be/8fhupzhAR_M
    --------------------------------------------------------------------------------------------------------------------
    Parameters:
    ----------

    :param FOLDER_OUT: str
            Folder in which the results will be saved (if SAVE_T_POD=True)
    :param K: np.array
            Temporal correlation matrix
    :param SAVE_T_POD: bool
            A flag deciding whether the results are saved on disk or not. If the MEMORY_SAVING feature is active, it is
            switched True by default.
    :param n_Modes: int
           number of modes that will be computed
    --------------------------------------------------------------------------------------------------------------------
    Returns:
    --------

    :return: Psi_P: np.array
            POD Psis
    :return: Sigma_P: np.array
            POD Sigmas
    """
    # Solver 1: Use the standard SVD
    # Psi_P, Lambda_P, _ = np.linalg.svd(K)
    # Sigma_P = np.sqrt(Lambda_P)

    # Solver 2: Use randomized SVD ############## WARNING #################
    svd = TruncatedSVD(n_Modes)
    svd.fit_transform(K)
    Psi_P = svd.components_.T
    Lambda_P=svd.singular_values_
    Sigma_P=np.sqrt(Lambda_P)

    if SAVE_T_POD:
        os.makedirs(FOLDER_OUT + "/POD/", exist_ok=True)
        print("Saving POD temporal basis")
        np.savez(FOLDER_OUT + '/POD/temporal_basis', Psis=Psi_P, Lambdas=Lambda_P, Sigmas=Sigma_P)

    return Psi_P, Sigma_P


import numpy as np
import matplotlib.pyplot as plt

def f(x):
    return 2*x**2

# np.arange(start, stop, step) to give us smoother line
x = np.arange(0, 5, 1e-3)
y = f(x)

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

colors = ['k', 'g', 'r', 'b', 'c']

def approximate_tangent_line(x, approximate_derivative):
    return (approximate_derivative*x) + b

for i in range(5):
    p2_delta = 1e-4
    x1 = i
    x2 = x1 + p2_delta
    y1 = f(x1)
    y2 = f(x2)

    print((x1,y1), (x2,y2))
    approximate_derivative = (y2-y1)/(x2-x1)
    b = y2 -  approximate_derivative*x2

    to_plot = [x1-0.5, x1, x1+0.5]

    plt.scatter(x1, y1, c=colors[i])
    plt.plot([point for point in to_plot],
        [approximate_tangent_line(point, approximate_derivative)
        for point in to_plot], c=colors[i])

    print('approximate derivative for f(x)',
        f'where x = {x1} is {approximate_derivative}')

plt.savefig('graph2.png')


import decimal

from mpmath import inf, mp, nsum

def pi_mpmath(n):
    """Calculate PI to the Nth digit.
    I figured using mpmath would work well, but this is not very
    efficient. With n = 1000, it took over a minute on my laptop.
    """
    mp.dps = n
    return str(nsum(lambda k: (4/(8*k+1) - 2/(8*k+4) - 1/(8*k+5) - 1/(8*k+6)) / 16**k, (0, inf)))

def pi_decimal(n):
    """Calculate PI to the Nth digit.
    This one uses the native python Decimal type that allows arbitrary
    precision. With n = 1000, it took less than a second on my laptop.
    """
    D = decimal.Decimal
    decimal.getcontext().prec = n+10
    pi = D(0)
    k = 0
    tail_prev = ''
    while True:
        pi += (D(4)/D(8*k+1) - D(2)/D(8*k+4) - D(1)/D(8*k+5) - D(1)/D(8*k+6)) / D(16)**D(k)
        tail = str(pi)[n+1:-1]
        if tail == tail_prev:
            break
        tail_prev = tail
        k += 1
    decimal.getcontext().prec = n
    return str(pi * D(1))

number_of_places = 1
# Reading the precision in from the decimals file.
with open("/precision/decimals", "r") as decimals_f:
    number_of_places = int(decimals_f.read())

# Dumping the value of pi in the pi file.
with open("/computed/pi", "w") as computed_f:
    computed_f.write(pi_decimal(number_of_places))


# requires scipy, dtw, fastdtw, numpy, matplotlib
# all available by pip install
from dtw import dtw
import numpy as np
import matplotlib.pyplot as plt
from numpy.linalg import norm
import math
from scipy.spatial.distance import euclidean
from fastdtw import fastdtw
from scipy.stats.stats import pearsonr

# load the time serie we want to compare
x = np.array([])
# load the time series of known points
y = [np.array([])]

# test time series
x = np.array([1, 1, 1, 1, 2, 4, 2, 1, 2, 1])
y = [np.array([10, 11, 11, 11, 2, 4, 3, 7, 8, 9]),  np.array([1, 1, 1, 2, 2, 2, 2, 3, 2, 1])]

x1 = np.array([1, 1, 1, 3, 6, 13, 25, 22, 7, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
y1 = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 5, 12, 24, 23, 8, 3, 1, 1, 1, 1, 1, 1])

"""

Root Mean Squared Error calculation

"""
def rmse(predictions, targets):
    return np.sqrt(((predictions - targets) ** 2).mean())

print ("RMSE: " + str(rmse(x, y[0])))
print ("RMSE: " + str(rmse(x1,y1)))

"""

Euclidian distance between 2 time series

"""
def euclid_dist(t1,t2):
    return np.sqrt(sum((t1-t2)**2))
print("Euclidian distance: " + str(euclid_dist(x,y[0])))
print ("Euclidian distance: " + str(euclid_dist(x1,y1)))

"""

Log returns, returns, moving average
Pearson's correlation coefficient

"""

x_logreturn = np.log(1 + (x / np.roll(x, -1)))
y_logreturn = np.log(1 + (y[0] / np.roll(y[0], -1)))
x1_logreturn = np.log(1 + (x1 / np.roll(x1, -1)))
y1_logreturn = np.log(1 + (y1 / np.roll(y1, -1)))

print ("Pearson's correlation on log returns: " + str(pearsonr(x_logreturn,y_logreturn)))
print ("Pearson's correlation on log returns: " + str(pearsonr(x1_logreturn,y1_logreturn)))

# DTW implementation 1
def dtw1 (x, y):
    x_reshaped = x.reshape(-1, 1)
    y_reshaped = y.reshape(-1, 1)
    dist, cost, acc, path = dtw(x_reshaped, y_reshaped, dist=lambda x_reshaped, y_reshaped: norm(x_reshaped - y_reshaped, ord=1))
    return dist

# DTW Implementation 2 Ref : https://jeremykun.com/2012/07/25/dynamic-time-warping/
def dtw2(seqA, seqB, d = lambda x,y: abs(x-y)):
    # create the cost matrix
    numRows, numCols = len(seqA), len(seqB)
    cost = [[0 for _ in range(numCols)] for _ in range(numRows)]
    # initialize the first row and column
    cost[0][0] = d(seqA[0], seqB[0])
    for i in range(1, numRows):
        cost[i][0] = cost[i-1][0] + d(seqA[i], seqB[0])
    for j in range(1, numCols):
        cost[0][j] = cost[0][j-1] + d(seqA[0], seqB[j])
    # fill in the rest of the matrix
    for i in range(1, numRows):
        for j in range(1, numCols):
            choices = cost[i-1][j], cost[i][j-1], cost[i-1][j-1]
            cost[i][j] = min(choices) + d(seqA[i], seqB[j])
    return cost[-1][-1]

# DTW Implementation 3 Ref: http://alexminnaar.com/time-series-classification-and-clustering-with-python.html
def dtw3(s1, s2):
    DTW={}
    for i in range(len(s1)):
        DTW[(i, -1)] = float('inf')
    for i in range(len(s2)):
        DTW[(-1, i)] = float('inf')
    DTW[(-1, -1)] = 0
    for i in range(len(s1)):
        for j in range(len(s2)):
            dist= (s1[i]-s2[j])**2
            DTW[(i, j)] = dist + min(DTW[(i-1, j)],DTW[(i, j-1)], DTW[(i-1, j-1)])
    return np.sqrt(DTW[len(s1)-1, len(s2)-1])

"""
#¤ DTW Implementation 4 Ref: https://effectiveml.com/dynamic-time-warping-for-sequence-classification.html
Implementation to fix before using
def dtw4(p,q):
    p = p.reshape(-1, 1)
    q = q.reshape(-1, 1)
    ep = np.sqrt(np.sum(np.square(p),axis=1));
    eq = np.sqrt(np.sum(np.square(q),axis=1));
    D = 1 - np.dot(p,q.T)/np.outer(ep,eq) # work out D all at once
    S = np.zeros_like(D)
    Lp = np.shape(p)[0]
    Lq = np.shape(q)[0]
    N = np.shape(p)[1]
    for i in range(Lp):
        for j in range(Lq):
            if i==0 and j==0:  S[i,j] = D[i,j]
            elif i==0: S[i,j] = S[i,j-1] + D[i,j]
            elif j==0: S[i,j] = S[i-1,j] + D[i,j]
            else: S[i,j] = np.min([S[i-1,j],S[i,j-1],S[i-1,j-1]]) + D[i,j]
    return np.sqrt(S[-1,-1]) # return the bottom right hand corner distance
"""

# DTW Implementation 5 Ref : https://pypi.org/project/fastdtw/0.3.0/
def dtw5 (x, y):
    x_reshaped = x.reshape(-1, 1)
    y_reshaped = y.reshape(-1, 1)
    distance, path = fastdtw(x_reshaped, y_reshaped, dist=euclidean)
    return distance

# iterating over the known points and find the most similar
rmse_result = []
euclid_result = []
dtw1_result = []
dtw2_result = []
dtw3_result = []
dtw5_result = []
for i in range(len(y)):
    rmse_result.append(rmse(x, y[i]))
    euclid_result.append(euclid_dist(x,y[i]))
    dtw1_result.append(dtw1(x, y[i]))
    dtw2_result.append(dtw2(x, y[i]))
    dtw3_result.append(dtw3(x, y[i]))
    dtw5_result.append(dtw5(x, y[i]))

print("")
print(rmse_result)
print(euclid_result)
print(dtw1_result)
print(dtw2_result)
print(dtw3_result)
print(dtw5_result)
print("")

print ("Most similar rmse is point               : " + str(np.argmin(rmse_result)) + " " +str(rmse_result[np.argmin(rmse_result)]))
print ("Most similar euclidian distance is point : " + str(np.argmin(euclid_result)) + " " +str(euclid_result[np.argmin(euclid_result)]))
print ("Most similar dtw1 is point               : " + str(np.argmin(dtw1_result)) + " " +str(dtw1_result[np.argmin(dtw1_result)]))
print ("Most similar dtw2 is point               : " + str(np.argmin(dtw2_result)) + " " +str(dtw2_result[np.argmin(dtw2_result)]))
print ("Most similar dtw3 is point               : " + str(np.argmin(dtw3_result)) + " " +str(dtw3_result[np.argmin(dtw3_result)]))
print ("Most similar dtw5 is point               : " + str(np.argmin(dtw5_result)) + " " +str(dtw5_result[np.argmin(dtw5_result)]))


import numpy as np
import cv2

def gradient_map(gray):
    # Image derivatives
    SCALE = 1
    DELTA = 0
    DDEPTH = cv2.CV_16S  ## to avoid overflow

    grad_x = cv2.Sobel(gray, DDEPTH, 1, 0, ksize=3, scale=SCALE, delta=DELTA)
    grad_y = cv2.Sobel(gray, DDEPTH, 0, 1, ksize=3, scale=SCALE, delta=DELTA)

    grad_x = np.float32(grad_x)
    grad_x = grad_x * (1 / 512)
    grad_y = np.float32(grad_y)
    grad_y = grad_y * (1 / 512)

    # Gradient and smoothing
    grad_x2 = cv2.multiply(grad_x, grad_x)
    grad_y2 = cv2.multiply(grad_y, grad_y)

    # Magnitude of the gradient
    Mag = np.sqrt(grad_x2 + grad_y2)

    # Orientation of the gradient
    theta = np.arctan(cv2.divide(grad_y, grad_x + np.finfo(float).eps))

    return theta, Mag

def orientation_map(gray, n):
    # Image derivatives
    SCALE = 1
    DELTA = 0
    DDEPTH = cv2.CV_16S  ## to avoid overflow

    grad_x = cv2.Sobel(gray, DDEPTH, 1, 0, ksize=3, scale=SCALE, delta=DELTA)
    grad_y = cv2.Sobel(gray, DDEPTH, 0, 1, ksize=3, scale=SCALE, delta=DELTA)

    grad_x = np.float32(grad_x)
    grad_x = grad_x * (1 / 512)
    grad_y = np.float32(grad_y)
    grad_y = grad_y * (1 / 512)

    # Gradient and smoothing
    grad_x2 = cv2.multiply(grad_x, grad_x)
    grad_y2 = cv2.multiply(grad_y, grad_y)
    grad_xy = cv2.multiply(grad_x, grad_y)
    g_x2 = cv2.blur(grad_x2, (n, n))
    g_y2 = cv2.blur(grad_y2, (n, n))
    g_xy = cv2.blur(grad_xy, (n, n))

    # Magnitude of the gradient
    Mag = np.sqrt(grad_x2 + grad_y2)
    M = cv2.blur(Mag, (n, n))

    # Gradient local aggregation
    vx = 2 * g_xy
    vy = g_x2 - g_y2
    fi = cv2.divide(vx, vy + np.finfo(float).eps)

    case1 = vy >= 0
    case2 = np.logical_and(vy < 0, vx >= 0)
    values1 = 0.5 * np.arctan(fi)
    values2 = 0.5 * (np.arctan(fi) + np.pi)
    values3 = 0.5 * (np.arctan(fi) - np.pi)
    theta = np.copy(values3)
    theta[case1] = values1[case1]
    theta[case2] = values2[case2]

    return theta, M


import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import linprog

c = [-150, -75]
A = [[10, 4], [3, 2], [2, 5]]
b = [1000, 360, 600]

res = linprog(c, A, b)
print(res)

plt.style.use('seaborn-whitegrid')

fig = plt.figure()
ax = plt.axes()

plt.xlabel("x")
plt.xlim(0, 200)
plt.ylabel("y")
#plt.ylim(-800, 800);

x = np.linspace(0, 200, 1000)

l1 = ax.plot(x, (1000 - 10*x)/4, color='blue', label='10x+4y=1000')
l2 = ax.plot(x, (360 - 3*x)/2, color='red', label='3x+2y=360')
l3 = ax.plot(x, (600 - 2*x)/5, color='green', label='2x+5y=600')
l4 = ax.plot(res.x[0], res.x[1], marker="x")
ax.text(2, 6, "150x+75y->max\nx>=0\ny>=0\n10x+4y<=1000\n3x+2y<=360\n2x+5y<=600\n")

ax.legend()
plt.show()


from numpy import *
# PCA analysis
def pca(X):
    """
    :param X: n-by-d data matrix X. Rows of X correspond to observations and columns correspond to dimensions.
    :return: Y - transformed new coordinate in new space, eig_vec - eigenvectors, eig_value
    """
    X = array(X);
    if (X.ndim != 2) or (X.shape[0]==1):
        raise ValueError('dimension of X should be 2 and X shoud have >1 samples')
    #subtract mean
    avg = mean(X,axis=0)
    avg = tile(avg,(X.shape[0],1))
    X -= avg;#WARNING: INPUT X WILL BE CHANGED
    #covariance matrix
    C = dot(X.transpose(),X)/(X.shape[0]-1)
    eig_values,eig_vecs = linalg.eig(C)
    idx = eig_values.argsort()
    idx = idx[ : :-1]
    eig_values = eig_values[idx]
    eig_vecs = eig_vecs[:,idx]
    #new coordinate in new space
    Y = dot(X,eig_vecs)

    return (Y, eig_vecs, eig_values


import numpy as np
import scipy.stats as st
import cvxpy as cp
import pandas as pd
import matplotlib.pyplot as plt
R = pd.read_csv('asset_return_data.csv', index_col=0)
R = R.asfreq(pd.infer_freq(R.index))
T, N = R.shape
np.random.seed(8888)
BenchmarkIndex = R.dot(np.tile(1.0/N, N)) \
                 + st.norm.rvs(loc=0.0, scale=3.0, size=T)
MovingWindow = 96
BackTesting = T - MovingWindow
V_Tracking = np.zeros(BackTesting)
Weight = cp.Variable(N)
Error = cp.Variable(MovingWindow)
TrackingError = cp.sum_squares(Error)
Asset_srT = R / np.sqrt(T)
Index_srT = BenchmarkIndex / np.sqrt(T)
for Month in range(0, BackTesting):
    Asset = Asset_srT.values[Month:(Month + MovingWindow), :]
    Index = Index_srT.values[Month:(Month + MovingWindow)]
    Min_TrackingError = cp.Problem(cp.Minimize(TrackingError),
                                   [Index - Asset @ Weight == Error,
                                    cp.sum(Weight) == 1.0,
                                    Weight >= 0.0])
    Min_TrackingError.solve(solver=cp.ECOS)
    V_Tracking[Month] = R.values[Month + MovingWindow, :].dot(Weight.value)
fig1 = plt.figure(num=1, facecolor='w')
plt.plot(list(range(1, BackTesting + 1)), BenchmarkIndex[MovingWindow:], 'b-')
plt.plot(list(range(1, BackTesting + 1)), V_Tracking, 'r--')
plt.legend(['benchmark index', 'index fund'], loc='best', frameon=False)
plt.xlabel('year')
plt.ylabel('return (%)')
plt.xticks(list(range(12, BackTesting + 1, 12)),
           pd.date_range(R.index[MovingWindow], periods=BackTesting//12,
                         freq='AS').year)
plt.show()


from typing import Union

from scipy.stats import geom, rv_discrete

from probability.custom_types.external_custom_types import FloatArray1d
from probability.distributions.mixins.attributes import PFloatDMixin
from probability.distributions.mixins.calculable_mixin import CalculableMixin
from probability.distributions.mixins.rv_discrete_1d_mixin import \
    RVDiscrete1dMixin
from probability.utils import num_format, is_scalar

class Geometric(
    RVDiscrete1dMixin,
    PFloatDMixin,
    CalculableMixin,
    object
):
    """
    The (shifted) geometric distribution gives the probability that the first
    occurrence of success requires k independent trials, each with success
    probability p.

    https://en.wikipedia.org/wiki/Geometric_distribution
    """
    def __init__(self, p: float):
        """
        Create a new shifted geometric distribution.

        :param p: Probability of success in any individual trial.
        """
        self._p: float = p
        self._reset_distribution()

    def _reset_distribution(self):

        self._distribution: rv_discrete = geom(self._p)

    @property
    def lower_bound(self) -> int:
        return 1

    @property
    def upper_bound(self) -> int:
        return int(self.ppf().at(0.99))

    def mode(self) -> int:

        return 1

    @staticmethod
    def fit(data: FloatArray1d, **kwargs) -> 'Geometric':
        """
        Fit a Geometric distribution to the data of one or more experiments
        using the maximum likelihood estimate for p.

        :param data: Iterable of data to fit to. Each result represents the
                     result of a single trial, and should be 0 or 1.
        """
        n = sum(data)  # number of experiments
        k = len(data)  # number of trials
        p = n / k
        return Geometric(p=p)

    @staticmethod
    def fits(data: FloatArray1d, **kwargs) -> 'Geometric':
        """
        Fit a Geometric distribution to the distribution of results of a
        series of experiments using the method of moments.

        https://en.wikipedia.org/wiki/Geometric_distribution
        #Parameter_estimation

        :param data: Iterable of results to fit to. Each value represents the
                     number of trials in an experiment to get 1 success, and
                     should be at least 1.
        """
        n = len(data)  # number of experiments
        k = sum(data)  # number of trials
        p = n / k
        return Geometric(p=p)

    def __str__(self):

        return f'Geometric(p={num_format(self._p, 3)})'

    def __repr__(self):

        return f'Geometric(p={self._p})'

    def __eq__(self, other: Union['Geometric', int, float]):

        if is_scalar(other):
            return self.pmf().at(other)
        else:
            return abs(self._p - other._p) < 1e-10

    def __ne__(
            self, other: Union['Geometric', int, float]
    ) -> Union[bool, float]:

        if is_scalar(other):
            return 1 - self.pmf().at(other)
        else:
            return not self.__eq__(other)


import numpy as np
import matplotlib.pylab as plt

def forward_difference(f, x):
	h = 1e-4 # 0.001
	return (f(x+h) - f(x)) / h

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

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

# it returns a tangent line of function f where x-point is x
def tangent_line(f, x):
	d = central_difference(f, x)	# df(x)/dx
	y = f(x) - d*x	# tangent point of y-axis
	return lambda t: d*t + y	# return a tangent line

def main():
	x = np.arange(0.0, 20.0, 0.1)
	y = function_1(x)
	plt.xlabel("x")
	plt.ylabel("f(x)")

	tf = tangent_line(function_1, 5)
	y2 = tf(x)

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

if __name__ == '__main__':
	main()


#----------------------------------------------
#   Name: Roberto Sanchez
#   Date: December 07, 2017
#   Assignement 6 - Markov Chains
#------------Importing packages-----------------
import numpy as np
import matplotlib.pyplot as plt
import random
# MarkovChain Definition
def MarkovChain(N,n):
    S = [0 for i in range(0,n)]
    X = np.chararray((n,N))
    X[:] = 0
    M = np.zeros((n,3))
    prob = {'p11' : 1/3, 'p12' : 1/3, 'p13' : 1/3, 'p21' : 1/2, 'p23' : 1/2, 'p22' : 0, 'p31' : 1/4, 'p32' : 1/4, 'p33' : 1/2}
    dist = {'d01' : 1/4, 'd03' : 1/4, 'd02' : 1/2}
    for j in range(0,N):
        r0 = np.random.random();
        if r0 <= dist['d01']:
            s0 = 'R'
        elif r0>dist['d01'] and r0<=dist['d01']+dist['d02']:
            s0 = 'N'
        elif r0>dist['d01']+dist['d02']:
            s0 = 'S'
        S[0] = s0
#
        for k in range(0,n-1):
            s = S[k]
            r = np.random.random();
            if s == 'R':
                if r<=prob['p11']:
                    S[k+1]='R'
                elif r>prob['p11'] and r<=prob['p11']+prob['p12']:
                    S[k+1]='N'
                elif r>prob['p11']+prob['p12']:
                    S[k+1]='S'
            elif s == 'N':
                if r<=prob['p21']:
                    S[k+1]='R'
                elif r>prob['p21'] and r<=prob['p21']+prob['p22']:
                    S[k+1]='N'
                elif r>prob['p21']+prob['p22']:
                    S[k+1]='S'
            elif s == 'S':
                if r<=prob['p31']:
                    S[k+1]='R'
                elif r>=prob['p31'] and r<=prob['p31']+prob['p32']:
                    S[k+1]='N'
                elif r>prob['p31']+prob['p32']:
                    S[k+1]='S'
        X[:,j]=S
    for j in range(0,n):
        ma=0
        mb=0
        mc=0
        x=X[j,:]
        for i in range(N):
            if str(x[i],'utf-8') == 'R':
                ma += 1
        for p in range(N):
            if str(x[p],'utf-8') == 'N':
                mb += 1
        for q in range(N):
            if str(x[q],'utf-8') == 'S':
                mc += 1
        M[j,:] = [ma/N,mb/N,mc/N]

# ---------------------------------Plot-------------------------------------
    plt.figure(1)
    nv = np.linspace(0,n,num=15)
    plt.plot(nv,M[:,0],color = 'blue',marker='*',markersize=6)
    plt.plot(nv,M[:,1],color = 'green',marker='+',markersize=6)
    plt.plot(nv,M[:,2],color = 'red',marker='^',markersize=6)
    plt.title('Simulation results -- States R,N,S')
    plt.xlabel('Time step (n)')
    plt.ylabel('Prob (State)')
    plt.legend((['State R'],['State N'],['State S']))
    plt.show()
    P = np.matrix([[prob['p11'],prob['p12'],prob['p13']],[prob['p21'],prob['p22'],prob['p23']],[prob['p31'],prob['p32'],prob['p33']]])
    y0 = np.matrix([1/4,1/2,1/4])
    Y = np.zeros((n,3))
    Y[0,:] = y0
    for k in range(0,n-1):
        Y[k+1,:] = np.matmul(Y[k,:],P)
    # Figure 2 Chart
    plt.figure(2)
    plt.plot(nv,Y[:,0],color = 'blue',marker='o',markersize=6)
    plt.plot(nv,Y[:,1],color = 'green',marker='o',markersize=6)
    plt.plot(nv,Y[:,2],color = 'red',marker='o',markersize=6)
    plt.title('Results based on State Transition Matix -- States R,N,S')
    plt.xlabel('Time step(n)')
    plt.ylabel('Prob(State)')
    plt.legend((['State R'],['State N'],['State S']))
    plt.show()
MarkovChain(10000,15)


#########################################################################
#            NUMERICAL DERIVATIVES
#
# This module explore compare the difference between 2-points, 3-points
# and 4-points derivatives, with the difference centered or one-sided.
#
#    Copied from Mike Zingale's, with addition of d2r and d2l.
#    (Marina von Steinkirch, spring 2013)
#
#########################################################################

import math
import numpy
import pylab

""" Functions """

def func(x):
    """ the function to be plotted """
    f = numpy.sin(x)
    return f

def fprime(x):
    """ the analytic derivative of func(x) """
    fp = numpy.cos(x)
    return fp

def d1l(dx, fc, i):
    """ first-order, left-sided derivative at index i """
    D = (fc[i] - fc[i-1])/dx
    return D

def d1r(dx, fc, i):
    """ first-order, right-sided derivative at index i """
    D = (fc[i+1] - fc[i])/dx
    return D

def d2(dx, fc, i):
    """ second-order centered derivative at index i """
    D = fc[i+1] - fc[i-1]
    D = D/(2.0*dx)
    return D

def d2r(dx, fc, i):
    """ second-order right-sided derivative at index i """
    D = -fc[i] + 4.0*fc[i+1] - 3.0*fc[i+2]
    D = D/(2.0*dx)
    return D

def d2l(dx, fc, i):
    """ second-order left-sided derivative at index i """
    D = -fc[i+2] + 4.0*fc[i+1] -3.0*fc[i]
    D = D/(2.0*dx)
    return D

def d4(dx, fc, i):
    """ fourth-order centered derivative at index i """
    D = -fc[i+2] + 8.0*fc[i+1] - 8.0*fc[i-1] + fc[i-2]
    D = D/(12.0*dx)
    return D

def line(x, slope, x0, y0):
    return y0 + slope*(x - x0)

""" Constants """

xl = 0.0
xr = math.pi
coarse_point_chosen = 3     # which for this griding is pi/3

""" Main Program """

# fine and  grid (to show exact function and for differencing)
# linspace(start, stop, number_of_points)
fine = numpy.linspace(xl, xr, 500)      # 500 points, from 0 to pi
coarse = numpy.linspace(xl, xr, 10)     # 10 points, from 0 to pi

# define the analytic function for fine and for coarse griding, and the derivative for coarse
f = func(fine)
c = func(coarse)
analytic = fprime(coarse[coarse_point_chosen])

# equally spaced dx for coarse points
dx = coarse[1] - coarse[0]

# get the derivative approximations
Dl = d1l(dx, c, coarse_point_chosen)
Dr = d1r(dx, c, coarse_point_chosen)
D2 = d2(dx, c, coarse_point_chosen)
D2r = d2r(dx, c, coarse_point_chosen)
D2l = d2l(dx, c, coarse_point_chosen)
D4 = d4(dx, c, coarse_point_chosen)

""" Plotting """
# plot the fine and discrete gridded analytic function
pylab.plot(fine, f, color="0.5", lw=2)
pylab.scatter(coarse, c)

# function and point values
x0 = coarse[coarse_point_chosen]
y0 = func(x0)
xplot = numpy.linspace(coarse[1], coarse[5], 50)

pylab.plot(xplot, line(xplot, Dl, x0, y0), "b--", label="left-sided first-order approx")
pylab.plot(xplot, line(xplot, Dr, x0, y0), "bp", label="right-sided first-order approx")
pylab.plot(xplot, line(xplot, D2, x0, y0), "y", lw=3,  label="centered second-order approx")
pylab.plot(xplot, line(xplot, D2l, x0, y0), "r--", label="left-sized second-order approx")
pylab.plot(xplot, line(xplot, D2r, x0, y0), "rp", label="right-sized second-order approx")
pylab.plot(xplot, line(xplot, D4, x0, y0), "m*", lw=3, label="centered fourth-order approx")
#pylab.plot(xplot, line(xplot, analytic, x0, y0), color="0.5", ls=":", label="analytic")

print "analytic:          ", analytic
print "left-sided O(dx):  ", Dl
print "right-sided O(dx): ", Dr
print "centered O(dx**2): ", D2
print "left-sided O(dx**2): ", D2l
print "right-sided O(dx**2): ", D2r
print "centered O(dx**4): ", D4

leg = pylab.legend(loc=2,labelspacing=0.1)
ltext = leg.get_texts()
pylab.setp(ltext, fontsize='small')
leg.draw_frame(1)

# axes run through 0
# http://matplotlib.org/examples/pylab_examples/spine_placement_demo.html
ax = pylab.gca()
ax.spines['left'].set_position('zero')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_color('none')
ax.spines['left'].set_smart_bounds(True)
ax.spines['bottom'].set_smart_bounds(True)
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')

pylab.xlabel("x (radians)")
pylab.ylabel("Sin(x)")
pylab.grid(True)
pylab.xlim(0,2.0)

pylab.savefig("fprime.png")

print "\nDone!"


import numpy as np
from scipy.stats import t

def outliers_iqr(x, ret='filtered', coef = 1.5):

    """
    Simple detection of potential outliers based on interquartile range (IQR).

    Data that lie within the lower and upper limits are considered
    non-outliers. The lower limit is the number that lies 1.5 IQRs below
    (coefficient may be changed with an argument, see Parameters)
    the first quartile; the upper limit is the number that lies 1.5 IQRs
    above the third quartile.

    Parameters
    ----------
    x : array_like or np.ndarray, 1d
        An array, any object exposing the array interface, containing
        p values.

    ret : str, optional
        Specifies object to be returned. Available options are:
        'filtered' : return a filtered array (default)
        'outliers' : return outliers
        'indices' : return indices of non-outliers
        'outliers_indices' : return indices of outliers

    coef : float, optional
        Coefficient by which IQR is multiplied. Default is 1.5.

    Returns
    -------
    filtered : np.ndarray
        Filtered array (default).

    indices : np.ndarray, optional
        Array with indices of elements lying within the specified limits.

    outliers : np.ndarray, optional
        Array with outliers.

    outliers_indices : np.ndarray, optional
        Array with indices of outlier elements.

    Examples
    --------
    >>> x = np.array([4, 5, 6, 10, 12, 4, 3, 1, 2, 3, 23, 5, 3])
    >>> outliers_iqr(x, ret = 'outliers')
    array([12, 23])

    """

    arr = np.copy(x)

    q1, q3 = np.percentile(arr, [25, 75])
    iqr = q3 - q1
    ll = q1 - iqr * coef
    ul = q3 + iqr * coef

    if ret == 'indices':
        return np.where((arr > ll) & (arr < ul))[0]
    elif ret == 'outliers':
        return arr[(arr < ll) | (arr > ul)]
    elif ret == 'outliers_indices':
        return np.where((arr < ll) | (arr > ul))[0]
    else:
        return x[(x > ll) & (x < ul)]

def outliers_grubbs(x, hypo = False, alpha = 0.05):

    """
    Grubbs' Test for Outliers [1]_. This is the two-sided version of the test.

    The null hypothesis implies that there are no outliers in the data set.

    Parameters
    ----------
    x : array_like or ndarray, 1d
        An array, any object exposing the array interface, containing
        data to test for an outlier in.

    hypo : bool, optional
        Specifies whether to return a bool value of a hypothesis test result.
        Returns True when we can reject the null hypothesis. Otherwise, False.
        Available options are:
        1) True - return a hypothesis test result
        2) False - return a filtered array without an outlier (default)

    alpha : float, optional
        Significance level for a hypothesis test. Default is 0.05.

    Returns
    -------
    arr : np.ndarray
        Filtered array if alternative hypo is True, otherwise an unfiltered
        array.

    hypo : bool, optional
        Null hypothesis test result.

    Notes
    -----
    .. [1] http://www.itl.nist.gov/div898/handbook/eda/section3/eda35h1.htm

    Examples
    --------

    >>> x = np.array([199.31,199.53,200.19,200.82,201.92,201.95,202.18,245.57])
    >>> ph.outliers_grubbs(x)
    array([ 199.31,  199.53,  200.19,  200.82,  201.92,  201.95,  202.18])
    """

    arr = np.copy(x)
    val = np.max(np.abs(arr - np.mean(arr)))
    ind = np.argmax(np.abs(arr - np.mean(arr)))
    G = val / np.std(arr, ddof=1)
    N = len(arr)
    result = G > (N - 1)/np.sqrt(N) * np.sqrt((t.ppf(1-alpha/(2*N), N-2) ** 2) / (N - 2 + t.ppf(1-alpha/(2*N), N-2) ** 2))

    if hypo:
        return result
    else:
        if result:
            return np.delete(arr, ind)
        else:
            return arr

def outliers_tietjen(x, k, hypo = False, alpha = 0.05):

    """
    Tietjen-Moore test [1]_ to detect multiple outliers in a univariate
    data set that follows an approximately normal distribution.
    The Tietjen-Moore test [2]_ is a generalization of the Grubbs' test to
    the case of multiple outliers. If testing for a single outlier,
    the Tietjen-Moore test is equivalent to the Grubbs' test.

    The null hypothesis implies that there are no outliers in the data set.

    Parameters
    ----------
    x : array_like or np.ndarray, 1d
        An array, any object exposing the array interface, containing
        data to test for an outlier in.

    k : int
        Number of potential outliers to test for. Function tests for
        outliers in both tails.

    hypo : bool, optional
        Specifies whether to return a bool value of a hypothesis test result.
        Returns True when we can reject the null hypothesis. Otherwise, False.
        Available options are:
        1) True - return a hypothesis test result
        2) False - return a filtered array without outliers (default)

    alpha : float, optional
        Significance level for a hypothesis test. Default is 0.05.

    Returns
    -------
    arr : np.ndarray
        Filtered array if alternative hypo is True, otherwise an unfiltered
        array.

    hypo : bool, optional
        Null hypothesis test result.

    Notes
    -----
    .. [1] Tietjen and Moore (August 1972), Some Grubbs-Type Statistics
        for the Detection of Outliers, Technometrics, 14(3), pp. 583-597.
    .. [2] http://www.itl.nist.gov/div898/handbook/eda/section3/eda35h2.htm

    Examples
    --------
    >>> x = np.array([-1.40, -0.44, -0.30, -0.24, -0.22, -0.13, -0.05, 0.06,
    0.10, 0.18, 0.20, 0.39, 0.48, 0.63, 1.01])
    >>> outliers_tietjen(x, 2)
    array([-0.44, -0.3 , -0.24, -0.22, -0.13, -0.05,  0.06,  0.1 ,  0.18,
    0.2 ,  0.39,  0.48,  0.63])

    """

    arr = np.copy(x)
    n = arr.size
    def tietjen(x_, k_):
        x_mean = x_.mean()
        r = np.abs(x_ - x_mean)
        z = x_[r.argsort()]
        E = np.sum((z[:-k_] - z[:-k_].mean()) ** 2) / np.sum((z - x_mean) ** 2)
        return E

    e_x = tietjen(arr, k)
    e_norm = np.zeros(10000)

    for i in np.arange(10000):
        norm = np.random.normal(size=n)
        e_norm[i] = tietjen(norm, k)

    CV = np.percentile(e_norm, alpha * 100)
    result = e_x < CV

    if hypo:
        return result
    else:
        if result:
            ind = np.argpartition(np.abs(arr - arr.mean()), -k)[-k:]
            return np.delete(arr, ind)
        else:
            return arr

def outliers_gesd(x, outliers = 5, hypo = False, report = False, alpha=0.05):

    """
    The generalized (Extreme Studentized Deviate) ESD test is used
    to detect one or more outliers in a univariate data set that follows
    an approximately normal distribution [1]_.

    Parameters
    ----------
    x : array_like or np.ndarray, 1d
        An array, any object exposing the array interface, containing
        data to test for outliers.

    outliers : int, optional
        Number of potential outliers to test for. Test is two-tailed, i.e.
        maximum and minimum values are checked for potential outliers.

    hypo : bool, optional
        Specifies whether to return a bool value of a hypothesis test result.
        Returns True when we can reject the null hypothesis. Otherwise, False.
        Available options are:
        1) True - return a hypothesis test result
        2) False - return a filtered array without an outlier (default)

    report : bool, optional
        Specifies whether to return a summary table of the test.
        Available options are:
        1) True - return a summary table
        2) False - return the array with outliers removed. (default)

    alpha : float, optional
        Significance level for a hypothesis test. Default is 0.05.

    Returns
    -------
    result : np.ndarray
        Filtered array if alternative hypo is True, otherwise an unfiltered
        (input) array.

    report : str, optional
        If `report` argument is True, test report is returned instead of
        the result.

    Notes
    -----
    .. [1] Rosner, Bernard (May 1983), Percentage Points for a Generalized
        ESD Many-Outlier Procedure,Technometrics, 25(2), pp. 165-172.

    Examples
    --------
    >>> data = np.array([-0.25, 0.68, 0.94, 1.15, 1.2, 1.26, 1.26, 1.34,
        1.38, 1.43, 1.49, 1.49, 1.55, 1.56, 1.58, 1.65, 1.69, 1.7, 1.76,
        1.77, 1.81, 1.91, 1.94, 1.96, 1.99, 2.06, 2.09, 2.1, 2.14, 2.15,
        2.23, 2.24, 2.26, 2.35, 2.37, 2.4, 2.47, 2.54, 2.62, 2.64, 2.9,
        2.92, 2.92, 2.93, 3.21, 3.26, 3.3, 3.59, 3.68, 4.3, 4.64, 5.34,
        5.42, 6.01])

    >>> outliers_gesd(data, 5)
    array([-0.25,  0.68,  0.94,  1.15,  1.2 ,  1.26,  1.26,  1.34,  1.38,
            1.43,  1.49,  1.49,  1.55,  1.56,  1.58,  1.65,  1.69,  1.7 ,
            1.76,  1.77,  1.81,  1.91,  1.94,  1.96,  1.99,  2.06,  2.09,
            2.1 ,  2.14,  2.15,  2.23,  2.24,  2.26,  2.35,  2.37,  2.4 ,
            2.47,  2.54,  2.62,  2.64,  2.9 ,  2.92,  2.92,  2.93,  3.21,
            3.26,  3.3 ,  3.59,  3.68,  4.3 ,  4.64])

    >>> outliers_gesd(data, outliers = 5, report = True)
    H0: no outliers in the data
    Ha: up to 5 outliers in the data
    Significance level:  α = 0.05
    Reject H0 if Ri > Critical Value (λi)

    Summary Table for Two-Tailed Test
    ---------------------------------------
          Exact           Test     Critical
      Number of      Statistic    Value, λi
    Outliers, i      Value, Ri          5 %
    ---------------------------------------
              1          3.119        3.159
              2          2.943        3.151
              3          3.179        3.144 *
              4           2.81        3.136
              5          2.816        3.128

    """

    rs, ls = np.zeros(outliers, dtype = float), np.zeros(outliers, dtype = float)
    ms = []

    data_proc = np.copy(x)
    argsort_index = np.argsort(data_proc)
    data = data_proc[argsort_index]
    n = data_proc.size

    for i in np.arange(outliers):

        abs_d = np.abs(data_proc - np.mean(data_proc))

        # R-value calculation
        R = np.max(abs_d) / np.std(data_proc, ddof=1)
        rs[i] = R

        # Masked values
        lms = ms[-1] if len(ms) > 0 else []
        ms.append(lms + np.where(data == data_proc[np.argmax(abs_d)])[0].tolist())

        # Lambdas calculation
        p = 1 - alpha / (2 * (n - i))
        df = n - i - 2
        t_ppr = t.ppf(p, df)
        lambd = ((n - i - 1) * t_ppr) / np.sqrt((n - i - 2 + t_ppr**2) * (n - i))
        ls[i] = lambd

        # Remove the observation that maximizes |xi − xmean|
        data_proc = np.delete(data_proc, np.argmax(abs_d))

    if report:

        report = ["H0: no outliers in the data",
                  "Ha: up to " + str(outliers) + " outliers in the data",
                  "Significance level:  α = " + str(alpha),
                  "Reject H0 if Ri > Critical Value (λi)", "",
                  "Summary Table for Two-Tailed Test",
                  "---------------------------------------",
                  "      Exact           Test     Critical",
                  "  Number of      Statistic    Value, λi",
                  "Outliers, i      Value, Ri          5 %",
                  "---------------------------------------"]

        for i, (r, l) in enumerate(zip(rs, ls)):
            report.append('{: >11s}'.format(str(i+1)) + \
                          '{: >15s}'.format(str(np.round(r, 3))) + \
                          '{: >13s}'.format(str(np.round(l, 3))) + (" *" if r > l else ""))

        return "\n".join(report)

    else:
        # Remove masked values
        # for which the test statistic is greater
        # than the critical value and return the result

        if any(rs > ls):
            if hypo:
                data[:] = False
                data[ms[np.max(np.where(rs > ls))]] = True
                # rearrange data so mask is in same order as incoming data
                data = np.vstack((data, np.arange(0, data.shape[0])[argsort_index]))
                data = data[0, data.argsort()[1,]]
                data = data.astype('bool')
            else:
                data = np.delete(data, ms[np.max(np.where(rs > ls))])

        return data


import numpy as np
import time

def length(point1, point2):
    return np.sqrt((point1.x - point2.x)**2 + (point1.y - point2.y)**2)

def nearesrNeighborHeuristic(nodeCount, points):
    solution = [0]
    unvisited_node = list(range(1, nodeCount)) # from 1 to nodeCount - 1
    while len(unvisited_node) != 0:
        exploring_node = solution[-1] # the current city we are in
        closed_distance = -1
        closed_node = None
        for node in unvisited_node:
            if closed_distance < 0:
                closed_distance = length(points[exploring_node], points[node])
                closed_node = node
            elif length(points[exploring_node], points[node]) < closed_distance:
                closed_distance = length(points[exploring_node], points[node])
                closed_node = node
        next_visit_node = closed_node
        solution.append(next_visit_node)
        unvisited_node.remove(next_visit_node)
    return solution

def objectiveVal(path, points):
    totalDist = 0
    nodeCount = len(path)
    for i in range(nodeCount):
        totalDist += length(points[path[i] % (nodeCount-2)], points[path[i+1] % (nodeCount-2)])
    return totalDist

def two_opt(nodeCount, points, timelimit = None):
    feasible_sol = nearesrNeighborHeuristic(nodeCount, points)
    start_time = time.time()
    def checkCrossArcs(path):
        crossArc = False
        largest_diff = 0
        swap_ix = None
        for pair1 in range(0, len(path)-2):
            for pair2 in range(pair1+1, len(path)-1):
                i, j = path[pair1], path[pair1+1]
                k, l = path[pair2], path[pair2+1]
                d_ij = length(points[i], points[j])
                d_kl = length(points[k], points[l])
                d_ik = length(points[i], points[k])
                d_jl = length(points[j], points[l])
                if d_ij + d_kl > d_ik + d_jl:
                    if (d_ij + d_kl)- (d_ik + d_jl) > largest_diff:
                        largest_diff = (d_ij + d_kl)  - (d_ik + d_jl)
                        swap_ix = pair1, pair1+1, pair2, pair2+1
                        crossArc = True
        return crossArc, swap_ix
    terminal,swap_ix = checkCrossArcs(feasible_sol)
    while terminal:
        i, j, k, l = swap_ix
        improved_sol = feasible_sol[:j] + feasible_sol[j:k+1][::-1] + feasible_sol[k+1:]
        feasible_sol = improved_sol
        terminal, swap_ix = checkCrossArcs(feasible_sol)
        if time.time() - start_time > timelimit:
            return feasible_sol, objectiveVal(feasible_sol, points)
        #print(objectiveVal(feasible_sol, points))
    totalDist = objectiveVal(feasible_sol, points)
    assert len(feasible_sol) == nodeCount, "There are some nodes left unvisited."
    return feasible_sol, totalDist


from numpy import prod, zeros, sqrt
from numpy.random import randn
from scipy.linalg import qr
from sklearn.metrics import mean_squared_error

def godec(X, rank=1, card=None, iterated_power=1, max_iter=100, tol=0.001):
    """
    GoDec - Go Decomposition (Tianyi Zhou and Dacheng Tao, 2011)

    The algorithm estimate the low-rank part L and the sparse part S of a matrix X = L + S + G with noise G.

    Parameters
    ----------
    X : array-like, shape (n_features, n_samples), which will be decomposed into a sparse matrix S
        and a low-rank matrix L.

    rank : int >= 1, optional
        The rank of low-rank matrix. The default is 1.

    card : int >= 0, optional
        The cardinality of the sparse matrix. The default is None (number of array elements in X).

    iterated_power : int >= 1, optional
        Number of iterations for the power method, increasing it lead to better accuracy and more time cost. The default is 1.

    max_iter : int >= 0, optional
        Maximum number of iterations to be run. The default is 100.

    tol : float >= 0, optional
        Tolerance for stopping criteria. The default is 0.001.

    Returns
    -------
    L : array-like, low-rank matrix.

    S : array-like, sparse matrix.

    LS : array-like, reconstruction matrix.

    RMSE : root-mean-square error.

    References
    ----------
    Zhou, T. and Tao, D. "GoDec: Randomized Lo-rank & Sparse Matrix Decomposition in Noisy Case", ICML 2011.
    """
    iter = 1
    RMSE = []
    card = prod(X.shape) if card is None else card

    X = X.T if(X.shape[0] < X.shape[1]) else X
    m, n = X.shape

    # Initialization of L and S
    L = X
    S = zeros(X.shape)
    LS = zeros(X.shape)

    while True:
        # Update of L
        Y2 = randn(n, rank)
        for i in range(iterated_power):
            Y1 = L.dot(Y2)
            Y2 = L.T.dot(Y1)
        Q, R = qr(Y2, mode='economic')
        L_new = (L.dot(Q)).dot(Q.T)

        # Update of S
        T = L - L_new + S
        L = L_new
        T_vec = T.reshape(-1)
        S_vec = S.reshape(-1)
        idx = abs(T_vec).argsort()[::-1]
        S_vec[idx[:card]] = T_vec[idx[:card]]
        S = S_vec.reshape(S.shape)

        # Reconstruction
        LS = L + S

        # Stopping criteria
        error = sqrt(mean_squared_error(X, LS))
        RMSE.append(error)

        print("iter: ", iter, "error: ", error)
        if (error <= tol) or (iter >= max_iter):
            break
        else:
            iter = iter + 1

    return L, S, LS, RMSE


import numpy as np

from metrics import r2_score

class SimpleLinearRegression1:
    """
    模型训练中使用 for 循环
    """

    def __init__(self):
        """初始化简单线性回归模型"""
        self.a_ = None
        self.b_ = None

    def fit(self, x_train, y_train):
        """根据训练数据集训练模型"""
        x_mean = np.mean(x_train)
        y_mean = np.mean(y_train)

        num = 0.0
        d = 0.0

        for x_i, y_i in zip(x_train, y_train):
            num += (x_i - x_mean) * (y_i - y_mean)
            d += (x_i - x_mean) ** 2

        self.a_ = num / d
        self.b_ = y_mean - self.a_ * x_mean

        return self

    def predict(self, x_predict):
        """根据给定的待测数据集，返回预测的结果向量"""
        return np.array([self._predict(x) for x in x_predict])

    def _predict(self, x_single):
        """预测给定的单个待测数据"""
        return self.a_* x_single + self.b_

    def score(self, x_test, y_test):
        """根据测试数据集确定当前模型的准确度"""
        y_predict = self.predict(x_test)
        return r2_score(y_test, y_predict)

    def __repr__(self):
        return "SimpleLinearRegression1()"

class SimpleLinearRegression2:
    """
    模型训练中使用向量化运算
    """

    def __init__(self):
        """初始化简单线性回归模型"""
        self.a_ = None
        self.b_ = None

    def fit(self, x_train, y_train):
        """根据训练数据集训练模型"""
        x_mean = np.mean(x_train)
        y_mean = np.mean(y_train)

        num = (x_train - x_mean).dot(y_train - y_mean)
        d = (x_train - x_mean).dot(x_train - x_mean)

        self.a_ = num / d
        self.b_ = y_mean - self.a_ * x_mean

        return self

    def predict(self, x_predict):
        """根据给定的待测数据集，返回预测的结果向量"""
        return np.array([self._predict(x) for x in x_predict])

    def _predict(self, x_single):
        """预测给定的单个待测数据"""
        return self.a_* x_single + self.b_

    def score(self, x_test, y_test):
        """根据测试数据集确定当前模型的准确度"""
        y_predict = self.predict(x_test)
        return r2_score(y_test, y_predict)

    def __repr__(self):
        return "SimpleLinearRegression2()"


import numpy as np
import scipy.linalg as LA

def lqr_gain(A,B,Q,R):
  '''
  Arguments:
    State transition matrices (A,B)
    LQR Costs (Q,R)
  Outputs:
    K: optimal infinite-horizon LQR gain matrix given
  '''

  # solve DARE:
  M=LA.solve_discrete_are(A,B,Q,R)

  # K=(B'MB + R)^(-1)*(B'MA)
  return np.dot(LA.inv(np.dot(np.dot(B.T,M),B)+R),(np.dot(np.dot(B.T,M),A)))

def cost_inf_K(A,B,Q,R,K):
  '''
    Arguments:
      State transition matrices (A,B)
      LQR Costs (Q,R)
      Control Gain K
    Outputs:
      cost: Infinite time horizon LQR cost of static gain K
  '''
  cl_map = A+B.dot(K)
  if np.amax(np.abs(LA.eigvals(cl_map)))<(1.0-1.0e-6):
    cost = np.trace(LA.solve_discrete_lyapunov(cl_map.T,Q+np.dot(K.T,R.dot(K))))
  else:
    cost = float("inf")

  return cost

def cost_finite_model(A_true,B_true,Q,R,x0,T,A_dat,B_dat):
  '''
    Arguments:
      True Model state transition matrices (A_true,B_true)
      LQR Costs (Q,R)
      Initial State x0
      Time Horizon T
      Nominal Model state transition matrices (A_dat,B_dat)
    Outputs:
      cost: finite time horizon LQR cost when control is computed using
      (A_dat,B_dat) but executed on system (A_true,B_true)
  '''
  d,p = B_true.shape

  # Ricatti recursion
  M = np.zeros((d,d,T))
  M[:,:,-1]=Q
  for k in range(T-2,-1,-1):
    AMA = np.dot(A_dat.T,M[:,:,k+1].dot(A_dat))
    AMB = np.dot(A_dat.T,M[:,:,k+1].dot(B_dat))
    BMB = np.dot(B_dat.T,M[:,:,k+1].dot(B_dat))
    M[:,:,k] = Q + AMA - np.dot(AMB,LA.inv(R+BMB).dot(AMB.T))

  # compute contols and costs using these Ricatti iterates
  cost = 0
  x = x0
  for k in range(T):
    AMB = np.dot(A_dat.T,M[:,:,k].dot(B_dat))
    BMB = np.dot(B_dat.T,M[:,:,k].dot(B_dat))
    u = -np.dot(LA.inv(R+BMB),np.dot(AMB.T,x))
    x = A_true.dot(x)+B_true.dot(u)
    cost = cost+np.dot(x.T,Q.dot(x))+np.dot(u.T,R.dot(u))

  return cost.flatten()[0]

def cost_finite_K(A_true,B_true,Q,R,x0,T,K):
  '''
    Arguments:
      True Model state transition matrices (A_true,B_true)
      LQR Costs (Q,R)
      Initial State x0
      Time Horizon T
      Static Control Gain K
    Outputs:
      cost: finite time horizon LQR cost when control is static gain K on
      system (A_true,B_true)
  '''

  d,p = B_true.shape

  cost = 0
  x = x0
  for k in range(T):
    u = np.dot(K,x)
    x = A_true.dot(x)+B_true.dot(u)
    cost = cost+np.dot(x.T,Q.dot(x))+np.dot(u.T,R.dot(u))

  return cost.flatten()

def lsqr_estimator(A,B,Q,R,x0,eq_err,N,T):
  '''
    Arguments:
      state transition matrices (A,B)
      LQR Costs (Q,R)
      Initial State x0
      magnitude of noise in dynamics eq_err
      Number of rollouts N
      Time Horizon T
    Outputs:
      Estimated State Transition Matrices (A_nom,B_nom) from least squares
  '''

  d,p = B.shape

  # storage matrices
  X_store = np.zeros((d,N,T+1))
  U_store = np.zeros((p,N,T))

  # simulate
  for k in range(N):
    x = x0
    X_store[:,k,0] = x0.flatten()
    for t in range(T):
      u = np.random.randn(p,1)
      x = A.dot(x)+B.dot(u)+eq_err*np.random.randn(d,1)
      X_store[:,k,t+1] = x.flatten()
      U_store[:,k,t] = u.flatten()

  ### Solve for nominal model
  tmp = np.linalg.lstsq(np.vstack((X_store[:,:,0:T].reshape(d,N*T),
                                U_store.reshape(p,N*T))).T,
                    X_store[:,:,1:(T+1)].reshape(d,N*T).T)[0]
  A_nom = tmp[0:d,:].T
  B_nom = tmp[d:(d+p),:].T
  return (A_nom,B_nom)

def random_search_linear_policy(A,B,Q,R,x0,eq_err,N,T,
    explore_mag = 4e-2, step_size = 5e-1, batch_size = 4):
  '''
    Arguments:
      state transition matrices (A,B)
      LQR Costs (Q,R)
      Initial State x0
      magnitude of noise in dynamics eq_err
      Number of rollouts N
      Time Horizon T

      hyperparameters:
        explore_mag = magnitude of the noise to explore
        step_size
        batch_size = number of directions per minibatches
        safeguard: maximum absolute value of entries of controller gain

    Outputs:
      Static Control Gain K optimized on LQR cost by random search
  '''

  d,p = B.shape

  # initial condition for K
  K0 = 1e-3*np.random.randn(p,d)
  ###

  #### ALGORITHM
  K = K0
  for k in range(N):
    reward_store = []
    mini_batch = np.zeros((p,d))
    for j in range(batch_size):
      V = explore_mag*np.random.randn(p,d)
      for sign in [-1,1]:
        x = x0
        reward = 0
        for t in range(T):
          u = np.dot(K+sign*V,x)
          x = A.dot(x)+B.dot(u)+eq_err*np.random.randn(d,1)
          reward += -np.dot(x.T,Q.dot(x))-np.dot(u.T,R.dot(u))
        mini_batch += (reward*sign)*V
        reward_store.append(reward)
    K += (step_size/np.std(reward_store)/batch_size)*mini_batch

  return K

def uniform_random_linear_policy(A,B,Q,R,x0,eq_err,N,T,linf_norm=3):
  '''
    Arguments:
      state transition matrices (A,B)
      LQR Costs (Q,R)
      Initial State x0
      magnitude of noise in dynamics eq_err
      Number of rollouts N
      Time Horizon T

      hyperparameters
          linf_norm = maximum absolute value of entries of controller gain

    Outputs:
      Static Control Gain K optimized on LQR cost by uniformly sampling policies
      in bounded region
  '''

  d,p = B.shape

  #### "ALGORITHM"
  best_K = np.empty((p,d))
  best_reward = -float("inf")
  for k in range(N):
    K = np.random.uniform(-linf_norm,linf_norm,(p,d))
    x = x0
    reward = 0
    for t in range(T):
      u = np.dot(K,x)
      x = A.dot(x)+B.dot(u)+eq_err*np.random.randn(d,1)
      reward += -np.dot(x.T,Q.dot(x))-np.dot(u.T,R.dot(u))
    if reward>best_reward:
        best_reward = reward
        best_K = K

  return best_K

def policy_gradient_linear_policy(A,B,Q,R,x0,eq_err,N,T,
  explore_mag = 5e-2,step_size = 2, batch_size = 40, safeguard = 2):
  '''
    Arguments:
      state transition matrices (A,B)
      LQR Costs (Q,R)
      Initial State x0
      magnitude of noise in dynamics eq_err
      Number of rollouts N
      Time Horizon T

      hyperparameters
         explore_mag magnitude of the noise to explore
         step_size
         batch_size: number of stochastic gradients per minibatch
         safeguard: maximum absolute value of entries of controller gain

    Outputs:
      Static Control Gain K optimized on LQR cost by Policy Gradient
  '''

  d,p = B.shape

  # initial condition for K
  K0 = 1e-3*np.random.randn(p,d)
  ###

  X_store = np.zeros((d,T))
  V_store = np.zeros((p,T))

  #### ALGORITHM
  K = K0
  baseline = 0
  for k in range(N):
    new_baseline = 0
    mini_batch = np.zeros((p,d))
    for j in range(batch_size):
      x = x0
      reward = 0
      for t in range(T):
        v = explore_mag*np.random.randn(p,1)
        X_store[:,t] = x.flatten()
        V_store[:,t] = v.flatten()
        u = np.dot(K,x)+v
        x = A.dot(x)+B.dot(u)+eq_err*np.random.randn(d,1)
        reward += -np.dot(x.T,Q.dot(x))-np.dot(u.T,R.dot(u))
      mini_batch += ((reward-baseline)/batch_size)*np.dot(V_store,X_store.T)
      new_baseline += reward/batch_size
    K += step_size*mini_batch
    K = np.minimum(np.maximum(K,-safeguard),safeguard)
    baseline = new_baseline
  return K


# 将阈值转换为偏置实现AND、OR、NAND
# 在阈值的式子为 w1 * x1 + w2 * x2 <= theta 为0， w1 * x1 + w2 * x2 > theta 为1
# 令theta = -b, 通过移项可得到如下式子
# w1 * x1 + w2 * x2 + b <= 0 为 0， w1 * x1 + w2 * x2 + b > 0 为 1
# w1, w2是控制输入信号的重要性参数， 而bias偏置的值决定了激活神经元的难易程度
import numpy as np

def AND(x1, x2): # 使用numpy完成权重和的计算
    x = np.array([x1, x2])
    w = np.array([0.5, 0.5])
    b = -0.7
    res = np.sum(w * x) + b
    if res <= 0:
        return 0
    else:
        return 1

def OR(x1, x2):
    x = np.array([x1, x2])
    w = np.array([0.5, 0.5])
    b = -0.3
    res = np.sum(w * x) + b
    if res <= 0:
        return 0
    else:
        return 1

def NAND(x1, x2):
    x = np.array([x1, x2])
    w = np.array([-0.5, -0.5])
    b = 0.7
    res = np.sum(w * x) + b
    if res <= 0:
        return 0
    else:
        return 1

print('Test AND:')
print(AND(0, 0))
print(AND(0, 1))
print(AND(1, 0))
print(AND(1, 1))

print('Test OR:')
print(OR(0, 0))
print(OR(0, 1))
print(OR(1, 0))
print(OR(1, 1))

print('Test NAND:')
print(NAND(0, 0))
print(NAND(0, 1))
print(NAND(1, 0))
print(NAND(1, 1))


import copy
import heapq
import numpy as np
from typing import Iterable, Tuple

def normalize(vector):
    """
    Returns the normalized vector (same dtype) using the L2 norm (||v||2) for a vector, v.
    """
    return vector / np.sqrt(np.dot(vector, np.conjugate(vector)))

def cosine_similarity(vector1, vector2):
    """
    Returns the cosine of the angle between vector1 and vector 2
    """
    vector1 = np.array(vector1)
    vector2 = np.array(vector2)

    norm1 = np.sqrt(np.dot(vector1, np.conjugate(vector1)))
    norm2 = np.sqrt(np.dot(vector2, np.conjugate(vector2)))
    return np.real(np.dot(vector1, np.conjugate(vector2)) / (norm1*norm2))

def bind(vec1, vec2, field=np.float):
    if np.issubdtype(field, np.complex128):
        return circular_convolution_complex(vec1, vec2)
    else:
        return circular_convolution(vec1, vec2)

def circular_convolution(vec1, vec2):
    """
    Returns a vector that is the circular convolution of vec1 and vec2.

    Implementation is one of several suggestions from
    https://stackoverflow.com/questions/35474078/python-1d-array-circular-convolution

    vec1 and vec2: array-like, e.g., list of floats
    """
    return np.real(np.fft.ifft(np.fft.fft(vec1) * np.fft.fft(vec2)))

def exact_circular_correlation(vec1, vec2):
    """
    Returns a vector that is the circular correlation of vec1 and vec2.
    vec1 and vec2: array-like, e.g., list of floats
    """
    return np.real(np.fft.ifft(np.fft.fft(vec1) / np.fft.fft(vec2)))

def circular_convolution_complex(vec1, vec2):
    """
    Returns a vector from complex vectors by adding their respective phase angles
    """
    return vec1 * vec2

def circular_correlation_complex(vec1, vec2):
    """
    Returns a vector from complex vectors by subtracting their respective phase angles
    """
    return vec1 / vec2

def create_dense_random_vector(dimension: int, seed=None, field=np.float):
    if seed:
        np.random.seed(seed)

    if np.issubdtype(field, np.complex128):
        real_part = np.random.uniform(low=-1, high=1, size=dimension)
        complex_part = np.random.uniform(low=-1, high=1, size=dimension)
        return real_part + 1j * complex_part

    return np.random.uniform(low=-1, high=1, size=dimension)

def get_k_neighbors_from_pairs(object_vectors: Iterable[Tuple[object, Iterable[float]]],
                               query_vector: Iterable[float], k: int) -> Iterable[Tuple[object, float]]:
    """
    Gets the nearest k vectors and their cosine similarity scores.
    :param object_vectors: Iterable of (object, vector), e.g., from calling .items on a dict[word:vector]
    :param query_vector: Vector for matching
    :param k: Number of neighbors to find
    :return: Iterable of (object, score) pairs.
    """
    object_scores = iter(map(lambda word_vec: (word_vec[0], cosine_similarity(word_vec[1], query_vector)),
                             object_vectors))
    k_best = heapq.nlargest(k, object_scores, key=lambda x: x[1])
    return k_best


# -*- coding: utf-8 -*-
"""
Interpolazione numerica in base di Lagrange

Pn = sommatoria di j = 0 -> yj * Lj
"""
import numpy as np
import math

"""
    np.poly() -> dato un vettore es [2,3] restituisce (x-2)(x-3)

"""
def polinomioLagrange(xnodi, i):
    n = xnodi.size
    xzeri = []
    if i == 0:
        xzeri = xnodi[1:n]
    else:
        xzeri = np.append(xnodi[:i], xnodi[i + 1:n])
    num = np.poly(xzeri)
    den = np.polyval(num, xnodi[i])
    return num/den

"""
    Devo restituire le varie approssimazioni
    mi costruisco L = [L0(x0), ..., L0(xn)]
                      [...,     ...       ]
                      [Ln(x0), ..., Ln(xn)]

    Avendo poi y della forma y = [y0, ..., yn]
    facendo y[:] * L[ : , k] ottengo Pn(k)

"""
def interpolL(x, y, xx):
    """"
        %funzione che determina in un insieme di punti il valore del polinomio
        %interpolante ottenuto dalla formula di Lagrange.
        % DATI INPUT
        %  x  vettore con i nodi dell'interpolazione
        %  f  vettore con i valori dei nodi
        %  xx vettore con i punti in cui si vuole calcolare il polinomio
        % DATI OUTPUT
        %  y vettore contenente i valori assunti dal polinomio interpolante
        %
     """
    m = x.size
    n = xx.size
    somma = 0
    L = np.zeros((m,n))
    for i in range(m):
        L[i, :] = np.polyval(polinomioLagrange(x, i), xx)
    return np.dot(y,L)

"""
    Nodi di Chebishev
"""
def zeri_Cheb(a,b,n):
    t1=(a+b)/2
    t2=(b-a)/2
    x=np.zeros((n+1,))
    for k in range(n+1):
        x[k]=t1+t2*np.cos(((2*k+1)/(2*(n+1))*math.pi))
    return x


import numpy as np
import matplotlib.pyplot as plt

'''
 Utility functions
'''
def calc_phi2d(fx, fy, dx=1, dy=1):
	'''
	Calculate phi by integrating dphi = fx*dx + fy*dy, phi[0,0] = 0 (<---> [fx, fy] = grad(phi))
	'''
	ny,nx=fx.shape
	phi=np.zeros((ny,nx))
	for jx in range(1,nx):
		phi[0,jx]=phi[0,jx-1]+fx[0,jx]*dx
	for jy in range(1,ny):
		phi[jy,:]=phi[jy-1,:]+fy[jy,:]*dy
	return phi

def calc_psi2d(fx, fy, dx=1, dy=1):#solenoidal flows
	'''
	Calcualte psi by integrating dpsi = -fy*dx + fx*dy, psi[0,0]=0.
	Notes:
		1. (fx=dpsi/dy,fy=-dpsi/dx) is called Hamiltonian gradient of psi, and	contours of psi give vector field (fx, fy);
		2. div(f)=0
	'''
	#FIXME: avoid loop for higher performance
	ny,nx=fx.shape
	psi=np.zeros((ny,nx))
	for jx in range(1,nx):
		#psi[0,jx]=psi[0,jx-1]-0.5*(fy[0,jx-1]+fy[0,jx])*dx
		psi[0,jx]=psi[0,jx-1]-fy[0,jx]*dx
	for jy in range(1,ny):
		#psi[jy,:]=psi[jy-1,:]+0.5*(fx[jy-1,:]+fx[jy,:])*dy
		psi[jy,:]=psi[jy-1,:]+fx[jy,:]*dy
	# since f = rot(A) gives extra restraints on f (e.g., div(f)=0)
	# it makes sense that information provided by fy[1:nx,:] is useless here
	return psi

def calc_psi2d_Kai(fx,fy, dx=1, dy=1):
	ny,nx=fx.shape
	psi=np.zeros((ny,nx))
	for i in range(1,ny):
	    psi[i,0] = psi[i-1,0] + dy * ( fx[i,0] + fx[i-1,0] )/2.
	for j in range(1,nx):
	    psi[:,j] = psi[:,j-1] - dx * ( fy[:,j-1] + fy[:,j] )/2.
	return psi


#!/usr/bin/env python3
# coding=utf-8

# https://www.jianshu.com/p/550c90dfffa0
# 如果两个数组的后缘维度（即从末尾开始算起的维度）的轴长度相等或其中一方的长度为1，则认为他们是广播兼容的，广播会在缺失和(或)长度为1的维度上进行。

import numpy as np

arr = np.arange(12).reshape(4, 3)
print(arr)  # (4, 3)
print(f'arr.ndim={arr.ndim}\n')

mean0 = arr.mean(0)  # (3,)
print(f'mean0={mean0}')

print(f'arr-mean0={arr - mean0}\n')

mean1 = arr.mean(1)  # (4,)
mean1 = mean1.reshape((4, 1))
print(f'mean1={mean1}')

print(f'arr-mean1={arr - mean1}')


# Converted from fdstencil.ipynb and then edited

# coding: utf-8

# # Computing finite difference stencils
#
# [AMath 585, Winter Quarter 2020](http://staff.washington.edu/rjl/classes/am585w2020/) at the University of Washington. Developed by R.J. LeVeque and distributed under the [BSD license](https://github.com/rjleveque/amath585w2020/blob/master/LICENSE).  You are free to modify and use as you please, with attribution.
#
# These notebooks are all [available on Github](https://github.com/rjleveque/amath585w2020/).

# This notebook illustrates how finite difference coefficients can be determined for approximating the $k$th derivative of a function at some point $\bar x$, based on function values at $n$ nearby points, with $n > k$.  If the points have uniform spacing $\Delta x$, then the order of accuracy will be ${\cal O}\left((\Delta x)^{n-k}\right)$ in general, but the points need not be equally spaced.
#
# The general idea is to determine the interpolating polynomial of degree $n-1$ and compute the $k$th derivative of this at the specified point.
#

# In[2]:

from pylab import *
from scipy.special import factorial

# ## Vandermonde matrix solution
#
# The simplest way to determine the coefficients is to derive an $n \times n$ linear system of equations involving a Vandermonde matrix, as described in Section 1.5 of [[FDM-book]](http://faculty.washington.edu/rjl/fdmbook/), and implemented there as a simple Matlab code (also available as `fdcoeffV.m` from the book website).  Here is the Python equivalent:

# In[3]:

def fdcoeffV(k,xbar,x):
    x = array(x)  # in case a list or tuple passed in, convert to numpy array
    n = len(x)
    if k >=n:
        raise ValueError('*** len(x) must be larger than k')

    A = ones((n,n))
    xrow = x - xbar  # displacement vector

    for i in range(1,n):
        A[i,:] = (xrow**i) / factorial(i)

    condA = cond(A)  # condition number
    if condA > 1e8:
        print("Warning: condition number of Vandermonde matrix is approximately %.1e" % condA)

    b = zeros(x.shape)
    b[k] = 1.

    c = solve(A,b)

    return c

# ## Printing the stencil
#
# If you want to compute the weights in general, we can choose $\Delta x = 1$ and then no
# te that any second-derivative difference approximation will have $\Delta x^2$ in the denominator.  In the function below this is made more general, to work for any order $k$ derivative, but assuming equally spaced points.

# In[5]:

def fdstencil(k, jbar, stencil_points):
    """
    Compute and print the finite difference stencil for an order k derivative
    using at least k+1 equally spaced points.
    The stencil_points are thus assumed to be integers (indices of stencil points)
    as is jbar, the index at which the approximation is to be used.

    For example, the standard second order stencil for u''(x_0)
    can be printed via
        fdstencil(2, 0, [-1,0,1])
    """
    assert type(jbar) is int, '*** jbar should be an integer'
    stencil_pts = array(stencil_points)
    assert stencil_pts.dtype == int, '*** stencil_points should be integers'

    c = fdcoeffV(k, jbar, stencil_pts)

    print("Stencil for approximation to order %s derivative at U_{%s} is:"           % (str(k), str(jbar)))
    coeffs = ['%s / h^2' % str(cj) for cj in c]
    s = '['
    for j in range(len(c)):
        subj = str(stencil_pts[j])
        cj = c[j]
        sj = '%g U_{%s}' % (abs(cj), stencil_pts[j])
        if cj >= 0:
            s = s + ' + ' + sj
        else:
            s = s + ' - ' + sj
    s = s + '] / h^%i' % k
    print(s)
    return c

# ## Fornberg's method
#

# A more stable approach was introduced by B. Fornberg in
# - Generation of Finite Difference Formulas on Arbitrarily Spaced Grids, *Mathematics of Computation* 51(1988) pp. 699-706, [doi:10.1090/S0025-5718-1988-0935077-0](https://doi.org/10.1090/S0025-5718-1988-0935077-0)
#
# and explained in these classroom notes,
# - Calculation of weights in finite difference formulas, *SIAM Review* 40 (1998), pp. 685-691, [doi:10.1137/S0036144596322507](https://doi.org/10.1137/S0036144596322507)
#
# This approach is implemented in Matlab in the file `fdcoeffF.m` available from the website [[FDM-book]](http://faculty.washington.edu/rjl/fdmbook/).
#
# A Python version is below.  This is based directly on the Fortran code in the SIAM Review paper listed above (which uses 0-based indexing, like Python, whereas the Matlab code is 1-based).
#
# **Note:** Forberg's algorithm can be used to simultaneously compute the coefficients for derivatives of order `0, 1, ..., m` where `m <= n-1`. This gives a coefficient matrix `C(1:n,1:m)` whose k'th column gives the coefficients for the k'th derivative.
#
# In the version below we set `m=k` and only compute the coefficients for derivatives of order up to order k, and then return only the k'th column of the resulting `C` matrix (converted to a row vector).   This routine is then compatible with ` fdcoeffV` defined above.    If the optional argument `fullC` is `True` then the full matrix of coefficients for derivatives of all orders up to `n-1` will be returned.

# In[10]:

def fdcoeffF(k, xbar, x, fullC=False):
    n = len(x) - 1
    if k > n:
        raise ValueError('*** len(x) must be larger than k')

    m = k  # for consistency with Fornberg's notation
    c1 = 1.
    c4 = x[0] - xbar
    C = zeros((n+1,m+1))
    C[0,0] = 1.
    for i in range(1,n+1):
        mn = min(i,m)
        c2 = 1.
        c5 = c4
        c4 = x[i] - xbar
        for j in range(i):
            c3 = x[i] - x[j]
            c2 = c2*c3
            if j==i-1:
                for s in range(mn,0,-1):
                    C[i,s] = c1*(s*C[i-1,s-1] - c5*C[i-1,s])/c2
                C[i,0] = -c1*c5*C[i-1,0]/c2
            for s in range(mn,0,-1):
                C[j,s] = (c4*C[j,s] - s*C[j,s-1])/c3
            C[j,0] = c4*C[j,0]/c3
        c1 = c2

    if fullC:
        return C
    else:
        c = C[:,-1] # last column of C
        return c


# Copyright (C) 2021-2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
"""Numpy optimizers test functions module."""
from typing import Dict

import numpy as np

def rosenbrock_func(point: Dict[str, np.ndarray]) -> float:
    """
    Calculate Rosenbrock function.

    More details: https://en.wikipedia.org/wiki/Rosenbrock_function
    """
    return (1 - point['x'])**2 + 100 * (point['y'] - point['x']**2)**2

def _get_rosenbrock_grads(point: Dict[str, np.ndarray]) -> Dict[str, np.ndarray]:
    """Calculate gradients for Rosenbrock function."""
    grads = {'x': np.array([0]), 'y': np.array([0])}
    grads['x'] = -2 * (1 - point['x']) - 400 * point['x'] * (point['y'] - point['x']**2)
    grads['y'] = grads['y'] + 200 * (point['y'] - point['x']**2)
    return grads

def mc_cormick_func(point: Dict[str, np.ndarray]) -> float:
    """
    Calculate McCormick function.

    More details: https://en.wikipedia.org/wiki/Test_functions_for_optimization
    """
    return (np.sin(point['x'] + point['y'])
            + (point['x'] - point['y'])**2
            - 1.5 * point['x'] + 2.5 * point['y'] + 1)

def _get_mc_cormick_grads(point: Dict[str, np.ndarray]) -> Dict[str, np.ndarray]:
    """Calculate gradients for McCormick function."""
    grads = {'x': np.array([0]), 'y': np.array([0])}
    grads['x'] = np.cos(point['x'] + point['y']) + 2 * (point['x'] - point['y']) - 1.5
    grads['y'] = np.cos(point['x'] + point['y']) - 2 * (point['x'] - point['y']) + 2.5
    return grads

rosenbrock_func.get_grads = _get_rosenbrock_grads
rosenbrock_func.true_answer = {'x': np.array([1.0]), 'y': np.array([1.0])}

mc_cormick_func.get_grads = _get_mc_cormick_grads
mc_cormick_func.true_answer = {'x': np.array([-0.54719]), 'y': np.array([-1.54719])}

__all__ = [
    'rosenbrock_func',
    'mc_cormick_func',
]


import numpy

def linear(yin, xin, xout, method='linear'):
    """
    Interpolate the curve defined by (xin, yin) at points xout. The array
    xin must be monotonically increasing. The output has the same data type as
    the input yin.

    :param yin: y values of input curve
    :param xin: x values of input curve
    :param xout: x values of output interpolated curve
    :param method: interpolation method ('linear' | 'nearest')

    @:rtype: numpy array with interpolated curve
    """
    lenxin = len(xin)

    i1 = numpy.searchsorted(xin, xout)
    i1[i1 == 0] = 1
    i1[i1 == lenxin] = lenxin - 1

    x0 = xin[i1 - 1]
    x1 = xin[i1]
    y0 = yin[i1 - 1]
    y1 = yin[i1]

    if method == 'linear':
        return (xout - x0) / (x1 - x0) * (y1 - y0) + y0
    elif method == 'nearest':
        return numpy.where(numpy.abs(xout - x0) < numpy.abs(xout - x1), y0, y1)
    else:
        raise ValueError('Invalid interpolation method: %s' % method)  # Python code here

import numpy as np
from math import sqrt

def cubic_splines(x0, x, y):
    """
    Interpolate a 1-D function using cubic splines.
      x0 : a float or an 1d-array
      x : (N,) array_like
          A 1-D array of real/complex values.
      y : (N,) array_like
          A 1-D array of real values. The length of y along the
          interpolation axis must be equal to the length of x.

    Implement a trick to generate at first step the cholesky matrice L of
    the tridiagonal matrice A (thus L is a bidiagonal matrice that
    can be solved in two distinct loops).

    additional ref: www.math.uh.edu/~jingqiu/math4364/spline.pdf
    """
    x = np.asfarray(x)
    y = np.asfarray(y)

    # remove non finite values
    # indexes = np.isfinite(x)
    # x = x[indexes]
    # y = y[indexes]

    # check if sorted
    if np.any(np.diff(x) < 0):
        indexes = np.argsort(x)
        x = x[indexes]
        y = y[indexes]

    size = len(x)

    xdiff = np.diff(x)
    ydiff = np.diff(y)

    # allocate buffer matrices
    Li = np.empty(size)
    Li_1 = np.empty(size - 1)
    z = np.empty(size)

    # fill diagonals Li and Li-1 and solve [L][y] = [B]
    Li[0] = sqrt(2 * xdiff[0])
    Li_1[0] = 0.0
    B0 = 0.0  # natural boundary
    z[0] = B0 / Li[0]

    for i in range(1, size - 1, 1):
        Li_1[i] = xdiff[i - 1] / Li[i - 1]
        Li[i] = sqrt(2 * (xdiff[i - 1] + xdiff[i]) - Li_1[i - 1] * Li_1[i - 1])
        Bi = 6 * (ydiff[i] / xdiff[i] - ydiff[i - 1] / xdiff[i - 1])
        z[i] = (Bi - Li_1[i - 1] * z[i - 1]) / Li[i]

    i = size - 1
    Li_1[i - 1] = xdiff[-1] / Li[i - 1]
    Li[i] = sqrt(2 * xdiff[-1] - Li_1[i - 1] * Li_1[i - 1])
    Bi = 0.0  # natural boundary
    z[i] = (Bi - Li_1[i - 1] * z[i - 1]) / Li[i]

    # solve [L.T][x] = [y]
    i = size - 1
    z[i] = z[i] / Li[i]
    for i in range(size - 2, -1, -1):
        z[i] = (z[i] - Li_1[i - 1] * z[i + 1]) / Li[i]

    # find index
    index = x.searchsorted(x0)
    np.clip(index, 1, size - 1, index)

    xi1, xi0 = x[index], x[index - 1]
    yi1, yi0 = y[index], y[index - 1]
    zi1, zi0 = z[index], z[index - 1]
    hi1 = xi1 - xi0

    # calculate cubic
    f0 = zi0 / (6 * hi1) * (xi1 - x0) ** 3 + \
         zi1 / (6 * hi1) * (x0 - xi0) ** 3 + \
         (yi1 / hi1 - zi1 * hi1 / 6) * (x0 - xi0) + \
         (yi0 / hi1 - zi0 * hi1 / 6) * (xi1 - x0)
    return f0


"""
Methods for computing pure Nash equilibria of a normal form game.
(For now, only brute force method is supported)

"""

import numpy as np

def pure_nash_brute(g, tol=None):
    """
    Find all pure Nash equilibria of a normal form game by brute force.

    Parameters
    ----------
    g : NormalFormGame
    tol : scalar(float), optional(default=None)
        Tolerance level used in determining best responses. If None,
        default to the value of the `tol` attribute of `g`.

    Returns
    -------
    NEs : list(tuple(int))
        List of tuples of Nash equilibrium pure actions.
        If no pure Nash equilibrium is found, return empty list.

    Examples
    --------
    Consider the "Prisoners' Dilemma" game:

    >>> PD_bimatrix = [[(1, 1), (-2, 3)],
    ...                [(3, -2), (0, 0)]]
    >>> g_PD = NormalFormGame(PD_bimatrix)
    >>> pure_nash_brute(g_PD)
    [(1, 1)]

    If we consider the "Matching Pennies" game, which has no pure nash
    equilibirum:

    >>> MP_bimatrix = [[(1, -1), (-1, 1)],
    ...                [(-1, 1), (1, -1)]]
    >>> g_MP = NormalFormGame(MP_bimatrix)
    >>> pure_nash_brute(g_MP)
    []

    """
    return list(pure_nash_brute_gen(g, tol=tol))

def pure_nash_brute_gen(g, tol=None):
    """
    Generator version of `pure_nash_brute`.

    Parameters
    ----------
    g : NormalFormGame
    tol : scalar(float), optional(default=None)
        Tolerance level used in determining best responses. If None,
        default to the value of the `tol` attribute of `g`.

    Yields
    ------
    out : tuple(int)
        Tuple of Nash equilibrium pure actions.

    """
    for a in np.ndindex(*g.nums_actions):
        if g.is_nash(a, tol=tol):
            yield a


import numpy as np

def softmax(predictions):
    '''
    Computes probabilities from scores

    Arguments:
      predictions, np array, shape is either (N) or (batch_size, N) -
        classifier output

    Returns:
      probs, np array of the same shape as predictions -
        probability for every class, 0..1
    '''
    def softmax_row(v):
      exps = np.exp(v - v.max())
      r = exps / exps.sum()
      r[r == 0.0] = np.finfo(float).eps
      return r

    if len(predictions.shape) == 1:
      return softmax_row(predictions)
    else:
      return np.apply_along_axis(softmax_row, 1, predictions)

def cross_entropy_loss(probs, target_index):
    '''
    Computes cross-entropy loss

    Arguments:
      probs, np array, shape is either (N) or (batch_size, N) -
        probabilities for every class
      target_index: np array of int, shape is (1) or (batch_size) -
        index of the true class for given sample(s)

    Returns:
      loss: single value
    '''
    if len(probs.shape) == 1:
      return -np.log(probs[target_index])
    else:
      return np.negative(np.log(probs[np.arange(probs.shape[0]), target_index])).mean()

def l2_regularization(W, reg_strength):
    """
    Computes L2 regularization loss on weights and its gradient

    Arguments:
      W, np array - weights
      reg_strength - float value

    Returns:
      loss, single value - l2 regularization loss
      gradient, np.array same shape as W - gradient of weight by l2 loss
    """
    loss = reg_strength * (W ** 2).sum()
    grad = 2 * reg_strength * W
    return loss, grad

def softmax_with_cross_entropy(predictions, target_index):
    """
    Computes softmax and cross-entropy loss for model predictions,
    including the gradient

    Arguments:
      predictions, np array, shape is either (N) or (batch_size, N) -
        classifier output
      target_index: np array of int, shape is (1) or (batch_size) -
        index of the true class for given sample(s)

    Returns:
      loss, single value - cross-entropy loss
      dprediction, np array same shape as predictions - gradient of predictions by loss value
    """
    probs = softmax(predictions)
    loss = cross_entropy_loss(probs, target_index)
    dprediction = probs.copy()

    if len(predictions.shape) == 1:
      dprediction[target_index] -= 1
    else:
      dprediction[np.arange(dprediction.shape[0]), target_index] -= 1
      dprediction /= dprediction.shape[0]

    return loss, dprediction

class Param:
    """
    Trainable parameter of the model
    Captures both parameter value and the gradient
    """

    def __init__(self, value):
        self.value = value
        self.grad = np.zeros_like(value)

class ReLULayer:
    def __init__(self):
        self.X = None

    def forward(self, X):
        Z = np.vectorize(lambda x: x if x > 0 else 0, [X.dtype])(X)
        self.X = X.copy()
        return Z

    def backward(self, d_out):
        """
        Backward pass

        Arguments:
        d_out, np array (batch_size, num_features) - gradient
           of loss function with respect to output

        Returns:
        d_result: np array (batch_size, num_features) - gradient
          with respect to input
        """
        return np.where(self.X > 0, d_out, 0.0)

    def params(self):
        # ReLU Doesn't have any parameters
        return {}

class FullyConnectedLayer:
    def __init__(self, n_input, n_output):
        self.W = Param(0.001 * np.random.randn(n_input, n_output) * np.sqrt(2 / n_output))
        self.B = Param(0.001 * np.random.randn(1, n_output) * np.sqrt(2 / n_output))
        self.X = None

    def forward(self, X):
        self.X = X.copy()
        return self.X @ self.W.value + self.B.value

    def backward(self, d_out):
        """
        Backward pass
        Computes gradient with respect to input and
        accumulates gradients within self.W and self.B

        Arguments:
        d_out, np array (batch_size, n_output) - gradient
           of loss function with respect to output

        Returns:
        d_result: np array (batch_size, n_input) - gradient
          with respect to input
        """
        self.W.grad += self.X.T @ d_out
        self.B.grad += d_out.sum(axis=0)[np.newaxis, :]
        return d_out @ self.W.value.T

    def params(self):
        return {'W': self.W, 'B': self.B}


import matplotlib.pyplot as plt
import numpy as np

learning_rate=0.1
def unit_step(v):
	if v >= 0:
		return 1
	else:
		return 0

def perceptron(x, w,b):
    v = np.dot(w, x)+b
    y =unit_step(v)
    return y

def NOR_percep(x,w,b):
    result = perceptron(x, w,b)
    return result

#random initialize

w = [0.2,0.1]
b=0.1
x=[[1.0,1.0],[1.0,0.0],[0.0,1.0],[0.0,0.0]]
y=[0.0,0.0,0.0,1.0]

for i in range(0,3):
    for j in range (0,len(x)):#one iteration
        #it is given from gradient descent formula
        w[0] = w[0] + learning_rate*np.multiply(x[j][0],(y[j]-NOR_percep(x[j],w,b)))
        w[1] = w[1] + learning_rate*np.multiply(x[j][1],(y[j]-NOR_percep(x[j],w,b)))
        b=b+ learning_rate*(y[j]- NOR_percep(x[j],w,b))

print(" w after 3 epochs is : ",w)

example1 = np.array([1, 1])
example2 = np.array([1, 0])
example3 = np.array([0, 1])
example4 = np.array([0, 0])

print("1 NOR 1: ",NOR_percep(example1,w,b))
print("1 NOR 0: ",NOR_percep(example2,w,b))
print("0 NOR 1: ",NOR_percep(example3,w,b))
print("0 NOR 0: ",NOR_percep(example4,w,b))


import numpy as np

'''
Problem 2.2
'''

def euler_inertial(n, u0, f, Dt):
    """

    Parameters
    ----------
    n : int
        Number of time steps
    u0
    f
    Dt

    Returns
    -------
    x, y, u, v : np.ndarray

    """
    # Initial conditions
    u = [u0]
    v = [0.]
    x = [0.]
    y = [0.]

    # Proceed
    for ii in range(n):
        # Velocity
        u += [u[-1] + f*Dt*v[-1]]
        v += [v[-1] - f*Dt*u[-2]] # Note we had already updated u!
        # Position
        x += [x[-1] + Dt*u[-1]]
        y += [y[-1] + Dt*v[-1]]
    # Return
    return np.array(x), np.array(y), np.array(u), np.array(v)

def semi_implicit_inertial(n, u0, f, Dt):
    """

    Parameters
    ----------
    n : int
        Number of time steps
    u0
    f
    Dt

    Returns
    -------
    u, v : np.ndarray

    """
    # Initial conditions
    u = [u0]
    v = [0.]

    # Proceed
    for ii in range(n):
        # Velocity
        u += [ (f*Dt*v[-1] + u[-1]*(1-0.25*(f*Dt*f*Dt)))/(1+0.25*f*Dt*f*Dt) ]
        v += [(-f * Dt * u[-1] + v[-1] * (1 + 0.25 * (f * Dt * f * Dt))) / (1 - 0.25 * f * Dt * f * Dt)]
    # Return
    return np.array(u), np.array(v)


import numpy as np

import matplotlib.pyplot as plt
#%matplotlib inline

#import matplotlib.image as img
#import PIL.Image as Image
from PIL import Image
import math
import cmath

import time

import csv

from numpy import binary_repr

from fractions import gcd

class DCT(object):
    """
    This class DCT implements all the procedures for transforming a given 2D digital image
    into its corresponding frequency-domain image (Forward DCT Transform)
    """

    def __init__():
        pass

    @classmethod
    def __computeSinglePoint2DCT(self, imge, u, v, N):
        """
        A private method that computes a single value of the 2D-DCT from a given image.

        Parameters
        ----------
        imge : ndarray
            The input image.

        u : ndarray
            The index in x-dimension.

        v : ndarray
            The index in y-dimension.

        N : int
            Size of the image.

        Returns
        -------
        result : float
            The computed single value of the DCT.
        """
        result = 0

        for x in xrange(N):
            for y in xrange(N):
                result += imge[x, y] * math.cos(((2*x + 1)*u*math.pi)/(2*N)) * math.cos(((2*y + 1)*v*math.pi)/(2*N))

        #Add the tau value to the result
        if (u==0) and (v==0):
            result = result/N
        elif (u==0) or (v==0):
            result = (math.sqrt(2.0)*result)/N
        else:
            result = (2.0*result)/N

        return result

    @classmethod
    def __computeSinglePointInverse2DCT(self, imge, x, y, N):
        """
        A private method that computes a single value of the 2D-DCT from a given image.

        Parameters
        ----------
        imge : ndarray
            The input image.

        u : ndarray
            The index in x-dimension.

        v : ndarray
            The index in y-dimension.

        N : int
            Size of the image.

        Returns
        -------
        result : float
            The computed single value of the DCT.
        """
        result = 0

        for u in xrange(N):
            for v in xrange(N):
                if (u==0) and (v==0):
                    tau = 1.0/N
                elif (u==0) or (v==0):
                    tau = math.sqrt(2.0)/N
                else:
                    tau = 2.0/N
                result += tau * imge[u, v] * math.cos(((2*x + 1)*u*math.pi)/(2*N)) * math.cos(((2*y + 1)*v*math.pi)/(2*N))

        return result

    @classmethod
    def computeForward2DDCT(self, imge):
        """
        Computes/generates the 2D DCT of an input image in spatial domain.

        Parameters
        ----------
        imge : ndarray
            The input image to be transformed.

        Returns
        -------
        final2DDFT : ndarray
            The transformed image.
        """

        # Assuming a square image
        N = imge.shape[0]
        final2DDCT = np.zeros([N, N], dtype=float)
        for u in xrange(N):
            for v in xrange(N):
                #Compute the DCT value for each cells/points in the resulting transformed image.
                final2DDCT[u, v] = DCT.__computeSinglePoint2DCT(imge, u, v, N)
        return final2DDCT

    @classmethod
    def computeInverse2DDCT(self, imge):
        """
        Computes/generates the 2D DCT of an input image in spatial domain.

        Parameters
        ----------
        imge : ndarray
            The input image to be transformed.

        Returns
        -------
        final2DDFT : ndarray
            The transformed image.
        """

        # Assuming a square image
        N = imge.shape[0]
        finalInverse2DDCT = np.zeros([N, N], dtype=float)
        for x in xrange(N):
            for y in xrange(N):
                #Compute the DCT value for each cells/points in the resulting transformed image.
                finalInverse2DDCT[x, y] = DCT.__computeSinglePointInverse2DCT(imge, x, y, N)
        return finalInverse2DDCT

    @classmethod
    def normalize2DDCTByLog(self, dctImge):
        """
        Computes the log transformation of the transformed DCT image to make the range
        of the DCT values b/n 0 to 255

        Parameters
        ----------
        dctImge : ndarray
            The input DCT transformed image.

        Returns
        -------
        dctNormImge : ndarray
            The normalized version of the transformed image.
        """

        #Normalize the DCT values of a transformed image:
        dctImge = np.absolute(dctImge)
        dctNormImge = (255/ math.log10(255)) * np.log10(1 + (255/(np.max(dctImge))*dctImge))

        return dctNormImge