import numpy as np
import random
import time
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号

# 二维目标函数

def Rastrigrin(x: np.ndarray):
    return 20 + x[0] ** 2 - 10 * np.cos(2 * np.pi * x[0]) + x[1] ** 2 - 10 * np.cos(2 * np.pi * x[1])

def obj_fun(x: np.ndarray):
    return abs(0.2 * x[0]) + 10 * np.sin(5 * x[0]) + 7 * np.cos(4 * x[0])

def obj_fun2(x: np.ndarray):
    return (1 - x[0]) ** 2 + 100 * (x[0] - x[1] ** 2) ** 2

def Easom(x: np.ndarray):
    return -1 * np.cos(x[0]) * np.cos(x[1]) * np.exp(-1 * (x[0] - np.pi) ** 2 - (x[0] - np.pi) ** 2)

def obj_fun_test1(x: np.ndarray):
    return -1 * (10 + np.sin(1 / x) / ((x - 0.16) ** 2 + 0.1))

def Bohachevsky(x: np.ndarray):
    return x[0] ** 2 + x[1] ** 2 - 0.3 * np.cos(3 * np.pi * x[0]) + 0.3 * np.cos(4 * np.pi * x[1]) + 0.3

def Schaffersf6(x: np.ndarray):
    return 0.5 + (np.sin(x[0] ** 2 + x[1] ** 2) ** 2 - 0.5) / (1 + 0.001 * (x[0] ** 2 + x[1] ** 2) ** 2) ** 2

def Shubert(x: np.ndarray):
    s1 = 0
    s2 = 0
    for i in range(1, 6):
        s1 += i * np.cos((i + 1) * x[0] + i)
        s2 += i * np.cos((i + 1) * x[1] + i)
    return s1 + s2

# 一维目标函数
def fun1(x):
    return -(x + 10 * np.sin(5 * x) + 7 * np.cos(4 * x))

class GSA():
    def __init__(self,T_max=400,T_min=10,pop=10,new_pop=10,cur_g=1,p=0.9,tour_n=10,func=fun1,shape=1, **kwargs):
        """
        @param T_max: 最大温度
        @param T_min: 最小温度
        @param pop: 种群的个体数量
        @param new_pop: 每个个体生成的新解的数量
        @param cur_g: 当前迭代代数
        @param p: 锦标赛中的概率p
        @param tour_n: 一次锦标赛的选手数
        @param func: 函数
        @param x_best: 最优点
        @param y_best: 最优点的函数值
        @param shape: x的维度
        """
        self.T_max = T_max
        self.T = T_max  # 当前温度
        self.T_min = T_min
        self.pop = pop
        self.new_pop = new_pop
        self.cur_g = cur_g
        self.p = p
        self.tour_n = tour_n
        self.func = func
        self.shape = shape
        self.x_best = [0]*shape
        self.y_best = 0
        self.x_history = []
        self.T_history = [self.T]
        self.m, self.n, self.quench = kwargs.get('m', 1), kwargs.get('n', 1), kwargs.get('quench', 1)
        self.lower, self.upper = kwargs.get('lower', -10), kwargs.get('upper', 10)
        self.c = self.m * np.exp(-self.n * self.quench)

    def xrange(self, xmin: np.ndarray, xmax: np.ndarray):  # 输入x范围，尚未考虑多维情形
        self.xmin = xmin
        print('x的下界是'+str(xmin))
        self.xmax = xmax
        print('x的上界是'+str(xmax))

    def init_pop(self):
        pop1 = []
        x_init = [10,10]
        for i in range(self.pop):
            pop1.append(self.get_new(x_init))
        return pop1

    def judge(self, df):
        if df < 0:  # 新解 < 原解 ---> 直接接受
            return True
        else:
            pp = np.exp(-1 * (df / self.T))  # 新解 > 原解 ---> 计算接受概率p
            rand_p = random.random()  # 产生 rand_p ~ Uniform(0, 1)
            if pp > rand_p:
                return True
            else:
                return False

    def get_new(self,x):
        u = np.random.uniform(-1, 1, size=self.shape)  # 产生均匀分布的[Xi,……] u ~ Uniform(0, 1, size = d)
        x_new = x + 20 * np.sign(u) * self.T * ((1 + 1.0 / self.T) ** np.abs(u) - 1.0)
        return x_new

    def generate(self,old_pop):
        x_new = []

        for i in range(len(old_pop)):
            while len(x_new) < (i+1)*self.new_pop:
                xnew = self.get_new(old_pop[i])

                # Metropolis准则
                df = self.func(xnew) - self.func(old_pop[i])  # 计算函数值差值
                if self.judge(df):
                    x_new.append(xnew)

        return x_new

    def tournament(self,x):
        """
        计算每个点的适应值，选择N个点作为生存集（GA）锦标赛。
        如果适应值最大的点被舍弃了，那么就随机舍弃一个点，又把它加回来。
        处理为先把适应值最大的点加进去
        """
        survive = []
        x_cur = x
        y_cur = [self.func(xx) for xx in x]
        best_index = y_cur.index(min(y_cur))
        self.x_best = x_cur[best_index]
        self.y_best = y_cur[best_index]
        survive.append(self.x_best)  # 先把最好的点放进去
        del(x_cur[best_index])  # 把最好的点删了，其他的进行锦标赛，最后选出self.pop-1个点

        #     进行选择（锦标赛方法）
        #     choose k (the tournament size) individuals from the population at random
        #     choose the best individual from the tournament with probability p
        #     choose the second best individual with probability p*(1-p)
        #     choose the third best individual with probability p*((1-p)^2)
        #     and so on

        fitness = [self.y_best - self.func(xx) for xx in x_cur]
        num_individuals = len(fitness)
        indices = list(range(len(fitness)))
        selected_indices = []
        selection_size = self.pop - 1

        while (len(selected_indices) < selection_size):
            np.random.shuffle(indices)
            idx_tournament = indices[0:self.tour_n]
            fit = []
            for i in range(len(idx_tournament)):
                fit.append(fitness[idx_tournament[i]])
            maxindex = fit.index(max(fit))
            winner = idx_tournament[maxindex]
            r = random.random()
            if r < self.p:
                selected_indices.append(winner)
            selected_indices = list(set(selected_indices))

        for i in range(len(selected_indices)):
            survive.append(x_cur[selected_indices[i]])

        return survive

    def cool(self):
        self.T = self.T * 0.7
        self.T_history.append(self.T)

    def disp(self):
        if self.shape == 1:
            pass

        elif self.shape == 2:
            fig = plt.figure()
            ax = plt.axes(projection='3d')
            x0_list = [[0 for col in range(len(self.x_history[0]))] for row in range(len(self.x_history))]
            x1_list = [[0 for col in range(len(self.x_history[0]))] for row in range(len(self.x_history))]
            for i in range(len(self.x_history)):
                for j in range(len(self.x_history[i])):
                    x0_list[i][j] = self.x_history[i][j][0]
                    x1_list[i][j] = self.x_history[i][j][1]
            x_min, x_max = min(min(row) for row in x0_list), max(max(row) for row in x0_list)
            y_min, y_max = min(min(row) for row in x1_list), max(max(row) for row in x1_list)
            x = np.arange(x_min, x_max, 0.01)
            y = np.arange(y_min, y_max, 0.01)
            X, Y = np.meshgrid(x, y)
            Z = self.func(np.array([X, Y]))
            ax.plot_surface(X, Y, Z, cmap='Blues', alpha=0.3)

            for i in range(len(self.x_history)):
                x0, x1, t = x0_list[i], x1_list[i], self.T_history[i]
                print(x0)
                y = [self.func(x) for x in self.x_history[i]]
                plt.title("当前温度 ："+str(t))
                p = ax.scatter(x0, x1, y, c='b')
                plt.pause(0.5)
                if i != len(self.x_history) - 1:
                    p.remove()
            plt.scatter(self.x_best[0], self.x_best[1], self.y_best, c='r', marker='*')
            plt.xlabel("x0")
            plt.ylabel("x1")
            plt.show()

    def main(self):
        pop1 = self.init_pop()
        self.x_history.append(pop1)
        while self.T > self.T_min:

            print('----------------当前迭代代数是' + str(self.cur_g) + '-------------------')
            new_pop = self.generate(pop1)
            pop1 = self.tournament(new_pop)
            self.x_history.append(pop1)
            self.cool()
            print('当前温度'+str(self.T))
            self.cur_g = self.cur_g + 1

            sur_pop_y = [self.func(xx) for xx in pop1]
            best_index = sur_pop_y.index(min(sur_pop_y))
            self.x_best = pop1[best_index]
            self.y_best = sur_pop_y[best_index]

class fastGSA(GSA):
    def __init__(self, T_max=400, T_min=10, pop=10, new_pop=10, cur_g=1, p=0.9, tour_n=10, func=fun1, shape=1, **kwargs):
        super().__init__(T_max, T_min, pop, new_pop, cur_g, p, tour_n, func, shape, **kwargs)
        self.name = 'SAFast'
        self.m, self.n, self.quench = kwargs.get('m', 1), kwargs.get('n', 1), kwargs.get('quench', 1)
        self.lower, self.upper = kwargs.get('lower', -10), kwargs.get('upper', 10)
        self.c = self.m * np.exp(-self.n * self.quench)

    def get_new(self, x):
        r = np.random.uniform(-1, 1, size=self.shape)
        xc = np.sign(r) * self.T * ((1 + 1.0 / self.T) ** np.abs(r) - 1.0)
        x_new = x + xc * (self.upper - self.lower)
        return x_new

    def cool(self):
        self.T = self.T_max * np.exp(-self.c * (self.cur_g * 10) ** self.quench)
        self.T_history.append(self.T)

class cauchyGSA(GSA):
    def __init__(self, T_max=400, T_min=10, pop=10, new_pop=10, cur_g=1, p=0.9, tour_n=10, func=fun1, shape=1, **kwargs):
        super().__init__(T_max, T_min, pop, new_pop, cur_g, p, tour_n, func, shape, **kwargs)
        self.name = 'SACauchy'
        self.learn_rate = kwargs.get('learn_rate', 0.5)

    def get_new_x(self, x):
        u = np.random.uniform(-np.pi / 2, np.pi / 2, size=self.shape)
        xc = self.learn_rate * self.T * np.tan(u)
        x_new = x + xc
        return x_new

    def cool_down(self):
        self.T = self.T * 0.7
        self.T_history.append(self.T)

class BoltzmannGSA(GSA):
    def __init__(self, T_max=400, T_min=10, pop=10, new_pop=10, cur_g=1, p=0.9, tour_n=10, func=fun1, shape=1, **kwargs):
        super().__init__(T_max, T_min, pop, new_pop, cur_g, p, tour_n, func, shape, **kwargs)
        self.name = 'SABoltzmann'
        self.lower, self.upper = kwargs.get('lower', -10), kwargs.get('upper', 10)
        self.learn_rate = kwargs.get('learn_rate', 0.5)

    def get_new_x(self, x):
        std = min(np.sqrt(self.T), (self.upper - self.lower) / 3.0 / self.learn_rate) * np.ones(self.shape)
        xc = np.random.normal(0, 1.0, size=self.shape)
        x_new = x + xc * std * self.learn_rate
        return x_new

    def cool_down(self):
        self.T = self.T * 0.7
        self.T_history.append(self.T)

if __name__ == "__main__":
    start = time.time()  # 开始计时
    x_init = np.array([10, 10])  # 设置初始点（行向量）
    x_min = np.array([-10, -10])
    x_max = np.array([10, 10])
    # demo = GSA(func=Rastrigrin,T_max=1000,T_min=10,pop=30,new_pop=15,cur_g=1,p=0.9,tour_n=15,shape=2)
    demo = fastGSA(func=Rastrigrin, T_max=1, T_min=1e-5, pop=30, new_pop=15, cur_g=1, p=0.9, tour_n=15, shape=2)
    # demo = cauchyGSA(func=Rastrigrin, T_max=1000, T_min=100, pop=30, new_pop=15, cur_g=1, p=0.9, tour_n=15, shape=2)
    # demo = BoltzmannGSA(func=Rastrigrin, T_max=1000, T_min=1, pop=30, new_pop=15, cur_g=1, p=0.9, tour_n=15, shape=2)
    demo.xrange(x_min, x_max)
    demo.main()
    demo.disp()
    end = time.time()  # 结束计时
    print("Duration time: %0.3f" % (end - start))  # 打印运行时间
    print('iterations:' + str(demo.cur_g))
    print('x_best:'+str(demo.x_best))
    print('y_best:'+str(demo.y_best))


import numpy as np

def rotationMatrix(inc, dec, normal=True):
    """
        Take an inclination and declination angle and return a rotation matrix

    """

    phi = -np.deg2rad(np.asarray(inc))
    theta = -np.deg2rad(np.asarray(dec))

    Rx = np.asarray(
        [[1, 0, 0], [0, np.cos(phi), -np.sin(phi)], [0, np.sin(phi), np.cos(phi)]]
    )

    Rz = np.asarray(
        [
            [np.cos(theta), -np.sin(theta), 0],
            [np.sin(theta), np.cos(theta), 0],
            [0, 0, 1],
        ]
    )

    if normal:
        R = Rz.dot(Rx)
    else:
        R = Rx.dot(Rz)

    return R

def dipazm_2_xyz(dip, azm_N):
    """
    dipazm_2_xyz(dip,azm_N)

    Function converting degree angles for dip and azimuth from north to a
    3-components in cartesian coordinates.

    INPUT
    dip     : Value or vector of dip from horizontal in DEGREE
    azm_N   : Value or vector of azimuth from north in DEGREE

    OUTPUT
    M       : [n-by-3] Array of xyz components of a unit vector in cartesian

    Created on Dec, 20th 2015

    @author: dominiquef
    """

    # Modify azimuth from North to Cartesian-X
    azm_X = (450.0 - azm_N) % 360.0

    D = np.deg2rad(np.asarray(dip))
    I = np.deg2rad(azm_X)

    M = np.zeros(3)
    M[0] = np.cos(D) * np.cos(I)
    M[1] = np.cos(D) * np.sin(I)
    M[2] = np.sin(D)

    return M


import numpy as np
from scipy.special import erf

fraction_of_gaussian_area = lambda sigma, x1, x2: 0.5 * ( erf(-x1/(np.sqrt(2)*sigma)) + erf(x2/(np.sqrt(2)*sigma)) )

