#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Tue Aug 25 10:59:01 2020

@author: Jean-Philippe Kuntzer
"""

import numpy as np
import matplotlib.pyplot as plt

def main():
    x = np.linspace(-1,1,10000)
    xp = np.abs(x)
    y1 = phi(xp,'L')
    y2 = phi(xp,'TPS')
    y3 = phi(xp,'G')
    y4 = phi(xp,'HMQ')
    y5 = phi(xp,'HIMQ')
    y6 = phi(xp,'C0')
    y7 = phi(xp,'C2')
    y8 = phi(xp,'C4')
    y9 = phi(xp,'C6')
    y10 = phi(xp,'EH')

    # linewidth
    lwdth = 10
    titleSize = 28
    labelSize = 20

    fig = plt.figure()
    ax = plt.subplot(111)
    ax.plot(x, y1, label='$Linear: \phi = r $', linewidth=lwdth)
    # ax.plot(x, y2, label='$\phi = r^2 ln(r) $', linewidth=lwdth)
    ax.plot(x, y3, 'r', label='Gaussian: $\phi = e^{-\epsilon r^2}$', linewidth=lwdth)
    # ax.plot(x, y4, label='$\phi = \sqrt{1+(\epsilon r)^2}$', linewidth=lwdth)
    # ax.plot(x, y5, label='$\phi = (\sqrt{1+(\epsilon r)^2})^{-1}$', linewidth=lwdth)
    ax.plot(x, y6, label='Wendland $C_0: \phi = (1−r)^2$', linewidth=lwdth)
    # ax.plot(x, y7, label='$\phi = (1−r)^4 (4r+ 1)$', linewidth=lwdth)
    # ax.plot(x, y8, label='$\phi = (1−r)^6 (35r^2+ 18r+ 3)$', linewidth=lwdth)
    # ax.plot(x, y9, label='$\phi = (1−r)^8 (32r^3+ 25r^2+ 8r+ 1)$', linewidth=lwdth)
    # ax.plot(x, y10, label='Euclid hat $ \phi = \pi*((1/12 r^3) - r \epsilon^2 + 4/3 \epsilon^3)$', linewidth=lwdth)

    plt.title('Common radial basis functions', fontsize=titleSize)
    ax.legend(loc='upper right',fontsize=labelSize)
    plt.xlabel('x',fontsize=labelSize)
    plt.ylabel('$\phi(x)$',fontsize=labelSize)
    plt.xticks(fontsize=labelSize)
    plt.yticks(fontsize=labelSize)
    plt.show()

# φ(r) =
# φ(r) = (1−r)4(4r+ 1)
# φ(r) = (1−r)6(35r2+ 18r+ 3)
# φ(r) = (1−r)8(32r3+ 25r2+ 8r+ 1)

def phi(r,fun):
    """
    Set of radial basis functions that the user can choose of. After some
    test "Wendland C2" seems to be the better choice, but this is really
    up to user preference.
    """
    eps = 1
    # r = np.linalg.norm(x1-x2)
    if fun == "G":
        # Gaussian
        phi_x = np.exp(-eps*r**2)
    elif fun == "L":
        # Linear
        phi_x = r
    elif fun == 'cubic':
        # Cubic
        phi_x = r**3
    elif fun == "TPS":
        # Thin plate spline
        phi_x = r**2 * np.log(r)
    elif fun == "Po4":
        # Polyharmonic spline
        phi_x = r**3 * np.log(r**r)
    elif fun == "Po4":
        # Polyharmonic spline
        phi_x = r**5 * np.log(r**r)
    elif fun == "HMQ":
        # Hardy's multiquadratic
        phi_x = (eps**2 + r**2)**0.5
    elif fun == "HIMQ":
        # Hardy's inverse multiquadratic
        phi_x = 1/(eps**2 + r**2)**0.5
    elif fun == "C0":
        # Wendland's C0
        phi_x = (1-r)**2
    elif fun == "C2":
        # Wendland's C2
        phi_x = (1-r)**4 * (4*r + 1)
    elif fun == "C4":
        # Wendland's C4
        phi_x = (1-r)**6 * (35*r**2 + 18*r + 3)
        # print('C4')
    elif fun == "C6":
        # Wendland's C6
        phi_x = (1-r)**8 * (32*r**3 + 25*r**2 + 8*r + 1)
    elif fun == "EH":
        # Euclid's hat
        phi_x = np.pi*((1/12*r**3) - r*eps**2 + 4/3*eps**3)
    return phi_x

if __name__ == "__main__":
    main()


import numpy as np
from math import pow,exp
# NumPy provides familiar mathematical functions such as sin, cos, and exp. In NumPy, these are
# called “universal functions”(ufunc). Within NumPy, these functions operate elementwise on an
# array, producing an array as output.

# exp Return e raised to the power of x. where e = 2.71828183
# both are almost same there is minor difference between both
print("<---- exp function explanation ---->")
print("Power : ",pow(2.71828183,2))
print("Exponential : ",exp(2))

print("<---- Universal functions ---->")
a = np.arange(3)
print("a Array : ",a)
print("exp of a : ",np.exp(a))
print("sqrt of a : ",np.sqrt(a))
b = np.array([4,5,-6])
print("add : ",np.add(a,b))


import pandas as pd
import numpy as np

"""object oriented path generator, based on numpy and pandas"""

class GeometricBrownianMotion:
    """simulate simple geometric brownian motion path with drift term and volatility term to be constant"""
    """ dS_t = mu*S_t*dt + sigma*S_t*dW_t, W_t stands for standard Brownian motion"""
    """https://en.wikipedia.org/wiki/Geometric_Brownian_motion"""
    def fit(self,mu,sigma):
        self.mu=mu
        self.sigma=sigma
    def __condition_mean(self,S_pre,mu_pre,sigma_pre,time_step):
        """
        param S_pre: previous spot price
        param mu_pre: previous drift rate
        param sigma_pre: previous volatility
        param time_step: constant time step, e.g dt
        return: the condition mean given previous spot, drift, and volatility
        """
        return np.log(S_pre) + (mu_pre - 0.5*sigma_pre**2)*time_step
    def __condition_std(self,sigma_pre,time_step):
        """
        param sigma_pre: previous volatility
        param time_step: constant time step, e.g dt
        return: the condition std given volatility and time step
        """
        return np.sqrt(sigma_pre**2*time_step)

    def simulate(self,s0,time_step,T,N=10000,Random_State=42):
        """
        param s0: initial stock spot price
        param time_step: constant time step, e.g dt
        param T: time to maturity
        param N: number of samples to generate
        param Random_State: fixed random state for simulation
        return: stock prices follow geometric Brownian motion from time 0 to time T
        """
        self.T=T
        time_steps = np.arange(0,T+time_step,time_step,dtype=float)
        rand = np.random.RandomState(Random_State)
        samples = np.empty((N,len(time_steps)))
        samples[:,0]=s0 # set initial value

        """The next stock price log-normal distributed given previous"""
        for i in range(1,len(time_steps)):
            samples[:,i] = np.exp(
                rand.normal(
                    self.__condition_mean(S_pre=samples[:,i-1],mu_pre=self.mu,sigma_pre=self.sigma,time_step=time_step),
                    self.__condition_std(sigma_pre=self.sigma,time_step=time_step)
                )
            )
        return pd.DataFrame(samples,index=range(1,N+1),columns=time_steps)


import numpy as np

def l2distance(X,Z=None):
    '''
    Compute the Euclidean distance matrix.
    Syntax:
    D=l2distance(X,Z)
    Input:
    X: nxd data matrix with n vectors (rows) of dimensionality d
    Z: mxd data matrix with m vectors (rows) of dimensionality d

    Output:
    Matrix D of size nxm
    D(i,j) is the Euclidean distance of X(i,:) and Z(j,:)

    call with only one input:
    l2distance(X)=l2distance(X,X)
    '''
    if Z is None:
        Z=X

    n,d1=X.shape
    m,d2=Z.shape
    assert (d1==d2), "Dimensions of input vectors must match!"

    G = X @ Z.T
    S = np.tile(np.diag(X @ X.T), (m,1)).T
    R = np.tile(np.diag(Z @ Z.T), (n,1))

    D2 = S + R - 2 * G
    D = D2.copy()
    D[D < 0.0000000001] = 0
    D = np.sqrt(D)
    return D

def findknn(xTr,xTe,k):
    '''
    Find the k nearest neighbors of xTe in xTr.

    Input:
    xTr = nxd input matrix with n row-vectors of dimensionality d
    xTe = mxd input matrix with m row-vectors of dimensionality d
    k = number of nearest neighbors to be found

    Output:
    indices = kxm matrix, where indices(i,j) is the i^th nearest neighbor of xTe(j,:)
    dists = Euclidean distances to the respective nearest neighbors
    '''

    dists_all = l2distance(xTr, xTe)
    indices = np.argsort(dists_all, axis = 0)[:k]
    dists = np.sort(dists_all, axis = 0)[:k]

    return indices, dists

def accuracy(truth,preds):
    '''
    Analyze the accuracy of a prediction against the ground truth

    Input:
    truth = n-dimensional vector of true class labels
    preds = n-dimensional vector of predictions

    Output:
    accuracy = scalar (percent of predictions that are correct)
    '''

    truth = truth.flatten()
    preds = preds.flatten()

    return (sum(truth == preds)/len(truth))

def mode(datalist):
    '''
    Find the mode of a list of data
    '''
    contents = set(datalist)
    maximum = 0
    for c in contents:
        num = datalist.count(c)
        if num > maximum:
            maximum = num
            m = c
    return m

def modeC(array):
    '''
    Find the mode in each column of an array
    '''
    m = np.zeros(array.shape[1])
    for i in range(array.shape[1]):
        m[i] = mode(list(array[:,i]))
    return m

def knnclassifier(xTr,yTr,xTe,k):
    '''
    k-nn classifier

    Input:
    xTr = nxd input matrix with n row-vectors of dimensionality d
    xTe = mxd input matrix with m row-vectors of dimensionality d
    k = number of nearest neighbors to be found

    Output:

    preds = predicted labels, ie preds(i) is the predicted label of xTe(i,:)
    '''

    yTr = yTr.flatten()

    ind, dist = findknn(xTr,xTe,k)

    return (modeC(yTr[ind]))


"""
Solver for single-stage, zero-sum matrix-form games using scipy default
linear programming routines.

Original by Matthew Farrugia-Roberts, 2021

Students
* please note this implementation is not guaranteed to be free of errors,
  for example it has not been extensively tested.
* please report bugs to <matt.farrugia@unimelb.edu.au>.
* please feel free adapt for your own use-case.
"""

import numpy as np
import scipy.optimize as opt

def solve_game(V, maximiser=True, rowplayer=True):
    """
    Given a utility matrix V for a zero-sum game, compute a mixed-strategy
    security strategy/Nash equilibrium solution along with the bound on the
    expected value of the game to the player.
    By default, assume the player is the MAXIMISER and chooses the ROW of V,
    and the opponent is the MINIMISER choosing the COLUMN. Use the flags to
    change this behaviour.

    Parameters
    ----------
    * V: (n, m)-array or array-like; utility/payoff matrix;
    * maximiser: bool (default True); compute strategy for the maximiser.
        Set False to play as the minimiser.
    * rowplayer: bool (default True); compute strategy for the row-chooser.
        Set False to play as the column-chooser.

    Returns
    -------
    * s: (n,)-array; probability vector; an equilibrium mixed strategy over
        the rows (or columns) ensuring expected value v.
    * v: float; mixed security level / guaranteed minimum (or maximum)
        expected value of the equilibrium mixed strategy.

    Exceptions
    ----------
    * OptimisationError: If the optimisation reports failure. The message
        from the optimiser will accompany this exception.
    """
    V = np.asarray(V)
    # lprog will solve for the column-maximiser
    if rowplayer:
        V = V.T
    if not maximiser:
        V = -V
    m, n = V.shape
    # ensure positive
    c = -V.min()
    Vpos = V + c
    # solve linear program
    res = opt.linprog(
        np.ones(n),
        A_ub=-Vpos,
        b_ub=-np.ones(m),
    )
    if res.status:
        raise OptimisationError(res.message) # TODO: propagate whole result
    # compute startegy and value
    v = 1 / res.x.sum()
    s = res.x * v
    v = v - c # re-scale
    if not maximiser:
        v = -v
    return s, v

class OptimisationError(Exception):
    """For if the optimiser reports failure."""

if __name__ == "__main__":
    # Rock paper scissors example (row player, maximiser)
    print("test: rock paper scissors")
    RPS = np.array([
        [  0, -1, +1 ],
        [ +1,  0, -1 ],
        [ -1, +1,  0 ],
    ])
    print("game:", *RPS, sep="\n ")
    print("soln:", *solve_game(RPS))
    print("true:", np.array([1/3, 1/3, 1/3]), 0.0)
    print()

    print("test: textbook example")
    # Hespanha textbook example (column player, minimiser)
    A = np.array([
        [  3,  0 ],
        [ -1,  1 ],
    ])
    print("game:", A, sep="\n")
    print("soln:", *solve_game(A, maximiser=False, rowplayer=False))
    print("true:", np.array([1/5, 4/5]), 3/5)
    print()


"""
Defines functions to manipulate matrices.
"""

import numpy as np
import scipy.linalg as la

