# Alexis Mendoza Valencia
# Estudiante Tecnológico de Monterrey, campus guadalajara

# Convergencia

"""
Nota: Para explicar este método recomiendo utilizar
      una pagina web llamada Cocalc para correr el metodo.
      Al menos es la pagina que yo utilice, pues ya tiene
      incluidas las librerias necesarias: numpy y mathplotlib.
      La liga es: cocalc.com
"""

import numpy as np
import matplotlib.pyplot as plt

"""
Definicion: La convergencia es un termino que se utiliza en
            limites. Decimos que una funcion converge a un
            valor cuando se acerca hasta este.
            Por ejemplo, si graficamos un logaritmo natural,
            podemos ver que si el numero se hace mas pequeño
            el valor tiende a cero. En este caso se acerca,
            mas nunca lo toca (sabemos que ln(0) no esta definido.

            Vamos a ver ejemplos entonces.
"""

import math
def logaritmo(x):
    return math.log(x)

def convergencia(funcion, valor_inferior, valor_superior):
    valores_X = np.linspace(valor_inferior, valor_superior, 100)
    valores_Y = [funcion(valores_X[i]) for i in range(len(valores_X))]

    plt.plot(valores_X, valores_Y)
    plt.grid()
    plt.show()

print("Ejemplo 1")
convergencia(logaritmo, 0.01, 10)
convergencia(logaritmo, 0.000001, 1)
"""
Veremos entonces al correr el programa que mientras el valor inferior
sea mas pequeño tendremos un valor de Y que se acerca cada vez mas a cero.

Pero ahora, podemos ver esto de diferentes formas. Un ejemplo es la raiz
de dos.

Si tenemos que x = raiz(2)
podemos decir que x^2 = 2
Por lo tanto, x^2 - 2 = 0

Estariamos buscando entonces el valor mas pequeño para el cual es valida
esta funcion, que buscamos que tenga convergencia a cero.
"""

def raiz_dos(x):
    return abs(x**2 - 2)

print("Ejemplo 2")
convergencia(raiz_dos, 0, 2)
convergencia(raiz_dos, 1.25, 1.75)
convergencia(raiz_dos, 1.4, 1.44)

"""
Y asi, poco a poco, nos acercamos al valor deseado.
"""


import math
import numpy as np

def evaluate(job_id, params):

    x = params['X']
    y = params['Y']

    #print 'Evaluating at (%f, %f)' % (x, y)

    # if x < 0 or x > 5.0 or y > 5.0:
    #     return np.nan
    # Feasible region: x in [0,5] and y in [0,5]

    obj1 = michalewicz(75, x, y)
    obj2 = michalewicz(100, x, y)
    obj3 = beale(x, y)
    obj4 = stybilinski(x, y)

    return {
        "michalewicz_75"        : obj1,
        "michalewicz_100"       : obj2,
        "beale"                 : obj3,
        "stybilinski"           : obj4,
    }

def michalewicz(m,x,y):
    x = scale_from_unity(x,0,np.pi)
    y = scale_from_unity(y,0, np.pi)
    return -1*(np.sin(x)*np.sin(x**2/np.pi)**(2*m) + np.sin(y)*np.sin(2*y**2/np.pi)**(2*m))

def easom(x,y):
    x = scale_from_unity(x,-100, 100)
    y = scale_from_unity(y,-100, 100)
    return -np.cos(x)*np.cos(y)*np.exp(-(x-np.pi)**2 - (y-np.pi)**2)

def beale(x,y):
    x = scale_from_unity(x,-4.5, 4.5)
    y = scale_from_unity(y,-4.5, 4.5)
    return ((1.5-x+x*y)*2 + (2.25-x+x*y**2)**2 + (2.625-x+x*y**3)**2)/10000

def stybilinski(x,y):
    x = scale_from_unity(x,-5, 5)
    y = scale_from_unity(y,-5, 5)
    return (x**4 + y**4 - 16*(x**2 + y**2) + 5*(x + y))/100

def gramacy(x,y):
    x = scale_from_unity(x, -2, 6)
    y = scale_from_unity(y, -2, 6)
    return x*np.exp(-x**2 -y**2)

def griewank(x,y):
    x = scale_from_unity(x, -600, 600)
    y = scale_from_unity(y, -600, 600)
    return (x**2 + y**2)/4000 - np.cos(x)*np.cos(y/np.sqrt(2))+1

def branin(x,y):
    x = scale_from_unity(x, -10, 10)
    y = scale_from_unity(y, -10, 10)

    return float(np.square(y - (5.1/(4*np.square(math.pi)))*np.square(x) + (5/math.pi)*x- 6) + 10*(1-(1./(8*math.pi)))*np.cos(x) + 10)

def levy(x,y):
    x = scale_from_unity(x, -5, 10)
    y = scale_from_unity(y, 0, 15)
    w1 = (x-1)/4 + 1
    w2 = (y-1)/4 + 1
    return np.square(np.sin(np.pi*w1)) + (w2-1)**2*(1 + np.sin(2* np.pi*w2)**2)

scale = lambda x, a, b, a2, b2: (x-a)*(b2-a2)/(b-a)+a2
def scale_from_unity(x, a, b):
    return scale(x, -1,1,a,b)

def main(job_id, params):
    try:
        return evaluate(job_id, params)
    except Exception as ex:
        print ex
        print 'An error occurred in parabolic.py'
        return np.nan


import numpy as np

def sigmoid(X):
    """
    Apply Sigmoid on X Vector.

    PARAMETERS
    ==========

    X: ndarray(dtype=float, ndim=1)
        Array containing Input Values.

    RETURNS
    =======

    ndarray(dtype=float,ndim=1)
        Output Vector after Vectorised Operation.
    """
    return 1/(1 + np.exp(-X))

def tanh(X):
    """
    Apply Inverse of Tan on X Vector.

    PARAMETERS
    ==========

    X: ndarray(dtype=float, ndim=1)
        Array containing Input Values.

    RETURNS
    =======

    ndarray(dtype=float,ndim=1)
        Output Vector after Vectorised Operation.
    """
    return np.tanh(X)

def softmax(X):
    """
    Apply Softmax on X Vector.

    PARAMETERS
    ==========

    X: ndarray(dtype=float, ndim=1)
        Array containing Input Values.
    Sum: float
        Sum of values of Input Array.

    RETURNS
    =======

    ndarray(dtype=float,ndim=1)
        Output Vector after Vectorised Operation.
    """
    Sum = np.sum(np.exp(X))
    return np.exp(X)/Sum

def softsign(X):
    """
    Apply Softsign on X Vector.

    PARAMETERS
    ==========

    X: ndarray(dtype=float, ndim=1)
        Array containing Input Values.

    RETURNS
    =======

    ndarray(dtype=float,ndim=1)
        Output Vector after Vectorised Operation.
    """
    return X/(np.abs(X) + 1)

def relu(X):
    """
    Apply Rectified Linear Unit on X Vector.

    PARAMETERS
    ==========

    X: ndarray(dtype=float, ndim=1)
        Array containing Input Values.

    RETURNS
    =======

    ndarray(dtype=float,ndim=1)
        Output Vector after Vectorised Operation.
    """
    return np.maximum(0, X)

def leakyRelu(X):
    """
    Apply Leaky Rectified Linear Unit on X Vector.

    PARAMETERS
    ==========

    X: ndarray(dtype=float, ndim=1)
        Array containing Input Values.

    RETURNS
    =======

    ndarray(dtype=float,ndim=1)
        Output Vector after Vectorised Operation.
    """
    return np.maximum(0.01*X, X)

def elu(X, alpha=1.0):
    """
    Apply Exponential Linear Unit on X Vector.

    PARAMETERS
    ==========

    X: ndarray(dtype=float, ndim=1)
        Array containing Input Values.
    alpha: float
        Curve Constant for Values of X less than 0.

    RETURNS
    =======

    ndarray(dtype=float,ndim=1)
        Output Vector after Vectorised Operation.
    """
    assert(alpha > 0)
    return np.maximum(0, X) + np.minimum(0, alpha*(np.exp(X) - 1))


import numpy as np
import matplotlib.pyplot as plt

# Función de Rosembrock
def Rosembrock(x, params = {}):
    '''
    Parámetros
    -----------
        n    : dimensión de muestra
        x    : vector de valores [x_1, x_2, ..., x_n]
    Regresa
    -----------
        fx : Evaluación en la función de Rosembrock
    '''
    n  = x.shape[0]
    fx = 0.0

    for i in range(n-1) :
        fx += 100.0 * (x[i+1] - x[i]* x[i]) * (x[i+1] - x[i]* x[i])
        fx += (1 - x[i])*(1 - x[i])

    return fx

def Rosembrock_Grad(x, params = {}) :
    '''
    Calcula el gradiente de la función de Rosembrock

    Parámetros
    -----------
        x   : Vector de valores [x_1, x_2, ..., x_n]
    Regresa
    -----------
        Gfx : Vector gradiente [D_x_1 f(x), D_x_2 f(x), ..., D_x_n (fx)]

    '''

    n   = x.shape[0]
    Gfx = np.zeros(n, dtype = np.float64)

    # Gfx[0]  = - 400.0 * (x[1] - x[0]* x[0])* x[0] - 2.0 * ( 1- x[0])

    # val = [i+1 for i in range(n-1)]
    for i in range(n-1) :
        Gfx[i] = -400.0 * x[i+1]*x[i] + 400.0 * x[i]**3 + 2*x[i] - 2.0

    Gfx[n-1] = 200.0 * (x[n-1] - x[n-2]**2)

    return Gfx

def Rosembrock_Hess(x, params = {}) :
    '''
    Calcula la matrix Hessiana de la función de Rosembrock

    Parámetros
    -----------
        x  : Array de valores [x_1, x_2, ..., x_n]
    Regresa
    -----------
        Hf : Matrix Hesianna
    '''

    n = x.shape[0]
    Hf = np.zeros((n,n), dtype = np.float64)

    for i in range(n-1) :
        Hf[i][i]   = 1200.0 * x[i] ** 2 - 400.0 * x[i+1] + 2.0
        Hf[i][i+1] = - 400.0 * x[i]
    Hf[n-1][n-2] = -400.0 * x[n-2]
    Hf[n-1][n-1] = 200.0

    return Hf
# Función de Wood

def Wood(x, params = {}) :
    '''
    Calcula el valor de la función de Wood f(x)
    Parámetros
    -----------
         x: Array de valores [x_1, x_2, ..., x_n]
    Regresa
    -----------
        fx : Valor f(x)
    '''
    # Reviso Dimensión
    if (x.shape[0] != 4):
        print('Revisar vector x')
        return

    fx  = 100.0 * (x[0] * x[0] - x[1]) *  (x[0] * x[0] - x[1])
    fx += (x[2]* x[2] - 1.0) * (x[2]* x[2] - 1.0)
    fx += 90.0 * (x[2]*x[2] - x[3]) * (x[2]*x[2] - x[3])
    fx += 10.1 * ( (x[1] - 1.0) * (x[1] - 1.0) + (x[3] - 1.0) * (x[3] - 1.0) )
    fx += 19.8 * (x[1] - 1.0) * (x[3] - 1.0)

    return fx

def Wood_Grad(x, params = {}) :
    '''
    Calcula el gradiente de la función de Wood
    Parámetros
    -----------
         x  : Array de valores [x_1, x_2, ..., x_n]
    Regresa
    -----------
        Gfx : Vector gradiente Gfx
    '''

    # Reviso Dimensión
    if (x.shape[0] != 4):
        print('Revisar vector x')
        return

    n   = x.shape[0]
    Gfx = np.zeros(n, dtype = np.float64)

    Gfx[0] = 400.0 * (x[0]* x[0] - x[1]) * x[0] + 2.0 * (x[0] - 1.0)
    Gfx[1] = - 200.0 * (x[0]* x[0] - x[1]) + 20.2 * (x[1] - 1.0) + 19.8 * (x[3] - 1.0)
    Gfx[2] = 2.0 * (x[2] - 1.0) + 360.0 * (x[2]* x[2] - x[3]) * x[2]
    Gfx[3] = -180.0 * (x[2]* x[2] - x[3]) + 20.2 * (x[3] - 1.0) + 19.8 * (x[1] - 1)

    return Gfx

def Wood_Hess(x, params = {}) :
    '''
    Calcula matrix Hessiana de la función de Wood
    Parámetros
    -----------
         x  : Array de valores [x_1, x_2, ..., x_n]
    Regresa
    -----------
        Hfx : Matrix Hessiana Hfx
    '''
    # Reviso Dimensión
    if (x.shape[0] != 4):
        print('Revisar vector x')
        return

    n   = x.shape[0]

    Hfx = np.zeros((n, n), dtype=np.float64)

    Hfx[0][0] = 1200.0 * x[0]*x[0] - 400.0 * x[1] + 2.0
    Hfx[0][1] = Hfx[1][0] = - 400.0 * x[0]
    Hfx[1][1] = Hfx[3][3] = 220.2
    Hfx[2][2] = 1080.0 * x[2]* x[2] - 360 * x[3] + 2.0
    Hfx[3][1] = Hfx[1][3] = 19.8
    Hfx[3][2] = Hfx[2][3] = - 360.0 * x[2]

    return Hfx

def Branin(x, params = {}) :
  '''
  Calcula valor de la función de Branin
  '''
  n = x.shape[0]
  a = params['a']
  b = params['b']
  c = params['c']
  r = params['r']
  s = params['s']
  t = params['t']

  f  = a * (x[1] - b * x[0]**2 + c * x[0] - r)**2
  f += s * (1.0 - t) * np.cos(x[0]) + s

  return f

def Branin_Grad (x, params = {}) :
  n = x.shape[0]
  a = params['a']
  b = params['b']
  c = params['c']
  r = params['r']
  s = params['s']
  t = params['t']

  f_grad = np.zeros(2, dtype = np.float64)

  f_grad[0]  = 2.0 * a * (x[1] - b * x[0]**2 + c * x[0] - r) * (-2.0 * b * x[0] + c)
  f_grad[0] -= s * (1.0 - t) * np.sin(x[0])

  f_grad[1] = 2.0 * a * (x[1] - b * x[0]**2 + c * x[0] - r)

  return f_grad