def test_fraction_of_gaussian_area():
    # Test fraction_of_gaussian_area
    x = np.linspace(-0.5, 0.5, 100000)
    gaussian= lambda sigma: np.exp(-x**2/2./sigma**2)/sigma/np.sqrt(2*np.pi)
    sigma = 0.1
    y = gaussian(sigma)
    x1 = -0.05
    x2 = 0.07
    A_anal = fraction_of_gaussian_area(sigma, x1, x2)
    A_num = y[(x>=x1)*(x<x2)].sum()/x.size
    assert np.isclose(A_anal, A_num)


import numpy as np
from scipy import linalg

from numpy import pi

from ..base import Classifier
from ..exceptions import MethodNotSupportedError

__all__ = [
    "GaussianDist",
]

class GaussianDist(Classifier):
    """Gaussian Distribution Anomaly Detection Algorithm

    Builds a Gaussion Distribution from the given data and it's
    features. Based on the feature's distributions the algorithm
    separates the anomalies from the normal examples.

    Parameters:
    -----------
    multi : boolean, whether to model Multivariate Gaussian Distribution
    or a Univariate one

    """

    def __init__(self, multi=False):
        self.multi = multi

        self.mean = None
        self.std = None
        self.sigma = None

    def fit(self, X, y=None):
        self.mean = np.mean(X, axis=0)
        self.std = np.std(X, axis=0)
        if self.multi:
            self.sigma = np.cov(np.transpose(X))

        return self

    def predict(self, X):
        X = super().predict(X)
        assert len(X.shape) == 2

        return np.array([self.p(x) for x in X])

    def predict_proba(self, X):
        raise MethodNotSupportedError("""Probability predictions are not supported
                                        for Gaussian Distribution Anomaly Detection.""")

    def decision_function(self, X):
        raise MethodNotSupportedError("Decision function is not supported. Use `predict` instead.")

    def evaluate(self, X, y):
        raise MethodNotSupportedError("Evaluation methods are not available yet...")

    def p(self, x):
        assert type(x) is list or type(x) is np.ndarray
        x = np.array(x)

        if not self.multi:
            return np.prod([
                1/(np.sqrt(2*pi) * self.std[j]) * \
                np.exp(-(x[j] - self.mean[j])**2 / (2*self.std[j]**2)) for j in range(x.size)
            ])

        else:
            return 1/(np.power(2*pi, x.size/2)*np.power(linalg.det(self.sigma), 1/2)) * \
            np.exp(-1/2 * (x - self.mean).T @ linalg.inv(self.sigma) @ (x-self.mean))


import numpy as np
from scipy.signal import resample

def _cov(x, y):
    x = x - np.mean(x)
    y = y - np.mean(y)
    return np.mean(np.multiply(x,y))

def _pcc(x, y):
    s_x = np.std(x)
    s_y = np.std(y)
    return _cov(x,y)/(s_y * s_x)

def _remove_unvoiced(x):
    y = np.zeros(len(x))
    i = 0
    for v in x:
        if v > 0:
            y[i] = v
            i += 1
    return y[:i]

def pcc_on_2_pitch_curve(ref, dut, remove_unvoiced=True):
    """ This function computes the Pearsons correlation ceofficient (PCC) between 2 pitch curves.

    Args:
        ref (numpy array): sequence of an arbitrary length (assumes unvoiced segments are indicated by numbers <= 0)
        dut (numpy array): sequence of an arbitrary length (assumes unvoiced segments are indicated by numbers <= 0)
        remove_unvoiced (bool, optional):if True, all unvoiced segments will be removed from the pitch curves. Defaults to True.

    Returns:
        float: the PCC between both sequences
    """

    # will remove all parts of the signal that are unvoiced
    if remove_unvoiced:
        ref = _remove_unvoiced(ref)
        dut = _remove_unvoiced(dut)

    # resample the second signal to get the same lenght
    dut_rs = resample(dut, len(ref))

    # compute the Pearson's correlation
    return _pcc(ref, dut_rs)


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

import numpy as np
import matplotlib.pyplot as plt

curve_data = np.genfromtxt('data_1.csv', delimiter = ',')
# Extract x and y coordinates from the curve data
x = curve_data[1:,0]
y = curve_data[1:,1]

x_squared = np.power(x,2)
x_matrix = np.transpose([x_squared, x, np.ones(np.shape(x))])

#Least Square Implementation
def least_square_curvefitting():
    '''
    Function to fit a curve using Least squares given minimum number of points (x,y) from the data.
    Y = XB
    E = (Y - XB)T(Y - XB)
    dE/dB = 0
    B = inv(XTX)*XT*Y
    '''

    B = np.matmul(np.linalg.inv(np.matmul(np.transpose(x_matrix), x_matrix)),
                  np.matmul(np.transpose(x_matrix), y))
    y_estimate = np.matmul(x_matrix, np.transpose(B))
    plt.figure()
    plt.xlabel('x axis')
    plt.ylabel('y axis')
    plt.title('Data_1')
    plt.scatter(x, y, label = 'Scattered data')
    plt.plot(x, y_estimate, 'r', label = 'Curve Fitting')
    plt.legend()
    plt.savefig('least_square_curvefitting_data_1.png')

#Least Square with Regularization
def least_square_regularization():
    '''
    Function to fit a curve using Least squares given minimum number of points (x,y) from the data.
    Y = XB
    E = (Y - XB)T(Y - XB)
    dE/dB = 0
    B = (inv(XTX)+L)*(XT*Y)
    '''
    L=np.dot(np.identity(3),5)
    Breg = np.matmul(np.linalg.inv((np.matmul(np.transpose(x_matrix), x_matrix))+L), np.matmul(np.transpose(x_matrix), y))
    y_estimatereg = np.matmul(x_matrix, np.transpose(Breg))
    plt.figure()
    plt.xlabel('x axis')
    plt.ylabel('y axis')
    plt.title('Data_1')
    plt.scatter(x, y, label = 'Scattered data')
    plt.plot(x, y_estimatereg , 'r', label = 'Curve Fitting')
    plt.legend()
    plt.savefig('least_square_regularization_data_1.png')

#Total Least Square
def total_least_square():
    yn=np.reshape(y,(y.shape[0],1))
    a=np.concatenate((x_matrix,yn),axis=1)
    u, s, vh = np.linalg.svd(a, full_matrices=True)
    V = vh.T
    Vxy = V[:3,3]
    Vyy = V[3,3]
    a_tls = - Vxy / Vyy
    VV=V[:,3].reshape(4,1)
    Xtyt=np.matmul(np.matmul(a,VV),VV.T)
    Xtyt=Xtyt[:,0:3]
    y_tls=np.matmul((Xtyt+x_matrix),a_tls)
    plt.figure()
    plt.xlabel('x axis')
    plt.ylabel('y axis')
    plt.title('Data_1')
    plt.scatter(x, y, label = 'Scattered data')
    plt.plot(x, y+y_tls , 'r', label = 'Curve Fitting')
    plt.legend()
    plt.savefig('total_least_square_data_1.png')

def main():
    least_square_curvefitting()
    least_square_regularization()
    total_least_square()

if __name__=='__main__':
    main()


from numpy import genfromtxt
import numpy as np

temp = genfromtxt('Q2Q3_input.csv', delimiter=',') #Generate the numpy array
inp = temp[1:, 1:]
mean = inp.mean(axis = 0)
std = inp.std(axis = 0)
#normed = (inp - inp.mean(axis = 0)) / inp.std(axis = 0)
normed = inp

def sq_dist(a, b):
	return np.sum((a - b) ** 2, axis = 1) #will return squared distance

c1 = np.array([1,1,1,1,1,1])
c2 = np.array([0,0,0,0,0,0])

for i in range(50):
	d_a = sq_dist(c1, normed) #Distance from cluster to points
	d_b = sq_dist(c2, normed)

	w1 = np.divide(d_b, d_a + d_b) #Find the fuzzy weights
	w2 = np.divide(d_a, d_a + d_b)

	sse = np.sum(np.multiply(d_a, w1) + np.multiply(d_b, w2)) #Calculate SSE

	w_1 = w1.reshape(695, 1)
	w_2 = w2.reshape(695, 1)

	w1_sq = np.multiply(w_1**2, np.ones((695, 6))) #Broadcasting
	w2_sq = np.multiply(w_2**2, np.ones((695, 6)))

	w1_m = np.multiply(w1_sq, normed) #M step starts here
	w2_m = np.multiply(w2_sq, normed) #This is the numerator

	new_c1 = np.sum(w1_m, axis = 0) / np.sum(w1**2) #Sum up the numerator and divide it by weights
	new_c2 = np.sum(w2_m, axis = 0) / np.sum(w2**2)
	dist = np.sum(np.abs(new_c1 - c1)) + np.sum(np.abs(new_c2 - c2))

	print(sse)
	print(new_c1)
	print(new_c2)

	if dist < 0.001: #break conditions
		break

	c1 = new_c1
	c2 = new_c2


from scipy.stats import logistic
from numpy import tanh

SIGMOIDE = lambda x: logistic.cdf(x)
HIPERBOLICA = lambda x: tanh(x)


##module LUdecomp
'''
a= LUdecomp(a)
LUdecompsition: [L][U]=[a]

x= LUsolve(a,b)

solution phase: solves [L][U]{x}={b}
'''

import numpy as np

def LUdecomp(a):
    n = len(a)
    for k in range(0,n-1):
        for i in range(k+1,n):
            if a[i,k] != 0:
                lam = a[i,k]/a[k,k]
                a[i,k+1:n]=a[i,k+1:n]-lam*a[k,k+1:n]
                a[i,k]=lam
    return a

def LUsolve(a,b):
    n = len(a)
    for k in range(1,n):
        b[k] = b[k]-np.dot(a[k,0:k],b[0:k])
    b[n-1] = b[n-1]/a[n-1,n-1]

    for k in range(n-2,-1,-1):
        b[k] = (b[k]-np.dot(a[k,k+1:n],b[k+1:n]))/a[k,k]

    return b


# Calculates an estimation for 1/pi
# with various convergences

import numpy as np

def Borwein_2(k):			# Quadratic convergence
	a = np.sqrt(2)
	b = 0
	p = 2 + np.sqrt(2)
	for i in range(k):
		b = (1+b)*np.sqrt(a)/(a+b)
		a = (np.sqrt(a)+1/np.sqrt(a))/2
		p = ((1+a)*p*b)/(1+b)
	return p

def Borwein_3(k):			# Cubic convergence
	a = 1./3
	s = (np.sqrt(3)-1)/2
	for i in range(k):
		r = 3/(1+2*(1-s**3)**(1./3))
		s = (r-1)/2
		a = r**2*a-3**i*(r**2-1)
	return a

def Borwein_4(k):			# Quartic convergence
	a = 6-4*np.sqrt(2)
	y = np.sqrt(2)-1
	for i in range(k):
		y = (1-(1-y**4)**(1./4))/(1+(1-y**4)**(1./4))
		a = a*(1+y)**4 -2**(2*i+3)*y*(1+y+y**2)
	return a


import numpy as np

N = np.array([[ 3.35774, -1.68663, -0.62988, -1.04123],
              [-1.68663,  3.15898, -0.90703, -0.56532],
              [-0.62988, -0.90703,  4.05644, -2.51953],
              [-1.04123, -0.56532, -2.51953, 4.12608]])
# Moore-Penrose pseudo inverse
print('Determinant: {:.3f}'.format(np.linalg.det(N)))
inv1 = np.linalg.pinv(N)
print('Moore-Penrose pseudo inverse')
print(inv1)
print('N == N * inv1 * N ? {}'.format(np.allclose(N, N.dot(inv1).dot(N))))
# Singular value decomposition (SVD)
U, S, VT = np.linalg.svd(N)     # attention V transpose is returned!
S1 = np.array([ 1.0/s if abs(s) > 1e-6 else 0 for s in S])
inv2 = VT.T.dot(np.diag(S1)).dot(U.T)
print('SVD general inverse')
print(inv2)
print('inv1 == inv2 ? {}'.format(np.allclose(inv1, inv2)))
# regularize matrix with eigenvalues
lamb, E = np.linalg.eig(N)
N1 = np.zeros(N.shape)
for i in range(lamb.size):
    if abs(lamb[i]) < 1e-6:
        N1 = N1 + np.outer(E[:,i], E[:,i])  # diad product
inv3 = np.linalg.inv(N + N1) - N1
print('General inverse from regularization')
print(inv3)
print('inv1 == inv3 ? {}'.format(np.allclose(inv1, inv3)))


import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
from os import path

def plotData(x, y, h,title):
    plt.subplots()
    plt.plot(x, h)
    plt.scatter(x, y, c='red')
    plt.grid(color='gray', linestyle='--', linewidth=.6,
             axis='both', which='both', alpha=.4)
    plt.title(title)
    plt.show()

def calcR2(actual_y, predicted_y, avg_y):
    return np.square(predicted_y - avg_y).sum()/np.square(actual_y - avg_y).sum()

def calcSlope(x, y, mean_x, mean_y):
    numerator = np.sum((x - mean_x) * (y - mean_y))
    denominator = np.sum(np.square(x - mean_x))
    return numerator/denominator

def run():
    data_file = path.join(path.dirname(__file__),'../../data/food-cart.csv')
    data = np.genfromtxt(data_file, delimiter=',')
    X = data[:,0]
    Y = data[:,1]
    mean_x = np.mean(X)
    mean_y = np.mean(Y)
    slope_m = calcSlope(X, Y, mean_x, mean_y)
    intercept_c = mean_y - mean_x * slope_m
    m = len(X)
    H = []
    for i in range(0, m):
        H.append(slope_m * X[i] + intercept_c)
    title = "Slope : {}, Y Intercept : {}".format(slope_m, intercept_c)
    print("Slope : {}, Y Intercept : {}, Prediction Accuracy (R2 Error) : {}".format(
        slope_m, intercept_c, calcR2(Y, H, mean_y)))
    plotData(X, Y, H,title)


#!/usr/local/bin/python3

import numpy as np

def gompertz_func_fit(x,a,b,c):
    return c*np.exp(-a*np.exp(-b*x))

