import numpy as np
import matplotlib.pyplot as plt

x = np.arange(-5.0, 5.0, 0.1)

##You can adjust the slope and intercept to verify the changes in the graph
y = 2*(x) + 3
y_noise = 2 * np.random.normal(size=x.size)
ydata = y + y_noise
#plt.figure(figsize=(8,6))
plt.plot(x, ydata,  'bo')
plt.plot(x,y, 'r')
plt.ylabel('Dependent Variable')
plt.xlabel('Independent Variable')
plt.show()

x = np.arange(-5.0, 5.0, 0.1)

##You can adjust the slope and intercept to verify the changes in the graph
y = 1*(x**3) + 1*(x**2) + 1*x + 3
y_noise = 20 * np.random.normal(size=x.size)
ydata = y + y_noise
plt.plot(x, ydata,  'bo')
plt.plot(x,y, 'r')
plt.ylabel('Dependent Variable')
plt.xlabel('Independent Variable')
plt.show()

x = np.arange(-5.0, 5.0, 0.1)

##You can adjust the slope and intercept to verify the changes in the graph

y = np.power(x,2)
y_noise = 2 * np.random.normal(size=x.size)
ydata = y + y_noise
plt.plot(x, ydata,  'bo')
plt.plot(x,y, 'r')
plt.ylabel('Dependent Variable')
plt.xlabel('Independent Variable')
plt.show()

X = np.arange(-5.0, 5.0, 0.1)

##You can adjust the slope and intercept to verify the changes in the graph

Y= np.exp(X)

plt.plot(X,Y)
plt.ylabel('Dependent Variable')
plt.xlabel('Independent Variable')
plt.show()

X = np.arange(-5.0, 5.0, 0.1)

Y = np.log(X)

plt.plot(X,Y)
plt.ylabel('Dependent Variable')
plt.xlabel('Independent Variable')
plt.show()

X = np.arange(-5.0, 5.0, 0.1)

Y = 1-4/(1+np.power(3, X-2))

plt.plot(X,Y)
plt.ylabel('Dependent Variable')
plt.xlabel('Independent Variable')
plt.show()

import numpy as np
import pandas as pd

# downloading dataset
#wget -nv -O china_gdp.csv https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/IBMDeveloperSkillsNetwork-ML0101EN-SkillsNetwork/labs/Module%202/data/china_gdp.csv

df = pd.read_csv("china_gdp.csv")
df.head(10)

plt.figure(figsize=(8,5))
x_data, y_data = (df["Year"].values, df["Value"].values)
plt.plot(x_data, y_data, 'ro')
plt.ylabel('GDP')
plt.xlabel('Year')
plt.show()

X = np.arange(-5.0, 5.0, 0.1)
Y = 1.0 / (1.0 + np.exp(-X))

plt.plot(X,Y)
plt.ylabel('Dependent Variable')
plt.xlabel('Independent Variable')
plt.show()

def sigmoid(x, Beta_1, Beta_2):
    y = 1 / (1 + np.exp(-Beta_1 * (x - Beta_2)))
    return y

beta_1 = 0.10
beta_2 = 1990.0

#logistic function
Y_pred = sigmoid(x_data, beta_1 , beta_2)

#plot initial prediction against datapoints
plt.plot(x_data, Y_pred*15000000000000.)
plt.plot(x_data, y_data, 'ro')

# Lets normalize our data
xdata =x_data/max(x_data)
ydata =y_data/max(y_data)

from scipy.optimize import curve_fit
popt, pcov = curve_fit(sigmoid, xdata, ydata)
#print the final parameters
print(" beta_1 = %f, beta_2 = %f" % (popt[0], popt[1]))

x = np.linspace(1960, 2015, 55)
x = x/max(x)
plt.figure(figsize=(8,5))
y = sigmoid(x, *popt)
plt.plot(xdata, ydata, 'ro', label='data')
plt.plot(x,y, linewidth=3.0, label='fit')
plt.legend(loc='best')
plt.ylabel('GDP')
plt.xlabel('Year')
plt.show()


import numpy as np

# Gets the Hermitian matrix G such that Inner(p,q) = [p]*G[q]
def constructHermitianG(n):
	G = np.zeros((n,n))
	for i in range(n):
		for j in range(n):
			if (i+j)%2==0:
				G[i][j] = 2.0/(i+j+1.0)
	return G

# Get the inner product of the polynomials
def innerPolynomials(p,q):
	G = constructHermitianG(p.shape[0])
	p = p.reshape((1,p.shape[0]))
	q = q.reshape((q.shape[0],1))
	return (np.matmul(np.conjugate(p),(np.matmul(G,q))))

# Implementation of Modified GS algorithm to get the matrix for the Orthogonal polynomials
def orthogonalizePolynomials(P):

	# Initialising the Q and R matrices
	rows = P.shape[0]
	cols = P.shape[1]
	Q = np.zeros((rows,cols))
	R = np.zeros((cols,cols))

	# Writing the Modified GS pseudocode
	for i in range(cols):
		R[i][i] = np.sqrt(innerPolynomials(P[:,i],P[:,i]))
		Q[:,i] = P[:,i] * 1.0/R[i][i]
		for j in range(i+1,cols):
			R[i][j] = innerPolynomials(Q[:,i],P[:,j])
			P[:,j] = P[:,j] - R[i][j] * Q[:,i]
	return Q

# A function to check whether the implementation is correct or not
# The obtained co-efficients should be multiples of the Legendre Polynomials
def checkCorrectness(n):
	Q = orthogonalizePolynomials(np.eye(n))
	print(Q)

if __name__=='__main__':
	checkCorrectness(5)


"""
Created on May 17, 2020.
Homography

@authors:
Soroosh Tayebi Arasteh <soroosh.arasteh@fau.de> https://github.com/tayebiarasteh/
Amin Heydarshahi <amin.heydarshahi@fau.de> https://github.com/aminheydarshahi/
"""
import numpy as np
import cv2
import pdb

def computeHomography(points1, points2):
    '''
    Compute a homography matrix from 4 point matches.

    :points1: list of 4 points (tuple)
    :points2: list of 4 points (tuple)
    '''
    assert(len(points1) == 4)
    assert(len(points2) == 4)

    # 8x9 matrix A based on the formula from the manual sheet.
    A = np.zeros((8,9))
    for i in range(len(points1)):
        A[i*2:i*2 +2] = np.array([[-points1[i][0], -points1[i][1], -1, 0,0,0,
                                   points1[i][0]*points2[i][0], points1[i][1]*points2[i][0], points2[i][0]],
                          [0,0,0, -points1[i][0], -points1[i][1], -1,
                           points1[i][0]*points2[i][1], points1[i][1]*points2[i][1], points2[i][1]]])

    # SVD decomposition on A
    U, s, V_transposed = np.linalg.svd(A, full_matrices=True)
    V = np.transpose(V_transposed)

    # homogeneous solution of Ah=0 as the rightmost column vector of V.
    H = V[:,-1].reshape(3,3)

    # Normalize H by 1/h8.
    H /= V[:,-1][-1]

    return H

def testHomography():
    '''
    A small test to validate the implementation of computeHomography().
    '''
    points1 = [(1, 1), (3, 7), (2, -5), (10, 11)]
    points2 = [(25, 156), (51, -83), (-144, 5), (345, 15)]

    H = computeHomography(points1, points2)

    print ("Testing Homography...")
    print ("Your result:" + str(H))

    Href = np.array([[-151.2372466105457,   36.67990057507507,   130.7447340624461],
                 [-27.31264543681857,   10.22762978292494,   118.0943169422209],
                 [-0.04233528054472634, -0.3101691983762523, 1]])

    print ("Reference: " + str(Href))

    error = Href - H
    e   = np.linalg.norm(error)
    print ("Error: " + str(e))

    if (e < 1e-10):
        print ("Test: SUCCESS!")
    else:
        print ("Test: FAIL!")
    print ("============================")


from sympy import *
init_printing(pretty_print=true)
x = Symbol('x')

def h(a, b, n):
    resultado = (b - a) / n
    return resultado

def f(x):
    return exp(x)

def aplicando_h(a, b, h):
    lista = [a]
    elemento = 0
    while True:
        if elemento < b:
            elemento = lista[-1] + h
            lista.append(elemento)
        else:
            break
    return lista

def aplicando_x(lista_x):
    lista_x_aplicado = []
    for c in lista_x:
        elemento = f(c)
        lista_x_aplicado.append(elemento)
    return lista_x_aplicado

def trapezios(y, h, subintervalos):
    soma_trapezios = 0
    for c in range(0, subintervalos):
        soma_trapezios += ((y[c] + y[c + 1]) * (h/2))
    return soma_trapezios

def erro(valor_real, valor_soma):
    erro = valor_soma - valor_real
    return erro

# Para 1 subintervalo:
h_1 = h(1, 4, 1)
integral = Integral(exp(x), (x, 1, 4)).doit().evalf()
valores_x_h1 = aplicando_h(1, 4, h_1)
valores_x_h1_aplicados = aplicando_x(valores_x_h1)
trapezios_h_1 = trapezios(valores_x_h1_aplicados, h_1, 1).evalf()
erro_h1 = erro(integral, trapezios_h_1)
porcentagem_erro_h1 = (erro_h1 / integral) * 100

# Para 4 subintervalo:
h_4 = h(1, 4, 4)
valores_x_h4 = aplicando_h(1, 4, h_4)
valores_x_h4_aplicados = aplicando_x(valores_x_h4)
trapezios_h_4 = trapezios(valores_x_h4_aplicados, h_4, 4).evalf()
erro_h4 = erro(integral, trapezios_h_4)
porcentagem_erro_h4 = (erro_h4 / integral) * 100

# Para 10 subintervalo:
h_10 = h(1, 4, 10)
valores_x_h10 = aplicando_h(1, 4, h_10)
valores_x_h10_aplicados = aplicando_x(valores_x_h10)
trapezios_h_10 = trapezios(valores_x_h10_aplicados, h_10, 10).evalf()
erro_h10 = erro(integral, trapezios_h_10)
porcentagem_erro_h10 = (erro_h10 / integral) * 100

# Para 100 subintervalo:
h_100 = h(1, 4, 100)
valores_x_h100 = aplicando_h(1, 4, h_100)
valores_x_h100_aplicados = aplicando_x(valores_x_h100)
trapezios_h_100 = trapezios(valores_x_h100_aplicados, h_100, 100).evalf()
erro_h100 = erro(integral, trapezios_h_100)
porcentagem_erro_h100 = (erro_h100 / integral) * 100

print('Para 1 subintervalo: ')
print('-' * 30)
print(f'Soma dos trapézios = {trapezios_h_1:.3f}')
print(f'Integral = {integral:.3f}')
print(f'Erro = {erro_h1:.3f}')
print(f'Porcentagem do erro = {porcentagem_erro_h1:.3f}%')
print('-' * 30)
print('Para 4 subintervalos: ')
print('-' * 30)
print(f'Soma dos trapézios = {trapezios_h_4:.3f}')
print(f'Erro = {erro_h4:.3f}')
print(f'Porcentagem do erro = {porcentagem_erro_h4:.3f}%')
print('-' * 30)
print('Para 10 subintervalos: ')
print('-' * 30)
print(f'Soma dos trapézios = {trapezios_h_10:.3f}')
print(f'Erro = {erro_h10:.3f}')
print(f'Porcentagem do erro = {porcentagem_erro_h10:.3f}%')
print('-' * 30)
print('Para 100 subintervalos: ')
print('-' * 30)
print(f'Soma dos trapézios = {trapezios_h_100:.3f}')
print(f'Erro = {erro_h100:.3f}')
print(f'Porcentagem do erro = {porcentagem_erro_h100:.3f}%')


#python3
#plot logistic map equation
#logistic map equation:  x := r*x*(1-x)

import numpy as np
import matplotlib.pyplot as plt

# Logistic function implementation
def logistic_eq(r,x):
    return r*x*(1-x)
    #return r*x*(1-x)**2

def showLogisticMap():
    # Show the logistic function
    x = np.linspace(0, 1)
    plt.plot(x, logistic_eq(2, x), 'k')
    plt.show()

# Iterate the function for a given r
def logistic_equation_orbit(seed, r, n_iter, n_skip=0):
    print('Orbit for seed {0}, growth rate of {1}, plotting {2} iterations after skipping {3}'.format(seed, r, n_iter, n_skip))
    X_t=[]
    T=[]
    t=0
    x = seed
    # Iterate the logistic equation, printing only if n_skip steps have been skipped
    for i in range(n_iter + n_skip):
        if i >= n_skip:
            X_t.append(x)
            T.append(t)
            t+=1
        x = logistic_eq(r,x)
    # Configure and decorate the plot
    plt.plot(T, X_t)
    plt.ylim(0, 1)
    plt.xlim(0, T[-1])
    plt.xlabel('Time t')
    plt.ylabel('X_t')
    plt.show()

# Create the bifurcation diagram
def bifurcation_diagram(seed, n_skip, n_iter, step=0.0001, r_min=0):
    print("Starting with x0 seed {0}, skip plotting first {1} iterations, then plot next {2} iterations.".format(seed, n_skip, n_iter))
    # Array of r values, the x axis of the bifurcation plot
    R = []
    # Array of x_t values, the y axis of the bifurcation plot
    X = []

    # Create the r values to loop. For each r value we will plot n_iter points
    r_range = np.linspace(r_min, 4, int(1/step))

    for r in r_range:
        x = seed
        # For each r, iterate the logistic function and collect datapoint if n_skip iterations have occurred
        for i in range(n_iter+n_skip+1):
            if i >= n_skip:
                R.append(r)
                X.append(x)

            x = logistic_eq(r,x)
    # Plot the data
    plt.plot(R, X, ls='', marker=',')
    plt.ylim(0, 1)
    plt.xlim(r_min, 4)
    plt.xlabel('r')
    plt.ylabel('X')
    plt.show()

def main():
    #logistic_equation_orbit(0.1, 3.05, 100)
    #logistic_equation_orbit(0.1, 3.9, 100)
    #logistic_equation_orbit(0.1, 3.0, 100)

    #bifurcation_diagram(0.2, 100, 5)
    bifurcation_diagram(0.2, 100, 10)
    #bifurcation_diagram(0.1, 100, 1000)

if __name__=='__main__':
    main()


import numpy as np
import matplotlib.pylab as plt
import scipy
import scipy.linalg
import sys

def lu_decomposition(A):
    m, n = A.shape

    LU = np.copy(A)
    pivots = np.empty(n, dtype=int)
    # initialise the pivot row and column
    h = 0
    k = 0
    while h < m and k < n:
        # Find the k-th pivot:
        pivots[k] = np.argmax(LU[h:, k]) + h
        if LU[pivots[k], k] == 0:
            # No pivot in this column, pass to next column
            k = k+1
        else:
            # swap rows
            LU[[h, pivots[k]], :] = LU[[pivots[k], h], :]
            # Do for all rows below pivot:
            for i in range(h+1, m):
                f = LU[i, k] / LU[h, k]
                # Store f as the new L column values
                LU[i, k] = f
                # Do for all remaining elements in current row:
                for j in range(k + 1, n):
                    LU[i, j] = LU[i, j] - LU[h, j] * f
            # Increase pivot row and column
            h = h + 1
            k = k + 1
    return LU, pivots

