import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import glob
from scipy import stats, interpolate

# Regression

px = [625, 882, 1134, 1149]
nm = [671, 690, 708, 709]

slope, intercept, r_value, p_value, std_err = stats.linregress(px, nm)
print(f"a={slope:.5f}", f"b={intercept:.3f}",f"σ={std_err:.3e}")

x_new = np.array(range(1339))
nm = x_new*slope+intercept

cm = 1/(632.8e-7) - 1/(nm*1e-7)
i=0

# Spectre
for file in glob.glob("*.txt"):
    soln = file.split("_")[2]
    df = pd.read_csv(file)
    y = df.iloc[:, 2]
    y = y / max(abs(y))
    plt.plot(nm, y, label=f"{soln}", linewidth=0.5)

plt.xlabel("$\lambda$ [nm]")
plt.ylabel("Intensité relative [-]")
plt.legend(loc="upper center", ncol=5, bbox_to_anchor=(0.5, 1.1))
plt.savefig("Spectre_huiles.png")
plt.show()
plt.clf()

for file in glob.glob("*.txt"):
    soln = file.split("_")[2]
    print(soln)
    df = pd.read_csv(file)
    y = df.iloc[:, 2]
    # plt.plot(cm, y, label=f"{soln}", linewidth=0.5) # Spectre
    d = 25
    f2 = interpolate.interp1d(cm[200:][::d], y[200:][::d], kind='quadratic')
    y = y[200:1200]-f2(cm[200:1200])
    y = y / max(y)
    mask_1444 = (cm[200:1200] > 1400) & (cm[200:1200] < 1500)
    mask_1661 = (cm[200:1200] > 1600) & (cm[200:1200] < 1700)
    I_1444 = max(y[mask_1444])
    I_1661 = max(y[mask_1661])
    # plt.plot(cm[200:1200], f2(cm[200:1200]), label=f"{soln}", linewidth=0.5) # Background
    plt.plot(cm[200:1200], y+2*i, label=f"{soln}", linewidth=0.5)
    print(f"r: {I_1444/I_1661*100:.2f}%")
    i += 1

plt.xlabel("ν [1/cm]")
plt.ylabel("Intensité relative [-]")
plt.legend(loc="upper center", ncol=i, bbox_to_anchor=(0.5, 1.1))
plt.savefig("Raman_huiles.png")
plt.show()
plt.clf()


import math
import numpy as np

with open("input.txt") as f:
    instructions_raw = f.read().splitlines()

instructions_prepared = np.array([[int(bit) for bit in binary_number] for binary_number in instructions_raw])

height, width = instructions_prepared.shape
most_common_bit_threshold = int(height/2)

ones_per_column = np.count_nonzero(instructions_prepared, axis=0)
column_thresholds = ones_per_column - most_common_bit_threshold  # positive value -> 1, negative value -> 0
print(column_thresholds)
gamma = np.where(column_thresholds > 0, 1, 0)
epsilon = np.where(column_thresholds < 0, 1, 0)
print(f"  gamma = {gamma} \nepsilon = {epsilon}")

exponent = 2 ** np.arange(width-1, -1, -1)
print(exponent)
gamma_rate = np.sum(exponent * gamma)
epsilon_rate = np.sum(exponent * epsilon)
print(f"Calculated power consumption is {gamma_rate * epsilon_rate}, correct answer of part 1 is 841526.")

# part 2
# consider first bit of those numbers
# keep numbers selected by bit critera for the type of rating
# if there is only 1 number left, stop and return value
def calculate_gamma(instructions_prepared):
    height, width = instructions_prepared.shape
    most_common_bit_threshold = int(math.ceil(height / 2))  # round up when dealing with odd numbers

    ones_per_column = np.count_nonzero(instructions_prepared, axis=0)
    column_thresholds = ones_per_column - most_common_bit_threshold  # positive value -> 1, negative value -> 0
    print(column_thresholds)
    gamma = np.where(column_thresholds >= 0, 1, 0)  # use >= to map equal number of occurance to 1 to keep values with a one when there is a tie
    return gamma

instructions = instructions_prepared.tolist()

for column in range(width):
    gamma = calculate_gamma(np.array(instructions))
    bit_criteria = gamma[column]
    print(f"Calculated gamma {gamma}, column {column}, using bit_criteria {bit_criteria}")

    instructions = [row for row in instructions if row[column] == bit_criteria]
    print(instructions)
    if len(instructions) == 1:
        print(f"Found rating {instructions}")
        break

oxygen_generator_rating = np.sum(exponent * np.array(instructions))
print(oxygen_generator_rating)

def calculate_epsilon(instructions_prepared):
    height, width = instructions_prepared.shape
    most_common_bit_threshold = int(math.ceil(height / 2))  # round up when dealing with odd numbers

    ones_per_column = np.count_nonzero(instructions_prepared, axis=0)
    column_thresholds = ones_per_column - most_common_bit_threshold  # positive value -> 1, negative value -> 0
    #TODO pick the value that occurs the least often
    print("ones_per_column", ones_per_column, "column_thresholds", column_thresholds)
    #epsilon = np.where(column_thresholds <= 1, 0, 1)  # use < to map equal number of occurance to 0 to keep values with a 0 when there is a tie
    epsilon = np.where(ones_per_column < most_common_bit_threshold, 1, 0)
    return epsilon

instructions = instructions_prepared.tolist()
for column in range(width):
    epsilon = calculate_epsilon(np.array(instructions))
    bit_criteria = epsilon[column]
    print(f"Calculated epsilon {epsilon}, column {column}, using bit_criteria {bit_criteria}")

    instructions = [row for row in instructions if row[column] == bit_criteria]
    print(instructions)
    if len(instructions) == 1:
        print(f"Found rating {instructions}")
        break

co2_scrubber_rating = np.sum(exponent * np.array(instructions))
print(co2_scrubber_rating)

life_support_rating = oxygen_generator_rating * co2_scrubber_rating
print(f"Calculated life_support_rating = {life_support_rating}")  # correct answer is 4790390


# example of a stiff non-linear system -- this is the example defined in
# the VODE source code
#
# y1' = -0.04 y1 + 1.e4 y2 y3
# y2' =  0.04 y1 - 1.e4 y2 y3 - 3.e7 y2**2
# y3' =                         3.e7 y2**2
#
# y1(0) = 1
# y2(0) = 0
# y3(0) = 0
#
# long term behavior: y1 -> 0, y2 -> 0, y3 -> 1

import numpy as np

def rhs(t, Y):
    """ RHS of the system -- using 0-based indexing """
    y1 = Y[0]
    y2 = Y[1]
    y3 = Y[2]

    dy1dt = -0.04*y1 + 1.e4*y2*y3
    dy2dt =  0.04*y1 - 1.e4*y2*y3 - 3.e7*y2**2
    dy3dt =                         3.e7*y2**2

    return np.array([dy1dt, dy2dt, dy3dt])

def jac(t, Y):
    """ J_{i,j} = df_i/dy_j """

    y1 = Y[0]
    y2 = Y[1]
    y3 = Y[2]

    df1dy1 = -0.04
    df1dy2 = 1.e4*y3
    df1dy3 = 1.e4*y2

    df2dy1 = 0.04
    df2dy2 = -1.e4*y3 - 6.e7*y2
    df2dy3 = -1.e4*y2

    df3dy1 = 0.0
    df3dy2 = 6.e7*y2
    df3dy3 = 0.0

    return np.array([[ df1dy1, df1dy2, df1dy3 ],
                     [ df2dy1, df2dy2, df2dy3 ],
                     [ df3dy1, df3dy2, df3dy3 ]])


import numpy as np
from scipy.optimize import leastsq

##########################
### SEVERAL FUNCTIONS
##########################

def sigmo(x, base, amplitude, xhalf, slope):
    """
    Return the sigmoid curve for the x values.
    """
    return base + amplitude/(1.+np.exp(-slope*(x-xhalf)))

def expo_decay(x, amplitude, tconstant, base):
    """
    Return the exponential decay curve for the x values.
    """
    return amplitude*np.exp(-x/tconstant) + base

def expo_plateau(x, plateau, amplitude, tconstant):
    """
    Return the exponential plateau curve for the x values.
    """
    return plateau - amplitude*np.exp(-x/tconstant)

def hill(x, baseline, amplitude, tconstant, hillcoef):
    """
    Return the hill equation curve for the x values.
    """
    return baseline+amplitude*(x**hillcoef)/(x**hillcoef+tconstant**hillcoef)

def hill2(x, baseline, amplitude, tconstant, hillcoef):
    """
    Return the hill equation curve for the x values.
    """
    return (amplitude/(1.+((x/tconstant)**hillcoef))) + baseline

def double_expo_waveform(x, A, alpha, beta, offset):
    """
    Return the corresponding double exponential waveform curve
    Note: alpha<<beta
    """
    return A*(np.exp(-alpha*x)-np.exp(-beta*x))+offset


import numpy as np

def _normalize_score(scores, weights=None):
    """Normalize scores according to weights"""
    if weights is None:
        return scores.mean()
    else:
        return np.mean(np.dot(scores.T, weights) / weights.sum())

def mse(model, X, y, weights=None):
    """Mean Squared Error"""
    pred = model.predict(X)
    scores = np.square(y - pred)

    return _normalize_score(scores, weights)

def rmse(model, X, y, weights=None):
    """Root Mean Squared Error"""
    return np.sqrt(mse(model, X, y, weights))

def mae(model, X, y, weights=None):
    """Mean Aquared Error"""
    pred = model.predict(X)
    scores = np.abs(y - pred)

    return _normalize_score(scores, weights)

def poisson(model, X, y, weights=None):
    """Poisson Loss"""
    # from LightGBM implementation:
    # poiss_loss = pred - y * log(pred)
    # pred_exp = exp(pred)
    # poiss_loss = pred_exp - y * pred

    if np.any(y < 0):
        raise ValueError("Some value(s) of y are negative which is"
                         " not allowed for Poisson regression.")

    pred = np.exp(model.predict(X))
    scores = pred - y * np.log(pred)

    return _normalize_score(scores, weights)

def hamming(model, X, y, classes=None, weights=None):
    """Hamming Loss"""
    # classes param exist only for compatibility w/ crossentropy

    pred = model.predict(X)
    scores = (y != pred).astype(int)

    return _normalize_score(scores, weights)

def crossentropy(model, X, y, classes, weights=None):
    """Cross Entropy Loss"""
    pred = model.predict_proba(X)
    pred = pred.clip(1e-5, 1 - 1e-5)

    # loop in each class to limit memory spikes
    entropy = 0.
    for c,cls in enumerate(classes):
        y_cls = (y == cls).astype(int)
        scores = - (y_cls * np.log(pred[:,c]))
        entropy += _normalize_score(scores, weights)

    return entropy


import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler as sc
from sklearn.decomposition import PCA
from scipy import linalg as alg
r=pd.read_csv('bank_contacts.csv')
r_std=sc().fit_transform(r)
"""
method to find covariance matrix manually
mean_vec=np.mean(r_std,axis=0)
cov_mat=(r_std-mean_vec).T.dot((r_std-mean_vec)) / (r_std.shape[0]-1)
print("covariance matrix:",cov_mat)
"""
#by function
cov_mat=np.cov(r_std.T)
eig_val,eig_vec=alg.eig(cov_mat)
print(eig_val)
print(eig_vec)
#for visually confirming eigen pairs
eig_pairs=[(np.abs(eig_val[i]),eig_vec[:,i]) for i in range(len(eig_val))]
for i in eig_pairs:
    print(i[0])

t=PCA(n_components=2)
t.fit(r)
print(t.explained_variance_ratio_)
##for cumulative variance
#print(t.explained_variance_ratio_.sum())
u=t.fit(r_std)
f=t.transform(r_std)
print(f.shape)
print(r_std.shape)
plt.plot(np.cumsum(t.explained_variance_ratio_))
plt.xlabel('Number of components')
plt.ylabel('Cumulative explained variance')
plt.figure()
plt.scatter(f[:,0],f[:,1],cmap='plasma')
plt.xlabel('First pc')
plt.ylabel('Second pc')
plt.show()


"""
Functions from week 9
"""
import numpy as np
import pandas as pd
import xarray as xr
import matplotlib.pyplot as plt
from scipy import stats, linalg
import statsmodels.api as sm

# First we are going to rewrite the regression and trend functions to calculate linear regressions with p-values.

def regression(dependant, independant):
    independant = independant.reshape(-1, 1)
    if all(independant == 0):
        slope = np.array([np.nan])
        intercept = np.array([np.nan])
        prediction = np.array([np.nan]*18)
        p_value = np.nan
    else:
        slope, intercept = linalg.lstsq(independant, dependant)[:2]
        prediction = independant[0] * slope + intercept
        newX = pd.DataFrame({"Constant": np.ones(len(independant))}).join(
            pd.DataFrame(independant))
        MSE = (sum((dependant-prediction)**2))/(len(newX)-len(newX.columns))
        var_b = MSE*(np.linalg.inv(np.dot(newX.T, newX)).diagonal())
        sd_b = np.sqrt(var_b)
        ts_b = slope / sd_b
        p_values = [2*(1-stats.t.cdf(np.abs(i), (len(newX)-len(newX[0]))))
                    for i in ts_b]
        p_value = p_values[1]

    return slope, prediction, p_value

def fast_single_regression(dependant, independant):

    print(f'Running regression for {dependant.name} from {independant.name}')

    dependant = dependant.sortby('time')
    independant = independant.sortby('time')
    slope, intercept, r_value, p_value, std_err = xr.apply_ufunc(stats.linregress, independant, dependant,
                                                                 input_core_dims=[
                                                                     ['time'], ['time']],
                                                                 output_core_dims=[
                                                                     [], [], [], [], []],
                                                                 vectorize=True)
    print('Regression finished')

    out = xr.Dataset()
    out['dependant'] = dependant
    out['indepenant'] = independant
    out['regression'] = slope
    out['pvalues'] = p_value
    out['correlation'] = r_value
    out['intercept'] = intercept
    out['prediction'] = intercept + slope * independant

    return out

def calculate_trend(da):
    independant = da.time.astype(float).values * 1e9*60*60*24*365

    def trend(dependant): return stats.linregress(dependant, independant)

    slope, intercept, r_value, p_value, std_err = xr.apply_ufunc(trend, da,
                                                                 input_core_dims=[
                                                                     ['time'], ['time']],
                                                                 output_core_dims=[
                                                                     [], [], [], [], []],
                                                                 vectorize=True)
    print('Regression finished')
    print(regression_result)

