import math

import numpy as np
from numba import jit

from ..util.derivatives import numerical_gradients as ng

@jit
def sigmoid_prob(x, A, B):
    return 1./(1+math.exp(A*x+B))

@jit
def label(f, threshold=0.):
    if f == threshold:
        return 0
    else:
        return 1 if f > threshold else -1

@jit
def entropy_error_fcn(scores, A, B):
    fcnval = 0.
    for score in scores:
        y = label(score)
        t = (y+1) / 2
        p = sigmoid_prob(score, A, B)
        if p > 0 and p < 1:
            fcnval += - t*math.log(p) - (1-t)*math.log(1-p)
        elif p == 0.:
            fcnval += - (1-t)*math.log(1-p)
        elif p == 1.:
            fcnval += - t*math.log(p)
    return fcnval

@jit
def gradient_search_min_entropy_error_fcn(scores, tol=1e-1, sigma=0.1):
    max_score = max(scores)
    min_score = min(scores)
    initA = -30. / (max_score - min_score)
    initB = -0.5*(max_score+min_score) * initA

    A = initA
    B = initB
    curr_fcn_val = entropy_error_fcn(scores, A, B)
    converged = False
    while not converged:
        dfA, dfB = ng.tensor_gradient(lambda X: entropy_error_fcn(scores, X[0], X[1]),
                                      np.array([A, B]),
                                      tol=tol)
        new_fcn_val = entropy_error_fcn(scores, A-sigma*dfA, B-sigma*dfB)
        if abs(new_fcn_val-curr_fcn_val) < tol:
            converged=True
        curr_fcn_val = new_fcn_val
        A = A-sigma*dfA
        B = B-sigma*dfB

    return A, B


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

# # Álgebra Linear Computacional básica

# ## Objetivos
#
# - Associar conceitos abstratos de Álgebra Linear a estruturas computacionais;
# - Realizar operações básicas com vetores e matrizes;
# - Saber como resolver sistemas lineares de pequeno porte;
# - Calcular autovalores e autovetores em matrizes reais;

# ## Matrizes e vetores
#
# Uma matriz ${\bf A}$ de ordem $m \times n$ pode ser escrita como:
#
# $${\bf A} =
# \begin{bmatrix}
# a_{11} & a_{12} & \ldots & a_{1n} \\
# a_{21} & a_{22} & \ldots & a_{2n} \\
# \vdots & \vdots & \ddots & \vdots \\
# a_{m1} & a_{m2} & \ldots & a_{mn}
# \end{bmatrix}$$
#

# As colunas de uma matriz com $m$ linhas correspondem a $n$ vetores $\vec{v}_1, \vec{v}_2, \ldots,\vec{v}_n$, de maneira que
#
# $${\bf A} =
# \begin{bmatrix}
# \vec{v}_1 & \vec{v}_2 & \ldots & \vec{v}_n \\
# \end{bmatrix}$$
#
# é uma representação equivalente para a matriz anterior.

# Em Python, usamos o módulo _numpy_ para trabalhar com matrizes e vetores. Vetores são _arrays_ 1D, ao passo que matrizes são _arrays_ 2D, ou seja, um "_array_ de _arrays_".

# **Exemplo.** Represente computacionalmente os vetores do $\mathbb{R}^3$ a seguir:
#
# - $\vec{u} = 3\vec{i} - 2\vec{j} + 9\vec{k}$
# - $\vec{v} = -2\vec{i} + 4\vec{j}$
# - $\vec{w} = \vec{i}$

# In[1]:

import numpy as np

u = np.array([3,-2,9])
v = np.array([-2,4,0])
w = np.array([1,0,0])
print(u), print(v), print(w);

# **Exemplo.** Represente computacionalmente a matriz 3 x 3 dada por
#
# $${\bf A} =
# \begin{bmatrix}
# \vec{u} & \vec{v} & \vec{w} \\
# \end{bmatrix}$$

# Observe que os vetores devem ser escritos como "coluna".

# In[2]:

A = np.array([[3,-2,1],[-2,4,0],[9,0,0]])
print(A)

# **Exemplo.** Represente computacionalmente a matriz
#
# $$
# \begin{bmatrix}
# 2 & -2 \\
# 4 & 1 \\
# 2 & 1
# \end{bmatrix}$$
#

# Vamos escrever linha por linha.

# In[3]:

L1 = np.array([2,-2]) # linha 1
L2 = np.array([4,1]) # linha 2
L3 = np.array([2,1]) # linha 3

A2 = np.array([L1,L2,L3]) # lista de listas
print(A2)

# Diretamente, poderíamos também definir:

# In[4]:

A3 = np.array([[2,-2],[4,1],[2,1]])
print(A3)

# Note que cada lista representa uma linha.

# ### Transposição
#
# Matrizes e vetores podem ser transpostos com `.T`:

# In[5]:

A2T = A2.T
print(A2T)

# Assim, com as variáveis antes definidas, poderíamos, equivalentemente, fazer para ${\bf A}$:

# In[6]:

# modo 2: matriz transposta
At = np.array([u,v,w]).T
print(At)

# ### Teste de igualdade
#
# Podemos verificar a igualdade entre matrizes como

# In[7]:

A == At

# No caso de vetores:

# In[8]:

# vetor "linha" não difere
# do vetor "coluna"
u == u.T

# ## Operações fundamentais

# ### Adição e subtração
#
# A adição (subtração) de matrizes e vetores pode ser realizada de modo usual com computação vetorizada.

# **Exemplo:** $\vec{u} \pm \vec{v}$

# In[9]:

# adição
ad = u + v
print(ad)

# subtração
sub = u - v
print(sub)

# **Exemplo:** $\bf{A} \pm \bf{B}$, com
#
# $${\bf B} =
# \begin{bmatrix}
# \vec{u} & 2\vec{u} & 3\vec{v} \\
# \end{bmatrix}$$

# In[10]:

# adição

B = np.array([u,2*u,3*v]).T

print(B)

ad2 = A + B
print(ad2)

sub2 = A - B
print(sub2)

# ### Produto interno
#
# O produto interno $\langle \vec{u}, \vec{v}\rangle$ é computado com `.dot`:

# In[11]:

pi = np.dot(u,v)
print(pi)

pi2 = np.dot(np.array([3,1]),np.array([-1,-1]))
print(pi2)

# ### Norma de vetor
#
# A norma $||\vec{u}||$ de um vetor $\vec{u}$ é calculada como:

# In[12]:

np.sqrt(np.dot(u,u))

# ### Produto de matrizes
#
# O produto $\bf{A}\bf{B}$ entre matrizes bidimensionais pode ser calculado com `np.dot`, mas recomenda-se usar `np.matmul`.

# In[13]:

# não tem o mesmo efeito para
# matrizes A e B de tamanhos arbitrários
np.dot(A,B)

# In[14]:

# uso recomendado para a operação tradicional
np.matmul(A,B)

# ### Produto entre matriz e vetor
#
# Neste caso, sendo ${\vec{\vec A}}$ (dois símbolos indicam que a matriz é uma grandeza de ordem 2, ao passo que o vetor é de ordem 1 e aqui usamos para consistência de notação) e ${\vec{b}}$ uma matriz $m \times n$ e um vetor $n \times 1$, respectivamente, o produto $\vec{\vec{A}}\vec{b}$ é dado por:

# In[15]:

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

np.dot(A,b)

# ## Demais operações com `numpy.linalg`
#
# Para outras operações, devemos utilizar o submódulo `numpy.linalg`. Para importá-lo com o _alias_ `lin`, fazemos:

# In[16]:

import numpy.linalg as lin

# ### Determinante
#
# O determinante de ${\bf A}$ é dado por $\det({\bf A})$ e pode ser computado pela função `det`.

# In[17]:

# calculando o determinante da matriz
det = lin.det(A)
print(det)

# ### Inversa de uma matriz
#
# A inversa de uma matriz é dada por ${\bf A}^{-1}$, onde ${\bf A}{\bf A}^{-1}={\bf I}$, e ${\bf I}$ é a matriz identidade.
# Para usar esta função, devemos fazer:

# In[18]:

B2 = np.array([[1,2,3],
              [2,3,4],
              [1,2,0]])

B3 = lin.inv(B2)
print(np.matmul(B3,B2))

# ### Soluções de sistemas lineares
#
# Para resolver sistemas lineares, devemos escrever as equações em forma de matriz, ou seja, ${\vec{\vec A}}\vec{x} = \vec{b}$ e utilizar `solve`.

# **Exemplo:** Resolva o sistema linear abaixo:
#
# $$\begin{cases}
# ax + by = c \\
# dx + ey = f
# \end{cases}$$
#
# para $a = -4$, $b = 1$, $c = 1/2$, $d = 3$, $e = 5$ e $f = 10$,

# In[19]:

A = np.array([[-4,1],[1/2,3]])

b = np.array([5,10]).T

# solução
x = lin.solve(A, b)
print(x)

# ### Inversa de matriz
#
# A inversa de uma matriz (faça esta operação apenas para matrizes quadradas de pequena dimensão) pode ser encontrada como:

# In[20]:

Ainv = lin.inv(A)
print(Ainv)

# Para realizar uma "prova real" da solução do sistema anterior, poderíamos fazer:

# In[21]:

x2 = np.dot(lin.inv(A), b)
print(x2)

# Note, entretanto que:

# In[22]:

x == x2

# Isto ocorre devido a erros numéricos. Um teste mais adequado deve computar a norma do vetor "erro", dado por ${\bf e} = \bf{b} - \bf{A}\bf{x}$. A norma pode ser calculada diretamente com:

# In[23]:

e = b - np.dot(A,x)
lin.norm(e)

# Isto é, esperamos que $||{\bf e}|| \approx 0$ quando a solução do sistema for exata, a menos de erros numéricos.

# ## Algumas matrizes especiais

# ### Nula
#
# Para criar uma matriz nula de ordem _m x n_, usamos `zeros`.

# In[24]:

m,n = 3,4
np.zeros((m,n))

# ### Identidade
#
# Uma matriz identidade (quadrada) de ordem _p_ é criada com `eye`.

# In[25]:

p = 4
np.eye(p)

# ### Matriz de "uns"
#
# Uma matriz composta apenas de valores 1 de ordem _m x n_ pode ser criada com `ones`:

# In[26]:

np.ones((3,5))

# ### Triangular inferior
#
# A matriz triangular inferior de uma dada matriz pode ser criada com `tril`. Note que podemos também defini-la explicitamente, linha a linha.

# In[27]:

# os valores correspondentes
# são zerados
np.tril(B)

# ### Triangular superior
#
# A matriz triangular superior de uma dada matriz pode ser criada com `triu`. Note que podemos também defini-la explicitamente, linha a linha.

# In[28]:

np.triu(B)

# **Exercício.** Por que há dois valores `False` no teste a seguir?

# In[29]:

B == np.tril(B) + np.triu(B)

# ## Autovalores e autovetores
#
# Um vetor ${\bf v} \in V$, ${\bf v} \neq {\bf 0}$ é vetor próprio de ${\bf A}$ se existir $\lambda \in \mathbb{R}$ tal que
#
# $${\bf Av}=\lambda {\bf v}.$$
#
# O número real $\lambda$ é denominado valor próprio (autovalor) de ${\bf A}$ associado ao vetor próprio (autovetor) ${\bf v}$.

# In[30]:

A = np.array([[2,1],
             [1,-5]])

w, v = lin.eig(A)
a,b = w

# autovalores
print(a,b)

# autovetor 1
print(v[:,0])

# autovetor 2
print(v[:,1])

# ## Somas e valores extremos
#
# Podemos calcular somas de elementos de matrizes e vetores de maneiras diferentes. Para matrizes, em particular, há soma total, por linha, ou por coluna.

# In[31]:

a = np.array([1,-2,-3,10])

# soma de todos os elementos
np.sum(a)

# In[32]:

# modo alternativo
a.sum()

# In[33]:

# soma total de matriz
O = np.ones((5,3))

np.sum(O)

# In[34]:

# modo alternativo
O.sum()

# In[35]:

# soma por linha
np.sum(O,axis=0)

# In[36]:

# soma por coluna
np.sum(O,axis=1)

# Valores máximos e mínimos, absolutos ou não, também podem ser computados com funções simples.

# In[37]:

# min
np.min(a)

# In[38]:

# max
np.max(a)

# In[39]:

# modo alternativo
a.min()

# In[40]:

a.max()

# In[41]:

# mínimo absoluto
np.abs(a).min()

# In[42]:

# máximo absoluto
np.abs(a).max()

# In[43]:

O2 = np.array([[-4,5],[2,7]])

# min
np.min(O2)

# In[44]:

# max
np.max(O2)

# In[45]:

O2.min()

# In[46]:

O2.max()

# In[47]:

np.abs(O2).min()

# In[48]:

np.abs(O2).max()


import numpy as np

def Jacobian(func,y,fev=None, args=()):
    '''
    Forward differentiation formula to estimate the jacobian.
    func: R^n --> R^n
    '''
    fe_tot = 0
    if(fev is None):
        fev=func(y,args)
        fe_tot +=1

    n=len(y)
    J= np.zeros((n,n))
    # The step is the machine precision * 10^2
    # It is problem dependent... (add as ~tolerance)
    # Even better, copy how h is chosen by scipy/integrate/odepack/prja.f (lines 80-100)
    # Explained in section 3.4.5 of Description and Use of LSODE, the Livermore Solver for ODEs
    h = np.finfo(np.dtype(type(y[0]))).eps*1e2
    for i in range(n):
        yi = y[i]
        y[i] += h
        J[:,i] = 1/h*(func(y,args)-fev)
        y[i]=yi
        fe_tot +=1

    return (J,fe_tot)


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

def costFunctionReg(theta, x, y, _lambda):
    import numpy as np
    from ex2_logistic_regression.sigmoid import sigmoid
    h = sigmoid(np.dot(x, theta.T))

    m, _ = np.shape(x)
    cost = (np.dot(-y, np.log(h)) - np.dot(1 - y, (np.log(1 - h))))
    reg_param = sum(np.power(theta[1:], 2)) * _lambda / 2 / m
    return (cost + reg_param) / m

def compute_grad_reg(theta, x, y, _lambda):
    import numpy as np
    m, n = np.shape(x)
    from ex2_logistic_regression.sigmoid import sigmoid

    h = sigmoid(np.dot(x, theta.T))
    t = h - y
    grad = [sum(np.dot(t, x)) / m] * n

    for c in range(1, n):
        grad[c] = grad[c] + _lambda / m * theta[c]
    return grad


import matplotlib.pyplot as plt
import numpy as np
from sklearn import datasets

