#!/usr/bin/evn python

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

# !!! copyright to https://gist.github.com/amroamroamro/1db8d69b4b65e8bc66a6

def estimateTiltingPlane(points, points2interp = [], bounds = [], res = 100, order = 1, return_RMSE = False):
    """
    Method to estimate a tilting plane based on a set of points

    Args:
        points: (Required) n x 3 numpy array with the x, y and z coordinates of known points
        points2interp: (Optional, defaults to []) n x 2 numpy array with points to interpolate
        bounds: (Optional, defaults to []) list with xmin, xmax, ymin and ymax
        resolution: (Optional, defaults to 100) resolution of the output points
        order: (Optional, defaults to 1) 1 or 2 indicating whether to fit a linear or quadratic plane
        return_RMSE: (Optional, defaults to False) True/False to return the RMSE as well
    """

    if len(points2interp) == 0:
        if len(bounds) == 4:
            xmin, xmax, ymin, ymax = bounds
        else:
            xmin = np.min(points[:,0])
            xmax = np.max(points[:,0])
            ymin = np.min(points[:,1])
            ymax = np.max(points[:,1])

        # regular grid covering the domain of the data
        X,Y = np.meshgrid(np.arange(xmin, xmax, res), np.arange(ymin, ymax, res))
        XX = X.flatten()
        YY = Y.flatten()
    else:
        X, Y = points2interp[:,0], points2interp[:,1]
        XX, YY = points2interp[:,0], points2interp[:,1]

    data = points

    if order == 1:
        # best-fit linear plane
        A = np.c_[data[:,0], data[:,1], np.ones(data.shape[0])]
        C,residues,_,_ = scipy.linalg.lstsq(A, data[:,2])    # coefficients

        # evaluate it on grid
        Z = C[0]*X + C[1]*Y + C[2]

    elif order == 2:
        # best-fit quadratic curve
        A = np.c_[np.ones(data.shape[0]), data[:,:2], np.prod(data[:,:2], axis=1), data[:,:2]**2]
        C,residues,_,_ = scipy.linalg.lstsq(A, data[:,2])

        # evaluate it on a grid
        Z = np.dot(np.c_[np.ones(XX.shape), XX, YY, XX*YY, XX**2, YY**2], C).reshape(X.shape)

    if return_RMSE:
        # calculate root mean square error
        RMSE = residues
        return [XX, YY, Z.flatten()], RMSE
    else:
        return [XX, YY, Z.flatten()]


import numpy as np

from scipy import stats

class Statistics:
    @staticmethod
    def getNumAnalyzedSamples(samples):
        return len(samples)

    @staticmethod
    def getMin(samples):
        return np.min(samples)

    @staticmethod
    def getMax(samples):
        return np.max(samples)

    @staticmethod
    def getMean(samples):
        return np.mean(samples)

    @staticmethod
    def getPercentile(samples, percentage):
        # Based on #10 (Plotly's implementation): http://jse.amstat.org/v14n3/langford.html
        sorted_samples = np.sort(samples)

        norm_percentage = percentage / 100.0

        # From #10: n * p + 0.5, we substract 1 because array indexing starts from 0
        percentile_pos     = (Statistics.getNumAnalyzedSamples(samples) * norm_percentage + 0.5) - 1
        percentile_pos_int = int(percentile_pos)

        if percentile_pos.is_integer():
            return sorted_samples[percentile_pos_int]

        percentile_pos_decimal_part = percentile_pos - percentile_pos_int

        percentile = sorted_samples[percentile_pos_int] + percentile_pos_decimal_part * (sorted_samples[percentile_pos_int + 1] - sorted_samples[percentile_pos_int])

        return percentile

    @staticmethod
    def getIQR(samples):
        q1 = Statistics.getPercentile(samples, 25)
        q3 = Statistics.getPercentile(samples, 75)

        iqr = q3 - q1

        return iqr

    @staticmethod
    def getTukeyFences(samples, sensitivity=1.5):
        # Tukey's fences for outliers detection
        q1 = Statistics.getPercentile(samples, 25)
        q3 = Statistics.getPercentile(samples, 75)

        iqr = q3 - q1

        lower_fence = q1 - sensitivity * iqr
        upper_fence = q3 + sensitivity * iqr

        return lower_fence, upper_fence

    @staticmethod
    def getVariance(samples, ddof=0):
        # delta degrees of freedom: Plotly uses 0, for unbiased variance set to: 1
        variance = np.var(samples, ddof=ddof)

        return variance

    @staticmethod
    def getStdDev(samples, ddof=0):
        # delta degrees of freedom: Plotly uses 0, for unbiased variance set to: 1
        std_dev = np.std(samples, ddof=ddof)

        return std_dev

    @staticmethod
    def getStdErr(samples):
        return Statistics.getStdDev(samples) / np.sqrt(Statistics.getNumAnalyzedSamples(samples));

    @staticmethod
    def getMargin(samples, confidence_level=0.999):
        _, z_level = stats.t.interval(confidence_level, Statistics.getNumAnalyzedSamples(samples) - 1)
        std_err    = Statistics.getStdErr(samples)

        margin = z_level * std_err

        return margin

    @staticmethod
    def getConfidenceInterval(samples, confidence_level=0.999):
        mean   = Statistics.getMean(samples)
        margin = Statistics.getMargin(samples, confidence_level)

        lower = mean - margin
        upper = mean + margin

        return {"ci_lower": lower, "ci_upper": upper, "ci_level": confidence_level * 100.0}

    @staticmethod
    def getSkewness(samples):
        return stats.skew(samples)

    @staticmethod
    def getKurtosis(samples):
        return stats.kurtosis(samples, fisher=False)


import numpy as np

p = [0.2,0.2,0.2,0.2,0.2]
world = ['green','red','red','green','green']
measurements = ['red','green']
motions = [1,1]
pHit = 0.6
pMiss = 0.2
pExact = 0.8
pOvershoot = 0.1
pUndershoot = 0.1

def sense(p,Z):
	q = []
	for i in range(len(p)):
		hit = (Z == world[i])
		q.append(p[i] * (hit * pHit + (1-hit) * pMiss))
	# Normalize
	return np.array(q)/sum(q)

def sense2D(p,Z,world,sense_acc):
	q = np.ones_like(p)
	rows, cols  = q.shape

	for i in range(rows):
		for j in range(cols):
			hit = (Z == world[i,j])
			if (hit):
				q[i,j] = p[i,j] * (pHit * sense_acc + pMiss * (1- sense_acc))
			else:
				q[i,j] = p[i,j] * (pHit * (1- sense_acc) + pMiss * sense_acc)
	# Normalize
	return q/np.sum(q)

def move(p,U):
	l = len(p)
	q = [ pExact * p[(i-U)%l]
		+ pOvershoot * p[(i-U-1)%l]
		+ pUndershoot * p[(i-U+1)%l]
		for i in range(l)]
	return q

def move2D(p,U,p_move):
	q = np.ones_like(p)
	rows = q.shape[0]
	cols = q.shape[1]
	for i in range(rows):
		for j in range(cols):
			q[i,j] = p_move * p[(i-U[0])%rows][(j-U[1])%cols] + (1-p_move) * p[i,j]
	return q/np.sum(q)

# 1D
for k in range(len(motions)):
	# sense first then move
	p = sense(p,measurements[k])
	p = move(p,motions[k])

# Also assume that at each step, the robot:
# 1) first makes a movement,
# 2) then takes a measurement.
#
# Motion:
#  [0,0] - stay
#  [0,1] - right
#  [0,-1] - left
#  [1,0] - down
#  [-1,0] - up

def localize(world,measurements,motions,sensor_right,p_move):
	if (len(measurements) != len(motions)):
		raise ValueError("len of measures should be same as motions")

	# initializes p to a uniform distribution over a grid of the same dimensions as colors
	pinit = 1.0 / float(len(colors)) / float(len(colors[0]))
	p = np.array([[pinit for row in range(len(colors[0]))] for col in range(len(colors))])

    # >>> Insert your code here <<<
	for i in range(len(motions)):
		p = sense2D(p,measurements[i],world,sensor_right)
		p = move2D(p,motions[i],p_move)

	return p

def show(p):
    rows = ['[' + ','.join(map(lambda x: '{0:.5f}'.format(x),r)) + ']' for r in p]
    print('[' + ',\n '.join(rows) + ']')

#############################################################
# For the following test case, your output should be
# [[0.01105, 0.02464, 0.06799, 0.04472, 0.02465],
#  [0.00715, 0.01017, 0.08696, 0.07988, 0.00935],
#  [0.00739, 0.00894, 0.11272, 0.35350, 0.04065],
#  [0.00910, 0.00715, 0.01434, 0.04313, 0.03642]]
# (within a tolerance of +/- 0.001 for each entry)

colors = np.array([['R','G','G','R','R'],
          			['R','R','G','R','R'],
          			['R','R','G','G','R'],
          			['R','R','R','R','R']])

#
# The essence is that the state matchs all
# measures during the motions keeps the highest
# in the end, it is not compute the most likely path.
#

measurements = ['G','G','G','G','G']
motions = [[0,0],[0,1],[1,0],[1,0],[0,1]]
p = localize(colors,measurements,motions,sensor_right = 0.7, p_move = 0.8)
show(p) # displays your answer


#!/usr/bin/env
# -*- coding: utf-8 -*-
# Copyright (C) Victor M. Mendiola Lau - All Rights Reserved
# Unauthorized copying of this file, via any medium is strictly prohibited
# Proprietary and confidential
# Written by Victor M. Mendiola Lau <ryuzakyl@gmail.com>, August 2016

from math import sqrt

from scipy.spatial.distance import correlation as scy_correlation

def correlation(x, y):
    """Computes the correlation coefficient.

    Args:
        x (list): The first vector.
        y (list): The second vector.

    Returns:
        float: The correlation coefficient between vectors x and y.

    """

    # getting the length of the vectors
    x_length = len(x)
    y_length = len(y)

    # validating parameters
    if x_length != y_length:
        raise Exception('Vectors with different sizes')

    # computing the means of both vectors
    mean_x = 0.0
    mean_y = 0.0
    for i in range(x_length):
        mean_x += x[i]
        mean_y += y[i]

    # dividing by the length of the vectors
    mean_x /= x_length
    mean_y /= y_length

    # computing the values k, f1 and f2
    k = 0.0
    f1 = 0.0
    f2 = 0.0
    for i in range(x_length):
        # computing the offsets
        offset_x = x[i] - mean_x
        offset_y = y[i] - mean_y

        # updating k, f1 and f2
        k += offset_x * offset_y
        f1 += offset_x * offset_x
        f2 += offset_y * offset_y

    # returning the computed correlation distance
    return k / (sqrt(f1) * sqrt(f2))

def probabilistic_correlation(x, y):
    """Computes the correlation coefficient as a probability value.

    Args:
        x (list): The first vector.
        y (list): The second vector.

    Returns:
        float: The correlation probability value between vectors x and y.

    """

    # computing the correlation value
    value = correlation(x, y)

    # moving value to interval [0, 2]
    value += 1.0

    # stretching value to the interval [0, 1]
    return 0.5 * value

def dis_correlation_manual(x, y):
    """Computes the correlation coefficient as a dissimilarity measure.

    Args:
        x (list): The first vector.
        y (list): The second vector.

    Returns:
        float: The dissimilarity value between vectors x and y.

    Examples:
        >>> dis_correlation_manual([1.0, 2.0, 3.0], [4.0, 5.0, 6.0])
        2.220446049250313e-16
        >>> dis_correlation_manual([1.0, 2.0, 3.0], [6.0, 5.0, 4.0])
        1.9999999999999998
        >>> dis_correlation_manual([1.0, 2.0, 3.0, 20.0, 150.0, 3.0], [6.0, 5.0, 4.0, 3.0, 2.0, 1.0])
        1.4245486433123256
        >>> dis_correlation_manual([1.0, 2.0, 3.0, 20.0, 150.0, 3.0], [1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
        0.5754513566876744

    """

    # computing the correlation value
    value = correlation(x, y)

    # setting dissimilarity value to the interval [0, 2]
    return 1 - value

