#%%
import numpy as np
import pandas as pd
import seaborn as sns
sns.set(style="darkgrid")
import matplotlib.pyplot as plt
import cvxpy as cp  #pip install cvxpy --upgrade
import yfinance as yf
#%% Multi-asset analysis
#Calculate returns of individual stocks
tickers=['AAPL','FB','AMZN','BAC','GE','MSFT','TSLA','NFLX']
df=pd.DataFrame()
for t in tickers:
    df[t]=yf.download(t, start='2010-01-01')['Adj Close']

df.head(5)
# %%
print(df.iloc[0])

(df/df.iloc[0]).plot(figsize=(16,5))
plt.title('Price change')
plt.xlabel('Date')
plt.ylabel('Percentage')
plt.show
# %%
returns=(df/df.shift(1))-1
returns= returns.dropna()
print(returns.tail(5))
daily_returns=returns.mean()*250
print(daily_returns)

plt.bar(tickers,list(daily_returns),align='center',alpha=0.5)
plt.ylabel('Value')
plt.title('Annual return')
plt.show()

# %%
# train test split 70-30
T_trn = round(len(returns)*.7)
ret_mat = np.ascontiguousarray(returns.iloc[1::,:])
ret_trn = ret_mat[:T_trn,]
ret_tst = ret_mat[T_trn:,]

mu_trn = np.mean(ret_trn, axis=0)
sigma_trn = np.cov(ret_trn.T)
print(mu_trn,sigma_trn)
# Equally weighted portfolio
w_EWP = np.ones(returns.shape[1])/returns.shape[1]
w_EWP
#%% Minimum variance portfolio:
# As investors are assumed risk-averse but still want to make a good profit
# we introduce the global minimum variance portfolio and the minimum variance portfolio with shortselling contraints

def GMVP(Sigma):
    ones = np.ones(Sigma.shape[0])
    Sigma_inv_1 = np.linalg.solve(Sigma, ones) # same as Sigma_inv @ ones
    w = Sigma_inv_1 / (np.sum(Sigma_inv_1))
    return w
w_GMVP = GMVP(sigma_trn)
print(w_GMVP)

# Minimum variance portfolio with short-selling
def MVP(mu, Sigma, cons ,w_EWP):
    w = cp.Variable(len(mu))
    variance = cp.quad_form(w, Sigma)
    expected_return = w @ mu
    if cons == "no short sell":
        constraint = [w @ mu >= w_EWP @ mu, w >= 0, cp.sum(w) == 1]
    if cons == "short sell":
        constraint = [w @ mu >= w_EWP @ mu,cp.sum(w) == 1]
    problem = cp.Problem(cp.Minimize(variance), constraint)
    problem.solve()
    return w.value
w_MVP_short = MVP(mu_trn, sigma_trn,"short sell",w_EWP)
print(w_MVP_short)
#%% Maximum sharpe ratio portfolio
# This portfolio show the optimized return/risks ratios
def MSR(mu, Sigma):
    ones = np.ones(Sigma.shape[0])
    Sigma_inv_1 = np.linalg.solve(Sigma, mu) # same as Sigma_inv @ ones
    w = Sigma_inv_1 / (np.sum(Sigma_inv_1  ))
    return w
w_MSR = MSR(mu_trn, sigma_trn)
print(w_MSR)

#%%
allocation = pd.DataFrame([w_GMVP,w_MVP_short,w_EWP,w_MSR],columns = tickers)
allocation.index = ['GMVP','MVP','EWP','MSR']
allocation.T.plot.bar(figsize = (15,5))
plt.xticks(rotation=0)
plt.show()

print(allocation)

#%%
def performance(returns,allocation):
    ret = []
    vol = []
    sharpe = []
    wealth = []
    for i in range(4):
        ret.append((np.array(returns) @ (allocation.iloc[i,:].T)).mean())
        vol.append(np.sqrt(np.dot(allocation.iloc[i,:],np.dot(returns.cov(),allocation.iloc[i,:].T))))
        sharpe.append(ret[i]/vol[i])
    performance = pd.DataFrame([ret,vol,sharpe],columns = allocation.index).T
    return performance

performance  = pd.concat([performance(returns.iloc[:T_trn,],allocation),
                         performance(returns.iloc[T_trn:,],allocation)], axis = 1)
performance.columns = ['avg excess return train','volatility train','sharpe ratio train',
                       'avg excess return test','volatility test','sharpe ratio test']
print(performance)

#%%

performance.T.plot.bar(figsize = (15,5))
plt.xticks(rotation=0)
plt.show()

#%%
wealth_geom_trn = []
wealth_geom_tst = []
for i in range(4):
    wealth_geom_trn.append(np.cumprod(np.array(returns.iloc[:T_trn,]) @ (allocation.iloc[i,:].T)+1))
    wealth_geom_tst.append(np.cumprod(np.array(returns.iloc[T_trn:,]) @ (allocation.iloc[i,:].T)+1))

# plots
fig, (ax1, ax2) = plt.subplots(1, 2,figsize=(15,5))
plt.figure(figsize=(10,15))
ax1.plot(np.array(wealth_geom_trn).T)
ax2.plot(np.array(wealth_geom_tst).T)
fig.suptitle('Portfolios performance (compounded)')
ax1.set_title("in-sample")
ax2.set_title("out-of-sample")
ax1.legend(allocation.index,loc = 2)
ax2.legend(allocation.index,loc = 2)
plt.show()

#%%

# zoom in on the in sample plot:

fig, (ax1, ax2) = plt.subplots(1, 2,figsize=(15,5))
plt.figure(figsize=(10,15))
ax1.plot(np.array(wealth_geom_trn).T)
ax2.plot(np.array(wealth_geom_tst).T)
fig.suptitle('Portfolios performance (compounded)')
ax1.set_title("in-sample")
ax2.set_title("out-of-sample")
ax1.legend(allocation.index,loc = 2)
ax2.legend(allocation.index,loc = 2)
ax1.set(ylim=(0,10))
plt.show()

#%%
'''
MSR:
As we can see that, the MSR allow short-selling the GE stocks,
which results in significant return in train set
Although it shows the best performance in test set, we can see that the
the performance dropped dramatically for MSR portfolio
This portfolio is good for risk seekers, not risk adverse investors

GMVP:
This portfolio shows consistency in both train and test dataset
As can be seen from the chart, GMVP show lowest volatility but also lowest returns
This portfolio is the safest bet for risk-adverse investors

MVP:
For this portfolio, we set the expected return to be equal to EWP portfolio.
Although we did include the short-selling option in building this portfolio,
the allocation doesn't have short position.
This portfolio shows higher return than GMVP still with a certain low volatility
In theory the MVP is risk-return optimal for risk-adverse investors.

EWP:
This portfolio is a naive way to build a portoflio.
It isn't belong to Mean-variance framework or Markowitz framework and doesn't optimize any criteria.
However, sometimes this portfolio yields good results, as in this case with this particular dataset of growing companies.

'''

# %%


# Gerhard van der Linde - 19 February 2018
# G00364778
# Excercise #4
# Euler Problem # 5
#
# Write a Python program using for and range to calculate the smallest
# positive number that is evenly divisible by all of the numbers from 1 to 20.

# ANSWER: Smallest value devisable by 1 to 20 is 232792560. (calculated in 46.820 s)

# Optimisation steps were required to arrive at a workable solution.
# The factors was reduce to 11,12,13,14,15,16,17,18,19,20 since the
# smaller values were all factors of the bigger ones in any case. Also
# by incrementing by twenty every cycle reduced a lot of unnessesary attemps
# that would have failed in a test in any case, so this resulted in a 47 second
# solution that was more acceptable.

'''
import time # to calculate the execution time

foundanswer=False # set the starting condition of the value to false
c=20 # Start the test value at twenty
maxval=20
#devisors=[2,3,5,7,11,13,17,19]
devisors=[11,13,14,15,16,17,18,19]
#devisors=[11,12,13,14,15,16,17,18,19]

tstart=time.time()
while foundanswer == False:
    c+=20 # increment the test value by twenty every cycle the answer is not found
    testval=0 # reset the testval for the next test cyle
    #for x in range(11,maxval+1):
    for x in range(len(devisors)):
        #print(c,devisors[x],c%devisors[x])
        testval+=c%devisors[x] # sum all the test values and test for zero
    if testval==0:
        foundanswer=True
        tend=time.time()
        print('Smallest value devisable by 1 to %i is %i. (calculated in %0.3f s)' %(maxval, c,tend-tstart))
'''
# script output sample
# Smallest value devisable by 1 to 20 is 232792560. (calculated in 19.231 s)

# A lengthy group discussion on the topic showed some smarter mathmatical approaches is possible.

# n  Pfact   Prime   Prime Multiple  N
# 1  -
# 2  2       y                       2
# 3  3       y                       3
# 4  2x2             y               2
# 5  5       y                       5
# 6  2x3
# 7  7       y                       7
# 8  2x2x2           y               2
# 9  3x3             y               3
# 10 2x5
# 11 11      y                      11
# 12 2x2x3
# 13 13      y                      13
# 14 2x7
# 15 3x5
# 16 2x2x2x2         y               2
# 17 17      y                      17
# 18 2x3x3
# 19 19      y                      19
# 20 2x2x5

# So in order to calculate the number the table above aims to provide a mathmatical guid
# for developing an improved algorythm to solve the question.

# The answer is a simple multiplcation and includes all the prime numbers in the range plus
# all the occurances of prime facors in the range, so lines 4, 8, 9 and 16, so the final formula
#  for the sample range(20) is: N = 2 * 3 * 2 * 5 * 7 * 2 * 3 * 11 * 13 * 2 * 17 * 19 = 232792560

# Constructing an algorythm from this information then:
# 1. For the range of values(20 for example) list the prime numbers
# 2. generate a sorted list of prime factors in same range to test against
# 3. create a list of primes and and prime factors in the range
# 4. multiply the list to get the final answer

import numpy as np # to calculate product of list N
import time # to calculate the execution time
tstart=time.time()
TestRange = 27
max_r=int(TestRange**.5)# experimental - not sure about this
#max_r=TestRange
print('\n                     Test range: 1 - ',TestRange)
#print('max_r:',max_r) # determine the range of primes to explore for factors
#l=[i for i in range(2,TestRange+1)] # generate a list of numbers to test
p=[] # prime numbers
pf=[] # prime factors
def testprimes(x):
    if x < 2:
        return False
    for n in range(2,x):
        if x % n == 0:
            return False
    return True

for i in range(1,TestRange+1):
    test=testprimes(i)
    if test==True:
        p.append(i) # 1. generate a list of primes numbers in the range
print('prime numbers in the test range:',p)

for i in p:
    if i <= max_r: # only check for number possible smaller than testrange
        #print('\t',i)
        for j in range(2,max_r+1):
            if i**j<TestRange:
                pf.append(i) # 2. Generate the list of prme factors in the range
                #print('\t',i,'^', j,'=', i**j)
print('prime factors in the test range:',pf)
N=pf+p # 3. Combine the two lists
print('   product of the two lists (N): {} = {:,d}'.format(N,np.prod(N)))# calculate the product ofthe list
tend=time.time()
print('                Processing Time: {:f} ms\n'.format((tend-tstart)*100))

# Sample output generated during testing:

#                      Test range: 1 -  20
# prime numbers in the test range: [2, 3, 5, 7, 11, 13, 17, 19]
# prime factors in the test range: [2, 2, 2, 3]
#    product of the two lists (N): [2, 2, 2, 3, 2, 3, 5, 7, 11, 13, 17, 19] = 232,792,560
#                 Processing Time: 0.100327 ms


'''
This is a 2-dimensional example of the rejection method for
better monte carlo estimation. It works as the 1-D case (see 1-D_ARMC.py).
We write this code, in order for the user to understand how the
N-dimensional case is implimented.

As you can see, the 2-D case is almost identical to the 1-D.
The only difference is in #1-4
'''
import numpy as np

def func(x,y):#1
    return y*np.sin(10*(x+1))

N_tot=5000

sample=np.random.rand(N_tot,2)#2

F_sample=[func(i,j) for i,j in sample]#3
abs_F_sample=[ np.abs(func(i,j)) for i,j in sample]#4
_max=np.max(abs_F_sample)

I=0
N_acc=0
for i in np.arange(N_tot):
    _tmp_rand=np.random.rand()
    wi=F_sample[i]
    abs_wi=abs_F_sample[i]
    if _tmp_rand<=abs_wi/_max:
        N_acc+=1.
        I+=wi/np.abs(wi)

norm=N_acc/float(N_tot)*_max
print  (I/(N_acc/norm))


import numpy as np