class PCA:
    def __init__(self, n_components):
        self.n_components = n_components
        self.components, self.mean = None, None

    def fit(self, X):
        # Mean centering
        self.mean = np.mean(X, axis=0)
        X = X - self.mean

        # Get the covariance
        cov = np.cov(X.T)

        # eigenvalues, eigenvectors
        eigenvalues, eigenvectors = np.linalg.eig(cov)

        # Eigen vector sort
        eigenvectors = eigenvectors.T
        idxs = np.argsort(eigenvalues)[::-1]

        eigenvectors = eigenvectors[idxs]

        # store first n eigenvectors
        self.components = eigenvectors[0: self.n_components]

    def transform(self, X):
        X = X - self.mean
        return np.dot(X, self.components.T)

# Testing
if __name__ == "__main__":
    data = datasets.load_iris()
    X = data.data
    y = data.target

    # Project the data onto the 2 primary principal components
    pca = PCA(2)
    pca.fit(X)
    X_projected = pca.transform(X)

    # Display the shapes
    print("Shape of X:", X.shape)
    print("Shape of transformed X:", X_projected.shape)

    # Get the projections
    x1, x2 = X_projected[:, 0], X_projected[:, 1]

    # Create the graph
    plt.scatter(x1, x2, c=y, edgecolor="none", alpha=0.8, cmap=plt.cm.get_cmap("viridis", 3))

    plt.xlabel("Principal Component 1")
    plt.ylabel("Principal Component 2")
    plt.colorbar()
    plt.show()


import pandas as pd
import numpy as np
#
from pyta.helper import er

def kaufmans_adaptive_moving_average(c: pd.Series, n: int = 10, n_fast: int = 2, n_slow: int = 30) -> pd.Series:
    er_ = er(c, n)
    # SC = [ER x (2/(2+1) - 2/(30+1)) + 2/(30+1)]2
    fast_ema: float = 2 / (n_fast + 1)
    slow_ema: float = 2 / (n_slow + 1)
    sc = (er_ * (fast_ema - slow_ema) + slow_ema) ** 2
    kama = pd.Series([np.nan] * (len(c) - 1))
    # First kama entry is close price.
    kama[n - 1] = c[n - 1]
    # KAMA = Prior KAMA + SC x (Price - Prior KAMA)
    for i in range(n, len(c)):
        kama[i] = (kama[i - 1] + sc[i] * (c[i] - kama[i - 1]))
    return kama

kaufmans_adaptive_moving_average.__doc__ = """Kaufman's Adaptive Moving Average (KAMA)

A moving average that accounts for volatility and market noise.

Source:
    https://school.stockcharts.com/doku.php?id=technical_indicators:kaufman_s_adaptive_moving_average

Formula:
    ER = Change / Volatility
    SC = [ER * (2 / (n_fast + 1) - 2 / (n_slow + 1)) + 2 / (n_slow + 1)] ** 2
    KAMA = Prior KAMA + SC x (Price - Prior KAMA)

Arguments:
    c (pdSeries) : Close series.
    n (int)      : Periods for ER [Default: 10]
    n_fast (int) : Periods for fast SC [Default: 2]
    n_slow (int) : Periods for slow SC [Default: 30]

Returns:
    pdSeries
"""


import scipy.special as sc
from scipy import integrate
from numpy import exp

yp = 40
yc = 50
alpha = 0.2
beta = 1
t = 7
delta_y = 1.4

shape = alpha*t
gamma_shape = sc.gamma(shape)
gamma_shape_1 = sc.gamma(shape-1)
gammainc_yp = sc.gammaincc(shape-1, yp)*gamma_shape_1  # because funct calculates normalized value; divided by gamma(a)
k = (beta**shape)/gamma_shape

# Creating list of values to approximate f(y-yp) and estimate the integral using trapezoidal rule
n = 50
step = (yc-yp)/n
Y = [yp+(i*step) for i in range(n+1)]

def u(y0):  # primitive found at https://www.integral-calculator.com/
    return k*((sc.gammaincc(shape-1, yp-y0)*gamma_shape_1)-gammainc_yp)

def g(x):
    return k*((x**(shape-1))*exp(-beta*x))

def G(x):
    return sc.gammaincc(shape, beta*x)  # the division by gamma_shape is included

def fy(y):
    f = lambda y0: u(y0)*g(y-y0)/(1+G(yp-y0))
    fy_yp = integrate.quad(f, yp-delta_y, yp)
    print("Error estimated for integral in ", y, " is ", fy_yp[1])   # quick check
    return fy_yp[0]

if __name__ == "__main__":
    fy_array = list(map(fy, Y))
    ps = integrate.simps(fy_array, dx=step)
    print("ps = ", ps)


import numpy as np

def ra_to_deg(hour: float, minute: float, second: float) -> float:
    """ Convert RA from (hr, min, sec) -> degree """
    return  (hour + minute / 60. + second / 3600.) * 15.

def dec_to_deg(degree: float, minute: float, second: float) -> float:
    """ Convert Dec from (deg, arcmin, arcsec) -> degree """
    deg_sign = 1. if (degree + np.absolute(degree)) > 0 else - 1.
    return  deg_sign * (np.absolute(degree) + minute / 60. + second / 3600.)

def dist_modulus_to_distance(dm: float) -> float:
    """ Convert distance modulus -> distance (pc)

    : dm : distance modulus
    """
    return  10.**(dm / 5. + 1.)

def distance_to_dist_modulus(distance: float) -> float:
    """ Convert distance (pc) -> distance modulus """
    return  5. * np.log10(distance) - 5.


# This performs a centered (or unbiased) root mean squared error (cRMSE)
# calculation, essentially calculate rmse using de-meaned data:
# rmse = root(mean( ( (x - mean(x)) - (y - mean(y)) )^2 ))
#
# Joseph Lee <joseph.lee at pnnl.gov>

import numpy as np

class crmse:

    def compute(self, x, y):

        x_demean = x - np.mean(x)
        y_demean = y - np.mean(y)

        return float(np.sqrt(np.mean((x_demean - y_demean)**2)))


import math
import numpy as np
import scipy

def cosine_sim(vecA, vecB):
  """
  Return the cosine simularity between two vectors vecA and vecB.
  @return integer between zero and one inclusive. One indicates identicial
  """
  csim = np.dot(vecA, vecB) / (np.linalg.norm(vecA) * np.linalg.norm(vecB))
  if np.isnan(np.sum(csim)):
    return 0
  return csim

def angular_similarity(vecA, vecB):
  """
  Return the angular simularity between two vectors vecA and vecB.
  @return integer between zero and one inclusive. One indicates identicial
  """
  #Ensure cosine is between zero and one
  cosdist=max(0,min(scipy.spatial.distance.cosine(vecA,vecB),1))
  return 1-math.acos(1-cosdist)/math.pi


import numpy as np
import random
random.seed(0)

class Perceptron:
    def __init__(self, eps=0.01, max_iters=2000):
        self.eps = eps
        # self.best_params = None
        self.losses = None
        self.n_iters = None
        self.activation_func=self._step_func
        self.max_iters = max_iters
        self.weights = None  #size=(n_iter,max_iters)
        self.bias= None #size=(1,max_iters)

    def _step_func(self, X):
        return np.where(X >= 0, 1, -1)

    def compute_loss(self, X, y, w, b): # Ls = |xi ; hs(xi) != yi| / |s|
        n = len(X)
        S = 0

        for i in range(n):

            linear_output = np.dot(X[i], w) + b
            y_predicted =self.activation_func(linear_output)
            if y_predicted != y[i]:
                S += 1

        Ls = S / n

        return Ls

    def fit(self, X, y):

        n_samples,n_features = X.shape

        # Init weights and bias
        # self.weights=np.zeros(n_features)
        # self.weights=[random.random()*1 for i in range(n_features)]
        # self.weights=np.ones(n_features)
        self._initialise_weights(X)
        self.bias= 0

        # Compute the loss function
        loss = self.compute_loss(X, y, self.weights,self.bias)
        n_iters = 0
        Weights = [(self.weights,self.bias)]
        self.losses = [loss]

        while loss >= self.eps: # Stop condition

            for i in range(n_samples):
                # Compute the linear output
                linear_output = np.dot(X[i], self.weights) + self.bias

                # Adjusting weights and bias
                if self.activation_func(linear_output) * y[i] < 0: # If the point is misclassified
                    self.weights = self.weights + y[i] * X[i]   # w = w + yi.xi
                    self.bias = self.bias + y[i]                # b = b + yi

            Weights.append((self.weights,self.bias))

            loss = self.compute_loss(X, y, self.weights, self.bias )

            self.losses.append(loss)

            n_iters = n_iters + 1

            if n_iters >= self.max_iters:
                break

        self.n_iters = n_iters
        print("le nombre d'iterations est: ", self.n_iters)

        return [Weights,self.losses,self.n_iters]

    def predict(self, X):
        linear_output= np.dot(X, self.weights)+self.bias
        y_predicted= self.activation_func(linear_output)
        return y_predicted

    def _initialise_weights(self, X):
        """ Initialise weigths - normal distribution sample with standard dev 0.01 """
        random_gen = np.random.RandomState(1)
        self.weights = random_gen.normal(loc = 0.0, scale = 0.01, size = X.shape[1])
        return self


## Speed Comparison ##

# This program is to show the advantage of using numpy arrays for matrix functions
# and how they compare to lists. Increase the x/y values to make the matrix larger.
# numpy is much faster.

# Results depend on the system, but here is some rough numbers based on mine
# Eg. 200x200 matrix = ~1115 times faster
# Eg. 2000x2000 matrix = ~

import numpy as np
# import cupy as np # Used for cuda support
from datetime import datetime

# matrix size
x = 2000
y = 2000

# create an x*y matrix
A = np.random.choice(10, size=(x,y))
# create an x*y matrix
B = np.random.choice(10, size=(x,y))
# create an x*y matrix for the results
result= np.zeros((x,y))

# take timestamp
t0 = datetime.now()
# Slow calculation
for i in range(len(A)):
  # iterating by coloum by B
  for j in range(len(B[0])):
      # iterating by rows of B
      for k in range(len(B)):
          result[i][j] += A[i][k] * B[k][j]
# read duration
dt1 = datetime.now() - t0

t0 = datetime.now()
#Fast calculation
result = A @ B # can also be done with np.dot(A,B) or matmul
dt2 = datetime.now() - t0

print("Time for slow method:", dt1.total_seconds())
print("Time for fast method:", dt2.total_seconds())
print("(slow / fast) Factor:", dt1.total_seconds() / dt2.total_seconds())


"""
Title: Finding the Total Charge of an Equal Charge Electric Dipole using the Two-dimensional Trapezoidal Method

Solution to Problem Set 2, Problem 3
~ Arsh R. Nadkarni

To run:
python charge_dipole_2_3.py

"""
# import libraries
import math
import numpy as np
import matplotlib.pyplot as plt

def totalcharge(n):
    # function to be integrated (integrand)
    def f(theta,phi):
        return 2*np.sin(theta)+0.1*np.cos(phi)*(np.sin(theta))**2
    # construct the trapezoidRule function based on Problem 2
    def trapezoidalRule(f,n):
        a = 0
        b = 2*np.pi
        c = 0
        d = np.pi
        hx = (b - a)/n
        hy = (d - a)/n
        I1 = f(a, c) + f(b, d)
        I2 = 0
        i = 0
        j = 0
        for i in range (n+1):
            for j in range (n+1):
                x = a + (i * hx)
                y = a + (j * hy)
                I2 += f(x, y)
                i += 1
                j += 1
        I = ((hx/2) * (hy/2) * (I1 + (2 * I2)))
        return I
    # function to return the total charge
    def answer(f,n):
        q=1.60217662*1e-19
        global ans
        ans=(q/(4*np.pi))*(trapezoidalRule(f,n))
        return ans
    return answer(f,n)

# function to evaluate the error and plot the relative error as a function of n on a log-log plot, comparing it with the expected order of convergence
def B():
    t = 4
    N = np.zeros(t)
    err = np.zeros(t)
    I = np.zeros(t)
    aval = -1.56*1e-19 # analytical value of the integral (Calculated by Hand)
    for i in range(t):
        n = 50 * (2**i) # n= 50,100,200,400
        N[i] = n
        I[i] = totalcharge(n)
        err[i] = abs(aval-I[i])
    figure,a = plt.subplots()
    a.loglog(N,err,'r--',label='Relative Error',lw=3)
    a.loglog(N,1/N**2,'b-',label='Expected Convergence (1/$n^2$)',lw=3)
    a.set_xlabel('log(n)',fontweight='bold')
    a.set_ylabel('log(Relative Error)',fontweight='bold')
    plt.legend()
    plt.title('Relative Error as a function of n: log-log Plot', fontweight='bold')
    plt.show()
    figure,b = plt.subplots()
    b.plot(N,err,'r--',label='Relative Error',lw=3)
    b.plot(N,1/N**2,'b-',label='Expected Convergence (1/$n^2$)',lw=3)
    b.set_xlabel('n',fontweight='bold')
    b.set_ylabel('Relative Error',fontweight='bold')
    plt.legend()
    plt.title('Relative Error as a function of n', fontweight='bold')
    plt.show()
B()

"""
The value obtained numerically converges to the expected result (O(1/n^2)) at n = 400 approximately.
This can be verified using the plots above.

"""

Problem Sets/Problem Set 2/charge_dipole_2_3.py


"""
    Автор: Орел Максим
    Группа: КБ-161
    Вариант: 11 (task 1: var 2; task 2: var 11)
    Дата создания: 6/05/2018
    Python Version: 3.6
"""
import matplotlib.pyplot as plt
import numpy as np

# http://mathprofi.ru/metod_prjamougolnikov.html
# http://mathprofi.ru/formula_simpsona_metod_trapecij.html

a = -1
b = 2
h = 0.1
h2 = [2, 4, 6, 8, 10, 10000]

def pauline(point):
    return 1 + 0.9 * point + 0.8 * point ** 2 + 0.7 * point ** 3 + 0.5 * point ** 4

def rectangle_left(left, right):
    return (right - left) * pauline(left)

def rectangle_right(left, right):
    return (right - left) * pauline(right)

def trapeze(left, right):
    return (right - left) / 2 * (pauline(left) + pauline(right))

def simpson(left, right):
    return (right - left) / 6 * (pauline(left) + 4 * pauline((left + right) / 2) + pauline(right))

def f(point):
    return (2 * point) ** 3 * np.cos(point)

def trapeze2(left, right):
    return (right - left) / 2 * (f(left) + f(right))

def simpson2(left, right):
    return (right - left) / 6 * (f(left) + 4 * f((left + right) / 2) + f(right))

def sum(coef, left, right):
    return_sum = 0
    for i in coef:
        return_sum += f((right + left) / 2 + ((right - left) / 2) * i)
    return return_sum