def dis_correlation_scipy(x, y):
        """Computes the correlation distance between `x` and `y`.

        Args:
            x (list): The first vector.
            y (list): The second vector.

        Returns:
            double: The correlation distance between vectors `x` and `y`.

        References:
            * https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.correlation.html

        Examples:
            >>> dis_correlation_scipy([1.0, 2.0, 3.0], [4.0, 5.0, 6.0])
            2.2204460492503131e-16
            >>> dis_correlation_scipy([1.0, 2.0, 3.0], [6.0, 5.0, 4.0])
            1.9999999999999998
            >>> dis_correlation_scipy([1.0, 2.0, 3.0, 20.0, 150.0, 3.0], [6.0, 5.0, 4.0, 3.0, 2.0, 1.0])
            1.4245486433123256
            >>> dis_correlation_scipy([1.0, 2.0, 3.0, 20.0, 150.0, 3.0], [1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
            0.57545135668767444

        """

        # returning the correlation distance
        return scy_correlation(x, y)


import numpy as np
import matplotlib.pyplot as plt
from utils import sigmoid , gradient_descent_logistic_regresion

X1=np.array([]) # grade 1
X2=np.array([]) # grade 2
Y=np.array([]) # admitted to university
file = open("data\\ex2data1.txt", "r")
for row in file:
  ar = row.split(",")
  X1 = np.append(X1,float(ar[0]))
  X2 = np.append(X2,float(ar[1]))
  Y = np.append(Y,float(ar[2]))

m = Y.size # should be same as the size of X1,X2
X0 = np.ones(m)

scale_factor = 100

# using scale_factor of 1 will cause an error "Desired error not necessarily achieved due to precision loss."
# the problem is that X0 is 1 while X1,X2 are between 0 - 100 so 100 scale them to be around 1
X1 = X1/scale_factor
X2 = X2/scale_factor
X= np.vstack((X0,X1,X2)).T
Teta = [100,100,100] # arbitrary initial condition

iterations = 0
alfa = 0.01 # learning rate
max_iterations = 1000

while iterations <  max_iterations:
    res = gradient_descent_logistic_regresion(Teta,X,Y,alfa)
    Teta = res["Teta"]
    J = res["J"]
    iterations += 1

print("J : {:0.2f} , teta0 : {:0.2f} , teta1 : {:0.2f} ,  teta2 : {:0.2f}".format(J,Teta[0], Teta[1],Teta[2]))

def prob(x1,x2):
    return sigmoid(Teta[0]+Teta[1]*x1/scale_factor+Teta[2]*x2/scale_factor)

print("h @ x1=90 , x2=90",prob(90,90))

ar_index_pass = np.where(Y == 1)
ar_index_fail = np.where(Y == 0)

x2_0_5_line = -(Teta[0]+Teta[1]*X1)/Teta[2] # prob 0.5

plt.title('+ : pass , o : fail \ncomputed gradient descent 0.5 logistic regression decision boundary')
plt.xlabel("grade1")
plt.ylabel("grade2")
plt.plot(scale_factor*X1[ar_index_pass],scale_factor*X2[ar_index_pass],'+')
plt.plot(scale_factor*X1[ar_index_fail],scale_factor*X2[ar_index_fail],'o')
plt.plot(scale_factor*X1,scale_factor*x2_0_5_line)
plt.show()


from sympy import primerange
from typing import List, Dict
from pprint import pprint

def _prime_number_less_than_n(number: int) -> List[int]:
    return list(primerange(0, number))

def _calculate_vp_n_factorial(number: int, prime: int) -> int:

    vp_n_factorial = 0
    power = 1
    while number//(prime**power):
        vp_n_factorial += number//(prime**power)
        power += 1

    return vp_n_factorial

# returns the power of all prime numbers that are part of the decomposition of n!
def factorial_factoring(number: int) -> str:
    return ''.join(
        f'({prime}^{_calculate_vp_n_factorial(number, prime)})'
        for prime in _prime_number_less_than_n(number)
    )

# returns a dictionary with a prime number and its power in decomposing n!
def factorial_factoring_dict(number: int) -> Dict[int, int]:
    return {
        prime: _calculate_vp_n_factorial(number, prime)
        for prime in _prime_number_less_than_n(number)
    }

print(factorial_factoring(50))
pprint(factorial_factoring_dict(50), width=1)


"""
==============================================================
Curve fitting: temperature as a function of month of the year
==============================================================

We have the min and max temperatures in Alaska for each months of the
year. We would like to find a function to describe this yearly evolution.

For this, we will fit a periodic function.
"""

############################################################
# The data
############################################################
import numpy as np

temp_max = np.array([17,  19,  21,  28,  33,  38, 37,  37,  31,  23,  19,  18])
temp_min = np.array([-62, -59, -56, -46, -32, -18, -9, -13, -25, -46, -52, -58])

import matplotlib.pyplot as plt
months = np.arange(12)
plt.plot(months, temp_max, 'ro')
plt.plot(months, temp_min, 'bo')
plt.xlabel('Month')
plt.ylabel('Min and max temperature')

############################################################
# Fitting it to a periodic function
############################################################

from scipy import optimize
def yearly_temps(times, avg, ampl, time_offset):
    return (avg
            + ampl * np.cos((times + time_offset) * 2 * np.pi / times.max()))

res_max, cov_max = optimize.curve_fit(yearly_temps, months,
                                      temp_max, [20, 10, 0])
res_min, cov_min = optimize.curve_fit(yearly_temps, months,
                                      temp_min, [-40, 20, 0])

############################################################
# Plotting the fit
############################################################

days = np.linspace(0, 12, num=365)

plt.figure()
plt.plot(months, temp_max, 'ro')
plt.plot(days, yearly_temps(days, *res_max), 'r-')
plt.plot(months, temp_min, 'bo')
plt.plot(days, yearly_temps(days, *res_min), 'b-')
plt.xlabel('Month')
plt.ylabel('Temperature ($^\circ$C)')

plt.show()


import numpy as np
from dataio import DataIO

class SampleSinus(object):
    """ Sample sinus function f(x) = sin(2 pi x)

    TARGET
    - create input and target vector
    - target includes noise (measurement error)

    1) create x (length N), loop through bounded interval
    2) Compute N times f(x) = sin( 2 pi x)
    3) Draw N times e from N(0, 0.3)
    4) Add e to f(x)
    5) Write to file (option xt)

    SINUS FUNCTION
    - create input and target vector
    - to draw sinus function
    - no noise

    1) create x (length N), loop through bounded interval
    2) compue N times f(x) = sin( 2 pi x)
    3) Write to file (option xt)

    INPUT
    - create input vector (no target vector)
    1) Draw N times x from Uniform(0, 1)
    2) Write to file (option x)

    """

    SEED = 123456 # random seed

    X_LBU = 0.0     # Lower Bound Uniform distribution
    X_UBU = 1.0     # Upper Bound Uniform distribution
    STD_NOISE = 0.3 # standard deviation normal error distribution

    NLARGE = 10001  # decritization number of f(x) (vizualization)

    def __init__(self, seed=None):
        """ Initialize SampleSinus object. """

        # Check if random seed is provided
        if seed is None:
            seed = self.SEED
        # construct random state object
        self.random_state = np.random.RandomState(seed)

    def target(self, N, file_name, xdistr=None):
        """ Execute sampling operation (see class description)

        :param N - int, number of data points.
        :param file_name - str, name of the output file.

        :output None - write data to file.
        """
        if xdistr is None or xdistr == 'evenly':
            x = np.linspace(self.X_LBU, self.X_UBU, N)
        elif xdistr == 'random':
            x = self.random_state.uniform(self.X_LBU, self.X_UBU, N)

        y = np.sin(2*np.pi*x)
        e = self.random_state.normal(loc=0.0, scale=self.STD_NOISE, size=N)
        t = y + e
        title = 'input\ttarget\terror'
        DataIO.write_data( [x, t, e], file_name, title)

    def function(self, file_name):
        x = np.linspace(self.X_LBU, self.X_UBU, self.NLARGE)
        f = np.sin(2*np.pi*x)
        title = 'input\tsinus function'
        DataIO.write_data([x, f], file_name, title)

    def input(self, N, file_name):
        x = self.random_state.uniform(self.X_LBU, self.X_UBU, N)
        title = 'input'
        DataIO.write_data([x], file_name, title)


#difference between normal linear fit and parabolic fit using polynomial regression

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style('whitegrid')
import pandas as pd
#generating random numbers for equation y=X^3+100+e
np.random.seed(42)
n_samples=100
X=np.linspace(0,10,100)
rng=np.random.randn(n_samples)*100
y=X**3+rng+100
#plot the data in scatter plot
plt.figure(figsize=(10,8));
plt.scatter(X,y);
plt.show()

#performing linear regression on the X and y data
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score
lr=LinearRegression()
lr.fit(X.reshape(-1,1),y);
model_pred=lr.predict(X.reshape(-1,1))
#prediction plot will be linear
plt.figure(figsize=(10,8));
plt.scatter(X,y);
plt.plot(X,model_pred);
#r2 score will be low
print(r2_score(y,model_pred))
plt.show()

#performing linear regression on the X and y data with polynomial regression
from sklearn.preprocessing import PolynomialFeatures
poly_reg=PolynomialFeatures(degree=3)
X_poly=poly_reg.fit_transform(X.reshape(-1,1))
lin_reg_2=LinearRegression()
lin_reg_2.fit(X_poly,y.reshape(-1,1))
y_pred=lin_reg_2.predict(X_poly)
#prediction plot will be parabolic
plt.figure(figsize=(10,8));
plt.scatter(X,y);
plt.plot(X,y_pred);
#r2 score will be high (best fit)
print(r2_score(y,y_pred))
plt.show()


import numpy as np

class NeuronLayers():

    def __init__(self, number_of_neurons, number_of_inputs):
        self.weight = 2 * np.random.random((number_of_inputs, number_of_neurons)) - 1

class NeuralNetwork():

    def __init__(self, layer1, layer2):
        self.layer1 = layer1
        self.layer2 = layer2

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

    def __sigmoid_derivative(self, x):
        return x * (1 - x)

    def __step_function(self, x):
        out = 0
        if(x[0] >= 0.5):
            out = 1

        return out

    def think(self, inputs):
        output_from_layer1 = self.__sigmoid(np.dot(inputs, self.layer1.weight))
        output_from_layer2 = self.__sigmoid(np.dot(output_from_layer1, self.layer2.weight))
        integer_out = self.__step_function(output_from_layer2)
        return output_from_layer1, output_from_layer2, integer_out

    def train(self, training_set_inputs, training_set_outputs, e_poch):
        for i in range(e_poch):

            output_from_layer1, output_from_layer2, integer_out = self.think(training_set_inputs)

            layer2_error = training_set_outputs - output_from_layer2
            layer2_delta = layer2_error * self.__sigmoid_derivative(output_from_layer2)

            layer1_error = layer2_delta.dot(self.layer2.weight.T)
            layer1_delta = layer1_error * self.__sigmoid_derivative(output_from_layer1)

            layer1_ajustment = training_set_inputs.T.dot(layer1_delta)
            layer2_ajustment = output_from_layer1.T.dot(layer2_delta)

            self.layer1.weight += layer1_ajustment
            self.layer2.weight += layer2_ajustment

    def print_weights(self):
        print("layer 1")
        print(self.layer1.weight)
        print("layer 2")
        print(self.layer2.weight)

if __name__ == "__main__":
    np.random.seed(1)

    layer1 = NeuronLayers(4, 3)
    layer2 = NeuronLayers(1, 4)

    network = NeuralNetwork(layer1, layer2)

    print("The start up weight in the network")
    network.print_weights()

    train_set_inputs = np.array([[0, 0, 1], [0, 1, 1], [1, 0, 1], [0, 1, 0], [1, 0, 0], [1, 1, 1], [0, 0, 0]])
    train_set_outputs = np.array([[1, 1, 1, 1, 1, 1, 0]]).T

    network.train(train_set_inputs, train_set_outputs, 100)

    print("The weight after the training")
    network.print_weights()

    print("Test to see if the model are good enough by inputing [1, 1, 0]")
    hidden_state, org_out, integer_out = network.think(np.array([1, 1, 0]))
    print(integer_out)


import numpy as np

from .transformations import affine_transformation

def get_chebyshev_nodes(lb, ub, order):
    """
    Provides chebyshev quadratures nodes
    scaled to live on the interval [lb, ub], of specified order
    The nodes are reversed from traditional chebyshev nodes
        (so that the lowest valued node comes first)
    Returns:
        unscaled nodes
        scaled nodes
        scaling ratio
    """
    xc, _ = np.polynomial.chebyshev.chebgauss(order)
    x, rat = affine_transformation(xc[::-1], -1, 1, lb, ub, return_ratio=True)
    return xc[::-1], x, rat