def Branin_Hess(x, params = {}) :
  n = x.shape[0]
  a = params['a']
  b = params['b']
  c = params['c']
  r = params['r']
  s = params['s']
  t = params['t']

  f_Hess = np.zeros((2,2), dtype = np.float64)

  f_Hess[0] = -4.0 * a * b * (x[1] - b * x[0]**2 + c * x[0] - r)
  f_Hess[0] += 2.0 * a * (-2.0 * b * x[0] + c) **2 - s * (1.0 - t) * np.cos(x[1])

  f_Hess[0][1] = f_Hess[1][0] = 2.0 * a * ( -2.0 * b * x[0] + c)

  f_Hess[1][1] = 2.0 * a

  return f_Hess

def get_newton_direction(g_k, d_k) :
  p_k = np.linalg.solve(d_k, -g_k)
  return p_k

def cholesky_plus_identity(A, beta = 1e-3, max_iter = 100):
  diag = np.diag(A)
  min_aii = min(diag)
  if min_aii > 0 :
    tau = 0.0
  else :
    tau = - min_aii + beta

  k = 0
  while k < max_iter :
    A = A + tau * np.eye(diag.shape[0])
    try :
      A = np.linalg.cholesky(A)
    except :
      tau = max(2.0 * tau, beta)
    else :
      k = max_iter
    k += 1

  return A

def newton_modified (params = []) :
    '''
    '''
    # Cargo parámetros
    x_k        = params['x_0']
    x_k_next   = None
    f          = params['f']
    f_grad     = params['f_grad']
    f_hess     = params['f_hess']
    max_iter   = params['max_iter']
    tau_x      = params['tau_x']
    tau_f      = params['tau_f']
    tau_f_grad = params['tau_grad']
    beta       = params['beta']
    max_iter_c = params['cholesky']['max_iter']

    # Subpámetros para la función
    if f.__name__ == 'Branin' :
      sub_params = {
                    'a' : params['a'],
                    'b' : params['b'],
                    'c' : params['c'],
                    'r' : params['r'],
                    's' : params['s'],
                    't' : params['t']
                  }
    else :
      sub_params = {}

    # Guardo Parámetros
    f_hist = []
    f_hist.append(f(x_k, params = sub_params))

    g_hist = []
    g_hist.append(np.linalg.norm(f_grad(x_k, params = sub_params)))

    # Comienza descenso
    k = 0

    while True:
        # Calculo Gradiente
        g_k = f_grad(x_k, params = sub_params)
        # Calculo Hessiano
        H_k = f_hess(x_k, params = sub_params)
        # Factorizo usando Cholesky Modificado
        L = cholesky_plus_identity(H_k, beta, max_iter_c)
        # Resuelvo sistema hacia atrás
        y   = np.linalg.solve(L, -g_k)
        d_k = np.linalg.solve(L.T, y)

        # Calculo siguiente valor x_k+1
        x_k_next = x_k + d_k

        # Guardo Parámetros
        f_hist.append(f(x_k_next, sub_params))
        g_hist.append(np.linalg.norm(f_grad(x_k_next, sub_params)))

        # Criterios de paro
        if (k > max_iter) :
            break

        if np.linalg.norm(x_k_next - x_k)/max(np.linalg.norm(x_k), 1.0) < tau_x :
            break

        if np.abs(f(x_k_next, sub_params) - f(x_k, sub_params)) / max(np.linalg.norm(f(x_k, sub_params)), 1.0) < tau_f :
            break

        if np.linalg.norm(f_grad(x_k_next, sub_params)) < tau_f_grad :
            break

        # Guardo valor anterior
        x_k = x_k_next
        k   = k + 1

    return np.array(f_hist), np.array(g_hist), x_k_next

def get_graf(f_hist, g_hist) :
  plt.plot(range(f_hist.shape[0]), f_hist)
  # naming the x axis
  plt.xlabel('k')
  # naming the y axis
  plt.ylabel('f_k')

  # giving a title to my graph
  plt.title('Iteration vs Fuction Value')

  # function to show the plot
  plt.show()

  plt.plot(range(g_hist.shape[0]), g_hist)
  # naming the x axis
  plt.xlabel('k')
  # naming the y axis
  plt.ylabel('g_k')

  # giving a title to my graph
  plt.title('Iteration vs Gradient Norm')

  # function to show the plot
  plt.show()


#%%
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import dm6103 as dm

# Part I
titanic = dm.api_dsLand('Titanic', 'id')

# Part II
nfl = dm.api_dsLand('nfl2008_fga')
nfl.dropna(inplace=True)

#%% [markdown]

# # Part I
# Titanic dataset - statsmodels
#
# | Variable | Definition | Key/Notes  |
# | ---- | ---- | ---- |
# | survival | Survived or not | 0 = No, 1 = Yes |
# | pclass | Ticket class | 1 = 1st, 2 = 2nd, 3 = 3rd |
# | sex | Gender / Sex |  |
# | age | Age in years |  |
# | sibsp | # of siblings / spouses on the Titanic |  |
# | parch | # of parents / children on the Titanic |  |
# | ticket | Ticket number (for superstitious ones) |  |
# | fare | Passenger fare |  |
# | embarked | Port of Embarkation | C: Cherbourg, Q: Queenstown, S: Southampton  |
#
#%%
# ## Question 1
# With the Titanic dataset, perform some summary visualizations:
#
# ### a. Histogram on age. Maybe a stacked histogram on age with male-female as two series if possible
titanic.dropna()
titanic.pivot(columns='sex').age.plot(kind = 'hist', stacked=True)
plt.title("Stacked histogram on age with sex")
plt.xlabel("Age")
plt.show()
# ### b. proportion summary of male-female, survived-dead
crosstab = pd.crosstab(titanic['survived'], titanic['sex'], margins = True)
print(crosstab)
# ### c. pie chart for “Ticketclass”
Ticketclass = titanic['pclass'].value_counts()
fig, ax = plt.subplots()
ax.pie(Ticketclass, labels=Ticketclass.index, autopct='%1.1f%%')
plt.title("pie chart for Ticketclass")
plt.legend()
plt.show()
# ### d. A single visualization chart that shows info of survival, age, pclass, and sex.
sexcolors = np.where(titanic['pclass']==1,'r','-')
sexcolors[titanic['pclass']==2] = 'b'
sexcolors[titanic['pclass']==3] = 'g'

ax1 = titanic[titanic.sex=='male'].plot(x="age", y="survived", kind="scatter", color=sexcolors[titanic.sex=='male'], marker='^', s=7, label='male')
titanic[titanic.sex=='female'].plot(x="age", y="survived", kind="scatter", color=sexcolors[titanic.sex=='female'], marker='+', s=7, label='female', ax = ax1)
plt.title("Age vs Survival")
plt.show()

#%%
# ## Question 2
# Build a logistic regression model for survival using the statsmodels library. As we did before, include the features that you find plausible. Make sure categorical variables are use properly. If the coefficient(s) turns out insignificant, drop it and re-build.
import statsmodels.api as sm
from statsmodels.formula.api import glm
Logit = glm(formula='survived~C(pclass)+age+C(sex)+sibsp+parch', data=titanic, family=sm.families.Binomial())
LogitFit = Logit.fit()
print(LogitFit.summary())

# When alpha = 0.05, the p-value of "parch" is 0.840. Thus we fail to reject the null hypothesis that the coefficient of "parch" is 0.
#%%
# Re-build the model.
Logit_re = glm(formula='survived~C(pclass)+age+C(sex)+sibsp', data=titanic, family=sm.families.Binomial())
LogitFit_re = Logit_re.fit()
print(LogitFit_re.summary())

#%%
# ## Question 3
# Interpret your result. What are the factors and how do they affect the chance of survival (or the survival odds ratio)? What is the predicted probability of survival for a 30-year-old female with a second class ticket, no siblings, 3 parents/children on the trip? Use whatever variables that are relevant in your model.

# Using pclass =1 as the base line, when the pclass =2, the odds ratio of survived becomes exp(-0.9510) times. When the pclass =3, the odds ratio of survived becomes exp(-2.1575) times.
# Using female as the base line, the odds ratio of male survived becomes exp(-2.7393) times.
# 1 degree increase in age makes odds ratio of survived exp(-0.0181) times as before while other variables stay the same.
# 1 degree increase in age makes odds ratio of survived exp(-0.2804) times as before while other variables stay the same.
pred = pd.DataFrame({'pclass':2, 'sex':"female", 'age':30, 'sibsp':0}, pd.Index(range(1)))
print("The predicted probability of survival for a 30-year-old female with a second class ticket, no siblings is %.2f" % LogitFit_re.predict(pred))

#%%
# ## Question 4
# Try three different cut-off values at 0.3, 0.5, and 0.7. What are the a) Total accuracy of the model b) The precision of the model (average for 0 and 1), and c) the recall rate of the model (average for 0 and 1)
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report

titanic['sex'][titanic['sex']=='male'] = 0
titanic['sex'][titanic['sex']=='female'] = 1

xsurvival = titanic[['pclass', 'age', 'sex', 'sibsp']]
ysurvival = titanic['survived']

x_trainSurvival, x_testSurvival, y_trainSurvival, y_testSurvival = train_test_split(xsurvival, ysurvival, random_state=1 )

survivallogit = LogisticRegression()
survivallogit.fit(x_trainSurvival, y_trainSurvival)

def predictcutoff(arr, cutoff):
  arrbool = arr[:,1]>cutoff
  arr= arr[:,1]*arrbool/arr[:,1]
  return arr.astype(int)

test = survivallogit.predict_proba(x_testSurvival)
# a
y_pred3 = predictcutoff(test, 0.3)
print(classification_report(y_testSurvival, y_pred3))
# a) The accuracy of the model is 0.78. The precision for "0" is 0.86, for "1" is 0.70. The recall rate for "0" is 0.73, for "1" is 0.84.
print('----------------------------------------------')
y_pred5 = predictcutoff(test, 0.5)
print(classification_report(y_testSurvival, y_pred5))
# b) The accuracy of the model is 0.79. The precision for "0" is 0.79, for "1" is 0.80. The recall rate for "0" is 0.88, for "1" is 0.68.
print('----------------------------------------------')
y_pred7 = predictcutoff(test, 0.7)
print(classification_report(y_testSurvival, y_pred7))
# c) The accuracy of the model is 0.75. The precision for "0" is 0.70, for "1" is 0.95. The recall rate for "0" is 0.98, for "1" is 0.43.
print('----------------------------------------------')
#%%[markdown]
# # Part II
# NFL field goal dataset - SciKitLearn
#
# | Variable | Definition | Key/Notes  |
# | ---- | ---- | ---- |
# | AwayTeam | Name of visiting team | |
# | HomeTeam | Name of home team | |
# | qtr | quarter | 1, 2, 3, 4 |
# | min | Time: minutes in the game |  |
# | sec | Time: seconds in the game |  |
# | kickteam | Name of kicking team |  |
# | distance | Distance of the kick, from goal post (yards) |  |
# | timerem | Time remaining in game (seconds) |  |
# | GOOD | Whether the kick is good or no good | If not GOOD: |
# | Missed | If the kick misses the mark | either Missed |
# | Blocked | If the kick is blocked by the defense | or blocked |
#
#%%
# ## Question 5
# With the nfl dataset, perform some summary visualizations.
print(nfl.info())

nfl.pivot(columns='GOOD').timerem.plot(kind = 'hist', stacked=True)
plt.title("Stacked histogram on Time remaining with GOOD")
plt.xlabel("Time remaining in game (seconds)")
plt.show()

qtrclass = nfl['qtr'].value_counts()
fig, ax = plt.subplots()
ax.pie(qtrclass, labels=qtrclass.index, autopct='%1.1f%%')
plt.show()

sns.boxplot(x="GOOD", y="distance", data=nfl)
plt.title("Boxplot for GOOD VS distance")
plt.show()
# %%
# ## Question 6
# Using the SciKitLearn library, build a logistic regression model overall (not individual team or kicker) to predict the chances of a successful field goal. What variables do you have in your model?
Logit2 = glm(formula='GOOD~min+sec+distance+timerem', data=nfl, family=sm.families.Binomial())
Logit2Fit = Logit2.fit()
print(Logit2Fit.summary())

# When alpha = 0.05, only the p-value of "distance" is less than 0.05. Thus we only include "distance" in our model using the scikitlearn library.

#%%
xgood = nfl[['distance']]
ygood = nfl['GOOD']

x_traingood, x_testgood, y_traingood, y_testgood = train_test_split(xgood, ygood, random_state=1 )

goodlogit = LogisticRegression()
goodlogit.fit(x_traingood, y_traingood)
print('The coeficient of distance is',goodlogit.coef_)
print('The intercept of distance is',goodlogit.intercept_)

#%%
# ## Question 7
# Someone has a feeling that home teams are more relaxed and have a friendly crowd, they should kick better field goals. Use your model to find out if that is subtantiated or not.
nfl['ifhome'] = 0
boolhome = nfl['HomeTeam']==nfl['kickteam']
nfl['ifhome'] = nfl['ifhome'].add(boolhome)

Logitifhome = glm(formula='GOOD~C(ifhome)', data=nfl, family=sm.families.Binomial())
LogitFitifhome = Logitifhome.fit()
print(LogitFitifhome.summary())
#
#  When alpha = 0.05, the p-value of "ifhome" is 0.093. Thus we fail to reject the null hypothesis that the coefficient of "ifhome" is 0.
# %%
# ## Question 8
# From what you found, do home teams and road teams have different chances of making a successful field goal? If one does, is that true for all distances, or only with a certain range?

# According to the results of question 7, there is no different of making a successful field goal between two home teams and road teams. We also train models of different teams below, the coefficient of distance in two models are close.
hometeam = nfl[nfl['HomeTeam']==nfl['kickteam']]
roadteam = nfl[nfl['AwayTeam']==nfl['kickteam']]