def diagonalise(A):
    """Diagonalise a matrix.

    Parameters:
        A (ndarry): A square matrix.

    Returns:
        ndarray: A diagonalised matrix.
    """
    eigenvals, eigenvecs = la.eig(A)
    return np.diag(eigenvals)

def matrixOfEigenvec(A):
    """Calculate a matrix of eigenvectors.
    Parameters:
        A (ndarray): A square matrix.

    Returns:
        ndarray: A matrix of eigenvectors
    """
    eigenvals, eigenvecs = la.eig(A)
    return eigenvecs

#----------------------------------FOR DEBUGGING------------------------------
def printNonZeroMatrixElements(A):
    """Prints non-zero matrix elements

    Parameters:
        A (ndarray): A matrix
    """
    for i, row in enumerate(A):
        for j, element in enumerate(row):
            if(element != 0):
                print(f"[{i}, {j}] = {element}")


""" Orbital Mechanics for Engineering Students Example 1.6
Question:
Given the position, velocity, and acceleration of a particle P at a given
instant (R, V, A), find the coordinates of the center of curvature at that
instant.
Written by: J.X.J. Bannwarth
"""
from numpy import array, cross, dot
from numpy.linalg import norm

def PrintVec(v, name, unit = ''):
    print(f"{name} = {v[0]:.4f} i + {v[1]:.4f} j + {v[2]:.4f} k {unit}")

# Title
print("Orbital Mechanics for Engineering Students Example 1.6")

# Point P
R = array([250., 630., 430.])
V = array([90., 125., 170.])
A = array([16., 125., 30.])

# Tangential vector
Ut = V / norm(V)
PrintVec(Ut, "U_t")

# Binormal vector
Ub = cross(V,A) / norm(cross(V,A))
PrintVec(Ub, "U_b")

# Normal vector
Un = cross(Ub, Ut)
PrintVec(Un, "U_n")

# Normal acceleration
an = dot(A,Un)

# Radius of curvature
rho = norm(V)**2 / an
print(f"rho = {rho:.4f} m")

# Location of center of curvature
RCP = rho*Un
RC = R + RCP
print("Final answer:")
PrintVec(RC, "R_C", "m")


import numpy as np

class ContigencyMatrix:
    def __init__(self, y_true, y_pred):
        """
        Contigency matrix calculated as intersection of indices

        Matrix:
        rows: prediction labels
        cols: ground truth labels

        Example:
        ground truth labels: [0,0,0,1,1,1]
        prediction labels  : [0,0,1,0,1,1]

        TP = sum([F,F,F,T,T,T] *
                 [F,F,T,F,T,T]) = 2
        FP = sum([T,T,T,F,F,F] *
                 [F,F,T,F,T,T]) = 1
        """
        self.y_true = y_true
        self.y_pred = y_pred

        # Sort labels by frequency. Base class equals the most frequent class
        self.vals, self.cnts = np.unique(y_true, return_counts=True)
        if len(self.vals) == 2:
            self.binary = True
            if 0 in self.vals:
                self.labels = np.concatenate((self.vals[self.vals!=0], [0]))
        else:
            self.binary = False
            self.labels = tuple(self.vals[np.argsort(self.cnts)])

        # Sanity check
        if len(self.labels) < 2:
            raise ValueError('Only one class in the train')

        # Indices of Real and Prediction values by label
        self.true_idx = {label: y_true==label for label in self.labels}
        self.pred_idx = {label: y_pred==label for label in self.labels}

        self.mtx = np.zeros((len(self.labels), len(self.labels)))
        for row, row_label in enumerate(self.labels):
            for col, col_label in enumerate(self.labels):
                self.mtx[row, col] = np.sum(self.true_idx[col_label]*self.pred_idx[row_label])

    def accuracy(self):
        return np.trace(self.mtx)/np.sum(self.mtx)

    def precision(self):
        if self.binary:
            self.precision_score = self.mtx[0,0]/sum(self.mtx[0,:])
        else:
            self.precision_score = np.array([self.mtx[i,i]/sum(self.mtx[i,:]) for i in range(self.mtx.shape[0])])
        return np.average(self.precision_score)

    def recall(self):
        if self.binary:
            self.recall_score = self.mtx[0,0]/sum(self.mtx[:,0])
        else:
            self.recall_score = np.array([self.mtx[i,i]/sum(self.mtx[:,i]) for i in range(self.mtx.shape[0])])
        return np.average(self.recall_score)

    def f1(self):
        self.f1_score = 2*np.average(self.recall_score)*np.average(self.precision_score)/(
            np.average(self.recall_score+self.precision_score))
        return self.f1_score

def multiclass_accuracy(prediction, ground_truth):
    """
    Computes metrics for multiclass classification

    Arguments:
    prediction, np array of int (num_samples) - model predictions
    ground_truth, np array of int (num_samples) - true labels

    Returns:
    accuracy - ratio of accurate predictions to total samples
    """

    # TODO: Implement computing accuracy
    cm = ContigencyMatrix(ground_truth, prediction)
    return cm.accuracy()


import numpy as np
import ast

def vandermonde(x,y):
    x = ast.literal_eval(x)
    y = ast.literal_eval(y)
    y = np.array(y)
    x = np.array(x)
    vanx = np.vander(x)
    yT = y.T
    invX = np.linalg.inv(vanx) # Get the inverse of matrix x
    dotPoint = np.dot(invX,yT) # dot point between inverse x and transpose Y
    sizePol = len(dotPoint)
    countPol = sizePol
    polynom = ""
    for i in range(sizePol):
        polynom += str(np.round(dotPoint[i],4))+ "*x^"+str(countPol-1)+"+"
        countPol -= 1
        if(countPol == 1):
            break
    polynom += str(round(dotPoint[sizePol-1],4))
    polynom = polynom.replace(" ", "").replace("--", "+").replace("++", "+").replace("+-", "-").replace("-+", "-")
    results = {
        'vMatrix': vanx.tolist(),
        'values': np.round(dotPoint,14).tolist(),
        'polynom': polynom
    }
    return results


import numpy as np

from scipy.stats import skew, kurtosis

import metalearn.metafeatures.constants as consts

def profile_distribution(data):
    """
    Compute the mean, standard deviation, min, quartile1, quartile2, quartile3, and max of a vector

    Parameters
    ----------
    data: array of real values

    Returns
    -------
    features = dictionary containing the min, max, mean, and standard deviation
    """
    if len(data) == 0:
        return (np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan)
    else:
        ddof = 1 if len(data) > 1 else 0
        dist_mean = np.mean(data)
        dist_stdev = np.std(data, ddof=ddof)
        dist_min, dist_quartile1, dist_quartile2, dist_quartile3, dist_max = np.percentile(data, [0,25,50,75,100])
        dist_skew = skew(data)
        dist_kurtosis = kurtosis(data)
    return (dist_mean, dist_stdev, dist_skew, dist_kurtosis, dist_min, dist_quartile1, dist_quartile2, dist_quartile3, dist_max)

def get_numeric_features(dataframe, column_types):
    return [feature for feature in dataframe.columns if column_types[feature] == consts.NUMERIC]

def get_categorical_features(dataframe, column_types):
    return [feature for feature in dataframe.columns if column_types[feature] == consts.CATEGORICAL]

def dtype_is_numeric(dtype):
    return "int" in str(dtype) or "float" in str(dtype)


# For this part of the assignment, please implement your own code for all computations,
# Do not use inbuilt functions like fft from either numpy, opencv or other libraries

from numpy import exp, pi, zeros

class DFT:

    def forward_transform(self, matrix):
        """Computes the forward Fourier transform of the input matrix
        takes as input:
        matrix: a 2d matrix
        returns a complex matrix representing fourier transform"""
        print(type(matrix), '<<<<<<')

        # matrix[r][c] --> matrix[y][x]
        # (u,v) <- (x,y) -> (c,r)

        _matrix = zeros(matrix.shape, dtype=complex)
        K, L = matrix.shape[0] , matrix.shape[1]

        for v in range(0,matrix.shape[0]):
            for u in range(0, matrix.shape[1]):
                _sum = 0
                for y in range(0, matrix.shape[0]):
                    for x in range(0, matrix.shape[1]):
                        _sum += matrix[y,x] * \
                                exp( -1j*2*pi*( (u*x/L) + (v*y/K) ) )
                _matrix[v,u] = (( 1 / (K*L) )+0j) * _sum

        return _matrix

    def inverse_transform(self, matrix):
        """Computes the inverse Fourier transform of the input matrix
        matrix: a 2d matrix (DFT) usually complex
        takes as input:
        returns a complex matrix representing the inverse fourier transform"""

        _matrix = zeros(matrix.shape, dtype=complex)
        K, L = matrix.shape[0], matrix.shape[1]

        for v in range(0, matrix.shape[0]):
            for u in range(0, matrix.shape[1]):
                _sum = 0
                for y in range(0, matrix.shape[0]):
                    for x in range(0, matrix.shape[1]):
                        _sum += matrix[y,x] * \
                                exp( 1j*2*pi*( (u*x/L) + (v*y/K) ) )
                _matrix[v,u] = _sum

        return _matrix

    def magnitude(self, matrix):
        """Computes the magnitude of the DFT
        takes as input:
        matrix: a 2d matrix
        returns a matrix representing magnitude of the dft"""

        from numpy import sqrt

        for v in range(0, matrix.shape[0]):
            for u in range(0, matrix.shape[1]):
                matrix[v,u] = sqrt( (matrix[v,u].real)**2 + (matrix[v,u].imag)**2 )

        return matrix

    def discrete_cosine_tranform(self, matrix):
        """Computes the discrete cosine transform of the input matrix
        takes as input:
        matrix: a 2d matrix
        returns a matrix representing discrete cosine transform"""

        from numpy import cos, sin

        _matrix = zeros(matrix.shape, dtype=complex)
        K, L = matrix.shape[0], matrix.shape[1]

        for v in range(0, matrix.shape[0]):
            for u in range(0, matrix.shape[1]):
                _sum = 0
                for y in range(0, matrix.shape[0]):
                    for x in range(0, matrix.shape[1]):
                        _sum += matrix[y,x] * \
                                cos( (pi/L)*(x+0.5)*u ) * \
                                cos( (pi/K)*(y+0.5)*v )
                _matrix[v,u] = _sum

        return _matrix


import csv
import numpy as np
import scipy
import scipy.stats as stats

def get_simple_regression_samples(n,b0=-0.3,b1=0.5,error=0.2,seed=None):
    if seed:
        np.random.seed(seed)

    trueX =  np.random.uniform(-1,1,n)
    trueT = b0 + (b1*trueX)
    return np.array([trueX]).T, trueT + np.random.normal(0,error,n)

def fit_linear_lstsq(xdata,ydata):
    """
    y = b0 + b1*x
    """

    matrix = []
    n,d = xdata.shape

    for i in range(n):
        matrix.append([1.0, xdata[i,0]])

    coeffs = scipy.linalg.basic.lstsq(matrix,ydata)[0]
    #yFit =coeffs[0] + (coeffs[1] * xdata)

    return coeffs

def get_rsme(targets,predictions):
    """return the root mean sq error"""
    if targets.shape[0] != predictions.shape[0] or targets.shape[1] != predictions.shape[1]:
        raise Exception("bad dims")

    return(np.linalg.norm(predictions - targets) / np.sqrt(n))

if __name__ == "__main__":

    ## simple linear regression
    n = 12
    x,y = get_simple_regression_samples(n,seed=42)
    print('x = %s'%[round(_x,2) for _x in x])
    print('y = %s'%[round(_y,2) for _y in y])

    y_pred, coeffs = fit_linear_lstsq(x,y)

    print(coeffs)

    #slope, intercept, r_value, p_value, std_err = stats.linregress(t,t_predict)
    #print intercept,slope


# start import modules
import numpy as np
import matplotlib.pyplot as plt
# end import modules

savefig=True

# start simulation
np.random.seed(999)         # for reproducibility
pH=0.6                       # biased coin
flips=np.random.rand(2**12) # simulates 4096 coin flips
heads=flips<pH              # boolean array, heads[i]=True if flip i is heads
# end simulation

# start bayesian setup
def prior(pH):
    p=np.zeros_like(pH)
    p[(0<=pH)&(pH<=1)]=1      # allowed range: 0<=pH<=1
    return p                # uniform prior
def likelihood(pH,data):
    N = len(data)
    no_of_heads = sum(data)
    no_of_tails = N - no_of_heads
    return pH**no_of_heads * (1-pH)**no_of_tails
def posterior(pH,data):
    p=prior(pH)*likelihood(pH,data)
    norm=np.trapz(p,pH)
    return p/norm
# end bayesian setup

# start plotting
pH=np.linspace(0,1,1000)
fig, axs = plt.subplots(nrows=4,ncols=3,sharex=True,sharey='row',figsize=(14,14))
axs_vec=np.reshape(axs,-1)
axs_vec[0].plot(pH,prior(pH))
for ndouble in range(11):
    ax=axs_vec[1+ndouble]
    ax.plot(pH,posterior(pH,heads[:2**ndouble]))
    ax.text(0.1, 0.8, '$N={0}$'.format(2**ndouble), transform=ax.transAxes)
for row in range(4): axs[row,0].set_ylabel('$p(p_H|D_\mathrm{obs},I)$')
for col in range(3): axs[-1,col].set_xlabel('$p_H$')
# end plotting