class ChebyshevInterpolater(object):
    def __init__(self, x, data):
        self.x = x
        self.n = self.x.shape[0]
        self.data = data
        self.coefs = np.polynomial.chebyshev.chebfit(self.x, self.data, self.n-1)
    def __call__(self, x_out):
        return np.polynomial.chebyshev.chebval(x_out, self.coefs)


import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm, t
np.random.seed(1)

#%%
N = 1_0
mu = 5
sd = 2

#%%
x = np.random.randn(N)*sd + mu

#%% Z-CI
mu_hat = x.mean()
sigma_hat = x.std(ddof=1)
z_left = norm.ppf(0.0250)
z_right = norm.ppf(0.9750)
left_ci = mu_hat + z_left*sigma_hat/np.sqrt(N)
right_ci = mu_hat + z_right*sigma_hat/np.sqrt(N)
print(f"Mean: {mu_hat}")
print(f"Confidence Interval is [{left_ci}, {right_ci}]")

#%% t-CI
mu_hat = x.mean()
sigma_hat = x.std(ddof=1)
t_left = t.ppf(0.0250, df=N-1)
t_right = t.ppf(0.9750, df=N-1)
left_ci = mu_hat + t_left*sigma_hat/np.sqrt(N)
right_ci = mu_hat + t_right*sigma_hat/np.sqrt(N)
print(f"Mean: {mu_hat}")
print(f"Confidence Interval is [{left_ci}, {right_ci}]")

#%%
def experiment():
    x = np.random.randn(N) * sd + mu
    mu_hat = x.mean()
    sigma_hat = x.std(ddof=1)
    t_left = t.ppf(0.0250, df=N - 1)
    t_right = t.ppf(0.9750, df=N - 1)
    left_ci = mu_hat + t_left * sigma_hat / np.sqrt(N)
    right_ci = mu_hat + t_right * sigma_hat / np.sqrt(N)
    return mu<right_ci and mu>left_ci

def multi_experiment(n):
    results = [experiment() for _ in range(n)]
    return np.mean(results)

#%%
result = multi_experiment(10_000)
print(result)


import numpy as np

from optimizer import Optimizer

class Polyak(Optimizer):
    """
    Polyak adaptive gradient descent, proposed in
        (B. T. Poyal, "Introduction to Optimization")
    which can be accessed, e.g., here:
        https://www.researchgate.net/publication/342978480_Introduction_to_Optimization

    Arguments:
        f_opt (float): precise value of the objective's minimum. If an underestimate is given,
            the algorirthm can be unstable; if an overestimate is given, will not converge below
            the overestimate.
        lr_min (float, optional): the smallest step-size, useful when
            an overestimate of the optimal value is given (default: 0)
        lr_max (float, optional): the laregest allowed step-size, useful when
            an underestimate of the optimal value is given (defaul: np.inf)
    """
    def __init__(self, f_opt, lr_min=0, lr_max=np.inf, *args, **kwargs):
        super(Polyak, self).__init__(*args, **kwargs)
        self.f_opt = f_opt
        self.lr_min = lr_min
        self.lr_max = lr_max

    def step(self):
        self.grad = self.loss.gradient(self.x)
        self.estimate_new_stepsize()
        self.x -= self.lr * self.grad

    def estimate_new_stepsize(self):
        loss_gap = self.loss.value(self.x) - self.f_opt
        self.lr = loss_gap / self.loss.norm(self.grad)**2
        self.lr = min(self.lr, self.lr_max)
        self.lr = max(self.lr, self.lr_min)

    def init_run(self, *args, **kwargs):
        super(Polyak, self).init_run(*args, **kwargs)
        self.trace.lrs = []

    def update_trace(self):
        super(Polyak, self).update_trace()
        self.trace.lrs.append(self.lr)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Tue Aug 25 06:45:58 2020

A script to illustrate some basic plotting and matrix operations in Python