def imgquadfit(img,imgunc=None,doalpha=False):

    '''
    Fits a quadratic surface to an image

    Input Parameters
    ----------------
    img : array_like
        The image to to fit.

    imgunc : array_like, optional
        The uncertainty image.

    doalpha : {False, True}, optional
        If true, then A**T * A is computed directly instead of A.

    Returns
    --------
    numpy.ndarray
        The coefficients of the fit

    Notes
    -----
    Standard least squares fitting (e.g. Numerical Recipes).
    The program fits the following function:

    z = c[0] + c[1]*x + c[2]*y + c[3]x^2 + c[4]y^2 + c[5]*x*y

    where x is the column number and y is the row number.

    By default, the design matrix A is constructed.  The alpha
    (A^T ## A) and beta (A^T ## b) arrays of the normal equations
    are then constructed and then passed to np.linalg.solve.

    If the dataset to be fit is large and/or the number of coefficients
    is large, the design matrix A can be large (ncoeff,ndat) and may
    take too much memory and/or too much time to construct.  In this case,
    set `doalpha` to True and the alpha and beta arrays are constructed
    directly.  There is a trade off since constructing the alpha and beta
    arrays directly takes longer for smaller surfaces.

    Unlike polyfit1d and polyfit2d, this function ONLY computes the
    coefficients.

    Examples
    --------
    later?

    Modification History
    --------------------
    2022-06-17 - Written by M. Cushing, University of Toledo.
    Based on Spextool IDL program mc_quadfit.pro

    '''

# Do basic things and create basic things

    nrows, ncols = img.shape
    if imgunc is None: imgunc = np.full((nrows,ncols),1.0)

# Create coordinate images

    ximg = np.tile(np.arange(ncols,dtype=float),(nrows,1))
    yimg = np.tile(np.reshape(np.arange(nrows,dtype=float),\
                   (nrows,1)),(1,ncols))

# Ravel the images

    x = np.ravel(ximg)
    y = np.ravel(yimg)
    z = np.ravel(img)
    zunc = np.ravel(imgunc)

# Get rid of NaNs

    znonan = ~np.isnan(z)
    nnan = np.size(znonan)-np.sum(znonan)
    xx = x[znonan]
    yy = y[znonan]
    zz = z[znonan]
    zzunc = zunc[znonan]

# Set up to construct the equations

    ndat = len(xx)
    ncoeffs = 6
    exp = np.array([[0,0],[1,0],[0,1],[2,0],[0,2],[1,1]])

    b = zz/zzunc

# Now start the linear algebra construction

    if doalpha is True:

        alpha = np.empty((ncoeffs,ncoeffs))
        beta = np.empty(ncoeffs)

        for i in range(ncoeffs):

            for j in range(i,ncoeffs):

                at = (xx**exp[i,0] * yy**exp[i,1])/zzunc
                a  = (xx**exp[j,0] * yy**exp[j,1])/zzunc

                val = np.sum(at*a)

                alpha[j,i] = val
                alpha[i,j] = val
                beta[i] = np.sum(at*b)

    elif doalpha is False:

        AT = np.empty((ncoeffs,ndat))
        for i in range(ncoeffs):

            AT[i,:] = xx**exp[i,0]*yy**exp[i,1]/zzunc

        alpha = np.matmul(AT,np.transpose(AT))
        beta = np.matmul(AT,b)

# Solve things

    coeffs = np.linalg.solve(alpha,beta)

    return coeffs


# SI infectous model
# Author: José Alfredo de León
# 09.10.2020

# Program 03b: Nonlinear system, phase portrait with vector plot.
# See Figure 3.12.
import matplotlib.pyplot as plt
import numpy as np
from scipy.integrate import odeint
import pylab as pl

beta = 1
# The 2-dimensional nonlinear system.
def dx_dt(x, t):
    return [-beta*x[0]*x[1], beta*x[0]*x[1]]
# Trajectories in forward time.
ts = np.linspace(0, 10, 500)
ic = np.linspace(-5, 5, 10)
for r in ic:
    for s in ic:
        x0 = [r, s]
        xs = odeint(dx_dt, x0, ts)
        plt.plot(xs[:,0], xs[:,1], "r-")

# Trajectories in backward time.
ts = np.linspace(0, -10, 500)
ic = np.linspace(-5, 5, 10)
for r in ic:
    for s in ic:
        x0 = [r, s]
        xs = odeint(dx_dt, x0, ts)
        plt.plot(xs[:,0], xs[:,1], "r-")

# Label the axes and set fontsizes.
plt.xlabel("S", fontsize=12)
plt.ylabel("I", fontsize=12)
plt.tick_params(labelsize=10)
plt.xlim(-5, 5)
plt.ylim(-5, 5);

# Plot the vectorfield.
X, Y = np.mgrid[-5:5:12j, -5:5:12j]
u = -beta*X*Y
v = beta*X*Y
pl.quiver(X, Y, u, v, color = 'b')
plt.savefig('SI-infeccioso.pdf')


"""
This module provides functions for generating regularizer matrices
for regularized least squares.
"""
from typing import List
import numpy as np

def get_regularizer_matrix(n_features: int,
                           ridge: float = 0.0,
                           curvature: float = 1.0
                           ) -> np.ndarray:
    """
    Generates additive regularization matrix for linear regression
        using curvature penalty and/or L2 (ridge) penalty.
        The curvature penalty here applies to adjacent coefficients
        in one dimension.

    Args:
        n_features (int): number of features in linear regression problem.
        curvature (float): curvature regularization strength (multiplicative).
            Rule-of-thumb may be similar to ridge regression,
            e.g. optimized through cross-validation between 1e-3 to 1e3
        ridge (float): L2 regularization strength (multiplicative)
            for ridge regression.

    Returns:
        matrix (numpy.ndarray): square matrix of size (n_features x n_features)
            with ridge and curvature penalty a.k.a. fused ridge regression.
    """
    reg_diag = np.eye(n_features) * 2 * curvature
    reg_minus = np.eye(n_features, k=-1) * -curvature
    reg_plus = np.eye(n_features, k=1) * -curvature
    matrix = reg_diag + reg_minus + reg_plus
    matrix[0, 0] /= 2
    matrix[n_features - 1, n_features - 1] /= 2
    if ridge > 0:
        matrix = matrix + np.eye(n_features) * ridge
    return matrix

def combine_regularizer_matrices(matrices: List) -> np.ndarray:
    """
    Combine square penalty matrices.
        Example:
            [X---      [Y-      [Z--        [X--------
             -X--       -Y]      -Z-         -X-------
             --X-                --Z]        --X------
             ---X]                           ---X-----
            (4x4)      (2x2)    (3x3)   ->   ----Y----  (9x9)
                                             -----Y---
                                             ------Z--
                                             -------Z-
                                             --------Z]
    Args:
        matrices (list): square matrices, e.g. for separate optimization
            objectives like A-A, A-B, B-B interactions.

    Returns:
        full_matrix (numpy.ndarray): square penalty matrix equal in length
            to the sum of constituent lengths.
    """
    sizes = [len(m) for m in matrices]
    n_features = int(np.sum(sizes))
    full_matrix = np.zeros((n_features, n_features))
    origins = np.insert(np.cumsum(sizes), 0, 0)
    for i, matrix in enumerate(matrices):
        start = origins[i]
        end = origins[i + 1]
        full_matrix[start:end, start:end] = matrix
    return full_matrix

def get_penalty_matrix_2D(L: int,
                          M: int,
                          ridge: float = 0.0,
                          curvature: float = 1.0
                          ) -> np.ndarray:
    """
    Generates additive regularization matrix for linear regression
        using curvature penalty and/or L2 (ridge) penalty.
        Curvature penalty here applies to coefficients that are spatially
        related in two dimensions that may not be adjacent after flattening
        for linear least-squares.

    Args:
        L (int): length of coefficient matrix before flattening.
        M (int): width of coefficient matrix before flattening.
        ridge (float): L2 (ridge) regularization strength.
        curvature (float): Local curvature regularization strength.

    Returns:
        matrix_2d (numpy.ndarray): square penalty matrix for linear
            least-squares of shape (L*M , L*M).
    """
    matrix_2d = np.zeros((L * M, L, M))
    idx = 0
    for i in range(L):
        for j in range(M):
            if any([i == 0, i == L - 1, j == 0, j == M - 1]):
                matrix_2d[idx, i, j] = 1
            else:
                matrix_2d[idx, i, j] = 2

            if i > 0:
                matrix_2d[idx, i - 1, j] = -1
            if i + 1 < L:
                matrix_2d[idx, i + 1, j] = -1
            if j > 0:
                matrix_2d[idx, i, j - 1] = -1
            if j + 1 < M:
                matrix_2d[idx, i, j + 1] = -1
            idx += 1
    matrix_2d = matrix_2d.reshape(L * M, L * M) * curvature
    if ridge > 0:
        matrix_2d = matrix_2d + np.eye(L * M) * ridge
    return matrix_2d

def get_penalty_matrix_3D(L: int,
                          M: int,
                          N: int,
                          ridge: float = 0.0,
                          curvature: float = 1.0,
                          periodic=(False, False, True),
                          ) -> np.ndarray:
    """
    Generates additive regularization matrix for linear regression
        using curvature penalty and/or L2 (ridge) penalty.
        Curvature penalty here applies to coefficients that are spatially
        related in three dimensions that may not be adjacent after flattening
        for linear least-squares

    Args:
        L (int): length of coefficient matrix before flattening.
        M (int): width of coefficient matrix before flattening.
        N (int): depth of coefficient matrix before flattening.
        ridge (float): L2 (ridge) regularization strength.
        curvature (float): Local curvature regularization strength.
        periodic (list): periodicity in regularization for three dimensions.
            Default (False, False, True) for periodicity in angular space.

    Returns:
        matrix_3d (numpy.ndarray): square penalty matrix for linear
            least-squares of shape (L*M*N , L*M*N).
    """
    matrix_3d = np.zeros((L * M * N, L, M, N))
    idx = 0
    i_pbc, j_pbc, k_pbc = periodic

    for i in range(L):
        for j in range(M):
            for k in range(N):
                center_value = 2
                # i dimension
                if i_pbc:
                    matrix_3d[idx, (i - 1) % L, j, k] = -1
                    matrix_3d[idx, (i + 1) % L, j, k] = -1
                else:
                    if i > 0:  # lower bound
                        matrix_3d[idx, i - 1, j, k] = -1
                    else:
                        center_value = 1
                    if i + 1 < L:  # upper bound
                        matrix_3d[idx, i + 1, j, k] = -1
                    else:
                        center_value = 1
                # j dimension
                if j_pbc:
                    matrix_3d[idx, i, (j - 1) % M, k] = -1
                    matrix_3d[idx, i, (j + 1) % M, k] = -1
                else:
                    if j > 0:  # lower bound
                        matrix_3d[idx, i, j - 1, k] = -1
                    else:
                        center_value = 1
                    if j + 1 < M:
                        matrix_3d[idx, i, j + 1, k] = -1
                    else:
                        center_value = 1
                # k dimension
                if k_pbc:
                    matrix_3d[idx, i, j, (k - 1) % N] = -1
                    matrix_3d[idx, i, j, (k + 1) % N] = -1
                else:
                    if k > 0:  # lower bound
                        matrix_3d[idx, i, j, k - 1] = -1
                    else:
                        center_value = 1
                    if k + 1 < N:  # upper bound
                        matrix_3d[idx, i, j, k + 1] = -1
                    else:
                        center_value = 1

                matrix_3d[idx, i, j, k] = center_value

                idx += 1
    matrix_3d = matrix_3d.reshape(L * M * N, L * M * N) * curvature
    if ridge > 0:
        matrix_3d = matrix_3d + np.eye(L * M * N) * ridge
    return matrix_3d


import numpy as np

#     |a|
#     |b|
# U = |c|
#     |d|
#     |e|
#     |f|
#
# MU = C
M = np.array([
    [-4.,  1.,  1.,  0.,  0.,  0.],
    [ 1., -4.,  0.,  1.,  0.,  0.],
    [ 1.,  0., -4.,  1.,  1.,  0.],
    [ 0.,  1.,  1., -4.,  0.,  1.],
    [ 0.,  0.,  2.,  0., -4.,  1.],
    [ 0.,  0.,  0.,  2.,  1., -4.],
])
C = np.array([
       2.,
      -4.,
       5.,
     -28.,
      -2.,
    -116.,
])

if not np.all(M.diagonal() == M[0,0]):
    print('행렬의 대각성분 값이 모두 같아야합니다.')
    exit(1)

# If M = X + kI:
#     MU = C
#  => XU + kU = C
#  => U = -1/k*XU + 1/k*C
#
# U = AU + B
#     where A = -1/k * X,
#           B =  1/k * C
k = M[0,0]

X = M.copy()
np.fill_diagonal(X, 0)

A = -1/k * X
B =  1/k * C

# Initial values
U = np.array([0., 0., 0., 0., 0., 0.])
print(f'''\
U = V = [u_11  u_12  u_21  u_22  u_31  u_32]

U = {U}''')

for i in range(5):
    U = A@U + B
    letter = 'V' if i % 2 == 0 else 'U'
    print(f'{letter} = {U}')

print(f'''
Result after 5 steps:
  u_11 = {U[0]}
  u_12 = {U[1]}
  u_21 = {U[2]}
  u_22 = {U[3]}
''')

for i in range(5, 20):
    U = A@U + B
    letter = 'V' if i % 2 == 0 else 'U'
    print(f'{letter} = {U}')

print(f'''
Result after 20 steps:
  u_11 = {U[0]}
  u_12 = {U[1]}
  u_21 = {U[2]}
  u_22 = {U[3]}
''')


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

def get_sphere(n):
    s = [(2 * np.pi) * random() for i in range(n)]
    t = [np.pi * np.random.random() for i in range(n)]
    x = np.cos(s) * np.sin(t)
    y = np.sin(s) * np.sin(t)
    z = np.cos(t)
    return x, y, z

x, y, z = get_sphere(300)
S = np.row_stack([x, y, z])