def gompertz_func(x, pars):
    a = pars[0]
    b = pars[1]
    c = pars[2]
    return c*np.exp(-a*np.exp(-b*x))
################################################
###############################################
##############################################
def gompertz_der_func_fit(x,a,b,c):
    return a*b*c*np.exp(-a*np.exp(-b*x) - b*x)

def gompertz_der_func(x, pars):
    a = pars[0]
    b = pars[1]
    c = pars[2]
    return a*b*c*np.exp(-a*np.exp(-b*x) - b*x)
################################################
###############################################
##############################################
def logistic_func_fit(x,N,xmax,b):
    return N/(1+np.exp(-(x-xmax)/b))

def logistic_func(x, pars):
    N = pars[0]
    xmax = pars[1]
    b = pars[2]
    return N/(1+np.exp(-(x-xmax)/b))
################################################
###############################################
##############################################
def logistic_der_func_fit(x,N,xmax,b):
    return ((N/b)*np.exp(-(x-xmax)/b))/((1+np.exp(-(x-xmax)/b))**2)

def logistic_der_func(x, pars):
    N = pars[0]
    xmax = pars[1]
    b = pars[2]
    return ((N/b)*np.exp(-(x-xmax)/b))/((1+np.exp(-(x-xmax)/b))**2)
################################################
###############################################
##############################################

def logistic_gen_func_fit(x,a,m,n,tau):
    return a*( (1+m*np.exp(-x/tau))/ (1+n*np.exp(-x/tau)) )

def logistic_gen_func(x, pars):
    a = pars[0]
    m = pars[1]
    n = pars[2]
    tau = pars[3]
    return a*( (1+m*np.exp(-x/tau))/ (1+n*np.exp(-x/tau)) )

def logistic_gen_der_func_fit(x,a,m,n,tau):
    return (a/(1+n*np.exp(-x/tau))**2) * \
                    ( (n/tau)*np.exp(-x/tau) - (m/tau)*np.exp(-x/tau) )

def logistic_gen_der_func(x, pars):
    a = pars[0]
    m = pars[1]
    n = pars[2]
    tau = pars[3]
    return (a/(1+n*np.exp(-x/tau))**2) * \
                    ( (n/tau)*np.exp(-x/tau) - (m/tau)*np.exp(-x/tau) )
################################################
###############################################
##############################################
def lin_func_fit(x, a,b):
    return a + b * x

def lin_func(x, pars):
    intercept = pars[0]
    coeff = pars[1]
    return intercept + coeff * x
################################################
###############################################
##############################################
def exp_func_fit(x, a,b):
    return a*np.exp(b*x)

def exp_func(x, pars):
    a = pars[0]
    b = pars[1]
    return a*np.exp(b*x)
################################################
###############################################
##############################################
map_func = {'linear':{'get_fit':lin_func_fit,
                     'get_values':lin_func,
                     'p0':None},
            'exponential':{'get_fit':exp_func_fit,
                     'get_values':exp_func,
                     'p0':None},

            'gompertz':{'get_fit':gompertz_func_fit,
                     'get_values':gompertz_func,
                       'p0':[10,1,100]},
            'gompertz_der':{'get_fit':gompertz_der_func_fit,
                     'get_values':gompertz_der_func,
                       'p0':[10,1,100]},

            'logistic':{'get_fit':logistic_func_fit,
                     'get_values':logistic_func,
                       'p0':None},
            'logistic_der':{'get_fit':logistic_der_func_fit,
                     'get_values':logistic_der_func,
                       'p0':None},

            'logistic_gen':{'get_fit':logistic_gen_func_fit,
                     'get_values':logistic_gen_func,
                       'p0':None},
            'logistic_gen_der':{'get_fit':logistic_gen_der_func_fit,
                     'get_values':logistic_gen_der_func,
                       'p0':None},
                 }


import numpy

'''
Gradient descent is an optimization algorithm for finding the local minimum of a
differentiable function. We continue running the algorithm until our parameters
converge to an approximate solution.
'''

# these are our training examples
# size is our input, so that is a "feature"
# # of bedrooms is an input, so that is also a feature
# price is our output, so that is the "target"
sizes = [2104, 1416, 1534, 852, 1234]
bedrooms = [3, 2, 3, 2, 4]
prices = [400, 232, 315, 178, 231]

# `m` is the number of training examples, meaning the number of rows
# `n` is the number of features
m, n = len(prices), 2

# initialize our parameters at 0
params = [0, 0, 0]

# our learning rate
alpha = 1E-8

# let's express our features as one list
features = [[1 for _ in range(m)], sizes, bedrooms]

# our hypothesis function
h = lambda x, i: sum([params[j] * x[j][i] for j in range(n+1)])

# distance between hypothesis and targets
# iterate
while d := numpy.sqrt(sum([numpy.square(h(features, i) - prices[i]) for i in range(m)])) > 50:
    for i in range(m):
        for j in range(n+1):
            # this is stochastic gradient descent
            params[j] -= alpha / m * (h(features, i) - prices[i]) * features[j][i]

print(params)
print([h(features, i) for i in range(m)])

# let's reset...
params = [0, 0, 0]

# distance between hypothesis and targets
# iterate
while d := numpy.sqrt(sum([numpy.square(h(features, i) - prices[i]) for i in range(m)])) > 50:
    for j in range(n+1):
        # this is batch gradient descent
        pd = 0
        for i in range(m):
            pd += (h(features, i) - prices[i]) * features[j][i]
        params[j] -= alpha / m * pd

print(params)
print([h(features, i) for i in range(m)])

'''
As you can see above, these are the applications of the same algorithm but in different
loops. One is a batch, and the other is stochastic. They produce very similar results.
However, stochastic is much faster when working with a large number of training examples.

DISCLAIMER:
Note that these algorithms are pythonic interpretations of the pseudo-code, and there are other
methods of optimizing this. These samples are just to represent the algorithm at play.
'''


import numpy as np

def E(x):
    u = x[0]
    v = x[1]
    return (u*np.exp(v) - 2*v*np.exp(-u))**2

def grad_E(x):
    grad = np.zeros(2)
    u = x[0]
    v = x[1]
    grad[0] = 2*(u*np.exp(v) - 2*v*np.exp(-u))*(np.exp(v) + 2*v*np.exp(-u))
    grad[1] = 2*(u*np.exp(v) - 2*v*np.exp(-u))*(u*np.exp(v) - 2*np.exp(-u))
    return grad

def coord_descent(E, grad_E, init=np.ones(2), eta=0.1, tol=1e-6, max_iter=10**5):
    iters=0
    u = init
    error = E(u)
    while error > tol and iters < max_iter:
        u[0] = u[0] - eta*grad_E(u)[0]
        u[1] = u[1] - eta*grad_E(u)[1]
        error = E(u)
        iters = iters+1
        print(grad_E(u))
        print(u)
    return u, iters, error

u, iters, error = coord_descent(E, grad_E, init=np.ones(2), tol=1e-30, max_iter=15)
print(u, iters, error)


import scipy
import numpy as np
from scipy.ndimage import zoom
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.ticker import LinearLocator, FormatStrFormatter
from matplotlib import cm
# Changing figure's font to Times New Roman
plt.rcParams["font.family"] = "serif"
plt.rcParams["font.serif"] = "Times New Roman"

'''
NAC 3-D Plot Code
'''
# 3-D axis creation
fig = plt.figure(figsize=plt.figaspect(0.5))
ax = fig.add_subplot(1, 2, 1, projection='3d')

# Two functions for multiplication, and creating transformation matrix W
sigmoid = lambda y: 1 / (1 + np.exp(-y))
tanh = lambda x: ((np.exp(x)-np.exp(-x))/(np.exp(x)+np.exp(-x)))

# Make data
X = np.arange(-10, 10, 0.25)
Y = np.arange(-10, 10, 0.25)
X, Y = np.meshgrid(X, Y)
# Final function for Transformation Matrix W
Z = tanh(X)*sigmoid(Y)

# Axis Labelling
ax.set_xlabel('tanh(x)', fontsize=14)
ax.set_ylabel('sigmoid(y)', fontsize=14)
ax.set_zlabel('W', fontsize=16)
ax.set_title('(a) NAC surface plot', fontsize=18, y=-0.04)

# Plot the surface.
surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm,
                       linewidth=0, antialiased=False)

# Customize the z axis.
ax.set_zlim(-1.11, 1.11)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))

# Add a color bar which maps values to colors.
fig.colorbar(surf, shrink=0.4, aspect=6)

'''
NALU 3-D Plot Code
'''

ax = fig.add_subplot(1, 2, 2, projection='3d')

# Sub-function decleration for NALU units
sigmoid = lambda y: 1 / (1 + np.exp(-y))
tanh = lambda x: ((np.exp(x)-np.exp(-x))/(np.exp(x)+np.exp(-x)))
# learned gate
g_x = lambda x: 1 / (1 + np.exp(-x))
# small epsilon value
eps = 10e-2
# log-exponential space
m_y = lambda y: np.exp(np.log(np.abs(y)+eps))

# Make data
X = np.arange(-10, 10, 0.25)
Y = np.arange(-10, 10, 0.25)
X, Y = np.meshgrid(X, Y)

# Final function for Transformation Matrix W
W_nac = tanh(X)*sigmoid(Y)
# Final function for NALU
Z = ( (g_x(Y)*W_nac) + ((1-g_x(Y))*m_y(Y)) )

# Axis Labelling
ax.set_xlabel('tanh(x)', fontsize=14)
ax.set_ylabel('sig-M/G(y), exp(log(|y|+eps))', fontsize=14)
ax.set_zlabel('Y', fontsize=16)
ax.set_title('(b) NALU surface plot',  fontsize=18, y=-0.04)

# Plot the surface.
surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm,
                       linewidth=0, antialiased=False)

# Customize the z axis.
ax.set_zlim(-1.1, 10.1)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))

fig.colorbar(surf, shrink=0.4, aspect=6)

# show plot
plt.show()
# save plot in given directory
fig.savefig('final_assets/nac-nalu-comparison.png')


import numpy as np
#np.random.seed(0)
def sigmoid (x):
    return 1/(1 + np.exp(-x))
def sigmoid_derivative(x):
    return x * (1 - x)
#Input datasets
inputs = np.array([[0,0],[0,1],[1,0],[1,1]])
expected_output = np.array([[0],[1],[1],[0]])
epochs = 10000
lr = 0.1
inputLayerNeurons, hiddenLayerNeurons, outputLayerNeurons = 2,2,1
#Random weights and bias initialization
hidden_weights = np.random.uniform(size=(inputLayerNeurons,hiddenLayerNeurons))
hidden_bias =np.random.uniform(size=(1,hiddenLayerNeurons))
output_weights = np.random.uniform(size=(hiddenLayerNeurons,outputLayerNeurons))
output_bias = np.random.uniform(size=(1,outputLayerNeurons))
print("Initial hidden weights: ",end='')
print(*hidden_weights)
print("Initial hidden biases: ",end='')
print(*hidden_bias)
print("Initial output weights: ",end='')
print(*output_weights)
print("Initial output biases: ",end='')
print(*output_bias)
#Training algorithm
for _ in range(epochs):
#Forward Propagation
    hidden_layer_activation = np.dot(inputs,hidden_weights)
    hidden_layer_activation += hidden_bias
    hidden_layer_output = sigmoid(hidden_layer_activation)
    output_layer_activation =np.dot(hidden_layer_output,output_weights)
    output_layer_activation += output_bias
    predicted_output = sigmoid(output_layer_activation)
#Backpropagation
    error = expected_output - predicted_output
    d_predicted_output = error * sigmoid_derivative(predicted_output)
    error_hidden_layer = d_predicted_output.dot(output_weights.T)
    d_hidden_layer = error_hidden_layer * sigmoid_derivative(hidden_layer_output)
#Updating Weights and Biases
    output_weights +=hidden_layer_output.T.dot(d_predicted_output) * lr
    output_bias += np.sum(d_predicted_output,axis=0,keepdims=True)* lr
    hidden_weights += inputs.T.dot(d_hidden_layer) * lr
    hidden_bias += np.sum(d_hidden_layer,axis=0,keepdims=True) *lr
print("Final hidden weights: ",end='')
print(*hidden_weights)
print("Final hidden bias: ",end='')
print(*hidden_bias)
print("Final output weights: ",end='')
print(*output_weights)
print("Final output bias: ",end='')
print(*output_bias)
print("\nOutput from neural network after 10,000 epochs: ",end='')
print(*predicted_output)


import pandas as pd
from sklearn import linear_model, model_selection
import statsmodels.api as sm
import matplotlib.pyplot as plt
import seaborn as sns

df_fifa = pd.read_csv('../dataframes/df_fifa.csv')

#Construção do modelo estatístico
df_fifa_2 = df_fifa[["Overall","Value","Wage","Release_Clause"]]
df_fifa_2.dropna(inplace=True)

# variável preditora : Value
# variável alvo : Release_Clause

X_sm = sm.add_constant(df_fifa_2["Value"])
results = sm.OLS(df_fifa_2["Release_Clause"], X_sm).fit()

print(results.summary())

#Gráfico de correlação
sns.lmplot(x = "Value", y = "Release_Clause",data=df_fifa_2, line_kws={'color': 'red'})
plt.xlabel("Valor de mercado do jogador")
plt.ylabel("Valor da quebra do contrato do jogador")
plt.title("Valor de mercado \n x \n Valor da quebra do contrato", fontweight='bold')
name = "../galeria/{}/Modelo_estatistico_fifa_1.{}"
plt.savefig(name.format("PNG", "png"))
plt.savefig(name.format("PDF", "pdf"))
plt.show()

#Observa-se um alto grau de correlação entre a quebra do contrato
#de um jogador com o seu valor de mercado
#Que encontramos um R² do modelo feito aproximado de 0,935

##############################

independentes = df_fifa_2.drop("Value", axis = 1)
dependente = df_fifa_2["Value"]

#Dividir o conjunto de dados em treino e teste
X_treino, X_teste, Y_treino, Y_teste = model_selection.train_test_split(independentes, dependente, test_size = 0.4, random_state = 1)#40% separado para teste

print(X_treino.shape)
print(X_teste.shape)
print(Y_treino.shape)
print(Y_teste.shape)

model = linear_model.LinearRegression()
model.fit(X_treino, Y_treino)