xgoodhome = hometeam[['distance']]
ygoodhome = hometeam['GOOD']

x_traingoodhome, x_testgoodhome, y_traingoodhome, y_testgoodhome = train_test_split(xgoodhome, ygoodhome, random_state=1 )

goodhomelogit = LogisticRegression()
goodhomelogit.fit(x_traingoodhome, y_traingoodhome)
print('When being hometeams, the coeficient of distance is',goodhomelogit.coef_)
print('When being hometeams, the intercept of distance is',goodhomelogit.intercept_)
hometeam = nfl[nfl['HomeTeam']==nfl['kickteam']]
roadteam = nfl[nfl['AwayTeam']==nfl['kickteam']]

xgoodroad = roadteam[['distance']]
ygoodroad = roadteam['GOOD']

x_traingoodroad, x_testgoodroad, y_traingoodroad, y_testgoodroad = train_test_split(xgoodroad, ygoodroad, random_state=1 )

goodroadlogit = LogisticRegression()
goodroadlogit.fit(x_traingoodroad, y_traingoodroad)
print('When being roadteams, the coeficient of distance is',goodroadlogit.coef_)
print('When being roadteams, the intercept of distance is',goodroadlogit.intercept_)

# %%
# titanic.dropna()

import numpy,import statsmodels,from statsmodels


def valor_presente(precio, cuotas, inflacion):
    inflacion_mensual = 1. / (inflacion / 100. + 1.)**(1. / 12.)
    return (precio/cuotas) * (1-inflacion_mensual**cuotas)/(1-inflacion_mensual)

precio = float(input("Precio: "))
cuotas = int(input("Cuotas sin interés: "))
inflacion = float(input("Inflación esperada mientras pagás las cuotas: "))
valor = valor_presente(precio, cuotas, inflacion)

print("Con esa inflación, ese plan de pagos es equivalente a pagar $" + \
    str(round(valor,2)) + " al contado.")

precio_contado = float(input("Precio al contado: "))

print("Te conviene pagarlo " + \
    ("al contado." if precio_contado < valor else "en cuotas."))
print("Te ahorrás $" + str(round(abs(precio_contado-valor),2)))

from scipy import optimize

def dif_contado(infla):
    return valor_presente(precio, cuotas, infla) - precio_contado

inflacion_equivalente = optimize.newton(dif_contado, .99)

print("Para que las dos formas de pago sean igualmente convenientes, la " + \
    "inflación debería ser de " + str(round(inflacion_equivalente,2)) + "%")
print("Si esperás que la inflación sea mayor, te conviene pagarlo en " + \
    "cuotas. Si no, al contado.")


'''
Monte Carlo simulation for calculating the avarage distance between any two points sampled uniformily on a circle of radius R.
'''
#import relevant libraries
import numpy as np
import matplotlib.pyplot as plt
from random import uniform
import pandas as pd

def sample_circle(number_of_points, R = 1, plot = False):
    '''
    Samples uniformly on a circle of radius R through the algorithm defined on the main paper.
    If plot = True, shows a scatter drawing of the sampled points and the circle of radius R
    Input: number_of_points, R = 1, plot = False
    Output: the x and y coordinates of the sampled points
    '''
    x_coord = []
    y_coord = []
    #sample on the square RxR until we get exactly number_of_points inside the circle
    while(len(x_coord))<number_of_points:
        x = uniform(-R, R)
        y = uniform(-R,R)

        #as defined in the main paper, we accept the sampled point if inside the circle of radius R, otherwise reject it
        if np.sqrt(x**2+y**2)< R:
            x_coord.append(x)
            y_coord.append(y)

    #plots the sampled points if plot = True, together with the circle of radius R
    if plot == True:
        x_circ = np.linspace(-R, R, 100)
        y_circ_pos = np.sqrt(R**2-x_circ**2)
        y_circ_neg = -np.sqrt(R**2-x_circ**2)

        plt.scatter(x_coord, y_coord)
        plt.plot(x_circ, y_circ_pos, c = 'r', linewidth=4)
        plt.plot(x_circ, y_circ_neg, c = 'r', linewidth=4)
        plt.axis("equal")
        plt.title('Uniform Sampling on The Circle of Radius R')
        plt.show()

    #transform the coordinates into numpy arrays
    x_coord = np.array(x_coord)
    y_coord = np.array(y_coord)
    x_coord = x_coord.reshape(-1,1)
    y_coord = y_coord.reshape(-1,1)
    coordinates = np.concatenate((x_coord,y_coord),axis=1)

    return coordinates

def estimator(number_of_points, R):
    '''
    Samples 2x(number_of_points) on circles of radius R, then calculates the Monte Carlo estimator and its standard deviaton,as
    described in the main paper
    Input: number_of_points, R
    Output: (Monte Carlo estimator, standard deviation of the Monte Carlo estimator)
    '''
    points_1 = sample_circle(number_of_points, R)
    points_2 = sample_circle(number_of_points, R)
    dist_vector = []
    #calculates the distance between points in each of the sampled circles
    for i in range(len(points_1)):
        for j in range(len(points_2)):
            dif = points_1[i]-points_2[j]
            dist_vector.append(np.sqrt(np.dot(dif,dif)))
    return (np.mean(dist_vector), np.std(dist_vector))


import numpy as np

# Collection of activation functions
# Reference: https://en.wikipedia.org/wiki/Activation_function

class Sigmoid():
    def __init__(self): pass

    def function(self, x):
        return 1 / (1 + np.exp(-x))

    def gradient(self, x):
        return self.function(x) * (1 - self.function(x))

class TanH():
    def __init__(self): pass

    def function(self, x):
        return 2 / (1 + np.exp(-2*x)) - 1

    def gradient(self, x):
        return 1 - np.power(self.function(x), 2)

class ReLU():
    def __init__(self): pass

    def function(self, x):
        # If x < 0: output = 0 else: output = x
        h = x
        h[x < 0] = 0
        return h

    def gradient(self, x):
        # If x < 0: output = 0 else: output = 1
        dx = np.zeros(np.shape(x))
        dx[x >= 0] = 1
        return dx

class LeakyReLU():
    def __init__(self): pass

    def function(self, x):
        # If x < 0: output = 0.01*x else: output = x
        h = x
        h[h < 0] = 0.01 * h[h < 0]
        return h

    def gradient(self, x):
        # If x < 0: output = 0.01 else: output = 1
        dx = np.ones(np.shape(x))
        dx[x < 0] = 0.01
        return dx

class ExpLU():
    def __init__(self, alpha=0.1):
        self.alpha = alpha

    def function(self, x):
        # If x < 0: output = alpha*(exp(x)-1) else: output = x
        h = x
        h[h < 0] = self.alpha * (np.exp(h[h < 0]) - 1)
        return h

    def gradient(self, x):
        # If x < 0: output = f(alpha, x) + alpha else: output = 1
        dx = self.function(x) + self.alpha
        dx[x >= 0] = 1
        return dx

class SoftPlus():
    def __init__(self): pass

    def function(self, x):
        return np.log(1 + np.exp(x))

    def gradient(self, x):
        return Sigmoid().function(x)


import numpy as np
from math import sqrt
from mpl_toolkits.mplot3d import Axes3D
from sklearn.datasets import make_circles
import matplotlib.pyplot as plt
import pylab as pl

"""
Demonstrates how a linearly nonseparable dataset in R^2 can be
linearly separable in R^3 after a transformation via an appropriate
kernel function.

Generates a 2D non-separable dataset, and projects it to R^3 using
a polynomial kernel [x1, x2] -> [x1, x2, x1^2.0 + x2^2.0], where
it is now linearly separable in R^3.

Usage:
    $ python demo_data_transform.py
"""

def randrange(n, vmin, vmax):
    return (vmax-vmin)*np.random.rand(n) + vmin

def fn_kernel(x1, x2):
    """ Implements a kernel phi(x1,y1) = [x1, y1, x1^2 + y1^2] """
    return np.array([x1, x2, x1**2.0 + x2**2.0])

""" Generate linearly nonseparable dataset (in R^2) """

n = 200

X, Y = make_circles(n_samples=n, noise=0.07, factor=0.4)

A = X[np.where(Y == 0)]
B = X[np.where(Y == 1)]

X0_orig = A[:, 0]
Y0_orig = A[:, 1]

X1_orig = B[:, 0]
Y1_orig = B[:, 1]

frac0 = len(np.where(Y == 0)[0]) / float(len(Y))
frac1 = len(np.where(Y == 1)[0]) / float(len(Y))

print("Percentage of '0' labels:", frac0)
print("Percentage of '1' labels:", frac1)

A = np.array([fn_kernel(x,y) for x,y in zip(np.ravel(X0_orig), np.ravel(Y0_orig))])
X0 = A[:, 0]
Y0 = A[:, 1]
Z0 = A[:, 2]

A = np.array([fn_kernel(x,y) for x,y in zip(np.ravel(X1_orig), np.ravel(Y1_orig))])
X1 = A[:, 0]
Y1 = A[:, 1]
Z1 = A[:, 2]

def plot_no_decision_boundary():
    fig = plt.figure(figsize=(20,8))
    ax = fig.add_subplot(122, projection='3d')

    ax.scatter(X0, Y0, Z0, c='r', marker='o')
    ax.scatter(X1, Y1, Z1, c='b', marker='^')

    ax.set_xlabel('X Label')
    ax.set_ylabel('Y Label')
    ax.set_zlabel('Z Label')
    ax.set_title("Data in R^3 (separable)")

    # Project data to X/Y plane
    ax2d = fig.add_subplot(121)
    ax2d.scatter(X0, Y0, c='r', marker='o')
    ax2d.scatter(X1, Y1, c='b', marker='^')

    ax2d.set_xlabel('X Label')
    ax2d.set_ylabel('Y Label')
    ax2d.set_title("Data projected to R^2 (nonseparable)")

    plt.show()

def plot_decision_boundary():
    fig = plt.figure(figsize=(20,8))
    ax = fig.add_subplot(121, projection='3d')

    ax.scatter(X0, Y0, Z0, c='r', marker='o')
    ax.scatter(X1, Y1, Z1, c='b', marker='^')

    ax.set_xlabel('X Label')
    ax.set_ylabel('Y Label')
    ax.set_zlabel('Z Label')
    ax.set_title("Data in R^3 (separable w/ hyperplane)")

    x = np.arange(-1.25, 1.25, 0.1)
    y = np.arange(-1.25, 1.25, 0.1)
    X, Y = np.meshgrid(x, y)

    Z = np.zeros(X.shape)
    Z[:,:] = 0.5

    ax.plot_surface(X, Y, Z, color='#09F911')

    # Project data to X/Y plane
    ax2d = fig.add_subplot(122)
    ax2d.scatter(X0, Y0, c='r', marker='o')
    ax2d.scatter(X1, Y1, c='b', marker='^')

    ax2d.add_patch(pl.Circle((0,0), radius=sqrt(0.5),
                   fill=False, linestyle='solid', linewidth=4.0,
                   color='black'))
    ax2d.add_patch(pl.Circle((0,0), radius=sqrt(0.5),
                   fill=False, linestyle='dashed', linewidth=1.5,
                   color='#09F911'))

    ax2d.set_xlabel('X Label')
    ax2d.set_ylabel('Y Label')
    ax2d.set_title("Data projected to R^2 (hyperplane projection shown)")

    plt.show()

def main():
    print("...Projecting dataset to R^3 (no decision boundary)...")
    plot_no_decision_boundary()
    print("...Projecting dataset to R^3 (with decision boundary)...")
    plot_decision_boundary()
    print("...Done.")

if __name__ == '__main__':
    main()


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

def func(x):
    return ((x[0] - 0.5)**2 + (x[1] + 0.5)**2)*np.cos(0.5*x[0]*x[1])

x_r = np.linspace(-1, 1)
y_r = np.linspace(-2, 2)

x, y = np.meshgrid(x_r, y_r)

z = func([x, y])

fig = plt.figure(tight_layout=True)
ax = fig.add_subplot(projection="3d")
ax.tick_params(axis="both", which="major", labelsize=9)
ax.set(xlabel="x", ylabel="y", zlabel="z")
ax.set_title("Objective function")

ax.plot_surface(x, y, z, alpha=0.7)

x0 = np.array([-0.5, 1.0])
ax.plot([x0[0]], [x0[1]], func(x0), "r*")

result = optimize.minimize(func, x0, tol=1e-6, method="Nelder-Mead")
print(result)

ax.plot([result.x[0]], [result.x[1]], [result.fun], "r*")

plt.show()


'''
A Pythagorean triplet is a set of three natural numbers, a < b < c, for which,

a^2 + b^2 = c^2
For example, 3^2 + 4^2 = 9 + 16 = 25 = 5^2.

There exists exactly one Pythagorean triplet for which a + b + c = 1000.
Find the product a*b*c.
'''

# Use diophantine equation solver from SymPy to solve diophantine (integer) equations.
#
# Manual steps:
#
# 1 - get general solution to x^2 + y^2 = z^2 - returns solution(s) in terms of integers p and q:
#     diophantine(x * x + y * y - z * z)
#     -> set([(2*p*q, p**2 - q**2, p**2 + q**2)])
#
# 2 - sub values for x, y, z (in terms of p & q) into x + y + z = limit to get another diophantine equation:
#     sum([2*p*q, p**2 - q**2, p**2 + q**2])
#     -> 2*p**2 + 2*p*q
#
# We now have a diophantine equation for the perimeter of a diophantine right triangle - so we get:
# 2 * p * q + 2 * p^2 = limit
# -> p * p + p * q = limit / 2
#
# Automation:
#
# Solve the diophantine equation, and get multiple candidates for p, q.
# For each one, substitute back in to get x, y, z.  In all cases, x * x + y * y = z * z; and x + y + z = limit
# so we need to find out which solutions actually form a real triangle: (x, y, z) all > 0