# Correlations

def fast_single_correlation(dependant, independant):

    print(f'Running correlation for {dependant.name} and {independant.name}')

    correlation, pvalues = xr.apply_ufunc(stats.pearsonr, dependant, independant,
                                          input_core_dims=[
                                              ['time'], ['time']],
                                          output_core_dims=[
                                              [], []],
                                          vectorize=True)

    print('correlation finished')

    out = xr.Dataset()
    out['correlation'] = correlation
    out['pvalues'] = pvalues

    return out

import numpy,from scipy,import statsmodels


import numpy as np
import csv
import random
import math

def f_sigmoid(x):
    """The sigmoid function."""
    return 10.0/(10.0+math.exp(-x))

def df_sigmoid(x):
    """Derivative of the sigmoid function."""
    return f_sigmoid(x)*(10.0-f_sigmoid(x))

class Percepetron:

  def __init__(self, input_size, eta=0.01, threshold=1e-3):
    """
      Generate the random initial weights
    """
    self.w = np.random.uniform([-1, 1, input_size+1])
    self.fnet = np.vectorize(f_sigmoid)
    self.dfnet = np.vectorize(df_sigmoid)
    self.eta = eta
    self.threshold = threshold
    self.sqerror = 0

  def init_wieghts(self):
    pass

  def train(self, dataset):

    print("Training ...")

    # dataset = [ [x1_1, x1_2, ..., x1_n, y1],
    #             [x2_1, x2_2, ..., x2_n, y2],
    #             ... ]
    n = dataset.shape[0]
    iteration = 0

    while True:

      self.sqerror = 0.0

      np.apply_along_axis(self.apply_learning_equation, axis=1, arr=dataset)

      print("############################")
      print("Error: {}, Threshold: {}".format(self.sqerror/n, self.threshold))
      # print(self)

      if (self.sqerror/n) < self.threshold:
        break
      if iteration > 50000:
         break

    print("Training Done...")

  def apply_learning_equation(self, dataset):
    """
      Training Equations
      w_1(t+1) = w_1(t) + 2*eta*(y-sigma(net))*sigma'_{w1}(net)
      w_2(t+1) = w_2(t) + 2*eta*(y-sigma(net))*sigma'_{w2}(net)
      b(t+1) = b(t) + 2*eta*(y-sigma(net))
      sigma(net) = fnet(net)
      sigma'(net) = dfnet(net)
    """
    col = dataset.shape[0]

    # dataset = [x1, x2, ..., xn, Y]
    # x = [x1, x2, ..., xn, 1]
    # y = [Y]
    x = np.append(dataset[:-1], 1)
    y = dataset[-1]

    # Transpose X, Y
    x = x.T
    y = y.T

    net = np.matmul(self.w, x)

    y_o = self.fnet(net)
    error = y - y_o

    self.sqerror += np.linalg.norm(error)**2
    delta = 2*error*self.dfnet(x)

    self.w[:-1] += (2.0*self.eta*delta[:-1])
    self.w[-1] += (2.0*self.eta*error)

  def test(self, x):
    y = np.matmul(self.w, np.append(x,1))
    return self.fnet(y)

  def __str__(self):
    return 'Weights: {}'.format(self.w)

def train_and_test_and(filename):

  dataset = np.loadtxt(open(filename, "rb"), delimiter=" ")

  print("DataSet: {}".format(dataset))
  dimension = dataset.shape[1]

  p = Percepetron(dimension)
  p.train(dataset)

  a = 0
  b = 0
  print("{} OR {}: {}".format(a,b, p.test(np.array([a, b]))))

  a = 0
  b = 1
  print("{} OR {}: {}".format(a,b, p.test(np.array([a, b]))))

  a = 1
  b = 0
  print("{} OR {}: {}".format(a,b, p.test(np.array([a, b]))))

  a = 1
  b = 1
  print("{} OR {}: {}".format(a,b, p.test(np.array([a, b]))))

if __name__ == "__main__":
  train_and_test_and("AND.dat")


import numpy as np
from .Model import Model
from src.si.data.dataset import Dataset
from src.si.util.metrics import mse

class LinearRegression(Model):
    def __init__(self, gd=False, epochs=1000, lr=0.001):
        super(LinearRegression, self).__init__()
        self.gd = gd  # gradient descendent
        self.theta = None
        self.num_iterations = epochs
        self.lr = lr  # learning rate, velocidade de atualização de parametros

    def fit(self, dataset):
        X, Y = dataset.getXy()
        X = np.hstack((np.ones((X.shape[0], 1)), X))
        self.X = X
        self.Y = Y
        if self.gd:  # se quiser com gradiente
            self.train_gd(X, Y)
        else:
            self.train_closed(X, Y) # se quiser étodo dos mins quadrados
        self.is_fited = True

    def train_gd(self, X, Y):
        m = X.shape[0]
        n = X.shape[1]
        self.history = {}  # vai guardar o resultado das thetas e dos erros (custos) a cada iteração
        self.theta = np.zeros(n)  # matriz de zeros com o tamanho do dataset (numero de linhas)
        for epoch in range(self.num_iterations):  # para cada it
            grad = 1/m*(X.dot(self.theta)-Y).dot(X)  # função diferenciavel
            self.theta -= self.lr*grad  # atualização do theta a cada iteração
            self.history[epoch] = [self.theta[:], self.cost()]  # guarda esta iteração e os seus resultados

    def train_closed(self, X, Y):
        self.theta = np.linalg.inv(X.T.dot(X)).dot(X.T).dot(Y)  # metodo dos minimos quadrados e que minimiza a função erro

    def predict(self, x):  # faz as previsões
        assert self.is_fited
        _X = np.hstack(([1], x))
        return np.dot(self.theta, _X)

    def cost(self):  # calcula o erro
        y_pred = np.dot(self.X, self.theta)
        return mse(self.Y, y_pred)/2

class LinearRegressionReg(LinearRegression):  # para sobreajustamento, penaliza valores altos dos parâmetros na função de custo
    def __init__(self, gd=False, epochs=1000, lbd=1):
        super(LinearRegressionReg, self).__init__()
        self.lbd = lbd  # parametro de regularização
        self.gd = gd
        self.num_iterations = epochs

    def train_closed(self, X, Y):
        n = X.shape[1]
        identity = np.eye(n)  # matriz identidade
        identity[0, 0] = 0  # mudar o primeiro elemento para 0, para nao dar biased
        self.theta = np.linalg.inv(X.T.dot(X)+self.lbd*identity).dot(X.T).dot(Y)  # método analitico
        self.is_fited = True

    def train_gd(self, X, Y):
        m = X.shape[0]  # nº linhas
        n = X.shape[1]  # nº colunas
        self.history = {}
        self.theta = np.zeros(n)  # matriz de zeros com o tamanho do dataset
        lbds = np.full(m, self.lbd)  # matriz quadradada com o nº linhas do dataset e com os valores de regularização
        lbds[0] = 0
        for epoch in range(self.num_iterations):
            grad = (X.dot(self.theta)-Y).dot(X)  # metodo do gradiente
            self.theta -= (self.lr/m)*(lbds+grad)  # atualiza os valores de theta
            self.history[epoch] = [self.theta[:], self.cost()]  # guarda atualizações e erros de cada iteração

    def predict(self, X):
        assert self.is_fited
        _x = np.hstack(([1], X))
        return np.dot(self.theta, _x)

    def cost(self):
        y_pred = np.dot(self.X, self.theta)
        return mse(self.Y, y_pred)/2


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

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

# # Let’s run the basic SIR model

# describe the model
def deriv(y, t, N, beta, k, delta, alpha, rho):
    S, E, I, R, D = y
    dSdt = -beta * S * I / N
    dEdt = beta * S * I / N - delta * E
    dIdt = delta * E - (1 - alpha ) * k * I - alpha * rho* I
    dRdt = (1 - alpha) * k * I
    dDdt = alpha * rho * I #death
    return dSdt, dEdt, dIdt, dRdt, dDdt

#death % per age group in sweden (dead/confirmed sick)
alpha_by_agegroup = {
    "0-29": 0.00001, "30-59": 0.0001, "60-89": 0.08, "89+": 0.305
}

#swedish age distribution
proportion_of_agegroup = {
    "0-29": 0.36, "30-59": 0.385, "60-89": 0.245, "89+": 0.01
}

# describe the parameters
N =  10000000
beta = 0.4    #expected amount of people an infected person infects per day
k = 1/7
delta = 1/10 #incubation period
rho = 1/9 #days until death
alpha = sum(alpha_by_agegroup[i] * proportion_of_agegroup[i]
            for i in list(alpha_by_agegroup.keys()))
S0, E0, I0, R0, D0 = N-1, 1, 0, 0, 0  # initial conditions: one infected, rest susceptible

t = np.linspace(0, 299, 300) # Grid of time points (in days)
y0 = S0, E0, I0, R0, D0 # Initial conditions vector

# Integrate the SIR equations over the time grid, t.
ret = odeint(deriv, y0, t, args=(N, beta, k, delta, alpha, rho))
S, E, I, R, D = ret.T

def plotsir(t, S, E, I, R, D):
  f, ax = plt.subplots(1,1,figsize=(10,4))
  ax.plot(t, S, 'b', alpha=0.7, linewidth=2, label='Susceptible')
  ax.plot(t, E, 'y', alpha=0.7, linewidth=2, label='Exposed')
  ax.plot(t, I, 'r', alpha=0.7, linewidth=2, label='Infected')
  ax.plot(t, R, 'g', alpha=0.7, linewidth=2, label='Recovered')
  ax.plot(t, D, 'k', alpha=0.7, linewidth=2, label='Dead')
  #added total plot to see that math adds up
  #ax.plot(t, S+E+I+R+D, 'k--', alpha=0.7, linewidth=2, label='Total')

  ax.set_xlabel('Time (days)')

  ax.yaxis.set_tick_params(length=0)
  ax.xaxis.set_tick_params(length=0)
  ax.grid(b=True, which='major', c='w', lw=2, ls='-')
  legend = ax.legend()
  legend.get_frame().set_alpha(0.5)
  for spine in ('top', 'right', 'bottom', 'left'):
      ax.spines[spine].set_visible(False)
  #plt.savefig("Plot.png")
  plt.show();

# plot the graph

plotsir(t, S, E, I, R, D)


import numpy as np

from slick_dnn.autograd import Autograd

class ArcTan(Autograd):
    """ Applies the arctan function element-wise. """

    def forward(self, ctx, x):
        """ ArcTan(x) = :math:`tan^{-1}(x)` """

        ctx.save_for_back(x)
        return np.arctan(x)

    def backward(self, ctx, grad):
        """ ArcTan(x)' = :math:`\\frac{1}{x^2 + 1}`"""

        tensor, = ctx.data_for_back
        return grad / (tensor * tensor + 1)

class ReLU(Autograd):
    """ Applies the ReLU function element-wise. """

    def forward(self, ctx, x):
        """ ReLU(x) = :math:`max(0,x)`"""

        ctx.save_for_back(x)
        return np.clip(x, a_min=0, a_max=None)

    def backward(self, ctx, grad):
        """ReLU(x)' = :math:`\\begin{cases}
        0 & \\text{if  }  x < 0 \\newline
        1 & \\text{if  }  x > 0
        \\end{cases}`
        """

        t, = ctx.data_for_back
        return np.where(t < 0, 0, grad)

class Sigmoid(Autograd):
    """ Applies the Sigmoid function element-wise. """

    def forward(self, ctx, x):
        """Sigmoid(x) = :math:`\\frac{1}{ 1 + e^{-x} }`"""

        sig = 1 / (1 + np.exp(-x))
        ctx.save_for_back(sig)
        return sig

    def backward(self, ctx, grad):
        """Sigmoid(x)' = :math:`\\frac{ e^{-x} }{ (1+ e^{-x})^2 }`"""

        sig, = ctx.data_for_back
        return sig * (1 - sig) * grad

class Softmax(Autograd):
    """Applies the Softmax function element-wise. """

    def forward(self, ctx, x) -> np.array:
        """ :math:`Softmax(x_i) = \\frac{exp(x_i)}{\\sum_j{exp(x_j)}}` """

        e_x = np.exp(x)
        res = e_x / np.sum(e_x, axis=-1, keepdims=True)
        ctx.save_for_back(res)
        return res

    def backward(self, ctx, grad):
        """:math:`Softmax(x_i)' = \\frac{ exp(x_i) * \\sum_{j \\neq i}{exp(x_j)} }{ (\\sum_j{exp(x_j)})^2 }`"""

        res, = ctx.data_for_back
        return grad * res * (1 - res)

class Softplus(Autograd):
    """ Applies the softplus function element-wise. """

    def forward(self, ctx, x) -> np.array:
        """ Softplus(x) = :math:`ln(1 + e^x)` """

        ctx.save_for_back(1 + np.exp(-x))
        return np.log(1 + np.exp(x))

    def backward(self, ctx, grad):
        """ Softplus'(x) = :math:`\\frac{1}{1 + e^{-x}}` """

        denominator, = ctx.data_for_back
        return grad / denominator

class Softsign(Autograd):
    """ Applies the softsign function element-wise. """

    def forward(self, ctx, x):
        """ Softsign(x) = :math:`\\frac{1}{1 + |x|}` """

        denominator = 1 + np.abs(x)
        ctx.save_for_back(denominator)
        return x / denominator

    def backward(self, ctx, grad):
        """ Softsign'(x) = :math:`\\frac{1}{(1 + |x|)^2}` """

        denominator, = ctx.data_for_back
        return grad / (denominator * denominator)

class Tanh(Autograd):
    """ Applies the tanh function element-wise. """

    def forward(self, ctx, x):
        """Tanh(x)"""

        tanh = np.tanh(x)
        ctx.save_for_back(tanh)
        return tanh

    def backward(self, ctx, grad):
        """ Tanh(x)' = :math:`1 - Tanh^2(x)` """

        tanh, = ctx.data_for_back
        return (1 - tanh * tanh) * grad


from numpy import array as vector