Y_previsto = model.predict(X_teste)
fig = plt.figure()
plt.scatter(Y_teste, Y_previsto)
fig.set_size_inches(6, 6)
plt.xlabel("Valor do Jogador")
plt.ylabel("Valor do Jogador Previsto")
plt.title("Valor do Jogador \n x \n Valor do Jogador Previsto", fontweight='bold')
name = "../galeria/{}/Modelo_estatistico_fifa_2.{}"
plt.savefig(name.format("PNG", "png"))
plt.savefig(name.format("PDF", "pdf"))
plt.show()

fig = plt.figure()
plt.scatter(model.predict(X_treino), model.predict(X_treino) - Y_treino, color = "green", s = 10, label = "Treino")
plt.scatter(model.predict(X_teste), model.predict(X_teste) - Y_teste, color = "blue", s = 10, label = "Teste")

fig.set_size_inches(6, 6)
plt.legend()
plt.title("Diferença entre treino e teste", fontweight='bold')
name = "../galeria/{}/Modelo_estatistico_fifa_3.{}"
plt.savefig(name.format("PNG", "png"))
plt.savefig(name.format("PDF", "pdf"))
plt.show()

print("\n####################################\n")
print(model.score(X_treino, Y_treino))
print("Intercept:", model.intercept_)
print("Coefs:", model.coef_)

print("\n####################################\n")


#!/usr/bin/env python
import numpy as np
from typing import Callable

def rectangle(a: float, b: float, f: Callable[[np.array], np.array],
        h: float) -> float:
    return h * np.sum(f(np.arange(a + h / 2, b + h / 2, h)))

def trapezoid(a: float, b: float, f: Callable[[np.array], np.array],
        h: float) -> float:
    return h / 2 * (f(a) + 2 * np.sum(f(np.arange(a + h, b, h))) + f(b))

def simpson(a: float, b: float, f: Callable[[np.array], np.array],
        h: float) -> float:
    return h / 6 * (f(a) + 2 * np.sum(f(np.arange(a + h, b, h))) +
        4 * np.sum(f(np.arange(a + h / 2, b + h / 2, h))) + f(b))


"""
Code to explore operations on numpy arrays.
"""

# imports
import numpy as np

print('\nCreate a vector (a 1-D array) from a [list]')
a = np.array([1,2,3,4,5,6])
print(a)

print('\nIndexing is just like you learned for lists')
print('e.g. a[::2] gets every other item')
print(a[::2])

print('\nThe index of the last element is -1')
print('so a[-1] gives:')
print(a[-1])

# create 2-D arrays using arange and reshape
# - arange makes an array of integers, starting at 0
# - reshape reshapes an array according to the "size"
# in the tuple you pass to it.
aa = np.array(np.arange(12)).reshape((6,2))
bb = np.array(np.arange(12)).reshape((2,6))
print('\n2-D arrays of different shapes:')
print('\naa =')
print(aa)
print('\nbb =')
print(bb)

# indexing: for 2-D arrays:
# - axis 0 is the rows and
# - axis 1 is the columns,
# and you address each axis in turn in the [], separated by commas
print('\nIndexing a 2-D array: aa[:3, :]')
print(aa[:3, :]) # the second : means "all items of that axis"

print('\nElement-by-element operation (like .* in MATLAB): ')
#
print('\n - exponentiation')
print(aa**2.7) # exponentiation is **
#
print('\n - square root')
print(np.sqrt(aa)) # square root
#
print('\n - Error from aa + bb')
try:
    print(aa + bb) # addition (throws an error because aa and bb are different shapes)
except ValueError as err:
    print(err)
print('\n - aa + bb.T works')
print(aa + bb.T) # need to take transpose of bb so it has the same shape as aa

print('\nMatrix multiplication using @: aa @ bb')
print(aa @ bb)

print('\nMatrix multiplication using @: bb @ aa')
print(bb @ aa)

print('\nConcatenate two arrays')
cc = np.concatenate((aa,aa), axis=1)
print(cc)

print('\nFlatten an array')
print(aa.flatten())

print('\n Make a Boolean mask of aa >= 5')
mask = aa >= 5
print(mask)

print('\n Use the mask to get just those elements of aa: aa[mask]')
print(aa[mask])
print('Note that this returns just a 1-D arrray (flattened)')

"""
Exercises to try on your own:

What happens if you make a copy of a by typing b = a,
and then change an element in a by hand?  Does b change as well?
Now try it again using the copy method b = a.copy() and see
what happens.

To change an element by hand just assign it: a[3] = 17

You can convert an array of integers to an array of floats this way:
af = a.astype(float)

Figure out how to find the mean, standard deviation, max, and index of
the max of an array.

Missing data is represented as the number np.nan.  What happens if
you put a nan in your array and then calculate the mean?  You have
to convert the array to floats first before you can put a nan in.
Then also try the method np.nanmean() on the array with a nan.

Look around in other sub-modules using dir() and help():
* np.random (quickly generate arrays of random numbers, e.g. np.random.randn())
* np.linalg (linear algebra stuff like the inverse of a matrix: np.linalg.inv())
* np.ma (masked arrays)
* scipy.stats (have to import scipy first)

"""


import numpy as np
from math import *
from fractionalBrownianmotion import fBm

def ornstein_uhlenbeck(dt,n,drift,sigma,H):
    '''
    Generates a path of fractional Ornstein Uhlenbeck using a Euler scheme with timestep dt

    args:
        dt:    timestep for the Euler scheme (float)
        n:     sample size (array)
        drift:      drift parameter (float)
        sigma:      diffusion parameter (float)
        H:          hurst parameter (float)
    '''
    T = dt*n
    trials = 1
    B = fBm(T,n,H,trials)
    x = np.zeros((n+1,trials))
    for i in range(n):
        x[i+1,:] = x[i,:] - dt * x[i,:] * drift + sigma*(B[i+1,:]-B[i,:])
    return x[:,0]

def increments(x,q):
    '''
    Given a discrete sample path x, generates a q-dimensional sample which contains
    the original sample and its increments (to the qth order)

    args:
        x:    sample (array)
        q:     number of increments desired (float)

    returns: (n x q)-array
    '''
    n = len(x)-q
    X = np.zeros((n,q))
    for i in range(1,q):
        X[:,i] = x[i:i+n] - x[0:n]
    X[:,0] = x[0:n]
    return X

def true_sample(n,q,h,xi,sigma,H):
    '''
    Given an observation timestep h, generates the associated discrete sample path of fractional Ornstein Uhlenbeck.

    args:
        n:     sample size (float)
        q:     number of increments desired (float)
        h:     observation timestep
        xi:    drift parameter
        sigma: diffusion parameter
        H:     hurst parameter

    returns:  Observed process + its increments ->  (n x q)-array
    '''
    dt = 0.001
    x = ornstein_uhlenbeck(dt,int((n+q)*(h/dt)), xi, sigma, H)
    good_indices = [i for i in range(int((n+q)*(h/dt))) if i % int(1/h) == 0]
    x = np.array([x[i] for i in good_indices])
    X = increments(x,q)
    return X

def euler_sample(n,q,gamma,xi,sigma,H):
    x = ornstein_uhlenbeck(gamma,n+q, xi, sigma, H)
    X = increments(x,q)
    return X


import numpy as np

# Split an array into multiple sub-arrays as views into ary.
# **indices_or_sections**
# If indices_or_sections is an integer, N, the array will be divided into N equal arrays along
# axis. If such a split is not possible, an error is raised.
# If indices_or_sections is a 1-D array of sorted integers, the entries indicate where along
# axis the array is split. For example, [2, 3] would, for axis=0, result in
# ary[:2]
# ary[2:3]
# ary[3:]
# If an index exceeds the dimension of the array along axis, an empty sub-array is returned
# correspondingly.
# axis : The axis along which to split, default is 0(row).
print("<---- split method ---->")
a = np.arange(1.0, 11.0)
print("Array a : ", a)
print("split array by specifying integer : ", np.split(ary=a, indices_or_sections=5, axis=0))
a = np.arange(9.0)
print("split array by specifying sorted integer : ", np.split(ary=a, indices_or_sections=(2, 5, 6, 10)))

print("<---- array_split ---->")
#  The only difference between these functions is that array_split allows indices_or_sections
#  to be an integer that does not equally divide the axis. For an array of length l that should
#  be split into n sections, it returns l % n sub-arrays of size l//n + 1 and the rest of
#  size l//n.
a = np.arange(11.0)
print("Array a : ", a)
# if we want unequal partition then we can use this
print("array_split array by specifying integer : ", np.array_split(ary=a, indices_or_sections=4, axis=0))


"""
Test CGN for an equality-constrained nonlinear least-squares problem.
Based on tests (29) in More, Garbow and Hillstrom "Testing Unconstrained Optimization Software"
"""

import numpy as np
import scipy.optimize as sciopt

import cgn
from tests.acceptance.problem import TestProblem
from tests.acceptance.do_test import do_test

n = 300
m = n
h = 1 / (n+1)
t = np.arange(1, n+1) * h

def dbv(x):
    """
    Implements discrete boundary value function
    :param x:
    :return:
    """
    u = np.power(x + t + 1, 3)
    x_patched = np.concatenate((np.zeros(1), x, np.zeros(1)))
    x_less = np.roll(x_patched, 1)[1:-1]  # now x_less[i] = x[i-1]
    x_more = np.roll(x_patched, -1)[1:-1] # now x_more[i] = x[i+1]
    f = 2 * x - x_less - x_more + 0.5 * h**2 * u
    return f

def dbv_jac(x):
    """
    Implements the Jacobian of the discrete boundary value function
    """
    jac = np.zeros((n, n))
    du = 3 * np.power(x+t+1, 2)
    # fill row-wise
    for i in range(n):
        if i>0: jac[i, i-1] = - 1
        if i<n-1: jac[i, i+1] = - 1
        jac[i, i] = 2 + 0.5 * h**2 * du[i]
    return jac

class NonlinearConstrainedProblem(TestProblem):

    def __init__(self):
        TestProblem.__init__(self)
        x0 = t * (1 - t)
        x = cgn.Parameter(start=np.zeros(n), name="x")
        x.beta = 0.
        self._problem = cgn.Problem(parameters=[x], fun=dbv, jac=dbv_jac)
        # compute reference solution with scipy.optimize:
        loss_fun = self._problem.costfun
        loss_grad = self._problem.costgrad
        x_ref = sciopt.minimize(fun=loss_fun, jac=loss_grad, x0=x0).x
        self._minimum = loss_fun(x_ref)

        # Make a sum-to-one constraint that holds at the reference solution.
        a = np.ones((1, n))
        b = np.array(a @ x_ref).reshape((1,))
        x0 = x0 / np.sum(x0) * np.sum(x_ref)
        assert np.isclose(a @ x0, b).all()
        x.start = x0 * np.ones(n)
        # Add the constraint to the cgn.Problem object
        eqcon = cgn.LinearConstraint(parameters=[x], a=a, b=b, ctype="eq")
        self._problem.constraints.append(eqcon)
        # Resolve problem with SLSQP
        def eqfun(x):
            return a @ x - b
        def eqjac(x):
            return a
        eqcon = {"type": "eq", "fun": eqfun, "jac": eqjac}
        x_ref = sciopt.minimize(fun=loss_fun, jac=loss_grad, x0=x0, constraints=(eqcon)).x
        self._minimum = self._problem.costfun(x_ref)
        self._tol = 1e-3

def test_nonlinear_constrained():
    ncp = NonlinearConstrainedProblem()
    do_test(ncp)


# --------------
# Code starts here

import numpy as np

# Code starts here

# Adjacency matrix
adj_mat = np.array([[0,0,0,0,0,0,1/3,0],
                   [1/2,0,1/2,1/3,0,0,0,0],
                   [1/2,0,0,0,0,0,0,0],
                   [0,1,0,0,0,0,0,0],
                  [0,0,1/2,1/3,0,0,1/3,0],
                   [0,0,0,1/3,1/3,0,0,1/2],
                   [0,0,0,0,1/3,0,0,1/2],
                   [0,0,0,0,1/3,1,1/3,0]])

# Compute eigenvalues and eigencevectrs
eigenvalues,eigencevectors  = np.linalg.eig(adj_mat)

eigen_first = eigencevectors[:,0]
eigen_1 = abs(eigen_first)/(np.linalg.norm(eigen_first,1))
max_idx = -1
maxi = eigen_1[0]
print(eigen_1)
for i in range(1,len(eigen_1)):
    if eigen_1[i]>maxi:
        maxi= eigen_1[i]
        max_idx = i
page = max_idx + 1
print(page)

# Eigen vector corresponding to 1

# most important page

# Code ends here

# --------------
# Code starts here

# Initialize stationary vector I
init_I = np.array([1,0,0,0,0,0,0,0]
)

# Perform iterations for power method
for i in range(10):
    init_I = np.dot(adj_mat,init_I)
    init_I = (init_I)/ (np.linalg.norm(init_I,1))

print(init_I)
max_idx = -1
maxi = init_I[0]

for i in range(1,len(init_I)):
    if init_I[i]>maxi:
        maxi= init_I[i]
        max_idx = i
power_page = max_idx + 1
print(page)

# Code ends here

# --------------
# Code starts here

# New Adjancency matrix
# New Adjancency matrix
new_adj_mat = np.array([[0,0,0,0,0,0,0,0],
                   [1/2,0,1/2,1/3,0,0,0,0],
                  [1/2,0,0,0,0,0,0,0],
                   [0,1,0,0,0,0,0,0],
                   [0,0,1/2,1/3,0,0,1/2,0],
                   [0,0,0,1/3,1/3,0,0,1/2],
                   [0,0,0,0,1/3,0,0,1/2],
                   [0,0,0,0,1/3,1,1/2,0]])

# Initialize stationary vector I
new_init_I = np.array([1,0,0,0,0,0,0,0]
)

# Perform iterations for power method
for i in range(10):
    new_init_I = np.dot(new_adj_mat,new_init_I)
    new_init_I = (new_init_I)/ (np.linalg.norm(new_init_I,1))

print(new_init_I)
max_idx = -1
maxi = new_init_I[0]

for i in range(1,len(new_init_I)):
    if new_init_I[i]>maxi:
        maxi= new_init_I[i]
        max_idx = i
new_power_page = max_idx + 1

# Code ends here

# --------------
# Alpha value
alpha = 0.85

