#
# MIT License
#
# Copyright (C) 2021 <FacuFalcone - CaidevOficial>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# You should have received a copy of the MIT license
# along with this program.  If not, see <https://opensource.org/licenses/MIT>.
#
# @author Facundo Falcone <CaidevOficial>

import matplotlib.pyplot as plt
import numpy as np

def gaussianFunction(x, mu, sigma):
    """[summary]
    Calculates the value of the gaussian function in the point x.
    Args:
        x (array): [Vector of x values]
        mu (int): [Mean of the gaussian function]
        sigma (int): [Standard deviation of the gaussian function]
    Returns:
        [float]: [Value of the gaussian function]
    """
    return np.exp(-(x - mu)**2 / (2 * sigma**2))

if __name__ == "__main__":
    # Generate a normal distribution with mean 0 and standard deviation 1
    mu, sigma = 0, 1
    x = np.linspace(-5, 5, 100)
    y = gaussianFunction(x, mu, sigma)

    # Graficamos
    plt.plot(x, y, 'b--')
    plt.xlabel('X Axis')
    plt.ylabel('Y Axis')
    plt.title('Gaussian Function for range [-5, 5]')
    plt.savefig('Clase_02/Docs/GaussianFunction.png', dpi=300)
    plt.show()


r"""Implementation of the log-sum-exp trick.

The goal is to calculate \log\sum_{i}e^{x_{i}} in a numeric
stable manner.
"""
import typing as t

import numpy as np

def log_sum_exp(vals: np.ndarray,
                shift_coeff: t.Optional[t.Union[int, float]] = None) -> float:
    r"""Calculate \log_{e}\sum_{i}e^{vals_{i}} in a numeric stable manner."""
    if shift_coeff is None:
        shift_coeff = np.max(vals)

    if np.isinf(shift_coeff):
        return np.inf

    return shift_coeff + np.log(np.sum(np.exp(vals - shift_coeff)))

def _test():
    import scipy.special

    np.random.seed(16)
    for _ in np.arange(1024):
        size = np.random.randint(2, 16384)
        vals = np.random.randint(
            -1e+16, 1e+16, size=size) + np.random.random(size=size)
        assert np.allclose(log_sum_exp(vals), scipy.special.logsumexp(vals))

if __name__ == "__main__":
    _test()


import numpy as np

def print_matrix(mat):
    for line in mat:
        print('  '.join(map(str, line)))
    print()

def input_matrix(matrix, n):
    return np.reshape(matrix, (n, n))

def read_data_from_txt_file(filename):
    file = open(filename, "r")
    tmp = file.readlines()
    TMPdata = []
    for line in tmp:
        TMPdata.append(int(line.strip()))
    return TMPdata

def conditional(mat, norm):
    inv = np.linalg.inv(np.array(mat))
    if norm == "fro":
        print(f"A norm:{norm} = {np.linalg.norm(mat, 'fro')}\nA^-1 norm:{norm} = {np.linalg.norm(inv, 'fro')}")
        return np.linalg.norm(mat, "fro") * np.linalg.norm(inv, "fro")

    if norm == "nuc":
        print(f"A norm:{norm} = {np.linalg.norm(mat, 'nuc')}\nA^-1 norm:{norm} = {np.linalg.norm(inv, 'nuc')}")
        return np.linalg.norm(mat, "nuc") * np.linalg.norm(inv, "nuc")

    if norm == "inf":
        print(f"A norm:{norm} = {np.linalg.norm(mat, np.inf)}\nA^-1 norm:{norm} = {np.linalg.norm(inv, np.inf)}")
        return np.linalg.norm(mat, np.inf) * np.linalg.norm(inv, np.inf)

    if norm == "-inf":
        print(f"A norm:{norm} = {np.linalg.norm(mat, -np.inf)}\nA^-1 norm:{norm} = {np.linalg.norm(inv, -np.inf)}")
        return np.linalg.norm(mat, -np.inf) * np.linalg.norm(inv, -np.inf)

    if norm == "1":
        print(f"A norm:{norm} = {np.linalg.norm(mat, 1)}\nA^-1 norm:{norm} = {np.linalg.norm(inv, 1)}")
        return np.linalg.norm(mat, 1) * np.linalg.norm(inv, 1)

    if norm == "-1":
        print(f"A norm:{norm} = {np.linalg.norm(mat, -1)}\nA^-1 norm:{norm} = {np.linalg.norm(inv, -1)}")
        return np.linalg.norm(mat, -1) * np.linalg.norm(inv, -1)

    if norm == "2":
        print(f"A norm:{norm} = {np.linalg.norm(mat, 2)}\nA^-1 norm:{norm} = {np.linalg.norm(inv, 2)}")
        return np.linalg.norm(mat, 2) * np.linalg.norm(inv, 2)

    if norm == "-2":
        print(f"A norm:{norm} = {np.linalg.norm(mat, -2)}\nA^-1 norm:{norm} = {np.linalg.norm(inv, -2)}")
        return np.linalg.norm(mat, -2) * np.linalg.norm(inv, -2)
    return "Error"

data = read_data_from_txt_file("data.txt")
A = input_matrix(data, int(np.sqrt(len(data))))
A_inv = np.linalg.inv(A)
print_matrix(A_inv)
B = np.array([1 for i in range(int(np.sqrt(len(data))))])
X = np.linalg.inv(A).dot(B)
print_matrix(A)
eig = np.linalg.eig(A.dot(A.transpose()))
print(eig)
print(X)
t = np.array([[65, 28], [28, 32]])
print_matrix(t)
norm = '2'
print(f"cond(A){norm}={conditional(A, norm)}")


from scipy.stats import chisquare

# can we assume anything from our sample
significance = 0.05

# what do we expect to see in proportions?
expected_proportions = [.05, .1, .15, .7]

# what counts did we see in our sample?
observed_counts = [27, 73, 82, 468]

########################
# how big was our sample
sample_size = sum(observed_counts)

# we derive our comparison counts here for  our expected proportions, based on the sample size
expected_counts = [float(sample_size) * x for x in expected_proportions]

# Get the stat data
(chi_stat, p_value) = chisquare(observed_counts, expected_counts)

# report
print('chi_stat: %0.5f, p_value: %0.5f' % (chi_stat, p_value))

if p_value > significance:
    print("Fail to reject the null hypothesis - we have nothing else to say")
else:
    print("Reject the null hypothesis - suggest the alternative hypothesis is true")


import numpy as np

class Activation:
    def step_function(self,x):
        y = x > 0
        return y.astype(np.int)

    #왜 sigmoid?
    # 출력이 0~1 사이이고, 미분이 쉬운 함수들중 가장 그럴듯 해서 많이 사용하게됨. 미분은 exp이 쉽기때문에
    def sigmoid(self,x):
        return 1/(1 + np.exp(-x))

    def RelU(self,x):
        return np.maximum(0,x)

     #왜 softmax?
    # 출력이 0~1 사이이고, 미분이 쉬운 함수들중 가장 그럴듯 해서 많이 사용하게됨. 미분은 exp이 쉽기때문에
    def softmax(self,xs):
        if xs.ndim == 2:
            xs = xs.T
            xs = xs - np.max(xs, axis=0)
            y = np.exp(xs) / np.sum(np.exp(xs), axis=0)
            return y.T

        xs = xs - np.max(xs) # 오버플로 대책
        return np.exp(xs) / np.sum(np.exp(xs))

class LossFun:
    def MSE(self,ts,ys):
        batch_size = ys.shape[0]
        if ys.ndim == 1:
            ts = ts.reshape(1,ts.size)
            ys = ys.reshape(1,ys.size)
        return 1/2*(np.sum((ys - ts)**2))  / batch_size

    #def CEE(self,ts,ys,IsOneHot = True):
    #    batch_size = ys.shape[0]
    #    if ys.ndim == 1:
    #        ts = ts.reshape(1,ts.size)
    #        ys = ys.reshape(1,ys.size)
    #
    #    if IsOneHot :
    #         return -np.sum(ts*np.log(ys + 1e-7) ) / batch_size
    #    else:
    #         return -np.sum(np.log(ys[np.arange(batch_size) , ts] + 1e-7) ) / batch_size

    def CEE(self,ts,ys):
        batch_size = ys.shape[0]

        if ys.ndim == 1:
            ts = ts.reshape(1,ts.size)
            ys = ys.reshape(1,ys.size)

        if ts.size == ys.size:
            ts = ts.argmax(axis=1)

        return -np.sum(np.log(ys[np.arange(batch_size), ts])) / batch_size

class Diff:
    def NGradient(self,f,x):
        h = 1e-4 # 0.0001
        grad = np.zeros_like(x)

        it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
        while not it.finished:
            idx = it.multi_index
            tmp_val = x[idx]
            x[idx] = float(tmp_val) + h
            fxh1 = f(x) # f(x+h)

            x[idx] = tmp_val - h
            fxh2 = f(x) # f(x-h)
            grad[idx] = (fxh1 - fxh2) / (2*h)

            x[idx] = tmp_val # 값 복원
            it.iternext()

        return grad

    def diff(self,f,x):
        h = 1e-4
        return (f(x+h)-f(x-h)) / (2*h)

    def gradient(self,f,x):
        h = 1e-4
        grad = np.zeros_like(x)

        for idx in range(x.size):
            tmpVal = x[idx]

            x[idx] = tmpVal + h
            fxh1 = f(x)

            x[idx] = tmpVal - h
            fxh2 = f(x)

            grad[idx] = (fxh1 - fxh2) / (2*h)
            x[idx] = tmpVal
        return grad

    def diff_Line(self,a,x0,y0,x):
        return a*(x - x0)+y0

class SGD:
    def NGradient(self,f,s):
        h = 1e-4 # 0.0001
        grad = np.zeros_like(x)

        it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
        while not it.finished:
            idx = it.multi_index
            tmp_val = x[idx]
            x[idx] = float(tmp_val) + h
            fxh1 = f(x) # f(x+h)

            x[idx] = tmp_val - h
            fxh2 = f(x) # f(x-h)
            grad[idx] = (fxh1 - fxh2) / (2*h)

            x[idx] = tmp_val # 값 복원
            it.iternext()

        return grad

    def gradient(self,f,x):
        h = 1e-4
        grad = np.zeros_like(x)

        for idx in range(x.size):
            tmpVal = x[idx]

            x[idx] = tmpVal + h
            fxh1 = f(x)

            x[idx] = tmpVal - h
            fxh2 = f(x)

            grad[idx] = (fxh1 - fxh2) / (2*h)
            x[idx] = tmpVal
        return grad

    def sgd(self,f,x0 , lr = 0.01 , iter = 1000 ):
        x = x0
        for i in range(iter):
            grad = self.gradient(f , x)
            x -= lr * grad
        return x

    def sgdHistory(self,f,x0 , lr = 0.01 , iter = 100 ):
        his = []
        his.append(x0)
        x = x0
        for i in range(iter):
            grad = self.gradient(f , x)
            x -= lr * grad
            his.append(list(x))
        return x , his


import math
import numpy as np
import matplotlib.pyplot as plt
import pandas
import scipy.linalg as linalg

# Parameters
np.random.seed(421)
# generating data
class_means = np.array([[0.0, 2.5], [-2.5, -2.0], [2.5, -2.0]])

class_covariances = np.array([[
    [3.2, 0.0],
    [0.0, 1.2]],
    [
        [1.2, 0.8],
        [0.8, 1.2]
    ],
    [
        [1.2, -0.8],
        [-0.8, 1.2]
    ]
])

# number of data points for each class.
class_sizes = np.array([120, 80, 100])

points1 = np.random.multivariate_normal(class_means[0, :], class_covariances[0, :, :], class_sizes[0])
points2 = np.random.multivariate_normal(class_means[1, :], class_covariances[1, :, :], class_sizes[1])
points3 = np.random.multivariate_normal(class_means[2, :], class_covariances[2, :, :], class_sizes[2])
points = np.vstack((points1, points2, points3))

# generating the labels.
y = np.concatenate((np.repeat(1, class_sizes[0]), np.repeat(2, class_sizes[1]), np.repeat(3, class_sizes[2])))

x1 = points[:, 0]
x2 = points[:, 1]
# Plotting Data
plt.figure(figsize=(10, 10))
plt.plot(points1[:, 0], points1[:, 1], "r.", markersize=12)
plt.plot(points2[:, 0], points2[:, 1], "g.", markersize=12)
plt.plot(points3[:, 0], points3[:, 1], "b.", markersize=12)
plt.xlabel("$x_1$")
plt.ylabel("$x_2$")
plt.show()

# Part 3 - Estimation

# number of classes
K = np.max(y)
# number of data points
N = points.shape[0]

# Parameter Estimation
# Mean
# axis = 0
sample_means = [np.mean(points[y == (c + 1)], axis=0) for c in range(K)]
print("sample_means")
print(sample_means)

# Covariances
sample_covariances = [
    np.dot(np.transpose(points[y == (c + 1)] - sample_means[c]), points[y == (c + 1)] - sample_means[c]) / class_sizes[
        c] for c in range(K)]
