import numpy as np
import matplotlib.pyplot as plt

class PayOff:
    def __init__(self, TheOptionsType_, Strike_):
        '''
        Inputs:
        =========
        TheOptionsType_: string (European call, European put, Binary call, Binary put)
        Strike_: float strike price
        '''

        self.__TheOptionsType = TheOptionsType_
        self.__Strike = Strike_

    def __call__(self,spot):
        # Overloading the ( ) operator
        '''
        inputs:
        =========
        spot: numpy array of spot prices

        returns:
        =========
        payoff value for each option
        '''

        if self.__TheOptionsType == 'European call':
            return np.maximum(spot - self.__Strike,0)
        elif self.__TheOptionsType == 'European put':
            return np.maximum(self.__Strike - spot,0)
        elif self.__TheOptionsType == 'Binary call':
            return np.piecewise(spot, [spot < self.__Strike, spot >= self.__Strike], [0, 1])
        elif self.__TheOptionsType == 'Binary put':
            return np.piecewise(spot, [spot <= self.__Strike, spot > self.__Strike], [1, 0])
        else:
            raise Exception('Unknown option type found.')

def GBM_fd(S0, r, d, sigma, T, num_steps = 50, num_paths = 1, plot = False, seed = None):
    '''
    Simulate Geometric Brownian Motion Stochastic Differential Equation using Euler'Maruyama method (finite difference)
    to calculate terminal stock price

    inputs:
    ===========

    S0: initial stock price
    r: risk-free interest rate
    d: dividend
    sigma: volatility
    T: time to maturity
    num_steps: numer of discretization steps
    num_paths: number of paths (integer)
    plot: plot of all paths generated
    seed: controls the random number generator np.random.RandomState().standard_normal()

    returns:
    =========
    numpy array of terminal stock prices of all paths
    '''

    dt = T / num_steps

    if num_paths == 1:
        S_path = [0] * (num_steps + 1)
    else:
        S_path = np.zeros((num_steps + 1, num_paths))

    S_path[0] = S0
    standard_normal_array = np.random.RandomState(seed).standard_normal((num_steps, num_paths))

    for i in range(1, num_steps + 1):
        S_path[i] = S_path[i-1] + r * S_path[i-1] * dt + S_path[i-1] * sigma * np.sqrt(dt) * standard_normal_array[i-1]

    if plot:
        plt.figure(figsize= (10,6))
        plt.plot(S_path)
        plt.xlabel('number of discretization steps')
        plt.ylabel('Stock price');

    return S_path[-1]

def GBM_formula(S0, mu, d, sigma, T, num_paths = 1, seed = None):
    '''
    Simulate Geometric Brownian Motion using analytical formula

    inputs:
    ===========

    S0: initial stock price
    mu: drift
    d: dividend
    sigma: volatility
    T: time to maturity
    num_paths: number of paths (integer)
    seed: Control normal distribution generation

    returns
    ===========

    numpy array containing stock price(s) at time T
    '''

    return S0 * np.exp((mu - d - 0.5 * sigma ** 2) * T + sigma * np.sqrt(T) * np.random.RandomState(seed).standard_normal(num_paths))

def monte_carlo(payoff):
    '''
    Monte Carlo method:
    1. Average all payoffs at time T
    2. Discount the average using continuously compounded risk-free interest rate

    inputs:
    ===========

    payoff: 1D numpy array of payoffs

    returns
    ===========
    integer approximate price of an option
    '''

    return np.mean(payoff)

def black_scholes_monte_carlo_pricer(option_type, S0, K, r, d, sigma, T, num_steps = 100, num_paths = 20, full_list = False, seed = None):
    '''
    Apply Monte Carlo method to price Black Schole option

    inputs:
    ==========

    option_type: string (European call, European put, Binary call, Binary put)
    S0: initial stock price
    K: strike price
    r: risk-free interest rate
    d: dividend
    sigma: volatility
    T: time to maturity
    num_steps: numer of discretization steps
    num_paths: int number of paths
    full_list: boolean return full list of monte carlo values if True, otherwise return the last value
    seed: None or int

    returns:
    ==========

    Black-Scholes price of the option using monte carlo method
    '''

    if not full_list:
        ST = GBM_fd(S0, r, d, sigma, T, num_steps, 2**num_paths, False, seed)
        payoff = np.exp(- r * T) * PayOff(option_type, K)(ST)
        return monte_carlo(payoff)

    mc = [0] * num_paths

    for num_path in range(1, num_paths + 1):
        ST = GBM_fd(S0, r, d, sigma, T, num_steps, 2**num_path, False, seed)
        payoff = np.exp(- r * T) * PayOff(option_type, K)(ST)
        mc[num_path - 1] = monte_carlo(payoff)

    return mc


#! /usr/bin/env python

##########################################################################################
# calc_doublePendulumFreq.py
#
# script to calculate the linearized frequencies for a double-pendulum
#
# NOTE: Any plotting is set up for output, not viewing on screen.
#       So, it will likely be ugly on screen. The saved PDFs should look
#       better.
#
# Created: 04/22/15
#   - Joshua Vaughan
#   - joshua.vaughan@louisiana.edu
#   - http://www.ucs.louisiana.edu/~jev9637
#
# Modified:
#   *
#
##########################################################################################

import numpy as np
import matplotlib.pyplot as plt

def calc_doublePendulumFreq(mh, mp, l1, l2, g = 9.81):
    """
    Function to calculate the linearized frequencies of a double-pendulum

    Arguments:
      mh : mass of the hook (kg)
      mp : mass of the payload (kg)
      l1 : supension cable length (m)
      l2 : rigging length (m)
      g : gravitation acceleration (9.81 m/s^2 is default, we're on Earth)

    Returns:
      The two natural frequencies (rad/s)

    """
    R = mp / mh

    beta = np.sqrt((1 + R)**2 * (1.0/l1 + 1.0/l2)**2 - 4 * ((1.0 + R) / (l1 * l2)))

    w1 = np.sqrt(g / 2) * np.sqrt((1+R) * (1.0/l1 + 1.0/l2) - beta)
    w2 = np.sqrt(g / 2) * np.sqrt((1+R) * (1.0/l1 + 1.0/l2) + beta)

    return w1, w2

if __name__ == '__main__':
    # Define system parameters
    mh = 50.0           # mass of the hook (kg)
    mp = 22.7/2           # mass of the payload (kg)
    l1 = 3.5            # suspension cable length (m)
    l2 = 1.8            # rigging length (m)

    w1, w2 = calc_doublePendulumFreq(mh, mp, l1, l2)

    print 'The natrual frequencies are {:0.4f} rad/s and {:0.4f} rad/s.'.format(w1, w2)
    print 'The natrual frequencies are {:0.4f} Hz and {:0.4f} Hz.'.format(w1/(2*np.pi), w2/(2*np.pi))

    # We can also look at a range of payload masses
    mp = np.arange(10, 50, 1)

    w1, w2 = calc_doublePendulumFreq(mh, mp, l1, l2)

    # Set the plot size - 3x2 aspect ratio is best
    fig = plt.figure(figsize=(6,4))
    ax = plt.gca()
    plt.subplots_adjust(bottom=0.17, left=0.17, top=0.96, right=0.96)

    # Change the axis units to CMUSerif-Roman
    plt.setp(ax.get_ymajorticklabels(),fontsize=18)
    plt.setp(ax.get_xmajorticklabels(),fontsize=18)

    ax.spines['right'].set_color('none')
    ax.spines['top'].set_color('none')

    ax.xaxis.set_ticks_position('bottom')
    ax.yaxis.set_ticks_position('left')

    # Turn on the plot grid and set appropriate linestyle and color
    ax.grid(True,linestyle=':', color='0.75')
    ax.set_axisbelow(True)

    # Define the X and Y axis labels
    plt.xlabel('Payload Mass (kg)', fontsize=22, weight='bold', labelpad=5)
    plt.ylabel('Frequency (Hz)', fontsize=22, weight='bold', labelpad=10)

    plt.plot(mp, w1 / (2*np.pi), linewidth=2, linestyle="-", label=r'$\omega_1$')
    plt.plot(mp, w2 / (2*np.pi), linewidth=2, linestyle="--", label=r'$\omega_2$')

    # uncomment below and set limits if needed
    # plt.xlim(0,5)
    plt.ylim(0,0.75)

    # Create the legend, then fix the fontsize
    leg = plt.legend(loc='upper right', ncol = 2, fancybox=True)
    ltext  = leg.get_texts()
    plt.setp(ltext,fontsize=18)

    # Adjust the page layout filling the page using the new tight_layout command
    plt.tight_layout(pad=0.5)

    # save the figure as a high-res pdf in the current folder
    # plt.savefig('plot_filename.pdf')

    # show the figure
    plt.show()

Misc Python Tools and Helpers/calc_doublePendulumFreq.py


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

import pandas as pd
import numpy as np
#from sklearn.preprocessing import StandardScaler

def PCA(dataset, col_mean):

    cov_mat = np.dot((dataset - col_mean),(dataset - col_mean).T) / (dataset.shape[1])
    eigenvalues, eigenvectors = np.linalg.eig(cov_mat)

    # Make a list of (eigenvalue, eigenvector) tuples
    eigen_pairs = [(np.abs(eigenvalues[i]), eigenvectors[:,i]) for i in range(len(eigenvalues))]

    # Sort the (eigenvalue, eigenvector) tuples from high to low
    eigen_pairs.sort()
    eigen_pairs = eigen_pairs[::-1]

    components = []
    for i in range(4):
        components.append(eigen_pairs[i][1].reshape(4,1))

    eigen_vec = np.hstack((components))

    return eigen_vec

def MSE(dataset, component_mat):

    mse = np.sum(np.square(component_mat-dataset))/dataset.shape[1]
    return mse

def reconstruct(u, mean, data, n_components):

    ri = np.dot(u.T, (data - mean)) #ri = UT mi = UT(xi − mean ({x})).

    #generate pi
    pi = np.zeros(ri.shape)
    pi[:n_components, :] = ri[:n_components, :]
    #xi = Upi + mean ({x})
    #unrotate and untranslate adding mean: xˆi = Upi + mean ({x})
    xi = np.dot(u, pi)
    #reshape xi to have the shape of the original data

    xi += mean
    return xi

N = np.array(pd.read_csv('iris.csv', na_values=" ?")).T
data1 = np.array(pd.read_csv('dataI.csv', na_values=" ?")).T
data2 = np.array(pd.read_csv('dataII.csv', na_values=" ?")).T
data3 = np.array(pd.read_csv('dataIII.csv', na_values=" ?")).T
data4 = np.array(pd.read_csv('dataIV.csv', na_values=" ?")).T
data5 = np.array(pd.read_csv('dataV.csv', na_values=" ?")).T
noisy_data = [data1, data2, data3, data4, data5]

N0 = np.mean(N, axis=1, keepdims=True)
u_noiseless = PCA(N, N0 )

data1_2pc_representation = []
mse_matrix = []

# For each of the noisy datasets
# Estimate the MSE between
# A) noiseless data and the PC representation of the noisy dataset
# computed from mean and cov_mat of the noiseless data
# B) noisy data and the PC representation of the same data
# computed from mean and cov_mat of the data itself
for i in range(len(noisy_data)):
    mse = []
    #noiseless
    for j in range(5):
        #do not dot if it is 0 PC
        if j != 0:
            #reconstructed version on noisy dataset i using PC of noiseless data j
            xi = reconstruct(u_noiseless, N0, noisy_data[i], j)
        else:
            xi = N0

        #MSE between the noiseless version N and the pca representation
        mse.append(MSE(N, xi))

    mean_noisy = np.mean(noisy_data[i], axis=1, keepdims=True)
    u_noisy = PCA(noisy_data[i], mean_noisy)

    for k in range(5):

        if k != 0:
            #reconstructed version on noisy dataset i using k PC computed from
            # mean and covmat of the same set
            xi = reconstruct(u_noisy,mean_noisy, noisy_data[i], k)
            if i == 0 and k==2:
                data1_2pc_representation = xi.T
        else:
            xi = mean_noisy

        #MSE between the noisy version i and the k-th pca representation of
        mse.append(MSE(N, xi))

    mse_matrix.append(mse)

mse_matrix = np.around(mse_matrix, decimals=2)
data1_2pc_representation = np.around(data1_2pc_representation, decimals=2)

print(mse_matrix)

np.savetxt("shterev2-numbers.csv", mse_matrix, delimiter=",", fmt='%5.4f')
np.savetxt("shterev2-recon.csv", data1_2pc_representation, delimiter=",", fmt='%5.4f')


#%%
"""
Created on July 05  2021
Impact of conditional expectation pricing (Black-Scholes with Jump volatility)

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak
"""
import numpy as np
import matplotlib.pyplot as plt
import enum
import scipy.stats as st

# This class defines puts and calls
class OptionType(enum.Enum):
    CALL = 1.0
    PUT = -1.0

def GeneratePaths(NoOfPaths,NoOfSteps,S0,T,muJ,sigmaJ,r):
    # Create empty matrices for Poisson process and for compensated Poisson process
    X = np.zeros([NoOfPaths, NoOfSteps+1])
    S = np.zeros([NoOfPaths, NoOfSteps+1])
    time = np.zeros([NoOfSteps+1])

    dt = T / float(NoOfSteps)
    X[:,0] = np.log(S0)
    S[:,0] = S0

    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])
    J = np.random.normal(muJ,sigmaJ,[NoOfPaths,NoOfSteps])
    for i in range(0,NoOfSteps):
        # making sure that samples from normal have mean 0 and variance 1
        if NoOfPaths > 1:
            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])

        X[:,i+1]  = X[:,i] + (r - 0.5*J[:,i]**2.0)*dt +J[:,i]*np.sqrt(dt)* Z[:,i]
        time[i+1] = time[i] +dt

    S = np.exp(X)
    paths = {"time":time,"X":X,"S":S,"J":J}
    return paths