A1 = np.random.rand(3, 3)
A2 = np.random.rand(3, 3)
A3 = np.random.rand(3, 3)

ellipsoid1 = A1 @ S
ellipsoid2 = A2 @ S
ellipsoid3 = A3 @ S

U1, sigma1, V1 = svd(A1)
U2, sigma2, V2 = svd(A2)
U3, sigma3, V3 = svd(A3)

x_start, y_start, z_start = [[0] * 3] * 3
x_end = [0] * 3
y_end = [0] * 3
z_end = [0] * 3

for i, s1 in enumerate(np.diag(sigma1)):
    a = np.dot(U1, s1)
    x_end[i] = a[0]
    y_end[i] = a[1]
    z_end[i] = a[2]
quiver1 = [x_start, y_start, z_start, x_end, y_end, z_end]

# plot 1

fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_aspect('equal')
ax.scatter(x, y, z)
ax.scatter(ellipsoid1[0], ellipsoid1[1], ellipsoid1[2], color='red')
ax.quiver(x_start, y_start, z_start, x_end, y_end, z_end, color='black')
ax.set_xlim(-1.5, 1.5)
ax.set_ylim(-1.5, 1.5)
ax.set_zlim(-1.5, 1.5)
plt.show()

for i, s2 in enumerate(np.diag(sigma2)):
    a = np.dot(U2, s2)
    x_end[i] = a[0]
    y_end[i] = a[1]
    z_end[i] = a[2]
quiver2 = [x_start, y_start, z_start, x_end, y_end, z_end]

# plot 2

fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_aspect('equal')
ax.scatter(x, y, z)
ax.scatter(ellipsoid2[0], ellipsoid2[1], ellipsoid2[2], color='orange')
ax.quiver(x_start, y_start, z_start, x_end, y_end, z_end, color='black')
ax.set_xlim(-1.5, 1.5)
ax.set_ylim(-1.5, 1.5)
ax.set_zlim(-1.5, 1.5)
plt.show()

# plot 3

for i, s3 in enumerate(np.diag(sigma3)):
    a = np.dot(U3, s3)
    x_end[i] = a[0]
    y_end[i] = a[1]
    z_end[i] = a[2]
quiver3 = [x_start, y_start, z_start, x_end, y_end, z_end]

fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_aspect('equal')
ax.scatter(x, y, z)
ax.scatter(ellipsoid3[0], ellipsoid3[1], ellipsoid3[2], color='yellow')
ax.quiver(x_start, y_start, z_start, x_end, y_end, z_end, color='black')
ax.set_xlim(-1.5, 1.5)
ax.set_ylim(-1.5, 1.5)
ax.set_zlim(-1.5, 1.5)
plt.show()

# ellipsoid with max/min singular value > 100

A = np.random.rand(3, 3)
U, sigma, V = svd(A)

while sigma[0] / sigma[2] < 100:
    A = np.random.rand(3, 3)
    U, sigma, V = svd(A)

print(A)

ellipsoid = A @ S

fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_title("Max/min singular value > 100")
ax.set_aspect('equal')
ax.scatter(x, y, z)
ax.scatter(ellipsoid[0], ellipsoid[1], ellipsoid[2], color='green')
ax.set_xlim(-1.5, 1.5)
ax.set_ylim(-1.5, 1.5)
ax.set_zlim(-1.5, 1.5)
plt.show()

# plot S V^T, S sigma V^T, S U sigma V^T

U, sigma, V = svd(A1)
ellipsoid = S.transpose() @ V
ellipsoid = ellipsoid.transpose()

fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_title("S * V^T")
ax.set_aspect('equal')
ax.scatter(x, y, z)
ax.scatter(ellipsoid[0], ellipsoid[1], ellipsoid[2], color='red')
ax.set_xlim(-1.5, 1.5)
ax.set_ylim(-1.5, 1.5)
ax.set_zlim(-1.5, 1.5)
plt.show()

ellipsoid = S.transpose() * sigma @ V
ellipsoid = ellipsoid.transpose()

fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_title("S * sigma * V^T")
ax.set_aspect('equal')
ax.scatter(x, y, z)
ax.scatter(ellipsoid[0], ellipsoid[1], ellipsoid[2], color='orange')
ax.set_xlim(-1.5, 1.5)
ax.set_ylim(-1.5, 1.5)
ax.set_zlim(-1.5, 1.5)
plt.show()

ellipsoid = S.transpose() @ U * sigma @ V
ellipsoid = ellipsoid.transpose()

fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_title("S * U * sigma * V^T")
ax.set_aspect('equal')
ax.scatter(x, y, z)
ax.scatter(ellipsoid[0], ellipsoid[1], ellipsoid[2], color='yellow')
ax.set_xlim(-1.5, 1.5)
ax.set_ylim(-1.5, 1.5)
ax.set_zlim(-1.5, 1.5)
plt.show()


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

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

def relu(x):
    return max(0, x)

def gelu(x):
    return 0.5 * x * (1 + np.tanh(np.sqrt(2/np.pi)*(x + 0.044715*x**3)))

x_axis = np.arange(-3, 3, 0.1)
tan_h = [np.tanh(x) for x in x_axis]
sig = [sigmoid(x) for x in x_axis]
rel = [relu(x) for x in x_axis]
gel = [gelu(x) for x in x_axis]

names = ['tanh', 'Sigmoid', 'ReLU', 'GELU']

fig, ax = plt.subplots()
ax.plot(x_axis, tan_h, alpha=0.8)
ax.plot(x_axis, sig, alpha=0.8)
ax.plot(x_axis, rel, alpha=0.8)
ax.plot(x_axis, gel, alpha=0.8)

ax.set(title='Activation Functions')

plt.legend(names, loc='upper left', title='Activation Function')

fig.savefig("plots/activation_functions.png")
plt.show()


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

mpl.rcParams['axes.labelsize'] = 'x-large'
mpl.rcParams['legend.fontsize'] = 'x-large'
mpl.rcParams['xtick.labelsize'] = 'x-large'
mpl.rcParams['ytick.labelsize'] = 'x-large'
output_dir = './'

def T_k(k, x):
    return np.cos(k*np.arccos(x))

x = np.linspace(-1, 1, 100)

for k in range(6):
    plt.plot(x, T_k(k, x), label=f'T_{k}(x)', lw = 2.0)

plt.grid(True)
plt.xlabel('$x$')
plt.ylabel('$T_k(x)$')
plt.legend()
# plt.savefig('cheby.eps')

N = 20
k = np.arange(N+1)
xk = np.cos(k*np.pi/N)

plt.figure()
plt.plot(xk, np.zeros_like(xk), marker='x',lw=0.0)
plt.grid(True)
plt.xlabel('$x$')
plt.savefig('cgl_nodes.eps')
plt.show()


import numpy as np
from scipy.signal import convolve

def composite(y, dx=1, order=3):
    """
    Integrate y(x) for regularly spaced x.

    Parameters
    ----------
    y : array_like
        One-dimensional array to integrate.
    dx : float, optional
        Spacing between elements of `y`.
    order : int, optional
        Order of the interpolating polynomials. Default is 3, and other valid
        values are 1 (trapz), 2 (simps), 4, 5, 6 and 7.

    Returns
    -------
    composite : float
        Definite integral as approximated by the interpolating polynomials.

    See Also
    --------
    scipy.integrate.trapezoid, scipy.integrate.simpson
    gradiompy.integrate.cumulative_composite

	Note
	----
	The result is exact if `y` is sampled from a polynomial function of order
	`order` or less.

    Example
    -------
    >>> import numpy as np
    >>> from scipy import integrate as sp_integrate
    >>> from gradiompy import integrate as gp_integrate

    >>> x = np.linspace(-3,3,num=15)
    >>> dx = x[1]-x[0]
    >>> y = 8*x + 3*x**2 + x**3 - 2*x**5 + x**6 + 1/5*x**7
    >>> y_int = 4*x**2 + x**3 + 1/4*x**4 - 1/3*x**6 + 1/7*x**7 + 1/40*x**8
    >>> y_int = y_int[-1]-y_int[0]
    >>> y_int_trapz = sp_integrate.trapezoid(y,dx=dx)
    >>> y_int_simps = sp_integrate.simpson(y,dx=dx)
    >>> print('Integration error:')
    >>> print('  scipy.integrate')
    >>> print('    trapz    = %9.6f' % abs(y_int_trapz-y_int))
    >>> print('    simpson  = %9.6f' % abs(y_int_simps-y_int))
    >>> print('  gradiompy.integrate')
    >>> for order in range(1,8,2):
    >>>     y_int_composite = gp_integrate.composite(y,dx,order=order)
    >>>     print('    order %i  = %9.6f' % (order,abs(y_int_composite-y_int)))

    """
    # format inputs
    y = np.asarray(y)
    order = max(1,min(7,order))

    # integration coefficients
    coef_dic = {
        1: np.array([-1])/2.,
        2: np.array([-15,4,-1])/24.,
        3: np.array([-16,7,-4,1])/24.,
        4: np.array([-965,462,-336,146,-27])/1440.,
        5: np.array([-981,542,-496,306,-107,16])/1440.,
        6: np.array([-84161,55688,-66109,57024,-31523,9976,-1375])/120960.,
        7: np.array([-85376,64193,-91624,99549,-74048,35491,-9880,1215])/120960.}
    c_boundary = coef_dic[order] * dx

    # cumpute the integral
    m = len(c_boundary)
    z = np.sum(y) * dx
    dz_ini = c_boundary.dot(y[:+m])
    dz_end = c_boundary.dot(np.flip(y[-m:]))
    return z + dz_ini + dz_end

def cumulative_composite(y, dx=1, initial=[], order=3):
    """
    Cumulatively integrate y(x) for regularly spaced x.

    Parameters
    ----------
    y : array_like
        One-dimensional array to integrate.
    dx : float, optional
        Spacing between elements of `y`.
    initial : scalar, optional
        If given, insert this value at the beginning of the returned result.
        Typically this value should be 0. Default is [], which means no
        value at ``x[0]`` is returned and `res` has one element less than `y`
        along the axis of integration.
    order : int, optional
        Order of the interpolating polynomials. Default is 3, and other valid
        values are 1 (cumtrapz), 2 (cumsimps), 4, 5, 6 and 7. Odd values
        should be preferred over even ones.

    Returns
    -------
    cumulative_composite : ndarray
        The result of cumulative integration of `y`. If `initial` is empty or
        zero, the last element of this list is equal to the definite integral
        of y(x).

    See Also
    --------
    numpy.cumsum, numpy.convolve
    scipy.integrate.cumulative_trapezoid
    gradiompy.integrate.composite

    Note
	----
	The result is exact if `y` is sampled from a polynomial function of order
	`order` or less.

    Example
    -------
    >>> import numpy as np
    >>> from scipy import integrate as sp_integrate
    >>> from gradiompy import integrate as gp_integrate

    >>> x = np.linspace(-3,3,num=15)
    >>> dx = x[1]-x[0]
    >>> y = 8*x + 3*x**2 + x**3 - 2*x**5 + x**6 + 1/5*x**7
    >>> y_int = 4*x**2 + x**3 + 1/4*x**4 - 1/3*x**6 + 1/7*x**7 + 1/40*x**8
    >>> y_int_trapz = y_int [0] + sp_integrate.cumulative_trapezoid(y,dx=dx,initial=0)
    >>> print('Integration error:')
    >>> print('  scipy.integrate')
    >>> print('    cumtrapz = %9.6f' % np.linalg.norm(y_int_trapz-y_int))
    >>> print('  gradiompy.integrate')
    >>> for order in range(1,8,2):
    >>>     y_int_composite = gp_integrate.cumulative_composite(y,dx,order=order,initial=y_int[0])
    >>>     print('    order %i  = %9.6f' % (order,np.linalg.norm(y_int_composite-y_int)))

    """
    # format inputs
    y = np.asarray(y)
    if np.isscalar(initial): initial = [initial]
    order = max(1,min(7,order))

    # integration coefficients
    coef_dic = {
        1: (np.array([[]]),
            np.array([1,1])/2.),
        2: (np.array([[5,8,-1]])/12.,
            np.array([-1,13,13,-1])/24.),
        3: (np.array([[9,19,-5,1]])/24.,
            np.array([-1,13,13,-1])/24.),
        4: (np.array([[251,646,-264,106,-19],
                      [-19,346,456,-74,11]])/720.,
            np.array([11,-93,802,802,-93,11])/1440.),
        5: (np.array([[475,1427,-798,482,-173,27],
                      [-27,637,1022,-258,77,-11]])/1440.,
            np.array([11,-93,802,802,-93,11])/1440.),
        6: (np.array([[19087,65112,-46461,37504,-20211,6312,-863],
                      [-863,25128,46989,-16256,7299,-2088,271],
                      [271,-2760,30819,37504,-6771,1608,-191]])/60480.,
            np.array([-191,1879,-9531,68323,68323,-9531,1879,-191])/120960.),
        7: (np.array([[36799,139849,-121797,123133,-88547,41499,-11351,1375],
                      [-1375,47799,101349,-44797,26883,-11547,2999,-351],
                      [351,-4183,57627,81693,-20227,7227,-1719,191]])/120960.,
            np.array([-191,1879,-9531,68323,68323,-9531,1879,-191])/120960.)}
    coef = coef_dic[order]
    c_boundary = coef[0] * dx
    c_convolve = coef[1] * dx

    # compute the list to be accumulated
    m = c_boundary.shape[1]
    if m > 0:
        dy_ini = c_boundary.dot(y[:m])
        dy_end = np.flip(c_boundary.dot(np.flip(y[-m:])))
    else:
        dy_ini = []
        dy_end = []
    dy_mid = convolve(y,c_convolve,mode='valid',method='direct')
    dy = np.concatenate((initial,dy_ini,dy_mid,dy_end))

    # return the cumulative integral
    return np.cumsum(dy)