# Explizites Euler-Verfahren
def euler_method(f,t0,x0,t1,h):
    t = t0; x = x0
    a = [[t,x]]
    for k in range(0,1+int((t1-t0)/h)):
        t = t0 + k*h
        x = x + h*f(t,x)
        a.append([t,x])
    return a

def sir_model(beta,gamma):
    def f(t,x):
        s,i,r = x
        return vector([
            -beta*s*i,
            beta*s*i - gamma*i,
            gamma*i
        ])
    return f

def sir_simulation(beta,gamma,i0,days,step=0.1):
    x0 = vector([1.0-i0,i0,0.0])
    model = sir_model(beta,gamma)
    return euler_method(model,0,x0,days,step)

def diagram(simulation):
    import matplotlib
    import matplotlib.pyplot as plot
    font = {'size': 14}
    matplotlib.rc('font', **font)

    figure,axes = plot.subplots()
    t,x = zip(*simulation())
    s,i,r = zip(*x)
    axes.plot(t,s, color = "#000000")
    axes.plot(t,i, color = "#000000")
    axes.plot(t,r, color = "#000000",
        linestyle = '--')
    plot.savefig("01-sir.pdf")

def simulation1():
    N = 83200000; R0 = 2.0; gamma = 1/4.0
    return sir_simulation(
        beta = R0*gamma, gamma = gamma,
        i0 = 10000.0/N, days = 80)

diagram(simulation1)


# Using convolutions in numpy

import numpy as np
from scipy.ndimage import convolve
from itertools import combinations

def read_file(test = True):
    if test:
        filename = '../tests/day20.txt'
    else:
        filename = '../input/day20.txt'
    with open(filename) as file:
        image = list()
        for i, line in enumerate(file):
            if i == 0:
                algorithm = np.array([int(char == "#") for char in line.strip()])
            elif line.strip():
                image.append(line.strip())
    image = np.pad([[int(char == "#") for char in row] for row in image], (51,51))
    return algorithm, image

def print_set(input_set):
    pass

def puzzle1(test = True):
    alg, img = read_file(test)

    # kernel for convolution
    kernel = 2**np.arange(9).reshape(3,3)

    for i in range(2):

        # compute number for each pixel
        temp = convolve(img, kernel)

        # apply algorithm to light/dim each pixel
        img = alg[temp]
    print(img.sum())

def puzzle2(test = True):
    alg, img = read_file(test)

    # kernel for convolution
    kernel = 2**np.arange(9).reshape(3,3)

    for i in range(50):
        # compute number for each pixel
        temp = convolve(img, kernel)

        # apply algorithm to light/dim each pixel
        img = alg[temp]
    print(img.sum())

puzzle1()
puzzle2()


import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm
from scipy.stats import multivariate_normal as mvn

norm.pdf(0)

# mean: loc, stddev: scale
norm.pdf(0, loc=5, scale=10)

r = np.random.randn(10)

# probability distribution function:
norm.pdf(r)

# log probability:
norm.logpdf(r)

# cumulative distribution function:
norm.cdf(r)

# log cumulative distribution function:
norm.logcdf(r)

# sampling from standard normal:
r = np.random.randn(10000)
plt.hist(r, bins=100)
plt.show()

mean = 5
stddev = 10
r = stddev*np.random.randn(10000)+mean
plt.hist(r, bins=100)
plt.show()

# spherical Gaussian:
r = np.random.randn(10000, 2)
plt.scatter(r[:,0], r[:,1])
plt.show()

# elliptical Gaussian:
r[:,1] = stddev*r[:,1]+mean
plt.scatter(r[:,0], r[:,1])
plt.axis('equal')
plt.show()

# non-axis-aligned Gaussian:
cov = np.array([[1,0.8],[0.8,3]]) # covariant matrix, covariance: 0.8 in both dimensions
mu = np.array([0,2])
r = mvn.rvs(mean=mu, cov=cov, size=1000)
plt.scatter(r[:,0], r[:,1])
plt.axis('equal')
plt.show()

r = np.random.multivariate_normal(mean=mu, cov=cov, size=1000)
plt.scatter(r[:,0], r[:,1])
plt.axis('equal')
plt.show()

# loading Matlab .mat files: scipy.io.loadmat

# loading WAV files: scipy.io.wavfile.read (default sampling: 44100 Hz)
# saving WAV files: scipy.io.wavfile.write

# signal processing, filtering: scipy.signal
# convolution: scipy.signal.convolve, scipy.signal.convolve2d

# FFT is in numpy:
x = np.linspace(0, 100, 10000)
y = np.sin(x) + np.sin(3*x) + np.sin(5*x)
plt.plot(x, y)
plt.show()

Y = np.fft.fft(y)
plt.plot(np.abs(Y))
plt.show()

2*np.pi*16/100
2*np.pi*48/100
2*np.pi*80/100


#!/bin/py

# m/s   |  hot   | cold
# 3        34.9  | 29.8
# 2        40.09 | 9.3
# 1        34.5  | .8745
# 0        20.0  | 0.0
#

import numpy as np

speed = [0,1,2,3]
h     = [7.0,12.5,30.09,54.9]
c     = [0.0,.8745,7.3,24.8]

hot  = 11.65*np.array(h)
cold = 11.65*np.array(c)

#
#
#
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.tick_params(axis='x', labelsize=20)
ax.tick_params(axis='y', labelsize=20)

#plt.plot(speed,hot,'v-', label='Hot Wind',linewidth=4,markersize=10,color='blue')
plt.plot(speed,cold,'o--', label='Cold Wind',linewidth=4,markersize=10,color='green')

#overrx = np.linspace(1.0, 5.0, num=50)
plt.xlim([-0.3,3.5])
plt.ylim([0,700])

fsz=20
plt.ylabel(r'Kinetic Energy Flux (Watts)',
           fontsize=fsz)

plt.xlabel(r'Wind Speed (m/s)',fontsize=fsz)

# might not need this
#plt.axes().set_aspect('equal', 'datalim')

plt.legend(loc='best')
plt.tight_layout()
#plt.show()
plt.savefig('wind_only.png')

#
# nick
# 10/14/16
#


"""Module contains functions for compute distances."""

from numba import njit
import numpy as np

@njit
def mahalanobis(x, mean, inv_cov, square=False):
    """
    Compute the Mahalanobis distance.

    Parameters
    ----------
    x : numpy.ndarray, shape=(m, n)
        N-dimensional set of points.

    mean : numpy.ndarray, shape=(1, n)
        Mean point of the data.

    square : bool, optional
        If returned distance is on same scale of `x` (`square=False`) or it is
        squared (`square=True`). The default is False.

    Returns
    -------
    dist : numpy.array, shape=(m, 1)
        Distance among `mean` and the points in `x`.

    """
    diff = x - mean

    dist = np.dot(diff, np.dot(inv_cov, diff.T))
    dist = np.diag(dist)

    if not square:
        dist = np.sqrt(dist)

    return dist.reshape(mean.shape[0], 1)

@njit
def euclidean(x, y, square=False):
    """
    Compute the Euclidean distance.

    Parameters
    ----------
    x : numpy.ndarray, shape=(1, n)
        N-dimensional point.

    y : numpy.ndarray, shape=(m, n)
        Set of points N-dimensional .

    square : bool, optional
        If returned distance is on same scale of `x` (`square=False`) or it is
        squared (`square=True`). The default is False.

    Returns
    -------
    dist : numpy.array, shape=(m,)
        Distance among `x` and the points in `y`.

    """
    diff = x - y

    dist = np.dot(diff, diff.T)
    dist = np.diag(dist)

    if not square:
        dist = np.sqrt(dist)

    return dist


import numpy as np

def fitness_function_float(function_number):

    if function_number == 1:
        return lambda chromosome: -(np.abs(chromosome[0]) + np.cos(chromosome[0]))
    elif function_number == 2:
        return lambda chromosome: -(np.abs(chromosome[0]) + np.sin(chromosome[0]))
    elif function_number == 3:
        return lambda chromosome: -(chromosome ** 2).sum()
    elif function_number == 4:
        return lambda chromosome: -np.sum(
            np.abs(chromosome) - 10 * np.cos(np.sqrt(np.abs(10 * chromosome)))
        )
    elif function_number == 5:
        return lambda chromosome: -(chromosome[0] ** 2 + chromosome[0]) * np.cos(
            chromosome[0]
        )
    elif function_number == 6:
        return lambda chromosome: -(
            chromosome[0] * np.sin(4 * chromosome[0])
            + 1.1 * chromosome[1] * np.sin(2 * chromosome[1])
        )
    elif function_number == 7:  # Bohachevsky, bound in [-100,100], opt at (0,0)
        return lambda chromosome: -(
            0.7
            + chromosome[0] ** 2
            + 2.0 * chromosome[1] ** 2
            - 0.3 * np.cos(3 * np.pi * chromosome[0])
            - 0.4 * np.cos(4 * np.pi * chromosome[1])
        )

    elif function_number == 8:  # Goldstein, bounds in [-2,2], opt at (0,-1)
        a = 1 + (chromosome[0] + chromosome[1] + 1) ** 2 * (
            19
            - 14 * chromosome[0]
            + 3 * chromosome[0] ** 2
            - 14 * chromosome[1]
            + 6 * chromosome[0] * chromosome[1]
            + 3 * chromosome[1] ** 2
        )
        b = 30 + (2 * chromosome[0] - 3 * chromosome[1]) ** 2 * (
            18
            - 32 * chromosome[0]
            + 12 * chromosome[0] ** 2
            + 48 * chromosome[1]
            - 36 * chromosome[0] * chromosome[1]
            + 27 * chromosome[1] ** 2
        )
        return lambda chromosome: -a * b

    elif function_number == 9:  # Hartmann3, bounds in [0,1], opt at (0.11,0.55,0.85)
        a = np.array(
            [[3.0, 10.0, 30.0], [0.1, 10.0, 35.0], [3.0, 10.0, 30.0], [0.1, 10.0, 35.0]]
        )
        c = np.array([1.0, 1.2, 3.0, 3.2])
        p = np.array(
            [
                [0.36890, 0.11700, 0.26730],
                [0.46990, 0.43870, 0.74700],
                [0.10910, 0.87320, 0.55470],
                [0.03815, 0.57430, 0.88280],
            ]
        )
        return lambda chromosome: np.sum(
            c * np.exp(-np.sum(a * (chromosome[:] - p) ** 2, -1)), -1
        )

    elif (
        function_number == 10
    ):  # Hartmann6, bounds in [0,1]. opt at (0.2, 0.15, 0.47, 0.27, 0.31, 0.65)
        a = np.array(
            [
                [10.0, 3.0, 17.0, 3.5, 1.7, 8.0],
                [0.05, 10.0, 17.0, 0.1, 8.0, 14.0],
                [3.0, 3.5, 1.7, 10.0, 17.0, 8.0],
                [17.0, 8.0, 0.05, 10.0, 0.1, 14.0],
            ]
        )
        c = np.array([1.0, 1.2, 3.0, 3.2])
        p = np.array(
            [
                [0.1312, 0.1696, 0.5569, 0.0124, 0.8283, 0.5886],
                [0.2329, 0.4135, 0.8307, 0.3736, 0.1004, 0.9991],
                [0.2348, 0.1451, 0.3522, 0.2883, 0.3047, 0.6650],
                [0.4047, 0.8828, 0.8732, 0.5743, 0.1091, 0.0381],
            ]
        )
        return (
            lambda chromosome: (
                np.sum(c * np.exp(-np.sum(a * (chromosome[:] - p) ** 2, -1)), -1) + 2.58
            )
            / 1.94
        )


from sympy.functions import exp
from symplyphysics import (
    symbols, Eq, pretty, solve, Quantity, units, S,
    Probability, validate_input, expr_to_quantity, convert_to
)

# Description
## The resonance escape probability, symbolized by p, is the probability that a neutron will be
## slowed to thermal energy and escape resonance capture. This probability is defined as the ratio
## of the number of neutrons that reach thermal energies to the number of fast neutrons that slow down.

# Conditions
## - Homogeneous reactor.
## - Weak fast absorptions.
## - Very predominant absorber.

## Law: p ≈ e^(-(Na * Ieff) / (ξ * Σs_mod))
## Where:
## e - exponent.
## Na - atomic number density of the absorber.
##    See [atomic number density](./chemistry/atomic_number_density_from_material_density_atomic_weight.py) implementation.
## ξ - average lethargy gain per scattering event. Lethargy is a dimensionless logarithm of the ratio of the energy of
##    source neutrons to the energy of neutrons after a collision.
## Ieff - effective resonance integral. In practical situations, this integral strongly depends on the geometry of the unit cell.
##    It is too complex to calculate Ieff. Please refer to the tables and specific energy levels for proper data.
## Σs_mod - moderator's macroscopic scattering cross-section.
##   See [macroscopic cross-section](./macroscopic_cross_section_from_free_mean_path.py) implementation.
## p - resonance escape probability

absorber_atomic_number_density = symbols('absorber_atomic_number_density')
effective_resonance_integral = symbols('effective_resonance_integral')
average_lethargy_change = symbols('average_lethargy_change')
macroscopic_scattering_cross_section_moderator = symbols('macroscopic_scattering_cross_section_moderator')
resonance_escape_probability = symbols('resonance_escape_probability')

law = Eq(resonance_escape_probability,
    exp(-1 * (absorber_atomic_number_density * effective_resonance_integral) /
    (average_lethargy_change * macroscopic_scattering_cross_section_moderator)))

def print():
    return pretty(law, use_unicode=False)

@validate_input(
    absorber_atomic_number_density_=(1 / units.length ** 3),
    effective_resonance_integral_=(units.length ** 2),
    macroscopic_scattering_cross_section_moderator_=(1 / units.length))
def calculate_resonance_escape_probability(
    absorber_atomic_number_density_: Quantity,
    effective_resonance_integral_: Quantity,
    average_lethargy_change_: float,
    macroscopic_scattering_cross_section_moderator_: Quantity) -> Probability:

    result_factor_expr = solve(law, resonance_escape_probability, dict=True)[0][resonance_escape_probability]
    result_expr = result_factor_expr.subs({
        absorber_atomic_number_density: absorber_atomic_number_density_,
        effective_resonance_integral: effective_resonance_integral_,
        average_lethargy_change: average_lethargy_change_,
        macroscopic_scattering_cross_section_moderator: macroscopic_scattering_cross_section_moderator_})
    result_factor = expr_to_quantity(result_expr, 'resonance_escape_factor')
    return Probability(convert_to(result_factor, S.One).n())