print("sample_covariances")
print(sample_covariances)

# Class Priors
class_priors = [np.mean(y == (c + 1)) for c in range(K)]
print("Class Priors")
print(class_priors)

# Multivariate Parametric Classification
# From class notes on Multivariate Parametric Classification
D = 3
y_predicted = []
for i in range(points.shape[0]):
    wc0 = [
        -1 / 2 * np.dot(np.dot(np.transpose(sample_means[c]), np.linalg.inv(sample_covariances[c])), sample_means[c]) -
        D / 2 * np.log(2 * math.pi) - 1 / 2 * np.log(np.linalg.det(sample_covariances[c])) + np.log(class_priors[c]) for
        c in range(K)]
    wc = [np.dot(np.linalg.inv(sample_covariances[c]), sample_means[c]) for c in range(K)]
    Wc = [-1 / 2 * np.linalg.inv(sample_covariances[c]) for c in range(K)]
    g = [np.dot(np.dot(np.transpose(points[i]), Wc[c]), points[i]) + np.dot(np.transpose(wc[c]), points[i]) + wc0[c] for
         c in range(K)]
    # take the maximum of gc(x)
    y_predicted.append(np.argmax(g) + 1)
y_predicted = np.array(y_predicted)

# Confusion Matrix
confusion_matrix = pandas.crosstab(y_predicted, y, rownames=['y_pred'], colnames=['y_truth'])
print(confusion_matrix)

# Plotting the decision boundaries
# From lab 03
x1_interval = np.linspace(-6, +6, 1201)
x2_interval = np.linspace(-6, +6, 1201)
x1_grid, x2_grid = np.meshgrid(x1_interval, x2_interval)
# discriminant_values = [Wc[c][0][0] * x1_grid + Wc[c][0][1] * x2_grid + wc[c][0] for c in range(K)]
plt.figure(figsize=(10, 10))
plt.plot(points[y == 1, 0], points[y == 1, 1], "r.", markersize=10)
plt.plot(points[y == 2, 0], points[y == 2, 1], "g.", markersize=10)
plt.plot(points[y == 3, 0], points[y == 3, 1], "b.", markersize=10)
plt.plot(points[y_predicted != y, 0], points[y_predicted != y, 1], "ko", markersize = 12, fillstyle = "none")
# plt.contour(x1_grid, x2_grid, discriminant_values, levels=0, colors="k")
plt.show()


import numpy as np
import pandas as pd

def swap(A, b, i, j):
    A[j, :], A[i, :] = A[i, :], A[j, :].copy()
    b[j], b[i] = b[i], b[j].copy()
    return

def forward(A, b, n):

    for i in range(0, n - 1):
        if A[i][i] == 0:
            pivot = np.nonzero(A[i:, i])[0][0]
            swap(A, b, i, pivot)
        else:
            for j in range(i + 1, n):
                d = A[j][i] / A[i][i]
                for k in range(i + 1, n):
                    A[j, k] = A[j, k] - (d * A[i, k])
                b[j] = b[j] - (d * b[i])
    return

def back(A, b, n):

    if A[n-1, n-1] == 0:
        raise ValueError("System has infinite solutions.")

    for i in range(n-1, -1, -1):
        b[i] = b[i]/A[i, i]
        for j in range(i-1, -1, -1):
            b[j] -= A[j, i]*b[i]

    return

def gaussian_elimination(A, b):
    n = len(b)
    forward(A, b, n)
    back(A, b, n)
    return b

df = pd.read_excel('data.xlsx', engine='openpyxl')
A = df.to_numpy()
b = np.array([3, 7, 11, 16, 21, 26, 24, 28, 23], dtype="float")
print(gaussian_elimination(A, b))


import numpy as np

def kldistance(p, q):
    return np.sum(p * (np.log(p) - np.log(q)))

p = np.array([0.4, 0.6])
q = np.array([0.5, 0.5])
print(kldistance(p, q))
print(kldistance(q, p))


#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
20170425
Resolver sistema de ecuaciones usando algebra lineal

Objetivo: Tomar un sistema de ecuaciones y resolverlo usando algebra de matrices y vectores

Fuentes:
    video de youtube

Status:

Notas:

    Se tiene el sistema de ecuaciones

        1a + 1b = 35
        2a + 4b = 94

    Planteandolo en forma de matrices y vectores:

        AX = B

        Donde A es una matriz, X un vector y B un vector

        A = [1 1, 2 4]
        X = [A B]T
        B = [35 94]T

    Para encontrar X:
        X = A_inversa * B

Keywords:
    robotica, matriz, resolver sistema ecuaciones,

Autor: Jaime Andres Cataño Bernal

"""

import numpy as np

# define matrices
A = np.matrix([[1,1],[2,4]])
B = np.matrix([[35],[94]])

# Find the inverse of A
A_inverse = np.linalg.inv(A)

# Solving for X
X = A_inverse * B

print X


# Suppose the demand for a certain item is given by D(p) -5p^2 7p +600, where p represents the price of the item in dollars.

# a. Find the rate of change of demand with respect to price.
# b. Find and interpret the rate of change of demand when the price is $10.

from sympy import symbols, Limit, Derivative, Integral

p = symbols( 'p' )
fP = -5*p**2 - 7*p + 600

dP = Derivative( fP, p ).doit()

# The rate of change of demand with respect to price is
dP

# When the price is $10, demand is:
dP.subs( { p: 10 } )
# decreasing at a rate of about  107 items for each increase in price of $1.


import numpy as np
from scipy.stats import weibull_min, lognorm
from Bayes import Bayes
from matplotlib import pyplot as plt

testDists = ['norm', 'weibull_min', 'lognorm']

def testNormal(hLen, totN, figSize, pltRange):
    """
    Brief test with data following normal distribution.
    """
    # Create some imaginary data
    obs = np.random.normal(15.4, 1.3, size = totN)
    model = obs + np.random.normal(0.0, 4.5, size = len(obs))

    # Start Testing
    # Create an instance of the object
    bs = Bayes(hLen, distType = 'norm')

    # Use the first #hLen of them for training
    obsTrain = obs[:hLen]
    modelTrain = model[:hLen]

    # The rest to be used dynamically
    obsDyn = obs[hLen:]
    modelDyn = model[hLen:]
    fcst = np.zeros_like(obsDyn)

    # Perform an initial training of the model
    bs.trainMe(obsTrain, modelTrain)

    for ij in range(len(obsDyn)):
        # Provide a correction to the forecast
        fcst[ij] = bs.adjustForecast(modelDyn[ij])

        # Update system
        bs.trainMe([obsDyn[ij]], [modelDyn[ij]])

    # Show evidence!
    plt.figure(figsize = figSize)
    plt.plot(obsDyn[pltRange], label='obs')
    plt.plot(modelDyn[pltRange], '--', label='model')
    plt.plot(fcst[pltRange], '*', label='Bayes')
    plt.title('Normal Dist Data')
    plt.legend()
    plt.show()
    return

def testWeibull(hLen, totN, figSize, pltRange):
    """
    Brief test with data following weibull distribution.
    """
    # Create some imaginary data
    obs = weibull_min.rvs(c = 1.96, scale = 2.1, size = totN) # c: shape
    model = obs + np.random.normal(0.0, 2.5, size = len(obs))

    # Start Testing
    # Create an instance of the object
    bs = Bayes(hLen, distType = 'weibull_min')

    # Use the first #hLen of them for training
    obsTrain = obs[:hLen]
    modelTrain = model[:hLen]

    # The rest to be used dynamically
    obsDyn = obs[hLen:]
    modelDyn = model[hLen:]
    fcst = np.zeros_like(obsDyn)

    # Perform an initial training of the model
    bs.trainMe(obsTrain, modelTrain)

    for ij in range(len(obsDyn)):
        # Provide a correction to the forecast
        fcst[ij] = bs.adjustForecast(modelDyn[ij])

        # Update system
        bs.trainMe([obsDyn[ij]], [modelDyn[ij]])

    # Show evidence!
    plt.figure(figsize = figSize)
    plt.plot(obsDyn[pltRange], label='obs')
    plt.plot(modelDyn[pltRange], '--', label='model')
    plt.plot(fcst[pltRange], '*', label='Bayes')
    plt.title('Weibull Dist Data')
    plt.legend()
    plt.show()
    return

def testLognormal(hLen, totN, figSize, pltRange):
    """
    Brief test with data following lognormal distribution.
    """
    # Create some imaginary data
    obs = lognorm.rvs(s = 0.94, scale = 1.9, size = totN)
    model = obs + np.random.normal(0.0, 2.5, size = len(obs))

    # Start Testing
    # Create an instance of the object
    bs = Bayes(hLen, distType = 'lognorm')

    # Use the first #hLen of them for training
    obsTrain = obs[:hLen]
    modelTrain = model[:hLen]

    # The rest to be used dynamically
    obsDyn = obs[hLen:]
    modelDyn = model[hLen:]
    fcst = np.zeros_like(obsDyn)

    # Perform an initial training of the model
    bs.trainMe(obsTrain, modelTrain)

    for ij in range(len(obsDyn)):
        # Provide a correction to the forecast
        fcst[ij] = bs.adjustForecast(modelDyn[ij])

        # Update system
        bs.trainMe([obsDyn[ij]], [modelDyn[ij]])

    # Show evidence!
    plt.figure(figsize = figSize)
    plt.plot(obsDyn[pltRange], label='obs')
    plt.plot(modelDyn[pltRange], '--', label='model')
    plt.plot(fcst[pltRange], '*', label='Bayes')
    plt.title('Lognormal Dist Data')
    plt.legend()
    plt.show()
    return

if __name__ == '__main__':

    # Define some generic values
    history = 100
    totalNum = 2000
    plotNum = 40

    # Define the plot range and size of the figure
    figSize = (11, 9)
    pIn = int(totalNum * .5)
    pltRange = range(pIn, pIn + plotNum)

    for item in testDists:
        if item.lower() == 'norm':
            print('\n *****   Normal Distribution data testing    ***** ')
            testNormal(history, totalNum, figSize, pltRange)
            print('\n ***** Normal Distribution data testing  END ***** ')
        elif item.lower() == 'weibull_min':
            print('\n *****   Weibull Distribution data testing    ***** ')
            testWeibull(history, totalNum, figSize, pltRange)
            print('\n ***** Weibull Distribution data testing  END ***** ')
        elif item.lower() == 'lognorm':
            print('\n *****   Lognormal Distribution data testing    ***** ')
            testLognormal(history, totalNum, figSize, pltRange)
            print('\n ***** Lognormal Distribution data testing  END ***** ')
        else:
            raise TypeError('Unknown distribution used for testing...')


import numpy as np

# Write a function that takes as input a list of numbers, and returns
# the list of values given by the softmax function.
def softmax(L):
    expL = np.exp(L)
    sumExpL = sum(expL)
    result = []
    for i in expL:
        result.append(i*1.0/sumExpL)
    return result


import os, sys
import numpy as np

def Simpson(func, dp):
    N = len(func)
    integral_sum = 0.0

    if N % 2 == 1:
        k_end = (N-1)//2
    else:
        k_end = N//2 - 1

    for k in range(k_end):
        integral_sum += func[2*k] + 4.0*func[2*k + 1] + func[2*k + 2]

    integral_sum = dp/3.0*integral_sum

    if N % 2 == 0:
        integral_sum += dp/2.0*(func[N - 2]+func[N - 1])

    return integral_sum

def L2norm(x, w):
    if len(x) != len(w):
        print('Mesh x and function don''t match!')
        exit()

    func = np.power(np.abs(w), 2)

    h = x[1] - x[0]                # spacing

    return np.sqrt(Simpson(func, h))

if __name__ == '__main__':
    is_filter = False
    N = 150       # Number of entries for the moving average filter
    funcs = {}
    Idx = 1
    try:
        path = 'C:/Users/mexika/Documents/Qt_code/CudaFEMproject/prak_results/'
        for filename in os.listdir(path):
            if filename.endswith('.txt'):
                with open(path + filename, 'r') as file_read:
                    data = file_read.readlines()
                data = [line.rstrip() for line in data]
                x, y = [], []
                for d in data:
                    x.append(float(d.split()[0]))
                    y.append(float(d.split()[1]))
                if is_filter:
                    y = np.convolve(y, np.ones(N)/N, mode='valid')
                    if N % 2 == 0:
                        x = x[N//2-1:-N//2]
                    else:
                        x = x[(N-1)//2:-(N-1)//2]

                funcs[Idx] = (x,y)
                print(Idx,'\t',filename)
                Idx += 1
    except Exception as e:
        print('Something wrong with postprocessing')
        sys.exit(0)

    for i in range(1, Idx - 1):
        x = funcs[i][0]
        y1 = np.array(funcs[i][1])
        y2 = np.array(funcs[i+1][1])
        norm_y2 = L2norm(x,y2)
        norm_diff = L2norm(x,y1-y2)
        print('L2 norm (y',i,', y',i+1,') =\t', L2norm(x,y1-y2),' ( ', norm_diff/norm_y2*100,'% )\t L2 norm (y',i+1,') =\t',norm_y2, sep='')


"""
Partial Correlation in Python (clone of Matlab's partialcorr)

This uses the linear regression approach to compute the partial
correlation (might be slow for a huge number of variables). The
algorithm is detailed here:

    http://en.wikipedia.org/wiki/Partial_correlation#Using_linear_regression

Taking X and Y two variables of interest and Z the matrix with all the variable minus {X, Y},
the algorithm can be summarized as

    1) perform a normal linear least-squares regression with X as the target and Z as the predictor
    2) calculate the residuals in Step #1
    3) perform a normal linear least-squares regression with Y as the target and Z as the predictor
    4) calculate the residuals in Step #3
    5) calculate the correlation coefficient between the residuals from Steps #2 and #4;

    The result is the partial correlation between X and Y while controlling for the effect of Z