def trapezoid(y, dx=1):
    return composite(y, dx=dx, order=1)

def simpson(y, dx=1):
    return composite(y, dx=dx, order=3)

def cumulative_trapezoid(y, dx=1, initial=[]):
    return cumulative_composite(y, dx=dx, initial=initial, order=1)

def cumulative_simpson(y, dx=1, initial=[]):
    return cumulative_composite(y, dx=dx, initial=initial, order=3)


import numpy as np
import matplotlib.pyplot as plt
from math import sin, cos, exp, sqrt, log

# Graph parameters
plt.rcParams["xtick.labelsize"] = 14
plt.rcParams["ytick.labelsize"] = 14
plt.rcParams["axes.labelsize"] = 16

def main():

    # Constants
    a = 0.0  # Start the interval
    b = 10.0  # Finish the interval
    xa = 0.0  # Initial condition, x(a)
    exact_N = int(1e3)  # Number of points for the exact solution
    exact_h = (b - a) / exact_N

    c = 1.0  # Constant for the function

    # Exact solution
    exact_x = []
    time = np.arange(a, b, exact_h)
    for t in time:
        exact_x.append(exact_sol(t, a, xa, c))

    # Euler and Runge-kutta 2 solution
    N_list = []
    for i in range(3, 11):
        N_list.append(2**i)

    meanerror_rk2 = []
    meanerror_euler = []

    for N in N_list:  # Number of steps for rk2
        h = (b - a) / N  # Size of one step
        x = xa

        t_rk2 = np.arange(a, b, h)
        x_rk2 = []
        x_euler = []
        xe = x
        error_rk2 = 0.0
        error_euler = 0.0

        for t in t_rk2:

            error_rk2 += (x - exact_sol(t,a,xa,c)) ** 2  # Accumulating the error of Runge-Kutta 2
            error_euler += (xe - exact_sol(t,a,xa,c)) ** 2  # Accumulating the error of Euler

            x_rk2.append(x)
            k1 = h * f(x, t, c)
            k2 = h * f(x + 0.5 * k1, t + 0.5 * h, c)
            x += k2
            x_euler.append(xe)
            xe += k1

        if N == 128:
            plt.figure(figsize=(12,12))
            plt.plot(t_rk2, x_rk2, 'gs', label="Runge-Kutta2")
            plt.plot(t_rk2, x_euler, 'ro', label="Euler")
            plt.plot(time, exact_x, label="Exact solution")
            plt.xlabel("t")
            plt.ylabel("x(t)")
            plt.legend(loc='best', fontsize='xx-large')
            plt.show()

        error_rk2 /= len(t_rk2)
        error_euler /= len(t_rk2)
        meanerror_rk2.append(np.sqrt(error_rk2))
        meanerror_euler.append(np.sqrt(error_euler))

    # Graph
    plt.figure(figsize=(12,12))
    plt.loglog(N_list, meanerror_rk2, 'gs', label="Runge-Kutta2")
    plt.loglog(N_list, meanerror_euler, 'ro', label="Euler")
    plt.xlabel("N")
    plt.ylabel("Mean Error")
    plt.legend(loc='best', fontsize='x-large')
    plt.show()

    print((log(meanerror_euler[0])-log(meanerror_euler[len(N_list)-1]))/(log(1024.0)-log(8.0)))
    print((log(meanerror_rk2[0])-log(meanerror_rk2[len(N_list)-1]))/(log(1024.0)-log(8.0)))

def exact_sol(t, a, xa, c):
    b = xa - 2.0 * exp(-c*a) * cos(a)
    return 2.0 * exp(-c*t) * cos(t) + b

def f(x, t, c):
    return -2.0 * exp(-c*t) * (c * cos(t) + sin(t))

if __name__ == "__main__":
    main()


__all__ = ("rotate",)

from typing import Tuple, Union

import numpy as np

def rotate(
    pivot: np.ndarray,
    turning: np.ndarray,
    angle: Union[Tuple[float], Tuple[float, float, float]],
) -> np.ndarray:
    pivot = np.asarray(pivot)
    turning = np.asarray(turning)
    assert pivot.shape == turning.shape
    matrix = get_rotate_matrix(angle)
    return matrix @ (turning - pivot) + pivot

def get_rotate_matrix(
    angle: Union[Tuple[float], Tuple[float, float, float]]
) -> np.ndarray:
    if len(angle) == 1:
        return rotate_matrix_2d(angle[0])
    elif len(angle) == 3:
        return rotate_matrix_3d(angle)
    else:
        raise ValueError(f"ndim should be 2 or 3")

def rotate_matrix_2d(angle: float) -> np.ndarray:
    return np.array([[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]])

def rotate_matrix_3d(angle: Tuple[float, float, float]) -> np.ndarray:
    rotate_matrix_x = np.array(
        [
            [1, 0, 0],
            [0, np.cos(angle[0]), -np.sin(angle[0])],
            [0, np.sin(angle[0]), np.cos(angle[0])],
        ]
    )
    rotate_matrix_y = np.array(
        [
            [np.cos(angle[1]), 0, np.sin(angle[1])],
            [0, 1, 0],
            [-np.sin(angle[1]), 0, np.cos(angle[1])],
        ]
    )
    rotate_matrix_z = np.array(
        [
            [np.cos(angle[2]), -np.sin(angle[2]), 0],
            [np.sin(angle[2]), np.cos(angle[2]), 0],
            [0, 0, 1],
        ]
    )
    return rotate_matrix_x @ rotate_matrix_y @ rotate_matrix_z


# Plots various neural net activation functions.

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

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

def relu(z):
    return np.maximum(0, z)

def heaviside(z):
    return (z > 0)

def softplus(z):
    return np.log(1+np.exp(z))

def lrelu(z, lam=0.1):
    return np.maximum(lam*z, z)

def elu(z, alpha=1):
    return np.where(z < 0, alpha * (np.exp(z) - 1), z)

def elu2(z, lam=0.5):
    return np.maximum(0, z) + np.minimum(0, lam*(np.exp(z) - 1))

def swish(z):
    return z * sigmoid(z)

from scipy.special import erfc

# alpha and scale to self normalize with mean 0 and standard deviation 1
# (see equation 14 in the SELU paper):
alpha_0_1 = -np.sqrt(2 / np.pi) / (erfc(1/np.sqrt(2)) * np.exp(1/2) - 1)
scale_0_1 = (1 - erfc(1 / np.sqrt(2)) * np.sqrt(np.e)) * np.sqrt(2 * np.pi) * (2 * erfc(np.sqrt(2))*np.e**2 + np.pi*erfc(1/np.sqrt(2))**2*np.e - 2*(2+np.pi)*erfc(1/np.sqrt(2))*np.sqrt(np.e)+np.pi+2)**(-1/2)

def selu(z, scale=scale_0_1, alpha=alpha_0_1):
    return scale * elu(z, alpha)

z = np.linspace(-5, 5, 200)

#plt.figure(figsize=(11,4))
plt.figure()
plt.plot(z, sigmoid(z), "b-", linewidth=2, label="Sigmoid")
plt.plot(z, np.tanh(z), "g--", linewidth=2, label="Tanh")
plt.plot(z, relu(z), "m-.", linewidth=2, label="ReLU")
plt.grid(True)
plt.legend(loc="lower right", fontsize=14)
plt.title("Activation functions", fontsize=14)
plt.axis([-5, 5, -1.2, 1.2])
pml.savefig('activationFuns.pdf')
plt.show()

#plt.figure(figsize=(11,4))
plt.figure()
plt.plot(z, relu(z), "r-", linewidth=2, label="ReLU")
plt.plot(z, lrelu(z), "g--", linewidth=2, label="LReLU")
plt.plot(z, elu(z), "b-", linewidth=2, label="ELU")
plt.plot(z, selu(z), "k:", linewidth=2, label="SELU")
plt.plot(z, swish(z), "m-.", linewidth=2, label="swish")
plt.grid(True)
plt.legend(loc="upper left", fontsize=14)
plt.title("Activation functions", fontsize=14)
plt.axis([-2, 2, -1.2, 2])
pml.savefig('activationFuns2.pdf')
plt.show()

# From https://github.com/ageron/handson-ml2/blob/master/11_training_deep_neural_networks.ipynb
plt.figure()
z = np.linspace(-5, 5, 200)
plt.plot([-5, 5], [0, 0], 'k-')
plt.plot([-5, 5], [1, 1], 'k--')
plt.plot([0, 0], [-0.2, 1.2], 'k-')
plt.plot([-5, 5], [-3/4, 7/4], 'g--')
plt.plot(z, sigmoid(z), "b-", linewidth=2)
props = dict(facecolor='black', shrink=0.1)
plt.annotate('Saturating', xytext=(3.5, 0.7), xy=(5, 1), arrowprops=props, fontsize=14, ha="center")
plt.annotate('Saturating', xytext=(-3.5, 0.3), xy=(-5, 0), arrowprops=props, fontsize=14, ha="center")
plt.annotate('Linear', xytext=(2, 0.2), xy=(0, 0.5), arrowprops=props, fontsize=14, ha="center")
plt.grid(True)
plt.title("Sigmoid activation function", fontsize=14)
plt.axis([-5, 5, -0.2, 1.2])
pml.savefig("sigmoid_saturation_plot.pdf")
plt.show()


import numpy as np

def correlated_timeseries_example(N=10000, tau=5.0, seed=None):
    """Generate synthetic timeseries data with known correlation time.

    Parameters
    ----------
    N : int, optional
        length (in number of samples) of timeseries to generate
    tau : float, optional
        correlation time (in number of samples) for timeseries
    seed : int, optional
        If not None, specify the numpy random number seed.

    Returns
    -------
    dih : np.ndarray, shape=(num_dihedrals), dtype=float
        dih[i,j] gives the dihedral angle at traj[i] correponding to indices[j].

    Notes
    -----

    Synthetic timeseries generated using bivariate Gaussian process described
    by Janke (Eq. 41 of Ref. [1]).

    As noted in Eq. 45-46 of Ref. [1], the true integrated autocorrelation time will be given by
    tau_int = (1/2) coth(1 / 2 tau) = (1/2) (1+rho)/(1-rho)
    which, for tau >> 1, is approximated by
    tau_int = tau + 1/(12 tau) + O(1/tau^3)
    So for tau >> 1, tau_int is approximately the given exponential tau.

    References
    ----------
    .. [1] Janke W. Statistical analysis of simulations: Data correlations and error estimation.  In 'Quantum Simulations of Complex Many-Body Systems: From Theory to Algorithms'. NIC Series, VOl. 10, pages 423-445, 2002.

    Examples
    --------

    Generate a timeseries of length 10000 with correlation time of 10.

    >>> A_t = correlated_timeseries_example(N=10000, tau=10.0)

    Generate an uncorrelated timeseries of length 1000.

    >>> A_t = correlated_timeseries_example(N=1000, tau=1.0)

    Generate a correlated timeseries with correlation time longer than the length.

    >>> A_t = correlated_timeseries_example(N=1000, tau=2000.0)

    """

    # Set random number generator into a known state for reproducibility.
    random = np.random.RandomState(seed)

    # Compute correlation coefficient rho, 0 <= rho < 1.
    rho = np.exp(-1.0 / tau)
    sigma = np.sqrt(1.0 - rho * rho)

    # Generate uncorrelated Gaussian variates.
    e_n = random.randn(N)

    # Generate correlated signal from uncorrelated Gaussian variates using correlation coefficient.
    # NOTE: This will be slow.
    # TODO: Can we speed this up using vector operations?
    A_n = np.zeros([N], np.float32)
    A_n[0] = e_n[0]
    for n in range(1, N):
        A_n[n] = rho * A_n[n - 1] + sigma * e_n[n]

    return A_n


"""
Example to solve Kinematics of 2DoF Scara Arm.
"""
# Funções das Bibliotecas Utilizadas
from sympy import symbols, trigsimp, pprint
from sympy.physics.mechanics import dynamicsymbols
from sympy.physics.vector import ReferenceFrame, Vector
from sympy.physics.vector import time_derivative

# Variáveis Simbólicas
THETA_1, THETA_2 = dynamicsymbols('theta_1 theta_2')
L_1, L_2 = symbols('l_1 l_2', positive=True)

# Referenciais
# Referencial Parado
B0 = ReferenceFrame('B0')
B1 = ReferenceFrame('B1')
# Referencial móvel: theta_1 em relação a B0.z
B1.orient(B0, 'Axis', [THETA_1, B0.z])
B2 = ReferenceFrame('B2')
# Referencial móvel: theta_2 em relação a B1.z
B2.orient(B1, 'Axis', [THETA_2, B1.z])