def EUOptionPriceFromMCPaths(CP,S,K,T,r):
    # S is a vector of Monte Carlo samples at T
    if CP == OptionType.CALL:
        return np.exp(-r*T)*np.mean(np.maximum(S-K,0.0))
    elif CP == OptionType.PUT:
        return np.exp(-r*T)*np.mean(np.maximum(K-S,0.0))

def BS_Call_Put_Option_Price(CP,S_0,K,sigma,t,T,r):
    K = np.array(K).reshape([len(K),1])
    d1 = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0))
    * (T-t)) / (sigma * np.sqrt(T-t))
    d2 = d1 - sigma * np.sqrt(T-t)
    if CP == OptionType.CALL:
        value = st.norm.cdf(d1) * S_0 - st.norm.cdf(d2) * K * np.exp(-r * (T-t))
    elif CP == OptionType.PUT:
        value = st.norm.cdf(-d2) * K * np.exp(-r * (T-t)) - st.norm.cdf(-d1)*S_0
    return value

def CallOption_CondExpectation(NoOfPaths,T,S0,K,J,r):

    # Jumps at time T
    J_i = J[:,-1]

    result = np.zeros([NoOfPaths])

    for j in range(0,NoOfPaths):
        sigma = J_i[j]
        result[j] = BS_Call_Put_Option_Price(OptionType.CALL,S0,[K],sigma,0.0,T,r)

    return np.mean(result)

def mainCalculation():
    NoOfPaths = 25
    NoOfSteps = 500
    T = 5
    muJ = 0.3
    sigmaJ = 0.005

    S0 =100
    r  =0.00
    Paths = GeneratePaths(NoOfPaths,NoOfSteps,S0, T,muJ,sigmaJ,r)
    timeGrid = Paths["time"]
    X = Paths["X"]
    S = Paths["S"]

    plt.figure(1)
    plt.plot(timeGrid, np.transpose(X))
    plt.grid()
    plt.xlabel("time")
    plt.ylabel("X(t)")

    plt.figure(2)
    plt.plot(timeGrid, np.transpose(S))
    plt.grid()
    plt.xlabel("time")
    plt.ylabel("S(t)")

    # Check the convergence for a given strike
    K = 80
    CP =OptionType.CALL

    NGrid = range(100,10000,1000)
    NoOfRuns = len(NGrid)

    resultMC = np.zeros([NoOfRuns])
    resultCondExp = np.zeros([NoOfRuns])

    for (i,N) in enumerate(NGrid):
            print(N)
            Paths = GeneratePaths(N,NoOfSteps,S0, T,muJ,sigmaJ,r)
            timeGrid = Paths["time"]
            S = Paths["S"]
            resultMC[i] = EUOptionPriceFromMCPaths(CP,S[:,-1],K,T,r)

            J = Paths["J"]

            resultCondExp[i]=CallOption_CondExpectation(NoOfPaths,T,S0,K,J,r)

    plt.figure(3)
    plt.plot(NGrid,resultMC)
    plt.plot(NGrid,resultCondExp)
    plt.legend(['MC','Conditional Expectation'])
    plt.title('Call Option Price- Convergence')
    plt.xlabel('Number of Paths')
    plt.ylabel('Option price for a given strike, K')
    plt.grid()

mainCalculation()

Lecture 02-Understanding of Filtrations and Measures/Materials/Black_Scholes_Jumps.py


""" Tools:
Numpy
numpy.array(tuple)
array.shape --> rows, columns
array.dtype
numpy.zeros(shape, dtype=float, order='C')
numpy.ones(shape, dtype=None, order='C')[source]
numpy.empty(shape, dtype=float, order='C')
numpy.eye(N, M=None, k=0, dtype=<type 'float'>)
numpy.arange([start, ]stop, [step, ]dtype=None)
"""

import numpy as np

# You can parse a list as an object

myList = [1,2,3,4]

myArray1 = np.array(myList)

#print myArray1

# you can then quickly make a multi-dimensional array from tuples or lists of lists

myList2 = [11,22,33,44]

myList3 = [111,222,333,444]

myLists = [myList, myList2, myList3]

myArray2 = np.array(myLists)

#print myArray2
#print myArray2.shape

# We can check the TYPE of the array using dtype
#print myArray2.dtype

# Make a list of zeros, default floating type

zeros1 = np.zeros([5,5])
#print zeros1

# Make an array of 1s, default floating type

ones1 = np.ones([5,5])
#print ones1

# Make an empty matrix

empty1 = np.empty(5)
#print empty1

# Make an identity matrix

identityMatrix = np.eye(5)
#print identityMatrix

# Make an evenly spaced list, starting at 3, ending at 50 and going up by 4's

myArray3 = np.arange(3,50,4)
#print myArray3

Python for Data Analysis and Visualisation/Lecture7-Creating Arrays.py


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

def hw1p3a(train_data, train_target, test_data, test_target, plot_gate):
    # from sklearn.datasets import load_boston
    # import numpy as np
    # from numpy import linalg as linalg

    # boston = load_boston()
    # b_data = boston.data
    # b_target = boston.target
    #
    # t0 = np.median(b_target)
    # divider = 406
    #
    # bDataTrainS = b_data[:divider,:]
    # bDataTestS = b_data[divider:,:]
    #
    # bTargetTrainS = b_target[:divider]
    # bTargetTestS = b_target[divider:]

    bDataTrainS = train_data
    bTargetTrainS = train_target

    bDataTestS = test_data
    bTargetTestS = test_target

    # class1 -> 0, > t0 ; class2 -> 1, <= t0;  result > 0-> 1-> class2
    class1 = bTargetTrainS == 0
    class2 = bTargetTrainS == 1

    N1 = sum(class1)
    N2 = sum(class2)

    m1 = np.mean(bDataTrainS[class1],0)[None,:] # 1*D
    m2 = np.mean(bDataTrainS[class2,:],0)[None,:]
    m = np.mean(bDataTrainS,0)[None,:]

    x1 = bDataTrainS[class1] # N * D=13
    x2 = bDataTrainS[class2]

    Sw = (x1-m1).transpose().dot((x1-m1)) + (x2-m2).transpose().dot(x2-m2)

    # w = linalg.inv(Sw).dot((m2-m1).transpose())
    w = linalg.pinv(Sw).dot((m2-m1).transpose())
    w = w/linalg.norm(w) # D*1
    # print(w)

    m = m.transpose() # D*1

    def classifier(x,win,mid):
        # x is N*D data matrix
        # x = x.reshape((D,1))

        x = x.transpose()
        D = len(win)
        win = win.reshape((D,1))
        mid = mid.reshape((D,1))
        # return np.inner(w,x)>0
        return (win.transpose().dot(x-mid))[0]>0 # D contracted, inner product on D, feature space.

    # test classifier:
    # a = np.array([np.ones([1,13]),np.ones([1,13])*2])[:,0,:]
    # print(a.shape)
    # print(classifier(a,w))

    # Histogram the projected points:
    # %matplotlib inline # jupyter notebook magical command
    # import matplotlib.pyplot as plt

    # x = [x1,x2,x1[:50]+x2[:50],x1[:50]*x2[:50]]
    x = [x1,x2]
    y = []

    if plot_gate:
        plt.figure()

        plt.subplot(2,1,1)
        for num,labName in zip([0,1],"01"):
            y_i = w.transpose().dot(x[num].transpose()-m)  # 1*N
            # print(y.shape)
            plt.scatter(y_i, np.ones(y_i.shape)*.5, label = ("class"+labName), cmap = plt.get_cmap('gist_rainbow'))
            y.append(y_i[0])
        plt.vlines(0,0,1)
        plt.legend()
        plt.title('Projected points of Training set in the last run of validation')

        plt.subplot(2,1,2)
        n_bins = 20
        plt.hist(y, n_bins, histtype= 'bar')
        # plt.title('Hist of projected points')
        # plt.legend()

    x11 = bDataTestS[np.where(bTargetTestS == 0)] # N * D=13
    x22 = bDataTestS[np.where(bTargetTestS == 1)]
    xx = [x11, x22]
    if plot_gate:
        plt.figure()

        plt.subplot(2,1,1)
        for num,labName in zip([0,1],"01"):
            y_i = w.transpose().dot(xx[num].transpose()-m)  # 1*N
            # print(y.shape)
            plt.scatter(y_i, np.ones(y_i.shape)*.5, label = ("class"+labName), cmap = plt.get_cmap('gist_rainbow'))
            # y.append(y_i[0])
        plt.vlines(0,0,1)
        plt.legend()
        plt.title('Projected points of Testing set in the last run of validation')

        plt.subplot(2,1,2)
        n_bins = 20
        plt.hist(y, n_bins, histtype= 'bar')
        # plt.title('Hist of projected points')
        # plt.legend()

    # Error rate:
    x_test = bDataTestS
    y_test = bTargetTestS
    result_test = classifier(x_test,w,m)

    err = np.mean(y_test != result_test)

    x_train = bDataTrainS
    y_train = bTargetTrainS
    result_train = classifier(x_train,w,m)

    err_train = np.mean(y_train != result_train)

    return err, err_train, y_test, result_test, y_train, result_train


import numpy as np
import random

def softmax(x):
    """
    Compute the softmax function for each row of the input x.
    """

    if x.ndim == 1 \
        or (x.ndim == 2 and x.shape[0] == 1 ) \
        or (x.ndim == 2 and x.shape[1] == 1):
        x -= np.max(x)
        return np.exp(x) / np.sum(np.exp(x))
    elif x.ndim == 2:
        x-= np.max(x, axis = 1).reshape(-1,1)
        return np.exp(x) / np.sum(np.exp(x), axis=1).reshape(-1,1)
    else:
        raise ValueError("can only accept 1 or 2-d inputs to softmax")

    return x

def sigmoid(x):
    """
    Computes the sigmoid function for the input.
    """

    return 1. / (1 + np.exp(-x))

def sigmoid_grad(f):
    """
    Computes the gradient for the sigmoid function
    """
    return f * (1.0 - f)

def normalizeRows(x):
    """ Row normalization function """
    norm = np.sqrt(np.sum(x**2 , axis = 1))
    return (x.T / norm).T


from dataclasses import dataclass

import numpy as np
from scipy.stats import norm

@dataclass
class option:
    S: np.ndarray
    K: np.ndarray
    T: np.ndarray
    v: np.ndarray
    r: np.ndarray
    q: np.ndarray

def __ds(o: option) -> tuple:
    denominator = o.v * np.sqrt(o.T)
    first_term  = np.log(o.S / o.K)
    second_term = o.r - o.q + o.v * o.v / 2

    d1 = (1 / denominator) * (first_term + second_term * o.T)
    d2 = d1 - denominator

    return d1, d2

def call_delta(o: option) -> np.ndarray:
    d1, d2 = __ds(o)
    deltas = np.exp(- o.q * o.T) * norm.cdf(d1)
    return deltas

def call_gamma(o: option) -> np.ndarray:
    d1, _ = __ds(o)
    g1 = np.exp(- o.q * o.T) * norm.pdf(d1)
    g2 = o.S * o.v * np.sqrt(o.T)
    return g1 / g2

def call_theta(o: option) -> np.ndarray:
    d1, d2 = __ds(o)
    first_term  = - o.S * np.exp(-o.q * o.T) * norm.pdf(d1) * v / (2 * np.sqrt(o.T))
    second_term = - o.r * o.K * np.exp(-o.r * o.T) * norm.cdf(d2)
    third_term = o.q * o.S * np.exp(-o.q * o.T) * norm.cdf(d1)

    this_theta = first_term + second_term + third_term

    return this_theta / 100

def call_vega(o: option) -> np.ndarray:
    d1, _ = __ds(o)
    this_vega = o.S * np.exp(-o.q * o.T) * norm.pdf(d1) * np.sqrt(o.T)
    return this_vega / 100

def call_rho(o: option) -> np.ndarray:
    _, d2 = __ds(o)
    return o.K * np.exp(-o.r * o.T) * norm.cdf(d2) / 100

def call_dollar_delta(Notional: float, o: option) -> np.ndarray:
    return Notional * call_delta(o)

def call_dollar_gamma(Notional: float, o: option) -> np.ndarray:
    return Notional * call_gamma(o) * o.S / 100

def call_dollar_theta(Notional: float, o: option) -> np.ndarray:
    return Notional * call_theta(o)

def call_dollar_vega(Notional: float, o: option) -> np.ndarray:
    return Notional * call_vega(o)

def call_dollar_rho(Notional: float, o: option) -> np.ndarray:
    return Notional * call_rho(o)

def put_delta(o: option) -> np.ndarray:
    d1, _ = __ds(o)
    deltas = np.exp(- o.q * o.T) * (norm.cdf(d1) - 1)
    return deltas

def put_gamma(o: option) -> np.ndarray:
    d1, _ = __ds(o)
    g1 = np.exp(- o.q * o.T) * norm.pdf(d1)
    g2 = o.S * o.v * np.sqrt(o.T)
    return g1 / g2

def put_theta(o: option) -> np.ndarray:
    d1, d2 = __ds(o)
    first_term  = - np.exp(o.q * o.T) * o.S * norm.pdf(d1) * o.v / (2 * np.sqrt(o.T))
    second_term = + o.r * o.K * np.exp(-o.r * o.T) * norm.cdf(-d2)
    third_term  = - o.q * o.S * np.exp(-o.q * o.T) * norm.cdf(-d1)

    this_theta = first_term + second_term + third_term

    return this_theta / 100

def put_vega(o: option) -> np.ndarray:
    d1, _ = __ds(o)
    this_vega = o.S * np.exp(-o.q * o.T) * norm.pdf(d1) * np.sqrt(o.T)
    return this_vega / 100

def put_rho(o: option) -> np.ndarray:
    _, d2 = __ds(o)
    return - o.K * np.exp(-o.r * o.T) * norm.cdf(-d2) / 100

def put_dollar_delta(Notional: float, o: option) -> np.ndarray:
    return Notional * put_delta(o)