def random_matrix(n):
    R = np.random.rand(n, n)
    A = np.zeros((n, n))
    triu = np.triu_indices(n)
    A[triu] = R[triu]
    return A

def random_non_singular_matrix(n):
    A = np.random.rand(n, n)
    while np.linalg.cond(A) > 1/sys.float_info.epsilon:
        A = np.random.rand(n, n)
    return A

As = [
    np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]),
    random_non_singular_matrix(3),
    random_non_singular_matrix(4),
    random_non_singular_matrix(5),
    random_non_singular_matrix(6),
]

def pivots_to_row_indices(pivots):
    n = len(pivots)
    indices = np.array(range(0, n))
    for i, p in enumerate(pivots):
        indices[i], indices[p] = indices[p], indices[i]
    return indices

def calculate_L_mult_U(LU):
    L = np.tril(LU)
    np.fill_diagonal(L, 1)
    U = np.triu(LU)
    return L @ U

for A in As:
    LU_scipy, pivots_scipy = scipy.linalg.lu_factor(A)
    row_indices_scipy = pivots_to_row_indices(pivots_scipy)
    LU_mine, pivots_mine = lu_decomposition(A)
    row_indices_mine = pivots_to_row_indices(pivots_mine)

    np.testing.assert_almost_equal(calculate_L_mult_U(LU_scipy),  A[row_indices_scipy])
    np.testing.assert_almost_equal(calculate_L_mult_U(LU_mine),  A[row_indices_mine])


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

''' basic example '''
import numpy as np
import matplotlib.pyplot as plt
from scipy import interpolate

x = np.arange(0,10)
y = np.exp(-x/3.0)
f = interpolate.interp1d(x, y, fill_value='extrapolate')

print (f(9))
print (f(15)) # extrapolated value got here

import numpy as np
import scipy.interpolate as interpolate

'''
Extrapolation is the process of
finding a value outside two points
on a line or a curve.
'''

kinds = ('nearest',
         'zero',
         'linear',
         'slinear',
         'quadratic',
         'cubic')

''' x- axis values '''
x = np.array([0.0,1.0,2.0,3.0,
              4.0,5.0,6.0,7.0,
              8.0,9.0])

''' y- axis values '''
y = np.array([10,5,39,3,6,9,41,73,57,88])

''' plot original values '''
plt.plot(x, y, 'o')

''' new x-axis values to be interpolated '''
new_x = np.linspace(0.0, 19.0, 25)

''' extrapolation for kind = 'nearest' '''
new_y = interpolate.interp1d(x,y,kind='nearest',fill_value='extrapolate')(new_x)
plt.plot(new_x, new_y, color='C1', linewidth=1)

''' extrapolation for kind = 'zero' does not work and not applicable '''
#new_y = interpolate.interp1d(x,y,kind='zero',fill_value='extrapolate')(new_x)
#plt.plot(new_x, new_y, color='C2', linewidth=1)

''' extrapolation for kind = 'linear' '''
new_y = interpolate.interp1d(x,y,kind='linear',fill_value='extrapolate')(new_x)
plt.plot(new_x, new_y, color='C3', linewidth=1)

''' extrapolation for kind = 'slinear' does not work and not applicable '''
#new_y = interpolate.interp1d(x,y,kind='slinear',fill_value='extrapolate')(new_x)
#plt.plot(new_x, new_y, color='C4', linewidth=1)

''' extrapolation for kind = 'cubic' does not work and not applicable '''
#new_y = interpolate.interp1d(x,y,kind='cubic',fill_value='extrapolate')(new_x)
#plt.plot(new_x, new_y, color='C5', linewidth=1)

plt.legend(['original','nearest','linear'])
plt.show()

''' Extrapolation using InterpolatedUnivariateSpline and custom order type '''
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import InterpolatedUnivariateSpline

# given values
xi = np.array([0.2, 0.5, 0.7, 0.9])
yi = np.array([0.3, 0.1, 0.2, 0.1])
# expand x values
x = np.linspace(0, 1.5, 50)
# spline order: 1=linear, 2=quadratic, 3=cubic ...
order = 1
# do inter/extrapolation
s = InterpolatedUnivariateSpline(xi, yi, k=order)
y = s(x)

# example showing the interpolation for following kind
# linear, quadratic and cubic interpolation
#plt.figure()
plt.plot(xi, yi)

for order_new in range(1, 4):
    s = InterpolatedUnivariateSpline(xi, yi, k=order_new)
    y = s(x)
    plt.plot(x, y)
plt.legend(['linear','quadratic','cubic'])
plt.show()


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

x = np.matrix([74.4, 93.4, 110.4, 130.6, 148.9, 170.7, 191.1, 211.8, 231.7, 259.1]).transpose()
y = np.matrix([1.64, 2.03, 3.16, 3.96, 4.78, 6.21, 7.28, 8.91, 8.79, 8.63]).transpose()

x_log10 = np.log10(x)
A = np.concatenate((np.matrix(np.ones(x.shape[0])).transpose(), x_log10), axis=1)
b = np.log10(y)

betas = inv(A.transpose() * A) * A.transpose() * b

y_log_fit = A * betas

plt.figure(figsize=(10,8))
plt.plot(np.array(x_log10), np.array(b), "ro", markersize=8)
plt.plot(np.array(x_log10), np.array(y_log_fit), "b", linewidth=2)
plt.xlabel("$\log_{10}x$ (kg)", fontsize=24)
plt.ylabel("$\log_{10}y$ (kg)", fontsize=24)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.legend(["Data", "Best fit, $\log_{10}y = %.3f + %.3f\log_{10}x$" % (betas.item((0, 0)), betas.item((1, 0)))], loc="upper left", fontsize=20)
plt.savefig("problem_1_log_fit.eps", format="eps", dpi=1000)
plt.show()

print betas.item((1, 0))

y_fit = np.power(10, betas.item((0, 0))) * np.power(x, betas.item((1, 0)))

plt.figure(figsize=(10,8))
plt.plot(np.array(x), np.array(y), "ro", markersize=8)
plt.plot(np.array(x), np.array(y_fit), "b", linewidth=2)
plt.xlabel("$x$ (kg)", fontsize=24)
plt.ylabel("$y$ (kg)", fontsize=24)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.legend(["Data", "Best fit, $y = %.3f * x ^ {%.3f}$" % (np.power(10, betas.item((0, 0))), betas.item((1, 0)))], loc="upper left", fontsize=20)
plt.savefig("problem_1_fit.eps", format="eps", dpi=1000)
plt.show()


import numpy as np

def interp(P0,P1,t):
    return (1-t)*P0+t*P1

def deCasteljau(P:np.array,t:float)->np.array:
    n=P.shape[1]
    if n==1:
        return P
    else:
        nP=np.zeros((P.shape[0],n-1))
        for i in range(n-1):
            nP[:,i]=interp(P[:,i],P[:,i+1],t)
        return deCasteljau(nP,t)

def B(i:int,t:float)->float:
    if i==0:
        return (1-t)**3
    elif i==1:
        return 3*(1-t)**2*t
    elif i==2:
        return 3*(1-t)*t**2
    elif i==3:
        return t**3

def bezier(P:np.array,t:float)->np.array:
    return (P[:,0]*B(0,t)+
            P[:,1]*B(1,t)+
            P[:,2]*B(2,t)+
            P[:,3]*B(3,t))

def flatness(P:np.array)->float:
    """
    Calculate the flatness of a given curve
    From https://www.joshondesign.com/2018/07/11/bezier-curves and
    https://hcklbrrfnn.wordpress.com/2012/08/20/piecewise-linear-approximation-of-bezier-curves/

    """
    u=(3*P[:,1]-2*P[:,0]-P[:,3])**2
    v=(3*P[:,2]-2*P[:,3]-P[:,0])**2
    w=np.where(u<v)
    u[w]=v[w]
    return np.sum(u)

def split(P:np.array,t:float)->tuple:
    p01=interp(P[:,0],P[:,1],t)
    p12=interp(P[:,1],P[:,2],t)
    p23=interp(P[:,2],P[:,3],t)
    p012=interp(p01,p12,t)
    p123=interp(p12,p23,t)
    p0123=interp(p012,p123,t)
    return (np.hstack((P[:,0],p01,p012,p0123)),np.hstack((p0123,p123,p23,P[:,1])))

def flatten(P:np.array,tol:float=1)->np.array:
    """
    Return a polyline which approximates the given cubic Bezier curve to within
    the given tolerance
    """
    if flatness(P)<tol:
        return np.hstack((P[:,0],P[:,3]))
    else:
        P0,P1=split(P,0.5)
        return np.hstack((flatten(P0,tol),flatten(P1,tol)))

def arc_l90(theta:float)->np.array:
    """
    Create a cubic Bezier curve which approximates a circular arc. Based on
    https://pomax.github.io/bezierinfo/#circles_cubic .

    :param theta: Angle of arc. The arc start on the +X axis and grows towards +Y. The math works up to
    180deg, but the approximation is noticeably worse, so we recommend only
    using this up to 90deg. At this angle, the maximum error between an actual circle and this curve
    is 2.7e-4 (2.7 unit error on a circle with radius 10,000 units).
    :return: Control points for a single Bezier curve which approximates the arc, in the form of a 2-row, 4-column
             numpy array.
    """
    k=4*np.tan(theta/4)/3
    c=np.cos(theta)
    s=np.sin(theta)
    result=np.zeros((2,4))
    result[:,0]=(1,0)
    result[:,1]=(1,k)
    result[:,2]=(c+k*s,s-k*c)
    result[:,3]=(c,s)
    return result


'''
Author: jianzhnie
Date: 2021-12-22 09:44:16
LastEditTime: 2021-12-22 09:46:32
LastEditors: jianzhnie
Description:

'''

import numpy as np

def softmax_(x):
    exp_x = np.exp(x)
    softmax_x = exp_x / np.sum(exp_x)
    return softmax_x

def softmax(x):
    x = x - np.max(x)
    exp_x = np.exp(x)
    softmax_x = exp_x / np.sum(exp_x)
    return softmax_x


import numpy as np

# equivalent to MATLAB sph2cart & cart2sph
def cart2sph(x,y,z):
    azimuth = np.arctan2(y,x)
    elevation = np.arctan2(z,np.sqrt(x**2 + y**2))
    r = np.sqrt(x**2 + y**2 + z**2)
    return azimuth, elevation, r

def sph2cart(azimuth,elevation,r):
    x = r * np.cos(elevation) * np.cos(azimuth)
    y = r * np.cos(elevation) * np.sin(azimuth)
    z = r * np.sin(elevation)
    return x, y, z

# get the angular distances from a single point to a list of points
def getDistances(p_az, p_el, grid_az, grid_el, input_format='deg', return_format='rad'):

    if input_format == 'deg':
        p_az = p_az * np.pi / 180
        p_el = p_el * np.pi / 180
        grid_az = grid_az * np.pi / 180
        grid_el = grid_el * np.pi / 180

    x1, y1, z1 = sph2cart(p_az, p_el, 1);
    x2, y2, z2 = sph2cart(grid_az, grid_el, 1);

    # make the single point value a matrix with same dimensions as the grid
    x1 = x1 * np.ones_like(x2)
    y1 = y1 * np.ones_like(z2)
    z1 = z1 * np.ones_like(z2)

    dotProduct = np.einsum('ji,ji->i', [x1, y1, z1], [x2, y2, z2])

    distances = np.arccos(np.clip(dotProduct, -1.0, 1.0));

    if return_format == 'deg':
        distances = distances * 180 / np.pi

    return distances

# get the angular distance from one point to another
def angularDistance(az1, el1, az2, el2, input_format='deg', return_format='deg'):

    if input_format == 'deg':
        az1 = az1 * np.pi / 180
        az2 = az2 * np.pi / 180
        el1 = el1 * np.pi / 180
        el2 = el2 * np.pi / 180

    x1, y1, z1 = sph2cart(az1, el1, 1);
    x2, y2, z2 = sph2cart(az2, el2, 1);

    # distance = np.arctan2(np.linalg.norm(np.cross(xyz1, xyz2)), np.dot(xyz1, xyz2)) / 180;
    distance = np.arccos(np.clip(np.dot([x1, y1, z1], [x2, y2, z2]), -1.0, 1.0));
    # distance = np.arccos(np.clip(0.5, -1.0, 1.0)) / np.pi;

    if return_format == 'deg':
        distance = distance * 180 / np.pi

    return distance


## Tools for solving differential equations. Also includes a solver for quadratic
## equations and a simple implementation of the bisection method.

import numpy as np

def fe(f, t0, y0, h, N):
    """"Solve IVP given by y' = f(t, y), y(t_0) = y_0 with step size h > 0, for N steps,
    using the Forward-Euler method.
    Also works if y is an n-vector and f is a vector-valued function."""
    t = t0 + np.array([i * h for i in range(N+1)])
    m = len(y0)
    y = np.zeros((N+1, m))
    y[0] = y0

    # Repeatedly approximate next value.
    for n in range(N):
        y[n+1] = y[n] + h*f(t[n], y[n])
    return t, y

def rk4(f, t0, y0, h, N):
    """"Solve IVP given by y' = f(t, y), y(t_0) = y_0 with step size h > 0, for N steps,
    using the Runge-Kutta 4 method.
    Also works if y is an n-vector and f is a vector-valued function."""
    t = t0 + np.array([i * h for i in range(N+1)])
    m = len(y0)
    y = np.zeros((N+1, m))
    y[0] = y0

    # Repeatedly approximate next value.
    for n in range(N):
        k1 = f(t[n], y[n])
        k2 = f(t[n] + h/2, y[n] + k1 * h/2)
        k3 = f(t[n] + h/2, y[n] + k2 * h/2)
        k4 = f(t[n] + h, y[n] + k3 * h)
        y[n+1] = y[n] + h * (k1 + 2 * k2 + 2 * k3 + k4) / 6

    return t, y

def solve_quadratic(a, b, c):
    """Returns the two solutions of the quadratic equation ax^2 + bx + c = 0."""
    D = b ** 2 - 4 * a * c
    assert D >= 0
    return (-b + np.sqrt(D)) / (2 * a), (-b - np.sqrt(D)) / (2 * a)

def bisect(f, x_low, x_high, n):
    """Apply bisection method n times to function f."""
    s = np.sign(f(x_low))
    t = np.sign(f(x_high))
    assert s != t

    for _ in range(n):
        x = (x_low + x_high) / 2
        y = f(x)
        if y == 0:
            return x
        elif np.sign(y) == s:
            x_low = x
        else:
            x_high = x
    return (x_low + x_high) / 2


import numpy
from matplotlib import pyplot

def basic_integral(f, a, b, N):
    x, dx = numpy.linspace(a, b, N+1, retstep=True)
    fx = f(x)
    return dx * numpy.sum(fx[:-1])

def trapezoidal(f, a, b, N):
    x, dx = numpy.linspace(a, b, N+1, retstep=True)
    fx = f(x)
    return dx * ( (fx[0] + fx[-1])/2 + numpy.sum(fx[1:-1]) )