# Code starts here

# Modified adjancency matrix
G = (alpha*new_adj_mat) + (1-alpha)*(1 / len(new_adj_mat))*np.ones(new_adj_mat.shape)

# Initialize stationary vector I
final_init_I = np.array([1,0,0,0,0,0,0,0])

# Perform iterations for power method
for i in range(1000):
    final_init_I = np.dot(G,final_init_I)
    final_init_I = final_init_I/(np.linalg.norm(final_init_I,1))

print(final_init_I)

# Code ends here


# Simpson's rule -- odd intervals

import math
import numpy
import pylab

# function we wish to integrate
def fun(x):
    return numpy.sin(math.pi*x)

# analytic value of the integral of fun() above
def I_exact(a,b):
    return (1.0/math.pi)*(-math.cos(math.pi*b) + math.cos(math.pi*a))

# do a Simpson's integration by breaking up the domain [a,b] into N
# slabs.  Note: we consider the N odd case here:
def simp(a,b,f,N):

    xedge = numpy.linspace(a,b,N+1)

    integral = 0.0

    if N%2 == 0:
        M = N
        odd = 0
    else:
        M = N - 1
        odd = 1

    delta = (xedge[1] - xedge[0])

    n = 0
    while n < M:
        integral += (1.0/3.0)*delta*(f(xedge[n]) +
                                     4.0*f(xedge[n+1]) +
                                     f(xedge[n+2]))
        n += 2

    # if we had an odd # of bins, do the last one:
    if (odd):
        integral += (delta/12.0)*(-f(xedge[N-2]) +
                                   8.0*f(xedge[N-1]) +
                                   5.0*f(xedge[N]))

    return integral

a = 0.0
b = 1.0

N = [3, 7, 15, 31]

delta = []
error = []

for n in N:
    t = simp(a,b,fun,n)
    e = t - I_exact(a,b)
    dx = (b - a)/n

    print dx, t, e

    # for plotting
    delta.append(dx)
    error.append(e)

d = numpy.array(delta)
e = numpy.array(error)
pylab.scatter(d, e)

# plot a line representing delta**4 scaling
pylab.plot(d, e[0]*(d/d[0])**4)

ax = pylab.gca()
ax.set_xscale('log')
ax.set_yscale('log')

pylab.xlabel(r"$\delta$")
pylab.ylabel("absolute error")

pylab.savefig("simp-odd.eps")


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Oct  7 11:04:44 2021

@author: denisdumont
"""
###############################################################################
#Dice or Coin throws : Calulate "N_mean" means over "N_throw" throws of dice
#and verify the expected normal law distribution
###############################################################################

import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
from collections import Counter

N=10000 #Nombre de moyenne
n=1000 #Nombre de lancer
m=24 #Nombre de face du dé (2 pour une pièce)

#QUESTIONS 1 et 2
lancers=np.random.randint(1,m+1,n)
moyenne=np.mean(lancers)
print(moyenne)
print(Counter(lancers))

#QUESTION 3 et 4
N=10000
n=1000

list_moyenne=[]
for i in np.arange(1,N):
    lancers=np.random.randint(1,m+1,n)
    moyenne=np.mean(lancers)
    list_moyenne.append(moyenne)

prob,interv=np.histogram(list_moyenne,bins='auto',density=True)
#Milieu de chaque interval
mid_interv=[(interv[i]+interv[i+1])/2. for i in range(len(interv)-1)]
#Graphique
plt.figure(1)
plt.plot(mid_interv,prob,'o')
plt.xlabel('Value')
plt.ylabel('Probability')

#Definition de la fonction gaussienne
def gauss(x,a,mu,sigma):
    return a*np.exp(-1/2*((x-mu)/sigma)**2)

#Ajsutement et plot en graphique
popt,pcov=curve_fit(gauss,mid_interv,prob,p0=[1/(np.sqrt(np.var(np.arange(1,m+1)))*np.sqrt(2*np.pi)),np.mean(np.arange(1,m+1)),0.1])#Need to adjust p0 to help the fit,by default p0=[1,1,1]
plt.plot(mid_interv,gauss(mid_interv, *popt),'r-',label='Fit: a=%5.3f, mu=%5.3f, sigma=%5.3f' % tuple(popt))

#Graph prediction theoriques
Mean_theo= np.mean(np.arange(1,m+1)) #Calculation of the theoretical mean
Sigma_x=np.sqrt(np.var(np.arange(1,m+1)))
Sigma_theo=Sigma_x/np.sqrt(n) #Calculation of the theoretical standard deviation
x=np.linspace(Mean_theo-3*Sigma_theo,Mean_theo+3*Sigma_theo)
lab="Pred theo: a="+str(round(1/(Sigma_theo*np.sqrt(2*np.pi)),2))+str(round(Mean_theo,2))+", mu="+str(round(Mean_theo,2))+", sigma="+str(round(Sigma_theo,3))
plt.plot(x,gauss(x,1/(Sigma_theo*np.sqrt(2*np.pi)),Mean_theo,Sigma_theo),'g-.',label=lab)
plt.legend()
plt.show()

#QUESTION 5
list_n=[1000,10000,100000]
list_sigma=[]
for j in list_n:
    list_moyenne=[]
    for i in np.arange(1,N):
        lancers=np.random.randint(1,m+1,j)
        moyenne=np.mean(lancers)
        list_moyenne.append(moyenne)

    prob,interv=np.histogram(list_moyenne,bins='auto',density=True)
    #Milieu de chaque interval
    mid_interv=[(interv[i]+interv[i+1])/2. for i in range(len(interv)-1)]
    #Graphique
    plt.figure(2)
    plt.plot(mid_interv,prob,'o')
    plt.xlabel('Value')
    plt.ylabel('Probability')

    #Definition de la fonction gaussienne
    def gauss(x,a,mu,sigma):
        return a*np.exp(-1/2*((x-mu)/sigma)**2)

    #Ajsutement et plot en graphique
    popt,pcov=curve_fit(gauss,mid_interv,prob,p0=[1/(np.sqrt(np.var(np.arange(1,m+1)))*np.sqrt(2*np.pi)),np.mean(np.arange(1,m+1)),0.1])#Need to adjust p0 to help the fit,by default p0=[1,1,1]
    plt.plot(mid_interv,gauss(mid_interv, *popt),'r-',label='Fit: a=%5.3f, mu=%5.3f, sigma=%5.3f' % tuple(popt))
    list_sigma.append(abs(popt[2]))

plt.figure(3)
plt.loglog(list_n,list_sigma,'o',label='data')
plt.loglog(list_n,1/np.sqrt(np.array(list_n)),label='1/n**0.5')
plt.legend()
#Plus on augmente n plus la valeur moyenne ets précise + on observe bien une
#evolution en 1/n^0.5 de l'ecart type comme vu au cours theorique(cfr. figur 3)

#QUESTIONS 6 et 7
#mettre m=6 et m=24


# -*- coding: utf-8 -*-
"""
Coursework 2: Logistical regression
"""

#%%
"""
Imports and definitions
"""

import numpy as np
from pandas.io.parsers import read_csv
import matplotlib.pyplot as plt
from sklearn.preprocessing import PolynomialFeatures
import scipy.optimize as opt

def carga_csv(file_name):
    """
    carga el fichero csv especificado y lo
    devuelve en un array de numpy
    """
    valores = read_csv(file_name,header=None).values
    #suponemos que siempre trabajaremos con float
    return valores.astype(float)

def sigmoid(z):
    """
    sigmoid function
    can be applied on numbers and on numpy arrays of any dimensions
    """
    return 1 / (1 + np.exp(-z))

def coste(theta, X, Y):
    """
    cost function
    computes J(theta) for a given dataset
    """
    m = np.shape(X)[0]
    H = sigmoid((np.dot(X,theta)))
    J = -1/m * ( np.log(H).transpose().dot(Y)
                 + np.log(1-H).transpose().dot(1-Y))
    return J

def gradiente(theta, X, Y):
    """
    gradient function
    computes the gradient of J at a given theta for a given dataset
    """
    m = np.shape(X)[0]
    H = sigmoid(np.dot(X,theta))
    G = 1/m * X.transpose().dot(H - Y)
    return G

def coste_reg(theta, X, Y, lamb):
    """
    cost function with regularization
    computes J(theta) for a given dataset
    with a regularization factor of lambda
    """
    m = np.shape(X)[0]
    H = sigmoid((np.dot(X,theta)))
    J = -1/m * ( np.log(H).transpose().dot(Y)
                 + np.log(1-H).transpose().dot(1-Y))
    reg = lamb/(2*m)*np.sum(theta[1:]**2)
    return J + reg

def gradiente_reg(theta, X, Y, lamb):
    """
    gradient function with regularization
    computes the gradient of J at a given theta for a given dataset
    with a regularization factor of lambda
    """
    m = np.shape(X)[0]
    n = np.shape(X)[1]
    H = sigmoid(np.dot(X,theta))
    G = 1/m * X.transpose().dot(H - Y)
    reg = np.ones(n,)
    reg[0] = 0
    reg = (lamb/m)*reg*theta
    return G + reg

def precision(theta,X,Y):
    """
    accuracy function
    computes the accuracy of the logistic model theta on X with true target variable Y
    """
    m = np.shape(X)[0]
    H = sigmoid(np.dot(X,theta))
    H[H >= 0.5] = 1
    H[H < 0.5] = 0
    return np.sum(H == Y)/m

def pinta_frontera_recta(X, Y, theta):
    """
    draws straight decision borders
    """
    plt.figure()
    x1_min, x1_max = X[:, 0].min(), X[:, 0].max()
    x2_min, x2_max = X[:, 1].min(), X[:, 1].max()
    xx1, xx2 = np.meshgrid(np.linspace(x1_min, x1_max),
               np.linspace(x2_min, x2_max))
    h = sigmoid(np.c_[np.ones((xx1.ravel().shape[0], 1)),
                      xx1.ravel(),
                      xx2.ravel()].dot(theta))
    h = h.reshape(xx1.shape)
    # el cuarto parámetro es el valor de z cuya frontera se
    # quiere pintar
    plt.contour(xx1, xx2, h, [0.5], linewidths=1, colors='b')
    # Add the data to the graph
    plt.scatter(X[pos, 0],X[pos, 1], marker='+', c='k')
    plt.scatter(X[not_pos, 0], X[not_pos, 1], marker='o', c='y')
    plt.xlabel('Exam 1 score')
    plt.ylabel('Exam 2 score')
    plt.show()

def pinta_frontera_curva(X, Y, theta, poly, lamb):
    """
    draws polynomial decision borders
    adds the value of the regularization parameter for wich it was computed
    """
    plt.figure()
    x1_min, x1_max = X[:, 0].min(), X[:, 0].max()
    x2_min, x2_max = X[:, 1].min(), X[:, 1].max()
    xx1, xx2 = np.meshgrid(np.linspace(x1_min, x1_max),
                           np.linspace(x2_min, x2_max))
    h = sigmoid(poly.fit_transform(np.c_[xx1.ravel(),
                            xx2.ravel()]).dot(theta))
    h = h.reshape(xx1.shape)
    # el cuarto parámetro es el valor de z cuya frontera se
    # quiere pintar
    plt.contour(xx1, xx2, h, [0.5], linewidths=1, colors='b')
    # add the data to the graph
    plt.scatter(X[pos, 0],X[pos, 1], marker='+', c='k')
    plt.scatter(X[not_pos, 0], X[not_pos, 1], marker='o', c='y')
    plt.title('lambda = ' + str(lamb))
    plt.xlabel('Exam 1 score')
    plt.ylabel('Exam 2 score')
    plt.show()

#%%
"""
1 - Logistical regression
"""

# loading the data
datos = carga_csv('ex2data1.csv')
X = datos[:, :-1]
Y = datos[:, -1]
m = np.shape(X)[0]
n = np.shape(X)[1]

# Visualizing the data
pos = np.where(Y == 1)
not_pos = np.where(Y == 0)
plt.scatter(X[pos, 0],X[pos, 1], marker='+', c='k', label = 'admited')
plt.scatter(X[not_pos, 0], X[not_pos, 1], marker='o', c='y', label = 'not admited')
plt.legend()
plt.xlabel('Exam 1 score')
plt.ylabel('Exam 2 score')

# Add the column of 1s
X = np.hstack([np.ones([m,1]),X])

theta = np.zeros((n+1,))
# Compute the best value for theta
result = opt.fmin_tnc(func=coste, x0=theta, fprime=gradiente, args=(X,Y), messages=0)
theta_opt = result[0]

# Display the border
pinta_frontera_recta(X[:,1:], Y, theta_opt)

# Display the accuracy of the model
p = precision(theta_opt,X,Y)
print('Model accuracy : {0:1.3g}'.format(p))

#%%
"""
2 - Regularized logistical regression
"""

datos = carga_csv('ex2data2.csv')
X = datos[:, :-1]
Y = datos[:, -1]
m = np.shape(X)[0]

# Visualizing the data
pos = np.where(Y == 1)
not_pos = np.where(Y == 0)
plt.scatter(X[pos, 0],X[pos, 1], marker='+', c='k', label = 'y = 1')
plt.scatter(X[not_pos, 0], X[not_pos, 1], marker='o', c='y', label = 'y = 0')
plt.legend()
plt.xlabel('Microchip test 1')
plt.ylabel('Microchip test 2')

# Adding new attributes
poly = PolynomialFeatures(6)
X_pol = poly.fit_transform(X)
n = np.shape(X_pol)[1]

theta = np.zeros((n,))
lamb_arr = [0,0.3,1,3,10,30]
# Compute the optimal value for theta using various values of lambda
for lamb in lamb_arr:
    # Compute the best value for theta
    result = opt.fmin_tnc(func=coste_reg, x0=theta, fprime=gradiente_reg,
                                        args=(X_pol,Y,lamb), messages = 0)
    theta_opt = result[0]
    # Display the border
    pinta_frontera_curva(X_pol[:,1:], Y, theta_opt,poly,lamb)
    # Display the accuracy of the model
    p = precision(theta_opt,X_pol,Y)
    print('With lambda = ' + str(lamb) + ' we get an accuracy of {0:1.3g}'.format(p))


# -*- coding: utf-8 -*-
"""
Created on Mon Dec 11 20:02:37 2017

