import matplotlib.pylab as pl
import random
import math
import numpy as np

"""
NumRep Checkpoint 1: MonteCarlo simulations of Muon decay
Simon McLaren
"""

# Returns a value for the muon lifetime probability density at a specified time
def evaluateMuonDistribution(time, tau):
    return float((1/tau)*math.exp(-(time/tau)))

muonLifetime = 2.2 # in microseconds
numberofsimulateddecays = 1000
numberofsimulatedlifetimes = 500
totalMuonLifetimes = []
# Parameters for sampling a random number from the distribution
fmax = evaluateMuonDistribution(0, muonLifetime) # Max value of e^-x function is at 0 for the range 0 to inf
mint = 0.
maxt = 22.

# Simulate 500 muon lifetimes
for j in range(numberofsimulatedlifetimes):
    listOfDecayTimes = []
    # Simulate 1000 muon decay times
    for i in range(numberofsimulateddecays):
        # Draw a random time from the distribution using the box method
        y2 = 10.
        y1 = 1.
        while y2 > y1:
            randomt = mint + (maxt - mint) * random.random()
            y1 = evaluateMuonDistribution(randomt, muonLifetime)
            y2 = fmax * random.random()
        listOfDecayTimes.append(randomt)

    estimatedMuonLifetime = (sum(listOfDecayTimes)/len(listOfDecayTimes))
    totalMuonLifetimes.append(estimatedMuonLifetime)

# Plot histogram
pl.hist(totalMuonLifetimes[:], bins = 100, range = [1,3])
pl.xlabel("Estimated Muon Lifetime (microseconds)")
pl.title("Hisogram of Muon Lifetimes")
pl.savefig("muonlifetimehistogram.png")

totalMuonLifetimes = np.array(totalMuonLifetimes)

# Print results
simulationMuonLifetime = np.mean(totalMuonLifetimes) # Estimated muon lifetime from the simulation
standardDeviationMuonLifetime = np.std(totalMuonLifetimes) # Standard deviation of the data
print "The estimated muon lifetime is %f microseconds." %(simulationMuonLifetime)
print "The standard deviation of the 500 muon lifetimes is %f." %(standardDeviationMuonLifetime)
print "The standard error of the mean is %f." %(standardDeviationMuonLifetime/math.sqrt(numberofsimulatedlifetimes))


import numpy as np
from numpy import floor
import matplotlib.pyplot as plt
class CubicHermite():
    def __init__(self,xi,yi):
        '''
        inited by control points
        :param xi:
        :param yi:
        '''
        self.xi=xi
        self.yi=yi
        assert len(xi)==len(yi)
        self.num = len(xi)

    def _get_arr(self,idx):
        if idx<0:
            return self.yi[0]
        elif idx>=len(self.yi):
            return self.yi[-1]
        else:
            return self.yi[idx]
    def _CubicHermite(self,A,B,C,D,t):
        '''

        '''
        a = -0.5*A  + 1.5* B - 1.5 * C +0.5* D
        b = A -2.5*B + 2*C - 0.5*D
        c = -0.5*A  + 0.5*C
        d = B
        return a * t * t * t + b * t * t + c * t + d

    def _fit(self,x,i):

        idx=0
        if x<=self.xi[0]:
            idx=0
        elif x>=self.xi[-1]:
            idx=len(self.xi)-1
        else:
            while idx< len(self.yi)-1:
                if x>=self.xi[idx] and x<self.xi[idx+1]:
                    break
                idx+=1

        x = i*self.step
        t = x - floor(x)
        A = self._get_arr(idx - 1)
        B = self._get_arr(idx + 0)
        C = self._get_arr(idx + 1)
        D = self._get_arr(idx + 2)
        y = self._CubicHermite(A, B, C, D, t)
        return y

    def __call__(self, xs):
        ys = []
        i=0
        self.num_samples = len(xs)
        self.step = (self.num-1)/(self.num_samples-1)
        for x in xs:
            ys.append(self._fit(x,i))
            i+=1

        return np.array(ys)

if __name__ == '__main__':
    xi = np.array([0, 50, 100, 150, 200])
    yi = np.array([0, -100, 0, -50, 0])
    interp_hermite = CubicHermite(xi,yi)

    x=np.linspace(0,200,29)
    #print(x)
    y = interp_hermite(x)
    print(y)

    plt.plot(x,y,'b-*')
    plt.plot(xi,yi,'ro')

    plt.show()


import numpy as np
import pytest

import skbot.trajectory as trj

@pytest.mark.parametrize(
    ["t", "f", "expected"],
    [
        [np.linspace(0, 5, 200), lambda x: x, 0.5 * 5 ** 2],
        [np.linspace(0, 5, 500), lambda x: x ** 2, 1 / 3 * 5 ** 3],
        [np.linspace(0, 0, 200), lambda x: x, 0],
    ],
)
def test_integration(t, f, expected):
    f_val = f(t)
    estimate = trj.utils.integral(f_val, t)
    assert np.isclose(estimate, expected)

def test_cumulative_integration():
    t = np.linspace(0, 5, 200)
    f = lambda x: x
    f_val = f(t)
    estimate = trj.utils.cumulative_integral(f_val, t)
    expected = 0.5 * t ** 2
    assert np.allclose(estimate, expected)


from sympy import Symbol, Derivative
import numpy as np

#
#
#  Trabalho2:Parte 3 -Encontra uma raiz aproximada para os sistemas de equações abaixo,usando o metodo de Newton para sistemas não lineares.
#
#  F(x1,x2) = 6x1^3 + x1*x2 - 3x2^3 - 4 = 0
#  F(x1,x2) = x2^2 - 18x1*x2^2 + 16x2^3 + 1 = 0
#
#
#
#
#
#
#  Autor       : Marlon Brendo Ramos
#  Instituição : Universidade Federal de Uberlândia
#  Curso       : Sistemas de Informação
#  Disciplina  : Métodos Numéricos
#  Tecnologias : Python3.7
#
#  --------------------------------------------------------------------------------------------------------------------------
#  Esse programa permite a resolução do sistema de equações não lineares abaixo,usando o método de Newton
#
#  F(x1,x2) = 6x1^3 + x1*x2 - 3x2^3 - 4 = 0
#  F(x1,x2) = x2^2 - 18x1*x2^2 + 16x2^3 + 1 = 0
#
#  O programa imprime todas as iterações na saída padrão e seus respectivos valores de k , x^k+1 e || x^k+1 - x^k || ,
#  considerando a precisão de e=10^-3 ou 0.001.
#
#
#
#  Exemplo de saída:
#
#--------Iteracao k ------------- 0
#
#   Xk =  [2, 2]
#   X^k+1 =  [1.36864407 1.33474576]
#   || x^k+1 - x^k || =  0.6652542372881356
#
#----------------------------------
#...
#
#

#Calcula  a matriz jacobiana, utilizando
#derivadas parciais em relaçao a x e y e monta
# uma matriz com as derivadas
def calculaMatrizjacobiana():

    f1 = '6*x**3 + x*y - 3*y**3 -4'
    f2 = 'x**2 - 18*x*y**2 + 16*y**3 +1'
    x1= Symbol('x')
    x2= Symbol('y')

    matrizjacobiana = np.zeros((2,2))
    matrizjacobiana=[

                      [ str( Derivative.doit(Derivative(f1, x1))),   str( Derivative.doit(Derivative(f1, x2)) ) ],
                      [ str( Derivative.doit(Derivative(f2, x1)) ) , str( Derivative.doit(Derivative(f2, x2)) ) ]

    ]

    return matrizjacobiana

#Define J(x^k) iniciada por valores de  xk
#tal que x0[2,2]
def defineJxk(xk,matrizjacobiana):

    x=xk[0]
    y=xk[1]
    Fxk=[["",""],["",""]]
    for i in range(2):
        for j in range(2):
                Fxk[i][j]=eval(matrizjacobiana[i][j])

    return Fxk

#Define -F(x^k).Calcula-se o valor  das funções no xk
def defineFxk(xk):
    return [
            ( 6*xk[0]**3 + xk[0]*xk[1] - 3*xk[1]**3 -4)      * -1,
            (xk[0]**2 - 18*xk[0]*xk[1]**2 + 16*xk[1]**3 +1 ) * -1
    ]

if "__main__" == __name__:

    norma=1
    k=0
    xk=[2,2] #inicia os vetores x0 como [2,2]

    matrizjacobiana=calculaMatrizjacobiana()

    while norma > 0.001:

        Jxk=defineJxk(xk,matrizjacobiana)
        Fxk=defineFxk(xk)

        #Buscando a inversa das matrizes para achar as soluções
        inv=np.linalg.inv(Jxk) #Aplica a inversa da matriz jacobiana
        sk=np.dot(inv,Fxk) #Encontra as soluções x1,x2 para o sistema
        xkprox=xk + sk #Soma os vetores de X^k + S^k
        norma=max(abs(xkprox-xk)) #Encontra a norma fazendo || x^k+1 - x^k ||

        print("--------Iteracao k = {:d}-------------".format(k))
        print("\nXk = ",xk)
        print("X^k+1 = ",xkprox)
        print("|| x^k+1 - x^k || = ",norma)
        print("\n")
        xk=xkprox #atribui o vetor anterior como sendo o próximo
        k+=1


# --------------
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# Load the dataframe
df = pd.read_csv(path)

#Code starts here
#Task 1
p_a=df[df['fico']>700].shape[0]/df.shape[0]
print("Probability of fico credit score greater than 700 is ",p_a)

p_b=df[df['purpose']=='debt_consolidation'].shape[0]/df.shape[0]
print("Probability of purpose equal to debt_consolidation is ",p_b)

df1=df[df['purpose']=='debt_consolidation']
# df1.head()
p_a_b=df1[df1['fico']>700].shape[0]/df1.shape[0]
print("probablityp(B|A)",p_a_b)

result= (p_a_b==p_a)
print(result)

#Task 2
prob_lp=df[df['paid.back.loan']=='Yes'].shape[0]/df.shape[0]
print(prob_lp)

prob_cs=df[df['credit.policy']=='Yes'].shape[0]/df.shape[0]
print(prob_cs)

new_df=df[df['paid.back.loan']=='Yes']
# new_df.head()

prob_pd_cs=new_df[new_df['credit.policy']=='Yes'].shape[0]/new_df.shape[0]
print(prob_pd_cs)

bayes=prob_pd_cs*prob_lp/prob_cs
print("value of bayes is",round(bayes,4))

#Task 3
pur=df['purpose'].value_counts(normalize=True)
pur.plot(kind='bar')
plt.xlabel("purpose")
plt.ylabel("P(purl)")
plt.show()

df1=df[df['paid.back.loan'] == 'No']
# df1.head()

purpl=df1['purpose'].value_counts(normalize=True)
purpl.plot(kind='bar')
plt.xlabel("purpose where paid.back.loan is No")
plt.ylabel("p(purpl)")
plt.show()

#Task 4
inst_median=df['installment'].median()
print(inst_median)

inst_mean=df['installment'].mean()
print(inst_mean)

df['installment'].hist(normed=True,bins=50)
plt.axvline(x=inst_median,color='r')
plt.axvline(x=inst_mean,color='g')
plt.xlabel("installment")
plt.ylabel("Probability")
plt.show()

df['log.annual.inc'].hist(normed=True,bins=50)
plt.xlabel("log.annual.inc")
plt.ylabel("Probability")
plt.show()


"""
This module implements various modules of the network.
You should fill in code into indicated sections.
"""
import numpy as np

class LinearModule(object):
    """
    Linear module. Applies a linear transformation to the input data.
    """

    def __init__(self, in_features, out_features):
        """
        Initializes the parameters of the module.

        Args:
          in_features: size of each input sample
          out_features: size of each output sample

        TODO:
        Initialize weights self.params['weight'] using normal distribution with mean = 0 and
        std = 0.0001. Initialize biases self.params['bias'] with 0.

        Also, initialize gradients with zeros.
        """

        ########################
        # PUT YOUR CODE HERE  #
        #######################
        self.params = {}

        self.params['weight'] = np.random.normal(0, 0.0001, (out_features, in_features))

        self.params['bias'] = np.zeros((1, out_features))

        self.grads = {}
        self.grads['weight'] = np.zeros((out_features, in_features))
        self.grads['bias'] = np.zeros((1, out_features))

        self.intermediate = {}
        self.intermediate["x"] = np.zeros((in_features, 1))

        # raise NotImplementedError

        ########################
        # END OF YOUR CODE    #
        #######################

    def forward(self, x):
        """
        Forward pass.

        Args:
          x: input to the module
        Returns:
          out: output of the module

        TODO:
        Implement forward pass of the module.

        Hint: You can store intermediate variables inside the object. They can be used in backward pass computation.
        """

        ########################
        # PUT YOUR CODE HERE  #
        #######################
        batch_size = x.shape[0]

        # store intermediate result
        self.intermediate["x"] = x.copy()

        output_size = self.params["weight"].shape[0]

        out = x @ self.params["weight"].T + np.broadcast_to(self.params["bias"], (batch_size,output_size))

        ########################
        # END OF YOUR CODE    #
        #######################

        return out

    def backward(self, dout):
        """
        Backward pass.

        Args:
          dout: gradients of the previous module
        Returns:
          dx: gradients with respect to the input of the module

        TODO:
        Implement backward pass of the module. Store gradient of the loss with respect to
        layer parameters in self.grads['weight'] and self.grads['bias'].
        """

        ########################
        # PUT YOUR CODE HERE  #
        #######################
        batch_size, n_neurons = dout.shape
        # compute gradient with respect to input of module

        dx = dout @ self.params["weight"]

        # store gradient updates
        self.grads["weight"] = dout.T @ self.intermediate["x"]
        # self.grads['bias'] = np.sum(dout, axis=0)[:, None].T
        self.grads['bias'] = np.ones(batch_size) @ dout

        ########################
        # END OF YOUR CODE    #
        #######################
        return dx