if __name__=="__main__":
    print("Basic integral")
    I2 = basic_integral(numpy.sin, 0, numpy.pi/2, 2)
    print(I2)
    I20 = basic_integral(numpy.sin, 0, numpy.pi/2, 20)
    print(I20)
    I200 = basic_integral(numpy.sin, 0, numpy.pi/2, 200)
    print(I200)
    Npoints = 2**numpy.arange(1,20)
    dx_all = numpy.pi/2/Npoints
    errors = numpy.zeros_like(dx_all)
    for i, N in enumerate(Npoints):
        I = basic_integral(numpy.sin, 0, numpy.pi/2, N)
        errors[i] = abs(I - 1)
    pyplot.loglog(dx_all, errors, 'kx')
    pyplot.loglog(dx_all, errors[-1]*(dx_all/dx_all[-1]), 'b-')
    pyplot.xlabel(r"$\Delta x$")
    pyplot.ylabel("Error")
    pyplot.show()

    print("Trapezoidal rule")
    I2 = trapezoidal(numpy.sin, 0, numpy.pi/2, 2)
    print(I2)
    I20 = trapezoidal(numpy.sin, 0, numpy.pi/2, 20)
    print(I20)
    I200 = trapezoidal(numpy.sin, 0, numpy.pi/2, 200)
    print(I200)
    Npoints = 2**numpy.arange(1,20)
    dx_all = numpy.pi/2/Npoints
    errors = numpy.zeros_like(dx_all)
    for i, N in enumerate(Npoints):
        I = trapezoidal(numpy.sin, 0, numpy.pi/2, N)
        errors[i] = abs(I - 1)
    pyplot.loglog(dx_all, errors, 'kx')
    pyplot.loglog(dx_all, errors[-1]*(dx_all/dx_all[-1])**2, 'b-')
    pyplot.xlabel(r"$\Delta x$")
    pyplot.ylabel("Error")
    pyplot.show()


import numpy as np
print("np.__version__ : \n",np.__version__)

vec1 = np.array([1, 2, 3, 4, 5]) # [1 2 3 4 5] is created
print("vec1 : \n",vec1)
print("vec1.size : \n",vec1.size)
vec2 = np.arange(5) # [0 1 2 3 4] is created
print("vec2 : \n",vec2)
print("vec2.size : \n",vec2.size)

print("np.dot(vec1,vec2) : ",np.dot(vec1,vec2)) # multiply vectors item by item and sum -> 40

vec1 = np.append(vec1,6)
print("vec1 after np.append(vec1,6)",vec1)

# create matrix from vector
mat_vec1 = np.array([vec1]) # matrix 1x6
mat_vec1_transpose = mat_vec1.T # matrix 6x1

print("vec1'*vec1",np.matmul(mat_vec1_transpose,mat_vec1)) # 6 x 6

# ************** handle matrix
mat1 = np.array([[1 , 0] , [3 , 4]])
print("mat1.shape : \n",mat1.shape) # number rows , number cols -> 2 , 2
print("mat1 : \n",mat1)
mat2 = np.array([[0 , 5] , [6 , 7]])
print("mat2 : \n",mat2)

# ----- multiply matrix
print("np.matmul(mat1,mat2) : \n",np.matmul(mat1,mat2))

# ----- transpose matrix
print("mat1.T : \n",mat1.T)

# ----- inverse matrix
print("inverse mat1 \n",np.linalg.inv(mat1))
print("mat1*inverse mat1 is identity\n",np.matmul(mat1,np.linalg.inv(mat1)))


# Two vector are orthogonal if u.v = |u||v|cos@ = 0 or u _|_ v = 90*

import numpy as np

a = np.array([-2, 4, 3])
b = np.array([3, 3, -2])

def is_orthogonal():
    res = np.dot(a, b)

    if not res:
        print("Orthogonal")
    else:
        print("not orthogonal")

is_orthogonal()


import scipy.integrate as spi
import numpy as np
import matplotlib.pyplot as plt
import json
import os
import pandas as pd

def run(args):
    DIR     = "Results\\SIR_{}".format(args["Name"])
    os.makedirs(DIR,exist_ok=True)
    POP     = args["Population"]
    R0      = args["R0"]
    DR      = args["Death_Rate"]
    GAMMA   = args["GAMMA"] = 1/args["Inc_Period"]
    BETA    = args["BETA"]  = GAMMA*R0
    TS      = 1       #Time step
    Ndays   = args["Days"]       #Number of days
    Inf0    = args["Init_Inf"]/args["Population"]
    Susp0   = 1-Inf0
    Rec0    = 0
    Dec0    = 0
    INPUT   = (Susp0, Inf0, Rec0,Dec0)
    # INPUT   = {'S':Susp0, 'I':Inf0, 'R':Rec0}
    strs    = ['S','I','R','D']

    def diff_eqs(INP,t):
        '''The main set of equations'''
        Y   = np.zeros((4))
        V   = dict(zip(strs,INP)) #[Susp,Inf,Rec]
        Y[0] = - BETA * V['S'] * V['I']
        Y[1] = BETA * V['S'] * V['I'] - GAMMA * V['I']
        Y[2] = GAMMA * V['I'] * (1-DR)
        Y[3] = GAMMA * V['I'] * DR
        return Y   # For odeint

    t_start = 0.0; t_end = 1.0*Ndays; t_inc = 1.0*TS
    t_range = np.arange(t_start, t_end+t_inc, t_inc)
    RES     = spi.odeint(diff_eqs,INPUT,t_range)

    plt.subplot(211)
    plt.plot(RES[:,0]*POP, '-g', label='Suspectible')
    plt.plot(RES[:,1]*POP, '-m', label='Infectious')
    plt.plot(RES[:,2]*POP, '-b', label='Recoveries')
    plt.plot(RES[:,3]*POP, '-r', label='Deaths')
    plt.legend(loc=0)
    plt.title('SIR Model for R0='+str(R0)[:4])
    # plt.xticks(np.arange(0,Ndays,TS))/
    plt.xlabel('Timestep')
    plt.ylabel('Number')
    plt.grid()

    plt.subplot(212)
    plt.plot(RES[:,1]*POP, '-r', label='Infectious')
    plt.xlabel('Timestep')
    plt.ylabel('Infectious')
    # plt.show()

    df = pd.DataFrame(np.round(RES*POP),columns=["Suspectible",'Infected','Recovered',"Died"])
    df.index.name = "Day"

    plt.savefig(DIR+"//graph.png")
    json.dump(args,open(DIR+"//params.json", 'w'),indent = 4)
    df.to_csv(DIR+"//output.csv")

with open("SIR_parameters.json") as f:
    args = json.load(f)

Nos = 9
R0s = np.linspace(1,4,Nos)
for x in range(Nos):
    args["Name"] = R0s[x]
    args["R0"] = R0s[x]
# run(args)


# %% Imports
import os
import sys
import numpy as np
from scipy.spatial.transform import Rotation
import matplotlib.pyplot as plt

sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/../")
from utils.viz.viz import plot_fustrum, plot_crs, set_3d_axes_equal

# %% Test angles
euler_deg = [45, 15, -25]

# %% Conversion: Euler angles to rotation matrix
R = Rotation.from_euler('xyz', euler_deg, degrees=True)
print(R.as_matrix())

# %%
##  Intrinsic & extrinsic rotations

# %% Vizualization
def plot_rotation(x, y, z, intrinsic=True):
    plt.figure(figsize=(12,10))
    ax = plt.axes(projection='3d')
    if intrinsic:
        R = Rotation.from_euler('XYZ', [x, y, z], degrees=True)
    else:
        R = Rotation.from_euler('xyz', [x, y, z], degrees=True)
    plot_fustrum(ax, [0, 0, 0], R.as_matrix(), img_limits=[1, 0.5], f=2.0, scale=1.0, c='k')
    plot_crs(ax, np.eye(3), X=[-2, -2, 0])
    crs = R.as_matrix()
    plot_crs(ax, crs)
    set_3d_axes_equal(ax)

plot_rotation(0, 0, 0)

# %% Rotation around X axis (red)
plot_rotation(90, 0, 0)

# %% Rotation around Y axis in the rotated system
# This is the rotation around a downward looking Z axis in the world
plot_rotation(90, 90, 0)

# %% Rotation around Z axis in the double rotated system
# This is the rotation around the X axis in the world
plot_rotation(90, 90, 45)

# %% Explanation
# link: https://en.wikipedia.org/wiki/Euler_angles#Definition_by_intrinsic_rotations
# Twelve combinations of x, y, z:
# Used in mechatrocins: z-x-z, x-y-x, y-z-y, z-y-z, x-z-x, y-x-y
# Navigation: x-y-z, y-z-x, z-x-y, x-z-y, z-y-x, y-x-z

# %% Looking at the specs of scipy:
# link: https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.transform.Rotation.from_euler.html
# {‘X’, ‘Y’, ‘Z’} for intrinsic rotations, or
# {‘x’, ‘y’, ‘z’} for extrinsic rotations.

# %% Ok let's play the same just with extrinsic rotation
plot_rotation(90, 0, 0, intrinsic=False)
# this is the same as before

# %% Rotation around Y axis in the (!!!) world system
plot_rotation(90, 90, 0, intrinsic=False)
# this is different

# %% Rotation around Z axis in the (!!!) world system
plot_rotation(90, 90, 45, intrinsic=False)

# %%
# So we have 12 combinations for intrinsic and 12 combinations for extrinics
# This is all together 24 combinations to describe rotations with Euler angles.

# %% Statement 1
# A sequence of intrinsic rotations produces the same total rotations
# as the sequence of extrinsic rotations with the same angles but in reverse order.
# Proof: https://math.stackexchange.com/questions/1137745/proof-of-the-extrinsic-to-intrinsic-rotation-transform/3314025
rpy = euler_deg
R_int = Rotation.from_euler('xyz', rpy, degrees=True) # extrinsic
ypr = [euler_deg[2], euler_deg[1], euler_deg[0]]
R_ext = Rotation.from_euler('ZYX', ypr, degrees=True) # intrinsic
chk = np.linalg.norm(R_int.as_matrix() - R_ext.as_matrix())
print(f"Difference of the two matrices: {chk}")

# %% Composing rotation matrix from euler angles
# 1. Order of angles
# 2. Order of multiplying matrices

R_x = Rotation.from_euler('x', euler_deg[0], degrees=True).as_matrix()
R_y = Rotation.from_euler('y', euler_deg[1], degrees=True).as_matrix()
R_z = Rotation.from_euler('z', euler_deg[2], degrees=True).as_matrix()

R_int = Rotation.from_euler('XYZ', euler_deg, degrees=True).as_matrix()
chk = np.linalg.norm(R_x @ R_y @ R_z - R_int)
print(f'Intrinsics check: {chk}')

R_ext = Rotation.from_euler('xyz', euler_deg, degrees=True).as_matrix()
chk = np.linalg.norm(R_z @ R_y @ R_x - R_ext)
print(f'Extrinsics check: {chk}')

# %%
## Conversion: rotiation matrix to Euler angles

# %%
R = Rotation.from_euler('xyz', euler_deg, degrees=True)
print(R.as_matrix())
print(R.as_euler('xyz', degrees=True))

# %% Gimbal lock
# link: https://matthew-brett.github.io/transforms3d/gimbal_lock.html
R = Rotation.from_euler('xyz', [10, -90, 10], degrees=True)
print(R.as_matrix())
print(R.as_euler('xyz', degrees=True))

# %%
import math
from math import sin, cos

def getR(pitch, roll, yaw):
    R1 = [cos(yaw)*cos(roll)+sin(yaw)*sin(pitch)*sin(roll), -sin(yaw)*cos(roll)+cos(yaw)*sin(pitch)*sin(roll), -cos(pitch)*sin(roll)]
    R2 = [cos(yaw)*sin(roll)-sin(yaw)*sin(pitch)*cos(roll), -sin(yaw)*sin(roll)-cos(yaw)*sin(pitch)*cos(roll), cos(pitch)*cos(roll)]
    R3 = [-sin(yaw)*cos(pitch), -cos(yaw)*cos(pitch), -sin(pitch)]
    return np.vstack([R1, R2, R3])

yaw = 35/180*math.pi
pitch = -45/180*math.pi
roll = -185/180*math.pi

R_fn = getR(pitch, roll, yaw)
R = Rotation.from_euler('zxz', [yaw, pitch, roll], degrees=False).as_matrix()

print(np.linalg.norm(R_fn-R))
np.set_printoptions(suppress=True)
print(R_fn)
print(' ')
print(R)

# %%

# %%
print(Rotation.from_euler('XYZ', [90, 45, 0], degrees=True).as_matrix())
# %%
print(np.linalg.det(R1))
print(R1.T - np.linalg.inv(R1))

# %%
R1.T - np.linalg.inv(R1)

# %%


#ZADANIE 1
#Napisz program realizujacy poszukiwanie miejsc zerowych
#powyzszych funkcji z punktu a) i b). Wykorzystaj metode graficzna,
#liniowej inkrementacji i bisekcji. Stworz odpowiednie funkcje
#implementujace wymienione metody poszukiwania miejsc zerowych.
#Dobierz odpowiednio obszary wyszukiwania. Wykonaj analize bledow.
#Opisz w sprawozdaniu wnioski.

import numpy as np
import matplotlib.pyplot as plt

def fxA(x):
    return 7 * x**5 + 9 * x**2 - 5 * x

def fxB(x):
    return (1 / ((x - 0.3)**2 + 0.01)) - (1 / ((x - 0.8)**2) + 0.04)

def linearIncremental(fx, xstart, xd, maxincr):
    x = xstart
    fstart = fx(x)
    for i in range(maxincr):
        x = xstart + i * xd
        if fstart * fx(x) < 0:
            break
    if fstart * fx(x) > 0:
        raise Exception("Nie znaleziono rozwiazania!")
    else:
        return x - (xd * fx(x)) / (fx(x)-fx(x - xd))

def bisection(fx, a, b, err):
    while np.absolute(b - a) > err:
        midPoint = (a + b) * 0.5
        if fx(midPoint) * fx(a) < 0:
            b = midPoint
        midPoint = (a + b) * 0.5
        if fx(midPoint) * fx(b) < 0:
            a = midPoint
    return b - (b - a) * fx(b) / (fx(b) - fx(a))

print("Funkcja A")
print("\nTest metody graficznej")
x = np.arange(-3, 3, 0.1)
plt.plot(x, fxA(x), 'r.')
plt.grid(True)
plt.show()

print("\nTest metody inkrementacji")
err = fxA(linearIncremental(fxA, -3, 0.01, 500))
print("x1 = ",linearIncremental(fxA, -3, 0.01, 500), "fx(x1) = ", err)
err = fxA(linearIncremental(fxA, 0, 0.01, 500))
print("x2 = ",linearIncremental(fxA, 0, 0.01, 500), "fx(x2) = ", err)
err = fxA(linearIncremental(fxA, 0.5, 0.01, 500))
print("x3 = ",linearIncremental(fxA, 0.5, 0.01, 500), "fx(x3) = ", err)