if __name__ == "__main__":
    try:
        x = np.linspace(a, b)
        plt.plot(x, pauline(x))
        plt.grid()
        plt.show()

        # task 1
        print('Метод левого прямоугольника: {0}'.format(rectangle_left(a, b)))
        print('Метод правого прямоугольника: {0}'.format(rectangle_right(a, b)))
        print('Метод трапеции: {0}'.format(trapeze(a, b)))
        print('Метод Симпсона: {0}'.format(simpson(a, b)))

        points = np.arange(a, b + h, h)
        A = [0, 0, 0, 0]
        for i in range(0, len(points) - 1):
            A[0] += rectangle_left(points[i], points[i + 1])
            A[1] += rectangle_right(points[i], points[i + 1])
            A[2] += trapeze(points[i], points[i + 1])
            A[3] += simpson(points[i], points[i + 1])

        print('Метод левого прямоугольника (обобщённый): {0}'.format(A[0]))
        print('Метод правого прямоугольника (обобщённый): {0}'.format(A[1]))
        print('Метод трапеции (обобщённый): {0}'.format(A[2]))
        print('Метод Симпсона (обобщённый): {0}'.format(A[3]))

        # task 2
        x = np.linspace(a, b)
        plt.plot(x, f(x))
        plt.grid()
        plt.show()

        print()
        for k in h2:
            h = (b - a) / k
            points = np.arange(a, b + h, h)
            A = [0, 0]
            for i in range(0, len(points) - 1):
                A[0] += trapeze2(points[i], points[i + 1])
                A[1] += simpson2(points[i], points[i + 1])
            print('Метод трапеций (h = (b-a)/{0}): {1}'.format(k, A[0]))
            print('Метод Симпсона (h = (b-a)/{0}): {1}'.format(k, A[1]))
            print()

        coef = [0.707197, 0, -0.707197]
        answer = sum(coef, a, b) * (b - a) / 3
        print('Чебышев (n = 3):', answer)

        coef = [0.794654, 0.187592, -0.187592, -0.794654]
        answer = sum(coef, a, b) * (b - a) / 4
        print('Чебышев (n = 4):', answer)

        coef = [0.832498, 0.374541, 0, -0.374541, -0.832498]
        answer = sum(coef, a, b) * (b - a) / 5
        print('Чебышев (n = 5):', answer)

        # answer must be near -2.44

    except Exception as e:
        print(e)


import numpy as np

def classic_multi(A, B):
    if len(B) != len(A[0]):
        print("Different dimension of the matrics")
        return

    n = len(A)
    m = len(A[0])
    t = len(B[0])

    answer = [[0 for i in range(t)] for j in range(n)]
    for i in range(n):
        for j in range(m):
            for k in range(t):
                answer[i][k] += A[i][j] * B[j][k]
    return answer

if __name__ == "__main__":
    A = [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6]]
    B = [[1, 2, 3, 4, 5], [2, 3, 4, 5, 6], [3, 4, 5, 6, 7], [4, 5, 6, 7, 8]]
    print(classic_multi(A, B))


__author__ = 'HarperMain'

from scipy.stats import norm
import numpy as np

# class EuropeanLookbackGreeks():
#
#     def __init__(self, spot, strike, rate, dividend, sigma, expiry, t):
#
#         self.spot = spot
#         self.strike = strike
#         self.rate = rate
#         self.dividend = dividend
#         self.sigma = sigma
#         self.expiry = expiry
#         self.t = t
#
#         self.tau = tau = self.expiry-self.t
#         self.d1 = (np.log(spot/strike) + (rate - dividend + 1/2 * sigma**2)*tau) / (np.sqrt(tau))
#         self.d2 = self.d1 - sigma * np.sqrt(tau)
#
#         self.Delta = self.EuroDelta()
#         self.Gamma = self.EuroGamma()
#         self.Theta = self.EuroTheta(rate, strike, tau, sigma, spot)
#         self.Rho = self.EuroRho(tau, strike, rate)
#         self.Vega = self.EuroVega(tau, spot)

def EuroDelta(d1):
    # Delta is the price sensitivity
    Delta = norm.cdf(d1)
    return Delta

def EuroGamma(d1, spot, sigma, tau):
    # Gamma is a second order time-price sensitivity
    Gamma = norm.ppf(norm.cdf(d1)) / (spot*sigma*np.sqrt(tau))
    return Gamma

def EuroTheta(d1, d2, rate, strike, tau, sigma, spot):
    # Theta is the time sensitivity
    Theta = -rate*strike*np.exp(-rate*tau) * norm.cdf(d2) - \
            (sigma*spot*norm.ppf(norm.cdf(d1))/(2*tau))
    return Theta

def EuroRho(d2, tau, strike, rate):
    # Rho is the interest rate sensitivity
    Rho = tau*strike*np.exp(-rate*tau) * norm.cdf(d2)
    return Rho

def EuroVega(d1, tau, spot):
    # Vega is a volatility sensitivity
    Vega = np.sqrt(tau)*spot*norm.cdf(d1)
    return Vega

def EuroD1(spot, strike, rate, dividend, sigma, tau):
    d1 = ((np.log(spot/strike) + (rate - dividend + 1/2 *
                                 sigma**2)*tau))/ (np.sqrt(tau))
    return d1

def EuroD2(d1, sigma, tau):
    d2 = d1 - sigma * np.sqrt(tau)
    return d2

    # def ReturnGreeks(self):
    #     EuroGreeks = {}
    #     EuroGreeks['Delta'] = self.Delta
    #     EuroGreeks['Gamma'] = self.Gamma
    #     EuroGreeks['Theta'] = self.Theta
    #     EuroGreeks['Rho'] = self.Rho
    #     EuroGreeks['Vega'] = self.Vega
    #
    #     return EuroGreeks


from math import sqrt,cos,sin
import numpy as np
from numpy.linalg import inv
import matplotlib.pyplot as plt

#### Sub routine 1 ####

##### Calculating effective properties of unidirectional composite lamina ####
## Inputs - E1_fibre,E2_fibre,nu12_fibre,nu23_fibre,G12_fibre,G23_fibre,E_matrix,nu_matrix,G_matrix,Volume_fraction_fibre
## Outputs - E1 ,E2, E3, nu12, nu13, nu23, G12, G13, G23 of effective lamina
def effective_lamina_properties(E1_fibre,E2_fibre,nu12_fibre,nu23_fibre,G12_fibre,G23_fibre,E_matrix,nu_matrix,G_matrix,Volume_fraction_fibre,chamis_correction=False):
    E1 = Volume_fraction_fibre*E1_fibre + (1-Volume_fraction_fibre)*E_matrix
    E2 = (E2_fibre * E_matrix )/(E2_fibre*(1-Volume_fraction_fibre)+E_matrix*Volume_fraction_fibre)
    E3 = (E2_fibre * E_matrix )/(E2_fibre*(1-Volume_fraction_fibre)+E_matrix*Volume_fraction_fibre)
    nu12 = (1-Volume_fraction_fibre)*nu_matrix + Volume_fraction_fibre*nu12_fibre
    nu13 = (1-Volume_fraction_fibre)*nu_matrix + Volume_fraction_fibre*nu12_fibre
    nu23 = (1-Volume_fraction_fibre)*nu_matrix + Volume_fraction_fibre*nu23_fibre
    G12 = (G12_fibre * G_matrix)/(G12_fibre*(1-Volume_fraction_fibre)+G_matrix*Volume_fraction_fibre)
    G13 = (G12_fibre * G_matrix)/(G12_fibre*(1-Volume_fraction_fibre)+G_matrix*Volume_fraction_fibre)
    G23 = (G23_fibre * G_matrix)/(G23_fibre*(1-Volume_fraction_fibre)+G_matrix*Volume_fraction_fibre)
    if chamis_correction :
        E22 = (E2_fibre * E_matrix )/(E2_fibre*(1-sqrt(Volume_fraction_fibre))+E_matrix*sqrt(Volume_fraction_fibre))
        E22 = (E2_fibre * E_matrix )/(E2_fibre*(1-sqrt(Volume_fraction_fibre))+E_matrix*sqrt(Volume_fraction_fibre))
        G12 = (G12_fibre * G_matrix)/(G12_fibre*(1-sqrt(Volume_fraction_fibre))+G_matrix*sqrt(Volume_fraction_fibre))
        G13 = (G12_fibre * G_matrix)/(G12_fibre*(1-sqrt(Volume_fraction_fibre))+G_matrix*sqrt(Volume_fraction_fibre))
        G23 = (G23_fibre * G_matrix)/(G23_fibre*(1-sqrt(Volume_fraction_fibre))+G_matrix*sqrt(Volume_fraction_fibre))
    E1,E2,E3,nu12,nu13,nu23,G12,G13,G23 = round(E1,3),round(E2,3),round(E3,3),round(nu12,3),round(nu13,3),round(nu23,3),round(G12,3),round(G13,3),round(G23,3)
    return E1,E2,E3,nu12,nu13,nu23,G12,G13,G23

#### Sub routine 2 ####

##### Calculating S and Q matrix for lamina ####
## Inputs -E1,E2,E3,nu12,nu13,nu23,G12,G13,G23
## Outputs - Q(3x3 Matrix), S(3x3 Matrix)

def stiffness_compliance_matrix(E1=0,E2=0,E3=0,nu12=0,nu13=0,nu23=0,G12=0,G13=0,G23=0):
    S_matrix = np.zeros((6,6),dtype=float)
    if(E1!=0):
        S_matrix[0][0] = 1/E1
        S_matrix[0][1] = (-1*nu12)/E1
        S_matrix[0][2] = (-1*nu13)/E1
    S_matrix[1][0] = S_matrix[0][1]
    if(E2!=0): S_matrix[1][1] = 1/E2
    if(E1!=0): S_matrix[1][2] = (-1*nu23)/E1
    S_matrix[2][0] = S_matrix[0][2]
    S_matrix[2][1] = S_matrix[1][2]
    if(E3!=0): S_matrix[2][2] = 1/E3
    if(G23!=0): S_matrix[3][3] = 1/G23
    if(G13!=0): S_matrix[4][4] = 1/G13
    if(G12!=0): S_matrix[5][5] = 1/G12

    nu21 = (nu12 * E2)/E1
    nu31 = (nu13 * E3)/E1
    nu32 = (nu23 * E3)/E2
    try:
        Q_matrix = inv(S_matrix)
    except:
        pass

    S_planar = np.zeros((3,3),dtype=float)
    S_planar[0][0] = S_matrix[0][0]
    S_planar[0][1] = S_matrix[0][1]
    S_planar[1][0] = S_matrix[1][0]
    S_planar[1][1] = S_matrix[1][1]
    S_planar[2][2] = S_matrix[5][5]
    Q_planar = inv(S_planar)
    Q_planar,S_planar = np.around(Q_planar,decimals = 3),np.around(S_planar,decimals = 3)
    return Q_planar,S_planar

#### Calculate the modulus and elastic propties at another angle ####
## Inputs - Q_matrix(3X3 Matrix)/S_matrix(3X3 Matrix)/Material_Properties([E1,nu12,E2,G12])
## Outputs - Ex,nuxy,Ey,Gxy,etaxy_x,etaxy_y
def effective_lamina_properties_angle(Q_matrix=False,S_matrix=False,Material_properties=False,data=1,angle=0):
    m = np.cos(np.deg2rad(angle))
    n = np.sin(np.deg2rad(angle))
    if Q_matrix == True:
        data = inv(data)
        Ex = 1/(pow(m,4)*data[0][0]+pow(m,2)*pow(n,2)*(data[2][2]+2*data[0][1])+pow(n,4)*data[1][1])
        nuxy = Ex*(-pow(m,2)*pow(n,2)*(data[0][0]+data[1][1]-data[2][2])-data[0][1]*(pow(m,4)+pow(n,4)))
        Ey = 1/(pow(n,4)*data[0][0]+pow(m,2)*pow(n,2)*(data[2][2]+2*data[0][1])+pow(m,4)*data[1][1])
        Gxy = 1/(4*pow(m,2)*pow(n,2)*(data[0][0]+data[1][1]-2*data[0][1])+pow(pow(m,2)-pow(n,2),2)*data[2][2])
        etaxy_x = Ex*(pow(m,3)*n*(2*data[0][0]-2*data[0][1]-data[2][2])-pow(n,3)*m*(2*data[1][1]-2*data[0][1]-data[2][2]))
        etaxy_y = Ey*(pow(n,3)*m*(2*data[0][0]-2*data[0][1]-data[2][2])-pow(m,3)*n*(2*data[1][1]-2*data[0][1]-data[2][2]))

    if S_matrix == True:
        Ex = 1/(pow(m,4)*data[0][0]+pow(m,2)*pow(n,2)*(data[2][2]+2*data[0][1])+pow(n,4)*data[1][1])
        nuxy = Ex*(-pow(m,2)*pow(n,2)*(data[0][0]+data[1][1]-data[2][2])-data[0][1]*(pow(m,4)+pow(n,4)))
        Ey = 1/(pow(n,4)*data[0][0]+pow(m,2)*pow(n,2)*(data[2][2]+2*data[0][1])+pow(m,4)*data[1][1])
        Gxy = 1/(4*pow(m,2)*pow(n,2)*(data[0][0]+data[1][1]-2*data[0][1])+pow(pow(m,2)-pow(n,2),2)*data[2][2])
        etaxy_x = Ex*(pow(m,3)*n*(2*data[0][0]-2*data[0][1]-data[2][2])-pow(n,3)*m*(2*data[1][1]-2*data[0][1]-data[2][2]))
        etaxy_y = Ey*(pow(n,3)*m*(2*data[0][0]-2*data[0][1]-data[2][2])-pow(m,3)*n*(2*data[1][1]-2*data[0][1]-data[2][2]))

    if Material_properties == True:
        Ex = 1/(pow(m,4)/data[0]+pow(m,2)*pow(n,2)*(1/data[3]-(2*data[1])/data[0])+pow(n,4)/data[2])
        nuxy = Ex*(-1*pow(m,2)*pow(n,2)*(1/data[0]+1/data[2]-1/data[3])+(data[1]*(pow(m,4)+pow(n,4)))/data[0])
        Ey = 1/(pow(n,4)/data[0]+pow(m,2)*pow(n,2)*(1/data[3]-(2*data[1])/data[0])+pow(m,4)/data[2])
        Gxy = 1/(4*pow(m,2)*pow(n,2)*(1/data[0]+1/data[2]+(2*data[1])/data[0])+(pow(pow(m,2)-pow(n,2),2))/data[3])
        etaxy_x = abs(Ex*(pow(m,3)*n*(2/data[0]+(2*data[1])/data[0]-1/data[3])-pow(n,3)*m*(2/data[2]+(2*data[1])/data[0]-1/data[3])))
        etaxy_y = abs(Ey*(pow(n,3)*m*(2/data[0]+(2*data[1])/data[0]-1/data[3])-pow(m,3)*n*(2/data[2]+(2*data[1])/data[0]-1/data[3])))
    try:
        Ex,nuxy,Ey,Gxy,etaxy_x,etaxy_y = round(Ex,3),round(nuxy,3),round(Ey,3),round(Gxy,3),round(etaxy_x,3),round(etaxy_y,3)
    except:
        Ex,nuxy,Ey,Gxy,etaxy_x,etaxy_y = np.around(Ex,3),np.round(nuxy,3),np.around(Ey,3),np.around(Gxy,3),np.around(etaxy_x,3),np.around(etaxy_y,3)
    return Ex,nuxy,Ey,Gxy,etaxy_x,etaxy_y