def put_dollar_gamma(Notional: float, o: option) -> np.ndarray:
    return Notional * put_gamma(o) * o.S / 100

def put_dollar_theta(Notional: float, o: option) -> np.ndarray:
    return Notional * put_theta(o)

def put_dollar_vega(Notional: float, o: option) -> np.ndarray:
    return Notional * put_vega(o)

def put_dollar_rho(Notional: float, o: option) -> np.ndarray:
    return Notional * put_rho(o)

"""
def __ds(S: np.ndarray, \
       K: np.ndarray, \
       T: np.ndarray, \
       v: np.ndarray, \
       r: np.ndarray, \
       q: np.ndarray) -> tuple:

    denominator = v * np.sqrt(T)
    first_term  = np.log(S / K)
    second_term = r - q + v * v / 2

    d1 = (1 / denominator) * (first_term + second_term * T)
    d2 = d1 - denominator

    return d1, d2

def call_delta(S: np.ndarray, \
               K: np.ndarray, \
               T: np.ndarray, \
               v: np.ndarray, \
               r: np.ndarray, \
               q: np.ndarray) -> np.ndarray:

    d1, d2 = __ds(S, K, T, v, r, q)
    deltas = np.exp(- q * T) * norm.cdf(d1)
    return deltas

def call_gamma(S: np.ndarray, \
               K: np.ndarray, \
               T: np.ndarray, \
               v: np.ndarray, \
               r: np.ndarray, \
               q: np.ndarray) -> np.ndarray:

    d1, d2 = __ds(S, K, T, v, r, q)
    g1 = np.exp(- q * T) * norm.pdf(d1)
    g2 = S * v * np.sqrt(T)
    return g1 / g2

def call_theta(S: np.ndarray, \
               K: np.ndarray, \
               T: np.ndarray, \
               v: np.ndarray, \
               r: np.ndarray, \
               q: np.ndarray) -> np.ndarray:

    d1, d2 = __ds(S, K, T, v, r, q)
    first_term  = - S * np.exp(-q * T) * norm.pdf(d1) * v / (2 * np.sqrt(T))
    second_term = - r * K * np.exp(-r * T) * norm.cdf(d2)
    third_term = q * S * np.exp(-q * T) * norm.cdf(d1)

    this_theta = first_term + second_term + third_term

    return this_theta / 100

def call_vega(S: np.ndarray, \
              K: np.ndarray, \
              T: np.ndarray, \
              v: np.ndarray, \
              r: np.ndarray, \
              q: np.ndarray) -> np.ndarray:

    d1, _ = __ds(S, K, T, v, r, q)
    this_vega = S * np.exp(-q * T) * norm.pdf(d1) * np.sqrt(T)
    return this_vega / 100

def call_rho(S: np.ndarray, \
             K: np.ndarray, \
             T: np.ndarray, \
             v: np.ndarray, \
             r: np.ndarray, \
             q: np.ndarray) -> np.ndarray:

    _, d2 = __ds(S, K, T, v, r, q)
    return K * np.exp(-r * T) * norm.cdf(d2) / 100

def call_dollar_delta(Notional: float, \
                      S: np.ndarray,   \
                      K: np.ndarray,   \
                      T: np.ndarray,   \
                      v: np.ndarray,   \
                      r: np.ndarray,   \
                      q: np.ndarray) -> np.ndarray:
    return Notional * call_delta(S, K, T, v, r, q)

def call_dollar_gamma(Notional: float, \
                      S: np.ndarray,   \
                      K: np.ndarray,   \
                      T: np.ndarray,   \
                      v: np.ndarray,   \
                      r: np.ndarray,   \
                      q: np.ndarray) -> np.ndarray:

    return Notional * call_gamma(S, K, T, v, r, q) * S  / 100

def call_dollar_theta(Notional: float, \
                      S: np.ndarray,   \
                      K: np.ndarray,   \
                      T: np.ndarray,   \
                      v: np.ndarray,   \
                      r: np.ndarray,   \
                      q: np.ndarray) -> np.ndarray:
    return Notional * call_theta(S, K, T, v, r, q)

def call_dollar_vega(Notional: float, \
                     S: np.ndarray,   \
                     K: np.ndarray,   \
                     T: np.ndarray,   \
                     v: np.ndarray,   \
                     r: np.ndarray,   \
                     q: np.ndarray) -> np.ndarray:
    return Notional * call_vega(S, K, T, v, r, q)

def call_dollar_rho(Notional: float,  \
                    S: np.ndarray,    \
                    K: np.ndarray,    \
                    T: np.ndarray,    \
                    v: np.ndarray,    \
                    r: np.ndarray,    \
                    q: np.ndarray) -> np.ndarray:
    return Notional * call_rho(S, K, T, v, r, q)

def put_delta(S: np.ndarray, \
              K: np.ndarray, \
              T: np.ndarray, \
              v: np.ndarray, \
              r: np.ndarray, \
              q: np.ndarray) -> np.ndarray:

    d1, d2 = __ds(S, K, T, v, r, q)
    deltas = np.exp(- q * T) * (norm.cdf(d1) - 1)
    return deltas

def put_gamma(S: np.ndarray, \
              K: np.ndarray, \
              T: np.ndarray, \
              v: np.ndarray, \
              r: np.ndarray, \
              q: np.ndarray) -> np.ndarray:

    d1, _ = __ds(S, K, T, v, r, q)
    g1 = np.exp(- q * T) * norm.pdf(d1)
    g2 = S * v * np.sqrt(T)
    return g1 / g2

def put_theta(S: np.ndarray, \
              K: np.ndarray, \
              T: np.ndarray, \
              v: np.ndarray, \
              r: np.ndarray, \
              q: np.ndarray) -> np.ndarray:

    d1, d2 = __ds(S, K, T, v, r, q)
    first_term  = - np.exp(q * T) * S * norm.pdf(d1) * v / (2 * np.sqrt(T))
    second_term = + r * K * np.exp(-r * T) * norm.cdf(-d2)
    third_term  = - q * S * np.exp(-q * T) * norm.cdf(-d1)

    this_theta = first_term + second_term + third_term

    return this_theta / 100

def put_vega(S: np.ndarray, \
             K: np.ndarray, \
             T: np.ndarray, \
             v: np.ndarray, \
             r: np.ndarray, \
             q: np.ndarray) -> np.ndarray:

    d1, _ = __ds(S, K, T, v, r, q)
    this_vega = S * np.exp(-q * T) * norm.pdf(d1) * np.sqrt(T)
    return this_vega / 100

def put_rho(S: np.ndarray, \
            K: np.ndarray, \
            T: np.ndarray, \
            v: np.ndarray, \
            r: np.ndarray, \
            q: np.ndarray) -> np.ndarray:
    _, d2 = __ds(S, K, T, v, r, q)
    return - K * np.exp(-r * T) * norm.cdf(-d2) / 100

def put_dollar_delta(Notional: float, \
                     S: np.ndarray,   \
                     K: np.ndarray,   \
                     T: np.ndarray,   \
                     v: np.ndarray,   \
                     r: np.ndarray,   \
                     q: np.ndarray) -> np.ndarray:
    return Notional * put_delta(S, K, T, v, r, q)

def put_dollar_gamma(Notional: float, \
                     S: np.ndarray,   \
                     K: np.ndarray,   \
                     T: np.ndarray,   \
                     v: np.ndarray,   \
                     r: np.ndarray,   \
                     q: np.ndarray) -> np.ndarray:
    return Notional * put_gamma(S, K, T, v, r, q) * S  / 100

def put_dollar_theta(Notional: float, \
                     S: np.ndarray,   \
                     K: np.ndarray,   \
                     T: np.ndarray,   \
                     v: np.ndarray,   \
                     r: np.ndarray,   \
                     q: np.ndarray) -> np.ndarray:
    return Notional * put_theta(S, K, T, v, r, q)

def put_dollar_vega(Notional: float, \
                    S: np.ndarray,   \
                    K: np.ndarray,   \
                    T: np.ndarray,   \
                    v: np.ndarray,   \
                    r: np.ndarray,   \
                    q: np.ndarray) -> np.ndarray:
    return Notional * put_vega(S, K, T, v, r, q)

def put_dollar_rho(Notional: float,  \
                   S: np.ndarray,    \
                   K: np.ndarray,    \
                   T: np.ndarray,    \
                   v: np.ndarray,    \
                   r: np.ndarray,    \
                   q: np.ndarray) -> np.ndarray:

    return Notional * put_rho(S, K, T, v, r, q)
"""


import numpy as np
from numpy import linalg as LA
import pandas as pd
import matplotlib.pyplot as plt
import os
from utils import covariance, LinearLeastSquare, TotalLeastSquare, RANSAC

np.set_printoptions(formatter={'all':lambda x: str(x)})
file =os.path.join("../data","insurance_data.csv")
# Read cvs data into a dataframe
data = pd.read_csv(file)
x = data['age'].to_numpy()
y = data['charges'].to_numpy()
# Perform min-max normalization to bring data to the same scale
x_n = (x - np.min(x))/(np.max(x) - np.min(x))
y_n = (y - np.min(y))/(np.max(y) - np.min(y))

# Q1 _____________________________________________________________
# Compute Covariance matrix of original & normalized data
cov_mat = np.array([[covariance(x, x), covariance(x, y)], [covariance(x, y), covariance(y, y)]])
print("Covariance Matrix of original data: ")
print(cov_mat)

cov_mat = np.array([[covariance(x_n, x_n), covariance(x_n, y_n)], [covariance(x_n, y_n), covariance(y_n, y_n)]])
print("Covariance Matrix of normalized data: ")
print(cov_mat)
eig_val, eig_vec = LA.eig(cov_mat)

eig_vec1 = eig_vec[:,np.argmax(eig_val)] # Eigen vector corresponding to max eigen value
eig_vec2 = eig_vec[:,np.argmin(eig_val)] # Eigen vector corresponding to max eigen value

print("Dot product of eigen vectors is: ", np.dot(eig_vec1, eig_vec2))

plt.figure(1)
plt.scatter(x, y)
plt.title("Principal Components")
plt.xlabel('age')
plt.ylabel('insurance_charges')

# Rescale each eigen vector to fit the graph and plot them at the mean of the data
plt.quiver(np.mean(x), np.mean(y), eig_vec1[0]*(np.max(x) - np.min(x))*np.max(eig_val) , eig_vec1[1]*(np.max(y) - np.min(y))*np.max(eig_val) , label = 'Principal Component 1', color = 'r', units='xy', angles='xy', scale_units='xy', scale=0.2)
plt.quiver(np.mean(x), np.mean(y), eig_vec2[0]*(np.max(x) - np.min(x))*np.min(eig_val) , eig_vec2[1]*(np.max(y) - np.min(y))*np.min(eig_val) , label = 'Principal Component 2', units='xy', angles='xy', scale_units='xy', scale =0.2)

plt.legend()
# __________________________________________________________________

# Q2________________________________________________________________

plt.figure(2)
plt.scatter(x, y)
plt.title("Line Fitting using LS & TLS")
plt.xlabel('age')
plt.ylabel('insurance_charges')
ls = LinearLeastSquare()
# Fit original data using LinearLeastSquare as it is scale invariant
m, c = ls.fit_line(x, y)
y1 = m*x + c
plt.plot(x, y1, label='LS', color='r')

# Fit normalized data using TotalLeastSquare
tls = TotalLeastSquare()
m, c = tls.fit_line(x_n, y_n)

x2 = np.linspace(np.min(x_n), np.max(x_n), x_n.size)
x_ps = np.linspace(np.min(x), np.max(x), x.size)

y2 = m*x2 + c
# Rescale data by performing inverse of min-max normalization
y2 = y2*(np.max(y) - np.min(y)) + np.min(y)
plt.plot(x_ps, y2, label="TLS", color='g')
plt.legend()

plt.figure(3)
# Fit normalized data using RANSAC
"""Optional : fit_line function of RANSAC class takes an optional boolean flag, "optimal_fit" while returns the slope and intercept after running Linear Least Square on only the inliers as a final step.

Set to False as default for comparison between LS, TLS & RANSAC
"""
ransac = RANSAC()
m, c, mask = ransac.fit_line(x_n, y_n, 0.001, p=0.99, optimal_fit = False)
# Plot outliers
plt.scatter(x[np.invert(mask)], y[np.invert(mask)], label='Outliers', color = 'tab:orange')
# Plot inliers
plt.scatter(x[mask], y[mask], label='Inliers', color='g')
plt.title("Line Fitting using RANSAC")
plt.xlabel('age')
plt.ylabel('insurance_charges')
y3 = m*x2 + c
# Rescale data by performing inverse of min-max normalization
y3 = y3*(np.max(y) - np.min(y)) + np.min(y)
plt.plot(x_ps, y3, color='r')
plt.legend()

plt.show()


# title: Gradient descent
# author: Jiahong Ma
# data: 2020/11/24

import math
import numpy as np
from matplotlib import pyplot as plt

def f(x):
    x1 = x[0]
    x2 = x[1]
    return math.exp(x1 + 3 * x2 - 0.1) + math.exp(x1 - 3 * x2 - 0.1) + math.exp(-x1 - 0.1)

def grad(x):
    x1 = x[0]
    x2 = x[1]
    gradx1 = math.exp(x1 + 3 * x2 - 0.1) * 1 + math.exp(x1 - 3 * x2 - 0.1) * 1 + math.exp(-x1 - 0.1) * -1
    gradx2 = math.exp(x1 + 3 * x2 - 0.1) * 3 + math.exp(x1 - 3 * x2 - 0.1) * -3
    return np.asarray([gradx1, gradx2])

def norm(grad):
    gradx1 = grad[0]
    gradx2 = grad[1]
    return (gradx1 ** 2 + gradx2 ** 2) ** 0.5