@author: zettergm
"""

# Load external modules
import numpy as np
import numpy.linalg as nla
import scipy.linalg as sla
import matplotlib.pyplot as plt

from funexamples import fun1D,fun2D

# Define some variable so we can plot things
x=np.linspace(-10,10,100)
y=np.linspace(-10,10,100)
[X,Y]=np.meshgrid(x,y)

# Example function evaluations to plot
y1=fun1D(x,1,1,3)
y2=fun1D(x,1.5,-1,2)
Z1=fun2D(X,Y,2,0,0,3,10)
Z2=fun2D(X,Y,5,2,-2,2,2)

# Basic 1D Python plotting with matplotlib
plt.figure(1)
plt.plot(x,y1,x,y2)
plt.xlabel("x")
plt.ylabel("y")
plt.title("Plot of two Gaussian functions")
plt.legend(["curve 1","curve 2"])
plt.show()

# Basic 2D Python plotting with matplotlib
plt.figure(2)
im1=plt.pcolor(X,Y,Z1)
plt.colorbar(im1)
plt.xlabel("x")
plt.ylabel("y")
plt.title("2D Gaussian 1")
plt.show()

fig3=plt.figure(3)
im2=plt.pcolor(X,Y,Z2)
plt.colorbar(im2)
plt.xlabel("x")
plt.ylabel("y")
plt.title("2D Gaussian 2")
plt.show()

# this matrix and vector represent the system of equations:
# 1)   x +  2y +  4z = 18
# 2)  2x + 12y -  2z =  9
# 3)  5x + 26y +  5z = 14
A=np.array([[1, 2, 4],[2, 12, -2],[5,26,5]])      #matrix of coefficients
b=np.array([[18], [9], [14]])                     #this is the column vector representing the RHS of the system
B=np.transpose(A)                                 #transpose of A, for illustration of differences from MATLAB

# Some basic commands to printlay variables
print("A = ")
print(A)

print("b = ")
print(b)

# Compute some matrix properties and print them
print("det(A) = ")
print(nla.det(A))     # numpy.linalg.det(A) longhand

print("trace(A) = ")
print(np.trace(A))

print("condition # of A = ")
print(nla.cond(A))

print("transpose(A) = ")
print(np.transpose(A))

print("Aii (diagonal elements of A) = ")
print(np.diag(A))

# Matrix functions
print("A*B (matrix multiplication) = ")
print(A@B)

print("Aij*Bij (scalar multiplication) = ")
print(A*B)

print("5*A (multiplication) = ")
print(5*A)

print("Solution (x) of the system A*x=b:  ")
print("x = ")
xvec=nla.solve(A,b)
print(xvec)

print("I (3x3 identity matrix) = ")
print(np.eye(3,3))

print("A^{-1} (inverse of A) = ")
Ainv=nla.solve(A,np.eye(3,3))
print(Ainv)

# slicing and concatenation of array/matrices
print("2nd row of A = ")
print(A[1,:])     #in Python arrays start at zero...

print("2nd column of A = ")
print(A[:,1])

print("flat list (column vector) of matrix elements of A:  ")
print(A.flatten())

print("Horizontal concatenation of A with itself:  ")
print(np.concatenate((A,A),axis=1))

print("Vertical concatenation of A with itself:  ")
print(np.concatenate((A,A),axis=0))

print("diagonal matrix:  ")
a1=np.arange(10,0,-1)                        # count backward, note endpoint excluded
print(np.diag(a1))

print("LU decomposition of A (A=L*U):  ")
[P,L,U]=sla.lu(A)
print("L = ")
print(L)
print("U = ")
print(U)

print("Eigenvalues of A = ")
[lam,psi]=sla.eig(A)      #note output args swapped from matlab
print(lam)
print("Eigenvectors of A = ")
print(psi[:,0])
print(psi[:,1])
print(psi[:,2])


import numpy as np

from ..in_place import (
    elastic_net_,
    general_plastic_net_,
    hard_plastic_net_,
    lasso_,
    ordinary_least_squares_,
    plastic_lasso_,
    plastic_ridge_,
    ridge_,
    soft_plastic_net_,
    unified_plastic_net_,
)

def ordinary_least_squares(X, y, tol=1e-8, max_iter=1000):
    r"""
    Ordinary least squares regression.  This function finds the :math:`\vec{\beta}` that minimizes

    .. math::

        \tfrac{1}{2N}||\vec{y}-X\vec{\beta}||_2^2

    Args:
        X (numpy.ndarray): shape (N,D) data matrix.
        y (numpy.ndarray): shape (N,) target vector.
        tol (float): convergence criterion for coordinate descent. coordinate descent runs until the maximum element-wise change in **beta** is less than **tol**.
        max_iter (int): maximum number of update passes through all P elements of **beta**, in case **tol** is never met.

    Returns:
        (numpy.ndarray): shape (D,) coefficient vector.
    """

    N, D = X.shape
    beta = np.zeros(D, dtype=np.float64)
    r = y - np.dot(X, beta)

    ordinary_least_squares_(beta, r, X, tol=tol, max_iter=max_iter)

    return beta

def ridge(X, y, lambda_total=1.0, tol=1e-8, max_iter=1000):
    r"""
    Ridge regression.  This function finds the :math:`\vec{\beta}` that minimizes

    .. math::

        \tfrac{1}{2N} ||\vec{y}-X\vec{\beta}||_2^2 + \lambda \tfrac{1}{2} ||\vec{\beta}||_2^2

    Args:
        X (numpy.ndarray): shape (N,D) data matrix.
        y (numpy.ndarray): shape (N,) target vector.
        lambda_total (float): must be non-negative. total regularization penalty strength.
        tol (float): convergence criterion for coordinate descent. coordinate descent runs until the maximum element-wise change in **beta** is less than **tol**.
        max_iter (int): maximum number of update passes through all P elements of **beta**, in case **tol** is never met.

    Returns:
        (numpy.ndarray): shape (D,) coefficient vector.
    """

    N, D = X.shape
    beta = np.zeros(D, dtype=np.float64)
    r = y - np.dot(X, beta)

    ridge_(beta, r, X, lambda_total=lambda_total, tol=tol, max_iter=max_iter)

    return beta

def lasso(X, y, lambda_total=1.0, tol=1e-8, max_iter=1000):
    r"""
    Lasso regression.  This function finds the :math:`\vec{\beta}` that minimizes

    .. math::

        \tfrac{1}{2N} ||\vec{y}-X\vec{\beta}||_2^2 + \lambda ||\vec{\beta}||_1

    Args:
        X (numpy.ndarray): shape (N,D) data matrix.
        y (numpy.ndarray): shape (N,) target vector.
        lambda_total (float): must be non-negative. total regularization penalty strength.
        tol (float): convergence criterion for coordinate descent. coordinate descent runs until the maximum element-wise change in **beta** is less than **tol**.
        max_iter (int): maximum number of update passes through all P elements of **beta**, in case **tol** is never met.

    Returns:
        (numpy.ndarray): shape (D,) coefficient vector.
    """

    N, D = X.shape
    beta = np.zeros(D, dtype=np.float64)
    r = y - np.dot(X, beta)

    lasso_(beta, r, X, lambda_total=lambda_total, tol=tol, max_iter=max_iter)

    return beta

def elastic_net(X, y, lambda_total=1.0, alpha=0.75, tol=1e-8, max_iter=1000):
    r"""
    Elastic net regression.  This function finds the :math:`\vec{\beta}` that minimizes

    .. math::

        \tfrac{1}{2N} ||\vec{y}-X\vec{\beta}||_2^2 + \lambda \bigl( \alpha||\vec{\beta}||_1 + (1-\alpha) \tfrac{1}{2} ||\vec{\beta}||_2^2 \bigr)

    Args:
        X (numpy.ndarray): shape (N,D) data matrix.
        y (numpy.ndarray): shape (N,) target vector.
        lambda_total (float): must be non-negative. total regularization penalty strength.
        alpha (float): mixing parameter between L1 and L1 penalties. must be between zero and one. :math:`\alpha=0` is pure L2 penalty, :math:`\alpha=1` is pure L1 penalty.
        tol (float): convergence criterion for coordinate descent. coordinate descent runs until the maximum element-wise change in **beta** is less than **tol**.
        max_iter (int): maximum number of update passes through all P elements of **beta**, in case **tol** is never met.

    Returns:
        (numpy.ndarray): shape (D,) coefficient vector.
    """

    N, D = X.shape
    beta = np.zeros(D, dtype=np.float64)
    r = y - np.dot(X, beta)

    elastic_net_(
        beta, r, X, lambda_total=lambda_total, alpha=alpha, tol=tol, max_iter=max_iter
    )

    return beta

def general_plastic_net(
    X, y, xi, zeta, lambda_total=1.0, alpha=0.75, tol=1e-8, max_iter=1000
):
    r"""
    General plastic net regression.  This function finds the :math:`\vec{\beta}` that minimizes

    .. math::

        \tfrac{1}{2N} ||\vec{y}-X\vec{\beta}||_2^2 + \lambda \bigl( \alpha||\vec{\beta}-\vec{\xi}||_1 + (1-\alpha) \tfrac{1}{2} ||\vec{\beta}-\vec{\zeta}||_2^2 \bigr)

    Args:
        X (numpy.ndarray): shape (N,P) data matrix.
        y (numpy.ndarray): shape (N,) target vector.
        xi (numpy.ndarray): shape (P,) target for L1 penalty.
        zeta (numpy.ndarray): shape (P,) target for L2 penalty.
        lambda_total (float): must be non-negative. total regularization penalty strength.
        alpha (float): mixing parameter between L1 and L1 penalties. must be between zero and one. :math:`\alpha=0` is pure L2 penalty, :math:`\alpha=1` is pure L1 penalty.
        tol (float): convergence criterion for coordinate descent. coordinate descent runs until the maximum element-wise change in **beta** is less than **tol**.
        max_iter (int): maximum number of update passes through all P elements of **beta**, in case **tol** is never met.

    Returns:
        (numpy.ndarray): shape (D,) coefficient vector.
    """

    N, D = X.shape
    beta = np.zeros(D, dtype=np.float64)
    r = y - np.dot(X, beta)

    general_plastic_net_(
        beta,
        r,
        X,
        xi,
        zeta,
        lambda_total=lambda_total,
        alpha=alpha,
        tol=tol,
        max_iter=max_iter,
    )

    return beta

def plastic_ridge(X, y, zeta, lambda_total=1.0, tol=1e-8, max_iter=1000):
    r"""
    Plastic ridge regression.  This function finds the :math:`\vec{\beta}` that minimizes

    .. math::

        \tfrac{1}{2N} ||\vec{y}-X\vec{\beta}||_2^2 + \lambda \tfrac{1}{2} ||\vec{\beta}-\vec{\zeta}||_2^2

    Args:
        X (numpy.ndarray): shape (N,P) data matrix.
        y (numpy.ndarray): shape (N,) target vector.
        zeta (numpy.ndarray): shape (P,) target for L2 penalty.
        lambda_total (float): must be non-negative. total regularization penalty strength.
        tol (float): convergence criterion for coordinate descent. coordinate descent runs until the maximum element-wise change in **beta** is less than **tol**.
        max_iter (int): maximum number of update passes through all P elements of **beta**, in case **tol** is never met.

    Returns:
        (numpy.ndarray): shape (D,) coefficient vector.
    """

    N, D = X.shape
    beta = np.zeros(D, dtype=np.float64)
    r = y - np.dot(X, beta)

    plastic_ridge_(
        beta, r, X, zeta, lambda_total=lambda_total, tol=tol, max_iter=max_iter
    )

    return beta

def plastic_lasso(X, y, xi, lambda_total=1.0, tol=1e-8, max_iter=1000):
    r"""
    Plastic lasso regression.  This function finds the :math:`\vec{\beta}` that minimizes

    .. math::

        \tfrac{1}{2N} ||\vec{y}-X\vec{\beta}||_2^2 + \lambda ||\vec{\beta}-\vec{\xi}||_1

    Args:
        X (numpy.ndarray): shape (N,P) data matrix.
        y (numpy.ndarray): shape (N,) target vector.
        xi (numpy.ndarray): shape (P,) target for L1 penalty.
        lambda_total (float): must be non-negative. total regularization penalty strength.
        tol (float): convergence criterion for coordinate descent. coordinate descent runs until the maximum element-wise change in **beta** is less than **tol**.
        max_iter (int): maximum number of update passes through all P elements of **beta**, in case **tol** is never met.

    Returns:
        (numpy.ndarray): shape (D,) coefficient vector.
    """

    N, D = X.shape
    beta = np.zeros(D, dtype=np.float64)
    r = y - np.dot(X, beta)

    plastic_lasso_(
        beta, r, X, xi, lambda_total=lambda_total, tol=tol, max_iter=max_iter
    )

    return beta

def hard_plastic_net(X, y, xi, lambda_total=1.0, alpha=0.75, tol=1e-8, max_iter=1000):
    r"""
    Hard plastic net regression.  This function finds the :math:`\vec{\beta}` that minimizes

    .. math::

        \tfrac{1}{2N} ||\vec{y}-X\vec{\beta}||_2^2 + \lambda \bigl( \alpha||\vec{\beta}-\vec{\xi}||_1 + (1-\alpha) \tfrac{1}{2} ||\vec{\beta}||_2^2 \bigr)

    Args:
        X (numpy.ndarray): shape (N,P) data matrix.
        y (numpy.ndarray): shape (N,) target vector.
        xi (numpy.ndarray): shape (P,) target for L1 penalty.
        lambda_total (float): must be non-negative. total regularization penalty strength.
        alpha (float): mixing parameter between L1 and L1 penalties. must be between zero and one. :math:`\alpha=0` is pure L2 penalty, :math:`\alpha=1` is pure L1 penalty.
        tol (float): convergence criterion for coordinate descent. coordinate descent runs until the maximum element-wise change in **beta** is less than **tol**.
        max_iter (int): maximum number of update passes through all P elements of **beta**, in case **tol** is never met.

    Returns:
        (numpy.ndarray): shape (D,) coefficient vector.
    """

    N, D = X.shape
    beta = np.zeros(D, dtype=np.float64)
    r = y - np.dot(X, beta)

    hard_plastic_net_(
        beta,
        r,
        X,
        xi,
        lambda_total=lambda_total,
        alpha=alpha,
        tol=tol,
        max_iter=max_iter,
    )

    return beta

def soft_plastic_net(X, y, zeta, lambda_total=1.0, alpha=0.75, tol=1e-8, max_iter=1000):
    r"""
    Soft plastic net regression.  This function finds the :math:`\vec{\beta}` that minimizes

    .. math::

        \tfrac{1}{2N} ||\vec{y}-X\vec{\beta}||_2^2 + \lambda \bigl( \alpha||\vec{\beta}||_1 + (1-\alpha) \tfrac{1}{2} ||\vec{\beta}-\vec{\zeta}||_2^2 \bigr)

    Args:
        X (numpy.ndarray): shape (N,P) data matrix.
        y (numpy.ndarray): shape (N,) target vector.
        zeta (numpy.ndarray): shape (P,) target for L2 penalty.
        lambda_total (float): must be non-negative. total regularization penalty strength.
        alpha (float): mixing parameter between L1 and L1 penalties. must be between zero and one. :math:`\alpha=0` is pure L2 penalty, :math:`\alpha=1` is pure L1 penalty.
        tol (float): convergence criterion for coordinate descent. coordinate descent runs until the maximum element-wise change in **beta** is less than **tol**.
        max_iter (int): maximum number of update passes through all P elements of **beta**, in case **tol** is never met.
    Returns:
        (numpy.ndarray): shape (D,) coefficient vector.
    """

    N, D = X.shape
    beta = np.zeros(D, dtype=np.float64)
    r = y - np.dot(X, beta)

    soft_plastic_net_(
        beta,
        r,
        X,
        zeta,
        lambda_total=lambda_total,
        alpha=alpha,
        tol=tol,
        max_iter=max_iter,
    )

    return beta

def unified_plastic_net(
    X, y, xi, lambda_total=1.0, alpha=0.75, tol=1e-8, max_iter=1000
):
    r"""
    Unified plastic net regression.  This function finds the :math:`\vec{\beta}` that minimizes

    .. math::

        \tfrac{1}{2N} ||\vec{y}-X\vec{\beta}||_2^2 + \lambda \bigl( \alpha||\vec{\beta}-\vec{\xi}||_1 + (1-\alpha) \tfrac{1}{2} ||\vec{\beta}-\vec{\xi}||_2^2 \bigr)

    Args:
        X (numpy.ndarray): shape (N,P) data matrix.
        y (numpy.ndarray): shape (N,) target vector.
        xi (numpy.ndarray): shape (P,) target for both L1 and L2 penalties.
        lambda_total (float): must be non-negative. total regularization penalty strength.
        alpha (float): mixing parameter between L1 and L1 penalties. must be between zero and one. :math:`\alpha=0` is pure L2 penalty, :math:`\alpha=1` is pure L1 penalty.
        tol (float): convergence criterion for coordinate descent. coordinate descent runs until the maximum element-wise change in **beta** is less than **tol**.
        max_iter (int): maximum number of update passes through all P elements of **beta**, in case **tol** is never met.
    Returns:
        (numpy.ndarray): shape (D,) coefficient vector.
    """

    N, D = X.shape
    beta = np.zeros(D, dtype=np.float64)
    r = y - np.dot(X, beta)

    unified_plastic_net_(
        beta,
        r,
        X,
        xi,
        lambda_total=lambda_total,
        alpha=alpha,
        tol=tol,
        max_iter=max_iter,
    )

    return beta


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

import numpy as np
import matplotlib.pyplot as plt
np.set_printoptions(threshold=np.nan)
np.set_printoptions(precision=5)

def f(u,t):
    return u**2 - np.sin(t) - np.cos(t)**2

#<startEuler>
def fw_euler(f,u0,N,T):
    k = T/N
    u = np.zeros(N+1)
    u[0] = u0
    for n in range(N):
        u[n+1] = u[n] + k*f(u[n],k*n)
    return u
#<endEuler>

#<startRK>
def r_k(f,u0,N,T):
    k = T/N
    u = np.zeros(N+1)
    u[0] = u0
    for n in range(N):
        tn = k*n
        Y1 = u[n]
        Y2 = u[n] + k/2*f(Y1,tn)
        Y3 = u[n] + k/2*f(Y2,tn+k/2)
        Y4 = u[n] + k*f(Y3,tn+k/2)
        u[n+1] = u[n] + k/6*( f(Y1,tn) + 2*f(Y2,tn+k/2) + 2*f(Y3,tn+k/2) + f(Y4,tn+k))
    return u
#<endRK>

def make_plots():
    error_e = []
    error_rk = []
    Ns = np.array([25,50,100,200,400,800,1600])
    for N in Ns:
        T = 8
        x = np.linspace(0,T,N+1)
        u_true = np.cos(x)
        u_e = fw_euler(f,1,N,T)
        u_rk = r_k(f,1,N,T)

        error_e.append(np.linalg.norm(u_true-u_e,np.inf))
        error_rk.append(np.linalg.norm(u_true-u_rk,np.inf))

        if N==25:
            plt.figure()
            plt.plot(x,u_true,color='.5')
            plt.plot(x,u_e,color='.1',marker='o',ms=4,linestyle='None')
            plt.plot(x,u_rk,color='.1',marker='s',ms=4,linestyle='None')
            plt.savefig('img/5/N25.pdf')

    ks = 8/Ns
    coeff_e = np.polyfit(np.log10(ks), np.log10(error_e), 1)
    fit_e = np.poly1d(coeff_e)

    coeff_rk = np.polyfit(np.log10(ks), np.log10(error_rk), 1)
    fit_rk = np.poly1d(coeff_rk)

    np.savetxt('img/5/coeff_e.txt',[coeff_e[0]],fmt='%.2f',delimiter='',newline='')
    np.savetxt('img/5/coeff_rk.txt',[coeff_rk[0]],fmt='%.2f',delimiter='',newline='')

    print(coeff_e,coeff_rk)

    fig = plt.figure()
    ax = plt.gca()
    plt.plot(ks,10**(fit_e(np.log10(ks))),'.5')
    plt.plot(ks,10**(fit_rk(np.log10(ks))),'.5')
    ax.plot(ks,error_e,color='k',marker='o',ms=4,linestyle='None')
    ax.plot(ks,error_rk,color='k',marker='s',ms=4,linestyle='None')
    ax.set_yscale('log')
    ax.set_xscale('log')
    plt.savefig('img/5/error.pdf')

make_plots()


'''
Created on Jul 21, 2017

@author: arnon
'''

import numpy as np

def meanabsdev(vector, Double=None, Median=None, NaN=None):
    ''' MeanAbsDev computes the mean absolute deviation (average deviation)
    of an N-element vector.

    Args:
        vector: An N-element vector of type integer, float or double.
        Double: If set to a non-zero value, MEANABSDEV performs its
                computations in double precision arithmetic and returns
                a double precision result. If not set to a non-zero value,
                the computations and result depend upon the type of the
                input data (integer and float data return float results,
                while double data returns double results). This has no
                effect if the Median keyword is set.
        Median: If set to a non-zero value, meanabsdev will return
                the average deviation from the median, rather than
                the mean.  If Median is not set, meanabsdev will return
                the average deviation from the mean.
        NaN: If set, treat NaN data as missing.

    Returns:
        mean absolute deviation
    '''
    if Median is not None:
        if NaN is not None:
            whereNotNaN = np.where(np.isfinite(vector))
            whereNotNaN = whereNotNaN[0]
            nanCount = len(whereNotNaN)

            if nanCount > 0:
                middle = np.median(vector[whereNotNaN])
            else:
                middle = 0
                raise Exception("Trying to calculate Median but all items are NaN")
        else:
            middle = np.median(vector)
    else:
        middle = np.mean(vector,)

    return np.mean(np.abs(vector - middle))


import numpy as np
import csv

def mapFeature(X1, X2):
# MAPFEATURE Feature mapping function to polynomial features
#
#   MAPFEATURE(X1, X2) maps the two input features
#   to quadratic features used in the regularization exercise.
#
#   Returns a new feature array with more features, comprising of
#   X1, X2, X1.^2, X2.^2, X1*X2, X1*X2.^2, etc..
#
#   Inputs X1, X2 must be the same size
    degree = 6
    out = np.ones([len(X1), (degree+1)*(degree+2)/2])
    idx = 1
    for i in range(1, degree+1):
        for j in range(0, i+1):
            a1 = X1 ** (i-j)
            a2 = X2 ** j
            out[:, idx] = a1*a2
            idx += 1
    return out

if __name__ == "__main__":
    with open('ex2data2.txt', 'rb') as csvfile:
        data = np.loadtxt(csvfile, delimiter=",")
        new_data = mapFeature(data[:,0], data[:,1])
        print new_data


import numerical_differentiaition as nd
import numpy as np

def gradientDecent(x, epsilon, n) :
    '''
    경사감소법
    :param x: init weights
    :param epsilon: learning rate
    :param n: number of method running
    :return: optimal weights
    '''
    xVal=np.array(x)
    for i in range(n) :
        grad=nd.numericalGradient(sample_function, xVal)
        xVal-=epsilon*grad
    return xVal

def sample_function(x) :
    return x[0]**2 + x[1]**2

w=np.array([-3.0, 4.0])

print('n = 100, init weights = [-3, 4]')
print('learning rate : 0.1\n', gradientDecent(w, 0.1, 100))
print('learning rate : 2\n', gradientDecent(w, 2, 100))
print('learning rate : 0.001\n', gradientDecent(w, 0.001, 100))


# -*- coding: utf-8 -*-
"""
Created on Thu Nov 22 23:18:15 2018