# Vetores Posição entre os Pontos
# Vetor Nulo
B0_R_OA = Vector(0)
# Vetor que liga os pontos A e B expresso no referencial móvel B1
B1_R_AB = L_1 * B1.x
# Vetor que liga os pontos B e C expresso no referencial móvel B2
B2_R_BC = L_2 * B2.x

# Cinemática do ponto A em relação ao referencial B0
R_A = B0_R_OA
V_A = time_derivative(R_A, B0)
A_A = time_derivative(V_A, B0)

# Cinemática do ponto B em relação ao referencial B0
R_B = R_A + B1_R_AB.express(B0)
V_B = time_derivative(R_B, B0)
A_B = time_derivative(V_B, B0)

# Cinemática do ponto C em relação ao referencial B0
R_C = R_B.express(B0) + B2_R_BC.express(B0)
V_C = (time_derivative(R_C, B0))
A_C = (time_derivative(V_C, B0))

# Simplificação dos Resultados
R_A = (R_A.to_matrix(B0)).applyfunc(trigsimp)
V_A = (V_A.to_matrix(B0)).applyfunc(trigsimp)
A_A = (A_A.to_matrix(B0)).applyfunc(trigsimp)
R_B = (R_B.to_matrix(B0)).applyfunc(trigsimp)
V_B = (V_B.to_matrix(B0)).applyfunc(trigsimp)
A_B = (A_B.to_matrix(B0)).applyfunc(trigsimp)
R_C = (R_C.to_matrix(B0)).applyfunc(trigsimp)
V_C = (V_C.to_matrix(B0)).applyfunc(trigsimp)
A_C = (A_C.to_matrix(B0)).applyfunc(trigsimp)

# Resultados de C
pprint(R_C)
pprint(V_C)
pprint(A_C)


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

# Práctica 1, Ejercicio 1
# David Cabezas Berrido

import numpy as np
import sympy as sp
import matplotlib.pyplot as plt

#-------------------------------------------------------------------------------#
#------------- Ejercicio sobre la búsqueda iterativa de óptimos ---------------#
#-------------------------------------------------------------------------------#

#------------------------------Ejercicio 1 ------------------------------------#

u, v = sp.symbols('u v')

# Función a minimizar
E_sym=sp.Lambda((u,v), sp.simplify((u*sp.exp(v)-2*v*sp.exp(-u))**2))
def E(w):
    return sp.N(E_sym(w[0],w[1]))

# Derivada parcial de E respecto de u
Eu_sym=sp.Lambda((u,v), sp.simplify(sp.diff(E_sym(u,v),u)))
def Eu(w):
	return sp.N(Eu_sym(w[0],w[1]))

# Derivada parcial de E respecto de v
Ev_sym=sp.Lambda((u,v), sp.simplify(sp.diff(E_sym(u,v),v)))
def Ev(w):
	return sp.N(Ev_sym(w[0],w[1]))

# Gradiente de E
def gradE(w):
	return np.array((Eu(w), Ev(w)),np.float64)

# Algoritmo del gradiente descendente
def gd(w, lr, grad_fun, fun, epsilon, max_iters = 1000):

	it = 0

	while it < max_iters and fun(w)>epsilon:
		it+=1
		grad=grad_fun(w) # Calcula gradiente
		w=w-lr*grad      # Actualiza el punto
		# print(w, fun(w))

	return w, it

# Punto inicial
w=np.array([1,1],np.float64)
# Número máximo de iteraciones
max_iters=100
# Learning rate
lr=0.1
# Margen de error
epsilon=10**(-14)

w, num_ite = gd(w,lr,gradE,E,epsilon,max_iters)

print ('\nGRADIENTE DESCENDENTE')
print ('\nEjercicio 1\n')
print ('Numero de iteraciones: ', num_ite)
input("\n--- Pulsar Intro para continuar ---\n")
print ('Coordenadas obtenidas: (', w[0], ', ', w[1],')')

input("\n--- Pulsar Intro para continuar ---\n")

#----------------------------- Ejercicio 2 --------------------------------#

x, y = sp.symbols('x y')

# Función a minimizar
f_sym=sp.Lambda((x,y), sp.simplify((x-2)**2+2*(y+2)**2+2*sp.sin(2*sp.pi*x)*sp.sin(2*sp.pi*y)))
def f(w):
	return float(f_sym(w[0],w[1]))

# Derivada parcial de f respecto de x
fx_sym=sp.Lambda((x,y), sp.simplify(sp.diff(f_sym(x,y),x)))
def fx(w):
	return float(fx_sym(w[0],w[1]))

# Derivada parcial de f respecto de y
fy_sym=sp.Lambda((x,y), sp.simplify(sp.diff(f_sym(x,y),y)))
def fy(w):
	return float(fy_sym(w[0],w[1]))

# Gradiente de f
def gradf(w):
	return np.array((fx(w), fy(w)),np.float64)

# a) Usar gradiente descendente para minimizar la función f, con punto inicial (1,-1)
# tasa de aprendizaje 0.01 y max 50 iteraciones. Repetir con tasa de aprend. 0.1

def gd_grafica(w, lr, grad_fun, fun, max_iters = 1000):

	graf = []
	graf.append(fun(w))

	for _ in range(max_iters):
		grad=grad_fun(w)
		w=w-lr*grad
		graf.append(fun(w))
		#print(w, fun(w))

	graf = np.array(graf,np.float64)

	plt.plot(range(0,max_iters+1), graf, 'bo')
	plt.xlabel('Iteraciones')
	plt.ylabel('f(x,y)')
	plt.show()

# Punto inicial
w=np.array([1,-1],np.float64)
# Número máximo de iteraciones
max_iters=50

print ('Resultados ejercicio 2\n')
print ('\nGrafica con learning rate igual a 0.01')
lr=0.01 # Learning rate
gd_grafica(w,lr,gradf,f,max_iters)
print ('\nGrafica con learning rate igual a 0.1')
lr=0.1 # Learning rate
gd_grafica(w,lr,gradf,f,max_iters)
input("\n--- Pulsar tecla para continuar ---\n")

# b) Obtener el valor minimo y los valores de (x,y) con los
# puntos de inicio siguientes:

def gd(w, lr, grad_fun, fun, max_iters = 1000):

	for _ in range(max_iters):
		grad=grad_fun(w)
		w=w-lr*grad
		#print(w, fun(w))

	return w

lr=0.01 # Learning rate (no se especifica)
# Número máximo de iteraciones (tampoco se especifica)
max_iters=50

print ('Punto de inicio: (2.1, -2.1)\n')
w=gd(np.array((2.1,-2.1),np.float64),lr,gradf,f,max_iters)
print ('(x,y) = (', w[0], ', ', w[1],')\n')
print ('Valor minimo: ',f(w))

input("\n--- Pulsar tecla para continuar ---\n")

print ('Punto de inicio: (3.0, -3.0)\n')
w=gd(np.array((3.0,-3.0),np.float64),lr,gradf,f,max_iters)
print ('(x,y) = (', w[0], ', ', w[1],')\n')
print ('Valor minimo: ',f(w))

input("\n--- Pulsar tecla para continuar ---\n")

print ('Punto de inicio: (1.5, 1.5)\n')
w=gd(np.array((1.5,1.5),np.float64),lr,gradf,f,max_iters)
print ('(x,y) = (', w[0], ', ', w[1],')\n')
print ('Valor minimo: ',f(w))

input("\n--- Pulsar tecla para continuar ---\n")

print ('Punto de inicio: (1.0, -1.0)\n')
w=gd(np.array((1.0,-1.0),np.float64),lr,gradf,f,max_iters)
print ('(x,y) = (', w[0], ', ', w[1],')\n')
print ('Valor mínimo: ',f(w))

input("\n--- Pulsar tecla para salir ---\n")


import numpy as np
import matplotlib.pyplot as plt

# Initializing important parametres
a = 0.5
b, r = 0.1, 0.1

f = lambda v: v*(a-v)*(v-1)
dvdt = lambda v, w, I: f(v)-w+I
dwdt = lambda v, w: b*v-r*w

def euler_integrate(v, w, dt, I, ttot):
	niter = int(ttot/dt)
	vhist = []
	whist = []
	for i in range(niter):
		v, w = (v + dvdt(v, w, I)*dt), (w + dwdt(v, w)*dt)
		vhist.append(v)
		whist.append(w)

	return vhist, whist

def nullclines(I, v):
	vnc = f(v) + I
	wnc = b*v/r
	return vnc, wnc

def plot_fig(y, title="", ylim=(), x=None, xlim=()):
	plt.figure()
	plt.title(title)
	color = ["b", "r"]
	if not x:
		for i in y:
			plt.plot(i)
	else:
		for i,j in enumerate(y):
			plt.plot(x, j, color=color[i])
	if ylim: plt.ylim(ylim)
	if xlim: plt.xlim(xlim)
	plt.xlabel("Time")
	plt.ylabel("Voltage")
	plt.legend(["V(t)", "W(t)"])
	plt.grid()
	plt.show()

def plot_nullclines(I, xlim, ylim, positions):
	color = ['palegreen', 'gold', 'tomato', 'deepskyblue', 'lightsteelblue']*3
	v = np.linspace(xlim[0], xlim[1], 100)
	w = np.linspace(xlim[0], xlim[1], 100)

	v_mesh, w_mesh = np.meshgrid(v, w)
	v_vel = dvdt(v_mesh, w_mesh, I)
	w_vel = dwdt(v_mesh, w_mesh)

	vnc, wnc = nullclines(I, v)

	plt.figure()
	plt.plot(v, vnc, 'b')
	plt.plot(v, wnc, 'r')
	plt.legend(['v nullcline', 'w nullcline'])
	plt.ylim(ylim[0], ylim[1])
	title = "Phase Plot for I=" + str(round(I, 2))
	plt.title(title)
	plt.xlabel('v-values')
	plt.ylabel('w-vaues')

	if positions:
		for i in range(len(positions)):
			plt.streamplot(v_mesh, w_mesh, v_vel, w_vel, density=2, start_points=[positions[i]], color=color[i], integration_direction="forward", arrowsize=2)
	else:
		plt.streamplot(v_mesh, w_mesh, v_vel, w_vel, color=color[0])
	plt.grid()
	plt.show()

#####################################################
# Case 1: I_ext = 0
dt = 0.1
ttot = 100
I = 0

# Nullclines
plot_nullclines(I, (-0.5,1.5), (-0.45,0.45), [])
plot_nullclines(I, (-0.5,1.5), (-0.45,0.45), [[0,0], [0.4,0], [0.6, 0], [1, 0]])

# Subcase 1: V(0) < a and W(0) = 0
x = list(np.linspace(0,100,1000))
vhist, whist = euler_integrate(0.4, 0, dt, I, ttot)
plot_fig([vhist, whist], "I=0, Subcase 1: V(0) < a and W(0) = 0", (-0.2,0.75), x)

# Subcase 2: V(0) > a and W(0) = 0
vhist, whist = euler_integrate(0.6, 0, 0.1, I, 100)
plot_fig([vhist, whist], "I=0, Subcase 2: V(0) > a and W(0) = 0", (-0.2,0.75), x)

#####################################################
# Case 2: I_ext = 0.6
dt = 0.1
ttot = 100
I = 0.6

# Nullclines
plot_nullclines(I, (-0.5,1.5), (0.15,0.95), [])
plot_nullclines(I, (-0.5,1.5), (0.15,0.95), [[0,0], [0.4,0], [0.6, 0], [1, 0]])

# Subcase 1: V(0) < a and W(0) = 0
x = list(np.linspace(0,100,1000))
vhist, whist = euler_integrate(0.4, 0, dt, I, ttot)
plot_fig([vhist, whist], "I=0.6, Subcase 1: V(0) < a and W(0) = 0", (-0.2,1.5), x)

# Subcase 2: V(0) > a and W(0) = 0
vhist, whist = euler_integrate(0.6, 0, 0.1, I, 100)
plot_fig([vhist, whist], "I=0.6, Subcase 2: V(0) > a and W(0) = 0", (-0.2,1.5), x)

#####################################################
# Case 3: I_ext = 1
dt = 0.1
ttot = 100
I = 1

# Nullclines
plot_nullclines(I, (-0.5,1.5), (0.55,1.45), [])
plot_nullclines(I, (-0.5,1.5), (0.55,1.45), [[0,0.6], [0.4,0.6], [0.6, 0.6], [1, 0.6], [0,1.4], [0.4,1.4], [0.6, 1.4], [1, 1.4]])

# Subcase 1: V(0) < a and W(0) = 0
x = list(np.linspace(0,100,1000))
vhist, whist = euler_integrate(0.4, 0, dt, I, ttot)
plot_fig([vhist, whist], "I=1, Subcase 1: V(0) < a and W(0) = 0", (-0.2,2), x)

# Subcase 2: V(0) > a and W(0) = 0
vhist, whist = euler_integrate(0.6, 0, 0.1, I, 100)
plot_fig([vhist, whist], "I=1, Subcase 2: V(0) > a and W(0) = 0", (-0.2,2), x)

#####################################################
# Case 4: I_ext = 0.02
dt = 0.1
ttot = 100
I = 0.02

# Choosing parameters
b, r = 0.01, 0.8