def gradient_descent(alpha, beta, epsilon, x0):
    assert (alpha > 0 and alpha < 0.5)
    assert (beta > 0 and beta < 1)

    x = [x0]
    d = [-1 * grad(x0)]
    k = 0

    while (norm(grad(x[k])) > epsilon):
        t = 1
        rawf = f(x[k] + t * d[k])
        newf = f(x[k]) + alpha * t * np.dot(grad(x[k]), d[k])
        while (rawf > newf):
            t = beta * t
            rawf = f(x[k] + t * d[k])
            newf = f(x[k]) + alpha * t * np.dot(grad(x[k]), d[k])

        newx = x[k] + t * d[k]
        x.append(newx)
        d.append(-1 * grad(newx))
        k = k + 1

    return x

def distance(result):
    dist = []
    for x in result:
        dist.append(f(x) - TRUTH)
    return dist

def alpha_exp(k):
    alpha = k
    betalist = [0.1,  0.3, 0.5, 0.7, 0.9]
    dist = []
    for x in betalist:
        tempresult = (gradient_descent(alpha = alpha, beta = x, epsilon = 0.35, x0 = np.asarray([0.45,0.45])))
        dist.append(distance(tempresult))
    return dist,betalist

def beta_exp(k):
    beta = k
    alphalist = [0.1, 0.2, 0.3, 0.4]
    dist = []
    for x in alphalist:
        tempresult = (gradient_descent(alpha = x, beta = beta, epsilon = 0.1, x0 = np.asarray([0.12,0.12])))
        dist.append(distance(tempresult))
    return dist,alphalist

#--------------------------------------------truth------------------------------------------
t = gradient_descent(alpha = 0.3, beta = 0.3, epsilon = 0.000000001, x0 = np.asarray([1,1]))
TRUTH = f(t[len(t)-1])

for x in t:
    print('delta:')
    print(f(x)-TRUTH)


#implements the LVQ algorithm

import numpy as np

#reading of data from files
def reading_data(string):
    with open(string, 'r') as f:
        data = f.readlines()

    all_data = []
    all_label = []

    for item in data:
        x = item.split(',')
        #obtaining label from data
        label = x[-1].rstrip()
        #obtaining features from data
        indv_data = x[:-1]
        indv_data = np.array(indv_data).astype(np.float)

        all_data.append(indv_data)

        #converting labels into one-hot vectors
        if label == 'Iris-setosa':
            label_vec = 0
            all_label.append(label_vec)
        elif label == 'Iris-versicolor':
            label_vec = 1
            all_label.append(label_vec)
        elif label == 'Iris-virginica':
            label_vec = 2
            all_label.append(label_vec)

    return (all_data, all_label)

#calculating euclidean distance
def euclidean_distance(input_vec, weights):
    distance = 0
    for i in range(len(weights)):
        distance += (input_vec[i] - weights[i])**2
    return np.sqrt(distance)

#shuffling of data and labels
def shuffle(data, labels):
    index = [i for i in range(len(data))]
    np.random.shuffle(index)
    data = np.array(data)
    labels = np.array(labels)
    return data[index], labels[index]

f = open("LVQ1.txt", "w")

all_data, all_label = reading_data('iris_train.txt')
all_data, all_label = shuffle(all_data, all_label)

test_data, test_label = reading_data('iris_test.txt')
test_data, test_label = shuffle(test_data, test_label)

#initialising weights and other parameters
np.random.seed(4)
weights = np.random.rand(3,4)
print(weights)
alpha = 0.3
n_epochs = 50

for epoch in range(n_epochs):
    cluster_error = np.zeros(3)
    lr = alpha * (1 - epoch/n_epochs)

    for i in range(len(all_data)):
        #calculating euclidean distance between data and each node
        distances = []
        for j in range(3):
            distances.append(euclidean_distance(all_data[i], weights[j]))
        #obtaining the predicted label
        predicted_label = int(distances.index(min(distances)))
        actual_label = int(all_label[i])

        #calculation of mean squared error
        error = all_data[i] - weights[predicted_label]
        mean_squared_error = np.dot(error, error)
        cluster_error[predicted_label] += mean_squared_error

        #performing weight change
        if (predicted_label == actual_label):
            delta_w = lr * error
        else:
            delta_w = -lr * error
        weights[predicted_label] += delta_w

        #weights normalisation
        weights[predicted_label] /= np.linalg.norm(weights[predicted_label])

    print("epoch:", epoch, "error:", cluster_error)
    f.write("epoch: " + str(epoch) + " error: " + str(cluster_error) + "\n")

print("weights", weights)
f.write("codebook vector 0: " + str(weights[0]) + "\n")
f.write("codebook vector 1: " + str(weights[1]) + "\n")
f.write("codebook vector 2: " + str(weights[2]) + "\n")

#calculation of confusion matrix
confusion_matrix = np.zeros([3,3])
correct_predictions = 0

#testing on test data
for i in range(len(test_data)):
    distances = []
    for j in range(3):
        distances.append(euclidean_distance(test_data[i], weights[j]))
    predicted_label = int(distances.index(min(distances)))
    actual_label = int(test_label[i])
    if (int(predicted_label) == int(actual_label)):
        correct_predictions += 1

    confusion_matrix[predicted_label][actual_label] += 1

print(confusion_matrix)
print("accuracy", correct_predictions/len(test_data))

f.close()


#The covariance matrix and its eigenvalues the hard way
from random import random, seed
import numpy as np

def covariance(x, y, n):
    sum = 0.0
    mean_x = np.mean(x)
    mean_y = np.mean(y)
    for i in range(0, n):
        sum += (x[(i)]-mean_x)*(y[i]-mean_y)
    return  sum/n

n = 10

x = np.random.normal(size=n)
y = np.random.normal(size=n)
z = x*x*x+y*y +0.5*np.random.normal(size=n)
covxx = covariance(x,x,n)
covxy = covariance(x,y,n)
covxz = covariance(x,z,n)
covyy = covariance(y,y,n)
covyz = covariance(y,z,n)
covzz = covariance(z,z,n)

SigmaCov = np.array([ [covxx, covxy, covxz], [covxy, covyy, covyz], [covxz, covyz, covzz]])
print(SigmaCov)

EigValues, EigVectors = np.linalg.eig(SigmaCov)
# sort eigenvectors and eigenvalues
permute = EigValues.argsort()
EigValues = EigValues[permute]
EigVectors = EigVectors[:,permute]
print(EigValues)
print(EigVectors)


# Importing the dependancies
import numpy as np
import matplotlib.pyplot as plt
from planar_utils import *
# Loading the data
X, y = load_data()

# Visualizing the data
plot_planar_data(X, y)

# Defining the sigmoid function
def sigmoid(z):
    return 1 / (1 + np.exp(-z))

# Defining the relu function
def relu(z):
    return z * (z > 0)

# Defining the derivative during back propagation
def relu_derivative(z):
    return 1 * (z > 0)

# Defining some global variables
hidden_neurons = 10
epochs = 10000
m = X.shape[1]
learning_rate = 1.2
cost = [0]*epochs # For visualizaion
epsilon = 1e-8

# Initializing the weights
W1 = np.random.randn(hidden_neurons, X.shape[0])*0.01
b1 = np.zeros((hidden_neurons, 1))
W2 = np.random.randn(y.shape[0], hidden_neurons)*0.01
b2 = np.zeros((y.shape[0], 1))

v_t = np.zeros((4,))

for epoch in range(epochs):

    # Forward propagation
    Z1 = np.dot(W1, X) + b1
    A1 = np.tanh(Z1)

    Z2 = np.dot(W2, A1) + b2
    A2 = sigmoid(Z2)

    # Cost
    logprobs = np.multiply(np.log(A2 + epsilon), y) + np.multiply((1 - y), np.log(1 - A2 + epsilon))

    cost[epoch] = - np.sum(logprobs) / m
    if epoch%1000 == 0: # Printing the cost after every 1000 iterations for debugging
        print(f'Cost after {epoch} iterations = {cost[epoch]}')

    # Backward propagation
    dZ2= A2 - y
    dW2 = (1/m) * np.dot(dZ2, A1.T)
    db2 = (1/m) * np.sum(dZ2, axis=1, keepdims=True)
    dZ1 = np.multiply(np.dot(W2.T, dZ2), 1-np.power(A1, 2))
    dW1 = (1/m) * np.dot(dZ1, X.T)
    db1 = (1/m) * np.sum(dZ1, axis=1, keepdims=True)

    grads = np.array([dW2, db2, dW1, db1])
    # caculating the exponential average.
    v_t = 0.8*v_t + learning_rate*grads
    # Updating the parameters
    W2 -= v_t[0]
    b2 -= v_t[1]
    W1 -= v_t[2]
    b1 -= v_t[3]

plt.style.use('fivethirtyeight')

# Visualizing the cost function
plt.plot(cost, label = 'cost')
plt.xlabel('Epochs')
plt.ylabel('Cost')
plt.legend()
plt.show()

# Predicting
Z1 = np.dot(W1, X) + b1
A1 = np.tanh(Z1)

Z2 = np.dot(W2, A1) + b2
y_pred = sigmoid(Z2)

print ('Accuracy: %d' % float((np.dot(y, y_pred.T) + np.dot(1-y, 1-y_pred.T))/float(y.size)*100) + '%')


import numpy as np
import math
import numpy.linalg as LA
import matplotlib.pyplot as plt

def det(i):
    ans = i[0,0]*i[1,1]*i[2,2] + i[0,1]*i[1,2]*i[2,0]+i[0,2]*i[1,0]*i[2,1] - i[0,2]*i[1,1]*i[2,0] - i[0,0]*i[1,2]*i[2,1] - i[0,1]*i[1,0]*i[2,2]
    return ans

def def_re(i):
    print('mathans:' + str(det(i)))
    print('pyans  :' + str(LA.det(i)))

a = np.array([[1,2,3],
              [4,5,6],
              [7,8,9]])

b = np.array([[1,1,3],
              [4,78,6],
              [7,8,-4]])

print(a,type(a))

def_re(b)
def_re(a@b)
def_re(a)

print(LA.det(a))
print("#####")
print(det(a))

b = np.array([[1,2,3]])

print(b,type(b))

print(a@b.T)
# print(a@b) Undifined


import numpy as np

class KMeans:

    def __init__(self, k, dataset):
        self.dataset = dataset
        self.k = k
        self.centroids = []

        # Randomly choose centroids
        self.randomize_centroids()

    @staticmethod
    def distance(point, centroid):
        return np.linalg.norm(point-centroid)

    @property
    def predicted(self):
        return np.array([self.predict(p) for p in self.dataset])

    def randomize_centroids(self):
        self.centroids = self.dataset[np.random.choice(len(self.dataset), self.k, replace=False), :]

    def get_prepared(self):
        return zip(self.dataset, self.predicted)

    def get_points_in_clusters(self):
        return [self.dataset[np.where(self.predicted == c)] for c in range(self.k)]

    def error(self):
        distances = [self.distance(point, self.centroids[int(cluster)]) for point, cluster in self.get_prepared()]
        return 1 / len(self.dataset) * sum(distances)

    def predict(self, point):
        return int(np.argmin([self.distance(point, centroid) for centroid in self.centroids]))

    def train(self, epochs=50):
        minimal_error = np.inf
        minimal_centroids = self.centroids

        for e in range(epochs):
            self.randomize_centroids()
            self.update_centroids()

            if self.error() < minimal_error:
                minimal_centroids = self.centroids
                minimal_error = self.error()

        self.centroids = minimal_centroids

    def update_centroids(self):
        predicted = None
        while (predicted is None) or (not all(predicted == self.predicted)):
            predicted = self.predicted
            self.centroids = list(map(lambda x: np.sum(x, axis=0) / (len(x) or 1), self.get_points_in_clusters()))


# Import packages
import numpy as np
from numpy import inf
from keras import backend as K
import math

# Tangens hyperbolicus function
def tanh(x, derivative=False):
    if (derivative):

        return (1 - (x**2))

    return ((np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x)))

# Tangens hyperbolicus function with inf check
def tanhyperbolic(x, derivative=False):
    output = ((np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x)))

    # Check if output contains any NaN values
    if np.isnan(output).any():

        if -math.inf in x:
            output = np.nan_to_num(output, nan=-1)

        if -math.inf in x:

            output = np.nan_to_num(output, nan=1)

    if (derivative):

        return (1 - (x**2))

    return output

# Inverted symmetric sigmoid function with inf check
def inv_sigmoid_inversion(x, derivative=False):

    if np.isnan(x).any() or np.isinf(x).any():
        x = np.nan_to_num(x, neginf=-0.5, posinf=0.5)

    if (derivative):

        if np.isnan(x).any() or np.isinf(x).any():
            x = np.nan_to_num(x, neginf=-0.5, posinf=0.5)

        return -1 / ((x**2) - 0.25)

    return (-K.log((1 / (x + 0.5) - 1)))

# Inverted symmetric sigmoid function
def inv_sigmoid(x, derivative=False):

    if (derivative):
        return -1 / ((x**2) - 0.25)

    return (-K.log((1 / (x + 0.5) - 1)))

# Symmetric sigmoid function
def sigmoid(x, derivative=False):

    if (derivative):
        return np.exp(-x) / ((np.exp(-x) + 1)**2)

    return ((1 / (1 + np.exp(-x))) - 0.5)

# Linear Function
def linear(x, derivative=False):
    return x


# Monte Carlo Valuation of a European Option in a Black-Scholes World
# With implementation of Antithetic and Delta-based control variate method
# by Michal Lyskawinski
# 10/31/2016

from math import *
import numpy as np
import random
from scipy.stats import norm

# Computation of Black-Scholes
def CBS(S, K, T, r, sigma,t, option):

    t2t = T-t # time to maturity
    # Calculations for the solution to BSM equation
    dplus = (1 / (sigma * sqrt(t2t))) * ((log(S / K)) + (r + ((sigma ** 2) / 2)) * t2t)
    dminus = (1 / (sigma * sqrt(t2t))) * ((log(S / K)) + (r - (sigma ** 2) / 2) * t2t)

    # Calculating price of Call and Put
    if option == 'Call':
        return S * norm.cdf(dplus) - K * exp(-r * t2t) * norm.cdf(dminus)
    elif option == 'Put':
        return K * exp(-r * t2t) * norm.cdf(-dminus) - S * norm.cdf(-dplus)

