# -*- coding: utf-8 -*-
"""
Notes and tries on Chaper 02 (Think Stats 2, Allen B. Downey)

Self-study on statistics using pyhton
@author: Github: @rafaelmm82
"""

import thinkstats2
import thinkplot
import nsfg
import math
import matplotlib.pyplot as plt
import numpy as np

hist = thinkstats2.Hist([1, 2, 2, 3, 5])
hist

hist.Freq(2)

hist[2]

hist.Freq(4)

hist.Values()

for val in sorted(hist.Values()):
    print(val, hist[val])

for val, freq in hist.Items():
    print(val, freq)

thinkplot.Hist(hist)
thinkplot.Config(xlabel='value', ylabel='frequency', legend='false')

preg = nsfg.ReadFemPreg()
live = preg[preg.outcome == 1]

# 2.1
hist = thinkstats2.Hist(live.birthwgt_lb, label='birthwgt_lb')
thinkplot.Hist(hist)
thinkplot.Config(xlabel='pounds', ylabel='frequency')

# 2.2
hist = thinkstats2.Hist(live.birthwgt_oz, label='birthwgt_oz')
thinkplot.Hist(hist)
thinkplot.Config(xlabel='ounces', ylabel='frequency')

# 2.3
hist = thinkstats2.Hist(np.floor(live.agepreg), label='agepreg')
thinkplot.Hist(hist)
thinkplot.Config(xlabel='agepreg', ylabel='frequency')

# 2.4
hist = thinkstats2.Hist(live.prglngth, label='prglength')
thinkplot.Hist(hist)
thinkplot.Config(xlabel='pregnancy weeks', ylabel='frequency')

for weeks, freq in hist.Smallest(10):
    print(weeks, freq)

for weeks, freq in hist.Largest(10):
    print(weeks, freq)

# First Babies

firsts = live[live.birthord == 1]
others = live[live.birthord != 1]

firsts_hist = thinkstats2.Hist(firsts.prglngth)
others_hist = thinkstats2.Hist(others.prglngth)

width = 0.45
thinkplot.PrePlot(2)
thinkplot.Hist(firsts_hist, align='right', width=width)
thinkplot.Hist(others_hist, align='left', width=width)
thinkplot.Config(xlabel='weeks', ylabel='Count', xlim=[27, 46])

plt.hist(firsts_hist, 20, color='r')
plt.hist(others_hist, 20, color='b')
plt.show()

def CohenEffectSize(group1, group2):
    diff = group1.mean() - group2.mean()

    var1 = group1.var()
    var2 = group2.var()
    n1, n2 = len(group1), len(group2)

    pooled_var = (n1 * var1 + n2 * var2) / (n1 + n2)
    d = diff / math.sqrt(pooled_var)
    return d

cohendiff = CohenEffectSize(firsts.prglngth, others.prglngth)

"""
 Exerc 2.1
 Based on about nine thousands pregnancies records from a great american census
 , a group of data scientits conducteded some analysis to understanding if
 there are any traces or elements to affirm or not that firsts babies come
 or not early them others. The conclusions, based on statistical derivative
 measurements, the fact graph analysis and standard and numerical accepted
 procedures, they concluded that there are no effective evidence that first
 babies tend to born too early ro too later them others.

"""


#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time     : 2018/09/13 14:25
# @Author   : Iydon
# @File     : course2.2.py
import numpy as np
import time, random, threading

def tic(): globals()['TIC_TIME'] = time.time()
toc = lambda :time.time()-globals()['TIC_TIME']

def Newton_method(f, df, start:float=0.0, max_step:int=32, sign_dig:int=6)->float:
    """
    Newton method.
    ---------------------------
    Args:
        None.

    Returns:
        None.

    Raises:
        None.
    """
    fun = lambda x: x - f(x)/df(x)
    return fixed_point(fun, start, max_step, sign_dig)

def fixed_point(fun, start:float, max_step:int, sign_dig:int)->float:
    """
    Fixed-point iteration to find the zero.
    ----------------------------
    Args:
        fun: Function.
        start: Float, the first iteration point.
        max_step: Integer, max number of iteration.
        sign_dig: Integer, significant digits.

    Returns:
        Float zero.
        zero | fun(zero)\\sim 0.

    Raises:
        None.
    """
    fl = lambda x: round(x, 100)
    eps = 10**(-sign_dig)
    new_val = fun(start)
    for i in range(0, max_step):
        old_val = fl(new_val)
        new_val = fl(fun(old_val))
        if abs(old_val-new_val)<=2*eps:
            return (i, new_val)
    return "Max_step..."

def secant_method(f, start:list, max_step:int=32, eps:float=1e-6) -> float:
    """
    Secant method.
    ----------------------------
    Args:
        f: Function.
        start: List of float, the first iteration point.
        max_step: Integer, max number of iteration.

    Returns:
        Float zero.
        zero | fun(zero)\\sim 0.

    Raises:
        None.
    """
    p = [i    for i in start]
    q = [f(i) for i in start]
    for i in range(max_step):
        _p = p[-1] - q[-1]*(p[-1]-p[0])/(q[-1]-q[0])
        if abs(_p-p[-1])<eps:
            return (i, _p)
        p = [p[-1], _p]
        q = [q[-1], f(_p)]
    return False

def false_position(f, start:list, max_step:int=32, eps:float=1e-6) -> float:
    """
    False position.
    ----------------------------
    Args:
        f: Function.
        start: List of float, the first iteration point.
        max_step: Integer, max number of iteration.

    Returns:
        Float zero.
        zero | fun(zero)\\sim 0.

    Raises:
        None.
    """
    p = [i    for i in start]
    q = [f(i) for i in start]
    for i in range(max_step):
        _p = p[-1] - q[-1]*(p[-1]-p[0])/(q[-1]-q[0])
        if abs(_p-p[-1]) < eps:
            return (i, _p)
        _q = f(_p)
        if _q*q[-1] < 0:
            p[0] = p[-1]
            q[0] = q[-1]
        p[-1] = _p
        q[-1] = _q
    return False

def diff(f, eps:float=1e-6, left_flag=True):
    """
    \\frac{f(x+eps)-f(x)}{eps}.
    """
    if isinstance(f, type(lambda:0)):
        if left_flag:
            return lambda x: (f(x+eps)-f(x))/eps
        else:
            return lambda x: (f(x)-f(x-eps))/eps

f = lambda x: np.cos(x)
df = lambda x: -np.sin(x)
result = Newton_method(f, df, 9)
print(result)

df = diff(f, eps=1e-3)
result = Newton_method(f, df, 9)
print(result)

result = secant_method(f, [0,1])
print(result)

result = false_position(f, [0,1])
print(result)


# -*- coding: utf-8 -*-
"""
Created on Tue Mar 14 09:34:17 2017
@author: lamahamadeh
"""

'''
Case Study about an introduction to kNN classifiction
'''

#Finding the Euclidean Distance Between Two Points
#---------------------------------------------------

import numpy as np

def distance(p1,p2):
    "find the distance betweeen points p1 and p2"
    return np.sqrt(np.sum(np.power(p2 - p1, 2))) #Euclidean distance

p1 = np.array([1,1]) #p1=(x1,y1)
p2 = np.array([4,4]) #p2=(x2,y2)

print (distance(p1,p2))

#------------------------------------------------------------------------------

#Majority Vote
#-------------

import random

def majority_vote(votes):
    "Return the most common element in votes"
    vote_counts = {}
    for vote in votes:
        if vote in vote_counts:
            vote_counts[vote] += 1
        else:
            vote_counts[vote] = 1
    #items method
    winners = []
    max_counts = max(vote_counts.values()) #ask Python about the frequency the most appearing value has
    for vote, count in vote_counts.items(): #this returns a tuple of (vote,count)
        if count == max_counts:
            winners.append(vote)

    return random.choice(winners) #in case of a tie situation, Python picks a random candidate (element) and returns it.

votes = [1, 2, 3, 1, 2 ,3 ,3 ,3 ,3, 2, 2, 2] #here number 2 and 3 has the same appearing frequency: 5 (tie situation)
winner = majority_vote(votes)
print (winner) #it can be seen after running the code several times that this method picks one of the candidates
#and returns it randomly.

'''
#another method
#--------------
import scipy.stats as ss

def majority_vote_short(votes):
    "Return the most common element in votes "
    mode , count = ss.mstats.mode(votes)
    return mode

votes = [1, 2, 3, 1, 2 ,3 ,3 ,3 ,3]
winner_short = majority_vote_short(votes)
print (winner_short) # it can be seen after running the code several times that this method always
#returns number 3 as the most common candidate and forgets about the other one: 2
'''

#------------------------------------------------------------------------------

#Finding Nearest Neighbors
#--------------------------

points = np.array([[1,1], [1,2], [1,3], [2,1], [2,2], [2,3], [3,1], [3,2], [3,3]]) #create a numebr of points as a vector(training data)/predictors
p = np.array([2.5,2])#a single point with coordinates: x=2.5 and y=2 (testing data)

#plotting the points and point p
import matplotlib.pyplot as plt

plt.plot(points[:,0], points[:,1],"ro") #training points/data
plt.plot(p[0],p[1],"bo") #testing point/data
plt.axis([0.5, 3.5, 0.5, 3.5])

distances = np.zeros(points.shape[0])#'.shape[0]' here reutrn a tuple of array dimensions. this creates a ndarray where
#its elements are all zeros and has the size of 'points' array, i.e., 9.
print (distances); print(len(distances))

for i in range(len(distances)):# loop over all points
    distances[i] = distance(p,points[i]) # compute the distance between the testing point p and every other training points using the function 'distnace'

print(distances)#reutrn the distances values between all the points and the point p
#it can be seen from the plot that point[2,2] and point[3,2] are the nearest to the testing point p[2.5,2]
#this can be proved by calculating the distance between the testing point p and these two points,, i.e., distance=0.5.
print(points[4])#return the training point number 4: p4=[2,2]
print(distances[4])#calculate the distnace between the testing point p and the training point p4
print(points[7])#return the training point number 7: p7=[3,2]
print(distances[7])#calculate the distnace between the testing point p and the training point p7

#construct a 'find_nearest_neighbor' function
def find_nearest_neighbor(p, points, k=5): #k=5 is a default value, you can choose any value for k afterwards up to 5.
    """find the k nearest neighbors of point p nd return their indices """
    distances = np.zeros(points.shape[0])#'.shape[0]' here reutrn a tuple of array dimensions.
    for i in range(len(distances)):# loop over all points
        distances[i] = distance(p,points[i]) # compute the distance between point p and every other point using the function 'distnace'
    ind = np.argsort(distances)#'argsort' is a function that sorts the distnaces indinces from short to long.
    #np.argsort(distances) it sorts an array according to a single argument and returns the 'sorted indices'
    return ind[0:k] # or for short ind[:k]. This step is to return those k points that are nearest to point p

#k=2
ind = find_nearest_neighbor(p, points, 2)
print(ind) #return the indices of the nearest points ind[0:2]
print(points[ind])# return the nearest points

#k=3
ind = find_nearest_neighbor(p, points, 3)
print(ind) #return the indices of the nearest points ind[0:3]
print(points[ind])# return the nearest points

#k=4
ind = find_nearest_neighbor(p, points, 4)
print(ind) #return the indices of the nearest points ind[0:4]
print(points[ind])# return the nearest points

#construct a 'knn_predict' function
def knn_predict(p, points, outcomes, k=5):
    #find k nearest neighbors
    ind = find_nearest_neighbor(p, points, k)
    return majority_vote(outcomes[ind])
    #predict the class/categroy/target of the testing point p based on majority vote of the nearest training points

outcomes = np.array([0,0,0,0,1,1,1,1,1]) # an array for classes definition/targets: we have two numeric classes: 0 and 1

print(len(outcomes))#the same as the points array length

print(knn_predict(np.array([2.5, 2.7]), points, outcomes, k=2)) #class 1
print(knn_predict(np.array([1.0, 2.7]), points, outcomes, k=2)) #class 0

#------------------------------------------------------------------------------

#Generating Synthetic Data
#-------------------------

from scipy import stats as ss

def generate_synthetic_data(n=50): #n is the number of generated random training points from normal distribution
    """Create two sets of points from bivariate normal distributions."""
    points = np.concatenate((ss.norm(0,1).rvs((n,2)),ss.norm(1,1).rvs((n,2))), axis=0) #norm(mean, standard deviation)
    #'.rvs' Random variates of given type. Here we have: .rvs((number of rows, number of columns))
    # 'axis = 0' means that we are concatenating along the rows of these arrays
    #the whole lemgth/size of points array is 2nx2: nx2 for the first generated points set and nx2 for the second one
    outcomes = np.concatenate((np.repeat(0,n), np.repeat(1,n)), axis=0) #generate two ndarrays/classes/outcomes/targets: the first one has 0 values and length(n)
    #and the second one with values 1 and length n.
    #0 and 1 here refer to the names of classes
    #axis =0 means that the concatenating happens along the rows
    return (points, outcomes)

n=20

(points, outcomes) = generate_synthetic_data(n) #this gives 20 points from class 0 and the other 20 from class 1

plt.figure()
plt.plot(points[:n,0], points[:n,1], "ro") #this refers to the first class 0 (n points)
plt.plot(points[n:,0], points[n:,1], "bo") #this refers to the second class 1 (n points)