# Nullclines
plot_nullclines(I, (-0.5,1.5), (-0.45,0.45), [])
plot_nullclines(I, (-0.5,1.5), (-0.45,0.45), [[0,0.4], [0.1,0.4], [-0.1,0.4], [0,-0.4], [0.4,0.4], [0.6, 0.4], [1, 0.4], [0.1,-0.4], [-0.1,-0.4], [0.4,-0.4], [0.6, -0.4], [1, -0.4]])

# Subcase 1: V(0) < a and W(0) = 0
x = list(np.linspace(0,100,1000))
vhist, whist = euler_integrate(0.4, 0, dt, I, ttot)
plot_fig([vhist, whist], "I=0.02, Subcase 1: V(0) < a and W(0) = 0", (-0.2,2), x)

# Subcase 2: V(0) > a and W(0) = 0
vhist, whist = euler_integrate(0.6, 0, 0.1, I, 100)
plot_fig([vhist, whist], "I=0.02, Subcase 2: V(0) > a and W(0) = 0", (-0.2,2), x)


import numpy as np
from numpy import log, dot, e

class LogisticRegression:
    def __init__(self, lr=0.05, epochs=100, intercept=True):
        self.lr = lr
        self.epochs = epochs
        self.intercept = intercept
        self.bias = 0

    def addIntercept(self, X):
        intercept = np.ones((X.shape[0], 1))
        return np.concatenate((intercept, X), axis=1)

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

    def cost(self, h, y):
        return (-y * np.log(h) - (1 - y) * np.log(1 - h)).mean()

    def fit(self, X, y):
        if self.intercept:
            X = self.addIntercept(X)

        self.loss = []

        # weights initialization
        self.weight = np.zeros(X.shape[1])

        for i in range(self.epochs):
            z = np.dot(X, self.weight)
            h = self.sigmoid(z + self.bias)

            gradient = np.dot(X.T, (h - y)) / y.size
            bGradient = np.sum(h - y) / y.size

            self.weight -= self.lr * gradient
            self.bias -= self.lr * bGradient

            self.loss.append(self.cost(h, y))

    def predict_prob(self, X):
        if self.intercept:
            X = self.addIntercept(X)

        return self.sigmoid(np.dot(X, self.weight) + self.bias)

    def predict(self, X):
        return self.predict_prob(X) >= 0.5


"""
  Name     : c10_03_exchange_futures.py
  Book     : Python for Finance (2nd ed.)
  Publisher: Packt Publishing Ltd.
  Author   : Yuxing Yan
  Date     : 6/6/2017
  email    : yany@canisius.edu
             paulyxy@hotmail.com
"""

import scipy as sp
def futuresExchangeRate(s0,rateDomestic,rateForeign,T):
    futureEx=s0*sp.exp((rateDomestic-rateForeign)*T)
    return futureEx

s0=1.25
rHome=0.01
rForeigh=0.02
T=3./12.
futures=futuresExchangeRate(s0,rHome,rForeigh,T)
print("futures=",futures)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Aug 23 20:14:35 2019

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

x_val = np.arange(-10,10,0.1)

#x_val = [0.2]
summation = 0
relative_errors = []

approx_y = []
actual_y = []

for x in x_val:
    print('x = ',x)
    print("sin(x) = ", np.sin(x))
    actual_y.append(np.sin(x))
    initial_fact = 1.0
    N = 5
    #if x == 0.0:
        #continue

    summation = 0.0

    fact_array = np.arange(0,N+1,1)

    for n in fact_array:

        value = ((2*n) + 1)
        fact = (value)*initial_fact
        initial_fact = fact

        denomenator = fact

        iteration = (((-1)**(n))*((x)**(value)))/denomenator

        summation = summation + iteration

        if np.abs(iteration/summation) <= (1e-8):
            print("iteration/summation = ", np.abs(iteration/summation))
            print('sin(x) approximation complete ', n)
            break

    print('sin(x) approx ', summation)

    abs_err = np.sin(x) - summation
    rel_err = abs_err/x
    relative_errors.append(rel_err)
    approx_y.append(summation)

plt.plot(x_val, relative_errors,  '.', lw=1)
plt.xlabel('x')
plt.ylabel('relative error')
plt.show()

plt.plot(x_val, actual_y, '.', lw = 1, label = 'f(x) = sin(x)')
plt.plot(x_val, approx_y, '.', lw = 1, label = 'sin(x) approximation')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.show()

plt.plot(x_val, actual_y, '.', lw = 1, label = 'f(x) = sin(x)')
plt.plot(x_val, approx_y, '.', lw = 1, label = 'sin(x) approximation')
plt.xlabel('x')
plt.ylabel('y')
plt.xlim(-10,10)
plt.ylim(-3,3)
plt.legend()
plt.show()


#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Python version: 3.6

import numpy as np
import math

def getSequence():
    inSequence = np.array([])
    inSeqString = input("\nPlease enter the array (elements separated by space): ")
    seqSplited = inSeqString.split()
    for i in range(len(seqSplited)):
        inSequence = np.append(inSequence, float(seqSplited[i]))
    return inSequence

def dft(seq):
    l = len(seq)
    out = np.zeros(l, dtype=complex)
    for i in range(l):
        for j in range(l):
            out[i] = out[i] + seq[j]*np.exp((-2j*math.pi*j*i)/l)
    return out

def ditfft(seq):
    l = len(seq)
    half_l = l // 2
    out = np.zeros(l, dtype=complex)

    if l == 1:
        out[0] = seq[0]
    else:
        seq_even = seq[0::2] #get all even indexed elements
        seq_odd = seq[1::2]  #get all odd indexed elements
        out_even = ditfft(seq_even)
        out_odd = ditfft(seq_odd)
        for m in range(l):
            out[m] = out_even[m % half_l] + out_odd[m % half_l] * np.exp(-1j*2*math.pi*m/l)
    return out

def diffft(seq):
    l = len(seq)
    half_l = l // 2
    out = np.zeros(l, dtype=complex)
    out1 = np.zeros(half_l, dtype=complex)
    out2 = np.zeros(half_l, dtype=complex)
    seq1 = np.zeros(half_l, dtype=complex)
    seq2 = np.zeros(half_l, dtype=complex)
    if l == 1:
        out[0] = seq[0]
    else:
        seq_hi = seq[0:half_l:1] #get 1st half of input array
        seq_lo = seq[half_l:l:1]  #get 2nd half of input array
        for m in range(half_l):
            seq1[m] = seq_hi[m] + seq_lo[m]
            seq2[m] = (seq_hi[m] - seq_lo[m]) * np.exp(-1j*2*math.pi*m/l)

        out1 = diffft(seq1)
        out2 = diffft(seq2)
        out = np.concatenate([out1,out2])
    return out

def reArrange(seq):
    l = len(seq)
    n = np.int(np.log2(l))
    out = np.zeros(l, dtype=complex)
    for i in range(l):
        bin_i = bin(i)
        b_i = str(bin_i)[2::]
        b = ''.join(['0'*(n-len(b_i)),b_i])
        k = 0
        for element in range(len(b)):
            if b[element] == '1':
                k = k + (2**element)
        out[k] = seq[i]
    return out

def viewable(seq):
    for i in range(len(seq)):
        if abs(seq[i].real) < 10**(-10):
            seq[i] = 0 + 1j*seq[i].imag
        if abs(seq[i].imag) < 10**(-10):
            seq[i] = seq[i].real
    return seq

def dct(seq):
    l = len(seq)
    out = np.zeros(l)
    for i in range(l):
        for j in range(l):
            out[i] = out[i] + 2*seq[j]*np.cos((math.pi*i*((2*j)+1))/(2*l))
    return out

def viewable_r(seq):
    for i in range(len(seq)):
        if abs(seq[i]) < 10**(-10):
            seq[i] = 0
    return seq

def dst(seq):
    l = len(seq)
    out = np.zeros(l)
    for i in range(l):
        for j in range(l):
            out[i] = out[i] + seq[j]*np.sin((math.pi*(i+1)*((2*j)+1))/(2*l))
    return out

def wht(seq):
    raise NotImplementedError()

def slant(seq):
    raise NotImplementedError()

def haar(seq):
    raise NotImplementedError()

def klt(seq):
    raise NotImplementedError()


from typing import Tuple, cast, Optional, List

import numpy as np

def z_normalization(data: np.ndarray) -> np.ndarray:
    std = data.std()
    if std == 0:
        return data

    result = data.copy()
    mean = result.mean()
    return (result - mean) / std

def calculate_mean(data: np.ndarray) -> float:
    return sum(data) / len(data)

def calculate_covariance_matrix(data: np.ndarray) -> Tuple[float, np.ndarray]:
    data_mean = data.mean()

    cov_matrix = 0.
    for vec in data:
        cov_matrix += np.dot((vec - data_mean).reshape(len(vec), 1), (vec - data_mean).reshape(1, len(vec)))
    cov_matrix /= len(data)

    return data_mean, cov_matrix

def mahalanobis_distance(x: np.ndarray, mean: float, cov_matrix: np.ndarray) -> float:
    distance = np.dot(x - mean, np.linalg.inv(cov_matrix))
    distance = np.dot(distance, (x - mean).T)
    return cast(float, distance)

def squared_error(predictions: np.ndarray, targets: np.ndarray) -> np.ndarray:
    return np.sqrt((predictions - targets) ** 2)

def ewma(data: np.ndarray, window: int, alpha: Optional[float] = None) -> np.ndarray:
    """Exponential weighted moving average"""
    alpha = alpha or (2. / (window + 1))
    t_ = window - 1
    ema = np.zeros_like(data, dtype=float)

    for i in range(len(data)):
        # Special Case
        if i > t_:
            ema[i] = (data[i] - ema[i - 1]) * alpha + ema[i - 1]
        else:
            ema[i] = np.mean(data[:i + 1])
    return ema

def find_anomaly_points(data: np.ndarray, offset: int = 500, threshold: float = None) -> List[int]:
    assert offset >= 1

    threshold = threshold or (data.mean() + data.std() * 3)
    anomaly_points = []

    i = 0
    while i <= len(data) - 1:
        if data[i] >= threshold:
            anomaly_points.append(i)
            i += offset
        else:
            i += 1

    return anomaly_points

def find_anomaly_points_by_median(data: np.array, offset: int = 500, threshold: int = 10) -> List[int]:
    """https://stackoverflow.com/a/45399188"""
    assert offset >= 1

    distances = np.abs(data - np.median(data))
    mdev = np.median(distances)

    anomaly_points = []

    i = 0
    while i <= len(data) - 1:
        if (distances[i] / (mdev or 1.)) >= threshold:
            anomaly_points.append(i)
            i += offset
        else:
            i += 1

    return anomaly_points


# -*- coding: utf-8 -*-
"""
Created on Tue Nov 24 09:38:13 2020

@author: RPP
"""

import numpy as np
import matplotlib.pyplot as plt

def gradientDescent(lr, maxEp):
    plt.close()
    plt.clf()
    minx = -5
    maxx = 5
    plotError(minx, maxx)

    #start GD in the min-max range
    nEp = 0
    x = np.random.rand() * (maxx-minx) + minx
    E = error(x)
    print("Erro 0: %f\t x0: %f" % (E, x))
    plt.plot(x, E, 'bo')
    plt.pause(1)

    #iterate GD
    while abs(E) > 10**(-5) and nEp < maxEp:
        xAnt = x
        Eant = E
        dx = -lr * dE_dx(x)
        x += dx
        E = error(x)
        nEp += 1
        print("nEp: %d - Erro: %f\t x: %f" % (nEp, E, x))

        plt.plot(xAnt, Eant, 'bo', x, E, 'ro')
        plt.plot([xAnt, x], [Eant, E], 'b')
        plt.draw()
        #wait = input("Press Enter to continue.")
        plt.pause(1)

def plotError(minx, maxx):
    step = 0.01
    x = np.arange(minx, maxx+step, step)
    E = error(x)
    plt.plot(x, E)

def error(x):
    return x ** 2

def dE_dx(x):
    return 2*x

if __name__ == "__main__":
    #gradientDescent(0.1, 20)
    #gradientDescent(1.5, 20)
    gradientDescent(0.3, 20)
    #gradientDescent(1, 20)
    #gradientDescent(0.5, 20)


'''
Nicholas Geneva
ngeneva@nd.edu
November 3, 2017
'''
import sys
import random
import numpy as np
import scipy as sc
import matplotlib as mlp
import matplotlib.pyplot as plt

from matplotlib import rc
from scipy import special
from scipy import stats

def markovChainWeight(x0, b0 = -1, b1 = 1, tk = 'uniform'):
    '''
    Completes a Markov chain for SIS MC integrals.
    See Ref: https://doi.org/10.1016/j.amc.2010.03.138
    Args:
        x0 (float) = point of interest
        b0 (float) = Lower bound of integral
        b1 (float) = Upper bound of integral
        tk (string) = Transition kernel type (uniform, guassian)
    Returns:
        w (float) = calculated weight
    '''
    k = 1
    #Construct markov chain
    if(tk == 'uniform'):
        while(True):
            #Transition kernal to next location
            x1 = np.random.uniform(b0-0.5,b1+0.5)
            pd = 1.0 - (b1-b0)/(b1-b0+1.0) #Probability of killing the chain
            p = 1.0/(b1-b0+1) #General probability

            if(x1 > b1 or x1 < b0): #Kill markov chain
                break
            k = k * 0.5*(x1 - x0)/p
            x0 = x1

    elif(tk == 'guassian'):
        mu = 0
        sigma = 0.75
        #Probability of killing the chain
        pd = 1.0 - (sc.stats.norm.cdf(b1, mu, sigma) - sc.stats.norm.cdf(b0, mu, sigma))
        while(True):
            #Transition to
            x1 = np.random.normal(mu,sigma)
            p =  sc.stats.norm.pdf(x1, mu, sigma)
            if(x1 > b1 or x1 < b0): #Kill markov chain
                break

            k = k * 0.5*(x1 - x0)/p
            x0 = x1
    else:
        print('Transition Kernel not supported')

    return k * x0/pd