# Initialize parameters
S = 100
r = 0.06
sig = 0.2
T = 1
K = 100
N = 10
M = 100
div = 0.03  # In percentage
option = 'Put'

# Precompute constants
dt = T/N
nu = r - div - 0.5*(sig**2)
nudt = nu*dt
sigsdt = sig*sqrt(dt)
erddt = exp((r-div)*dt)
beta1 = -1

sum_CT = 0
sum_CT2 = 0

for j in range(1,M): # For each simulation
    St1 = S
    St2 = S
    cv1 = 0
    cv2 = 0

    for i in range(1,N): # For each time step
        t = (i - 1) * dt
        delta1 = CBS(St1, K, T, r, sig, t, option)
        delta2 = CBS(St2, K, T, r, sig, t, option)
        eps = np.random.normal(0, 1)
        Stn1 = St1 * exp(nudt + sigsdt * eps)
        Stn2 = St2 * exp(nudt + sigsdt * (-eps))
        cv1a = cv1 + delta1 * (Stn1 - St1 * erddt)
        cv2a = cv2 + delta2 * (Stn2 - St2 * erddt)
        St1 = Stn1
        St2 = Stn2

    if option == 'Call':
        CT = 0.5*(max(0, St1 - K) + beta1*cv1a + max(0, St2 - K) + beta1*cv2a)
        sum_CT = sum_CT + CT
        sum_CT2 = sum_CT2 + CT*CT
    elif option == 'Put':
        CT = 0.5*(max(0, K - St1) + beta1*cv1a + max(0, K - St2) + beta1*cv2a)
        sum_CT = sum_CT + CT
        sum_CT2 = sum_CT2 + CT * CT
    else:
        break

Value = sum_CT/M*exp(-r*T)
SD = sqrt((sum_CT2 - sum_CT*sum_CT/M)*exp(-2*r*T)/(M-1))
SE = SD/sqrt(M)

print('The Value of European',option,'Option is',Value)
print('The Standard Deviation of this Option is',SD)
print('The Standard Error in this case is',SE)


import math
import statistics as stats
import numpy as np
from Modulos.Utils import Truncate

def FuncionAcumuladaExponencial(x, valor_lambda):
    '''La función devuelve el valor de la función acumulada para la distribución exponencial valuada en X
       Parámetros: x: variable a valuar la función
                   valor_lambda: valor del lambda calculado para la serie'''
    return 1-math.exp(-valor_lambda*x)

def FuncionDensidadNormal(x, media, desviacion_estandar):
    '''La función devuelve el valor de la función de densidad para la distribución normal valuada en X
       Parámetros: x: variable a valuar la función
                   media: media calculada para la serie
                   desviacion_estandar: desviación estandar calculada para la serie'''
    return (math.exp(-0.5*((x-media)/desviacion_estandar)**2))/(desviacion_estandar*math.sqrt(2*math.pi))

def FuncionAcumuladaUniforme(a, b, x):
    '''La función devuelve el valor de la función acumulada para la distribución uniforme valuada en X
        Parámetros: x: variable a valuar la función
                    a: extremo superior
                    b: extremo inferior'''
    return (x-a)/(b-a)

def ProbabilidadAcumuladaExponencial(desde, hasta, valor_lambda):
    '''La función devuelve el valor de la probabilidad acumulada para la distribución Exponencial
       Parámetros: desde: valor inicial del intervalo
                   hasta: valor final del intervalo
                   valor_lambda: valor del lambda calculado para la serie'''
    return FuncionAcumuladaExponencial(hasta, valor_lambda) - FuncionAcumuladaExponencial(desde, valor_lambda)

def ProbabilidadAcumuladaUniforme(desde, hasta, a, b):
    '''La función devuelve el valor de la probabilidad acumulada para la distribución uniforme
        Parámetros: desde: valor inicial del intervalo
                    hasta: valor final del intervalo
                    a: extremo superior
                    b: extremo inferior'''
    return FuncionAcumuladaUniforme(a, b, hasta) - FuncionAcumuladaUniforme(a, b, desde)

def FrecuenciasEsperadas(tamaño_muestra, intervalos, tipoDistribucion, media, desviacion_estandar, a, b):
    '''La función calcula la frecuencia esperada para cada intervalo según el tipo de distribución elegida
       Parámetros: tamaño_muestra: entero que representa la cantidad de elementos de la serie
                   intervalos: intervalos Dict<str, extremos> Diccionario que utiliza como clave la representacion del intervalo
                   tipoDistribucion: entero que representa el tipo de distribución elegida como hipótesis nula (0=uniforme, 1=exponencial, 2=normal)
                   media: media calculada para la serie
                   desviacion_estandar: desviacion estandar calculada para la serie
                   a: valor minimo de la serie
                   b: valor maximo de la serie
       Return: lista con frecuencias esperadas'''

    frec_esp_arr = []
    if tipoDistribucion == 1:
        valor_lambda = Truncate(1/media, 7)

    for i in intervalos:
        intervalo = intervalos[i]
        desde, hasta = intervalo[0], intervalo[1]
        if tipoDistribucion == 0:
          prob = ProbabilidadAcumuladaUniforme(desde, hasta, a, b)
          frec_esp = round(prob*tamaño_muestra)
        elif tipoDistribucion == 1:
          prob = ProbabilidadAcumuladaExponencial(desde, hasta, valor_lambda)
          frec_esp = Truncate(prob*tamaño_muestra, 4)

        elif tipoDistribucion == 2:
          marca_clase = (desde+hasta)/2
          prob = FuncionDensidadNormal(marca_clase, media, desviacion_estandar) * (hasta-desde)
          frec_esp = Truncate(prob*tamaño_muestra, 4)

        frec_esp_arr.append(frec_esp)

    return frec_esp_arr

def testFrecuenciasEsperadasExponencial():
    arr = [0.10, 0.25, 1.53, 2.83, 3.50, 4.14, 5.65, 6.96, 7.19, 8.25,1.20, 5.24, 4.75, 3.96, 2.21, 3.15, 2.53, 1.16, 0.32, 0.90, 0.87, 1.34, 1.87, 2.91, 0.71, 1.69, 0.69, 0.55, 0.43, 0.26]
    intervalos = {'0 - 1': [0, 1], '1 - 2': [1, 2], '2 - 3': [2, 3], '3 - 4': [3, 4], '4 - 5': [4, 5], '5 - 6': [5, 6], '6 - 7': [6, 7], '7 - 8': [7, 8], '8 - 9': [8, 9], '9 - 10': [9, 10]}
    tipoDistribucion = 1
    tamaño_muestra = 30
    a, b = min(arr), max(arr)
    media = stats.mean(arr)
    print('Media:', media)
    desviacion_estandar = np.std(arr, ddof=1)
    frec_esp = FrecuenciasEsperadas(tamaño_muestra, intervalos, tipoDistribucion, media, desviacion_estandar, a, b)
    print(frec_esp)

def testFrecuenciasEsperadasNormal():
    arr = [1.56, 2.21, 3.15, 4.61, 4.18, 5.20, 6.94, 7.71, 5.15, 6.76, 7.28, 4.23, 3.21, 2.75, 4.69, 5.86, 6.25, 4.27, 4.91, 4.78, 2.46, 3.97, 5.71, 6.19, 4.20, 3.48, 5.83, 6.36, 5.90, 5.43]
    intervalos = {'0 - 1': [0, 1], '1 - 2': [1, 2], '2 - 3': [2, 3], '3 - 4': [3, 4], '4 - 5': [4, 5], '5 - 6': [5, 6], '6 - 7': [6, 7], '7 - 8': [7, 8], '8 - 9': [8, 9], '9 - 10': [9, 10]}
    media = stats.mean(arr)
    print('Media:', media)
    desviacion_estandar = np.std(arr, ddof=1)
    print('Desv estandar: ', desviacion_estandar)
    tipoDistribucion = 2
    tamaño_muestra = 30
    a, b = min(arr), max(arr)
    frec_esp = FrecuenciasEsperadas(tamaño_muestra, intervalos, tipoDistribucion, media, desviacion_estandar, a, b)
    print(frec_esp)

def testFrecuenciasEsperadasUniforme():
    arr = [0.15, 0.22, 0.41, 0.65, 0.84, 0.81, 0.62, 0.45, 0.32, 0.07, 0.11, 0.29, 0.58, 0.73, 0.93, 0.97, 0.79, 0.55, 0.35, 0.09, 0.99, 0.51, 0.35, 0.02, 0.19, 0.24, 0.98, 0.10, 0.31, 0.17]
    intervalos = {'0.0 - 0.2': [0.0, 0.2], '0.2 - 0.4': [0.2, 0.4], '0.4 - 0.6': [0.4, 0.6], '0.6 - 0.8': [0.6, 0.8], '0.8 - 1.0': [0.8, 1.0]}
    media = stats.mean(arr)
    desviacion_estandar = np.std(arr, ddof=1)
    tipoDistribucion = 0
    tamaño_muestra = 30
    a, b = min(arr), max(arr)
    print(a,b)
    frec_esp = FrecuenciasEsperadas(tamaño_muestra, intervalos, tipoDistribucion, media, desviacion_estandar, a, b)
    print(frec_esp)


import numpy as np

def kyle_lambda(prices: np.ndarray, volumes: np.ndarray) -> np.ndarray:
    r"""Kyle's Lambda

    A measure of market impact cost from [Kyle (1985)](https://doi.org/10.2307/1913210),
    which can be interpreted as the cost of demanding a certain amount of liquidity over a given time period.

    It is also used as a measure of market liquidity and can be estimated by the
    volume required to move the price of a security by one dollar. Sometimes referred
    to as Kyles lambda, this measure is an inverse proxy of liquidity, with higher
    values of lambda implying lower liquidity and market depth. The authors estimate
    this measure on a daily basis by using all transactions during normal trading hours
    on each day.

    Given the sequence of

    * intraday returns $[R_{i,1}, R_{i,2}, ..., R_{i,T}]$,
    * prices $[p_{i,1}, p_{i,2}, ..., p_{i,T}]$, and
    * volumes $[v_{i,1}, v_{i,2}, ..., v_{i,T}]$

    for security $i$ during a specific day, the following regression is estimated:

    $$
    R_{i,t} = \alpha_i + \lambda_i \text{Sgn}(t) \ln(v_{i,t}\times p_{i,t}) + \varepsilon_{i,t}
    $$

    where $\text{Sgn}(t)$ is -1 or + 1 depending on the direction of the trade, i.e., “buy” or “sell”,
    as determined according to the following rule:

    * if $R_{i,t}$ is positive, the value +1 is assigned to that transaction (to indicate net buying),
    * if $R_{i,t}$ is negative, the value −1 is assigned to that transaction (to indicate net selling).

    Any interval with zero return receives the same sign as that of the most recent transaction with a non-zero return
    (using returns from the prior day, if necessary).

    Args:
        prices (np.ndarray): (n_securities, n_periods) array of security prices
        volumes (np.ndarray): (n_securities, n_periods) array of security volumes

    Returns:
        np.ndarray: Array of Kyle's Lambda estimates for the securities

        !!! note
            The `prices` data is used to calculate the asset returns, which is 1 less than the number of
            prices. As such, the `volumes` data of the first period is not used in the calculation.

    Examples:
        >>> import numpy as np
        >>> from frds.measures import kyle_lambda

        3 assets daily volume for 6 days
        >>> volumes = np.array(
        ...     [[100, 180, 900, 970, 430, 110], [200, 250, 400, 590, 260, 600], [300, 700, 220, 110, 290, 310]]
        ... )

        Their daily prices
        >>> prices = np.array(
        ...     [[44, 39, 36, 28, 23, 18], [82, 81, 79, 40, 26, 13], [55, 67, 13, 72, 10, 65]]
        ... )

        Calculate their respective Kyle's Lambda estimates.
        >>> kyle_lambda(prices, volumes)
        array([-0.02198189, -0.1951004 ,  0.22752204])

    References:
        * [Kyle (1985)](https://doi.org/10.2307/1913210),
            Continuous auctions and insider trading, *Econometrica*, 53, 1315-1355.
        * [Bisias, Flood, Lo, and Valavanis (2012)](https://doi.org/10.1146/annurev-financial-110311-101754),
            A survey of systemic risk analytics, *Annual Review of Financial Economics*, 4, 255-296.
    """

    assert volumes.shape == prices.shape

    n_securities, n_periods = prices.shape
    # number of returns is 1 less than prices
    n_periods -= 1

    # calculate the asset returns
    returns = np.diff(prices, axis=1) / np.roll(prices, 1)[:, 1:]

    # volumes and prices of the first period are not used
    volumes = np.delete(volumes, (0), axis=1)  # volumes = volumes[:,1:]
    prices = np.delete(prices, (0), axis=1)  # prices = prices[:, 1:]

    lambdas = np.zeros(n_securities)
    for i in range(n_securities):
        mod_signs = np.zeros(n_periods)
        np.sign(returns[i], out=mod_signs)
        for t in range(1, len(mod_signs)):
            if mod_signs[t] == 0:
                mod_signs[t] = mod_signs[t - 1]

        X = np.vstack([np.ones(n_periods), mod_signs * np.log(prices[i] * volumes[i])])
        betas = np.linalg.lstsq(X.T, returns[i], rcond=None)[0]
        lambdas[i] = betas[1]

    return lambdas


#!/usr/bin/python
# -*- coding: latin-1 -*-
"""
Utilities contains a number of frequently used functions.
"""

import numpy as np
import numpy.linalg as LA