print("\nTest metody bisekcji")
print("x1 = ", bisection(fxA, -5, 1, 0.001))
print("x2 = ", bisection(fxA, -4, 1, 0.001))
print("x3 = ", bisection(fxA, -3, 1, 0.001))

print("\n\nFunkcja B")
print("\nTest metody graficznej")
x = np.arange(-3, 3, 0.1)
plt.plot(x, fxB(x), 'y.')
plt.grid(True)
plt.show()

print("\nTest metody inkrementacji")
err = fxB(linearIncremental(fxB, -3, 0.01, 500))
print("x1 = ",linearIncremental(fxB, -3, 0.01, 500), "fx(x1) = ", err)
err = fxB(linearIncremental(fxB, 0, 0.01, 500))
print("x2 = ",linearIncremental(fxB, 0, 0.01, 500), "fx(x2) = ", err)
err = fxB(linearIncremental(fxB, 0.5, 0.01, 500))
print("x3 = ",linearIncremental(fxB, 0.5, 0.01, 500), "fx(x3) = ", err)

print("\nTest metody bisekcji")
print("x1 = ", bisection(fxB, -5, 1, 0.001))
print("x2 = ", bisection(fxB, -4, 1, 0.001))
print("x3 = ", bisection(fxB, -3, 1, 0.001))

#Program umozliwia znalezienie miejsc zerowych funkcji na trzy sposoby:
#- metody graficznej,
#- metody liniowej inkrementacji,
#- metody bisekcji.
#Przedstawione w programie metody znalezienia miejsc zerowych nie sa idealne.
#Metoda liniowej inkrementacji zwraca wysoki blad.
#Metoda bisekcji nie zwraca dokladnych wartosci miejsc zerowych.


import numpy as np

def ecdf(data):
    """Compute ECDF for a one-dimensional array of measurements."""

    # Number of data points: n
    n = len(data)

    # x-data for the ECDF: x
    x = np.sort(data)

    # y-data for the ECDF: y
    y = np.arange(1, n+1) / n

    return x, y

def pearson_r(data_1, data_2):
    '''Calculates Pearson correlection coefficient'''
    return np.corrcoef(data_1, data_2)[0,1]

# bootstrap functions

def bootstrap_replicate_1d(data, func):
    '''Generate bootstrap replicate of 1D data'''
    bs_sample = np.random.choice(data, len(data))
    return func(bs_sample)

def draw_bs_reps(data, func, size=1):
    """Draw bootstrap replicates."""
    # Initialize array of replicates: bs_replicates
    bs_replicates = np.empty(size)

    # Generate replicates
    for i in range(size):
        bs_replicates[i] = bootstrap_replicate_1d(data, func)

    return bs_replicates

def draw_bs_pairs(x, y, func, size=1):
    """Perform pairs bootstrap for replicates."""
    # Set up array of indices to sample from: inds
    inds = np.arange(len(x))

    # Initialize replicates
    bs_replicates = np.empty(size)

    # Generate replicates
    for i in range(size):
        bs_inds = np.random.choice(inds, len(inds))
        bs_x, bs_y = x[bs_inds], y[bs_inds]
        bs_replicates[i] = func(bs_x, bs_y)

    return bs_replicates

def draw_bs_pairs_linreg(x, y, size=1):
    """Perform pairs bootstrap for linear regression."""

    # Set up array of indices to sample from: inds
    inds = np.arange(len(x))

    # Initialize replicates: bs_slope_reps, bs_intercept_reps
    bs_slope_reps = np.empty(size)
    bs_intercept_reps = np.empty(size)

    # Generate replicates
    for i in range(size):
        bs_inds = np.random.choice(inds, size=len(inds))
        bs_x, bs_y = x[bs_inds], y[bs_inds]
        bs_slope_reps[i], bs_intercept_reps[i] = np.polyfit(bs_x, bs_y, 1)

    return bs_slope_reps, bs_intercept_reps

# hypothesis testing functions

def permutation_sample(data1, data2):
    """Generate a permutation sample from two data sets."""

    # Concatenate the data sets: data
    data = np.concatenate((data1, data2))

    # Permute the concatenated array: permuted_data
    permuted_data = np.random.permutation(data)

    # Split the permuted array into two: perm_sample_1, perm_sample_2
    perm_sample_1 = permuted_data[:len(data1)]
    perm_sample_2 = permuted_data[len(data1):]

    return perm_sample_1, perm_sample_2

def draw_perm_reps(data_1, data_2, func, size=1):
    """Generate multiple permutation replicates."""

    # Initialize array of replicates: perm_replicates
    perm_replicates = np.empty(size)

    for i in range(size):
        # Generate permutation sample
        perm_sample_1, perm_sample_2 = permutation_sample(data_1, data_2)

        # Compute the test statistic
        perm_replicates[i] = func(perm_sample_1, perm_sample_2)

    return perm_replicates


#NUMPY EXERCISES
# import numpy as np
a = np.array([4, 10, 12, 23, -2, -1, 0, 0, 0, -6, 3, -7])

#1.How many negative numbers are there?()
len(a[a < 0])

#2.How many positive numbers are there?
len(a[a > 0])

#3.How many even positive numbers are there?
len(a[(a > 0) & (a % 2 == 0)])

#4.If you were to add 3 to each data point, how many positive numbers would there be?
a_plus_three = a + 3
a_plus_three

len(a_plus_three)

#5.If you squared each number, what would the new mean and standard deviation be?
a_squared = a**2
a_squared_mean = a_squared.mean()

a_squared_mean

a_std = a_squared.std()

a_std

#6.Centering- Subtracting the mean from each data point. Center the data set.
a_centered = a - a.mean()

a_centered

#7.Calculate the z-score for each data point

a_z_score = a_centered / a.std()

a_z_score

#8. ################################################################################
#                             More Numpy Practice                              #
################################################################################

import numpy as np
# Life w/o numpy to life with numpy

## Setup 1
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Use python's built in functionality/operators to determine the following:
# Exercise 1 - Make a variable called sum_of_a to hold the sum of all the numbers in above list
sum_of_a = sum(a)

sum_of_a
# Exercise 2 - Make a variable named min_of_a to hold the minimum of all the numbers in the above list

min_of_a = min(a)

min_of_a

# Exercise 3 - Make a variable named max_of_a to hold the max number of all the numbers in the above list
max_of_a = max(a)

max_of_a

# Exercise 4 - Make a variable named mean_of_a to hold the average of all the numbers in the above list
mean_of_a = sum(a) / len(a)

mean_of_a

# Exercise 5 - Make a variable named product_of_a to hold the product of multiplying all the numbers in the above list together
product_of_a = 1

for n in a:
    product_of_a *= n
product_of_a

# Exercise 6 - Make a variable named squares_of_a. It should hold each number in a squared like [1, 4, 9, 16, 25...]

squares_of_a = [n ** 2 for n in a]

# Exercise 7 - Make a variable named odds_in_a. It should hold only the odd numbers

odds_in_a = [n for n in a if n % 2 == 1]

odds_in_a
# Exercise 8 - Make a variable named evens_in_a. It should hold only the evens.

evens_in_a = [n for n in a if n % 2 == 0]

evens_in_a
## What about life in two dimensions? A list of lists is matrix, a table, a spreadsheet, a chessboard...
## Setup 2: Consider what it would take to find the sum, min, max, average, sum, product, and list of squares for this list of two lists.
b = [
    [3, 4, 5],
    [6, 7, 8]
]

# Exercise 1 - refactor the following to use numpy. Use sum_of_b as the variable. **Hint, you'll first need to make sure that the "b" variable is a numpy array**
b = np.array(b)
sum_of_b = b.sum()

sum_of_b

# Exercise 2 - refactor the following to use numpy.
min_of_b = b.min()

min_of_b

# Exercise 3 - refactor the following maximum calculation to find the answer with numpy.
max_of_b = b.max()

max_of_b

# Exercise 4 - refactor the following using numpy to find the mean of b
mean_of_b = b.mean()

mean_of_b

# Exercise 5 - refactor the following to use numpy for calculating the product of all numbers multiplied together.
product_of_b = b.prod()

product_of_b

# Exercise 6 - refactor the following to use numpy to find the list of squares
squares_of_b = b ** 2

squares_of_b

# Exercise 7 - refactor using numpy to determine the odds_in_b
odds_in_b = b[b % 2 == 1]

odds_in_b

# Exercise 8 - refactor the following to use numpy to filter only the even numbers
evens_in_b = b[b % 2 == 0]

evens_in_b

# Exercise 9 - print out the shape of the array b.

print(b.shape)

# Exercise 10 - transpose the array b.
b.T
b.T.shape

# Exercise 11 - reshape the array b to be a single list of 6 numbers. (1 x 6)
b.flatten()

# Exercise 12 - reshape the array b to be a list of 6 lists, each containing only 1 number (6 x 1)
b.reshape(6,1)
print(b.reshape)

## Setup 3
c = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

c = np.array(c)

# HINT, you'll first need to make sure that the "c" variable is a numpy array prior to using numpy array methods.
# Exercise 1 - Find the min, max, sum, and product of c.
c.min(), c.max(), c.sum(), c.prod()

# Exercise 2 - Determine the standard deviation of c.
c.std()

# Exercise 3 - Determine the variance of c.
c.std() ** 2

# Exercise 4 - Print out the shape of the array c
c.shape

# Exercise 5 - Transpose c and print out transposed result.
c.T

# Exercise 6 - Get the dot product of the array c with c.
c.dot(c)

# Exercise 7 - Write the code necessary to sum up the result of c times c transposed. Answer should be 261
(c * c.T).sum()

# Exercise 8 - Write the code necessary to determine the product of c times c transposed. Answer should be 131681894400.
(c * c.T).prod()

## Setup 4
d = [
    [90, 30, 45, 0, 120, 180],
    [45, -90, -30, 270, 90, 0],
    [60, 45, -45, 90, -45, 180]
]

d = np.array(d)

print(d)
# Exercise 1 - Find the sine of all the numbers in d
np.sin(d)

# Exercise 2 - Find the cosine of all the numbers in d
np.cos(d)

# Exercise 3 - Find the tangent of all the numbers in d
np.tan(d)

# Exercise 4 - Find all the negative numbers in d
d[d < 0]

# Exercise 5 - Find all the positive numbers in d
d[d > 0]

# Exercise 6 - Return an array of only the unique numbers in d.
np.unique(d)

# Exercise 7 - Determine how many unique numbers there are in d.
np.unique(d).size

# Exercise 8 - Print out the shape of d.
d.shape

# Exercise 9 - Transpose and then print out the shape of d.
d.T.shape

# Exercise 10 - Reshape d into an array of 9 x 2
d.reshape(9, 2)


# Undergraduate Student: Arturo Burgos
# Professor: João Rodrigo Andrade
# Federal University of Uberlândia - UFU, Fluid Mechanics Laboratory - MFLab, Block 5P, Uberlândia, MG, Brazil

# Fourth exercise: Solving a Linear System --> ax = b

# Here I first set conditions

import numpy as np
from numpy import linalg as lin
np.seterr(divide='ignore', invalid='ignore')

a = np.array([
    [-4, 1, 0, 1, 0, 0, 0, 0, 0],
    [1, -4, 1, 0, 1, 0, 0, 0, 0],
    [0, 1, -4, 0, 0, 1, 0, 0, 0],
    [1, 0, 0, -4, 1, 0, 1, 0, 0],
    [0, 1, 0, 1, -4, 1, 0, 1, 0],
    [0, 0, 1, 0, 1, -4, 0, 0, 1],
    [0, 0, 0, 1, 0, 0, -4, 1, 0],
    [0, 0, 0, 0, 1, 0, 1, -4, 1],
    [0, 0, 0, 0, 0, 1, 0, 1, -4]
    ])

print('The coefficient Matrix is:')
print(a)
print('\n')

b = np.array([-50, -50, -150, 0, 0, -100, -50, -50, -150])

print('The result Matrix is:')
print(b)
print('\n')

x_k = np.zeros(9)
x_k1 = np.ones(9)

# Here I set the tolerance
tolerance = 0.000000001

# Here I set the iterations
ite = 0

# Here I set the error based in the Infinite norm
erro = np.ones(9)
erro = lin.norm((x_k1 - x_k),np.inf)

while (erro>tolerance): # because the error is not an array anymore there is no problem with this while,
    # we do not have to put .any()
    for i in range(0,9):

        x_k1[i] = b[i]

        for j in range(0,9):

            if j!=i:

                x_k1[i] =  x_k1[i] - a[i,j]*x_k[j]

        x_k1[i] =  x_k1[i]/ a[i,i]

    erro = lin.norm((x_k1 - x_k),np.inf)
    x_k = x_k1.copy()
    ite = ite + 1

print('The number of iterations is: ')
print(ite)
print('\n')

print('The solution is:')
print(x_k1)
print('\n')

print('Note that now the error is not an array anymore, but is normalized :')
print(erro)
print('\n')


# coding: utf-8
import numpy as np
import math as m
import matplotlib.pyplot as plt
import time as tm

# Newton-Raphson Method
def Newton_Raphson(f, H, U0, N, epsilon):
    """Newton_Raphson takes 5 arguments :
    f: studied function (given as an array)
    H: jacobian matrix of f
    U0: start point
    N: maximum of iterations
    epsilon: precision
    and returns the calculated zeros of f """
    U=U0
    V=np.linalg.lstsq(H(U),-f(U))[0] #function resolving Ax=B
    UplusV=U+V
    i=1
    while(i<N and ((np.linalg.norm(UplusV-U))>epsilon)):
        U=UplusV
        V=np.linalg.lstsq(H(U),-f(U))[0]
        UplusV=U+V
        i+=1
    return UplusV

# Newton Method with Backtracking
def Newton_BT(f,H,U0,N,epsilon):
    """Newton_BT takes 5 arguments :
    f: studied function (given as an array)
    H: jacobian matrix of f
    U0: start point
    N: maximum of iterations
    epsilon: precision
    and returns the zeros of f,
    calculated through the Newton method with backtracking"""
    U=U0
    for i in range(1,N):
        Va=f(U)
        Na=np.linalg.norm(Va)
        if (Na<epsilon):
            return U
        dV=H(U)
        dU=-1*np.linalg.lstsq(dV,Va)[0]
        lambd=1.0
        while (np.linalg.norm(f(U+lambd*dU))>=Na):
            lambd=lambd*(2.0/3)
        U=U+lambd*dU
    return U

def Newton_BT_error_curve(f,H,U0,N,epsilon,realZero):
    """Newton_BT_error_curves takes 6 arguments :
    f: studied function (given as an array)
    H: jacobian matrix of f
    U0: start point
    N: maximum of iterations
    epsilon: precision
    realZero: the real zero of f that we want to calculate
    The function prints the curve
    representing Error on the calculated zeros depending on the number of done iterations,
    calculated through the Newton method with backtracking"""

    y=[]
    nb_point=N-1
    x=np.linspace(1,N,nb_point)

    Nzero=np.linalg.norm(realZero)#exact zero norm

    U=U0
    for i in range(1,N):
        Va=f(U)
        Na=np.linalg.norm(Va)
        if (Na<epsilon):
            for j in range(i,N):
                y.append(abs(Nzero-np.linalg.norm(U)))
            break
        dV=H(U)
        dU=-1*np.linalg.lstsq(dV,Va)[0]
        lambd=1.0
        while (np.linalg.norm(f(U+lambd*dU))>=Na):
            lambd=lambd*(2.0/3)
        U=U+lambd*dU
        y.append(abs(Nzero-np.linalg.norm(U)))
    plt.plot(x,y)
    plt.ylabel("Error from the NR with backtracking method")
    plt.xlabel("Number of iterations of the NR with backtracking Method")
    plt.title("Error on the calculated zeros depending on the max of iterations of the NR with backtracking method")
    plt.show()