## Helper function
def plot_properties_angle(data,angle=0):
    x = np.linspace(0,90,90)
    Ex,nuxy,Ey,Gxy,etaxy_x,etaxy_y = effective_lamina_properties_angle(Material_properties=True,data=data,angle=x)
    fig, axs = plt.subplots(3, 2)
    fig.suptitle('Laminate properties with Angle(theta)',fontsize='xx-large',fontweight='bold')
    axs[0,0].plot(x,Ex,color='red',label='Ex')
    axs[0,0].set_title('Ex v/s angle')
    axs[0,0].grid(color = 'green', linestyle = '--', linewidth = 0.4)
    axs[0,0].legend()
    axs[0,0].set(ylabel='Stress(GPa)')
    axs[0,1].plot(x,Ey,color='blue',label='Ey')
    axs[0,1].set_title('Ey v/s angle')
    axs[0,1].grid(color = 'green', linestyle = '--', linewidth = 0.4)
    axs[0,1].legend()
    axs[0,1].set(ylabel='Stress(GPa)')
    axs[1,0].plot(x,Gxy,color='green',label='Gxy')
    axs[1,0].set_title('Gxy v/s angle')
    axs[1,0].grid(color = 'green', linestyle = '--', linewidth = 0.4)
    axs[1,0].legend()
    axs[1,0].set(ylabel='Stress(GPa)')
    axs[1,1].plot(x,nuxy,color='orange',label='nuxy')
    axs[1,1].set_title('nuxy v/s angle')
    axs[1,1].grid(color = 'green', linestyle = '--', linewidth = 0.4)
    axs[1,1].legend()
    axs[1,1].set(ylabel='Poisson ratio')
    axs[2,0].plot(x,etaxy_x,color='magenta',label='etaxy-x')
    axs[2,0].set_title('etaxy-x v/s angle')
    axs[2,0].grid(color = 'green', linestyle = '--', linewidth = 0.4)
    axs[2,0].legend()
    axs[2,0].set(xlabel='angle(degrees)',ylabel='shear coupling ratio')
    axs[2,1].plot(x,etaxy_y,color='cyan',label='etaxy-y')
    axs[2,1].set_title('etaxy-y v/s angle')
    axs[2,1].grid(color = 'green', linestyle = '--', linewidth = 0.4)
    axs[2,1].legend()
    axs[2,1].set(xlabel='angle(degrees)',ylabel='shear coupling ratio')
    '''
    for ax in fig.get_axes():
        ax.label_outer()
    '''
    Ex_ang,nuxy_ang,Ey_ang,Gxy_ang,etaxy_x_ang,etaxy_y_ang = effective_lamina_properties_angle(Material_properties=True,data=data,angle=angle)
    axs[0,0].plot(angle,Ex_ang,'bo')
    axs[0,1].plot(angle,Ey_ang,'bo')
    axs[1,0].plot(angle,Gxy_ang,'bo')
    axs[1,1].plot(angle,nuxy_ang,'bo')
    axs[2,0].plot(angle,etaxy_x_ang,'bo')
    axs[2,1].plot(angle,etaxy_y_ang,'bo')
    plt.show()
    fig.savefig('lamina_variation_angle.jpg')
    return fig

#### Calculate of Stiffness Matrix at angle ####
## Inputs - Material_Propties([E1,nu12,E2,G12]),Angle
## Outputs - Q_planar_angle
def Stiffness_matrix_angle(data,angle):
    Q_planar_angle = np.zeros((3,3),dtype=float)
    m = np.cos(np.deg2rad(angle))
    n = np.sin(np.deg2rad(angle))
    Q_planar,S_planar = stiffness_compliance_matrix(E1=data[0],E2=data[2],nu12=data[1],G12=data[3])
    Q_planar_angle[0][0] = pow(m,4)*Q_planar[0][0]+2*pow(m,2)*pow(n,2)*(Q_planar[0][1]+2*Q_planar[2][2])+pow(n,4)*Q_planar[1][1]
    Q_planar_angle[0][1] = pow(m,2)*pow(n,2)*(Q_planar[0][0]+Q_planar[1][1]-4*Q_planar[2][2])+Q_planar[0][1]*(pow(m,4)+pow(n,4))
    Q_planar_angle[0][2] = pow(m,3)*n*(Q_planar[0][0]-Q_planar[0][1]-2*Q_planar[2][2])+pow(n,3)*m*(Q_planar[0][1]-Q_planar[1][1]+2*Q_planar[2][2])
    Q_planar_angle[1][0] = Q_planar_angle[0][1]
    Q_planar_angle[1][1] = pow(n,4)*Q_planar[0][0]+2*pow(m,2)*pow(n,2)*(Q_planar[0][1]+2*Q_planar[2][2])+pow(m,4)*Q_planar[1][1]
    Q_planar_angle[1][2] = pow(n,3)*m*(Q_planar[0][0]-Q_planar[0][1]+2*Q_planar[2][2])-pow(n,3)*m*(Q_planar[0][1]-Q_planar[1][1]-2*Q_planar[2][2])
    Q_planar_angle[2][0] = Q_planar_angle[0][2]
    Q_planar_angle[2][1] = Q_planar_angle[1][2]
    Q_planar_angle[2][2] = pow(m,2)*pow(n,2)*(Q_planar[0][0]+Q_planar[1][1]-2*Q_planar[0][1])+pow(pow(m,2)-pow(n,2),2)*Q_planar[2][2]
    Q_planar_angle = np.around(Q_planar_angle,decimals=3)
    return Q_planar_angle

#### Sub routine 3 ####

##### Calculating A B D matrix for laminate ####
## Inputs - material properties of 0 deg lamina, lamina_sequence,lamina_thickness
## Outputs - A,B,D

def Laminate_parameters(data,laminate_sequence,lamina_thickness=1000):
    z_sequence = []
    Q_matrices = []
    no_laminas = len(laminate_sequence)
    lamina_thickness = lamina_thickness/no_laminas
    for i in range(no_laminas):
        Q_matrices.append(Stiffness_matrix_angle(data,laminate_sequence[i]))
    z_sequence.append(-(no_laminas/2)*lamina_thickness)
    for i in range(no_laminas):
        z_sequence.append(z_sequence[-1]+lamina_thickness)
    A = 0
    B = 0
    D = 0
    for i in range(no_laminas):
        A += Q_matrices[i] * (z_sequence[i+1]-z_sequence[i])
        B += Q_matrices[i] * (pow(z_sequence[i+1],2)-pow(z_sequence[i],2))
        D += Q_matrices[i] * (pow(z_sequence[i+1],3)-pow(z_sequence[i],3))
    B = B/2
    D = D/3
    z_laminas = []
    for i in range(no_laminas):
        z_laminas.append((z_sequence[i+1]+z_sequence[i])/2)
    A,B,D = np.around(A,decimals=3),np.around(B,decimals=3),np.around(D,decimals=3)
    return A,B,D,z_laminas,Q_matrices,laminate_sequence

#### Calculation of stress vectors for each lamina
## Inputs - A,B,D matrix of laminate ,N(Normal traction),M(Bending moment)
## Oututs - Stress lamina (3*1 vector) for each lamina

def stress_lamina(A,B,D,z_laminas,Q_matrices,laminate_sequence,N,M):
    laminate_matrix = np.block([[A,B],[B,D]])
    traction_bending_vector = np.transpose(np.array([N[0],N[1],N[2],M[0],M[1],M[2]]))
    strain_curvature_vector = np.dot(inv(laminate_matrix),traction_bending_vector)
    print(strain_curvature_vector)
    strain_laminas = []
    stress_laminas = []
    for i in range(len(z_laminas)):
        strain_laminas.append(strain_curvature_vector[:3]+z_laminas[i]*strain_curvature_vector[3:])
    for i in range(len(z_laminas)):
        stress_laminas.append(angle_correction(np.dot(Q_matrices[i],strain_laminas[i]),laminate_sequence[i]))
    return stress_laminas

#Helper Function
def angle_correction(stress_xy,angle):
    m = np.cos(np.deg2rad(angle))
    n = np.sin(np.deg2rad(angle))
    T = np.zeros((3,3),dtype=float)
    T[0][0] = pow(m,2)
    T[0][1] = pow(n,2)
    T[0][2] = 2*m*n
    T[1][0] = pow(n,2)
    T[1][1] = pow(m,2)
    T[1][2] = -2*m*n
    T[2][0] = -m*n
    T[2][1] = m*n
    T[2][2] = pow(m,2)-pow(n,2)
    stress_12 = np.dot(T,stress_xy)
    return stress_12

#### Sub routine 4 ####

### Implementation of Hashin Failure 2D
## Input - Stress Lamina, Strength of Lamina
## Output - True(if Failure occurs)/False(if Failure doesnt occurs)
def hashin_failure(stress_lamina,X,X_,Y,Y_,S):
    result = True
    safety_factor = 2
    ## Calculating for Fibre ##
    if stress_lamina[0]>0: #Tensile fibre failure
        if(pow(stress_lamina[0]/X,2)+pow(stress_lamina[2]/S,2)<1):
            result = result and False
        else:
            result = result and True
        safety_factor = quadratic_solver_safety(pow(stress_lamina[0]/X,2)+pow(stress_lamina[2]/S,2),0,-1)
    else: #Compressive fibre failure
        if(pow(stress_lamina[0]/X_,2)<1):
            result = result and False
        else:
            result = result and True
        safety_factor = quadratic_solver_safety(pow(stress_lamina[0]/X_,2),0,-1)
    ## Calculating for Matrix ##
    if stress_lamina[1]>0: #Matrix Tensile Failure
        if(pow(stress_lamina[1]/Y,2)+pow(stress_lamina[2]/S,2)<1):
            result = result and False
        else:
            result = result and True
        safety_factor = min(safety_factor,quadratic_solver_safety(pow(stress_lamina[1]/Y,2)+pow(stress_lamina[2]/S,2),0,-1))
    else: #Matrix Compressive Failure
        if((pow(Y_/2*S,2)-1)*(stress_lamina[1]/Y_)+pow(stress_lamina[1]/4*S,2)+pow(stress_lamina[2]/S,2)<1):
            result = result and False
        else:
            result = result and True
        safety_factor = min(safety_factor,quadratic_solver_safety(pow(stress_lamina[1]/4*S,2)+pow(stress_lamina[2]/S,2),(pow(Y_/2*S,2)-1)*(stress_lamina[1]/Y_),-1))
    safety_factor = round(safety_factor,3)
    return result,safety_factor

## Helper function ##
def quadratic_solver_safety(a,b,c):
    # calculate the discriminant
    d = (b**2) - (4*a*c)

    # find two solutions
    sol1 = (-b-sqrt(d))/(2*a)
    sol2 = (-b+sqrt(d))/(2*a)
    if(sol1>0 and sol2>0):
        return min(sol1,sol2)
    elif(sol1>0 or sol2>0):
        if(sol1>0): return sol1
        else: return sol2
    else:
        raise Exception("Quadratic coeffecients give illegal answers")

if __name__ == "__main__":
    ### My composite properties ###
    ## Fibre name - AS4 + Matrix name - BSL914c Epoxy ##
    ## (Fibre Details) E1_Fibre,E2_fibre,nu12_fibre,nu23_fibre,G12_fibre,G23_fibre,Volume_fraction_fibre,chamis_correction ###
    ## (Matrix Details )E_matrix,nu_matrix,G_matrix ##
    E1_fibre = 225
    E2_fibre = 15
    G12_fibre = 15
    nu12_fibre = 0.2
    nu23_fibre = 0.25
    G23_fibre = E2_fibre/(2*(1+nu23_fibre))
    E_matrix = 4
    nu_matrix = 0.35
    G_matrix = E_matrix/(2*(1+nu_matrix))
    Volume_fraction_fibre = 0.516
    E1,E2,E3,nu12,nu13,nu23,G12,G13,G23 = effective_lamina_properties(E1_fibre,E2_fibre,nu12_fibre,nu23_fibre,G12_fibre,G23_fibre,E_matrix,nu_matrix,G_matrix,Volume_fraction_fibre)
    #print(E1,E2,E3,nu12,nu13,nu23,G12,G13,G23)
    #print(G23_fibre,G_matrix)
    Q_planar,S_planar = stiffness_compliance_matrix(E1,E2,E3,nu12,nu13,nu23,G12,G13,G23)
    #print(Q_planar,S_planar)
    #Ex,nuxy,Ey,Gxy,etaxy_x,etaxy_y = effective_lamina_properties_angle(Material_properties=True,data=[E1,nu12,E2,G12],angle=50)
    #fig = plot_properties_angle([E1,nu12,E2,G12],20)
    #Q_planar_angle = Stiffness_matrix_angle(data=[E1,nu12,E2,G12],angle=90)
    #print(Q_planar_angle)
    A,B,D,z_laminas,Q_matrices,laminate_seq = Laminate_parameters(data=[E1,nu12,E2,G12],laminate_sequence=[0,29,-29,90,90,-29,29,0],lamina_thickness=1000)
    print(D)
    #print(A,B,D,z_laminas,Q_matrices)
    #print(D)
    stress_laminas = stress_lamina(A,B,D,z_laminas,Q_matrices,laminate_seq,N=[100000,0,0],M=[0,0,0])
    #print(stress_laminas[0])
    result,safety_factor = hashin_failure(stress_laminas[0],X=1550,X_=1550,Y=150,Y_=220,S=93)
    #print(result,safety_factor)


from math import exp, sqrt
import numpy as np