from sympy.solvers.diophantine import diophantine
from sympy import symbols

def main_body(limit):
    p, q = symbols('p q', integer = True)
    return [x * y * z for x, y, z in ((2 * a * b, a * a - b * b, a * a + b * b) for a, b in diophantine(p * p + q * p - limit / 2)) if x > 0 and y > 0][0]

def time_test(limit, count = 1000):
    import timeit
    return str(timeit.timeit(stmt = 'main_body(' + str(limit) + ')', setup = 'from __main__ import main_body', number = count) / count * 1000) + ' ms'

if __name__ == '__main__':
    assert main_body(12) == 60

    limit = 1000
    print time_test(limit, 300)
    result = main_body(limit)
    print result

    assert result == 31875000


from __future__ import division

import math
import numpy as np
import ittk_helpers as hlp
from ittk_exceptions import ITTKException
from ittk_helpers import probs
from ittk_helpers import cond_probs
from numpy import array, shape, where, in1d

# Note: All functions default to log base 2.

def check_prob_sum(arr):
    return int(sum(arr)) == 1

def entropy(x):
    x = probs(x)
    total = 0
    for x_i in x:
        if x_i == 0:
            continue
        total -= x_i * np.log2(x_i)
    return total

def mutual_information(x, y, normalized=False, base=2):
    """
    Compute the mutual information between two sets of observations.
    First converts observations to discrete conditional probability distribution, then computes their MI.

    :param x:
     List or numpy array.
    :param y:
     List or numpy array.
    :param normalized:
     Normalize the inputs. Defaults to False.
    :param base:
     The log base used in the MI calculation. Defaults to 2.
    :return:
     Float: the mutual information between x and y.
    """
    x = hlp.check_numpy_array(x)
    y = hlp.check_numpy_array(y)
    numobs = len(x)
    if numobs != len(y):
        raise Exception("Inputs not of matching length")
    mutual_info = 0.0
    uniq_x = set(x)
    uniq_y = set(y)
    for _x in uniq_x:
        for _y in uniq_y:
            px = shape(where(x == _x))[1] / numobs
            py = shape(where(y == _y))[1] / numobs
            pxy = len(where(in1d(where(x == _x)[0],
                                 where(y == _y)[0]) == True)[0]) / numobs
            if pxy > 0.0:
                mutual_info += pxy * math.log((pxy / (px * py)), base)
    if normalized: mutual_info = mutual_info / np.log2(numobs)
    return mutual_info

# Variation of information
def information_variation(x, y):
    """

    :param x:
     List or numpy array
    :param y:
     List or numpy array
    :return:
     Float: the information variation of x and y
    """
    x = hlp.check_numpy_array(x)
    y = hlp.check_numpy_array(y)
    return entropy(x) + entropy(y) - (2 * mutual_information(x, y))

def kldiv(X, Y, isprobs=False):
    if isprobs == False:
        p = probs(X)
        q = probs(Y)
    else:
        p = X
        q = Y
    p, q = hlp.match_arrays(p, q)
    logpq = np.array([])
    for i in range(len(p)):
        if q[i] == 0 or p[i] == 0:
            logpq = np.append(logpq, 0)
        else:
            logpq = np.append(logpq, np.log2(p[i] / q[i]))
    kldivergence = np.dot(p, logpq)
    return kldivergence

def lag(x, y, lag_points=1):
    """
    Example:

    x, y = lag(x, y, 5)

    Note: this will reduce the length of the sequence by the number of lag points; in the above case, 5.

    :param x:
     numpy array
    :param y:
     numpy array
    :param lag_points:
     Integer. Number of places to shift by. Defaults to 1.
    :return:
     Tuple: (numpy array, numpy_array)
    """
    for i in range(lag_points):
        x.pop(0)
        y.pop()
    return x, y

def lagged_mutual_information(x, y, lag_points=1):
    """
    Convenience method to lag and do mutual information in one shot.

    :param x:
     numpy array
    :param y:
     numpy array
    :param lag_points:
     Integer. Number of places to shift by. Defaults to 1.
    :return:
     Float: the mutual information of x and y, after lag is applied.
    """
    x, y = lag(x, y, lag_points)
    return mutual_information(x, y)

def tsallis_entropy(X, entropic_index=0.99):
    """
    :param X:
        Python list or Numpy array. Should sum to 1.
    :param entropic_index:
        Real number [0, 1]. Defaults to 0.99.
    :return:
        Returns the real-valued Tsallis entropy.
    :raises:
        Raises generic ITTKException if 1 is passed, since this would result in a division by zero.
    """
    if int(entropic_index) == 1:
        raise ITTKException("Entropic index, 'q', cannot be 1 for tsallis entropy. Must be on real interval (0, 1).")
    return (1.0/(entropic_index-1)) * (1.0 - sum([p ** entropic_index for p in X]))


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

np.random.seed(0)

#mean = 50 * exp(0.1 * 3) = 67.49294
#var = 580.786

x0 = 50
T = 3
mu = 0.1
sigma = 0.2
time_step = 0.01
n_steps = int(T / time_step)

n_runs = 10**2
n_runs_to_plot = 5
delta_bm = np.random.normal(0, math.sqrt(time_step), (n_steps, n_runs))
geometric_bm = np.zeros((n_steps+1, n_runs))
geometric_bm[0,:] = x0

for i in np.arange(0, n_steps):
	geometric_bm[i+1,:] = geometric_bm[i,:] * (1 + mu * time_step + sigma * delta_bm[i])

mean = np.mean(geometric_bm[n_steps-1,:])
var = np.var(geometric_bm[n_steps-1,:])
print('mean: %s' % mean)
print('var: %s' % var)
plt.plot(np.linspace(0, T, n_steps+1), geometric_bm[:,0:(n_runs_to_plot-1)])
plt.axis([0, T, 0, geometric_bm[:,0:(n_runs_to_plot-1)].max()+5])
plt.ylabel('X_t')
plt.xlabel('Time')
#plt.show()

################
## correlated GBM
################
T = 60
time_step = 0.1
n_steps = int(T / time_step)
dim = 5
n_runs = 10**5
x0 = np.array([50, 20, 30, 45, 70])
mus = np.array([0.1,0.05,0.12,0.2,0.08])
sigmas = np.array([0.2, 0.15, 0.3, 0.12, 0.15])

#upper = np.matrix([[0,0.5,0.5,0.5,0.5],[0,0,0.5,0.5,0.5],[0,0,0,0.5,0.5],[0,0,0,0,0.5],[0,0,0,0,0]])
W = np.random.rand(dim, dim)
#B is pos. def. by Gershgorin's theorem

#use for positive correlation
B = np.dot(W, np.transpose(W))

#use for negative correlation
#B = np.dot(W, np.transpose(W)) + 3*np.diag([2, 3, 3, 5, 4])

E = np.diag(np.sqrt(1./np.diagonal(B))) #used to put ones on the diagonals
corr = E.dot(B).dot(E)
print('\nCorrelation matrix: ')
print(corr)
L = np.linalg.cholesky(corr)

prices_at_final_time = np.empty((dim, n_runs))

delta_bm = np.random.normal(0, math.sqrt(time_step), (dim, n_runs, n_steps))

for d in np.arange(0, dim):
	corr_geometric_bm = np.zeros((n_steps+1, n_runs))
	corr_geometric_bm[0,:] = x0[d]

	for i in np.arange(0, n_steps):
		#for delta_corr_bm: rows = which time point, columns = which run
		delta_corr_bm = np.transpose(L[d,:].dot(delta_bm[:,:,i]))
		corr_geometric_bm[i+1,:] = corr_geometric_bm[i,:] * (1 + mus[d] * time_step + sigmas[d] * delta_corr_bm)

	prices_at_final_time[d,:] = corr_geometric_bm[-1,:]

print('\nExpected means: ')
expected_means = x0 * np.exp(mus * T)
print(expected_means)

print('\nSample means: ')
sample_means = np.mean(prices_at_final_time, axis = 1)
print(sample_means)

print('\nSample correlation coefficients:')
corr_coeffs = np.corrcoef(prices_at_final_time, rowvar = 1)
print(corr_coeffs)

plt.plot(np.linspace(0, T, n_steps+1), corr_geometric_bm[:,0:(n_runs_to_plot-1)])
plt.axis([0, T, 0, corr_geometric_bm[:,:].max()])
plt.ylabel('X_t')
plt.xlabel('Time')
#plt.show()


import numpy as np

__author__ = "Vincent Levorato"
__credits__ = "https://github.com/octo-technology/bdacore"
__license__ = "Apache 2.0"

def mean_absolute_deviation(x):
    """
    Mean Absolute Deviation
    """
    return np.nanmean(np.abs(x - np.nanmean(x)))

def median_absolute_deviation(x):
    """
    Median Absolute Deviation
    """
    return np.nanmedian(np.abs(x - np.nanmedian(x)))

def double_median_absolute_deviation(x):
    """
    If the underlying distribution of the data is unsymmetric, one should use the double median
    absolute deviation, instead of the classic mad.

    Parameters
    ----------
    x : array-like

    Returns
    -------
    Couple of values: left mad, right mad

    """
    median = np.nanmedian(x)
    absolute_deviation = np.abs(x - median)

    left_mad = np.nanmedian(absolute_deviation[absolute_deviation <= median])
    right_mad = np.nanmedian(absolute_deviation[absolute_deviation >= median])

    return left_mad, right_mad

def mad_outliers(x, cutoff=2, z_score_coeff=0.6745):
    """
    Median Absolute Deviation outliers method.

    It returns values as outliers if their residuals relative to the median exceeds 'n' times the ratio
    to the MAD. The cutoff parameter represents this 'n' times quantity.

    Main code: x_mad = np.abs(x - np.median(x)) / mad_value
    if x_mad contains a value above the cutoff parameter, it is considered as an outlier.

    Parameters
    ----------
    x : array-like

    cutoff : int, optional (default=2)
        amount of times residuals relative to the median exceed the ratio to the MAD

    z_score_coeff : float, optional (default=0.6745)
        0.6745 is the 0.75th quartile of the standard normal distribution, to which the MAD converges to.

    Returns
    -------
    Boolean array with outlier tag

    """
    mad_value = median_absolute_deviation(x)
    if mad_value == 0:
        return x * np.nan

    x_mad = (z_score_coeff * np.abs(x - np.nanmedian(x))) / mad_value
    return x_mad > cutoff

def fft_outliers(x, freq_cut_index=None, outlier_proportion=0.1):
    """
    Fast Fourier Transformation outliers method.

    The idea is to isolate high frequencies in frequency domain, i.e. fast variations. Isolating such frequencies
    allows to identify abrupt change in numerical series, which are considered here as outliers.

    Parameters
    ----------
    x : array-like

    freq_cut_index : int, optional (default=None)
        set the index in the FFT numpy array beyond which the frequency is considered to be filtered. The highest,
        the less filtering. By default, it sets this index to 0.9 of x length

    outlier_proportion : float, optional (default=0.1)
        set the proportion of outliers to return

    Returns
    -------
    Boolean array with outlier tag

    """
    if freq_cut_index is None:
        freq_cut_index = int(0.9 * float(len(x)))

    fft_x = np.fft.fft(x)
    for freq_index in range(0, len(fft_x)):
        if freq_index >= freq_cut_index:
            fft_x[freq_index] = 0

    ifft_x = np.real(np.fft.ifft(fft_x).round())
    diff_fft_x = np.abs(x - ifft_x)
    outlier_amount = int(len(x) * outlier_proportion)
    outliers = np.zeros(len(x), dtype='bool')
    for i in range(0, outlier_amount):
        position_outlier = np.argmax(diff_fft_x)
        outliers[position_outlier] = True
        diff_fft_x[position_outlier] = -1

    return outliers


import numpy as np
import matplotlib.pyplot as plt

def pdf(costh, P_mu):
    # define our probability density function
    return 0.5 * (1.0 - 1.0 / 3.0 * P_mu * costh)

def acc_rej(N_measurements, P_mu):
    x = np.random.uniform(-1.0, 1.0, size=N_measurements)

    x_axis = np.linspace(-1.0,1.0, 1000)
    fmax= np.amax(pdf(x_axis,P_mu)) #find the maximum of the function

    u = np.random.uniform(0, fmax, size= N_measurements)

    # we use a mask in order to reject the values we don't want
    data_pdf = x[u < pdf(x,P_mu)]
    return data_pdf

N_measurements=100000

first_experiment=acc_rej(N_measurements,0.5)
mean = np.mean(first_experiment)
P_mu_first=-9.*mean
print(P_mu_first)

P_mu_est = np.array([])
N_experiments=0
while N_experiments < 1000:
    data_pdf = acc_rej(N_measurements, P_mu_first)
    mean = np.mean(data_pdf)
    P_mu_est = np.append(P_mu_est,-9. * mean)
    N_experiments += 1

variance = np.var(P_mu_est)
sigma = np.sqrt(variance)
print(variance)
print(sigma)

plt.hist(P_mu_est, bins=30, histtype="step", color="black", linewidth="1")
plt.xlabel(r'$\hat{P}_\mu$')
plt.ylabel(r'$N(\hat{P}_\mu)$')

# plt.savefig(r"C:\Users\Aleix López\Desktop\acc_rej.jpg")
plt.show()

Projects/Probability and Monte Carlo Techniques/_3_variance_parameter.py


## Import modules
import numpy as np
import scipy.linalg as la