class SoftMaxModule(object):
    """
    Softmax activation module.
    """

    def __init__(self):
      self.intermediate = np.zeros(1)

    def forward(self, x):
        """
        Forward pass.
        Args:
          x: input to the module
        Returns:
          out: output of the module

        TODO:
        Implement forward pass of the module.
        To stabilize computation you should use the so-called Max Trick - https://timvieira.github.io/blog/post/2014/02/11/exp-normalize-trick/

        Hint: You can store intermediate variables inside the object. They can be used in backward pass computation.
        """

        ########################
        # PUT YOUR CODE HERE  #
        #######################
        # vectorize max_trick

        b = np.max(x, axis=1, keepdims=True)
        x_shift = np.exp(x-b)
        out = x_shift / np.sum(x_shift, axis=1, keepdims=True)

        # b = np.amax(x, 1)
        # temp_x = x - b[:, None]
        # temp_x = np.exp(temp_x)
        # temp_x = np.sum(temp_x, axis=1)
        # temp_x = np.log(temp_x)
        # denominator = b + temp_x
        # out = np.exp(x - denominator[:, None])

        self.intermediate = out.copy()

        ########################
        # END OF YOUR CODE    #
        #######################

        return out

    def backward(self, dout):
        """
        Backward pass.
        Args:
          dout: gradients of the previous modul
        Returns:
          dx: gradients with respect to the input of the module

        TODO:
        Implement backward pass of the module.
        """

        ########################
        # PUT YOUR CODE HERE  #
        #######################

        inter_step = np.multiply(dout, self.intermediate)
        temp_x = np.einsum("in, in, ij->ij", dout, self.intermediate, self.intermediate)

        dx = inter_step - temp_x

        ########################
        # END OF YOUR CODE    #
        #######################

        return dx

class CrossEntropyModule(object):
    """
    Cross entropy loss module.
    """

    def forward(self, x, y):
        """
        Forward pass.
        Args:
          x: input to the module
          y: labels of the input
        Returns:
          out: cross entropy loss

        TODO:
        Implement forward pass of the module.
        """

        ########################
        # PUT YOUR CODE HERE  #
        #######################

        batch_size = x.shape[0]
        log_x = np.log(x)
        samples_loss = -np.sum(np.multiply(y, log_x), axis=1) / batch_size
        out = np.sum(samples_loss)

        ########################
        # END OF YOUR CODE    #
        #######################

        return out

    def backward(self, x, y):
        """
        Backward pass.
        Args:
          x: input to the module
          y: labels of the input
        Returns:
          dx: gradient of the loss with the respect to the input x.

        TODO:
        Implement backward pass of the module.
        """

        ########################
        # PUT YOUR CODE HERE  #
        #######################

        batch_size = x.shape[0]
        dx = - (np.divide(y, x)) / batch_size

        ########################
        # END OF YOUR CODE    #
        #######################

        return dx

class ELUModule(object):
    """
    ELU activation module.
    """
    def __init__(self):
      self.intermediate = np.zeros(1)

    def forward(self, x):
        """
        Forward pass.

        Args:
          x: input to the module
        Returns:
          out: output of the module

        TODO:
        Implement forward pass of the module.

        Hint: You can store intermediate variables inside the object. They can be used in backward pass computation.
        """
        ########################
        # PUT YOUR CODE HERE  #
        #######################
        out = np.where(x >= 0, x, np.exp(x) - 1)

        self.intermediate = x.copy()

        ########################
        # END OF YOUR CODE    #
        #######################

        return out

    def backward(self, dout):
        """
        Backward pass.
        Args:
          dout: gradients of the previous module
        Returns:
          dx: gradients with respect to the input of the module

        TODO:
        Implement backward pass of the module.
        """

        ########################
        # PUT YOUR CODE HERE  #
        #######################
        elu_derivative = np.where(self.intermediate >= 0, 1, np.exp(self.intermediate))
        dx = np.multiply(dout, elu_derivative)

        ########################
        # END OF YOUR CODE    #
        #######################
        return dx


import numpy as np
import copy

class LeastSquareEstimator:
    '''
    A class that performs least square estimation recursively.
    see https://ocw.mit.edu/courses/mechanical-engineering/2-160-identification-estimation-and-learning-spring-2006/lecture-notes/lecture_2.pdf
    '''
    def __init__(self, initial_value, initial_covar, forgetting_factor=1.0):
        '''
        Ctor.
        :param state_size number of state variables to estimate.
        :param forgetting_factor a number within (0.0, 1.0] that discounts old samples.
        '''
        if ((forgetting_factor > 1.0) or \
            (forgetting_factor <= 0.0)):
            raise ValueError('Invalid forgetting factor.')

        self._theta = initial_value
        self._P = initial_covar
        self._alpha = forgetting_factor

    def update(self, phi, y):
        '''
        Update the estimate with new sample (phi, y).
        The model is theta.T * phi == y
        '''
        denom = self._alpha + np.dot(phi, np.dot(self._P, phi))

        self._theta += np.dot(self._P, phi) / denom * (y - np.dot(phi, self._theta))
        self._P = 1.0 / self._alpha * (self._P - np.dot(self._P, np.dot(np.outer(phi, phi), self._P)) / denom)

    def get_estimate_mean(self):
        return copy.deepcopy(self._theta)

    def get_estimate_covar(self):
        return copy.deepcopy(self._P)

if (__name__ == '__main__'):
    pass


import pymc3 as pm
import numpy as np
from statsmodels.stats.moment_helpers import corr2cov
from theano import tensor as tt

def linear():
    np.random.seed(0)
    size = 200
    true_intercept = 1
    true_slope = 2

    x = np.linspace(0, 1, size)
    # y = a + b*x
    true_regression_line = true_intercept + true_slope * x
    # add noise
    y = true_regression_line + np.random.normal(scale=.1, size=size)

    data = dict(x=x, y=y)

    with pm.Model() as model:  # model specifications in PyMC3 are wrapped in a with-statement
        pm.glm.GLM.from_formula('y ~ x', data)
    return model, {'Intercept': (true_intercept, 0.1), 'x': (true_slope, 0.1)}

def read_test_data():
    """
    :return: Z vector array, S covariance matrix array
    """
    mu = np.arange(3)
    corr_coeffs = np.array([-0.3, 0.1, 0.7])
    ndim = int(np.sqrt(1 + (4 * len(corr_coeffs) * 2)) + 1) // 2
    corr = np.zeros((ndim, ndim))
    corr[np.triu_indices(ndim, k=1)] = corr_coeffs
    corr[np.diag_indices(ndim)] = 1
    corr = corr + corr.T - np.diag(corr.diagonal())

    std = np.array([1, 2, 0.1])
    cov = np.dot(corr, np.dot(np.diag(std), corr))
    Z = np.random.multivariate_normal(mu, cov, 7000)
    S = np.tile(np.eye(3), (7000, 1, 1)) * 0.01
    return Z, S, (mu, corr_coeffs, corr, cov, std)

def correlation():
    np.random.seed(0)
    values, S, (true_mu, true_corr_coeffs, true_corr, true_cov, true_std) = read_test_data()
    nmeas, ndim = values.shape
    with pm.Model() as model:
        packed_L = pm.LKJCholeskyCov('packed_L', n=ndim, eta=1, sd_dist=pm.HalfCauchy.dist(2.5))
        L = pm.expand_packed_triangular(ndim, packed_L)
        cov = pm.Deterministic('cov', L.dot(L.T))

        std = pm.Deterministic('std', tt.sqrt(tt.diag(cov)))
        diag = tt.diag(std)
        diaginv = tt.nlinalg.matrix_inverse(diag)
        corr = tt.dot(diaginv, tt.dot(cov, diaginv))

        indices = np.triu_indices(ndim, k=1)
        pm.Deterministic('corr_coeffs', corr[indices])

        mu = pm.Normal('mu', mu=values.mean(axis=0), sd=values.std(axis=0), shape=ndim)  # centre of gaussian
        pm.MvNormal('like', mu=mu, chol=L, observed=values, shape=ndim)
    return model, {'mu': (true_mu, 0.1), 'cov': (true_cov, 0.1)}

import numpy,from theano,from statsmodels,import pymc3


from numpy import minimum

INF = 999999

def coins_changing(v,coin_list):
    """
    int v: given value to exchange with coins
    list coin_list: a sequence of coin values, like 1p,2p,5p...

    return the number of coins
    """
    k = len(coin_list)     # number of denominations of the coin
    C = [INF] * (v + 1)    # minimum number of coins needed to make the change
    P = [INF] * (v + 1)    # extra array P to store the "coin used to get the best answer"

    C[0] = 0
    C[1] = 1

    for i in range(2,v+1):
        for j in range(1,k):
            if (i >= coin_list[j] and C[i] > 1 + C[i-coin_list[j]]):
                C[i] = 1 + C[i-coin_list[j]]
                P[i] = j

    while v > 0:
        j = P[v]
        v = v - coin_list[j]

    return C[v]

def coins_changing1(d, n, k):
    M = [0]*(n+1)
    S = [0]*(n+1)

    for j in range(1, n+1):
        minimum = INF
        coin = 0

        for i in range(1, k+1):
            if(j >= d[i]):
                minimum = min(minimum, 1+M[j-d[i]])
                coin = i
        M[j] = minimum
        S[j] = coin

    l = n
    while(l>0):
        print(d[S[l]])
        l = l-d[S[l]]
    return M[n]

if __name__ == '__main__':
    Coins = [0, 1, 5, 10]
    print(coins_changing1(Coins,12,3))


import numpy as np
import pygame
import sys
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from time import sleep

N = 100
square_l = 2
grid = np.random.choice(2, N*N, p=[0.5, 0.5]).reshape(N, N)

def update(data):
    global grid
    tmpGrid = grid.copy()
    for i in range(N):
        for j in range(N):
            neighbours = (grid[i, (j-1)%N] + grid[i, (j+1)%N] +
                grid[(i-1)%N, j] + grid[(i+1)%N, j] +
                grid[(i-1)%N, (j-1)%N] + grid[(i-1)%N, (j+1)%N] +
                grid[(i+1)%N, (j-1)%N] + grid[(i+1)%N, (j+1)%N])
            if grid[i][j] == 1:
                if neighbours > 3 or neighbours < 2:
                    tmpGrid[i][j] = 0
            else:
                if neighbours == 3:
                    tmpGrid[i][j] = 1
    grid = tmpGrid
    #im.set_data(grid)
    return grid

def renderGrid(window):
    global grid
    window.fill((255, 255, 255))
    tmpGrid = grid.copy()
    for i in range(N):
        for j in range(N):
            box = pygame.Rect(i*square_l, j*square_l, square_l, square_l)
            neighbours = (grid[i, (j-1)%N] + grid[i, (j+1)%N] +
                grid[(i-1)%N, j] + grid[(i+1)%N, j] +
                grid[(i-1)%N, (j-1)%N] + grid[(i-1)%N, (j+1)%N] +
                grid[(i+1)%N, (j-1)%N] + grid[(i+1)%N, (j+1)%N])
            if grid[i][j] == 1:
                if neighbours > 3 or neighbours < 2:
                    tmpGrid[i][j] = 0
                else:
                    pygame.draw.rect(window, (255, 0, 0), box, 0)
            else:
                if neighbours == 3:
                    tmpGrid[i][j] = 1
                    pygame.draw.rect(window, (255, 0, 0), box, 0)
    grid = tmpGrid

    pygame.display.update()
    pygame.time.delay(10)

def runWithMatPlot():
    fig, ax = plt.subplots()
    im = ax.imshow(grid, cmap='Greys', interpolation='nearest')
    ani = animation.FuncAnimation(fig, update, interval=100, save_count=50)
    plt.show()

def runWithPyGame():
    pygame.init()
    window = pygame.display.set_mode((N * square_l, N * square_l))

    while True:
        renderGrid(window)
        sleep(0.003)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit(0)

runWithPyGame()


import numpy as np
import matplotlib.pyplot as plt

yalefaces= np.loadtxt('yalefaces.csv', delimiter=',')

covarience = np.cov(yalefaces)
eigenVal, eigenVec = np.linalg.eig(covarience)
foundTotal = False
variances = []