class NPeriodBOPM:

    def __init__(self, op_type,
                        underlying, strike,
                        volatility, risk_free,
                        nperiods, time_in_years,
                        factor_method = 'Jarrow',
                        trade_position = 'Long'
                        ):

        self.op_type = op_type
        self.underlying = underlying
        self.strike = strike
        self.volatility = volatility
        self.risk_free = risk_free
        self.nperiods = nperiods
        self.time_in_years = time_in_years
        self.factor_method = factor_method
        self.trade_position = trade_position

        # internal calculations
        self.deltatime = self.__deltatime_calc()

        # when using Jarrow-Rudd specification
        if self.factor_method == 'Jarrow':
            self.upfactor = self.__jarrow_calc()[0]
            self.downfactor = self.__jarrow_calc()[1]
            self.up_neutral = self.__jarrow_calc()[2]
            self.down_neutral = self.__jarrow_calc()[3]

        # when using Cox-Ross-Rubinstein specification:
        if self.factor_method == 'Cox':
            self.upfactor = self.__cox_calc()[0]
            self.downfactor = self.__cox_calc()[1]
            self.up_neutral = self.__cox_calc()[2]
            self.down_neutral = self.__cox_calc()[3]

        self.underlying_vector = self.__underlying_vector_calc()
        self.payoff_vector = self.__payoff_vector_calc()
        self.price_vector = self.__price_calc()
        self.price = self.price_vector[0]

    # internal calc

    def __deltatime_calc(self):
        return self.time_in_years / self.nperiods

    def __jarrow_calc(self):
        up_factor = exp(self.risk_free*self.deltatime
                        - ((self.volatility**2) / 2) * self.deltatime
                        + self.volatility*sqrt(self.deltatime)
                        )

        dn_factor = exp(self.risk_free*self.deltatime
                        - ((self.volatility**2) / 2) * self.deltatime
                        - self.volatility*sqrt(self.deltatime)
                        )

        # The risk-neutral probabilites
        up_neutral = (
            (exp(self.risk_free * self.deltatime) - dn_factor)
                / (up_factor - dn_factor)
                    )

        dn_neutral = 1-up_neutral
        return (up_factor, dn_factor, up_neutral, dn_neutral)

    def __cox_calc(self):
        up_factor = exp(self.volatility*sqrt(self.deltatime))
        dn_factor = 1/up_factor

        # The risk-neutral probabilites
        up_neutral = ((exp(self.risk_free * self.deltatime) - dn_factor)
                    / (up_factor - dn_factor) )
        dn_neutral = 1-up_neutral

        return (up_factor, dn_factor, up_neutral, dn_neutral)

    def __underlying_vector_calc(self):
        # Special thanks to cantaro86 for posting his solution on github
        underlying_values = np.array(
            [(self.underlying * self.upfactor**j *
            self.downfactor**(self.nperiods-j))
            for j in range(self.nperiods + 1)])
        return underlying_values

    def __payoff_vector_calc(self):
        # Special thanks to cantaro86 for posting his solution on github
        payoffs = np.zeros(self.nperiods + 1) # get array going
        if self.op_type == 'Call':
            payoffs[:] = np.maximum(self.underlying_vector - self.strike, 0.0)
        else:
            payoffs[:] = np.maximum(self.strike - self.underlying_vector, 0.0)
        return payoffs

    def __price_calc(self):
        # Special thanks to cantaro86 for posting his solution on github
        price_vector = self.payoff_vector
        # find prices
        for i in range(self.nperiods-1, -1, -1):
            price_vector[:-1] = (
                np.exp(-self.risk_free*self.deltatime)
                * (self.up_neutral * price_vector[1:]
                    + self.down_neutral * price_vector[:-1]))
        return price_vector

    def get_price(self):
        return self.price

#test:
#example = NPeriodBOPM('Call', 100, 100, 0.20, 0.10, 2000, 1)
#print(example.deltatime)
#print(example.upfactor)
#print(example.downfactor)
#print(example.underlying_vector)
#print(example.price_vector)
#print(example.price)


import numpy
import sympy

from sympy import init_printing
init_printing(use_latex=True)

x, nu, t = sympy.symbols('x nu t')
phi = (sympy.exp(-(x - 4 * t)**2 / (4 * nu * (t + 1))) +
       sympy.exp(-(x - 4 * t - 2 * numpy.pi)**2 / (4 * nu * (t + 1))))
phi

# It's maybe a little small, but that looks right.  Now to evaluate our partial derivative $\frac{\partial \phi}{\partial x}$ is a trivial task.

phiprime = phi.diff(x)
phiprime

# If you want to see the unrendered version, just use the Python print command.

# In[48]:

print(phiprime)

#
# Now that we have the Pythonic version of our derivative, we can finish writing out the full initial condition equation and then translate it into a usable Python expression.  For this, we'll use the *lambdify* function, which takes a SymPy symbolic equation and turns it into a callable function.

# In[49]:

from sympy.utilities.lambdify import lambdify

u = -2 * nu * (phiprime / phi) + 4
print(u)

# ### Lambdify
#
# To lambdify this expression into a useable function, we tell lambdify which variables to request and the function we want to plug them in to.

ufunc = lambdify((t, x, nu), u)
print(ufunc(1, 4, 3))

# ### Back to Burgers' Equation
#
# Now that we have the initial conditions set up, we can proceed and finish setting up the problem.  We can generate the plot of the initial condition using our lambdify-ed function.

# In[51]:

from matplotlib import pyplot

###variable declarations
nx = 101
nt = 100
dx = 2.0 * numpy.pi / (nx - 1)
nu = .07
dt = dx * nu

x = numpy.linspace(0, 2 * numpy.pi, nx)
un = numpy.empty(nx)
t = 0

u = numpy.asarray([ufunc(t, x0, nu) for x0 in x])
u

# In[52]:

pyplot.figure(figsize=(11, 7), dpi=100)
pyplot.plot(x, u, marker='o', lw=2)
pyplot.xlim([0, 2 * numpy.pi])
pyplot.ylim([0, 10]);

# This is definitely not the hat function we've been dealing with until now. We call it a "saw-tooth function".  Let's proceed forward and see what happens.

# ### Periodic Boundary Conditions
#
# One of the big differences between Step 4 and the previous lessons is the use of *periodic* boundary conditions.  If you experiment with Steps 1 and 2 and make the simulation run longer (by increasing `nt`) you will notice that the wave will keep moving to the right until it no longer even shows up in the plot.
#
# With periodic boundary conditions, when a point gets to the right-hand side of the frame, it *wraps around* back to the front of the frame.
#
# Recall the discretization that we worked out at the beginning of this notebook:
#
# $$u_i^{n+1} = u_i^n - u_i^n \frac{\Delta t}{\Delta x} (u_i^n - u_{i-1}^n) + \nu \frac{\Delta t}{\Delta x^2}(u_{i+1}^n - 2u_i^n + u_{i-1}^n)$$
#
# What does $u_{i+1}^n$ *mean* when $i$ is already at the end of the frame?
#
# Think about this for a minute before proceeding.
#
#

for n in range(nt): # looping over time
    un = u.copy()   # creating a copy to use for the iteration
    for i in range(1, nx-1): # looping over space
        u[i] = un[i] - un[i] * dt / dx *(un[i] - un[i-1]) + nu * dt / dx**2 *                (un[i+1] - 2 * un[i] + un[i-1]) #looping over all of the internal nodes
    u[0] = un[0] - un[0] * dt / dx * (un[0] - un[-2]) + nu * dt / dx**2 *                (un[1] - 2 * un[0] + un[-2]) #setting the value for the first node
    u[-1] = u[0] #setting the value for the last not

u_analytical = numpy.asarray([ufunc(nt * dt, xi, nu) for xi in x]) #calculating the analytical solution

# In[54]:

pyplot.figure(figsize=(11, 7), dpi=100)
pyplot.plot(x,u, marker='o', lw=2, label='Computational')
pyplot.plot(x, u_analytical, label='Analytical')
pyplot.xlim([0, 2 * numpy.pi])
pyplot.ylim([0, 10])
pyplot.legend();


import numpy as np
from numpy.random import uniform

def monte_carlo_pi(n):
    p = 0
    for i in range (n):
        u, v = uniform(), uniform()
        if np.sqrt((u - 0.5)**2 + (v - 0.5)**2) <= 0.5:
            p += 1
    return (p/n)*4

monte_carlo_pi(100000)


# 1) Using your RW script from week 7, create a downsampled, random and event-based sparse sampling of 10 RW
#    (RW_01 - RW_10) with length 100.  Please make sure you use a continuous, uniform distribution for your
#    RW this week. This means the values should be in between -1, +1 but not only these values.

# https://numpy.org/doc/stable/reference/random/generated/numpy.random.uniform.html

# "low" = -1
# "high" = 1

# 1a) Calculate a reference RW_ref as the ensemble average of the 10 RW. The RW_ref will be used in the following steps as your "ground truth" to compare all other data with.
# 1b) Downsample RW_01 by every 2nd value, and every 10th value.
# 1c) Randomly (uniform distribution) resample RW_01 with 10 and 50 samples.
# 1d) Perform event-based resampling of RW_01 with the thresholds of λ = 0.5 and 0.8. Don't forget to take the first derivative before thresholding!

import numpy as np
import matplotlib.pyplot as plt

# Parameters
RW_LENGTH = 100
NR_OF_WALKS = 10
HIGH = 1.0
LOW = -1.0

# Random walk array
RAND_WALKS = np.zeros((NR_OF_WALKS, RW_LENGTH))

# Ensemble average array
RW_ref = np.zeros(RW_LENGTH)

# Calculations
for i in range (NR_OF_WALKS):
    for t in range(RW_LENGTH - 1):
        # 1)
        RAND_WALKS[i][t + 1] = RAND_WALKS[i][t] + np.random.uniform(low=LOW, high=HIGH)

# 1a)
RW_ref = np.sum(RAND_WALKS, axis=0) / NR_OF_WALKS

# 1b)
every2nd_indices = np.arange(0, RAND_WALKS[0].size, 2)
downsampled_2nd_RW01 = np.delete(RAND_WALKS[0], every2nd_indices)

every10th_indices = np.arange(0, RAND_WALKS[0].size, 10)
downsampled_10th_RW01 = np.delete(RAND_WALKS[0], every10th_indices)

# 1c)
indices_10 = np.sort(np.random.uniform(low=0, high=RW_LENGTH, size=10).astype(int))
resampled_with10_RW01 = np.take(RAND_WALKS[0], indices_10)

indices_50 = np.sort(np.random.uniform(low=0, high=RW_LENGTH, size=50).astype(int))
resampled_with50_RW01 = np.take(RAND_WALKS[0], indices_50)

# 1d)
LAMBDA_1 = 0.5
evnt_bsd_05 = np.zeros(0)
t_indices_05 = np.zeros(0)

LAMBDA_2 = 0.8
evnt_bsd_08 = np.zeros(0)
t_indices_08 = np.zeros(0)

prev_t = 0
for t, val in enumerate(RAND_WALKS[0]):
    if (t == 0):
        dx_dt = 0
    else:
        dx_dt = (RAND_WALKS[0][t] - RAND_WALKS[0][t - 1]) / (t - prev_t)
        prev_t = t

    if (abs(dx_dt) >= LAMBDA_1):
        evnt_bsd_05 = np.append(evnt_bsd_05, dx_dt)
        t_indices_05 = np.append(t_indices_05, t)

    if (abs(dx_dt) >= LAMBDA_2):
        evnt_bsd_08 = np.append(evnt_bsd_08, dx_dt)
        t_indices_08 = np.append(t_indices_08, t)

t_indices_05 = np.asarray(t_indices_05, dtype=int)
t_indices_08 = np.asarray(t_indices_08, dtype=int)

# 2) Calculate the RMSE of your 1b, 1c and 1d against the RW_ref.
#    Which method worked the best, and why?

# RMSE 1b
kept_indices_2nd = np.arange(1, RW_LENGTH, 2)
kept_indices_10th = np.delete(np.arange(0, RAND_WALKS[0].size, 1), every10th_indices)

rmse1b_1 = 0
for t, val in enumerate(kept_indices_2nd):
    rmse1b_1 = rmse1b_1 + np.square((RW_ref[val] - downsampled_2nd_RW01[t]))
rmse1b_1 = np.sqrt(rmse1b_1 / len(kept_indices_2nd))
print ("RMSE 1b_1: {}".format(rmse1b_1))

rmse1b_2 = 0
for t, val in enumerate(kept_indices_10th):
    rmse1b_2 = rmse1b_2 + np.square((RW_ref[val] - downsampled_10th_RW01[t]))
rmse1b_2 = np.sqrt(rmse1b_2 / len(kept_indices_10th))
print ("RMSE 1b_2: {}".format(rmse1b_2))

# RMSE 1c
rmse1c_1 = 0
for t, val in enumerate(indices_10):
    rmse1c_1 = rmse1c_1 + np.square((RW_ref[val] - resampled_with10_RW01[t]))
rmse1c_1 = np.sqrt(rmse1c_1 / len(indices_10))
print ("RMSE 1c_1: {}".format(rmse1c_1))

rmse1c_2 = 0
for t, val in enumerate(indices_50):
    rmse1c_2 = rmse1c_2 + np.square((RW_ref[val] - resampled_with50_RW01[t]))
rmse1c_2 = np.sqrt(rmse1c_2 / len(indices_50))
print ("RMSE 1c_2: {}".format(rmse1c_2))

# RMSE 1d
rmse1d_1 = 0
for t, val in enumerate(t_indices_05):
    rmse1d_1 = rmse1d_1 + np.square((RW_ref[val] - evnt_bsd_05[t]))
rmse1d_1 = np.sqrt(rmse1d_1 / len(t_indices_05))
print ("RMSE 1d_1: {}".format(rmse1d_1))

rmse1d_2 = 0
for t, val in enumerate(t_indices_08):
    rmse1d_2 = rmse1d_2 + np.square((RW_ref[val] - evnt_bsd_08[t]))
rmse1d_2 = np.sqrt(rmse1d_2 / len(t_indices_08))
print ("RMSE 1d_2: {}".format(rmse1d_2))

# Plotting
#plt.plot(RAND_WALKS[0], color='b', label='RW_01')
plt.plot(RW_ref, color='r', label='Ensemble')

plt.plot(indices_10, resampled_with10_RW01, 'k.--', label='resampled_10')
plt.plot(indices_50, resampled_with50_RW01, 'c.--', label='resampled_50')

plt.plot(t_indices_05, evnt_bsd_05, 'g:.', label='evnt-bsd resample 0.5')
plt.plot(t_indices_08, evnt_bsd_08, 'y:.', label='evnt-bsd resample 0.8')

plt.ylabel("Y")
plt.xlabel("Number of steps")
plt.legend()
plt.show()


import numpy as np
from ..exceptions import NotFittedError
from ..utils import check_features, check_target