'''
Description.
Function used to compute the center of the circle given two points and a
radius. Takes only real inputs and gives only real outputs.
Function originally named: circ_cent.m for MatLab(R)
By: Chiran 2010, BST license

# Input(s):
First circle point (pt1Vec).

Second circle point (pt2Vec).

Circle radius (radius).

# Output(s):
Row vector of the point where the first possible center (centerVec1);

Row vector of the point where the second possible center (centerVec2).

# Example1:
pt1Vec = np.array([40, 12]); pt2Vec = np.array([4.347, 24]); radius = 34.4848
One of the solutions is: np.array([31.3936, 45.3936]).

---
centerVec1, centerVec2 = circleby2ptsradius(pt1Vec, pt2Vec, radius)
'''
def circleby2ptsradius(pt1Vec, pt2Vec, radius):

    if radius < la.norm(pt1Vec-pt2Vec)/2:
        centerVec1 = np.array(['NaN', 'NaN'])
        centerVec2 = np.array(['NaN', 'NaN'])
    else:
        a = pt1Vec[0]**2-pt2Vec[0]**2
        b = pt1Vec[1]**2-pt2Vec[1]**2
        c = -2*(pt1Vec[0]-pt2Vec[0])
        d = -2*(pt1Vec[1]-pt2Vec[1])
        e = a+b
        Coeff1 = 1+(d/c)**2
        Coeff2 = ((2*d*e/c**2) +(pt2Vec[0]*2*d/c) -2*pt2Vec[1])
        Coeff3 = ((e/c)**2+(2*pt2Vec[0]*e/c)+pt2Vec[0]**2+pt2Vec[1]**2-\
            radius**2)

        All_coeff = np.array([Coeff1, Coeff2, Coeff3])
        Eq_root = np.roots(All_coeff)

        # centersArray--center of the circle. It is a 2x2 matrix. First row
        # represents first possible center (x1, y1) and second row is the
        # second possible center.
        centersArray = np.zeros((len(Eq_root),2))

        for i in list(range(len(Eq_root))):
            x = -(e+d*Eq_root[i])/c
            centersArray[i,0] = x
            centersArray[i,1] = Eq_root[i]

        # Check if values in centerArray are real or unreal numbers
        if la.norm(centersArray.imag) != 0:
            print('Circle with the specified radius does not pass through',
                'specified points', sep=" ")
            centerVec1 = np.array(['NaN', 'NaN'])
            centerVec2 = np.array(['NaN', 'NaN'])
        else:
            centerVec1 = centersArray[0, :]
            centerVec2 = centersArray[1, :]

    return centerVec1, centerVec2