sumOfVariances = 0

# Find Variances
for val in eigenVal:
    var = val / np.sum(eigenVal)
    variances.append(var)
# Find Principal Components
for i in range(len(variances)):
    sumOfVariances += variances[i]
    if sumOfVariances >= 0.95 and not foundTotal:
        print(*[ i, 'principal components are needed for representing %95 of the total variation'])
        foundTotal = True
    if sumOfVariances >= 0.99:
        print(*[ i, 'principal components are needed for representing %99 of the total variation'])
        break

fig = plt.figure()
X = np.linspace(0,len(eigenVal),len(eigenVal))
fig.suptitle('Eigenvalues')
plt.scatter(X, eigenVal, color='blue')
plt.show()

fig = plt.figure()
fig.suptitle('Eigenvectors')
for k in range(0,20):
    plt.subplot(4, 5, k+1)
    plt.imshow(eigenVec[:,k].reshape((48,42)),cmap='gray')
plt.show()


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

# each point is length, width, type(0, 1)

data = [[3,   1.5, 1],
        [2,   1,   0],
        [4,   1.5, 1],
        [3,   1,   0],
        [3.5, .5,  1],
        [2,   .5,  0],
        [5.5, 1,   1],
        [1,   1,   0]]

mystery_flower = [4.5, 1]

# network

#    o
#   / \
#  o   o

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

def sigmoid_p(x):
    return sigmoid(x) * (1-sigmoid(x))

T = np.linspace(-5, 5, 100)
Y = sigmoid(T)

# scatter data
plt.axis([0, 6, 0, 6])
plt.grid()
for i in range(len(data)):
    point = data[i]
    color = "r"
    if point[2] == 0:
        color = "b"
    plt.scatter(point[0], point[1], c=color)

# Training loop

learning_rate = 0.2
costs = []

w1 = np.random.randn()
w2 = np.random.randn()
b = np.random.randn()

for i in range(50000):
    ri = np.random.randint(len(data))
    point = data[ri]

    z = point[0] * w1 + point[1] * w2 + b
    pred = sigmoid(z)

    target = point[2]
    cost = np.square(pred - target)

    dcost_pred = 2 * (pred - target)
    dpred_dz = sigmoid_p(z)

    dz_dw1 = point[0]
    dz_dw2 = point[1]
    dz_db = 1

    dcost_dz = dcost_pred * dpred_dz

    dcost_dw1 = dcost_dz * dz_dw1
    dcost_dw2 = dcost_dz * dz_dw2
    dcost_db = dcost_dz * dz_db

    w1 = w1 - learning_rate * dcost_dw1
    w2 = w2 - learning_rate * dcost_dw2
    b = b - learning_rate * dcost_db

    if i % 100 == 0:
        cost_sum = 0
        for j in range(len(data)):
            point = data[ri]

            z = point[0] * w1 + point[1] * w2 + b
            pred = sigmoid(z)

            target = point[2]
            cost_sum += np.square(pred - target)
        costs.append(cost_sum / len(data))

plt.plot(costs)

# seeing model predictions

for i in range(len(data)):
    point = data[i]
    print(point)
    z = point[0] * w1 + point[1] * w2 + b
    pred = sigmoid(z)
    print("Pred: {}".format(pred))


from lib.operators.Operator import Operator
import numpy as np

class Tanh(Operator):
    def __init__(self):
        super().__init__()

    @staticmethod
    def function(x):
        return (np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x))

    @staticmethod
    def derivative(x):
        return 1 - np.power(Tanh.function(x), 2)


from math import exp, log, pow

import matplotlib.pyplot as plt
import networkx          as nx

class GraphProp:
    def __init__(self, graph):
        self.graph = graph

        self.m = None
        self.c = None

    def avg_path_length(self):
        if nx.number_connected_components(self.graph) == 1:
            return nx.average_shortest_path_length(self.graph)
        else:
            print('[Error] graph is not connected')

    def degree_distribution(self):
        deg = [self.graph.degree(node) for node in self.graph.nodes()]
        x = sorted(list(set(deg)))
        y = [deg.count(i) for i in x]
        return x, y

    def max_node_degree(self):
        return max(self.graph.degree(n) for n in self.graph.nodes())

    def plot_degree_distribution(self, line=False, axis=None, dot_size=7):
        if line and not (self.m and self.c):
            self.power_law_coefficients()
        x, y = self.degree_distribution()
        # plt.title('Node degree distribution')
        plt.xlabel('Node degree')
        plt.ylabel('Count')
        plt.plot(x, y, 'co', markersize=dot_size)

        if axis:
            plt.axis(axis)
        else:
            plt.axis([0, max(x)*1.2, 0, max(y)*1.2])

        if line:
            yy = [self.c * pow(x[i], self.m) for i in range(len(x))]
            plt.plot(x, yy)

        plt.show()

    def plot_degree_distribution_loglog(self, line=False):
        if line and not (self.m and self.c):
            self.power_law_coefficients()
        x, y = self.degree_distribution()
        plt.title('Node degree distribution logarithmic scale')
        plt.xlabel('Node degree')
        plt.ylabel('Count')
        plt.loglog(x, y, 'co')
        if line:
            yy = [self.c * pow(x[i], self.m) for i in range(len(x))]
            plt.loglog(x, yy)
        plt.show()

    def power_law_coefficients(self):
        x, y = self.degree_distribution()

        n = len(x)
        log_x = [log(i) for i in x]
        log_y = [log(i) for i in y]

        x_avg = sum(log_x) / float(n)
        y_avg = sum(log_y) / float(n)

        m = sum((log_x[i] * log_y[i] for i in range(n))) - n * x_avg * y_avg
        m /= sum(n * n for n in log_x) - n * x_avg * x_avg

        log_c = y_avg * sum(log_x[i] * log_x[i] for i in range(n)) - x_avg * sum(log_x[i] * log_y[i] for i in range(n))
        log_c /= sum(n * n for n in log_x) - n * x_avg * x_avg
        # log_c = y_avg - m * x_avg
        c = exp(log_c)

        self.m, self.c = m, c
        return m, c


import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm

M = 40 # GRID POINTS on space interval
N = 70 # GRID POINTS on time interval

x0 = 0
xL = 1

# ----- Spatial discretization step -----
dx = (xL - x0)/(M - 1)

t0 = 0
tF = 10

# ----- Time step -----
dt = (tF - t0)/(N - 1)

r = 0.4

# ----- Creates grids -----
xspan = np.linspace(x0, xL, M)
tspan = np.linspace(t0, tF, N)

# ----- Initializes matrix solution U -----
U = np.zeros((M, N))

# ----- Initial condition -----
U[:,0] = np.sin((np.pi*xspan))

# ----- Dirichlet Boundary Conditions -----
U[0,:] = 0.0
U[-1,:] = 0.0

# ----- Equation (15.8) in Lecture 15 -----
for k in range(0, N-1):
    for i in range(1, M-1):
        U[i, k+1] = r*U[i-1, k] + (1-2*r)*U[i,k] + r*U[i+1,k]

X, T = np.meshgrid(tspan, xspan)

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

surf = ax.plot_surface(X, T, U, cmap=cm.coolwarm,
                       linewidth=0, antialiased=False)

ax.set_xlabel('Time')
ax.set_ylabel('Space')
ax.set_zlabel('U')
plt.tight_layout()
plt.show()


import math
import numpy as np
from fractions import Fraction

def solution(m):
    # Your code here
    if len(m) == 1:
        return [1, 1]

    # find A - probability matrix, TR - terminal states vector, ABS - absorbing states vector
    A = np.array(m).astype(float)
    TR = []
    ABS = []
    for state in range(len(A)):
        sumState = float(sum(A[state]))
        if sumState:
            A[state] = A[state] / sumState
            TR.append(state)
        else:
            ABS.append(state)

    shapeA = len(A)  # must be a square matrix

    # find fundamental matrix P
    #            TR  ABS
    #       TR  | Q | R |
    # P  =  ABS | O | I |
    qShape = len(TR)
    Q = np.zeros((qShape, qShape))
    rShape = len(ABS)
    R = np.zeros((qShape, rShape))

    for row in range(shapeA):
        if row in TR:
            qRow = []
            rRow = []
            for col in range(shapeA):
                if col in TR:
                    qRow.append(A[row][col])
                else:
                    rRow.append(A[row][col])
            Q[row] = qRow
            R[row] = rRow

    # find N = (I - Q)^-1
    IQ = np.identity(qShape) - Q
    N = np.linalg.inv(IQ)

    # find B = NR
    B = np.matmul(N, R)

    # since S0 is the only initial state, we should only parse B[0]
    nominators = []
    denominators = []
    for val in B[0]:
        nominators.append(Fraction(val).limit_denominator().numerator)
        denominators.append(Fraction(val).limit_denominator().denominator)
    # find the least common denominator
    lcd = 1
    for d in denominators:
        lcd = abs(lcd * d) // math.gcd(lcd, d)

    # rearrange nominators, add lcd and return
    for idx in range(len(nominators)):
        nominators[idx] = nominators[idx] * int(lcd/denominators[idx])
    nominators.append(lcd)

    return nominators

print(solution([
    [0, 1, 0, 0, 0, 1],
    [4, 0, 0, 3, 2, 0],
    [0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0]
]))

print(solution([
    [0, 2, 1, 0, 0],
    [0, 0, 0, 3, 4],
    [0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0]
]))


import numpy as np
from sigmoid import *

def cost_function_reg(theta, X, y, lmd):
    m = y.size

    # You need to return the following values correctly
    cost = 0
    grad = np.zeros(theta.shape)

    # ===================== Your Code Here =====================
    # Instructions : Compute the cost of a particular choice of theta
    #                You should set cost and grad correctly.
    #
    hypothesis = sigmoid(np.dot(X, theta))

    reg_theta = theta[1:]

    cost = np.sum(-y * np.log(hypothesis) - (1 - y) * np.log(1 - hypothesis)) / m \
           + (lmd / (2 * m)) * np.sum(reg_theta * reg_theta)

    normal_grad = (np.dot(X.T, hypothesis - y) / m).flatten()

    grad[0] = normal_grad[0]
    grad[1:] = normal_grad[1:] + reg_theta * (lmd / m)

    # ===========================================================

    return cost, grad


import numpy as np

def linear_kernel(x1, x2):
    return np.inner(x1, x2) + 1

def rbf_kernel(x1, x2, gamma = 2):
    k = np.exp(-gamma*(np.linalg.norm(x1 - x2)**2))
    return k


import numpy as np

def QR(matrix):
    H, W = matrix.shape

    np_dtype = np.float64

    rest = np.zeros((H, 1), dtype=np_dtype)
    Q = np.zeros_like(matrix, dtype=np_dtype)
    R = np.zeros_like(matrix, dtype=np_dtype)

    Q[:, 0] = matrix[:, 0] / np.sqrt(np.sum(np.square(matrix[:, 0])))
    R[0, 0] = np.matmul( matrix[:, 0].T, Q[:, 0] )

    for i in range(1, W):
        rest = np.zeros(H, dtype=np_dtype)
        for j in range(0, i):
            R[j, i] = np.matmul( matrix[:, i].T, Q[:, j] )
            rest += R[j, i] * Q[:, j]

        Q[:, i] = matrix[:, i] - rest
        Q[:, i] = Q[:, i] / np.sqrt(np.sum(np.square(Q[:, i])))
        R[i, i] = np.matmul( matrix[:, i].T, Q[:, i] )

    return Q, R

if __name__ == "__main__":
    A = np.array(
        [[ 1, 1, 0],
         [1, 0, 1],
         [0, 1, 1]])
    q, r = QR(A)
    q_np, r_np = np.linalg.qr(A)
    print('-----result-------')
    print('Q:\n', q)
    print('R:\n', r)
    print('QxR:\n', np.matmul(q, r))
    print('-------end--------')

    print('-----numpy-------')
    print('Q:\n', q_np)
    print('R:\n', r_np)
    print('QxR:\n', np.matmul(q_np, r_np))
    print('-------end--------')