@author: Tian
"""

import numpy as np

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

def predict(X, w):
    return sigmoid(np.dot(X,w))

__classify=np.vectorize(lambda pred: 1 if pred>=0.5 else 0)

def classify(pred):
    return __classify(pred)

def loss(X, y, w):
    pred=predict(X,w)
    class1_cost = y*np.log(pred)
    class2_cost = (1-y)*np.log(1-pred)
    cost = -class1_cost.sum() - class2_cost.sum()
    return cost/len(y)

def gradient(X, y, w):
    n = X.shape[0] if X.ndim != 1 else X.size
    predictions = predict(X, w)
    gradient = np.dot(X.T, predictions - y)
    return gradient / n

def train(X, y, w, lrate, iters=1):
    if y.ndim == 1:
        y.resize([len(y),1])
    for _ in range(iters):
        grad=gradient(X, y, w)
        w -= grad*lrate
    return w, grad

def trainBatch(X, y, w, lrate, pf, pl):
    return train(X[pf:pl,:], y[pf:pl], w, lrate, 1)

def accuracy(predicted_labels, actual_labels):
    diff = predicted_labels - actual_labels
    return 1.0 - (float(np.count_nonzero(diff)) / len(diff))

def dis_gradient(Xs,ys,w,lid,idx,cnt):
    return gradient(Xs[lid][idx:idx+cnt,:], ys[lid][idx:idx+cnt], w)


import math as mt
def f(x):
    return x**(2)*mt.cos(x**(2))
xi=[]
x0=1.5
x1=2.5
h=0.1
n=(x1-x0)/h
fa=f(x0)
fn=f(x1)

while x0<x1+h:
    xi.append(x0)
    x0=x0+h

print("x=",xi)
length=len(xi)

fxi=[]
for i in range(0,length):
    fx=f(xi[i])
    fxi.append(fx)
print("Nilai hasil fungsi =",fxi)

sigma=0
for i in range(1,length-1):
    sigma=sigma+fxi[i]
print("sigma =",sigma)
## METODE TRAPEZOID
hasil=(fa+2*sigma+fn)*h/2
print("Hasil Trapezoid =", hasil)

sigmaGanjil=0
sigmaGenap=0
#METODE SIMPSON
for i in range(1,length-1):
    if i%2!=0 :
        sigmaGanjil=sigmaGanjil+fxi[i]
    else:
        sigmaGenap=sigmaGenap+fxi[i]
print("hasil sigma ganjil = ",sigmaGanjil,", hasil sigma genap =",sigmaGenap)
simpson=(fa+4*sigmaGanjil+2*sigmaGenap+fn)*h/3
print("Nilai Hasil Simpson =",simpson)

from sympy import *
x=Symbol('x')
fd=x**(2)*cos(x**(2))
integral=fd.integrate(x)
print("Hasil Integral=",integral)

from scipy import integrate
hasilIntegral=integrate.quad(f,1.5,2.5)
print("Hasil Integral Eksak",hasilIntegral)

er_simson=abs(simpson-hasilIntegral[0])
er_trape=abs(hasil-hasilIntegral[0])
print("Hasil Eror Sympson = ", er_simson)
print("Hasil Eror Trapezoid = ", er_trape)


import numpy as np
import pandas as pd
from typing import Tuple

def two_p_diff_ci(
    series_a_r_count: int, series_b_r_count: int, total_count: int
) -> Tuple[float, ...]:
    """Takes series_a row count, series_b row count and total count of rows
    in a data set and returns difference in two proportions together with
    lower and upper bounds of 95 % confidence interval."""

    first_p = series_a_r_count / total_count
    second_p = series_b_r_count / total_count
    first_p_se = np.sqrt(first_p * (1 - first_p) / total_count)
    second_p_se = np.sqrt(second_p * (1 - second_p) / total_count)
    se_diff = np.sqrt(first_p_se ** 2 + second_p_se ** 2)
    diff_in_p = first_p - second_p
    lcb = diff_in_p - 1.96 * se_diff
    ucb = diff_in_p + 1.96 * se_diff

    return diff_in_p, lcb, ucb

def diff_in_two_means_unpooled_ci(
    series_a: pd.Series, series_b: pd.Series
) -> Tuple[float, ...]:
    """Takes series_a and series_b as an input and returns two series
    mean difference, lower and upper bounds of 95 % confidence interval"""

    s1_sem = series_a.std() / np.sqrt(series_a.count())
    s2_sem = series_b.std() / np.sqrt(series_b.count())
    sem_diff = np.sqrt(s1_sem ** 2 + s2_sem ** 2)
    mean_diff = series_a.mean() - series_b.mean()
    lcb = mean_diff - 2 * sem_diff
    ucb = mean_diff + 2 * sem_diff

    return mean_diff, lcb, ucb

def sampling_mean_diff_ci(
    series_a: pd.Series,
    series_b: pd.Series,
    number_of_samples: int,
    alpha: float = 0.95,
) -> Tuple[np.ndarray, float]:
    """Takes two series, number of samples and alpha parameter and returns
    actual mean difference, array od sampled mean differences, lower and upper bounds
    of confidence interval controled by alpha parameter"""

    actual_mean_diff = series_a.mean() - series_b.mean()
    mean_diff = np.zeros((number_of_samples,))
    for i in range(number_of_samples):
        mean1 = np.random.choice(series_a, series_a.size).mean()
        mean2 = np.random.choice(series_b, series_b.size).mean()
        mean_diff[i] = mean1 - mean2
    quant = (1 - alpha) / 2
    lcb = np.quantile(mean_diff, quant)
    ucb = np.quantile(mean_diff, 1 - quant)

    return actual_mean_diff, mean_diff, lcb, ucb

def sampling_mean(
    series_a: pd.Series, series_b: pd.Series, sample_size: int, number_of_samples: int
) -> Tuple[np.ndarray]:
    """Samples number_of_samples from two series with with given sample_size
    and returns numpy arrays with means"""

    s1 = np.empty(sample_size)
    s2 = np.empty(sample_size)
    for i in range(number_of_samples):
        s1[i] = series_a.sample(sample_size, replace=True).mean()
        s2[i] = series_b.sample(sample_size, replace=True).mean()

    return s1, s2

def perm_test(series_a: pd.Series, series_b: pd.Series) -> float:
    """Makes a permutation test from provided input of two series
    and returns mean difference of permutation result"""

    s1 = series_a.reset_index(drop=True)
    s2 = series_b.reset_index(drop=True)
    data = pd.concat([s1, s2], ignore_index=True)
    total_n = s1.shape[0] + s2.shape[0]
    index2 = set(np.random.choice(range(total_n), s2.shape[0], replace=False))
    index1 = set(range(total_n)) - index2

    return data.iloc[list(index1)].mean() - data.iloc[list(index2)].mean()


#import packages
import numpy as np
import matplotlib.pyplot as plt
import random

def dataprocessing():
    #This function Takes in data and normalizes it for easier distance calculation
    #parameters: None
    #return: fdata - the normalized data
    #It returns the normalized data
    f = open("data.txt", "r+", encoding ="utf-8")
    raw = f.read().split()
    f.close()
    data = np.array(raw)
    a1 =[]
    a2 =[]
    na1 = []
    na2 = []
    dataset = []
    #transform the data to float value
    for i in range (len(data)):
        k = data[i].split(",")
        a1.append(float(k[0]))
        a2.append(float(k[1]))
    #normalize the data
    for i in range (len(data)):
        na1.append(round((a1[i]-min(a1))/(max(a1) - min(a1)),3))
        na2.append(round((a2[i]-min(a2))/(max(a2) - min(a2)),3))
    #turn data into an array dataset
    for i in range (len(data)):
        d =[]
        d.append(na1[i])
        d.append(na2[i])
        dataset.append(d)
    fdata = np.array(dataset)
    #return the normalized data
    return fdata

def kmeans(data,k):
    #This function is a implementation of The K-means algorithm
    #Prameters: data - the data set and k - number of clusters
    #Return:
    # - centers: array of final centers
    # - count: number of iterations
    # - cluster:labels (the assignment of points to centers)
    # - errorvalL: final error value
    #I implement this code based on the textbook code
    errorval = 0
    # Find the minimum and maximum values for each feature
    minima = data.min(axis=0)
    maxima = data.max(axis=0)
    # Pick the centre locations randomly
    centers = np.random.rand(k,2)*(maxima-minima)+minima
    oldCenters = np.random.rand(k,2)*(maxima-minima)+minima
    nData = len(data)
    count = 0
    #loop until the centers stop moving
    while np.sum(np.sum(oldCenters-centers))!= 0:

        oldCenters = centers.copy()
        count += 1

        # Compute distances
        distances = np.ones((1,nData))*np.sum((data-centers[0,:])**2,axis=1)
        for j in range(k-1):
            distances = np.append(distances,np.ones((1,nData))*np.sum((data-centers[j+1,:])**2,axis=1),axis=0)
        # Identify the closest cluster
        cls = distances.argmin(axis=0)
        cluster = np.transpose(cls*np.ones((1,nData)))

        # Update the cluster centres
        for j in range(k):
            thisCluster = np.where(cluster==j,1,0)
            if sum(thisCluster)>0:
                centers[j,:] = np.sum(data*thisCluster,axis=0)/np.sum(thisCluster)
    #calculate the error value based on the center and it corresponding clusters
    for i in range (k):
        cluster1 = np.where(cluster==i,1,0)
        errorval += np.sum((((data*cluster1)[~np.all((data*cluster1)==0,axis=1)])-centers[i,:])**2)
    return centers, count, cluster, errorval

def main():
    #The main function
    errorlist=[]
    #k value
    k = 4
    #get the data
    data = dataprocessing()
    #get the returns value from the kmeans
    centers, iterations, indices, errorval = kmeans(data,k)
    #output
    print('My optimal value for k is this situation is 4')
    print('{} centers are: '.format(k), centers)
    print('The number of iterations is:', iterations)
    print('The final error value for k={} is'.format(k), errorval)
    print('The list labels (The center index of each point in the dataset, ranging from 0-3) is the following: \n', indices.tolist())
    #get the data for k from 1 to 10
    for i in range (1,11):
        c, it, ind, error = kmeans(data,i)
        kk = [i,error]
        errorlist.append(kk)
    errorlist[3][1] = errorval
    #put the data to an array for plotting
    errorlist = np.array(errorlist)
    return centers, data, errorlist

#output, print out the desired e centers, the number of iterations
#to convergence, the final error (sum of squared distances from points to centers), and
#labels (the assignment of points to centers).
centers, data, err = main()

plt.scatter(np.array(centers)[:, 0], np.array(centers)[:, 1], color='red')
plt.scatter(data[:, 0], data[:, 1], alpha=0.1, color ='black')
plt.title('A graph of the original data and the centers ')
plt.show()

#error value of k clusters with k from 1 to 10
print(err)

#graph of error values of k clusters with k from 1 to 10
plt.plot(err[:, 0], err[:, 1], color='red')
plt.title('A graph of error vs k')
plt.show()


#import libraries required
import numpy as np
import random

import matplotlib.pyplot as plt
import seaborn as sns

from scipy.stats import norm
from scipy.stats import poisson
from scipy.stats import binom

def sample_distribution_func(samples, distribution, mean=0, sd=1, mu=3, n=10, p=0.5):
    """This function will draw random numbers from a given distribution (Normal, Poisson, Binomial).

        It takes one argument for the number of samples and a second argument which specifies the
        distribution (Normal, Poisson or Binomial).

        The function can also handle additional parameters depending on the distribution chosen"""

    f_sampled_distribution = np.zeros(shape=[0, 1])

    if distribution == "Normal":
        f_sampled_distribution = norm.rvs(size=samples, loc=mean, scale=sd)
        print (samples, "samples of a", distribution, "distribution with parameter values of mean =", mean, "and sd =", sd)
        return f_sampled_distribution
    elif distribution == "Poisson":
        f_sampled_distribution = poisson.rvs(size=samples, mu=mu)
        print (samples, "samples of a", distribution, "distribution with a parameter value of mu =", mu)
        return f_sampled_distribution
    elif distribution == "Binomial":
        f_sampled_distribution = binom.rvs(size=samples, n=n, p=p)
        print (samples, "samples of a", distribution, "distribution with parameter values of n =", n, "and p =", p)
        return f_sampled_distribution
    else:
        print('ERROR!:', distribution, "distribution is not defined within function")
        return f_sampled_distribution


from math import *
import sympy as sp
from scipy.misc import derivative
from prettytable import PrettyTable

def funcion(expr, x_eval, y_eval):
    x = x_eval
    y = y_eval
    funcion = eval(expr)
    return funcion

def derivar(ecuacion):

    x = sp.Symbol('x')
    y = sp.Symbol('y')
    derv_x = str(sp.Derivative(ecuacion, x, evaluate=True))
    derv_y = str(sp.Derivative(ecuacion, y, evaluate=True))

    return derv_x, derv_y

def mTaylor(ecuacion, x0, x1, y0, n):
    n = int(n)
    h = (x1-x0)/n
    x = x0
    y = y0
    dfdx, dfdy = derivar(ecuacion)
    dfxy_literal = dfdx+'+'+dfdy+'*('+ecuacion+')'
    # constructor tabla
    enc_y = f'y{x1}'
    tabla = PrettyTable(["Iteracion", "xi", "yi", enc_y])
    tabla.title = "METODO DE TAYLOR DE SEGUNDO ORDEN "
    for i in range(0, n):
        fxy = funcion(ecuacion, x, y)
        dfxy = funcion(dfxy_literal, x, y)
        y_cal = y+h*fxy+((h**2)/2)*dfxy
        if i < 10:
            tabla.add_row(
                [i+1, "{0:.4f}".format(x), "{0:.6f}".format(y), "{0:.6f}".format(y_cal)])
        x = x+h
        y = y_cal
        # print(y_cal)
    # print(fxy,dfxy)
    print("\n")
    print(tabla)
    print(f'             y({x1}) = {"{0:.8f}".format(y_cal)}')

# datos de ejemplo
""" x0 = 0
x1 = 1
y = 2
ecuacion = 'x-y'
n = 100
mTaylor(ecuacion, x0, x1, y, n) """

""" x0 = 0
x1 = 1
y = 1
ecuacion = 'cos(x*y)'
n = 5
mTaylor(ecuacion, x0, x1, y, n) """

""" x0 = 0
x1 = 1
y = 1
ecuacion = 'x+y'
n = 10
mTaylor(ecuacion, x0, x1, y, n) """

src/6. Ecuaciones diferenciales ordinarias/Metodos/Taylor.py


from sys import argv
from math import sqrt, trunc
from numpy import zeros, sum, ndarray
import time

from numba import jit, float32, boolean, int32, float64, int64

compile_start = time.time()

@jit([boolean(int32)])
def check_prime(num):
  has_divisor = False

  if(num ==2):
    return True
  #Check 2 specially
  if num%2 == 0:
  # Don't need to check any further - return right now
    return False
  for i in range(3, trunc(sqrt(num))+1, 2):
    if num%i == 0:
      has_divisor = True
      # Don't need to check any further - at least one divisor exists
      break
  #Either we found a divisor, or there are none
  return not has_divisor

compile_end = time.time()
print("Compilation of check_prime took {} seconds".format(compile_end-compile_start))

def check_prime_nj(num):
  has_divisor = False

  if(num ==2):
    return True
  #Check 2 specially
  if num%2 == 0:
  # Don't need to check any further - return right now
    return False
  for i in range(3, trunc(sqrt(num))+1, 2):
    if num%i == 0:
      has_divisor = True
      # Don't need to check any further - at least one divisor exists
      break
  #Either we found a divisor, or there are none
  return not has_divisor

compile_start = time.time()

@jit((int64, int64, float64[:]))
def main_loop(lower, length, flags):
  for i in range(1, length):
    flags[i] = check_prime(lower+i)

compile_end = time.time()
print("Compilation of main_loop took {} seconds".format(compile_end-compile_start))

def main_loop_nj(lower, length, flags):
  for i in range(1, length):
    flags[i] = check_prime_nj(lower+i)

def main(lower, upper, do_jit=True):

  length = upper - lower
  flags = zeros(length)
  start_time = time.time()
  if(do_jit):
    main_loop(lower, length, flags)
  else:
    main_loop_nj(lower, length, flags)
  end_time = time.time()
  print("Found ", int(sum(flags)), " primes in", end_time-start_time, ' s')

if __name__ == "__main__":

  try:
    lower = int(input('Enter lower bnd: '))
  except:
    print("I didn't understand. I'll try 100000")
    lower = 100000
  try:
    upper = int(input('Enter upper bnd: '))
  except:
    print("I didn't understand. I'll try 200000")
    upper = 200000

  main(lower, upper)


from math import hypot
import numpy as np
import json
import sys

def givens_rotation(A):
    """Perform QR decomposition of matrix A using Givens rotation."""
    (num_rows, num_cols) = np.shape(A)

    # Initialize orthogonal matrix Q and upper triangular matrix R.
    Q = np.identity(num_rows)
    R = np.copy(A)

    # Iterate over lower triangular matrix.
    (rows, cols) = np.tril_indices(num_rows, -1, num_cols)
    for (row, col) in zip(rows, cols):

        # Compute Givens rotation matrix and
        # zero-out lower triangular matrix entries.
        if R[row, col] != 0:
            (c, s) = _givens_rotation_matrix_entries(R[col, col], R[row, col])

            G = np.identity(num_rows)
            G[[col, row], [col, row]] = c
            G[row, col] = s
            G[col, row] = -s

            R = np.dot(G, R)
            Q = np.dot(Q, G.T)

    return (Q, R)

def _givens_rotation_matrix_entries(a, b):
    """Compute matrix entries for Givens rotation."""
    r = hypot(a, b)
    c = a/r
    s = -b/r

    return (c, s)

n = int(sys.argv[1])
A = np.random.rand(n, n);
Q,R = givens_rotation(A)
result = {
    "A": A.tolist(),
    "R": R.tolist(),
    "Q": Q.tolist()
}
# print(result)
print(json.dumps(result))
sys.stdout.flush()


import numpy as np
import torch
import torch.nn as nn

"""
The modules/function here implement custom versions of layer normalization in PyTorch.
In contrast to more advanced implementations no use of a running mean/variance is made.
You should fill in code into indicated sections.
"""

######################################################################################
# Code for Question 3.1
######################################################################################

class CustomLayerNormAutograd(nn.Module):
    """
    This nn.module implements a custom version of the layer norm operation for MLPs.
    The operations called in self.forward track the history if the input tensors have the
    flag requires_grad set to True. The backward pass does not need to be implemented, it
    is dealt with by the automatic differentiation provided by PyTorch.
    """

    def __init__(self, n_neurons, eps=1e-5):
        """
        Initializes CustomLayerNormAutograd object.

        Args:
          n_neurons: int specifying the number of neurons
          eps: small float to be added to the variance for stability

        TODO:
          Save parameters for the number of neurons and eps.
          Initialize parameters gamma and beta via nn.Parameter
        """
        super(CustomLayerNormAutograd, self).__init__()

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

        self.n_neurons = n_neurons
        self.eps = eps

        self.gamma = nn.Parameter(torch.ones(n_neurons))
        self.beta = nn.Parameter(torch.zeros(n_neurons))

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

    def forward(self, input):
        """
        Compute the layer normalization

        Args:
          input: input tensor of shape (n_batch, n_neurons)
        Returns:
          out: layer-normalized tensor

        TODO:
          Check for the correctness of the shape of the input tensor.
          Implement layer normalization forward pass as given in the assignment.
          For the case that you make use of torch.var be aware that the flag unbiased=False should be set.
        """

        ########################
        # PUT YOUR CODE HERE  #
        #######################
        batch_size, num_neurons = input.shape

        assert self.n_neurons == num_neurons, "{} channels are needed but there are only {}".format(self.n_neurons, num_neurons)

        # compute mean
        mu = torch.mean(input, dim=1, keepdim=True)

        # compute var
        var = torch.var(input, dim=1, unbiased=False, keepdim=True)
        var = (var+self.eps)**(-1./2.)

        # normalize
        X_norm = input - mu
        X_norm = X_norm * var

        # scale and shift
        gamma_matrix = self.gamma.expand(batch_size, -1)
        beta_matrix = self.beta.expand(batch_size, -1)

        out = gamma_matrix * X_norm + beta_matrix

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

        return out

######################################################################################
# Code for Question 3.2 b)
######################################################################################

class CustomLayerNormManualFunction(torch.autograd.Function):
    """
    This torch.autograd.Function implements a functional custom version of the layer norm operation for MLPs.
    Using torch.autograd.Function allows you to write a custom backward function.
    The function will be called from the nn.Module CustomLayerNormManualModule
    Inside forward the tensors are (automatically) not recorded for automatic differentiation since the backward
    pass is done via the backward method.
    The forward pass is not called directly but via the apply() method. This makes sure that the context objects
    are dealt with correctly. Example:
      my_bn_fct = CustomLayerNormManualFunction()
      normalized = fct.apply(input, gamma, beta, eps)
    """

    @staticmethod
    def forward(ctx, input, gamma, beta, eps=1e-5):
        """
        Compute the layer normalization

        Args:
          ctx: context object handling storing and retrival of tensors and constants and specifying
               whether tensors need gradients in backward pass
          input: input tensor of shape (n_batch, n_neurons)
          gamma: variance scaling tensor, applied per neuron, shpae (n_neurons)
          beta: mean bias tensor, applied per neuron, shpae (n_neurons)
          eps: small float added to the variance for stability
        Returns:
          out: layer-normalized tensor

        TODO:
          Implement the forward pass of layer normalization
          Store constant non-tensor objects via ctx.constant=myconstant
          Store tensors which you need in the backward pass via ctx.save_for_backward(tensor1, tensor2, ...)
          Intermediate results can be decided to be either recomputed in the backward pass or to be stored
          for the backward pass. Do not store tensors which are unnecessary for the backward pass to save memory!
          For the case that you make use of torch.var be aware that the flag unbiased=False should be set.
        """

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

        batch_size, num_neurons = input.shape

        # compute mean
        mu = torch.mean(input, dim=1, keepdim=True)

        # compute variance
        var = torch.var(input, dim=1, unbiased=False, keepdim=True)
        var = (var+eps)**(-1./2.)

        # normalize
        X_norm = input - mu
        X_norm = X_norm * var

        # scale and shift
        gamma_matrix = gamma.expand(batch_size, -1)
        beta_matrix = beta.expand(batch_size, -1)

        out = gamma_matrix * X_norm + beta_matrix

        ctx.save_for_backward(input, gamma, beta)

        ctx.variance = var
        ctx.mu = mu
        ctx.X_norm = X_norm

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

        return out

    @staticmethod
    def backward(ctx, grad_output):
        """
        Compute backward pass of the layer normalization.

        Args:
          ctx: context object handling storing and retrival of tensors and constants and specifying
               whether tensors need gradients in backward pass
        Returns:
          out: tuple containing gradients for all input arguments

        TODO:
          Retrieve saved tensors and constants via ctx.saved_tensors and ctx.constant
          Compute gradients for inputs where ctx.needs_input_grad[idx] is True. Set gradients for other
          inputs to None. This should be decided dynamically.
        """

        ########################
        # PUT YOUR CODE HERE  #
        #######################
        X, gamma, beta = ctx.saved_tensors
        grad_input = grad_gamma = grad_beta = None

        batch_size, num_neurons = grad_output.shape

        if ctx.needs_input_grad[0]:

          first = grad_output * torch.mm(ctx.variance, gamma[:, None].T)

          second = torch.sum(first, axis=1, keepdim=True).expand(-1, num_neurons) / num_neurons

          var_cubed = ctx.variance**3
          x_minus_mu = X - ctx.mu.expand(-1, num_neurons)

          third_prod = grad_output * torch.mm(var_cubed, gamma[:, None].T) * x_minus_mu
          third_sum = torch.sum(third_prod, axis=1, keepdim=True).expand(-1, num_neurons) / num_neurons
          third = (x_minus_mu * third_sum)

          grad_input = first - second - third

        if ctx.needs_input_grad[1]:
          grad_gamma = torch.sum(grad_output * ctx.X_norm, dim=0)

        if ctx.needs_input_grad[2]:
          grad_beta = torch.sum(grad_output, dim=0)

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

        # return gradients of the three tensor inputs and None for the constant eps
        return grad_input, grad_gamma, grad_beta, None

######################################################################################
# Code for Question 3.2 c)
######################################################################################

class CustomLayerNormManualModule(nn.Module):
    """
    This nn.module implements a custom version of the layer norm operation for MLPs.
    In self.forward the functional version CustomLayerNormManualFunction.forward is called.
    The automatic differentiation of PyTorch calls the backward method of this function in the backward pass.
    """

    def __init__(self, n_neurons, eps=1e-5):
        """
        Initializes CustomLayerNormManualModule object.

        Args:
          n_neurons: int specifying the number of neurons
          eps: small float to be added to the variance for stability

        TODO:
          Save parameters for the number of neurons and eps.
          Initialize parameters gamma and beta via nn.Parameter
        """
        super(CustomLayerNormManualModule, self).__init__()

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

        self.n_neurons = n_neurons
        self.eps = eps

        self.gamma = nn.Parameter(torch.ones(n_neurons))
        self.beta = nn.Parameter(torch.zeros(n_neurons))
        ########################
        # END OF YOUR CODE    #
        #######################

    def forward(self, input):
        """
        Compute the layer normalization via CustomLayerNormManualFunction

        Args:
          input: input tensor of shape (n_batch, n_neurons)
        Returns:
          out: layer-normalized tensor

        TODO:
          Check for the correctness of the shape of the input tensor.
          Instantiate a CustomLayerNormManualFunction.
          Call it via its .apply() method.
        """

        ########################
        # PUT YOUR CODE HERE  #
        #######################
        batch_size, num_neurons = input.shape

        assert self.n_neurons == num_neurons, "{} channels are needed but there are only {}".format(self.n_neurons, num_neurons)

        custom_layer_norm = CustomLayerNormManualFunction()
        out = custom_layer_norm.apply(input, self.gamma, self.beta, self.eps)

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

        return out

if __name__ == '__main__':
    # create test batch
    n_batch = 8
    n_neurons = 128
    # create random tensor with variance 2 and mean 3
    x = 2 * torch.randn(n_batch, n_neurons, requires_grad=True) + 10
    print('Input data:\n\tmeans={}\n\tvars={}'.format(x.mean(dim=1).data, x.var(dim=1).data))

    # test CustomLayerNormAutograd
    print('3.1) Test automatic differentation version')
    bn_auto = CustomLayerNormAutograd(n_neurons)
    y_auto = bn_auto(x)
    print('\tmeans={}\n\tvars={}'.format(y_auto.mean(dim=1).data, y_auto.var(dim=1).data))

    # test CustomLayerNormManualFunction
    # this is recommended to be done in double precision
    print('3.2 b) Test functional version')
    input = x.double()
    gamma = torch.sqrt(10 * torch.arange(n_neurons, dtype=torch.float64, requires_grad=True))
    beta = 100 * torch.arange(n_neurons, dtype=torch.float64, requires_grad=True)
    bn_manual_fct = CustomLayerNormManualFunction(n_neurons)
    y_manual_fct = bn_manual_fct.apply(input, gamma, beta)
    print('\tmeans={}\n\tvars={}'.format(y_manual_fct.mean(dim=1).data, y_manual_fct.var(dim=1).data))
    # gradient check
    grad_correct = torch.autograd.gradcheck(bn_manual_fct.apply, (input, gamma, beta))
    if grad_correct:
        print('\tgradient check successful')
    else:
        raise ValueError('gradient check failed')

    # test CustomLayerNormManualModule
    print('3.2 c) Test module of functional version')
    bn_manual_mod = CustomLayerNormManualModule(n_neurons)
    y_manual_mod = bn_manual_mod(x)
    print('\tmeans={}\n\tvars={}'.format(y_manual_mod.mean(dim=1).data, y_manual_mod.var(dim=1).data))


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

import numpy as np
import pandas as pd

from functools import partial
from math import sqrt
from multiprocessing import Pool
from typing import Callable, Optional

AVAILABLE_METRICS = ['mse', 'rmse', 'mape', 'smape', 'mase', 'rmsse',
                     'mini_owa', 'pinball_loss']

######################################################################
# METRICS
######################################################################

def mse(y: np.array, y_hat:np.array) -> float:
    """Calculates Mean Squared Error.

    MSE measures the prediction accuracy of a
    forecasting method by calculating the squared deviation
    of the prediction and the true value at a given time and
    averages these devations over the length of the series.

    Parameters
    ----------
    y: numpy array
        actual test values
    y_hat: numpy array
        predicted values

    Returns
    -------
    scalar:
        MSE
    """
    mse = np.mean(np.square(y - y_hat))

    return mse

def rmse(y: np.array, y_hat:np.array) -> float:
    """Calculates Root Mean Squared Error.

    RMSE measures the prediction accuracy of a
    forecasting method by calculating the squared deviation
    of the prediction and the true value at a given time and
    averages these devations over the length of the series.
    Finally the RMSE will be in the same scale
    as the original time series so its comparison with other
    series is possible only if they share a common scale.

    Parameters
    ----------
    y: numpy array
      actual test values
    y_hat: numpy array
      predicted values

    Returns
    -------
    scalar: RMSE
    """
    rmse = sqrt(np.mean(np.square(y - y_hat)))

    return rmse

def mape(y: np.array, y_hat:np.array) -> float:
    """Calculates Mean Absolute Percentage Error.

    MAPE measures the relative prediction accuracy of a
    forecasting method by calculating the percentual deviation
    of the prediction and the true value at a given time and
    averages these devations over the length of the series.

    Parameters
    ----------
    y: numpy array
      actual test values
    y_hat: numpy array
      predicted values

    Returns
    -------
    scalar: MAPE
    """
    mape = np.mean(np.abs(y - y_hat) / np.abs(y))
    mape = 100 * mape

    return mape

def smape(y: np.array, y_hat:np.array) -> float:
    """Calculates Symmetric Mean Absolute Percentage Error.

    SMAPE measures the relative prediction accuracy of a
    forecasting method by calculating the relative deviation
    of the prediction and the true value scaled by the sum of the
    absolute values for the prediction and true value at a
    given time, then averages these devations over the length
    of the series. This allows the SMAPE to have bounds between
    0% and 200% which is desireble compared to normal MAPE that
    may be undetermined.

    Parameters
    ----------
    y: numpy array
      actual test values
    y_hat: numpy array
      predicted values

    Returns
    -------
    scalar: SMAPE
    """
    scale = np.abs(y) + np.abs(y_hat)
    scale[scale == 0] = 1e-3
    smape = np.mean(np.abs(y - y_hat) / scale)
    smape = 200 * smape

    return smape

def mase(y: np.array, y_hat: np.array,
         y_train: np.array, seasonality: int = 1) -> float:
    """Calculates the M4 Mean Absolute Scaled Error.

    MASE measures the relative prediction accuracy of a
    forecasting method by comparinng the mean absolute errors
    of the prediction and the true value against the mean
    absolute errors of the seasonal naive model.

    Parameters
    ----------
    y: numpy array
      actual test values
    y_hat: numpy array
      predicted values
    y_train: numpy array
      actual train values for Naive1 predictions
    seasonality: int
      main frequency of the time series
      Hourly 24,  Daily 7, Weekly 52,
      Monthly 12, Quarterly 4, Yearly 1

    Returns
    -------
    scalar: MASE
    """
    scale = np.mean(abs(y_train[seasonality:] - y_train[:-seasonality]))
    mase = np.mean(abs(y - y_hat)) / scale
    mase = 100 * mase

    return mase

def rmsse(y: np.array, y_hat: np.array,
         y_train: np.array, seasonality: int = 1) -> float:
    """Calculates the M5 Root Mean Squared Scaled Error.

    Parameters
    ----------
    y: numpy array
      actual test values
    y_hat: numpy array of len h (forecasting horizon)
      predicted values
    y_train: numpy array
       actual train values
    seasonality: int
      main frequency of the time series
      Hourly 24,  Daily 7, Weekly 52,
      Monthly 12, Quarterly 4, Yearly 1

    Returns
    -------
    scalar: RMSSE
    """
    scale = np.mean(np.square(y_train[seasonality:] - y_train[:-seasonality]))
    rmsse = sqrt(mse(y, y_hat) / scale)
    rmsse = 100 * rmsse

    return rmsse

def mini_owa(y: np.array, y_hat: np.array,
             y_train: np.array,
             seasonality: int,
             y_bench: np.array):
    """Calculates the Overall Weighted Average for a single series.

    MASE, sMAPE for Naive2 and current model
    then calculatess Overall Weighted Average.

    Parameters
    ----------
    y: numpy array
        actual test values
    y_hat: numpy array of len h (forecasting horizon)
        predicted values
    y_train: numpy array
        insample values of the series for scale
    seasonality: int
        main frequency of the time series
        Hourly 24,  Daily 7, Weekly 52,
        Monthly 12, Quarterly 4, Yearly 1
    y_bench: numpy array of len h (forecasting horizon)
        predicted values of the benchmark model

    Returns
    -------
    return: mini_OWA
    """
    mase_y = mase(y, y_hat, y_train, seasonality)
    mase_bench = mase(y, y_bench, y_train, seasonality)

    smape_y = smape(y, y_hat)
    smape_bench = smape(y, y_bench)

    mini_owa = ((mase_y/mase_bench) + (smape_y/smape_bench))/2

    return mini_owa

def pinball_loss(y: np.array, y_hat: np.array, tau: int = 0.5):
    """Calculates the Pinball Loss.

    The Pinball loss measures the deviation of a quantile forecast.
    By weighting the absolute deviation in a non symmetric way, the
    loss pays more attention to under or over estimation.
    A common value for tau is 0.5 for the deviation from the median.

    Parameters
    ----------
    y: numpy array
      actual test values
    y_hat: numpy array of len h (forecasting horizon)
      predicted values
    tau: float
      Fixes the quantile against which the predictions are compared.

    Returns
    -------
    return: pinball_loss
    """
    delta_y = y - y_hat
    pinball = np.maximum(tau * delta_y, (tau-1) * delta_y)
    pinball = pinball.mean()

    return pinball

######################################################################
#  PANEL EVALUATION
######################################################################

def _evaluate_ts(uid, y_test, y_hat,
                 y_train, metric,
                 seasonality, y_bench, metric_name):
    y_test_uid = y_test.loc[uid].y.values
    y_hat_uid = y_hat.loc[uid].y_hat.values

    if metric_name in ['mase', 'rmsse']:
        y_train_uid = y_train.loc[uid].y.values
        evaluation_uid = metric(y=y_test_uid, y_hat=y_hat_uid,
                                y_train=y_train_uid,
                                seasonality=seasonality)
    elif metric_name in ['mini_owa']:
        y_train_uid = y_train.loc[uid].y.values
        y_bench_uid = y_bench.loc[uid].y_hat.values
        evaluation_uid = metric(y=y_test_uid, y_hat=y_hat_uid,
                                y_train=y_train_uid,
                                seasonality=seasonality,
                                y_bench=y_bench_uid)

    else:
         evaluation_uid = metric(y=y_test_uid, y_hat=y_hat_uid)

    return uid, evaluation_uid

def evaluate_panel(y_test: pd.DataFrame,
                   y_hat: pd.DataFrame,
                   y_train: pd.DataFrame,
                   metric: Callable,
                   seasonality: Optional[int] = None,
                   y_bench: Optional[pd.DataFrame] = None,
                   threads: Optional[int] = None):
    """Calculates a specific metric for y and y_hat (and y_train, if needed).

    Parameters
    ----------
    y_test: pandas df
        df with columns ['unique_id', 'ds', 'y']
    y_hat: pandas df
        df with columns ['unique_id', 'ds', 'y_hat']
    y_train: pandas df
        df with columns ['unique_id', 'ds', 'y'] (train)
        This is used in the scaled metrics ('mase', 'rmsse').
    metric: callable
        loss function
    seasonality: int
        Main frequency of the time series.
        Used in ('mase', 'rmsse').
        Commonly used seasonalities:
            Hourly: 24,
            Daily: 7,
            Weekly: 52,
            Monthly: 12,
            Quarterly: 4,
            Yearly: 1.
    y_bench: pandas df
        df with columns ['unique_id', 'ds', 'y_hat']
        predicted values of the benchmark model
        This is used in 'mini_owa'.
    threads: int
        Number of threads to use. Use None (default) for parallel processing.

    Returns
    ------
    pandas dataframe:
        loss ofr each unique_id in the panel data
    """
    metric_name = metric.__code__.co_name
    uids = y_test['unique_id'].unique()
    y_hat_uids = y_hat['unique_id'].unique()

    assert len(y_test)==len(y_hat), "not same length"
    assert all(uids == y_hat_uids), "not same u_ids"

    y_test = y_test.set_index(['unique_id', 'ds'])
    y_hat = y_hat.set_index(['unique_id', 'ds'])

    if metric_name in ['mase', 'rmsse']:
        y_train = y_train.set_index(['unique_id', 'ds'])

    elif metric_name in ['mini_owa']:
        y_train = y_train.set_index(['unique_id', 'ds'])
        y_bench = y_bench.set_index(['unique_id', 'ds'])

    partial_evaluation = partial(_evaluate_ts, y_test=y_test, y_hat=y_hat,
                                 y_train=y_train, metric=metric,
                                 seasonality=seasonality,
                                 y_bench=y_bench,
                                 metric_name=metric_name)

    with Pool(threads) as pool:
        evaluations = pool.map(partial_evaluation, uids)

    evaluations = pd.DataFrame(evaluations, columns=['unique_id', 'error'])

    return evaluations


from matplotlib.image import imread
import numpy as np
import matplotlib.pyplot as plt
import os
import pywt

plt.rcParams['figure.figsize'] = [16, 16]
plt.rcParams.update({'font.size': 18})

A = imread('headshot.jpg')
B = np.mean(A, -1)                      # convert RGB to grayscale

# Wavelet Compression
n = 4
w = 'db1'                               # daubechies wavelet
coeffs = pywt.wavedec2(B, wavelet=w, level=n)
coeff_arr, coeff_slices = pywt.coeffs_to_array(coeffs)
Csort = np.sort(np.abs(coeff_arr.reshape(-1)))

for keep in (0.1, 0.05, 0.01, 0.005):
    thresh = Csort[int(np.floor((1-keep)*len(Csort)))]
    ind = np.abs(coeff_arr) > thresh
    Cfilt = coeff_arr * ind                                         # threshold small indices

    coeffs_filt = pywt.array_to_coeffs(Cfilt, coeff_slices, output_format='wavedec2')

    # Plot reconstruction
    Arecon = pywt.waverec2(coeffs_filt, wavelet=w)
    plt.figure()
    plt.imshow(Arecon.astype('uint8'), cmap='gray_r')
    plt.axis('off')
    plt.rcParams['figure.figsize'] = [8, 8]
    plt.title('keep = ' + str(keep))
    plt.show()

""" Can code FFT2 vs Wavelet2 and compare to performance -- homework
    can compute the norm of the error to compare performance """


# -*- coding: utf-8 -*-
"""
Created on Sun Nov  3 16:49:40 2019