@author: Anastasios Tzavellas
"""

import numpy as np
import scipy.integrate as integrate

def df(x, xValues, dValues):
    """Derivative using partial differences
    second order"""
    h = xValues[1] - xValues[0]
    theta = (x - xValues[0]) / h
    return (dValues[1] + 0.5 * (2*theta - 1)*dValues[2]) / h

def d2f(x, xValues, dValues):
    """Second Derivative using partial differences
    second order"""
    h = xValues[1] - xValues[0]
    return dValues[2] / np.power(h, 2)

def differenceTable(fxValues):
    """Calculates difference table and returns its
    top diagonal"""
    n = fxValues.size
    dValues = np.copy(fxValues)
    for i in range(1, n):
        for j in reversed(range(i, n)):
            dValues[j] = dValues[j] - dValues[j-1]
    return dValues

def dividedDifferenceTable(xValues, fxValues):
    """Calculates divided difference table and returs its
    top diagonal"""
    n = fxValues.size
    dValues = np.copy(fxValues)
    for i in range(1, n):
        for j in reversed(range(i, n)):
            dValues[j] = (dValues[j] - dValues[j-1]) / \
                         (xValues[j] - xValues[j-i])
    return dValues

def addCoefficient(xValues, fxValues, coefficients):
    """Adds extra Newton Polynomial Coefficient"""
    n = coefficients.size
    x = xValues[n]  # Get x of new point to interpolate
    y_x = fxValues[n]  # Get y of new point to interpolate
    p_x = NestedMultiplication(x, xValues, coefficients)  # evaluate p(x)
    product = 1.
    for i in range(n):
        product = product * (x - xValues[i])  # (x-x0)...(x-x_n-1)
    newCoefficient = (y_x - p_x) / product  # new coefficient
    return np.append(coefficients, newCoefficient)

def NestedMultiplication(x, xValues, coeff):
    """Evaluates Newton Polynomial at x in nested form
    given the interpolating points and its coefficents"""
    n = coeff.size
    y = coeff[n-1]
    for i in reversed(range(n - 1)):
        y = coeff[i] + (x - xValues[i]) * y
    return y

def lSquaresMonomial(f, order, a, b):
    """Solves least squares with monomial polynomials"""
    n = order + 1
    S = np.zeros((n, n))
    for i in range(n):
        for j in range(n):
            S[i, j] = integrate.quad(lambda x: np.power(x, i+j),
             a,
             b)[0]
    y = np.zeros(n)
    for i in range(n):
        y[i] = integrate.quad(lambda x: f(x) * np.power(x, i),
         a,
         b)[0]
        # Round to 0 if less than 1e-8
        y[i] = 0. if np.isclose(y[i], 0., atol=1e-8) else y[i]
    p = np.linalg.solve(S, y)
    return np.poly1d(np.flip(p, 0))

def lSquaresOrthogonal(f, w, phi, order, a, b):
    """Solves least squares system using orthogonal
    polynomials with weight w(x) and base phi(x)"""
    c = np.zeros(order)
    for i in range(order):
        c[i] = integrate.quad(lambda x: np.power(phi(x, i), 2),
         a,
         b)[0]
    A = np.zeros(order)
    for i in range(order):
        A[i] = integrate.quad(lambda x: f(x) * phi(x, i),
         a,
         b)[0] / c[i]
    legpoly = np.polynomial.legendre.leg2poly(A)
    return np.poly1d(np.flip(legpoly, 0))


'''This program simulates Sell off model described in "Mathematical modeling of
	allegiance to political parties", Iacopo Poli, 2015

    Copyright (C) 2015 - Iacopo Poli

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.'''

import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint
from paperlib import randomic
plt.ion()

N = 1000 # population sample
density = 35.5 # density per squared km of population
influence_area= 100 # squared kilometer of efficacy

#defining random constants

#fidelity rates
a1 = randomic(N, density, influence_area)
a2 = randomic(N, density, influence_area)
#rates of return
b1 = randomic(N, density, influence_area)
b2 = randomic(N, density, influence_area)
#rates of exchange
s1 = randomic(N, density, influence_area)
s2 = randomic(N, density, influence_area)

print 'a1:', a1
print 'a2:', a2
print 'b1:', b1
print 'b2:', b2
print 's1:', s1
print 's2:', s2

#defining system of equations
def f(y, t):
        S = y[0]
        L1 = y[1]
        L2 = y[2]
        # the model equations
        f0 = -a1*L1*S-a2*L2*S+b1*L1*S+b2*L2*S
        f1 = a1*L1*S-b1*L1*S+s1*L1*L2-s2*L1*L2
        f2 = a2*S*L2-b2*L2*S-s1*L1*L2+s2*L1*L2
        return [f0, f1, f2]

# initial conditions
S = N-2               # initial population
L1 = 1                  # initial loyal to party 1 population
L2 = 1                  # initial loyal to party 2 population
y0 = [S, L1, L2]       # initial condition vector
t  = np.linspace(0, 3., 100)   # time grid in months

# solve the ODEs
soln = odeint(f, y0, t)
S = soln[:, 0]
L1 = soln[:, 1]
L2 = soln[:, 2]

# plot results
plt.figure()
plt.plot(t, S, label='Susceptibles')
plt.plot(t, L1, label='Loyal 1')
plt.plot(t, L2, label='Loyal 2')
plt.xlabel('Months from start of the campaign')
plt.ylabel('Population')
plt.title('Campaign - No Init. Pop. of Loyals')
plt.legend(loc=0)
plt.pause(120)


import numpy as np

from ..validation import check_array, check_consistent_length

def mean_absolute_error(y_true, y_pred):
    """
    Returns the mean absolute error.

    Parameters
    ----------
    y_true : array_like
        The correct targets.
    y_pred : array_like
        The estimated targets.

    Returns
    -------
    float
        The mean absolute error.
    """
    y_true, y_pred = _validate_targets(y_true, y_pred)
    return np.average(np.abs(y_true - y_pred))

def median_absolute_error(y_true, y_pred):
    """
    Returns the median absolute error.

    Parameters
    ----------
    y_true : array_like
        The correct targets.
    y_pred : array_like
        The estimated targets.

    Returns
    -------
    float
        The median absolute error.
    """
    y_true, y_pred = _validate_targets(y_true, y_pred)
    return np.median(np.abs(y_true - y_pred))

def mean_squared_error(y_true, y_pred, root=False):
    """
    Returns the mean squared error (MSE) if root = False, else
    it returns the root mean squared error (RMSE)

    Parameters
    ----------
    y_true : array_like
        The correct targets.
    y_pred : array_like
        The estimated targets.
    root : bool, default=False
        If True, then the root mean squared error will be returned.

    Returns
    -------
    mse
        The (root) mean squared error.
    """
    y_true, y_pred = _validate_targets(y_true, y_pred)
    mse = np.average((y_true - y_pred) ** 2)
    if root:
        return np.sqrt(mse)
    return mse

def max_error(y_true, y_pred):
    """
    Returns the max absolute error

    Parameters
    ----------
    y_true : array_like
        The correct targets.
    y_pred : array_like
        The estimated targets.

    Returns
    -------
    float
        The max absolute error.
    """
    y_true, y_pred = _validate_targets(y_true, y_pred)
    return np.max(np.abs(y_true - y_pred))

def r2_score(y_true, y_pred):
    """
    Returns the :math:`R^2` score.
    Worst score is 0 and best possible score is 1.

    Parameters
    ----------
    y_true : array_like
        The correct targets.
    y_pred : array_like
        The estimated targets.

    Returns
    -------
    float
        The r2 score.

    Notes
    -----
    As the r-squared measure increases artificially with the number of
    regressors, it is important to compare it with the adjusted r-squared,
    which eliminates this problem by scaling it with a factor
    that is anti-proportionally to the number of regressors in the model.
    If the adjusted r-squared is significantly lower than the r-squared
    the model may have too many regressors which do not have a significant
    impact on the models predictions. Therefore, in a "good" model,
    both measures are close to each other.
    """
    y_true, y_pred = _validate_targets(y_true, y_pred)
    residual_sum_of_squares = np.sum((y_true - y_pred) ** 2)
    total_sum_of_squares = np.sum((y_true - np.average(y_true) ** 2))
    return 1 - residual_sum_of_squares / total_sum_of_squares

def adjusted_r2_score(y_true, y_pred, n_features):
    """
    Returns the :math:`R^2` score.

    The higher the value, the better. Because of the scaling factor, unlike
    the R2_score, this measure is *not* in the interval [0, 1].

    Parameters
    ----------
    y_true : array_like
        The correct targets.
    y_pred : array_like
        The estimated targets.
    n_features : int
        The number of features in the model.

    Returns
    -------
    float
        The adjusted r2 score.
    """
    y_true, y_pred = _validate_targets(y_true, y_pred)
    n_samples: int = len(y_true)
    return 1 - ((n_samples - 1) / (n_samples - n_features)) * (
        1 - r2_score(y_true, y_pred)
    )

def _validate_targets(y_true, y_pred):
    # TODO: Does not raise an error, if targets are not 1D, but it should
    y_true = check_array(y_true, ensure_2d=False)
    y_pred = check_array(y_pred, ensure_2d=False)
    check_consistent_length(y_true, y_pred)
    return y_true, y_pred


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

"""
Plot L1-norm
"""

import numpy as np
import matplotlib.pyplot as plt

from mpl_toolkits.mplot3d import axes3d
from matplotlib import cm

# Build datas ###############

x = np.arange(-5, 5, 0.1)
y = np.arange(-5, 5, 0.1)

xx,yy = np.meshgrid(x, y)
z = abs(xx) + abs(yy)

# Plot data #################

fig = plt.figure()

#ax = axes3d.Axes3D(fig)
#ax.plot_wireframe(xx, yy, z)

ax = fig.gca(projection='3d')

ax.plot_surface(xx, yy, z, rstride=5, cstride=5, alpha=0.3)
cset = ax.contourf(xx, yy, z, zdir='z', offset=0, cmap=cm.coolwarm)

ax.set_xlabel(r'$x_1$')
ax.set_ylabel(r'$x_2$')
ax.set_zlabel(r'$||x||_{1}$')

# SAVE FILES ######################
plt.savefig("norm_l1.png")

plt.show()


# coding: utf-8

#11,12,13
import numpy as np
from graphviz import Graph
import matplotlib.pyplot as plt

train_data = np.loadtxt('hw3_train.dat.txt')
test_data = np.loadtxt('hw3_test.dat.txt')
train_x = train_data[:,0:-1]
train_y = train_data[:,-1]
test_x = test_data[:,0:-1]
test_y = test_data[:,-1]

class node():
    def __init__(self,threshold,index,value=None):
        self.threshold = threshold
        self.index = index
        self.value = value
        self.leftNode = None
        self.rightNode = None

def gini_index(Y):
    length = len(Y)
    if length == 0:
        return 1
    else:
        return 1 - (np.sum(Y==1)/length)**2 - (np.sum(Y==-1)/length)**2

def decision_stump(train_x,train_y):
    error = train_y.shape[0]
    for i in range(len(train_x[0])):
        sort_array = np.sort(train_x[:,i])
        sort_array2 = []
        sort_array2 = np.append(sort_array[1:],0)
        threshold=[]
        threshold = np.append(-1*float("inf"),((sort_array + sort_array2)/2)[:-1])
        threshold = np.append(threshold,float("inf"))
        for theta in threshold:
            Y1 = train_y[train_x[:,i] < theta]
            Y2 = train_y[train_x[:,i] >= theta]
            impurity = Y1.shape[0]*gini_index(Y1)+Y2.shape[0]*gini_index(Y2)
            if error > impurity:
                error = impurity
                s = theta
                feature_index = i
    return (feature_index,s)

def stop_cut(X, Y):
    if gini_index(Y)==0 or X.shape[0]==1 or np.sum(X!=X[0])==0:
        return True
    else:
        return False

def decision_tree(train_x,train_y):
    if stop_cut(train_x,train_y):
        tree_node = node(None,None,train_y[0])
        return tree_node
    feature_index,s = decision_stump(train_x,train_y)
    pos1 = train_x[:,feature_index] < s
    pos2 = train_x[:,feature_index] >= s
    left_train_x = train_x[pos1,:]
    left_train_y = train_y[pos1]
    right_train_x = train_x[pos2,:]
    right_train_y = train_y[pos2]
    tree_node = node(s,feature_index)
    tree_node.leftNode = decision_tree(left_train_x,left_train_y)
    tree_node.rightNode = decision_tree(right_train_x,right_train_y)
    return tree_node

def draw_tree(root,node,leaf):
    if root.value ==None:
        node += 1
        graph.node(name='%d' %(node),label="%d;%5f" %(root.index+1,root.threshold))
        if root.leftNode != None:
            node,leaf = draw_tree(root.leftNode,node,leaf)
        if root.rightNode != None:
            node,leaf = draw_tree(root.rightNode,node,leaf)
    else:
        leaf += 1
        graph.node(name='l%d' %leaf,label="class = %d" %root.value)
    return node,leaf

def predict_tree(root,X):
    if root.value != None:
        return root.value
    threshold = root.threshold; index = root.index
    if X[index] < threshold:
        return predict_tree(root.leftNode,X)
    else:
        return predict_tree(root.rightNode,X)

def error(train_x,train_y,tree):
    predict = np.zeros(train_y.shape)
    for num in range(train_x.shape[0]):
        predict[num] = predict_tree(tree, train_x[num,:])
    return predict

tree = decision_tree(train_x,train_y)
graph = Graph('tree', format='svg')
internal_num,leaf = draw_tree(tree,0,0)

graph.edge('1','2')
graph.edge('2','3')
graph.edge('3','l1')
graph.edge('3','l2')
graph.edge('2','4')
graph.edge('4','5')
graph.edge('5','6')
graph.edge('6','l3')
graph.edge('6','l4')
graph.edge('5','7')
graph.edge('7','l5')
graph.edge('7','l6')
graph.edge('4','8')
graph.edge('8','9')
graph.edge('9','l7')
graph.edge('9','l8')
graph.edge('8','l9')
graph.edge('1','10')
graph.edge('10','l10')
graph.edge('10','l11')

predict_y = error(train_x,train_y,tree)
E_in = np.sum(predict_y!=train_y)/train_y.shape[0]
print("E in = %f" %E_in)
predict_y = error(test_x,test_y,tree)
E_out = np.sum(predict_y!=test_y)/test_y.shape[0]
print("E out = %f" %E_out)

#13
def purning_tree(X,Y,root,set_height,start_height,Error):
    if root.value == None and start_height < set_height:
        start_height += 1
        threshold = root.threshold; index = root.index
        X_left = X[X[:,index] < threshold,:]
        Y_left = Y[X[:,index] < threshold]
        X_right = X[X[:,index] >= threshold,:]
        Y_right = Y[X[:,index] >= threshold]
        Error = purning_tree(X_left,Y_left,root.leftNode,set_height,start_height,Error)
        Error = purning_tree(X_right,Y_right,root.rightNode,set_height,start_height,Error)
    elif root.value == None and start_height == set_height:
        if np.sum(Y) >= 0:
            error = np.sum(np.ones(np.shape(Y)) != Y)
            #print(error)
            return Error+error
        else:
            error = np.sum(-1*np.ones(np.shape(Y)) != Y)
            #print(error)
            return Error+error
    elif root.value != None and start_height <= set_height:
        error = np.sum(root.value * np.ones(np.shape(Y)) != Y)
        #print(error)
        return Error+error
    return Error

def purning_error(total_height,X,Y):
    error = []
    for height in range(total_height):
        error.append(purning_tree(X,Y,tree,height,0,0))
    Error = np.array(error)/Y.shape[0]
    return Error

total_height = 6
E_in = purning_error(total_height,train_x,train_y)
E_out = purning_error(total_height,test_x,test_y)

plt.plot(E_in,color='purple',linewidth=1,linestyle='-',label="E in")
plt.plot(E_out,color='red',linewidth=1,linestyle='-',label="E out")
plt.xlabel('Height')
plt.ylabel('Error')
plt.legend(loc='upper right')
plt.savefig('Q13.jpg')
plt.show()

#14,15
def bagging(X,Y,rate):
    pos = np.random.randint(0, X.shape[0],size = int(X.shape[0]*rate))
    return X[pos, :], Y[pos]

def random_forest(X, Y,iteration,rate):
    Treenode = []
    for i in range(iteration):
        bag_train_x, bag_train_y = bagging(X, Y,rate)
        node = decision_tree(bag_train_x, bag_train_y)
        Treenode.append(node)
    return Treenode

T = 30000
E_in = []
G_E_in = []
Gt = []
G_E_out = []
Gt_out = []
#build tree
treeArray = random_forest(train_x, train_y,T,0.8)

for i in range(T):
    predict_y = error(train_x,train_y,treeArray[i])
    G_predict_y = np.reshape(error(train_x,train_y,treeArray[i]),(-1,1))
    G_out_predict_y = np.reshape(error(test_x,test_y,treeArray[i]),(-1,1))
    if i==0:
        Gt = G_predict_y
        Gt_out = G_out_predict_y
    else:
        Gt = np.concatenate((Gt,G_predict_y),axis = 1)
        Gt_out = np.concatenate((Gt_out,G_out_predict_y),axis = 1)
    E_in.append(np.sum(predict_y!=train_y)/train_y.shape[0])
    G_E_in.append(np.sum(np.sign(np.sum(Gt,axis=1)) != train_y)/train_y.shape[0])
    G_E_out.append(np.sum(np.sign(np.sum(Gt_out,axis=1)) != test_y)/test_y.shape[0])

plt.hist(E_in,color='green')
plt.xlabel('Ein(gt)')
plt.ylabel('Count')
plt.savefig('Q14.jpg')
plt.show()

plt.plot(G_E_in)
plt.xlabel('T')
plt.ylabel('Ein(Gt)')
plt.savefig('Q15.jpg')
plt.show()

plt.plot(G_E_out)
plt.xlabel('T')
plt.ylabel('Eout(Gt)')
plt.savefig('Q16.jpg')
plt.show()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sat Jan 23 2021

@authors: Dr. M. S. Ramkarthik and Dr. Pranay Barkataki
"""