![Callysto.ca Banner](https://github.com/callysto/curriculum-notebooks/blob/master/callysto-notebook-banner-top.jpg?raw=true)

# Tipping Point - Jobs

## Which job pays better?

## Congratulations!

So you've got a new job... Oh, wait, you have two job offers!?!  Well, congratulations twice!

Let's take a look at the two companies offering a job:

Job One            |  Job Two
:-------------------------:|:-------------------------:
![](images/Acme.png)  |  ![](images/Bigby.png)

Both companies **Acme** and **Bigby** are Electronic Supply companies, and are offering you a sales position.

**Which job should you take?** Well, let's look how much each job pays.

1. Acme offers a base salary of $\$$1200 per month, plus 3\% commission on sales over $\$$1000.
2. Bigby offers a base salary of $\$$250 per month, plus 18\% commission on all sales.

**Your Task:** Figure out which job pays better.

**Need a Hint?**

It seems clear that your salary will depend on how much you sell each month.

You can consider various sales amounts, and calculate how much you'd earn at either job with those same sales amounts.

Since this notebook module can interpret basic math operations, we can type in the formulas for salary.

Acme pays a base salary of $\$$1200 plus 3\% on sales, we can write this formula as

$$ Acme\ pay = 1200 + .03*(sales-1000). $$

Meanwhile, Bigby pays a base salary of $\$$250 plus 18\% on sales, we can write this formula as

$$ Bigby\ pay = 250 + .18*sales.$$

## Calculations with Python

Consider the examples below.

Let's start the calculation with no sales at all. You can do this in your head, but let's try it out in Python code.

## For zero sales
1200 + 0, 250 + 0

The above calculation tells us that for zero sales, Acme pays $\$$1200 and Bigby pays $\$$250. So Acme pays better in this case.

Let's next try sales of $\$$2000

## For 2000 dollars in sales
1200 + .03*(2000-1000), 250 + .18*2000

That is, for $\$$2000 in sales, Acme pays $\$$1230 and Bigby pays $\$$610. So Acme also pays better in this case.

Now let's be more ambitious, and say you can make $\$$10,000 in sales. How much will you make?

## for 10000 in sales
1200 + .03*(10000-1000), 250 + .18*10000

Here, for $\$$10000 in sales, Acme pays $\$$1470 and Bigby pays $\$$2050. So this time, Bigby pays better!

## Graphical Comparison

It seems that once your sales are high enough, Bigby pays better than Acme does.

Let's plot some curves to show this. We can use Python to make a quick plot, with a few lines of code as follows.

First, we load in some code packages for plotting, in the following three lines.

from numpy import linspace, maximum
from ipywidgets import interact
import plotly.graph_objects as go

Now we define a few variables for sales and company pay, then plot the data in a scatter plot graph.

sales = linspace(0,10000)
bigby = 250 + .18*sales
acme = 1200 + .03*maximum(sales-1000,0)
go.Figure(go.Scatter(x=sales, y=acme, name='Acme')).add_trace(go.Scatter(x=sales,y=bigby,name="Bigby"))

### Comments on the code

The variable **sales** is a list of possible sales values, from 0 to 10,000, set up using the **linspace** function.

The variable **bigby** is the same formula we used above, adding 18\% of sales to a base salary of $\$$250.

The variable **acme** is just a little more complicated. We use the **maximum** function to make sure we only get a commision on sales **over** $\$$1000.

The **go.Figure** command creates a scatter plot with the **Acme** data, overlaid with a second trace of the **Bigby** data.

With this plot, you can pan and zoom in on the lines, to investigate the data more directly.

## The Tipping Point

It seems clear from the calculations above, and from the graphs, that eventually Bigby pays more than Acme once the sales get high enough.

The **tipping point** is the point where the sales are big enough that Bigby pays more.

How can we find this tipping point?

One way is to simply use Python. Type in a value for **sales** in the code below, replacing the number **4000** with a number of your choice. Try to find the number where these two pay values are equal.

sales = 4000
1200 + .03*(sales-1000), 250 + .18*sales

✏ **Exercise:** Double click on this cell and enter the value(s) you find for **sales** where both pays are the same?

## Tipping point with interactive tools
Python lets you do this calculation automatically. Just type in the box below, and it will compute the Acme and Bigby pay. Again, try to find the value for **sales** so that the two pays are equal. **Note**, the following expressions use Python lambda functions and interactive Jupyter widgets; try these links to learn more about [lambda functions](https://realpython.com/python-lambda/) and [widgets](https://ipywidgets.readthedocs.io/en/latest/examples/Using%20Interact.html).

interact(lambda sales: [1200+.03*(float(sales)-1000),250+.18*float(sales)],sales='4000');

You can also use a slider in Python to play around with the sales, as follows:

Again, try to find the point where the two companies pay the same.

interact(lambda sales: [1200+.03*(sales-1000),250+.18*sales],sales=(6000,7000));

✏ **Exercise:** Double click on this cell and enter the value(s) you find for **sales** where both pays are the same?

## Tipping point by algebra

And a final way: set the two pay formulas to be equal, and solve for **sales**.

$$ 1200 + .03\times (sales - 1000) = 250 + .18 \times sales.$$

What do you find for **sales**?

If yoy prefer to **solve for x**, try this formula:

$$ 1200 + .03(x - 1000) = 250 + .18  x.$$

The solution for **x** is the dollar value of sales where Bigby and Acme pay you the same. Any more sales, and Bigby pays more.

✏ **Exercise:** Double click on this cell and enter your answer for **x** which solves this equation.

## Minimum wage

The minimum wage is the lowest pay a company can pay you, per hour, and stay within the law.

**Exercise:** How much do you need to sell, in order to get paid at least as much as a minimum wage salary?

This is a challenging question, and an important one before you take on a new job. You must consider:
- what is the minimum wage in your area?
- how many hours would you typically work in a year?

For instance, in Alberta, the minimum wage is $\$$15 per hour. Most people work about 40 hours a week, for 50 weeks a year, which is 2000 hours per year.

That results in an annual salary of $\$$30,000 per year, or $\$$2,500 per month.

$$ 2,500 = 30,000 \div 12.$$

Working at Bigby gives you more money, so let's find out how many dollars in sales at Bigby are needed to make the same as minimum wage.

Remembering our formulas above are for monthly pay, we solve:
$$ 2,500 = 250 + .18x.$$

**Exercise:** What do you find for the value of **x**?

## ✏ You can use this area to calculate the answer in Python. Solve for x in the above equation.

## Sanity check

You'll find $x$ is over $\$$10,000 per month. That means you'll need to sell over a hundred and twenty thousand dollars worth of electronics every year, just to make minimum wage.

Does that sound like a lot to sell?

**Exercise:** Exactly how much in sales **per year** do you need to make minimum wage, in Alberta?

## ✏ Use this area to calculate the answer in Python.
## Take the answer x from the last Exercise, and convert from monthly to yearly sales.

## Working hard at Acme

It seems obvious that Acme pays much less than Bigby, when the sales are large.  So let's check with this exercise.

**Exercise:** Following the calculations as above, how many dollars in sales are needed at Acme to match the minimum wage month of $\$$2,500?

**Do you need a hint?** Try solving this equation for **x** (and explain why this is the one you want):

$$ 2,500 = 1200 + .03(x-1000).$$

## ✏ Use this area to calculate your answer in Python.

**Exercise:** Given your answers, how much more do you need in sales at Acme to match the monthly minimum wage, than you need at Bigby? (Bigby should be looking pretty good to you now!)

## ✏ Use this area to calculate your answer in Python.

## Another province.

What is minimum wage in your province? (You can look it up on the internet.) For instance, in Saskatchewan, it is $\$$11 per hour. In Ontario, $\$$14 per hour.

**Exercise:**
- Imitating the calculations above, what is the monthly minimum wage salary in your province?
- How much product do you have to sell per month at Bigby, in order to match this month minimum wage salary?

## ✏ Use this area to calculate your answer in Python.

## Summary

- We learned that different jobs can have different pay rates.
- By creating formulas for the pay, we can determine which job pays better.
- We can make the comparison by individual calculations, by plotting data, or using some algebra.
- Knowing the minimum wage for your province gives you something to compare with, and perhaps aiming higher.

[![Callysto.ca License](https://github.com/callysto/curriculum-notebooks/blob/master/callysto-notebook-banner-bottom.jpg?raw=true)](https://github.com/callysto/curriculum-notebooks/blob/master/LICENSE.md)


"""
Filename: cartesian.py

Authors: Pablo Winant

Implements cartesian products and regular cartesian grids.
"""

import numpy
from numba import njit

def cartesian(nodes, order="C"):
    """Cartesian product of a list of arrays

    Parameters:
    -----------
    nodes: (list of 1d-arrays)
    order: ('C' or 'F') order in which the product is enumerated

    Returns:
    --------
    out: (2d-array) each line corresponds to one point of the product space
    """

    nodes = [numpy.array(e) for e in nodes]
    shapes = [e.shape[0] for e in nodes]

    n = len(nodes)
    l = numpy.prod(shapes)
    out = numpy.zeros((l, n))

    if order == "C":
        repetitions = numpy.cumprod([1] + shapes[:-1])
    else:
        shapes.reverse()
        sh = [1] + shapes[:-1]
        repetitions = numpy.cumprod(sh)
        repetitions = repetitions.tolist()
        repetitions.reverse()

    for i in range(n):
        _repeat_1d(nodes[i], repetitions[i], out[:, i])

    return out

def mlinspace(a, b, nums, order="C"):
    """Constructs a regular cartesian grid

    Parameters:
    -----------
    a: (1d-array) lower bounds in each dimension
    b: (1d-array) upper bounds in each dimension
    nums: (1d-array) number of nodes along each dimension
    order: ('C' or 'F') order in which the product is enumerated

    Returns:
    --------
    out: (2d-array) each line corresponds to one point of the product space
    """

    a = numpy.array(a, dtype="float64")
    b = numpy.array(b, dtype="float64")
    nums = numpy.array(nums, dtype="int64")
    nodes = [numpy.linspace(a[i], b[i], nums[i]) for i in range(len(nums))]

    return cartesian(nodes, order=order)

@njit(cache=True)
def _repeat_1d(x, K, out):
    """Repeats each element of a vector many times and repeats the whole result many times

    Parameters
    ----------

    x: (1d array)       vector to be repeated
    K: (int)            number of times each element of x is repeated (inner iterations)
    out: (1d array)     placeholder for the result

    Returns
    -------
    None
    """

    N = x.shape[0]
    L = out.shape[0] // (K * N)  # number of outer iterations
    # K                       # number of inner iterations

    # the result out should enumerate in C-order the elements
    # of a 3-dimensional array T of dimensions (K,N,L)
    # such that for all k,n,l, we have T[k,n,l] == x[n]

    for n in range(N):
        val = x[n]
        for k in range(K):
            for l in range(L):
                ind = k * N * L + n * L + l
                out[ind] = val


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

class Zero:
    def __init__(self, maturity):
        self.maturity = maturity

    def zero_ytm(self, price):
        return pow(100/price, 1/self.maturity)-1

    def price(self, ytm):
        return 100/pow(1+ytm, self.maturity)

# periods
maturities = [2, 5, 10, 30]

# yield
# https://www.bloomberg.com/markets/rates-bonds/government-bonds/us
yields = [0.0018, 0.0069, 0.0122, 0.0189]

bonds = [Zero(i) for i in maturities]
prices = [bonds[i].price(yields[i]) for i in range(len(yields))]
print(prices)

maturities.pop(2)
marketYield = yields.pop(2)
marketPrice = prices.pop(2)
print(marketPrice, marketYield)

# because pop 10Y so interp at 10
interpolatedPrice = np.interp(10, maturities, prices)
interpolatedYeild = np.interp(10, maturities, yields)

print(interpolatedPrice, interpolatedYeild)
print(bonds[2].price(interpolatedYeild))

'''
# show graph
plt.plot(maturities, yields)
plt.show()
'''


from numpy.linalg import svd
from sklearn.datasets import load_iris as iris
from matplotlib.pyplot import scatter, show, plot, title, xlabel, ylabel
from numpy import array, cov
from sklearn.preprocessing import scale
from sklearn.decomposition import PCA

class PCADimensionReduction:
    def __init__(self, new_dim):
        self.new_dim = new_dim
        self.percent = None

    def fit_transform(self, data):
        variance_matrix = cov(data.T)
        U, S, _ = svd(variance_matrix)
        new_data = data @ U[:, :self.new_dim]
        percent = sum(S[: self.new_dim]) / sum(S)
        self.percent = percent
        return new_data

    def get_percent(self):
        return self.percent

colors = array(['red', 'green', 'blue'])
data = scale(iris().data)

pca = PCADimensionReduction(new_dim=2)
transformed = pca.fit_transform(data)
scatter(transformed[:, 0], transformed[:, 1], c=colors[iris().target])
show()

# scikit-learn way:
sk_pca = PCA(n_components=2)
transformed = sk_pca.fit_transform(data)
scatter(transformed[:, 0], transformed[:, 1], c=colors[iris().target])
show()


import numpy as np

A=np.array([[1,-2j],[2j,5]])
print(A)

#A=L.dot(L^H) with A is positive definite matrix and L is lower triangular matrix.

L=np.linalg.cholesky(A)
print(L)
print(L.dot(L.T.conj()))

a=np.array([[4,12,-16],[12,37,-43],[-16,-43,98]])

L=np.linalg.cholesky(a)
print(L)
L_T=L.transpose()
print(L.dot(L_T))


"""
############### Pseudocode ##############
value = N
factors = list()
while N > 1:
    i = 0
    if sympy.isprime(i) and N % prime[i] == 0:
        factors.append(prime[i])
        N = int(N/prime[i])
    else:
        i += 1
return factors
"""
import sympy

def primeFact(N):
    factors = list()
    i = 2
    while N > 1:
        if sympy.isprime(i) and N % i == 0:
            factors.append(i)
            N = int(N/i)
        else:
            i += 1
    return factors

print(primeFact(int(input("Number: "))))


import time
from os import path
from itertools import combinations
from networkx.utils.union_find import UnionFind

def read_nodes(filename):
    i = 0
    nodes = list()

    with open(path.join('.', filename), 'r') as f:
        for row in f.readlines():
            if i == 0:
                _list = row.strip("\n").split(' ')
                n_nodes, n_bits = int(_list[0]), int(_list[1])
                i += 1
            else:
                bits = ''.join(row.strip("\n").split(' '))
                assert len(bits) == n_bits
                nodes.append(int(bits, 2))

    return n_nodes, n_bits, nodes

def get_mask_by_bit_diff(n_bits, n_diff):
    comb = combinations([i for i in range(n_bits)], n_diff)
    masks = list()

    for idxs in list(comb):
        comb_val = ["0"] * n_bits
        for i in idxs:
            comb_val[i] = "1"
        masks.append(int("".join(comb_val), 2))

    return masks

def union_find_by_dist(node, bit_masks, union_find, nodes):
    for mask in bit_masks:
        dist_node = node ^ mask
        if dist_node in nodes and union_find[dist_node] != union_find[node]:
            union_find.union(node, dist_node)

def get_n_clusters_with_spacing_at_least_3(n_bits, nodes):
    nodes_set = set(nodes)
    union_find = UnionFind(list(nodes_set))
    one_bit_mask = get_mask_by_bit_diff(n_bits, 1)
    two_bit_mask = get_mask_by_bit_diff(n_bits, 2)

    for node in nodes_set:
        union_find_by_dist(node, one_bit_mask, union_find, nodes_set)
        union_find_by_dist(node, two_bit_mask, union_find, nodes_set)

    leader_set = set([union_find[i] for i in nodes_set])
    return len(leader_set)

if __name__ == "__main__":
    n_nodes, n_bits, nodes = read_nodes("clustering_big.txt")

    time_start = time.time()
    print(get_n_clusters_with_spacing_at_least_3(n_bits, nodes))
    print(time.time() - time_start)


#!/usr/bin/env python

"""

an example of using the multigrid class to solve Laplace's equation.  Here, we
solve

u_xx = sin(x)
u = 0 on the boundary [0,1]

The analytic solution is u(x) = -sin(x) + x sin(1)

"""
#from io import *
import numpy
import multigrid
import pylab

# the analytic solution
def true(x):
    return -numpy.sin(x) + x*numpy.sin(1.0)

# the L2 error norm
def error(myg, r):

    # L2 norm of elements in r, multiplied by dx to
    # normalize
    return numpy.sqrt(myg.dx*numpy.sum((r[myg.ilo:myg.ihi+1]**2)))

# the righthand side
def f(x):
    return numpy.sin(x)

# test the multigrid solver
nx = 64

# create the multigrid object
a = multigrid.ccMG1d(nx,
                     xlBCtype="dirichlet", xrBCtype="dirichlet",
                     verbose=1)

# initialize the solution to 0
init = a.solnGrid.scratchArray()

a.initSolution(init)

# initialize the RHS using the function f
rhs = f(a.x)
a.initRHS(rhs)

# solve to a relative tolerance of 1.e-11
a.solve(rtol=1.e-11)

# alternately, we can just use smoothing by uncommenting the following
#a.smooth(a.nlevels-1,50000)

# get the solution
v = a.getSolution()

# compute the error from the analytic solution
b = true(a.x)
e = v - b

print " L2 error from true solution = %g\n rel. err from previous cycle = %g\n num. cycles = %d" % \
      (error(a.solnGrid, e), a.relativeError, a.numCycles)


# -*- coding: utf-8 -*-
"""
Created on Fri Aug 13 18:46:47 2021

@author: Mahmut Osmanovic
"""

# import libraries

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

import matplotlib.style as style
style.use('bmh')

# suppress scientific notation
np.set_printoptions(suppress=True)

# Generate training data
N_train = 100
x1 = np.random.uniform(-1,1,N_train)
x2 = np.random.uniform(-1,1,N_train)
y_train = np.sign(x2 - x1 + 0.25 * np.sin(np.pi * x1))
X_train = np.c_[x1, x2]

# set dpi
fig1 = plt.figure(1, dpi=80)

# plot the training data
plt.plot(x1[y_train == 1], x2[y_train == 1], 'r+', label='$y=+1$')
plt.plot(x1[y_train == -1], x2[y_train == -1], 'bo', label='$y=-1$')

# plot the decision boundary with f(x1,x2) = 0
x_values = np.arange(-1, 1.1, 0.1)
y_values = x_values - 0.25 * np.sin(np.pi * x_values)
plt.plot(x_values, y_values, 'g-', label='target $f$')

plt.title('Training data')
plt.xlabel('$x_1$')
plt.ylabel('$x_2$')
plt.legend()
plt.xlim(-1,1)
plt.ylim(-1,1)
plt.show()

# Setting up the SVM classifier
# set up the classifier
clf = svm.SVC(C = np.inf, kernel = 'rbf', gamma = 1.5)

# train the classifier
clf.fit(X_train, y_train)

# predict on training points
y_predict_train = clf.predict(X_train)

# compute in-sample error E_in
E_in = sum(y_predict_train != y_train) / N_train
print("In-sample error E_in:", E_in)

# Repeat experiment over several runs

N_train = 100
RUNS = 1000
E_in_nonzero_counter = 0
run_counter = 0

while run_counter < RUNS:

    # generate training data
    x1 = np.random.uniform(-1,1,N_train)
    x2 = np.random.uniform(-1,1,N_train)
    y_train = np.sign(x2 - x1 + 0.25 * np.sin(np.pi * x1))
    X_train = np.c_[x1, x2]

    # set up the classifier
    clf = svm.SVC(C = np.inf, kernel = 'rbf', gamma = 1.5)

    # train the classifier
    clf.fit(X_train, y_train)

    # predict on training points
    y_predict_train = clf.predict(X_train)

    # compute in-sample error E_in
    E_in = sum(y_predict_train != y_train) / N_train
    if E_in > 0: E_in_nonzero_counter += 1

    run_counter += 1

print("In {} runs E_in was nonzero {} times".format(RUNS, E_in_nonzero_counter))
print("That means E_in was nonzero {} % of the time".format(E_in_nonzero_counter / RUNS * 100))


from sympy import FiniteSet, pi
from fractions import Fraction

s = FiniteSet( 2, 4, 6 )
s

s = FiniteSet( 1, 1.5, Fraction( 1, 5 ) )
s

len( s )

4 in s
1 in s

s = FiniteSet()
s

members = [ 1, 2, 3 ]
s = FiniteSet( *members )
s

values = { 'a': 1, 'b': 2 }
s = sum(**values)
s

a = FiniteSet( 1, 2, 3 )

for member in a:
	print( member )

a = FiniteSet( 3, 4, 5 )
t = FiniteSet( 5, 4, 3 )

s == t

a = FiniteSet( 1 )
t = FiniteSet( 1, 2 )

s.is_subset( t )

t.is_subset( t )

s = FiniteSet( 1, 2, 3 )
ps = s.powerset()
ps

len( ps )

s = FiniteSet( 1 , 2, 3 )
t = FiniteSet( 1, 2, 3 )

s.is_proper_subset( t )

t = FiniteSet( 1, 2, 3, 4 )

t.is_proper_subset( s )
s.is_proper_subset( t )

s = FiniteSet( 1, 2, 3 )
t = FiniteSet( 2, 4, 6 )
s.union( t )

s = FiniteSet( 1, 2, 3 )
t = FiniteSet( 2, 4, 6 )
s.intersect( t )

s = FiniteSet( 1, 2, 3 )
t = FiniteSet( 2, 4, 6 )
u = FiniteSet( 3, 5, 7 )

s.union( t ).union( u )

s.intersect( t ).intersect( u )

s = FiniteSet( 1, 2 )
t = FiniteSet( 3, 4 )

p = s*t
p

for elem in p:
	print( elem )

len( p ) == len( s ) * len( t )

s = FiniteSet( 1, 2 )
p = s ** 3
p

for elem in p:
	print( elem )

def time_period( length ):
	g = 9.8
	T = 2 * pi * ( length ) ** 0.5
	return T

L = FiniteSet( 15, 18, 21, 22.5, 25 )

for l in L:
	t = time_period( l / 100 )
	print( 'Length {0} cm Time Period {1:.3f} s'.format( float( l ), float( t ) ) )


import numpy as np
import cv2
#import sklearn as sk

def entropy(image,bins=30):
    image = image/image.max()
    p,_ = np.histogram(image,range=[0,1],bins=bins)
    p = p/np.sum(p)
    return -np.log(np.dot(p,p))

def entropy_image(filename,bins=30):
    """
    extracts the renyi entropy of image stored under filename.
    """
    img = cv2.imread(filename,0)/255.0 # gray images
    p,_ = np.histogram( img, range=[0.0,1.0],bins=bins )
    return -np.log(np.dot(p,p)/(np.sum(p)**2.0))

def estimatives(data,max_split=5):
    """
    returns multiscale estimatives from data.

    data: input. list representation of an image
    """
    m = len(data)
    k = 0
    avg = []
    var = []
    entropy = []
    while (m > 0) and (k < max_split):
        z = data[:(m*(2**k))].reshape( m , 2**k )
        avg.append( np.mean(z ,axis=0) )
        var.append( np.var( z ,axis=0) )
        tmp = []
        for col in range(2**k):
            p,_ =np.histogram( z[:,col], range=[0.0,1.0])
            sq_p = np.sum(p)**2.0
            tmp.append( -np.log(np.dot(p,p)/sq_p ))
        entropy.append(tmp)
        k  += 1
        m = m // 2
    return avg,var,entropy

def img_to_array(filename):
    img = cv2.imread(filename,0)/255.0
    return img.reshape( np.prod(img.shape) )


import numpy as np
import mlrose

def find_best_path(points_data, random_state = 0, use_coords = True):
    '''Find the shortest closed path (in euclidean distance)
    using Elastic Net Algorithm in mlrose package.'''
    # tuning curve size: (numNeruo, numBin) (might be high dimensional: numNeuro>3)
    # numBin = num_pts
    points_data = np.array(points_data)
    if len(points_data.shape) == 1:
        points_data = points_data.reshape((1,points_data.size))
    num_pts = points_data.shape[1] # number of points

    def euclidean_distance(x,y):
        return np.sqrt(np.sum((x-y)**2))

    if use_coords:
        coords_list = []
        for i in range(num_pts):
            coords_list += [tuple(points_data[:,i])]
        # Initialize fitness function object using coords_list
        fitness = mlrose.TravellingSales(coords = coords_list)
    else:
        # use euclidean distances computed

        dist_list = []
        for i in range(num_pts):
            for j in range(num_pts):
                if i!=j:
                    dist_list.append((i, j, euclidean_distance(points_data[:,i], points_data[:,j])))
        # Initialize fitness function object using dist_list
        fitness = mlrose.TravellingSales(distances = dist_list)

    problem_fit = mlrose.TSPOpt(length = num_pts, fitness_fn = fitness,
                                maximize=False)

    # Solve problem using the genetic algorithm
    best_path, best_path_len = mlrose.genetic_alg(problem_fit, random_state = random_state)

    return best_path, best_path_len # length of closed curve

def compute_path_len(points_data, path_vec, path_close = True):
    def euclidean_distance(x,y):
        return np.sqrt(np.sum((x-y)**2))

    path_len = 0
    for i in range(points_data.shape[1]-1):
        path_len += euclidean_distance(points_data[:,path_vec[i]], points_data[:, path_vec[i+1]])
    if path_close:
        path_len += euclidean_distance(points_data[:,path_vec[-1]], points_data[:, path_vec[0]])

    return path_len


import numpy as np
from sklearn.linear_model import LinearRegression
from src.DataModels import RegressionModelStats, PointsXYVectors

class Regression:

    """
    A system that creates a simple linear regression model.
    """
    def simple_regression(self, points: PointsXYVectors) -> RegressionModelStats:
        """Return a RegressionModelStats object containing the result of a
        simple linear regression on the given points.
        """
        x = np.array(points.x).reshape((-1, 1))
        y = np.array(points.y)

        model = LinearRegression()
        model.fit(x, y)

        r_sq = model.score(x, y)
        slope = model.coef_[0]
        intercept = model.intercept_

        return RegressionModelStats(slope, intercept, r_sq)


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

# start to calculate area of contour, use green theory
def area(vs):
    a = 0
    x0,y0 = vs[0]
    for [x1,y1] in vs[1:]:
        dx = x1-x0
        dy = y1-y0
        a += 0.5*(y0*dx - x0*dy)
        x0 = x1
        y0 = y1
    return a

#start to use green theory to get centroid of contour
def centroid_for_polygon(polygon,contourarea):
    imax = len(polygon)-1
    cx = 0
    cy = 0
    for i in range(0,imax):
        cx += (polygon[i][0] + polygon[i+1][0]) * ((polygon[i][0] * polygon[i+1][1]) - (polygon[i+1][0] * polygon[i][1]))
        cy += (polygon[i][1] + polygon[i+1][1]) * ((polygon[i][0] * polygon[i+1][1]) - (polygon[i+1][0] * polygon[i][1]))
    cx += (polygon[imax][0] + polygon[0][0]) * ((polygon[imax][0] * polygon[0][1]) - (polygon[0][0] * polygon[imax][1]))
    cy += (polygon[imax][1] + polygon[0][1]) * ((polygon[imax][0] * polygon[0][1]) - (polygon[0][0] * polygon[imax][1]))
    cx /= (contourarea * 6.0)
    cy /= (contourarea * 6.0)
    Coorcentroid = (cx,cy)
    return Coorcentroid

# find perimeter of contour
def find_perimeter(v):
    imax = len(v) - 1
    perimeter = 0
    for i in range(0, imax):
        perimeter += ((v[i][0] - v[i + 1][0]) ** 2 + (v[i][1] - v[i + 1][1]) ** 2) ** 0.5
    return perimeter

# find radial length of sample points
def find_radial_length(sample, center):
    radlen = list()
    smax = len(sample)
    for i in range(0, smax):
        samplen = ((sample[i][0] - center[0]) ** 2 + (sample[i][1] - center[1]) ** 2) ** 0.5
        radlen.append(samplen)
    minradial = min(radlen)
    maxradial = max(radlen)
    return radlen, minradial, maxradial

# find radius of whole points
def find_radius(v, center):
    imax = len(v)
    radiussum = 0
    radius_list = list()
    for i in range(0, imax):
        radius = ((v[i][0] - center[0]) ** 2 + (v[i][1] - center[1]) ** 2) ** 0.5
        radius_list.append(radius)
        radiussum += radius
    radius = float(radiussum) / float(imax)
    maxradius = max(radius_list)
    normalized_radius = float(radius) / float(maxradius)
    return normalized_radius, maxradius

# find curvature
def get_curvature(x, y):
    curvlist = list()
    dx = np.array(np.gradient(x))
    dy = np.array(np.gradient(y))

    d2x_dt2 = np.gradient(dx)
    d2y_dt2 = np.gradient(dy)

    for i in range(len(dx)):
        divi = (dx[i] * dx[i] + dy[i] * dy[i]) ** 1.5
        if divi != 0:
            curvature = np.abs(d2x_dt2[i] * dy[i] - dx[i] * d2y_dt2[i]) / divi
            curvature = curvature**2
            curvlist.append(curvature)
        else:
            curvature = np.abs(d2x_dt2[i] * dy[i] - dx[i] * d2y_dt2[i])
            curvature = curvature**2
            curvlist.append(curvature)
    return curvlist

# find bending energy
def BendingEnergy(sample,samplex,sampley):

    imax = len(sample)
    curvature = get_curvature(samplex,sampley)
    sumcurv = sum(curvature)
    be = float(sumcurv)/float(imax)
    return curvature,be

# random generate angle with 0.01 interval
delta = 0.01
x = np.arange(0, 2*pi, delta)
y = np.arange(0, 2*pi, delta)

# get radius of circle
r = 200

# get centroid axis of circle
xc = 100
yc = 100

# get circle points
for i in range(len(x)):
    x[i] = xc + r*cos(x[i])
    y[i] = yc + r*sin(y[i])

# construct circle points by combining two random x and y lists
v = np.column_stack((x,y))

figure()
plt.plot(x,y)

samplestep = 1
sample = list()
samplex = list()
sampley = list()

# select sample by interval of samplestep
randIndex = np.arange(0,len(v),samplestep)
for i in randIndex:
    samplex.append(v[i-1][0])
    sampley.append(v[i-1][1])
    sample.append(v[i-1])

contourarea = np.abs(area(v))
realarea = pi*(r**2)
print 'right area:', realarea
print 'area:',contourarea,'\n'

Coorcentroid = centroid_for_polygon(v,contourarea)
realcentroid = (xc,yc)
print 'right centroid:',realcentroid
print 'centroid of contour:', Coorcentroid,'\n'

perimeter = find_perimeter(v)
realperimeter = 2*pi*r
print 'right perimeter:',realperimeter
print 'perimeter:', perimeter,'\n'

# find compactness
Compactness = float(perimeter**2)/float(contourarea)
realcompact = float(realperimeter**2)/float(realarea)
print 'right compactness', realcompact
print 'compactness:',Compactness, '\n'

radlen,minradial,maxradial = find_radial_length(sample,Coorcentroid)
print 'radial length list:',radlen
print 'minimum of radial length:',minradial
print 'maximum of radial length:',maxradial,'\n'

normradius,maxradius = find_radius(v,Coorcentroid)
print 'normalized radius:',normradius
print 'maximum radius:',maxradius,'\n'

# find difference between radial and radius
diff_rad = list()
numcom = 0
for i in range(0,len(sample)):
    normal_radial = float(radlen[i])/float(maxradial)
    compartworad = abs(normal_radial-normradius)
    diff_rad.append(compartworad)

    if compartworad <= 0.01:
        numcom +=1

#print 'difference between radial length and normalized radius:', diff_rad
print 'number of difference between radial length and normalized radius below 0.01:',numcom,'\n'

# find entropy of lesion contour
p = float(numcom)/float(len(sample))
#print p
if p >=1:
    Entropy = -(p*math.log(p))
elif p<=0:
    Entropy = -((1-p)*math.log(1-p))
else:
    Entropy = -(p*math.log(p)+(1-p)*math.log(1-p))
print 'entropy:',Entropy

# find ratio of minimum to maximum radial length
radialratio = float(minradial)/float(maxradial)
realradialratio = 1
print 'real radial ratio:',realradialratio
print 'ratio of minimum to maximum radial length:',radialratio, '\n'

curvature,bendingenergy = BendingEnergy(sample,samplex,sampley)
print 'curvature:',curvature
print 'bending energy:', bendingenergy

numsample = len(sample)
numv = len(v)
print 'number of samples:',numsample
print 'number of vertices:',numv
#show()


import numpy as np

def tanh(x):
    return np.tanh(x)

def tanh_prime(x):
    return 1 - np.tanh(x) ** 2


# -*- coding: utf-8 -*-
"""
uses the x, y data below to complete the tasks in parts 1-6 below.

1. Fit a linear curve to the data
2. Fit a cubic curve using the SciPy library
3. Find the area underneath the cubic curve over the domain of the data using the tools in SciPy
4. Plot the data, the linear fit, and the cubic fit in Matplotlib. Make sure to give the plot a title and an x and y label. Save this figure.
5. Put the area of the curve on the plot somewhere as text using Matplotlib
6. Use the Bayesian information criterion to justify which model (linear or cubic) is preferable. Include this justification in your journal entry for the week.

Created on Mon Jul 23 16:54:15 2018

@author: Sam Li
"""

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

def BIC(y, yhat, k, weight = 1):
    err = y - yhat
    sigma = np.std(np.real(err))
    n = len(y)
    B = n*np.log(sigma**2) + weight*k*np.log(n)
    return B

x = [ 1.,   1.5,  2.,   2.5,  3.,   3.5,  4.,   4.5,  5.,   5.5,  6.,   6.5,  7.,   7.5, 8.,   8.5,  9.,   9.5, 10. ]
y = [3.43, 4.94, 6.45, 9.22, 6.32, 6.11, 4.63, 8.95, 7.8, 8.35, 11.45, 14.71, 11.97, 12.46, 17.42, 17.0, 15.45, 19.15, 20.86]

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

coefficients_lin = np.polyfit(x, y, 1)
y_lin = np.poly1d(coefficients_lin)

coefficients_cubic = np.polyfit(x, y, 3)
y_cubic = np.poly1d(coefficients_cubic)

area = quad(y_cubic, np.min(x), np.max(x))

plt.plot(x, y_lin(x), 'r-', label='Linear')
plt.plot(x, y_cubic(x), 'g-', label='Cubic, Area = ' + str(area[0]))
plt.fill_between(x, y_cubic(x), alpha='0.2', color='green')
plt.xlabel('X')
plt.ylabel('Y')
plt.legend()
plt.title('Linear and Cubic Fit')
plt.savefig('SamLi_01.png')

lin_bic = BIC(y,y_lin(x),2)
cub_bic = BIC(y, y_cubic(x), 4)
print('Linear BIC = ', lin_bic)
print('Cubic BIC = ', cub_bic)


from sympy import S, zeros, Matrix, nsimplify, factorial, maximum, Interval, symbols, Function, ones, Integral, solve, legendre, chebyshevt
from ..genericas import matriz_inversa

import numpy as np

def polinomio_lagrange_integracion(f, x_vals=None, grado=3, var=symbols('x'), I=[0, 1]):
    """Aplica el método de lagrange para integrar una función.

    Args:
        f (funcion): Función a integrar.
        x_vals (list, optional): Lista de nodos donde se evaluará el polinomio. Si None, selecciona tantos nodos como grado, de manera equidistante. Defaults to None.
        grado (int, optional): Número de nodos. Defaults to 3.
        var (variable, optional): Variable de integración. Defaults to symbols('x').
        I (list, optional): Intervalo de integración. Defaults to [0, 1].

    Returns:
        p (polinomio): Polinomio de integración
        val (float): Valor aproximado de la integral, tras la evaluación de p en los nodos.
    """

    ff = Function('f')

    if x_vals is None:
        x_vals = np.linspace(I[0], I[1], grado)

    G = ones(len(x_vals), len(x_vals))
    for row in range(1, len(x_vals)):
        for col in range(len(x_vals)):
            G[row, col] = x_vals[col] ** row

    x_mat = nsimplify(Matrix([(I[1] ** (i + 1) - I[0] ** (i + 1)) / (i + 1) for i in range(len(x_vals))]), rational=True)

    # Con esto creamos el vector de valores de A
    a = nsimplify(matriz_inversa(G) * x_mat, rational=True)

    p, val = S(0), S(0)
    for i in range(len(x_vals)):
        p += a[i] * ff(x_vals[i])
        val += a[i] * f.subs(var, x_vals[i])

    return p, val

def polinomio_newton_integracion(f, x_vals=None, grado=3, var=symbols('x'), I=[0, 1]):
    """Aplica el método de lagrange para integrar una función usando las funciones w de Newton.

    Args:
        f (funcion): Función a integrar.
        x_vals (list, optional): Lista de nodos donde se evaluará el polinomio. Si None, selecciona tantos nodos como grado, de manera equidistante. Defaults to None.
        grado (int, optional): Número de nodos. Defaults to 3.
        var (variable, optional): Variable de integración. Defaults to symbols('x').
        I (list, optional): Intervalo de integración. Defaults to [0, 1].

    Returns:
        p (polinomio): Polinomio de integración
        val (float): Valor aproximado de la integral, tras la evaluación de p en los nodos.
    """

    ff = Function('f')

    if x_vals is None:
        x_vals = np.linspace(I[0], I[1], grado)

    # Creamos la lista de omegas, que tienen la expresión [w_1, w_2, w_3, etc]
    lista_w = [1, (var - x_vals[0])]
    for x_val in x_vals[1:]:
        lista_w.append(lista_w[-1] * (var - x_val))

    G = zeros(len(x_vals), len(x_vals))
    x_mat = zeros(len(x_vals), 1)
    for row in range(len(x_vals)):
        for col in range(row, len(x_vals)):
            if row == 0:
                G[row, col] = 1
            else:
                G[row, col] = lista_w[row].subs(var, x_vals[col])

        x_mat[row, 0] = Integral(lista_w[row], (var, I[0], I[1])).doit()

    # Con esto creamos el vector de valores de A
    a = matriz_inversa(G) * x_mat

    p, val = S(0), S(0)
    for i in range(len(x_vals)):
        p += a[i] * ff(x_vals[i])
        val += a[i] * f.subs(var, x_vals[i])

    return p, val

def integral_positiva_newton(omega, I, var, x_vals):
    # Esta función la creo para que calcule la integral del valor absoluto. Como las integrales son de la forma (x-x0)(x-x1)(x-x2)(x-x3)...
    # con xi diferentes y en orden creciente, la función va a tomar un valor negativo si el número de términos es impar, y positivo si par
    # para a < x_0, y de ahí va a ir alternando signos. Como pueden surgir casos diferentes (que I[0] sea x_vals[0]) y la casuistica cambia
    # para cada x_i, x_i+1 tomaremos la integral y evaluaremos el signo, para ponerlo a la integral de nuevo.
    integral = S(0)

    x_vals_I = [I[0]] + x_vals + [I[1]]
    for i in range(len(x_vals_I) - 1):
        a, b = x_vals_I[i], x_vals_I[i + 1]
        integral_i = Integral(omega, (var, a, b)).doit()

        if integral_i > 0:
            signo = 1
        elif integral_i < 0:
            signo = -1
        else:  # a = b y la integral es cero
            continue
        integral += signo * integral_i

    return integral

def error_cuadratura_integral(f, var=symbols('x'), x_vals=None, I=[0, 1], grado=2):
    """Calcula el error máximo de cuadratura de integral en base al polinomio de aproximación

    Args:
        f (funcion): Función a integrar.
        var (variable, optional): Variable de integración. Defaults to symbols('x').
        x_vals (list, optional): Lista de nodos donde se evaluará el polinomio. Si None, selecciona tantos nodos como grado, de manera equidistante. Defaults to None.
        I (list, optional): Intervalo de integración. Defaults to [0, 1].
        grado (int, optional): Número de nodos. Defaults to 3.

    Returns:
        e (float): Error de aproximación
        max_diff_f: Valor de máximo de diferencia (entre el factorial)
        int_omega: Valor de la integral de Newton
    """
    if x_vals is None:
        x_vals = np.linspace(I[0], I[1], grado)

    # Hallamos el primer término, el del máximo de la derivada
    diff_f = f
    for _ in range(len(x_vals)):
        diff_f = diff_f.diff(var)

    max_diff_f = nsimplify(maximum(diff_f, var, Interval(I[0], I[1])) / factorial(len(x_vals)), rational=True)

    # Ahora hallamos el de la integral
    omega = S(1)
    for x_val in x_vals:
        omega *= (var - x_val)

    int_omega = nsimplify(integral_positiva_newton(omega, I, var, x_vals), rational=True)

    e = max_diff_f * int_omega

    return e, max_diff_f, int_omega

def formulas_newton_cotes(f, orden=1, x_vals=None, y_vals=None, I=[0, 1], var=symbols('x')):
    """Calcula las fórmulas de Newton-Cotes para la resolución de la integral.

    Args:
        f (funcion): Función a integrar.
        orden (int): Orden de las fórmulas de Newton-Cotes.
        x_vals (list, optional): Lista de nodos donde se evaluará el polinomio. Si None, selecciona tantos nodos como grado, de manera equidistante. Defaults to None.
        y_vals (list, optional): Lista de valores de la función. Emplear si no hay una función dada.
        I (list, optional): Intervalo de integración. Defaults to [0, 1].
        var (variable, optional): Variable de integración. Defaults to symbols('x').

    Returns:
        Q (polinomio): Fórmula de Newton-Cotes simbólica.
        val (float): Evaluación de la fórmula de Newton-Cotes en los nodos de evaluación.
    """

    ff = Function('f')

    if x_vals is None:
        x_vals = np.linspace(I[0], I[1], orden + 1)
    if y_vals is None:
        y_vals = [f.subs(var, i) for i in x_vals]

    G = ones(len(x_vals), len(x_vals))
    for row in range(1, len(x_vals)):
        for col in range(len(x_vals)):
            G[row, col] = x_vals[col] ** row

    x_mat = nsimplify(Matrix([(I[1] ** (i + 1) - I[0] ** (i + 1)) / (i + 1) for i in range(len(x_vals))]), rational=True)

    # Con esto creamos el vector de valores de A
    a = nsimplify(matriz_inversa(G) * x_mat, rational=True)

    Q, val = S(0), S(0)
    for i in range(len(x_vals)):
        Q += a[i] * ff(nsimplify(x_vals[i], rational=True))
        val += nsimplify(a[i] * f.subs(var, x_vals[i]), rational=True)

    return Q, val

def error_trapecio(f, var=symbols('x'), I=[0, 1]):
    """Calcula el error máximo de cuadratura de integral usando las fórmulas de Newton-Cotes para el trapecio
    Args:
        f (funcion): Función a integrar.
        var (variable, optional): Variable de integración. Defaults to symbols('x').
        I (list, optional): Intervalo de integración. Defaults to [0, 1].

    Returns:
        e (float): Error de aproximación
        max_diff_f: Valor de máximo de diferencia (entre el factorial)
        int_omega: Valor de la integral de Newton
    """

    diff_f = f
    for _ in range(2):
        diff_f = diff_f.diff(var)

    max_diff_f = nsimplify(maximum(diff_f, var, Interval(I[0], I[1])), rational=True)

    factor_division = (I[1] - I[0]) ** 3 / 12

    e = max_diff_f * factor_division

    return e, max_diff_f, factor_division

def error_simpson(f, var=symbols('x'), I=[0, 1]):
    """Calcula el error máximo de cuadratura de integral usando las fórmulas de Newton-Cotes para Simpson
    Args:
        f (funcion): Función a integrar.
        var (variable, optional): Variable de integración. Defaults to symbols('x').
        I (list, optional): Intervalo de integración. Defaults to [0, 1].

    Returns:
        e (float): Error de aproximación
        max_diff_f: Valor de máximo de diferencia (entre el factorial)
        int_omega: Valor de la integral de Newton
    """

    diff_f = f
    for _ in range(4):
        diff_f = diff_f.diff(var)

    max_diff_f = nsimplify(maximum(diff_f, var, Interval(I[0], I[1])), rational=True)

    factor_division = ((I[1] - I[0]) / 2) ** 5 / 90

    e = max_diff_f * factor_division

    return e, max_diff_f, factor_division

def formulas_newton_cotes_m(f, orden=1, m=10, I=[0, 1], var=symbols('x')):
    """Calcula las fórmulas de Newton-Cotes para la resolución de la integral usando m particiones del intervalo.

    Args:
        f (funcion): Función a integrar.
        orden (int): Orden de las fórmulas de Newton-Cotes.
        m (int): Número de particiones del intervalo. Si m=1 la funcion es idéntica a formulas_newton_cotes
        I (list, optional): Intervalo de integración. Defaults to [0, 1].
        var (variable, optional): Variable de integración. Defaults to symbols('x').

    Returns:
        Q (polinomio): Fórmula de Newton-Cotes simbólica.
        val (float): Evaluación de la fórmula de Newton-Cotes en los nodos de evaluación.
    """

    lista_vals, lista_Q = [], []
    lista_m = np.linspace(I[0], I[1], m + 1)

    for m_i in range(m):
        m0, mf = lista_m[m_i], lista_m[m_i + 1]
        Q, val = formulas_newton_cotes(f, orden=orden, I=[m0, mf], var=var)
        lista_Q.append(Q)
        lista_vals.append(val)

    Q = sum(lista_Q)
    val = sum(lista_vals)

    return Q, val

def error_trapecio_m(f, var=symbols('x'), I=[0, 1], m=2):
    """Calcula el error máximo de cuadratura de integral usando las fórmulas de Newton-Cotes para el trapecio
    Args:
        f (funcion): Función a integrar.
        var (variable, optional): Variable de integración. Defaults to symbols('x').
        I (list, optional): Intervalo de integración. Defaults to [0, 1].
    m (int): Número de particiones del intervalo. Si m=1 la funcion es idéntica a error_trapecio
    Returns:
        e (float): Error de aproximación
        max_diff_f: Valor de máximo de diferencia (entre el factorial)
        int_omega: Valor de la integral de Newton
    """
    diff_f = f
    for _ in range(2):
        diff_f = diff_f.diff(var)

    a, b = I  # Me da fallo random si no hago esto!
    max_diff_f = nsimplify(maximum(diff_f, var, Interval(a, b)), rational=True)

    factor_division = (I[1] - I[0]) ** 3 / (12 * (m ** 2))

    e = max_diff_f * factor_division

    return e, max_diff_f, factor_division

def error_simpson_m(f, var=symbols('x'), I=[0, 1], m=2):
    """Calcula el error máximo de cuadratura de integral usando las fórmulas de Newton-Cotes para Simpson
    Args:
        f (funcion): Función a integrar.
        var (variable, optional): Variable de integración. Defaults to symbols('x').
        I (list, optional): Intervalo de integración. Defaults to [0, 1].
    m (int): Número de particiones del intervalo. Si m=1 la funcion es idéntica a error_trapecio
    Returns:
        e (float): Error de aproximación
        max_diff_f: Valor de máximo de diferencia (entre el factorial)
        int_omega: Valor de la integral de Newton
    """
    diff_f = f
    for _ in range(4):
        diff_f = diff_f.diff(var)

    a, b = I  # Me da fallo random si no hago esto!
    max_diff_f = nsimplify(maximum(diff_f, var, Interval(a, b)), rational=True)

    factor_division = (I[1] - I[0]) ** 5 / (90 * (m ** 4) * (2 ** 5))

    e = max_diff_f * factor_division

    return e, max_diff_f, factor_division

def formulas_gauss(f, I=[-1, 1], var=symbols('x'), grado=3, modo='legendre'):
    """Calcula las fórmulas de Newton-Cotes para la resolución de la integral nodos de Chebyshev o Legendre.

    Args:
        f (funcion): Función a integrar.
        I (list, optional): Intervalo de integración. Defaults to [0, 1].
        var (variable, optional): Variable de integración. Defaults to symbols('x').
        grado (int): Grado del polinomio de Legendre o Chebyshev

    Returns:
        Q (polinomio): Fórmula de Gauss simbólica.
        val (float): Evaluación de la fórmula de Gauss en los nodos de evaluación.
    """

    ff = Function('f')

    x_vals = []
    if modo == 'legendre':
        x_vals = sorted(solve(legendre(grado, var)))
    elif modo == 'chebyshev':
        x_vals = sorted(solve(chebyshevt(grado, var)))

    G = ones(len(x_vals), len(x_vals))
    for row in range(1, len(x_vals)):
        for col in range(len(x_vals)):
            G[row, col] = x_vals[col] ** row

    x_mat = nsimplify(Matrix([((1) ** (i + 1) - (-1) ** (i + 1)) / (i + 1) for i in range(len(x_vals))]), rational=True)

    # Con esto creamos el vector de valores de A
    a = nsimplify((I[1] - I[0]) / 2 * (matriz_inversa(G) * x_mat), rational=True)

    # Aquí efectuamos el cambio de variable:
    cambio_var = (I[1] - I[0]) / 2 * var + (I[1] + I[0]) / 2
    Q, val = S(0), S(0)
    for i in range(len(x_vals)):
        Q += a[i] * ff(nsimplify(cambio_var.subs(var, x_vals[i]), rational=True))
        val += nsimplify(a[i] * f.subs(var, cambio_var.subs(var, x_vals[i])), rational=True)

    return Q, val


# Gradient Descent, Newton Descent, Conjugate Descent
import numpy as np
from typing import *

class GradientDescent:
    def __init__(self, func: Callable, grad: Callable, lr: float, num_iters: int, threshold: float = 0.):
        """
        Initialize gradient descent.
        :param func: Function to minimize, should accept an np.ndarray and return a scalar.
        :param grad: Function that returns the gradient of func, should accept an np.ndarray and return an np.ndarray.
        :param lr: Learning rate.
        :param num_iters: Number of iterations to perform.
        :param threshold: Threshold for convergence and early stopping.
        """
        assert callable(func)
        assert callable(grad)
        assert lr > 0
        assert num_iters >= 0
        assert threshold >= 0
        self.f = func
        self.g = grad
        self.lr = lr
        self.num_iters = num_iters
        self.threshold = threshold

        # Data for plotting
        self.parameters = []
        self.function_values = []

    def optimize(self, init: np.ndarray):
        """
        Run gradient descent.
        :param init: Initial value for starting gradient descent. Should be able to be called with func and grad.
        """
        x = init
        prev_f_x = None
        for _ in range(self.num_iters):
            self.parameters.append(x)

            # Get the function value
            f_x = self.f(x)
            self.function_values.append(f_x)

            # Check for convergence
            if prev_f_x is not None and np.linalg.norm(prev_f_x - x) < self.threshold:
                break

            # Make a step towards the optimal solution
            x -= self.lr * self.g(x)

            # Prepare for the next iteration
            prev_f_x = f_x

class NewtonDescent:
    def __init__(self, func: Callable, grad: Callable, hess: Callable, num_iters: int, threshold: float = 0.):
        """
        Initialize newton descent.
        :param func: Function to minimize, should accept an np.ndarray and return a scalar.
        :param grad: Function that returns the gradient of func, should accept an np.ndarray and return an np.ndarray.
        :param hess: Function that returns the hessian of func, should accept an np.ndarray and return an np.ndarray.
        :param num_iters: Number of iterations to perform.
        :param threshold: Threshold for convergence and early stopping.
        """
        assert callable(func)
        assert callable(grad)
        assert callable(hess)
        assert num_iters >= 0
        assert threshold >= 0
        self.f = func
        self.g = grad
        self.h = hess
        self.num_iters = num_iters
        self.threshold = threshold

        # Data for plotting
        self.parameters = []
        self.function_values = []

    def optimize(self, init: np.ndarray):
        """
        Run newton descent.
        :param init: Initial value for starting newton descent. Should be able to be called with func, grad, and hess.
        """
        x = init
        prev_f_x = None
        for _ in range(self.num_iters):
            self.parameters.append(x)

            # Get the function value
            f_x = self.f(x)
            self.function_values.append(f_x)

            # Check for convergence
            if prev_f_x is not None and np.linalg.norm(prev_f_x - x) < self.threshold:
                break

            # Make a step towards the optimal solution
            x -= np.linalg.inv(self.h(x)) @ self.g(x)

            # Prepare for the next iteration
            prev_f_x = f_x

class ConjugateGradientDescent:
    def __init__(self, A: np.ndarray, b: np.ndarray, loss: Callable, lr: float, num_iters: int, threshold: float = 0.):
        """
        Initialize conjugate gradient descent, which solves the problem Ax = b.
        Implementation based on https://www.cs.cmu.edu/~quake-papers/painless-conjugate-gradient.pdf
        :param A: A, must be symmetric positive-definite.
        :param b: b
        :param loss: Optional function that converts A and b into a loss value. Called as func(A, x, b).
        :param lr: Learning rate.
        :param num_iters: Number of iterations to perform.
        :param threshold: Threshold for convergence and early stopping.
        """
        assert A == A.T  # Symmetric
        assert all(np.linalg.eigvals(A) > 0)  # PSD
        assert callable(loss)
        assert lr > 0
        assert num_iters >= 0
        assert threshold >= 0
        self.A = A
        self.b = b
        self.loss = loss
        self.lr = lr
        self.num_iters = num_iters
        self.epsilon = threshold

        # Data for plotting
        self.parameters = []
        self.loss_values = []

    def optimize(self, init: np.ndarray):
        """
        Run conjugate gradient descent.
        :param init: Initial value for starting conjugate gradient descent. Should be able to be called with func and
            grad.
        """
        x = init
        r = self.b - (self.A @ x)
        d = r
        delta_new = r.T @ r
        delta_0 = delta_new
        for i in range(self.num_iters):
            # Get the loss value
            self.parameters.append(x)
            loss = self.loss(self.A, x, self.b)
            self.loss_values.append(loss)

            # Check for convergence
            if delta_new < (self.epsilon ** 2) * delta_0:
                break

            # Make a step towards the optimal solution
            q = self.A @ d
            alpha = delta_new / (d.T @ q)
            x = x + (alpha * d)

            # Refresh r to avoid floating point error
            if i % 50 == 0:
                r = self.b - (self.A @ x)
            else:
                r = r - (alpha * q)

            # Prepare for the next iteration
            delta_old = delta_new
            delta_new = r.T @ r
            beta = delta_new / delta_old
            d = r + (beta * d)


import numpy as np

def sum_squared_error(y, t):
    return 0.5 * np.sum((y - t) ** 2)

if __name__ == "__main__":
    t = 2
    t_hot = np.zeros(10)
    t_hot[t] = 1
    y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
    y = np.array(y)
    print(sum_squared_error(y, t_hot))
    y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
    y = np.array(y)
    print(sum_squared_error(y, t_hot))


import numpy as np
def featureMap(x1, x2, m):
    degree = 6
    out = np.ones((m, 1))

    # Example for instance of features for only power of 3
    # x1^3, x1^2 * x2, x1 * x2^2, x2^3
    # We can see that powers x1 decreases from 3 to 0,
    # powers of x2 increases from 0 to 3
    # So first loop increases from 1 to degree
    # second loop increases from 0 to counter of first loop
    for i in range(1, degree+1):
        for j in range(i+1):
            new_feature_vec = (x1 ** (i-j)) * (x2 ** j)
            out = np.concatenate((out, new_feature_vec), axis=1)

    return out


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

def mandelbrot(x, y, threshold):

    # initial conditions
    c = complex(x, y)
    z = complex(0, 0)

    for i in range(threshold):
        z = z**2 + c
        if abs(z) > 4.:  # it diverged
            return i

    return threshold - 1  # it didn't diverge

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

x_start, y_start = -2, -1.5  # an interesting region starts here
width, height = 3, 3  # for 3 units up and right
density_per_unit = 250  # how many pixles per unit

# real and imaginary axis
re = np.linspace(x_start, x_start + width, width * density_per_unit )
im = np.linspace(y_start, y_start + height, height * density_per_unit)

fig = plt.figure(figsize=(10, 10))  # instantiate a figure to draw
ax = plt.axes()  # create an axes object

def animate(i):
    ax.clear()  # clear axes object
    ax.set_xticks([], [])  # clear x-axis ticks
    ax.set_yticks([], [])  # clear y-axis ticks

    X = np.empty((len(re), len(im)))  # re-initialize the array-like image
    threshold = round(1.15**(i + 1))  # calculate the current threshold

    # iterations for the current threshold
    for i in range(len(re)):
        for j in range(len(im)):
            X[i, j] = mandelbrot(re[i], im[j], threshold)

    # associate colors to the iterations with an iterpolation
    img = ax.imshow(X.T, interpolation="bicubic", cmap='magma')
    return [img]

anim = animation.FuncAnimation(fig, animate, frames=45, interval=120, blit=True)
anim.save('mandelbrot.gif',writer='imagemagick')


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Feb  5 12:34:29 2021

@author: xiongn
"""

import numpy as np
import matplotlib.pyplot as plt

class ProbabilityModel:

    # Returns a single sample (independent of values returned on previous calls).
    # The returned value is an element of the model's sample space.
    def sample(self):
        pass

# The sample space of this probability model is the set of real numbers, and
# the probability measure is defined by the density function
# p(x) = 1/(sigma * (2*pi)^(1/2)) * exp(-(x-mu)^2/2*sigma^2)
class UnivariateNormal(ProbabilityModel):

    # Initializes a univariate normal probability model object
    # parameterized by mu and (a positive) sigma
    def __init__(self,mu,sigma):
        self.mu = mu
        self.sigma = sigma

    def sample(self):
        u1 = np.random.uniform(0, 1)
        u2 = np.random.uniform(0, 1)
        z = self.mu + np.sqrt(-2 * np.log(u1)) * np.cos(2 * np.pi * u2) * self.sigma
        return z

    def plot_his(self):
        z = []
        for i in range(5000):
            z.append(self.sample())
        plt.hist(z)
        plt.show()

# The sample space of this probability model is the set of D dimensional real
# column vectors (modeled as numpy.array of size D x 1), and the probability
# measure is defined by the density function
# p(x) = 1/(det(Sigma)^(1/2) * (2*pi)^(D/2)) * exp( -(1/2) * (x-mu)^T * Sigma^-1 * (x-mu) )
class MultiVariateNormal(ProbabilityModel):

    # Initializes a multivariate normal probability model object
    # parameterized by Mu (numpy.array of size D x 1) expectation vector
    # and symmetric positive definite covariance Sigma (numpy.array of size D x D)
    def __init__(self,Mu,Sigma):
        self.Mu = Mu
        self.Sigma = Sigma

    def sample(self):
        L = np.linalg.cholesky(self.Sigma)
        u = []
        for i in range(self.Mu.size):
            u.append(UnivariateNormal(0, 1).sample())
        z = self.Mu + np.dot(L, u)
        return z

    def plot_his(self):
        x = []
        y = []
        for i in range(5000):
            z = self.sample()
            x.append(z[0])
            y.append(z[1])
        plt.scatter(x, y)
        plt.show()

# The sample space of this probability model is the finite discrete set {0..k-1}, and
# the probability measure is defined by the atomic probabilities
# P(i) = ap[i]
class Categorical(ProbabilityModel):

    # Initializes a categorical (a.k.a. multinom, multinoulli, finite discrete)
    # probability model object with distribution parameterized by the atomic probabilities vector
    # ap (numpy.array of size k).
    def __init__(self,ap):
        self.ap = ap

    def sample(self):
        cum = np.cumsum(self.ap)
        u = np.random.uniform(0, 1)
        for i in range(len(self.ap)):
            if u < cum[i]:
                return i

    def plot_his(self):
        z = []
        for i in range(5000):
            z.append(self.sample())
        plt.hist(z)
        plt.show()

# The sample space of this probability model is the union of the sample spaces of
# the underlying probability models, and the probability measure is defined by
# the atomic probability vector and the densities of the supplied probability models
# p(x) = sum ad[i] p_i(x)
class MixtureModel(ProbabilityModel):

    # Initializes a mixture-model object parameterized by the
    # atomic probabilities vector ap (numpy.array of size k) and by the tuple of
    # probability models pm
    def __init__(self,ap,pm):
        self.ap = ap
        self.pm = pm

    def sample(self):
        rand = np.random.uniform(0, 1);
        pts = self.pm[int(rand*len(self.ap))].sample();
        return pts;

    def plot(self):
        x = []
        y = []
        for i in range(5000):
            z = self.sample()
            x.append(z[0])
            y.append(z[1])
        plt.scatter(x, y)
        plt.show()

if __name__ == '__main__':
    U = UnivariateNormal(1, 1)
    U.plot_his()
    C = Categorical(np.array([0.1, 0.1, 0.3, 0.3, 0.2]))
    C.plot_his()
    M = MultiVariateNormal(np.array([1, 1]), np.array([[1., 0.5], [0.5, 1.]]))
    M.plot_his()
    cov = np.array([[1., 0], [0, 1.]])
    pm = [MultiVariateNormal(np.array([1, 1]), cov),MultiVariateNormal(np.array([-1, 1]), cov),MultiVariateNormal(np.array([1, -1]), cov),
          MultiVariateNormal(np.array([-1, -1]), cov)]
    pm=tuple(pm)
    Mix = MixtureModel(np.array([.25, .25, .25, .25]), pm)
    Mix.plot()


"""Functions for data randomization."""
import numpy as np

def random_array(min_val: float, max_val: float, size: int) -> np.ndarray:
    """
    Creates a random array.

    :param min_val: Minimum value for array item.
    :param max_val: Maximum value for array item.
    :param size: Size of the array.
    :return: array
    """
    return np.random.uniform(min_val, max_val, size=size)

def random_sigma(average_sigma: float, size: int) -> np.ndarray:
    """
    Creates random standard deviation (aka. sigma).

    :param average_sigma: average value for sigma.
    :param size: Size of the returned array.
    :return: array of sigma values.
    """
    return np.random.exponential(average_sigma, size=size)

def random_error(scales: np.ndarray) -> np.ndarray:
    """
    Generates random errors based on an array of scales.

    :param scales: array of standard deviations.
    :return: errors array.
    """
    return np.random.normal(scale=scales)


import numpy as np
from matplotlib import pyplot as plt

def cost(x, theta, y):
    return 1/(2*len(x)) * np.sum((np.dot(x, theta) - y) ** 2)

def gradient_descent(x, theta, y, alpha, iterations):
    j_vals = np.zeros((iterations, 1))
    new_theta = np.copy(theta)

    for i in range(0, iterations):
        new_theta -= alpha/x.shape[0] * np.dot(x.T, (np.dot(x, new_theta) - y))
        j_vals[i] = cost(x, new_theta, y)

    return new_theta, j_vals

def predict(x, theta):
    return np.dot(x, theta)[0, 0]

def normalize(x):
    return (x - x.mean())/x.std(), x.mean(), x.std()

def plot_cost(j_vals):
    print('Displaying cost history...')
    plt.plot(j_vals)
    plt.title('Cost History over 1500 iterations of GD')
    plt.legend('J')
    plt.xlabel('N° Iterations')
    plt.ylabel('Cost')
    plt.show()


# Constants for project 1
from numpy import array

FLOYD = 1/16*array([[0,0,7],
                    [3,5,1]])

STEVENSON = 1/200*array([[0 , 0, 0, 0, 0,32, 0],
                         [12, 0,26, 0,30, 0,16],
                         [0 ,12, 0,26, 0,12, 0],
                         [5 , 0,12, 0,12, 0, 5]])

BURKES = 1/32*array([[0,0,0,8,4],
                     [2,4,8,4,2]])

SIERRA = 1/32*array([[0,0,0,5,3],
                     [2,4,5,4,2],
                     [0,2,3,2,0]])

STUCKI = 1/42*array([[0,0,0,8,4],
                     [2,4,8,4,2],
                     [1,2,4,2,1]])

JARVIS = 1/48*array([[0,0,0,7,5],
                     [3,5,7,5,3],
                     [1,3,5,3,1]])

DISTS = [FLOYD,STEVENSON,BURKES,SIERRA,STUCKI,JARVIS]
NAMES = ['FLOYD','STEVENSON','BURKES','SIERRA','STUCKI','JARVIS']

def select_dist(name):
    dist = DISTS if name.lower()=='all' else [DISTS[NAMES.index(name.upper())]]
    return dist


# -*- coding: utf-8 -*-
"""
Spyder Editor

This is a temporary script file.
"""

import random as rd
import numpy as np
import seaborn as sn
import pandas as pd

mu1 = -1
mu2 = 3
sig1 = 0.5
sig2 = 1
N = 100
np.random.seed(10)
x11=np.random.randn(N,1)*sig1 + mu1
x12=np.random.randn(N,1)*sig1 + mu1+3
x21=np.random.randn(N,1)*sig2 + mu2
x22=np.random.randn(N,1)*sig2 + mu2+3
c = np.vstack((-np.ones((N,1)), np.ones((N,1))))
x1 = np.hstack((x11,x12))
x2 = np.hstack((x21,x22))

X = np.hstack( (np.vstack( (x1,x2) ),c) )
np.random.shuffle(X)
dataset = pd.DataFrame(data=X, columns=['x','y','c'])

#Visualize first your dataset and then write the Kmean algorithm