@author: ATTUIL RUBEN
@studentid:3672225
@email:ruben-attuil@outlook.fr
"""

import numpy as np
import scipy.linalg as la
import matplotlib.pyplot as plt

N = 12
k = 1
m = 1

# Création de la matrice d'inertie M
M = (2*np.eye(N) - np.eye(N, k=-1) - np.eye(N, k=1))*k/m
print("Matrix M is :")
print(M)

#Affichage des valeurs et vecteurs propres

e_val,e_vec=la.eig(M)
print("Eigenvalues are :")
print(e_val)

print("Eigenvectors are :")
print(e_vec)

time_sample = np.linspace(0,40,100)
W=np.zeros(len(e_val))#creation d'un tableau qui va contenir les pulsations propres
F=np.zeros(len(e_val))#creation d'un tableau qui va contenir les fréquences propres associées
for i in range(len(e_val)):
    w=np.sqrt(e_val[i])
    W[i]=w #remplissage du tableau des pulsations propres

for i in range(3):
    #E represente le déplacement de chaque masse ici 3 masses étudiées
    E1=e_vec[0,i]*np.cos(w*time_sample)
    E2=e_vec[1,i]*np.cos(w*time_sample)
    E3=e_vec[2,i]*np.cos(w*time_sample)
    # x représente la position de chaquze masse ici 3 masses étudiées
    x1=1+E1
    x2=2*(1+E2)
    x3=3*(1+E3)
    #tracé des graphes représentant la position de chaque masse en fonction du temps
    plt.figure()
    plt.plot(time_sample,x1, c='b', label='x1')
    plt.plot(time_sample,x2, c='r', label='x2')
    plt.plot(time_sample,x3, c='g', label='x3')

    plt.title("mode normal"+ str(i+1))
    plt.xlabel("t")
    plt.ylabel("mouvement de chaque masse")
    plt.legend(loc='upper left')

    plt.show()
    plt.savefig('./mode_'+str(i+1)+'.png')

for i in range (len(W)):
    F[i]=W[i]/(2*np.pi)#remplissage du tableau contenant les fréquences propres
mode=[0,1,2,3,4,5,6,7,8,9,10,11]
#Tracé du graphe répresentant les fréquences naturelles en fonction du numero de leur mode
plt.figure()
plt.scatter(mode,F)
plt.title("Natural fresquencies versus mode number")
plt.xlabel("mode number")
plt.ylabel("F")
plt.show()

print("naturals frequencies")
print(F)


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

def run_SEIR_ODE_model(
        N: 'population size',
        E0: 'init. exposed population',
        I0: 'init. infected population',
        R0: 'init. removed population',
        beta: 'infection probability',
        gamma: 'removal probability',
        alpha_inv: 'incubation period',
        t_max: 'numer of days to run'
    ) -> pd.DataFrame:

    S0 = N - I0 - R0 - E0
    alpha = 1/alpha_inv

    # A grid of time points (in days)
    t = range(t_max)

    # The SEIR model differential equations.
    def deriv(y, t, N, beta, gamma, alpha):
        S, E, I, R = y
        dSdt = -beta * S * I / N
        dEdt = -dSdt - alpha*E
        dIdt = alpha*E - gamma*I
        dRdt = gamma * I
        return dSdt, dEdt, dIdt, dRdt

    # Initial conditions vector
    y0 = S0, E0, I0, R0

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

    return pd.DataFrame({'S': S, 'E': E, 'I': I, 'R': R}, index=t)

if __name__ == '__main__':
    N = 13_000_000
    E0, I0, R0 = 0, 152, 1
    beta, gamma, alpha_inv = 1.75, 0.5, 5
    t_max = 60
    results = run_SEIR_ODE_model(N, E0, I0, R0, beta, gamma, alpha_inv, t_max)

    # plot
    plt.style.use('ggplot')
    (results
     # .div(1_000_000)
     [['E', 'I']]
     .plot(figsize=(8,6), fontsize=20, logy=True))
    params_title = (
        f'SEIR($\gamma$={gamma}, $\\beta$={beta}, $\\alpha$={1/alpha_inv}, $N$={N}, '
        f'$E_0$={E0}, $I_0$={I0}, $R_0$={R0})'
    )
    plt.title(f'Number of People Affected with model:\n' + params_title,
              fontsize=20)
    plt.legend(['Exposed', 'Infected'], fontsize=20)
    plt.xlabel('Days', fontsize=20)
    plt.ylabel('People', fontsize=20)
    plt.show()


# support/Loss_n_ActivationFunction.py

import numpy as np

# Activation Function

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

def tanhDerivative(x):
    t = np.tanh(x)
    return 1-np.power(t,2);

def sigmoid(x):
    # prevent overflow
    x = np.clip(x, -600, 600 )
    return 1.0 / (1 + np.exp(-x))

def sigmoidDerivative(x):
    S = sigmoid(x)
    return S * (1 - S)

def softmax(x):
    x = np.clip(x, -600, 600 )
    return np.exp(x) / np.sum(np.exp(x), axis=1).reshape(-1,1)

def softmaxDerivative(x):
    S = softmax(x)
    return S * (1 - S)

def relu(x):
    return (x > 0) * x # for x>0 = x, for x <= 0 = 0

def reluDerivative(x):
    return (x > 0) * 1 # for x>0 = 1, for x <= 0 = 0

# Loss Function

def mse(y, yEst):
    return np.mean(np.power(y-yEst, 2));

def mseDerivative(y, yEst):
    m = y.shape[0]
    return 2*(yEst-y)/m;

def crossEntropy(y, yEst):
    eps = 1e-5
    return  - np.sum(y*np.log(yEst+eps), axis =1)

def crossEntropyDerivative(y, yEst):
    m = y.shape[0]
    c = np.sum(yEst - y, axis = 0)
    return c/m


import numpy as np

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

# Legendre Polynomial

def legendre_polynomials(S, k):

    u0 = np.ones(S.shape)
    x1 = S
    x2 = (3*S**2 -1 )/2
    x3 = (5*S**3-3*S)/2
    x4 = (35*S**4-30*S**2 +3)/8
    x5 = (63*S**5 - 70*S**3 + 15*S)/8

    X = [np.stack([u0,x1,x2], axis=1),
         np.stack([u0, x1, x2, x3], axis= 1),
         np.stack([u0, x1, x2, x3, x4], axis=1)]

    return X[k-2]

# the case for one price, startin from left to right
def laguerre_polynomials_ind(S, k):

    u0 = np.ones(S.shape)
    x1 = 1 - S
    x2 = 1 - 2*S + S**2/2
    x3 = 1 - 3*S + 3*S**2/2 - S**3/6
    x4 = 1 - 4*S + 3*S**2 - 2*S**3/3 + S**4/24

    X  = [np.stack([u0, x1, x2]),
          np.stack([u0, x1, x2, x3]),
          np.stack([u0, x1, x2, x3, x4])]

    return X[k-2]

def laguerre_polynomials(S, k):

    #  the first k terms of Laguerre Polynomials (k<=4)
#    x1 = np.exp(-S/2)
#    x2 = np.exp(-S/2) * (1 - S)
#    x3 = np.exp(-S/2) * (1 - 2*S + S**2/2)
#    x4 = np.exp(-S/2) * (1 - 3*S + 3* S**2/2 - S**3/6)

    u0 = np.ones(S.shape)
    x1 = 1 - S
    x2 = 1 - 2*S + S**2/2
    x3 = 1 - 3*S + 3*S**2/2 - S**3/6
    x4 = 1 - 4*S + 3*S**2 - 2*S**3/3 + S**4/24

    X  = [np.stack([u0, x1, x2], axis = 1),
          np.stack([u0, x1, x2, x3], axis = 1),
          np.stack([u0, x1, x2, x3, x4], axis = 1)]

    return X[k-2]