if __name__ == '__main__':
    plt.close('all')
    mlp.rcParams['font.family'] = ['times new roman'] # default is sans-serif
    rc('text', usetex=True)

    N = 10000
    y = [0]
    for i in range(N):
        y.append(y[-1] + markovChainWeight(0,b0 = -1,b1 = 1,tk = 'guassian'))
    yNorm = [y0 / float(i+1) for i, y0 in enumerate(y)]

    print('Guassian Estimate: %0.5f' %(y[-1]/float(N)))

    y = [0]
    for i in range(N):
        y.append(y[-1] + markovChainWeight(0,b0 = -1,b1 = 1,tk = 'uniform'))
    yNorm = [y0 / float(i+1) for i, y0 in enumerate(y)]

    print('Uniform Estimate: %0.5f' % (y[-1]/float(N)))


"""
Objetivo: Resolver questão 2b do terceiro laboratorio.
    é exatamente o mesmo codigo da questão anterior porém mudando a função a ser passada e o palpite
"""
import matplotlib.pyplot as plt
import numpy as np
from Newton_Raphson import newton_raphson

y = lambda x: 2*x**3 - 7*x**2 + x  # f(x) = 2x^3 - 7x^2 + x
y_linha = lambda x: 6*x**2 - 14*x + 1  # f'(x) = 6x^2 - 14x + 1
ini = -1  # ponto inicial, escolhido arbitariamente
#Verificar o arquivo Nexton-Raphson.py para entender o funcionamento dessa função
resp = newton_raphson(ini, y, y_linha, 'q2')
print(f'Para essa X0 = {ini} levamos {resp["iterações"] - 1} iterações e temos como valor de f(x) = {resp["modulo"]}')
print('Abaixo seguem os graficos dos valores de f(x) e dos erros, respectivamente:')

# Grafico com os valores de f(x)
x_a = np.linspace(-1.2, 1.2, 30)  # valores escolhidos para melhor visualização do grafico
y_a = [y(i) for i in x_a]
plt.style.use('ggplot')
plt.figure(figsize=(7, 5))
plt.title(f'F(x) por X\ncom X0 = {ini}')
plt.xlabel('Valores de x')
plt.ylabel('Valores de f(x)')
# função com valores entre 0.4 e 0.8
plt.plot(x_a, y_a, label='Função exata')
# Nossa aproximação Newton-Raphson por iteração
plt.plot(resp['valores'], resp['valores_função'], label='Valores de Newton-Raphson por iteração')
plt.tight_layout()
plt.legend(loc='best')
plt.show()

# Grafico com os valores dos erros
plt.style.use('ggplot')
plt.figure(figsize=(7, 5))
plt.title('Erro por Iterações')
plt.xlabel('Numero de iterações')
plt.ylabel('Valores dos erros')
plt.plot([i for i in range(1, resp['iterações'])], resp['erros'])
plt.tight_layout()
plt.show()


"""ODE Integration Functions

This library contains a suite of functions to generate simulations of
stochastic and deterministic disease progressions with and without noise due to
model error.

"""

import numpy as np
import math

def SIRode(y0, time, beta, gamma):
    """Integrate SIR epidemic model

    Simulate a very basic deterministic SIR system.

    :param 2x1 numpy array y0: initial conditions
    :param Ntimestep length numpy array time: Vector of time points that \
    solution is returned at
    :param float beta: transmission rate
    :param float gamma: recovery rate

    :returns: (2)x(Ntimestep) numpy array Xsim: first row S(t), second row I(t)

    """

    Xsim = rk4(SIR_D, y0, time, args=(beta,gamma,))
    Xsim = Xsim.transpose()
    return Xsim

# Derivative for basic SIR system.
def SIR_D(y,t,beta,gamma):
    # y[0] = susceptible
    # y[1] = infected
    dy = np.zeros((2,1))

    dy[0] = -(beta)*y[0]*y[1]
    dy[1] = (beta)*y[0]*y[1] - gamma*y[1]
    return dy

###############################################################################
# Simulate a modified deterministic SIR system with parameters built to
# have a power-law scaling for the contact rate as in,
# "Semi-empirical power-law scaling of new infection rate to model
#  epidemic dynamics with inhomogeneous mixing."
#  Mathematical Biosciences 203 (2006) 301-318
# beta = transmission rate
# gamma = recovery rate
# nu = contact rate power-law
# Returns numpy array:
#    Xsim = (2)x(Ntimestep) first row S(t), second row I(t).
def SIRmod1ode(y0, time, beta, gamma, nu):
    # y0 = 2x1 numpy array of initial conditions.
    # time = Ntimestep length numpy array
    # beta = scalar
    # gamma = scalar
    # nu = scalar

    Xsim = rk4(SIRmod1_D, y0, time, args=(beta,gamma,nu,))
    Xsim = Xsim.transpose()
    return Xsim

# Derivative for SIR system.
def SIRmod1_D(y,t,beta,gamma,nu):
    # y[0] = susceptible
    # y[1] = infected
    dy = np.zeros((2,1))

    dy[0] = -(beta)*y[1]*math.pow(y[0],nu)
    dy[1] = (beta)*y[1]*math.pow(y[0],nu) - gamma*y[1]
    return dy

###############################################################################
# Simulate a modified deterministic SIR system with parameters built to
# have a power-law scaling for the contact rate as in,
# "Semi-empirical power-law scaling of new infection rate to model
#  epidemic dynamics with inhomogeneous mixing."
#  Mathematical Biosciences 203 (2006) 301-318
# With this instance we also introduce a time varying transmission rate
# similar to that used in, "Modeling the spread of influenza among cities"
# by James M. Hyman and Tara LaForce 2003.
# beta = transmission rate
# gamma = recovery rate
# nu = contact rate power-law
# alpha = amplitude of transmission rate variation
#         beta varies over [1-alpha,1+alpha]
# c = time of peak transmission rate
# w = width of ramp up and ramp down of transmission

# Returns numpy array:
#    Xsim = (2)x(Ntimestep) first row S(t), second row I(t).
def SIRmod2ode(y0, time, beta, gamma, nu, alpha, c, w):
    # y0 = 2x1 numpy array of initial conditions.
    # time = Ntimestep length numpy array
    # beta = scalar
    # mu = scalar
    # nu = scalar

    Xsim = rk4(SIRmod2_D, y0, time, args=(beta,gamma,nu,alpha,c,w,))
    Xsim = Xsim.transpose()
    return Xsim

# Derivative for SIR system.
def SIRmod2_D(y,t,beta,gamma,nu,alpha,c,w):
    # y[0] = susceptible
    # y[1] = infected
    # t = scalar time point
    dy = np.zeros((2,1))

    dy[0] = -(trans_rate(t,beta,alpha,c,w))*y[1]*math.pow(y[0],nu)
    dy[1] = (trans_rate(t,beta,alpha,c,w))*y[1]*math.pow(y[0],nu) - gamma*y[1]
    return dy

###############################################################################
# Simulate a basic deterministic SEIR system with parameters
# beta = transmission rate
# mu = incubation rate
# gamma = recovery rate
# Returns numpy array:
#    Xsim = (3)x(Ntimestep) first row S(t), second row I(t),
#           third row E(t).
def SEIRode(y0, time, beta, mu, gamma):
    # y0 = 3x1 numpy array of initial conditions.
    # time = Ntimestep length numpy array
    # beta = scalar
    # mu = scalar
    # gamma = scalar

    Xsim = rk4(SEIR_D, y0, time, args=(beta,mu,gamma,))
    Xsim = Xsim.transpose()
    return Xsim

# Derivative for basic SEIR system.
def SEIR_D(y,t,beta,mu,gamma):
    # y[0] = susceptible
    # y[1] = exposed
    # y[2] = infected/infectious
    dy = np.zeros((3,1))

    dy[0] = -(beta)*y[0]*y[2]
    dy[1] = (beta)*y[0]*y[2] - mu*y[1]
    dy[2] = mu*y[1] - gamma*y[2]
    return dy

###############################################################################
# Simulate a deterministic SEIR system with parameters built to
# have a power-law scaling for the contact rate as in,
# "Semi-empirical power-law scaling of new infection rate to model
#  epidemic dynamics with inhomogeneous mixing."
#  Mathematical Biosciences 203 (2006) 301-318
# With this instance we also introduce a time varying transmission rate
# similar to that used in, "Modeling the spread of influenza among cities"
# by James M. Hyman and Tara LaForce 2003.

# Returns numpy array:
#    Xsim = (3)x(Ntimestep) first row S(t), second row I(t),
#           third row E(t).
# Variables are (S0,E0,I0,beta,mu,gamma,nu,alpha,c,w)
# S0 = initial susceptible ratio
# E0 = initial exposed ratio
# I0 = initial infected ratio
# R0 = initial recovered ratio, S0+E0+I0+R0=1
# beta = base transmission rate (S0->E0)
# mu = incubation rate (E0->I0)
# gamma = recovery rate (I0->R0)
# nu = contact power-law scaling
# alpha = amplitude of transmission rate variation
#         beta varies over [1-alpha,1+alpha]
# c = time of peak transmission rate
# w = width of ramp up and ramp down of transmission
def SEIRplusode(y0, time, beta, mu, gamma, nu, alpha, c, w):
    # y0 = 3x1 numpy array of initial conditions.
    # time = Ntimestep length numpy array
    # beta = scalar
    # mu = scalar
    # gamma = scalar

    Xsim = rk4(SEIRplus_D, y0, time, args=(beta,mu,gamma,nu,alpha,c,w,))
    Xsim = Xsim.transpose()
    return Xsim

# Derivative for SEIR-plus system.
def SEIRplus_D(y,t,beta,mu,gamma,nu,alpha,c,w):
    # y[0] = susceptible
    # y[1] = exposed
    # y[2] = infected/infectious
    dy = np.zeros((3,1))

    dy[0] = -(trans_rate(t,beta,alpha,c,w))*y[2]*math.pow(y[0],nu)
    dy[1] = (trans_rate(t,beta,alpha,c,w))*y[2]*math.pow(y[0],nu) - mu*y[1]
    dy[2] = mu*y[1] - gamma*y[2]
    return dy

###############################################################################
# Time variable transmission function used in several of the above models.
# This varies over [beta*(1-alpha),beta*(1+alpha)]
def trans_rate(t,beta,alpha,c,w):
    k = 5.0 # parameter controlling transition length
    m = 4.0 # parameter controlling smoothness
    if (math.fabs(2.0*(t-c)/w) < 1):
        rate = (2*math.pow((1-math.pow(math.fabs(2.0*(t-c)/w),k)),m)) - 1.0
    else:
        rate = -1.0

    return (beta*(1 + alpha*rate))

###############################################################################
###############################################################################
# rk4-algorithm used in several of the above simulations.
#
# Integrate an ODE specified by
# y' = f(y,t,args), y(t0) = y0, t0 < t < T
# Using fourth order Runge-Kutta on a specified time grid.

# f() must return a numpy column array of outputs of the same
# dimension as that of y. It must take a numpy column array of
# variable values y, a single float time t, and any other arguments.

# The output is a numpy array of y values at the times in t of
# dimensions (len(t),len(y0)).
def rk4(func, y0, t, args=()):
    output = np.zeros((len(t),len(y0)))
    output[0,:] = y0[:,0].copy()
    yiter = y0.copy()

    for i in range(len(t)-1):
        h = t[i+1] - t[i]
        k1 = h*func(yiter, t[i], *args)
        k2 = h*func(yiter + .5*k1, t[i] + .5*h, *args)
        k3 = h*func(yiter + .5*k2, t[i] + .5*h, *args)
        k4 = h*func(yiter + k3, t[i] + h, *args)
        yiter = yiter + 0.16666666*(k1 + 2*k2 + 2*k3 + k4)
        output[i+1,:] = yiter[:,0].copy()

    return output


import numpy as np

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

def init_matrix(size, init, std=None):

    input_size, output_size = size

    if init == 'zero':
        weights = np.zeros(shape=(input_size, output_size))

    elif init == 'sqrt_fan_in':
        sqrt_fan_in = np.sqrt(input_size)
        weights = np.random.uniform(low=-1.0/sqrt_fan_in, high=1.0/sqrt_fan_in, size=(input_size, output_size))

    elif init == 'glorot_uniform':
        limit = np.sqrt(6. / (input_size + output_size))
        weights = np.random.uniform(low=-limit, high=limit, size=(input_size, output_size))

    elif init == 'glorot_normal':
        scale = np.sqrt(2. / (input_size + output_size))
        weights = np.random.normal(loc=0.0, scale=scale, size=(input_size, output_size))

    elif init == 'alexnet':
        weights = np.random.normal(loc=0.0, scale=0.01, size=(input_size, output_size))

    elif init == 'normal':
        scale = std
        weights = np.random.normal(loc=0.0, scale=scale, size=(input_size, output_size))

    else:
        weights = np.random.normal(loc=0.0, scale=1.0, size=(input_size, output_size))

    return weights

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