#------------------------------------------------------------------------------

#Making a Prediction Grid
#-------------------------

def make_prediction_grid(predictors, outcomes, limits, h, k):
    """classify each point on the prediction grid."""
    (x_min, x_max, y_min, y_max) = limits
    xs = np.arange(x_min, x_max, h) #h is step size
    ys = np.arange(y_min, y_max, h) #h is step size
    xx, yy = np.meshgrid(xs, ys)#this creates a two dimensional array

    prediction_grid = np.zeros(xx.shape, dtype = int) # the type here is 'int' becasue both classes are integers: either 0 or 1
    for i,x in enumerate(xs):
        for j,y in enumerate(ys):
            p = np.array([x,y])
            prediction_grid[j,i] = knn_predict(p, predictors, outcomes, k)
            #It can be noticed here that we have put [j,i] not the other way around. The reason for that is
            # that in the prediction grid, y always refers to the rows of the grid
            #however, x refers to the columns of the grid.
            #It is important in the square brackets to begin with the the first eleemnt corresponds to the rows of the array
            #and the second element corrsponds to the coloumn of the array.
    return (xx, yy, prediction_grid)

'''
NOTE about enumerate:
---------------------
enumerate return a tuple where the first element is the index and the second element is the specific object itself.
for example: if we define a list of season as:
seasons = ['spring', 'winter', 'summer', 'autumn']
enumerate(seasons) #this does nothing
list(enumerate(seasons))#this returns tuples that contain the index of each season being the first element of the tuple
#and the name of the season being the second element of the tuple.
we can illustrate this using for loopas following:

for ind,season in enumerate(seasone):
    print(ind,season)
and as we have seen before this return tuples of all the element in seasons list.
'''
#------------------------------------------------------------------------------

#Plotting the Prediction Grid
#-----------------------------

def plot_prediction_grid (xx, yy, prediction_grid, filename):
    """ Plot KNN predictions for every point on the grid."""
    from matplotlib.colors import ListedColormap
    background_colormap = ListedColormap (["hotpink","lightskyblue", "yellowgreen"])
    observation_colormap = ListedColormap (["red","blue","green"])
    plt.figure(figsize =(10,10))
    plt.pcolormesh(xx, yy, prediction_grid, cmap = background_colormap, alpha = 0.5)
    plt.scatter(predictors[:,0], predictors [:,1], c = outcomes, cmap = observation_colormap, s = 50)
    plt.xlabel('Variable 1'); plt.ylabel('Variable 2')
    plt.xticks(()); plt.yticks(())
    plt.xlim (np.min(xx), np.max(xx))
    plt.ylim (np.min(yy), np.max(yy))
    plt.savefig(filename)

(predictors, outcomes) = generate_synthetic_data() #in case you don't define a value for n it will be the default value defined in the function, i.e, n=50.

k = 5; filename = "knn_synth_5.pdf"; limits = (-3, 4, -3 ,4); h = 0.1
(xx, yy, prediction_grid) = make_prediction_grid(predictors, outcomes, limits, h ,k)
plot_prediction_grid(xx, yy, prediction_grid, filename)

k = 50; filename = "knn_synth_50.pdf"; limits = (-3, 4, -3 ,4); h = 0.1
(xx, yy, prediction_grid) = make_prediction_grid(predictors, outcomes, limits, h ,k)
plot_prediction_grid(xx, yy, prediction_grid, filename)

#Looking at the plot for k equals 50, we can see that the decsion boundary is pretty smooth.
#In contrast, if you look at the plot where k equals to 5, you'll see that the shape of the descison boundary is more complicated
#which means that k contriols the smoothness of the fit.
#It seems that you might be able to find a value of k that maximises the accuracy of the predictions.
#But that's somewhat short sighted.
#this is because what you really care about is not how well your method performs on the training data set,
#the data set that we have seen so far.
#but rather how well it performs on a future dataset you have not yet seen.
#It turns out that using a value for k that is too large or too small is not optimal.
# a phenomenon that is known as the bias-variance tradeoff. This suggests that some intermediate values of k
#might be best.

#------------------------------------------------------------------------------

#Applying the kNN Method
#------------------------

from sklearn import datasets
iris = datasets.load_iris() #iris is a built-in dataset in sklearn

print iris['data'] #the size of the dataset is 150x4: 150 rows (observations) and 4 columns (covariates:sepal length, sepal width, petal length and petal width )

predictors = iris.data[:, 0:2] #subset
outcomes = iris.target #classes

plt.plot(predictors[outcomes==0][:,0], predictors[outcomes==0][:,1], 'ro') #the first species/ feature that is contained in the iris dataset
plt.plot(predictors[outcomes==1][:,0], predictors[outcomes==1][:,1], 'go') #the second species/ feature that is contained in the iris dataset
plt.plot(predictors[outcomes==2][:,0], predictors[outcomes==2][:,1], 'bo') #the third species/ feature that is contained in the iris dataset
plt.savefig('iris.pdf')

#prediction plot
k = 5; filename = "iris_grid.pdf"; limits = (4,8,1.5,4.5); h = 0.1
(xx, yy, prediction_grid) = make_prediction_grid(predictors, outcomes, limits, h ,k)
plot_prediction_grid(xx, yy, prediction_grid, filename)#print the training data

my_predictions = np.array([knn_predict(p,predictors, outcomes,5) for p in predictors])
print my_predictions.shape # (150,)

#compare between our own-made knn classifier and the sklearn classifier
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors = 5)
knn.fit(predictors, outcomes)
sk_predictions = knn.predict(predictors)

print sk_predictions.shape #(150,)

sk_predictions == my_predictions

print np.mean(sk_predictions == my_predictions)
print 100*np.mean(sk_predictions == my_predictions) #precentage of how both methods are close

print 100*np.mean(sk_predictions == outcomes) #83.333
print 100*np.mean(my_predictions == outcomes) #84.666

# It can be seen clearly that our own-made predictor is slightly better than the sklearn predictior: it is accurate approximately 85% of the time.

#------------------------------------------------------------------------------


# distance, dot- and crossproduct of vectors through class
import math
import numpy as np

class Vector():
    """numpydoc:
    A new class Vector with functions
    to calculate the distance, the dot
    and cross products of vectors.

    Attributes
    ----------
    x, y, z : 'float'
        The vectors coordinates.

    Returns
    -------
    ret1 : float
        Distance/Magnitude between two vectors
    ret2 : float
        Dot product between two vectors
    ret3 : floats
        cross product between two vectors
        three floats representing the resulting vector

    Note:
    ----
    .. _NumPy Documentation HOWTO:
        https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt
    pro: Better for complex descriptions and documentation
    con: Uses more vertical space
    """

    def __init__(self, x, y, z):
        """Construction of the necessary attributes for the vector.

        Parameters
        ----------
        x, y, z : 'float'
            The vectors coordinates.
        """

        self.x = x
        self.y = y
        self.z = z

    # function for magnitude between two vectors (betrag)
    def distance(self, other):
        """Prints the distance between two vectors
        if the coordinates for the other vector is passed.

        Parameters
        ----------
        other : floats
            actually a list of floats representing vector.

        Returns
        -------
        ret1 : float
            Distance/Magnitude between two vectors
        """
        return math.sqrt((other.x - self.x)**2 + (other.y - self.y)**2 + (other.z - self.z)**2)

    def dot(self, other):
        """Prints the dot product between two vectors
        if the coordinates for the other vector is passed.

        Parameters
        ----------
        other : floats
            actually a list of floats representing vector.

        Returns
        -------
        ret1 : float
            Dot product between two vectors
        """
        return ((self.x * other.x) + (self.y * other.y) + (self.z * other.z))

    def cross(self, other):
        """Prints the cross product between two vectors
        if the coordinates for the other vector is passed.

        Parameters
        ----------
        other : floats
            actually a list of floats representing vector.

        Returns
        -------
        ret2 : floats
            cross product between two vectors
        """
        return Vector((self.y * other.z) - (self.z * other.y),
        (self.x * other.z) - (self.z * other.x),
        (self.x * other.y) - (self.y * other.x))

    # formatting output of vector
    def __repr__(self):
        """Returns the object representation. In this case string.

        Parameters
        ----------
        self : floats
            actually a list of floats representing vector.

        Returns
        -------
        ret3 : sring
            cross product between two vectors formatted.
        """
        out = "x = " + str(self.x)

        if self.y >= 0:
            out = out + "+ "
        out = out + "\n y = " + str(self.y)

        if self.z >= 0:
            out = out + "+ "
        out = out + "\n z = " + str(self.z)

        return out

if __name__ == "__main__":
    """The global variable, __name__, in the module that is the entry
    point to your program, is '__main__'.
    Otherwise, it's the name you import the module by.
    So, code under the if block will only run if the module is the
    entry point to your program.
    It allows the code in the module to be importable by other modules,
    without executing the code block beneath on import.

    Notes
    -----
    Calling the class and seperate functions and printing their values.
    """

    v1 = Vector(2.8, -4.7, 0.4)
    v2 = Vector(-8.1, 3.0, -10.6)
    value1 = v1.distance(v2)
    value2 = v1.dot(v2)
    v3 = v1.cross(v2)

    print("Value1 = ", value1, "\nValue2 = ", value2, "\nv3: \n", v3)
    print("checking vector instance of v3: ", isinstance(v3, Vector))


import numpy as np

from coptim.optimizer import Optimizer

class BFGSMethod(Optimizer):
    def __init__(self):
        # TODO: More metrics: vector of x's, objective values, etc.
        self.iterations = 0

    def psi(self, t, sigma, x, d, func):
        return func.eval(x + t * d) - \
               func.eval(x) - \
               sigma * t * func.gradient(x).dot(d)

    def wolfe_powell_rule_phase_B(self, a, b, rho, sigma, x, d, func):
        a_j = a
        b_j = b
        stop = False

        while not stop:
            t_j = a_j + (b_j - a_j) / 2

            if self.psi(t_j, sigma, x, d, func) >= 0:
                b_j = t_j

            if self.psi(t_j, sigma, x, d, func) < 0 and \
                    func.gradient(x + t_j * d).dot(d) >= rho * func.gradient(x).dot(d):
                t = t_j
                stop = True

            if self.psi(t_j, sigma, x, d, func) < 0 and \
                    func.gradient(x + t_j * d).dot(d) < rho * func.gradient(x).dot(d):
                a_j = t_j

        return t

    def step_size(self, rho, sigma, x, d, func):
        gamma = 2
        t_i = 1
        stop = False
        i = 0

        while not stop:
            if self.psi(t_i, sigma, x, d, func) >= 0:
                a = 0
                b = t_i

                t_i = self.wolfe_powell_rule_phase_B(a, b, rho, sigma, x, d, func)

            if self.psi(t_i, sigma, x, d, func) < 0 and \
                    func.gradient(x + t_i * d).dot(d) >= rho * func.gradient(x).dot(d):
                t = t_i
                stop = True

            if self.psi(t_i, sigma, x, d, func) < 0 and \
                    func.gradient(x + t_i * d).dot(d) < rho * func.gradient(x).dot(d):
                i += 1
                t_i = gamma * t_i

        return t

    def optimize(self, x_0, H_0, rho, sigma, epsilon, func):
        x = x_0
        H = H_0
        while self.stopping_criteria(x, func, epsilon):
            nabla_x = -func.gradient(x)

            inverse = np.linalg.inv(H)
            d = inverse.dot(nabla_x)

            step_size = self.step_size(rho=rho,
                                       sigma=sigma,
                                       x=x,
                                       d=d,
                                       func=func)

            x_new = x + step_size * d
            s = x_new - x
            y = func.gradient(x_new) - func.gradient(x)

            H = H + (np.outer(y, y.T) / np.dot(y.T, s)) - np.outer(H.dot(s), s.T).dot(H) / (np.dot(np.dot(s.T, H), s))

            x = x_new
            self.iterations += 1

        return x

    def stopping_criteria(self, x, func, epsilon):
        return np.linalg.norm(func.gradient(x)) >= epsilon


# -*- coding: utf-8 -*-
"""
Created on Tue Aug 14 16:43:29 2018

Problem:
    If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.
    Find the sum of all the multiples of 3 or 5 below 1000.

Answer: 233168

@author: tlane
"""
import numpy as np

#Generate a multiplication array
mult = np.array(range(1,350))

#Generate arrays of multiples of 3's and 5's
threes = 3 * mult
fives = 5 * mult

#Make list of multiples that are below 1000
nums = list()

for num in threes:
    if num < 1000:
        nums.append(num)

for num in fives:
    if num < 1000:
        nums.append(num)

#Sum all multiples less than 1000
nums = list(set(nums)) #removes duplicates
total = sum(nums)
print(total)


import numpy as np
from numpy.linalg import svd, norm, qr
from numpy import tensordot

def unfold(T, dim):
    """
    Unfolds a tensor T into a matrix, taking the dimension "dim" of T as the first dimension of the matrix,
    and flattening all the other dimensions into the other one dimension of the matrix.

    dim starts from 0.

    :param T: a tensor .
    :type T: numpy.ndarray
    :param dim: the dimension based on which the unfolding is made
    :type dim: int
    :returns: 2D numpy.array -- a matricisation of T.
    """
    Tm=np.moveaxis(T, dim, 0)
    return Tm.reshape(T.shape[dim],-1)