class LinearRegression(object):
    """Linear regression estimator.

    Can be solved in closed form using the normal equation or singular
    value decomposition. It's also possible to fit using a batch
    gradient descent algorithm.

    Attributes:
        learning_rate: Rate of update for gradient descent at
        each iteration. Not used for other solving methods (float).
        num_iterations: Number of iterations for gradient descent (int).
        save_cost: Whether to save or not the cost of each iteration.
        Only used for gradient descent (boolean).
        grad_descent_cost: List with the cost for each iteration when
        using gradient descent (list of floats).
        parameters: nd array with the model parameters (theta).
    """

    def __init__(self, solving_method='svd', learning_rate=0.01,
                 num_iter=1000, save_cost=False):
        self.solving_method = solving_method
        self.learning_rate = learning_rate
        self.num_iterations = num_iter
        self.save_cost = save_cost

        solvers = ['svd', 'normal_equation', 'gradient_descent']
        if self.solving_method not in solvers:
            raise ValueError("solving_method must be one of: ", solvers)

    def fit(self, x, y):
        """Fit a linear regression model to features x and target y.

        Arguments:
            x: Features - nd array with shape (m_samples, n_features).
            If you have a pandas Dataframe use df.values.
            y: Target values. Can be a one or two dimensional numpy
            array with shape (m_samples,) or (m_samples, 1)
        """
        check_features(x)
        check_target(y)
        x = np.insert(x, 0, 1, axis=1)  # add bias
        y = y.reshape(-1, 1) if y.ndim == 1 else y

        if self.solving_method == 'svd':
            self.parameters = np.linalg.pinv(x) @ y
        elif self.solving_method == 'normal_equation':
            # Normal equation: inv(X'*X) * X' * Y
            self.parameters = np.linalg.pinv(x.T @ x) @ x.T @ y
        else:
            self.parameters = np.zeros(shape=(x.shape[1], 1))
            self.batch_gradient_descent(x, y)
        return self

    def predict(self, x):
        """Return predictions for ndarray with shape (m_samples, n_features)."""
        check_features(x)
        x = np.insert(x, 0, 1, axis=1)  # add bias
        try:
            return self.hypothesis(x)
        except AttributeError:
            raise NotFittedError("You must fit before making predictions.")

    def hypothesis(self, x):
        """Hypothesis for linear regression (ax + b)."""
        return x @ self.parameters

    def gradient_function(self, x, y, predictions=None):
        """Compute the gradient with the current parameters."""
        if predictions is None:
            predictions = self.hypothesis(x)
        return x.T @ (predictions - y)

    def cost_function(self, x, y, predictions=None):
        """Return the cost for the current parameters using MSE."""
        if predictions is None:
            predictions = self.hypothesis(x)
        squared_error = np.sum(np.square(predictions - y))
        return squared_error / (2 * x.shape[0])

    def batch_gradient_descent(self, x, y):
        """Batch gradient descent implementation (vectorized)."""

        self.grad_descent_cost = []
        for _ in range(self.num_iterations):
            # Make predictions and calculate gradient
            predictions = self.hypothesis(x)
            gradient = self.gradient_function(x, y, predictions)

            # Update current parameters
            self.parameters = self.parameters - self.learning_rate / x.shape[0] * gradient
            if self.save_cost:
                self.grad_descent_cost.append(self.cost_function(x, y, predictions))

lr = LinearRegression()


import numpy as np
import math
import matplotlib.pyplot as plt
from scipy.stats import beta, norm

def bernoulli_posterior(data, prior):
	n_1 = sum(data)
	n_2 = len(data) - n_1
	x = np.arange(0,1.01,step=.01)
	y = beta.pdf(x, prior[0] + n_1 - 1, prior[1] + n_2 - 1)
	plt.plot(x, y)
	plt.show()
	return

def problem1():
	data = np.fromfile('trial.csv', dtype=int, sep='\n')
	bernoulli_posterior(data,np.array([8., 2.]))

def multinomial_beta(alpha):
	return np.prod(np.array([math.gamma(alpha[i]) for i in xrange(len(alpha))]))/math.gamma(np.sum(alpha))

def multinomial_posterior(data, prior):
	categories = np.sort(list(set(data)))
	n = len(categories)
	sums = np.zeros(n)
	for i in xrange(n):
		sums[i] = sum(data==categories[i])
	return prior + sums

def problem2():
	with open('fruit.csv', 'r') as fruit:
		data = np.array([line.strip() for line in fruit])
	multinomial_posterior(data, np.array([5., 3., 4., 2., 1.]))

def invgammapdf(x, alpha, beta):
	alpha = float(alpha)
	beta = float(beta)
	if not np.isscalar(x):
		return (beta**alpha / math.gamma(alpha))*np.array([(xi**(-alpha - 1))*math.exp(-beta/xi) for xi in x])
	else:
		return (beta**alpha / math.gamma(alpha))*(x**(-alpha - 1))*math.exp(-beta/x)

def mean_posterior(data, prior, variance):
	n = float(len(data))
	sample_mean = np.mean(data)
	mu_0 = prior[0]
	sigma2_0 = prior[1]
	posterior_mean = ((mu_0*variance)/n + sample_mean*sigma2_0)/(variance/n + sigma2_0)
	posterior_variance = 1./(n/variance + 1./sigma2_0)
	return posterior_mean,posterior_variance

def problem3():
	data = np.fromfile('examscores.csv', dtype=float, sep='\n')

	posterior_mean, posterior_variance = mean_posterior(data, np.array([74., 25.]), 36.)
	x = np.arange(0, 100.1, step=0.1)
	y = norm.pdf(x, loc=posterior_mean, scale=np.sqrt(posterior_variance))
	plt.plot(x, y)
	plt.show()

def variance_posterior(data, prior, mean):
	n = float(len(data))
	alpha = prior[0]
	beta = prior[1]
	posterior_alpha = alpha + n/2.
	posterior_beta = beta + np.dot(data-mean, data-mean)/2.
	return posterior_alpha, posterior_beta

posterior_alpha, posterior_beta = variance_posterior(data, np.array([2., 25.]), 62.)
x = np.arange(0.1, 100.1, step=0.1)
y = invgammapdf(x, posterior_alpha, posterior_beta)
plt.plot(x, y)
plt.show()


# -*- coding: utf-8 -*-
"""
Created on Thu Sep 23 01:25:36 2021

@author: Mahfuz_Shazol
"""

# frommula A^+ =VD^+ U^T

import numpy as np

A=np.array([
            [-1,2],
            [3,-2],
            [5,7]
           ])

U ,d ,VT =np.linalg.svd(A)

D=np.diag(d)
print(D)

D_inv=np.linalg.inv(D)
print(D_inv)

print(A.shape[0])
print(A.shape[1])

D_plus=np.zeros((3,2)).T
print(D_plus)

D_plus[:2,:2]=D_inv
print(D_plus)

result=np.dot(VT.T,np.dot(D_plus,U.T))

print('A^+',result)

#numpy has build in function
result=np.linalg.pinv(A)
print(result)


# Program 02e: Numerical and truncated series solutions.
# See Figure 2.6.

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

def ODE2(X, t):
    x = X[0]
    y = X[1]
    dxdt = y
    dydt = x - t ** 2 * y
    return [dxdt, dydt]

X0 = [1, 0]
t = np.linspace(0, 10, 1000)

sol = odeint(ODE2, X0, t)

x = sol[:, 0]
y = sol[:, 1]

fig, ax = plt.subplots()
ax.plot(t, x, label='Numerical')
ax.plot(t, 1 + t**2 / 2 + t**4 / 24, 'r-', label='Truncated series')
plt.xlabel('t', fontsize=15)
plt.ylabel('x', fontsize=15)
plt.tick_params(labelsize=15)
plt.xlim(0, 4)
plt.ylim(0, 4)

ax.legend(loc='lower center', shadow=True)

plt.show()


"""
Created on Dec 6, 2021
Author: Haoming(Hammond) Liu
Email: hl3797@nyu.edu
"""

import os
import numpy as np
from scipy.linalg import rq

# check input file & load points
assert os.path.isfile('world.txt')
assert os.path.isfile('image.txt')
world_pts = np.loadtxt('world.txt')
image_pts = np.loadtxt('image.txt')
# print(world_pts.shape, image_pts.shape)

# concatenate ones
N = world_pts.shape[1]
world_pts = np.vstack((world_pts, np.ones((1, N))))
image_pts = np.vstack((image_pts, np.ones((1, N))))
print('world points:', world_pts, sep='\n')
print('image points:', image_pts, sep='\n')

# constracut matrix A
A_rows = []
zero_item = np.zeros((1, 4))
for i in range(N):
    xi, yi, wi = tuple(image_pts.T[i])
    Xi = world_pts.T[i].reshape(1, 4)
    A_rows.append(np.hstack((zero_item, -wi*Xi, yi*Xi)))
    A_rows.append(np.hstack((wi*Xi, zero_item, -xi*Xi)))
A = np.vstack(A_rows)
print('Matrix A: shape =', A.shape)
print(A)

# solve P using SVD
_, s, v = np.linalg.svd(A)
P = v[np.argmin(s)].reshape(3, 4)
print('Matrix P: shape =', P.shape)
print(P)
inhomo = lambda x: x / x[-1]
verify = np.matmul(P, world_pts)
print('verify proj: shape = image_pts =', image_pts.shape)
print(image_pts - inhomo(verify))

# solve C using SVD (null-space of P)
_, s, v = np.linalg.svd(P)
C1 = inhomo(v[-1].reshape(4, 1))[:-1].T.squeeze()
print('Projection Center C: shape =', C1.shape)
print(C1)

# alternative route
r, K = np.linalg.qr(P.T)
R, t = r[:-1].T, r[-1].T
C2 = np.linalg.solve(-R, t)
print('Alternative Route:')
print(C2)


import math

import numpy
from numpy.linalg import norm
from scipy.fft import dctn

from aydin.analysis.resolution_estimate import resolution_estimate

def snr_estimate(image, display_images: bool = False) -> float:
    """Estimates the signal to noise ratio of an image in DB.

    A value of 0 means that the signal and noise have roughly the same energy,
    a negative value means that the noise is stronger than the signal,
    and reciprocally, a positive value means that the signal is stronger
    than the noise.

    Parameters
    ----------
    image : numpy.typing.ArrayLike
        Image to compute SNR estimate for.

    display_images: bool
        If true displays the spectrum in a napari window.

    Returns
    -------
    Returns an estimate of the image's signal-to-noise ratio in dB.
    """

    # First we estimate resolution:
    frequency, image = resolution_estimate(image)

    # import napari
    # viewer = napari.Viewer()
    # viewer.add_image(image, name='clean_image')
    # napari.run()

    # cast and copy:
    image = image.astype(numpy.float32)

    # Normalise:
    image -= image.mean()
    variance = image.var()
    if variance > 0:
        image /= variance

    # Compute the DCT:
    image_dct = dctn(image, workers=-1)

    # Compute frequency map:
    f = numpy.zeros_like(image)
    axis_grid = tuple(numpy.linspace(0, 1, s) for s in image.shape)
    for x in numpy.meshgrid(*axis_grid, indexing='ij'):
        f += x**2
    f = numpy.sqrt(f)

    # define two domains:
    signal_domain = f <= frequency
    noise_domain = f > frequency

    # First we measure the energy of both signa and noise:
    signal_energy = norm(image_dct[signal_domain]) ** 2
    noise_energy = norm(image_dct[noise_domain]) ** 2

    if display_images:
        import napari

        viewer = napari.Viewer()
        viewer.add_image(image, name='image')
        viewer.add_image(numpy.log1p(numpy.abs(image_dct)), name='image_dct')

        image_dct_signal = image_dct.copy()
        image_dct_signal[noise_domain] = 0
        viewer.add_image(
            numpy.log1p(numpy.abs(image_dct_signal)), name='image_dct_signal'
        )

        image_dct_noise = image_dct.copy()
        image_dct_noise[signal_domain] = 0
        viewer.add_image(
            numpy.log1p(numpy.abs(image_dct_noise)), name='image_dct_noise'
        )
        napari.run()

    # However, this is an underestimate of the noise, because we assume that
    # the noise is uniformly distributed in frequency space. Therefore, we need
    # to correct this first estimate. For this we need the 'volume, in frequency
    # space of both domains:

    signal_domain_volume = numpy.sum(signal_domain) / image_dct.size
    noise_domain_volume = 1 - signal_domain_volume

    # We re-estimate the noise energy assuming the same density over the whole
    # spectrum:
    corrected_noise_energy = noise_energy / noise_domain_volume

    # For the signal energy we remove the energy that comes from the noise:
    corrected_signal_energy = (
        signal_energy - signal_domain_volume * corrected_noise_energy
    )

    # We can't let the signal energy to go below zero:
    corrected_signal_energy = max(1e-16, corrected_signal_energy)

    # Signal to noise ratio in dB:
    noise_ratio = 10 * math.log10(corrected_signal_energy / corrected_noise_energy)

    return noise_ratio


# Generates x,y points in a Fermat spiral
# Bryan Miller
# theta = 137.508*np.pi/180., golden angle
# b = number of spirals
# a = scale factor, default gives positions in degrees

import numpy as np

# Calculate points in a Fermat spiral
def fermat(n_point,a=0.03,b=1.0,theta=2.399963):
    # local variable with the correct type
    l_npt = int(n_point)
    l_a = float(a)
    l_b = float(b)
    l_theta = float(theta)

    x = np.zeros(n_point)
    y = np.zeros(n_point)

    for ii in range(l_npt):
        l_ii = float(ii)
        x[ii] = l_a*np.sqrt(l_ii)*np.cos(l_b*l_ii*l_theta)
        y[ii] = l_a*np.sqrt(l_ii)*np.sin(l_b*l_ii*l_theta)

    return x,y

# Determine scale factor needed to produce a given offset in n_point points
def fermat_scale(n_point,offset,b=1.0,theta=2.399963):
    # local variable with the correct type
    l_npt = float(n_point)
    l_offset = float(offset)
    l_b = float(b)
    l_theta = float(theta)

    a = l_offset/(np.sqrt(l_npt*(np.cos(l_b*l_npt*l_theta)**2 + np.sin(l_b*l_npt*l_theta)**2)))

    return a


import numpy as np
import math