'''
This program plot compton scatteing

the equation is:

             h
      Δλ = ----- (1 - cos(θ))
             mc

'''

import sys,os
sys.path.insert(1,os.path.join(sys.path[0],'..'))
from constants.constants import h,m_e,c

import matplotlib.pyplot as plt
import numpy as np

theta = np.linspace(0,np.pi,100)
lambda_ = h / (m_e *c) * (1 - np.cos(theta))

plt.plot(theta , lambda_,label = r'$\Delta\lambda = \frac{h}{m_0c}\left(1-\cos\theta\right)$')
plt.axvline(np.pi/2 , ymax = 0.5 , color='r' , linestyle = ':')
plt.xticks([0 , np.pi/2 , theta[-1]] , [0 , r'$\pi/2$' , r'$\pi$'])
plt.yticks([0 , lambda_[-1]] , [0 , r'$\frac{2h}{m_0c}$'])
plt.xlabel(r'$\theta\;\longrightarrow$')
plt.ylabel(r'$\Delta\lambda\;\longrightarrow$')
plt.legend()
plt.show()


# Comparison of high-level programming languages
# This is Python (requires version 3.6+)

# Function
def power(x, a):
    return x**a

# Lambda function
power = lambda x, a: x**a

# Variable assignment
x = power(2, 0.5)

# Standard output
print(x)

# Formatted output
print("{:5.2f}".format(x))  # Since 3.4
print(f"{x:5.2f}")  # Since 3.6
print("%5.2f" % x)  # 'Old style'

# Definite loop
for i in range(5):
    print(i)

# Indefinite loop
i = 0
while i < 5:
    print(i)
    i += 1

# List
symbols = ['H', 'He', 'Li']
print(symbols)

# Dictionary
elements = {
    'H': 1,
    'He': 2,
    'Li': 3
}
print(elements)

# Iterating over collections
for symbol, n in elements.items():
    print(f"{symbol}: {n}")

# Array literals
import numpy as np

# Vector (1d)
x = np.array([1, 2, 3])

# Matrix (2d)
A = np.array([[1, 2],
              [3, 4],
              [5, 6]])
print(x)
print(A)

# 3d array (3x2x3)
Z = np.ones((3, 2, 3))
print(Z)

# Array dimensions
print(x.shape)
print(A.shape)
print(Z.shape)

# Indexing arrays
print(x[1])
print(A[1,1])
print(A[1])

# Slicing arrays
print(x[1:])
print(A[:,1])

# Concatenation of vector and 2-d array
C = np.hstack([x.reshape(-1, 1), A])
# or
C = np.column_stack([x, A])
print(C)

# Array broadcasting
print(1 - A)
print(A + x.reshape(-1, 1))

# Linear Algebra
A = np.array([[0.8, 0], [0, 1]])
K = np.array([[0.942],[1.074]])
C = np.array([[0.2, 1]])

# Matrix inverse
# Since Python 3.5
X = np.linalg.inv(np.eye(2) - A + K @ C)
# Prior to 3.5
X = np.linalg.inv(np.eye(2) - A + K.dot(C))

print(X)

# Symbolic math
from sympy import symbols, solve, diff
a, b, c, x = symbols('a b c x')
y = a*x**2 + b*x + c
dydx = diff(y, x)
x_sol = solve(dydx, x)
y_sol = y.subs(x, x_sol[0])
print(dydx)
print(x_sol)
print(y_sol)

# Vectorizable function
def lorenz(y, sigma, beta, rho):
    return (sigma * (y[1] - y[0]),
            y[0] * (rho - y[2]) - y[1],
            y[0] * y[1] - beta * y[2])

# Parameter values
beta = 8 / 3
sigma = 10
rho = 28

# Test1 - Single function call
y0 = (-8, 8, 27)
assert lorenz(y0, sigma, beta, rho) == (160, -16, -136)

# Test 2 - Single function calls with for loop
def make_function_calls(f, y, n, *args, **kwargs):
    dy = np.empty_like(y)
    for i in range(n):
        dy[:, i] = f(y[:, i], *args, **kwargs)
    return dy

y = np.repeat(np.array((-8, 8, 27)).reshape(-1,1), 10, axis=1)
dy = make_function_calls(lorenz, y, 10, sigma=sigma, beta=beta, rho=rho)
assert dy.shape == y.shape
assert np.array_equiv(dy.T, (160, -16, -136))

# Test 3 - vectorized function call
y = np.repeat(np.array((-8, 8, 27)).reshape(-1,1), 10, axis=1)
dy = lorenz(y, sigma, beta, rho)
dy = np.vstack(dy)
assert dy.shape == y.shape
assert np.array_equiv(dy.T, (160, -16, -136))

# Test 4 - Numpy apply method (slowest, not vectorized)
dy = np.apply_along_axis(lorenz, 0, y, sigma=sigma, beta=beta, rho=rho)
assert dy.shape == y.shape
assert np.array_equiv(dy.T, (160, -16, -136))

# Test 5 - Numpy vectorized function
vlorenz = np.vectorize(lorenz, excluded={"sigma", "beta", "rho"})

# Speed test results
# Test 1 - Single function call: 504 ns ± 6.08 ns
# Test 2 - Single function calls with for loop: 37.2 ms ± 302 µs
# Test 3 - Vectorized function calls: 59.0 µs ± 381 ns
# Test 4 - Numpy apply method 54.7 ms ± 511 µs

# Timing code (use in IPython or Jupyter notebook):
# n = 10000
# y = np.random.randn(3, n)
# y0 = y[:, 0]
# def make_function_calls(f, y, n, *args, **kwargs):
#     dy = np.empty_like(y)
#     for i in range(n):
#         dy[:, i] = f(y[:, i], *args, **kwargs)
#     return dy
# %timeit dy = lorenz(y0, sigma, beta, rho)
# %timeit dy = make_function_calls(lorenz, y, n, sigma=sigma,
#                                  beta=beta, rho=rho)
# %timeit dy = lorenz(y, sigma, beta, rho)
# %timeit dy = np.apply_along_axis(lorenz, 0, y, sigma=sigma,
#                                  beta=beta, rho=rho)

# Lorenz system with forcing
def lorenz(y, u, sigma=10, beta=8/3, rho=28):
    return (sigma * (y[1] - y[0]) + u,
            y[0] * (rho - y[2]) - y[1],
            y[0] * y[1] - beta * y[2])


#*********************************************************************
#
#  Copyright (c) 2017, Saurav Agarwal.
#  All rights reserved.
#
#*********************************************************************

# Authors: Saurav Agarwal

import numpy as np
import math as m

# normalize angle to between -pi to pi
def normalize_angle_to_pi_range(theta):

    while (theta > m.pi):

        theta -= 2 * m.pi

    while (theta < -m.pi):

        theta += 2 * m.pi

    return theta

# convert degrees to radian
def deg2rad(theta):

	return m.pi*theta / 180.0

# convert
def rad2deg(theta):

	return 180.0 * theta / m.pi


# Sampling from a discrete distribution

import numpy as np
import numpy.random as npr
import matplotlib.pyplot as plt
import bisect

def normalize(pmf):
    return pmf/(np.sum(np.abs(pmf)))

# This sampling function samples from a discrete distribution with probability mass function "pmf"
# and support coordinates specified in "support" by mapping a uniform random variable "u" onto the
# cumulative distribution function "cdf" and finding the associated support coordinate index
def sample(pmf, support=None):
    if support is None:
        support = np.arange(pmf.size)
    # Compute the cdf
    cdf = np.cumsum(pmf)
    # Sample from the uniform distribution
    u = npr.rand()
    # Find first index where cdf exceeds the uniform sample
    idx = bisect.bisect_left(cdf, u)
    # Map the index onto the support and return the sample
    return support[idx]

# This sampling function does the same thing as sample(), but uses the NumPy built-in function numpy.random.choice()
def sample_choice(pmf, support=None):
    return npr.choice(support, p=pmf)

# Number of points in the pmf support
n = 50
n2 = int(n/2)
# Define a support
support = np.hstack([np.linspace(-10, 0, n2), np.linspace(0+10/n2, 30, n-n2)])
# Define a pmf
a = np.linspace(0, 6, n)
pmf = normalize(np.abs(0.1*a**2 + np.sin(a)))
# Compute the pdf
d = np.diff(support)
ds = (np.hstack([d, d[-1]]) + np.hstack([d[0], d]))/2
pdf = pmf/ds

# Number of Monte Carlo samples
N = 100000
# Take Monte Carlo samples to get an empirical estimate of the pmf
samples = np.zeros(N)
for i in range(N):
    samples[i] = sample(pmf, support)

# Plot the pdf and empirical estimate
plt.close('all')
plt.style.use('../conlab.mplstyle')
plt.step(support, pdf, where='post', color='C0', label='True pdf')
plt.hist(samples, bins=support, density=True, align='mid', color='k', alpha=0.4, label='Monte Carlo pdf')
plt.legend()
plt.tight_layout()
plt.show()


import numpy as np

############################################################################
################### THIS HELPER IS ONLY FOR CONV WITH BN ###################
############################################################################
############### Note that: There are no parameters b anymore. ##############

def nn_f(X, W):
	# X : (m, n_prev)
	# W : (n, n_prev)
	Z = X @ W.T
	cache = (X, W)
	return Z, cache

def nn_b(dZ, cache):

	# dZ : (m, n)
	# cache : X, W
	X, W = cache
	m, _ = dZ.shape

	dW = dZ.T @ X / m
	dX = dZ @ W

	return dX, dW

def zero_pad(X, pad):

	#  X  : shape(m, n_H, n_W, n_C)
	# pad : scalar

	return np.pad(X,((0,0),(pad,pad),(pad,pad),(0,0)),'constant',constant_values=(0,0))

def conv_f(X, W, params):

	# X -> input,  shape : (m, in_H, in_W, in_C)
	# W -> filter, shape : (filters, f, f, in_C)
	# params: dict{'s':s, 'pad':pad}

	# Return:
	# Z -> conv output, shape : (m, out_H, out_W, filters)
	# cache -> Z, W (cache them for backward)

	filters, f, f, in_C = W.shape
	m, in_H, in_W, in_C = X.shape
	s, pad = params['s'], params['pad']

	out_H = int((in_H+2*pad-f)/s)+1
	out_W = int((in_W+2*pad-f)/s)+1

	X_pad = zero_pad(X, pad) if pad != 0 else X
	Z = np.zeros((m, out_H, out_W, filters), dtype=np.float32)

	for i in range(m):
		for h in range(out_H):
			for w in range(out_W):
				v_s, v_e, h_s, h_e = h*s, h*s+f, w*s, w*s+f
				x_slice = X_pad[i, v_s:v_e, h_s:h_e, :]
				Z[i,h,w,:] = ((W * x_slice).reshape(filters, -1).sum(1)).astype(np.float32)

	cache = (X, W, params)
	return Z, cache

def conv_b(dZ, cache):

	# dZ is the derivative
	# cache (X, W, params)

	X, W, params = cache
	m, n_H, n_W, _ = dZ.shape
	filters, f, f, _ = W.shape
	s, pad = params['s'], params['pad']

	dX = np.zeros(X.shape, dtype=np.float32)
	dW = np.zeros(W.shape, dtype=np.float32)

	for i in range(m):
		for h in range(n_H):
			for w in range(n_W):
				v_s, v_e, h_s, h_e = h*s, h*s+f, w*s, w*s+f
				x_slice = X[i, v_s:v_e, h_s:h_e, :]
				dW += (x_slice * dZ[i,h,w,:][:, None, None, None]).astype(np.float32)
				dX[i, v_s:v_e, h_s:h_e, :] += np.sum(W * dZ[i,h,w,:][:, None, None, None], axis=0).astype(np.float32)

	return dX, dW/m

def pool_f(X, params, mode=0):
	# NOTE THAT: POOLING LAYER DOESN'T HAVE WEIGHTS
	# Input parameters:
	# X -> input shape:(m, in_H, in_W, in_C)
	# params -> dict{f, s, out_C}
	# mode -> max=0, avg=1, default:0

	# Return:
	# Z -> output shape:(m, out_H, out_W, out_C)
	# cache -> X, params, mode (sava them for backwards)

	m, in_H, in_W, in_C = X.shape
	f, s = params['f'], params['s']

	out_H = int((in_H-f)/s)+1
	out_W = int((in_W-f)/s)+1

	Z = np.zeros((m, out_H, out_W, in_C), dtype=np.float32)
	pool = np.max if mode == 0 else np.mean

	for i in range(m):
		x_single = X[i]
		for h in range(out_H):
			for w in range(out_W):
				v_s, v_e, h_s, h_e = h*s, h*s+f, w*s, w*s+f
				x_slice = x_single[v_s:v_e, h_s:h_e, :]
				Z[i,h,w,:] = pool(x_slice.reshape(-1, in_C), axis=0)

	cache = (X, params, mode)
	return Z, cache

def pool_b(dZ, cache):

	# mode: 0->max, 1->avg
	X, params, mode = cache
	m, n_H, n_W, n_C = dZ.shape
	f, s = params['f'], params['s']
	dX = np.zeros(X.shape)
	pool = max_pool_b if mode == 0 else avg_pool_b

	for i in range(m):
		for h in range(n_H):
			for w in range(n_W):
				v_s, v_e, h_s, h_e = h*s, h*s+f, w*s, w*s+f
				dz_block = dZ[i, v_s, h_s, :]
				x_block = X[i, v_s:v_e, h_s:h_e, :]
				dX[i, v_s:v_e, h_s:h_e, :] = pool(dz_block, x_block)
	return dX

def max_pool_b(dz, x):
	# dz : (n_c,)
	# x  : (f,f,n_c)
	return (x == x.max(axis=(0,1))).astype(np.int16) * dz

def avg_pool_b(dz, x):
	# dz : (n_c,)
	# x  : (f,f,n_c)
	return np.full(x.shape, dz/(x.shape[0]**2))

def relu(Z):
	# Z : any shape
	return np.maximum(0, Z)

def relu_b(dA, Z):
	dZ = np.array(dA, copy=True)
	dZ[ Z<=0 ] = 0
	return dZ