'''
BSD 2 license.

Copyright (c) 2016, Universidad Nacional de Colombia, Ludger O.
   Suarez-Burgoa and Exneyder Andrés Montoya Araque.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''


#!/usr/bin/python
# -*- coding: utf8 -*-
import sys, math, numpy
from decimal import *
from math import factorial
from time import time
from numba import jit

# http://thelivingpearl.com/2013/05/28/computing-pi-with-python/
# http://www.numberworld.org/misc_runs/pi-5t/details.html

# Bellard's Formula PI

#@jit(nopython=True) # Set "nopython" mode for best performance, equivalent to @njit
def bellard(n):
    pi = Decimal(0)
    k = 0
    while k < n:
      pi += (Decimal(-1)**k/(1024**k))*( Decimal(256)/(10*k+1) + Decimal(1)/(10*k+9) - Decimal(64)/(10*k+3) - Decimal(32)/(4*k+1) - Decimal(4)/(10*k+5) - Decimal(4)/(10*k+7) - Decimal(1)/(4*k+3))
      k += 1
    pi = pi * 1/(2**6)
    return pi

# Bailey-Borwein-Plouffe formula

#@jit(nopython=True)
def bbp(n):
    pi = 0
    k = 0
    while k < n:
        pi += (Decimal(1)/(16**k))*((Decimal(4)/(8*k+1))-(Decimal(2)/(8*k+4))-(Decimal(1)/(8*k+5))-(Decimal(1)/(8*k+6)))
        k += 1
    return pi

# http://www.craig-wood.com/nick/articles/pi-chudnovsky/

#@jit(nopython=True) # Set "nopython" mode for best performance, equivalent to @njit
def chudnovsky(n):
    pi = Decimal(0)
    k = 0
    while k < n:
        pi += (Decimal(-1)**k)*(Decimal(factorial(6*k))/((factorial(k)**3)*(factorial(3*k)))* (13591409+545140134*k)/(640320**(3*k)))
        k += 1
    pi = pi * Decimal(10005).sqrt()/4270934400
    pi = pi**(-1)
    return pi

@jit(nopython=True)
def chudnovsky2(n):
    pi = 13591409
    ak = 1
    k = 1
    while k < n:
        ak *= -((6*k-5)*(2*k-1)*(6*k-1))/(k*k*k*26680*640320*640320)

        val = ak*(13591409 + 545140134*k)

        #d = Decimal((6*k-5)*(2*k-1)*(6*k-1))/Decimal(k*k*k*26680*640320*640320)

        pi += val
        k += 1
    pi = pi * numpy.sqrt(10005)/4270934400.
    pi = pi**(-1)
    return pi

# arctan
# http://www.craig-wood.com/nick/articles/pi-machin/

def arctan(x):
    """
    Calculate arctan(1/x)

    arctan(1/x) = 1/x - 1/3*x**3 + 1/5*x**5 - ... (x >= 1)

    This calculates it in fixed point, using the value for one passed in
    """
    x2 = x*x
    x = Decimal(x)

    total = Decimal(0)
    sign = 1
    for i in range(1, 512, 2):
        #total += sign / (i * x)
        total += sign / (i * x ** i)
        sign = -sign
        #x *= x2
        #print(total)
    return total

def pi_machin(n):
    return 4*(4*arctan(5) - arctan(239))

def pi_gauss(one):
    return 4*(12*arctan(18) + 8*arctan(57) - 5*arctan(239))

if __name__ == "__main__":

  N = 768 # вывод до числа Фейнмана

  getcontext().prec = N

  start_all = time()

  print("Atan")
  start = time()
  my_pi = pi_machin(N)
  print("Pi = {}".format(str(my_pi)))
  print("Time {} ms".format(float(round((time()-start)*1e6)/1e3)))

  print("\nBBP")
  start = time()
  my_pi = bbp(N)
  print("Pi = {}".format(str(my_pi)))
  print("Time {} ms".format(float(round((time()-start)*1e6)/1e3)))

  print("\nBellard")
  start = time()
  my_pi = bellard(N)
  print("Pi = {}".format(str(my_pi)))
  print("Time {} ms".format(float(round((time()-start)*1e6)/1e3)))

  print("\nChudnovsky")
  start = time()
  my_pi = chudnovsky(N/14)
  print("Pi = {}".format(str(my_pi)))
  print("Time {} ms".format(float(round((time()-start)*1e6)/1e3)))

  print("\nChudnovsky2")
  start = time()
  my_pi = chudnovsky2(N/14)
  print("Pi = {}".format(str(my_pi)))
  print("Time {} ms".format(float(round((time()-start)*1e6)/1e3)))

  print("\nAll Time {} millisecond, {} microseconds, and {} nanoseconds".format(int(round((time()-start_all)*1e6)/1e3),
  	int((float(round((time()-start_all)*1e6)/1e3)-int(round((time()-start_all)*1e6)/1e3))*1000),
  	abs(int((float(round((time()-start_all)*1e9)/1e3)-int(round((time()-start_all)*1e9)/1e3))*100))))


"""
Largest prime factor
Problem 3
The prime factors of 13195 are 5, 7, 13 and 29.

What is the largest prime factor of the number 600851475143 ?
"""
import numpy as np

def find_smallest_prime_factor(number):
    upper_bound = int(np.sqrt(number)) + 1

    for i in range(2, upper_bound):
        if number % i == 0:
            return i

    return number

def find_largest_prime_factor(number):
    while True:
        smallest_factor = find_smallest_prime_factor(number)

        if smallest_factor < number:
            number //= smallest_factor
        else:
            return number

result = find_largest_prime_factor(600851475143)
print('result: ', result)

"""
6857
"""


from numpy import random

EPOCHS = 40_000
RANDOM_ADJUSTMENTS = 32
STEP_SIZE = 0.0001
VERBOSE = False

def cost_function(outputs, expected_values):
    total_cost = 0

    # evaluate the sum of every
    # output comparing it to the
    # expected value, squaring
    # the difference
    for o, y in zip(outputs, expected_values):
        total_cost += (o - y) * (o - y) # () ** 2 is stupid

    # return the sum divided
    # by the number of outputs
    return total_cost / len(outputs)

def evaluate_outputs(coeff, y_int, inputs):
    # compute the outputs by
    # plugging the inputs in the
    # current line to get the outputs
    return [coeff * x + y_int for x in inputs]

def min_random_adjustment(coeff, y_int, inputs, expected_values):
    # generating random numbers
    # without using the random
    # standard library because it'ss dumb
    random_adj_coeff = [random.normal(0, 1) * STEP_SIZE for _ in range(RANDOM_ADJUSTMENTS)]
    random_adj_y_int = [random.normal(0, 1) * STEP_SIZE for _ in range(RANDOM_ADJUSTMENTS)]

    average_costs = []

    # compute the average cost
    # of every single adjustment
    for idx, (rand_coeff, rand_y_int) in enumerate(zip(random_adj_coeff, random_adj_y_int)):
        # compute the outputs with the adjustments
        # applied to the current values
        outputs_adj = evaluate_outputs(coeff + rand_coeff, y_int + rand_y_int, inputs)

        # compute the current average cost
        average_cost_adj = cost_function(outputs_adj, expected_values)

        average_costs.append((idx, average_cost_adj))

    index = min(average_costs, key=lambda t: t[1])[0]

    # return the best adjustments
    return random_adj_coeff[index], random_adj_y_int[index]

dataset = [(-2, 5), (3, 7), (6, 8.2), (-10, 1.8), (-5, 3.8)]

# extract the input
# and the output vectors
# from the given dataset
inputs = [x for x, _ in dataset]
expected_values = [y for _, y in dataset]

# starting values
random_coeff = 3.6
random_y_int = 5.2

for epoch in range(EPOCHS):
    # compute the best adjustments
    best_adj_coeff, best_adj_y_int = min_random_adjustment(random_coeff, random_y_int, inputs, expected_values)

    # implement the adjustments
    random_coeff += best_adj_coeff
    random_y_int += best_adj_y_int

    # print the current average cost
    if VERBOSE:
        # compute the outputs
        outputs = evaluate_outputs(random_coeff, random_y_int, inputs)

        # compute the average cost
        average_cost = cost_function(outputs, expected_values)

        print(f"Epoch number {epoch + 1}, average cost is: {average_cost:.15f}") # should decrease with each epoch

# print the final result
print(random_coeff, random_y_int)


'''
Code given from professor Michael Plexousakis, instructor of the course.
http://users.tem.uoc.gr/~plex/mem253-Fall2017/
'''
import numpy as np
import gaussQuad as gq
import matplotlib.pyplot as plt

# Finite element method to solve the two-point boundary value problem
#
#    -u'' + q u = f,  in [l, r]
#
# with zero Dirichlet boundary condtions.

# The functions q and f

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

# Endpoints and partition of [l,r]

l = 0.0;  r = 1.0;  N = 99;  h = (r - l) / (N+1);  x = np.linspace(l, r, N+2)

# The system matrix is tridiagonal symmetric positive definite. Compute the
# main diagonal and the first diagonal above. Use a Gauss quadrature rule
# exact for polynomials of degree <= 4

nq, p, w = gq.gaussQuad(4)

d = np.zeros(N)
c = np.zeros(N)

for j in range(N+1):
	xj = x[j];  s1 = 0.0;  s2 = 0.0;  s3 = 0.0
	for k in range(nq):
		qk = q(xj + h*p[k])
		s1 += w[k] * qk * (1 - p[k])**2
		s2 += w[k] * qk * (1 - p[k])*p[k]
		s3 += w[k] * qk *      p[k] *p[k]

	if j > 0:           d[j-1] += h * s1
	if j > 0 and j < N: c[j-1] += h * s2
	if j < N:           d[j  ] += h * s3

for j in range(N):
	d[j] += 2 / h
	c[j] -= 1 / h

# Use a Gauss quadrature rule exact for polynomials of degree <= 5

b = np.zeros(N)
nq, p, w = gq.gaussQuad(5)

for j in range(N+1):
	xj = x[j];  s1 = 0.0;  s2 = 0.0
	for k in range(nq):
		fk = f(xj + h*p[k])
		s1 += w[k] * fk * (1 - p[k])
		s2 += w[k] * fk *      p[k]

	if j > 0: b[j-1] += h * s1
	if j < N: b[j  ] += h * s2

# Solve the system

for k in range(1,N):
	t = c[k-1] / d[k-1]
	d[k] -= t * c[k-1]
	b[k] -= t * b[k-1]

b[N-1] = b[N-1] / d[N-1]
for k in range(N-2, -1, -1):
	b[k] = (b[k] - c[k] * b[k+1]) / d[k]

# Plot the solution

u = np.lib.pad(b, (1,1), 'constant', constant_values=(0, 0))
plt.plot(x, u, 'k-')
plt.legend(('u'), loc='upper right', shadow=True)
plt.show()

Finite Elements Method (given code)/fem.py


import numpy as np

def rsolve(A, b):
    """
    Solve system x*A = b for x. Can be used to efficiently calculate x=b*inv(A).

    Parameters
    ----------
    A: np.array, system matrix
    b: np.array, right hand side

    Returns
    -------
    solution of x*A = b
    """
    return np.linalg.solve(A.T, b.T).T

def normal_log_pdf(x, mu, covmat):
    """
    Log-likelihood of a multivariate normal distribution.

    Parameters
    ----------
    x: np.array, input vector
    mu: np.array, mean vector
    covmat: np.array, covariance matrix

    Returns
    -------
    likelihood value as scalar
    """

    data_misfit = 0.5*np.sum((x-mu)*np.linalg.solve(covmat, x-mu))
    norm_constant = np.log(np.linalg.det(2 * np.pi * covmat))

    return data_misfit + norm_constant


import numpy as np
import matplotlib.pyplot as plt

def newton_method(f, df, x0, eps, steps):
    x = x0
    for i in range(steps):
        x_next = x - f(x) / df(x)
        if abs(x_next - x) <= eps:
            return x_next
        x = x_next
    return x

def plot_newton_fractal(f, df, top_left=-2+2.0j, botton_right=2-2.0j, eps=1e-10, steps=100, rows=200, columns=200, cmap="jet"):

    xs = np.linspace(top_left.imag, botton_right.imag, rows)
    ys = np.linspace(top_left.real, botton_right.real, columns)

    fractal = np.array([[newton_method(f, df, x + y*1j, eps, steps) for x in xs] for y in ys])

    plt.subplot(2,2,1)
    plt.title("Real part")
    plt.ylabel("Im")
    plt.pcolormesh(xs, ys, fractal.real, cmap=cmap)
    plt.colorbar()

    plt.subplot(2,2,2)
    plt.title("Imaginary part")
    plt.pcolormesh(xs, ys, fractal.imag, cmap=cmap)
    plt.colorbar()

    plt.subplot(2,2,3)
    plt.title("Absolute value")
    plt.xlabel("Re")
    plt.ylabel("Im")
    plt.pcolormesh(xs, ys, np.abs(fractal), cmap=cmap)
    plt.colorbar()

    plt.subplot(2,2,4)
    plt.xlabel("Re")
    plt.title("Angle")
    plt.pcolormesh(xs, ys, np.angle(fractal), cmap=cmap)
    plt.colorbar()

    plt.tight_layout()

if __name__ == "__main__":
    plot_newton_fractal(lambda z: z**3 - 1, lambda z: 3*z**2)
    plt.show()


import numpy as np

# --------------------------------------------------------------
#                Rotation Functions
# --------------------------------------------------------------

def rotxM(theta):
    """Return x rotation matrix"""
    theta = theta * np.pi / 180
    M = [[1, 0, 0], [0, np.cos(theta), -np.sin(theta)], [0, np.sin(theta), np.cos(theta)]]
    return M

def rotyM(theta):
    """Return y rotation matrix"""
    theta = theta * np.pi / 180
    M = [[np.cos(theta), 0, np.sin(theta)], [0, 1, 0], [-np.sin(theta), 0, np.cos(theta)]]
    return M

def rotzM(theta):
    """Return z rotation matrix"""
    theta = theta * np.pi / 180
    M = [[np.cos(theta), -np.sin(theta), 0], [np.sin(theta), np.cos(theta), 0], [0, 0, 1]]
    return M

def rotxV(x, theta):
    """Rotate a coordinate in the local x frame"""
    M = [[1, 0, 0], [0, np.cos(theta), -np.sin(theta)], [0, np.sin(theta), np.cos(theta)]]
    return np.dot(M, x)

def rotyV(x, theta):
    """Rotate a coordinate in the local y frame"""
    M = [[np.cos(theta), 0, np.sin(theta)], [0, 1, 0], [-np.sin(theta), 0, np.cos(theta)]]
    return np.dot(M, x)

def rotzV(x, theta):
    """Roate a coordinate in the local z frame"""
    M = [[np.cos(theta), -np.sin(theta), 0], [np.sin(theta), np.cos(theta), 0], [0, 0, 1]]
    return np.dot(M, x)

def rotVbyW(V, W, theta):
    """Rotate a vector V, about an axis W by angle theta"""
    ux = W[0]
    uy = W[1]
    uz = W[2]

    c = np.cos(theta)
    s = np.sin(theta)
    if (
        np.array(theta).dtype == np.dtype("D")
        or np.array(W).dtype == np.dtype("D")
        or np.array(V).dtype == np.dtype("D")
    ):
        dtype = "D"
    else:
        dtype = "d"

    R = np.zeros((3, 3), dtype)

    R[0, 0] = ux ** 2 + (1 - ux ** 2) * c
    R[0, 1] = ux * uy * (1 - c) - uz * s
    R[0, 2] = ux * uz * (1 - c) + uy * s

    R[1, 0] = ux * uy * (1 - c) + uz * s
    R[1, 1] = uy ** 2 + (1 - uy ** 2) * c
    R[1, 2] = uy * uz * (1 - c) - ux * s

    R[2, 0] = ux * uz * (1 - c) - uy * s
    R[2, 1] = uy * uz * (1 - c) + ux * s
    R[2, 2] = uz ** 2 + (1 - uz ** 2) * c

    return np.dot(R, V)

# --------------------------------------------------------------
#                Array Rotation and Flipping Functions
# --------------------------------------------------------------

def rotateCCW(inArray):
    """Rotate the inArray array 90 degrees CCW"""
    rows = inArray.shape[0]
    cols = inArray.shape[1]
    output = np.empty([cols, rows], inArray.dtype)

    for row in range(rows):
        for col in range(cols):
            output[cols - col - 1][row] = inArray[row][col]

    return output

def rotateCW(inArray):
    """Rotate the inArray array 90 degrees CW"""
    rows = inArray.shape[0]
    cols = inArray.shape[1]
    output = np.empty([cols, rows], inArray.dtype)

    for row in range(rows):
        for col in range(cols):
            output[col][rows - row - 1] = inArray[row][col]

    return output

def reverseRows(inArray):
    """Flip Rows (horizontally)"""
    rows = inArray.shape[0]
    cols = inArray.shape[1]
    output = np.empty([rows, cols], inArray.dtype)
    for row in range(rows):
        output[row] = inArray[row][::-1].copy()

    return output

def reverseCols(inArray):
    """Flip Cols (vertically)"""
    rows = inArray.shape[0]
    cols = inArray.shape[1]
    output = np.empty([rows, cols], inArray.dtype)
    for col in range(cols):
        output[:, col] = inArray[:, col][::-1].copy()

    return output

def orientArray(index, inArray):
    """Take an input array inArray, and rotate/flip according to the index
    output from quadOrientation (in orientation.py)"""

    if index == 0:
        outArray = inArray.copy()
    elif index == 1:
        outArray = rotateCCW(inArray)
        outArray = rotateCCW(outArray)
        outArray = reverseRows(outArray)
    elif index == 2:
        outArray = reverseRows(inArray)
    elif index == 3:
        outArray = rotateCCW(inArray)  # Verified working
        outArray = rotateCCW(outArray)
    elif index == 4:
        outArray = rotateCW(inArray)
        outArray = reverseRows(outArray)
    elif index == 5:
        outArray = rotateCCW(inArray)
    elif index == 6:
        outArray = rotateCW(inArray)
    elif index == 7:
        outArray = rotateCCW(inArray)
        outArray = reverseRows(outArray)

    return outArray


# Program to add two matrices using list comprehension

X = [[12, 7, 3],
     [4, 5, 6],
     [7, 8, 9]]

Y = [[5, 8, 1],
     [6, 7, 3],
     [4, 5, 9]]

result = [[X[i][j] + Y[i][j]  for j in range(len(X[0]))] for i in range(len(X))]

for r in result:
    print(r)

# Using NumPy
import numpy as np

A = np.array([[2, 4], [5, -6]])
B = np.array([[9, -3], [3, 6]])
C = A + B      # element wise addition
print(C)

# Program to transpose a matrix using list comprehension

X = [[12, 7],
     [4, 5],
     [3, 8]]

result = [[X[j][i] for j in range(len(X))] for i in range(len(X[0]))]

for r in result:
    print(r)

# Using NumPy
A = np.array([[1, 1], [2, 1], [3, -3]])
print(A.transpose())

# Program to multiply two matrices using list comprehension

# 3x3 matrix
X = [[12, 7, 3],
     [4, 5, 6],
     [7, 8, 9]]

# 3x4 matrix
Y = [[5, 8, 1, 2],
     [6, 7, 3, 0],
     [4, 5, 9, 1]]

# result is 3x4
result = [[sum(a*b for a,b in zip(X_row,Y_col)) for Y_col in zip(*Y)] for X_row in X]

for r in result:
    print(r)

# Using NumPy
A = np.array([[3, 6, 7], [5, -3, 0]])
B = np.array([[1, 1], [2, 1], [3, -3]])
C = A.dot(B)
print(C)


# -*- coding: utf-8 -*-
"""
Created on Fri Jan 19 10:05:43 2018

@author: philt
"""

import numpy as np
import matplotlib.pyplot as plt

# load the data
X = []
Y = []
for line in open('data_poly.csv'):
    x, y = line.split(',')
    x = float(x)

    X.append([1, x, x*x]) # add the bias term
    Y.append(float(y))

# let's turn X and Y into numpy arrays since that will be useful later
X = np.array(X)
Y = np.array(Y)

# let's plot the data to see what it looks like
plt.scatter(X[:,1], Y)
plt.show()

# apply the equations we learned to calculate a and b
# numpy has a special method for solving Ax = b
# so we don't use x = inv(A)*b
# note: the * operator does element-by-element multiplication in numpy
#       np.dot() does what we expect for matrix multiplication
w = np.linalg.solve(np.dot(X.T, X), np.dot(X.T, Y))
Yhat = np.dot(X, w)

# Plot the results
plt.scatter(X[:,1], Yhat)
plt.plot(sorted(X[:,1]), sorted(Yhat))
plt.show()

# determine how good the model is by computing the r-squared
d1 = Y - Yhat
d2 = Y - Y.mean()
r2 = 1 - d1.dot(d1) / d2.dot(d2)
print("the r-squared is:", r2)


import pandas as pd
import numpy as np
import math
from decimal import Decimal

def jaccard_similarity(dataFrame, row):
    intersection_cardinality = len(set.intersection(*[set(dataFrame), set(row)]))
    union_cardinality = len(set.union(*[set(dataFrame), set(row)]))
    return 1 - (intersection_cardinality/float(union_cardinality))

def nth_root(value, n_root):
    root_value = 1/float(n_root)
    return round(Decimal(value) ** Decimal(root_value), 3)

def minkowski_distance(x, y, p_value):
    return nth_root(sum(pow(abs(a-b), p_value) for a, b in zip(x, y)), p_value)

def manhattan_distance(dataFrame, row):
    return sum(abs(dataFrame - row) for dataFrame, row in zip(dataFrame, row))

def euclidian_distance(dataFrame, row):
    return np.linalg.norm(dataFrame.iloc[:, :-1].sub(row[:-1]), axis=1)

def cosine_similarity(dataFrame, row):
    return 1 - np.dot(dataFrame, row) / (np.sqrt(np.dot(dataFrame, dataFrame)) * np.sqrt(np.dot(row, row)))

def lowest_distance_rows(index, dataFrame,  euc_prediction, defined_row=None,  num_of_rows=1, multiple_lines=False):
    best_matching_species = []

    if not defined_row:
        defined_row = dataFrame.iloc[index].tolist()

    if euc_prediction == True:
        dataFrame['distance'] = euclidian_distance(dataFrame, defined_row)
    else:
        dataFrame['distance'] = dataFrame.iloc[:, :-1].apply(cosine_similarity, row=(defined_row[:-1]), axis=1)

    best_matching_species = dataFrame.sort_values(['distance']).iloc[0, -2]

    if multiple_lines:
        best_matching_species = dataFrame.sort_values(
            ['distance']).iloc[1:num_of_rows+1]

    dataFrame.drop('distance', axis=1, inplace=True)

    return best_matching_species

def randomize_data(dataFrame):
    dataFrame = dataFrame.sample(frac=1).reset_index(drop=True)
    return dataFrame[:100], dataFrame[100:].reset_index(drop=True)

def check_right_prediction(possible_predictions, answer):
    if answer in possible_predictions:
        return True
    return False

def count_predictions(test_data, train_data, num_of_rows, euclidian_prediction=True):
    right_predictions = 0
    for i in range(len(test_data.index)):
        row_of_test = test_data.iloc[i].tolist()
        if check_right_prediction(lowest_distance_rows(i, train_data, euclidian_prediction, row_of_test, num_of_rows), row_of_test[-1]):
            right_predictions += 1
    return right_predictions

# opening iris data
pd.options.mode.chained_assignment = None

dataFrame = pd.read_csv("iris.csv")
list_of_ranges = [1, 3, 5]

# mean, max and min of each column (a, b)
print(dataFrame.mean(), "\n")
print(dataFrame.iloc[:, :-1].max(), "\n")
print(dataFrame.iloc[:, :-1].min(), "\n")

# 10 rows with lower sum (c)
print(dataFrame.sum(axis=1, numeric_only=True).sort_values(
    ascending=True).head(10), '\n')

# closest line(s) of nth line (d, e ,f)
for show_range in list_of_ranges:
    print(lowest_distance_rows(11, dataFrame, False, None, show_range, True), '\n')

# 1st Task - Mix Data
dataFrame = dataFrame.sample(frac=1).reset_index(drop=True)

# 2nd Task - Separe Training and Test Data (2/1)
train_data, test_data = dataFrame[:100], dataFrame[100:].reset_index(drop=True)

# 3rd Task - Make Predictions Based on Euc. Distance & Cosine Similarity

num_of_right_predictions = []

for show_range in list_of_ranges:
    print('Actual Range:',show_range)
    # print("Euclidian Distance - Number of right predictions: ", count_predictions(test_data, train_data, show_range))
    print('Right Predictions - Euclidian Distance:', count_predictions(test_data, train_data, show_range) / 50 * 100,'%')
    train_data, test_data = randomize_data(dataFrame)
    # print("Cosine Similarity - Number of right predictions:",count_predictions(test_data, train_data, show_range, False))
    print('Right Predictions - Cosine Similarity:', count_predictions(test_data, train_data, show_range, False) / 50 * 100,'%')
    print()


import numpy as np
from . import wavelet

# Imported from I3D

def L0(x, mu):
    """ Proximal operator of the L0 norm
    """
    return x * (abs(x) > mu)

def L1(x, mu):
    """ Proximal operator of the L1 norm
    """
    return np.sign(x) * np.maximum(0, abs(x) - mu)

def L1_wavelet(x, mu):
    """ Compute the proximal operator evaluated at x of the function
        mu*||Wx||_1
        where W is the orthogonal wavelet transform and mu > 0
    """
    w = wavelet.transform(x)
    w = L1(w, mu)  # the prox.L1
    p = wavelet.inverse_transform(w)
    return p

def L2_sq(x, mu):
    """ Proximal operator of the L2 squared norm: 0.5*norm(.,2)**2 """
    return x / (1+mu)

def KL(x, mu, y):
    """ Proximal operator of the Kullback-Liebler divergence: KL(y,.)
            KL(y;x) = \sum_i y_i*ln(y_i/x_i) + x_i - y_i
    """
    return 0.5 * (x - mu + np.sqrt( (x-mu)**2 + 4*mu*y) )

def simplex(x, z=1):
    # Projection sur le simplexe https://gist.github.com/mblondel/6f3b7aaad90606b98f71
    dimension = x.shape
    v = x.flatten()
    u = np.sort(v)[::-1]
    cssv = np.cumsum(u) - z
    ind = np.arange(u.shape[0]) + 1
    cond = u - cssv / ind > 0
    rho = ind[cond][-1]
    theta = cssv[cond][-1] / float(rho)
    w = np.maximum(v - theta, 0)
    return w.reshape(dimension)

def L1_ball(x, s=1):
    # Projection sur la boule L1 https://gist.github.com/daien/1272551/edd95a6154106f8e28209a1c7964623ef8397246
    dimension = x.shape
    v = x.flatten()
    u = np.abs(v) # compute the vector of absolute values
    if u.sum() <= s: # check if v is already a solution
        return x
    w = simplex(u, s) # project *u* on the simplex
    w *= np.sign(v) # compute the solution to the original problem on v
    return w.reshape(dimension)

def L1_wavelet_ball(x, mu=1):
    """ Compute the projection of x onto the set
        ||Wx||_1 <= mu
        where W is the orthogonal wavelet transform and mu > 0
    """
    return wavelet.inverse_transform(L1_ball(wavelet.transform(x), mu))


import numpy as np
import matplotlib.pyplot as plt

n=20

def f_lin(x):
    return 8*x-7

def f_exp(x):
    return np.exp(2*x)

def f_patr(x):
    return 5*x**2 - 9*x + 4

def regLiniara(X, Y):
    """

    :param X: Vectorul de valori x
    :param Y: Vectorul de valori random y
    :return: a si b astfel incat y = ax + b sa fie cat mai apropiata de a cuprinde penctele (xi, yi)
    """
    A = np.zeros((2, 2))
    w = np.zeros((2, 1))
    for i in range(n):
        A[0][0] += X[i]**2
        A[0][1] += X[i]
        A[1][0] += X[i]
        w[0][0] += X[i]*Y[i]
        w[1][0] += Y[i]
    A[1][1] = n
    sol = gaussPp(A, w, 10**-16)
    a = sol[0][0]
    b = sol[1][0]
    return a, b

def regPol(X, Y):
    """

    :param X: Vectorul de valori x
    :param Y: Vectorul de valori random y
    :return: a, b si c astfel incat y = ax^2 + bx + c sa fie cat mai apropiata de a cuprinde penctele (xi, yi)
    """
    A = np.zeros((3,3))
    w = np.zeros((3, 1))
    for i in range(n):
        A[0][0] += X[i]**4
        A[0][1] += X[i]**3
        A[0][2] += X[i]**2
        A[1][0] += X[i] ** 3
        A[1][1] += X[i] ** 2
        A[1][2] += X[i]
        A[2][0] += X[i] ** 2
        A[2][1] += X[i]
        w[0][0] += Y[i] * X[i]**2
        w[1][0] += Y[i] * X[i]
        w[2][0] += Y[i]
    A[2][2] = n
    sol = gaussPp(A, w, 10**-16)
    a = sol[0][0]
    b = sol[1][0]
    c = sol[2][0]
    return a, b, c

def regExp(X, Y):
    """

    :param X: Vectorul de valori x
    :param Y: Vectorul de valori random y
    :return: a si b astfel incat y = b*e^(ax) sa fie cat mai apropiata de a cuprinde penctele (xi, yi)
    """
    A = np.zeros((2,2))
    w = np.zeros((2,1))
    for i in range(n):
        A[0][0] += X[i]**2
        A[0][1] += X[i]
        A[1][0] += X[i]
        w[0][0] += np.log(Y[i])*X[i]
        w[1][0] += np.log(Y[i])
    A[1][1] = n
    sol = gaussPp(A, w, 10**-16)
    a = sol[0][0]
    b_prim = sol[1][0]
    b = np.exp(b_prim)
    return a, b

def metSubsDesc(A, b, tol):
    """
    param A: matrice patratica, superior triunghiulara, cu toate elem pe diag principala nenule
    param b: vectorul term liberi
    param tol: val numerica ft mica in rap cu care vom compara numerele apropiate de 0
    return: sol sistem
    """

    m, n = np.shape(A)
    if m != n:
        print("Matricea nu este patratica. Introduceti o alta matrice.")
        x = None
        return x

    # Verificam daca matricea este superior triunghiulara
    for i in range(m):
        for j in range(i):
            if abs(A[i][j]) > tol:
                print("Matricea nu este superior triunghiulara")
                x = None
                return x

    # Verificam daca elementele de pe diagonala principala sunt nule (sist comp det)
    for i in range(n):
        if A[i][i] == 0:
            print("Sistemul nu este compatibil determinat")
            x = None
            return x
    x = np.zeros((n, 1))
    x[n - 1] = 1 / A[n - 1][n - 1] * b[n - 1]
    k = n - 2
    while k >= 0:
        sum = 0
        for i in range(k + 1, n):
            sum += A[k][i] * x[i]
        x[k] = 1 / A[k][k] * (b[k] - sum)
        k = k - 1
    return x

def gaussPp(A, b, tol):
    """
    param A: matricea asoc sistemului, patratica
    param b: vectorul term liberi
    param tol: val cu care comparam nr nenule
    return x: solutia sistemului, numarul de interschimbari de linii si lista de pivoti
    """
    m, n = np.shape(A)
    if m != n:
        print("Matricea nu este patratica. Introduceti o alta matrice.")
        x = None
        return x

    A_extins = np.concatenate((A, b), axis=1)  # axis=0 l-ar pune pe b o noua linie, 1 il pune drept coloana
    for k in range(n - 1):
        max = A_extins[k][k]
        p = k
        for j in range(k + 1, n):
            if abs(A_extins[j][k]) > abs(max):
                max = A_extins[j][k]
                p = j
        if abs(max) <= tol:
            print("Sistemul nu admite solutie unica")
            x = None
            return x

        if (p != k):
            A_extins[[p, k]] = A_extins[[k, p]]  # swap linia p cu linia k
        for j in range(k + 1, n):
            A_extins[j] = A_extins[j] - (A_extins[j][k] / A_extins[k][k]) * A_extins[k]

    if abs(A_extins[n - 1][n - 1]) <= tol:
        print("Sistemul nu admite solutie unica")
        x = None
        return x

    x = metSubsDesc(A_extins[:, 0:n], A_extins[:, n], tol)
    return x

X = np.linspace(1, n, n)

Y = np.random.rand(n)

Y1 = f_lin(X) + Y

print("Valorile random obtinute: "+str(Y))
a, b = regLiniara(X, Y1)
p1 = a*X[0] + b
p2 = a*X[n-1] + b

plt.plot(X, Y1, 'o', color='red', markersize=10)
plt.plot([X[0], X[n-1]], [p1, p2], '-', color='blue')
plt.show()

Y2 = f_patr(X) + Y
a, b, c = regPol(X, Y2)

Y_aux = []
for i in range(n):
    Y_aux.append(a*X[i]**2+b*X[i]+c)
plt.plot(X, Y2, 'o', color='red', markersize=10)
plt.plot(X, Y_aux, '-', color='blue')
plt.show()

Y3 = f_exp(X) + Y
a, b = regExp(X, Y3)

Y_aux = []
for i in range(n):
    Y_aux.append(b*np.exp(a*X[i]))

plt.plot(X, Y3, 'o', color='red', markersize=10)
plt.plot(X, Y_aux, '-', color='blue')
plt.show()


# coding: utf-8

# # Function dftmatrix
#
# ## Synopse
#
# Kernel matrix for the 1-D Discrete Fourier Transform DFT.
#
# - **A = dftmatrix(N)**
#
#   - **A**: Output image, square N x N, complex
#
#
#   - **N**: Integer, number of points of the DFT

# In[1]:

import numpy as np

def dftmatrix(N):
    x = np.arange(N).reshape(N,1)
    u = x
    Wn = np.exp(-1j*2*np.pi/N)
    A = (1./np.sqrt(N)) * (Wn ** u.dot(x.T))
    return A

# ## Examples

# In[1]:

testing = (__name__ == "__main__")

if testing:
    get_ipython().system(' jupyter nbconvert --to python dftmatrix.ipynb')
    import numpy as np
    import sys,os
    import matplotlib.image as mpimg
    ea979path = os.path.abspath('../../')
    if ea979path not in sys.path:
        sys.path.append(ea979path)
    import ea979.src as ia

# ### Example 1

# In[2]:

if testing:
    A = ia.dftmatrix(128)
    ia.adshow(ia.normalize(A.real),'A.real')
    ia.adshow(ia.normalize(A.imag),'A.imag')

# Example 2
# ---------

# In[3]:

if testing:
    A = ia.dftmatrix(4)
    print('A=\n', A.round(1))
    print('A-A.T=\n', A - A.T)
    print((np.abs(np.linalg.inv(A)-np.conjugate(A))).max() < 10E-15)

# ### Example 3
#
# Showing the product $\mathbf{x}\mathbf{u}^T$:

# In[4]:

if testing:
    u = x = np.arange(10).reshape(10,1)
    print('u xT=\n', u.dot(x.T))

# ## Equation
#
#
# $$ \begin{matrix}
#     W_N &=& \exp{\frac{-j2\pi}{N}} \\ A_N &=& \frac{1}{\sqrt{N}} (W_N)^{\mathbf{u} \mathbf{x}^T} \\ \mathbf{u} &=& \mathbf{x} = [0, 1, 2, \ldots, N-1]^T
# \end{matrix} $$

# $$ \begin{matrix}
#         A_N       &=& A_N^T \ \mbox{symmetric} \\
#        (A_N)^{-1} &=& (A_N)^*\ \mbox{column orthogonality, unitary matrix}
# \end{matrix} $$

# ## See Also
#
# - `dft` - Discrete Fourier Transform
# - `dftmatrixexamples` - Visualization of the DFT matrix
#
# ## References
#
# - http://en.wikipedia.org/wiki/DFT_matrix

# In[6]:

if testing:
    print('testing dftmatrix')
    print(repr(np.floor(0.5 + 10E4*ia.dftmatrix(4).real) / 10E4) == repr(np.array(
          [[ 0.5,  0.5,  0.5,  0.5],
           [ 0.5,  0. , -0.5,  0. ],
           [ 0.5, -0.5,  0.5, -0.5],
           [ 0.5,  0. , -0.5,  0. ]])))
    print(repr(np.floor(0.5 + 10E4*ia.dftmatrix(4).imag) / 10E4) == repr(np.array(
          [[ 0. ,  0. ,  0. ,  0. ],
           [ 0. , -0.5,  0. ,  0.5],
           [ 0. ,  0. ,  0. ,  0. ],
           [ 0. ,  0.5,  0. , -0.5]])))

# In[ ]:


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

This file contains the Kernel class. An object that
returns a kernel function

"""

import numpy as np

class Kernel():
    def __init__(self, choice, param1=None, param2=None):
        self.kernel = set_kernel_by_choice(choice, param1, param2)
        self.choice = choice

    def get_kernel(self):
        return self.kernel

    def get_type(self):
        typ = self.choice
        if typ == 'linear':
            return 'linear'
        if typ == 'polynomial':
            return 'poly'
        if typ == 'gaussian':
            return 'rbf'
        if typ == 'sigmoid':
            return 'sigmoid'
        else:
            return 'other'

def set_kernel_by_choice(choice, param1, param2):
    if choice == 'linear':
        return lambda x1, x2: np.inner(x1, x2)
    elif choice == 'sigmoid':
        return lambda x1, x2: np.tanh(param1 * np.dot(x1, x2) + param2)
    elif choice == 'gaussian':
        return lambda x1, x2: np.exp(-1*param1*np.linalg.norm(np.subtract(x1, x2)))
    elif choice == 'polynomial':
        return lambda x1, x2: (param1 + np.inner(x1, x2)) ** param2


import numpy as np
import scipy as sp
import matplotlib.pyplot as plt
from scipy import special
from scipy.stats import norm

def get_data():
  true_dis = np.random.uniform(0,1,size=800)
  pr_dis = []
  for num in true_dis:
    if num <= 0.25:
      pr = np.random.normal(0,1)
    elif num <= 0.5:
      pr = np.random.normal(3,1)
    elif num <= 0.75:
      pr = np.random.normal(6,1)
    else:
      pr = np.random.normal(9,1)

    pr_dis.append(pr)
  return pr_dis

def get_prazen(data,h,pr_dis):
  n = len(pr_dis)
  y = []
  # print (n)
  for num in data:
    # summ = 0.0
    diff = [1 if np.abs(num-pr) <= h/2.0 else 0 for pr in pr_dis ]
    summ = sum(diff)
    # print ("SUM - ",summ)
    summ/=(n*h*1.0)
    y.append(summ)

  # print ("Prazen " - y)
  return y

#Q-7-a
# getting alphas
X = np.arange(-5,10,0.1)
alpha = X
pr_dis = get_data()
# True distribution
Y0 = 0.25*(norm.pdf(alpha,0,1) + norm.pdf(alpha,3,1) + norm.pdf(alpha,6,1) + norm.pdf(alpha,9,1))
# get kernel density estimator
Y = get_prazen(alpha,0.1,pr_dis)
Y2 = get_prazen(alpha,1,pr_dis)
Y3 = get_prazen(alpha,7,pr_dis)

# plotting the graphs
plt.figure('Kernel Density Estimation',figsize=(15,10))
plt.plot(X, Y0 ,label='True pdf')
plt.plot(X, Y ,label='h = 0.1')
plt.plot(X, Y2 ,label='h = 1')
plt.plot(X, Y3 ,label='h = 7')

plt.xlabel('x', fontsize=18)
plt.ylabel('Pr[X<=x]', fontsize=18)
plt.legend(loc="upper left", prop={'size': 16})
plt.grid()
plt.show()
plt.savefig("PDF.png")

def get_bias(kd_matrix, true_matrix):
  # pr = np.mean(diff, axis=0)
  avg_kd_matrix = np.mean(kd_matrix,axis=0)
  pr = np.subtract(avg_kd_matrix, true_matrix)
  return pr

def get_var(kde_matrix, estimation):
  diff = np.subtract(kd_matrix, estimation)
  print (diff.shape)
  sqr = np.power(diff,2)
  pr = np.mean(sqr, axis=0)
  return pr

#Q-7-b
h_s = [0.01,0.1,0.3,0.6,1,3,7]
mse_list = []
var_list = []
bias_list = []
alpha = np.arange(-5,10,0.1)
y_0 = 0.25*(norm.pdf(alpha,0,1) + norm.pdf(alpha,3,1) + norm.pdf(alpha,6,1) + norm.pdf(alpha,9,1))

# generate 150 samples of 800 data points
raw_matrix = []
for i in range(150):
    b = get_data()
    raw_matrix.append(b)

for h in h_s:
  print ("h - ",h)
  kd_matrix = []
  true_matrix = []

  for i in range(150):
    b = raw_matrix[i]
    kde_0 = get_prazen(alpha,h,b)
    kd_matrix.append(kde_0)

  kd_matrix = np.array(kd_matrix)
  estimation = kd_matrix.mean(axis=0)

  bias = get_bias(kd_matrix, y_0)

  total_bias = [i**2 for i in bias]
  total_bias_2 = np.mean(total_bias)

  var = get_var(kd_matrix, estimation)
  var_total = np.mean(var)

  mse = var + bias**2
  mse_total = total_bias_2 + var_total

  mse_list.append(mse_total)
  bias_list.append(total_bias_2)
  var_list.append(var_total)

print ("Minimum value of MSE = {} at h = {}".format(min(mse_list),h_s[np.argmin(mse_list)]))

plt.figure('Bias Square vs h',figsize=(10,8))
plt.plot(h_s, bias_list)
plt.xlabel('h',fontsize=16)
plt.ylabel('bias-square',fontsize=18)
plt.grid()
plt.show()
plt.savefig("bias-square.png")

plt.figure('Variance vs h', figsize=(10,8))
plt.plot(h_s, var_list)
plt.xlabel('h',fontsize=16)
plt.ylabel('Variance',fontsize=16)
plt.grid()
plt.show()
plt.savefig("variance.png")


import numpy as np

with open('src/day7/input.txt') as f:
    positions = [int(i) for i in f.read().strip().split(',')]

def calc_total_loss(loss_fxn, data, parameter):
    loss = sum([loss_fxn(d, parameter) for d in positions])
    return loss

def l1_loss_fxn(point, parameter):
    return abs(point-parameter)

def part2_loss_fxn(point, parameter):
    # identity 1+2+..+n = n(n+1)/2
    n = abs(point - parameter)
    return n * (n + 1) / 2

def minimize_loss(loss_fxn, data, parameter_space):
    min_loss = float('inf')
    p_star = None  # Parameter that minimizes loss function
    for p in parameter_space:
        loss = calc_total_loss(loss_fxn, data, p)
        if loss < min_loss:
            min_loss = loss
            p_star = p
    return min_loss, p_star

# Brute Force search entire parameter space
parameter_space = range(min(positions), max(positions)+1)
part1_sol = minimize_loss(l1_loss_fxn, positions, parameter_space)

# Can prove median minimizes L1 loss
med = np.median(positions)
if type(med) is int:
    med_loss = calc_total_loss(l1_loss_fxn, positions, med)
else:
    med_loss = minimize_loss(l1_loss_fxn, positions, [np.floor(med), np.ceil(med)])

assert med_loss == part1_sol

part2_sol = minimize_loss(part2_loss_fxn, positions, parameter_space)
print("Part 1 Solution:", part1_sol[0])
print("Part 2 Solution:", part2_sol[0])


"""
From scratch implementation of K means clustering which is a unsupervised
clustering  method that works by iteratively computing new centroids and
moving centroids to the center of the new formed clusters.

Programmed by Aladdin Persson <aladdin.persson at hotmail dot com>
*    2020-05-28 Initial coding

"""

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs

class KMeansClustering:
    def __init__(self, X, num_clusters):
        self.K = num_clusters
        self.max_iterations = 100
        self.plot_figure = True
        self.num_examples = X.shape[0]
        self.num_features = X.shape[1]

    def initialize_random_centroids(self, X):
        centroids = np.zeros((self.K, self.num_features))

        for k in range(self.K):
            centroid = X[np.random.choice(range(self.num_examples))]
            centroids[k] = centroid

        return centroids

    def create_clusters(self, X, centroids):
        # Will contain a list of the points that are associated with that specific cluster
        clusters = [[] for _ in range(self.K)]

        # Loop through each point and check which is the closest cluster
        for point_idx, point in enumerate(X):
            closest_centroid = np.argmin(
                np.sqrt(np.sum((point - centroids) ** 2, axis=1))
            )
            clusters[closest_centroid].append(point_idx)

        return clusters

    def calculate_new_centroids(self, clusters, X):
        centroids = np.zeros((self.K, self.num_features))
        for idx, cluster in enumerate(clusters):
            new_centroid = np.mean(X[cluster], axis=0)
            centroids[idx] = new_centroid

        return centroids

    def predict_cluster(self, clusters, X):
        y_pred = np.zeros(self.num_examples)

        for cluster_idx, cluster in enumerate(clusters):
            for sample_idx in cluster:
                y_pred[sample_idx] = cluster_idx

        return y_pred

    def plot_fig(self, X, y):
        plt.scatter(X[:, 0], X[:, 1], c=y, s=40, cmap=plt.cm.Spectral)
        plt.show()

    def fit(self, X):
        centroids = self.initialize_random_centroids(X)

        for it in range(self.max_iterations):
            clusters = self.create_clusters(X, centroids)

            previous_centroids = centroids
            centroids = self.calculate_new_centroids(clusters, X)

            diff = centroids - previous_centroids

            if not diff.any():
                print("Termination criterion satisfied")
                break

        # Get label predictions
        y_pred = self.predict_cluster(clusters, X)

        if self.plot_figure:
            self.plot_fig(X, y_pred)

        return y_pred

if __name__ == "__main__":
    np.random.seed(10)
    num_clusters = 3
    X, _ = make_blobs(n_samples=1000, n_features=2, centers=num_clusters)

    Kmeans = KMeansClustering(X, num_clusters)
    y_pred = Kmeans.fit(X)


from IPython.display import display, Latex, Math

print("We can get rid of all parameters by setting: ")
display(Latex('$U=C/C_{max}$'))
print("and")
display(Latex('$T=t*\lambda$'))
print("both of which are unit-less")
print("Then the ODE becomes:")
print("")
display(Latex('$dU/dT=-U*\ln U,$'))
print("")
print("As we can see, there is no parameter, so we can use a natural scale for the time step (i.e., neither delta t = 0.00000001 nor delta t  = 1000000)")
print("")
print("Sequence of instructions for the forward Euler method:")
print("- delta_t=0.001")
print("- tmax=10")
print("- N=10000")
print("- U=array_of_zeros(1 row, 10001 columns ")
print("- U(1)=U0, initial value of the renormalized variable U")
print("- T=array_of_zeros(1 row, 10001 columns ")
print("- FOR i=1 to N+1 do T(i)=(i-1)*delta_t  end FOR loop   : we define the time grid")
print("- for i=1 to N do:")
print("      U(i+1)=U(i)-delta_t*U(i)*ln(U(i))")
print("end FOR loop")
print("plot({T(i),U(i)}")

print("")
print("Sequence of instructions for the RK4 method:")
print("- delta_t=0.001")
print("- tmax=10")
print("- N=10000")
print("- U=array_of_zeros(1 row, 10001 columns ")
print("- U(1)=U0, initial value of the renormalized variable U")
print("- T=array_of_zeros(1 row, 10001 columns ")
print("- FOR i=1 to N+1 do T(i)=(i-1)*delta_t  end FOR loop   : we define the time grid")
print("- for i=1 to N do:")
print("      k1=-U(i)*ln(U(i)) ")
print("      k2=-[U(i)+k1*delta_t/2]*ln([U(i)+k1*delta_t/2]) ")
print("      k3=-[U(i)+k2*delta_t/2]*ln([U(i)+k2*delta_t/2]) ")
print("      k4=-[U(i)+k3*delta_t]*ln([U(i)+k3*delta_t]) ")
print("      U(i+1)=U(i)+delta_t*(k1+2*k2+2*k3+k4)/6")
print("end FOR loop")
print("plot({T(i),U(i)}")

print("")
print("We note that the Euler method is equivalent to a Runge Kutta 4 scheme with k1=k2=k3=k4")

print("")
print("Python code for the Runge Kutta method:")

import numpy as np
import matplotlib.pyplot as plt

delta_t=0.001
tmax=10
N=10000
U = np.zeros(N+1)
T = np.zeros(N+1)
U[0]=0.5
for i in range(0,N):  # Python starts indexing rows and columns at 0, not 1 as in the algorithm above
        T[i] = i*delta_t
for i in range(0,N):
        k1=-U[i]*np.log(U[i])
        k2=-(U[i]+0.5*k1*delta_t)*np.log((U[i]+0.5*k1*delta_t))
        k3=-(U[i]+0.5*k2*delta_t)*np.log((U[i]+0.5*k2*delta_t))
        k4=-(U[i]+k3*delta_t)*np.log((U[i]+k3*delta_t))
        U[i+1]=U[i]+delta_t*(k1+2*k2+2*k3+k4)/6

# plot the time variations of all the node values
plt.figure()
plt.plot(U,'-')
plt.show()


import numpy as np

def OrdenDeConvergencia(xNmenos2, xNmenos1, xN, xNmas1):
    """
    Calcula el orden de convergencia para determinado punto aplicando la division que se realiza en la definicion.
    Si algun denominador resultase nulo, no se devolvera una constante, se devuelve None.
    """
    a = xNmas1 - xN
    if a == 0:
        return None
    b = xN - xNmenos1
    if b == 0:
        return None

    numerador = np.log(abs(a / b))

    c = xN - xNmenos1
    if c == 0:
        return None

    d = xNmenos1 - xNmenos2
    if d == 0:
        return None

    denominador = np.log(abs(c / d))
    if denominador == 0:
        return None
    return numerador / denominador

#funcion original que habiamos hecho para calcular la constante alfa
def CalcularHistoriaDeOrden2(historia):
    """
    Calcula la constante alfa, necesita de la historia de la busqueda de la raiz.
    Devuelve la historia del alfa y el alfa final conseguido
    """
    tope = len(historia)
    if tope < 5:
        return 0, np.array([])
    historiaDeOrden = np.zeros((tope - 4, 2))

    j = 0
    for i in range(2, tope - 2):
        ordenActual = OrdenDeConvergencia(historia[i - 2][1], historia[i - 1][1], historia[i][1], historia[i + 1][1])
        if not (ordenActual is None):
            historiaDeOrden[j][1] = ordenActual
            historiaDeOrden[j][0] = i
            j = j + 1
    historiaDeOrden = historiaDeOrden[:j]
    return historiaDeOrden[j - 1][1], historiaDeOrden

def CalcularHistoriaDeOrden(historiaRaices):
    """
    Calcula la constante alfa, necesita de la historia de la busqueda de la raiz.
    Devuelve la historia del alfa y el alfa final conseguido
    """
    nIteraciones = len(historiaRaices) - 1
    if len(historiaRaices) < 5:
        return 0, np.array([])
    alfa = np.zeros((nIteraciones - 1, 2))

    for n in range(3 - 1, nIteraciones - 1):
        e_n_mas_1 = historiaRaices[n + 1][1] - historiaRaices[n][1]
        e_n = historiaRaices[n][1] - historiaRaices[n - 1][1]
        e_n_menos_1 = historiaRaices[n - 1][1] - historiaRaices[n - 2][1]

        # Tira un warning de division por 0 debido a que varios numeros estan cercanos al mismo, se agrego para evitarlo,
        # si se saltean estas divisiones los graficos quedan peor.
        with np.errstate(divide='ignore'):
            alfa[n] = n, np.log10(np.abs(e_n_mas_1 / e_n)) / np.log10(np.abs(e_n / e_n_menos_1))

    return alfa[nIteraciones - 2][1], alfa

def ConstanteAsintotica(xN, xNmas1, alfa, raiz):
    """
    Realiza el calculo de la constante asintotica para una iteracion. Los valores recibidos son validos.
    Si resulta que el denominador es 0, se devuelve None.
    """
    numerador = abs((xNmas1 - raiz))
    denominador = abs(xN - raiz) ** alfa
    if denominador == 0:
        return None
    return numerador / denominador

def CalcularHistoriaConstanteAsintotica(historia, alfa):
    """
    Calcula la constante lambda, necesita de la historia de la busqueda de la raiz y del alfa obtenido previamente
    Devuelve la historia del lambda y el lambda final conseguido
    """
    if alfa <= 0:
        return 0, np.array([])
    tope = len(historia)
    raiz = historia[tope - 1][1]

    historiaConstanteAsintotica = np.zeros((tope - 2, 2))
    j = 0
    for i in range(0, tope - 2):
        constanteActual = ConstanteAsintotica(historia[i][1], historia[i + 1][1], alfa, raiz)
        if not (constanteActual is None):
            historiaConstanteAsintotica[j][1] = constanteActual
            historiaConstanteAsintotica[j][0] = i
            j = j + 1

    historiaConstanteAsintotica = historiaConstanteAsintotica[:j]
    return historiaConstanteAsintotica[j - 1][1], historiaConstanteAsintotica