# simplex algorithm
'''
Need a super class that takes constants into account in objFun
'''
class Simplex(object):
	'''
	max 3x+2y
	s.t. x+2y <=4
		 x-y <= 1
		 x,y >= 0

	with slack variables, translated into
	c = [3, 2]
	A = [[1,  2],
		 [1, -1]]
	b = [4, 1]

	For now, assume proper c, A, b inputs

	for min f(x), use duality of simplex
	'''
	'''
	method based on multiple sources:
	https://www.youtube.com/watch?v=XK26I9eoSl8
	https://hubpages.com/technology/Simplex-Algorithm-in-Python
	https://jeremykun.com/2014/12/01/
					linear-programming-and-the-simplex-algorithm/
	https://www.cs.cmu.edu/afs/cs/academic/
					class/15780-s16/www/slides/linear_prog.pdf
	MAIN SOURCE: https://www.youtube.com/watch?v=BdtdYlUIXak
	MAIN SOURCE: http://web.mit.edu/15.053/www/AMP-Chapter-04.pdf

	tableu from above example should look like
	x  y  s1  s2  P  b
	------------------
	1  2   1   0  0  4   Constraint Rows
	1  -1  0   1  0  1
	------------------
	-3 -2  0   0  1  0   ObjFun row
	'''
	'''
	Things to Consider:
	- objective function has constant number
	- infeasible solution
	- infinite solution

	Currently only handles feasible single point solution
	'''
	def __init__(self, c, A, b, maxi):
		self.varLen = len(c) # number of variables
		self.conLen = len(b) # number of slack variables(aka # of constraints)
		#np.concatenate((x, np.identity(len(x))),axis=1)
		self.maxi = maxi

		bCol = np.array([b + [0]], dtype=float) # assumes no constant val in objfun
		varTable = np.array(A+[c], dtype=float)
		mainTable = np.concatenate((varTable, bCol.T),axis=1)
		if not self.maxi: # minimization prob as dual prob
			mainTable = mainTable.T

		# split mainTable into table with factors and table with constants
		bCol = np.array([mainTable[:,-1]])
		if self.maxi:
			factorTable = mainTable[:,:self.varLen]
			slckAndP = np.identity(self.conLen+1) #num of slack = num of constraints
		else:
			factorTable = mainTable[:,:self.conLen]
			slckAndP = np.identity(self.varLen+1)
		# multiply -1 to objfun row
		factorTable[-1,:] *= -1

		self.tableu = np.concatenate((factorTable,slckAndP,bCol.T),axis=1)

	# if any variable with negative factor, can improve
	def canImprove(self):
		for varFactor in self.tableu[-1]: #objFun
			if varFactor < 0:
				return True
		return False

	# find pivRow & pivCol; same method for both maxi and mini
	def findPivot(self):
		# pivotCol is col with most negative factor in objfun
		minFact = 0 # minimum factor of objfun
		objFun = self.tableu[-1]
		for col in range(len(objFun)-2): # exclude P and b col
			if objFun[col] < minFact:
				minFact = objFun[col]
				self.pivCol = col
				#self.pivColSet = (self.pivColSet).union([col])

		ratios = []
		for row in range(len(self.tableu)-1): # exclude objfun row
			if (abs(self.tableu[row][self.pivCol]) > 0
				and self.tableu[row][-1]/self.tableu[row][self.pivCol] > 0):
				constant = self.tableu[row][-1]
				ratios.append(constant/self.tableu[row][self.pivCol])
			else: ratios.append(float("inf")) #just a place holder
		# pick row index minimizing the quotient
		self.pivRow = ratios.index(min(ratios))

	#destructively change tableu
	def pivot(self):
		pivR, pivC = self.pivRow, self.pivCol
		# divide whole pivRow so factor @ pivRow&Col = 1
		self.tableu[pivR] = np.divide(self.tableu[pivR], float(self.tableu[pivR][pivC]))

		# subtract the rest with pivRow*factor
		notPivRows = set(list(range(len(self.tableu)))).difference(set([pivR]))
		for row in notPivRows:
			factor = self.tableu[row][pivC]
			self.tableu[row] -= np.dot(self.tableu[pivR], factor)

	def primalSolution(self):
		# initialize values of all variables
		primal = [0]*self.varLen
		# find basic variable solutions and record them
		for col in range(self.varLen):
			oneFound = False
			varI = 0
			varRow = 0
			for row in range(len(self.tableu)):
				if self.tableu[row][col] == 1:
					if not oneFound: oneFound, varI, varRow = True, col, row
					else:
						oneFound = False
						break
			if oneFound: primal[varI] = self.tableu[varRow][-1]
		return primal

	def dualSolution(self):
		dual = [0]*self.varLen
		# for every "slack variables" in dual problem
		for col in range(self.conLen, self.conLen+self.varLen):
			objFunRow = self.tableu[-1]
			# solution of primal problem is the number at objfunrow
			newVal = objFunRow[col]
			dual[col-self.conLen] = newVal
		return dual

	def objectiveValue(self):
		return self.tableu[-1][-1]

	def simplex(self):
		#np.set_printoptions(precision=2)
		#print(self.tableu)
		while self.canImprove():
			self.findPivot()
			self.pivot()
			#print(self.tableu)
		if self.maxi: solution = self.primalSolution()
		else: solution = self.dualSolution()
		return solution, self.objectiveValue()


from __future__ import division
from __future__ import print_function

import numpy as np
import matplotlib

matplotlib.use('TkAgg')
import matplotlib.pyplot as plt

np.random.seed(0)  # set the random seed for reproducibility

def generate_sample(xmin, xmax, sample_size):
    x = np.linspace(start=xmin, stop=xmax, num=sample_size)
    pix = np.pi * x
    target = np.sin(pix) / pix + 0.1 * x
    noise = 0.05 * np.random.normal(loc=0., scale=1., size=sample_size)
    return x, target + noise

def calc_design_matrix(x, c, h):
    return np.exp(-(x[None] - c[:, None]) ** 2 / (2 * h ** 2))

def shuffle_two_array(x,y):
    shuffled_x = np.array([])
    shuffled_y = np.array([])
    permutation = np.random.permutation(len(x))
    for i in permutation:
        shuffled_x = np.append(shuffled_x,x[i])
        shuffled_y = np.append(shuffled_y,y[i])
    return (shuffled_x,shuffled_y)

def cross_validation (num,x,y) :
    best_l = None
    best_h = None
    shuffled_x,shuffled_y = shuffle_two_array(x,y)
    x_c = np.array_split(shuffled_x, num)
    y_c = np.array_split(shuffled_y, num)
    best_validation = np.inf
    for i in range(10):
        l = 10 ** (4-i)
        for j in range(10):
            h = 10 ** (4-j)
            validation_value = 0
            for n in range (num):
                x_train = np.array([])
                for le in range(len(x_c)):
                    if(le != n):
                        x_train = np.append(x_train,x_c[le])
                y_train = np.array([])
                for le in range(len(y_c)):
                    if(le != n):
                        y_train = np.append(y_train,y_c[le])
                x_test = x_c[n]
                y_test = y_c[n]
                k = calc_design_matrix(x_train, x_train, h)
                theta = np.linalg.solve(
                    k.T.dot(k) + l * np.identity(len(k)),
                    k.T.dot(y_train[:, None]))
                k_test = calc_design_matrix(x_train, x_test, h)
                pred_list = k_test.dot(theta)
                validation_value = validation_value + np.sum((y_test - pred_list.T) ** 2)
            validation_value = validation_value / num
            if (validation_value < best_validation) :
                best_validation = validation_value
                best_l = l
                best_h = h
    return (best_l,best_h)

# create sample
sample_size = 50
xmin, xmax = -3, 3
x, y = generate_sample(xmin=xmin, xmax=xmax, sample_size=sample_size)

# k-fold cross-validation
best_l, best_h = None, None

# --- implement here ---

best_l,best_h = cross_validation(5,x,y)

# --- end implement ---

print('Best (l, h): ({}, {})'.format(best_l, best_h))

#
# visualization
#

# calculate design matrix
k = calc_design_matrix(x, x, best_h)

# solve the least square problem
theta = np.linalg.solve(
    k.T.dot(k) + best_l * np.identity(len(k)),
    k.T.dot(y[:, None]))

# create data to visualize the prediction
X = np.linspace(start=xmin, stop=xmax, num=5000)
K = calc_design_matrix(x, X, best_h)
prediction = K.dot(theta)

plt.clf()
plt.scatter(x, y, c='green', marker='o')
plt.plot(X, prediction)
plt.savefig('ML1-homework1.png')


# Ajuste do circuito RC
import numpy as np
import matplotlib.pyplot as plt

def aj_lin(xi, yi):
    '''
    Realiza o ajuste linear de pontos em uma reta de ajuste no formato "y = ax + b"
    :param xi: coordenadas x dos pontos
    :param yi: coordenadas y dos pontos
    :return: coeficiente angular "a" e coeficiente linear "b" da reta de ajuste
    '''
    n = len(xi)
    mxy = sum(xi*yi)/n
    mx = sum(xi)/n
    my = sum(yi)/n
    mqx = sum(xi**2)/n
    a = (mxy - (mx*my))/(mqx - (mx**2))
    b = ((mqx*my) - (mx*mxy))/(mqx - (mx**2))
    return a, b

t, v = np.loadtxt('dados-rc.txt', unpack=True)
logv = np.log(v[1:])
art = np.linspace(0, 1.9)
cof = aj_lin(t[1:], logv)
v0, c = np.exp(cof[1]), 1/(10*cof[0])

vt = lambda tt: cof[0]*tt + cof[1]

texto = f'a = {cof[0]:.3f}\nb = {cof[1]:.3f}\nV={v0:.3f}.exp(-t/R.{-c:.3f})'

plt.scatter(t[1:], logv, s=30, marker='.', c='k')
plt.plot(art, vt(art), 'magenta')
plt.text(1, 1.5, texto, bbox=dict(boxstyle='square', ec='k', color='white'))
plt.grid()
plt.title('Descarga do Capacitor')
plt.xlabel('t(s)')
plt.ylabel('ln(V)')
#plt.show()
plt.savefig('atv_rc.jpeg')


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Special math functions
======================
Common special functions to be used across Sympy implemented models