def normalize(array):
    """
    Returns a normalized version of the provided array.

    Parameters
    ----------
    array : numpy.ndarray_like
        The array to be normalized. Afterwards np.linalg.norm(normalize(array))
        is approximately equal to 1.

    Returns
    -------
    normalized array : numpy.ndarray_like
        the array normalized to unit length:
        np.linalg.norm(normalize(array)) ~= 1.

    Raises
    ------
    ValueError
        if the input array contains Inf's or Nan's
    ZeroDivisionError
        if the length of the provided array has length ofzero the division will
        cause a ZeroDivisionError to be raised

    Examples
    --------
    >>> normalize([1, 0, 0])
    [1.0, 0.0, 0.0]
    >>> normalize([2, 4, 4])
    [0.33333333, 0.66666667, 0.66666667]
    >>> normalize(np.array((3,4,5)))
    [0.42426407, 0.56568542, 0.70710678]
    ### np.linalg.norm(normalize((x, y, z))) ~= 1
    """
    if not np.isfinite(array).all():
        raise ValueError("Can not normalize array {}".format(array)
                        + "because it contains NaN or Inf")
    norm = LA.norm(array)
    if norm < 1e-9:
        raise ZeroDivisionError("Division by zero")
    return array / norm

def check_finite(*args):
    """
    Validate the input parameters and raise ValueErrors if any contains
    incompatible values (Infs or NaNs) are present.

    Parameters
    ----------
    args : numpy.ndarray_like
        a list of lists or arrays

    Raises
    ------
    ValueError
        if any passed in element is Inf or NaN.
    """
    for e in args:
        if not np.isfinite(e).all():
            raise ValueError("Input was {}".format(e)
                            + ", but can not contain Inf or NaN")


import numpy as np

def uniform_mesh(d1, d2, p, m , element_type : str):
    PD  = 2  # Problem Dimension
    q   = np.array([[0, 0], [d1, 0], [0, d2], [d1, d2]]) # 4 corners
    NoN = (p + 1) * (m + 1)
    NoE = p*m
    NPE = 4

    ### Nodes ###
    NL  = np.zeros([NoN, PD])
    a   = (q[1, 0] - q[0, 0])/p # Increment in the horizontal dir.
    b   = (q[2, 1] - q[0, 1])/m # Increment in the Vertical dir.

    n   = 0 # This will allow us to go through rows in NL
    for i in range(1, m+2):         # Vertical loop
        for j in range(1, p+2):     # Horizontal loop
            NL[n, 0]    = q[0, 0] + (j - 1)*a   # x values
            NL[n, 1]    = q[0, 1] + (i - 1)*b   # y values
            n += 1

    ### Elements ###
    EL = np.zeros([NoE, NPE])
    for i in range(1, m+1):         # Vertical loop
        for j in range(1, p+1):     # Horizontal loop
            if j == 1:
                EL[(i-1)*p + j - 1, 0] = (i - 1)*(p + 1) + j
                EL[(i-1)*p + j - 1, 1] = EL[(i-1)*p + j - 1, 0] + 1
                EL[(i-1)*p + j - 1, 3] = EL[(i-1)*p + j - 1, 0] + (p + 1)
                EL[(i-1)*p + j - 1, 2] = EL[(i-1)*p + j - 1, 3] + 1
            else:
                EL[(i-1)*p + j - 1, 0] = EL[(i-1)*p + j - 2, 1]
                EL[(i-1)*p + j - 1, 3] = EL[(i-1)*p + j - 2, 2]
                EL[(i-1)*p + j - 1, 1] = EL[(i-1)*p + j - 1, 0] + 1
                EL[(i-1)*p + j - 1, 2] = EL[(i-1)*p + j - 1, 3] + 1

    if element_type == 'D2TR3N':
        NPE_new = 3
        NoE_new = 2*NoE
        EL_new  = np.zeros([NoE_new, NPE_new])

        for i in range(1, NoE+1):
            #for the first triangular element
            EL_new[2*(i-1), 0] = EL[i-1, 0]
            EL_new[2*(i-1), 1] = EL[i-1, 1]
            EL_new[2*(i-1), 2] = EL[i-1, 2]

            #for the second triangular element
            EL_new[2*(i-1) + 1, 0] = EL[i-1, 0]
            EL_new[2*(i-1) + 1, 1] = EL[i-1, 2]
            EL_new[2*(i-1) + 1, 2] = EL[i-1, 3]

        EL = EL_new

    EL = EL.astype(int)
    return (NL, EL)


import numpy as np

def gc1d(x):
    """
    Compute Gini Index for 1D array.

    References
    ----------
    [1] http://mathworld.wolfram.com/GiniCoefficient.html

    [2] Damgaard, C. and Weiner, J. "Describing Inequality in Plant Size
    or Fecundity." Ecology 81, 1139-1142, 2000.

    [3] Dixon, P. M.; Weiner, J.; Mitchell-Olds, T.; and Woodley, R.
    "Bootstrapping the Gini Coefficient of Inequality."
    Ecology 68, 1548-1551, 1987.

    [4] Dixon, P. M.; Weiner, J.; Mitchell-Olds, T.; and Woodley, R.
    "Erratum to 'Bootstrapping the Gini Coefficient of Inequality.' "
    Ecology 69, 1307, 1988.

    [5] https://en.wikipedia.org/wiki/Gini_coefficient

    [6] https://github.com/oliviaguest/gini/blob/master/gini.py
    """
    x = np.array(x, dtype="float64")
    if x.ndim != 1:
        raise ValueError("Only support 1D array.")
    if x.shape[0] == 0:
        raise ValueError("Array size cannot be 0.")
    if x.shape[0] == 1:
        return np.nan
    # sort into ascending order
    xs = np.sort(x)
    if xs[0] == xs[-1]:
        return 0
    # Wrap with np.int64 to prevent division by 0
    n = np.int64(x.shape[0])
    xs_ranks = np.arange(1, n+1)
    xmean = x.mean()
    gc = ((2 * xs_ranks - n - 1) * xs).sum() / (n * (n-1) * xmean)
    return gc

# Multiple testing correction adapted from
# https://github.com/CoBiG2/cobig_misc_scripts/blob/master/FDR.py
#
# Following is the lincense info:
# Copyright 2017 Francisco Pina Martins <f.pinamartins@gmail.com>
# This file is part of structure_threader.
# structure_threader is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# structure_threader is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with structure_threader. If not, see <http://www.gnu.org/licenses/>.
#
# Taken from https://stackoverflow.com/a/21739593/3091595, ported to python 3
# and improved readability.
def multiple_testing_correction(pvalues, correction_type="FDR"):
    """
    Consistent with R.

    correct_pvalues_for_multiple_testing([0.0, 0.01, 0.029, 0.03, 0.031, 0.05,
                                          0.069, 0.07, 0.071, 0.09, 0.1])
    """
    pvalues = np.array(pvalues)
    sample_size = pvalues.shape[0]
    qvalues = np.empty(sample_size)
    if correction_type == "Bonferroni":
        # Bonferroni correction
        qvalues = sample_size * pvalues
    elif correction_type == "FDR":
        # Benjamini-Hochberg, AKA - FDR test
        values = [(pvalue, i) for i, pvalue in enumerate(pvalues)]
        values.sort()
        values.reverse()
        new_values = []
        for i, vals in enumerate(values):
            rank = sample_size - i
            pvalue, index = vals
            new_values.append((sample_size/rank) * pvalue)
        for i in range(0, int(sample_size)-1):
            if new_values[i] < new_values[i+1]:
                new_values[i+1] = new_values[i]
        for i, vals in enumerate(values):
            pvalue, index = vals
            qvalues[index] = new_values[i]
    else:
        raise ValueError("Not supported correction type "
                         "{}".format(correction_type))
    return np.clip(qvalues, 0, 1)

# lower upper bound
# start, end, lb, ub should all be scalar
def bidir_ReLU(x, start, end, lb=0, ub=1):
    if start > end:
        raise ValueError("start should <= end"
                         "start: {}. end: {}.".format(start, end))

    if lb > ub:
        raise ValueError("lb should <= ub"
                         "lower bound: {}. "
                         "upper bound: {}. ".format(start, end))

    if start < end:
        width = end - start
        height = ub - lb
        return np.clip(a=height * (x - start) / width + lb,
                       a_min=lb, a_max=ub)
    else:
        # start == end
        return np.where(x >= start, ub, lb)


"""
Sequential Probability Ratio Tests
"""
import math
import numpy as np
import scipy
from scipy.special import comb

############# User Function #################

def seq_prob_ratio(ho, ha, n, a, b, distribution, with_replacement=True):
	"""
	Performs sequential probability ratio test of desired distribution type.
	Parameters
	----------
	ho : float or int
       null hypothesis
    ha : float or int
       alternate hypothesis
    a : float
       Type I Error
    b : float
       Type II Error
    n : list
       list of population values
    with_replacement : boolean (default : True)
       True: sample from n with replacement
       False: sample from n without replacement
	distribution : string
		type of distribution : {"normal", "bernoulli", "hypergeometric"}

	Returns
	-------
	String
	   Conclusion of test
	Other value types (different per type of test)
	"""
	if distribution == "normal":
		return normal_seq_ratio(ho, ha, a, b, n, with_replacement)
	elif distribution == "bernoulli":
		return bernoulli_seq_ratio(ho, ha, n, a, b, with_replacement)
	elif distribution == "hypergeometric":
		return hypergeom_seq_ratio(ho, ha, a, b, n, with_replacement)
	else:
		raise ValueError

####### Private Helper Functions #######

def bernoulli_lh(ho, ha, s, n):
	"""
	Returns the likelihood ratio for independently distributed bernoulli random variables.
	Parameters
	----------
	ho : float
	   null hypothesis
	ha : float
	   alternative hypothesis
	s : float or int
	   number of successes in sample
	n : float or int
	   total number of elements in sample
	Returns
	-------
	float
	   likelihood ratio of model
	"""

	null_lh = (ho ** s) * (1 - ho)**(n - s)
	alt_lh = (ha ** s) * (1 - ha)**(n - s)

	return alt_lh / null_lh

def bernoulli_seq_ratio(ho, ha, n, a, b, with_replacement=True):
	"""
	Performs seqential probability ratio test on independently distributed bernoulli random variables.
	Parameters
	----------
	ho : float
	   null hypothesis
	ha : float
	   alternative hypothesis
	n : list of {0, 1}
	   list of population values
	a : float
	   Type I Error
	b : float
	   Type II Error
	Returns
	-------
	String
	   Conclusion of test
	List
	   [proportion of successes when test haults, sample size when test haults, string of conclusion]
	"""

	A, B = b / (1 - a), (1 - b) / a

	assert (A < B)

	def test(ho, ha, n, s, A, B, ss, ts):
		"""
		Samples from n with replacement.
		Parameters
		----------
		ho : float
		   null hypothesis
		ha : float
		   alternative hypothesis
		n : list
		   list of population values
		s : int or float
		   current number of successes in sample
		A : float
		   lower bound parameter
		B : float
		   upper bound parameter
		ss : int
		   current sample size
		ts : float or int
		   current likelihood
		"""
		successes = s

		index = np.random.choice(len(n), 1, replace=True)[0]

		trial = n[index]

		ss += ss + 1

		if trial == 0:
			s += 1

		ts *= bernoulli_lh(ho, ha, s, ss)

		if ts >= B:
			return [s / ss, ss, "Accept null hypothesis."]
		elif ts <= A:
			return [s / ss, ss, "Reject null hypothesis."]
		else:
			return test(ho, ha, n, s, A, B, ss, ts)

	def test_wo(ho, ha, n, s, A, B, ss, ts):
		"""
		Samples from n without replacement.
		Parameters
		----------
		ho : float
		   null hypothesis
		ha : float
		   alternative hypothesis
		n : list
		   list of population values
		s : int or float
		   current number of successes in sample
		A : float
		   lower bound parameter
		B : float
		   upper bound parameter
		ss : int
		   current sample size
		ts : float or int
		   current likelihood
		"""

		index = np.random.choice(len(n), 1, replace=True)[0]

		trial = n[index]

		n.pop(index)

		ss += ss + 1

		if trial == 0:
			s += 1

		ts *= bernoulli_lh(ho, ha, s, ss)

		if ts >= B:
			return [s / ss, ss, "Accept null hypothesis."]
		elif ts <= A:
			return [s / ss, ss, "Reject null hypothesis."]
		else:
			if ss == len(n):
				return [None, "No Decision."]
			return test(ho, ha, n, s, A, B, ss, ts)

	if with_replacement == True:
		return test(ho, ha, n, 0, A, B, 0, 1)

	return test_wo(ho, ha, n, 0, A, B, 0, 1)

def normal_lh(ho, ha, x):

	"""
	Returns likelihood ratio for indepedently distributed normal variables.
	Parameters
	----------
	ho : float
	   null hypothesis
	ha : float
	   alternative hypothesis
	x : float or int
	   value of most recent sample
	Returns
	-------
	float
	   likelihood of model
	"""
	null_lh = math.exp(-((x - ho)**2))
	alt_lh = math.exp(-((x - ha)**2))

	return alt_lh / null_lh

