import numpy as np
from decimal import *
from matplotlib.patches import Ellipse
import matplotlib.transforms as transforms

def gaussian_dec(x, m, var):
    '''
    Computes the Gaussian pdf value (Decimal type) at x.
    x: value at which the pdf is computed (Decimal type)
    m: mean (Decimal type)
    var: variance
    '''
    p = np.exp(-(x-m)**Decimal(2)/(Decimal(2)*Decimal(var)))/(np.sqrt(Decimal(2)*Decimal(np.pi)*Decimal(var)))
    return p

def gaussian(x, m, var):
    '''
    Computes the Gaussian pdf value at x.
    x: value at which the pdf is computed (Decimal type)
    m: mean
    var: variance
    '''
    p = np.exp(-(x-m)**2/(2*var))/(np.sqrt(2*np.pi*var))
    return p

def laplace_dec(x, m, b):
    '''
    Computes the Laplace pdf value (Decimal type) at x.
    x: value at which the pdf is computed (Decimal type)
    m: mean
    b: scale parameter
    '''
    p = np.exp(-np.abs(x-m)/(Decimal(b)))/(Decimal(2)*Decimal(b))
    return p

def laplace(x, m, b):
    '''
    Computes the Laplace pdf value at x.
    x: value at which the pdf is computed
    m: mean
    b: scale parameter
    '''
    p = np.exp(-np.abs((x-m)/b))/(2*b)
    return p

def bayesian_update(L, mu):
    '''
    Computes the Bayesian update.
    L: likelihoods matrix
    mu: beliefs matrix
    '''
    aux = L*mu
    bu = aux/aux.sum(axis = 1)[:, None]
    return bu

def asl_bayesian_update(L, mu, delta):
    '''
    Computes the adaptive Bayesian update.
    L: likelihoods matrix
    mu: beliefs matrix
    delta: step size
    '''
    aux = L**(delta)*mu**(1-delta)
    bu = aux/aux.sum(axis = 1)[:, None]
    return bu

def DKL(m,n,dx):
    '''
    Computes the KL divergence between m and n.
    m: true distribution in vector form
    n: second distribution in vector form
    dx : sample size
    '''
    mn = m/n
    mnlog = np.log(mn)
    return np.sum(m*dx*mnlog)

def decimal_array(arr):
    '''
    Converts an array to an array of Decimal objects.
    arr: array to be converted
    '''
    if len(arr.shape) == 1:
        return np.array([Decimal(y) for y in arr])
    else:
        return np.array([[Decimal(x) for x in y] for y in arr])

def float_array(arr):
    '''
    Converts an array to an array of float objects.
    arr: array to be converted
    '''
    if len(arr.shape)==1:
        return np.array([float(y) for y in arr])
    else:
        return np.array([[float(x) for x in y] for y in arr])