def error_NR_depending_on_N(f,H,U0,N1,N2,step,real_zero,eps):
    """error_NR_depending_on_N prints the curve
    representing Error on the calculated zeros depending on the max of iterations of
    the NRMethod"""

    y=[]
    nb_point=int(float((N2-N1))/float(step))+1
    x=np.linspace(N1,N2,nb_point)

    Nzero=np.linalg.norm(real_zero)#exact zero norm

    while(N1<=N2):
        zero=Newton_Raphson(f,H,U0,N1,eps)
        y.append(abs(Nzero-np.linalg.norm(zero)))
        N1+=step
    plt.plot(x,y)
    plt.ylabel("Error from the NR Method")
    plt.xlabel("Max of iterations of the NR Method")
    plt.title("Error on the calculated zeros depending on the max of iterations of the NRMethod")
    plt.show()

if __name__ == '__main__':

    print("SOME TESTS ON NR ALGORITHM:")
    print("")

    functionU2D=Newton_Raphson(lambda A: np.array([A[0]*A[0]-2,A[1]*A[1]-3]),lambda A:  np.array([[(2*A[0]), 0], [0, (2*A[1])]]),np.array([1, 1]),100,0.00000001)

    print("TEST 1 NR, USING f:(x,y)->(x²-2,y²-3)")
    print("U0: (1,1)")
    print("Expected solution :")
    print("[~1.41421 ~1.73205]")
    print("Solution we get :")
    print(functionU2D)
    print("")
    print("")

    functionU1D=Newton_Raphson(lambda A: np.array([(A[0]**2)-2]),lambda A:  np.array([[2*A[0]]]),np.array([1]),100,0.00000001)

    print("TEST 2 NR, USING f:(x)->(x²-2)")
    print("U0: 1")
    print("Expected solution :")
    print("[~1.41421]")
    print("Solution we get :")
    print(functionU1D)
    print("")
    print("")

    print("TESTS ON (x³+4*x²+4), SHOWING NR PROBLEMS AND NR BACKTRACKING WINS :")
    print("")

    functionU1Dbis=Newton_Raphson(lambda A: np.array([(A[0]**3)+4*(A[0]**2)+4]),lambda A:  np.array([[3*(A[0]**2)+8*A[0]]]),np.array([-5]),100,0.00000001)

    print("TEST 2 NR, USING f:(x)->(x³+4*x²+4)")
    print("U0: -5 ")
    print("Expected solution :")
    print("[~-4.22417]")
    print("Solution we get :")
    print(functionU1Dbis)
    print("")
    print("")
    functionU1Dter=Newton_Raphson(lambda A: np.array([(A[0]**3)+4*(A[0]**2)+4]),lambda A:  np.array([[3*(A[0]**2)+8*A[0]]]),np.array([5]),100,0.00000001)

    print("TEST 3 NR, USING f:(x)->(x³+4*x²+4)")
    print("U0: 5 ")
    print("Expected solution :")
    print("[~-4.22417]")
    print("Solution we get :")
    print(functionU1Dter)
    print("-> The method fails to find the zero, because of the local extremums located between 5 and -4.22[...].")
    print("")

    functionUBT1=Newton_BT(lambda A: np.array([(A[0]**3)+4*(A[0]**2)+4]),lambda A:  np.array([[3*(A[0]**2)+8*A[0]]]),np.array([5]),100,0.00000001)

    print("TEST 1 BT, USING f:(x)->(x³+4*x²+4)")
    print("U0: 5 ")
    print("Expected solution :")
    print("[~-4.22417]")
    print("Solution we get :")
    print(functionUBT1)
    print("-> The method NR with backtracking can solve what NR actually can't.")
    print("")

    print("OTHER TESTS OF NEWTON WITH BACKTRACKING:")
    print("")
    functionUBT=Newton_BT(lambda A: np.array([A[0]*A[0]-2,A[1]*A[1]-A[1]-1]),lambda A:  np.array([[(2*A[0]), 0], [0, (2*A[1])-1]]),np.array([2,2]),100,0.000000000001)

    print("TEST 2 BT, USING f:(x,y)->(x²-2,y²-y-1)")
    print("U0: (2,2)")
    print("Expected solution :")
    print("[~1.41421 ~1.61803]")
    print("Solution we get :")
    print(functionUBT)
    print("")
    print("")

    functionUBT3=Newton_BT(lambda A: np.array([(A[0]**4)-2*(A[0]**3)-(A[0]**2)+2*A[0]+0.3]),lambda A:  np.array([[4*(A[0]**3)-6*(A[0]**2)-2*A[0]+2]]),np.array([-0.55]),100,0.001)

    print("TEST 3 BT, USING f:(X)->X^4-2X³-X²+2X+0.3")
    print("This test efficiently decreases the value lambda.")
    print("U0: -0.55")
    print("Expected solution :")
    print("[~ -0.142915]")
    print("Solution we get :")
    print(functionUBT3)
    print("")
    print("")

    print("Curves from the NR method :")
    print("CURVE ON THE EXAMPLE f : x -> (x³+4*x²+4), WITH START POINT -500, MAX OF 15 ITERATIONS ...")
    error_NR_depending_on_N(lambda A: np.array([(A[0]**3)+4*(A[0]**2)+4]),lambda A:  np.array([[3*(A[0]**2)+8*A[0]]]),np.array([-500]),0,15,1,np.array([-4.224169871088]),0.000001)
    print("... It converges.")
    print("")
    print("CURVE ON THE EXAMPLE f : x -> (x³+4*x²+4), WITH START POINT 50, MAX OF 15 ITERATIONS : ...")
    error_NR_depending_on_N(lambda A: np.array([(A[0]**3)+4*(A[0]**2)+4]),lambda A:  np.array([[3*(A[0]**2)+8*A[0]]]),np.array([50]),0,15,1,np.array([-4.224169871088]),0.000001)
    print("... It converges after a few fluctuations.")
    print("")
    print("CURVE ON THE EXAMPLE f : x -> (x³+4*x²+4), WITH START POINT 5, MAX OF 15 ITERATIONS : ...")
    error_NR_depending_on_N(lambda A: np.array([(A[0]**3)+4*(A[0]**2)+4]),lambda A:  np.array([[3*(A[0]**2)+8*A[0]]]),np.array([5]),0,15,1,np.array([-4.224169871088]),0.000001)
    print("... Obviously, it diverges.")

    print("")
    print("(/!\ backtracking)CURVE ON THE EXAMPLE f : x -> (x³+4*x²+4), WITH START POINT 5, 15 ITERATIONS OF THE NR WITH BACKTRACKING METHOD : ...")

    Newton_BT_error_curve(lambda A: np.array([(A[0]**3)+4*(A[0]**2)+4]),lambda A:  np.array([[3*(A[0]**2)+8*A[0]]]),np.array([5]),10,0.00001,np.array([-4.224169871088]))
    print("... The zero is well calculated thank to the backtracking.")


import superimport
import jax.numpy as jnp
import matplotlib.pyplot as plt
import pyprobml_utils as pml
from jax.scipy.stats import beta, bernoulli

# Points where we evaluate the pdf
x = jnp.linspace(0.001, 0.999, 100)

# Forms graph given the parameters of the prior, likelihood and posterior:
def make_graph(data, save_name):
    prior = beta.pdf(x, a=data["prior"]["a"], b=data["prior"]["b"])
    n_0 = data["likelihood"]["n_0"]
    n_1 = data["likelihood"]["n_1"]
    samples = jnp.concatenate([jnp.zeros(n_0), jnp.ones(n_1)])
    likelihood_function = jnp.vectorize(
        lambda p: jnp.exp(bernoulli.logpmf(samples, p).sum())
    )
    likelihood = likelihood_function(x)
    posterior = beta.pdf(x, a=data["posterior"]["a"], b=data["posterior"]["b"])

    fig, ax = plt.subplots()
    axt = ax.twinx()
    fig1 = ax.plot(
        x,
        prior,
        "k",
        label=f"prior Beta({data['prior']['a']}, {data['prior']['b']})",
        linewidth=2.0,
    )
    fig2 = axt.plot(x, likelihood, "r:", label=f"likelihood Bernoulli", linewidth=2.0)
    fig3 = ax.plot(
        x,
        posterior,
        "b-.",
        label=f"posterior Beta({data['posterior']['a']}, {data['posterior']['b']})",
        linewidth=2.0,
    )
    fig_list = fig1 + fig2 + fig3
    labels = [fig.get_label() for fig in fig_list]
    ax.legend(fig_list, labels, loc="upper left", shadow=True)
    axt.set_ylabel("Likelihood")
    ax.set_ylabel("Prior/Posterior")
    ax.set_title(f"$N_0$:{n_0}, $N_1$:{n_1}")
    pml.savefig(save_name)

data1 = {
    "prior": {"a": 1, "b": 1},
    "likelihood": {"n_0": 1, "n_1": 4},
    "posterior": {"a": 5, "b": 2},
}
make_graph(data1, "betaPostUninfSmallSample.pdf")

data2 = {
    "prior": {"a": 1, "b": 1},
    "likelihood": {"n_0": 10, "n_1": 40},
    "posterior": {"a": 41, "b": 11},
}
make_graph(data2, "betaPostUninfLargeSample.pdf")

data3 = {
    "prior": {"a": 2, "b": 2},
    "likelihood": {"n_0": 1, "n_1": 4},
    "posterior": {"a": 6, "b": 3},
}
make_graph(data3, "betaPostInfSmallSample.pdf")

data4 = {
    "prior": {"a": 2, "b": 2},
    "likelihood": {"n_0": 10, "n_1": 40},
    "posterior": {"a": 42, "b": 12},
}
make_graph(data4, "betaPostInfLargeSample.pdf")

plt.show()


import matplotlib.pyplot as plt
from numpy import zeros, mod, diag, eye, sqrt, tile, r_, concatenate

plt.style.use('seaborn')

from FactorAnalysis import FactorAnalysis
from NormalScenarios import NormalScenarios

def DimRedScenariosNormal(mu,sig2,k_,j_,method='Riccati',d=None):
    # This function generates Monte Carlo Scenarios from a multivariate normal
    # distribution with mean mu and covariance matrix sig2 through a dimension reduction
    # algorithm resorting to a linear factor model, where the matrix of loadings
    # is recovered through factor analysis of the correlation matrix
    # INPUTS
    # mu        :  [vector] (n_ x 1)  target mean
    # sig2      :  [matrix] (n_ x n_) target positive definite covariance matrix
    # k_        :  [scalar] number of factors to be considered for factor analysis (we reccomend k_ << n_)
    # j_        :  [scalar] Number of scenarios. If not even, j_ <- j_+1
    #   method  :  [string] Riccati (default), CPCA, PCA, LDL-Cholesky, Gram-Schmidt
    #   d       :  [matrix] (k_ x n_) full rank constraints matrix for CPCA
    # OUTPUTS
    # X         :  [matrix] (n_ x j_) panel of MC scenarios drawn from normal
    #                       distribution with mean mu and covariance matrix sig2
    # beta      :  [matrix] (optional) (n_ x k_) loadings matrix ensuing from factor
    # analysis of the correlation matrix
    #
    # For details on the exercise, see here .

    ## Code

    if mod(j_,2)!=0:
        j_=j_+1

    n_=sig2.shape[1]

    #Step 1. Correlation
    rho2=diag(diag(sig2)**(-1/2))@sig2@diag(diag(sig2)**(-1/2))

    #Step 2. Factor Loadings
    _, beta, _, _, _= FactorAnalysis(rho2,zeros((1,1)),k_)

    #Step 3. Residual Standard Deviation
    delta=sqrt(diag(eye((n_))-beta@beta.T))

    #Step 4. Systematic scenarios
    #sigm = r_[-1,r_[eye(k_),zeros((k_,n_))],r_[zeros((n_,k_)),eye(n_)]]
    sigm = concatenate(( concatenate( (eye(k_),zeros((k_,n_)) ) ,axis=1) , concatenate((zeros((n_,k_)),eye(n_)),axis=1) ))
    S,_=NormalScenarios(zeros((k_+n_,1)),sigm,j_,method,d)
    Z_tilde=S[:k_,:]

    #Step 5. Idiosyncratic scenarios
    U_tilde=S[k_:k_+n_,:]

    #Step 6. Output
    X=tile(mu, (1,j_))+diag(sqrt(diag(sig2)))@(beta@Z_tilde+diag(delta)@U_tilde)
    return X, beta


# Vectorizing the Haversine formula

## Original function
from numpy import sin, cos, pi, arcsin, sqrt
def get_distance(lat, lon, pcode_lat, pcode_lon):
    """
    Find the distance between `(lat,lon)` and the reference point
    `(pcode_lat,pcode_lon)`.
    """
    RAD_FACTOR = pi / 180.0  # degrees to radians for trig functions
    lat_in_rad = lat * RAD_FACTOR
    lon_in_rad = lon * RAD_FACTOR
    pcode_lat_in_rad = pcode_lat * RAD_FACTOR
    pcode_lon_in_rad = pcode_lon * RAD_FACTOR

    delta_lon = lon_in_rad - pcode_lon_in_rad
    delta_lat = lat_in_rad - pcode_lat_in_rad

    # Next two lines is the Haversine formula
    inverse_angle = (sin(delta_lat / 2) ** 2 + cos(pcode_lat_in_rad) *
                     cos(lat_in_rad) * sin(delta_lon / 2) ** 2)
    haversine_angle = 2 * arcsin(sqrt(inverse_angle))
    EARTH_RADIUS = 6367  # kilometers
    return haversine_angle * EARTH_RADIUS

# Random coordinates
from numpy import random
godatadriven = (52.3905927,4.8412508)

# First vectorization
points = random.randn(400000, 2) * 0.01
points[:, 0] = points[:, 0] + godatadriven[0]
points[:, 1] = points[:, 1] + godatadriven[1]
# "Scaling randn by 0.01 didn't require two for-loop for each of its elements: we simply told NumPy to multiply the whole array by 0.01 and it was done. The same when we added godatadriven[0] to points[:, 0]: we didn't have to write a for-loop for each element because once again NumPy took care of it."

# Measuring time
def iterate_distance():
    d = []
    for p in points:
        d.append(get_distance(p[0], p[1], godatadriven[0], godatadriven[1]))

import time

t1 = time.time()
iterate_distance()  # runs in 3.53 seconds
print("Running time of iterate_distance():\n{} us".format(1000000 * (time.time() - t1)))