def normal_seq_ratio(ho, ha, a, b, n, with_replacement=False):

	"""
	Performs sequential probability ratio test on independently distributed normal random variables Xi.
	Parameters
	----------
	ho : float or int
	   null hypothesis
	ha : float or int
	   alternative hypothesis
	a : float
	   Type I Error
	b : float
	   Type II Error
	n : list
	   list of all possible values of Xi for i = {1, 2, .....n}
	with_replacement : boolean
	   False: sample from n without replacement
	   True: sample from n with replacement
	Returns
	-------
	string
	   conclusion of test
	list
	   [sample size when test haults, mean when test haults, string of conclusion]
	"""
	A, B = b / (1 - a), (1 - b) / a

	assert (A < B)

	def test(ho, ha, s, u, A, B, ss, ts, n):

		"""
		Samples from n with replacement.
		Parameters
		----------
		ho : float
		   null hypothesis
		ha : float
		   alternative hypothesis
		s : int or float
		   cummulative sum of trial results
		u : float or int
		   current mean
		A : float
		   lower bound parameter
		B : float
		   upper bound parameter
		ss : int
		   current sample size
		ts : float or int
		   cumulative product of likelihood ratios
		n : list
		   list of all possible values of Xi where i = {1, 2....n}
		"""

		index = np.random.choice(len(n), 1, replace=True)[0]

		trial = n[index]

		s += trial

		ss += 1

		u = s / ss

		ts *= normal_lh(ho, ha, trial)

		if ts >= B:
			return [ss, u, "Accept null hypothesis."]
		elif ts <= A:
			return [ss, u, "Reject null hypothesis."]
		else:
			return test(ho, ha, s, u, A, B, ss, ts, n)

	def test_wo(ho, ha, s, u, A, B, ss, ts, n):

		"""
		Samples from n without replacement.
		Parameters
		----------
		ho : float
		   null hypothesis
		ha : float
		   alternative hypothesis
		s : int or float
		   cummulative sum of trial results
		u : float or int
		   current mean
		A : float
		   lower bound parameter
		B : float
		   upper bound parameter
		ss : int
		   current sample size
		ts : float or int
		   cumulative product of likelihood ratios
		n : list
		   list of all possible values of Xi where i = {1, 2....n}
		"""

		index = np.random.choice(len(n), 1, replace=True)[0]

		trial = n[index]

		n.pop(index)

		s += trial

		ss += 1

		u = s / ss

		ts *= normal_lh(ho, ha, trial)

		if ts >= B:
			return [ss, u, "Accept null hypothesis."]
		elif ts <= A:
			return [ss, u, "Reject null hypothesis."]
		else:
			if ss == len(n):
				return [None, "No Decision"]
			return test_wo(ho, ha, s, u, A, B, ss, ts, n)

	if with_replacement == True:

		return test(ho, ha, 0, 0, A, B, 0, 1, n)

	return test_wo(ho, ha, 0, 0, A, B, 0, 1, n)

def hypergeom_lh(ho, ha, trial, n, g, N):
	"""
	Returns likelihood ratio for independently distributed hypergeometric random variables.

	Parameters
	----------
	ho : float
	   null hypothesis
	ha : float
	   alternative hypothesis
	trial : float
	   number of good elements in recent sample
	n : float or int
	   sample size
	g : float or int
	   number of good elements in sample
	N : float or int
	   total population size
	Returns
	-------
	float
	   likelihood ratio of model

	"""
	ho_G, ha_G = ho * (N / n), ha * (N / n)

	null_lh = (comb(ho_G, g) * comb(N - ho_G, n - g))
	alt_lh = (comb(ha_G, g) * comb(N - ha_G, n - g))

	return alt_lh / null_lh

def hypergeom_seq_ratio(ho, ha, a, b, N, with_replacement=True):

	"""
	Performs sequential probability ratio test on independently distributed hypergeometric random variables.

	Parameters
	----------
	ho : float
	   null hypothesis
	ha : float
	   alternative hypothesis
	a : float
	   Type I Error
	b : float
	   Type II Error
	N : list
	   list of elements in population
	with_replacement : boolean
	   True: sample from N with replacement
	   False: sample from N without replacement
	Returns
	-------
	float
	   mean when test haults
	string
	   conclusion of test
	"""

	A, B = b / (1 - a), (1 - b) / a

	assert (A < B)

	def test(ho, ha, A, B, g, n, N, ts):
		"""
		Samples from N with replacement.
		Parameters
		----------
		ho : float
		   null hypothesis
		ha : float
		   alternative hypothesis
		A : float
		   lower parameter
		B : float
		   upper parameter
		g : float or int
		   number of good elements in sample
		n : float or int
		   current sample size
		N : list of {0, 1}
		   list of elements in total population
		ts : float
		   cumulative product of likelihood ratios
		"""

		index = np.random.choice(len(N), 1)[0]

		trial = N[index]

		n += 1

		if trial == 0:
			g += 1

		ts *= hypergeom_lh(ho, ha, trial, n, g, len(N))

		if ts >= B:
			return [n * (g/n), "Accept null hypothesis."]
		elif ts <= A:
			return [n * (g/n), "Reject null hypothesis."]
		else:
			return test(ho, ha, A, B, g, n, N, ts)

	def test_wo(ho, ha, A, B, g, n, N, ts):
		"""
		Samples from N without replacement.
		Parameters
		----------
		ho : float
		   null hypothesis
		ha : float
		   alternative hypothesis
		g : float or int
		   number of good elements in sample
		n : float or int
		   current sample size
		N : list of {0, 1}
		   list of elements in total population
		ts : float
		   cumulative product of likelihood ratios
		"""

		index = np.random.choice(len(N), 1)[0]

		trial = N[index]

		N.pop(index)

		n += 1

		if trial == 0:
			g += 1

		ts *= hypergeom_lh(ho, ha, trial, n, g, len(N))

		if ts >= B:
			return [n * (g/n), "Accept null hypothesis."]
		elif ts <= A:
			return [n * (g/n), "Reject null hypoethesis."]
		else:
			if n == len(N):
				return [None, 'No Decision']
			return test_wo(ho, ha, A, B, g, n, N, ts)

	if with_replacement == True:
		return test(ho, ha, A, B, 0, 0, N, 1)

	return test_wo(ho, ha, A, B, 0, 0, N, 1)


import pylab as pl
import numpy as np

def tam(path, report='full'):
    """
    Calculates the Time Alignment Measurement (TAM) based on an optimal warping path
    between two time series.
    Reference: Folgado et. al, Time Alignment Measurement for Time Series, 2018.

    :param path: (ndarray)
                A nested array containing the optimal warping path between the
                two sequences.
    :param report: (string)
                A string containing the report mode parameter.
    :return:    In case ``report=instants`` the number of indexes in advance, delay and phase
                will be returned. For ``report=ratios``, the ratio of advance, delay and phase
                will be returned. In case ``report=distance``, only the TAM will be returned.

    """
    # Delay and advance counting
    delay = len(pl.find(np.diff(path[0]) == 0))
    advance = len(pl.find(np.diff(path[1]) == 0))

    # Phase counting
    incumbent = pl.find((np.diff(path[0]) == 1) * (np.diff(path[1]) == 1))
    phase = len(incumbent)

    # Estimated and reference time series duration.
    len_estimation = path[1][-1]
    len_ref = path[0][-1]

    p_advance = advance * 1. / len_ref
    p_delay = delay * 1. / len_estimation
    p_phase = phase * 1. / np.min([len_ref, len_estimation])

    if report == 'instants':
        return np.array([advance, delay, phase])

    if report == 'ratios':
        return np.array([advance, delay, phase])

    if report == 'distance':
        return p_advance + p_delay + (1 - p_phase)

    if report == 'full':
        return np.array([advance, delay, phase, p_advance + p_delay + (1 - p_phase)])


r"""
Solve Helmholtz equation in 2D with periodic bcs in one direction
and Dirichlet in the other

   alpha u - \nabla^2 u = f,

Use Fourier basis for the periodic direction and Shen's Dirichlet basis for the
non-periodic direction.

The equation to solve is

     alpha (u, v) - (\nabla^2 u, v) = (f, v)

"""
import sys
import os
import importlib
from sympy import symbols, cos, sin
import numpy as np
from mpi4py import MPI
from shenfun import inner, div, grad, TestFunction, TrialFunction, FunctionSpace, \
    Array, Function, TensorProductSpace, dx

comm = MPI.COMM_WORLD

assert len(sys.argv) == 3, "Call with two command-line arguments"
assert sys.argv[-1].lower() in ('legendre', 'chebyshev', 'jacobi')
assert isinstance(int(sys.argv[-2]), int)

# Collect basis and solver from either Chebyshev or Legendre submodules
family = sys.argv[-1]
base = importlib.import_module('.'.join(('shenfun', family)))
Solver = base.la.Helmholtz

# Use sympy to compute a rhs, given an analytical solution
alpha = 2.
x, y = symbols("x,y", real=True)
ue = (cos(4*np.pi*x) + sin(2*y))*(1-x**2)
fe = alpha*ue - ue.diff(x, 2) - ue.diff(y, 2)

# Size of discretization
N = (int(sys.argv[-2]),)*2

SD = FunctionSpace(N[0], family, bc=(0, 0), scaled=True)
K1 = FunctionSpace(N[1], 'F', dtype='d')
T = TensorProductSpace(comm, (SD, K1), axes=(0, 1))
u = TrialFunction(T)
v = TestFunction(T)

# Get f on quad points
fj = Array(T, buffer=fe)

# Compute right hand side of Poisson equation
f_hat = Function(T)
f_hat = inner(v, fj, output_array=f_hat)

# Get left hand side of Helmholtz equation
matrices = inner(v, alpha*u - div(grad(u)))

# Create Helmholtz linear algebra solver
H = Solver(*matrices)

# Solve and transform to real space
u_hat = Function(T)           # Solution spectral space
u_hat = H(u_hat, f_hat)       # Solve
uq = Array(T)
uq = T.backward(u_hat, uq)

# Compare with analytical solution
uj = Array(T, buffer=ue)
print("Error=%2.16e" %(np.sqrt(dx(uj-uq)**2)))
assert np.allclose(uj, uq)

if 'pytest' not in os.environ:
    import matplotlib.pyplot as plt
    plt.figure()
    X = T.local_mesh(True) # With broadcasting=True the shape of X is local_shape, even though the number of datapoints are still the same as in 1D
    plt.contourf(X[0], X[1], uq)
    plt.colorbar()

    plt.figure()
    plt.contourf(X[0], X[1], uj)
    plt.colorbar()

    plt.figure()
    plt.contourf(X[0], X[1], uq-uj)
    plt.colorbar()
    plt.title('Error')

    plt.show()


# type: ignore

"""
Author: David Oniani

Description:
    A collection of activation functions and their derivatives.
"""

import numpy as np

import miniml.tensor as T
import miniml.layer as L

class LeakyReLU:
    def __init__(self, out_dim: int = 3, alpha: float = 1e-2) -> None:
        """Initilize variables for the LeakyReLU activation function."""

        self._units: int = out_dim
        self._alpha: float = alpha

    def forward(self, x: T.Tensor) -> T.Tensor:
        """Perform a forward pass (numerically stable)."""

        self._saved: T.Tensor = T.Ops.where(x > 0, x, x * self._alpha)
        return self._saved

    def backward(self, dF: T.Tensor) -> T.Tensor:
        """Perform a backpropagation.

        Gradient computation:
           (1) if x < 0, derivative is alpha
           (2) if x > 0, derivative is 1
           (3) if x = 0, derivative is not well-defined, but can treat as 1
        """

        return dF * T.Ops.where(self._saved < 0, self._alpha, 1)

class ReLU(L.Layer):
    def __init__(self, out_dim: int = 3) -> None:
        """Initilize variables for the ReLU activation function."""

        self._units: int = out_dim
        self.type = "ReLU"

    def forward(self, x: T.Tensor) -> T.Tensor:
        """Perform a forward pass (numerically stable)."""

        self._saved: T.Tensor = T.Ops.maximum(x, 0)
        return self._saved

    def backward(self, dF: T.Tensor) -> T.Tensor:
        """Perform a backpropagation.

        Gradient computation:
           (1) if x < 0, derivative is 0
           (2) if x > 0, derivative is 1
           (3) if x = 0, derivative is not well-defined, but can treat as 1
        """

        return dF * T.Ops.where(self._saved < 0, 0, 1)

class Tanh:
    def __init__(self, out_dim: int = 3) -> None:
        """Initilize variables for the Tanh activation function."""

        self._units: int = out_dim

    def forward(self, x: T.Tensor) -> T.Tensor:
        """Perform a forward pass (numerically stable)."""

        self._saved: T.Tensor = T.Ops.tanh(x)
        return self._saved

    def backward(self, dF: T.Tensor) -> T.Tensor:
        """Perform a backpropagation.

        Gradient computation:
            tanh(x) = (e^x - e^{-x}) / (e^x + e^{-x})

            tanh'(x) = ((e^x + e^{-x})^2 - (e^x - e^{-x})^2) / (e^x + e^{-x})^2
                     = 1 - ((e^x - e^{-x}) / (e^x + e^{-x}))^2
                     = 1 - tanh^2(x)
        """

        return dF * (1 - T.Ops.square(self._saved))

class Sigmoid(L.Layer):
    def __init__(self, out_dim: int = 3) -> None:
        """Initilize variables for the Sigmoid activation function."""

        self._units: int = out_dim
        self.type = "ReLU"

    def forward(self, x: T.Tensor) -> T.Tensor:
        """Perform a forward pass (numerically stable)."""

        fst: T.Tensor = 1 / (1 + T.Ops.exp(-x))
        pexp: T.Tensor = T.Ops.exp(x)
        snd: T.Tensor = pexp / (1 + pexp)
        self._saved: T.Tensor = T.Ops.where(x >= 0, fst, snd)
        return self._saved

    def backward(self, dF: T.Tensor) -> T.Tensor:
        """Perform a backpropagation.

        Gradient computation:
            If the sigmoid function is denoted as s, it can be shown that the
            derivative is computed as s * (1 - s).
        """

        return dF * self._saved * (1 - self._saved)

class Softmax:
    def __init__(self, out_dim: int = 3) -> None:
        """Initilize variables for the Softmax activation function."""

        self._units: int = out_dim

    def forward(self, x: T.Tensor) -> T.Tensor:
        """Perform a forward pass (numerically stable)."""

        exp: T.Tensor = T.Ops.exp(x - T.Reduce.max(x))
        self._saved: T.Tensor = exp / T.Reduce.sum(exp)
        return self._saved

    def backward(self, dF: T.Tensor) -> T.Tensor:
        """Perform a backpropagation."""

        raise NotImplementedError