def nModeProduct(T, M, n):
    """
    n-Mode product of a tensor T  and a matrix M,  the summation is made along the nth dim.
    definition in paper "A MULTILINEAR SINGULAR VALUE DECOMPOSITION" by LIEVEN DE LATHAUWER , BART DE MOOR , AND JOOS VANDEWALLE

    For example, n with value 0, 1, or 2, would specify the 1st, 2nd or 3rd dim of the tensor T.
    For the matrix M, this function always take the second dimension, as if to multiply T by M on the left side.

    :param T: a tensor .
    :type T: numpy.ndarray
    :param M: a matrix
    :type M: numpy.array
    :param n: serial number of a dimension of T along which the summation is made.
    :type n: int
    :returns: numpy.ndarray -- a result tensor .
    """

    P=tensordot(T, M, axes=([n], [1]))
    return np.rollaxis(P, len(T.shape)-1, n)

def subTensor(T, k=None, index=None):
    """
    extract a sub-tensor t = T[:k,:k,:k,...] or t = T[index].

    :param T: a tensor .
    :type T: numpy.ndarray
    :param k: a list of integer k, indices larger than k is to be truncated. if "index" presents, this argument is overridden.
    :type k: list
    :param index: a list of list of integer indicating extracted indices on every dimension.
    :type index: list
    :returns: numpy.ndarray -- a sub-tensor .
    """
    if k.any() is None and index.any() is None:
        return T
    elif index is not None:
        return T[np.ix_(*index)]
    else:
        if isinstance(k, int): # if only one integer is assigned to k
            k=k*np.ones((len(T.shape),), dtype=int)

        index=[None]*len(k)
        for i in range(len(k)):
            index[i]=list(range(k[i]))

        return T[np.ix_(*index)]

def HOSVD(A, k=None, tol=None):
    """
    High order svd of d-dim tensor A. so that A = S (*1) u1 (*2) u2 (*3) u3 ... (*d) ud,
    "(*n)" means n-mode product. S is the core, u1,u2,u3, ... are orthogonal basis.
    definition in paper "A MULTILINEAR SINGULAR VALUE DECOMPOSITION"
    by LIEVEN DE LATHAUWER , BART DE MOOR , AND JOOS VANDEWALLE

    :param A: a full tensor .
    :type A: numpy.ndarray

    :param k: rank for the truncation.
    :type k: numpy.list of integer or a single integer

    :param tol:  error torlerance of the truncation
    :type tol: numpy.float

    :returns: numpy.ndarray -- the core tensor S,
              numpy.list    -- a list of array containing basis
    """

    d=len(A.shape)

    if d==2:
        u, s, vt=svd(A, full_matrices=False)
        U=[u, vt.T]
        S=np.diag(s)
    else:
        U=[None]*d
        for j in range(0, d):
            U[j], s, vt=svd(unfold(A, j), full_matrices=False)

        S=A.copy()
        for i in range(0, d):
            S=nModeProduct(S, U[i].T, i)

    if k is not None:
        if isinstance(k, int): # if only one integer is assigned to k
            k=k*np.ones((len(A.shape),), dtype=int)

        S=subTensor(S, k=k)
        for j in range(0, d):
            U[j]=U[j][:, :k[j]]

    return S, U

def fast_qr(A):
    """
    This is a recursive partioned QR. about two times faster than QR for tall matrices
    """
    N,M=A.shape
    if N < M:
        Q, R =qr(A)  # not tall matrices, use normal qr

    elif M>16: # the minimal partition size is 32
        R=np.zeros((M,M))

        k= np.ceil(M/2).astype(int)
        qa, R[:k, :k]=fast_qr(A[:,:k])

        R[:k,k:] = np.dot(qa.T, A[:,k:])

        qb, R[k:, k:]=fast_qr(A[:,k:]-np.dot(qa, R[:k,k:]))

        Q = np.hstack((qa, qb))
    else: # reach the smallest partition size, no more partitions, use normal qr
        Q, R =qr(A)

    return Q, R

def new_expand_dims(a, axes):
    """
    This is a new version of extend_dims() function of numpy, it extend multiple dims rather than one.
    """
    # if int is passed, retain the same behaviour
    if type(axes) == int:
        return np.expand_dims(a, axes)
    # insert axes to given indices
    for ax in sorted(axes):
        a = np.expand_dims(a, ax)
    return a

if __name__=='__main__':

    # A = np.random.rand(7,7)
    A=np.arange(49).reshape((7, 7))
    print(A)

    S, U=HOSVD(A)
    print(S)
    print(U)
    print(norm(A-np.dot(U[0], np.dot(S, U[1].T))))

    S2, U2=HOSVD(A, 5)
    print(S2)
    print(U2)
    print(norm(A-np.dot(U2[0], np.dot(S2, U2[1].T))))
#    S,U=HOSVD2(A)
#    print S
#    print U
#    print norm(A-np.dot(U[0], np.dot(S, U[1].T)))

    print('END')


import numpy as np
import matplotlib.pyplot as plt

def step_function(x):
    '''
    계단 함수
    1 (0 < x)
    0 (0 >= x)
    '''
    return np.array(x>0, dtype=np.int)

def sigmoid(x):
    '''
    시그모이드
     0~1 사이의 연속적인 값 반환
    '''
    return 1/(1+np.exp(-x))

def relu(x):
    '''
    렐루
     x (0 < x)
     0 (0 >= x)
    '''
    return np.maximum(0,x)

def identity_function(x):
    '''
    regression 출력층에 사용
     입력을 그대로 반환
    '''
    return x

def softmax(x):
    '''
    classification 출력층에 사용
     출력층의 입력 전체를 입력으로 요구함
     출력의 합은 1이 됨 -> 출력을 확률로 해석할 수 있음
    '''
    max_x = np.max(x)
    exp_x = np.exp(x - max_x)
    sum_exp_x = np.sum(exp_x)
    y = exp_x / sum_exp_x
    return y

if __name__ == '__main__':
    '''
    plot
    '''
    # x=np.arange(-5, 5, 0.1)
    # y=relu(x)
    # plt.plot(x,y)
    # plt.ylim(-0.1, 1.1)
    # plt.show()

    '''
    softmax 내용
    '''
    # a = np.array([.3, 2.9, 4.0])
    # exp_a=np.exp(a)
    # print(exp_a)
    # sum_exp_a = np.sum(exp_a)
    # print(sum_exp_a)
    # y = exp_a / sum_exp_a
    # print(y)
    # print(softmax(a))

    a = np.array([1010, 1000, 990])
    # print(np.exp(a)/np.sum(np.exp(a)))  # [ nan  nan  nan] -> RuntimeWarning: overflow encountered in exp
    c = np.max(a)   # 1010
    print(a - c)
    print(np.exp(a-c)/np.sum(np.exp(a-c)))  # overflow 해결


example = """16,1,2,0,4,2,7,1,2,14"""

import numpy as np

def preprocess (data :str)-> list:
    return [int(x) for x in data.split(',')]