def init_filters(size, init, std=None):

    fh, fw, fin, fout = size

    if init == 'zero':
        weights = np.zeros(shape=(fh, fw, fin, fout))

    elif init == 'sqrt_fan_in':
        assert (False)

    elif init == 'glorot_uniform':
        limit = np.sqrt(6. / (fh*fw*fin + fh*fw*fout))
        weights = np.random.uniform(low=-limit, high=limit, size=(fh, fw, fin, fout))

    elif init == 'glorot_normal':
        scale = np.sqrt(2. / (fh*fw*fin + fh*fw*fout))
        weights = np.random.normal(loc=0.0, scale=scale, size=(fh, fw, fin, fout))

    elif init == 'alexnet':
        weights = np.random.normal(loc=0.0, scale=0.01, size=(fh, fw, fin, fout))

    elif init == 'normal':
        scale = std
        weights = np.random.normal(loc=0.0, scale=scale, size=(fh, fw, fin, fout))

    else:
        assert (False)

    return weights

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

def init_local_filters(size, init, std=None):

    h, w, fh, fw, fin, fout = size

    if init == 'zero':
        weights = np.zeros(shape=(h, w, fh*fw*fin, fout))

    elif init == 'sqrt_fan_in':
        assert (False)

    elif init == 'glorot_uniform':
        limit = np.sqrt(6. / (fh*fw*fin + fh*fw*fout))
        weights = np.random.uniform(low=-limit, high=limit, size=(h, w, fh*fw*fin, fout))

    elif init == 'glorot_normal':
        scale = np.sqrt(2. / (fh*fw*fin + fh*fw*fout))
        weights = np.random.normal(loc=0.0, scale=scale, size=(h, w, fh*fw*fin, fout))

    elif init == 'alexnet':
        weights = np.random.normal(loc=0.0, scale=0.01, size=(h, w, fh*fw*fin, fout))

    elif init == 'normal':
        scale = std
        weights = np.random.normal(loc=0.0, scale=scale, size=(h, w, fh*fw*fin, fout))

    else:
        assert (False)

    return weights

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


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

import time, datetime
import numpy as np
import pandas as pd
import scipy.stats as sp
import streamlit as st
import models.binomial as bn
import models.blackscholes as bs
import models.montecarlo as mc

class Option() :
    def __init__(self, ul_asset, type, strike, expiry, style="EU") :
        self.type = type
        self.ul_asset = ul_asset
        self.strike = strike
        self.expiry = expiry
        self.style = style

    def days_to_expiry(self) :
        today = datetime.datetime.now()
        expiry = datetime.datetime.strptime(self.expiry, "%Y-%m-%d")
        days_to_expiry = (expiry - today).days
        return days_to_expiry

    def get_payoff(self, spot) :
        if (self.type == "call") :
            payoff = np.maximum(0, spot - self.strike)
        elif (self.type == "put") :
            payoff = np.maximum(0, self.strike - spot)
        return payoff

    def get_price(self, model, **kwargs) :
        if (model == 1) :
            price = bn.price(self, spot=kwargs['spot'], riskfree=kwargs['riskfree'], dividend=kwargs['dividend'], volatility=kwargs['volatility'], steps=kwargs['steps'])
        elif (model == 2) :
            price = bs.price(self, spot=kwargs['spot'], riskfree=kwargs['riskfree'], dividend=kwargs['dividend'], volatility=kwargs['volatility'])
        elif (model == 3) :
            price = mc.price(self, spot=kwargs['spot'], riskfree=kwargs['riskfree'], dividend=kwargs['dividend'], volatility=kwargs['volatility'], steps=kwargs['steps'], simulations=kwargs['simulations'])
        return price

    def snpdf(self, spot, riskfree, dividend, volatility) :
        snpdf = (1 / (np.sqrt(2 * np.pi))) * (np.exp((-bs.d1(self, spot, riskfree, dividend, volatility) ** 2) / 2))
        return snpdf

    def get_delta(self, spot, riskfree, dividend, volatility) :
        cdf = sp.norm(0, 1).cdf
        t = self.days_to_expiry() / 365
        if (self.type == "call") :
            delta = np.exp(-dividend * t) * cdf(bs.d1(self, spot, riskfree, dividend, volatility))
        elif (self.type == "put") :
            delta = np.exp(-dividend * t) * (cdf(bs.d1(self, spot, riskfree, dividend, volatility)) - 1)
        return delta

    def get_gamma(self, spot, riskfree, dividend, volatility) :
        t = self.days_to_expiry() / 365
        gamma = (np.exp(-dividend * t) / (spot * volatility * np.sqrt(t))) * self.snpdf(spot, riskfree, dividend, volatility)
        return gamma

    def get_vega(self, spot, riskfree, dividend, volatility) :
        t = self.days_to_expiry() / 365
        vega = (0.01 * spot * np.exp(-dividend * t) * np.sqrt(t)) * self.snpdf(spot, riskfree, dividend, volatility)
        return vega

    def get_theta(self, spot, riskfree, dividend, volatility) :
        cdf = sp.norm(0, 1).cdf
        t = self.days_to_expiry() / 365
        if (self.type == "call") :
            theta = (-(((spot * volatility * np.exp(-dividend * t)) / (2 * np.sqrt(t))) * self.snpdf(spot, riskfree, dividend, volatility)) - (riskfree * self.strike * np.exp(-riskfree * t) * cdf(bs.d2(self, spot, riskfree, dividend, volatility))) + (dividend * spot * np.exp(-dividend * t) * cdf(bs.d1(self, spot, riskfree, dividend, volatility)))) / 365
        elif (self.type == "put") :
            theta = (-(((spot * volatility * np.exp(-dividend * t)) / (2 * np.sqrt(t))) * self.snpdf(spot, riskfree, dividend, volatility)) + (riskfree * self.strike * np.exp(-riskfree * t) * cdf(-bs.d2(self, spot, riskfree, dividend, volatility))) - (dividend * spot * np.exp(-dividend * t) * cdf(-bs.d1(self, spot, riskfree, dividend, volatility)))) / 365
        return theta

    def get_rho(self, spot, riskfree, dividend, volatility) :
        cdf = sp.norm(0, 1).cdf
        t = self.days_to_expiry() / 365
        if (self.type == "call") :
            rho = 0.01 * self.strike * t * np.exp(-riskfree * t) * cdf(bs.d2(self, spot, riskfree, dividend, volatility))
        elif (self.type == "put") :
            rho = -0.01 * self.strike * t * np.exp(-riskfree * t) * cdf(-bs.d2(self, spot, riskfree, dividend, volatility))
        return rho

    def get_greeks(self, spot, riskfree, dividend, volatility) :
        greeks = {
            "delta" : self.get_delta(spot, riskfree, dividend, volatility),
            "gamma" : self.get_gamma(spot, riskfree, dividend, volatility),
            "vega" : self.get_vega(spot, riskfree, dividend, volatility),
            "theta" : self.get_theta(spot, riskfree, dividend, volatility),
            "rho" : self.get_rho(spot, riskfree, dividend, volatility)
        }
        return greeks

class Call(Option) :
    def __init__(self, ul_asset, strike, expiry, style) :
        Option.__init__(self, ul_asset, type, strike, expiry, style)
        self.type = "call"

class Put(Option) :
    def __init__(self, ul_asset, strike, expiry, style) :
        Option.__init__(self, ul_asset, type, strike, expiry, style)
        self.type = "put"

model = {1:"Binomial", 2:"Black & Scholes", 3:"Monte Carlo"}
model = st.sidebar.selectbox(label="Model", options=list(model.keys()), index=1, format_func=lambda x: model[x])

if (model == 1) : # Binomial
    st.sidebar.header("Model Parameters")
    steps = st.sidebar.number_input(label="Steps", min_value=1, value=4)
    simulations = 0
elif (model == 2) : # Black & Scholes
    steps = 0
    simulations = 0
elif (model == 3) : # Monte Carlo
    st.sidebar.header("Model Parameters")
    steps = st.sidebar.number_input(label="Steps", min_value=1, value=100)
    simulations = st.sidebar.number_input(label="Simulations", min_value=1, max_value=100000, value=100000)

def user_input_features() :
    st.sidebar.header("Option")
    type = st.sidebar.selectbox(label="Type", options=["Call", "Put"])
    ul_asset = st.sidebar.text_input(label="Underlying Asset", value="EURUSD")
    spot = st.sidebar.number_input(label="Spot", min_value=0.0, value=1.1850, step=0.0005, format="%.4f")
    strike = st.sidebar.number_input(label="Strike", min_value=0.0, value=1.1650, step=0.0005, format="%.4f")
    expiry = st.sidebar.date_input(label="Expiry", value=datetime.date(2021, 12, 31))
    style = st.sidebar.selectbox(label="Style", options=["EU", "US"], index=0)

    st.sidebar.header("Market")
    riskfree = st.sidebar.slider(label="Riskfree Rate", min_value=0.0, max_value=10.0, value=1.0, step=0.05)
    dividend = st.sidebar.number_input(label="Dividend", min_value=0.0, value=0.0)
    volatility = st.sidebar.slider(label="Implied Volatility", min_value=0.0, max_value=1.0, value=0.2, step=0.0005)

    data = {
        "type" : type.lower(),
        "ul_asset" : ul_asset,
        "strike" : strike,
        "spot" : spot,
        "expiry" : expiry.strftime("%Y-%m-%d"),
        "style" : style,
        "riskfree" : riskfree,
        "dividend" : dividend,
        "volatility" : volatility
    }
    features = pd.DataFrame(data, index=[0])
    return features

st.header("User Input Parameters")
st.subheader("Option")
features = user_input_features()
st.write(features[['type', 'ul_asset', 'strike', 'spot', 'expiry', 'style']])
st.subheader("Market")
st.write(features[['riskfree', 'dividend', 'volatility']])

spot = features['spot'][0]
riskfree = features['riskfree'][0]
dividend = features['dividend'][0]
volatility = features['volatility'][0]

option = Option(
    ul_asset=features['ul_asset'][0],
    type=features['type'][0],
    strike=features['strike'][0],
    expiry=features['expiry'][0],
    style=features['style'][0]
)

st.header("Output")
payoff = {"payoff" : float(option.get_payoff(spot=spot))}
price = {"price" : float(option.get_price(model=model, spot=spot, riskfree=riskfree, dividend=dividend, volatility=volatility, steps=steps, simulations=simulations))}
greeks = option.get_greeks(spot=spot, riskfree=riskfree, dividend=dividend, volatility=volatility)
output = {**payoff, **price, **greeks}
df = pd.DataFrame(output, index=[0])
st.write(df)

def debug() :
    start = time.time()
    spot = 100.

    riskfree = .05
    dividend = 0.
    volatility = .2

    c = Call("INDEX", 99., "2020-12-31", "EU")

    p_bn = float(c.get_price(1, spot=spot, riskfree=riskfree, dividend=dividend, volatility=volatility, steps=4))
    t_bn = time.time() - start
    print(f"BN. {p_bn:.6f} ({t_bn:.6f} secs)")

    p_bs = float(c.get_price(2, spot=spot, riskfree=riskfree, dividend=dividend, volatility=volatility))
    t_bs = time.time() - start
    print(f"BS. {p_bs:.6f} ({t_bs:.6f} secs)")

    p_mc = float(c.get_price(3, spot=spot, riskfree=riskfree, dividend=dividend, volatility=volatility, steps=100, simulations=10000))
    t_mc = time.time() - start
    print(f"MC. {p_mc:.6f} ({t_mc:.6f} secs)")
    return

def main() :
    pass

if __name__ == "__main__" :
    main()


import numpy as np

def constitutive(E, nu, problem_type):
    """Calculate the constitutive matrix for the plane elasticity problem
    Inputs:
    1) E = Young's Modulus (N/m^2)
    2) nu = Poisson's ratio
    3) problem_type = type of problem, plane stress or plane strain
    problem_type = 0 (plane stress)
    problem_type = 1 (plane strain)

    Output:
    C = 3x3 constitutive matrix for the given material"""

    C = np.eye(3)
    if problem_type == 0:
        const = E/(1 - nu**2)
        C[0, 1] = nu
        C[2, 2] = (1-nu)/2
        C[1, 0] = nu

    else:
        const = E/((1+nu)*(1-2*nu))
        C[0, 0] = 1 - nu
        C[1, 1] = C[0, 0]
        C[0, 1] = nu
        C[1, 0] = C[0, 1]
        C[2, 2] = (1 - 2*nu)/2

    C = const * C
    return C


"""Methods based on multiplying the gradient by a diagonal definite-positive matrix"""
import numpy as np

from optimus.types import DirectionMethod
from optimus.types import Function

class DiagonalQuasiHessian(DirectionMethod):
    """Direction is gradient multiplied by an inverse diagonal-only Hessian."""

    def __call__(
        self, parameters: np.ndarray, objective_function: Function
    ) -> np.ndarray:
        gradient = objective_function.gradient(parameters)
        size = gradient.size
        self_second_partial_derivatives = np.array(
            [
                objective_function.partial_second_derivative(parameters, i, i)
                for i in range(size)
            ]
        )
        return gradient / self_second_partial_derivatives