Date: Nov 2014
Author: Fabian Pedregosa-Izquierdo, f@bianp.net
Testing: Valentina Borghesani, valentinaborghesani@gmail.com
"""

import numpy as np
from scipy import stats, linalg

def partial_corr(C):
    """
    Returns the sample linear partial correlation coefficients between pairs of variables in C, controlling
    for the remaining variables in C.

    Parameters
    ----------
    C : array-like, shape (n, p)
        Array with the different variables. Each column of C is taken as a variable

    Returns
    -------
    P : array-like, shape (p, p)
        P[i, j] contains the partial correlation of C[:, i] and C[:, j] controlling
        for the remaining variables in C.
    """

    C = np.asarray(C)
    p = C.shape[1]
    P_corr = np.zeros((p, p), dtype=np.float)
    for i in range(p):
        P_corr[i, i] = 1
        for j in range(i+1, p):
            idx = np.ones(p, dtype=np.bool)
            idx[i] = False
            idx[j] = False
            beta_i = linalg.lstsq(C[:, idx], C[:, j])[0]
            beta_j = linalg.lstsq(C[:, idx], C[:, i])[0]

            res_j = C[:, j] - C[:, idx].dot( beta_i)
            res_i = C[:, i] - C[:, idx].dot(beta_j)

            corr = stats.pearsonr(res_i, res_j)[0]
            P_corr[i, j] = corr
            P_corr[j, i] = corr

    return P_corr


"""
Problem Statement

sum

The sum tool returns the sum of array elements over a given axis.

import numpy

my_array = numpy.array([ [1, 2], [3, 4] ])

print numpy.sum(my_array, axis = 0)         #Output : [4 6]
print numpy.sum(my_array, axis = 1)         #Output : [3 7]
print numpy.sum(my_array, axis = None)      #Output : 10
print numpy.sum(my_array)                   #Output : 10
By default, the axis value is None. Therefore, it performs a sum over all
the dimensions of the input array.

prod

The prod tool returns the product of array elements over a given axis.

import numpy

my_array = numpy.array([ [1, 2], [3, 4] ])

print numpy.prod(my_array, axis = 0)            #Output : [3 8]
print numpy.prod(my_array, axis = 1)            #Output : [ 2 12]
print numpy.prod(my_array, axis = None)         #Output : 24
print numpy.prod(my_array)                      #Output : 24
By default, the axis value is None. Therefore, it performs the product over
all the dimensions of the input array.

Task

You are given a 2-D array with dimensions NXM.
Your task is to perform the sum tool over axis 0 and then find the product
of that result.

Input Format

The first line of input contains space separated values of N and M.
The next N lines contains M space separated integers.

Output Format

Compute the sum along axis 0. Then, print the product of that sum.

Sample Input

2 2
1 2
3 4
Sample Output

24
Explanation

The sum along axis 0 = [4 6]
The product of this sum = 24
"""
import numpy

n, m = map(int, raw_input().split())
a = numpy.array([map(int, raw_input().split()) for i in range(n)],
                dtype=numpy.int)
sum_array = numpy.sum(a, axis=0)
print numpy.prod(sum_array, axis=0)


import numpy as np

# support functions for perturbation analysis in parabolic partial differential equations
# most code obtained from github repository
# https://github.com/tclaudioe/Scientific-Computing

def cubic_spline(x, y, end=None, k1=0, k2=0, p1=0, p2=0):
    #x: x-coordinates of points
    #y: y-coordinates of points
    #end: Natural, Adjusted, Clamped, Parabolically, NaK

    n = len(x)
    A = np.zeros((3*n-3, 3*n-3))
    b = np.zeros(3*n-3)

    delta_x=np.diff(x)

    #Building the linear system of equations

    #1st property
    for i in np.arange(n-1):
        b[i]= y[i+1]-y[i]
        A[i,3*i:3*(i+1)] = [delta_x[i],delta_x[i]**2,delta_x[i]**3]
    #2nd property
    for i in np.arange(n-2):
        A[(n-1)+i,3*i:3*(i+1)+1]=[1, 2*delta_x[i], 3*delta_x[i]**2, -1]
    #3rd property
    for i in np.arange(n-2):
        A[(n-1)+(n-2)+i,3*i:3*(i+1)+2] = [0, 2, 6*delta_x[i], 0, -2]

    #Ending conditions (4th property)
    if end =='Natural':
        A[-2,1]= 2
        A[-1,-2] = 2
        A[-1,-1] = 6*delta_x[-1]

    elif end == 'Adjusted':
        A[-2,1]= 2
        A[-1,-2] = 2
        A[-1,-1] = 6*delta_x[-1]
        b[-2:] = [k1,k2]

    elif end == 'Clamped':
        A[-2,0]=1
        A[-1,-3:] = [1,2*delta_x[-1],3*delta_x[-1]**2]
        b[-2:] = [p1,p2]

    elif end == 'Parabolically':
        A[-2,2]=1
        A[-1,-1]=1

    elif end == 'NaK':
        A[-2,2:6]=[6,0,0,-6]
        A[-1,-4:]=[6,0,0,-6]

    #Solving the system
    sol = np.linalg.solve(A,b)
    S = {'b':sol[::3],
         'c':sol[1::3],
         'd':sol[2::3],
         'x':x,
         'y':y
        }
    return S

def cubic_spline_eval(xx,S):
    x=S['x']
    y=S['y']
    b=S['b']
    c=S['c']
    d=S['d']
    n=len(x)
    yy=float("nan")
    for i in np.arange(n-1):
        if x[i] <= xx and xx <= x[i+1]:
            yy = y[i]+b[i]*(xx-x[i])+c[i]*(xx-x[i])**2+d[i]*(xx-x[i])**3
    return yy

def cubic_spline_eval2(xx,S):
    x=S['x']
    y=S['y']
    b=S['b']
    c=S['c']
    d=S['d']
    n=len(x)
    yy=np.zeros_like(xx)
    for i in np.arange(n-1):
        jj = np.where(np.logical_and(x[i]<=xx,xx<=x[i+1]))
        yy[jj]=y[i]+b[i]*(xx[jj]-x[i])+c[i]*(xx[jj]-x[i])**2+d[i]*(xx[jj]-x[i])**3
    return yy

def bisect(f, a, b, verb=False, tol=10e-12):
    fa = f(a)
    fb = f(b)
    i = 0
    # Just checking if the sign is not negative => not root  necessarily
    if np.sign(f(a)*f(b)) >= 0:
        print('f(a)f(b)<0 not satisfied!')
        return None

    if verb:
        #Printing the evolution of the computation of the root
        print(' i |     a     |     c     |     b     |     fa    |     fc     |     fb     |   b-a')
        print('----------------------------------------------------------------------------------------')

    while(b-a)/2 > tol:
        c = (a+b)/2.
        fc = f(c)
        if verb:
            print('%2d | %.7f | %.7f | %.7f | %.7f | %.7f | %.7f | %.7f' % (i+1, a, c, b, fa, fc, fb, b-a))
        # Did we find the root?
        if fc == 0:
            print('f(c)==0')
            break
        elif np.sign(fa*fc) < 0:
            b = c
            fb = fc
        else:
            a = c
            fa = fc
        i += 1

    xc = (a+b)/2.
    return xc

def simpsons(myfun, N, a, b, verbose=False, text="", figname=""):
    f = np.vectorize(myfun) # So we can apply it to arrays without trouble
    x = np.linspace(a, b, N+1) # We want N bins, so N+1 points
    if N%2==1:
        if verbose: print("Simpsons rule only applicable to even number of segments")
        return np.nan
    dx = x[1]-x[0]
    xleft   = x[:-2:2]
    xmiddle = x[1::2]
    xright  = x[2::2]
    int_val = sum((f(xleft)+4*f(xmiddle)+f(xright))*dx/3)
    if verbose:
        xbin, ybin = simpsons_bins(f, xleft, xmiddle, xright)
        plot(f, xbin, ybin, int_val, N, text, figname)
    return int_val


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

Copyright (c) 2022 Jongrae.K

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

import numpy as np
from numpy import linspace
import numpy.random as rp

import matplotlib.pyplot as plt

# true probability density function (pdf)
var_x = 1;
mean_x = 0;
Omega_x = linspace(-5,5,1000);
px = (1/(np.sqrt(2*np.pi*var_x)))*np.exp(-(Omega_x-mean_x)**2/(2*var_x));

fig, ax = plt.subplots(nrows=1,ncols=1)
ax.plot(Omega_x,px,linewidth=3)

# generate N random numbers with the mean zero and the variance 1 using
# numpy.random.randn
N_all = np.array([100,10000])
x_bin = linspace(-5,5,30)
dx=np.mean(np.diff(x_bin))
line_style = ['rs-','go-']
for N_trial, lnsty in zip(N_all,line_style):
    x_rand = rp.randn(1,N_trial)

    # number of occurance of x_rand in x_bin
    N_occur = np.histogram(x_rand,bins=x_bin)
    N_occur = N_occur[0]

    ax.plot(x_bin[0:-1]+dx/2, N_occur/(dx*N_trial),lnsty);

ax.set_xlabel(r'Random Variable x Sampling Space: $\Omega_x$',fontsize=14)
ax.set_ylabel('probability density function',fontsize=14)
ax.legend((r'True $p(x)$','N=100','N=10,000'),loc='upper right',fontsize=14)

fig.savefig('compare_mu_sgm2_true_estimated_python.pdf')


# https://www.hackerrank.com/challenges/np-polynomials/problem

import numpy

# Inputs
standard_input = """1.1 2 3
0"""

p = [float(s) for s in input().split()]
# 1.1 2 3
x = int(input())
# 0

print(numpy.polyval(p, x))
# 3.0

""" Reference

[poly]

The poly tool returns the coefficients of a polynomial with the given sequence of roots.

print(numpy.poly([-1, 1, 1, 10]))
#Output : [  1 -11   9  11 -10]

[roots]

The roots tool returns the roots of a polynomial with the given coefficients.

print(numpy.roots([1, 0, -1]))
#Output : [-1.  1.]

[polyint]

The polyint tool returns an antiderivative (indefinite integral) of a polynomial.

print(numpy.polyint([1, 1, 1]))
#Output : [ 0.33333333  0.5         1.          0.        ]

[polyder]

The polyder tool returns the derivative of the specified order of a polynomial.

print(numpy.polyder([1, 1, 1, 1]))
#Output : [3 2 1]

[polyval]

The polyval tool evaluates the polynomial at specific value.

print(numpy.polyval([1, -2, 0, 2], 4))
Output : 34

[polyfit]

The polyfit tool fits a polynomial of a specified order to a set of data using a least-squares approach.