def min_fuel(crabs: list) -> int:
    crabs = sorted(crabs)
    med = crabs[len(crabs)//2]
    return sum(abs(x-med) for x in crabs)

crabs = preprocess(example)
min_fuel(crabs=crabs) == 37

# Part-2
def min_fuel_2(crabs: list) -> int:
    scores = list()
    # The minimum fuel location seems to be shifting from the median to the mean as distance is more costly now (squared)
    med = np.median(crabs)
    print(f"The Median: {med}")
    avg = np.mean(crabs)
    print(f"The Average: {avg}")
    # Want to scan ~20% of the max-min range around the rounded mean value (part-2 data works with a 1% scan smallest)
    pct = 0.20
    space = (max(crabs)-min(crabs))*(pct/2)
    space = round(space)
    for x in range(int(avg)-space, int(avg)+space):
        s = 0
        for crab in crabs:
            s += abs(x-crab)*(abs(x-crab)+1)/2
        scores.append(int(s))
    print("The location for the least fuel is {}".format(int(avg)-space + np.argmin(scores)) )
    return min(scores)

assert min_fuel_2(crabs=crabs) == 168

if __name__ == '__main__':
    with open('d7.txt') as f:
        content = f.read()

    CRABS = preprocess(content)
    print("The fuel consumption for the 1st part: {} \n".format(min_fuel(crabs= CRABS)))
    print("The fuel consumption for the 2nd part: {}".format(min_fuel_2(crabs= CRABS)))


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

sns.set_context('paper')
sns.set_style('whitegrid')

def newtons_method(f, fprime, guess, epsilon=1e-10, max_iter=50):
    approx = np.zeros(max_iter + 1)
    approx[0] = guess
    iterations = 0
    while iterations < max_iter:
        approx[iterations + 1] = approx[iterations] - f(approx[iterations]) / fprime(approx[iterations])
        if np.abs(approx[iterations + 1] - approx[iterations]) < epsilon:
            break
        iterations += 1

    return approx[:iterations+1], iterations

def f(x):
    return 54 * x**6 + 45 * x**5 - 102 * x**4 - 69 * x**3 + 35 * x**2 + 16 * x - 4

def fp(x):
    return 324 * x**5 + 225 * x**4 - 408 * x**3 - 207 * x**2 + 70 * x + 16

def g(x):
    const = 1 if (x-3/4) > 0 else -1
    return const * np.power(abs(1 - 3/(4*x)),1/3)

def gp(x):
    const = 1 if (x-3/4) > 0 else -1
    return const * np.power(abs(1-3/(4*x)),-2/3) * 1/(4*x**2)

x0 = 0.7500000001
approx, iterations = newtons_method(g, gp, x0)

# zero = lambda x: 0 * x
# x = np.linspace(-1.4,1.3,100)

# plt.plot(x, f(x))
# plt.plot(x, zero(x), 'k--')
# plt.xlim([-1.4,1.3])
#
# plt.title('Plot of $f(x) = 54x^6 + 45 x^5 - 102x^4 - 69x^3 + 35x^2 + 16x - 4$')
# plt.xlabel('$x$')
# plt.ylabel('$y$')
#
# plt.savefig('plot0.png', figsize=(4,4), transparent=True, bbox_inches='tight')

x = [-1.5, -0.7, 0.2, 0.5, 1.2]
results = [newtons_method(f, fp, x0) for x0 in x]

for i, x0 in enumerate(x):
    print('{:0.1f} & {:0.6f} & {:d} & {:0.6f} \\\\'.format(x0,
        results[i][0][-1],
        results[i][1],
        fp(results[i][0][-1])))

# plt.figure(figsize=(8,6))

# plt.scatter(np.arange(0,51), np.abs(approx))
#
# plt.semilogy()
# plt.xlim([0,30])
#
# plt.title('Absolute iterates of Newton\'s method for $f(x) = (1 - 3/(4x))^{1/3}$.')
# plt.ylabel('Absolute iterate approximation')
# plt.xlabel('Iteration')
#
# plt.savefig('plot.png', figsize=(4,4), transparent=True, bbox_inches='tight')


from sympy import *

if __name__ == '__main__':
    w = symbols('w')
    x = symbols('x')
    y = symbols('y')

    f = w * x

    cost = 1/2*(y-f)**2
    print ('Linear Regression:', diff(cost, w))
    # Linear Regression: x*(-y + (-y + 1)*exp(w*x))/(exp(w*x) + 1)
    # Equals to: x * (y - wx)

    sigmoid = 1 / (1 + exp(-f))
    cost = -y*log(sigmoid)-(1-y)*log(1-sigmoid)
    print ('Logistic Regression:', simplify(diff(cost, w)))
    # Logistic Regression: x*(-y + (-y + 1)*exp(w*x))/(exp(w*x) + 1)
    # Equals to: x * (y - sigmoid(wx))


import numpy as np
from matplotlib import pyplot as plt
from random import random as rd
def predictValue(X,theta):
    return np.sum(predict(X,theta))

def predict(X,theta):
    return X@theta

def hypothesis(X,Y,theta):
    X = np.append([[1]]*np.size(X,0),X,1)
    return (np.transpose(theta)@X - Y)

def costFunction(X,Y,theta):
    cost = X@theta - Y
    J = np.sum(np.transpose(cost)@cost)
    return J*(1/2*np.size(X,0))

    # def valueCostFunction(X,Y,theta):
    #     X = np.append([[1]]*np.size(X,0),X,1)
    #     m = np.size((Y,0))
    #     n = len(X[0,:])
    #     J = 0
    #     for i in range(1,m):
    #         J += np.sum(hypothesis(X,Y,theta)**2)
    #     J /= 2*m
    #     return J
    # def deritativeCostFunction(X,Y,theta):
    #     pass

def Normalization(x):
    _max = np.empty((1,x.shape[1]))
    for i in range(x.shape[1]):
        _max[0,i] = max(abs(x[:,i].max()),abs(x[:,i].min()))
    for i in range (x.shape[1]):
        x[:,i] = x[:,i] / max(abs(x[:,i].max()),abs(x[:,i].min()))
    return x,_max
def GradientDescent(X,Y, alpha = 0.0005,iter = 10000):
    X, _maxX = Normalization(X)
    Y, _maxY = Normalization(Y)
    X = np.append([[1]]*np.size(X,0),X,1) #check
    print('_maxX:\n',_maxX)
    m = np.size(X,0) #check
    n = np.size(X,1)
    J_hist = np.zeros((iter,2))
    theta = np.array([[0]]*n)
    preCost = costFunction(X,Y,theta)

    for i in range(250):
        theta = theta - (alpha / m) * (np.transpose(X)@(X@theta - Y))
        cost = costFunction(X,Y,theta)
        if np.round(cost,15) == np.round(preCost,15):
            print('Found optimal value of costFunction at {} is {}'.format(i,cost))
            #thêm tất cả các index còn lại sau khi break
            J_hist[i:,0] = range(i,iter)
            #giá trị J sau khi break sẽ như cũ trong những điểm còn lại
            J_hist[i:,1] = cost
            break
        else:
            J_hist[i,1] = cost
            J_hist[i,0] = i
            preCost = cost
    #print(theta)
    for i in range(_maxX.shape[1]):
        theta[1:,i] *= _maxX[0,i]
    #print(theta)

    yield theta
    yield J_hist


from numpy import linspace, arcsin as asin, pi
from scitools.std import plot, savefig, hold

def tilde_w(w, dt):
    return (2./dt)*asin(w*dt/2.)

def plot_phase_error():
    w = 1  # relevant value in a scaled problem
    m = linspace(1, 101, 101)
    period = 2*pi/w
    dt_values = [period/num_timesteps_per_period
                 for num_timesteps_per_period in (4, 8, 16, 32)]
    for dt in dt_values:
        e = m*2*pi*(1./w - 1/tilde_w(w, dt))
        plot(m, e, '-', title='peak location error (phase error)',
             xlabel='no of periods', ylabel='phase error')
        hold('on')
    savefig('phase_error.png')

plot_phase_error()


import numpy

def cyclic_swap(x, i, j, k, xhat):
    x[i], x[j], x[k] = x[j], x[k], xhat

def f(x):
    return x**4 - 2*x**3 + 3*x**2 - 2*x + 1

## it is a minimization method that does not use the gradient
def parabolic_search(f, a, b, tol, maxiter=100):
    """
    Successive parabolic interpolation is a technique
    for finding the extremum (minimum or maximum) of a
    continuous unimodal function by successively fitting parabolas
    (polynomials of degree two) to a function of one variable at three unique points or,
    in general, a function of n variables at 1+n(n+3)/2 points,
    and at each iteration replacing the "oldest" point with the extremum of the fitted parabola.
    # https://web.engr.oregonstate.edu/~webbky/MAE4020_5020_files/Section%205%20Root%20Finding%20and%20Optimization.pdf
    # MATH 381
    """
    c = (a + b)/2
    inds = array([f(a), f(b), f(c)]).argsort()
    x = array([a, b, c])[inds]
    y = array([f(xi) for xi in x])
    n = 0
    while absolute(x[-1] - x[1]) > tol and n < maxiter:
        p = (x[2] - x[0])**2*(y[2] - y[1]) - (x[2] - x[1])**2*(y[2] - y[0])
        q = ((x[2] - x[0])*(y[2] - y[1]) - (x[2] - x[1])*(y[2] - y[0]))
        xhat = x[2] - 0.5*(p/q)
        cyclic_swap(x, 0, 1, 2, xhat)
        cyclic_swap(y, 0, 1, 2, f(xhat))
        n += 1
    if n >= maxiter:
        print("max iterations reached without convergence")
    return x[-1]

# example
tol = 10.**-4
xmin = parabolic_search(f, -pi, 2*pi, tol, 1000)


import numpy as np

def running_mean(x):
    """
    Calculate running average
    Initialize M1 = x1 and S1 = 0.

    For subsequent x‘s, use the recurrence formulas

    Mk = Mk-1+ (xk – Mk-1)/k
    Sk = Sk-1+ (xk – Mk-1)*(xk – Mk).

    For 2 ≤ k ≤ n, the kth estimate of the variance is s2 = Sk/(k – 1).
    """
    out = np.zeros((x.size,3))
    for i in range(x.size):
        if i == 0:
            out[i,:] = (x[i],0,0)
        else:
            Mi = out[i-1,0] + (x[i] - out[i-1,0])/(i+1)
            Si = out[i-1,2] + (x[i] - out[i-1,0])*(x[i] - out[i,0])
            out[i,:] = (Mi,np.sqrt(np.abs(Si)/i**2),Si)
    return out[:,:2]

def runavr(x):
    out = np.zeros((x.size-1,3))
    for i in range(x.size-1):
        if i == 0:
            out[i,:] = (x[i],0,0)
        else:
            Mi = out[i-1,0] + (x[i] - out[i-1,0])/(i+1)
            Si = out[i-1,2] + (x[i] - out[i-1,0])*(x[i] - out[i,0])
            out[i,:] = (Mi,np.sqrt(Si/i**2),Si)
    return out


import math
import numpy as np
from numpy import random
import pandas as pd
import matplotlib.pyplot as plt
from cycler import cycler
from scipy.integrate import odeint, solve_ivp
plt.style.use('seaborn-whitegrid')

def OU_process():
    '''
    Solve the Langevin equation (describes time evolution of Brownian motion) using the Ornstein-Uhlenbeck process and the Euler-Maruyama method.
    '''

    mu = 10.0    # mean
    sigma = 1.0    # standard deviation
    tau = 0.05   # time constant (mean free time)

    T = 1.0    # total time
    dt = 0.001   # time step
    n = int(T/dt)    # number of time steps
    t = np.linspace(0.0, T, n)    # create time list (from 0 to T with step size T/N)

    x = np.zeros(n)     # create array to hold x values

    ### Define renormalised variables
    sigma_bis = sigma * np.sqrt(2.0/tau)
    sqrtdt = np.sqrt(dt)

    ### Implement the Euler-Maruyama method
    for i in range(n - 1):
        x[i+1] = x[i] + dt*(-(x[i] - mu) / tau) + (sigma_bis * sqrtdt * np.random.randn())

    fig, ax = plt.subplots(1, 1, figsize=(8, 4))
    ax.plot(t, x, lw=2)
    plt.show()

    ### Calculate estimated distribution
    ntrials = 10000
    X = np.zeros(ntrials)

    ### Create bins for the histograms.
    bins = np.linspace(-2., 14., 100)
    fig, ax = plt.subplots(1, 1, figsize=(8, 4))

    for i in range(n):
        # Update the process independently for all trials
        X += dt * (-(X - mu) / tau) + \
            sigma_bis * sqrtdt * np.random.randn(ntrials)

        # Display the histogram for various points in time
        if i in (5, 50, 500):
            hist, _ = np.histogram(X, bins=bins)
            ax.plot((bins[1:] + bins[:-1]) / 2, hist,
                    {5: '-', 50: '.', 500: '-.', }[i],
                    label=f"t={i * dt:.2f}")

        ax.legend()
    plt.show()

def langevin(t, inicon):

  m = 1.5
  k = 0.000001
  R = np.random.normal(0,1)

  x0 = inicon[0]
  v0 = inicon[1]

  dvdt = -m*v0 + k*R

  return [v0, dvdt]   # returns v0 as x_n+1

t = np.linspace(0, 1000, 1000000)

output = solve_ivp(langevin, [0, 1000], [0, 1], t_eval=t, method='RK45')

fig, ax = plt.figure(), plt.axes()
ax.plot(t, output.y[0])
ax.plot(t, output.y[1])
plt.show()

### Call function
# OU_process()


import numpy as np
import pandas as pd

# Numerical Analysis with Numpy Arrays

# 1. Create your first array with the elements [1,22.4,5,35,4,6.7,3,8,40] and print it.
# Experiment what the following functions do: ndim, shape, size and dtype:

array = np.array([1, 22.4, 5, 35, 4, 6.7, 3, 8, 40])

print('Answer : 01')
print(array)
print('Ndim value:')
print(array.ndim)
print('shape value:')
print(array.shape)
print('size value:')
print(array.size)
print('dtype value:')
print(array.dtype)

# 2. Create your first matrix with the elements [['a', 'b'],['c', 'd'],[3, 3]] and print it.
# Experiment what the following functions do: ndim, shape, size and dtype:

array2 = np.array([['a', 'b'],['c', 'd'],[3, 3]])

print('Answer : 02')
print(array2)
print('Ndim value:')
print(array2.ndim)
print('shape value:')
print(array2.shape)
print('size value:')
print(array2.size)
print('dtype value:')
print(array2.dtype)

# 3. Create numpy 1 dimension array using each of the functions arange and rand

array3 = np.arange(10)
array4 = np.random.rand(10)*10

print('Answer : 03')
print(array3)
print(array4)

# 4. Create numpy 2 dimensions matrix using each of the functions zeros and rand

matrix = np.zeros((2, 2))
matrix2 = np.random.rand(2, 2)

print('Answer : 04')
print(matrix)
print(matrix2)

# 5. Create an array containing 20 times the value 7. Reshape it to a 4 x 5 Matrix

array5 = np.ones(20)*7
matrix3 = array5.reshape(4,5)

print('Answer : 05')
print(array5)
print(matrix3)

# 6. Create a 6 x 6 matrix with all numbers up to 36, then print:
# only the first element on it
# only the last 2 rows for it
# only the two mid columns and 2 mid rows for it
# the sum of values for each column

matrix4 = np.arange(36)+1
matrix4 = matrix4.reshape(6,6)

print('Answer : 06')
print(matrix4)

print('sum of each column')
print(np.sum(matrix4,axis=0))

print('First element')
print(matrix4[0,0])

print('last 2 rows')
print(matrix4[4:,:])

print('two mid columns and 2 mid rows')
print(matrix4[2:4,2:4])


import scipy.stats as st
import scipy as sp

def tauchenhussey(N,mu,rho,sigma, baseSigma):
	"""
	Function tauchenhussey

	Purpose:    Finds a Markov chain whose sample paths
				approximate those of the AR(1) process
					z(t+1) = (1-rho)*mu + rho * z(t) + eps(t+1)
				where eps are normal with stddev sigma

	Format:     {Z, Zprob} = TauchenHussey(N,mu,rho,sigma,m)

	Input:      N         scalar, number of nodes for Z
		    mu        scalar, unconditional mean of process
		    rho       scalar
		    sigma     scalar, std. dev. of epsilons
		    baseSigma scalar, std. dev. used to calculate Gaussian
			quadrature weights and nodes, i.e. to build the
			grid. I recommend that you use
                        baseSigma = w*sigma +(1-w)*sigmaZ where sigmaZ = sigma/sqrt(1-rho^2),
				and w = 0.5 + rho/4. Tauchen & Hussey recommend
				baseSigma = sigma, and also mention baseSigma = sigmaZ.

	Output:     Z       N*1 vector, nodes for Z
				Zprob   N*N matrix, transition probabilities

	Author:		Benjamin Tengelsen, Brigham Young University (python)
				Martin Floden, Stockholm School of Economics (original)
				January 2007 (updated August 2007)

	This procedure is an implementation of Tauchen and Hussey's
	algorithm, Econometrica (1991, Vol. 59(2), pp. 371-396)
	"""

	Z     = sp.zeros((N,1))
	Zprob = sp.zeros((N,N))
	[Z,w] = gaussnorm(N,mu,baseSigma**2)
	for i in range(N):
		for j in range(N):
			EZprime    = (1-rho)*mu + rho*Z[i]
			Zprob[i,j] = w[j] * st.norm.pdf(Z[j],EZprime,sigma) / st.norm.pdf(Z[j],mu,baseSigma)

	for i in range(N):
		Zprob[i,:] = Zprob[i,:] / sum(Zprob[i,:])

	return Z.T,Zprob

def gaussnorm(n,mu,s2):
	"""
	Find Gaussian nodes and weights for the normal distribution
	n  = # nodes
	mu = mean
	s2 = variance
	"""
	[x0,w0] = gausshermite(n)
	x = x0*sp.sqrt(2.*s2) + mu
	w = w0/sp.sqrt(sp.pi)
	return [x,w]

def gausshermite(n):
	"""
	Gauss Hermite nodes and weights following 'Numerical Recipes for C'
	"""

	MAXIT = 10
	EPS   = 3e-14
	PIM4  = 0.7511255444649425

	x = sp.zeros((n,1))
	w = sp.zeros((n,1))

	m = int((n+1)/2)
	for i in range(m):
		if i == 0:
			z = sp.sqrt((2.*n+1)-1.85575*(2.*n+1)**(-0.16667))
		elif i == 1:
			z = z - 1.14*(n**0.426)/z
		elif i == 2:
			z = 1.86*z - 0.86*x[0]
		elif i == 3:
			z = 1.91*z - 0.91*x[1]
		else:
			z = 2*z - x[i-1]

		for iter in range(MAXIT):
			p1 = PIM4
			p2 = 0.
			for j in range(n):
				p3 = p2
				p2 = p1
				p1 = z*sp.sqrt(2./(j+1))*p2 - sp.sqrt(float(j)/(j+1))*p3
			pp = sp.sqrt(2.*n)*p2
			z1 = z
			z = z1 - p1/pp
			if sp.absolute(z-z1) <= EPS:
				break

		if iter>MAXIT:
			error('too many iterations'), end
		x[i,0]     = z
		x[n-i-1,0] = -z
		w[i,0]     = 2./pp/pp
		w[n-i-1,0] = w[i]

	x = x[::-1]
	return [x,w]


import datetime

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

infected_today = 15160
infected_two_weeks_ago = 653

population_size = 83042200
initially_infected = infected_today - infected_two_weeks_ago
initially_recovered = infected_two_weeks_ago
initially_susceptible = population_size - initially_infected - initially_recovered
contacts_per_person_per_day = 8
infection_rate_per_contact = 0.06
days_infectious = 5

beta = infection_rate_per_contact * contacts_per_person_per_day
gamma = 1 / days_infectious
R0 = beta / gamma

print("Contacts per person per day:", contacts_per_person_per_day)
print("Infection rate per contact:", infection_rate_per_contact)
print("Days infectious:", infection_rate_per_contact)
print("Beta", beta)
print("Gamma", gamma)
print("R0", R0)

# A grid of time points (in days)
plot_t = np.array(
    [datetime.date.today() + datetime.timedelta(days=i) for i in range(150)]
)
t = np.arange(150)

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

# Initial conditions vector
y0 = initially_susceptible, initially_infected, initially_recovered
# Integrate the SIR equations over the time grid, t.
ret = odeint(deriv, y0, t, args=(population_size, beta, gamma))
S, I, R = ret.T

# Plot the data on three separate curves for S(t), I(t) and R(t)
fig = plt.figure(facecolor="w")
ax = fig.add_subplot(111, axisbelow=True)
ax.plot(plot_t, S / 1000, alpha=0.5, lw=2, label="Susceptible")
ax.plot(plot_t, I / 1000, alpha=0.5, lw=2, label="Infected")
ax.plot(plot_t, R / 1000, alpha=0.5, lw=2, label="Recovered with immunity")
ax.set_xlabel("Time /days")
ax.set_ylabel("Number (1000s)")
# ax.set_ylim(0, 1.2)
ax.yaxis.set_tick_params(length=0)
ax.xaxis.set_tick_params(length=0)
ax.grid(b=True, which="major", c="w", lw=2, ls="-")
legend = ax.legend()
legend.get_frame().set_alpha(0.5)
for spine in ("top", "right", "bottom", "left"):
    ax.spines[spine].set_visible(False)
plt.show()

plt.plot(
    plot_t[1:],
    np.diff(I + R) / population_size,
    alpha=0.5,
    lw=2,
    label="Daily new infected",
)
plt.show()


from typing import Callable

import scipy.special as sci

def forward(order: int, f: Callable[[float], float], x: float, h: float, exact: bool = False) -> float:
    """Use forward finite difference formula of order `order` to compute the derivative of `f` at `x`.

    Args:
        order (int): Order of the derivate. (first, second ...).
        f (Callable[[float], float]): Function for which the derivative will be computed.
            Only one argument will be passed to it, `x`.
        x (float): Point at which the derivative will be computed.
        h (float): Error of the approximation # TODO: Is it the error?
        exact (bool, optional): Set the precision of the method.
            If False, floating comma numbers are used. Set to True to use long numbers. Defaults to False.

    Note:
        See more of `exact` parameter in https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.comb.html

    Returns:
        float: Derivative of order `order` of `f` evaluated at `x`.
    """
    return sum([(-1)**(order-k) * sci.comb(order, k, exact) * f(x + k*h) for k in range(order+1)]) / h**order

def backward(order: int, f: Callable[[float], float], x: float, h: float, exact: bool = False) -> float:
    """Use backward finite difference formula of order `order` to compute the derivative of `f` at `x`.

    Args:
        order (int): Order of the derivate. (first, second ...).
        f (Callable[[float], float]): Function for which the derivative will be computed.
            Only one argument will be passed to it, `x`.
        x (float): Point at which the derivative will be computed.
        h (float): Error of the approximation # TODO: Is it the error?
        exact (bool, optional): Set the precision of the method.
            If False, floating comma numbers are used. Set to True to use long numbers. Defaults to False.

    Note:
        See more of `exact` parameter in https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.comb.html

    Returns:
        float: Derivative of order `order` of `f` evaluated at `x`.
    """
    return sum([(-1)**(k) * sci.comb(order, k, exact) * f(x - k*h) for k in range(order+1)]) / h**order

def central(order: int, f: Callable[[float], float], x: float, h: float, exact: bool = False) -> float:
    """Use central finite difference formula of order `order` to compute the derivative of `f` at `x`.

    Args:
        order (int): Order of the derivate. (first, second ...).
        f (Callable[[float], float]): Function for which the derivative will be computed.
            Only one argument will be passed to it, `x`.
        x (float): Point at which the derivative will be computed.
        h (float): Error of the approximation # TODO: Is it the error?
        exact (bool, optional): Set the precision of the method.
            If False, floating comma numbers are used. Set to True to use long numbers. Defaults to False.

    Note:
        See more of `exact` parameter in https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.comb.html

    Returns:
        float: Derivative of order `order` of `f` evaluated at `x`.
    """
    return sum([(-1)**(k) * sci.comb(order, k, exact) * f(x - (order/2 - k)*h) for k in range(order+1)]) / h**order

if __name__ == '__main__':
    pass


from math import sqrt

from scipy.stats import t
from numpy import mean

def degrees_of_freedom(s1, s2, n1, n2):
    """
    Compute the number of degrees of freedom using the Satterhwaite Formula

    @param s1 The unbiased sample variance of the first sample
    @param s2 The unbiased sample variance of the second sample
    @param n1 Thu number of observations in the first sample
    @param n2 The number of observations in the second sample
    """
    #degreeoffreedom= numberofobservation-1

    a=s1 /n1
    b=s2 /n2
    c=1/(n1-1)
    d=1/(n2-1)

    degreeoffreedom=(a+b)**2 /( c * a**2 + d * b**2)

    return degreeoffreedom

def unbiased_sample_variance(observations, mean):
    """
    Compute the unbiased sample variance

    @param observations Iterable set of observations
    @param mean The estimated mean
    """
    n=1/ (len(observations) - 1)
    sample_=0
    for i in observations:
        sum0=(i-mean)**2
        sample_=sample_+sum0

    sample_var= n*sample_

    return sample_var

def t_statistic(mean1, mean2, n1, n2, svar1, svar2):
    """
    Compute the t-statistic for the given estimates
    """
    # Complete this funtion
    a=svar1
    b=svar2
    c=a/n1
    d=b/n2
    e= 1/sqrt(c+d)
    t_stat = e*(mean1-mean2)
    return t_stat

def t_test(sample1, sample2):
    """
    Return the two-tailed p-value of a t test with unequal variance for two samples.

    @param sample1 An iterable of the first sample
    @param sample2 An iterable of the second sample
    """
    m1=mean(sample1)
    m2=mean(sample2)
    n1=len(sample1)
    n2=len(sample2)
    s1=unbiased_sample_variance(sample1,m1)
    s2=unbiased_sample_variance(sample2,m2)

    degree=degrees_of_freedom(s1,s2,n1,n2)
    tsta=t_statistic(m1,m2,n1,n2,s1,s2)
    p=2*(1.0-t.cdf(abs(tsta),degree))
    return p

if __name__ == "__main__":
    v1 = [5, 7, 5, 3, 5, 3, 3, 9]
    v2 = [8, 1, 4, 6, 6, 4, 1, 2]

    print("p-value is %f" % t_test(v1, v2))


import numpy as np

#crear un array y y sumarle un número
arr = np.arange(11)
arr + 55    # array([55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65])

#multiplicarlo por un número
arr * 2     # array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18, 20])

#elevarlo al cuadrado
arr ** 2    # array([  0,   1,   4,   9,  16,  25,  36,  49,  64,  81, 100])

#calcular una función
np.tanh(arr)

"""
La salida será

array([ 0.        ,  0.76159416,  0.96402758,  0.99505475,  0.9993293 ,
        0.9999092 ,  0.99998771,  0.99999834,  0.99999977,  0.99999997,  1.        ])

"""

#creamos dos arrays
arr1 = np.arange(0,11)
arr2 = np.arange(20,31)

#los sumamos
arr1 + arr2     # array([20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40])

#multiplicamos
arr1 * arr2     # array([  0,  21,  44,  69,  96, 125, 156, 189, 224, 261, 300])

# >,<
arr1 > arr2

"""
    La salida será

    array([False, False, False, False, False, False, False, False, False,
           False, False], dtype=bool)

"""
# ==
arr1 == arr2 # ¡ojo! los arrays son de integers, no de floats

"""
    La salida será

    array([False, False, False, False, False, False, False, False, False,
       False, False], dtype=bool)
"""

A = np.array([[n+m*10 for n in range(5)] for m in range(5)])
A * A # el termino en ingles es element-wise
"""
array([[   0,    1,    4,    9,   16],
       [ 100,  121,  144,  169,  196],
       [ 400,  441,  484,  529,  576],
       [ 900,  961, 1024, 1089, 1156],
       [1600, 1681, 1764, 1849, 1936]])
"""

v1 = np.arange(0, 5)    # [0 1 2 3 4]
v1 * v1                 # array([ 0,  1,  4,  9, 16])


#------------------------------------------------------------------------------------------------------------#
#Chapter 3 - Decorrelating your data and dimension reduction

#------------------------------------------------------------------------------------------------------------#

#Correlated data in nature
# Perform the necessary imports
import matplotlib.pyplot as plt
from scipy.stats import pearsonr

# Assign the 0th column of grains: width
width = grains[:,0]

# Assign the 1st column of grains: length
length = grains[:,1]

# Scatter plot width vs length
plt.scatter(width, length)
plt.axis('equal')
plt.xlabel('width')
plt.ylabel('length')
plt.title('I suspect that width and length will be correlated.')
plt.show()

# Calculate the Pearson correlation
correlation, pvalue = pearsonr(width,length)

# Display the correlation
print(correlation)

#------------------------------------------------------------------------------------------------------------#
#Decorrelating the grain measurements with PCA
# Import PCA
from sklearn.decomposition import PCA

# Create PCA instance: model
model = PCA()

# Apply the fit_transform method of model to grains: pca_features
pca_features = model.fit_transform(grains)

# Assign 0th column of pca_features: xs
xs = pca_features[:,0]

# Assign 1st column of pca_features: ys
ys = pca_features[:,1]

# Scatter plot xs vs ys
plt.scatter(xs, ys)
plt.axis('equal')
plt.show()

# Calculate the Pearson correlation of xs and ys
correlation, pvalue = pearsonr(xs, ys)

# Display the correlation
print(correlation)

#------------------------------------------------------------------------------------------------------------#

#The first principal component
# Make a scatter plot of the untransformed points
plt.scatter(grains[:,0], grains[:,1])

# Create a PCA instance: model
model = PCA()

# Fit model to points=points
points=model.fit(grains)

# Get the mean of the grain samples: mean
mean = model.mean_

# Get the first principal component: first_pc
first_pc = model.components_[0,:]

# Plot first_pc as an arrow, starting at mean
plt.arrow(mean[0],mean[1], first_pc[0], first_pc[1], color='red', width=0.01)

# Keep axes on same scale
plt.axis('equal')
plt.show()

#------------------------------------------------------------------------------------------------------------#
#Variance of the PCA features
# Perform the necessary imports
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
import matplotlib.pyplot as plt

# Create scaler: scaler
scaler = StandardScaler()

# Create a PCA instance: pca
pca = PCA()

# Create pipeline: pipeline
pipeline = make_pipeline(scaler,pca)

# Fit the pipeline to 'samples'
pipeline.fit(samples)

# Plot the explained variances
features = range(pca.n_components_)
plt.bar(features, pca.explained_variance_)
plt.xlabel('PCA feature')
plt.ylabel('variance')
plt.xticks(features)
plt.show()

#------------------------------------------------------------------------------------------------------------#

#Dimension reduction of the fish measurements
# Import PCA
from sklearn.decomposition import PCA

# Create a PCA instance with 2 components: pca
pca = PCA(n_components=2)

# Fit the PCA instance to the scaled samples
pca.fit(scaled_samples)

# Transform the scaled samples: pca_features
pca_features = pca.transform(scaled_samples)

# Print the shape of pca_features
print(pca_features.shape)

#------------------------------------------------------------------------------------------------------------#
#A tf-idf word-frequency array
# Import TfidfVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer

# Create a TfidfVectorizer: tfidf
tfidf = TfidfVectorizer()

# Apply fit_transform to document: csr_mat
csr_mat = tfidf.fit_transform(documents)

# Print result of toarray() method
print(csr_mat.toarray())

# Get the words: words
words = tfidf.get_feature_names()

# Print words
print(words)

#------------------------------------------------------------------------------------------------------------#
#Clustering Wikipedia part I
# Perform the necessary imports
from sklearn.decomposition import TruncatedSVD
from sklearn.cluster import KMeans
from sklearn.pipeline import make_pipeline

# Create a TruncatedSVD instance: svd
svd = TruncatedSVD(n_components=50)

# Create a KMeans instance: kmeans
kmeans = KMeans(n_clusters=6)

# Create a pipeline: pipeline
pipeline = make_pipeline(svd,kmeans)

#------------------------------------------------------------------------------------------------------------#

#Clustering Wikipedia part II
# Import pandas
import pandas as pd

# Fit the pipeline to articles
pipeline.fit(articles)

# Calculate the cluster labels: labels
labels = pipeline.predict(articles)

# Create a DataFrame aligning labels and titles: df
df = pd.DataFrame({'label': labels, 'article': titles})

# Display df sorted by cluster label
print(df.sort_values('label'))

#------------------------------------------------------------------------------------------------------------#

#------------------------------------------------------------------------------------------------------------#

#------------------------------------------------------------------------------------------------------------#

#------------------------------------------------------------------------------------------------------------#

#------------------------------------------------------------------------------------------------------------#

#------------------------------------------------------------------------------------------------------------#

#------------------------------------------------------------------------------------------------------------#

#------------------------------------------------------------------------------------------------------------#

18.Unsupervised Learning in Python/Chapter 3 - Decorrelating your data and dimension reduction.py


# Bimodal distribution (mixture of two 1d Gaussians)
# Based on https://github.com/probml/pmtk3/blob/master/demos/bimodalDemo.m

import numpy as np
import matplotlib.pyplot as plt
import pyprobml_utils as pml

from scipy.stats import norm

# Define two normal distrubutions and their corresponding weights.
mu = [0, 2]
sigma = [1, 0.05]
n = [norm(loc=mu[i], scale=sigma[i]) for i in range(2)]
w = [0.5, 0.5]

# Define a set of x points for graphing.
xs = np.linspace(-2, 2*mu[1], 600)

# Combine the two distributions by their weights, evaluated at the x points.
p = sum(w[i] * n[i].pdf(xs) for i in range(2))

# Calculate the mean of the final distribution.
mean_p = np.mean(xs * p)

# Plot the final distribution and its mean.
linewidth = 3
plt.figure()
plt.plot(xs, p, 'black', linewidth=linewidth)
plt.vlines(mean_p, ymin=0, ymax=max(p), color='red', linewidth=linewidth)
pml.savefig('bimodalSpike.pdf')
plt.show()

# Another example, with two modes
mu = [0, 2]
sigma = [0.5, 0.5]
n = [norm(loc=mu[i], scale=sigma[i]) for i in range(2)]
w = [0.5, 0.5]
xs = np.linspace(-2, 2*mu[1], 600)
p = sum(w[i] * n[i].pdf(xs) for i in range(2))

plt.figure()
linewidth = 3
plt.plot(xs, p, 'black', linewidth=linewidth)
pml.savefig('bimodalDistribution.pdf')
plt.show()


# ------------------------------------------------------------------------------------------------------ #
# Author: Anna Lonergan                                                                                #
# Purpose: Script defining Newton-Raphson method for solving systems of linear and nonlinear equations   #
# ------------------------------------------------------------------------------------------------------ #

# Necessary Libraries:
import numpy as np
import numdifftools as nd
import pandas as pd

# Define NR (Newton-Raphson) function
def NR(F, x, tol = 0.000001, maxiter = 50):
    # Newton - Raphson Algorithm
    # Inputs:
    # F - a function. Defines the function(s) of the system, length = n.
    # x - list/array. initial guess for variables. length = n
    # tol - numeric. The error tolerance level. Default is 1e-6
    # maxiter - numeric. Number of maximum iterations to run algorithm. Default is 50.

    # Output:
    # solution - vector of length n which solves the system
    # nrout - data frame with information on norms and iteration number.
    ###############################################################################

    # Generate a dummy variable equal to zero. Dummy will be set to 1 if a solution is found.
    sol_found = 0

    # Create a base array to append results to
    results = np.zeros(len(x)+3)

    # Loop for Newton-Raphson Algorithm
    for iteration in range(1, maxiter + 1):

        # Step 2: Evaluate function at x, called Fval, and its Euclidean norm
        Fval = F(x)
        fnorm = np.linalg.norm(Fval, ord=2) # ord = 2 for Euclidean norm

        # Step 3: Compute the Jacobian Matrix
        jac = nd.Jacobian(F)(x)

        # Step 4: Solve for delta
        delta = np.linalg.solve(jac, -1*Fval)
        dnorm = np.linalg.norm(delta, ord = 2) # ord = 2 for Euclidean norm

        # Store iteration results
        row = np.zeros(len(x)+3)
        row[0] = iteration
        row[1] = fnorm
        row[2] = dnorm
        for i in range(len(x)):
            row[i+3] = x[i]
        results = np.vstack([results, row])

        # Stopping condition 1:
        if abs(fnorm) < tol:
            solution = x
            sol_found = 1
            break
        # Stopping condition 2:
        elif abs(dnorm) < tol:
            solution = x
            sol_found = 1
            break
        # If neither stopping condition is met, repeat steps 2 - 5 with updated x
        # Update x
        x = x + delta

    if sol_found == 1:
        iteration_table = pd.DataFrame(results[1:,0:])
        names1 = np.array(["Iteration #", "F norm", "D norm"])
        names2 = ['']*len(x)
        for i in range(len(x)):
            names2[i] = 'X_'+str(i)
        names = np.append(names1, names2)
        iteration_table.columns = names

        return solution, iteration_table
    else:
        print "Could not find a solution with given parameters."
        return results


#! /usr/bin/env python3
import numpy as np
""" common maths functions that are not provided by default"""

def gaussian(x, mu, sig):
	""" return a Gaussian distribution over x
	inputs:
		x => x axis data of distribution
		mu => the mean value of data
		sig => standard deviation of data
	returns: Gaussian distribution
	"""
	return 1 / (sig * np.sqrt(2 * np.pi)) * np.exp((-1 / 2) * ((x - mu) / sig)**2)

def gaussian_max1(x, mu, sig):
	""" return a Gaussian distribution over x where the pean of the gaussian is 1
	inputs:
		x => x axis data of distribution
		mu => the mean value of data
		sig => standard deviation of data
	returns: Gaussian distribution with a peak of 1
	"""
	return np.exp((-1 / 2) * ((x - mu) / sig)**2)

def pink_power(f, alpha=1, scale=1):
	""" power spectral density for pink noise
	inputs:
		f => frequency
		alpha => exponent characteristic to pink noise
		scale => scaling factor of proportionality
	returns: power spectral density"""
	return scale / f**alpha


import string
import numpy as np
import math
from sympy import Matrix #inverse key

#-------------------------
def letterToNumber(letter):
	return string.ascii_lowercase.index(letter)

def numberToLetter(number):
	return chr(int(number) + 97)
#-------------------------

module = 26 #english alphabet

#raw_message = "act"
raw_message = "attack is to night"
print("raw message: ",raw_message)

message = []

key = np.array([
	[3, 10, 20],
	[20, 9, 17],
	[9, 4, 17]
]) #3x3

key_rows = key.shape[0]
key_columns = key.shape[1]

if key_rows != key_columns:
	raise Exception('key must be square matrix!')

#-------------------------
for i in range(0, len(raw_message)):
	current_letter = raw_message[i:i+1].lower()
	if current_letter != ' ':
		letter_index = letterToNumber(current_letter)
		message.append(letter_index)

#-------------------------
#message must be multiplier of key line count.
#if not append beginning of the message to the end

if len(message) % key_rows != 0:
	for i in range(0, len(message)):
		message.append(message[i])
		if len(message) % key_rows == 0:
			break

#------------------------
#transform message to numpy array. we'll use numpy's matrix multiplication
message = np.array(message)
message_length = message.shape[0]
print("message: ",message)

#transform message array to matrix
message.resize(int(message_length/key_rows), key_rows)
#------------------------

encryption = np.matmul(message, key)
encryption = np.remainder(encryption, module)
print("encrypted text: \n",encryption)

#------------------------

#decryption
#------------------------
print("finding inverse key")
inverse_key = Matrix(key).inv_mod(module)
inverse_key = np.array(inverse_key) #sympy to numpy
inverse_key = inverse_key.astype(float)
print("inverse key: ",inverse_key)

#------------------------
print("validating inverse key. key times inverse key must be idendity matrix")
check = np.matmul(key, inverse_key)
check = np.remainder(check, module)
print("key times inverse key: ",check)
print("it is ",np.allclose(check, np.eye(3)))

#------------------------
print("decryption:")
decryption = np.matmul(encryption, inverse_key)
decryption = np.remainder(decryption, module).flatten()
print("decryption: ",decryption)

decrypted_message = ""
for i in range(0, len(decryption)):
	letter_num = int(decryption[i])
	letter = numberToLetter(decryption[i])
	decrypted_message = decrypted_message + letter

print("decrypted message: ",decrypted_message)


from __future__ import print_function
import matplotlib.pyplot as plt
import numpy as np

"""
Compute lookup table
"""
data_type = 16    # 16 or 32 signed integer
samp_freq = 32000
f_sine = 400

# periods
samp_per = 1./samp_freq
sine_per = 1./f_sine

# compute time instances
t_vals = np.arange(0, sine_per*1, samp_per)
LOOKUP_SIZE = len(t_vals)
n_vals = np.arange(LOOKUP_SIZE)

# compute the sine table
MAX_SINE = 2**(data_type-1)-1   # [-(2*data_type-1), 2**(data_type-1)]
w_mod = 2*np.pi*(f_sine/samp_freq)
sine_table = np.sin(w_mod*n_vals) * MAX_SINE
if data_type == 16:
    sine_table = sine_table.astype(np.int16)
    print_type = np.uint16
    print_format = '0x%04x'
elif data_type == 32:
    sine_table = sine_table.astype(np.int32)
    print_type = np.uint32
    print_format = '0x%08x'
else:
    raise ValueError("Invalid data type!")

"""
Print C code.
"""
print('#define SINE_TABLE_SIZE', str(LOOKUP_SIZE))
print('#define SIN_MAX', (print_format % MAX_SINE))
if data_type == 16:
    print('const int16_t sine_table[SINE_TABLE_SIZE] = {')
elif data_type == 32:
    print('const int32_t sine_table[SINE_TABLE_SIZE] = {')
print(','.join([print_format % i.astype(print_type) for i in sine_table]))
print('};')

"""
Visualize
"""
plt.figure()
plt.stem(n_vals, sine_table)
plt.grid()
plt.autoscale(enable=True, axis='x', tight=True)
plt.xlabel("Index")
plt.title("Sine table for %d Hz at %d Hz sampling rate" % (f_sine, samp_freq))
plt.show()


import numpy as np

def err_div(x, y, ex, ey):
    Q = x / y
    dQ = np.abs(Q) * np.sqrt((ex / x) ** 2 + (ey / y) ** 2)
    return Q, dQ

def err_multiply(x, y, ex, ey):
    Q = x * y
    dQ = np.abs(Q) * np.sqrt( (ex/x)**2 + (ey/y)**2 )
    return Q, dQ

def err_add(x, y, ex, ey):
    Q = x + y
    dQ = np.sqrt(ex**2 + ey**2)
    return Q, dQ

def err_power_1(x, p, ex):
    """error propogation for
    x^p where p is a constant
    """
    Q = x ** p
    dQ = np.abs(p * x**(p-1) * ex)
    return Q, dQ

def err_power_2(x, p, ex, ep):
    """error propogation for
    x^p where x & p have errors
    """
    Q = x ** p
    dQ_sq = Q**2 * ( (p * ex / x)**2 + (np.log(x) * ep)**2 )
    return Q, dQ_sq**0.5

def log_err(x, dx):
    return dx / (np.log(10) * x)

def log_errorbar(x, dx):
    logxerr = log_err(x,dx)
    logxupp = np.log10(x) + logxerr
    logxlow = np.log10(x) - logxerr
    xupp = 10**logxupp - x
    xlow = x - 10**logxlow
    return xupp, xlow

# 1st moment error is v * sqrt(2) sigma_w / w

# 2nd moment error


#!/usr/bin/env python3
# Marcos del Cueto
# Import libraries
import math
import random
import numpy as np
import matplotlib.pyplot as plt
from sklearn import linear_model
from sklearn.metrics import mean_squared_error, r2_score
from sklearn.kernel_ridge import KernelRidge

# Initialize lists and set random seed
list_x = []
list_y = []
list_x_pred = []
list_y_real = []
random.seed(2020)
# Create database with 21 points following quasi-lienar relation in interval x:[-5,5]
for i in range(-10,11):
    x = i/2
    rnd_number= random.uniform(-1,1)
    y = (x+4)*(x+1)*(x-1)*(x-3) + rnd_number
    list_x.append(x)
    list_y.append(y)
    print(x,y)
# Create list with 1060 points in interval x:[-5,5]
for i in range(-50,51):
    x = 0.1*i
    list_x_pred.append(x)
    list_y_real.append((x+4)*(x+1)*(x-1)*(x-3))
# Transform lists to np arrays
list_x = np.array(list_x).reshape(-1, 1)
list_x_pred = np.array(list_x_pred).reshape(-1, 1)
# Do linear regression using database with 21 points
list_y_pred = []
short_list_y_pred = []
rmse_list = []
# For each of the tested polynomial degree values
for alpha_value in [0.0001,0.1,10000,100000000]:
    krr = KernelRidge(alpha=alpha_value,kernel='polynomial',degree=4)
    krr.fit(list_x,list_y)
    list_y_pred.append(krr.predict(list_x_pred))
    new_y = krr.predict(list_x)
    short_list_y_pred.append(new_y)
    # Print rmse value
    rmse = math.sqrt(mean_squared_error(new_y, list_y))
    rmse_list.append(rmse)
    print('###########################')
    print('Alpha:', alpha_value)
    print('Root-mean-square error: %.1f' % rmse)
# Set axes and labels
fig, axs = plt.subplots(2, 2)
for ax in axs.flat:
    ax.set(xlabel='x', ylabel='y')
# Hide x labels and tick labels for top plots and y ticks for right plots.
for ax in axs.flat:
    ax.label_outer()
# Subplot top-left
axs[0, 0].scatter(list_x, list_y,color='C0')
axs[0, 0].plot(list_x_pred,list_y_pred[0],color='C1')
axs[0, 0].set_title(r'$\alpha = 10^{-4}$')
axs[0, 0].set_xlim(-5.5,5.5)
axs[0, 0].set_ylim(-100,500)
axs[0, 0].annotate(u'$RMSE$ = %.1f' % rmse_list[0], xy=(0.15,0.85), xycoords='axes fraction')
# Subplot top-right
axs[0, 1].scatter(list_x, list_y,color='C0')
axs[0, 1].plot(list_x_pred,list_y_pred[1], color='C1')
axs[0, 1].set_title(r'$\alpha = 10^{-1}$')
axs[0, 1].set_xlim(-5.5,5.5)
axs[0, 1].set_ylim(-100,500)
axs[0, 1].annotate(u'$RMSE$ = %.1f' % rmse_list[1], xy=(0.15,0.85), xycoords='axes fraction')
# Subplot bottom-left
axs[1, 0].scatter(list_x, list_y,color='C0')
axs[1, 0].plot(list_x_pred,list_y_pred[2], color='C1')
axs[1, 0].set_title(r'$\alpha = 10^{4}$')
axs[1, 0].set_xlim(-5.5,5.5)
axs[1, 0].set_ylim(-100,500)
axs[1, 0].annotate(u'$RMSE$ = %.1f' % rmse_list[2], xy=(0.15,0.85), xycoords='axes fraction')
# Subplot bottom-right
axs[1, 1].scatter(list_x, list_y,color='C0')
axs[1, 1].plot(list_x_pred,list_y_pred[3], color='C1')
axs[1, 1].set_title(r'$\alpha = 10^{8}$')
axs[1, 1].set_xlim(-5.5,5.5)
axs[1, 1].set_ylim(-100,500)
axs[1, 1].annotate(u'$RMSE$ = %.1f' % rmse_list[3], xy=(0.15,0.85), xycoords='axes fraction')
# Print plot to file
file_name='Figure_4.png'
plt.savefig(file_name,format='png',dpi=600)
plt.close()


# -*- coding: utf-8 -*-
"""
Created on Tue Nov  9 20:47:01 2021

@author: Oscar
"""

# importing modules
import numpy as np
import matplotlib.pyplot as plt
import math
# ------------------------------------------------------
# inputs
# functions that returns dy/dx
# i.e. the equation we want to solve: dy/dx = - y
def model(y,x):
    dydx = y*(1 - y)
    return dydx
# initial conditions
x0 = 0
y0 = math.exp(-4)/ (math.exp(-4)+1)
# total solution interval
x_final = 10
# step size
h = 0.1
# ------------------------------------------------------
# Euler method

# number of steps
n_step = math.ceil(x_final/h)

# Definition of arrays to store the solution
y_eul = np.zeros(n_step+1)
x_eul = np.zeros(n_step+1)

# Initialize first element of solution arrays
# with initial condition
y_eul[0] = y0
x_eul[0] = x0

# Populate the x array
for i in range(n_step):
    x_eul[i+1] = x_eul[i] + h

# Apply Euler method n_step times
for i in range(n_step):
    # compute the slope using the differential equation
    slope = model(y_eul[i],x_eul[i])
    # use the Euler method
    y_eul[i+1] = y_eul[i] + h * slope
# ------------------------------------------------------
# super refined sampling of the exact solution c*e^(-x)
# n_exact linearly spaced numbers
# only needed for plotting reference solution
# Definition of array to store the exact solution
n_exact = 1000
x_exact = np.linspace(0,x_final,n_exact+1)
y_exact = np.zeros(n_exact+1)
# exact values of the solution
for i in range(n_exact+1):
    y_exact[i] = math.exp(x_exact[i]-4)/(math.exp(x_exact[i]-4) + 1)
# ------------------------------------------------------
# ------------------------------------------------------
# plot results
plt.plot(x_eul, y_eul , 'b.-',x_exact, y_exact ,'r-')
plt.xlabel('x')
plt.ylabel('y(x)')
plt.show()
# ------------------------------------------------------


import pandas as pd
import numpy as np

from datetime import datetime
import pandas as pd

from scipy import optimize
from scipy import integrate

# %matplotlib inline
import matplotlib as mpl
import matplotlib.pyplot as plt
from plotly.tools import mpl_to_plotly
import seaborn as sns

sns.set(style="darkgrid")

mpl.rcParams['figure.figsize'] = (16, 9)
pd.set_option('display.max_rows', 500)

df_analyse=pd.read_csv('data/processed/datetime.csv',sep=';')
df_analyse.sort_values('date',ascending=True).head()

N0=1000000 #max susceptible population
beta=0.4   # infection spread dynamics
gamma=0.1  # recovery rate
# condition I0+S0+R0=N0
I0=df_analyse.Germany[35]
S0=N0-I0
R0=0

def SIR_model_t(SIR,t,beta,gamma):
    ''' Simple SIR model
        S: susceptible population
        t: time step, mandatory for integral.odeint
        I: infected people
        R: recovered people
        beta:

        overall condition is that the sum of changes (differnces) sum up to 0
        dS+dI+dR=0
        S+I+R= N (constant size of population)

    '''

    S,I,R=SIR
    dS_dt=-beta*S*I/N0          #S*I is the
    dI_dt=beta*S*I/N0-gamma*I
    dR_dt=gamma*I
    return dS_dt,dI_dt,dR_dt

def fit_odeint(x, beta, gamma):
    '''
    helper function for the integration
    '''
    ydata = np.array(df_analyse.Germany[35:])
    t=np.arange(len(ydata))
    return integrate.odeint(SIR_model_t, (S0, I0, R0), t, args=(beta, gamma))[:,1]
    # we only would like to get dI

def SIR_model(SIR,beta,gamma):
    ''' Simple SIR model
        S: susceptible population
        I: infected people
        R: recovered people
        beta:

        overall condition is that the sum of changes (differnces) sum up to 0
        dS+dI+dR=0
        S+I+R= N (constant size of population)

    '''

    S,I,R=SIR
    dS_dt=-beta*S*I/N0          #S*I is the
    dI_dt=beta*S*I/N0-gamma*I
    dR_dt=gamma*I
    return([dS_dt,dI_dt,dR_dt])

def SIR_fig():
    I0=df_analyse.Germany[35]
    S0=N0-I0
    R0=0
    SIR=np.array([S0,I0,R0])
    propagation_rates=pd.DataFrame(columns={'susceptible':S0,
                                            'infected':I0,
                                            'recoverd':R0})

    for each_t in np.arange(100):

        new_delta_vec=SIR_model(SIR,beta,gamma)

        SIR=SIR+new_delta_vec

        propagation_rates=propagation_rates.append({'susceptible':SIR[0],
                                                    'infected':SIR[1],
                                                    'recovered':SIR[2]}, ignore_index=True)
    ydata = np.array(df_analyse.Germany[35:])
    t=np.arange(len(ydata))
    # ensure re-initialization
    I0=ydata[0]
    S0=N0-I0
    R0=0
    popt=[0.4,0.1]
    fit_odeint(t, *popt)
    popt, pcov = optimize.curve_fit(fit_odeint, t, ydata)
    perr = np.sqrt(np.diag(pcov))
    fitted=fit_odeint(t, *popt)
    fig, ax = plt.subplots()
    plt.semilogy(t, ydata, 'o')
    plt.semilogy(t, fitted)
    plt.title("Fit of SIR model for Germany cases")
    plt.ylabel("Population infected")
    plt.xlabel("Days")
    #plt.show()
    html_fig = mpl_to_plotly(fig)
    return html_fig


from sympy.solvers import solve
from sympy import Symbol
from sympy import *
import matplotlib.pyplot as plt
import numpy as np
from scipy.sparse import *
from numpy.linalg import inv
from array import *
from scipy import linalg
x=Symbol('x')

# function to find k and f for linear elements
def kfmatixLin(nEle,domainLen,a,c,f):
    xb=[]
    xa=[]
    for i in range(1,nEle+1):
        xb.append(i*(domainLen/nEle))
        xa.append((i-1)*(domainLen/nEle))
    k=[]
    Ftemp=[]
    # for ith element  # NOTE: [[element 1 k's],[element 2 k's], ...]
    for i in range(nEle):
        psi_1 = (xb[i]-x)/(xb[i]-xa[i])
        psi_2 = (x-xa[i])/(xb[i]-xa[i])
        # print(psi_1)
        # print(psi_2)
        k.append([])
        Ftemp.append(integrate(f*psi_1 ,(x, xa[i], xb[i])))
        Ftemp.append(integrate(f*psi_2 ,(x, xa[i], xb[i])))
        k[i].append(integrate( a*diff(psi_1,x)*diff(psi_1,x)+c*psi_1*psi_1,(x, xa[i], xb[i])))
        k[i].append(integrate( a*diff(psi_1,x)*diff(psi_2,x)+c*psi_1*psi_2,(x, xa[i], xb[i])))
        k[i].append(integrate( a*diff(psi_2,x)*diff(psi_1,x)+c*psi_2*psi_1,(x, xa[i], xb[i])))
        k[i].append(integrate( a*diff(psi_2,x)*diff(psi_2,x)+c*psi_2*psi_2,(x, xa[i], xb[i])))

    F=[]
    F.append(Ftemp[0])
    for i in range(0,len(Ftemp)-2,2):
        F.append(Ftemp[i+1]+Ftemp[i+2])
    F.append(Ftemp[len(Ftemp)-1])

    print('k = ',k)
    # print('Ftemp',Ftemp)
    # print('F = ',F)
    # two diagonals of the tridiagonal matrix
    diagA=[]
    diagB=[]
    # for three element 4*4 k matrix
    for i in range(nEle):
        diagA.append(k[i][1])
    # print('diagA',diagA)
    # NOTE: no need for diagC as it will always be same as diagA

    diagB.append(k[0][0])
    for i in range(nEle-1):
        diagB.append(k[i][3]+k[i+1][0])
    diagB.append(k[nEle-1][3])
    # print('diagB',diagB)
    diagA=np.array(diagA, dtype=np.float64)
    diagB=np.array(diagB, dtype=np.float64)

    K = np.array( diags([diagB,diagA,diagA], [0,-1, 1]).todense() )
    return(K,F)

# function to find Q at the left and right ends
def solQ(k,f,nEle,bcs,method):

    k_modified = k[1:len(k[0])-1,1:len(k[0])-1]
    # print(k_modified)
    f_modified = f[1:len(f)-1]
    # print(f_modified)
    f_modified[0]=f_modified[0]-k[1][0]*bcs[0]-k[1][len(k[0])-1]*bcs[1]
    f_modified[1]=f_modified[1]-k[len(k)-2][0]*bcs[0]-k[len(k)-2][len(k[0])-1]*bcs[1]

    # print(f_modified)
    # print(linalg.inv(k_modified).dot(f_modified))
    sl=linalg.inv(k_modified).dot(f_modified)
    sol = []
    sol.append(bcs[0])
    for i in range(len(sl)):
        sol.append(sl[i])
    sol.append(bcs[1])
    # print(sol)
    return(sol)

# user inputs
domainLen=1
nEle=3
a=1
c=-1
f=-x*x
bcs=[0,10]

k,f=kfmatixLin(nEle,domainLen,a,c,f)
print("k",k)
print("\nf",f)
print("\nSolution",solQ(k,f,nEle,bcs,'linear'))

# for i in range(10):
#     nEle=i+3
#     print(nEle)
#     k,f=kfmatixLin(nEle,domainLen,a,c,f)
#     print("\nSolution",solQ(k,f,nEle,bcs,'linear'))

import numpy,from numpy,from scipy,from sympy


import numpy as np

def invert_matrix(mat_to_be_inverted):
    """
    Returns the inverse of the passed in matrix.
        :param A: The matrix to be inversed

        :return: The inverse of the matrix A
    """
    # assuming matrix to be invertible

    # Section 2: Make copies of A & I, AM & IM, to use for row ops
    n = len(mat_to_be_inverted)
    AM = mat_to_be_inverted.copy()
    I = np.identity(n)
    IM = I.copy()

    # Section 3: Perform row operations
    indices = list(range(n)) # to allow flexible row referencing ***
    for fd in range(n): # fd stands for focus diagonal
        fdScaler = 1.0 / AM[fd][fd]
        # FIRST: scale fd row with fd inverse.
        for j in range(n): # Use j to indicate column looping.
            AM[fd][j] *= fdScaler
            IM[fd][j] *= fdScaler
        # SECOND: operate on all rows except fd row as follows:
        for i in indices[0:fd] + indices[fd+1:]:
            # *** skip row with fd in it.
            crScaler = AM[i][fd] # cr stands for "current row".
            for j in range(n):
                # cr - crScaler * fdRow, but one element at a time.
                AM[i][j] = AM[i][j] - crScaler * AM[fd][j]
                IM[i][j] = IM[i][j] - crScaler * IM[fd][j]
    """
    # check is inverted matrix is correct
    print("this should be I")
    print(IM.dot(mat_to_be_inverted))
    """
    return IM


## 2. Calculating expected values ##

males_over50k = .241 * .669 * 32561
males_under50k = .759 * .669 * 32561
females_over50k = .241 * .331 * 32561
females_under50k = .759 * .331 * 32561

## 3. Calculating chi-squared ##

observed = [6662, 1179, 15128, 9592]
expected = [5249.8, 2597.4, 16533.5, 8180.3]
values = []

for i, obs in enumerate(observed):
    exp = expected[i]
    value = (obs - exp) ** 2 / exp
    values.append(value)

chisq_gender_income = sum(values)

## 4. Finding statistical significance ##

observed = [6662, 1179, 15128, 9592]
expected = [5249.8, 2597.4, 16533.5, 8180.3]

import numpy as np
from scipy.stats import chisquare

chisq_value, pvalue_gender_income = chisquare(observed, expected)

## 5. Cross tables ##

import pandas
table = pandas.crosstab(income["sex"], [income["race"]])
print(table)

## 6. Finding expected values ##

import pandas
from scipy.stats import chi2_contingency

table = pandas.crosstab(income["sex"], [income["race"]])
chisq_value, pvalue_gender_race, df, expected = chi2_contingency(table)

Probability Statistics Intermediate/Multi category chi-squared tests-173.py


import numpy as np
from math import e
import pandas as  pd

data=np.linspace(-1,1,19)
x_value=np.around(data,decimals=3)
count=(len(x_value))
print(count)
table=np.zeros((count,count))
y_value=[]
for i in x_value:
    y_value.append(np.around((e**i),decimals=3))
table[:,0]=y_value
#to calculate diffrence
for i in range(1, count):
    for j in range(count - i):
        table[j][i] = np.around(table[j + 1][i - 1] - table[j][i - 1],3)

#now to draw table
column_names = ["Y", "△1", "△2", "△3", "△4", "△5", "△6", "△7", "△8", "△9", "△10", "△11", "△12", "△13", "△14", "△15", "△16", "△17", "△18"]
draw=pd.DataFrame(table,x_value,column_names)
print(draw)


import numpy as np

__name__="Initialization"
def xavier_initiliazer(shape=None,uniform=True):
        seed = np.random.randint(1,1E3)
        random_engine = np.random.RandomState(seed=seed)
        if(uniform==True):
            range_init = np.sqrt(6.0/(shape[0]+shape[1]))
            return random_engine.uniform(-range_init,range_init,size=(shape[0],shape[1]))
        else:
            stdv = np.sqrt(3.0/(shape[0]+shape[1]))
            return random_engine.normal(0.0,scale=stdv,size=(shape[0],shape[1]))
def initialize_param(weights_layer,bias_layer,uniform_init=True):
    W = []
    b = []
    for weight in weights_layer:
        W.append(xavier_initiliazer(weight,uniform_init))
    for bias in bias_layer:
        b.append(np.zeros(bias))
    return W,b
def construct_candidate(neural_shape,uniform_init=True):
    weights_layer = [(neural_shape[t-1],neural_shape[t]) for t in range(1,len(neural_shape))]
    bias_layer = [neural_shape[t] for t in range(1,len(neural_shape))]
    W,b = initialize_param(weights_layer,bias_layer,uniform_init)
    param_network = zip(W,b)
    total_weights = []
    for param in param_network:
        temp_weight = np.concatenate([param[0].flatten(),param[1].flatten()])
        total_weights.append(temp_weight)
    return np.concatenate([total_weights[0],total_weights[1]]).flatten()
def construct_solution(number_of_solutions,neural_shape,uniform_init=True):
    return np.array([construct_candidate(neural_shape,uniform_init) for t in range(number_of_solutions)])


import matplotlib.pyplot as plt
import numpy as np
from numpy.random import exponential
import statsmodels
import scipy.stats

# PROBLEM 3
n = 10000
lambda_1 = 1
lambda_2 = 1 / 2
lambda_3 = 1 / 3
lambda_4 = 1 / 4
lambda_5 = 1 / 15
lambda_6 = 1 / 6

def longest_path_length(z1, z2, z3, z4, z5, z6):
    return max(z1 + z2 + z4, z1 + z3, z5 + z6)

def inverse_cdf_exponential_law(x,mean) :
    return mean * np.log(1 / (1-x))

## Simulations
Z1 = exponential(1/lambda_1,n)
Z2 = exponential(1/lambda_2,n)
Z3 = exponential(1/lambda_3,n)
Z4 = exponential(1/lambda_4,n)
Z5 = exponential(1/lambda_5,n)
Z6 = exponential(1/lambda_6,n)

L = []
for i in range(n) :
    L.append(longest_path_length(Z1[i],Z2[i],Z3[i],Z4[i],Z5[i],Z6[i]))
L = np.array(L)

# 1 - MC Standard
print("MC (standard) estimator : ", np.mean(L))
print("MC (standard) estimated variance : ", (1/n) * np.var(L))

# 2 - MC Control Variate
C = np.array([Z1 - np.mean(Z1),Z2 - np.mean(Z2), Z3 - np.mean(Z3),
              Z4 - np.mean(Z4),Z5 - np.mean(Z5), Z6 - np.mean(Z6)])

cov_vec = np.array([np.cov(L,C[0,])[0,1],np.cov(L,C[1,])[0,1],np.cov(L,C[2,])[0,1],
                    np.cov(L,C[3,])[0,1],np.cov(L,C[4,])[0,1],np.cov(L,C[5,])[0,1]])
Sigma = np.cov([C[0,],C[1,],C[2,],C[3,],C[4,],C[5,]])
lambda_star = np.dot(np.linalg.inv(Sigma), cov_vec)

L_CV = L - np.dot(lambda_star, C)
print("MC (control variate) estimator : ", np.mean(L_CV))
print("MC (control variate) estimated variance : ", (1/n) * np.var(L_CV))

# (1-alpha) % confidence interval.
alpha = 0.1
z = scipy.stats.norm.ppf(1-alpha/2)
inf = np.mean(L_CV) - (z/np.sqrt(n)) * np.var(L_CV)
sup = np.mean(L_CV) + (z/np.sqrt(n)) * np.var(L_CV)
print("({})% confidence interval : ".format((1-alpha)*100),"[",inf,",", sup, "]")

# 3 - MC Antithetic Variates
# TODO : Better to sample 6 uniforms at each simulation
U = np.random.uniform(0,1,n)

Z1_bis = [] ; Z1_bis_trans = []
Z2_bis = [] ; Z2_bis_trans = []
Z3_bis = [] ; Z3_bis_trans = []
Z4_bis = [] ; Z4_bis_trans = []
Z5_bis = [] ; Z5_bis_trans = []
Z6_bis = [] ; Z6_bis_trans = []

for i in range(n) :
    Z1_bis.append(inverse_cdf_exponential_law(U[i], 1 / lambda_1))
    Z2_bis.append(inverse_cdf_exponential_law(U[i], 1 / lambda_2))
    Z3_bis.append(inverse_cdf_exponential_law(U[i], 1 / lambda_3))
    Z4_bis.append(inverse_cdf_exponential_law(U[i], 1 / lambda_4))
    Z5_bis.append(inverse_cdf_exponential_law(U[i], 1 / lambda_5))
    Z6_bis.append(inverse_cdf_exponential_law(U[i], 1 / lambda_6))
    # antithetic tranformation of the sample U
    Z1_bis_trans.append(inverse_cdf_exponential_law(1-U[i], 1 / lambda_1))
    Z2_bis_trans.append(inverse_cdf_exponential_law(1-U[i], 1 / lambda_2))
    Z3_bis_trans.append(inverse_cdf_exponential_law(1-U[i], 1 / lambda_3))
    Z4_bis_trans.append(inverse_cdf_exponential_law(1-U[i], 1 / lambda_4))
    Z5_bis_trans.append(inverse_cdf_exponential_law(1-U[i], 1 / lambda_5))
    Z6_bis_trans.append(inverse_cdf_exponential_law(1-U[i], 1 / lambda_6))

L_anti = []
L_anti_trans = []
for i in range(n) :
    L_anti.append(longest_path_length(Z1_bis[i],Z2_bis[i],Z3_bis[i],
                                      Z4_bis[i],Z5_bis[i],Z6_bis[i]))
    L_anti_trans.append(longest_path_length(Z1_bis_trans[i], Z2_bis_trans[i],
                                            Z3_bis_trans[i], Z4_bis_trans[i],
                                            Z5_bis_trans[i], Z6_bis_trans[i]))
L_anti = np.array(L_anti)
L_anti_trans = np.array(L_anti_trans)

print("MC (antithetic variate) estimator : ",
      0.5 * np.mean(L_anti + L_anti_trans))
print("MC (antithetic variate) estimated variance : ",
      (0.25 / n) * np.var(L_anti + L_anti_trans))

# (1-alpha) % confidence interval.
alpha = 0.1
z = scipy.stats.norm.ppf(1-alpha/2)
inf = 0.5 * np.mean(L_anti + L_anti_trans) - (z/np.sqrt(n)) * np.var(L_var_red)
sup = 0.5 * np.mean(L_anti + L_anti_trans) + (z/np.sqrt(n)) * np.var(L_var_red)
print("({})% confidence interval : ".format((1-alpha)*100),"[",inf,",", sup, "]")

# PROBLEM 4
mean = np.array([0,0])
cov = np.array([[1,-0.7],[-0.7,1]])
# Simluations
n = 100000
X_Y = np.random.multivariate_normal(mean, cov, size=n)
# np.cov(X_Y[:,0], X_Y[:,1])
# Plots
plt.scatter(x=X_Y[:,0], y=X_Y[:,1])
plt.vlines(3, ymin=3, ymax = 8, color = "red", linestyles='solid')
plt.hlines(3, xmin=3, xmax = 8, color = "red")
plt.title("n = {} simulations".format(n))
plt.show()

# zone = [(x,y) for x,y in X_Y if x >= 1 and y >= 1]
# while len(zone) == 0 :
#     X_Y = np.random.multivariate_normal(mean, cov, size=n)
#     zone = [(x, y) for x, y in X_Y if x > 3 and y > 3]

# Importance Sampling
n = 100000
mean_2 = np.array([3,3])
Z = np.random.multivariate_normal(mean_2, cov, size=n)
f = scipy.stats.multivariate_normal(mean=[0,0], cov=[[1,-0.7],[-0.7,1]])
g = scipy.stats.multivariate_normal(mean=[3,3], cov=[[1,-0.7],[-0.7,1]])

# Plots
plt.scatter(x=Z[:,0], y=Z[:,1])
plt.vlines(3, ymin=3, ymax = 8, color = "red", linestyles='solid')
plt.hlines(3, xmin=3, xmax = 8, color = "red")
plt.title("n = {} simulations".format(n))
plt.show()

ratio = [] # only if Z belongs to A
for i in range(n) :
    if Z[i][0] >= 3 and Z[i][1] > 3 :
        ratio.append(f.pdf(Z[i]) / g.pdf(Z[i]))
ratio = np.array(ratio)

print("p_n = ", np.mean(ratio))

import numpy,from numpy,import scipy,import statsmodels


import numpy as np

def leastsq(x: np.ndarray, y: np.ndarray, sigma: float) -> 'tuple[[float, float], [float, float]]':
    """ Computes the least squares of the 1D vectors x and y.
    Raises:
        ValueError: If the lengths of the arrays are not equal.
        ValueError: If the array x is empty. It is checked after the lengths so y would also be empty.
    Returns:
        tuple((float, float), (float, float)): Returns a tuple containing two tuples.
            The first one contains at position 0 the slope (m in literature) and at position 1 its error.
            The second one contains at position 0 the y-intercept (b in literature) and at position 1 its error.
    """
    n = len(x)

    if n != len(y):
        raise ValueError(f'Length of the data array must be equal, length of x is {n} and y is {len(y)}. Please check. ')

    if n == 0:
        raise ValueError('Arrays cannot be empty. Please check.')

    sum_x, sum_y = np.sum(x), np.sum(y)
    sum_x2 = np.sum(x*x)
    sum_xy = np.sum(x*y)

    div = 1 / (sum_x**2 - n*sum_x2)
    m = (sum_x*sum_y - n*sum_xy) * div
    b = (sum_x*sum_xy - sum_y*sum_x2) * div

    m_e = np.sqrt(n*sigma**2 * (-1) * div)
    b_e = np.sqrt(sum_x2*sigma**2 * (-1) * div)

    return (m, m_e), (b, b_e)

if __name__ == '__main__':

    x = eval('[' + input("Introduce x data separated by ,:") + ']')
    y = eval('[' + input("Introduce y data separated by ,:") + ']')

    sigma = float(input("Introduce Sigma: "))

    leastsq = leastsq(np.array(x), np.array(y), sigma)

    print(f'm={leastsq[0][0]}  m_error={leastsq[0][1]} \n b={leastsq[1][0]}  b_error={leastsq[1][1]}')