if savefig:
    fig.savefig('../fig/coinflipping_fig_1.png')

if not savefig:
    plt.show()


# -*- coding: utf-8 -*-
"""
Created on Fri May  3 16:04:00 2019

@author: Miranda
"""

from sympy import mod_inverse as mminv
from sympy import isprime as isprime
import math
import random
import time
time.time() * 10000

def legendres(dis,p):
    """legendre symbol"""
    ls = pow(dis, (p-1)//2, p)
    if ls == -1 % p:
        return -1
    else: return ls

print(legendres(12,13))

def modroot(a,p):
    """Finds the square root of a modulo p"""
    a = a%p #reduces mod p
    ls=legendres(a,p) #checks Legendre Symbol
    if isprime(p) == 0:
        print("p not prime") #rejects if p not prime
        return
    if ls != 1:
        print("No real root") #rejects if Legendre Symbol is -1
        return False
    if p % 4 == 3: #if p mod 4 = 3, then sqrt of a must be +/- a^(p+1)/4
        b = pow(a, (p+1)//4, p)
        return b % p, -b % p
    else: #otherwise, go to TS algorithm
        return tonellishanks(a,p)

def tonellishanks(a,p):
    """Tonelli-Shanks algorithm for modular square roots"""
    #function is only ever called by modroot, so we need not check the Legendre Symbol
    t = (p-1)//2 #initialises t
    s = 1 #initialises s
    while t % 2 == 0:
        t = t//2 #divides t by two until the odd factor is found
        s = s+1 #stores power of two
    u = 2 #initialises u
    while legendres(u,p) == 1:
        u = random.randint(0,p) #chooses new u until a non-square is found
    v = pow(u,t,p) #computes v
    b = pow(a,(t+1)//2, p) #computes first guess
    #Now we find m - one binary digit at a time
    m = 0 #initialises m
    for ii in range(s-1): #from 0 to s-2:
        mii = pow(a,t*2**(s-2-ii),p) #computes value to decide if m_ii is 1 or 0
        if mii == 1:    mi=0 #stores m_ii
        else:   mi=1
        m = (m + mi*(2**(ii))) % p #computes m up to most current binary digit
    b = b*pow(v,m,p) % p #computes positive solution
    if b**2 % p != a % p: #double check
        b=tonellishanks(a,p)[0] #repeat if incorrect
    return b, -b % p

def modcube(a,p):
    """Finds the cube root of a modulo p --- algorithm by Prof. Cameron"""
    if p % 3 == 1 and p % 9 != 1: #Cameron method
        c = pow(a, (p - 1)//3, p) #check if cube root exists
        if c != 1: return  False #if not, reject
        else:
            if p%9==4: #if p mod 9 = 4...
                k = (2*p + 1)//9 #k can be given as this
            elif p%9==7: #if p mod 9 = 7...
                k = (p+2)//9 #k can be given as this
            else:
                k = random.randint(0,(p+1)/2) #otherwise choose random k
                while True:
                    k = k+1 #increase k until...
                    if 3*k % ((p-1)//3) == 1: #suitable k is found
                        break
            b = pow(a,k,p) #compute cube root
        return b
    elif p % 3 == 2: #we can use short formula:
        b = pow(a, (2*p-1)//3, p)
        return b

def w2check(a,b,p):
    """Checks for point of order two on WNF curve"""
    """Returns x coordinate alpha if exists"""
    dis = -16*(4 * a**3 + 27 * b**2) % p #calculates discriminant
    ldis = legendres(dis,p) #finds Legendre Symbol of discriminant
    if dis == 0:
        print("Discriminant = 0") #rejects if discriminant is 0
        return
    if ldis % 2 == 1:
        #1 root if -1, 3 if 1, doing both cases here
        r = ((b**2)*mminv(4,p) + (a**3)*mminv(27,p)) % p #using cubic formulae
        rt = modroot(r,p) #sqrt of r
        if not rt:
            print("No transformation") #if sqrt does not exist, no alpha exists
            return
        else:
            rt = rt[0] #assign one root to use in algebra
        bf = -b*mminv(2,p) % p #cubic formulae
        alpha = (modcube(bf+rt,p) + modcube(bf-rt,p)) #cubic formulae
        if (alpha**3 + a*alpha + b) % p == 0:
            return alpha % p #double checking value of alpha
        else:
            print("error") #reject otherwise, output alpha for error checking
        return alpha % p
    else:
        print("No point of order two")
        return

#Points

def wtom(a,b,p):
    """Converts WNF curve to Montgomery"""
    """Checks for conversion"""
    start = time.process_time()*1000
    alpha = w2check(a,b,p) #finds point of order two on W
    if not alpha:
        print("No conversion") #if no such point, reject
        return
    else:
        if not modroot(3 * alpha**2 + a, p): #checks if square root exists
            print("No conversion") #otherwise, reject
            return
        B = mminv(max(modroot(3 * alpha**2 + a, p)), p) #calulates B
        A = 3*alpha*B % p #calulates A
        print(B,"y**2 = x**3 +",A,"x**2 + x") #prints curve equation
        print("An Isomorphism!") #all existing W to M mappings are isomorphisms
        print(time.process_time()*1000 - start)
        return A,B,alpha

def mtow(A,B,p):
    """Converts Montgomery curve to WNF"""
    #conversion always exists - no need to check
    start = time.process_time() *1000
    a = ((3-A**2)*mminv(3*B**2,p)) % p #calculates a
    b = ((2*A**3 - 9*A)*mminv(27*B**3,p)) % p #calculates b
    print("y**2 = x**3 + ",a, "x +", b) #prints curve equation
    print("An Isomorphism!") #all existing W to M mappings are isomorphisms
    print(time.process_time()*1000 - start)
    return a,b

def mtoe(A,B,p):
    """Converts Montgomery curve to twisted Edwards"""
    """Checks for conversion"""
    #start = time.process_time()*1000
    if A < 3 % p and A > -2 % p: #checks requirement of A
        print("A in {-2,2}") #if fails, reject
        return
    elif B == 0 % p: #checks B non zero
        print("B = 0") #otherwise reject
        return
    a = ((A+2)*mminv(B,p)) % p #calculates a
    d = ((A-2)*mminv(B,p)) % p # calculates d
    print(a,"x**2 + y**2 = 1 + ",d,"x**2y**2") #prints curve equation
    if legendres(a,p) == legendres(d,p):
        print("Not an Isomorphism :(") #not isomorphic if a & d both square/not square
    elif legendres(a,p) == 1:
        print("An Isomorphism!") #isomorphic if a square, d not square
    elif legendres(a,p) == -1:
        print("Not an Isomorphism :(") #not isomorphic if a not square
   # print(time.process_time()*1000 - start)
    return a,d

def etom(a,d,p):
    """Converts twisted Edwards to Montgomery"""
    """Checks for conversion"""
    #start = time.process_time() *1000
    if a == d: #checks parameters distinct
        print("a=d") #otherwise reject
        return
    elif a == 0 or d == 0: #checks parameters non zero
        print("Must be non zero") #otherwise reject
        return
    A = (2*(a+d)*mminv(a-d,p)) % p #calculates A
    B = (4*mminv(a-d,p)) % p #calculates B
    print(B,"y**2 = x**3 +",A,"x**2 + x") #prints curve equation
    if legendres(a,p) == legendres(d,p):
        print("Not an Isomorphism :(") #not isomorphic if a & d both square/not square
    elif legendres(a,p) == 1:
        print("An Isomorphism!") #isomorphic if a square, d not square
    #print(time.process_time()*1000 - start)
    return A,B

def etow(a,d,p):
    """Converts twisted Edwards to Weierstrass"""
    """Uses that W must have a pt of order 4 therefore T is transformable to M
    """
    A,B = etom(a,d,p)
    a,b = mtow(A,B,p)
    return a,b

def wtoe(a,b,p):
    """Converts Weierstrass to twisted Edwards """
    """Uses that W must have a pt of order 4 therefore T is transformable to M
    """
    start = time.process_time() *1000
    A,B,al = wtom(a,b,p)
    a,d = mtoe(A,B,p)
    print(time.process_time()*1000 - start)
    return a,d

#Curves
def pwtom(p1,a,b,p,A,B):
    """Transforms a Weierstrass point to a Montgomery point"""
    x1,y1 = p1 #assigns coordinates
    if math.isinf(p1[0]): return math.inf,1 #inf -> inf
    if y1**2 % p != (x1**3 + a*x1 + b) % p: #checks if point is on curve
        return False #otherwise reject
    if not A: #if user does not input A, calulates it
        s,alpha = wtom(a,b,p)[1],wtom(a,b,p)[2] #finds B, alpha from wtom
        if not alpha: #checks if a point of order two exists
            print("No conversion") #otherwise reject
            return
    else:
        alpha = A*mminv(3*B,p) % p #if user inputs A,B calculate alpha from them
        s = B #as above
    #exceptional points
    if p1 == (alpha,0): return 0,0 #if point has order two, map to 0,0
    #general points
    else:
        return s*(x1-alpha) % p, s*y1 % p

def pmtow(p1,A,B,p):
    """Transforms a Montgomery point to a Weierstrass point"""
    start = time.process_time()*1000
    x1,y1 = p1 #assigns coordinates
    if B*y1**2 % p != (x1**3 + A*x1**2 + x1) % p: #checks if point is on curve
        return False #otherwise reject
    #exceptional points
    if math.isinf(x1):
        print(time.process_time()*1000 - start)
        return math.inf,1 #inf -> inf
    #general points
    else:
        print(time.process_time()*1000 - start)
        return (x1*mminv(B,p) + A*mminv(3*B,p)) % p, y1*mminv(B,p) % p

def pmtoe(p1,A,B,p):
    """Transforms a Montgomery point to a twisted Edwards point"""
    start = time.process_time()*1000
    if A < 3 % p and A > -2 % p: #checks requirements on A for curve mapping
        print("A in {-2,2}") #if fails, reject
        return False
    elif B == 0 % p: #checks requirements on B for curve mapping
        print("B = 0") #if fails, reject
        return False
    x1,y1 = p1 #assigns coordinates
    if math.isinf(x1): return 0,1 #inf -> (0,1)
    if B*y1**2 % p != (x1**3 + A*x1**2 + x1) % p: #checks if point is on curve
        return False #otherwise reject
    #exceptional points
    elif y1 == 0 % p:
        print(y1)
        if x1 == 0 % p: return 0, -1 % p #(0,0) -> (0,-1)
        elif 2*x1 % p == (-A + modroot((A+2)*(A-2),p)[0]) % p:
                return math.inf,2 #point of order two on M -> inf ('positive' root)
        elif 2*x1 % p == (-A - modroot((A+2)*(A-2),p)[0]) % p:
                return math.inf,-2 #point of order two on M -> inf ('negative' root)
        else: return
    elif x1 == -1 % p:
        if y1 == modroot((A-2)*mminv(B,p),p):
            return math.inf,4 #point of order four on M -> inf ('positive' root)
        elif y1 == -modroot((A-2)*mminv(B,p),p) % p:
            return math.inf,-4 #point of order four on M -> inf ('negative' root)
    #general points
    else:
        print(time.process_time()*1000 - start)
        return x1*mminv(y1,p) % p, (x1-1)*mminv(x1+1,p) % p

def petom(p1,a,d,p):
    """Transforms a twisted Edwards point to a Montgomery point"""
    if a == d: #checks curve mapping requirements
        print("a=d") #if fails, reject
        return False
    elif a == 0 or d == 0:
        print("Must be non zero")
        return False
    x1,y1 = p1 #assigns coordinates
    #exceptional points
    if math.isinf(x1):
        A = (2*(a+d)*mminv(a-d,p)) % p #calculates A
        B = (4*mminv(a-d,p)) % p #calculates B
        if y1 % p == 2:
            x = (-A + modroot((A+2)*(A-2),p)[0])*mminv(2,p) % p #point of order two on M ('positive' root)
            return x,0
        elif y1 % p == -2 % p:
            x = (-A - modroot((A+2)*(A-2),p)[0])*mminv(2,p) % p #point of order two on M ('negative' root)
            return x,0
        elif y1 % p == 4:
            y = modroot((A-2)*mminv(B,p),p) #point of order four on M ('positive' root)
            return -1,y
        elif y1 % p == -4 % p:
            y = -modroot((A-2)*mminv(B,p),p) % p #point of order four on M ('negative' root)
            return -1,y
        else: return #no other points at infinity should exist on a twisted Edwards curve
    if (a*x1**2 + y1**2) % p != (1 + d*(x1**2)*(y1**2)) % p: #checks point is on the curve
        return False #otherwise reject
    elif x1 % p == 0 and y1 % p == 1: return math.inf,1 #(0,1) -> inf
    elif x1 % p == 0 and y1 % p == -1 % p: return 0,0 #(0,-1) -> (0,0)
    #general points
    else:
        return (1+y1)*mminv(1-y1,p) % p, (1+y1)*mminv(x1*(1-y1),p) % p

def petow(p1,a,d,p):
    """Transforms a twisted Edwards point to a Weierstrass point"""
    if a == d: #checks curve mapping requirements
        print("a=d") #if fails, reject
        return
    elif a == 0 or d == 0:
        print("Must be non zero")
        return
    x1,y1 = p1 #assigns coordinates
    #exceptional points
    if math.isinf(x1):
        #computes inf seperately, since may be (inf,4) or (inf,2)
        p2 = petom(p1,a,d,p) #computes point at infinity -> point on montgomery
        A,B = (2*(a+d)*mminv(a-d,p)) % p, (4*mminv(a-d,p)) % p #calculates A and B
        p3 = pmtow(p2,A,B,p) #computes point on montgomery -> point on W
        return p3
    if (a*x1**2 + y1**2) % p != (1 + d*(x1**2)*(y1**2)) % p: #checks point is on curve
        return False #otherwise reject
    elif x1 % p == 0 and y1 % p == 1:
        return math.inf,1 #(0,1) -> inf -> inf
    else:
        p2 = petom(p1,a,d,p) #computes point on Montgomery
        A,B = (2*(a+d)*mminv(a-d,p)) % p, (4*mminv(a-d,p)) % p #calculates A and B
        p3 = pmtow(p2,A,B,p) #computes point on W
        return p3

def pwtoe(p1,a,b,p):
    """Transforms a Weierstrass point to a Montgomery point"""
    """Need to check req for W (pt of order 4 exists etc)
    """
    x1,y1 = p1 #assigns cordinates
    if math.isinf(x1):
        return 0,1 #inf -> inf -> (0,1)
    if y1**2 % p != (x1**3 + a*x1 + b) % p: #checks point is on curve
        return False #otherwise reject
    else:
        A,B,al = wtom(a,b,p) #calculate Montgomery parameters
        p2 = pwtom(p1,a,b,p,A,B) #calculate point on Montgomery
        p3 = pmtoe(p2,A,B,p) #calculate point on twisted Edwards
        return p3


import numpy
import math
LOG_BASE=2.0

def H(X, bins):
    """
    Calculates the entropy of X.
    If X is already discretised, set \ref bins to the amount of bins, aka states of X.
    If X is not discretised, \ref bins will be used, to diskretise X into  \ref bins states

    @param X numpy vector
    @param bins If X is already diskretised, amount of bins of X. If X is not discretised, amount of bins to diskretise X into
    @return Returns the entropy of X
    """
    p_x, _ = numpy.histogram(X, bins=bins)
    p_x = p_x/len(X)
    H_ = 0
    for p in p_x:
        if p > 0:
            H_ -= p*math.log(p)
    H_ = H_/math.log(bins)
    return H_

def H_cond(X, Y, bins):
    """
    Calculates the conditional entropy of X depending on Y.
    If X and Y are already discretised, set \ref bins to the amount of bins, aka states of X and Y.
    If X and Y are not discretised, \ref bins will be used, to diskretise X and Y into \ref bins states

    @param X numpy vector depending on \ref Y
    @param Y numpy vector
    @param bins If X and Y are already diskretised specify the amount of bins of X and Y. If X and Y are not discretised specifies the amount of bins to diskretise X into
                bins can be spcified as tuple, e.g. bins = (bins_x, bins_y), to diskretise X and Y independently, or as a single value to diskretise X and Y similar, e.g. bins = amount_of_bins
    @return Returns the conditional entropy
    """
    if (type(bins) == list):
        ybins = bins[1]
        xbins = bins[0]
        base = LOG_BASE
    else:
        ybins = bins
        xbins = bins
        base = bins

    p_xy, _,_ = numpy.histogram2d(X,Y, bins=[xbins, ybins])
    p_xy = p_xy / len(X)
    p_y, _ = numpy.histogram(Y, bins=ybins)
    p_y = p_y/len(Y)
    H = 0
    for i in range(ybins):
        for j in range(xbins):
            if (p_xy[j,i] > 0):
                p_cond = p_xy[j,i]/p_y[i]
                H -= p_y[i] * p_cond * math.log(p_cond)
    H = H/math.log(base)
    return H

def I(X,Y,bins):
    """
    Calculates the mutual information of X and Y.
    If X and Y are already discretised, set \ref bins to the amount of bins, aka states of X and Y.
    If X and Y are not discretised, \ref bins will be used, to diskretise X and Y into \ref bins states

    @param X numpy vector
    @param Y numpy vector
    @param bins If X and Y are already diskretised specify the amount of bins of X and Y. If X and Y are not discretised specifies the amount of bins to diskretise X and Y into
                bins can be spcified as tuple, e.g. bins = (bins_x, bins_y), to diskretise X and Y independently, or as a single value to diskretise X and Y similar, e.g. bins = amount_of_bins
    @return Returns the mutual information
    """
    if (type(bins) == list):
        ybins = bins[1]
        xbins = bins[0]
        base = LOG_BASE
    else:
        ybins = bins
        xbins = bins
        base = bins

    p_xy, _,_ = numpy.histogram2d(X,Y, bins=[xbins, ybins])
    p_xy = p_xy / len(X)
    p_y, _ = numpy.histogram(Y, bins=ybins)
    p_y = p_y/len(Y)
    p_x, _ = numpy.histogram(X, bins=xbins)
    p_x = p_x/len(X)
    I_ = 0
    for i in range(xbins):
        for j in range(ybins):
            if p_x[i] > 0 and p_y[j] > 0 and p_xy[i,j] > 0:
                I_ += p_xy[i,j] * math.log(p_xy[i,j]/(p_x[i]*p_y[j]))

    I_ = I_/math.log(base)

    return I_

def I_cond(X,Y,Z,bins):
    """
    Calculates the conditional mutual information of X and Y depending on Z.
    If X, Y and Z are already discretised, set \ref bins to the amount of bins, aka states of X, Y and Z.
    If X, Y and Z are not discretised, \ref bins will be used, to diskretise X, Y and Z into \ref bins states

    @param X numpy vector
    @param Y numpy vector
    @param Z numpy vector
    @param bins If X, Y and Z are already diskretised specify the amount of bins of X, Y and Z. If X, Y and Z are not discretised specifies the amount of bins to diskretise X, Y and Z into
                bins can be spcified as tuple, e.g. bins = (bins_x, bins_y, bins_z), to diskretise X, Y and Z independently, or as a single value to diskretise X, Y and Z similar, e.g. bins = amount_of_bins
    @return Returns the mutual information
    """
    if (type(bins) == list):
        xbins = bins[0]
        ybins = bins[1]
        zbins = bins[2]
        base = LOG_BASE
    else:
        ybins = bins
        xbins = bins
        zbins = bins
        base = bins

    XYZ = numpy.transpose(numpy.array([X,Y,Z]))
    p_xyz, _ = numpy.histogramdd(XYZ, bins=[xbins, ybins, zbins])
    p_xyz = p_xyz/ len(Z)
    p_xz, _, _ = numpy.histogram2d(X,Z, bins=[xbins, zbins])
    p_xz = p_xz / len(X)
    p_yz, _, _ = numpy.histogram2d(Y,Z, bins=[ybins, zbins])
    p_yz = p_yz / len(Y)
    p_z, _ = numpy.histogram(Z, bins=zbins)
    p_z = p_z/len(Z)

    I = 0
    for i in range(xbins):
        for j in range(ybins):
            for k in range(zbins):
                if (p_xyz[i,j,k] > 0 and
                p_xz[i,k] > 0 and
                p_yz[j,k] > 0 and
                p_z[k] > 0):
                    I += p_xyz[i,j,k] * math.log((p_z[k] * p_xyz[i,j,k]) / (p_xz[i,k] * p_yz[j,k]))
    I = I/math.log(base)
    return I


import numpy as np

def softmax(x):
    return np.exp(x)/np.sum(np.exp(x))

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


import numpy as np

def trapezine(func, z_min, z_max, epsilon):
    """
    Perform extended trapezoidal rule to calculate numerical integration

    Input:
        func: the function to be integrated
        z_min: lower limit of the integration
        z_max: upper limit of the integration
        epsilon: desired accuracy of the integration. e.g. 1e-8
    Returns:
        new_I: the numerical integration result with desired accuracy
        err: error of the process O(h^3)"""
    # Setting global variable
    evaluation = 0

    # Initial values of iteration
    h = z_max - z_min  # range of integration
    n = 2       # number of points
    # Initial value of integration with 1 step
    new_I = h * (func(z_max) + func(z_min)) / 2
    relative_accuracy = 1    # Assign a non-zero value to start the loop
    evaluation += 2

    # iteration until reach the desired relative accuracy or reach the machine
    # accuracy (machine epsilon)
    while relative_accuracy >= epsilon:
        h = h / 2  # update step-size
        z_array = np.arange(z_min, z_max + h, h)
        n = len(z_array)
        old_I = new_I  # update compared value
        # Half the previous value and add new points - reduce the repeated
        # evaluations of function
        new_I = old_I / 2 + h * \
            sum(func(z_array[2 * i - 1]) for i in range(1, int((n - 1) / 2) + 1))
        relative_accuracy = abs((new_I - old_I) / old_I)
        # Half of the terms in the new integral is "truly evaluated" in this
        # iteration step
        evaluation += int(n / 2 - 1)
        # print(n,h,new_I,relative_accuracy)
    # Evaluating error of the integration
    err = new_I * 0.5 * relative_accuracy

    # number of terms evaluated
    print("Number of evaluated terms: ", int(evaluation))
    return new_I, err

def simpson(func, z_min, z_max, epsilon):
    """
    Perform extended Simpson's rule to calculate numerical integration value.

    Input:
        func: the function to be integrated
        z_min: lower limit of the integration
        z_max: upper limit of the integration
        epsilon: desired accuracy of the integration. e.g. 1e-8
    Returns:
        new_I: the numerical integration result with desired accuracy
        err: error of the process O(h^5)"""
    # Setting global variable
    evaluation = 0
    # Initial values
    h = z_max - z_min
    # Implement the trapezoidal summation for the simpson's rule
    # Inital summation of 2-point trapezine
    T_j = h * (func(z_max) + func(z_min)) / 2
    h = h / 2
    # Update z_array just like trapezoidal
    z_array = np.arange(z_min, z_max + h, h)
    n = len(z_array)
    # Intial iterated trapezodial term
    T_j1 = T_j / 2 + h * sum(func(z_array[int(2 * t - 1)])
                             for t in range(1, int((n - 1) / 2) + 1))
    # Inital integral of simpson's method with 2 terms from trapezoidal method
    new_I = 4 * T_j1 / 3 - T_j / 3
    relative_accuracy = 1      # Assign a non-zero value to old_I to start the while loop
    evaluation += 3
    # Iterate until reach desired accuracy
    while relative_accuracy >= epsilon:
        old_I = new_I
        h = h / 2         # Similar to trapezoidal, half the step-size each loop
        # update z-array just like trapezoidal
        z_array = np.arange(z_min, z_max + h, h)
        n = len(z_array)
        T_j = T_j1        # update the T(rapezodial) summation
        T_j1 = T_j / 2 + h * \
            sum(func(z_array[int(2 * t - 1)]) for t in range(1, int((n - 1) / 2) + 1))
        new_I = 4 * T_j1 / 3 - T_j / 3
        evaluation += n // 2         # Evaluation number added similar to trapezoidal rule
        relative_accuracy = abs((new_I - old_I) / old_I)
    # Evaluate the error
    err = new_I * 0.5 * relative_accuracy

    # number of terms evaluated
    # each loop uses 2 generations of trapezoidal summation so evaluation is
    # 1.5 times of the number of terms in the last trapezoidal summation
    print("Number of evaluated terms: ", int(evaluation))
    return new_I, err


"""
Module for working with Bernstein polynomials.
"""

import numpy as np
import sympy as sp
import typing
import vorpy.symbolic

def bernstein_polynomial (t:typing.Any, *, degree:int, k:int) -> typing.Any:
    """
    Returns the kth Bernstein polynomial of given degree in the given symbol.
    It's assumed that the interval for the Bernstein polynomial is [0,1].

    TODO: This should also specify the interval over which the polynomial is defined.
    TODO: Should be able to specify dtype.
    """
    if degree < 0:
        raise TypeError(f'expected degree (which was {degree}) to be nonnegative.')
    if k < 0 or k > degree:
        raise ValueError(f'expected 0 <= k <= degree (but k = {k} and degree = {degree})')

    s = 1-t
    return sp.binomial(degree,k) * s**(degree-k) * t**k

def bernstein_polynomial_basis (t:typing.Any, *, degree:int) -> np.ndarray:
    """
    Returns a vector of Bernstein polynomials of the specified degree with k = 0 to degree.
    If t is a symbol, then this is a symbolic vector.  If t is a float, then this is a float
    valued vector.

    TODO: This should also specify the interval over which the polynomial is defined.
    TODO: Should be able to specify dtype.
    """
    return np.array([bernstein_polynomial(t, degree=degree, k=k) for k in range(degree+1)])

def bernstein_polynomial_L2_inner_product (t:typing.Any, *, degree:int) -> np.ndarray:
    B = bernstein_polynomial_basis(t, degree=degree)

    def L2_inner_product (f0, f1) -> typing.Any:
        return sp.integrate(f0*f1, (t, 0, 1))

    return np.array([
        [L2_inner_product(b_i,b_j) for b_j in B]
        for b_i in B
    ])

def bernstein_polynomial_L2_inner_product_inv (t:typing.Any, *, degree:int) -> np.ndarray:
    B = bernstein_polynomial_basis(t, degree=degree)

    def L2_inner_product (f0, f1) -> typing.Any:
        return sp.integrate(f0*f1, (t, 0, 1))

    L2_in_B = bernstein_polynomial_L2_inner_product(t, degree=degree)
    return np.array(sp.Matrix(L2_in_B).inv().tolist())


import scipy.integrate as spi

def arc_length(f,a,b,h=0.001,N=1000):
    '''Approximate the arc length of y=f(x) from x=a to x=b.

    Parameters
    ----------
    f : (vectorized) function of one variable
    a,b : numbers defining the interval [a,b]
    h : step size to use in difference formulas
    N : number of subintervals in trapezoid method

    Returns
    -------
    Approximation of the integral \int_a^b \sqrt{1 + (f'(x))^2} dx
    representing the arc length of y=f(x) from x=a to x=b.
    '''
    x = np.linspace(a,b,N+1)
    y = f(x)

    # Compute central difference formula for x_k for 1 <= k <= N-1
    h = np.min([h,(b-a)/N]) # Make sure that h is smaller than the size of the subintervals
    x_interior = x[1:-1]
    df_interior = (f(x_interior + h) - f(x_interior - h))/(2*h)

    # Use forward/backward difference formula at the endpoints
    df_a = (f(a + h) - f(a))/h
    df_b = (f(b) - f(b - h))/h
    df = np.hstack([[df_a],df_interior,[df_b]])

    # Compute values of the integrand in arc length formula
    y = np.sqrt(1 + df**2)

    # Compute the integral
    L = spi.trapz(y,x)

    return L

print(arc_length(lambda x: x,0,1))


from base_regression import BasicRegression

import numpy as np

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

def softmax(z):
    # Avoid numerical overflow by removing max
    # See: http://cs231n.github.io/linear-classify/
    e = np.exp(z - np.max(z))
    return e / np.sum(e)

class LogisticRegression(BasicRegression):
    """Binary logistic regression with gradient descent optimizer."""
    def _loss(self, theta):
        X, y, C = self.X, self.y, self.C

        predict = sigmoid(X.dot(theta))
        # prevent overflow or underflow
        predict = np.clip(predict, 1e-15, 1 - 1e-15)
        loss = - np.mean(np.sum(y * np.log(predict) + (1 - y) * np.log(1 - predict)))
        grad = (1.0 / self.n_samples) * (X.T.dot(predict - y))

        # Add regularization to the loss.
        loss += 0.5 * C * np.sum(theta * theta)
        grad += C * theta

        return loss, grad

    def _predict(self, X=None, threshold=0.5):
        """Predict class labels for samples in X."""
        X = self._add_intercept(X)
        predict = sigmoid(X.dot(self.theta))
        return np.where(predict > threshold , 1, 0)

class Softmax(BasicRegression):
    """Multi-class logistic regression with gradient descent optimizer."""
    def init_weights(self):
        """Mutli-class weights"""
        self.num_classes = np.max(self.y) + 1 # assume y takes values 0...K-1 where K is number of classes
        return np.random.normal(size=(self.n_features + 1, self.num_classes), scale=0.5)

    def _loss(self, theta):
        """
        Naive implementation for softmax loss due to its simplicity and readibility
        For vectorized version: https://github.com/transedward/cs231n/blob/master/assignment1/cs231n/classifiers/softmax.py#L62
        """
        X, y, C, n_samples, n_features = self.X, self.y, self.C, self.n_samples, self.n_features
        loss = 0.0
        grad = np.zeros_like(theta)
        for i in range(n_samples):
            scores = X[i].dot(theta)
            probability = softmax(scores)
            loss += -np.log(probability[y[i]])
            # gradient for softmax loss
            # http://ufldl.stanford.edu/wiki/index.php/Softmax_Regression
            for j in range(self.num_classes):
                grad[:, j] += (probability[j] - (j == y[i])) * X[i, :]

        # Right now the loss is a sum over all training examples, but we want it
        # to be an average instead so we divide by n_samples.
        loss /= n_samples
        grad /= n_samples

        # Add regularization to the loss.
        loss += 0.5 * C * np.sum(theta * theta)
        grad += C * theta

        return loss, grad

    def _predict(self, X=None):
        """Predict class labels for samples in X."""
        X = self._add_intercept(X)
        scores = X.dot(self.theta)
        predict = np.argmax(scores, axis=1)
        return predict


from scipy.special import factorial
from itertools import chain, combinations

def binomial(n, k):
    """
    Binomial coefficients, thanks to Andrew Dalke.
    """
    if 0 <= k <= n:
        n_tok = 1
        k_tok = 1
        for t in range(1, min(k, n - k) + 1):
            n_tok *= n
            k_tok *= t
            n -= 1
        return n_tok // k_tok
    else:
        return 0

def fact(n):
    """
    Wrap the predefined function factorial from scipy.misc
    :param n: integer
    :return: factorial of n type float
    """
    return float(factorial(n, True))

def power_lists(s):
    """
    Returns the power set of the list s, as a list of lists.
    :param s: list of elements.
    :return: power sets of the list s, as collection of all of the possible 'sub-lists' of the input list.
    """
    result = []
    for z in chain.from_iterable(combinations(s, r) for r in range(len(s) + 1)):
        result += [list(z)]
    return result


# coding: utf-8

from math import log
import copy
import pandas as pd
import matplotlib.pyplot as plt
import datetime as dt
import urllib.request, urllib.error
import numpy as np

pd.set_option("display.width",2000)

# Import and prepare data

url = "https://raw.githubusercontent.com/datadista/datasets/master/COVID%2019/nacional_covid19.csv"
base_name = "COVID-19-nacional"
fname =  base_name + ".csv"
urllib.request.urlretrieve(url,fname)
#try:
#    url = base_url + dt.datetime.today().strftime("%Y-%m-%d") + ".csv"
#    fname =  base_name + dt.datetime.today().strftime("%Y-%m-%d") + ".csv"
#    urllib.request.urlretrieve(url,fname)
#except urllib.error.HTTPError as e:
#    url = base_url + dt.datetime.today().strftime("%Y-%m-%d") + "_0.csv"
#    fname = base_name + dt.datetime.today().strftime("%Y-%m-%d") + "_0.csv"
#    urllib.request.urlretrieve(url)

def parse_date(fecha):
    return dt.datetime.strptime(fecha,"%Y-%m-%d").date()

df = pd.read_csv(fname,
                parse_dates={"dates": ["fecha"]},
                date_parser=parse_date,index_col="dates")

df.sort_index(inplace=True)

print("Doubling time (deaths) = {:} days".format(
        log(2)/log(df.fallecimientos[-1]/df.fallecimientos[-2])
     ))
print("Doubling time (cases) = {:} days".format(
        log(2)/log(df.casos[-1]/df.casos[-2])
     ))

#Plot
fig, ax = plt.subplots(2,1)
fig.suptitle("COVID-19 Spain Growth Rate")
x = df.index[-15:]

y = df.fallecimientos[-15:]
ax[0].set_title("(cumulative) deaths in Spain",y=0.8)
ax[0].plot(x,y,color="r",marker='.',label="Deaths")
double2days = [df.fallecimientos[-3]*2**(i/2) for i in range(3)]
double3days = [df.fallecimientos[-3]*2**(i/3) for i in range(3)]
double5days = [df.fallecimientos[-3]*2**(i/5) for i in range(3)]
ax[0].plot(df.index[-3:],double2days,label="Doubles every 2 days")
ax[0].plot(df.index[-3:],double3days,label="Doubles every 3 days")
ax[0].plot(df.index[-3:],double5days,label="Doubles every 5 days")
ax[0].set_yscale("log")

y = df.casos[-15:]
ax[1].set_title("(cumulative) cases in Spain",y=0.8)
ax[1].plot(x,y,color="k",marker='.',label="Confirmed cases")
double2days = [df.casos[-3]*2**(i/2) for i in range(3)]
double3days = [df.casos[-3]*2**(i/3) for i in range(3)]
double5days = [df.casos[-3]*2**(i/5) for i in range(3)]
ax[1].plot(df.index[-3:],double2days,label="Doubles every 2 days")
ax[1].plot(df.index[-3:],double3days,label="Doubles every 3 days")
ax[1].plot(df.index[-3:],double5days,label="Doubles every 5 days")
ax[1].set_yscale("log")

#plt.gcf().autofmt_xdate()
plt.legend()

for ax in fig.get_axes():
    ax.label_outer()

plt.show()


import math
import numpy as np

def vector_from_angle_and_length(angle, length):
    vector = (length * math.sin(angle), length * math.cos(angle))
    vector = np.array(vector)
    return vector

def polar_coordinates(x, y):
    angle = math.atan2(x, y)
    if angle < 0.0:
        angle += 2.0 * math.pi
    length = math.sqrt(x**2 + y**2)
    return angle, length

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


from sympy import *
t, dt = symbols('t dt')
P, Q = symbols('P Q', cls=Function)

# Target expression P(t_{n+1/2})*Q(t_{n+1/2})
# Simpler: P(0)*Q(0)
# Arithmetic means of each factor:
# 1/4*(P(-dt/2) + P(dt/2))*(Q(-dt/2) + Q(dt/2))
# Arithmetic mean of the product:
# 1/2*(P(-dt/2)*Q(-dt/2) + P(dt/2)*Q(dt/2))
# Let's Taylor expand to compare

target = P(0)*Q(0)
num_terms = 6
P_p = P(t).series(t, 0, num_terms).subs(t, dt/2)
print(P_p)
P_m = P(t).series(t, 0, num_terms).subs(t, -dt/2)
print(P_m)
Q_p = Q(t).series(t, 0, num_terms).subs(t, dt/2)
print(Q_p)
Q_m = Q(t).series(t, 0, num_terms).subs(t, -dt/2)
print(Q_m)

product_mean = Rational(1,2)*(P_m*Q_m + P_p*Q_p)
product_mean = simplify(expand(product_mean))
product_mean_error = product_mean - target

factor_mean = Rational(1,2)*(P_m + P_p)*Rational(1,2)*(Q_m + Q_p)
factor_mean = simplify(expand(factor_mean))
factor_mean_error = factor_mean - target

print('product_mean_error:', product_mean_error)
print('factor_mean_error:', factor_mean_error)


import numpy as np
# 矩阵乘法的几何意义 https://zhuanlan.zhihu.com/p/64606322
# 矩阵乘法  矩阵积 点积  matrix product
a = np.array([[4, 5], [6, 7]])
b = np.arange(4).reshape((2, 2))
print(a)
# [[4 5]
#  [6 7]]
print(b)
# [[0 1]
#  [2 3]]
# 矩阵乘法
c_dot = np.dot(a,b)
print(c_dot)
# [[10 19]
#  [14 27]]
c_dot_2 = a.dot(b)
# [[10 19]
#  [14 27]]
print(a @ b)

print(a * b)
"""
a1,1 * b1,1 + a1,2 * b2,1 + a1,n * bn,1  ,  a1,1 * b1,2 + a1,2 * b2,2 + a1,n * bn,2
a2,1 * b1,1 + a2,2 * b2,1 + a2,n * bn,1  ,  a2,1 * b1,2 + a2,2 * b2,2 + a2,n * bn,2
= A 的行数 ，B的列数
= [
[4*0+5*2,4*1+5*3],
[6*0+7*2,6*1+7*3]
]
"""


"""This module provides functions related to redemptions and interest."""

from typing import Optional, Union, Tuple
import pandas as pd
import numpy as np

def determine_annuity(
    months_to_legal_maturity: int, outstanding_balance: float, interest_rate:
        float) -> float:
    """Calculate the (monthly) annuity.
    For mortgage with specified months_to_legal_maturity (>=1), outstanding
    balance, and interest rate (decimal, annual).
    """
    tau = interest_rate / 12
    kappa = (1 + tau) ** months_to_legal_maturity
    return outstanding_balance * tau * kappa / (kappa - 1)

def determine_interest(outstanding_balance: float, interest_rate:
                       float) -> float:
    """Determine the interest of a mortgage.
    In a month in case the principal at start of the month is given by
    outstanding_balance and the interest rate is given by interest_rate
    (as decimal, annual).
    """
    return outstanding_balance * interest_rate / 12

def determine_redemption_linear(
    months_to_legal_maturity: int, outstanding_balance: float
) -> float:
    """Determine the redemption of linear mortgage.

    In month t in case the principal at start of the month is given by outstanding_balance
    """
    return outstanding_balance / months_to_legal_maturity

def determine_redemption_bullet(months_to_legal_maturity: int, outstanding_balance: float
) -> float:
    """Determine the redemption of bullet mortgage.

    In a month in case the principal at start of the month is given by outstanding_balance.
    """
    return outstanding_balance if months_to_legal_maturity == 1 else 0

def determine_redemption_annuity(
    months_to_legal_maturity: int,
    outstanding_balance: float,
    interest_rate: float,
    annuity: Optional[float] = None,
) -> float:
    """Calculate the redemption of an annuity mortgage.

    On basis of the outstanding_balance at the start of the month, the interest rate, and the
    months to legal maturity. The annuity can optionally be given in case it has
    been calculated elsewhere.
    """
    if annuity is None:
        annuity = determine_annuity(
            months_to_legal_maturity, outstanding_balance, interest_rate
        )
    return annuity - determine_interest(outstanding_balance, interest_rate)

def determine_redemption(
    months_to_legal_maturity: int,
    outstanding_balance: float,
    mortgage_type: str,
    interest_rate: Optional[float] = None,
) -> float:
    """Determine the redemption of mortgage.

    For mortgage of type mortgage_type, in a month in case the principal at
    start of the month is given by outstanding_balance. In case of
    annuity the interest_rate is required as input.
    """
    if mortgage_type == "bullet":
        return determine_redemption_bullet(
            months_to_legal_maturity, outstanding_balance
        )
    elif mortgage_type == "linear":
        return determine_redemption_linear(
            months_to_legal_maturity, outstanding_balance
        )
    elif mortgage_type == "annuity":
        return determine_redemption_annuity(
            months_to_legal_maturity, outstanding_balance, interest_rate, None
        )


import numpy as np
from scipy.signal import fftconvolve
from scipy.ndimage.filters import convolve

def gaussian2(size, sigma):
    """Returns a normalized circularly symmetric 2D gauss kernel array

    f(x,y) = A.e^{-(x^2/2*sigma^2 + y^2/2*sigma^2)} where

    A = 1/(2*pi*sigma^2)

    as define by Wolfram Mathworld
    http://mathworld.wolfram.com/GaussianFunction.html
    """
    A = 1 / (2.0 * np.pi * sigma**2)
    x, y = np.mgrid[-size // 2 + 1:size // 2 + 1, -size // 2 + 1:size // 2 + 1]
    g = A * np.exp(-((x**2 / (2.0 * sigma**2)) + (y**2 / (2.0 * sigma**2))))
    return g

def fspecial_gauss(size, sigma):
    """Function to mimic the 'fspecial' gaussian MATLAB function
    """
    x, y = np.mgrid[-size // 2 + 1:size // 2 + 1, -size // 2 + 1:size // 2 + 1]
    g = np.exp(-((x**2 + y**2) / (2.0 * sigma**2)))
    return g / g.sum()

size = 11
sigma = 1.5
window = fspecial_gauss(size, sigma)

def ssim(img1, img2, cs_map=False):
    """Return the Structural Similarity Map corresponding to input images img1
    and img2 (images are assumed to be uint8)

    This function attempts to mimic precisely the functionality of ssim.m a
    MATLAB provided by the author's of SSIM
    https://ece.uwaterloo.ca/~z70wang/research/ssim/ssim_index.m
    """
    # img1 = img1.astype('float32')
    # img2 = img2.astype('float32')

    K1 = 0.01
    K2 = 0.03
    L = 255
    C1 = (K1 * L) ** 2
    C2 = (K2 * L) ** 2

    mu1 = convolve(window, img1, mode='nearest')
    mu2 = convolve(window, img2, mode='nearest')
    mu1_sq = mu1 * mu1
    mu2_sq = mu2 * mu2
    mu1_mu2 = mu1 * mu2
    sigma1_sq = convolve(window, img1 * img1, mode='nearest') - mu1_sq
    sigma2_sq = convolve(window, img2 * img2, mode='nearest') - mu2_sq
    sigma12 = convolve(window, img1 * img2, mode='nearest') - mu1_mu2

    if cs_map:
        return (((2 * mu1_mu2 + C1) * (2 * sigma12 + C2)) /
                ((mu1_sq + mu2_sq + C1) * (sigma1_sq + sigma2_sq + C2)),
                (2.0 * sigma12 + C2) / (sigma1_sq + sigma2_sq + C2))
    else:
        return (((2 * mu1_mu2 + C1) * (2 * sigma12 + C2)) /
                ((mu1_sq + mu2_sq + C1) * (sigma1_sq + sigma2_sq + C2)))


import sympy
import argparse
from xml.dom import minidom
import svg.path as sp
import os
from subprocess import call

def main():
	parser = argparse.ArgumentParser(description = "Converts SVG paths to parametric equations for plotting")
	parser.add_argument('file', metavar = 'f', help="SVG file to parse")
	parser.add_argument('--usepoly', action='store_true')
	args = parser.parse_args()
	Convert(args.file, args.usepoly)

class Convert():
	def __init__(self, filename, polybeziers = False):
		self.filename = filename
		self.dir = os.path.dirname(self.filename)
		self.name = os.path.basename(self.filename).split(".")[0]
		self.texfilename = os.path.join(self.dir, self.name + ".tex")
		self.outfilename = os.path.join(self.dir, self.name + ".paths")

		self.usepoly = polybeziers

		self.doc = minidom.parse(self.filename)
		self.paths = [path.getAttribute('d') for path in self.doc.getElementsByTagName('path')]
		self.splitPaths()
		self.convertPaths()
		self.printEqns()

		self.doc.unlink()

	def splitPaths(self):
		self.parsedPaths = []
		for path in self.paths:
			self.parsedPaths.append(sp.parse_path(path))
	def typ (self, seg):
		if isinstance(seg, sp.path.CubicBezier):
			return "CubicBezier"
		elif isinstance(seg, sp.path.Line):
			return "Line"
		elif isinstance(seg, sp.path.QuadraticBezier):
			return "QuadraticBezier"
		elif isinstance(seg, sp.path.Arc):
			return "Arc"
	def convertPaths(self):
		self.equations = []
		for path in self.parsedPaths:
			if self.usepoly:
				prevInstance = "Move"
				prevSegment = False
				self.polypaths = []
				for segment in path:
					if isinstance(segment, sp.path.CubicBezier) and prevInstance in ["CubicBezier", "QuadraticBezier"] and segment.start == prevSegment.end:
						self.polypaths[-1].append(segment)
					else:
						self.polypaths.append([segment])
					prevInstance = self.typ(segment)
					prevSegment = segment

				for polysegment in self.polypaths:
					eqnsegment = []

					if len(polysegment) == 1:
						eqnsegment.append(self.convert(polysegment[0], self.typ(polysegment[0])))
					else:
						# combine those curvessss
						eqnsegment.append(self.convertToPoly(polysegment))

					self.equations.append(eqnsegment)
			else:
				eqnsegment = []
				for segment in path:
					eqnsegment.append(self.convert(segment, self.typ(segment)))
				self.equations.append(eqnsegment)
	def convert(self, seg, typ):
		start = {
			"x": seg.start.real,
			"y": seg.start.imag
		}

		end = {
			"x": seg.end.real,
			"y": seg.end.imag
		}

		t = sympy.symbols("t")
		if typ == "CubicBezier":
			c1 = {
				"x": seg.control1.real,
				"y": seg.control1.imag
			}

			c2 = {
				"x": seg.control2.real,
				"y": seg.control2.imag
			}

			xeqn = sympy.simplify(((1-t)**3)*(start["x"]) + (3*(1-t)**2)*t*(c1["x"]) + 3*(1-t)*(t**2)*(c2["x"]) + (t**3)*end["x"])
			yeqn = sympy.simplify(((1-t)**3)*(start["y"]) + (3*(1-t)**2)*t*(c1["y"]) + 3*(1-t)*(t**2)*(c2["y"]) + (t**3)*end["y"])

		if typ == "QuadraticBezier":
			c1 = {
				"x": seg.control1.real,
				"y": seg.control1.imag
			}

			xeqn = sympy.simplify(((1-t)**2)*(start["x"]) + (2*(1-t))*t*(c1["x"]) + (t**2)*end["x"])
			yeqn = sympy.simplify(((1-t)**2)*(start["y"]) + (2*(1-t))*t*(c1["y"]) + (t**2)*end["y"])

		elif typ == "Line":
			xeqn = sympy.simplify((1-t)*start["x"] + t*end["x"])
			yeqn = sympy.simplify((1-t)*start["y"] + t*end["y"])

		return [xeqn, yeqn]
	def convertToPoly(self, polyseg):
		points = []
		points.append({
			"x": polyseg[0].start.real,
			"y": polyseg[0].start.imag
		})

		for segment in polyseg:
			points.append({
				"x": segment.control1.real,
				"y": segment.control1.imag
			})

			if(self.typ(segment) == "CubicBezier"):
				points.append({
					"x": segment.control2.real,
					"y": segment.control2.imag
				})

			points.append({
				"x": segment.end.real,
				"y": segment.end.imag
			})

		t = sympy.symbols("t")
		xeqn = 0
		yeqn = 0
		i = 0;
		n = len(points) - 1
		for point in points:
			coeff = sympy.simplify(sympy.binomial(n, i)*(1-t)**(n-i)*t**i)
			xeqn += sympy.simplify(point["x"]*coeff)
			yeqn += sympy.simplify(point["y"]*coeff)
			i += 1
		simxeqn = sympy.simplify(xeqn)
		simyeqn = sympy.simplify(yeqn)

		return [simxeqn, simyeqn]

	def printEqns(self):
		if self.usepoly:
			self.outfile = open(self.outfilename, "w")

			counter = 0
			for segment in self.equations:
				self.outfile.write("=== Path " + str(counter + 1) + " ===\n")

				for equation in segment:
					self.outfile.write("x(t) = " + str(equation[0].evalf(11)) + "\n")
					self.outfile.write("y(t) = " + str(equation[1].evalf(11)) + "\n\n")
				counter += 1
			self.outfile.close()
			call(["cat", os.path.join(self.outfilename)])
		else:
			self.texfile = open(self.texfilename, "w")
			self.texfile.write("\\documentclass[a4paper]{report}\n")
			self.texfile.write("\\usepackage{amsmath}\n")
			self.texfile.write("\\usepackage[margin=0.5in]{geometry}\n")
			self.texfile.write("\\renewcommand{\\thesection}{\\arabic{section}}\n")
			self.texfile.write("\\begin{document}\n")
			self.texfile.write("Plot the following equations from $t=0$ to $t=1$:")

			counter = 0
			for segment in self.equations:
				self.texfile.write("\\section{Path " + str(counter + 1) + "}\n")

				for equation in segment:
					self.texfile.write("\\begin{align}\n")
					self.texfile.write("\\textrm{x}(t) &= " + sympy.latex(equation[0].evalf(11)) + "\\\\\n")
					self.texfile.write("\\textrm{y}(t) &= " + sympy.latex(equation[1].evalf(11)) + "\n")
					self.texfile.write("\\end{align}\n")

				counter += 1
			self.texfile.write("\\end{document}\n")

			self.texfile.close()
			self.compile()
	def compile(self):
		call(["pdflatex", "-output-directory=" + self.dir, self.texfilename])
		call(["rm", os.path.join(self.dir, self.name+".aux"), os.path.join(self.dir, self.name+".log")])
		call(["evince", os.path.join(self.dir, self.name+".pdf")])

main()


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

class NewtonRaphson:
    function = None
    derivative = None
    initial_point = None
    def __init__(self, function, derivative, initial_point):
        self.function = function
        self.derivative = derivative
        self.initial_point = initial_point

    def isInside(self,error):
        return abs(self.function(self.initial_point)) < error

    def solve(self, error, steps=False):
        for i in range(100):
            if steps:
                print("f(%f) = %f" % (self.initial_point, self.function(self.initial_point)))
            self.initial_point = self.initial_point - (self.function(self.initial_point)/self.derivative(self.initial_point))
            if self.isInside(error):
                print("Bingo!  f(%f) = %f" % (self.initial_point, self.function(self.initial_point)))
                return self.initial_point
        print('demasiadas iteraciones, busca un valor inicial mas conveniente')

# How to use it
#first we define an object of the class NewtonRhapson with receives the main function, its derivative and an initial point
nr = NewtonRaphson(lambda x: x**4 + x -3, lambda x: 4*(x**3) +1, 3)
#in this case we use lambdas to make it a one liner, but the function could be defined normally if it's preferred

#we can solve the equation by giving it a relative an absolute error (comparing it to the real result)
nr.solve(0.050)

#if we wanted to see each iteration of this process, we jus need to add a True to the arguments of the solve method
# nr.solve(0.050, True)


"""
Given an integer n, return any array containing n unique integers such that they add up to 0.
"""
from typing import List
import numpy as np

class Solution:
    def sumZero(self, n: int) -> List[int]:
        result = [0 for _ in range(n)]
        val = 1
        if n % 2 == 1:
            result[n // 2] = 0
            for idx in range(1, n // 2 + 1):
                result[n // 2 - idx] = -val
                result[n // 2 + idx] = val
                val += 1
        else:
            right = n // 2
            left = right - 1
            while right < n:
                result[right] = val
                result[left] = -val
                right += 1
                left -= 1
                val += 1
        return result

if __name__ == "__main__":

    def check(n:int, arr: List[int]) -> bool:
        if np.sum(arr) == 0 and len(np.unique(arr)) == n:
            return True
        return False

    actual = Solution().sumZero(5)
    assert check(5, actual) == True, actual

    actual = Solution().sumZero(4)
    assert check(4, actual) == True, actual

    actual = Solution().sumZero(3)
    assert check(3, actual) == True, actual

    actual = Solution().sumZero(2)
    assert check(2, actual) == True, actual

    actual = Solution().sumZero(1)
    assert check(1, actual) == True, actual


from timeit import default_timer as timer
from numpy.random import randint

def all_subset_sum(S, t, result, partial=[]):
    """ Returns all possible combinations of subset sum"""
    if sum(partial) == t:
        result.append(partial)
    elif sum(partial) < t:
        for i in range(len(S)):
            all_subset_sum(S[:i] + S[i+1:], t, result, partial + [S[i]])

def subset_sum1(S, t):
    """ Return True if there is a possible subset sum"""
    if t in S:
        return True
    else:
        for i in range(len(S)):
            if subset_sum1(S[i+1:], t - S[i]):
                return True
    return False

def subset_sum2(S, t):
    if not S:
        return False
    elif t in S:
        return True
    else:
        return subset_sum2(S[1:], t) or subset_sum2(S[1:], t - S[0])

def subset_sum_dp(S, t, i, memo=dict()):
    # key = "%d:%d" % (t, S[0])
    # if key in memo:
    #     return memo[key]
    # elif not S:
    #     return False
    # elif t == 0:
    #     return True
    # elif t < 0:
    #     return False
    # else:
    #     memo[key] = subset_sum_dp(S[1:], t, memo) or subset_sum_dp(S[1:], t - S[0], memo)
    # return memo[key]
    key = "%d:%d" % (t, i)
    if key in memo:
        return memo[key]
    if t == 0:
        return True
    elif t < 0:
        return False
    elif i < 0:
        return False
    else:
        memo[key] = subset_sum_dp(S, t - S[i], i - 1, memo) or \
                    subset_sum_dp(S, t, i - 1, memo)
    return memo[key]

if __name__ == "__main__":
    #S = list(randint(100, size=100, dtype=int))
    S = [2, 4, 6, 10]
    t = 16
    # result = []
    # start = timer()
    # all_subset_sum(S, t, result, [])
    # end = timer()
    # print("all subsets sum %s time (ms) %.4f\n" % (str(result), (end - start) * 1000))

    start = timer()
    result = subset_sum1(S, t)
    end = timer()
    print("Subset sum 1 result %s time (ms) %.4f" % (str(result), (end - start) * 1000))

    start = timer()
    result = subset_sum2(S, t)
    end = timer()
    print("Subset sum 2 result %s time (ms) %.4f" % (str(result), (end -start) * 1000))

    memo = dict()
    start = timer()
    result = subset_sum_dp(S, t, len(S) - 1, memo)
    end = timer()
    print("Subset sum DP result %s time (ms) %.4f" % (str(result), (end -start) * 1000))


#!/usr/bin/env python

"""
Title: Solving Lord Kelvin's Integral using Romberg Integration.

Solution to Problem Set 3, Problem 2
~ Arsh R. Nadkarni

To run:
python gaussian_lk_3_2.py

- Can change the no. of points by changing N in romberg()
- Can change the tolerance by changing eps in lk()

"""
# import libraries
from math import exp, pi, sqrt
import numpy as np

# construct trapezoid-rule ftion (taken from pseudo-code)
def trap(f, a, b, n):
    N = 2**n
    n = np.linspace(1, N//2, N//2)
    h = (b-a)/N
    In = 0
    for i in n:
        In = In + f(a + (2*i - 1)*h)
    return h*In

# construct romberg integrator function (taken from pseudo-code)
def romberg(f, a, b, eps):
    N = 30               # no. of points that make up the smaller intervals
    h = (b-a)/(N-1)      # length of each small interval
    I = []
    foo = []
    foo.append(1/2*(b - a)*(f(a) + f(b)))
    I.append(foo)
    for n in range(1, N):
        foo = []
        foo.append(0.5*I[n - 1][0] + trap(f, a, b, n))
        for k in range(1, n + 1):
            q = 4**k
            foo.append((q*foo[k - 1]-I[n-1][k-1])/(q - 1))
        I.append(foo)
        if(abs(I[n][n] - I[n][n - 1]) < eps * I[n][n - 1]):
            return I[n][n]
    print("Failure to converge!")

# construct a function to get an answer to the integral and find sigma
def lk():
    # define the function to be integrated and account for dividing by zero
    def f(x):
        if((exp(x) - 1) == 0):
            return 0
        else:
            return exp(-1*(x**2))
    # function arguments and integration limits
    a = -100    # lower limit (-inf.)
    b = 100     # upper limit (inf.)
    eps = 1e-15  # error tolerance
    # calculate the value of the integral without the constants
    answer = romberg(f, a, b, eps)
    print("Value of Lord Kelvin's Integral =", answer)
lk()

"""
It is observed that as the error tolerance is increased (going from 1e-5 to 1e-15),
the answer comes out to be even closer to the analytical value (1.77245). A MathError
is encountered if the limits of integration are increased beyond 100. However, there
is definitely symmetry in the function to be integrated about y=0, so we can just
integrate it from 0 to infinity (some large value) and multiply it by 2. This will add
the areas under the curve, which happen to be equal in this case.

"""

Problem Sets/Problem Set 3/gaussian_lk_3_2.py


import transform as tr
import numpy as np
import cmath as cm
import matplotlib.pyplot as plt

def main():
    # amount of bins equals to sample rate

    n = 32
    arguments = np.arange(0, n) * np.pi / 6
    function_values = list(map(lambda x: np.sin(x) + np.cos(4 * x), arguments))

    fft_result = tr.conf_dif_fft(function_values, n, 1)
    reverse_fft_result = tr.conf_dif_fft(fft_result, n, -1)

    dft_result = tr.conf_dft(function_values, n, 1)
    reverse_dft_result = tr.conf_dft(dft_result, n, -1)

    # normalizing the transformed output
    for i in range(n):
        fft_result[i] /= n
        dft_result[i] /= n

    frequencies = [x for x in range(-int(n / 2), int(n / 2), 1)]

    # shifting second half of transform results
    shifted_fft = tr.fft_shift(fft_result)
    shifted_dft = tr.fft_shift(dft_result)

    fft_amplitude_values = []
    dft_amplitude_values = []
    fft_phase_values = []
    dft_phase_values = []

    # ejecting magnitude and phase
    for i in range(n):
        fft_amplitude_values.append(abs(shifted_fft[i]))
        dft_amplitude_values.append(abs(shifted_dft[i]))
        fft_phase_values.append(cm.phase(shifted_fft[i]))
        dft_phase_values.append(cm.phase(shifted_dft[i]))

    # possible noise reduction
    threshold_fft = abs(max(fft_result)) / 10000
    threshold_dft = abs(max(dft_result)) / 10000

    for i in range(n):
        if abs(fft_phase_values[i]) < threshold_fft:
            fft_phase_values[i] = 0
        if abs(dft_phase_values[i]) < threshold_dft:
            dft_phase_values[i] = 0

    # plotting part
    fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2)

    ax1.plot(arguments, function_values)
    ax1.set(title='Function plot')
    ax1.grid()

    ax2.stem(frequencies, fft_amplitude_values, markerfmt=' ')
    ax2.set(title='Magnitude spectrum plot (FFT)')
    ax2.grid()

    ax3.stem(frequencies, fft_phase_values, markerfmt=' ')
    ax3.set(title='Phase spectrum plot (FFT)')
    ax3.grid()

    ax4.plot(arguments, reverse_fft_result)
    ax4.set(title='Reverse FFT plot')
    ax4.grid()

    fig2, ((ax5, ax6), (ax7, ax8)) = plt.subplots(2, 2)

    ax5.plot(arguments, function_values)
    ax5.set(title='Function plot')
    ax5.grid()

    ax6.stem(frequencies, dft_amplitude_values, markerfmt=' ')
    ax6.set(title='Magnitude spectrum plot (DFT)')
    ax6.grid()

    ax7.stem(frequencies, dft_phase_values, markerfmt=' ')
    ax7.set(title='Phase spectrum plot (DFT)')
    ax7.grid()

    ax8.plot(arguments, reverse_dft_result)
    ax8.set(title='Reverse DFT plot')
    ax8.grid()

    plt.show()

if __name__ == '__main__':
    main()


# Question 04, Lab 04
# AB Satyaprakash - 180123062

# imports ----------------------------------------------------------------------------
from sympy.abc import t
from sympy import evalf, integrate
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

# functions --------------------------------------------------------------------------

def f(x):
    return 1/(x+2)

def compositeTrapezoidalRule(X):
    sum = 0
    a, b = X[0], X[-1]
    n = X.shape[0]
    h = (b-a)/(n-1)
    for i in range(X.shape[0]):
        x = X[i]
        if(i == 0 or i == n-1):
            sum += f(x)/2
        else:
            sum += f(x)
    return (h*sum)

def compositeSimpsonRule(X):
    sum = 0
    a, b = X[0], X[-1]
    n = X.shape[0]
    h = (b-a)/(n-1)
    for i in range(X.shape[0]):
        x = X[i]
        if(i == 0 or i == n-1):
            sum += f(x)
        else:
            if(i % 2 == 0):
                sum += 2*f(x)
            else:
                sum += 4*f(x)
    return (h*sum)/3

# program body
func = 1/(t+2)
a, b = -1, 1

# Using sympy to integrate and evaluate the actual values before plotting!
I = integrate(func, (t, a, b)).evalf()
print('Actual value of integral is', I)

maxN = 30
actualArray, trapezoidalArray, simpsonArray, N = np.zeros(
    maxN), np.zeros(maxN), np.zeros(maxN), np.zeros(maxN)

for i in range(maxN):
    N[i] = i+1
    X = np.arange(a, b+0.0001, (b-a)/N[i])
    actualArray[i] = I
    trapezoidalArray[i] = compositeTrapezoidalRule(X)
    if(i == 0):
        simpsonArray[i] = np.NaN
    else:
        simpsonArray[i] = compositeSimpsonRule(X)

plt.plot(N, actualArray, label='Actual')
plt.plot(N, trapezoidalArray, label='Trapezoidal')
plt.plot(N, simpsonArray, label='Simpson')
plt.title('Comparing results for varying n')
plt.xlabel('Value of n')
plt.ylabel('Evaluated value of integral by different methods')
plt.legend()
plt.show()

data = np.vstack((N, trapezoidalArray, simpsonArray)).T

df = pd.DataFrame(data,
                  columns=['N-value', 'Trapezoidal', 'Simpson']).round(6)
df.set_index('N-value', inplace=True)
print('\n', df, '\n')

# Plotting the various curves from [-1,1]
# The original curve is given as 1/x+2

# Trapezoidal rule forms a trapezoid joining the points (-1,f(-1)) and (1, f(1))
# y = -1/3 x + 2/3

# Simpson rule forms a parabola joining the points (-1, f(-1)), (0,f(0)) and (1,f(1))
# y = 1/6 x^2 − 1/3 x + 1/2

X = np.arange(-1, 1, 0.01)
actualArray, trapezoidalArray, simpsonArray = [], [], []
for x in X:
    actualArray.append(1/(x+2))
    trapezoidalArray.append((-1/3)*x + (2/3))
    simpsonArray.append((1/6)*(x**2) - (1/3)*x + 1/2)

plt.plot(X, actualArray, color='blue')
plt.plot(X, trapezoidalArray, color='orange')
plt.fill_between(X, actualArray, 0, color='blue', alpha=0.2, label='Actual')
plt.fill_between(X, trapezoidalArray, 0, color='orange',
                 alpha=0.3, label='Trapezoidal')
plt.title('Actual vs Trapezoidal')
plt.ylabel('Area under curve for different rules')
plt.xlabel('Value of x from [-1, 1]')
plt.legend()
plt.show()

plt.plot(X, actualArray, color='blue')
plt.plot(X, simpsonArray, color='orange')
plt.fill_between(X, actualArray, 0, color='blue', alpha=0.2, label='Actual')
plt.fill_between(X, simpsonArray, 0, color='orange',
                 alpha=0.3, label='Simpson')
plt.title('Actual vs Simpson')
plt.ylabel('Area under curve for different rules')
plt.xlabel('Value of x from [-1, 1]')
plt.legend()
plt.show()

Semester 6/MA 322 (Scientific Computing Theory)/Labs/Lab 4/Code/q4.py


from scipy import reshape, sqrt, identity

# nDimPoints: list of n-dim tuples
# distFunc: calculates the distance based on the differences
# Ex: Manhatten would be: distFunc=sum(deltaPoint[d] for d in xrange(len(deltaPoint)
def calcDistanceMatrix(nDimPoints,
                       distFunc=lambda deltaPoint: sqrt(sum(deltaPoint[d]**2 for d in xrange(len(deltaPoint))))):
    nDimPoints = array(nDimPoints)
    dim = len(nDimPoints[0])
    delta = [None]*dim
    for d in xrange(dim):
        data = nDimPoints[:,d]
        delta[d] = data - reshape(data,(len(data),1)) # computes all possible combinations

    dist = distFunc(delta)
    dist = dist + identity(len(data))*dist.max() # eliminate self matching
    # dist is the matrix of distances from one coordinate to any other
    return dist

from numpy.matlib import repmat, repeat
def calcDistanceMatrixFastEuclidean(points):
    numPoints = len(points)
    distMat = sqrt(sum((repmat(points, numPoints, 1) - repeat(points, numPoints, axis=0))**2, axis=1))
    return distMat.reshape((numPoints,numPoints))

from numpy import mat, zeros, newaxis
def calcDistanceMatrixFastEuclidean2(nDimPoints):
    nDimPoints = array(nDimPoints)
    n,m = nDimPoints.shape
    delta = zeros((n,n),'d')
    for d in xrange(m):
        data = nDimPoints[:,d]
        delta += (data - data[:,newaxis])**2
    return sqrt(delta)

#################
# Unittest
#################
class CalcDistanceMatrixTestCase(unittest.TestCase):
    def setUp(self):
        self.distanceMatrixFunc = "calcDistanceMatrix"

    def test_2D(self):
        points = [[0, 0], [1, 1], [4, 5]]
        dm = eval("%s(points)"%self.distanceMatrixFunc)
        self.assertAlmostEqual(1.414213562373095049, dm[0][1])
        self.assertAlmostEqual(6.403124237432848686, dm[0][2])
        self.assertAlmostEqual(5, dm[1][2])
        self._testSymmetry(dm)

    def test_3D(self):
        points = [[0, 0, 0], [1.0, 1, 1], [4, 5, 6], [10,10,10]]
        dm = eval("%s(points)"%self.distanceMatrixFunc)
        self.assertAlmostEqual(1.732050807568877294, dm[0][1])
        self.assertAlmostEqual(8.77496438739212206, dm[0][2])
        self.assertAlmostEqual(17.32050807568877294, dm[0][3])
        self.assertAlmostEqual(7.071067811865475244, dm[1][2])
        self.assertAlmostEqual(15.58845726811989564, dm[1][3])
        self.assertAlmostEqual(8.77496438739212206, dm[2][3])
        self._testSymmetry(dm)

    def _testSymmetry(self, dm):
        for i in range(len(dm)):
            for j in range(len(dm)):
                self.assertEqual(dm[i][j], dm[j][i])

class CalcDistanceMatrixFastTestCase(CalcDistanceMatrixTestCase):
    def setUp(self):
        self.distanceMatrixFunc = "calcDistanceMatrixFastEuclidean"

class CalcDistanceMatrixFast2TestCase(CalcDistanceMatrixTestCase):
    def setUp(self):
        self.distanceMatrixFunc = "calcDistanceMatrixFastEuclidean2"


import numpy as np

def squared_norm(x, axis=None, keepdims=False):
    """Squared Euclidean norm of an array."""
    return (x ** 2).sum(axis=axis, keepdims=keepdims)

def norm(x, axis=None, keepdims=False):
    """Euclidean norm of an array."""
    return np.sqrt(squared_norm(x, axis=axis, keepdims=keepdims))

def normed(x, axis=None, keepdims=False):
    """Normalize an array."""
    eps = np.finfo(x.dtype).eps
    return x / (norm(x, axis=axis, keepdims=True) + eps)


# Code by Desh Iyer

# Python program to generate a z-Table using numpy and pandas
# This is an easier alternative to scouring the internet to find a csv file of a z-Table imo

# Import libs
from scipy.integrate import quad
import numpy as np
import pandas as pd

# Function to calculate the y value for corresponding x using
# https://www.thoughtco.com/thmb/pYkVzv3wJGeULFip-z1t75557WA=/1500x1124/filters:no_upscale():max_bytes(150000):strip_icc()/bellformula-56b749555f9b5829f8380dc8.jpg
# Where for a standard normal dist, mean = 0, sigma = 1
def normalProbabilityDensity(x):
    constant = 1.0 / np.sqrt(2 * np.pi)
    returnValue = constant * np.exp((-x ** 2) / 2.0)

    return returnValue

# Create a pandas dataframe with rows and cols indexed appropriately
zTable = pd.DataFrame(
    data = [],
    index = np.round(np.arange(8, -8.1, -.1), 2),
    columns = np.round(np.arange(0.00, .1, .01), 2)
)

# Set the cell values for the dataframe using the function to calc PDF of a Standard Normal Distribution
for index in zTable.index:
    for column in zTable.columns:
        z = np.round(index + column, 2)
        # Calculate the definite integral of the PDF using quad() and formula https://miro.medium.com/max/523/1*kIhMH2ny-EwkZJm-jV4BdQ.png
        value, _ = quad(normalProbabilityDensity, np.NINF, z)
        # Assign cell the value
        zTable.loc[index, column] = value

print(f'Z Table\n\n{zTable}')

zTable.to_excel(r'C:\Users\Volt\Code\B.Tech-CSE-Y2\applied-statistics\lab\experiment-5\zTable.xlsx')

zTable.to_csv(r'C:\Users\Volt\Code\B.Tech-CSE-Y2\applied-statistics\lab\experiment-5\zTable.txt')


import numpy as np
import random

# adjust_grouping updates the grouping array such that each data point is grouped
# with the nearest centroid
def adjust_grouping(data, grouping, centroids):

    # find closest cluster center for each data point
    for d_idx in range(len(data)):

        d = data[d_idx]
        min_dist = np.finfo(np.float).max
        min_c_idx = 0

        # check distance each center, keep track of minimum
        for c_idx in range(len(centroids)):
            c = centroids[c_idx]
            dist = (d - c).T @ (d - c)

            if dist < min_dist:
                min_dist = dist
                min_c_idx = c_idx

        # update grouping
        grouping[d_idx] = min_c_idx

# adjust_centroids updates the centroid positions in order to minimize the objective
# function (minimize euclidean distance between centroid and points in group)
def adjust_centroids(data, grouping, centroids):

    for c_idx in range(len(centroids)):
        c = centroids[c_idx]
        num_pts = num_points_in_cluster(grouping, c_idx)

        # if no points in this cluster, randomly set center to another cluster's center
        if num_pts == 0:
            # move centroid to location of a random data point
            # print("NO POINTS IN CLUSTER")
            centroids[c_idx] = data[random.randint(0, len(data)-1)]

            continue

        # still have points in cluster, calculate new position
        sum = np.zeros(data[0].shape)
        for d_idx in range(len(data)):
            if grouping[d_idx] != c_idx:    # skip data points not in cluster
                continue
            sum += data[d_idx]
        centroids[c_idx] = 1 / num_pts * sum

# num_points_in_cluster returns the number of data points within a specified cluster
def num_points_in_cluster(grouping, c_idx):
    count = 0
    for c in grouping:
        if c == c_idx:
            count += 1
    return count

# kmeans clusters the given data points into k clusters
#   data - The data array to cluster
#   k - number of clusters to create
#   threshold - number of consecutive iterations for the clustering to remain stable
#               in order to be considered converged
#   init - initialization method for clusters, must be "random_assignment" or "forgy"
def kmeans(data, k, threshold=10, init="random_assignment"):

    # initialize centroids and grouping
    if init == "random_assignment":
        centroids = np.tile(np.zeros(data[0].shape), (k,1))
        grouping = np.random.randint(low=0, high=k, size=len(data))
        adjust_centroids(data, grouping, centroids)
    elif init == "forgy":
        index = np.random.choice(data.shape[0], k, replace=False)
        centroids = data[index]               # cluster centroids
        grouping = np.zeros(len(data), dtype=np.int)      # assignment of data point to cluster
    else:
        raise ValueError("{} unrecognized. init must be either \"random_assignment\" or \"forgy\"!".format(init))

    # iterate until clusters have converged
    converged = False
    conv_count = 0

    while not converged:
        # save previous state
        previous_grouping = grouping.copy()

        # iterate grouping and cluster centroids
        adjust_grouping(data, grouping, centroids)
        adjust_centroids(data, grouping, centroids)

        # check if converged
        if (grouping == previous_grouping).all():
            conv_count += 1
            if conv_count == threshold:
                converged = True
        else:
            conv_count = 0

    return grouping, centroids


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

### Libraries
import os
import sys
import numpy as np
import numpy.linalg as linalg
import matplotlib.pyplot as mp

### Newton-Raphson
def Newton_Raphson(F, J, U0, N, epsilon):
    j = 0
    U = U0
    V = np.asmatrix(linalg.lstsq(J(U), -F(U))[0])
    while (j < N and linalg.norm(V) > epsilon):
        U = U + V
        V = np.asmatrix(linalg.lstsq(J(U), -F(U))[0])
        j += 1
    return U

def Newton_Raphson_Backtracking(F, J, U0, N, epsilon):
    j = 0
    U = U0
    V = np.asmatrix(linalg.lstsq(J(U), -F(U))[0])
    while (j < N and linalg.norm(F(U)) > epsilon):
        while linalg.norm(F(U + V)) > linalg.norm(F(U)): # Backtracking
            V = V * 2 / 3
        U = U + V
        V = np.asmatrix(linalg.lstsq(J(U), -F(U))[0])
        j += 1
    return U

### Graphs
def speed_Newton_Raphson_Backtracking(F, J, U0, N, epsilon):
    j = 0
    U = U0
    V = np.asmatrix(linalg.lstsq(J(U), -F(U))[0])
    speed = []
    iterations = []
    while (j < N and linalg.norm(F(U)) > epsilon):
        while linalg.norm(F(U + V)) > linalg.norm(F(U)):  # Backtracking
            V = V * 2 / 3
        U = U + V
        c = linalg.norm(V)
        speed = np.append(speed,c)
        iterations = np.append(iterations, j+1)
        V = np.asmatrix(linalg.lstsq(J(U), -F(U))[0])
        j += 1
    mp.plot(np.log(iterations),np.log(speed),'o')
    j = 0
    U = U0
    V = np.asmatrix(linalg.lstsq(J(U), -F(U))[0])
    speed1 = []
    iterations1 = []
    while (j < N and linalg.norm(F(U)) > epsilon):
        U = U + V
        c = linalg.norm(V)
        speed1 = np.append(speed1,c)
        iterations1 = np.append(iterations1, j+1)
        V = np.asmatrix(linalg.lstsq(J(U), -F(U))[0])
        j += 1
    mp.plot(np.log(iterations1),np.log(speed1))
    mp.xlabel("log(number of iterations)")
    mp.ylabel("log(speed of convergence)")
    mp.legend(["backtracking", "nobacktracking"])
    mp.grid(True)
    mp.show()

if __name__ == "__main__":
    if "graphs" in sys.argv:
        # Setup
        U0 = np.zeros([1,1])
        U0[0,0] = 1025
        epsilon = pow(10, -10)
        N = 10000
        def f(U):
            V = np.zeros([1,1])
            V[0,0] = pow(U[0,0],3) - 1
            return V
        def j(U):
            V = np.zeros([1,1])
            V[0,0] = 3*pow(U[0,0],2)
            return V

        # Ploting
        speed_Newton_Raphson_Backtracking(f,j,U0,N,epsilon)
        mp.clf()
        U0[0,0] = 0.25
        speed_Newton_Raphson_Backtracking(f,j,U0,N,epsilon)
    else:
        print("Usage: ./" + os.path.basename(__file__) + " graphs")


import numpy as np

def f(x):
    return np.exp(-1*x**2)

def g(x):
    return np.sin(x) / x

def compound_trapezoid_formula(inte, func, tol):
    """ Calculate the integral by compound trapezoid formula

    Args:
        inte: ndarray, the integrand interval
        func: function object, the integrand function
        tol: double, the iteration accurancy

    Returns:
        (T1, n):
            T1: double, the integral value by compound trapezoid formula
            n: int, the number of iteration
    """
    # calculate the initial value by trapezoid formula
    T0 = (inte[1] - inte[0]) * np.sum(func(inte)) / 2
    # calculate another integral value by compound trapezoid iteration formula
    n = 1
    x_list = np.linspace(inte[0], inte[1], 2**n + 1)
    T1 = T0 / 2 + (x_list[1] - x_list[0]) * np.sum(func(x_list[1::2]))
    # iteration
    while np.fabs(T1 - T0) >= tol:
        T0 = T1
        n += 1
        x_list = np.linspace(inte[0], inte[1], 2**n + 1)
        T1 = T0 / 2 + (x_list[1] - x_list[0]) * np.sum(func(x_list[1::2]))

    return (T1, n)

def compound_simpson_formula(inte, func, tol):
    """ Calculate the integral by compound Simpson formula

    Args:
        inte: ndarray, the integrand interval
        func: ndarray, the integrand function
        tol: double, the iteration accurancy

    Returns:
        (S1, n):
            S1: double, the integral value by compound Simpson formula
            n: int, the interation number
    """
    # calculate the initial by Simpson formula
    x_list = np.linspace(inte[0], inte[1], 3)
    S0 = (x_list[1] - x_list[0]) * np.sum(np.array([1, 4, 1]).dot(func(x_list))) / 3
    # calculate another integral value by compound Simpson iteration formula
    n = 1
    x_list = np.linspace(inte[0], inte[1], 2**(n+1)+1)
    S1 = S0 / 2 + (x_list[1] - x_list[0]) * (4 * np.sum(func(x_list[1::2])) - 2 * np.sum(func(x_list[2::4]))) / 3
    # iteration
    while np.fabs(S1 - S0) >= tol:
        S0 = S1
        n += 1
        x_list = np.linspace(inte[0], inte[1], 2**(n+1)+1)
        S1 = S0 / 2 + (x_list[1] - x_list[0]) * (4 * np.sum(func(x_list[1::2])) - 2 * np.sum(func(x_list[2::4]))) / 3

    return (S1, n)

if __name__=='__main__':
    # interval
    inte = np.array([1e-32, 1])
    # compound trapezoid formula
    value, n = compound_trapezoid_formula(inte, f, 1e-6)
    print(f"The first integral's value is {value:.6f} by compound trapezoid formula after {n} iterations")
    value, n = compound_trapezoid_formula(inte, g, 1e-6)
    print(f"The second integral's value is {value:.6f} by compound trapezoid formula after {n} iterations")
    # compound Simpson formula
    value, n = compound_simpson_formula(inte, f, 1e-6)
    print(f"The first integral's value is {value:.6f} by compound Simpson formula after {n} iterations")
    value, n = compound_simpson_formula(inte, g, 1e-6)
    print(f"The second integral's value is {value:.6f} by compound Simpson formula after {n} iterations")