print(numpy.polyfit([0,1,-1, 2, -2], [0,1,1, 4, 4], 2)
#Output : [  1.00000000e+00   0.00000000e+00  -3.97205465e-16]

-----------
The functions polyadd, polysub, polymul, and polydiv also handle proper addition, subtraction, multiplication, and division of polynomial coefficients, respectively.

"""

__Training__/Python - HackerRank/16. Numpy/Polynomials.py


from IMLearn.learners import UnivariateGaussian, MultivariateGaussian
import numpy as np
import plotly.graph_objects as go
import plotly.io as pio
import math

pio.templates.default = "simple_white"

def test_univariate_gaussian():
    # Question 1 - Draw samples and print fitted model
    mu, var, m = 10, 1, 1000

    data = np.random.normal(mu, var, m)
    estimator = UnivariateGaussian().fit(data)
    print(f"({estimator.mu_}, {estimator.var_})")

    # Question 2 - Empirically showing sample mean is consistent
    x = np.arange(10, 1010, 10)  # x = [10, 20,..., 100, 110, ..., 1000]
    y = []
    for i in x:
        estimator.fit(data[:i])
        y.append(math.fabs(mu - estimator.mu_))

    go.Figure(go.Scatter(x=x, y=y, mode='markers+lines'),
              layout=go.Layout(title="absolute distance between the estimated and true value of the "
                                     r"expectation, as a function of the sample size",
                               xaxis_title="number of samples",
                               yaxis_title="r$|\mu - \hat\mu|$")).show()

    # Question 3 - Plotting Empirical PDF of fitted model
    estimator.fit(data)
    pdf_data = estimator.pdf(data)

    go.Figure(go.Scatter(x=data, y=pdf_data, mode='markers'),
              layout=go.Layout(title="Empirical PDF of fitted model",
                               xaxis_title="samples",
                               yaxis_title="PDF values")).show()

def test_multivariate_gaussian():
    # Question 4 - Draw samples and print fitted model
    m = 1000
    mu = np.array([0, 0, 4, 0])
    cov = np.array([[1, 0.2, 0, 0.5],
                    [0.2, 2, 0, 0],
                    [0, 0, 1, 0],
                    [0.5, 0, 0, 1]])

    data = np.random.multivariate_normal(mu, cov, m)
    mult_estimator = MultivariateGaussian().fit(data)

    print(mult_estimator.mu_)
    print(mult_estimator.cov_)

    # Question 5 - Likelihood evaluation
    f = np.linspace(-10, 10, 200)
    log_likelihood_data = np.empty([f.size, f.size])

    for i in range(f.size):
        for j in range(f.size):
            mu = np.array([f[i], 0, f[j], 0])
            log_likelihood_data[i][j] = mult_estimator.log_likelihood(mu, cov, data)

    go.Figure(go.Heatmap(x=f, y=f, z=log_likelihood_data),
              layout=go.Layout(
                  title="Log Likelihood Heatmap",
                  xaxis_title="$f3\\ values$",
                  yaxis_title="$f1\\ values$")).show()

    # Question 6 - Maximum likelihood
    f1_index, f3_index = np.where(log_likelihood_data == np.amax(log_likelihood_data))
    f1, f3 = f[f1_index].round(3),  f[f3_index].round(3)
    # The Exercise 1 description didn't ask to print... to print uncomment next line
    # print(f1, f3)

if __name__ == '__main__':
    np.random.seed(0)
    test_univariate_gaussian()
    test_multivariate_gaussian()


# Reimplementing the notebook: https://github.com/pranavkantgaur/CourseraDLSpecialization/blob/master/Course1/Week1/Python_Basics_With_Numpy_v3a.ipynb from scratch.

import math
import numpy as np
import time

def basic_sigmoid(x):
    s = 1 / (1 + math.exp(-1.0 * x))
    return s

# limitation of math packahge
print(basic_sigmoid(3))
x = np.array([1, 2, 3])
print(np.exp(x))
#print(math.exp(x))

# without broadcasting
a = np.array([1, 2, 3])
b = np.array([2, 2, 2])
print ("a * b is: ", a * b)

#broadcasting
x = x + 3 # more memory-efficient
print("x is: ", x)

# two dimensions are same if, they are equal or one of them is 1

# axes are compared starting from the trailing axis to upwards.

# dimensions with 1 are streched or 'copied' to match the other.

# broadcasting in practice
x = np.arange(4)
print("x shape: ", x.shape)

xx = x.reshape(4, 1)
y = np.ones(5)
z = np.ones((3,4))

#print ("x + y is: ", x + y)
print ("xx + y is: ", xx + y)

# outer-addition operation using broadcasting
a = np.array([0, 1, 2, 3])
b = np.array([1, 2, 3])
print("Curent a's shape is: ", a.shape)
a = a[:, np.newaxis]
print("New a shape is: ", a.shape)

# implement sigmoid using numpy:
def numpy_sigmoid(x):
    s = 1 / (1 + np.exp(-1.0 * x))
    return s

x = np.array([1, 2, 3, 4])
print("Numpy's version of sigmoid:", numpy_sigmoid(x))

# sigmoid gradient, required for backpropogation implementation
# sigmoid graidient: sigma * (1- sigma)

def sigmoid_gradient(x):
    y = numpy_sigmoid(x)
    g = numpy_sigmoid(x) * (1 - numpy_sigmoid(x))
    return g

print("Sigmoid graidnent is: ", sigmoid_gradient(x))

# Reshaping arrays, exploring np.shape and np.reshape functions
# Note: np.reshape() and np.ndarray.reshape() are equivalent solutions to reshaping objects.
# np.reshape() is a function, whereas np.ndarray.reshape() is a method of class ndarray

x = np.zeros([256, 256, 3])
print("Current shape of x image is: ", x.shape)
x = x.reshape([256* 256 * 3, 1])
print("Image x shape is: ", x.shape)
y = x + 3 # result will be (256 X 256 X 3, 1)
print ("Shape of the augmented image is: ", y)

# implementing img2vect function for converting an 3-channel image to a 1D array
def img2vect(image):
  v = image.reshape(image.shape[0] * image.shape[1] * image.shape[2], 1)
  return v

image = np.array([[[1, 2, 3], [4, 5, 6], [7, 8, 9]], [[10,11,12],[13, 14, 15],[16, 17, 18]], [[19, 20, 21],[22, 23, 24],[25, 26, 27]]]) # 3 x3 x 3 array
v = img2vect(image)
print ("image vector: ", v)
print ("shape of image vector: ", v.shape)

# normalizing rows
def normalizeRows(x):
    x = np.linalg.norm(x, axis = 1, keepdims=True)
    return x

x = np.array([[1, 2, 3], [4, 5, 6]])
print("Original vector: ", x)
x_normalization_vector = normalizeRows(x)
normalized_x = x / x_normalization_vector # broadcasting, x.shape = (2, 3), x_normalization_vector.shape = (2, 1)
# x_normalization_vector reshaped into 2,3
print("Normalized vector: ", normalized_x)

# broadcasting and softmax function
def softmax(x):
    exp_x = np.exp(x)
    exp_x_normalization_vector = np.sum(exp_x, axis = 1, keepdims = True) #  keepdims=True makes sure that the following broadcasting will work.
    return exp_x / exp_x_normalization_vector

x = np.array([[1, 2, 3]]) # in nn forward inference, x will have shape (1, m), m: # training examples

x = np.array([
        [9, 2, 5, 0, 0],
            [7, 5, 0, 0 ,0]])

print("Softmax output: ", softmax(x))

# Vectorization: Classical dot, outer and elementwise product implementations
def classic_dot_product(v1, v2):
    vector_length = len(v1)
    dot_product = 0.0
    tick = time.process_time()
    for i in range(vector_length):
        dot_product += v1[i] * v2[i]
    tock = time.process_time()
    print("classic dot product took: ", tock - tick, "milliseconds!!")
    return dot_product

v1 = [1, 2, 4, 5, 6]
v2 = [7, 8, 9, 10, 11]
print("dot product of v1, v2: ", classic_dot_product(v1, v2))

def classic_outer_product(v1, v2):
    outer = np.zeros((len(v1), len(v2)))
    tick = time.process_time()
    for i in range(len(v1)):
        for j in range(len(v2)):
            outer[i, j] = v1[i] * v2[j]
    tock = time.process_time()
    print("classic outer product took: ", tock - tick, "milliseconds!!")
    return outer

print("outer product of v1, v2: ", classic_outer_product(v1, v2))

def classic_elementwise_product(v1, v2):
    elementwise_product = np.zeros((len(v1), 1))
    tick = time.process_time()
    for i in range(len(v1)):
        elementwise_product[i] = v1[i] * v2[i]
    tock = time.process_time()
    print("classic elementwise product took: ", tock-tick , "millisecods!!")
    return elementwise_product

print("classic elementwise product: ", classic_elementwise_product(v1, v2))

def classic_general_dot_product(v1):
    W = np.random.rand(3, len(v1))
    tick = time.process_time()
    gdot = np.zeros((W.shape[0], 1))
    for i in range(W.shape[0]):
        for j in range(len(v1)):
            gdot[i] += W[i, j] * v1[j]
    tock = time.process_time()
    print("classical general dot product took: ", tock - tick, "ms!!")
    return gdot

print("classic general dot product: ", classic_general_dot_product(v1))

## Lets Vectorize this code!!
def vectorized_dot_product(v1, v2):
  tick = time.process_time()
  dot_product = np.dot(v1, v2)
  tock = time.process_time()
  print("vectorized dot product took: ", tock - tick, "ms!!")
  return dot_product

print("vectorized dot product: ", vectorized_dot_product(v1, v2))

def vectorized_outer_product(v1, v2):
   tick = time.process_time()
   outer_product = np.outer(v1, v2)
   tock = time.process_time()
   print("vectorized outer product took: ", tock - tick, "ms!!")
   return outer_product

print("vectorized dot product: ", vectorized_outer_product(v1, v2))

def vectorized_elementwise_product(v1, v2):
    tick = time.process_time()
    elementwise_prod = np.multiply(v1, v2)
    tock = time.process_time()
    print("vectorized elementwise product took: ", tock - tick, "ms!!")
    return elementwise_prod

print("vectorized elementwise product: ", vectorized_elementwise_product(v1, v2))

def vectorized_general_dot_product(v1):
    W = np.random.rand(3, len(v1))
    tick = time.process_time()
    general_dot_product = np.dot(W, v1)
    tock = time.process_time()
    print("vectorized general dot product took: ", tock - tick, "ms!!")
    return general_dot_product

print("vectorized general dot product: ", vectorized_general_dot_product(v1))

# implementing numpy version of loss functions L1 and L2
def vectorized_L1(yhat, y):
    loss = np.sum(np.abs(yhat - y)) # yhat and y are (m, 1) vectors
    return loss

yhat = np.array([0.9, 0.2, 0.1, 0.4, 0.9])
y = np.array([1, 0, 0, 1, 1])
print("vectorized L1 result: ", vectorized_L1(yhat, y))

def vectorized_L2(yhat, y):
    loss = np.sum(np.square(yhat - y))
    return loss

print("vectorized L2 result: ", vectorized_L2(yhat, y))


'''
# Linear Regression: Decomposition Method
#----------------------------------
#
# Given Ax=b, solving for A:  y=a1*x1+a2*x2, a1=1
  for the sample  number of m
  XA=Y     shapel X:[m 2]  A:[2 1] Y:[m 1]
# This function shows how to use Tensorflow to
# solve linear regression via the matrix inverse.
why:comuter the INVERSE  (X.T*X)^(-1)  is inefficient when m ,n is big
# Given Ax=b, and a Cholesky decomposition such that
#  X.T*X= L*L' then we can get solve for A via
 X*A=Y->X.T*X*A=X.T*Y  s.t:X.T*X=L*L' ->L*L'*A=X.T*Y
 s.t:L'*A=Z  -> L*Z=X.T*Y
# 1) L*Z=X.T*Y  -> Z
# 2) L'*A=Z     -> A
'''
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
from tensorflow.python.framework import ops
ops.reset_default_graph()
sess=tf.Session()
x_vals=np.linspace(0,10,100)
y_vals=x_vals+np.random.normal(0,1,100)
x_vals_column=np.transpose(np.matrix(x_vals))
ones_column=np.matrix(np.ones((100,1)))#ones_column = np.transpose(np.matrix(np.repeat(1, 100)))
x=np.column_stack((ones_column,x_vals_column))
y=np.matrix(y_vals).T#y=np.transpose(np.matrix(y_vals))

#create tensors
x_tensor=tf.constant(x)
y_tensor=tf.constant(y)
# Find Cholesky Decomposition
x_xt=tf.matmul(tf.transpose(x_tensor),x_tensor)
L=tf.cholesky(x_xt)
#solve L*Z=X.T*Y
xt_y=tf.matmul(tf.transpose(x_tensor),y_tensor)
Z=tf.matrix_solve(L,xt_y)
#solve L'*A=Z
A=tf.matrix_solve(tf.transpose(L),Z)
A_eval=sess.run(A)
print("A:\n",A_eval)

#get the best fit line
best_fit_y=[]
for i in x_vals:
    best_fit_y.append(A_eval[0]+i*A_eval[1])
#best_fit_y=(np.dot(x,np.matrix(A_eval))).tolist()

#plot the fit line
plt.plot(x_vals,y_vals,"o",label="Data")
plt.plot(x_vals,best_fit_y,'r-',label="Best fit line",linewidth=3)
plt.legend(loc='upper letf')
plt.show()

cookbook/3 Linear Regression/Decomposition Method.py


import sympy

cos = sympy.cos
sin = sympy.sin
atan = sympy.atan2
acos = sympy.acos
asin = sympy.asin
pi = sympy.pi
sqrt = sympy.sqrt

tht = sympy.symbols('theta', real=True)
# xhat = 2*cos(tht)**2-sin(tht)**2
# yhat = 3*cos(tht)*sin(tht)
xhat = (2*cos(tht)**2-sin(tht)**2).expand().simplify()
yhat = (3*cos(tht)*sin(tht)).expand().simplify()
rsqr = xhat**2+yhat**2
# rsqr = xhat.expand().simplify()**2+yhat.expand().simplify()**2

sympy.pprint(xhat)
sympy.pprint(yhat)
# sympy.pprint(rsqr)
tidy_r2 = rsqr.expand().simplify()
sympy.pprint(tidy_r2)

# sympy.pprint(sqrt(rsqr))
sympy.pprint(sqrt(tidy_r2).expand().simplify())
tidy_r1 = sqrt(tidy_r2)

phi_tan = atan(yhat,xhat)
phi_sin = asin(yhat/tidy_r1)
phi_cos = acos(xhat/tidy_r1)

term1 = cos(phi_cos)
term2 = 3*cos(phi_cos-2*tht)
term2_alt = 3*(cos(phi_cos)*cos(2*tht)+sin(phi_sin)*sin(2*tht))

print('term1')
sympy.pprint(term1)
sympy.pprint(term2)
sympy.pprint(term2.expand())
sympy.pprint(term2_alt)
print('term2')
sympy.pprint(term2_alt.expand().simplify())


# -*- coding: utf-8 -*-
"""
Created on Mon Mar 29 19:56:28 2021

@author: Connor
"""
import numpy as np

def ode45_step(f, x, t, dt, *args):
    """
    One step of 4th Order Runge-Kutta method
    """
    k1 = dt * f(t, x, *args)
    k2 = dt * f(t + 0.5*dt, x + 0.5*k1, *args)
    k3 = dt * f(t + 0.5*dt, x + 0.5*k2, *args)
    k4 = dt * f(t + dt, x + k3, *args)
    return x + 1/6. * (k1 + 2*k2 + 2*k3 + k4)

def ode45(f, t, x0, *args):
    """
    4th Order Runge-Kutta method
    """
    n = len(t)
    x = np.zeros((n, len(x0)))
    x[0] = x0
    for i in range(n-1):
        dt = t[i+1] - t[i]
        x[i+1] = ode45_step(f, x[i], t[i], dt, *args)
    return x


#Autor: Angel Octavio Parada Flores
import matplotlib.pyplot as plt
import numpy as np
import sympy as sp
import math as mt

def Fourier(B_0, A_m, B_m, m, k, y):
    VA = 0
    VB = 0
    for i in range(0, m):
        VA += A_m[i]*np.sin((i+1)*2*k*y)
        VB += B_m[i]*np.cos((i+1)*2*k*y)
    return B_0 + VA + VB

x = sp.symbols("x")
m = int(input('Número de armónicos:', ))
I = float(input('Extremo inicial del periodo:',))
T = float(input('Periodo:', ))
F = sp.sympify(input('Función:', ))
p1 = input('¿Se va a reflejar la función? [S/n]:',)
p2 = 0
if p1 == 'n':
    p2 = input('¿Es una función en dos partes? [S/n]:',)

G = 0
if p1 == 'S':
    G = -F.subs(x, x - T/2)
elif p1 == 'n' and p2 == 'n':
    G = F
elif p1 == 'n' and p2 == 'S':
    G = sp.sympify(input('Función 2:', ))

k = mt.pi/T
B_0 = sp.integrate(F/T, (x, I, I+T/2)) + sp.integrate(G/T, (x, I+T/2, I+T))
A_m = []
B_m = []
for i in range (1, m+1):
    A_m.append(sp.integrate(F*(2/T)*sp.sin(i*2*k*x), (x, I, I+T/2)) + sp.integrate(G*(2/T)*sp.sin(i*2*k*x), (x, I+T/2, I+T)))
    B_m.append(sp.integrate(F*(2/T)*sp.cos(i*2*k*x), (x, I, I+T/2)) + sp.integrate(G*(2/T)*sp.cos(i*2*k*x), (x, I+T/2, I+T)))

y = np.arange(-T*3, T*3, 0.01)
plt.figure(figsize=(7,7))
plt.plot(y, Fourier(B_0, A_m, B_m, m, k, y), label = 'Núm. armónicos=' + repr(m))
plt.legend()
plt.grid()
plt.show()


import numpy as np

EPS = 1e-12

def safe_div(numerator, denominator):
    return numerator / (denominator + EPS)

def entropy(p, normalized=True):
    p = np.array(p, dtype=np.float32)
    if not normalized:
        p /= np.sum(p)
    ent = -1. * np.sum(p * np.log(p))
    return ent


'''
本模块储存着部分常用的模型

This module stores some commonly used models
'''
import numpy as np
from maysics import constant
from scipy.integrate import solve_ivp
from scipy.sparse import csr_matrix

def linear_r(x, y):
    '''
    线性回归模型

    参数
    ----
    x：一维或二维ndarray，自变量
    y：一维或二维ndarray，因变量

    返回
    ----
    一维ndarray：系数行矩阵
    损失函数：平均平方误差mse

    Linear Regression Model

    Parameters
    ----------
    x: 1-D or 2-D ndarray, independent variable
    y: 1-D or 2-D ndarray, dependent variable

    Return
    ------
    1-D ndarray: coefficient row matrix
    loss: mean squared error: mse
    '''
    x = np.array(x)
    if len(x.shape) == 1:
        x = np.array([x]).T
    y = np.array(y)
    x = np.hstack((x, np.ones((x.shape[0], 1))))
    coef = np.linalg.lstsq(x, y, rcond=None)[0].reshape(-1)
    y_pre = np.dot(x, np.array([coef]).T).reshape(y.shape)
    mse = ((y_pre - y)**2).mean()
    return coef, mse

def logistic(t, N0, r, K):
    '''
    Logistic人口增长模型
    该模型可得到解析解
    解的形式为：Nt = K * N0 / (N0 + (K - N0) * e**(-r * t))
    其中，Nt为t时刻的人口数

    参数
    ----
    t：时间
    N0：数或数组，现有人口数
    r：数类型，人口自然增长率
    K：数类型，环境资源允许的稳定人口数

    返回
    ----
    数，Nt

    Logisyic population growth models
    solution: Nt = K * N0 / (N0 + (K - N0) * e**(-r * t))
    Nt is the population at time 't'

    Parameters
    ----------
    t: time
    N0: num or array, initial population
    r: num, natural population growth rate
    K: num, stable population allowed by environmental resources

    Return
    ------
    num, Nt
    '''
    Nt_pre_down_ = N0 + (K - N0) * np.e**(-r * t)
    Nt = K * N0 / Nt_pre_down_
    return Nt

class ED():
    '''
    传染病模型
    本类可构建SI模型、SIR模型、SIRS模型、SEIR模型
    S是易感染者
    E是暴露者，染病但还不具有传染能力
    I是感染者，带有传染能力
    R是康复者(带有免疫力)或死者

    参数
    ----
    I0：数类型，初始感染者
    K：数类型，样本总数
    beta：数类型，感染率
    R：数类型，可选，初始康复者(带有免疫力)或死者，默认为0
    E：数类型，可选，暴露者，默认为0

    mathematical models of epidemic diseases
    this class is used to buil SI, SIR, SIRS, SEIR
    S means susceptible people
    E means exposed people, which are infected without ability to infect
    I means infected people with ability to infect
    R means recovered people with immunity or dead people

    Parameters
    ----------
    I0: initial infected people
    K: sample size
    beta: infection rate
    R: num, callable, recovered people with immunity or dead people, default=0
    E: num, callable, exposed people, default=0
    '''
    def __init__(self, I0, K, beta, R=0, E=0):
        self.I0 = I0
        self.K = K
        self.beta = beta
        self.R = R
        self.E = E
        self.S = K - I0 - R - E

    def SI(self, t):
        '''
        SI模型：
        该模型不需要再额外输入参数，且可得到解析解
        解的形式为：I = K*(1-(K-I0)/(K+I0+I0*e**(beta*K*t)))

        参数
        ----
        t：数或一维数组，需要预测的时间

        返回
        ----
        元组，(S(t), I(t))

        SI:
        no more parameters
        solution: I = K*(1-(K-I0)/(K+I0+I0*np.e**(beta*K*t)))

        Parameter
        ---------
        t: num or 1-D array, time for prediction

        Return
        ------
        tuple, (S(t), I(t))
        '''
        I_pre_down_ = self.K + self.I0 * (np.e**(self.beta * self.K * t) - 1)
        I_pre_up_ = self.K - self.I0
        I_func_ = self.K * (1 - I_pre_up_ / I_pre_down_)
        S_func_ = self.K - I_func_
        return S_func_, I_func_

    def SIR(self, gamma, t_span, method='RK45', t_eval=None):
        '''
        SIR模型：
        该模型不能得到解析解，给出的是基于solve_ivp得到的数值解数组

        参数
        ----
        gamma：数类型，康复率
        t_span：元组形式，求解的上下限
        method：字符串形式，可选，求解方法，可选择'RK45'、'RK23'、'DOP835'、'Radau'、'BDF'、'LSODA'
        t_eval：数组形式，可选，每当t等于该数组中的值时，会生成一个数值解

        返回
        ----
        solve_ivp数值解，顺序是S, I, R

        SIR:

        Parameters
        ----------
        gamma: num, recovery rate
        t_span: tuple, limits of solution
        method: str, callable, solving method, 'RK45', 'RK23', 'DOP835', 'Radau', 'BDF', 'LSODA' are optional
        t_eval: list, callable, when 't' equals the value in the list, it will generate a numerical solution

        Return
        ------
        numerical solution of solve_ivp, the order is S, I, R
        '''
        y0 = [self.S, self.I0, self.R]
        def epis_equas(t, x):
            y1 = -self.beta * x[1] * x[0]
            y2 = self.beta * x[1] * x[0] - gamma * x[1]
            y3 = gamma * x[1]
            return y1, y2, y3
        result = solve_ivp(epis_equas, t_span=t_span, y0=y0, method=method, t_eval=t_eval)
        return result.y

    def SIRS(self, gamma, alpha, t_span, method='RK45', t_eval=None):
        '''
        SIRS模型：
        该模型不能得到解析解，给出的是基于solve_ivp得到的数值解数组

        参数
        ----
        gamma：数类型，康复率
        alpha：衡量康复者获得免疫的时间
        t_span：元组形式，求解的上下限
        method：字符串形式，可选，求解方法，可选择'RK45'、'RK23'、'DOP835'、'Radau'、'BDF'、'LSODA'
        t_eval：数组形式，可选，每当t等于该数组中的值时，会生成一个数值解

        返回
        ----
        solve_ivp数值解，顺序是S, I, R

        SIRS:

        Parameters
        ----------
        gamma: num, recovery rate
        alpha: measuring the time that the recovered people with immunity
        t_span: tuple, limits of solution
        method: str, callable, solving method, 'RK45', 'RK23', 'DOP835', 'Radau', 'BDF', 'LSODA' are optional
        t_eval: list, callable, when 't' equals the value in the list, it will generate a numerical solution

        Return
        ------
        numerical solution of solve_ivp, the order is S, I, R
        '''
        y0= [self.S, self.I0, self.R]
        def epis_equas(t, x):
            y1 = -self.beta * x[1] * x[0] + alpha * x[2]
            y2 = self.beta * x[1] * x[0] - gamma * x[1]
            y3 = gamma * x[1] - alpha * x[2]
            return y1, y2, y3
        result = solve_ivp(epis_equas, t_span=t_span, y0=y0, method=method, t_eval=t_eval)
        return result.y

    def SEIR(self, gamma1, gamma2, alpha, t_span, method='RK45', t_eval=None):
        '''
        SEIR模型：
        该模型不能得到解析解，给出的是基于solve_ivp得到的数值解数组

        参数
        ----
        gamma1：数类型，潜伏期康复率
        gamma2：数类型，患者康复率
        alpha：数类型，衡量康复者获得免疫的时间
        t_span：元组形式，求解的上下限
        method：字符串形式，可选，求解方法，可选择'RK45'、'RK23'、'DOP835'、'Radau'、'BDF'、'LSODA'
        t_eval：数组形式，可选，每当t等于该数组中的值时，会生成一个数值解

        返回
        ----
        solve_ivp数值解，顺序是S, E, I, R

        SEIR:

        Parameters
        ----------
        gamma1: num, recovery rate of incubation
        gamma2: num, recovery rate of patients
        alpha: num, measuring the time that the recovered people with immunity
        t_span: tuple, limits of solution
        method: str, callable, solving method, 'RK45', 'RK23', 'DOP835', 'Radau', 'BDF', 'LSODA' are optional
        t_eval: list, callable, when 't' equals the value in the list, it will generate a numerical solution

        Return
        ------
        numerical solution of solve_ivp, the order is S, E, I, R
        '''
        y0 = [self.S, self.E, self.I0, self.R]
        def epis_equas(t, x):
            y1 = -self.beta * x[2] * x[0]
            y2 = self.beta * x[2] * x[0] - (alpha + gamma1) * x[1]
            y3 = alpha * x[1] - gamma2 * x[2]
            y4 = gamma1 * x[1] + gamma2 * x[2]
            return y1, y2, y3, y4
        result = solve_ivp(epis_equas, t_span=t_span, y0=y0, method=method, t_eval=t_eval)
        return result.y

class Leslie():
    '''
    Leslie模型
    解的形式为：Nt = M**t * N0
    其中，Nt是t时刻的个体数列表，M是Leslie矩阵

    参数
    ----
    N0：一维数组，各年龄层初始个体数目
    r：一维数组，各年龄层的生殖率
    s：一维数组，各年龄层到下一个年龄层的存活率
    age_range：整型，年龄段的跨度，默认为1

    属性
    ----
    Leslie_matrix：莱斯利矩阵

    Leslie model
    solution: Nt = M**t * N0
    Nt is the list of individuals at time 't' , M is Leslie matrix

    Parameters
    ----------
    N0: 1-D array, initial number of individuals in each age group
    r: 1-D array, reproductive rate of each age group
    s: 1-D array, survival rate to next age group of each group
    age_range: int, the span of age groups, default=1

    Attribute
    ---------
    Leslie_matrix: Leslie matrix
    '''
    def __init__(self, N0, r, s, age_range=1):
        self.N0 = np.array(N0)
        self.r = np.array(r)
        self.s = np.array(s)
        self.age_range = age_range

        Leslie_matrix = np.zeros((len(s), len(N0)))
        Leslie_matrix = np.vstack((r, Leslie_matrix))
        for i in range(1, len(N0)):
            Leslie_matrix[i][i-1] = s[i-1]
        self.Leslie_matrix = csr_matrix(Leslie_matrix)

    def predict(self, t):
        '''
        参数
        ----
        t：数或一维数组，时间

        返回
        ----
        数，Nt

        Parameter
        ---------
        t: num or 1-D array, time

        Return
        ------
        num, Nt
        '''
        t_times = t // self.age_range
        dense_Leslie_matrix = self.Leslie_matrix.todense()
        Nt = self.N0 * dense_Leslie_matrix**t_times
        return np.array(Nt)[0]


#Brunston Poon (c) 2015, MIT License
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
np.set_printoptions(suppress=True)

#data = np.loadtxt("gravity.csv",delimiter=",")
data = np.loadtxt("gravity.txt")
p, t = data[:,0], data[:,1]
#print("p:", p)
#print("t:", t)
v = []
for position in range(len(p)):
    if position == 0:
        v.append(p[position]/0.025)
    if position > 0:
        v.append((p[position]-p[position-1])/0.025)
plt.plot(t,p, 'o', label="Position vs Time", markersize = 3)
plt.legend()
plt.show()
plt.plot(t,v,'o', label="Velocity (from avg velocity) vs Time", markersize = 3)
plt.legend()
plt.show()

poly = np.polyfit(t,p,2)
print("p(t), ax^2+bx+c = {0}x^2 + {1}x + {2}".format(poly[0], poly[1], poly[2]))
A = np.vstack([t, np.ones(len(t))]).T
vtm, vtc = np.linalg.lstsq(A, v)[0]

vderiv = []
for position in range(len(p)):
    vderiv.append(2*poly[0]*position + poly[1])
plt.plot(t,vderiv,'o', label="Velocity (from deriv) vs Time", markersize = 3)
plt.legend()
plt.show()
print("v(t) from v_avg, cm/s = {0}x + {1}".format(vtm, vtc))
print("a(t) from v_avg, cm/m^s = {0}".format(vtm))
print("a(t) from v_avg, m/m^s = {0}".format(vtm/100))
print("a(t) from p(T), m/m^s = 2*a/100 = {0}".format(2*poly[0]/100))


# -*- coding: utf-8 -*-
"""
Created on Wed Dec  5 20:13:48 2018

@author: Moaz Magdy
"""
import numpy as np
import matplotlib.pyplot as plt

#Simple Linear regression without using matrices

# y = mx + b
# m = theta1, b = theta0

def cal_error(m,b,points): #Difference between labels and predictions
        totalerr = np.zeros(len(points))
        for i in range(len(points)):
            x = points[i,0]
            y = points[i,1]
            totalerr[i] = (y - ((m*x) + b))**2
        return sum(totalerr)/(float(len(points)))

def gradient_descent(learning_rate,num_iterations,initial_m,initial_b,points):
        N = len(points)
        plt.scatter(points[:,0], points[:,1])
        plt.show()
        updated_b = initial_b
        updated_m = initial_m
        for j in range(num_iterations):
            temp_b = 0
            temp_m = 0
            for i in range(0, len(points)):
                x = points[i,0]
                y = points[i,1]
                temp_b += -(2/N) * (y - ((initial_m * x) + initial_b))        # += To sum all values sequentially
                temp_m += -(2/N) * (y - ((initial_m * x) + initial_b)) * x
            updated_b = initial_b - (learning_rate * temp_b)
            updated_m = initial_m - (learning_rate * temp_m)
            initial_m = updated_m
            initial_b = updated_b
        return [updated_b, updated_m]

def run():
     points = np.loadtxt('C:\ex1data1.txt',delimiter=',')
     learning_rate = 0.0001
     num_iterations = 1000
     initial_m = 0
     initial_b = 0
     [b, m] = gradient_descent(learning_rate,num_iterations,initial_m,initial_b,points)
     print ("After {0} iterations b = {1}, m = {2}, error = {3}".format(num_iterations,b,m,cal_error(m,b,points)))

if __name__ == "__main__":
    run()


"""
@package coord
@author Anna Schneider
@version 0.1
@brief Contains class for Coord
"""

# import from standard library
import math

# import external packages
import numpy as np

class Coord(object):
    """ Cartesian and polar coordinates for given (x,y) pair.
        theta is in range (-pi, pi), degrees is in range(-180,180)
    """
    ##
    # @var x
    # @brief Float for x coord (Cartesian)
    #
    # @var y
    # @brief Float for y coord (Cartesian)

    def __init__(self, dx, dy):
        """ Constructor. Calculates (r, theta) given (x,y).

        @param dx Float for x coord

        @param dy Float for y coord

        """
        self.x = float(dx)
        self.y = float(dy)
        self._theta = None
        self._degrees = None
        self._r = None

    @property
    def r(self):
        """ Distance from the origin """
        if self._r is None:
            self._r = math.sqrt(self.x*self.x + self.y*self.y)
        return self._r

    @property
    def theta(self):
        """ Angle in radians for (r,theta) """
        if self._theta is None:
            if self.r > 0:
                c = self.x / self.r
                s = self.y / self.r
                self._theta = math.atan2(s,c)
            else:
                self._theta = 0.0

        # return
        return self._theta

    @property
    def degrees(self):
        """ Angle in degrees for (r,theta) """
        if self._degrees is None:
            self._degrees = math.degrees(self.theta)
        return self._degrees

    def rotate(self, radians):
        """ Return a new Coord that is rotated counterclockwise
            by the given number of radians. """
        new_x = self.x * math.cos(radians) + self.y * -math.sin(radians)
        new_y = self.x * math.sin(radians) + self.y * math.cos(radians)
        return Coord(new_x, new_y)

    def __repr__(self):
        """ Returns a printable string. """
        retstr = "(x,y) = (%2.1f, %2.1f); " % (self.x,self.y)
        degs = np.remainder(self.degrees, 360.0)
        retstr += "(r,theta) = (%2.1f, %3.1f)" % (self.r,degs)
        return retstr

    def __add__(self, other):
        """ Add two coords.

        @param self The left-hand coord

        @param other The right-hand coord

        @retval Coord object containing sum

        """
        return Coord(self.x + other.x, self.y + other.y)

    def __sub__(self, other):
        """ Subtract right-hand coord from left-hand.

        @param self The left-hand coord

        @param other The right-hand coord

        @retval Coord object containing sum

        """
        return Coord(self.x - other.x, self.y - other.y)

    def __eq__(self, other):
        tol = 1e-4
        return math.fabs(self.x - other.x) < tol and math.fabs(self.y - other.y) < tol


# coding: latin-1

# para executar no google colab:
# https://colab.research.google.com/drive/1rWLHIr5kqqI9BKYDBZHXXOlrkIIpR4OC?usp=sharing

from math import sqrt, log, e, sin, cos

class Bissecao:
    def __init__(self,a,b, precisao, numCasasDecimais):
        # a e b são os intervalos iniciais
        self.a = a #  início do intervalo
        self.b = b # fim do intervalo
        self.precisao = precisao
        self.numCasasDecimais = numCasasDecimais

    def funcao(self, x):
        return x**3-9*x+3

    def acharIntervalos(self):
        primeiro = True
        x = self.a
        x2 = None
        Fx2 = None
        raizes = []
        while x <= self.b:

            Fx = self.funcao(x)

            if not primeiro:
                if (Fx2 < 0 and Fx >= 0) or (Fx2 >= 0 and Fx < 0):
                    raizes.append([x2, x])

            Fx2 = Fx
            x2 = x
            primeiro = False
            x +=  1

        return raizes

    def metodoBissecao(self):
        raizes = self.acharIntervalos()

        if not raizes:
            print("Não foi possível encontrar raízes!\n\n")
        elif len(raizes) == 1:
            print("Foi encontrado 1 raiz\n\n")
        else:
            print(f"Foram encontradas {len(raizes)} raízes.\n\n")

        for raiz in raizes:
            a = round(raiz[0], self.numCasasDecimais)
            b = round(raiz[1], self.numCasasDecimais)
            #qtdEspacos = self.numCasasDecimais * 2
            iteracao = 1
            #n =  qtdEspacos + 1

            print("Iteração" , " " * self.qtdEspacos("iteracao") , "a" , " " * self.qtdEspacos("a") ,
                  "b" , " "  * self.qtdEspacos("b") , "m" , " " * self.qtdEspacos("m") , "b-a" , " " * (27) ,
                  "f(a)"  , "   "  , "f(m)" , "   " , "f(b)", sep="")

            while b-a > self.precisao:
                m = round((a+b)/2, self.numCasasDecimais)
                Fm = self.funcao(m)
                Fa = self.funcao(a)
                Fb = self.funcao(b)

                aSinal = "+" if Fa > 0 else "-"
                mSinal = "+" if Fm > 0 else "-"
                bSinal = "+" if Fb > 0 else "-"

                print(str(iteracao) , " " * self.qtdEspacos(iteracao) , str(a) , " " * self.qtdEspacos(a) , str(b) ,
                      " " *  self.qtdEspacos(b) , str(m) , " " *  self.qtdEspacos(m) , str(b-a) ,
                      " " * (30 - self.qtdCaracteres(b-a)) , str(aSinal)  , " " * 6 , str(mSinal)  , " " * 6  , str(bSinal),
                      sep='')

                if Fm * Fa < 0:
                    b = m
                elif Fm * Fb < 0:
                    a = m
                if iteracao > 50:
                    print("Limite máximo de iterações foi atingido!")
                    break

                iteracao += 1

            print(str(iteracao) , " " * self.qtdEspacos(iteracao) , str(a) , " " * self.qtdEspacos(a) , str(b) ,
                  " " *  self.qtdEspacos(b) , str(m) , " " *  self.qtdEspacos(m) , str(b-a) ,
                  " " * (30 - self.qtdCaracteres(b-a)) , str(aSinal)  , " " * 6   , str(mSinal)  , " " * 6  , str(bSinal), sep="")

            print("\n\n")

    def qtdCaracteres(self,num):
        num = str(num)
        return len(num)

    def qtdEspacos(self, num):
        num = str(num)
        return  self.numCasasDecimais * 2 + 9 - len(num)

    # def grafico(self):
    #     import matplotlib.pyplot as plt
    #     import numpy as np

    #     arrayValores = np.arange(self.a, self.b, 0.1)
    #     plt.plot(arrayValores, self.funcao(arrayValores))
    #     plt.show()

x = Bissecao(-5,5, 10**-5, 8)

#print(x.acharIntervalos())

x.metodoBissecao()

#x.grafico()


import numpy as np

def sshist(x, N=range(2, 501), SN=30):
    """
    Returns the optimal number of bins in a histogram used for density
    estimation.

    Optimization principle is to minimize expected L2 loss function between
    the histogram and an unknown underlying density function.
    An assumption made is merely that samples are drawn from the density
    independently each other.

    The optimal binwidth D* is obtained as a minimizer of the formula,
    (2K-V) / D^2,
    where K and V are mean and variance of sample counts across bins with width
    D. Optimal number of bins is given as (max(x) - min(x)) / D.

    Parameters
    ----------
    x : array_like
        One-dimensional data to fit histogram to.
    N : array_like, optional
        Array containing number of histogram bins to evaluate for fit.
        Default value = 500.
    SN : double, optional
        Scalar natural number defining number of bins for shift-averaging.

    Returns
    -------
    optN : int
        Optimal number of bins to represent the data in X
    N : double
        Maximum number of bins to be evaluated. Default value = 500.
    C : array_like
        Cost function C[i] of evaluating histogram fit with N[i] bins

    See Also
    --------
    sskernel, ssvkernel

    References
    ----------
    .. [1] H. Shimazaki and S. Shinomoto, "A method for selecting the bin size
           of a time histogram," in  Neural Computation 19(6), 1503-1527, 2007
           http://dx.doi.org/10.1162/neco.2007.19.6.1503
    """

    # determine range of input 'x'
    x_min = np.min(x)
    x_max = np.max(x)

    # get smallest difference 'dx' between all pairwise samples
    buf = np.abs(np.diff(np.sort(x)))
    dx = min(buf[buf > 0])

    # setup bins to evaluate
    N_MIN = 2
    N_MAX = min(np.floor((x_max - x_min) / (2*dx)), max(N))
    N = range(N_MIN, N_MAX+1)
    D = (x_max - x_min) / N

    # compute cost function over each possible number of bins
    Cs = np.zeros((len(N), SN))
    for i, n in enumerate(N):  # loop over number of bins
        shift = np.linspace(0, D[i], SN)
        for p, sh in enumerate(shift):  # loop over shift window positions

            # define bin edges
            edges = np.linspace(x_min + sh - D[i]/2,
                                x_max + sh - D[i]/2, N[i]+1)

            # count number of events in these bins
            ki = np.histogram(x, edges)

            # get mean and variance of events
            k = ki[0].mean()
            v = np.sum((ki[0] - k)**2) / N[i]

            Cs[i, p] = (2*k - v) / D[i]**2

    # average over shift window
    C = Cs.mean(axis=1)

    # get bin count that minimizes cost C
    idx = np.argmin(C)
    optN = N[idx]
    optD = D[idx]
    edges = np.linspace(x_min, x_max, optN)

    return optN, optD, edges, C, N


import numpy as np
import math
import scipy.stats

def calculate_implied_volatility_bs(maturity, strike, spot, q, price, threshold = 0.01, initial = 0.5):
	'''
	Calculates the implied volatility of the option using the Black-Scholes formula and the
	Newton method.
	'''

	# Initialize
	sigma = initial
	diff = 1

	while diff > threshold:
		# Calculate Black-Scholes spot
		d1 = (np.log(spot/strike) + (q**2 + sigma**2/2) * maturity)/(sigma*math.sqrt(maturity))
		d2 = d1 - sigma * math.sqrt(maturity)
		bs = spot * scipy.stats.norm.cdf(d1) - strike * math.exp(-q * maturity) * scipy.stats.norm.cdf(d2)

		# First derivaive of BS w.r.t volatility
		bs_der = spot * math.sqrt(maturity) * math.exp(-d1**2 / 2) / (math.sqrt(2 * math.pi))

		# Calculate the volatility difference
		diff = (price - bs) / bs_der

		# updaede
		sigma += diff

	return sigma

def delta(maturity, strike, spot, q, sigma):
	'''
	Calculates the option delta (price-sensitivity)
	'''

	# d1 from the Black-Scholes equation
	d1 = (np.log(spot/strike) + (q**2 + sigma**2 / 2) * maturity)/(sigma*math.sqrt(maturity))

	return scipy.stats.norm.cdf(d1)

def vega(maturity, strike, spot, q, sigma):
	'''
	Calculates the option vega (volatility-sensitivity)
	'''

	# d1 from the Black-Scholes equation
	d1 = (np.log(spot/strike) + (q**2 + sigma**2/2) * maturity) / (sigma * math.sqrt(maturity))

	# Calculate vega value
	vega = spot * math.exp(-d1**2 / 2)*math.sqrt(maturity) / (math.sqrt(2 * math.pi))

	# Return as fraction
	return vega/100

def delta_hedge(maturity, strike, spot, q, sigma):
	'''
	Returns the amount of stock to buy (short) for any given option position to
	make the position delta-neutral
	'''

	# Calculate delta. This is the number to short the underlying
	delta_bs = delta(maturity, strike, spot, q, sigma)

	return -delta_bs

def vega_hedge(maturity_1, maturity_2, strike, spot, q, sigma):
	'''
	Returns the multipliers on which to make the portfolio vega-neutral. We should buy
	alpha amount of the underlying and eta amount of replicating option
	with a longer maturity than the original option, maturity_2 > maturity_1
	'''

	# Calculate hedged option and replication option deltas
	delta_bs = delta(maturity_1, strike, spot, q, sigma)
	delta_rep = delta(maturity_2, strike, spot, q, sigma)

	# Calculate hedged option and replication option vegas
	vega_bs = vega(maturity_1, strike, spot, q, sigma)
	vega_rep = vega(maturity_2, strike, spot, q, sigma)

	# Alpha is the amount of stock to hold - eta is the amount of replication stock to hold
	alpha = -delta_bs + vega_bs / vega_rep * delta_rep
	eta = - vega_bs / vega_rep

	return alpha, eta

def delta_hedge_butterfly(maturity, strikes, spot, q, sigma):
	'''
	Consider you have a butterfly centered around strikes[1] with ITM and OTM call options at indexes
	0 and 2 respectively. This funcion returns you the total number of underlying to hedge the position.
	'''

	# Calculate the values
	long_ITM = delta_hedge(maturity, strikes[0], spot, q, sigma)
	long_OTM = delta_hedge(maturity, strikes[2], spot, q, sigma)
	short_ATM = delta_hedge(maturity, strikes[1], spot, q, sigma)

	return long_ITM + long_OTM - 2 * short_ATM

def vega_hedge_butterfly(maturity_1, maturity_2, strikes, spot, q, sigma):
	'''
	Consider you have a butterfly centered around strikes[1] with ITM and OTM call options at indexes
	0 and 2 respectively. This funcion returns you the total number of underlying to hedge the position
	for both alpha (the underlying) and eta (repeating call of longer maturity)
	'''

	# Calculate the values
	alpha_long_ITM, eta_long_ITM = vega_hedge(maturity_1, maturity_2, strikes[0], spot, q, sigma)
	alpha_long_OTM, eta_long_OTM = vega_hedge(maturity_1, maturity_2, strikes[2], spot, q, sigma)
	alpha_short_ATM, eta_short_ATM = vega_hedge(maturity_1, maturity_2, strikes[1], spot, q, sigma)

	return (alpha_long_ITM + alpha_long_OTM - 2 * alpha_short_ATM), eta_long_ITM, eta_long_OTM, - 2 * eta_short_ATM


import numpy as np
from scipy import spatial

def init_centers(X, k = 5):
    """function takes in an np.array() of N dimensions and cluster centers k
    and returns k number of points from the array"""
    rand_index = np.random.choice(len(X),size = k, replace=False)
    return X[rand_index, : ]

def dist_from_cen(X, centers, order_num = 1):
    """function takes N-D array X and N-D array of centers of length k
    and returns the distance of each element in X to centers"""
    m = len(X)
    if len(centers.shape) == 1:
        centers = centers.reshape((1,len(centers)))

    k = len(centers)

    dist_matrix = np.empty((m,k))  # len(X) long, len(centers) wide

    for i in range(m):
        dist = np.linalg.norm(X[i, :] - centers, ord=order_num, axis=1)
        # for each coordinate point find dist to all centers
        dist_matrix[i, :] = dist ** order_num

    return dist_matrix

def assign_cluster(dist_matrix):
    """ funtion takes in the distance matrix and finds the minimum distance t
        to the cluster centers"""
    return np.argmin(dist_matrix, axis = 1)

def new_center(X, cluster_labels):
    """Function groups all values assigned to cluster and computes the new
    cluster center from the average of all points in the N-D matrix"""

    m, d = X.shape
    k = max(cluster_labels) + 1

    updated_centers = np.empty((k, d))
    for i in range(k):
        updated_centers[i, :d] = np.mean(X[cluster_labels == i, :], axis=0)
    return updated_centers

def has_converged(old_centers, centers):
    return set([tuple(x) for x in old_centers]) == set([tuple(x) for x in centers])

def k_means(X, k, start_centers = None, max_steps = np.inf, o_num = 2):
    if start_centers is None:
        centers = init_centers(X,k)
    else:
        centers = start_centers

    cnt =1
    converged = False
    labels = np.zeros(len(X))

    while (not converged) and (cnt <= max_steps):
        old_centers = centers
        dist = dist_from_cen(X,old_centers, order_num= o_num)
        labels = assign_cluster(dist)
        centers = new_center(X, labels)
        converged = has_converged(old_centers, centers)
        cnt +=1

    return labels , centers

def k_mediod(X, k, o_num = 2,start_centers = None, max_steps = np.inf):

    if start_centers is None:
        centers = init_centers(X,k)
    else:
        centers = start_centers

    cnt = 1
    converged = False
    labels = np.zeros(len(X))

    while (not converged) and (cnt < max_steps):
        old_centers = centers
        dist = dist_from_cen(X, old_centers, order_num= o_num)
        labels = assign_cluster(dist)
        centers = mediod_update(X, old_centers, labels, pnum = o_num)
        converged = has_converged(old_centers, centers)
        cnt +=1

    return labels , centers


import numpy as np
import matplotlib.pyplot as plt

## Performs LU factorization with partial pivoting
def lup(A):
	m = A.shape[0]
	U = A.copy()
	L, P = np.eye(m), np.eye(m)

	for k in range(m-1):
		# Selects the index of the max element and swaps the required rows
		i = k + np.argmax(abs(U[k:,k]))
		U[[k,i],k:] = U[[i,k],k:]
		L[[k,i],:k] = L[[i,k],:k]
		P[[k,i],:] = P[[i,k],:]

		for j in range(k+1,m):
			L[j,k] = U[j,k]/U[k,k]
			U[j,k:] = U[j,k:] - L[j,k] * U[k,k:]
	return P, L, U

## Solves Ax=b using the partial pivoting LU decomposition as done above
def solveLup(P,L,U,b):
	# From Ax=b => PAx = Pb
	m = P.shape[0]
	b = P @ b

	# From PAx=b => LUx = b
	# Taking Ux=w => Lw = b
	# Solving the above equation to get w
	w = np.zeros(m)
	for i in range(m):
		rhs = b[i]
		for j in range(i):
			rhs = rhs - L[i,j] * w[j]
		w[i] = (rhs * 1.)/(L[i,i] * 1.)

	# Solving Ux = w to get x
	x = np.zeros(m)
	i = m-1
	while i>=0:
		rhs = w[i]
		for j in range(i+1,m):
			rhs = rhs - U[i,j] * x[j]
		x[i] = (rhs * 1.)/(U[i,i] * 1.)
		i = i-1

	return x.reshape(-1,1)

## Matrix A for which catastrophic rounding results occur
def instabilityMatrix(m):
	A = np.eye(m)
	A[:,m-1] = 1.
	for i in range(1,m):
		A[i,:i] = -1.
	return A

## Performs rook pivoting of the matrix U
def rook_pivot(U,row,col,k):
	row_pivot = k + np.argmax(abs(U[k:,col]))
	col_pivot = k + np.argmax(abs(U[row_pivot,k:]))

	if row_pivot==row and col_pivot==col:
		return row,col
	else:
		return rook_pivot(U,row_pivot,col_pivot,k)

## Performs LU factorization with rook pivoting
def lupq(A):
	m = A.shape[0]
	U = A.copy()
	L, P, Q = np.eye(m), np.eye(m), np.eye(m)

	for k in range(m-1):
		# Selects the index of the max element as obtained by rook pivoting
		i,l = rook_pivot(U,k,k,k)

		# Swaps the k,i rows and k,l columns of U
		U[[k,i],k:] = U[[i,k],k:]
		U[:,[k,l]] = U[:,[l,k]]

		# Swaps the k,i rows of L
		L[[k,i],:k] = L[[i,k],:k]

		# Swaps the k,i rows of P and k,l columns of Q
		P[[k,i],:] = P[[i,k],:]
		Q[:,[k,l]] = Q[:,[l,k]]

		for j in range(k+1,m):
			L[j,k] = U[j,k]/U[k,k]
			U[j,k:] = U[j,k:] - L[j,k] * U[k,k:]
	return P, Q, L, U

## Solves Ax=b using the rook pivoting LU decomposition as done above
def solveLupq(P,Q,L,U,b):
	# Making use of the solveLup function to get an x
	x = solveLup(P,L,U,b)

	# Now, making use of the Q matrix to get x=Qx
	x = Q @ x
	return x.reshape(-1,1)

## Generates the plots for growth-factor and backward-error
def get_plots(strin):
	m = range(1,61)
	rho_1, rho_2 = [],[]
	error_1, error_2 = [],[]

	for i in m:
		A = instabilityMatrix(i)
		b = np.random.randn(i,1)

		P, L, U = lup(A)
		x = (solveLup(P,L,U,b))
		rho_1.append((abs(U).max())/(abs(A).max()))
		error_1.append((np.linalg.norm((A @ x) - b,2))/(np.linalg.norm(b,2)))

		P, Q, L, U = lupq(A)
		x = (solveLupq(P, Q, L, U, b))
		rho_2.append((abs(U).max())/(abs(A).max()))
		error_2.append((np.linalg.norm((A @ x) - b,2))/(np.linalg.norm(b,2)))

	if strin=='growth':
		plt.plot(m,rho_1,'r',m,rho_2,'b')
		plt.xlabel('The dimensions of the matrix')
		plt.ylabel('The growth factor corresponding to the Instability matrix')
		plt.legend(['LU with partial pivoting','LU with rook pivoting'])
		plt.yscale('log')
		plt.title('Semi-log plot of growth factor v/s matrix dimensions')
		plt.savefig('q6_plot_growth.png')
	else:
		plt.plot(m,error_1,'r',m,error_2,'b')
		plt.xlabel('The dimensions of the matrix')
		plt.ylabel('The relative backward error for solving Ax=b')
		plt.legend(['LU with partial pivoting','LU with rook pivoting'])
		plt.yscale('log')
		plt.title('Semi-log plot of Relative Backward Error v/s matrix dimensions')
		plt.savefig('q6_plot_error.png')

## Checks for the correctness of the algorithms implemented
def check_correctness():
	A = instabilityMatrix(5)
	b = np.array([1.0, 2.0, 13.0,4.0,-2.0]).reshape(-1,1)

	print('Solved with the decomposition: PA = LU')
	P, L, U = lup(A)
	print(solveLup(P,L,U,b))
	print()

	print('Solved with the decomposition: PAQ = LU')
	P, Q, L, U = lupq(A)
	print(solveLupq(P, Q, L, U, b))
	print()

	print('Solved using the numpy solver')
	print(np.linalg.solve(A,b))
	print()

if __name__ == '__main__':
	get_plots('error')


from __future__ import division, print_function

import numpy as np
from scipy.signal import firls, freqz
import matplotlib.pyplot as plt

numtaps = 43

fs = 200
f1 = 15
f2 = 30

bands = np.array([0, f1, f1, f2, f2, 0.5*fs])
desired = np.array([1,  1,  1,  0,  0,      0])
taps1 = firls(numtaps, bands, desired, fs=fs)

w1, h1 = freqz(taps1, worN=8000)
w1 *= 0.5*fs/np.pi

wts = [100, .01, 1]
taps2 = firls(numtaps, bands, desired, weight=wts, fs=fs)

w2, h2 = freqz(taps2, worN=8000)
w2 *= 0.5*fs/np.pi

plt.figure(figsize=(4.0, 4.5))

plt.subplot(3, 1, 1)

for band, des in zip(bands.reshape(-1, 2), desired.reshape(-1, 2)):
    plt.plot(band, des, 'k', alpha=0.1, linewidth=4)
plt.plot(w1, np.abs(h1), alpha=0.9, label='uniform weight')
plt.plot(w2, np.abs(h2), alpha=0.9, label='weight:%s' % (wts,))

plt.ylim(-0.1, 1.1)
plt.ylabel('Gain')
ax = plt.gca()
xticks = np.r_[bands[::2], bands[-1]]
ax.set_xticks(xticks)
xticklabels = ['%g' % f for f in xticks]
ax.set_xticklabels(xticklabels)
plt.grid(alpha=0.25)
plt.legend(framealpha=1, shadow=True)
plt.title('Least Squares Filter Design', fontsize=10)

plt.subplot(3, 1, 2)

for band, des in zip(bands.reshape(-1, 2), desired.reshape(-1, 2)):
    plt.plot(band, des, 'k', alpha=0.1, linewidth=4)
plt.plot(w1, np.abs(h1), alpha=0.9)
plt.plot(w2, np.abs(h2), alpha=0.9)

ax = plt.gca()
ax.set_xticks(xticks)
ax.set_xticklabels(xticklabels)

plt.ylim(0.985, 1.015)
plt.ylabel('Gain')
plt.xlim(0, 1.1*f1)

plt.grid(alpha=0.25)

plt.subplot(3, 1, 3)

for band, des in zip(bands.reshape(-1, 2), desired.reshape(-1, 2)):
    plt.plot(band, des, 'k', alpha=0.1, linewidth=4)
plt.plot(w1, np.abs(h1), alpha=0.9)
plt.plot(w2, np.abs(h2), alpha=0.9)

ax = plt.gca()
ax.set_xticks(xticks)
ax.set_xticklabels(xticklabels)

plt.ylim(-0.002, 0.02)
plt.xlim(0.87*f2, 0.5*fs)
plt.grid(alpha=0.25)
plt.ylabel('Gain')

plt.xlabel('Frequency (Hz)')

plt.tight_layout()
# plt.savefig('firls_example.pdf')
plt.show()


import numpy as np

from typing import List
from collections import namedtuple

class BrownianMotion(object):

    BrownianOutcome = namedtuple('BrownianOutcome', ['history_vector', 'result'])

    def __init__(self, mean=0, stdev=1, seed=None):
        self.mean = mean
        self.stdev = stdev

        if seed:
            np.random.seed(seed)

    def _step(self, current):
        return current + np.random.normal(self.mean, self.stdev)

    def run_simulation(self, num_steps=1000, initial_value=0) -> BrownianOutcome:
        """Runs a brownian motion simulation based on the initial mean and standard deviation provided."""
        current = initial_value
        history_vector = [current]
        for i in range(0, num_steps):
            current = self._step(current)
            history_vector.append(current)

        return self.BrownianOutcome(history_vector=history_vector, result=current)


#%% Clear variable list
def clearall():
    """clear all globals"""
    for uniquevar in [var for var in globals().copy() if var[0] != "_" and var != 'clearall']:
        del globals()[uniquevar]
clearall()

#%%
# Python imports
import numpy as np  # Matrix and vector computation package
import matplotlib.pyplot as plt  # Plotting library
#import seaborn as sns
# Allow matplotlib to plot inside this notebook
#%matplotlib inline
# Set the seed of the np random number generator so that the tutorial is reproducable
np.random.seed(seed=1)

#%%
# Define the vector of input samples as x, with 20 values sampled from a uniform distribution between 0 and 1
x = np.random.uniform(0, 1, 20)

# Generate the target values t from x with small gaussian noise so the estimation won't be perfect.
# Define a function f that represents the line that generates t without noise
def f(x): return x * 2

# Create the targets t with some gaussian noise
noise_variance = 0.2  # Variance of the gaussian noise
# Gaussian noise error for each sample in x
noise = np.random.randn(x.shape[0]) * noise_variance
# Create targets t
t = f(x) + noise

# Plot the target t versus the input x
plt.plot(x, t, 'o', label='t')
# Plot the initial line
plt.plot([0, 1], [f(0), f(1)], 'b-', label='f(x)')
plt.xlabel('$x$', fontsize=15)
plt.ylabel('$t$', fontsize=15)
plt.ylim([0,2])
plt.title('inputs (t) vs targets (x)')
plt.grid()
plt.legend(loc=2)

#%%
# Define the neural network function y = x * w
def nn(x, w): return x * w

# Define the cost function
def cost(y, t): return ((t - y)**2).sum()

# Define a vector of weights for which we want to plot the cost
ws = np.linspace(0, 4, num=100)  # weight values
cost_ws = np.vectorize(lambda w: cost(nn(x, w) , t))(ws)  # cost for each weight in ws

# Plot the cost vs the given weight w
plt.plot(ws, cost_ws, 'r-')
plt.xlabel('$w$', fontsize=15)
plt.ylabel('$cost=SSE$', fontsize=15)
plt.title('cost vs weight')
plt.grid()

#%%
# define the gradient function. Remember that y = nn(x, w) = x * w
def gradient(w, x, t): return 2 * x * (nn(x, w) - t)

# define the update function delta w
def delta_w(w_k, x, t, learning_rate):
    return learning_rate * gradient(w_k, x, t).sum()

# Set the initial weight parameter
w = 0.1
# Set the learning rate
learning_rate = 0.1

# Start performing the gradient descent updates, and print the weights and cost:
nb_of_iterations = 4  # number of gradient descent updates
for i in range(nb_of_iterations):
    # Print the current w, and cost
    print('w({}): {:.4f} \t cost: {:.4f}'.format(i, w, cost(nn(x, w), t)))
    dw = delta_w(w, x, t, learning_rate)  # get the delta w update
    w = w - dw  # update the current weight parameter

# Print the final w, and cost
print('w({}): {:.4f} \t cost: {:.4f}'.format(nb_of_iterations, w, cost(nn(x, w), t)))

#%%
w = 0.1  # Set the initial weight parameter
# Start performing the gradient descent updates, and plot these steps
plt.plot(ws, cost_ws, 'r-')  # Plot the error curve
nb_of_iterations = 2  # number of gradient descent updates
for i in range(nb_of_iterations):
    dw = delta_w(w, x, t, learning_rate)  # get the delta w update
    # Plot the weight-cost value and the line that represents the update
    plt.plot(w, cost(nn(x, w), t), 'bo')  # Plot the weight cost value
    plt.plot([w, w-dw],[cost(nn(x, w), t), cost(nn(x, w-dw), t)], 'b-')
    plt.text(w, cost(nn(x, w), t)+0.5, '$w({})$'.format(i))
    w = w - dw  # update the current weight parameter

# Plot the last weight, axis, and show figure
plt.plot(w, cost(nn(x, w), t), 'bo')
plt.text(w, cost(nn(x, w), t)+0.5, '$w({})$'.format(nb_of_iterations))
plt.xlabel('$w$', fontsize=15)
plt.ylabel('$cost=SSE$', fontsize=15)
plt.title('Gradient descent updates plotted on cost function')
plt.grid()

#%%
w = 0
# Start performing the gradient descent updates, and print the weights and cost:
nb_of_iterations = 10  # number of gradient descent updates
for i in range(nb_of_iterations):
    dw = delta_w(w, x, t, learning_rate)  # get the delta w update
    w = w - dw  # update the current weight parameter

# Plot the target t versus the input x
plt.plot(x, t, 'o', label='t')
# Plot the initial line
plt.plot([0, 1], [f(0), f(1)], 'b-', label='f(x)')
# plot the fitted line
plt.plot([0, 1], [0*w, 1*w], 'r-', label='fitted line')
plt.xlabel('input x')
plt.ylabel('target t')
plt.ylim([0,2])
plt.title('input vs target')
plt.grid()
plt.legend(loc=2)
plt.show()

#%%
# Define the logistic function
def logistic(z): return 1 / (1 + np.exp(-z))

# Plot the logistic function
z = np.linspace(-6,6,100)
plt.plot(z, logistic(z), 'b-')
plt.xlabel('$z$', fontsize=15)
plt.ylabel('$\sigma(z)$', fontsize=15)
plt.title('logistic function')
plt.grid()
plt.show()

#%%
# Define the logistic function
def logistic_derivative(z): return logistic(z) * (1 - logistic(z))

# Plot the logistic function
z = np.linspace(-6,6,100)
plt.plot(z, logistic_derivative(z), 'r-')
plt.xlabel('$z$', fontsize=15)
plt.ylabel('$\\frac{\\partial \\sigma(z)}{\\partial z}$', fontsize=15)
plt.title('derivative of the logistic function')
plt.grid()
plt.show()

#%%


import numpy as np

from copy import deepcopy
from math import ceil
from typing import List, Callable

# region Number generation
class Generator:
    @staticmethod
    def __fibonacci_sequence(count: int = 1) -> List[int]:
        assert count > 0

        fibonacci_numbers: List[int] = list()

        for i in range(0, count):
            if i < 2:
                fibonacci_numbers.append(1)
            else:
                fibonacci_numbers.append(fibonacci_numbers[i - 1] + fibonacci_numbers[i - 2])

        return fibonacci_numbers

    @staticmethod
    def generate_interval(**kwargs):
        return list(range(kwargs.get("start", 0), kwargs.get("stop", 10), kwargs.get("step", 1)))

    @staticmethod
    def generate_gaussian(**kwargs):
        return list(map(lambda x: int(x),
                        np.random.normal(kwargs.get("mean", 0),
                                         kwargs.get("variance", 1),
                                         kwargs.get("count", 10))))

    @staticmethod
    def generate_fibonacci(**kwargs):
        return Generator.__fibonacci_sequence(kwargs.get("count", 10))
# endregion

# region Percentiles
def nearest_rank(numbers: List[int], n: int = 80) -> int:
    assert 0 < n < 101

    numbers.sort()
    return numbers[int(ceil(float(n) * len(numbers) / 100)) - 1]

def closest_rank_lerp(numbers: List[int], n: int = 80) -> int:
    assert 0 < n < 101
    assert len(numbers) > 0

    numbers.sort()

    def get_index_percentile(i: int, N: int):
        return 100. * (i + 0.5) / N

    p = list()

    for i, number in enumerate(numbers):
        p.append(get_index_percentile(i, len(numbers)))

    if n < p[0]:
        return numbers[0]
    elif n > p[-1]:
        return numbers[-1]
    else:
        index = 0

        for i, number in enumerate(p):
            if number > n:
                index = i - 1
                break

        return round(numbers[index] + len(numbers) * (n - p[index]) * (numbers[index + 1] - numbers[index]) / 100.)

class DistributionTester:
    def __init__(self,
                 generation_function: Callable = None,
                 percentile_function: Callable = None,
                 **kwargs):
        if generation_function is None:
            kwargs.update({"start": 0, "stop": 100, "step": 2})
            generation_function = Generator.generate_interval

        self._numbers = generation_function(**kwargs)
        self._numbers.sort()
        self.percentile_function = percentile_function

    def get_numbers(self):
        return deepcopy(self._numbers)

    def calculate_nth_percentile(self, n: int = 80, percentile_function: Callable = None):
        if percentile_function is None:
            if self.percentile_function is None:
                percentile_function = nearest_rank
            else:
                percentile_function = self.percentile_function

        return percentile_function(self._numbers, n)
# endregion