class LogSoftmax:
    def __init__(self, out_dim: int = 3) -> None:
        """Initilize variables for the LogSoftmax activation function."""

        self._units: int = out_dim

    def forward(self, x: T.Tensor) -> T.Tensor:
        """Perform a forward pass (numerically stable)."""

        exp: T.Tensor = T.Ops.exp(x - T.Reduce.max(x))
        self._saved: T.Tensor = T.Ops.log(exp / T.Reduce.sum(exp))
        return self._saved

    def backward(self, dF: T.Tensor) -> T.Tensor:
        """Perform a backpropagation."""

        raise NotImplementedError


## Adaptado de Nikhil Ketkar - Deep Learning with Python, A Hands-on Introduction
## Mostra o efeito da especializacao de um algoritmo ao incrementar o grau do
## polinomio utilizado no metodo de Minimos Quadrados
## Pega os primeiros 80 pontos pra treinar e os ultimos 20 pra testar

#Generate Toy Dataset
import pylab
import numpy
import matplotlib.pyplot as plt

degrees = [0, 1, 2, 3, 4, 5, 8, 10]
#degrees = []

numpy.random.seed (2)
x = numpy.linspace (-1,1,100)
signal = 2 + x + 2 * x * x
noise = numpy.random.normal (0, 0.1, 100)
y = signal + noise

plt.figure ()
plt.xticks (fontsize = 16)
plt.yticks (fontsize = 16)
plt.xticks (numpy.arange (0, 101, 10))
plt.yticks (numpy.arange (0, 5.1, 1))
plt.xlabel ('x', fontsize = 24)
plt.ylabel ('y', fontsize = 24)
#plt.ylabel ('$2x^2 + x + 2$', fontsize = 24)
plt.tight_layout ()

plt.plot (signal,'b');
plt.plot (y,'g', lw = 3, ls = ':')
plt.plot (noise, 'c')

x_train = x[0:80]
y_train = y[0:80]
plt.axvline (x = 60, linewidth = 2, color = 'black')
plt.legend (["Sinal sem ruido", "Sinal coletado", "Ruido gaussiano"], loc = 2, fontsize = 16)

#plt.show ()
plt.savefig ('ruido.png')

for degree in degrees:
  #pylab.figure ()
  degree += 1
  X_train = numpy.column_stack ([numpy.power (x_train,i) for i in range (0,degree)])
  model = numpy.dot (numpy.dot (numpy.linalg.inv (numpy.dot (X_train.transpose (),X_train)),X_train.transpose ()),y_train)
  predicted = numpy.dot (model, [numpy.power (x,i) for i in range (0,degree)])
  train_rmse1 = numpy.sqrt (numpy.sum (numpy.dot (y[0:80] - predicted[0:80], y_train - predicted[0:80])))
  test_rmse1 = numpy.sqrt (numpy.sum (numpy.dot (y[80:] - predicted[80:], y[80:] - predicted[80:])))

  plt.figure ()
  plt.xlabel ('x', fontsize = 24)
  plt.ylabel ('y', fontsize = 24)
  #plt.ylabel ('$2x^2 + x + 2$', fontsize = 24)
  plt.xticks (numpy.arange (-1, 1.2, 0.5))
  plt.yticks (numpy.arange (0, 5.1, 1))
  plt.xticks (fontsize = 16)
  plt.yticks (fontsize = 16)

  #my_dpi = 50
  #plt.figure (figsize= (800/my_dpi, 800/my_dpi), dpi = my_dpi)
  plt.tight_layout ()
  print ("Train RMSE (Degree = " + str (degree - 1) + ")", train_rmse1)
  print ("Test RMSE (Degree = " + str (degree - 1) + ")", test_rmse1)
  #print ("Test RMSE (Degree = 1)", test_rmse1)
  plt.plot (x,y,'g', lw = 3, ls = ':')
  plt.plot (x, predicted,'r')
  plt.axvline (x = 0.60, linewidth = 2, color = 'black')
  #plt.show ()
  plt.legend (["Sinal coletado", "Predito com grau " + str (degree - 1)], loc = 2, fontsize = 16)
  plt.savefig ('grau_' + str (degree - 1) + '.png')


from typing import Optional, Tuple

import numpy as np
import math
import matplotlib.pyplot as plt
from scipy.linalg import lu

def agh_superfast_matrix_multiply(a: np.matrix, b: np.matrix) -> np.matrix:
    """Perform totally ordinary multiplication of matrices.

    :param a: matrix with dimensions _ by _
    :param b: matrix with dimensions _ by _
    :return:  matrix with dimensions _ by _
    """

    (r1, c1) = a.shape
    (r2, c2) = b.shape
    if c1 != r2:
        raise ValueError('Dimensions do not match')
    res = np.asmatrix(np.zeros([r1, c2]))
    for i in range(0, r1):
        for j in range(0, c2):
            for k in range(0, c1):
                res[i, j] += a[i, k] * b[k, j]
    return res

def swapRows(a: np.matrix, i: int, j: int) -> np.matrix:
    r = np.matrix(a)
    r[i, :], r[j, :] = a[j, :], a[i, :]
    return r

def findMaxRow(a: np.matrix, pos: int) -> int:
    (n, _) = a.shape
    max_element = abs(a[pos, pos])
    max_row = pos

    for i in range(pos + 1, n):
        potential = abs(a[i, pos])
        if potential > max_element:
            max_element = potential
            max_row = i

    return max_row

def zero_column(a: np.matrix, pos: int, extended: np.matrix = None) -> np.matrix:
    (n, _) = a.shape
    res = np.matrix(a)

    for i in range(pos + 1, n):
        if res[pos, pos] == 0:
            maxRow = findMaxRow(res, pos)
            res = swapRows(res, pos, maxRow)
            if extended is not None:
                extended = swapRows(extended, pos, maxRow)
            if res[pos, pos] == 0:
                continue

        c = - res[i, pos] / res[pos, pos]
        for j in range(pos, n):
            res[i, j] += c * res[pos, j]

        if extended is not None:
            for j in range(extended.shape[1]):
                extended[i, j] += c * extended[pos, j]

    return res

def agh_superfast_gauss(a: np.matrix, pivot: bool = False, extended: np.matrix = None) -> np.matrix:
    (n1, n2) = a.shape
    if n1 != n2:
        raise ValueError('Only square matrices here')
    n = n1
    res = np.matrix(a, float)

    for i in range(n):
        if pivot:
            maxRow = findMaxRow(res, i)
            res = swapRows(res, i, maxRow)
            if extended is not None:
                extended = swapRows(extended, i, maxRow)
        res = zero_column(res, i, extended)

    return res

def agh_superfast_solve(a: np.matrix, b: np.matrix, pivot: bool = False) -> np.matrix:
    (n1, n2) = a.shape
    (n3, n4) = b.shape
    if n4 != 1 or n1 != n2 or n2 != n3:
        raise ValueError('Matrices dimensions do not match pattern (n, n), (n, 1)')
    n = n1
    y = np.matrix(b, dtype=float)
    g = agh_superfast_gauss(a, pivot, y)
    x = np.asmatrix(np.matrix(np.zeros(n)).transpose())

    for i in range(n - 1, -1, -1):
        x[i, 0] = y[i, 0] / g[i, i]
        for j in range(i - 1, -1, -1):
            y[j, 0] -= g[j, i] * x[i, 0]

    return x

def test_gauss():
    matrices = [
        np.matrix([
            [3, 2, 7, 11],
            [17, 3, 13, 5],
            [7, 23, 11, 17],
            [11, 7, 5, 2]
        ]),
        np.matrix([
            [0, 1, 2],
            [3, 0, 4],
            [5, 6, 7]
        ]),
        np.matrix([[0.0001, -5.0300, 5.8090, 7.8320],
                   [2.2660, 1.9950, 1.2120, 8.0080],
                   [8.8500, 5.6810, 4.5520, 1.3020],
                   [6.7750, -2.253, 2.9080, 3.9700]])
    ]

    for a in matrices:
        _, u = lu(a, permute_l=True)
        a1 = agh_superfast_gauss(a, True)
        print(list(map(np.linalg.det, [a, u, a1])))
        print(a1)
        print("Jest ok" if np.allclose(a1, u) else "Cos nie tak")
        print("-" * 100)

def pivot_matrix(a: np.matrix) -> np.matrix:
    r = np.asmatrix(np.zeros(a.shape))
    for i in range(r.shape[0]):
        maxRow = findMaxRow(a, i)
        r = swapRows(r, maxRow, i)
    return r

def so_lu(a: np.matrix) -> Optional[Tuple[np.matrix, np.matrix]]:
    (n1, n2) = a.shape
    if n1 != n2:
        raise ValueError('Only square matrices here')
    n = n1

    L = np.asmatrix(np.zeros((n, n)))
    U = np.matrix(a)
    np.fill_diagonal(L, 1)  # fill the diagonal of L with 1

    for i in range(n - 1):
        for j in range(i + 1, n):
            L[j, i] = U[j, i] / U[i, i]
            U[j, i] = 0
            for k in range(i + 1, n):
                U[j, k] = U[j, k] - L[j, i] * U[i, k]

    return (L, U)

def agh_superfast_lu(a: np.matrix) -> Optional[Tuple[np.matrix, np.matrix]]:
    (n1, n2) = a.shape
    if n1 != n2:
        raise ValueError('Only square matrices here')
    n = n1
    L = np.asmatrix(np.zeros((n, n)))
    U = np.asmatrix(np.zeros((n, n)))
    P = pivot_matrix(a)
    PA = np.dot(P, a)

    for j in range(n):
        # All diagonal entries of L are set to unity
        L[j, j] = 1.0

        # LaTeX: u_{ij} = a_{ij} - \sum_{k=1}^{i-1} u_{kj} l_{ik}
        for i in range(j + 1):
            s1 = sum(U[k, j] * L[i, k] for k in range(i))
            U[i, j] = P[i, j] - s1

        # LaTeX: l_{ij} = \frac{1}{u_{jj}} (a_{ij} - \sum_{k=1}^{j-1} u_{kj} l_{ik} )
        for i in range(j, n):
            s2 = sum(U[k, j] * L[i, k] for k in range(j))
            L[i, j] = (P[i, j] - s2) / U[j, j]

    return (L, U)

# test_gauss()

A = np.matrix([[0.0001, -5.0300, 5.8090, 7.8320],
               [2.2660, 1.9950, 1.2120, 8.0080],
               [8.8500, 5.6810, 4.5520, 1.3020],
               [6.7750, -2.253, 2.9080, 3.9700]])
b = np.asmatrix(np.matrix([9.5740, 7.2190, 5.7300, 6.2910]).transpose())

(l, u) = so_lu(A)
print(l)
print(u)
(l, u) = lu(A, permute_l=True)
print(l)
print(u)


import numpy as np
import matplotlib.pyplot as plt

k = 12
h = np.zeros(k)
h[0] = 1
h[1] = -0.5*h[0]
h[2] = -0.5*h[1] + 1

for n in range(3,k-1):
		h[n] = -0.5*h[n-1]

#subplots
plt.stem(range(0,k),h)
plt.title('Impulse Response Definition')
plt.xlabel('$n$')
plt.ylabel('$h(n)$')
plt.grid()# minor

plt.savefig('hndef.png')


import numpy as np
import scipy as sp
import scipy.optimize

def analytic_center(A,b,eps,x0,method='nelder-mead'):
    '''
    Input:
    - A   -- N x M
    - b   -- N
    - eps -- N
    - x0  -- M

    Output is a scipy OptimizeResult for

       max np.sum(eps*log(b-A@x))

    optimized using x0 as a starting point

    '''

    def func(x):
        if ((b-A@x)<=0).any():
            return np.inf
        return -np.sum(eps*np.log(b-A@x))

    def jac(x):
        return A.T@(eps/(b-A@x))

    if method=='nelder-mead':
        return sp.optimize.minimize(func,x0,method=method)
    else:
        return sp.optimize.minimize(func,x0,jac=jac,method=method)

def construct_reasonable_rectangle_problem(affines,epsilon=.1,maxsize=300):
    affines=np.require(affines).astype(float)
    n=affines.shape[0]
    assert affines.shape==(n,n)
    maxsize=np.require(maxsize).astype(float)
    if maxsize.shape==():
        maxsize=np.ones(n)*maxsize

    hopefully_feasible=np.ones(n)*5

    affines=np.sign(np.sum(affines,axis=1,keepdims=True)) * affines

    A=np.concatenate([
        -np.eye(n),  # -x <=0
        affines,     # A@x < 1
        np.eye(n),   # x < maxsize
    ],axis=0)
    b=np.concatenate([
        np.zeros(n),
        np.ones(n),
        maxsize
    ],axis=0)

    if not (A@hopefully_feasible<b).all():
        raise ValueError("affine transformation is too big, try another method")

    eps=np.r_[np.ones(n),np.ones(2*n)*epsilon]

    return A,b,eps,hopefully_feasible

def calc_reasonable_rectangles(affines,epsilon=.01,maxsize=300):
    '''
    Input
    - affines -- N
    - epsilon -- scalar
    - maxsize -- scalar or N

    Output
    - rectangle -- N, integer

    Finds a reasonably large value of rectangle so that

      (np.abs(affines@rectangle)<1).all()
      (rectangle<maxsize).all()

    We do this in two steps.  First, we get all the
    affines pointing in the same direction.

        affines=np.sign(np.sum(affines,axis=1,keepdims=True)) * affines

    and then solving the optimization problem

      max_rectangle
          np.sum(np.log(rectangle))
        + epsilon*np.sum(np.log(maxsize-rectangle))
        + epsilon*np.sum(np.log(b-affines@rectangle))
    '''

    A,b,eps,hopefully_feasible = construct_reasonable_rectangle_problem(
        affines,epsilon,maxsize
    )

    rez=analytic_center(A,b,eps,hopefully_feasible,method='nelder-mead')
    return np.floor(rez['x']).astype(int)