import numpy as np
import math

class QuantumMechanics:

    def __init__(self):
        """It is a quantum mechanics class, and it primarily intrinsic
            functions of uses numpy and scipy
        """

    def inner_product(self,vec1,vec2):
        """
        Here we compute the inner product
        Attributes:
            vec1: it is a column vector.
            vec2: it is the second vector.
        Returns:
            inn: inner product between vec1 and vec2.
        """
        inn=complex(0.0,0.0)
        assert len(vec1) ==  len(vec2),\
        "Dimension of two vectors not equal to each other"
        for i in range(0,len(vec1)):
            inn=inn+(np.conjugate(vec1[i])*vec2[i])

        return inn

    def norm_vec(self,vec):
        """
        Here we calculate norm of a vector
        Attributes:
            vec: column vector
        Returns:
            norm: it contains the norm of the column vector vec
        """
        norm=0.0
        for i in range(0,len(vec)):
            norm=norm+abs(np.conjugate(vec[i])*vec[i])
        return np.sqrt(norm)

    def normalization_vec(self,vec):
        """
        Here normalize a given vector
        Attributes:
            vec: unnormalized column vector
        Returns:
            vec: normalized column vector
        """
        norm=0.0
        for i in range(0,len(vec)):
            norm=norm+abs(np.conjugate(vec[i])*vec[i])
        vec=vec/np.sqrt(norm)
        return vec

    def outer_product_rvec(self,vec1,vec2):
        """
        Here we calculate the outer product
        Attributes:
            vec1: it is a column real vector
            vec2: it is another real vector
        Returns:
            matrix: outer product of vec1 and vec2
        """
        matrix = np.zeros([len(vec1),len(vec2)],dtype='float64')
        for i in range(0,len(vec1)):
            for j in range(0,len(vec2)):
                matrix[i,j]=vec1[i]*vec2[j]
        return matrix

    def outer_product_cvec(self,vec1,vec2):
        """
        Here we calculate the outer product
        Attributes:
            vec1: it is a column complex vector
            vec2: it is another complex vector
        Returns:
            matrix: outer product of vec1 and vec2
        """
        matrix = np.zeros([len(vec1),len(vec2)],dtype=np.complex_)
        for i in range(0,len(vec1)):
            for j in range(0,len(vec2)):
                matrix[i,j]=vec1[i]*np.conjugate(vec2[j])
        return matrix

    def tensor_product_matrix(self,A,B):
        """
        Here we calculate the tensor product of two matrix A and B
        Attributes:
            A: it is either a 1D or 2D array
            B: it is either a 1D or 2D array
        Returns: tensor product of A and B
        """
        if len(A.shape)==1:
            A=A.reshape(A.shape[0],1)
        if len(B.shape)==1:
            B=B.reshape(B.shape[0],1)
        return np.kron(A,B)

    def commutation(self,A,B):
        """
        Here we calculate commutation between matrices A and B
        Attributes:
            A: it is a square matrix
            B: it is another square matrix
        Returns: AB-BA matrix
        """
        return np.matmul(A,B)-np.matmul(B,A)

    def anti_commutation(self,A,B):
        """
        Here we calculate anti commutation between matrices A and B
        Attributes:
            A: input a square matrix
            B: input another square matrix
        Returns: AB+BA matrix
        """
        return np.matmul(A,B)+np.matmul(B,A)

    def rstate_shift(self,vec,shift=1,shift_direction='right'):
        """
        Shifting of a real quantum state
        Inputs:
            vec: real quantum state.
            shift: degree of the state.
            shift_direction: It shows the direction of the shift,
                             by default it is right
        Return:
            state2: shifted state of vec
        """
        assert vec.shape[0]%2==0,"Not a qubit quantum state"
        N=int(math.log(vec.shape[0],2))
        basis=np.zeros([N],dtype=int)
        state2=np.zeros([2**N],dtype='float64')
        for i in range(0,vec.shape[0]):
            if vec[i] != 0.0:
                basis=self.decimal_binary(i,N)
                basis=self.binary_shift(basis,shift=shift,\
                                        shift_direction=shift_direction)
                j=int(self.binary_decimal(basis))
                state2[j]=vec[i]

        return state2

    def cstate_shift(self,vec,shift=1,shift_direction='right'):
        """
        Shifting of a complex quantum state
        Inputs:
            vec: complex quantum state.
            shift: degree of the state.
            shift_direction: It shows the direction of the shift, by default
                            it is right
        Return:
            state2: shifted state of vec
        """
        assert vec.shape[0]%2==0,"Not a qubit quantum state"
        N=int(math.log(vec.shape[0],2))
        basis=np.zeros([N],dtype=int)
        state2=np.zeros([2**N],dtype=np.complex_)
        for i in range(0,vec.shape[0]):
            if vec[i] != complex(0.0,0.0):
                basis=self.decimal_binary(i,N)
                basis=self.binary_shift(basis,shift=shift,\
                                        shift_direction=shift_direction)
                j=self.binary_decimal(basis)
                state2[j]=vec[i]

        return state2

    def decimal_binary(self,i,N):
        """
        Decimal to binary conversion
        Inputs:
            i: input decimal number to changed to binary number
            N: number of the binary string
        Returns:
            bnum: binary number equivalent to i, it is column matrix.
        """
        bnum=np.zeros([N],dtype=int)
        for j in range(0,N):
            bnum[bnum.shape[0]-1-j]=i%2
            i=int(i/2)
        return bnum

    def binary_decimal(self,vec):
        """
        Binary to decimal conversion
        Input:
            vec:  array containing binary matrix elements i.e. 0 or 1
        Returns:
            dec: decimal equivalent number of binary array vec
        """
        dec=0
        for i in range(0,vec.shape[0]):
            dec=dec+int((2**(vec.shape[0]-1-i))*vec[i])
        dec=int(dec)
        return dec

    def binary_shift(self,vec,shift=1,shift_direction='right'):
        """
        Shifting of string of binary number
        Input:
            vec: array containing binary matrix elements i.e. 0 or 1
            shift: degree of the shift
            shift_direction: its value is either left or right
        Output:
            shift_vec: Shifted vec
        """
        assert shift_direction == 'left' or shift_direction == 'right',\
        'Not proper shift direction'
        assert shift >= 1 and shift < vec.shape[0],\
        "degree of shift is not proper"
        shift_vec=np.zeros([vec.shape[0]],dtype='int_')
        if shift_direction =='right':
            for i in range(0,vec.shape[0]):
                ishift=i+shift
                if ishift >= vec.shape[0]:
                    ishift=ishift-vec.shape[0]
                shift_vec[ishift]=vec[i]
        if shift_direction == 'left':
            for i in range(0,vec.shape[0]):
                ishift=i-shift
                if ishift < 0:
                    ishift=ishift+vec.shape[0]
                shift_vec[ishift]=vec[i]

        return shift_vec


# =============================================================================
# Measuring the performance of a buy and hold strategy
# Author : Mayank Rasu

# Please report bug/issues in the Q&A section
# =============================================================================

# Import necesary libraries
import pandas_datareader.data as pdr
import numpy as np
import datetime

# Download historical data for required stocks
ticker = "^GSPC"
SnP = pdr.get_data_yahoo(ticker,datetime.date.today()-datetime.timedelta(1825),datetime.date.today())

def CAGR(DF):
    "function to calculate the Cumulative Annual Growth Rate of a trading strategy"
    df = DF.copy()
    df["daily_ret"] = DF["Adj Close"].pct_change()
    df["cum_return"] = (1 + df["daily_ret"]).cumprod()
    n = len(df)/252
    CAGR = (df["cum_return"][-1])**(1/n) - 1
    return CAGR

def volatility(DF):
    "function to calculate annualized volatility of a trading strategy"
    df = DF.copy()
    df["daily_ret"] = DF["Adj Close"].pct_change()
    vol = df["daily_ret"].std() * np.sqrt(252)
    return vol

def sharpe(DF,rf):
    "function to calculate sharpe ratio ; rf is the risk free rate"
    df = DF.copy()
    sr = (CAGR(df) - rf)/volatility(df)
    return sr

def sortino(DF,rf):
    "function to calculate sortino ratio ; rf is the risk free rate"
    df = DF.copy()
    df["daily_ret"] = DF["Adj Close"].pct_change()
    neg_vol = df[df["daily_ret"]<0]["daily_ret"].std() * np.sqrt(252)
    sr = (CAGR(df) - rf)/neg_vol
    return sr

def max_dd(DF):
    "function to calculate max drawdown"
    df = DF.copy()
    df["daily_ret"] = DF["Adj Close"].pct_change()
    df["cum_return"] = (1 + df["daily_ret"]).cumprod()
    df["cum_roll_max"] = df["cum_return"].cummax()
    df["drawdown"] = df["cum_roll_max"] - df["cum_return"]
    df["drawdown_pct"] = df["drawdown"]/df["cum_roll_max"]
    max_dd = df["drawdown_pct"].max()
    return max_dd

def calmar(DF):
    "function to calculate calmar ratio"
    df = DF.copy()
    clmr = CAGR(df)/max_dd(df)
    return clmr


# 9.2.2 多次元ガウス分布のEMアルゴリズム

#%%

# 9.2.2項で利用するライブラリ
import numpy as np
from scipy.stats import multivariate_normal # 多次元ガウス分布
import matplotlib.pyplot as plt

#%%

## 真の分布の設定

# 次元数を設定:(固定)
D = 2

# クラスタ数を指定
K = 3

# K個の真の平均を指定
mu_truth_kd = np.array(
    [[5.0, 35.0],
     [-20.0, -10.0],
     [30.0, -20.0]]
)

# K個の真の共分散行列を指定
sigma2_truth_kdd = np.array(
    [[[250.0, 65.0], [65.0, 270.0]],
     [[125.0, -45.0], [-45.0, 175.0]],
     [[210.0, -15.0], [-15.0, 250.0]]]
)

# 真の混合係数を指定
pi_truth_k = np.array([0.45, 0.25, 0.3])

#%%

# 作図用のx軸のxの値を作成
x_1_line = np.linspace(
    np.min(mu_truth_kd[:, 0] - 3 * np.sqrt(sigma2_truth_kdd[:, 0, 0])),
    np.max(mu_truth_kd[:, 0] + 3 * np.sqrt(sigma2_truth_kdd[:, 0, 0])),
    num=300
)