# List comprehension
t1 = time.time()
[get_distance(p[0], p[1], godatadriven[0], godatadriven[1]) for p in points]
print("List comprehension time:\n{} us".format(1000000 * (time.time() - t1)))

# Vectorized
t1 = time.time()
get_distance(points[:, 0], points[:, 0], godatadriven[0], godatadriven[1])
print("Vectorized time:\n{} us".format(1000000 * (time.time() - t1)))
# We basically called the calculating function with the whole array on which it went through

# Further times and details: http://nbviewer.ipython.org/gist/gglanzani/9271842


# Question: https://projecteuler.net/problem=301

import numpy as np

# According to this https://en.wikipedia.org/wiki/Nim, the next player loses when the XOR of 3 heaps is 0.
# When XOR(n, 2n, 3n) == 0? It occurs when n has consecutive 1's.
# Why?
#           bit    b1 b2 b3
# carry of n+2n     x  y  z
#             n = ..a  1  1...
#            2n = ..1  1  b...
#            3n = ..c  d  e...
#  Case 1. z = 0
#    Case 1.1. a = 0, b = 0
#       -> cde = 001
#       -> bit b1 of XOR(n, 2n, 3n) = 1 -> XOR != 0
#    Case 1.2. a = 0, b = 1
#       -> cde = 010
#       -> bit b2 of XOR(n, 2n, 3n) = 1 -> XOR != 0
#    Case 1.3. a = 1, b = 0
#       -> cde = 101
#       -> bit b1 of XOR(n, 2n, 3n) = 1 -> XOR != 0
#    Case 1.4. a = 1, b = 1
#       -> cde = 110
#       -> bit b2 of XOR(n, 2n, 3n) = 1 -> XOR != 0
#  Case 2. z = 1
#    Case 2.1. b = 0
#       -> d = 1
#       -> bit b2 of XOR(n, 2n, 3n) = 1 -> XOR != 0
#    Case 2.2. b=1
#       -> e = 1
#       -> bit b3 of XOR(n, 2n, 3n) = 1 -> XOR != 0

N = 30 # 2^30 is 1000...000 with 30 zeros -> 31 digits in base 2 | we only need to count the case of <=30 digits and manually add the case of 2^30

# DP[i][j] -> number of ways of generate a base 2 number of <=i-digit that ends with j
DP = np.zeros((N+1, 2), dtype = np.uint32)
DP[1][0] = 1
DP[1][1] = 1

for i in range(2, N+1):
    DP[i][0] = DP[i-1][0] + DP[i-1][1]
    DP[i][1] = DP[i-1][0]

print(DP[N][0] + DP[N][1] + 1 - 1)
#                         ^^^^^^^
#                         add the case of 2^30, remove the case of 0


import numpy as np

#NumPy Array
#How to create an empty and a full NumPy array?
np.empty([2, 2], dtype=int) #Return a new array of given shape and type, without initializing entries.
np.full([2, 2], 0) #Return a new array of given shape and type, filled with fill_value.

#Create a Numpy array filled with all zeros
np.zeros((5,5), dtype=int) #Return a new array of given shape and type, filled with zeros.
#Create a Numpy array filled with all ones
np.ones((5,5), dtype=int) #Return a new array of given shape and type, filled with ones.

#Check whether a Numpy array contains a specified row
#tolist Return the array as an a.ndim-levels deep nested list of Python scalars.
[1, 1] in np.ones((2, 2)).tolist()

#How to Remove rows in Numpy array that contains non-numeric values?
#any Test whether any array element along a given axis evaluates to True.
#isnan Test element-wise for NaN and return result as a boolean array.
n_arr = np.array([[10.5, 22.5, 3.8],
                  [41, np.nan, np.nan],
                  [45.1, 16.2, 32.7]] )
n_arr[~np.isnan(n_arr).any(axis=1)]

#Remove single-dimensional entries from the shape of an array
#squeeze Remove axes of length one from a.
np.squeeze(np.array([[[0], [1], [2]]]))

#Find the number of occurrences of a sequence in a NumPy array
#array_repr Return the string representation of an array.
np.array_repr(np.array([[2, 8, 9, 4],
                   [9, 4, 9, 4],
                   [4, 5, 9, 7],
                   [2, 9, 4, 3]])).count("9, 4")

#Find the most frequent value in a NumPy array
#bincount Count number of occurrences of each value in array of non-negative ints.
#argmax Returns the indices of the maximum values along an axis.
np.bincount(np.array([1,2,3,4,5,1,2,1,1,1]) ).argmax()

#Combining a one and a two-dimensional NumPy Array
#arrange Return evenly spaced values within a given interval.
#reshape Gives a new shape to an array without changing its data.
#nditer Efficient multi-dimensional iterator object to iterate over arrays.
for a, b in np.nditer([np.arange(5) , np.arange(10).reshape(2,5)]):
    print("%d:%d" % (a, b),)

#How to build an array of all combinations of two NumPy arrays?
#meshgrid Return coordinate matrices from coordinate vectors.
#T The transposed array.
#One shape dimension can be -1. In this case, the value is inferred
# from the length of the array and remaining dimensions.
np.array(np.meshgrid(np.array([1, 2]) , np.array([4, 6]) )).T.reshape(-1, 2)

#How to add a border around a NumPy array?
#pad Pad an array.
np.pad(np.ones((2, 2)), pad_width=1, mode='constant', constant_values=0)

#How to compare two NumPy arrays?
#all Test whether all array elements along a given axis evaluate to True.
(np.array([[1, 2], [3, 4]]) == np.array([[1, 2], [3, 4]])).all()

#How to check whether specified values are present in NumPy array?
10 in np.array([[2, 3, 0], [4, 1, 6]])
100 in np.array([[2, 3, 0], [4, 1, 6]])
2 in np.array([[2, 3, 0], [4, 1, 6]])

#How to get all 2D diagonals of a 3D NumPy array?
#diagonal Return specified diagonals.
np.diagonal(np.arange(3 * 4 * 4).reshape(3, 4, 4),  axis1 = 1, axis2 = 2)

#Flatten a Matrix in Python using NumPy
#flatten Return a copy of the array collapsed into one dimension.
np.array([[2, 3], [4, 5]]).flatten()

#Flatten a 2d numpy array into 1d array
#flatten Return a copy of the array collapsed into one dimension.
np.array([[1, 2, 3], [2, 4, 5], [1, 2, 3]]).flatten()

#Move axes of an array to new positions
#moveaxis Move axes of an array to new positions. Other axes remain in
#  their original order.
np.moveaxis(np.zeros((1, 2, 3, 4)), 0, -1)

#Interchange two axes of an array
#swapaxes Interchange two axes of an array.
np.swapaxes(np.array([[2, 4, 6]]) , 0, 1)

#NumPy – Fibonacci Series using Binet Formula
#N is the first N numbers of Fibonacci
N = 11
a = np.arange(1, N)
# splitting of terms for easiness
sqrtFive = np.sqrt(5)
alpha = (1 + sqrtFive) / 2
beta = (1 - sqrtFive) / 2
# Implementation of formula
# np.rint is used for rounding off to integer
Fn = np.rint(((alpha ** a) - (beta ** a)) / (sqrtFive))
Fn

#Counts the number of non-zero values in the array
#count_nonzero Counts the number of non-zero values in the array a.
np.count_nonzero(np.array([[0, 1, 7, 0], [3, 0, 2, 19]]))

#Count the number of elements along a given axis
#size Return the number of elements along a give-n axis.
np.size(np.array([[1, 2, 3, 4], [5, 6, 7, 8]]), axis=0)

#Trim the leading and/or trailing zeros from a 1-D array
#trim_zeros Trim the leading and/or trailing zeros from a 1-D array or sequence.
np.trim_zeros(np.array((0, 0, 0, 1, 2, 3, 0, 2, 1, 0)))

#Change data type of given numpy array
#astype Copy of the array, cast to a specified type.
np.array([10, 20, 30, 40, 50]).astype('float64')

#Reverse a numpy array
#flip Reverse the order of elements in an array along the given axis.
np.flip(np.array([10, 20, 30, 40, 50]), 0)

#How to make a NumPy array read-only?
#flags Information about the memory layout of the array.
#WRITEABLE can only be set True if the array owns its own memory or the
# ultimate owner of the memory exposes a writeable buffer interface or is a string.
np.zeros(11).flags.writeable = False


import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
from matplotlib import cm
import scipy as sp
from scipy.sparse import csr_matrix, linalg
from scipy.stats import linregress
from scipy import linalg, optimize

# Escriba dos funciones que calculen A_h y b_h. La entrada debe ser N,f,g

def calcula_A(n):
    N = n+1
    h = 1/N
    down = np.ones(N-2)
    center = np.ones(N-1)
    upper = np.ones(N-2)
    d1 = -down
    d2 = 4*center
    d3 = -1*upper
    d = np.array([d1, d2, d3])
    offset = [-1, 0, 1]
    L4 = sp.sparse.diags(d, offset)

    dd1 = -down
    dd2 = np.zeros(N-1)
    dd3 = -upper
    dd = np.array([dd1, dd2, dd3])
    A1 = sp.sparse.diags(dd, offset)

    I = np.identity(N-1)

    L = sp.sparse.kron(A1, I)
    R = sp.sparse.kron(I, L4)

    A = (L + R) / h**2
    return A

def g(x,y):
    if y==1 and x<1 and x>0:
        return np.sin(2*np.pi*x)
    else:
        return 0

f = lambda x,y: 8*np.pi**2*np.sin(2*np.pi*x)*np.sin(2*np.pi*y)

def calcula_b(N, f, g):
    f_h = np.zeros((N+1)**2)
    g_h = np.zeros((N+1)**2)
    x = np.linspace(0,1,N+1)
    y = np.linspace(0,1,N+1)
    h = 1 / N

    for j in range(len(x)):
        for k in range(len(y)):
            f_h[(k)*(N)+j] = f(x[j], y[k])

    g_h[1]=N**2*(g(x[1],0)+g(0,y[1]))
    g_h[N-1]=N**2*(g(x[N-1],0)+g(1,y[1]))
    g_h[(N-1)**2-(N-2)]=N**2*(g(x[1],1)+g(0,y[N-1]))
    g_h[(N-1)**2]=N**2*(g(x[N-1],1)+g(1,y[N-1]))
    for j in range(2,N-1):#esto es para j in {2,...,N-2}
        g_h[j]=N**2*g(x[j],0)
        g_h[(N-1)*(N-2)+j]=N**2*g(x[j],1)
        g_h[j*(N-1)+1]=N**2*(g(0,y[j]))
        g_h[j*(N-1)]=N**2*g(1,y[j])

    b_h = f_h + g_h
    return b_h[N+1:-N]

## Parte 2
# Para N en {4,16}, grafique la solución numérica y la solución única de
# la ecuación.

N = [4, 16]

for i in range(2):
    u = sp.sparse.linalg.spsolve(calcula_A(N[i]), calcula_b(N[i], f, g))
    U = np.zeros((N[i], N[i]))
    counter = 0
    for j in range(N[i]):
        for k in range(N[i]):
            U[k][j] = u[k + j*(N[i])]

    x = np.linspace(0,1, N[i])

    X, Y = np.meshgrid(x, x)
    fig = plt.figure(i)
    fig.clf()
    ax = fig.add_subplot(111, projection='3d', elev=15, azim=10)
    ax.plot_surface(X, Y, np.transpose(U)) #, rstride=2, cstride=2, cmap=cm.plasma
    #ax.dist = 1
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_zlabel('u')
    fig.show()

# Parte 3
#
# para N en {4,8,16,32,64} , calcule el error en norma L2.

arreglo_N = [4, 8, 16, 32, 64]
errores_norm_2 = []

for i in range(len(arreglo_N)):
    N = arreglo_N[i]
    h = 1 / N

    u = sp.sparse.linalg.spsolve(calcula_A(N), calcula_b(N, f, g))
    U = np.zeros((N+1, N+1))

    for j in range(N):
        for k in range(N):
            U[k][j] = u[k + j*(N)]

    x = np.linspace(0,1,N+1)
    y = np.linspace(0,1,N+1)
    u_analitico = lambda x,y: np.sin(2*np.pi*x)*(np.sin(2*np.pi*y) + (np.sinh(2*np.pi*y) / np.sinh(2*np.pi)))
    U_analitico = np.zeros((N+1, N+1))

    for j in range(N+1):
        for k in range(N+1):
            U_analitico[j][k] = u_analitico(x[j], y[k])

    err = h * np.linalg.norm(U_analitico - U, 2)
    errores_norm_2.append(err)

# Grafique los respectivos valores en función de h, en escala logarítmica
# usando log log ¿qué puede observar?

""" arreglo_N = [ 2**(i) for i in range(2,7)]
arreglo_condicion_A_h = []

for N in arreglo_N:
    # cambiar por matriz A_h
    A_h = np.identity( (N-1)**2 )
    condicion_A_h = np.linalg.cond(A_h, p = 2)
    arreglo_condicion_A_h.append(condicion_A_h)

arreglo_h   = np.divide(1,arreglo_N)
arreglo_N_2 = np.multiply(arreglo_N,arreglo_N)

condicion_fig,condicion_ax = plt.subplots(2)
condicion_h  = condicion_ax[0]
condicion_n2 = condicion_ax[1]

condicion_h.loglog(arreglo_h,arreglo_condicion_A_h)
condicion_n2.loglog(arreglo_N_2,arreglo_condicion_A_h)

plt.show() """

# Calcule el orden de error experimental, es decir, estime mediante
# regresión lineal el valor de p tal que e_h sea de orden O(h^p)


import numpy as np

def integrate_trap(f, a, b, n):
    # Implementation of the trapezoidal rule
    h = (b-a)/n
    x = np.linspace(a, b, n+1)
    i=1
    area = h*(f(x[0]) + f(x[n]))/2
    while i<n:
        sup_rect = f(x[i])*h
        area += sup_rect
        i += 1
    return area

'''
We test the trapezoidal rule on the known sine funcion were the
definite integral in the interval [0, %*\textcolor{codeviolet}{$\pi$}*)/2] is equal to 1.
'''

sup_5 = integrate_trap(np.sin, 0, np.pi/2, 5)
sup_10 = integrate_trap(np.sin, 0, np.pi/2, 10)

print('Using n=5, the trapezoidal rule returns a value of {:.2f}'.format(sup_5))
print('Using n=10, the trapezoidal rule returns a value of {:.2f}'.format(sup_10))

'''
Output:
Using n=5, the trapezoidal rule returns a value of 0.99
Using n=10, the trapezoidal rule returns a value of 1.00
'''


import numpy as np

a0 = 3.56
a1 = 1.4859
a2 = 2.025

x = np.array([1,2,3,4,5])
y = np.array([7.7, 14.5, 26, 40, 62])

xm = np.mean(x).round(4)
ym = np.mean(y).round(4)

print('mean of x: ' + str(xm))
print('mean of y: ' + str(ym))

st = lambda yi : np.square(yi - ym).round(4)
sr = lambda xi,yi : np.square(yi-a0-a1*xi-a2*np.square(xi)).round(4)

def printline(): print('----------------------------------------------------------------------')

ST = 0
SR = 0