def sigmoid(Z):
	# Z : any shape
	return 1.0 / (1.0 + np.exp(-Z))

def sigmoid_b(dA, Z):
	return dA * sigmoid(Z) * (1-sigmoid(Z))

#########################################################
################ 2018.04.10 / 2018.04.20 ################
#########################################################

def bn_conv_f(X, gamma, beta, epsilon=1e-6):

	#  batch_norm
	#  between: (conv,pool) and (activation)
	#
	#     X : (m, n_H, n_W, n_C)
	#  beta : (n_C,)
	# gamma : (n_C,)
	#     Y : (m, n_H, n_W, n_C) <- output

	# NOTE THAT: ***********************************************************************************
	# We learn a pair of parameters gamma(k) and beta(k) per feature map, rather than per activation
	# **********************************************************************************************

	m, n_H, n_W, n_C = X.shape
	# dof : degree of freedom
	dof_mu = m*n_H*n_W
	dof_s = (m-1)*n_H*n_W

	mu = X.sum(axis=(0,1,2)) / dof_mu
	s = np.power(X-mu, 2).sum(axis=(0,1,2)) / dof_s
	X_norm = ((X-mu) / np.sqrt(s+epsilon)).astype(np.float32)
	Y = gamma*X_norm + beta

	cache = (mu, s, X, X_norm, gamma, beta, epsilon)
	return Y, cache

def bn_conv_b(dY, cache):

	# dY : (m, n_H, n_W, n_C)
	# cache : the forward cache
	m, n_H, n_W, n_C = dY.shape
	mu, s, X, X_norm, gamma, beta, epsilon = cache

	# dof : degree of freedom
	dof = m*n_H*n_W
	dof_s = (m-1)*n_H*n_W

	# calculate the derivation
	dX_norm = dY*gamma
	dbeta = dY.sum(axis=(0,1,2)) / dof
	dgamma = ((dY * X_norm).sum(axis=(0,1,2)) / dof).astype(np.float32)
	ds = -(dX_norm*(X-mu)*np.power(s+epsilon, -1.5)).astype(np.float32).sum(axis=(0,1,2)) / (2*dof_s)
	dmu = -(dX_norm*np.power(s+epsilon, -0.5).astype(np.float32) + 2*ds*(X-mu) / dof_s).sum(axis=(0,1,2)) / dof
	dX = dX_norm*np.power(s+epsilon, -0.5).astype(np.float32) + 2*ds*(X-mu) / dof_s + dmu / dof

	return dX, dgamma, dbeta

def bn_nn_f(X, gamma, beta, epsilon=1e-6):
	# X : (m, n)
	m, n = X.shape
	mu = X.sum(axis=0) / m
	s = np.power(X-mu, 2).sum(axis=0) / (m-1)
	X_norm = (X-mu) / np.sqrt(s+epsilon)
	Z = X_norm*gamma + beta

	cache = (mu, s, X, X_norm, gamma, beta, epsilon)
	return Z, cache

def bn_nn_b(dY, cache):

	#    dY : (m, n)
	# cache : (mu, s, X, X_norm, gamma, beta, epsilon)
	mu, s, X, X_norm, gamma, beta, epsilon = cache
	m, n = dY.shape

	# calculate the derivation
	dX_norm = dY*gamma
	dbeta = dY.sum(axis=(0)) / m
	dgamma = (dY*X_norm).sum(axis=(0)) / m
	ds = -(dX_norm*(X-mu)*np.power(s+epsilon, -1.5)).astype(np.float32).sum(axis=(0)) / (2*(m-1))
	dmu = -(dX_norm*np.power(s+epsilon, -0.5).astype(np.float32) + 2*ds*(X-mu)/(m-1)).sum(axis=(0)) / m
	dX = dX_norm*np.power(s+epsilon, -0.5).astype(np.float32) + 2*ds*(X-mu)/(m-1) + dmu/m

	return dX, dgamma, dbeta

def adam(params, grads, hps, t, lr, beta1=0.9, beta2=0.999, epsilon=1e-8):
	# combained momentun and RMSprob together

	# params : W,  gamma,  beta
	#    hps : Vdw, Vdgamma, Vdbeta, Sdw, Sdgamma, Sdbeta.
	W, gamma, beta = params
	keys = ['VdW', 'Vdgamma', 'Vdbeta', 'SdW', 'Sdgamma', 'Sdbeta']
	VdW, Vdgamma, Vdbeta, SdW, Sdgamma, Sdbeta = [hps[key] for key in keys]

	for l in W.keys():
		corr = 1-np.power(beta1, t)
		# update W
		VdW[l] = beta1*VdW[l] + (1-beta1)*grads[l]['dW']
		SdW[l] = beta2*SdW[l] + (1-beta2)*(grads[l]['dW']**2).astype(np.float32)
		grads[l]['dW'] -= lr*(VdW[l] / corr) / (np.sqrt(SdW[l] / corr)+epsilon)

		# update gamma
		Vdgamma[l] = beta1*Vdgamma[l] + (1-beta1)*grads[l]['dgamma']
		Sdgamma[l] = beta2*Sdgamma[l] + (1-beta2)*(grads[l]['dgamma']**2).astype(np.float32)
		grads[l]['dgamma'] -= lr*(Vdgamma[l] / corr) / (np.sqrt(Sdgamma[l] / corr)+epsilon)

		# update beta
		Vdbeta[l] = beta1*Vdbeta[l] + (1-beta1)*grads[l]['dbeta']
		Sdbeta[l] = beta2*Sdbeta[l] + (1-beta2)*(grads[l]['dbeta']**2).astype(np.float32)
		grads[l]['dbeta'] -= lr*(Vdbeta[l] / corr) / (np.sqrt(Sdbeta[l] / corr)+epsilon)


Concept
==============