# 作図用のy軸のxの値を作成
x_2_line = np.linspace(
    np.min(mu_truth_kd[:, 1] - 3 * np.sqrt(sigma2_truth_kdd[:, 1, 1])),
    np.max(mu_truth_kd[:, 1] + 3 * np.sqrt(sigma2_truth_kdd[:, 1, 1])),
    num=300
)

# 作図用の格子状の点を作成
x_1_grid, x_2_grid = np.meshgrid(x_1_line, x_2_line)

# 作図用のxの点を作成
x_point_arr = np.stack([x_1_grid.flatten(), x_2_grid.flatten()], axis=1)

# 作図用に各次元の要素数を保存
x_dim = x_1_grid.shape
print(x_dim)

# 真の分布を計算
model_dens = 0
for k in range(K):
    # クラスタkの分布の確率密度を計算
    tmp_dens = multivariate_normal.pdf(
        x=x_point_arr, mean=mu_truth_kd[k], cov=sigma2_truth_kdd[k]
    )

    # K個の分布を線形結合
    model_dens += pi_truth_k[k] * tmp_dens

#%%

# 真の分布を作図
plt.figure(figsize=(12, 9))
plt.contour(x_1_grid, x_2_grid, model_dens.reshape(x_dim)) # 真の分布
plt.suptitle('Mixture of Gaussians', fontsize=20)
plt.title('K=' + str(K), loc='left')
plt.xlabel('$x_1$')
plt.ylabel('$x_2$')
plt.colorbar() # 等高線の色
plt.show()

#%%

# (観測)データ数を指定
N = 250

# 潜在変数を生成
z_truth_nk = np.random.multinomial(n=1, pvals=pi_truth_k, size=N)

# クラスタ番号を抽出
_, z_truth_n = np.where(z_truth_nk == 1)

# (観測)データを生成
x_nd = np.array([
    np.random.multivariate_normal(
        mean=mu_truth_kd[k], cov=sigma2_truth_kdd[k], size=1
    ).flatten() for k in z_truth_n
])

#%%

# 観測データの散布図を作成
plt.figure(figsize=(12, 9))
for k in range(K):
    k_idx, = np.where(z_truth_n == k) # クラスタkのデータのインデック
    plt.scatter(x=x_nd[k_idx, 0], y=x_nd[k_idx, 1], label='cluster:' + str(k + 1)) # 観測データ
plt.contour(x_1_grid, x_2_grid, model_dens.reshape(x_dim), linestyles='--') # 真の分布
plt.suptitle('Mixture of Gaussians', fontsize=20)
plt.title('$N=' + str(N) + ', K=' + str(K) +
          ', \pi=(' + ', '.join([str(pi) for pi in pi_truth_k]) + ')$', loc='left')
plt.xlabel('$x_1$')
plt.ylabel('$x_2$')
plt.colorbar() # 等高線の色
plt.show()

#%%

## 初期値の設定

# 平均パラメータの初期値を生成
mu_kd = np.array([
    np.random.uniform(
        low=np.min(x_nd[:, d]), high=np.max(x_nd[:, d]), size=K
    ) for d in range(D)
]).T

# 共分散行列の初期値を指定
sigma2_kdd = np.array([np.identity(D) * 1000 for _ in range(K)])

# 混合係数の初期値を生成
pi_k = np.random.rand(3)
pi_k /= np.sum(pi_k) # 正規化

# 初期値による対数尤度を計算:式(9.14)
term_dens_nk = np.array(
    [multivariate_normal.pdf(x=x_nd, mean=mu_kd[k], cov=sigma2_kdd[k]) for k in range(K)]
).T
L = np.sum(np.log(np.sum(term_dens_nk, axis=1)))

#%%

# 初期値による混合分布を計算
init_dens = 0
for k in range(K):
    # クラスタkの分布の確率密度を計算
    tmp_dens = multivariate_normal.pdf(
        x=x_point_arr, mean=mu_kd[k], cov=sigma2_kdd[k]
    )

    # K個の分布線形結合
    init_dens += pi_k[k] * tmp_dens

# 初期値による分布を作図
plt.figure(figsize=(12, 9))
plt.contour(x_1_grid, x_2_grid, model_dens.reshape(x_dim),
            alpha=0.5, linestyles='dashed') # 真の分布
#plt.contour(x_1_grid, x_2_grid, init_dens.reshape(x_dim)) # 推定値による分布:(等高線)
plt.contourf(x_1_grid, x_2_grid, init_dens.reshape(x_dim), alpha=0.6) # 推定値による分布:(塗りつぶし)
plt.suptitle('Mixture of Gaussians', fontsize=20)
plt.title('iter:' + str(0) + ', K=' + str(K), loc='left')
plt.xlabel('$x_1$')
plt.ylabel('$x_2$')
plt.colorbar() # 等高線の色
plt.show()

#%%

## 推論処理

# 試行回数を指定
MaxIter = 100

# 推移の確認用の受け皿を作成
trace_L_i = [L]
trace_gamma_ink = [np.tile(np.nan, reps=(N, K))]
trace_mu_ikd = [mu_kd.copy()]
trace_sigma2_ikdd = [sigma2_kdd.copy()]
trace_pi_ik = [pi_k.copy()]

# 最尤推定
for i in range(MaxIter):

    # 負担率を計算:式(9.13)
    gamma_nk = np.array(
        [multivariate_normal.pdf(x=x_nd, mean=mu_kd[k], cov=sigma2_kdd[k]) for k in range(K)]
    ).T
    gamma_nk /= np.sum(gamma_nk, axis=1, keepdims=True) # 正規化

    # 各クラスタとなるデータ数の期待値を計算:式(9.18)
    N_k = np.sum(gamma_nk, axis=0)

    for k in range(K):
        # 平均パラメータの最尤解を計算:式(9.17)
        mu_kd[k] = np.dot(gamma_nk[:, k], x_nd) / N_k[k]

        # 共分散行列の最尤解を計算:式(9.19)
        term_x_nd = x_nd - mu_kd[k]
        sigma2_kdd[k] = np.dot(gamma_nk[:, k] * term_x_nd.T, term_x_nd) / N_k[k]

    # 混合係数の最尤解を計算:式(9.22)
    pi_k = N_k / N

    # 対数尤度を計算:式(9.14)
    tmp_dens_nk = np.array(
        [multivariate_normal.pdf(x=x_nd, mean=mu_kd[k], cov=sigma2_kdd[k]) for k in range(K)]
    ).T
    L = np.sum(np.log(np.sum(tmp_dens_nk, axis=1)))

    # i回目の結果を記録
    trace_L_i.append(L)
    trace_gamma_ink.append(gamma_nk.copy())
    trace_mu_ikd.append(mu_kd.copy())
    trace_sigma2_ikdd.append(sigma2_kdd.copy())
    trace_pi_ik.append(pi_k.copy())

    # 動作確認
    print(str(i + 1) + ' (' + str(np.round((i + 1) / MaxIter * 100, 1)) + '%)')

#%%

## 推論結果の確認

# K個のカラーマップを指定
colormap_list = ['Blues', 'Oranges', 'Greens']

# 負担率が最大のクラスタ番号を抽出
z_n = np.argmax(gamma_nk, axis=1)

# 割り当てられたクラスタとなる負担率(確率)を抽出
prob_z_n = gamma_nk[np.arange(N), z_n]

# 最後の更新値による混合分布を計算
res_dens = 0
for k in range(K):
    # クラスタkの分布の確率密度を計算
    tmp_dens = multivariate_normal.pdf(
        x=x_point_arr, mean=mu_kd[k], cov=sigma2_kdd[k]
    )

    # K個の分布を線形結合
    res_dens += pi_k[k] * tmp_dens

# 最後の更新値による分布を作図
plt.figure(figsize=(12, 9))
plt.contour(x_1_grid, x_2_grid, model_dens.reshape(x_dim),
            alpha=0.5, linestyles='dashed') # 真の分布
plt.scatter(x=mu_truth_kd[:, 0], y=mu_truth_kd[:, 1],
            color='red', s=100, marker='x') # 真の平均
plt.contourf(x_1_grid, x_2_grid, res_dens.reshape(x_dim), alpha=0.5) # 推定値による分布:(塗りつぶし)
for k in range(K):
    k_idx, = np.where(z_n == k) # クラスタkのデータのインデックス
    cm = plt.get_cmap(colormap_list[k]) # クラスタkのカラーマップを設定
    plt.scatter(x=x_nd[k_idx, 0], y=x_nd[k_idx, 1],
                c=[cm(p) for p in prob_z_n[k_idx]], label='cluster:' + str(k + 1)) # 負担率によるクラスタ
#plt.contour(x_1_grid, x_2_grid, res_dens.reshape(x_dim)) # 推定値による分布:(等高線)
#plt.colorbar() # 等高線の値:(等高線用)
plt.suptitle('Mixture of Gaussians:Maximum Likelihood', fontsize=20)
plt.title('$iter:' + str(MaxIter) +
          ', L=' + str(np.round(L, 1)) +
          ', N=' + str(N) +
          ', \pi=[' + ', '.join([str(pi) for pi in np.round(pi_k, 3)]) + ']$',
          loc='left')
plt.xlabel('$x_1$')
plt.ylabel('$x_2$')
plt.legend()
plt.show()

#%%

## 対数尤度の推移を作図
plt.figure(figsize=(12, 9))
plt.plot(np.arange(MaxIter + 1), np.array(trace_L_i))
plt.xlabel('iteration')
plt.ylabel('value')
plt.suptitle('Maximum Likelihood', fontsize=20)
plt.title('Log Likelihood', loc='left')
plt.grid() # グリッド線
plt.show()

#%%

## パラメータの更新値の推移の確認

# muの推移を作図
plt.figure(figsize=(12, 9))
plt.hlines(y=mu_truth_kd, xmin=0, xmax=MaxIter + 1, label='true val',
           color='red', linestyles='--') # 真の値
for k in range(K):
    for d in range(D):
        plt.plot(np.arange(MaxIter+1), np.array(trace_mu_ikd)[:, k, d],
                 label='k=' + str(k + 1) + ', d=' + str(d + 1))
plt.xlabel('iteration')
plt.ylabel('value')
plt.suptitle('Maximum Likelihood', fontsize=20)
plt.title('$\mu$', loc='left')
plt.legend() # 凡例
plt.grid() # グリッド線
plt.show()

#%%

# lambdaの推移を作図
plt.figure(figsize=(12, 9))
plt.hlines(y=sigma2_truth_kdd, xmin=0, xmax=MaxIter + 1, label='true val',
           color='red', linestyles='--') # 真の値
for k in range(K):
    for d1 in range(D):
        for d2 in range(D):
            plt.plot(np.arange(MaxIter + 1), np.array(trace_sigma2_ikdd)[:, k, d1, d2],
                     alpha=0.5, label='k=' + str(k + 1) + ', d=' + str(d1 + 1) + ', d''=' + str(d2 + 1))
plt.xlabel('iteration')
plt.ylabel('value')
plt.suptitle('Maximum Likelihood', fontsize=20)
plt.title('$\Sigma$', loc='left')
plt.legend() # 凡例
plt.grid() # グリッド線
plt.show()

#%%

# piの推移を作図
plt.figure(figsize=(12, 9))
plt.hlines(y=pi_truth_k, xmin=0, xmax=MaxIter + 1, label='true val',
           color='red', linestyles='--') # 真の値
for k in range(K):
    plt.plot(np.arange(MaxIter + 1), np.array(trace_pi_ik)[:, k], label='k=' + str(k + 1))
plt.xlabel('iteration')
plt.ylabel('value')
plt.suptitle('Maximum Likelihood', fontsize=20)
plt.title('$\pi$', loc='left')
plt.legend() # 凡例
plt.grid() # グリッド線
plt.show()

#%%

## アニメーションによる確認

# 追加ライブラリ
import matplotlib.animation as animation

#%%

## 分布の推移の確認

# K個のカラーマップを指定
colormap_list = ['Blues', 'Oranges', 'Greens']

# 画像サイズを指定
fig = plt.figure(figsize=(9, 9))

# 作図処理を関数として定義
def update_model(i):
    # i回目の更新値による混合分布を計算
    res_dens = 0
    for k in range(K):
        # クラスタkの分布の確率密度を計算
        tmp_dens = multivariate_normal.pdf(
            x=x_point_arr, mean=trace_mu_ikd[i][k], cov=trace_sigma2_ikdd[i][k]
        )

        # K個の分布を線形結合
        res_dens += trace_pi_ik[i][k] * tmp_dens

    # 前フレームのグラフを初期化
    plt.cla()

    # i回目の負担率を取得
    gamma_nk = trace_gamma_ink[i]

    # i回目のサンプルによる分布を作図
    plt.contour(x_1_grid, x_2_grid, model_dens.reshape(x_dim),
                alpha=0.5, linestyles='dashed') # 真の分布
    #plt.contour(x_1_grid, x_2_grid, res_dens.reshape(x_dim)) # 推定値による分布:(等高線)
    plt.contourf(x_1_grid, x_2_grid, res_dens.reshape(x_dim), alpha=0.5) # 推定値による分布:(塗りつぶし)
    plt.scatter(x=mu_truth_kd[:, 0], y=mu_truth_kd[:, 1],
                color='red', s=100, marker='x') # 真の平均
    for k in range(K):
        k_idx, = np.where(np.argmax(gamma_nk, axis=1) == k)
        cm = plt.get_cmap(colormap_list[k]) # クラスタkのカラーマップを設定
        plt.scatter(x=x_nd[k_idx, 0], y=x_nd[k_idx, 1],
                    c=[cm(p) for p in gamma_nk[k_idx, k]], label='cluster:' + str(k + 1)) # 負担率によるクラスタ
        plt.suptitle('Mixture of Gaussians:Maximum Likelihood', fontsize=20)
    plt.title('$iter:' + str(i) +
              ', L=' + str(np.round(trace_L_i[i], 1)) +
              ', N=' + str(N) +
              ', \pi=(' + ', '.join([str(pi) for pi in np.round(trace_pi_ik[i], 3)]) + ')$',
              loc='left')
    plt.xlabel('$x_1$')
    plt.ylabel('$x_2$')
    plt.legend()

# gif画像を作成
model_anime = animation.FuncAnimation(fig, update_model, frames=MaxIter + 1, interval=100)
model_anime.save("ch9_2_2_Model.gif")

#%%

print('end')