printline()
print('xi\t\tyi\t\t(yi-ym)^2\t(yi-a0-a1xi-a2xi^2)^2')
printline()
for i in range(len(x)):
    St = st(y[i])
    Sr = sr(x[i],y[i])
    ST = ST + St
    SR = SR + Sr
    print(str(x[i]) + '\t\t' + str(y[i]) + '\t\t' + str(St) + '\t\t' + str(Sr))

printline()
print('\t\t\t\t' + str(ST) + '\t\t' + str(SR))

n = len(x)

#S_yx = np.sqrt((SR/(n-2)))

S_y = np.sqrt((SR/(n-3)))
r2 = (ST-SR)/ST

print('Standard error: %f' %S_y)
print('Co-efficient of determination: %f' %r2)


# Experiment 01  Calculating the value of pi using Monte Carlo methods
'''
Inscribe circle into a square centered at the origin
A_c = pi*r^2
A_s = L*W = (2r)(2r) = 4r^2
(A_c/A_s) = (pi*r^2)/(4*r^2) = pi/4
pi = 4*A_c/A_s = 4*N_c/N_s
N_c <= N_s
'''
#%matplotlib
# Section A of Monte Carlo Lab
from numpy import random
import numpy as np
import matplotlib.pyplot as plt
import math

#storing vslues of pi
pi_values =[]

for i in range(1, 500):

#Number of dots
    N= 1000
#circle coordinates
    circle_x =[]
    circle_y=[]
# Square Coordinates
    square_x = []
    square_y = []
# Initialize Index
    j = 1
# create randomized (x,y) values from [-1, 1]
    while j <= N:
        x = random.uniform(-1, 1)
        y = random.uniform(-1, 1)
        if (x**2 + y**2 <= 1.0):
            circle_x.append(x)
            circle_y.append(y)
        else:
            square_x.append(x)
            square_y.append(y)
        j += 1

    pi = 4.0*len(circle_x)/float(N)

#Append the values of pi in list
    pi_values.append(pi)

#Calculatiing the errors
    avg_pi_errors = [abs(math.pi - pi) for pi in pi_values]

#Print the final value of Pi for each run
print (pi_values[-1])
print (avg_pi_errors[-1])
#Plot the Pi values
plt.axhline(y=math.pi, color='g', linestyle='-')
plt.plot(pi_values)
plt.ylim(2, 4)
plt.xlabel("Interations")
plt.ylabel("Value of Pi")
plt.show()

#Plot the error in the calculation
plt.axhline(y=0.0, color="g", linestyle="-")
plt.plot(avg_pi_errors)
plt.xlabel("Interations")
plt.ylabel("Error")
plt.show()

# Ploting circle and square

plt.plot(circle_x,circle_y, 'r.')
plt.plot(square_x, square_y, 'b.')
plt.grid()
plt.show()

N = float(N)
p = len(circle_x)/N
mu = N*p
s = np.sqrt(N*p*(1-p))
'''
dpi/pi = s/mu
dpi = pi*s/mu
'''
# Counts in Square
Ns = N
# Counts in Circle
Nc = len(circle_x)

#dpi = pi*s/mu
#dpi2 = 4.*np.sqrt(Nc*(1.-Nc/Ns))/Ns # Double Checking Pi Error

dpi=pi*np.sqrt((1./Nc + 1./Ns))

print("Pi is approximately : {:5.4f} +/- {:5.4f} ".format(pi,dpi))
print("Pi actually is :", np.pi)
print("The relative uncertainty is: {:.5f} %".format(100.*dpi/pi))

# Desired Relative Uncertainty [%]
e = [1.,0.1, 0.01, 0.00001]

#Ne = (4.-np.pi)*(1./np.pi)*(100./e[i])**2
# Approximate Number of events

for j in range(len(e)):

    Ne = (4.+np.pi)*(1./np.pi)*(100./e[j])**2

    print("Events required to obtain a statistical uncertainty\n\
of {:.5} % is: {:} +/- {:}".format(e[j],int(Ne),np.sqrt(int(Ne))))
    print("Events required to obtain a statistical uncertainty\ n \
of {:.5} % is: {:} +/- {:}".format(e[j],int(Ne),np.sqrt(int(Ne))))


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

class PolyRegression(nn.Module):
    """ Polynomial Regression module

    this will compute the polynomial regression with degree specified.
    for example, degree of 1 is the model for linear regression `y = a*x + b`,
    degree of 2 is the model for quadratic regression `y = a*(x^2) + b*x + c`, etc.

    keep in mind to always normalise, with interval of (0, 1], the data (`x` variable)
    you input to the model, if you don't want to cause a gradient explosion because
    the output value is too big to handle.

    Args:
        degree (int): the degree of polynomial regression

    """
    def __init__(self, degree):
        super(PolyRegression, self).__init__()
        self.degree = degree

        self.weight = nn.Parameter(torch.randn([degree+1, 1]))

    def regress_forward(self, x):
        x = torch.flatten(x)
        f = torch.stack([x.pow(a) for a in range(self.degree, -1, -1)], 1)
        y = f @ self.weight
        return y.squeeze()

    def forward(self, x):
        return self.regress_forward(x)

    def get_weight(self):
        return self.weight.detach().numpy().flatten()

def compute_r2(y, yreg):
    if isinstance(y, torch.Tensor):
        y = np.array(y.detach())
    if isinstance(yreg, torch.Tensor):
        yreg = np.array(yreg.detach())

    ss_tot = np.square(y - y.mean()).sum()
    ss_res = np.square(y - yreg).sum()
    return 1 - (ss_res/ss_tot)

def normalise(x):
    return (x - x.mean()) / x.std()

def fit(model, x, y, iteration=1000):
    if not isinstance(x, torch.Tensor):
        x = torch.tensor(x)
    if not isinstance(y, torch.Tensor):
        y = torch.tensor(y)

    criterion = nn.MSELoss()
    optim = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.95)

    loss_data = []
    for _ in range(iteration):
        yreg = model(x)
        loss = criterion(yreg, y)

        loss_data.append(loss.detach().item())

        optim.zero_grad()
        loss.backward()
        optim.step()

    return model.get_weight(), loss_data

if __name__ == "__main__":
    torch.manual_seed(123)      # uncomment this if you want a non-deterministic result

    x = normalise(torch.randn(10))
    y = 0.5*x.pow(3) + 7*x.pow(2) + 5*x + 2
    degree = 3

    model = PolyRegression(degree)
    weight, loss_data = fit(model, x, y)

    yreg = model(x)
    print(weight)
    print("R2: ", compute_r2(y, yreg))

    ## plot regression
    x_reg = torch.tensor(np.linspace(x.min(), x.max(), 200, endpoint=False), dtype=torch.float32)
    y_reg = model(x_reg).detach().numpy()
    x_reg = x_reg.numpy()
    plt.plot(x_reg, y_reg, color='#a7de77', label=f'Polynomial Regression, degree {degree}')
    plt.scatter(x, y, color='#9b354c', label='Data Point')
    plt.legend()
    # plt.plot(loss_data)
    plt.show()


# coding=utf8

import numpy as np
from scipy import stats

class Correlation:
    def __init__(self, arr1, arr2):
        self.arr1 = np.array(arr1)
        self.arr2 = np.array(arr2)
        if arr1.shape[0] != arr2.shape[0]:
            raise Exception('two arr length must be the same')
        self.length = self.arr1.shape[0]

    def use_scipy_normalize(self):
        # ddof=1 means divide n-1, ddof=0(default) means divide n
        return stats.zscore(self.arr1, ddof=1), stats.zscore(self.arr2, ddof=1)

    def normalize_with_n(self):
        mean_1, mean_2 = np.mean(self.arr1), np.mean(self.arr2)
        var1, var2 = np.sum((self.arr1 - mean_1) ** 2) / self.length, np.sum((self.arr2 - mean_2) ** 2) / self.length
        std1, std2 = np.sqrt(var1), np.sqrt(var2)
        return (self.arr1 - mean_1) / std1, (self.arr2 - mean_2) / std2

    def normalize_with_n_1(self):
        """
        divide n-1
        :return: normalized arr1, arr2
        """
        mean_1, mean_2 = np.mean(self.arr1), np.mean(self.arr2)
        var1, var2 = np.sum((self.arr1 - mean_1) ** 2) / \
            (self.length - 1), np.sum((self.arr2 - mean_2) ** 2) / (self.length - 1)
        std1, std2 = np.sqrt(var1), np.sqrt(var2)
        return (self.arr1 - mean_1) / std1, (self.arr2 - mean_2) / std2

    def get_correlation_index(self):
        arr1, arr2 = self.normalize_with_n()
        return np.sum(arr1 * arr2) / self.length

if __name__ == '__main__':
    # test if scipy.stats divide n or n - 1 to normalize data
    arr1 = np.array([74, 76, 77, 63, 63, 61, 72], dtype=np.float)
    arr2 = np.array([84, 83, 85, 74, 75, 81, 73], dtype=np.float)
    correlation = Correlation(arr1, arr2)
    print('scipy.stats get normalize data is: \n', correlation.use_scipy_normalize())
    print('divide n to normalize data is:\n', correlation.normalize_with_n())
    print('divide n - 1 to normalize data is:\n', correlation.normalize_with_n_1())
    print('actually in scipy.stats, zscore function params ddof=1 means divide n-1, ddof=0(default) means divide n')

    # test correlation index
    print('correlation index is ', correlation.get_correlation_index())


"""
example of calculations Lp norm for vector and matrix
"""

import numpy as np

def L1_norm(vec):
    return np.sum(np.abs(vec))

def L2_norm(vec):
    return np.sum(np.abs(vec) ** 2) ** 0.5

def Linf_norm(vec):
    return np.max(vec)

def Lp_norm(vec, p=2):
    if p < 1:
        raise Exception('p should be more or equal 1. {} given.'.format(p))
    return np.sum(np.abs(vec) ** p) ** (1 / p)

def Frobenius_norm(matrix):
    return np.sum(np.abs(matrix) ** 2) ** 0.5

if __name__ in '__main__':
    VECTOR = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    print('vector    : {}'.format(VECTOR))
    print('L1 norm   : {:.2f}'.format(L1_norm(VECTOR)))
    print('L2 norm   : {:.2f}'.format(L2_norm(VECTOR)))
    for p in range(3, 6):
        print('L{} norm   : {:.2f}'.format(p, Lp_norm(VECTOR, p)))
    print('Linf norm : {:.2f}'.format(Linf_norm(VECTOR)))

    MATRIX = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    print('')
    print('matrix: {}'.format(MATRIX))
    print('Frobenius norm: {:.2f}'.format(Frobenius_norm(MATRIX)))


import numpy as np

######################################################################################################
# NumPy Matrix multiplication 																		 #
######################################################################################################
#
# You've heard a lot about matrix multiplication in the last few videos – now you'll get to see how to
# do it with NumPy. However, it's important to know that NumPy supports several types of matrix multiplication.
#
# Element-wise Multiplication
# You saw some element-wise multiplication already. You accomplish that with the multiply function or the * operator.
#  Just to revisit, it would look like this:

m = np.array([[1,2,3],[4,5,6]])
print(m)
# displays the following result:
# array([[1, 2, 3],
#        [4, 5, 6]])

n = m * 0.25
print(n)
# displays the following result:
# array([[ 0.25,  0.5 ,  0.75],
#        [ 1.  ,  1.25,  1.5 ]])

print(m * n)
# displays the following result:
# array([[ 0.25,  1.  ,  2.25],
#        [ 4.  ,  6.25,  9.  ]])

print(np.multiply(m, n))   # equivalent to m * n
# displays the following result:
# array([[ 0.25,  1.  ,  2.25],
#        [ 4.  ,  6.25,  9.  ]])

# Matrix Product
# To find the matrix product, you use NumPy's matmul function.
#
# If your have compatible shapes, the it's as simple as this:

a = np.array([[1,2,3,4],[5,6,7,8]])
print(a)
# displays the following result:
# array([[1, 2, 3, 4],
#        [5, 6, 7, 8]])

print(a.shape)
# displays the following result:
# (2, 4)

b = np.array([[1,2,3],[4,5,6],[7,8,9],[10,11,12]])
print(b)
# displays the following result:
# array([[ 1,  2,  3],
#        [ 4,  5,  6],
#        [ 7,  8,  9],
#        [10, 11, 12]])

print(b.shape)
# displays the following result:
# (4, 3)

c = np.matmul(a, b)
print('A matmul B:\n', c)
# displays the following result:
# array([[ 70,  80,  90],
#        [158, 184, 210]])

print(c.shape)
# displays the following result:
# (2, 3)

# NumPy's dot function
# You may sometimes see NumPy's dot function in places where you would expect a matmul.
# It turns out that the results of dot and matmul are the same if the matrices are two dimensional.
#
# So these two results are equivalent:

a = np.array([[1,2],[3,4]])
print(a)
# displays the following result:
# array([[1, 2],
#        [3, 4]])

np.dot(a, a)
# displays the following result:
# array([[ 7, 10],
#        [15, 22]])

print(a.dot(a))  # you can call `dot` directly on the `ndarray`
# displays the following result:
# array([[ 7, 10],
#        [15, 22]])

print(np.matmul(a,a))
# array([[ 7, 10],
#        [15, 22]])


# -*- coding: utf-8 -*-
"""
Created on Thu Apr  9 17:50:43 2020

@author: Ngwaniwapho
"""
import numpy as np

#__all_ = ['easomFunction','wayburnSeader2Function']
def easomFunction(x):
    """
    −100 ≤ xi ≤ 100. The global minimum is located at x∗ = f(π, π),f(x∗) = −1
    """
    x1 = x['IndPosition0']
    x2 = x['IndPosition1']
    easomfunction = -np.cos(x1)*np.cos(x2)*np.exp(-1*(x1-np.pi)**2 -(x2 - np.pi)**2)
    return easomfunction

def bealeFunction(x):
    """
    subject to −4.5 ≤ xi ≤ 4.5. The global minimum is located at x∗ = (3, 0.5), f(x∗) = 0.
    """
    x1 = x['IndPosition0']
    x2 = x['IndPosition1']
    bealefunction = (1.5 - x1 + x1*x2)**2 + (2.25 - x1 + x1*x2**2)**2 + (2.625 - x1 + x1*x2**3)**2
    return bealefunction

def matyasFunction(x):
    """
    subject to −10 ≤ xi ≤ 10. The global minimum is located at x∗ = f(0, 0), f(x∗) = 0.
    """
    x1 = x['IndPosition0']
    x2 = x['IndPosition1']
    matayasfunction = 0.26*(x1**2 + x2**2)- 0.48*x1*x2
    return matayasfunction

def bohachevsky1Function(x):
    """
    subject to −100 ≤ xi ≤ 100. The global minimum is located at x∗ = f(0, 0), f(x∗) = 0.
    """
    x1 = x['IndPosition0']
    x2 = x['IndPosition1']
    bohachevsky1function = x1**2 + 2*x2**2 - 0.3*np.cos(3*np.pi*x1)-0.4*np.cos(4*np.pi*x2)+0.7
    return bohachevsky1function