"""

from sympy import besselj, bessely, legendre, I, sqrt, pi
from sympy import symbols

p,m,v,n,z = symbols('p m v n z')

# spherical Bessel function
sph_bessel1 = besselj(n+1/2,z)*sqrt(pi/(2*z))
# sphericl Neumann function
sph_bessel2 = bessely(n+1/2,z)*sqrt(pi/(2*z))
# spherical Hankel function of the second kind
sph_hankel2 = sph_bessel1 - I*sph_bessel2

h2_nz = sph_bessel1 - I*sph_bessel2 # more compact notation.

# This function can actuall be replaced by the inbuilt 'assoc_legendre' with varying mileage
# upon lambdification
# pmvz version of legendre function (Appendix II of Beranek & Mello 2012, eqn. 63)
# this is the equivalent of Latex: P^{m}_{v}(z)
legendre_mvz = ((-1)**m)*((1-z**2)**(m/2))*legendre(v,z).diff((z,m))


import numpy as np

H = 1 / np.sqrt(2) * np.array([[1.0, 1.0],
                               [1.0, -1.0]], dtype=np.complex64)

# Pauli X
X = np.array([[0, 1.0],
              [1.0, 0]], dtype=np.complex64)

# Pauli Y
Y = np.array([[0, -1j],
              [1j, 0]], dtype=np.complex64)

# Pauli Z
Z = np.array([[1.0, 0],
              [0, -1.0]], dtype=np.complex64)

def phase_shift(phi: np.real):
    return np.array([[1.0, 0],
                     [0, np.e ** (1.0j * phi)]], dtype=np.complex64)

# S is a pi/2 rotation about the z-axis
S = phase_shift(np.pi / 2)
Sdg = phase_shift(-np.pi / 2)

# T is a pi/4 rotation about the z-axis
T = phase_shift(np.pi / 4)
Tdg = phase_shift(-np.pi / 4)

# Cx is a controlled NOT gate.
# When applied to two qubits a rotation of pi/2 radians
# is applied to the target qubit if the control qubit is |1⟩
# |a, b⟩ --> |a, a ⊗ b⟩
# |00⟩ --> |00⟩, |01⟩ --> |01⟩, |10⟩ --> |11⟩, |11⟩ --> |10⟩
Cx = np.array([[1, 0, 0, 0],
               [0, 1, 0, 0],
               [0, 0, 0, 1],
               [0, 0, 1, 0]], dtype=np.complex64)


from numba import jit, vectorize, guvectorize, float64, complex64, int32, float32
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors

norm =colors.PowerNorm(0.9)
#From https://www.ibm.com/developerworks/community/blogs/jfp/entry/How_To_Compute_Mandelbrodt_Set_Quickly?lang=en
@jit(int32(complex64, int32))
def mandelbrot(c,maxiter):
    nreal = 0
    real = 0
    imag = 0
    for n in range(maxiter):
        nreal = real*real - imag*imag + c.real
        imag = 2* real*imag + c.imag
        real = nreal;
        if real * real + imag * imag > 4.0:
            return n
    return 0

@guvectorize([(complex64[:], int32[:], int32[:])], '(n),()->(n)',target='parallel')
def mandelbrot_numpy(c, maxit, output):
    maxiter = maxit[0]
    for i in range(c.shape[0]):
        output[i] = mandelbrot(c[i],maxiter)

def mandelbrot_set2(xmin,xmax,ymin,ymax,width,height,maxiter):
    r1 = np.linspace(xmin, xmax, width, dtype=np.float32)
    r2 = np.linspace(ymin, ymax, height, dtype=np.float32)
    c = r1 + r2[:,None]*1j
    n3 = mandelbrot_numpy(c,maxiter)
    return (r1,r2,n3.T)

xmin, xmax, xn = -2.25, +0.75, 3000/2
ymin, ymax, yn = -1.25, +1.25, 3000/2
maxiter = 1000

r1,r2,M = mandelbrot_set2(xmin,xmax,ymin,ymax,int(xn),int(yn),maxiter)
fig,ax = plt.subplots(figsize=(12,8))
ax.set_axis_off()
ax.set_title(r"The Mandelbrot Set for $Z_{n + 1} = Z_n^2 + c$ where $c \in \mathbb{C}$")
ax.imshow(M,
                    aspect='equal',
                    cmap='nipy_spectral',
                    norm=norm,
                    interpolation='spline36')
plt.show()
#plt.savefig('/Users/Llewelyn_home/Dropbox/Computation in Python/Chaos and nonlinear/Mandelbrot2.png')


from random import *
from math import sqrt
import matplotlib.pyplot as plt
import numpy as np
n =int(input("Enter the total number of points inside the square or cube "))
pi_true=np.pi

def pi_2D(n):
    inpoint1=0
    for i in range(n):
        x=random()
        y= random()
        if sqrt(x*x+y*y)<=1:
            inpoint1+=1

        pi_2D=4*inpoint1/n

    return pi_2D
    # print("number of points inside the sphere are ",inpoint1)
def pi_3D(n):
    inpoint2=0
    for j in range(n):
        x=random()
        y= random()
        z=random()
        if sqrt(x*x+y*y+z*z)<=1:
            inpoint2+=1

        pi_3D=6*inpoint2/n

    return pi_3D
    # print("num+ber of points inside the circle are ",inpoint2)

# print("The difference between actual value and obtained value ",error1)
print("The value of pi from 2D circle is= ",pi_2D(n))
print("-----------------------------------------------------")

# print("The difference between actual value and obtained value ",error2)
print("The value of pi from 3D sphere is= ",pi_3D(n))

list_n=[]
list_err2D=[]
list_err3D=[]

for k in range(7):
    n=pow(10,k)
    err2D=abs(pi_true-pi_2D(n))
    err3D=abs(pi_true-pi_3D(n))
    list_n.append(n)
    list_err2D.append(err2D)
    list_err3D.append(err3D)

plt.plot(list_n,list_err2D,marker="o",label="Variation")
plt.plot(list_n,list_err3D,marker="o",label="Variation")
plt.title(" Deviation from the true value of pi ")
plt.xlabel("n")
plt.ylabel("Error")
plt.legend()
plt.show()
'''
x=range(n)
plt.title("plot between error and points inside the square")
plt.plot(x,listy1)
plt.plot(x,listy2)
plt.xlabel("n")
plt.ylabel("error")
plt.show()
'''


## import numpy, panda, statsmodels
import pandas as pd
import numpy as np
import statsmodels.api as sm
import matplotlib as mpl
mpl.use('TkAgg') # backend adjustment
import matplotlib.pyplot as plt
from math import sqrt
import random
import seaborn as sns

class OLB_bias:
    def mystats(self,x):
        # isna function
        p = np.where(np.isnan(x))
        if len(p[0]) > 0:
            x = np.delete(x, p)
            return x
        else:
            pass

        m = np.mean(x)
        n = len(x)
        s = np.std(x)
        skew = sum((x - m)**3 /s**3) / n
        kurt = sum((x - m)**4 / s**4) / n - 3
        return {'n':n,
                'mean':m,
                'stdev':s,
                'skewness':skew,
                'kurtosis':kurt}

    def OLS_bias(self,M,n,a,b):
        np.random.seed(1)
        x = np.random.randn(n)
        e = np.zeros((M,n))
        y = np.zeros((M,n))
        ahat = np.zeros((M,1))
        bhat = np.zeros((M,1))
        vara = np.zeros((M,1))
        varb = np.zeros((M,1))
        i = 0

        for i in range(0, M):
            e[i,:] = np.random.randn(n)
            y[i,:] = a+b*x+e[i,:]
            x_experience = sm.add_constant(x)
            model1 = sm.OLS(y[i,:], x_experience)
            result1 = model1.fit()
            ahat[i] = result1.params[0]
            bhat[i] = result1.params[1]
            vara[i] = result1.bse[0]
            vara[i] = result1.bse[1]

        d = {'ahat':ahat.ravel(),
             'bhat':bhat.ravel(),
             'vara':vara.ravel(),
             'varb':varb.ravel()}
        datafour = pd.DataFrame(data=d)
        print datafour
        statistics = []
        for i in datafour:
            u = OLB_bias.mystats(self,datafour[i])
            statistics.append(u)
        print statistics

        self.bias_a = datafour['ahat']
        self.bias_b = datafour['bhat']
        d = {'bias_a': self.bias_a.ravel(),
             'bias_b':self.bias_b.ravel()}
        biasab = pd.DataFrame(data=d)
        #plots
        plt.hist(ahat, 100, facecolor="red")
        plt.xlabel('Estimates of a')
        plt.ylabel('Frequency')
        plt.title('Histogram of estimates a')
        plt.savefig('plot4.png')
        plt.clf()
        plt.hist(bhat, 100, facecolor="blue")
        plt.xlabel('Estimates of b')
        plt.ylabel('Frequency')
        plt.title('Histogram of estimates b')
        plt.savefig('plot5.png')
        plt.clf()
        #density
        sns.kdeplot(np.array(ahat.ravel()), bw=0.5)
        plt.title('Kernel density of a estimates')
        plt.savefig('plot6.png')
        plt.clf()
        sns.kdeplot(np.array(bhat.ravel()), bw=0.5)
        plt.title('Kernel density of b estimates')
        plt.savefig('plot7.png')
        plt.clf()

        return biasab

    # OLS_ebias
    def OLS_ebias(self,M, n, a, b):
        bias = OLB_bias.OLS_bias(self, M, n, a, b)
        self.a = self.bias_a
        self.b = self.bias_b
        d = {'bias_a': self.a.ravel(),
             'bias_b': self.b.ravel()}
        Ebias = pd.DataFrame(data=d)

    #OLS_consistent
    def OLS_consistent(self,M,n,a,b,I):

        ee = np.zeros((I,2))
        for t in range(0, I):
            Ebias = OLB_bias.OLS_ebias(self,M,n,a,b)
            ee[t,0]= self.a
            ee[t,1] = self.b
            n=n+300
        plt.plot(ee[:,0], c='red')
        plt.xlabel('loops of adding 300 observations')
        plt.title('Trend of variance ahat')
        plt.savefig('plot8.png')
        plt.clf()
        plt.plot(ee[:,1], c='blue')
        plt.xlabel('loops of adding 300 observations')
        plt.title('Trend of variance ahat')
        plt.savefig('plot9.png')
        plt.clf()
        print ee

olb = OLB_bias()
mout = olb.OLS_bias(1000, 100, 1.5, 0.7)
olb.OLS_consistent(500,300,1.5,0.7,10)

'''
    for(i in 1:M)
    {
      e[i,]<-rnorm(n)
      y[i,]<-a+b*x+e[i,]
      model<-lm(y[i,]~x)
      ahat[i]<-model$coefficient[1]
      bhat[i]<-model$coefficient[2]
      vara[i]<-summary(model)$coefficient[1,2]
      varb[i]<-summary(model)$coefficient[2,2]
    }
    datafour<-data.frame(ahat,bhat,vara,varb)
    statistics<-sapply(datafour,mystats)
    print(statistics)

    bias_a<-datafour[1]#-a
    bias_b<-datafour[2]#-b
    biasab<-data.frame(bias_a,bias_b)
    colnames(biasab)<-c("bias_a","bias_b")

    par(mfrow=c(2,2))
    hist(datafour$ahat,breaks=100,col="red",xlab="estimates of a",main="Histogram of estimates a")
    hist(datafour$bhat,breaks=100,col="blue",xlab="estimates of a",main="Histogram of estimates b")
    kd_a<-density(datafour$ahat)
    kd_b<-density(datafour$bhat)
    plot(kd_a,main="Kernel density of a estimates",col="red")
    plot(kd_b,main="Kernel density of b estimates",col="blue")

    return(biasab)
    #return(statistics)
}

mout<-OLS_bias(1000,100,1.5,0.7)

##OLS_consistent
OLS_consistent<-function(M,n,a,b,I)
{
    OLS_ebias<-function(M,n,a,b)
    {
      bias<-OLS_bias(M,n,a,b)
      a<-var(bias$bias_a)
      b<-var(bias$bias_b)
      Ebias<-data.frame(a,b)
    }

    ee<-matrix(nrow=I,ncol=2)
    for(t in 1:I)
    {
      Ebias<-OLS_ebias(M,n,a,b)
      ee[t,1]<-Ebias$a
      ee[t,2]<-Ebias$b
      n<-n+300
    }

    plot(ee[,1],xlab="loops of adding 300 observations",ylab=" ",main="Trend of variance ahat",col="red")
    plot(ee[,2],xlab="loops of adding 300 observations",ylab=" ",main="Trend of variance bhat",col="blue")
    ee
}

OLS_consistent(500,300,1.5,0.7,10)

'''


"""
This file runs BlackScholes algorithm on GPU
Created by: Aditya Atluri
"""
from Urutu import *
import numpy as np

@Urutu("gpu")
def black_scholes(si, xi, ti, d1, d2, call_result, put_result, sqrt_ti):
	A1 = 0.31938153
	A2 = -0.356563782
	A3 = 1.781477937
	A4 = -1.821255978
	A5 = 1.330274429
	RSQRT2PI = 0.39894228040
	R = 0.02
	V = 0.3
	tid = tx + bx * Tx
	sqrt_ti[tid] = sqrt(ti[tid])
	d1[tid] = (log(si[tid]/xi[tid]) + (R + 0.5 * V * V) * ti[tid]) / (V * sqrt_ti[tid])
	d2[tid] = d1[tid] - V * sqrt_ti[tid]
	K = 1.0 / (1.0 + 0.2316419 * abs(d1[tid]))
	d1[tid] = RSQRT2PI * exp(-0.5 * d1[tid] * d1[tid]) * (K * (A1 + K * (A2 + K * (A3 + K*(A4+K*A5)))))
	K = 1.0 / (1.0 + 0.2316419 * abs(d2[tid]))
	d2[tid] = RSQRT2PI * exp(-0.5 * d2[tid] * d2[tid]) * (K * (A1 + K * (A2 + K * (A3 + K*(A4+K*A5)))))
	put_result[tid] = exp(-R * ti[tid])
	call_result[tid] = si[tid] * d1[tid] - xi[tid] * put_result[tid] * d2[tid]
	put_result[tid] = xi[tid] * put_result[tid] * (1.0 - d2[tid]) - si[tid] * (1.0 - d1[tid])
	return call_result, put_result

def rand_floats(n, min, max):
	diff = np.float32(max) - np.float32(min)
	rands = np.array(np.random.random(n), dtype=np.float32)
	rands = rands * diff
	rands = rands + np.float32(min)
	return rands

def main():
	n = 100
	S = rand_floats(n, 5, 30)
	X = rand_floats(n, 1, 100)
	T = rand_floats(n, 0.25, 10)
	sqrt_ti = np.empty_like(X)
	call_result = np.empty_like(X)
	put_result = np.empty_like(X)
	d1 = np.empty_like(X)
	d2 = np.empty_like(X)
	print black_scholes([100,1,1],[30,1,1],S, X, T, d1, d2, call_result, put_result, sqrt_ti)
#	print black_scholes(True,S, X, T, d1, d2, call_result, put_result, sqrt_ti)

if __name__ == '__main__':
	main()


# -*- coding: utf-8 -*-
"""
Created on Mon Apr 20 13:05:18 2020

@author: ToshY
"""

import numpy as np

# Activation functions
class ActivationFunctions():
    """
    Providing several activation functions used at forward and
    backward propogation in neural networks.

    Methods
    -------
    identity(x)
        Identity activation function
    identity_deriv(x)
        Derivative of Identity activation function
    sigmoid(x)
        Sigmoid activation function
    sigmoid_deriv(x)
        Derivative of Sigmoid activation function
    tanh(x)
        Tanh activation activation function
    tanh_deriv(x)
        Derivative of Tanh activation function
    arctan(x)
        Arctan activation activation function
    arctan_deriv(x)
        Derivative of Arctan activation function
    arcsin(x)
        Arcsin activation function
    arcsin_deriv(x)
        Derivative of Arcsin activation function
    elu(x, alpha=0.01)
        ELU activation function
    elu_deriv(x, alpha=0.01)
        Derivative of ELU activation function
    selu(x, alpha = 1.67326, lmbda = 1.0507)
        SELU activation function
    selu_deriv(x, alpha = 1.67326, lmbda = 1.0507)
        Derivative of SELU activation function
    relu(x)
        RELU activation function
    relu_deriv(x)
        Derivative of RELU activation function
    prelu(x, alpha=0.01)
        PRELU activation function
    prelu_deriv(x, alpha=0.01)
        Derivative of PRELU activation function
    isru(x, alpha=0.01)
        ISRU activation function
    isru_deriv(x, alpha=0.01)
        Derivative of ISRU activation function
    isrlu(x, alpha=0.01)
        ISRLU activation function
    isrlu_deriv(x, alpha=0.01)
        Derivative of ISRLU activation function
    softsign(x)
        Softsign activation function
    softsign_deriv(x)
        Derivative of Softsign activation function
    softplus(x)
        Softplus activation function
    softplus_deriv(x)
        Derivative of Softplus activation function
    softexp(x)
        Softexp activation function
    softexp_deriv(x)
        Derivative of Softexp activation function
    bent(x)
        Bent activation function
    bent_deriv(x)
        Derivative of Bent activation function
    """

    def identity(x):
        return x

    def identity_deriv(x):
        return 1

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

    def sigmoid_deriv(x):
        xs = (1/(1+np.exp(-x)))
        return (xs*(1-xs))

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

    def tanh_deriv(x):
        return (1-np.power(np.tanh(x),2))

    def arctan(x):
        return np.arctan(x)

    def arctan_deriv(x):
        return (1/(np.power(x,2)+1))

    def arcsin(x):
        return np.arcsin(x)

    def arcsin_deriv(x):
        return (1/np.sqrt(np.power(x,2)+1))

    def elu(x, alpha=0.01):
        x[x<=0] = (alpha*(np.exp(x[x<=0])-1))
        return x

    def elu_deriv(x, alpha=0.01):
        dx = np.ones_like(x)
        dx[x<= 0] = ((alpha*(np.exp(x[x<=0])-1))+alpha)
        return dx

    def selu(x, alpha=1.67326, lmbda=1.0507):
        x[x<0] = (alpha*(np.exp(x[x<0])-1))
        return (lmbda*x)

    def selu_deriv(x, alpha=1.67326, lmbda=1.0507):
        dx = np.ones_like(x)
        dx[x<0] = (alpha*np.exp(x[x<0]))
        return (lmbda * dx)

    def relu(x):
        x[x<0] = 0
        return x

    def relu_deriv(x):
        dx = np.ones_like(x)
        dx[x<0] = 0
        return dx

    def prelu(x, alpha=0.01):
        x[x<0] = alpha*x[x<0]
        return x

    def prelu_deriv(x, alpha=0.01):
        dx = np.ones_like(x)
        dx[x<0] = alpha
        return dx

    def isru(x, alpha=0.01):
        return (x/np.sqrt(1+alpha*np.power(x,2)))

    def isru_deriv(x, alpha=0.01):
        return np.power((1/np.sqrt(1+alpha*np.power(x,2))),3)

    def isrlu(x, alpha=0.01):
        x[x<0] = (x[x<0]/np.sqrt(1+alpha*np.power(x[x<0],2)))
        return x

    def isrlu_deriv(x, alpha=0.01):
        dx = np.ones_like(x)
        dx[x<0] = np.power((1/np.sqrt(1+alpha*np.power(x[x<0],2))),3)
        return dx

    def softsign(x):
        return (x/(1+np.abs(x)))

    def softsign_deriv(x):
        return (1/np.power((1+np.abs(x)),2))

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

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

    def softexp(x, alpha=0.01):
        xt = x
        xt[alpha<0] = ((-np.log(1-alpha*(x[alpha<0]+alpha)))/alpha)
        xt[alpha>0] = (((np.exp(alpha*x[alpha>0])-1)/alpha)+alpha)
        return xt

    def softexp_deriv(x, alpha=0.01):
        dx=x
        dx[alpha<0] = (1/(1-alpha*(alpha+x[alpha<0])))
        dx[alpha>=0] = np.exp(alpha*x[alpha>=0])
        return dx

    def bent(x):
        return (((np.sqrt(np.power(x,2)+1)-1)/2)+x)

    def bent_deriv(x):
        return ((x/(2*np.sqrt(np.power(x,2)+1)))+1)