$$
\newcommand{\sumN}{\sum_{n = 1}^N}
\newcommand{\sumn}{\sum_n}
\newcommand{\bx}{\mathbf{x}}
\newcommand{\bbeta}{\boldsymbol{\beta}}
\newcommand{\btheta}{\boldsymbol{\theta}}
\newcommand{\bbetahat}{\boldsymbol{\hat{\beta}}}
\newcommand{\bthetahat}{\boldsymbol{\hat{\theta}}}
\newcommand{\dadb}[2]{\frac{\partial #1}{\partial #2}}
\newcommand{\by}{\mathbf{y}}
\newcommand{\bX}{\mathbf{X}}
\newcommand{\bphi}{\boldsymbol{\phi}}
\newcommand{\bPhi}{\boldsymbol{\Phi}}
$$

## Model Structure

*Linear regression* is a relatively simple method that is extremely widely-used. It is also a great stepping stone for more sophisticated methods, making it a natural algorithm to study first.

In linear regression, the target variable $y$ is assumed to follow a linear function of one or more predictor variables, $x_1, \dots, x_D$, plus some random error. Specifically, we assume the model for the $n^\text{th}$ observation in our sample is of the form

$$
y_n = \beta_0 + \beta_1 x_{n1} + \dots + \beta_Dx_{nD} + \epsilon_n.
$$

Here $\beta_0$ is the intercept term, $\beta_1$ through $\beta_D$ are the coefficients on our feature variables, and $\epsilon$ is an error term that represents the difference between the true $y$ value and the linear function of the predictors. Note that the terms with an $n$ in the subscript differ between observations while the terms without (namely the $\beta\text{s}$) do not.

The math behind linear regression often becomes easier when we use vectors to represent our predictors and coefficients. Let's define $\bx_n$ and $\bbeta$ as follows:

$$
\begin{align}
\bx_n &= \begin{pmatrix} 1 & x_{n1} & \dots & x_{nD} \end{pmatrix}^\top \\
\bbeta &= \begin{pmatrix} \beta_0 & \beta_1 & \dots & \beta_D \end{pmatrix}^\top.
\end{align}
$$

Note that $\bx_n$ includes a leading 1, corresponding to the intercept term $\beta_0$. Using these definitions, we can equivalently express $y_n$ as

$$
y_n = \bbeta^\top \bx_n + \epsilon_n.
$$

Below is an example of a dataset designed for linear regression. The input variable is generated randomly and the target variable is generated as a linear combination of that input variable plus an error term.

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# generate data
np.random.seed(123)
N = 20
beta0 = -4
beta1 = 2
x = np.random.randn(N)
e = np.random.randn(N)
y = beta0 + beta1*x + e
true_x = np.linspace(min(x), max(x), 100)
true_y = beta0 + beta1*true_x

# plot
fig, ax = plt.subplots()
sns.scatterplot(x, y, s = 40, label = 'Data')
sns.lineplot(true_x, true_y, color = 'red', label = 'True Model')
ax.set_xlabel('x', fontsize = 14)
ax.set_title(fr"$y = {beta0} + ${beta1}$x + \epsilon$", fontsize = 16)
ax.set_ylabel('y', fontsize=14, rotation=0, labelpad=10)
ax.legend(loc = 4)
sns.despine()

## Parameter Estimation

The previous section covers the entire structure we assume our data follows in linear regression. The machine learning task is then to estimate the parameters in $\bbeta$. These estimates are represented by $\hat{\beta}_0, \dots, \hat{\beta}_D$ or $\bbetahat$. The estimates give us *fitted values* for our target variable, represented by $\hat{y}_n$.

This task can be accomplished in two ways which, though slightly different conceptually, are identical mathematically. The first approach is through the lens of *minimizing loss*. A common practice in machine learning is to choose a loss function that defines how well a model with a given set of parameter estimates the observed data. The most common loss function for linear regression is squared error loss. This says the *loss* of our model is proportional to the sum of squared differences between the true $y_n$ values and the fitted values, $\hat{y}_n$. We then *fit* the model by finding the estimates $\bbetahat$ that minimize this loss function. This approach is covered in the subsection {doc}`s1/loss_minimization`.

The second approach is through the lens of *maximizing likelihood*. Another common practice in machine learning is to model the target as a random variable whose distribution depends on one or more parameters, and then find the parameters that maximize its likelihood. Under this approach, we will represent the target with $Y_n$ since we are treating it as a random variable. The most common model for $Y_n$ in linear regression is a Normal random variable with mean $E(Y_n) = \bbeta^\top \bx_n$. That is, we assume

$$
Y_n|\bx_n \sim \mathcal{N}(\bbeta^\top \bx_n, \sigma^2),
$$

and we find the values of $\bbetahat$ to maximize the likelihood. This approach is covered in subsection {doc}`s1/likelihood_maximization`.

Once we've estimated $\bbeta$, our model is *fit* and we can make predictions. The below graph is the same as the one above but includes our estimated line-of-best-fit, obtained by calculating $\hat{\beta}_0$ and $\hat{\beta}_1$.

# generate data
np.random.seed(123)
N = 20
beta0 = -4
beta1 = 2
x = np.random.randn(N)
e = np.random.randn(N)
y = beta0 + beta1*x + e
true_x = np.linspace(min(x), max(x), 100)
true_y = beta0 + beta1*true_x

# estimate model
beta1_hat = sum((x - np.mean(x))*(y - np.mean(y)))/sum((x - np.mean(x))**2)
beta0_hat = np.mean(y) - beta1_hat*np.mean(x)
fit_y = beta0_hat + beta1_hat*true_x

# plot
fig, ax = plt.subplots()
sns.scatterplot(x, y, s = 40, label = 'Data')
sns.lineplot(true_x, true_y, color = 'red', label = 'True Model')
sns.lineplot(true_x, fit_y, color = 'purple', label = 'Estimated Model')
ax.set_xlabel('x', fontsize = 14)
ax.set_title(fr"Linear Regression for $y = {beta0} + ${beta1}$x + \epsilon$", fontsize = 16)
ax.set_ylabel('y', fontsize=14, rotation=0, labelpad=10)
ax.legend(loc = 4)
sns.despine()

## Extensions of Ordinary Linear Regression

There are many important extensions to linear regression which make the model more flexible. Those include {doc}`/content/c2/s1/regularized`—which balances the bias-variance tradeoff for high-dimensional regression models—{doc}`/content/c2/s1/bayesian`—which allows for prior distributions on the coefficients—and {doc}`/content/c2/s1/GLMs`—which introduce non-linearity to regression models. These extensions are discussed in the next chapter.

```{toctree}
:hidden:
:titlesonly:

s1/loss_minimization
s1/likelihood_maximization
```


"""
Minimal Polynomials of Linear Recurrence Sequences

AUTHORS:

- William Stein
"""
# ****************************************************************************
#       Copyright (C) 2005 William Stein <wstein@gmail.com>
#
#  Distributed under the terms of the GNU General Public License (GPL)
#
#    This code is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    General Public License for more details.
#
#  The full text of the GPL is available at:
#
#                  https://www.gnu.org/licenses/
# ****************************************************************************

import sage.rings.rational_field

def berlekamp_massey(a):
    r"""
    Use the Berlekamp-Massey algorithm to find the minimal polynomial
    of a linear recurrence sequence `a`.

    The minimal polynomial of a linear recurrence `\{a_r\}` is
    by definition the unique monic polynomial `g`, such that if
    `\{a_r\}` satisfies a linear recurrence
    `a_{j+k} + b_{j-1} a_{j-1+k} + \cdots + b_0 a_k=0`
    (for all `k\geq 0`), then `g` divides the
    polynomial `x^j + \sum_{i=0}^{j-1} b_i x^i`.

    INPUT:

    -  ``a`` -- a list of even length of elements of a field (or domain)

    OUTPUT:

    the minimal polynomial of the sequence, as a polynomial over the
    field in which the entries of `a` live

    .. WARNING::

         The result is only guaranteed to be correct on the full
         sequence if there exists a linear recurrence of length less
         than half the length of `a`.

    EXAMPLES::

        sage: from sage.matrix.berlekamp_massey import berlekamp_massey
        sage: berlekamp_massey([1,2,1,2,1,2])
        x^2 - 1
        sage: berlekamp_massey([GF(7)(1),19,1,19])
        x^2 + 6
        sage: berlekamp_massey([2,2,1,2,1,191,393,132])
        x^4 - 36727/11711*x^3 + 34213/5019*x^2 + 7024942/35133*x - 335813/1673
        sage: berlekamp_massey(prime_range(2,38))
        x^6 - 14/9*x^5 - 7/9*x^4 + 157/54*x^3 - 25/27*x^2 - 73/18*x + 37/9

    TESTS::

        sage: berlekamp_massey("banana")
        Traceback (most recent call last):
        ...
        TypeError: argument must be a list or tuple
        sage: berlekamp_massey([1,2,5])
        Traceback (most recent call last):
        ...
        ValueError: argument must have an even number of terms
    """
    if not isinstance(a, (list, tuple)):
        raise TypeError("argument must be a list or tuple")
    if len(a) % 2:
        raise ValueError("argument must have an even number of terms")

    M = len(a) // 2

    try:
        K = a[0].parent().fraction_field()
    except AttributeError:
        K = sage.rings.rational_field.RationalField()
    R = K['x']
    x = R.gen()

    f = {-1: R(a), 0: x**(2 * M)}
    s = {-1: 1, 0: 0}
    j = 0
    while f[j].degree() >= M:
        j += 1
        qj, f[j] = f[j - 2].quo_rem(f[j - 1])
        s[j] = s[j - 2] - qj * s[j - 1]
    t = s[j].reverse()
    return ~(t[t.degree()]) * t  # make monic  (~ is inverse in python)


import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
import random
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

df_wine = pd.read_csv('https://archive.ics.uci.edu/ml/'
                      'machine-learning-databases/wine/wine.data',
                      header=None)

# if the Wine dataset is temporarily unavailable from the
# UCI machine learning repository, un-comment the following line
# of code to load the dataset from a local path:

# df_wine = pd.read_csv('wine.data', header=None)

# df_wine.columns = ['Class label', 'Alcohol', 'Malic acid', 'Ash',
#                    'Alcalinity of ash', 'Magnesium', 'Total phenols',
#                    'Flavanoids', 'Nonflavanoid phenols', 'Proanthocyanins',
#                    'Color intensity', 'Hue',
#                    'OD280/OD315 of diluted wines', 'Proline']

# print(df_wine.head())
#
# print(df_wine.tail())

# X, y = df_wine.iloc[:, 1:].values, df_wine.iloc[:, 0].values
# X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, stratify=y, random_state=0)
#
# sc = StandardScaler()
# X_train_std = sc.fit_transform(X_train)
# X_test_std = sc.transform(X_test)

print(df_wine.shape)
from sklearn.preprocessing import LabelEncoder
X = df_wine.loc[:, 1:].values
y = df_wine.loc[:, 0].values
le = LabelEncoder()
y = le.fit_transform(y)
print(le.classes_)

# 1) Split dataset into ratio 7:3 for training and test sets, respectively.
# Then use pipeline with StandardScaler(), PCA (n=3), and SVM with RBF kernel to fit the
# training set and predict the test set. Report the accuracy score.

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, stratify=y, random_state=0)

from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline

# pipe_lr = make_pipeline(StandardScaler(),PCA(n_components=2), LogisticRegression(random_state=1))
# pipe_lr.fit(X_train, y_train)
# y_pred = pipe_lr.predict(X_test)
# print('Test Accuracy: %.3f' % pipe_lr.score(X_test, y_test))

from sklearn.svm import SVC
pipe_lr = make_pipeline(StandardScaler(),PCA(n_components=3), SVC(random_state=1))
pipe_lr.fit(X_train, y_train)
y_pred = pipe_lr.predict(X_test)
print('Test Accuracy: %.3f' % pipe_lr.score(X_test, y_test))

# 2) Use StratifiedKFold cross-validation to report the accuracy score (mean with std).
# What are differences between standard k-fold and StratifiedKFold?
# What are differences between StratifiedKFold and cross_val_score (in [12] and [13] of this notebook)?

# KFold will divide your data set into prespecified number of folds, and every sample must be in one and only one fold.
# A fold is a subset of your dataset.
#
# ShuffleSplit will randomly sample your entire dataset during each iteration to generate a training set and a test set.
# The test_size and train_size parameters control how large the test and training test set should be for each iteration.
# Since you are sampling from the entire dataset during each iteration, values selected during one iteration, could be selected again during another iteration.
#
# Summary: ShuffleSplit works iteratively, KFold just divides the dataset into k folds.
#
# Difference when doing validation
#
# In KFold, during each round you will use one fold as the test set and all the remaining folds as your training set.
# However, in ShuffleSplit, during each round n you should only use the training and test set from iteration n.
# As your data set grows, cross validation time increases, making shufflesplits a more attractive alternate.
# If you can train your algorithm, with a certain percentage of your data as opposed to using all k-1 folds, ShuffleSplit is an attractive option.

from sklearn.model_selection import KFold
from sklearn import svm
svc = svm.SVC(C=1, kernel='linear')
kf = KFold(n_splits=10)
kf.get_n_splits(X)
# print(kf)
KF_scores = list()
for train_index, test_index in kf.split(X):
    # print("TRAIN:", train_index, "TEST:", test_index)
    X_train, X_test = X[train_index], X[test_index]
    y_train, y_test = y[train_index], y[test_index]
    KF_scores.append(svc.fit(X_train, y_train).score(X_test, y_test))
print('\nKF_scores: %s' % KF_scores)
print('KF accuracy: %.3f +/- %.3f' % (np.mean(KF_scores), np.std(KF_scores)))

from sklearn.model_selection import StratifiedKFold
kfold = StratifiedKFold(n_splits=10,random_state=1).split(X_train, y_train)
StratifiedKFold_scores = []
for k, (train, test) in enumerate(kfold):
    pipe_lr.fit(X_train[train], y_train[train])
    score = pipe_lr.score(X_train[test], y_train[test])
    StratifiedKFold_scores.append(score)
    print('StratifiedFold: %2d, Class dist.: %s, Acc: %.3f' % (k + 1,np.bincount(y_train[train]), score))

print('StratifiedKFold CV accuracy: %.3f +/- %.3f' % (np.mean(StratifiedKFold_scores), np.std(StratifiedKFold_scores)))

from sklearn.model_selection import cross_val_score

CV_scores = cross_val_score(estimator=pipe_lr, X=X_train,y=y_train, cv=10, n_jobs=1)
print('\nCV accuracy scores: %s' % CV_scores)
print('CV accuracy: %.3f +/- %.3f' % (np.mean(CV_scores), np.std(CV_scores)))

sns.set()
sns.set_context("talk")
plt.plot(KF_scores,color='r',lw = 2.0, linestyle='--',label='standard k-fold')
plt.plot(StratifiedKFold_scores,color='g', lw = 2.0, linestyle=':',label='StratifiedKFold')
plt.ylabel('Scores', fontsize = 16)
plt.xlabel('# of Fold', fontsize = 16)
plt.title("standard k-fold Vs. StratifiedKFold")
plt.legend(loc='best',fontsize = 14)
plt.show()

plt.plot(CV_scores,color='r',lw = 2.0, linestyle='--',label='CV_scores')
plt.plot(StratifiedKFold_scores,color='g', lw = 2.0, linestyle=':',label='StratifiedKFold')
plt.ylabel('Scores', fontsize = 16)
plt.xlabel('# of Fold', fontsize = 16)
plt.title("CV_scores Vs. StratifiedKFold")
plt.legend(loc='best',fontsize = 14)
plt.show()


# pylint: disable=invalid-name

"""
Module for obtaining quadrature points and weights.

Three types of gaussian quadrature are supported: normal Gaussian,
Radau quadrature, and Lobatto quadrature.  The first method does not
include either endpoint of integration, Radau quadrature includes one
endpoint of the integration range, and Lobatto quadrature includes both
endpoints.::

    import iad.quadrature

    n=8
    x, w = iad.quadrature.gauss(n)
    print(" i        x         weight")
    for i,x in enumerate(xi):
        print("%2d   %+.12f %+.12f" % (i, x[i], w[i]))

    x, w = iad.quadrature.radau(n)
    print(" i        x         weight")
    for i,x in enumerate(xi):
        print("%2d   %+.12f %+.12f" % (i, x[i], w[i]))

    x, w = iad.quadrature.lobatto(n)
    print(" i        x         weight")
    for i,x in enumerate(xi):
        print("%2d   %+.12f %+.12f" % (i, x[i], w[i]))

"""

import functools
import scipy.special
import scipy.optimize
import numpy as np

__all__ = ('gauss',
           'radau',
           'lobatto',
           )

def _gauss_func(n, x):
    """Zeroes of this function are the Gaussian quadrature points."""
    return scipy.special.legendre(n)(x)

def _radau_func(n, x):
    """Zeros of this function are the Radau quadrature points."""
    return (scipy.special.eval_legendre(n - 1, x) + scipy.special.eval_legendre(n, x)) / (1 + x)

def _lobatto_func(n, x):
    """Zeros of this function are the Lobatto quadrature points."""
    return scipy.special.legendre(n - 1).deriv(1)(x)

def gauss(n, a=-1, b=1):
    """
    Return abscissas and weights for Gaussian quadrature.

    The definite integral ranges from a to b.  The default
    interval is -1 to 1.  The quadrature approximation is
    just the sum of w_i f(x_i).  Neither a nor b is included
    in the list of quadrature abscissas.

    The result should be exact when integrating any polynomial
    of degree 2n-1 or less.

    If -a=b, then abscissas will be symmetric about the origin

    Args:
        n: number of quadrature points
        a: lower limit of integral
        b: upper limit of integral
    Returns:
        x: array of abscissas of length n
        w: array of weights of length n
    """
    x, w, _ = scipy.special.roots_legendre(n, mu=True)

    # scale for desired interval
    x *= 0.5 * (a - b)
    x += 0.5 * (a + b)
    w *= 0.5 * (b - a)

    return np.flip(x), np.flip(w)

def radau(n, a=-1, b=1):
    """
    Return abscissas and weights for Radau quadrature.

    The definite integral ranges from a to b.  The default
    interval is -1 to 1.  The quadrature approximation is
    just the sum of w_i f(x_i).  The upper endpoint b is include
    in the list of quadrature abscissas.

    The result should be exact when integrating any polynomial
    of degree 2n-2 or less.

    Args:
        n: number of quadrature points
        a: lower limit of integral
        b: upper limit of integral
    Returns:
        x: array of abscissas of length n
        w: array of weights of length n
    """
    x = np.zeros(n)
    w = np.zeros(n)
    x[0] = -1

    # the roots of P_{n} bracket the roots of P_{n}':
    brackets = scipy.special.roots_legendre(n)[0]

    f = functools.partial(_radau_func, n)
    for i in range(n - 1):
        x[i + 1] = scipy.optimize.brentq(f, brackets[i], brackets[i + 1])

    pp = scipy.special.legendre(n - 1).deriv(1)
    w[0] = 2 / n**2
    w[1:] = 1 / pp(x[1:])**2 / (1 - x[1:])

    # scale for desired interval
    x *= 0.5 * (a - b)
    x += 0.5 * (b + a)
    w *= 0.5 * (b - a)

    return np.flip(x), np.flip(w)

def lobatto(n, a=-1, b=1):
    """
    Return abscissas and weights for Lobatto quadrature.

    The definite integral ranges from a to b.  The default
    interval is -1 to 1.  The quadrature approximation is
    just the sum of w_i f(x_i).  Both endpoints a and b are include
    in the list of quadrature abscissas.

    The result should be exact when integrating any polynomial
    of degree 2n-3 or less.

    If -a=b, then abscissas will be symmetric about the origin

    Args:
        n: number of quadrature points
        a: lower limit of integral
        b: upper limit of integral
    Returns:
        x: array of abscissas of length n
        w: array of weights of length n
    """
    x = np.zeros(n)
    w = np.full(n, 2 / n / (n - 1))
    x[0] = -1
    x[-1] = 1

    # The roots of P_{n-1} bracket the roots of P_{n-1}':
    brackets = scipy.special.roots_legendre(n - 1)[0]

    f = functools.partial(_lobatto_func, n)
    for i in range(n - 2):
        x[i + 1] = scipy.optimize.brentq(f, brackets[i], brackets[i + 1])

    pp = scipy.special.legendre(n - 1)(x)
    w[1:-1] = w[1:-1] / pp[1:-1]**2

    # scale for desired interval
    x *= 0.5 * (a - b)
    x += 0.5 * (a + b)
    w *= 0.5 * (b - a)

    return np.flip(x), np.flip(w)


#!/usr/bin/env
# -*- coding: utf-8 -*-
# Módulo Jacobi: Métodos de Jacobi para resolver Sistemas de Equações

from pprint import pprint

import numpy as np

import construtor

# Calcula o vetor solução pelo método de Jacobi.
# Entradas: matriz, vetor de termos independentes, número máximo de iterações, tolerância
# Retorno: vetor solução
def v_sol(m, v, n_max, tol, prin):
    # Cria o chute inicial
    x = np.zeros(len(m[0]))

    # Cria o vetor d com os elementos da diagonal principal de m
    d = np.diag(m)
    # Cria o vetor r com os elementos de m e diagonal principal nula
    r = m - np.diagflat(d)

    # Executa a iteração
    i = 0
    while i < n_max:
        i += 1
        x_old = x.copy()
        x = (v - np.dot(r, x)) / d
        # Verifica a tolerância
        # O método np.allclose comapara todos os elementos dos dois vetores e retorna True caso a norma < tol.
        if np.allclose(x, x_old, atol=tol, rtol=tol):
            break
    # Retorna o vetor solução
    if prin:
        print('\nForam feitas ', i, ' iterações.')
    return x.tolist()

# Calcula o vetor solução, para a matriz de uma equação, pelo método de Jacobi.
# Entradas: q(x), r(x), malha de pontos, passo, número de pontos, y(a), y(b), número máximo de iterações, tolerância
# Retorno: vetor solução
def v_sol_mh(q, r, x, h, n, a_, b_, n_max, tol, prin=True):
    # Calcula a matriz e o vetor de termos independentes
    m_h = construtor.matriz(q, x, h, n)
    v_h = construtor.vetor(r, x, h, n, a_, b_)

    # Calcula e retorna o vetor solução
    return v_sol(m_h, v_h, n_max, tol, prin)

if __name__ == "__main__":
    # TESTE 1
    m = np.array([[2.0, 1.0], [5.0, 7.0]])
    v = np.array([11.0, 13.0])
    guess = np.array([1.0, 1.0])

    sol = v_sol(m, v, 25, 0.0001)

    print('m:')
    pprint(m)

    print('v:')
    pprint(v)

    print('x:')
    pprint(sol)


import numpy as np

class OrdinaryLeastSquares:
    """Ordinary Least Squares Linear Regression.

    Parameters:
    -----------
    None.
    """
    def __init__(self):
        self.weights = None

    def fit(self, X, y):
        if len(y.shape) == 1:
            y = y.reshape(-1, 1)
        self._fit(X, y)

    @staticmethod
    def _pad(X):
        return np.pad(X, ((0, 0), (1, 0)), 'constant', constant_values=1)

    def _fit(self, X, y, pad=True):
        if pad:
            X = self._pad(X)
        X_pseudo_inverse = np.linalg.pinv(X)  # Moore-Penrose pseudo-inverse for more efficiency
        self.weights = np.dot(X_pseudo_inverse, y)

    def predict(self, X):
        if self.weights is None:
            raise ValueError('no weights, run fit(X, y) to fit model before prediction')
        return self._predict(X)

    def _predict(self, X, pad=True):
        if pad:
            y_pred = np.dot(self._pad(X), self.weights)
        else:
            y_pred = np.dot(X, self.weights)
        return y_pred

    def score(self, X, y):
        if len(y.shape) == 1:
            y = y.reshape(-1, 1)
        y_pred = self.predict(X)
        mse = np.square(y_pred - y).mean()
        return mse

class OrdinaryLeastSquaresGD(OrdinaryLeastSquares):
    """Ordinary Least Squares Linear Regression with Gradient Descent.

    Parameters:
    -----------
    learning_rate: float
        The rate at which to update learned weights. Also known as alpha.
    n_iter: int
        Number of iterations to run in order to learn weights.
    early_stop: boolean
        Allows to stop the learning process if loss has plateaued.
    """
    def __init__(self, learning_rate=0.05, n_iter=1000, early_stop=True):
        super().__init__()
        self.learning_rate = learning_rate
        self.n_iter = n_iter
        self.early_stop = early_stop
        self.loss = []

    def _fit(self, X, y, pad=True):
        if pad:
            X = self._pad(X)
        self.weights = np.zeros((X.shape[1], 1))
        for iter in range(self.n_iter):
            # print('iter %d/%d' % (iter, self.n_iter))
            self._step(X, y)
            curr_loss = self.score(X[:, 1:], y)
            self.loss.append(curr_loss)
            if self.early_stop and (len(self.loss) >= 3) and (self.loss[-2] < self.loss[-1]):
                # print('early stop!')
                break

    def _step(self, X, y):
        n = X.shape[0]
        y_est = np.dot(X, self.weights)
        error = y_est - y
        grad = np.dot(X.T, error) / n
        self.weights = self.weights - (self.learning_rate * grad)


from sympy import symbols, sin, cos
from shenfun import *

# Use sympy to compute manufactured solution
x, y, z = symbols("x,y,z")
ue = (cos(4*x) + sin(2*y) + sin(4*z))*(1-x**2)
fe = ue.diff(x, 2) + ue.diff(y, 2) + ue.diff(z, 2)

C0 = FunctionSpace(32, 'Chebyshev', bc=(0, 0))
F1 = FunctionSpace(32, 'Fourier', dtype='D')
F2 = FunctionSpace(32, 'Fourier', dtype='d')
T = TensorProductSpace(comm, (C0, F1, F2))
u = TrialFunction(T)
v = TestFunction(T)

# Assemble left and right hand
f_hat = inner(v, Array(T, buffer=fe))
A = inner(v, div(grad(u)))

# Solve
solver = chebyshev.la.Helmholtz(*A)
u_hat = Function(T)
u_hat = solver(f_hat, u_hat)
uj = u_hat.backward()
assert np.linalg.norm(u_hat.backward()-Array(T, buffer=ue)) < 1e-12
print(u_hat.shape)


import numpy as np

def perceptron(data,b=0.1,a=1.0,K=200):
    (N,L)=data.shape;
    D=L-1;
    labs=np.unique(data[:,L-1]);
    C=labs.size;
    w = np.zeros((L,C));
    for k in range(1,K+1):
        E=0;
        for n in range(N):
             xn=np.concatenate(([1],data[n,:D]));
             cn=np.where(labs==data[n,L-1])[0][0];
             er=0;
             g=np.dot(w[:,cn],xn);
             for c in range(C):
                 if c != cn and np.dot(w[:,c],xn) + b > g:
                     w[:,c] = w[:,c] - a*xn;
                     er=1;
             if er==1:
                w[:,cn] = w[:,cn] + a*xn;
                E=E+1;
        if E==0:
            break;
    return w,E,k;


import numpy as np

def get_primes(n):
    arr = np.ones(n + 1, dtype=np.int8)
    arr[0], arr[1] = 0, 0
    for i in range(2, n + 1):
        if arr[i]:
            arr[i * i:n + 1:i] = 0
    return arr.nonzero()[0]

def count_of_occurrences(n, p):
    """
    Return Number of times 'p' occurs in  n!

    for eg: f(30 , 7) = 4
    """
    cnt = 0
    while n:
        n //= p
        cnt += n
    return cnt

def p231(n, r):
    primes = get_primes(n)

    res = 0

    for p in primes:
        res += p*(count_of_occurrences(n, p) -
                  count_of_occurrences(r, p) - count_of_occurrences(n-r, p))

    return res

if __name__ == "__main__":
    print(p231(20000000,5000000))


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#  ch2Python.py. Finding the best fitting line.
#  This is demonstration code, so it is transparent but inefficient.

import numpy as np
import matplotlib.pyplot as plt

x = [1.00,1.25,1.50,1.75,2.00,2.25,2.50,2.75,3.00,3.25,3.50,3.75,4.00]
y = [3.34,4.97,4.15,5.40,5.21,4.56,3.69,5.86,4.58,6.94,5.57,5.62,6.87]

# convert data to vectors.
x = np.array(x)
y = np.array(y)
xmean = np.mean(x)
ymean = np.mean(y)
# Find zero mean versions of x and y.
xzm = x - xmean
yzm = y - ymean

numerator = np.sum(xzm * yzm)
denominator = np.sum(xzm * xzm)
print("numerator = %0.3f" %numerator) # 8.693
print("denominator = %0.3f" %denominator) # 11.375

# Find slope b1.
# b1 = numerator/denominator # 0.764
b1 = np.sum(xzm * yzm) / ( np.sum(xzm * xzm) ) # 0.764

# Find intercept b0.
b0 = ymean - b1*xmean # 3.225
print('slope b1 = %6.3f\nintercept b0 = %6.3f' % (b1, b0))

# Draw best fitting line.
xx = np.array([-0.3, 3, 5])
yline = b1 * xx + b0
fig, ax = plt.subplots(figsize=(8, 6))
ax.plot(x, y, "ko", label="Data")
ax.plot(xx, yline, "k-",label="Best fitting line (LSE)")
ax.legend(loc="best")
plt.grid()

###############################
# END OF FILE.
###############################


import numpy as np
def sigmoid(x):  return 1 / (1 + np.exp(-x))
def dsigmoid(y): return y * (1.0 - y)
def tanh(x):     return np.tanh(x)
def dtanh(y):    return 1 - y*y


from sympy import *
import numpy as np

libceed = True

def div(v):
    d  = diff(v[0],x)
    d += diff(v[1],y)
    d += diff(v[2],z)
    return d

def curl(v):
    c = []
    c.append(  diff(v[2],y)-diff(v[1],z) )
    c.append(-(diff(v[2],x)-diff(v[0],z)))
    c.append(  diff(v[1],x)-diff(v[0],y) )
    return np.asarray(c)

x,y,z = symbols('x y z')
a0,b0,c0,d0,a1,b1,c1,d1,a2,b2,c2,d2 = symbols("a0 b0 c0 d0 a1 b1 c1 d1 a2 b2 c2 d2")
r0,r1,r2,r3,s0,s1,s2,s3,t0,t1,t2,t3 = symbols("r0 r1 r2 r3 s0 s1 s2 s3 t0 t1 t2 t3")

BDDF1 = np.asarray([a0+b0*x+c0*y+d0*z,
                    a1+b1*x+c1*y+d1*z,
                    a2+b2*x+c2*y+d2*z])
BDDF1 += r0*curl([0       ,0       ,x*y*z   ])
BDDF1 += r1*curl([0       ,0       ,x*y**2  ])
BDDF1 += r2*curl([0       ,0       ,x**2*z  ])
BDDF1 += r3*curl([0       ,0       ,x**2*y*z])
BDDF1 += s0*curl([x*y*z   ,0       ,0       ])
BDDF1 += s1*curl([y*z**2  ,0       ,0       ])
BDDF1 += s2*curl([x*y**2  ,0       ,0       ])
BDDF1 += s3*curl([x*y**2*z,0       ,0       ])
BDDF1 += t0*curl([0       ,x*y*z   ,0       ])
BDDF1 += t1*curl([0       ,x**2*z  ,0       ])
BDDF1 += t2*curl([0       ,y*z**2  ,0       ])
BDDF1 += t3*curl([0       ,x*y*z**2,0       ])

"""
     local numbering of Hex

     5--------7           z
   / |      / |            |
 6 --|---- 8  |            |
 |   |     |  |            /----y
 |   |     |  |           /
 |   1 -------3         x
 | /       | /
 2 --------4
"""
# normals
nl = [0.0,-1.0, 0.0]
nr = [0.0, 1.0, 0.0]
nbt = [0.0, 0.0, -1.0]
nt = [0.0, 0.0, 1.0]
nf = [1.0, 0.0, 0.0]
nbk = [-1.0, 0.0, 0.0]
# nodes
n1 = [-1., -1., -1.]
n2 = [1., -1., -1]
n4 = [1., 1., -1.]
n3 = [-1., 1., -1.]
n5 = [-1., -1., 1.]
n6 = [1., -1., 1]
n8 = [1., 1., 1.]
n7 = [-1., 1., 1.]

nodes = [n1, n2, n3, n4, n5, n6, n7, n8, n2, n1, n6, n5, n4, n3, n8, n7, n2, n4, n6, n8, n1, n3, n5, n7]
normals = [nbt, nbt, nbt, nbt, nt, nt, nt, nt, nl, nl, nl, nl, nr, nr, nr, nr, nf, nf, nf, nf, nbk, nbk, nbk, nbk]

for i in range(8): # for each vertex
    for j in range(3): # for each direction
        k = 3*i+j

        eqs = []
        for n in range(24):
            eqs.append(np.dot(BDDF1,normals[n]).subs({x:nodes[n][0],y:nodes[n][1],z:nodes[n][2]}))

        eqs[k] -= 1 # the k^th functions should be a 1, rest are 0
        sol = solve(eqs)
        ux = BDDF1[0].subs(sol)
        uy = BDDF1[1].subs(sol)
        uz = BDDF1[2].subs(sol)

        if libceed:
            def _f(fcn):
                fcn = fcn.replace("x**2","x*x")
                fcn = fcn.replace("y**2","y*y")
                fcn = fcn.replace("z**2","z*z")
                fcn = fcn.replace("x","x[0]")
                fcn = fcn.replace("y","x[1]")
                fcn = fcn.replace("z","x[2]")
                if "/8" in fcn: fcn = "(%s)*0.125;" % (fcn.replace("/8",""))
                if "/16" in fcn: fcn = "(%s)*0.0625;" % (fcn.replace("/16",""))
                return fcn
            print("Bx[%2d] = " % (k) + _f("%s" % (ux)),";")
            print("By[%2d] = " % (k) + _f("%s" % (uy)),";")
            print("Bz[%2d] = " % (k) + _f("%s" % (uz)),";")


#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Tests for quadrature points and weights.

:copyright:
    Martin van Driel (Martin@vanDriel.de), 2015
:license:
    GNU Lesser General Public License, Version 3
    (http://www.gnu.org/copyleft/lgpl.html)
"""
import numpy as np

from ..quadrature_points_weights import \
    gauss_lobatto_jacobi_quadruature_points_weights, \
    gauss_quadruature_points_weights, \
    gauss_lobatto_legendre_quadruature_points_weights

def test_gauss_quadruature_points_weights():

    # reference data for n = 5
    ref_points = [-0.906179845938664, -0.538469310105683, 0.,
                  0.538469310105683, 0.906179845938664]
    ref_weights = [0.236926885056189, 0.478628670499366, 0.568888888888889,
                   0.478628670499366, 0.236926885056189]

    points, weights = gauss_quadruature_points_weights(5)
    np.testing.assert_allclose(points, ref_points, atol=1e-15)
    np.testing.assert_allclose(weights, ref_weights, atol=1e-15)

def test_gauss_lobatto_legendre_quadruature_points_weights():

    # reference data for n = 5
    ref_points = [-1., -21 ** 0.5 / 7., 0., 21 ** 0.5 / 7., 1.]
    ref_weights = [1./10., 49./90., 32./45., 49./90., 1./10.]

    points, weights = gauss_lobatto_legendre_quadruature_points_weights(5)
    np.testing.assert_allclose(points, ref_points, atol=1e-15)
    np.testing.assert_allclose(weights, ref_weights, atol=1e-15)

def test_gauss_lobatto_jacobi_quadruature_points_weights():
    ref_points = [-1., -0.507787629558315, 0.132300820777323,
                  0.708820142114325, 1.]
    ref_weights = [0.013333333333333332, 0.28965669456116333,
                   0.73600436948163284, 0.79433893595720395,
                   0.16666666666666635]

    points, weights = gauss_lobatto_jacobi_quadruature_points_weights(5)
    np.testing.assert_allclose(points, ref_points, atol=1e-15)
    np.testing.assert_allclose(weights, ref_weights, atol=1e-15)


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

def RK2(func, X0,tmin,tmax,h,cons):
    N=int((tmax-tmin)/h)
    t = np.linspace(tmin,tmax,N)
    X  = np.zeros([N, len(X0)])
    X[0] = X0
    for i in range(N-1):
        k1 =h* func(t[i],X[i],cons)
        k2 = h*func( t[i] + h,X[i] +  k1,cons)
        X[i+1] = X[i] +  (k1 +k2 )/2
    return X,t

def f2(t,X,cons):
    k,m,b=cons
    x,z=X
    dx_dt=z
    dx2_dt2=-b*z/m-k*x/m
    return np.array([dx_dt, dx2_dt2])

def f3(t,X,cons):
    g,L=cons
    th,z=X
    dth_dt=z
    dth2_dt2=-g*th/L
    return np.array([dth_dt, dth2_dt2])

def f4(t,X,cons):
    k,m,g,L=cons
    w0=np.sqrt(g/L)
    xa,z1,xb,z2=X
    xb=np.radians(xb)
    xa=np.radians(xa)
    e1=z1
    e2=-(w0**2)*xa-(k/m)*(xa-xb)
    e3=z2
    e4=-(w0**2)*xb+(k/m)*(xa-xb)
    return np.array([e1,e2,e3,e4])

#3a
IC=[2,0];tmin=0;m=0.5;k=4;h=0.01;b=0#SI units
tp=2*np.pi*np.sqrt(m/k)
tmax=5*tp
cons=(k,m,b)
S=RK2(f2,IC,tmin,tmax,h,cons)
x,v= S[0].T
t=S[1]
t1=t/tp
fig, axs = plt.subplots(2)
fig.suptitle('SIMPLE HARMONIC OSCILLATOR',c="r")
axs[0].plot(t1,x,marker="*",c="orange")
axs[0].set(xlabel="time/time period ",ylabel="Dispacement")
axs[0].grid()
axs[1].plot(t1,v,marker="*",c="cyan")
axs[1].set(xlabel=" Time/Time Period ",ylabel="Velocity")
axs[1].grid()
plt.show()
print("----------------------------- SIMPLE HARMONIC OSCILLATOR---------------------------------")
d={"No. of time periods":t1,"Displacement":x,"Velocity":v}
print(pd.DataFrame(d))

#3b https://scipy-lectures.org/intro/scipy/auto_examples/plot_odeint_damped_spring_mass.html (ref)
IC=[1,0];tmin=0;h=0.1;m=0.5;k=4 #SI units
tmax=100
dis=[];vel=[];time=[]
ba=[0.2,np.sqrt(4*m*k),3]
for b in ba:
     cons=(m,k,b)
     S=RK2(f2,IC,tmin,tmax,h,cons)
     x,v= S[0].T
     t=S[1]
     dis.append(x)
     vel.append(v)
fig, ax = plt.subplots(3,2)
fig.suptitle('DAMPED HARMONIC OSCILLATOR',c="r")
ax[0,0].plot(t,dis[0],marker="*",c="r")
ax[0,0].set(xlabel="time ",ylabel="Dispacement",title="Underdamped")
ax[0,0].grid()
ax[0,1].plot(t,vel[0],marker="*",c="green")
ax[0,1].set(xlabel="time ",ylabel="Velocity",title="Underdamped")
ax[0,1].grid()
ax[1,0].plot(t,dis[1],marker="*",c="purple")
ax[1,0].set(xlabel="time ",ylabel="Dispacement",title="Critically Damped")
ax[1,0].grid()
ax[1,1].plot(t,vel[1],marker="*",c="darkblue")
ax[1,1].set(xlabel="time ",ylabel="Velocity",title="Critically Damped")
ax[1,1].grid()
ax[2,0].plot(t,dis[2],marker="*",c="brown")
ax[2,0].set(xlabel="time ",ylabel="Dispacement",title="Overdamped")
ax[2,0].grid()
ax[2,1].plot(t,vel[2],marker="*",c="violet")
ax[2,1].set(xlabel="time ",ylabel="Velocity",title="Overdamped")
ax[2,1].grid()
plt.show()
print("---------------------- DAMPED HARMONIC OSCILLATOR  (UNDERDAMPED)-----------------------")
d={"Time":t,"Displacement":dis[0],"Velocity":vel[0]}
print(pd.DataFrame(d))
print("---------------------- DAMPED HARMONIC OSCILLATOR  (CRITICALLY DAMPED)-----------------------")
d={"Time":t,"Displacement":dis[1],"Velocity":vel[1]}
print(pd.DataFrame(d))
print("---------------------- DAMPED HARMONIC OSCILLATOR  (OVERDAMPED)-----------------------")
d={"Time":t,"Displacement":dis[2],"Velocity":vel[2]}
print(pd.DataFrame(d))

#3c
IC=[1,0];tmin=0;g=9.8;L=2  #SI units
cons1=(g,L)
tp=2*np.pi*np.sqrt(L/g)
tmax=10*tp
h=tp/50
S=RK2(f3,IC,tmin,tmax,h,cons1)
x,v= S[0].T
t=S[1]
t1=t/tp
fig, axs = plt.subplots(2)
fig.suptitle('SIMPLE PENDULUM',c="r")
axs[0].plot(t1,x,marker="*",c="r")
axs[0].set(xlabel="Time/Time Period ",ylabel="Angular Dispacement")
axs[0].grid()
axs[1].plot(t1,v,marker="*",c="green")
axs[1].set(xlabel=" Time/Time Period ",ylabel="Angular Velocity")
axs[1].grid()
plt.show()
print("------------------------------ SIMPLE PENDULUM------------------------------")
d={"No. of time periods":t,"Angular Displacement":x,"Angular Velocity":v}
print(pd.DataFrame(d))

#3d
IC=[10,0,-10,0];tmax=80;tmin=0;h=0.01;m1=10;k1=90;g1=9.8;l1=10  #SI units
cons1=(k1,m1,g1,l1)
S=RK2(f4,IC,tmin,tmax,h,cons1)
x1,v1,x2,v2= S[0].T
t3=S[1]
fig, axs = plt.subplots(2, 2)
fig.suptitle('COUPLED SYSTEM',c="r")
axs[0, 0].plot(t3,x1,c="r")
axs[0,0].set(xlabel=" Time",ylabel="Displacement",title="Mass A")
axs[0,0].grid()
axs[0, 1].plot(t3,v1,c="green")
axs[0,1].set(xlabel=" Time",ylabel="Velocity",title="Mass A")
axs[0,1].grid()
axs[1, 0].plot(t3,x2,c="magenta")
axs[1,0].set(xlabel=" Time",ylabel="Displacement",title="Mass B")
axs[1,0].grid()
axs[1, 1].plot(t3,v2,c="green")
axs[1,1].set(xlabel=" Time",ylabel="Velocity",title="Mass B")
axs[1,1].grid()
plt.show()
print("---------------------- COUPLED PENDULUM-----------------------")
print("-------------------------------- MASS A ---------------------------------")
d={"Time":t3,"Displacement":x1,"Velocity":v1}
print(pd.DataFrame(d))
print("-------------------------------- MASS B ---------------------------------")
d={"Time":t3,"Displacement":x2,"Velocity":v2}
print(pd.DataFrame(d))


import numpy as np
from matplotlib import pyplot
from scipy.integrate import solve_ivp as ode45
from scipy.interpolate import CubicSpline

def seirmodel(t, y, gamma, sigma, eta, Rstar):
    n = 10**7
    dy = np.zeros(5)
    #beta(t) et e(t) donc 5eqn, page 2 equations
    dy[0] = (-y[4]*y[0]*y[2])/n                        #s
    dy[1] = (y[4]*y[0]*y[2])/n - (sigma*y[1])          #e
    dy[2] = (sigma*y[1]) - (gamma*y[2])                #x
    dy[3] = gamma*y[2]                                 #r
    dy[4] = eta*((gamma*Rstar(t))-y[4])                #beta
    return dy

"""
on utilise les interpolations cubiques car si n > 10, les intervalles
[0, 1] et [ n-1, n] seraient problématiques dû à la création d'un  polynôme de degré n-1
"""
def q5(t, R, gamma = 0.06, eta = 0.1, sigma = 0.2, n = 10**7, y0 = [10**7-100, 0, 100, 0, 4*0.06], t_span = [0,400]):
    try:
        if( len(t) != len(R)):
            raise ValueError('Les données ne sont pas valides !')
        if not t or not R:
            raise ValueError('Aucune donnée !')
        R_func = CubicSpline(t,R, bc_type='clamped')
        solution = ode45(lambda t_ode, y : seirmodel(t_ode, y, gamma, sigma, eta, R_func), t_span, y0)
        #Dessiner graphique
        pyplot.figure()
        pyplot.plot(solution.t, solution.y[0,:], label="Personnes suceptibles")
        pyplot.plot(solution.t, solution.y[1,:], label="Personnes exposées")
        pyplot.plot(solution.t, solution.y[2,:], label="Personnes infectées")
        pyplot.plot(solution.t, solution.y[3,:], label="Personnes guéries")
        pyplot.plot(t, R, 'ro', label="Données")
        pyplot.legend(loc="best")
        pyplot.title("Scénario")
        pyplot.xlabel("Temps(t)")
        pyplot.ylabel("Nombre de personnes")
        pyplot.savefig("pic_q5.png")
        pyplot.figure()

        x = np.linspace(0,400, 4000)
        pyplot.plot(x, R_func(x), label="Nombre de reproduction de la maladie (R)")
        pyplot.plot(t, R, 'ro', label="Données")

        pyplot.legend(loc='best')
        pyplot.title("Évolution du nombre de repoducction de la maladie selon un scénario donné")
        pyplot.xlabel('Temps')
        pyplot.savefig('pic_r_q5.png')
        return
    except ValueError as e:
        print('Erreur dans les données : ', e)
        return
    except Exception as e:
        print('Quelque chose s\'est mal passé : ', e)
        return

def test_q5():
    t = [ 0, 30, 60, 100, 145, 190, 200, 240, 300, 400]
    R = [ 2.5, 4.5, 2.8,2,  2.5, 3, 2, 2.5, 3, 1]
    q5(t, R)

test_q5()


import math
import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import accuracy_score, precision_score, recall_score

class Random2DGaussian(object):

    min_x = 0
    max_x = 10
    min_y = 0
    max_y = 10

    def __init__(self):
        self.mean = [(self.max_x-self.min_x) * np.random.random_sample() + self.min_x,
                        (self.max_y-self.min_y) * np.random.random_sample() + self.min_y]

        eigval_x = (np.random.random_sample() * (self.max_x - self.min_x) / 5)**2
        eigval_y = (np.random.random_sample() * (self.max_y - self.min_y) / 5)**2
        D = [[eigval_x, 0], [0, eigval_y]]

        angle = 2 * math.pi * np.random.random_sample()
        R = [[math.cos(angle), -math.sin(angle)], [math.sin(angle), math.cos(angle)]]

        self.cov = np.dot(np.dot(R, D), np.transpose(R))

    def get_sample(self, n):
        return np.random.multivariate_normal(self.mean, self.cov, size=n)

def binlogreg_train(X, Y_, param_niter=1000, param_delta=0.001):
    w, b = np.random.randn(2, 1), 0
    for i in range(param_niter):
        scores = np.dot(X, w) + b

        probs = np.exp(scores) / (1 + np.exp(scores))
        loss = -np.sum(np.log(probs[Y_==1]) + np.log(1 - probs[Y_==0]))

        if i % 100 == 0:
            print "Iteration {}: loss: {}".format(i, loss)

        dl_dscores = probs - Y_

        grad_w = np.dot(np.transpose(X), dl_dscores)
        grad_b = np.sum(dl_dscores)

        w += -param_delta * grad_w
        b += -param_delta * grad_b

    return w, b

def binlogreg_classify(X, w, b):
    s = np.dot(X, w) + b
    probs = np.exp(s) / (1 + np.exp(s))

    return probs

def sample_gauss_2d(C, N):

    X = []
    Y = []

    for i in range(C):
        gauss = Random2DGaussian()
        x = gauss.get_sample(N)
        X.extend(x)

        y=[[i] for j in range(N)]
        Y.extend(y)

    return np.array(X), np.array(Y)

def eval_perf_binary(Y, Y_):
    acc = accuracy_score(Y, Y_)
    prec = precision_score(Y, Y_, average='binary')
    rec = recall_score(Y, Y_, average='binary')
    return acc, prec, rec

def decision_func(X):
    scores = X[:,0] + X[:,1] - 5
    return scores>0.5

def graph_data(X, Y, Y_):
    correct_idx = (Y==Y_).T.flatten()
    wrong_idx = (Y!=Y_).T.flatten()

    plt.scatter(X[correct_idx, 0], X[correct_idx, 1], c=Y_[correct_idx], marker='o', label='correct')
    plt.scatter(X[wrong_idx, 0], X[wrong_idx, 1], c=Y_[wrong_idx], marker='s', label='wrong')
    plt.legend(loc='upper left')
    plt.show()

if __name__ == '__main__':
    X, Y_ = sample_gauss_2d(2, 100)
    w, b = binlogreg_train(X, Y_)
    probs = binlogreg_classify(X, w, b)
    Y = probs>0.5

    print eval_perf_binary(Y, Y_)

    graph_data(X, Y, Y_)


from .util import computeD1D2

from scipy.stats import norm
import numpy as np

def blackScholesCall(current: float, volatility: float, ttm: float,
                     strike: float, rf: float) -> float:
    """Function to compute the Black-Scholes-Merton price of a European Call
    Option, parameterized by the current underlying asset price, volatility,
    time to expiration, strike price, and risk-free rate.

    Arguments:
        current {float} -- Current price of the underlying asset.
        volatility {float} -- Volatility of the underlying asset price.
        ttm {float} -- Time to expiration (in years).
        strike {float} -- Strike price of the option contract.
        rf {float} -- Risk-free rate (annual).

    Returns:
        float -- Price of a European Call Option contract.
    """

    d1, d2 = computeD1D2(current, volatility, ttm, strike, rf)

    call = (current * norm.cdf(d1)) \
        - (strike * np.exp(-1 * rf * ttm) * norm.cdf(d2))

    return call


import numpy as np
import sympy

def createDFTMatrix(n):
    w = sympy.cos(-(2*sympy.pi)/n) + 1j*sympy.sin(-(2*sympy.pi/n))
    dftMatrix = [[None for _ in range(n)] for _ in range(n)]

    cnt = 0
    for i in range(n):
        for j in range(n):
            dftMatrix[i][j] = w**(cnt*j)
        cnt += 1

    return np.asarray(dftMatrix, dtype=np.complex)

if __name__ == "__main__":
    n = int(input("Antall elementer i vektoren: "))
    print("Elementene i vektoren:")
    x = []
    for _ in range(n):
        x.append(int(input()))

    x = np.asarray(x, dtype=np.complex)
    dftMatrix = createDFTMatrix(n)

    dft = np.asarray(1/sympy.sqrt(n)*dftMatrix.dot(x), dtype=np.complex)

    print(f"DFT(x) = {dft}")