def penHolderFunction(x):
    """
    subject to −11 ≤ xi ≤ 11. The four global minima are located at x∗ = f(±9.646168, ±9.646168), f(x∗) = −0.96354.
    """
#    f(i)=-exp(-1/abs(cos(x1(j))*cos(x2(i))*exp(abs(1-sqrt(x1(j).^2+x2(i).^2)/pi))))
#    penHolderfunction = -np.exp(np.abs(np.cos(x1)*np.cos(x2)*np.exp(np.abs(1-(np.sqrt(x1**2 + x2**2))/np.pi)))**(-1))
    x1 = x['IndPosition0']
    x2 = x['IndPosition1']
    penHolderfunction = -np.exp(-1/np.abs(np.cos(x1)*np.cos(x2)*np.exp(np.abs(1-(np.sqrt(x1**2 + x2**2))/np.pi))))
    return penHolderfunction

def wayburnSeader2Function(x):
    """
    subject to −500 ≤ 500. The global minimum is located at x∗ = f{(0.2, 1), (0.425, 1)}, f(x∗) = 0.
    """
    x1 = x['IndPosition0']
    x2 = x['IndPosition1']
    wayburn = (1.613 - 4*(x1 - 0.3125)**2 - 4*(x2 - 1.625)**2)**2 + (x2 - 1)**2
    return wayburn

def schaffer1Function(x):
    """
    −100 ≤ xi ≤ 100. The global minimum is located at x∗ = f(0, 0),f(x∗) = 0.
    """
    x1 = x['IndPosition0']
    x2 = x['IndPosition1']
    numeratorcomp   = (np.sin((x1**2 + x2**2)**2)**2) - 0.5
    denominatorcomp = (1 + 0.001 * (x1**2 + x2**2))**2
    scahffer1function = 0.5 + numeratorcomp /denominatorcomp
    return scahffer1function

def wolfeFunction(x):
    """
    0 ≤ xi ≤ 2. The global minimum is located at x* = f(0,0, 0),f(x*) = 0.
    """
    x1 = x['IndPosition0']
    x2 = x['IndPosition1']
    x3 = x['IndPosition2']
    wolfefunction = (4/3)*(((x1**2 + x2**2) - (x1*x2))**(0.75)) + x3
    return wolfefunction

def ackley2Function(x):
    """
    subject to −32 ≤ xi ≤ 32. The global minimum is located at origin x∗ = (0, 0),f(x∗) = −200.
    """
    x1 = x['IndPosition0']
    x2 = x['IndPosition1']
    ackley2function = -200*np.exp((-0.2*np.sqrt(x1**2 +x2**2)))
    return ackley2function

def goldsteinpriceFunction(x):
    """
    subject to −2 ≤ xi ≤ 2. The global minimum is located at x∗ = f(0,−1), f(x∗) = 3.
    """
    x1 = x['IndPosition0']
    x2 = x['IndPosition1']
    term1 = (x1 + x2 + 1)**2
    term3 = (2*x1 - 3*x2)**2
    goldsteinpricefunction = (1 + term1*(19 - 14*x1 +3*x1**2 - 14*x2 + 6*x1*x2 + 3*x2**2))*(30 + term3*(18 - 32*x1 + 12*x1**2+ 48*x2 - 36*x1*x2 + 27*x2**2))
    return goldsteinpricefunction

def boothFunction(x):
    """
    subject to −10 ≤ xi ≤ 10. The global minimum is located at x∗ = f(1,3), f(x∗) = 0.
    """
    x1 = x['IndPosition0']
    x2 = x['IndPosition1']
    boothfunction =((x1+2*x2-7)**2+(2*x1+x2-5)**2)
    return boothfunction

def brentFunction(x):
    """
    subject to −20 ≤ xi ≤ 0. The global minimum is located at x∗ = f(-10,-10), f(x∗) = np.exp(-200)
    """
    x1 = x['IndPosition0']
    x2 = x['IndPosition1']
    brentfunction = (x1 + 10)**2 + (x2 + 10)**2 + np.exp(-x1**2 - x2**2)
    return brentfunction

def powellsumFunction(x):
    """
    subject to −1 ≤ xi ≤ 1. The global minimum is located at x∗ = f(0,...,0), f(x∗) = 0
    """
    n = (x.shape)[1]
    absx = np.abs(x)

    powellsumfunction = 0
    for i in range(n):
        powellsumfunction = powellsumfunction + (absx.iloc[:, i]**((i+1) + 1))
    return powellsumfunction


import numpy as np

#ranged data
print(np.arange(5)) #[0 1 2 3 4]
print(np.arange(3.2)) #[ 0.  1.  2.  3.]
print(np.arange(3.2, 7.2, 0.8)) #[ 3.2  4.   4.8  5.6  6.4]

#linspace
print(np.linspace(2.0, 3.0, num=5))
#array([2.  , 2.25, 2.5 , 2.75, 3.  ])
print(np.linspace(2.0, 3.0, num=5, endpoint=False))
#array([2. ,  2.2,  2.4,  2.6,  2.8])
print(np.linspace(2.0, 3.0, num=5, retstep=True))
#(array([2.  ,  2.25,  2.5 ,  2.75,  3.  ]), 0.25)
print(np.linspace(5, 11, num=5))
#[  5.    6.5   8.    9.5  11. ]
print(np.linspace(5, 11, num=5, dtype=np.int32))
#[ 5  6  8  9 11]

#reshape
arr = np.arange(8)
reshaped_arr = arr.reshape(4, 2)
print(repr(reshaped_arr))
#array([[0, 1], [2, 3], [4, 5], [6, 7])
#one dimision can be -1, the value is inferred from the length of th array and remaining dimisions
reshaped_arr = arr.reshape(2, -1, 2)
print(repr(reshaped_arr))
#array([[[0, 1], [2, 3]], [[4, 5], [6, 7]]])
#Error : arr.reshape(4, 3)

#flatten : reshape an array into a 1D array
arr2 = reshaped_arr.flatten()
print(repr(arr2))
#array([0, 1, 2, 3, 4, 5, 6, 7])

#transpose axes(permutaion of range(n))
arr = np.arange(24)
arr = np.reshape(arr, (3, 4, 2))
transposed = np.transpose(arr, axes=(1, 2, 0))
print(arr.shape)
#(3, 4, 2)
print(transposed.shape)
#(4, 2, 3)

#np.zeros, np.ones    fill array with 0/1
#np.zeros_like, np.ones_like
arr = np.array([[1, 2], [3, 4]])
print(repr(np.zeros_like(arr)))
#array([[0, 0], [0, 0]])


import numpy as np
from sklearn.metrics import pairwise_distances

def get_minkowski_distance(p1: tuple, p2: tuple, r: int = 2) -> float:
    """
    Generates Minkowski Distance between two points, r is... dimensionality?
    """
    tmp = 0 # all values will be added to this variable, will be returned
    cols = len(p1)

    if isinstance(r, str):
        if r.lower() == "inf" or r.lower() == "infinity":
            tmp_lst = []
            for i in range(cols):
                tmp_lst.append(abs(p1[i] - p2[i]))

            return max(tmp_lst)

    else:
        for i in range(cols):
            tmp += (abs(p1[i] - p2[i])**r)

        return tmp**(1/r)

def create_minkowski_matrix(arr: np.ndarray, r: int|str) -> np.ndarray:
    """

    """
    d_arr = np.empty((len(arr), len(arr)))# [[] for i in range(len(tpl1))]

    for i, p1 in enumerate(arr):
        for j, p2 in enumerate(arr):
            d_arr[i][j] = (round(get_minkowski_distance(p1, p2, r), 8))

    return d_arr

if __name__=="__main__":
    r = 2
    for arr in [np.random.rand(2, 4) for i in range(4)]:
        print("-" * 50)
        print(create_minkowski_matrix(arr*10, r))
        print("\n")
        print(pairwise_distances(arr*10, metric="euclidean"))
        print("-" * 50)
        print("\n\n")


import numpy as np
import pymc3 as pm
import matplotlib.pyplot as plt
from scipy.stats import binom
from scipy.stats import mode

import seaborn as sns

p_positive_when_vampire = 0.95
p_positive_when_mortal = 0.01
p_is_vampire = 0.001
p_positive = p_positive_when_vampire*p_is_vampire+p_positive_when_mortal*(1. - p_is_vampire)

p_vampire_positive = p_positive_when_vampire*p_is_vampire / p_positive

print('the probability that you are a vampire given the vampire blood test was positive:%f'%p_vampire_positive)

def grid_aprox(grid_points=100,success=6,tosses=9):
    p_grid = np.linspace(0.,1.,grid_points)
    prior = np.repeat(5,grid_points)
    likehood = binom.pmf(success,tosses,p_grid)
    posterior = likehood*prior
    posterior = posterior/posterior.sum()

    return p_grid, posterior

print('--------------6/9-----------')
p_grid,posterior = grid_aprox(1000,6,9)

# plt.plot(p_grid,posterior)
# plt.show()

samples = np.random.choice(p_grid,p=posterior,size=int(1e4),replace=True)
_,(ax0,ax1,ax2) = plt.subplots(1,3)
ax0.plot(p_grid,posterior)
ax1.plot(samples,'o')
sns.kdeplot(samples,ax=ax2)
plt.show()

p_less_then_0_5 = sum(posterior[p_grid < 0.5])
print('posterior < 0.5 = %f'%p_less_then_0_5)
samples_less_0_5 = sum(samples < 0.5)/1e4
print('samples < 0.5 = %f'%samples_less_0_5)

print('between 0.5 and 0.75 posterior:%f'%(sum(posterior[(p_grid > 0.5) & (p_grid < 0.75)])))
print('between 0.5 and 0.75 samples:%f'%(sum((samples > 0.5) & (samples < 0.75))/1e4))

print('80 percentile:'+str(np.percentile(samples,80)))
print('middle 80 percentile(between 10 and 90 percent):'+str(np.percentile(samples,[10,90])))
print('middle 50 percentile(between 25 and 75 percent):'+str(np.percentile(samples,[25,75])))
print('high posterior density percentile interval 50:'+str(pm.hpd(samples,alpha=0.5)))
print('middle 50 percentile(between 80 and 90 percent):'+str(np.percentile(samples,[80,90])))
print('high posterior density percentile interval 80:'+str(pm.hpd(samples,alpha=0.8)))
print('high posterior density percentile interval 90:'+str(pm.hpd(samples,alpha=0.9)))
print('high posterior density percentile interval 95:'+str(pm.hpd(samples,alpha=0.95)))
print('maximum posteriori at prob =(%f,%f)'%(max(posterior),p_grid[posterior == max(posterior)]))
print('maximum posteriori at prob from samples: %f'%mode(samples)[0])
print('mean and median : %f,%f' %(np.mean(samples),np.median(samples)))
print('expected loss:%f'%(sum(posterior*abs(0.5-p_grid))))
loss = [sum(posterior*abs(p-p_grid)) for p in p_grid]
plt.plot(p_grid,loss)
plt.show()
print('min loss at probability:%f'%p_grid[loss == min(loss)])

print('expected loss:%f'%(sum(posterior*np.power(0.5-p_grid,2))))
loss = [sum(posterior*np.power(p-p_grid,2)) for p in p_grid]
plt.plot(p_grid,loss)
plt.show()
print('min loss at probability:%f'%p_grid[loss == min(loss)])

print('--------------3/3-----------')
p_grid,posterior = grid_aprox(1000,3,3)
samples = np.random.choice(p_grid,p=posterior,size=int(1e4),replace=True)
_,(ax0,ax1,ax2) = plt.subplots(1,3)
ax0.plot(p_grid,posterior)
ax1.plot(samples,'o')
sns.kdeplot(samples,ax=ax2)
plt.show()
print('middle 50 percentile(between 25 and 75 percent):'+str(np.percentile(samples,[25,75])))
print('high posterior density percentile interval 50:'+str(pm.hpd(samples,alpha=0.5)))
print('middle 50 percentile(between 80 and 90 percent):'+str(np.percentile(samples,[80,90])))
print('high posterior density percentile interval 80:'+str(pm.hpd(samples,alpha=0.8)))
print('high posterior density percentile interval 90:'+str(pm.hpd(samples,alpha=0.9)))
print('high posterior density percentile interval 95:'+str(pm.hpd(samples,alpha=0.95)))
print('maximum posteriori at prob =(%f,%f)'%(max(posterior),p_grid[posterior == max(posterior)]))
print('maximum posteriori at prob from samples: %f'%mode(samples)[0])
print('mean and median : %f,%f' %(np.mean(samples),np.median(samples)))

print('expected loss:%f'%(sum(posterior*abs(0.5-p_grid))))
loss = [sum(posterior*abs(p-p_grid)) for p in p_grid]
plt.plot(p_grid,loss)
plt.show()
print('min loss at probability:%f'%p_grid[loss == min(loss)])

print('expected loss:%f'%(sum(posterior*np.power(0.5-p_grid,2))))
loss = [sum(posterior*np.power(p-p_grid,2)) for p in p_grid]
plt.plot(p_grid,loss)
plt.show()
print('min loss at probability:%f'%p_grid[loss == min(loss)])

print('-----------------simulating---------')
# simulate globe tossing for water with 0.7 prob. in 2 throws
data = binom.pmf(range(3),2,0.7)
# 0 water 0.09, 1 water 0.42, 2 water 0.49
print(data)
# sample from distribution of simulation data - it is binom, so sample from binom distribution
# we are generating observations...that mean how many times did we see water when we tossed the globe 2x
samples = binom.rvs(n=2, p=0.7, size=1)
print(samples)
samples = binom.rvs(n=2, p=0.7, size=10)
print(samples)
dummy_w = binom.rvs(n=2, p=0.7, size=100000)
means = [(dummy_w == i).mean() for i in range(3)]
print(means)

dummy_w = binom.rvs(n=9, p=0.7, size=100000)
means = [(dummy_w == i).mean() for i in range(9)]
print(means)
plt.hist(dummy_w,bins=50)
plt.show()

dummy_w = binom.rvs(n=9, p=0.6, size=100000)
means = [(dummy_w == i).mean() for i in range(9)]
print(means)
plt.hist(dummy_w,bins=50)
plt.show()

dummy_w = binom.rvs(n=9, p=0.5, size=100000)
means = [(dummy_w == i).mean() for i in range(9)]
print(means)
plt.hist(dummy_w,bins=50)
plt.show()

dummy_w = binom.rvs(n=9, p=0.4, size=100000)
means = [(dummy_w == i).mean() for i in range(9)]
print(means)
plt.hist(dummy_w,bins=50)
plt.show()

dummy_w = binom.rvs(n=9, p=0.3, size=100000)
means = [(dummy_w == i).mean() for i in range(9)]
print(means)
plt.hist(dummy_w,bins=50)
plt.show()

#generate samples from posterior
p_grid, posterior = grid_aprox(grid_points=1000, success=6, tosses=9)
np.random.seed(100)
samples = np.random.choice(p_grid, p=posterior, size=int(1e4), replace=True)
dummy_w = binom.rvs(n=9, p=samples)
_,(ax0,ax1) = plt.subplots(1,2)
ax0.plot(posterior)
ax1.hist(dummy_w,bins=50)
plt.show()