def asl_markov(mu_0, csi, A, N_ITER, theta, b, delta = 0):
    '''
    Executes the adaptive social learning algorithm with Laplace likelihoods
    for the Markovian example
    mu_0: initial beliefs
    csi: observations
    A: list of combination matrices
    N_ITER: number of iterations
    theta: vector of means for the Laplace likelihoods
    b: scale parameter of the Laplace likelihoods
    delta: step size
    '''
    mu = mu_0.copy()
    N = len(A[0])
    MU = [mu]
    for i in range(N_ITER):
        L_i = np.array([laplace(csi[:,i], t, b) for t in theta]).T

        # unindentifiability
        L_i[:N//3, 1] = L_i[:N//3, 0]
        L_i[N//3:2*N//3, 1] = L_i[N//3:2*N//3, 2]
        L_i[2*N//3:, 2] = L_i[2*N//3:, 0]

        psi = asl_bayesian_update(L_i, mu, delta)
        decpsi = np.log(psi)

        mu = np.exp((A[i].T).dot(decpsi)) / np.sum(np.exp((A[i].T).dot(decpsi)), axis=1)[:, None]
        MU.append(mu)
    return MU

def sl_markov(mu_0, csi, A, N_ITER, thetadec, b):
    '''
    Executes the social learning algorithm with Laplace likelihoods
    for the Markovian example
    mu_0: initial beliefs
    csi: observations
    A: Combination matrix
    N_ITER: number of iterations
    thetadec: vector of means for the Laplace likelihoods (Decimal type)
    b: scale parameter of the Laplace likelihoods
    is_gaussian: flag indicating if the likelihoods are Gaussian
    '''
    mu = decimal_array(mu_0)
    N = len(A[0])
    MU = [mu]
    for i in range(N_ITER):
        L_i = np.array([laplace_dec(csi[:,i], t, b) for t in thetadec]).T

        # unindentifiability
        L_i[:N//3, 1] = L_i[:N//3, 0]
        L_i[N//3:2*N//3, 1] = L_i[N//3:2*N//3, 2]
        L_i[2*N//3:, 2] = L_i[2*N//3:, 0]

        psi = bayesian_update(L_i, mu)
        decpsi = np.array([[x.ln() for x in y] for y in psi])
        C = decimal_array(A[i])
        mu = np.exp((C.T).dot(decpsi)) / np.sum(np.exp((C.T).dot(decpsi)), axis=1)[:, None]
        MU.append(mu)
    return MU

def asl(mu_0, csi, A, N_ITER, theta, var, delta = 0, is_gaussian = False):
    '''
    Executes the adaptive social learning algorithm with Gaussian or Laplace likelihoods.
    mu_0: initial beliefs
    csi: observations
    A: Combination matrix
    N_ITER: number of iterations
    theta: vector of means for the likelihoods
    var: variance of Gaussian likelihoods/ scale parameter of the Laplace likelihoods
    delta: step size
    is_gaussian: flag indicating if the likelihoods are Gaussian
    '''
    mu = mu_0.copy()
    N = len(A)
    MU = [mu]
    for i in range(N_ITER):
        if is_gaussian:
            L_i = np.array([gaussian(csi[:, i], t, var) for t in theta]).T
        else:
            L_i = np.array([laplace(csi[:, i], t, var) for t in theta]).T

        # unindentifiability
        L_i[:N//3, 1] = L_i[:N//3, 0]
        L_i[N//3:2*N//3, 1] = L_i[N//3:2*N//3, 2]
        L_i[2*N//3:, 2] = L_i[2*N//3:, 0]

        psi = asl_bayesian_update(L_i, mu, delta)
        decpsi = np.log(psi)
        mu = np.exp((A.T).dot(decpsi)) / np.sum(np.exp((A.T).dot(decpsi)), axis=1)[:, None]
        MU.append(mu)
    return MU

def sl(mu_0, csi, A, N_ITER, thetadec, var, is_gaussian = False):
    '''
    Executes the social learning algorithm with Gaussian or Laplace likelihoods.
    mu_0: initial beliefs
    csi: observations
    A: Combination matrix
    N_ITER: number of iterations
    thetadec: vector of means for the likelihoods (Decimal type)
    var: variance of Gaussian likelihoods/ scale parameter of the Laplace likelihoods
    is_gaussian: flag indicating if the likelihoods are Gaussian
    '''
    mu = mu_0.copy()
    N = len(A)
    MU = [mu]
    for i in range(N_ITER):
        if is_gaussian:
            L_i = np.array([gaussian_dec(csi[:, i], t, var) for t in thetadec]).T
        else:
            L_i = np.array([laplace_dec(csi[:, i], t, var) for t in thetadec]).T

        # unindentifiability
        L_i[:N//3, 1] = L_i[:N//3, 0]
        L_i[N//3:2*N//3, 1] = L_i[N//3:2*N//3, 2]
        L_i[2*N//3:, 2] = L_i[2*N//3:, 0]

        psi = bayesian_update(L_i, mu)
        decpsi = np.array([[x.ln() for x in y] for y in psi])
        mu = np.exp((A.T).dot(decpsi)) / np.sum(np.exp((A.T).dot(decpsi)), axis =1)[:, None]
        MU.append(mu)
    return MU

def comb_matrix_from_p(p, G):
    '''
    Computes combination matrix from the Perron eigenvector and graph specification.
    G: adjacency matrix
    p: Perron eigenvector
    '''
    A = np.where(G > 0, (p.T * np.ones((10,1))).T, 0)
    np.fill_diagonal(A, 1 - np.sum(A - np.diag(np.diag(A)), axis=0))
    return A

def lmgf(thetazero, theta1, t):
    '''
    Computes the LMGF for the family of Laplace distributions, specified in section VIII. C.
    thetazero: true state of nature
    theta1: state different than true state
    t: vector of real values
    '''
    alpha = theta1 - thetazero
    if thetazero >= theta1:
        f = np.log(np.exp(alpha * (t + 1)) + np.exp(-alpha * t) - np.exp(alpha / 2)
                   * np.sinh(alpha*(t + 1/2))/(t + 1/2)) - np.log(2)
    elif thetazero<theta1:
        f = np.log(np.exp(-alpha * (t + 1)) + np.exp(alpha * t) + np.exp(-alpha / 2)
                   * np.sinh(alpha*(t + 1/2))/(t + 1/2)) - np.log(2)
    return f

def Cave(L, t1, t2, dt):
    '''
    Computes the covariance between the log likelihood ratios for two hypotheses.
    L: likelihood Functions
    t1: hypothesis 1
    t2: hypothesis 2
    dt: step size
    '''
    aux = L[0] / L[t1]
    auxlog = np.log(aux)
    aux1 = L[0] / L[t2]
    auxlog1 = np.log(aux1)
    return np.sum(L[0] * dt * auxlog1 * auxlog) - DKL(L[0], L[t1], dt) * DKL(L[0], L[t2], dt)

def confidence_ellipse(x, y, ax, n_std=3.0, facecolor='none', **kwargs):
    '''
    Create a plot of the covariance confidence ellipse of *x* and *y*. Inspired on the code in:
    https://matplotlib.org/devdocs/gallery/statistics/confidence_ellipse.html
    '''
    if x.size != y.size:
        raise ValueError("x and y must be the same size")
    cov = np.cov(x, y)
    pearson = cov[0, 1]/np.sqrt(cov[0, 0] * cov[1, 1])
    ell_radius_x = np.sqrt(1 + pearson)
    ell_radius_y = np.sqrt(1 - pearson)
    ellipse = Ellipse((0, 0),
        width=ell_radius_x * 2,
        height=ell_radius_y * 2,
        facecolor=facecolor,
        **kwargs)
    scale_x = np.sqrt(cov[0, 0]) * n_std
    mean_x = np.mean(x)
    scale_y = np.sqrt(cov[1, 1]) * n_std
    mean_y = np.mean(y)
    transf = transforms.Affine2D() \
        .rotate_deg(45) \
        .scale(scale_x, scale_y) \
        .translate(mean_x, mean_y)
    ellipse.set_transform(transf + ax.transData)
    return ax.add_patch(ellipse)

def gaussian_ellipse(C, Ave, ax, n_std=3.0, facecolor='none', **kwargs):
    '''
    Create a plot of the covariance confidence ellipse relative to the Covariance matrix C.
    C: covariance matrix
    Ave: average vector
    n_std: number of standard deviation ellipses
    '''
    cov = C
    pearson = cov[0, 1]/np.sqrt(cov[0, 0] * cov[1, 1])
    ell_radius_x = np.sqrt(1 + pearson)
    ell_radius_y = np.sqrt(1 - pearson)
    ellipse = Ellipse((0, 0),
        width = ell_radius_x * 2,
        height = ell_radius_y * 2,
        facecolor = facecolor,
        **kwargs)
    scale_x = np.sqrt(cov[0, 0]) * n_std
    mean_x = Ave[0]
    scale_y = np.sqrt(cov[1, 1]) * n_std
    mean_y = Ave[1]
    transf = transforms.Affine2D() \
        .rotate_deg(45) \
        .scale(scale_x, scale_y) \
        .translate(mean_x, mean_y)
    ellipse.set_transform(transf + ax.transData)
    return ax.add_patch(ellipse)


import numpy as np

def gamma_hrf(duration,A=1.,tau=1.08,n=3,delta=2.05,Fs=1.0):

    r"""A gamma function hrf model, with two parameters, based on [Boynton1996]_

    Parameters
    ----------

    duration: float, the length of the HRF (in the inverse units of the sampling
    rate)

    A: float, a scaling factor, sets the max of the function, defaults to 1

    tau: float The time constant of the gamma function, defaults to 1.08

    n: int, the phase delay of the gamma function, defaults to 3

    delta: a pure delay, allowing for an additional delay from the onset of the
    time-series to the beginning of the gamma hrf, defaults to 2.05

    Fs: float, the sampling rate, defaults to 1.0

    Returns
    -------

    h: the gamma function hrf, as a function of time

    Notes
    -----
    This is based on equation 3 in [Boynton1996]_:

    .. math::

        h(t) = \frac{(\frac{t-\delta}{\tau})^{(n-1)}e^{-(\frac{t-\delta}{\tau})}}{\tau(n-1)!}

    .. [Boynton1996] Geoffrey M. Boynton, Stephen A. Engel, Gary H. Glover and
       David J. Heeger (1996). Linear Systems Analysis of Functional Magnetic
       Resonance Imaging in Human V1. J Neurosci 16: 4207-4221

    """
    # XXX Maybe change to take out the time (Fs, duration, etc) from this and
    # instead implement this in units of sampling interval (pushing the time
    # aspect to the higher level)?
    if type(n) is not int:
        print ('gamma_hrf received unusual input, converting n from %s to %i'
               %(str(n),int(n)))

        n=int(n)

    sampling_interval = 1/float(Fs)

    #Prevent negative delta values:
    if delta<0:
        raise ValueError('in gamma_hrf, delta cannot be smaller than 0')

    #Prevent cases in which the delta is larger than the entire hrf:
    if delta>duration:
     raise ValueError('in gamma_hrf, delta cannot be larger than the duration')

    t_max = duration - delta

    t = np.hstack([np.zeros((delta*Fs)),np.linspace(0,t_max,t_max*Fs)])

    t_tau = t/tau

    h = (t_tau**(n-1) * np.exp(-1*(t_tau)) /
         (tau * factorial(n-1) ) )

    return A*h/max(h)

def polonsky_hrf(A, B, tau1, f1, tau2, f2,t_max,Fs=1.0):
    r""" HRF based on [Polonsky2000]_

    .. math::

       H(t) = exp(\frac{-t}{\tau_1}) sin(2\cdot\pi f_1 \cdot t) -a\cdot exp(-\frac{t}{\tau_2})*sin(2\pi f_2 t)

    .. [Polonsky2000] Alex Polonsky, Randolph Blake, Jochen Braun and David
       J. Heeger. Neuronal activity in human primary visual cortex correlates
       with perception during binocular rivalry. Nature Neuroscience 3: 1153-1159

    """
    sampling_interval = 1/float(Fs)

    t = np.arange(0,t_max,sampling_interval)

    h = (np.exp(-t/tau1) * np.sin(2*np.pi * f1 * t) -
            (B * np.exp(-t/tau2) * np.sin(2 * np.pi * f2 * t)))

    return A*h/max(h)


# import standard modules

# import third party modules
import numpy as np

# import project related modules
from simple_numpy_net.general import Layer

class Sigmoid(Layer):
    """
    create a sigmoid function which can be used within a neuronal network due to the fact that also the derivative
    is available. Sigmoid inherits form Layer
    """

    def __init__(self):
        super().__init__()  # call to parent class

    def __str__(self):
        """
        string representation of the layer.

        :return: str: returns the name of the activation function and it's function
        """
        return f"Activation: Sigmoid"

    def forward(self, x: np.array):
        """
        performs a sigmoid(x) operation of parameter x.

        :param x: numpy.array: data which does into the sigmoid function
        :return: numpy.array of sigmoid(x)
        """

        # line of code to prevent overflow in numpy since this is a common issue
        signal = np.clip(x, -150000, 150000)

        # perform the sigmoid operation with the overflow signal and data x
        # safe the result in global attribute cache, since the result will be needed for back propagation as well
        self.cache = 1.0 / (1.0 + np.exp(-1.0 * signal))
        return self.cache

    def backward(self, z: np.array):
        """
        takes an array with error values and performs a back propagation step for the Sigmoid activation function.
        will will return a new error array with respect to the Sigmoid function.

        :param z: np.array: array of error values
        :return: returns the derivative of sigmoid with respect to it's previous calculated output and error z
        """

        return (self.cache * (1.0 - self.cache)) * z

class RelU(Layer):
    """"
    RelU or Rectified Linear Unit function as activation function which will output the input directly if it's
    positive or put it to zero if not. RelU inherits form Layer
    """

    def __init__(self):
        super().__init__()  # call to parent class

    def __str__(self):
        """
        string representation of the activation layer and it's function
        :return: str: Type and Function
        """
        return "Activation: RelU"

    def forward(self, x: np.array):
        """
        RelU(x) operation - will out put the same array but with values < 0 as 0
        :param x: numpy.array: data to which goes into the RelU function

        :return: numpy.array: returns the numpy array with modified values < 0
        """

        # make a copy of x for the back propagation process
        self.cache = x.copy()

        # apply function and return new array
        return np.maximum(0, x)

    def backward(self, z: np.array):
        """
        creates a derivative of the RelU with respect to it's input and multiplies it with the incoming error array.

        :param z: numpy.array: array of error values to be back propagated
        :return: numpy.array: returns an array of new errors with the RelU layer taken into account
        """

        # as usual values smaller 0 will be set to zero. But since it's the derivative values greater 0 will be set to
        # 1 and the error is multiplied in order to propagate the error back.
        return np.where(self.cache < 0, 0, 1) * z

class Softmax:
    """
    provides only one function with is forward since backward is not needed. Produces a probability distribution over
    k input values.
    """

    def forward(self, x: np.array):
        """
        performs a Softmax(x) operation.
        :param x: numpy.array: data which will be feed into the Softmax function
        :return: numpy.array for a smooth probability distribution over input x
        """

        # operation to avoid overflow in numpy
        x = np.clip(x, -150000, 150000)

        # create an exponential of x and subtract the max value of x in order to make the function more stable
        exps = np.exp(x - np.max(x))

        # return results of softmax function
        return exps / np.sum(exps)


from sympy import expand, sympify
from sympy.core.sympify import SympifyError

def product(expr1, expr2):
    '''Product of two expressions'''
    prod = expand(expr1 * expr2)
    print(prod)

if __name__ == '__main__':
    try:
        expr1 = sympify(input('Enter the first expression: '))
        expr2 = sympify(input('Enter the second expression: '))
    except SympifyError:
        print('Invalid input')
    else:
        product(expr1, expr2)


#---------------------------------------------------------------------------------
# Generate a distribution of points following the Soneira-Peebles fractal model
# Author: Pablo Villanueva Domingo
# Last update: 25/6/20
#---------------------------------------------------------------------------------

import numpy as np
import matplotlib.pyplot as plt

#--- PARAMETERS ---#

# Reduction factor for the radii
lamb = 2
# Number of levels (not including the zero level)
n_levels = 5
# Maximum radius
R0 = 1000
# Size of the window
dimx = R0 + R0/lamb
# Mean number of points per cluster
eta = 5
# If 1, the number of points per cluster is a poisson process with eta as average number
eta_random = 0
# If 1, show scatter points
show_scatter = 1
# If 1, show scatter points
show_circles = 0
# Colors per level
cols = ["cyan", "r", "b", "g", "purple", "m", "k"]
#cols = []
#for i in range(n_levels+1):
#    cols.append("k")
# Random seed
seed = 0
# Size scatter points
s0 = 100
# Transparency for scatter points
alphapoints = .5

#--- FUNCTIONS ---#

# Poisson process in a disk
def poisson_process(eta, originx, originy, Rparent):

    if eta_random:
        num_points = np.random.poisson(eta)
    else:
        num_points = eta

    theta = 2.*np.pi*np.random.uniform(0, 1, num_points)
    rho = Rparent*np.sqrt(np.random.uniform(0, 1, num_points))

    xx = originx + rho*np.cos(theta)
    yy = originy + rho*np.sin(theta)

    return xx, yy

# Soneira-Peebles point process
def soneira_peebles_model(n_levels, eta, lamb, R0, seed):

    fig, ax = plt.subplots()
    margins = {  #     vvv margin in inches
    "left"   :     0.,
    "bottom" :     0.,
    "right"  :  1.,
    "top"    :  1.}
    fig.subplots_adjust(**margins)

    np.random.seed(seed=seed)
    Rparent = R0
    xparents, yparents = [0.], [0.]

    if show_scatter:
        ax.scatter(0., 0., s=s0, color=cols[0], alpha = alphapoints)
    if show_circles:
        circle = plt.Circle((0., 0.), Rparent, color=cols[0], alpha = 0.1)
        ax.add_artist(circle)

    for n in range(n_levels):
        R = Rparent/lamb
        pointsx, pointsy = [], []
        for i, j, in zip(xparents,yparents):
            originx, originy = i, j
            xx, yy = poisson_process(eta, originx, originy, Rparent)
            pointsx.extend(xx); pointsy.extend(yy)

        if show_scatter:
            ax.scatter(pointsx, pointsy, s=s0*(R/R0)**(2.), color=cols[n+1], alpha = alphapoints)
        if show_circles:
            for x, y in zip(pointsx, pointsy):
                circle = plt.Circle((x, y), R, color=cols[n+1], alpha = 0.3)
                ax.add_artist(circle)

        xparents, yparents = pointsx, pointsy
        Rparent = R

    if show_circles:
        plt.xlim([-dimx,dimx])
        plt.ylim([-dimx,dimx])
    plt.axis("off")
    plt.gca().set_aspect('equal', adjustable='box')
    plt.savefig("Plots/soneirapeebles_levels_%d_eta_%d_lambda_%d_seed_%d_eta_random_%d.png"%(n_levels, eta, lamb, seed, eta_random), bbox_inches='tight', dpi=300)
    plt.show()
    plt.close(fig)

#--- MAIN ---#

for seed in range(5):
    soneira_peebles_model(n_levels, eta, lamb, R0, seed)


import numpy as np
from scipy.optimize import fmin_cg

# Subtracting the mean of each Product's rating from given array of user ratings
def normalize_ratings(ratings):
    mean_ratings = np.nanmean(ratings, axis=0)
    return ratings - mean_ratings, mean_ratings

# Cost function for low rank matrix factorization
def cost(X, *args):

    num_users, num_products, num_features, ratings, mask, regularization_amount = args

    # Unroll P and Q
    P = X[0:(num_users * num_features)].reshape(num_users, num_features)
    Q = X[(num_users * num_features):].reshape(num_products, num_features)
    Q = Q.T

    # Calculate current cost
    return (np.sum(np.square(mask * (np.dot(P, Q) - ratings))) / 2) + ((regularization_amount / 2.0) * np.sum(np.square(Q.T))) + ((regularization_amount / 2.0) * np.sum(np.square(P)))

# Calculate the cost gradients with the current P and Q.
def gradient(X, *args):
    num_users, num_products, num_features, ratings, mask, regularization_amount = args

    # Unroll P and Q
    P = X[0:(num_users * num_features)].reshape(num_users, num_features)
    Q = X[(num_users * num_features):].reshape(num_products, num_features)
    Q = Q.T

    # Calculating current gradients for both P and Q
    P_grad = np.dot((mask * (np.dot(P, Q) - ratings)), Q.T) + (regularization_amount * P)
    Q_grad = np.dot((mask * (np.dot(P, Q) - ratings)).T, P) + (regularization_amount * Q.T)

    # Return the gradients as one rolled-up array as expected by fmin_cg
    return np.append(P_grad.ravel(), Q_grad.ravel())

# Factorising rating arrays into two latent featured arrays (User features & Product Features)
def low_rank_matrix_factorization(ratings, mask=None, num_features=15, regularization_amount=0.01):
    num_users, num_products = ratings.shape

    # If no mask is provided, consider all 'NaN' elements as missing and create a mask.
    if mask is None:
        mask = np.invert(np.isnan(ratings))

    # Replace NaN values with zero
    ratings = np.nan_to_num(ratings)

    # Create P and Q and fill with random numbers to start
    np.random.seed(0)
    P = np.random.randn(num_users, num_features)
    Q = np.random.randn(num_products, num_features)

    # Roll up P and Q into a contiguous array as fmin_cg expects
    initial = np.append(P.ravel(), Q.ravel())

    # Create an args array as fmin_cg expects
    args = (num_users, num_products, num_features, ratings, mask, regularization_amount)

    # Call fmin_cg to minimize the cost function and this find the best values for P and Q
    X = fmin_cg(cost, initial, fprime=gradient, args=args, maxiter=3000)

    # Unroll the new P and new Q arrays out of the contiguous array returned by fmin_cg
    nP = X[0:(num_users * num_features)].reshape(num_users, num_features)
    nQ = X[(num_users * num_features):].reshape(num_products, num_features)

    return nP, nQ.T

# Calculating Root Mean Squared Error between matrix of real & predicted ratings
def RMSE(real, predicted):
    return np.sqrt(np.nanmean(np.square(real - predicted)))


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

if not sys.warnoptions:
    warnings.simplefilter("ignore")

path_X = "logisticX.csv";
path_Y = "logisticY.csv";

read_1 = [];
with open(path_X) as csvfile:
    reader = csv.reader(csvfile, quoting=csv.QUOTE_NONNUMERIC) # change contents to floats
    m = 0;
    for row in reader: # each row is a list
        read_1.append(row)
        m+=1;

X_rawest = np.asarray(read_1)
mu = np.mean(X_rawest);
sigma = np.std(X_rawest);
X_raw = (X_rawest - mu)/sigma;
X_log = np.c_[ np.ones((m,1)), X_raw]

read_2 = [];
with open(path_Y) as csvfile:
    reader = csv.reader(csvfile, quoting=csv.QUOTE_NONNUMERIC) # change contents to floats
    for row in reader: # each row is a list
        read_2.append(row)

Y_log = np.asarray(read_2)

def sigmoid(z):
    a = 1.0/(1+ np.exp(-1.0*z));
    return a;

def compute_cost(X,Y,theta):
    m = Y.shape[0];
    h = sigmoid(np.matmul(X,theta));
    J =  (-1.0/m) * np.sum(Y * np.log(h) + (1-Y)* np.log(1- h));
    return J;

def der_cal(X,Y,theta,alpha):
    f1 = np.zeros(theta.shape);
    for i in range(theta.shape[0]):
        theta_plus = np.copy(theta);
        theta_minus = np.copy(theta);
        theta_plus[i] = theta_plus[i] + alpha;
        theta_minus[i] = theta_minus[i] - alpha;
        J_plus = compute_cost(X,Y,theta_plus);
        J = compute_cost(X,Y,theta);
        f1[i] = (J_plus - J)/(alpha);
    return f1;

def hessian_cal(X,Y,theta, alpha):
    H = np.zeros([theta.shape[0],theta.shape[0]]);
    for i in range (theta.shape[0]):
        theta_plus = np.copy(theta);
        theta_minus = np.copy(theta);
        theta_plus[i] = theta_plus[i] + alpha;
        theta_minus[i] = theta_minus[i] - alpha;
        J_plus = der_cal(X,Y,theta_plus,alpha);
        J = der_cal(X,Y,theta,alpha);
        H[i] = np.reshape((J_plus - J)/(alpha),3);
    return H;

def newton_method( X, Y, theta, alpha, num_iter):
    theta = np.zeros((X_log.shape[1],1));
    for i in range(num_iter):
        f1 = der_cal(X_log, Y_log, theta, alpha);
        H = hessian_cal(X_log, Y_log, theta, alpha);
        delta = np.matmul(np.linalg.pinv(H),f1);
        theta = theta - delta;
        if(np.linalg.norm(delta)<np.power(10.0,-5)):
            break;
    return theta;

alpha = 0.1;
num_iter = 500;
theta = np.zeros((X_log.shape[1],1));

theta_final = newton_method(X_log, Y_log, theta, alpha, num_iter);
print("Theta0 = ",theta_final[0,0]);
print("Theta1 = ",theta_final[1,0]);

input("Press Enter to go to part (b)")

plt.scatter(X_log[:,1], X_log[:,2], c=Y_log.reshape(-1))
ax = plt.gca()
x1 = np.arange(np.min(X_log[:,1]), np.max(X_log[:,1]), 0.01);
y1 = -(theta_final[0] + theta_final[1]*x1)/theta_final[2];
plt.plot(x1, y1, '-', c="red", linewidth=2.0)
plt.title('Logistical Regression');
plt.xlabel('x1');
plt.ylabel('x2');
plt.show();


#!/usr/bin/env/ python
# -*- coding: utf8 -*-

# 0 - import os
#     os.system('echo "0 5\n5 1\n12 1\n17 5\n22 5\n22 10\n29 10\n29 5\n34 5\n" > punti.dat')

# 1 - Leggere la serie di punti dal file appena creato con numpy

from __future__ import division # no more zero integer divisions
import os
import sys
import numpy as np
import matplotlib.pyplot as plt

print "Writing points..."

#os.system('echo "0 5\n5 0\n12 0\n17 5\n22 5\n23 7\n27 7\n28 5\n34 5\n" > punti.dat')
os.system('echo "0 5\n5 0\n20 0\n25 5\n40 5\n41 7\n44 7\n45 5\n50 5\n" > punti.dat')

print "Reading points from file..."
points_a = np.genfromtxt("punti.dat")
print "Read \n", points_a

# 2 - Passare da array a lista

print "Converting point array to list..."
points_l = []
for i in points_a:
	points_l.append([i[0], i[1]])

print "Points list \n", points_l

# 3 - Creare una funzione che interpola i punti in modo che tra due punti ce ne siano altri n
# inserendo i punti in mezzo con la sintassi delle liste; interpolare aggiungendo 3 punti
# ad ogni intervallo

def get_y(p1, p2, x):
	"""This function return the y value given a x value and two points
	to compute a straight line
	"""
	m = (p2[1]-p1[1])/(p2[0]-p1[0])
	q = -m*p2[0] + p2[1]
	y = m*x + q
	return y

def interp(l, n):
	"""Interpolate two points in a straight line inserting n new points
	"""
	temp = l
	for i in range(len(temp)-1):
		p1 = temp[i]
		p2 = temp[i+1]
		x = np.linspace(p1[0],p2[0], n+2)[1:-1]
		nl = []
		for j in range(len(x)):
			new_y = get_y(p1, p2, x[j])
			nl.append([x[j], new_y])
			#print "new sub list ", nl
		l = l[:i+i*n+1]+nl+l[i+i*n+1:]
	return l

new_points_l = interp(points_l, 3)

print "New points list \n", new_points_l

# 5 - Riconvertire da liste a vettore

new_points_a = np.array(new_points_l)

print "Interpolated list size ", new_points_a.shape[0]

# 6 - Trovare il minimo e il massimo dei punti in x e y con numpy e a mano con
# dei cicli (creando una funzione apposta) e confrontare il risultato

numpy_min_x = new_points_a[:,0].min()
numpy_min_y = new_points_a[:,1].min()
numpy_max_x = new_points_a[:,0].max()
numpy_max_y = new_points_a[:,1].max()

def find_min_max(l):
	l_min = 0
	l_max = 0
	for i in l:
		if i < l_min: l_min = i
		if i > l_max: l_max = i
	return l_min, l_max

hm_min_x, hm_max_x = find_min_max(new_points_a[:,0])
hm_min_y, hm_max_y = find_min_max(new_points_a[:,1])

print "numpy values ", numpy_min_x, numpy_max_x, numpy_min_y, numpy_max_y
print "handmade values ", hm_min_x, hm_max_x, hm_min_y, hm_max_y

# 7 - simmetrizzare le x dei punti rispetto a quello di indice 3 (= il quarto),
# equivalente a tracciare la retta x = coord_x[3] e riflettere tutti i punti
# con ascissa maggiore rispetto a tale retta

pivot_idx = int(new_points_a.shape[0]/2-4)
pivot = new_points_a[pivot_idx][0]

for i in range(new_points_a.shape[0]):
	if new_points_a[i][0] > pivot:
		new_points_a[i][0] = pivot - (new_points_a[i][0] - pivot)

# 7 - fare il plot dei punti e unirli con segmenti di colore nero e della retta passante per
# [0, 3] [35, 3] di colore blu e salvarlo con
# plt.savefig("plot.png")

plt.plot(new_points_a[:,0], new_points_a[:,1], linestyle = "-", marker = ".", markersize = 2, color = "black")
plt.plot([0, 35], [3, 3], linestyle = "-", marker = "", color = "blue")

plt.savefig("plot.png")

# 8 - Calcolare con bisezione e newmann dove la funzione data interseca y = 3 per x < 7
# e valutare chi ci mette meno con la funzione scritta prima


import numpy as np
from scipy import stats

class GaussianMixtureModel():
    """Density estimation with Gaussian Mixture Models (GMM).

    You can add new functions if you find it useful, but **do not** change
    the names or argument lists of the functions provided.
    """
    def __init__(self, X, K):
        """Initialise GMM class.

        Arguments:
          X -- data, N x D array
          K -- number of mixture components, int
        """
        self.X = X
        self.n = X.shape[0]
        self.D = X.shape[1]
        self.K = K

    def E_step(self, mu, S, pi):
        """Compute the E step of the EM algorithm.

        Arguments:
          mu -- component means, K x D array
          S -- component covariances, K x D x D array
          pi -- component weights, K x 1 array

        Returns:
          r_new -- updated component responsabilities, N x K array
        """
        # Assert that all arguments have the right shape
        assert(mu.shape == (self.K, self.D) and\
               S.shape  == (self.K, self.D, self.D) and\
               pi.shape == (self.K, 1))
        r_new = np.zeros((self.n, self.K))

        # Task 1: implement the E step and return updated responsabilities
        # Write your code from here...

        # ... to here.
        assert(r_new.shape == (self.n, self.K))
        return r_new

    def M_step(self, mu, r):
        """Compute the M step of the EM algorithm.

        Arguments:
          mu -- previous component means, K x D array
          r -- previous component responsabilities,  N x K array

        Returns:
          mu_new -- updated component means, K x D array
          S_new -- updated component covariances, K x D x D array
          pi_new -- updated component weights, K x 1 array
        """
        assert(mu.shape == (self.K, self.D) and\
               r.shape  == (self.n, self.K))
        mu_new = np.zeros((self.K, self.D))
        S_new  = np.zeros((self.K, self.D, self.D))
        pi_new = np.zeros((self.K, 1))

        # Task 2: implement the M step and return updated mixture parameters
        # Write your code from here...

        # ... to here.
        assert(mu_new.shape == (self.K, self.D) and\
               S_new.shape  == (self.K, self.D, self.D) and\
               pi_new.shape == (self.K, 1))
        return mu_new, S_new, pi_new

    def train(self, initial_params):
        """Fit a Gaussian Mixture Model (GMM) to the data in matrix X.

        Arguments:
          initial_params -- dictionary with fields 'mu', 'S', 'pi' and 'K'

        Returns:
          mu -- component means, K x D array
          S -- component covariances, K x D x D array
          pi -- component weights, K x 1 array
          r -- component responsabilities, N x K array
        """
        # Assert that initial_params has all the necessary fields
        assert(all([k in initial_params for k in ['mu', 'S', 'pi']]))

        mu = np.zeros((self.K, self.D))
        S  = np.zeros((self.K, self.D, self.D))
        pi = np.zeros((self.K, 1))
        r  = np.zeros((self.n, self.K))

        # Task 3: implement the EM loop to train the GMM
        # Write your code from here...

        # ... to here.
        assert(mu.shape == (self.K, self.D) and\
               S.shape  == (self.K, self.D, self.D) and\
               pi.shape == (self.K, 1) and\
               r.shape  == (self.n, self.K))
        return mu, S, pi, r

if __name__ == '__main__':
    np.random.seed(43)

    ##########################
    # You can put your tests here - marking
    # will be based on importing this code and calling
    # specific functions with custom input.
    # Do not write code outside the class definition or
    # this if-block.
    ##########################


import numpy as np
from exercises.nnCostFunction import getCost
from exercises.sigmoid import sigmoidGradient
from exercises.randomInitialize import rand

input_layer_size  = 400  # 20x20 Input Images of Digits
hidden_layer_size = 25   # 25 hidden units
num_labels = 10          # 10 labels, from 1 to 10
                          # (note that we have mapped "0" to label 10)
# =========== Part 1: Loading data ===============

X_org = np.genfromtxt('../../ex3/X_input.csv', delimiter=',')
y_org = np.genfromtxt('../../ex3/y_output.csv', delimiter=',')

y_org = y_org.reshape((len(y_org), 1))

m, n = X_org.shape

print('X_org shape', X_org.shape)
print('y_org shape', y_org.shape)

# ================ Part 2: Loading Parameters ================

Theta1 = np.genfromtxt('../data/Theta1.csv', delimiter=',')
print('Loaded Theta1 shape', Theta1.shape)

Theta2 = np.genfromtxt('../data/Theta2.csv', delimiter=',')
print('Loaded Theta2 shape', Theta2.shape)

# unrolling parameters
# First transpose, then normal flatten
tmpTheta1 = Theta1.flatten()
tmpTheta1 = tmpTheta1.reshape((len(tmpTheta1), 1))
print('Unrolling Theta1 shape', tmpTheta1.shape)

tmpTheta2 = Theta2.flatten()
tmpTheta2 = tmpTheta2.reshape((len(tmpTheta2), 1))
print('Unrolling Theta2 shape', tmpTheta2.shape)

nn_params = np.concatenate((tmpTheta1, tmpTheta2), axis=0)

# ================ Part 3: Compute Cost (Feedforward) ================
'''
lambd = 0
theta1 = np.arange(1,7).reshape((2, 3))
print('Original t1', theta1)
theta2 = np.arange(1,13).reshape((4, 3))
print('original t2', theta2)
tmpTheta1 = theta1.flatten()
tmpTheta1 = tmpTheta1.reshape((len(tmpTheta1), 1))

tmpTheta2 = theta2.flatten()
tmpTheta2 = tmpTheta2.reshape((len(tmpTheta2), 1))
nn_params = np.concatenate((tmpTheta1, tmpTheta2), axis=0)
cost = getCost(nn_params, 2, 2, 4, 1, 1, lambd)
'''
lambd = 0 # With out regularization
#print('nn_params 656', nn_params[655], '')
cost = getCost(np.copy(nn_params), input_layer_size, hidden_layer_size, num_labels, X_org, y_org, lambd)
print('Cost at parameters (loaded from ex4weights):', cost, ' value should be 0.287629')

# Implementing Regularization
print('\nChecking Cost Function (w/ Regularization) ... \n')

lambd = 1 # with regularization of lambda = 1

cost = getCost(np.copy(nn_params), input_layer_size, hidden_layer_size, num_labels, X_org, y_org, lambd)

# After regularization cost
print('Cost at parameters (loaded from ex4weights):', cost, '\n(this value should be about 0.383770)\n')

# Part 5 Evaluating Sigmoid Gradient

tmpArray = np.array([-1, -0.5, 0, 0.5, 1])
sigmGResults = sigmoidGradient(tmpArray)

print('Sigmoid Gradient Results', sigmGResults)

# Part 6 Random Initialization
initial_Theta1 = rand(input_layer_size, hidden_layer_size)
initial_Theta2 = rand(hidden_layer_size, num_labels)


import numpy as np

def relative_angle_distance_aux(road_points1, road_points2):
    """ Relative angle distance, similar to the approach in
    Rotation Invariant Distance Measures for Trajectories (http://alumni.cs.ucr.edu/~mvlachos/pubs/kdd04.pdf)
    """

    k = np.min([road_points1.shape[0], road_points2.shape[0]])
    dxs1 = road_points1[1:k, 0] - road_points1[0:k-1, 0]
    dys1 = road_points1[1:k, 1] - road_points1[0:k-1, 1]

    dxs2 = road_points2[1:k, 0] - road_points2[0:k-1, 0]
    dys2 = road_points2[1:k, 1] - road_points2[0:k-1, 1]

    delta_thetas1 = np.arctan2(dys1, dxs1)
    delta_thetas2 = np.arctan2(dys2, dxs2)

    delta_ss1 = np.sqrt(dxs1 * dxs1 + dys1 * dys1)
    delta_ss2 = np.sqrt(dxs2 * dxs2 + dys2 * dys2)

    ss1 = np.zeros(delta_ss1.shape[0] + 1)
    ss2 = np.zeros(delta_ss2.shape[0] + 1)
    ss1[1:] = np.cumsum(delta_ss1)
    ss2[1:] = np.cumsum(delta_ss2)

    # we want to take integral of |\Delta\Theta_1(s) - \Delta\Theta_2(s)|

    # print(ss1)
    # print(delta_thetas1)
    # print("")
    # print(ss2)
    # print(delta_thetas2)
    # print("")

    all_ss = [0.0]
    diff_delta_thetas = []

    index1 = 0
    index2 = 0
    while True:
        # print(all_ss[-1])
        # print(delta_thetas1[index1])
        # print(delta_thetas2[index2])
        # print("")
        if index1 < delta_thetas1.shape[0] and index2 < delta_thetas2.shape[0]:
            diff_delta_thetas.append(np.abs(delta_thetas1[index1] - delta_thetas2[index2]))
        else:
            break

        if ss1[index1 + 1] < ss2[index2 + 1]:
            index1 += 1
            all_ss.append(ss1[index1])
        else:
            index2 += 1
            all_ss.append(ss2[index2])

    integral = 0.0
    total_s = all_ss[-1]
    for i in range(len(all_ss) - 1):
        integral += diff_delta_thetas[i] * (all_ss[i + 1] - all_ss[i])

    return (integral, total_s)

def relative_angle_distance(road_points1, road_points2):
    (integral, total_s) = relative_angle_distance_aux(road_points1, road_points2)
    return integral

def normalized_relative_angle_distance(road_points1, road_points2):
    (integral, total_s) = relative_angle_distance_aux(road_points1, road_points2)
    return integral / total_s

# relative_angle_distance_aux(np.array([[0, 0], [2, 7], [2.5, 9], [3, 15], [10, 15]]),
#                             np.array([[0, 3], [2, 5], [1, 10], [-1, 9], [-2, 6], [-4.0, 2]]))
#
# print(relative_angle_distance(np.array([[0, 0], [2, 7], [2.5, 9], [3, 15], [10, 15]]),
#                               np.array([[0, 3], [2, 5], [1, 10], [-1, 9], [-2, 6], [-4.0, 2]])))
#
# print(normalized_relative_angle_distance(np.array([[0, 0], [2, 7], [2.5, 9], [3, 15], [10, 15]]),
#                                          np.array([[0, 3], [2, 5], [1, 10], [-1, 9], [-2, 6], [-4.0, 2]])))


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

def Lorenz63(t, y, sig, rho, beta):
# Lorenz '63 model
    out = np.zeros_like(y)
    out[0] = sig * ( y[1] - y[0] )
    out[1] = y[0] * ( rho - y[2] ) - y[1]
    out[2] = y[0] * y[1] - beta * y[2]
    return out

def forwardModel_r( xt0, time, rayleigh, prandtl, b):
#   perform integration of Lorentz63 model
#   default integrator for solve_ivp is RK4

    rho  = rayleigh
    beta = b
    sig  = prandtl

    myParams = np.array( [sig, rho, beta], dtype=float )
    tstart = time[0]
    tend = time[-1]
    y0 = np.array( xt0, dtype=float )
    sol = solve_ivp( Lorenz63, [tstart,tend], y0, args=myParams, dense_output=True)
    xt = sol.sol(time)

    return xt


import numpy as np
import os

def laplacian(Z, dx):
    """
    Calculates laplace operator for particular matrix
    :param Z: Combination of two initial matrices
    :param dx: float space step
    :return: Boundaries of matrix
    """
    Ztop = Z[0:-2, 1:-1]
    Zleft = Z[1:-1, 0:-2]
    Zbottom = Z[2:, 1:-1]
    Zright = Z[1:-1, 2:]
    Zcenter = Z[1:-1, 1:-1]
    return (Ztop + Zleft + Zbottom + Zright -
            4 * Zcenter) / dx**2

def turing(a, b, time):
    """
    calculates the turing patterns over time
    :param a: concentration of a
    :param b: concentration of b
    :param time: time that we want result for
    :return: turing pattern for particular time
    """
    size = 100
    dx = 2. / size  # space step
    tau = 2.00
    k = 0

    T = 6.0  # total time
    dt = .001  # time step
    n = int(T / dt)  # number of iterations
    step_plot = n // 6

    U = np.load(os.path.join('initialmatrices', 'U_initial.npy'))
    V = np.load(os.path.join('initialmatrices', 'V_initial.npy'))

    for i in range(n):

        # We compute the Laplacian of u and v.
        deltaU = laplacian(U, dx)
        deltaV = laplacian(V, dx)
        # We take the values of u and v inside the grid.
        Uc = U[1:-1, 1:-1]
        Vc = V[1:-1, 1:-1]
        # We update the variables.
        U[1:-1, 1:-1], V[1:-1, 1:-1] = \
            Uc + dt * (a * deltaU + Uc - Uc ** 3 - Vc + k), \
            Vc + dt * (b * deltaV + Uc - Vc) / tau

        # Neumann conditions: derivatives at the edges
        # are null.
        for Z in (U, V):
            Z[0, :] = Z[1, :]
            Z[-1, :] = Z[-2, :]
            Z[:, 0] = Z[:, 1]
            Z[:, -1] = Z[:, -2]

        if i * dt == time:
            return U


# Import all neccesary packages to solve assignment
import numpy as np # for creating arrays and essenatial math tools
from scipy import optimize # for finding optimal consumption and labor
%matplotlib inline
import matplotlib.pyplot as plt
%load_ext autoreload
%autoreload 2

# set all of the parameters of the labor supply problem
m = 1
v = 10
e = 0.3
t0 = 0.4
t1 = 0.1
k = 0.4

# a. Create utility function that will be optimized to find L* and C*
def u(L, c, v, e):
    ''' calculates utility based upon combinations of labor supply and consumption.

    Arguments:
    L(float) = labor supply
    c(float) = consumption
    v(float) = disutility of work
    e(float)= labor supply elasticity

    Returns:
    utility
    '''
    return np.log(c) - v*((L**(1+(1/e)))/(1 + (1/e)))

# b. Create best choice function used in minimization problem
def best_choices(L, w, m, v, e, t0, t1, k):
    ''' Function used to calculate negative utility

    Arguments:
    L(float) = labor supply
    w(float) = wage rate
    m(float) = initial cash
    v(float) = disutility of work
    e(float) = labor supply elasticity
    t0(float) = base tax level
    t1(float) = upper tax level
    k(float) = cutoff

    Returns:
    Negative utility function
    '''
    c = m + w*L - (t0*w*L + t1*max(w*L - k, 0))

    return -u(L, c, v, e)

# c. minimize negative utility function

def optimization(w, m, v, e, t0, t1, k):
    ''' Function used to calculate optimal labor and consumption at a given wage

    Arguments:
    w(float) = wage rate
    m(float) = initial cash
    v(float) = disutility of work
    e(float) = labor supply elasticity
    t0(float) = base tax level
    t1(float) = upper tax level
    k(float) = cutoff

    Returns:
    L = optimal labor supply at a given wage
    c = optimal consumption at a given wage

    '''
    sol = optimize.minimize_scalar(best_choices, method='bounded', bounds=(0,1), args=(w, m, v, e, t0, t1, k))

    L = sol.x
    c = m + w*L - (t0*w*L + t1*max(w*L - k, 0))
    return [L, c]

# a. create empty lists for Labor and consumption
L_best = [] # List of optimal values of labor for given wages
c_best = [] # list of optimal values of consumption given wages
N = 10000 # Number of wages to loop through

# b. loop through wage levels of 0.5 and 1.5 to find optimal consumption and labor
w = np.linspace(0.5, 1.5, N) # range of wages between 0.5 and 1.5 for optimizer to use

for w_now in w: # wage loop
    optimal_par = optimization(w_now, m, v, e, t0, t1, k)
    L_best.append(optimal_par[0]) #append optimal labor values
    c_best.append(optimal_par[1]) # append optimal consumption values

# c. plotting optimal consumption and labor as functions of wage
plt.style.use('dark_background') # setting personal style

fig = plt.figure(figsize=(10,4))

# Left plot - Optimal consumption to wage
ax_left = fig.add_subplot(1,2,1)
ax_left.plot(w, c_best)

ax_left.set_title('Optimal Consumption Relative to Wage Rate')
ax_left.set_xlabel('$Wage Rate$')
ax_left.set_ylabel('$Optimal Consumption$')
ax_left.grid(True)

# Right plot - Optimal Labor to wage
ax_right = fig.add_subplot(1,2,2)
ax_right.plot(w, L_best)

ax_right.set_title('Optimal Labor Supply Relative to Wage Rate')
ax_right.set_xlabel('$Wage Rate$')
ax_right.set_ylabel('$Labor Supply$')
ax_right.grid(True)

# a. Calculate total tax revenue based upon a given population
def totaltaxrevenue(w, v=10, e=0.3, t0=0.4, t1=0.1, k=0.4):
    ''' Calculates the total tax revenue recieved by a given population size and wage

    Arguments:
    w(float) = wage rate
    m(float) = initial cash
    v(float) = disutility of work
    e(float) = labor supply elasticity
    t0(float) = base tax level
    t1(float) = upper tax level
    k(float) = cutoff

    Returns:
    total tax revenue that will be recieved from the population'''
    T= 0
    for w_now in w:
        optimal_val = optimization(w_now, m, v, e, t0, t1, k)
        L = optimal_val[0]
        T += (t0*w_now*L + t1*max(w_now*L - k, 0))
    return T

# b. print total tax revenue collected
print("Total tax revenue collected" + " " + str(totaltaxrevenue(w)))

print("Total tax revenue collected" + " " + str(totaltaxrevenue(w, e=0.1)))

# a. Create an objective function to maxamize taxes
def best_tax_levels(tax, w, v, e, m):
    ''' Create function that outputs negative total tax revenue that will be used to find optimal values of T0, T1, and K

    Arguments:
    tax(float) = list of various tax paramaters
    w(float) = wage rate

    Results:
    Negative total tax revenue
    '''
    tax_low = tax[0]
    tax_high = tax[1]
    tax_cutoff = tax[2]

    return -totaltaxrevenue(w, v, e, tax_low, tax_high, tax_cutoff)

initial_guess = [.1, .1, .1] # Initial guess for optimizing tax revenue
sol = optimize.minimize(best_tax_levels, initial_guess, method='Nelder-Mead', args=(w, e, v, m))

tax1 = sol.x[0] # Optimal standard labor income tax
tax2 = sol.x[1] # Optimal top bracket labor income tax
tax3 = sol.x[2] # optimal cutoff

print("Standard labor income tax " + str(tax1))
print("Top bracket labor income tax " + str(tax2))
print("Cutoff " + str(tax3))


########################
### PERCEPTRON CLASS ###
########################

# Import libraries.
import numpy as np
import pandas as pd
import math
import random
from itertools import product

"""
- When the perceptron criterion or hinge loss are used to determine the loss function, it is assumed that the target values are -1 or 1.
- Sigmoid activation function is used with log_likelihood loss function. This model is known as logistic regression, since our objective is to predict a probability.
- Finally, the linear activation function is used with least square regression loss functions. This model is knows as linear regressios; its objective is to predict real values.
"""

# Clase Perceptrón.
class Perceptron:

    # Método constructor.
    def __init__(self, num_neurons, activation = "sign", criterion = "perceptron_criterion", learning_rate = 1):

        if(criterion == "hinge_loss" or criterion == "perceptron_criterion") and (activation != "sign" and activation != "linear"):
            raise Exception(criterion.replace("_", " ").capitalize() + " can only be use with sign or linear activation function.")
        if(criterion == "log_likelihood" and activation != "sigmoid"):
            raise Exception(criterion.replace("_"," ").capitalize() + " can only be used with Sigmoid activation function")
        if(criterion == "least_squares_regression" and activation != "linear"):
            raise Exception(criterion.replace("_"," ").capitalize() + " can only be used with linear activation function")
        if(criterion == "logistic_regression_alternate" and activation != "linear"):
            raise Exception(criterion.replace("_"," ").capitalize() + " can only be used with linear activation function")

        self.num_neurons = num_neurons
        self.activation = getattr(self, "_" + activation)
        self.weights = np.random.rand(1, num_neurons)[0]
        self.learning_rate = learning_rate
        self.criterion = getattr(self, "_" + criterion)
        self.loss_function = getattr(self, "_" + criterion + "_loss")

    # Forward method: computational step.
    def forward(self, vector_x):
        dotproduct = np.dot(vector_x, self.weights)
        return dotproduct

    """
    Funciones de activación.
    """
    # Sign activation.
    def _sign(self, x):
        return np.sign(x)

    # Linear activation.
    def _linear(self, x):
        return x

    # Sigmoid activation.
    def _sigmoid(self, x):
        return 1 / (1 + math.exp(-x))

    # Tanh activation.
    def _tanh(self, x):
        return 2 * self.sigmoid(2*x)-1

    # Relu activation.
    def _relu(self, x):
        return max(0, x)

    # HardTanh activation.
    def _hardTanh(self, x):
        return max(min(x, 1), -1)

    """
    Loss functions.
    """
    # Perceptron criterion.
    def _perceptron_criterion_loss(self,y_true,y_predicted):
        loss = max(0, -y_true * y_predicted)
        return loss

    # Hinge Loss.
    def _hinge_loss_loss(self,y_true,y_predicted):
        loss = max(0, 1 + (-y_true * y_predicted))
        return loss

    # Logistic regression.
    def _logistic_regression_loss(self,y_true,y_predicted):
        loss = -math.log(math.abs(y_true*(1/2)-0.5+y_predicted))
        return loss

    # Least squares regression.
    def _least_squares_regression_loss(self,y_true,y_predicted):
        loss = (y_true-y_predicted)**2
        return loss

    # Logistic regression (alternate).
    def _logistic_regression_alternate_loss(self, y_true, y_predicted):
        loss = math.log(1 + math.exp(-y_true * y_predicted))
        return loss

    # Widrow hoff loss.
    def _widrow_hoff_loss(self,y_true,y_predicted):
        loss = (y_true-y_predicted)**2
        return loss

    """
    Gradient update functions.
    """
    # 1. Perceptron criterion.
    def _perceptron_criterion(self, y_true, y_predicted, vector_x):
        if y_true != y_predicted:
            gradient = np.dot((y_true - y_predicted), vector_x)
            return gradient
        return 0

    # 2. Hinge Loss.
    def _hinge_loss(self, y_true, y_predicted, vector_x):
        gradient = np.dot(y_true, vector_x)
        return gradient

    #3. Log likelihood.
    def _log_likelihood(self, y_true, y_predicted, vector_x):
        gradient = -(y_true*vector_x)/(1+math.exp(y_true*y_predicted))
        return gradient

    #4. Least squares.
    def _least_squares_regression(self, y_true, y_predicted, vector_x):
        gradient = np.dot((y_true - y_predicted), vector_x)
        return gradient

    #4. Least squares.
    def _widrow_hoff(self, y_true, y_predicted, vector_x):
        gradient = np.dot((y_true - y_predicted), vector_x)
        return gradient

    """
    Train and predict methods.
    """
    # Train the perceptron.
    def fit(self, X, Y, max_iterations = 100):

        # Stochastic Gradient Descent.
        for i in range(max_iterations):

            # Select random point.
            randomPoint = random.randint(0, len(X) - 1)
            vector_x = X[randomPoint].copy()

            # Predict point category.
            y_predicted = self.forward(vector_x)
            y_true = Y[randomPoint]

            # Compute gradient.
            gradient = self.criterion(y_true, y_predicted, vector_x)

            # Update weights.
            self.weights = self.weights + self.learning_rate * gradient

    # Predict value/category.
    def predict(self, vector_x):
        dotproduct = np.dot(vector_x, self.weights)
        y_predicted = self.activation(dotproduct)
        return y_predicted

    # Get weights.
    def getWeights(self):
        return self.weights

# Return dataset with bias included.
def add_bias(dataset):
    for element in dataset:
        element.append(1)
    return dataset

# Predict list.
def predictList(perceptron, X, Y, show_results = False):

    error_criterion = np.zeros(len(perceptron.weights))
    accuracy = 0

    for i in range(0, len(X)):

        vector_x = X[i].copy()

        y_true = Y[i]
        y_predicted = perceptron.predict(vector_x)
        error = perceptron.loss_function(y_true, y_predicted)
        correct = (y_true == y_predicted)

        if show_results:
            print("Point: ", vector_x)
            print("True Label: ", y_true)
            print("Prediction: ", y_predicted)
            print("Error: ", error)
            print("")

        error_criterion += error
        if correct:
            accuracy += 1

    print("Criterion Error: ", error_criterion)
    print("Accuracy: ", accuracy / len(X))


import math
import numpy as np
from numpy.linalg import inv, det

def gaussian_function(mu, sigma_squared, x):
    ''' f takes in a mean and squared variance, and an input x
       and returns the gaussian value.'''
    coefficient = 1.0 / math.sqrt(2.0 * math.pi * sigma_squared)
    exponential = math.exp(-0.5 * (x - mu) ** 2 / sigma_squared)
    return coefficient * exponential

# the update function
def update(mean1, var1, mean2, var2):
    ''' This function takes in two means and two squared variance terms,
        and returns updated gaussian parameters.'''
    # Calculate the new parameters
    new_mean = (var2 * mean1 + var1 * mean2) / (var2 + var1)
    new_var = 1 / (1 / var2 + 1 / var1)

    return [new_mean, new_var]

# the motion update/predict function
def predict(mean1, var1, mean2, var2):
    ''' This function takes in two means and two squared variance terms,
        and returns updated gaussian parameters, after motion.'''
    # Calculate the new parameters
    new_mean = mean1 + mean2
    new_var = var1 + var2

    return [new_mean, new_var]

def gauss_pdf(X, M, S):
    """
    Computes the Predictive probability (likelihood) of measurement
    :param X: measurement vector
    :param M: Mean of predictive distribution of measurement vector
    :param S: the Covariance or predictive mean of measurement vector
    :return: Gaussian probability distribution
    """
    if M.shape[1] == 1:
        DX = X - np.tile(M, X.shape[1])
        E = 0.5 * np.sum(DX * (np.dot(inv(S), DX)), axis=0)
        E = E + 0.5 * M.shape[0] * np.log(2 * np.pi) + 0.5 * np.log(det(S))
        P = np.exp(-E)
    elif X.shape[1] == 1:
        DX = np.tile(X, M.shape[1]) - M
        E = 0.5 * np.sum(DX * (np.dot(inv(S), DX)), axis=0)
        E = E + 0.5 * M.shape[0] * np.log(2 * np.pi) + 0.5 * np.log(det(S))
        P = np.exp(-E)
    else:
        DX = X - M
        E = 0.5 * np.dot(DX.T, np.dot(inv(S), DX))
        E = E + 0.5 * M.shape[0] * np.log(2 * np.pi) + 0.5 * np.log(det(S))
        P = np.exp(-E)
    return (P[0], E[0])


# The curl of a vector field

The curl of a vector field ($\vec v$) is also known as the rotor or rotational of the field.  Mathematically, it is expressed as $\nabla \times \vec{v}$.  Where the operator $\nabla \times$ acts on the vector field $\vec v$.  It is a measured of the vorticity of a vector field.

#Python Toolboxes needed
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D #For 3D plots
from matplotlib import cm
#enable Latex syntax in matplotlib
from matplotlib import rc
%matplotlib notebook

The vectorial field (${\bf \vec P}$) has the following form:

$${\bf \hat P} = e^{-(x^2+y^2)}{\bf \hat x} + sin(xy) {\bf \hat y} $$

where $\hat x$ and $\hat y$ are the unit vectors in directions $x$ and $y$ respectively

#Create a mesh
x = np.arange(-1,1,0.1)
y = np.arange(-1,1,0.1)
xx,yy=np.meshgrid(x,y)#[xx,yy] = meshgrid(x,y);

#The parametric equations of the vectorial components
px = np.exp(-(xx**2+yy**2))
py = np.sin(xx*yy)
#The output
plt.figure('Vectorial field')
plt.quiver(xx,yy,px,py,edgecolor='k', facecolor='None',linewidth=0.5)
plt.streamplot(x, y, px, py, color='m', linewidth=1, cmap=plt.cm.inferno,density=2, arrowstyle='->', arrowsize=1.5)
plt.xlabel('x'), plt.ylabel('y');
plt.title(r'Vectorial Field 2-D: ${\bf \hat P} = e^{-(x^2+y^2)}{\bf \hat x} + sin(xy) {\bf \hat y} $')
plt.show()

The curl is a new vector field which is perpendicular to the original local one and its sign is determined by the right-hand rule.

[pxy,pxx]=np.gradient(px,0.1,0.1)#[pxx,pxy]=np.gradient(px,0.1,0.1)
[pyy,pyx]=np.gradient(py,0.1,0.1)#[pyx,pyy]=np.gradient(py,0.1,0.1)
#
curl_p_z=(pyx-pxy);

m,n=curl_p_z.shape
curl_p_x=np.zeros((m,n))
curl_p_y=np.zeros((m,n))

fig = plt.figure()
ax = fig.gca(projection='3d')
ax.quiver(xx,yy,np.zeros((m,n)),curl_p_x,curl_p_y,curl_p_z,3,'r',length=0.03,normalize=False)
ax.plot_surface(xx,yy,np.zeros((m,n)))#surf(xx,yy,zeros(m,n));shading flat;colormap pink;
#rotate3d;
ax.set_xlabel('x'),ax.set_ylabel('y'),ax.set_zlabel('z')
plt.show()

In this next plot we can see the magnitude of the vectors as a coloured image.  Notice where the curl reaches its maximum and minimum values.

fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(xx, yy,curl_p_z , cmap=cm.rainbow ,linewidth=0, antialiased=False,shade=True)
ax.view_init(azim=0, elev=90)
ax.set_xlabel('x'), ax.set_ylabel('y'), ax.set_zlabel(r'$|\nabla \times {\bf \vec P}|$');

from IPython.display import HTML

HTML('<iframe width="560" height="315" src="https://www.youtube.com/embed/rB83DpBJQsE" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>')


import numpy as np

class Brownian:
    """
    A Brownian motion class constructor
    """

    def __init__(self, x0=1):
        """
        Init class
        """
        assert (
            type(x0) == float or type(x0) == int or x0 is None
        ), "Expect a float or None for the initial value"

        self.x0 = float(x0)

    def gen_random_walk(self, n_step=100):
        """
        Generate motion by random walk

        Arguments:
            n_step: Number of steps

        Returns:
            A NumPy array with `n_steps` points
        """
        # Warning about the small number of steps
        # if n_step < 30:
        #     print("WARNING! The number of steps is small. It may not generate a good stochastic process sequence!")

        w = np.ones(n_step) * self.x0

        for i in range(1, n_step):
            # Sampling from the Normal distribution with probability 1/2
            yi = np.random.choice([1, -1])
            # Weiner process
            w[i] = w[i - 1] + (yi / np.sqrt(n_step))

        return w

    def gen_normal(self, n_step=100):
        """
        Generate motion by drawing from the Normal distribution

        Arguments:
            n_step: Number of steps

        Returns:
            A NumPy array with `n_steps` points
        """
        if n_step < 30:
            print(
                "WARNING! The number of steps is small. It may not generate a good stochastic process sequence!"
            )

        w = np.ones(n_step) * self.x0

        for i in range(1, n_step):
            # Sampling from the Normal distribution
            yi = np.random.normal()
            # Weiner process
            w[i] = w[i - 1] + (yi / np.sqrt(n_step))

        return w

    def stock_price(self, s0=100, mu=0.2, sigma=0.68, deltaT=52, dt=0.1):
        """
        Models a stock price S(t) using the Weiner process W(t) as
        `S(t) = S(0).exp{(mu-(sigma^2/2).t)+sigma.W(t)}`

        Arguments:
            s0: Iniital stock price, default 100
            mu: 'Drift' of the stock (upwards or downwards), default 1
            sigma: 'Volatility' of the stock, default 1
            deltaT: The time period for which the future prices are computed, default 52 (as in 52 weeks)
            dt (optional): The granularity of the time-period, default 0.1

        Returns:
            s: A NumPy array with the simulated stock prices over the time-period deltaT
        """
        n_step = int(deltaT / dt)
        time_vector = np.linspace(0, deltaT, num=n_step)
        # Stock variation
        stock_var = (mu - (sigma**2 / 2)) * time_vector
        # Forcefully set the initial value to zero for the stock price simulation
        self.x0 = 0
        # Weiner process (calls the `gen_normal` method)
        weiner_process = sigma * self.gen_normal(n_step)
        # Add two time series, take exponent, and multiply by the initial stock price
        s = s0 * (np.exp(stock_var + weiner_process))

        return s

def generate_bm_series(number):
    b = Brownian()
    series = b.gen_random_walk(number)
    return series

series = generate_bm_series(10)


# -*- coding: utf-8 -*-
"""
Created on Wed Nov 17 11:35:46 2021

@author: Bruno Veiga
"""
from pyticleswarm import run_pso
import numpy as np
#import benchmark_functions as bf

# Example 1

# Define fitness function
def func(x):
    # hypersphere function
    f = np.sum(x**2)

    return f
# or a benchmark can also be used
#func = bf.Hypersphere(n_dimensions=2)

# Create variables
n_vars= 2
low_bounds = -5.12
up_bounds = 5.12
# or for the benchmark function
# n_vars=func.n_dimensions()
# m = func.suggested_bounds()
# low_bounds = m[0]
# up_bounds = m[1]

"""
Stantard parameters values:

initial_solution        = []    Matrix or array containing the initial solutions or solution
brm_function            = 4     Function to handle boundary constraint violation.
n_jobs                  = -2    Number of concurrently running jobs.
direct_repair           = None  The direct repair function
perc_repair             = 0     Value between 0 and 1 that determines the percentage of iterations starting from the end where a repair function is applied
wmax                    = 0.5   The maximum value of the inertia weight
wmin                    = 0.1   The minimum value of the itertia weight
c1min                   = 0     Minimum value of the acceleration coefficient c1
c1max                   = 0.4   Maximum value of the acceleration coefficient c1
c2min                   = 0.1   Minimum value of the acceleration coefficient c2
c2max                   = 2     Maximum value of the acceleration coefficient c1
n_iterations            = 100   The total number of iterations
n_particles             = 10    The total number of particles
n_trials                = 30    The total number of trials
show_fitness_graphic    = False Boolean that indicates if the fitness graphic is to be shown or not
show_particle_graphics  = False Boolean that indicates if the particles graphics are to be shown or not (Only works with solutions of 2 dimensions)
"""
# Run PSO
res = run_pso(n_vars, fitness_function=func, low_bounds=low_bounds, up_bounds= up_bounds, c1min = 0.4, c1max = 0.6, c2min = 0.4,n_jobs=-2,
            c2max = 0.6, wmax = 0.6, wmin = 0.4, n_iterations=100,n_particles=100, brm_function=4, show_fitness_grapic=True)

# Print results
"""
Results:

res.fitness_value is a float correspending to the best fitness value with size = 1
res.it_fitness_value is a array with all the fitness values of the best execution with size = n_iterations
res.solution is the solution correspoding to best fitness value with size = n_vars
res.tot_exec_time is the total of time of execution of the algorithm with size = 1
res.avg_exec_time is the average of time of execution of the algorithm with size = 1
"""


"""Schmidt decomposition operation."""
from typing import List, Tuple, Union
from scipy.sparse import issparse, linalg

import numpy as np

def schmidt_decomposition(
    vec: np.ndarray, dim: Union[int, List[int], np.ndarray] = None, k_param: int = 0
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    r"""
    Compute the Schmidt decomposition of a bipartite vector [WikSD]_.

    Examples
    ==========

    Consider the :math:`3`-dimensional maximally entangled state

    .. math::
        u = \frac{1}{\sqrt{3}} \left( |000 \rangle + |111 \rangle + |222 \rangle \right)

    We can generate this state using the :code:`toqito` module as follows.

    >>> from toqito.states import max_entangled
    >>> max_entangled(3)
    [[0.57735027],
     [0.        ],
     [0.        ],
     [0.        ],
     [0.57735027],
     [0.        ],
     [0.        ],
     [0.        ],
     [0.57735027]]

    Computing the Schmidt decomposition of :math:`u`, we can obtain the corresponding singular
    values of :math:`u` as

    .. math::
        \frac{1}{\sqrt{3}} \left[1, 1, 1 \right]^{\text{T}}.

    >>> from toqito.states import max_entangled
    >>> from toqito.state_ops import schmidt_decomposition
    >>> singular_vals, u_mat, vt_mat = schmidt_decomposition(max_entangled(3))
    >>> singular_vals
    [[0.57735027]
     [0.57735027]
     [0.57735027]]
    >>> u_mat
    [[1. 0. 0.]
     [0. 1. 0.]
     [0. 0. 1.]]
    >>> vt_mat
    [[1. 0. 0.]
     [0. 1. 0.]
     [0. 0. 1.]]

    References
    ==========
    .. [WikSD] Wikipedia: Schmidt decomposition
        https://en.wikipedia.org/wiki/Schmidt_decomposition

    :param vec: A bipartite quantum state to compute the Schmidt decomposition of.
    :param dim: An array consisting of the dimensions of the subsystems (default gives subsystems
                equal dimensions).
    :param k_param: How many terms of the Schmidt decomposition should be computed (default is 0).
    :return: The Schmidt decomposition of the :code:`vec` input.
    """
    eps = np.finfo(float).eps

    if dim is None:
        dim = np.round(np.sqrt(len(vec)))
    if isinstance(dim, list):
        dim = np.array(dim)

    # Allow the user to enter a single number for `dim`.
    if isinstance(dim, float):
        dim = np.array([dim, len(vec) / dim])
        if np.abs(dim[1] - np.round(dim[1])) >= 2 * len(vec) * eps:
            raise ValueError(
                "InvalidDim: The value of `dim` must evenly divide"
                " `len(vec)`; please provide a `dim` array "
                "containing the dimensions of the subsystems."
            )
        dim[1] = np.round(dim[1])

    # Try to guess whether SVD or SVDS will be faster, and then perform the
    # appropriate singular value decomposition.
    adj = 20 + 1000 * (not issparse(vec))

    # Just a few Schmidt coefficients.
    if 0 < k_param <= np.ceil(np.min(dim) / adj):
        u_mat, singular_vals, vt_mat = linalg.svds(
            linalg.LinearOperator(np.reshape(vec, dim[::-1].astype(int)), k_param)
        )
    # Otherwise, use lots of Schmidt coefficients.
    else:
        u_mat, singular_vals, vt_mat = np.linalg.svd(np.reshape(vec, dim[::-1].astype(int)))

    if k_param > 0:
        u_mat = u_mat[:, :k_param]
        singular_vals = singular_vals[:k_param]
        vt_mat = vt_mat[:, :k_param]

    # singular_vals = np.diag(singular_vals)
    singular_vals = singular_vals.reshape(-1, 1)
    if k_param == 0:
        # Schmidt rank.
        r_param = np.sum(singular_vals > np.max(dim) * np.spacing(singular_vals[0]))
        # Schmidt coefficients.
        singular_vals = singular_vals[:r_param]
        u_mat = u_mat[:, :r_param]
        vt_mat = vt_mat[:, :r_param]

    u_mat = u_mat.conj().T
    return singular_vals, u_mat, vt_mat


#!/usr/bin/env python

from sympy import lambdify, diff, Matrix, cos, sin, exp, pprint
from sympy.plotting import plot, plot3d
from sympy.abc import x,y

''' Reference
    https://sistemas.eel.usp.br/docentes/arquivos/519033/LOM3026/Metodos_numericos_calculo_sistemas_equacoes_nao_lineares.pdf
'''

def jacobian_transpose(function):
    '''  Jacobian: init and set  '''
    j = (Matrix([function]).jacobian([x,y])).transpose()
    return (lambdify([x,y], j))

if __name__ == "__main__" :
    euler = exp(-( ( ((x-1)**2)/(2*(0.75**2)) ) + ( ((y-2)**2)/(2*(0.5**2)) )  ))
    f = 1 - euler + ( 0.04 * (((x-1)**2) + ((y-2)**2)) )

    f = x**2 * y**3
    j = (Matrix([f]).jacobian([x,y]))
    j = j.transpose()

    j = lambdify([x,y], j)
    pprint(j(5,2))


#! /usr/bin/env python
from __future__ import division
from scipy.integrate import ode
import numpy as np
import matplotlib.pyplot as plt

def SIR(a,b,beta, gamma,ya):
	def SIR_ode(t,y):
		return np.array([ -beta*y[0]*y[1], beta*y[0]*y[1]-gamma*y[1], gamma*y[1] ])

	example = ode(SIR_ode).set_integrator('dopri5',atol=1e-8,rtol=1e-8)
	example.set_initial_value(ya,a)

	dim, t = 3, np.linspace(a,b,501)
	Y = np.zeros((len(t),dim))
	Y[0,:] = ya
	for j in range(1,len(t)):
		Y[j,:] = example.integrate(t[j])

	return t, Y

#
# def SIS(a,b,beta, gamma,ya):
# 	def SIS_ode(t,y): return np.array([ -beta*y[0]*y[1] +  gamma*y[1],
# 										 beta*y[0]*y[1] - gamma*y[1]    ])
#
# 	example = ode(SIS_ode).set_integrator('dopri5',atol=1e-8,rtol=1e-8)
# 	example.set_initial_value(ya,a)
#
# 	dim, t = 2, np.linspace(a,b,501)
# 	Y = np.zeros((len(t),dim))
# 	Y[0,:] = ya
# 	for j in range(1,len(t)): Y[j,:] = example.integrate(t[j])
#
# 	plt.plot(t,Y[:,0],'-k',label='Susceptible')
# 	plt.plot(t,Y[:,1],'-r',label='Infectious')
# 	plt.axis([a,b,-.1,1.1])
# 	plt.legend(loc=1)
# 	plt.xlabel('T (days)',fontsize=16)
# 	plt.ylabel('Proportion of Population',fontsize=16)
# 	plt.show()
# 	# plt.clf()
# 	return t, Y
#
#
# def SIRS(a,b,beta, gamma, mu, f,ya):
# 	def SIRS_ode(t,y): return np.array([ -beta*y[0]*y[1]+ mu*(1-y[0]) + f*y[2],
# 										  beta*y[0]*y[1] - (gamma + mu) * y[1],
# 										  gamma*y[1] -mu*y[2] - f*y[2]						])
#
# 	example = ode(SIRS_ode).set_integrator('dopri5',atol=1e-8,rtol=1e-8)
# 	example.set_initial_value(ya,a)
#
# 	dim, t = 3, np.linspace(a,b,501)
# 	Y = np.zeros((len(t),dim))
# 	Y[0,:] = ya
# 	for j in range(1,len(t)): Y[j,:] = example.integrate(t[j])
#
# 	plt.plot(t,Y[:,0],'-k',label='Susceptible')
# 	plt.plot(t,Y[:,2],'-b',label='Recovered')
# 	plt.plot(t,Y[:,1],'-r',label='Infected')
# 	plt.axis([a,b,-.1,1.1])
# 	plt.legend(loc=1)
# 	plt.xlabel('T (days)',fontsize=16)
# 	plt.ylabel('Proportion of Population',fontsize=16)
# 	plt.show()
# 	# plt.clf()
# 	return t, Y
#
#

###########################################################################
######      THE SIR MODEL
######      beta  = average number of infectious contacts per day
######      gamma = 1./(average length of time in the infectious phase)

# beta, gamma = 2., 1.
# a, b, ya = 0., 50., np.array([1.-(1.67e-6), 1.67e-6,0.])

# beta, gamma = .340, .333
# a, b, ya = 0., 1600., np.array([1.-(6.25e-7), 6.25e-7,0.])

# t,Y = SIR(a,b,beta, gamma,ya)
# print "The Maximum fraction of the population that will be infected simultaneously is", max(Y[:,1])
###########################################################################

###########################################################################
######      THE SIS MODEL
######		Here we suppose N is the total population without infection. We do
######		suppose that S+I=1
######      beta  = average number of infectious contacts per day
######      gamma = 1./(average length of time in the infectious phase)

# beta, gamma = 3./10., 1./4.
# a, b, ya = 0., 400., np.array([1.-(1.667e-6), 1.667e-6])
# t,Y = SIS(a,b,beta, gamma,ya)
# print "The Maximum fraction of the population that will be infected simultaneously is", max(Y[:,1])
###########################################################################

###########################################################################
######      THE SIRS MODEL
######		Here we suppose N is the total population without infection. We do
######		suppose that S+I+R=1
######      beta  = average number of infectious contacts per day
######      gamma = 1./(average length of time in the infectious phase)

# beta, gamma = 3./10., .7/4.
# mu, f= .1, .3
# a, b, ya = 0., 3600., np.array([1.-50*(1.667e-6), 50*1.667e-6,0.])
# t,Y = SIRS(a,b,beta, gamma, mu, f, ya)
# print "The Maximum fraction of the population that will be infected simultaneously is", max(Y[:,1])
###########################################################################


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

# This code is part of Qiskit.
#
# (C) Copyright IBM 2018, 2019.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

import numpy as np

def tensorproduct(*args):
    """
    Calculate tensor product.

    m = tensorproduct(a,b,c,...) returns the kronecker product of its arguments.
    Each argument should either be a tensor, or a tuple containing a
    tensor and an integer, and tensor is put in zero-index slot.
    In the latter case, the integer specifies the repeat count for the tensor,
    e.g. tensorproduct(a,(b,3),c) = tensorproduct(a,b,b,b,c).

    Args:
            - args:
    Returns:
            np.ndarray: the tensor product
    """
    M = 1
    for j in range(len(args)):
        if isinstance(args[j], tuple):
            m = args[j][0] if isinstance(args[j][0], np.ndarray) else np.asarray(args[j][0])
            for k in range(args[j][1]):
                M = np.kron(M, m)
        else:
            m = args[j] if isinstance(args[j], np.ndarray) else np.asarray(args[j])
            M = np.kron(M, m)
    return M


''' Author: Songheng Yin,
    Contact: songheng.yin@mail.utoronto.ca'''

import numpy as np
from matplotlib import pyplot as plt

def shuffle_data(data):
    ''' perform uniformly random permutation'''
    t, X = data
    n = t.shape[0]
    for i in range(n - 1):
        j = np.random.randint(i, n - 1)
        X[[i, j], :] = X[[j, i], :]
        t[[i, j]] = t[[j, i]]
    return (t, X)

def split_data(data, num_folds, fold):
    t, X = data
    X_split = np.array_split(X, num_folds, axis=0)
    t_split = np.array_split(t, num_folds)
    data_fold = (t_split[fold - 1], X_split[fold - 1])
    X_rest = np.delete(X_split, fold - 1, axis=0)
    X_rest = np.concatenate(X_rest, axis=0)
    t_rest = np.delete(t_split, fold - 1, axis=0)
    t_rest = np.concatenate(t_rest, axis=0)
    data_rest = (t_rest, X_rest)
    return data_fold, data_rest

def train_model(data, lambd):
    t, X = data
    N, D = X.shape
    w = np.linalg.inv((X.T @ X) + lambd * N * np.identity(D))
    w = (w @ X.T) @ t
    return w

def predict(data, model):
    t, X = data
    y = X @ model
    return y

def loss(data, model):
    t, X = data
    return (np.linalg.norm(X @ model - t)) ** 2 / (2 * t.shape[0])

def cross_validation(data, num_folds, lambd_seq):
    data = shuffle_data(data)
    cv_error = np.zeros(len(lambd_seq))
    for i, lambd in enumerate(lambd_seq):
        for fold in range(1, num_folds + 1):
            val_cv, train_cv = split_data(data, num_folds=num_folds, fold=fold)
            model = train_model(train_cv, lambd)
            cv_error[i] += loss(val_cv, model) / num_folds
    return cv_error

if __name__ == '__main__':
    data_train = (
        np.genfromtxt('./data/data_train_y.csv', delimiter=','),
        np.genfromtxt('./data/data_train_X.csv', delimiter=','),
    )
    data_test = (
        np.genfromtxt('./data/data_test_y.csv', delimiter=','),
        np.genfromtxt('./data/data_test_X.csv', delimiter=','),
    )
    lambd_seq = np.linspace(0.00005, 0.005, num=50)

    # Question c
    training_errors, test_errors = [], []
    for lambd in lambd_seq:
        model = train_model(data_train, lambd)
        test_errors.append(loss(data_test, model))
        training_errors.append(loss(data_train, model))
    fig, ax = plt.subplots(1, 1)
    ax.plot(lambd_seq, training_errors, label='Training Error')
    ax.plot(lambd_seq, test_errors, label='Test Error')

    # Question d
    cv_5_error = cross_validation(data_train, 5, lambd_seq)
    cv_10_error = cross_validation(data_train, 10, lambd_seq)
    ax.plot(lambd_seq, cv_5_error, label='CV 5 folders error')
    ax.plot(lambd_seq, cv_10_error, label='CV 10 folders error')
    ax.set_xlabel('Lambda')
    ax.set_ylabel('Loss')
    plt.legend(loc='best')
    plt.show()
    plt.close()

    best_lambda_5 = lambd_seq[np.argmin(cv_5_error)]
    best_lambda_10 = lambd_seq[np.argmin(cv_10_error)]
    print('Best lambda by 5-folder CV: {}'.format(best_lambda_5))
    print('Best lambda by 10-folder CV: {}'.format(best_lambda_10))


## Euler_Maruyama_integration_AshivD.py
# Demonstration of Euler and Euler-Maruyama integration on the SHM problem.
# ------------------------- #
# Description:
# This project demonstrates numerical integration of the Simple Harmonic Motion DE
# using an Euler scheme. Numerical integration of the SHM DE perturbed by diagonal
# independent Wiener processes.
# ------------------------- #
# Created by: Ashiv Dhondea, RRSG, UCT.
# Date created: 04 July 2016
# Edits:
# ------------------------- #
# Theoretical background
# 1. Bayesian Signal Processing, Sarkka. 2013.
# 2. ekfukf toolbox documentation
# ------------------------- #
# Import libraries
import numpy as np
import matplotlib.pyplot as plt
#import sdeint
# f - frequency of SHM
f = 1.0; # [Hz]
omega2 = (2*np.pi*f)**2;

A = np.array([[0, 1.0],
              [ -omega2, 0]],dtype=np.float64);

def fnSHM(x,t):
    de = np.dot(A,x);
    return de

dt = f/1000;
num_samples =5*1/dt;
tspan = np.linspace(0.0, dt*num_samples, num_samples);

x0 = np.array([3.0, 0.0]);
X = np.zeros([2,len(tspan)],dtype=np.float64);
Y = np.zeros([2,len(tspan)],dtype=np.float64);
X[:,0] =x0;
P_0 = np.diag([1e-6,1e-6]);
Y[:,0] =x0 + np.random.multivariate_normal([0,0],np.sqrt(P_0));
# Dispersion matrix
L = np.array([[0.0,0.0],[0.0,1.0]],dtype=np.float64);
true_Qc =  np.diag([1.2,2.4]);
B = dt*true_Qc;

def G(x, t):
    return np.dot(L,B)

for index in range (0,len(tspan)-1):
    # Euler integration scheme.
    X[:,index+1] = X[:,index] + dt*fnSHM(X[:,index],tspan[index]);
    # Euler-Maruyama integration
    Y[:,index+1] = Y[:,index] + dt*fnSHM(Y[:,index],tspan[index])+ np.dot(L,np.random.multivariate_normal([0,0],B));
    #Y[:,index+1] = Y[:,index] + dt*fnSHM(Y[:,index],tspan[index])+ np.random.multivariate_normal([0,0],np.dot(L,B));

#result = sdeint.itoEuler(fnSHM, G, Y[:,0], tspan) # Euler-Maruyama

# Show results for the deterministic solution and the stochastic solution.
fig = plt.figure()
fig.suptitle('Euler integration on SHM DE')
plt.plot(tspan,X[0,:],'b.',label='Euler pos');
#plt.plot(tspan,Y[0,:],'bo',label='EM pos');
plt.plot(tspan,X[1,:],'r.',label='Euler vel');
#plt.plot(tspan,Y[1,:],'ro',label='EM vel');
plt.xlabel('t')
plt.legend(loc='upper left')
ax = plt.gca()
ax.grid(True)
plt.show()

## Compare results from my implementation with those from sdeint
fig = plt.figure()
fig.suptitle('Euler-Maruyama integration test')
plt.plot(tspan,Y[0,:],'b.',label='pos');
plt.plot(tspan,Y[1,:],'r.',label='vel');
#plt.plot(tspan,result[:,0],'g.',label='sdeint pos');
#plt.plot(tspan,result[:,1],'m.',label='sdeint vel');
plt.xlabel('t')
plt.legend(loc='upper left')
ax = plt.gca()
ax.grid(True)
plt.show()


# Implementing PageRank using random walks
# Procedure :
# 1. Create/take a directed graph.
# 2. Initially all the nodes have zero points.
# 3. Select a random node and make it the start node.
# 4. Perform random walks on the graph with the goal of visiting each node at least once.
# 	4.1. When a node is visited, increment its point(s) by one.
# 	4.2. If a node having no outlinks is reached, do teleportation, and resume the random walk.
# 5. Rank the nodes according to their accumulated points.
# 6. Compare the ranking generated with the inbuilt PageRank algorithm function.

import networkx as nx
import random
import numpy as np

# Function to add edges to the directed graph
def add_edges(G,p):
	for i in G.nodes():
		for j in G.nodes():
			if i != j :
				r = random.random()
				if r <= p:
					G.add_edge(i,j)
				else:
					continue
	return G

# Function to sort nodes according to their accumulated points
# We will denote "random-walk-point" by "RWP"
def get_nodes_sorted_by_RWP(RWP_list):
	RWP_array = np.array(RWP_list) # Converting list to numpy array
	nodes_sorted_by_RWP = np.argsort(-RWP_array) # The minus sign is used for sorting in descending order
	return nodes_sorted_by_RWP

# Function to perform random walks and increment random-walk-point of visited node by 1
def random_walk(G):
	nodes = list(G.nodes()) # List of nodes of graph
	RWP = [0 for i in range(G.number_of_nodes())]  # List containing RWP of all nodes which are initially 0
	s_node = random.choice(nodes)  # Choosing starting (source) node at random
	RWP[s_node] += 1
	outlinked_neigh = G.out_edges(s_node)
	c = 0 # Iteration variable
	while (c != 100000):
		if (len(outlinked_neigh) == 0): # Case when the node has no outlink
			focus = random.choice(nodes) # focus is the node currently being visited
		else:
			random_neigh = random.choice(list(outlinked_neigh))
			focus = random_neigh[1]
		RWP[focus] += 1
		outlinked_neigh = G.out_edges(focus)
		c += 1
	return RWP

# Function for main program
def main():
	G = nx.DiGraph() # Creating a directed graph object
	N = 10 # Number of nodes in the graph
	p = 0.3 # Probability for generating random graph
	G.add_nodes_from([i for i in range(N)]) # Adding nodes to the graph object
	G = add_edges(G,p) # Adding edges to the graph object, making it a random graph

	RWP_list = random_walk(G) # Performing random walks on the graph and getting list of RWP for nodes
	nodes_sorted_by_RWP = get_nodes_sorted_by_RWP(RWP_list) # Ranking (ordering) nodes on the basis of accumulated RWP

	print 'Nodes sorted (ranked) according to the accumulated random-walk-points : ',
	for i in nodes_sorted_by_RWP:
		print i,

	pr = nx.pagerank(G) # Getting PageRank of nodes, output is a dictionary
	pr_sorted = sorted(pr.items(), key=lambda x:x[1], reverse=True)
	print '\nNodes sorted (ranked) using the builtin PageRank algo function : ',
	for i in pr_sorted:
		print i[0],

main() # Invoking the main function

PageRank Implementation/PageRank - Random Walks/PageRank_random_walks.py


import scipy

def get_ramp(x0,x1,vmax,a,dt, output='ramp only'):
    """
    Generate a ramp trajectory from x0 to x1 with constant
    acceleration, a, to maximum velocity v_max.

    Note, the main purlpose of this routine is to generate a
    trajectory from x0 to x1. For this reason v_max and a are adjusted
    slightly to work with the given time step.

    Arguments:
     x0 = starting position
     x1 = ending position
     vmax = maximum allowed velocity
     a = constant acceleration

     Keywords:
       output = 'ramp only' or 'full'
       when ramp only is selected then only the velocity ramp is returned.
       If 'full' is selected the adjusted acceleration and maximum velocity
       are also returned.

    Ouput:
      ramp = ramp trajectory form x0 to x1

    """
    # Insure we are dealing with floating point numbers
    x0, x1 = float(x0), float(x1)
    vmax, a = float(vmax), float(a)
    dt = float(dt)
    vmax, a = abs(vmax), abs(a) # Make sure that v_max and a are positive

    # Check to see if there is anything to do
    if x0==x1:
        return scipy.array([x0])

    # Get distance and sign indicating direction
    dist = abs(x1-x0)
    sign = scipy.sign(x1-x0)

    # Determine if we will reach v_max
    t2vmax = vmax/a
    t2halfdist = scipy.sqrt(0.5*dist/a)

    if t2vmax > t2halfdist:
        # Trajectory w/o constant velocity segment
        T = scipy.sqrt(dist/a)
        n = int(scipy.round_((1.0/dt)*T))

        # Adjust accel and duration for rounding of n (discrete time steps)
        a = dist/(n*dt)**2
        T = scipy.sqrt(dist/a)

        # Generate trajectory
        t = scipy.linspace(0.0,2.0*T,2*n+1)
        def f1(t):
            return 0.5*sign*a*(t**2)
        def f2(t):
            s = t-T
            return f1(T)+ sign*a*T*s - 0.5*sign*a*s**2
        func_list = [f1,f2]
        cond_list = [t<=T, t>T]
        ramp = x0+scipy.piecewise(t,cond_list,func_list)

    else:
        # Trajectory w/ constant velocity segment
        # Compute acceleration time and adjust acceleration
        T1 = vmax/a
        n = int(scipy.round_(T1/dt))
        a = vmax/(n*dt) # Adjusted acceleration
        T1 = vmax/a # Adjusted acceleration time

        # Compute and adjust constant velocity time
        T2 = dist/vmax - T1
        m = int(scipy.round_(T2/dt))
        vmax = dist/(dt*(n+m)) # Adjusted max velocity
        T2 = dist/vmax - T1 # Adjusted constant velocity time

        # Generate trajectory
        t = scipy.linspace(0.0,2.0*T1+T2,2*n+m+1)
        def f1(t):
            return 0.5*sign*a*(t**2)
        def f2(t):
            s = t-T1
            return f1(T1) + sign*vmax*s
        def f3(t):
            s = t-T1-T2
            return f2(T1+T2)+sign*vmax*s-0.5*sign*a*s**2
        func_list = [f1,f2,f3]
        cond_list = [t<=T1, scipy.logical_and(t>T1,t<=T1+T2), t>T1+T2]
        ramp = x0+scipy.piecewise(t,cond_list,func_list)

    if output=='ramp only':
        return ramp
    elif output=='full':
        return ramp, vmax, a
    else:
        raise(ValueError, 'unknown keyword option output=%s'%(output,))


import numpy as np
import cv2
import time

def nothing(x):
    pass

def corner_detection(img_path, window_size, method):
    start = time.time()
    window_size = np.asarray(window_size, dtype=np.int)
    img_origin = cv2.imread(img_path)
    img_gray = cv2.cvtColor(img_origin, cv2.COLOR_BGR2GRAY)
    img_gray = cv2.GaussianBlur(img_gray, (3, 3), 0)
    window_size_halved = np.array(np.floor(window_size / 2), dtype=np.int)

    # Calculate Ix and Iy based on Sobel filter
    img_dx = cv2.Sobel(img_gray, cv2.CV_64F, 1, 0, ksize=5)
    img_dy = cv2.Sobel(img_gray, cv2.CV_64F, 0, 1, ksize=5)

    # Calculate (Ix)^2 integral
    img_dx_squared = np.square(img_dx)
    img_dx_squared_integral = cv2.integral(img_dx_squared)

    # Calculate (Iy)^2 integral
    img_dy_squared = np.square(img_dy)
    img_dy_squared_integral = cv2.integral(img_dy_squared)

    # Calculate Ix*Iy integral
    img_dxy = img_dx * img_dy
    img_dxy_integral = cv2.integral(img_dxy)

    # Initialize response matrix
    R_values = np.zeros([img_gray.shape[0],
                        img_gray.shape[1]], dtype=np.float)

    # Integrate and calculate all value in matrix R_values
    for i in range(window_size_halved[0], img_gray.shape[0] - window_size_halved[0]):
        if i % 10 == 0:
            print("Completed %0.2f%%" % (100*(i - window_size_halved[0]) /
                  (img_gray.shape[0] - window_size_halved[0])))
        for j in range(window_size_halved[1], img_gray.shape[1] - window_size_halved[1]):
            # Intergral implementation
            M_11 = img_dx_squared_integral[i - window_size_halved[0], j - window_size_halved[1]] +\
                img_dx_squared_integral[i + window_size_halved[0] + 1, j + window_size_halved[1] + 1] -\
                img_dx_squared_integral[i - window_size_halved[0], j + window_size_halved[1] + 1] -\
                img_dx_squared_integral[i + window_size_halved[0] + 1, j - window_size_halved[1]]

            M_22 = img_dy_squared_integral[i - window_size_halved[0], j - window_size_halved[1]] +\
                img_dy_squared_integral[i + window_size_halved[0] + 1, j + window_size_halved[1] + 1] -\
                img_dy_squared_integral[i - window_size_halved[0], j + window_size_halved[1] + 1] -\
                img_dy_squared_integral[i + window_size_halved[0] + 1, j - window_size_halved[1]]

            M_12 = img_dxy_integral[i - window_size_halved[0], j - window_size_halved[1]] +\
                img_dxy_integral[i + window_size_halved[0] + 1, j + window_size_halved[1] + 1] -\
                img_dxy_integral[i - window_size_halved[0], j + window_size_halved[1] + 1] -\
                img_dxy_integral[i + window_size_halved[0] + 1, j - window_size_halved[1]]
            # End of Intergral implementation
            if method == "Harris":
                # Harris response formula implementation
                trace = M_11+M_22
                det = M_11*M_22 - np.square(M_12)
                if trace > 0:
                    R_values[i, j] = det / trace
                pass
                # End of Harris implementation
            elif method == "Shi-Tomasi":
                # Shi - tomashi formula implementation
                M = np.array([[M_11, M_12], [M_12, M_22]], dtype=float)
                A = np.min(np.linalg.eigvalsh(M))
                R_values[i, j] = A
                # End of Shi - tomashi implementation
            else:
                raise NameError("Wrong method name, try \"Harris\" or \"Shi-Tomasi\"")

    stop = time.time()
    print("Completed 100%")
    print("Total processing time: %0.2f" % (stop - start))
    return R_values

if __name__ == "__main__":
    R_values = corner_detection("./Project/Tho/Linux/data/lena.jpg", (5, 5), "Harris")
    cv2.namedWindow("Result")
    cv2.createTrackbar("threshold", "Result", 2000, 6000, nothing)
    while True:
        threshold = cv2.getTrackbarPos("threshold", "Result")
        percent = R_values.max()*np.power(10, -threshold/1000)
        output = R_values.copy()
        output[output < percent] = 0
        output[output > percent] = 255
        corner = output.astype(np.uint8)
        cv2.imshow("Result", corner)
        if cv2.waitKey(100) & 0xff == 27:
            break


# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

# This Python script implements Remez' algorithm (https://en.wikipedia.org/wiki/Remez_algorithm).
# To use the script, you can modify what happens when this script is executed:
# See the `if __name__ == "__main__":` branch near the end of this file.
#
# Alternatively, `run_remez` can be imported and run directly, see the docstring
# of run_remez.

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

# Return n chebyshev nodes on the interval (a,b)
def _get_chebyshev_nodes(n, a, b):
    nodes = [.5 * (a + b) + .5 * (b - a) * math.cos((2 * k + 1) / (2. * n) * math.pi)
             for k in range(n)]
    return nodes

# Return the error on given nodes of a polynomial with coefficients polycoeff
# approximating the function with function values exactvals (on these nodes).
def _get_errors(exactvals, polycoeff, nodes):
    ys = np.polyval(polycoeff, nodes)
    for i in range(len(ys)):
        ys[i] = abs(ys[i] - exactvals[i])
    return ys

"""
Return the coefficients of a polynomial of degree d approximating
the function fun on the interval (a,b).

Args:
    fun: Function to approximate
    a: Left interval border
    b: Right interval border
    d: The polynomial degree will be d, 2*d or 2*d + 1 depending
        on the values of odd and even below
    odd: If True, use odd polynomial of degree 2*d+1
    even: If True, use even polynomial of degree 2*d
    tol: Tolerance to use when checking for convergence

Returns: Tuple where the first entry is the achieved absolute error
    and the second entry is a list of the polynomial coefficients in
    the order that is required by the QDK Numerics library. This is
    the inverse order compared to what np.polyval expects.
"""
def run_remez(fun, a, b, d=5, odd=False, even=False, tol=1.e-13):
    finished = False
    # initial set of points for the interpolation
    cn = _get_chebyshev_nodes(d + 2, a, b)
    # mesh on which we'll evaluate the error
    cn2 = _get_chebyshev_nodes(100 * d, a, b)

    # do at most 50 iterations and cancel if we "lose" an interpolation
    # point
    it = 0
    while not finished and len(cn) == d + 2 and it < 50:
        it += 1
        # set up the linear system of equations for Remez' algorithm
        b = np.array([fun(c) for c in cn])
        A = np.matrix(np.zeros([d + 2,d + 2]))
        for i in range(d + 2):
            x = 1.
            if odd:
                x *= cn[i]
            for j in range(d + 2):
                A[i, j] = x
                x *= cn[i]
                if odd or even:
                    x *= cn[i]
            A[i, -1] = (-1)**(i + 1)
        # this will give us a polynomial interpolation
        res = np.linalg.solve(A, b)

        # add padding for even/odd polynomials
        revlist = reversed(res[0:-1])
        sccoeff = []
        for c in revlist:
            sccoeff.append(c)
            if odd or even:
                sccoeff.append(0)
        if even:
            sccoeff = sccoeff[0:-1]
        # evaluate the approximation error
        errs = _get_errors([fun(c) for c in cn2], sccoeff, cn2)
        maximum_indices = []

        # determine points of locally maximal absolute error
        if errs[0] > errs[1]:
            maximum_indices.append(0)
        for i in range(1, len(errs) - 1):
            if errs[i] > errs[i-1] and errs[i] > errs[i+1]:
                maximum_indices.append(i)
        if errs[-1] > errs[-2]:
            maximum_indices.append(-1)

        # and choose those as new interpolation points
        # if not converged already.
        finished = True
        for idx in maximum_indices[1:]:
            if abs(errs[idx] - errs[maximum_indices[0]]) > tol:
                finished = False

        cn = [cn2[i] for i in maximum_indices]

    # plot approximation error for illustration
    plt.plot(cn2, abs(errs))
    plt.title("Plot of the approximation error")
    plt.xlabel('x')
    plt.ylabel('|poly_fit(x) - f(x)|')
    plt.show()
    return (max(abs(errs)), list(reversed(res[0:-1])))

if __name__ == "__main__":
    # the function to approximate
    def f(x):
       return math.sin(x)

    # f(x) is an odd function, so we can approximate it
    # using a polynomial of degree 2n+1
    odd = True
    even = False

    # approximate f(x) on the interval (a,b), where
    a = 0.
    b = math.pi

    # set the polynomial degree. If the function is even or odd, the polynomial
    # will be of degree `2*degree` or `2*degree + 1`, respectively.
    degree = 3

    # run Remez' algorithm
    err, coeffs = run_remez(f, a, b, degree, odd, even)

    # and output the coefficients & achieved approximation error
    oddEvenStr = ""
    if odd:
        oddEvenStr = " for odd powers of x"
    if even:
        oddEvenStr = " for even powers of x"

    print("Coefficients{}: {}".format(oddEvenStr, list(reversed(coeffs))))
    print("The polynomial achieves an L_inf error of {}.".format(err))


#!/bin/python
from random import randrange
from numpy import *

trainFile = array([list(map(lambda i: float(i), line.split(','))) for line in open('train_warfarin.csv', 'r')])
validationFile = array([list(map(lambda i: float(i), line.split(','))) for line in open('validation_warfarin.csv', 'r')])
testFile = array([list(map(lambda i: float(i), line.split(','))) for line in open('test_warfarin.csv', 'r')])

def cost(w, b, dataSet, labelSet):
	return average((labelSet - dot(w, dataSet) - b) ** 2)

def fileCost(file, result):
	return cost(result['w'], result['b'], file[:, 1:].transpose(), file[:, 0].transpose())

def analysys(file, rate):
	dataSet = file[:, 1:].transpose()
	labelSet = file[:, 0].transpose()
	initW = empty(dataSet.shape[0])
	initB = 0

	for count in range(100):
		half = labelSet - dot(initW, dataSet) - initB
		i = randrange(dataSet.shape[1])
		meta = dot(dataSet[:, i], half[i]) * 2 * rate

		initW += meta
		initB += average(half) * 2 * rate

	return {
		"w": initW,
		"b": initB,
		"cost": cost(initW, initB, dataSet, labelSet)
	}

def frange(start, stop, step):
	while start < stop:
		yield start
		start += step

bestRate = 0.01
bestResult = analysys(trainFile, bestRate)

for rate in frange(0.01, 0.25, 0.01):
	result = analysys(trainFile, rate)
	if fileCost(validationFile, result) < fileCost(validationFile, bestResult):
		bestResult = result
		bestRate = rate

print("Best learning rate: %s" % bestRate)
print("Trainning set error: %s" % bestResult['cost'])
print("Validation set error: %s" % fileCost(validationFile, bestResult))
print("Test set error: %s" % fileCost(testFile, bestResult))


import ransac.core as ransac
import math
import numpy as np

class Circle(ransac.Model):  # The input is a 2D point. The output is the distance from the circle
    def __init__(self, center=None, radius=None, zero_threshold=1e-6):
        super().__init__()
        self.center = center
        self.radius = radius
        self.zero_threshold = zero_threshold

    def Evaluate(self, x):  # Take an input variable x and return an output variable y
        distance_from_center = math.sqrt((x[0] - self.center[0])**2 + (x[1] - self.center[1])**2)
        return abs(distance_from_center - self.radius)  # Points located at a distance radius from the center will evaluate to 0.

    def Distance(self, y1, y2):  # Compute the distance between two output variables. Must return a float.
        return abs(y1 - y2)

    def Create(self, xy_tuples, **kwargs):  # Create a model with the given (x, y) observations
        if len(xy_tuples) < self.MinimumNumberOfDataToDefineModel():
            raise ValueError("Circle.Create(): len(xy_tuples) ({}) < {}".format(len(xy_tuples), self.MinimumNumberOfDataToDefineModel()))

        # (x - cx)^2 + (y - cy)^2 = r^2
        # (-2x) cx + (-2y) cy + (cx^2 + cy^2 - r^2) = -x^2 - y^2
        # | -2x   -2y    1 | | cx    |   | -x^2 - y^2 |
        # | ...            | | cy    | = | ...        |
        # | ...            | | gamma |   | ...        |
        # where gamma: = cx^2 + cy^2 - r^2

        A = np.zeros((len(xy_tuples), 3), dtype=float)
        b = np.zeros((len(xy_tuples), 1), dtype=float)
        for row in range(len(xy_tuples)):
            x0 = xy_tuples[row][0][0]
            x1 = xy_tuples[row][0][1]
            A[row, 0] = -2 * x0
            A[row, 1] = -2 * x1
            A[row, 2] = 1
            b[row, 0] = -x0**2 -x1**2
        z, residuals, rank, singular_values = np.linalg.lstsq(A, b, rcond=None)
        self.center = (z[0, 0], z[1, 0])
        self.radius = math.sqrt(self.center[0]**2 + self.center[1]**2 - z[2, 0])

    def MinimumNumberOfDataToDefineModel(self):  # The minimum number or (x, y) observations to define the model
        return 3