# Dado uma lista com n elementos, escreva um programa em Python que retorne as seguintes medidas:
# A média aritmética e geométrica;
# Moda;
# Mediana.

import numpy #importação para o cálculo do produto dos números da lista
from math import prod #importação para o cálculo do produto dos números da lista
import statistics #importação para a moda
import math #importação para efetuar alguns cálculos matemáticos (fórmulas)

lista_elementos = [1,5,6,2,5,9,10,15,2,5] #lista aleatória

#len() é a fórmula para contar a quantidade de elementos que tem na lista

# Média aritimética: a soma dos elementos da lista dividido pela quantidade de elementos que a lista tem
media_arit = sum(lista_elementos)/len(lista_elementos) #sun() é a fórmula de soma
print("A média aritmética é ", media_arit)

# Média geométrica: é a raíz dos N elementos da multiplicação dos elementos.ATENÇÃO para o 1/len pois é raíz
media_geo = numpy.prod(lista_elementos)**(1/len(lista_elementos))
print("A média geométrica é ", media_geo)
media_goe2 = prod(lista_elementos)**(1/len(lista_elementos))
print("A média geométrica de outro jeito é: ",media_goe2)

# Para calcular a mediana precisamos organizar a lista, e para facilitar eu criei um valor com a quantidade de elementos da lista para facilitar nos cálculos, pra não ficar um fórmula dentro de outra.

lista_ordenada = sorted(lista_elementos) #função para ordenar os elementos da lista de forma crescente
print(lista_ordenada)

elementos = len(lista_elementos)
print("Quantidade de elementos na lista: ", elementos)

meio=int(elementos/2) #função para verificar qual a posição do meio na lista (é posição, não é o número)
print("Posição do elemento central: ", meio+1) #como começa com índice zero, considerei o + 1 porque iniciamos a contagem em 1, 2, 3... e o python inicia a contagem do index em 0, 1, 2...

# Medida: SE a quantidade de elementos é par, a mediana é a soma do valor antecessor ao meio e o valor do meio, divido por 2. SE a quantidade de elementos é ímpar, a mediana é o valor central.
if (len(lista_ordenada)%2)==0: #verificar se o resto da divisão por 2 é zero, se for é par.
    mediana = ((lista_ordenada[meio-1])+lista_ordenada[meio])/2
    print("A mediana é: ", mediana)
else:
    mediana = lista_ordenada[meio] #função que acessa o valor do meio da lista
    print("A mediana é: ", mediana)
# Volte ao Ex05 para ver maiores explicações sobre essa questão do posicionamento.

# Moda: valor que aparece mais vezes na lista
# não consegui fazer sem usar fórumla!!!
# SE a quantidade de elementos da fórmula multimodal for apenas 1, significa que não é multimodal, é apenas 1 número com maior frequência. Caso contrário, significa que os valores são multimodais.
if (len(statistics.multimode(lista_ordenada))==1):
    moda = statistics.mode(lista_ordenada) #para o caso de ser somente uma moda
    print("A moda é: ",moda)
else:
    moda2 = statistics.multimode(lista_ordenada) #para o caso de ser multimodal
    print("A moda multimodal é: ", moda2)
#Fazer com a lista original gera resultados diferentes, pois ela vai considerar o primeiro valor que se repete da lista, por exemplo, vai retornar [5,2] na multimodal. E não [2,5] na lista ordenada.

#Statistics.multimode e statistics.mode são fórmulas de moda e moda multimodal.

# Prof indicou esse site: https://www.geeksforgeeks.org/finding-mean-median-mode-in-python-without-libraries/ para o cálculo da moda sem usar uma fórmula já definida.

# OBS: se você conseguir entender, por gentileza, me explique!!!!

#Segue o código que tem no link:

# Python program to print
# mode of elements
from collections import Counter

# list of elements to calculate mode
n_num = [1, 2, 3, 4, 5, 2]
n = len(n_num)

data = Counter(n_num)
get_mode = dict(data)
mode = [k for k, v in get_mode.items() if v == max(list(data.values()))]

if len(mode) == n:
    get_mode = "No mode found"
else:
    get_mode = "Mode is / are: " + ', '.join(map(str, mode))

print(get_mode)


# -*- coding: utf-8 -*-
"""
SYS-611: Example Markov Process Generator

@author: Paul T. Grogan, pgrogan@stevens.edu
"""

# import the python3 behavior for importing, division, and printing in python2
from __future__ import absolute_import, division, print_function

# import the numpy package and refer to it as `np`
# see http://docs.scipy.org/doc/numpy/reference/ for documentation
import numpy as np
# import the matplotlib.pyplot package and refer to it as `plt`
import matplotlib.pyplot as plt

# define the state transition function
def next_state(q):
    r = np.random.rand()
    # if it is a clear day
    if q == 0:
        if r < 186/250:
            # another clear day
            return 0
        elif r < (186+47)/250:
            # a rainy day next
            return 1
        else:
            # a snowy day next
            return 2
    # if it is a rainy day
    elif q == 1:
        if r < 47/89:
            # a clear day next
            return 0
        elif r < (47+40)/89:
            # another rainy day
            return 1
        else:
            # a snowy day next
            return 2
    # if it is a snowy day
    else:
        if r < 16/25:
            # a clear day next
            return 0
        elif r < (16+3)/25:
            # a rainy day next
            return 1
        else:
            # another snowy day
            return 2

# define the number of samples and create a state trajectory
num_samples = 100
np.random.seed(0)
q = np.zeros(num_samples)
# perform all the state transitions
for t in range(num_samples - 1):
    q[t+1] = next_state(q[t])

# create a plot of the state trajectory
plt.figure()
plt.step(range(num_samples), q, '-r')
plt.xlabel('Time ($t$)')
plt.ylabel('State ($q$)')

# estimate the stationary distribution from the samples
pi = np.zeros(3)
for i in range(3):
    pi[i] = np.sum(q==i)/num_samples

print('estimated stationary distribution (solved using simulation):')
print(' P(q=0) = {:.3f} (clear day)'.format(pi[0]))
print(' P(q=1) = {:.3f} (rainy day)'.format(pi[1]))
print(' P(q=2) = {:.3f} (snowy day)'.format(pi[2]))

#%% steady-state analysis

# formal state transition matrix
P = [[186/250, 47/250, 17/250],
     [47/89, 40/89, 2/89],
     [16/25, 3/25, 6/25]]

# compute the eigenvalues and eigenvectors of the transpose of P
w,v = np.linalg.eig(np.transpose(P))

# the stationary distribution is the normalized eigenvector
# corresponding to the eigenvalue of 1
pi_exact = v[:,0]/np.sum(v[:,0])

print('exact stationary distribution (solved using eigenvectors):')
print(' P(q=0) = {:.3f} (clear day)'.format(pi_exact[0]))
print(' P(q=1) = {:.3f} (rainy day)'.format(pi_exact[1]))
print(' P(q=2) = {:.3f} (snowy day)'.format(pi_exact[2]))


# The prime factors of 13195 are 5, 7, 13 and 29.
# What is the largest prime factor of the number 600851475143 ?

import numpy as np

def solve():

    number = 600851475143

    max_factor = int(np.sqrt(number))
    print("Max_factor", max_factor)

    # Looking for prime numbers

    numbers = [i for i in range(1, max_factor+1)]
    is_prime = [True for i in range(max_factor)]
#    print(numbers)
#    print(is_prime)

    current_prime_index = 1
    prime_index_condition = current_prime_index < len(is_prime)
    while prime_index_condition:
        print("Current prime number:", current_prime_index+1)
        # We bar the multiples
        is_prime_traversal = 2 * current_prime_index + 1
        condition = is_prime_traversal < len(is_prime)
        while condition:
            is_prime[is_prime_traversal] = False
            is_prime_traversal += current_prime_index + 1
            condition = is_prime_traversal < len(is_prime)
#        print(numbers)
#        print(is_prime)
        # Next prime
        next_prime_index = current_prime_index + 1
        condition = next_prime_index < len(is_prime)
        condition = condition and (is_prime[next_prime_index] == False)
        while condition:
            next_prime_index = next_prime_index + 1
            condition = (next_prime_index < len(is_prime))

            condition = condition and (is_prime[next_prime_index] == False)
        current_prime_index = next_prime_index
        prime_index_condition = current_prime_index < len(is_prime)

    prime_numbers = [n for n, p in zip(numbers, is_prime) if p]

    for factor in reversed(prime_numbers):
        if number%factor == 0:
            return factor

    return

print(solve())


# coding: utf-8
Возьмем как пример распределение Лапласса
# In[1]:

import numpy as np
import matplotlib.pyplot as plt
import scipy.stats as stat
get_ipython().run_line_magic('matplotlib', 'inline')

# In[2]:

a = 1.0
b = 0.0
ld = stat.laplace(loc=b, scale=a)
x = np.linspace(-4, 4, 1000)
plt.plot(x, stat.laplace.pdf(x))

# In[3]:

sample = ld.rvs(size=1000)
plt.hist(sample, normed=True, bins=30)
plt.ylabel('number of samples')
plt.xlabel('$x$')

# In[4]:

sizes = [5, 10, 30, 45]
samples = []
for i in sizes:
    samples.append(stat.laplace.rvs(size=[1000, i]))

# In[5]:

means_of_sample = []
for sample in samples:
    means = []
    for i in sample:
        means.append(np.mean(i))
    means_of_sample.append(means)

# In[6]:

mean = b
dispersion = 2 / ((2 * stat.laplace.pdf(mean)) ** 2)
print("mean: " + str(mean)
      + "\ndispersion: " + str(dispersion))

# In[7]:

norm_rv = stat.norm(mean, np.sqrt(dispersion / sizes[0]))
x = np.linspace(-4, 4, 1000)
pdf = norm_rv.pdf(x)
plt.plot(x, pdf)

plt.hist(means_of_sample[0], normed=True)
plt.ylabel('number of samples')
plt.xlabel('n = 5')

# In[11]:

norm_rv = stat.norm(mean, np.sqrt(dispersion / sizes[1]))
x = np.linspace(-4, 4, 1000)
pdf = norm_rv.pdf(x)
plt.plot(x, pdf)

plt.hist(means_of_sample[1], normed=True)
plt.ylabel('number of samples')
plt.xlabel('n = 30')

# In[12]:

norm_rv = stat.norm(mean, np.sqrt(dispersion / sizes[2]))
x = np.linspace(-4, 4, 1000)
pdf = norm_rv.pdf(x)
plt.plot(x, pdf)

plt.hist(means_of_sample[2], normed=True)
plt.ylabel('number of samples')
plt.xlabel('n = 50')

# In[13]:

norm_rv = stat.norm(mean, np.sqrt(dispersion / sizes[3]))
x = np.linspace(-4, 4, 1000)
pdf = norm_rv.pdf(x)
plt.plot(x, pdf)

plt.hist(means_of_sample[3], normed=True)
plt.ylabel('number of samples')
plt.xlabel('n = 100')

Таким образом, чем больше объем выборки, тем больше гистограмма похожа на нормальное распределение


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Feb  7 21:17:50 2018

@author: Daniel Yang (daniel.yj.yang@gmail.com)
"""

import numpy as np
from scipy import stats, exp
from matplotlib import pyplot as plt

# Normal Distribution
mu = 0 # mean
sigma = 1 # standard deviation
x = np.arange(-5,5,0.1)

y = stats.norm.pdf(x, mu, sigma)
plt.figure(num=None, figsize=(8, 6), dpi=80, facecolor='w', edgecolor='k')
plt.plot(x, y)
plt.title('Normal: mu=%.1f, sigma=%.1f'%(mu, sigma))
plt.xlabel('x')
plt.ylabel('Probability density')
plt.ylim(-0.05, 0.45)
plt.show()

y = stats.norm.cdf(x, mu, sigma)
plt.figure(num=None, figsize=(8, 6), dpi=80, facecolor='w', edgecolor='k')
plt.plot(x, y)
plt.title('Normal: mu=%.1f, sigma=%.1f'%(mu, sigma))
plt.xlabel('x')
plt.ylabel('Cumulative distribution')
plt.ylim(-0.1, 1.1)
plt.show()

# Bivariate normal distribution
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import multivariate_normal
#from mpl_toolkits.mplot3d import Axes3D

#Parameters to set
mu_x = 0
variance_x = 1

mu_y = 0
variance_y = 1

#Create grid and multivariate normal
x = np.linspace(-4,4,500)
y = np.linspace(-4,4,500)
X, Y = np.meshgrid(x,y)
pos = np.empty(X.shape + (2,))
pos[:, :, 0] = X; pos[:, :, 1] = Y
rv = multivariate_normal([mu_x, mu_y], [[variance_x, 0], [0, variance_y]])

#Make a 3D plot
fig = plt.figure(num=None, figsize=(8, 6), dpi=300, facecolor='w', edgecolor='k')
ax = fig.gca(projection='3d')
ax.plot_wireframe(X, Y, rv.pdf(pos),rcount=25, ccount=25)
#ax.plot_surface(X, Y, rv.pdf(pos),cmap='viridis',linewidth=0)
ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
ax.set_zlabel('Z axis')
plt.show()

#Make a 3D plot
fig = plt.figure(num=None, figsize=(8, 6), dpi=300, facecolor='w', edgecolor='k')
ax = fig.gca(projection='3d')
ax.plot_wireframe(X, Y, rv.cdf(pos),rcount=25, ccount=25)
#ax.plot_surface(X, Y, rv.pdf(pos),cmap='viridis',linewidth=0)
ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
ax.set_zlabel('Z axis')
plt.show()

# Student's t-distribution
mu = 0 # mean
sigma = 1 # standard deviation
df = 1
x = np.arange(-5,5,0.1)

y = stats.t.pdf(x, df, mu, sigma)
plt.figure(num=None, figsize=(8, 6), dpi=80, facecolor='w', edgecolor='k')
plt.plot(x, y)
plt.title('Student\' t-distribution: df=%.0f, mu=%.1f, sigma=%.1f'%(df, mu, sigma))
plt.xlabel('x')
plt.ylabel('Probability density')
plt.ylim(-0.05, 0.45)
plt.show()

y = stats.t.cdf(x, df, mu, sigma)
plt.figure(num=None, figsize=(8, 6), dpi=80, facecolor='w', edgecolor='k')
plt.plot(x, y)
plt.title('Student\' t-distribution: df=%.0f, mu=%.1f, sigma=%.1f'%(df, mu, sigma))
plt.xlabel('x')
plt.ylabel('Cumulative distribution')
plt.ylim(-0.1, 1.1)
plt.show()

# Poisson Distribution
# For discrete outcomes (e.g., the number of customers visiting your lane in a supermarket for check out during 4:30-4:50), with known expected average outcome
# Watch this for the Wal-Mart example (https://www.youtube.com/watch?v=8px7xuk_7OU)
n = np.arange(970, 1030)  # the number of possible occurrences of interest, say the number of visitors that might show up during 10-10:30pm on a Tuesday night
lambda_coef = 1000   # long-run average, say, the expected number of customers in Amazon website between 10:00-10:30pm on a Tuesday night is 1000
y = stats.poisson.pmf(n, lambda_coef)  # Probability Mass Function is the probability density function for discrete outcome
plt.figure(num=None, figsize=(8, 6), dpi=80, facecolor='w', edgecolor='k')
plt.plot(n, y, 'o-')
plt.show()
# Question: What is the probability that exactly 7 customers enter your lane between 4:30-4:45?
print("The probability that exactly 7 customers enter my lane between 4:30-4:45 is {0:.2f}%".format(100*stats.poisson.pmf(7, 10)))

# Bernoulli Distribution
k = np.array([0,1]) # 0 = tail, 1 = head
p = 0.7 # probability of the head
y = stats.bernoulli.pmf(k, p)
fig = plt.figure(num=None, figsize=(8, 6), dpi=80, facecolor='w', edgecolor='k')
ax = fig.add_axes([0,0,1,1])
ax.bar(['0','1'], y)
plt.xlabel('k')
plt.ylim(0,1)
plt.show()

y = stats.bernoulli.cdf(k, p)
fig = plt.figure(num=None, figsize=(8, 6), dpi=80, facecolor='w', edgecolor='k')
ax = fig.add_axes([0,0,1,1])
ax.bar(['0','1'], y)
plt.xlabel('k')
plt.ylim(0,1)
plt.show()

# Binomial Distribution
n = 10 # number of coins tossed
k = np.arange(0,n+1) # number of heads
p = 0.5 # probability of the head
y = stats.binom.pmf(k, n, p)
plt.figure(num=None, figsize=(8, 6), dpi=80, facecolor='w', edgecolor='k')
plt.bar(k,y)
plt.xlabel('k')
plt.plot(k, y, 'o-r')
plt.show()

y = stats.binom.cdf(k, n, p)
plt.figure(num=None, figsize=(8, 6), dpi=80, facecolor='w', edgecolor='k')
plt.bar(k,y)
plt.xlabel('k')
plt.plot(k, y, 'o-r')
plt.show()

# Multinomial Distribution
n = 20 # 10 # 3 # 2 # number of trials (k-sided dice rolled)
p = [1/6, 1/6, 1/6, 1/6, 1/6, 1/6] # probability of the each side of a 6-sided dice, should sum to 1

rv = stats.multinomial(n, p) # A multinomial random variable

x_samples = rv.rvs(size = 1000000, random_state=12345) # draw random samples.
x_samples = np.unique(x_samples, axis = 0)
# each sample consists of number of outcomes for event-1, event-2, ..., event-k, should num to n
# for example, if n = 9, then x = [2, 1, 2, 1, 2, 1] means the each of the odd-number sides (1,3,5) appearing 2 times and each of the even-number sides (2,4,6) appearing 1 time

x_samples.shape # the # of rows is the distinct possible outcomes of rolling the dice n times

y = rv.pmf(x = x_samples)

# Number of occurrences for each side
# [≥1, ≥1, ≥1, ≥1, ≥1, ≥1]
x_samples_subset = x_samples[np.where(
    (x_samples[:, 0] >= 1) &
    (x_samples[:, 1] >= 1) &
    (x_samples[:, 2] >= 1) &
    (x_samples[:, 3] >= 1) &
    (x_samples[:, 4] >= 1) &
    (x_samples[:, 5] >= 1))]

x_samples_subset.shape

y = rv.pmf(x = x_samples_subset)
sum(y)

# Investment example
n = 100
p = [75/100, 20/100, 5/100]
rv = stats.multinomial(n, p) # A multinomial random variable
x_samples = rv.rvs(size = 10000000, random_state=12345) # draw random samples.
x_samples = np.unique(x_samples, axis = 0)
x_samples.shape # the # of rows is the distinct possible outcomes of rolling the dice n times
# Number of occurrences for each side
# [≥1, ≥1, ≥1, ≥1, ≥1, ≥1]
x_samples_subset = x_samples[np.where(
    (x_samples[:, 0] >= 70) &
    (x_samples[:, 1] <= 25) &
    (x_samples[:, 2] <= 5))]

x_samples_subset.shape

y = rv.pmf(x = x_samples_subset)
sum(y)

# Geometric Distribution
k = np.arange(1,11) # number of heads
p = 0.5 # probability of the head
y = stats.geom.pmf(k, p)
plt.figure(num=None, figsize=(8, 6), dpi=80, facecolor='w', edgecolor='k')
plt.bar(k,y)
plt.xlabel('k')
plt.plot(k, y, 'o-r')
plt.show()

y = stats.geom.cdf(k, p)
plt.figure(num=None, figsize=(8, 6), dpi=80, facecolor='w', edgecolor='k')
plt.bar(k,y)
plt.xlabel('k')
plt.plot(k, y, 'o-r')
plt.show()

# Logistic distribution
# https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.logistic.html
loc, scale = 10, 1
s = np.random.logistic(loc, scale, 100000)
count, bins, ignored = plt.hist(s, bins=50)

def logistic_pdf(x, loc, scale):
    return exp((loc-x)/scale)/(scale*(1+exp((loc-x)/scale))**2)

def logistic_cdf(x, loc, scale):
    return 1/(1+(exp((loc-x)/scale)))

plt.plot(bins, logistic_pdf(bins, loc, scale)*count.max()/logistic_pdf(bins, loc, scale).max())
plt.show()

plt.plot(bins, logistic_cdf(bins, loc, scale)*count.max()/logistic_cdf(bins, loc, scale).max())
plt.show()

# Logistic distribution
loc, scale = 10, 1
x = np.arange(-5,25,0.1)
y = stats.logistic.pdf(x, loc, scale) # pdf
plt.figure(num=None, figsize=(8, 6), dpi=80, facecolor='w', edgecolor='k')
plt.plot(x, y)
plt.show()

y = stats.logistic.cdf(x, loc, scale) # cdf
plt.figure(num=None, figsize=(8, 6), dpi=80, facecolor='w', edgecolor='k')
plt.plot(x, y)
plt.show()

# Chi-square distribution
# https://machinelearningmastery.com/statistical-data-distributions/
# plot the chi-squared pdf
from numpy import arange
from matplotlib import pyplot
from scipy.stats import chi2
# define the distribution parameters
sample_space = arange(0, 10, 0.01)
dof = 1 # 3
# calculate the pdf
pdf = chi2.pdf(sample_space, dof)
# plot
pyplot.plot(sample_space, pdf)
pyplot.show()
# calculate the cdf
cdf = chi2.cdf(sample_space, dof)
# plot
pyplot.plot(sample_space, cdf)
pyplot.show()


import pandas as pd
import numpy as np

# pd.set_option('display.max_columns', None)
# pd.set_option('display.max_colwidth', None)

##############################################
# Model

# points = [(np.array([2]),4), (np.array([4]),2)]
# d = 1

# Generate data
iterationCount = 2000
true_w = np.array([1,2,3,4,5,]) # Reverse-engineer to get to this vector
d = len(true_w)
dfColNames = [f"w{s+1}" for s in range(d)]
dfColNames.append('F(w)')
points = []
for i in range(iterationCount):
    x = np.random.randn(d)
    y = true_w.dot(x) + np.random.randn()
    points.append((x,y))

def F(w):
    return sum((w.dot(x) - y)**2 for x, y in points) / len(points)

def dF(w):
    return sum(2*(w.dot(x) - y) * x for x, y in points) / len(points)

##############################################
# Algorithm

def gradientDescent(F, dF, d):
    w = np.zeros(d)
    eta = 0.01

    lst = []
    for t in range(iterationCount):
        l1 = []
        value = F(w)
        gradient = dF(w)
        w = w - eta * gradient
        l1.extend(w)
        l1.append(value)
        lst.append(l1)
    df = pd.DataFrame(lst, columns = dfColNames)
    df['Iteration'] = df.index
    return df

result = gradientDescent(F, dF, d)

# print(result)


#Gauss-Seidel iterations
import numpy as np
import numpy.linalg as la
#
x = 0.0
y = 0.0
ni = 8
#
A = np.array([ [3.0, 2.0],
               [1.0, 4.0] ])
b = np.array([7.0, 9.0])
#
# does not converge for this:
#A = np.array([ [1.0, 2.0],
#               [3.0, 4.0] ])
#b = np.array([5.0, 11.0])
#
D = np.diag(np.diag(A))
L = np.tril(A) - D
U = np.triu(A) - D
A2 = np.matmul(la.inv(L+D), U)
normA2 = la.norm(A2)
print('||A2||=', normA2)
#
print('A=')
print(A)
print('L=')
print(L)
print('U=')
print(U)
print('D=')
print(D)
#
for i in range(ni):
    x = (b[0] - A[0,1]*y)/A[0,0]
    y = (b[1] - A[1,0]*x)/A[1,1]
    print('i=%i, x=%.2f, y=%.2f'%(i, x, y))
#
# this does converge, but slower...
#for i in range(ni):
#    x = (b[0] - A[0,1]*y0)/A[0,0]
#    y = (b[1] - A[1,0]*x0)/A[1,1]
#    print('i=%i, x=%.2f, y=%.2f'%(i, x, y))
#    x0 = x
#    y0 = y
print('done!')


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

from fits import CubicFit, CubicEval, PolynomialFit, PolynomialEval

Fits = [(lambda x : np.exp(-0.5*x*x), r'$\exp{-\frac{1}{2}x^2}$'),
        (np.sin, r'$\sin x$')]

def PlotCubic(x,y, xr, yr):
    plt.plot(x,y, 'ro' , label="points")
    plt.plot(xr, yr, label="fit")
    plt.title('Cubic Spline')
    plt.xlabel("x")
    plt.ylabel("f(x)")
    plt.legend()
    plt.show()

def CubicRun(x, y):
    xr = np.arange(x[0],x[-1]+0.1,0.1)
    c , cn = CubicFit(x,y)
    yr = np.array([CubicEval(x, c, xr[i]) for i in range(len(xr))])
    PlotCubic(x, y, xr, yr)

def CubicTest(FX):
    fX, fStr = FX[0], FX[1]
    x = np.array([i/6 + i/12 for i in range(-12,12)])
    CubicRun(x, fX(x))

def SplineTest(x, y):
    CubicRun(np.array(x), np.array(y))

def PolynomialTest(FX):
    fX, fStr = FX[0], FX[1]

    xp = np.arange(-2,2,0.01)
    yp = fX(xp)

    x = np.arange(-2,2,0.45)
    y = fX(x)
    p, cond = PolynomialFit(x,y)
    yhat = np.array([PolynomialEval(p, xp[i]) for i in range(0,len(xp))])

    plt.plot(xp, yp, label=fStr)
    plt.plot(xp, yhat,label=r'$fit$')
    plt.legend()
    plt.xlabel(r'$x$')
    plt.ylabel(r'$f(x)$')
    plt.title(fStr)
    plt.show()

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Various Mathematical Fits')
    parser.add_argument('-f','--fit', choices=['poly', 'cubic', 'spline'])
    parser.add_argument('-i','--ix', type=int, default=0)
    parser.add_argument('-x','--x', nargs='+', default=[-12,-6.1,-3, -0.5, 1.1, 4, 8,12])
    parser.add_argument('-y','--y', nargs='+', default=[3,-14, 10, 0, 16,-2, 12, -2])
    args = parser.parse_args()
    ix = args.ix

    if args.fit == 'poly':
        PolynomialTest(Fits[ix])
    elif args.fit == 'cubic':
        CubicTest(Fits[ix])
    elif args.fit == 'spline':
        x, y  = [float(x) for x in args.x], [float(y) for y in args.y]
        asset len(x) == len(y)
        SplineTest(x, y)


# coding: utf-8

# # Modelado epidemilogico del coronavirus
#
# ### Creditos al libro "Learning Scientific Programming with Python is published by Cambridge University Press (ISBN: 9781107428225)."
#
#
# https://scipython.com/book/chapter-8-scipy/additional-examples/the-sir-epidemic-model/
#

# ### Tambien agradecimientos a : Análisis del Covid-19 por medio de un modelo SEIR
# https://institucional.us.es/blogimus/2020/03/covid-19-analisis-por-medio-de-un-modelo-seir/
#

# The SIR epidemic model
# A simple mathematical description of the spread of a disease in a population is the so-called SIR model, which divides the (fixed) population of N individuals into three "compartments" which may vary as a function of time, t:
#
# S(t) are those susceptible but not yet infected with the disease;
#
# I(t) is the number of infectious individuals;
#
# R(t) are those individuals who have recovered from the disease and now have immunity to it.
#
# The SIR model describes the change in the population of each of these compartments in terms of two parameters, β and γ. β describes the effective contact rate of the disease: an infected individual comes into contact with βN other individuals per unit time (of which the fraction that are susceptible to contracting the disease is S/N). γ is the mean recovery rate: that is, 1/γ is the mean period of time during which an infected individual can pass it on.
#
# The differential equations describing this model were first derived by Kermack and McKendrick [Proc. R. Soc. A, 115, 772 (1927)]:
#
# Variables :
#
# #### β beta Contact rate
#
# #### γ gamma, mean recovery rate,
#
# #### S(t) are those susceptible but not yet infected with the disease;
#
# #### I(t) is the number of infectious individuals;
#
# #### R(t) are those individuals who have recovered and now have immunity to it.
#
# dS/dt=−βSI/N,
#
# dI/dt=βSI/N − γI,
#
# dR/dt=γI.
#
# The following Python code integrates these equations for a disease characterised by parameters β=0.2, 1/γ=10days in a population of N=1000 (perhaps 'flu in a school). The model is started with a single infected individual on day 0: I(0)=1. The plotted curves of S(t), I(t) and R(t) are styled to look a bit nicer than Matplotlib's defaults.

# In[25]:

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

# Total population, N.
N = 47500000
# Initial number of infected and recovered individuals, I0 and R0.
I0, R0 = 1, 0
# Everyone else, S0, is susceptible to infection initially.
S0 = N - I0 - R0
# Contact rate, beta, and mean recovery rate, gamma, (in 1/days).
beta, gamma = 0.2, 1./20
# A grid of time points (in days)
t = np.linspace(0, 160, 160)

# The SIR model differential equations.
def deriv(y, t, N, beta, gamma):
    S, I, R = y
    dSdt = -beta * S * I / N
    dIdt = beta * S * I / N - gamma * I
    dRdt = gamma * I
    return dSdt, dIdt, dRdt

# Initial conditions vector
y0 = S0, I0, R0
# Integrate the SIR equations over the time grid, t.
ret = odeint(deriv, y0, t, args=(N, beta, gamma))
S, I, R = ret.T

# Plot the data on three separate curves for S(t), I(t) and R(t)
fig = plt.figure(facecolor='w')
ax = fig.add_subplot(111,  axisbelow=True)
ax.plot(t, S/1000, 'b', alpha=0.5, lw=2, label='Susceptible')
ax.plot(t, I/1000, 'r', alpha=0.5, lw=2, label='Infected')
ax.plot(t, R/1000, 'g', alpha=0.5, lw=2, label='Recovered with immunity')
ax.set_xlabel('Time /days')
ax.set_ylabel('Number (1000s)')
#ax.set_ylim(0,1.2)
ax.yaxis.set_tick_params(length=0)
ax.xaxis.set_tick_params(length=0)
ax.grid(b=True, which='major', c='w', lw=2, ls='-')
legend = ax.legend()
legend.get_frame().set_alpha(0.5)
for spine in ('top', 'right', 'bottom', 'left'):
    ax.spines[spine].set_visible(False)
plt.show()


import math
import numpy
from bisect import bisect_left

def primes_up_to(upto=1000000):
    primes=numpy.arange(3,upto+1,2)
    isprime=numpy.ones((upto-1)/2,dtype=bool)
    for factor in primes[:int(math.sqrt(upto))]:
        if isprime[(factor-2)/2]: isprime[(factor*3-2)/2::factor]=0
    return numpy.insert(primes[isprime],0,2)

def primesfrom2to(n):
    """ Input n>=6, Returns a array of primes, 2 <= p < n """
    sieve = numpy.ones(n//3 + (n%6==2), dtype=numpy.bool)
    for i in range(1,int(n**0.5)//3+1):
        if sieve[i]:
            k=3*i+1|1
            sieve[       k*k//3     ::2*k] = False
            sieve[k*(k-2*(i&1)+4)//3::2*k] = False
    return numpy.r_[2,3,((3*numpy.nonzero(sieve)[0][1:]+1)|1)]

def primes_starting_at(start=1000000, upto=2000000):
    assert(start < upto)
    p = primesfrom2to(upto)
    return p[bisect_left(p, start):]

if __name__ == "__main__":
    #print(primes_starting_at(1000000, 1100000))
    print(primesfrom2to(2000000))


from math import ceil, sqrt

from scipy.special import binom
import scipy.sparse as sp

def veclen_to_matsize(L):
  n = (sqrt(1+8*L) - 1)/2
  assert n == int(n)
  return int(n)

def matsize_to_veclen(n):
  return n*(n+1)//2

def k_to_ij(k, L):
  '''
  Given a gram matrix Q represented by a vector
    V = [Q_00, ... Q_0n, Q_11, ..., Q_1n, Q22, ..., ] of length L,
    for given k, compute i,j s.t. Q_ij = V[k]
  '''
  if (k >= L):
    raise IndexError("Index out of range")
  # inverse formula for arithmetic series
  n = (sqrt(1+8*L) - 1)/2
  # get first index
  i = int(ceil( (2*n+1)/2 - sqrt( ((2*n+1)/2)**2 -2 * (k+1)  ) ) - 1)
  # second index
  k1 = (2*n+1-i)*i/2 - 1
  j = int(i + k - k1 - 1)
  return i,j

def ij_to_k(i,j,L):
  '''
  Given a symmetric matrix Q represented by a vector
  V = [Q_00, ... Q_0n, Q_11, ..., Q_1n] of length L,
  for given i,j , compute k s.t. Q_ij = V(k)
  '''
  n = (sqrt(1+8*L) - 1)/2
  i_at1 = min(i,j)+1
  j_at1 = max(j,i)+1
  k_at1 = int((n + n-i_at1)*(i_at1-1)/2 + j_at1)
  return k_at1 - 1

def vec_to_mat(vec):
  '''convert vector representation of gram matrix to gram matrix'''
  L = len(vec)
  n = int((sqrt(1+8*L) - 1)/2)
  return [[vec[ij_to_k(i,j,L)] for i in range(n) ] for j in range(n)]

def mat_to_vec(mat):
  '''retrieve vector representation of gram matrix'''
  n = mat.shape[0]
  L = int(n*(n+1)/2)
  ret = [0. for i in range(L)]
  for k in range(L):
    i,j = k_to_ij(k, L)
    ret[k] = (mat[i,j] + mat[j,i]) / 2
  return ret

def multinomial(params):
  if len(params) == 1:
    return 1
  return binom(sum(params), params[-1]) * multinomial(params[:-1])

def double_factorial(n):
  ret = 1
  for i in range(n, 0, -2):
    ret *= i
  return ret

def speye(n, pos=0, tot=None):
  '''
  return a sparse identity matrix [ 0  I  0]
  with size n x tot and where the identity matrix
  starts at pos
  '''
  if tot is None:
    tot = n
  return sp.coo_matrix( ([1.] * n, (range(n), range(pos, pos+n))), (n,tot) )

def spzeros(n, m):
  return sp.coo_matrix( (n,m) )


"""
Checks if a system of forces is in static equilibrium.
"""
from __future__ import annotations

from numpy import array, cos, cross, ndarray, radians, sin

def polar_force(
    magnitude: float, angle: float, radian_mode: bool = False
) -> list[float]:
    """
    Resolves force along rectangular components.
    (force, angle) => (force_x, force_y)
    >>> import math
    >>> force = polar_force(10, 45)
    >>> math.isclose(force[0], 7.071067811865477)
    True
    >>> math.isclose(force[1], 7.0710678118654755)
    True
    >>> polar_force(10, 3.14, radian_mode=True)
    [-9.999987317275396, 0.01592652916486828]
    """
    if radian_mode:
        return [magnitude * cos(angle), magnitude * sin(angle)]
    return [magnitude * cos(radians(angle)), magnitude * sin(radians(angle))]

def in_static_equilibrium(
    forces: ndarray, location: ndarray, eps: float = 10**-1
) -> bool:
    """
    Check if a system is in equilibrium.
    It takes two numpy.array objects.
    forces ==>  [
                        [force1_x, force1_y],
                        [force2_x, force2_y],
                        ....]
    location ==>  [
                        [x1, y1],
                        [x2, y2],
                        ....]
    >>> force = array([[1, 1], [-1, 2]])
    >>> location = array([[1, 0], [10, 0]])
    >>> in_static_equilibrium(force, location)
    False
    """
    # summation of moments is zero
    moments: ndarray = cross(location, forces)
    sum_moments: float = sum(moments)
    return abs(sum_moments) < eps

if __name__ == "__main__":
    # Test to check if it works
    forces = array(
        [
            polar_force(718.4, 180 - 30),
            polar_force(879.54, 45),
            polar_force(100, -90),
        ]
    )

    location = array([[0, 0], [0, 0], [0, 0]])

    assert in_static_equilibrium(forces, location)

    # Problem 1 in image_data/2D_problems.jpg
    forces = array(
        [
            polar_force(30 * 9.81, 15),
            polar_force(215, 180 - 45),
            polar_force(264, 90 - 30),
        ]
    )

    location = array([[0, 0], [0, 0], [0, 0]])

    assert in_static_equilibrium(forces, location)

    # Problem in image_data/2D_problems_1.jpg
    forces = array([[0, -2000], [0, -1200], [0, 15600], [0, -12400]])

    location = array([[0, 0], [6, 0], [10, 0], [12, 0]])

    assert in_static_equilibrium(forces, location)

    import doctest

    doctest.testmod()


import numpy as np
import matplotlib.pyplot as plt
from scipy import linalg as la

def PCA(dat, center=False, percentage=0.8):
    M, N = dat.shape
    if center:
        mu = np.mean(dat,0)
        dat -= mu

    U, L, Vh = la.svd(dat, full_matrices=False)

    V = Vh.T.conjugate()
    SIGMA = np.diag(L)
    X = U.dot(SIGMA)
    Lam = L**2

    normalized_eigenvalues = Lam/Lam.sum(dtype=float)
    csum = [normalized_eigenvalues[:i+1].sum() for i in xrange(N)]
    n_components = [x < percentage for x in csum].index(False) + 1

    return (normalized_eigenvalues,
            V[:,0:n_components],
            SIGMA[0:n_components,0:n_components],
            X[:,0:n_components])

def scree(normalized_eigenvalues):
    fig = plt.figure()
    plt.plot(normalized_eigenvalues,'b-', normalized_eigenvalues, 'bo')
    plt.xlabel("Principal Components")
    plt.ylabel("Percentage of Variance")
    return fig


import numpy as np
'''
ベクトル[の|][演算|計算]を[する|行う]
行列[の|][演算|計算]を[する|行う]
numpyを[使う|入れる|インポートする]
'''

np.array(l)
'''
@type(l,リスト)から配列を[作る|作成する|作成]
'''
np.array(t)
'''
@type(t,タプル)から配列を作る
'''

a.shape()
'''
@type(a,配列)の[形状|形]を調べる
'''

a.dtype()
'''
@type(a,配列)の[データ型|型]を調べる
'''

np.ndim(x)
'''
@type(x,配列)の[次元数|次元の数]を調べる
'''

np.arange(10)
'''
0から9までの配列を作る
'''

x.reshape(3,3)
'''
@type(x,配列)を3×3の多次元配列に変形する
'''

np.concatenate([a,b])
'''
@type(a,配列)と@type(b,配列)を[列方向|縦方向]に連結する
'''

np.concatenate([a,b], axis=0)
'''
@type(a,配列)と@type(b,配列)を[列方向|縦方向]に連結する
'''

np.concatenate([a,b], axis=1)
'''
@type(a,配列)と@type(b,配列)を[行方向|横方向]に連結する
'''

np.sum(x)
'''
@type(x,配列)の[合計値|合計]を調べる
'''

np.sum(x, axis=0)
'''
@type(x,配列)の列ごとの[合計値|合計]を調べる
'''

np.sum(x, axis=1)
'''
@type(x,配列)の行ごとの[合計値|合計]を調べる
'''

np.mean(x)
'''
@type(x,配列)の[平均値|平均]を調べる
'''

np.mean(x, axis=0)
'''
@type(x,配列)の列ごとの[平均値|平均]を調べる
'''

np.mean(x, axis=1)
'''
@type(x,配列)の行ごとの[平均値|平均]を調べる
'''

np.min(x)
'''
@type(x,配列)の[最小値|最小]を調べる
'''

np.min(x, axis=0)
'''
@type(x,配列)の列ごとの[最小値|最小]を調べる
'''

np.min(x, axis=1)
'''
@type(x,配列)の行ごとの[最小値|最小]を調べる
'''

np.max(x)
'''
@type(x,配列)の[最大値|最大]を調べる
'''

np.max(x, axis=0)
'''
@type(x,配列)の列ごとの[最大値|最大]を調べる
'''

np.max(x, axis=1)
'''
@type(x,配列)の行ごとの[最大値|最大]を調べる
'''

np.std(x)
'''
@type(x,配列)の標準偏差を調べる
'''

np.std(x, axis=0)
'''
@type(x,配列)の列ごとの標準偏差を調べる
'''

np.std(x, axis=1)
'''
@type(x,配列)の行ごとの標準偏差を調べる
'''

np.var(x)
'''
@type(x,配列)の分散を調べる
'''

np.var(x, axis=0)
'''
@type(x,配列)の列ごとの分散を調べる
'''

np.var(x, axis=1)
'''
@type(x,配列)の行ごとの分散を調べる
'''

np.eye(3)
'''
3×3の単位行列を作る
'''

np.identity(3)
'''
3×3の単位行列を作る
'''

np.empty(5)
'''
要素数5の[空配列|空の配列]を作る
'''

np.empty((2, 3))
'''
2×3の[空配列|空の配列]を作る
'''

np.empty_like(x)
'''
@type(x,配列)と同じ大きさの[空配列|空の配列]を作る
'''

np.gcd(a,b)
'''
@type(a,配列)と@type(b,配列)の要素ごとの最大公約数を調べる
'''

np.lcm(a,b)
'''
@type(a,配列)と@type(b,配列)の要素ごとの最小公倍数を調べる
'''

np.unique(x)
'''
@type(x,配列)から重複を除いた配列を作る
@type(x,配列)のユニークな要素を調べる
'''

u, counts = np.unique(a, return_counts=True)
'''
@type(x,配列)のユニークな要素とその個数を調べる
'''

u, indices = np.unique(x, return_index=True)
'''
@type(x,配列)のユニークな要素とその位置を調べる
'''

np.cumsum(x)
'''
@type(x,配列)の累積和を調べる
'''

np.cumprod(x)
'''
@type(x,配列)の累積積を調べる
'''

x.flatten()
'''
@type(x,配列)を[一次元にする|一次元化|平坦化]
'''


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

import numpy as np

def up(volatility, duration) :
    u = np.exp(volatility * np.sqrt(duration))
    return u

def down(volatility, duration) :
    d = np.exp(-volatility * np.sqrt(duration))
    return d

def probability_u(volatility, duration, riskfree) :
    u = up(volatility, duration)
    d = down(volatility, duration)
    probability_u = (np.exp(riskfree * duration) - d) / (u - d)
    return probability_u

def probability_d(volatility, duration, riskfree) :
    u = up(volatility, duration)
    d = down(volatility, duration)
    probability_d = (np.exp(-riskfree * duration) - d) / (u - d)
    return probability_d

def price(option, spot, riskfree, dividend, volatility, steps=4) :
    dt = option.days_to_expiry() / (365 * steps)

    u = up(volatility, dt)
    d = down(volatility, dt)

    p = probability_u(volatility, dt, riskfree)
    q = probability_d(volatility, dt, riskfree)

    ul_price = np.zeros([steps + 1, steps + 1])
    ul_price[0, 0] = spot
    for i in range(1, steps + 1) :
        ul_price[i, 0] = ul_price[i - 1, 0] * u
        for j in range(1, i + 1) :
            ul_price[i, j] = ul_price[i - 1, j - 1] * d

    option_price = np.zeros([steps + 1, steps + 1])
    for j in range(steps + 1) :
        if option.type == "call" :
            option_price[steps, j] = max(0, ul_price[steps, j] - option.strike)
        elif option.type == "put" :
            option_price[steps, j] = max(0, option.strike - ul_price[steps, j])

    for i in range(steps)[::-1] :
        for j in range(i + 1) :
            if option.style == "EU" :
                option_price[i, j] = np.exp(-riskfree * dt) * (p * option_price[i + 1, j] + q * option_price[i + 1, j + 1])
            elif option.style == "US" :
                if option.type == "call" :
                    option_price[i, j] = max(ul_price[i, j] - option.strike, np.exp(-riskfree * dt) * (p * option_price[i + 1, j] + q * option_price[i + 1, j + 1]))
                elif option.type == "put" :
                    option_price[i, j] = max(option.strike - ul_price[i, j], np.exp(-riskfree * dt) * (p * option_price[i + 1, j] + q * option_price[i + 1, j + 1]))

    return option_price[0, 0]

def main() :
    pass

if __name__ == "__main__" :
    main()


'''
Starting in the top left corner of a 2×2 grid, and only being able to move to
the right and down, there are exactly 6 routes to the bottom right corner.

How many such routes are there through a 20×20 grid?
'''

import numpy as np
from itertools import product

# brute force
def lattice_paths_brute_force(n):

    paths = [p for p in product([(0,1),(1,0)], repeat=2*n) if any(np.sum(p, axis=0)==[n,n])]

    return len(paths)

lattice_paths_brute_force(3)

from math import factorial

def lattice_paths(n):
    # this can be thought of like pascal's triangle.
    # trying to get middle number of row 2n

    return factorial(n*2) / (factorial(n*2 - n) * factorial(n))

lattice_paths(20)


# IMPORTS
import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt
import matplotlib.style as style
from IPython.core.display import HTML

# PLOTTING CONFIG
%matplotlib inline
style.use('fivethirtyeight')
plt.rcParams["figure.figsize"] = (14, 7)
HTML("""
<style>
.output_png {
    display: table-cell;
    text-align: center;
    vertical-align: center;
}
</style>
""")
plt.figure(dpi=100)

# PDF MU = 0
plt.plot(np.linspace(-4, 4, 100),
         stats.norm.pdf(np.linspace(-4, 4, 100)),
        )
plt.fill_between(np.linspace(-4, 4, 100),
                 stats.norm.pdf(np.linspace(-4, 4, 100)),
                 alpha=.15,
                )

# PDF MU = 2
plt.plot(np.linspace(-4, 4, 100),
         stats.norm.pdf(np.linspace(-4, 4, 100), loc=2),
        )
plt.fill_between(np.linspace(-4, 4, 100),
                 stats.norm.pdf(np.linspace(-4, 4, 100),loc=2),
                 alpha=.15,
                )

# PDF MU = -2
plt.plot(np.linspace(-4, 4, 100),
         stats.norm.pdf(np.linspace(-4, 4, 100), loc=-2),
        )
plt.fill_between(np.linspace(-4, 4, 100),
                 stats.norm.pdf(np.linspace(-4, 4, 100),loc=-2),
                 alpha=.15,
                )

# LEGEND
plt.text(x=-1, y=.35, s="$ \mu = 0$", rotation=65, alpha=.75, weight="bold", color="#008fd5")
plt.text(x=1, y=.35, s="$ \mu = 2$", rotation=65, alpha=.75, weight="bold", color="#fc4f30")
plt.text(x=-3, y=.35, s="$ \mu = -2$", rotation=65, alpha=.75, weight="bold", color="#e5ae38")

# TICKS
plt.tick_params(axis = 'both', which = 'major', labelsize = 18)
plt.axhline(y = 0, color = 'black', linewidth = 1.3, alpha = .7)

# TITLE, SUBTITLE & FOOTER
plt.text(x = -5, y = 0.51, s = "Normal Distribution - $ \mu $",
               fontsize = 26, weight = 'bold', alpha = .75)
plt.text(x = -5, y = 0.45,
         s = 'Depicted below are three normally distributed random variables with varying $ \mu $. As one can easily\nsee the parameter $\mu$ shifts the distribution along the x-axis.',
         fontsize = 19, alpha = .85)
plt.text(x = -5,y = -0.075,
         s = '   ©Joshua Görner                                                                                github.com/jgoerner   ',
         fontsize = 14, color = '#f0f0f0', backgroundcolor = 'grey');


#!/usr/bin/python3
import numpy as np

def log(x_value):
  x = x_value-1
  x2 = x*x
  a0 = -np.log(2); a1 = 2;a2 = -1;a3 = 2/3.;a4 = -0.5;
  T0 = 1; T1 = x; T2 = 2*x2 - 1; T3 = 4*x2*x - 3*x; T4 = 8*x2*x2 - 8*x2 + 1;
  return a0*T0 + a1*T1 + a2*T2 + a3*T3 + a4*T4

def log2(x_value):
  if x_value < 2:
    t = x_value-1;
    t2 = t*t
    t4 = t2*t2
    t6 = t4*t2
    return  t - 0.5*t2 + 1./3*t2*t - 0.25*t4 + 0.2*t4*t - 1./6*t6
  elif x_value < 6:
    t = x_value-3
    t2 = t*t
    t4 = t2*t2
    t6 = t4*t2
    return  np.log(3) + 1./3*t - 1./18*t2 + 1./81*t2*t - 1/324.*t4 + 1./1215*t4*t - 1./4374*t6
  else:
    t = x_value-9
    t2 = t*t
    t4 = t2*t2
    t6 = t4*t2
    return  np.log(9) + 1./9*t - 1./162*t2 + 1./2187*t2*t - 1/26244.*t4 + 1./295245*t4*t - 1./3188646*t6

import matplotlib.pyplot as plt
fig = plt.figure()
x_list = np.linspace(0.2, 20, 200)

plt.plot(x_list, np.log(x_list), label="exact")
#plt.plot(x_list, [log(x) for x in x_list], label="Chebyshev")
plt.plot(x_list, [log2(x) for x in x_list], label="Taylor")
plt.legend()

# define global plotting parameters
plt.rcParams.update({'font.size': 16})
plt.rcParams['lines.linewidth'] = 3

fig = plt.figure()
plt.plot(x_list, [(np.log(x)-log2(x))/np.log(x) for x in x_list])
plt.grid()
plt.savefig("apxlog.pdf")
plt.show()

max_rel_error = 0
location = 0
for x in np.linspace(0.2,20,200):
  rel_error =  (np.log(x)-log2(x))/np.log(x)
  print("{}, {}, {}, error: {}".format(x, np.log(x), log(x), rel_error))
  old_rel_error = max_rel_error
  max_rel_error = max(max_rel_error, rel_error)
  if old_rel_error != max_rel_error:
    location = x

print("maximum relative error: {} at {}".format(max_rel_error, location))

plt.show()


from abc import ABC,  abstractmethod
from math import sqrt, exp
import numpy as np
def unison_shuffled_copies(a, b, random = None):
	if random is None:
		random = np.random
	assert len(a) == len(b)
	p = random.permutation(len(a))
	return a[p], b[p]

class BaseActivation(ABC):
	"""Base class for all activation functions"""
	@property
	@abstractmethod
	def name(self):
		raise NotImplementedError

	@abstractmethod
	def function(self, x):
		pass

	@abstractmethod
	def derivative(self, x):
		pass

class Identity(BaseActivation):
	""" Identity activation for testing"""
	name = "identity"

	def function(self, x):
		return x

	def derivative(self, x):
		return 1

class Sigmoid(BaseActivation):
	""" Identity activation for testing"""
	name = "sigmoid"

	def function(self, x):
		return 1 / (1 + exp(-x))

	def derivative(self, x):
		return self.function(x)*(1-self.function(x))

def softmax(X, theta = 1.0, axis = None):
	"""
	Compute the softmax of each element along an axis of X.

	Parameters
	----------
	X: ND-Array. Probably should be floats.
	theta (optional): float parameter, used as a multiplier
		prior to exponentiation. Default = 1.0
	axis (optional): axis to compute values along. Default is the
		first non-singleton axis.

	Returns an array the same size as X. The result will sum to 1
	along the specified axis.
	"""

	# make X at least 2d
	y = np.atleast_2d(X)

	# find axis
	if axis is None:
		axis = next(j[0] for j in enumerate(y.shape) if j[1] > 1)

	# multiply y against the theta parameter,
	y = y * float(theta)

	# subtract the max for numerical stability
	y = y - np.expand_dims(np.max(y, axis = axis), axis)

	# exponentiate y
	y = np.exp(y)

	# take the sum along the specified axis
	ax_sum = np.expand_dims(np.sum(y, axis = axis), axis)

	# finally: divide elementwise
	p = y / ax_sum

	# flatten if X was 1D
	if len(X.shape) == 1: p = p.flatten()

	return p


# Do not import any additional 3rd party external libraries as they will not
# be available to AutoLab and are not needed (or allowed)

import numpy as np
import os

class Activation(object):
    """
    Interface for activation functions (non-linearities).

    In all implementations, the state attribute must contain the result,
    i.e. the output of forward (it will be tested).
    """

    # No additional work is needed for this class, as it acts like an
    # abstract base class for the others

    # Note that these activation functions are scalar operations. I.e, they
    # shouldn't change the shape of the input.

    def __init__(self):
        self.state = None

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

    def forward(self, x):
        raise NotImplemented

    def derivative(self):
        raise NotImplemented

class Identity(Activation):
    """
    Identity function (already implemented).
    """

    # This class is a gimme as it is already implemented for you as an example

    def __init__(self):
        super(Identity, self).__init__()

    def forward(self, x):
        self.state = x
        return x

    def derivative(self):
        return 1.0

class Sigmoid(Activation):
    """
    Sigmoid non-linearity
    """

    # Remember do not change the function signatures as those are needed
    # to stay the same for AutoLab.

    def __init__(self):
        super(Sigmoid, self).__init__()

    def forward(self, x):
        # DONE:
        # Might we need to store something before returning?
        # self.state = ???
        # Hint: You can use np.exp() function
        # return self.state
        self.state = x
        self.state[x>=0] = 1.0 / (1.0 + np.exp(- self.state[x>=0]))
        self.state[x<0] = np.exp(self.state[x<0]) / ( np.exp(self.state[x<0]) + 1.0)
        return self.state

    def derivative(self):
        # DONE:
        # Maybe something we need later in here...
        # return ???
        # Maybe something we need later in here...
        return self.state * (1.0 - self.state)

class Tanh(Activation):
    """
    Tanh non-linearity
    """
    def __init__(self):
        super(Tanh, self).__init__()

    def forward(self, x):
        # DONE:
        # self.state = ???
        # Hint: You can use np.exp() function
        # return self.state
        self.state = (1 - np.exp(-2 * x)) / (1 + np.exp(-2 * x))
        return self.state

    def derivative(self):
        # DONE:
        # return ???
        return (1 - self.state**2)

class ReLU(Activation):
    """
    ReLU non-linearity
    """
    def __init__(self):
        super(ReLU, self).__init__()

    def forward(self, x):
        # DONE:
        # self.state = ???
        # return self.state
        self.state = np.maximum(x, 0)
        return self.state

    def derivative(self):
        # DONE
        # return ???
        tmp = self.state
        tmp[tmp > 0] = 1
        return tmp


#Purpose: To perform the fourier transform
#import necessary packages
import numpy as np
from matplotlib import pyplot as plt
from PIL import Image, ImageFilter, ImageEnhance

#Function 1: Performs the fourier transform with steps
#to get Magnitude_Spectrum
#Steps: fourier transform, fourier shift, gets the magnitude spectrum
#Inputs: image
#Outputs: magntidue spectrum array
def Magnitude_Spectrum(image):
    """Peforms a fourier transform and returns the m_spec"""
    fshift = fourier_fshift(image)
    m_spec= np.log(np.abs(fshift))
    return m_spec

#Function 2: Performs the fourier transform to get fshift
#Steps: fourier transform, fourier shift, gets the magnitude Spectrum
#Inputs: image
#Outputs: fshift
def fourier_fshift(image):
    """peforms the fourier transform and returns the fshift"""
    f = np.fft.fft2(image)
    fshift= np.fft.fftshift(f)
    return fshift

#Function 3: Plotting the magnitude spectrum made my fourier transform
#Steps: plots the Magnitude Spectrum
#Input: the magnitude spectrum Output
#Output: image
# def Plot_M_Spec(m_spec):
#     plt.show(m_spec)
#     plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
#     plt.show()
#     return

#Function 4: Performs an inverse fourier transform
#Steps: unshifts the image, inverse fourier, normalizes the image
#Inputs: f_shift
#Outputs: image array of inverse fouriered image
def inverse_fourier(f_shift):
    image_revert = np.fft.ifftshift(f_shift)
    image = np.fft.ifft2(image_revert)
    image = np.abs(image)
    return image


import numpy as np
import scipy as sp

import matplotlib.pyplot as plt

from scipy.stats import beta as beta_dist
beta_pdf = beta_dist.pdf

prior_alpha = 25
prior_beta = 75

args = (prior_alpha, prior_beta)
prior_over_33, err = sp.integrate.quad(beta_pdf, 0.33, 1, args=args)
print("Prior probability", prior_over_33)
# 0.037830787030165056

observed_successes = 122
observed_failures = 257

posterior_alpha = prior_alpha + observed_successes
posterior_beta = prior_beta + observed_failures

args = (posterior_alpha, posterior_beta)
posterior_over_33, err2 = sp.integrate.quad(beta_pdf, 0.33, 1, args=args)
print("Posterior probability", posterior_over_33)
# 0.13686193416281017

p = np.linspace(0, 1, 500)
prior_dist = beta_pdf(p, prior_alpha, prior_beta)
posterior_dist = beta_pdf(p, posterior_alpha, posterior_beta)

fig, ax = plt.subplots()
ax.plot(p, prior_dist, "k--", label="Prior")
ax.plot(p, posterior_dist, "k", label="Posterior")
ax.legend()
ax.set_xlabel("Success rate")
ax.set_ylabel("Density")
ax.set_title("Prior and posterior distributions for success rate")

plt.show()


"""
Implements inequality and segregation measures such as Gini, Lorenz Curve

"""

import numpy as np
from numba import njit, prange

@njit
def lorenz_curve(y):
    """
    Calculates the Lorenz Curve, a graphical representation of the distribution of income
    or wealth.

    It returns the cumulative share of people (x-axis) and the cumulative share of income earned

    Parameters
    ----------
    y : array_like(float or int, ndim=1)
        Array of income/wealth for each individual. Unordered or ordered is fine.

    Returns
    -------
    cum_people : array_like(float, ndim=1)
        Cumulative share of people for each person index (i/n)
    cum_income : array_like(float, ndim=1)
        Cumulative share of income for each person index

    References
    ----------
    .. [1] https://en.wikipedia.org/wiki/Lorenz_curve

    Examples
    --------
    >>> a_val, n = 3, 10_000
    >>> y = np.random.pareto(a_val, size=n)
    >>> f_vals, l_vals = lorenz(y)

    """

    n = len(y)
    y = np.sort(y)
    s = np.zeros(n + 1)
    s[1:] = np.cumsum(y)
    cum_people = np.zeros(n + 1)
    cum_income = np.zeros(n + 1)
    for i in range(1, n + 1):
        cum_people[i] = i / n
        cum_income[i] = s[i] / s[n]
    return cum_people, cum_income

@njit(parallel=True)
def gini_coefficient(y):
    r"""
    Implements the Gini inequality index

    Parameters
    -----------
    y : array_like(float)
        Array of income/wealth for each individual. Ordered or unordered is fine

    Returns
    -------
    Gini index: float
        The gini index describing the inequality of the array of income/wealth

    References
    ----------

    https://en.wikipedia.org/wiki/Gini_coefficient
    """
    n = len(y)
    i_sum = np.zeros(n)
    for i in prange(n):
        for j in range(n):
            i_sum[i] += abs(y[i] - y[j])
    return np.sum(i_sum) / (2 * n * np.sum(y))

def shorrocks_index(A):
    r"""
    Implements Shorrocks mobility index

    Parameters
    -----------
    A : array_like(float)
        Square matrix with transition probabilities (mobility matrix) of
        dimension m

    Returns
    --------
    Shorrocks index: float
        The Shorrocks mobility index calculated as

        .. math::

            s(A) = \frac{m - \sum_j a_{jj} }{m - 1} \in (0, 1)

        An index equal to 0 indicates complete immobility.

    References
    -----------
    .. [1] Wealth distribution and social mobility in the US: A quantitative approach
       (Benhabib, Bisin, Luo, 2017).
       https://www.econ.nyu.edu/user/bisina/RevisionAugust.pdf
    """

    A = np.asarray(A)  # Convert to array if not already
    m, n = A.shape

    if m != n:
        raise ValueError('A must be a square matrix')

    diag_sum = np.diag(A).sum()

    return (m - diag_sum) / (m - 1)


# demonstrasi darii implementasi fungsi softmax

# Ini adalah fungsi yang mengambil vektor dari K
# bilangan real sebagai input, dan menormalkan
# ke dalam distribusi probabilitas yang terdiri
# dari K probabilitas proporsional
# ke eksponensial dari angka input.
# Setelah softmax, elemen dari
# vektor selalu berjumlah 1.

# referensi
# https://en.wikipedia.org/wiki/Softmax_function

import numpy as np

def softmax(vector):
    """
    Menerapkan fungsi softmax
    Parameter:
        vector (np.array,list,tuple): Array berbentuk numpy (1,n)
        terdiri dari nilai-nilai nyata atau daftar serupa,tuple
    Pengembalian:
        softmax_vec (np.array):
        Input array numpy setelah diterapkan
        softmax.
    Vektor softmax menambahkan hingga satu.
    Kita perlu membatasi untuk
    presisi

    >>> vec = np.array([5, 5])
    >>> softmax(vec)
    array([0.5, 0.5])

    >>> softmax([0])
    array([1.])
    """
    exponent_vector = np.exp(vector)

    # Jumlahkan semua eksponensialnya
    sum_exponent = np.sum(exponent_vector)

    # bagi setiap eksponen dengan jumlah
    # semua eksponen
    softmax_vector = exponent_vector / sum_exponent

    return softmax_vector

if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=True)


import numpy as np
from scipy import linalg as sla
a=np.array([[16,-1,1,2],
            [2,12,1,-1],
            [1,3,-24,2],
            [4,-2,1,20]],dtype='f')
#initial value s.t. np.linang.norm(x) is 1
x=np.array([0.5,0.5,0.5,0.5]).T

epsilon=0.0001
maxiteration=1000

for iteration in range(maxiteration):
    x_new =a@x
    eigen=x_new.dot(x)
    if(np.linalg.norm(x_new-eigen*x)<epsilon):
        break
    #normalize
    x_new/=np.linalg.norm(x_new)
    #update x
    x=x_new
else:
    print("fail to calc eigen value")
    exit(1)

print("num of iteration is %d"%iteration)
print("one of the eigen value of matrix a is %f"%eigen)
print("its eigen vector is \n{}".format(x))
print("compare SciPy result...")
print(sla.eig(a))


#! /usr/bin/env python
#
#  Various series for circumference derived by the kerala school
#
import numpy as np
from fractions import Fraction

# The vyAse vAridhinihate series
def vyase(v, n=10, samskara=3, calc_type="fraction"):
    '''
    Calculates the paridhi (circumference) from the vyAsa (diameter)

    Uses the famous Madhava series

    व्यासे वारिधिनिहते रूपहृते व्याससागराभिहते ।
    त्रिशरादिविषमसंख्याभक्तमृणं स्वं पृथक्क्रमात् कुर्यात् ॥

    Inputs:
                   v: diameter
                   n: number of terms
      samskara: None (default) or
                      k for kth order end-correction
                      where k in (0, 1, 2, 3)
           calc_type: "fraction" (default): uses fractions
                      "float": uses numpy float128
    Returns:
                   p: circumference
    '''
    assert ((calc_type == "fraction") or (calc_type == "float")), \
        f"Unknown calc_type {calc_type}: pass 'float' or 'fraction'"
    if calc_type == "fraction":
        t = Fraction(4*v, 1) # First term
    else:
        t = np.float128(4*v)  # First term
    # Initialize the series to first term
    s = t
    sg = -1   # Sign of next term
    # C = 4v - 4v/3 + 4v/5 - 4v/7 ...
    for i in range(2, n+1):   # Ends after the nth term
        s += sg*t/(2*i - 1)    # 4v/(2n-1) is the last term
        sg *= -1
    # Add end term
    # Last divisor
    if samskara == 0:
        # Zeroth order correction
        # a_p = 4v/2p, where 4v/p is the last term
        p = 2*n - 1
        s += sg*t/(2*p)
    elif samskara == 1:
        # First order
        # a_p = 4v/(2p+2) = 4v/4n
        s += sg*t/(4*n)
    elif samskara == 2:
        # Second Order
        # a_p = 4v/((2p+2) + 4/(2p+2))
        #     = 4v.n/(4n^2 + 1)
        s += sg*t*n/(4*n**2 + 1)
    elif samskara == 3:
        # Third Order
        # a_p = 4v/((2p+2) + 4/((2p+2) + 16/(2p+2)))
        #     = 4v.(n^2 + 1)/(4n^3+5n)
        s += sg*t*(n**2 + 1)/(4*n**3 + 5*n)
    return s

# The vyAsavargAdravihatAt series
def vyasavargad(v, n=10, calc_type="fraction"):
    '''
    Calculates the paridhi (circumference) from the vyAsa (diameter)

    Uses the vyAsavargAdravihatAt series
    vyāsavargād ravihatāt padam syāt prathamam phalam |
    tadāditastrisamkhyāptam phalam syāduttarottaram ||
    rupādyayugmasamkhyābhirhr.tes.ves.u yathākramam |
    vis.amānāmyutestyaktvā samam hi paridhirbhavet ||

    Inputs:
                   v: diameter
                   n: number of terms
           calc_type: "fraction" (default): uses fractions
                      "float": uses numpy float128
    Returns:
                   p: circumference
    '''
    assert ((calc_type == "fraction") or (calc_type == "float")), \
        f"Unknown calc_type {calc_type}: pass 'float' or 'fraction'"
    if calc_type == "fraction":
        # Fixme implement Aryabhata square root algo
        t = Fraction(np.sqrt(12 * v**2)) # First term
    else:
        t = np.sqrt(np.float128(12 * v**2))  # First term
    p = t
    s = -1   # Sign of next term
    # C = sqrt(12d^2) (1 - 1/3.3 + 1/(3^2.5)+1/(3^3.7) ...)
    for i in range(1,n):
        if calc_type == "fraction":
            p += Fraction(s*t, (3**i*(2*i+1)))
        else:
            p += s*t/np.float64(3**i*(2*i+1))

        s = s*-1
    return p

def samapanchahatayoh(v, n=10, calc_type="fraction"):
    '''
    Calculates the paridhi (circumference) from the vyAsa (diameter) using
the samapanchahatayoH series

    समपञ्चाहतयो या रुपाद्ययुजां चतुर्घ्नमूलयुताः
    ताभिः षोडशगुणितात् पृथगाहृतेषु विषमयुतेः
    समफलयुतिमपहाय स्यादिष्टव्याससंभवः परिधिः

    Inputs:
                   v: diameter
                   n: number of terms
           calc_type: "fraction" (default): uses fractions
                      "float": uses numpy float128
    Returns:
                   p: circumference
    '''
    assert ((calc_type == "fraction") or (calc_type == "float")), \
        f"Unknown calc_type {calc_type}: pass 'float' or 'fraction'"
    sg = 1   # Sign of next term
    t = 16*v
    s = 0
    # C = 16d(1/1^5+4.1 - 1/3^5+4*3 + 1/5^5+4*5 ...)
    for i in range(0,n):
        o = 2*i+1
        if calc_type == "fraction":
            s += Fraction(sg*t, (o**5+4*o))
        else:
            s += sg*t/np.float64(o**5+4*o)

        sg = sg*-1
    return s

def vyasad(v, n=10, calc_type="fraction"):
    '''
    Calculates the paridhi (circumference) from the vyAsa (diameter) using
the vyAsAd vAridhinihatAt series

    व्यासाद् वारिधिनिहतात् पृथगाप्तं त्र्याद्ययुग्विमूलघनैः
    त्रिघ्नव्यासे स्वमृणं क्रमशः कृत्वा परिधिरानेयः ||
    C = 3D + 4D(1 /(3^3-3) - 1/(5^3-5) + 1/(7^3-7) ….)

    Inputs:
                   v: diameter
                   n: number of terms
           calc_type: "fraction" (default): uses fractions
                      "float": uses numpy float128
    Returns:
                   p: circumference
    '''
    assert ((calc_type == "fraction") or (calc_type == "float")), \
        f"Unknown calc_type {calc_type}: pass 'float' or 'fraction'"
    sg = 1   # Sign of next term
    t = 4*v
    s = 3*v
    # C = 3D + 4D(1 /(3^3-3) - 1/(5^3-5) + 1/(7^3-7) ….)
    for i in range(1,n):
        o = 2*i+1
        if calc_type == "fraction":
            s += Fraction(sg*t, (o**3-o))
        else:
            s += sg*t/np.float64(o**3-o)
        sg = sg*-1
    return s

__all__ = ["vyase", "vyasavargad", "samapanchahatayoh", "vyasad"]


'''

Find the k post offices located closest to you, given your location and a list of locations of all post offices available.
Locations are given in 2D coordinates in [X, Y], where X and Y are integers.
Euclidean distance is applied to find the distance between you and a post office.
Assume your location is [m, n] and the location of a post office is [p, q], the Euclidean distance between the office and
you is SquareRoot((m - p) * (m - p) + (n - q) * (n - q)).
K is a positive integer much smaller than the given number of post offices. from aonecode.com

e.g.
Input
you: [0, 0]
post_offices: [[-16, 5], [-1, 2], [4, 3], [10, -2], [0, 3], [-5, -9]]
k = 3

Output from aonecode.com
[[-1, 2], [0, 3], [4, 3]]
'''

# [m, n] - subject's location

import numpy as np

origin = [0,0]

dist_dict = {}

def get_k_nearest_post_offices(post_ofcs, k):
    for ind, val in enumerate(post_ofcs):
        eucl_dist = np.sqrt((origin[0] - val[0]) * (origin[0] - val[0]) + (origin[1] - val[1]) * (origin[1] - val[1]))
        dist_dict[eucl_dist] = val

    #print(dist_dict)
    return sorted(dist_dict)[:k]

k_nearest_po_keys = get_k_nearest_post_offices([[-16, 5], [-1, 2], [4, 3], [10, -2], [0, 3], [-5, -9]], 3)

for key in k_nearest_po_keys:
    print(dist_dict[key])

# to preserve duplicate keys if needed
# from collections import defaultdict
#
# d = defaultdict(list)
#
#
# def get_k_nearest_post_offices(post_ofcs, k):
#     for ind, val in enumerate(post_ofcs):
#         eucl_dist = np.sqrt((origin[0] - val[0]) * (origin[0] - val[0]) + (origin[1] - val[1]) * (origin[1] - val[1]))
#         d[eucl_dist].append(val)
#     return sorted(dist_dict)[:k]
#
# k_nearest_po_keys = get_k_nearest_post_offices([[-16, 5], [-1, 2], [4, 3], [10, -2], [0, 3], [-5, -9]], 3)
#
# for key in k_nearest_po_keys:
#     print(dist_dict[key])


import numpy as np
import math

def pendulum_derivatives(theta, omega, g=9.8, l=1):
    """
    \dot{\theta} = \omega
    \dot{\omega} = -\frac{g \sin\theta}{l}
    :param theta: angel of the pendulum
    :param omega: angular velocity of the pendulum
    :param g: gravitational acceleration
    :param l: length of the pendulum
    :return: derivative of angel, derivative of angular velocity
    """
    d_theta = omega
    d_omega = - np.sin(theta) * g / l
    return d_theta, d_omega

def simulate(sample_num, time_scale=0.1, simulate_length=100,
             theta_std=np.pi, omega_std=1,
             seed=623, noise_std=0.5):
    """
    Simulate single pendulum.
    :param sample_num:
    :param time_scale:
    :param simulate_length:
    :param theta_std:
    :param omega_std:
    :param seed:
    :param noise_std:
    :return: simulated data
    """
    np.random.seed(seed)
    batch_size = math.ceil(sample_num / simulate_length)

    data = np.zeros([simulate_length * batch_size, 4])
    theta = np.random.randn(batch_size, 1) * theta_std
    omega = np.random.randn(batch_size, 1) * omega_std

    for i in range(simulate_length):
        d_theta, d_omega = pendulum_derivatives(theta, omega)
        data[i * batch_size: (i + 1) * batch_size] = np.hstack((theta, omega, d_theta, d_omega))
        theta += d_theta * time_scale
        omega += d_omega * time_scale

    return data[:sample_num]

if __name__ == '__main__':
    data = simulate(3010)
    print(data[-1])
    print(data.shape)


import numpy as np
import matplotlib.pyplot as plt
import time
plt.rcParams['figure.figsize'] = [12, 12]
plt.rcParams.update({'font.size': 18})

n = 20
k = np.arange(1,n+1)
x = np.cos((k-1)*np.pi/(n-1))
#Input function
f=np.sin(np.pi*x)
#Analytically obtain the Derivative
df=np.pi*np.cos(np.pi*x)
df2=-np.pi**2*np.sin(np.pi*x)
# -- Differentiation via chebfft based on
# -- chebdifft.m matlab version
def chebdifft(f,M):
	N=len(f)

	a = np.flipud(f[1:N-1])
	a = np.concatenate((f,a))
	a0 = np.fft.fft(a)

	ones = np.ones(N-2)
	a = np.concatenate(([0.5],ones,[0.5] ))
	a0 = a0[0:N]*a/(N-1)  #a0 contains Chebyshev coefficients of f
	#print(a0)

	# Recursion formula for computing coefficients of ell'th derivative
	a = np.zeros((N,M+1),dtype="complex_")
	a[:,0] = a0
	for ell in np.arange(1,M+1):
		a[N-ell-1,ell]=2*(N-ell)*a[N-ell,ell-1];
		for k in np.arange(N-ell-2,0,-1):

			a[k,ell]=a[k+2,ell]+2*(k+1)*a[k+1,ell-1]
		a[0,ell]=a[1,ell-1]+a[2,ell]/2

	# Transform back to nphysical space
	b1 = [2*a[0,M]]
	b2 = a[1:N-1,M]
	b3 = [2*a[N-1,M]]
	b4 = np.flipud(b2)
	back = np.concatenate((b1,b2,b3,b4))
	Dmf = 0.5*np.fft.fft(back)
	# Real data in, real derivative out
	Dmf = Dmf[0:N]
	return np.real(Dmf)

t0 = time.time()
dfFFT = chebdifft(f,1)
dfFFT2 = chebdifft(f,2)
t1 = time.time()

print("Total time: {:}".format(t1-t0))
##Plot results
plt.plot(x, df.real, color='k', LineWidth=2, label='True Derivative')
# plt.plot(x, dfFD.real, '--', color='b', LineWidth=1.5, label='Finite Difference')
plt.plot(x, dfFFT.real, '--', color='c', LineWidth=1.5, label='Spectral Derivative')
plt.xlabel('X values')
plt.ylabel('Y values')
plt.legend()
plt.show()

##Plot results
plt.plot(x, df2.real, color='k', LineWidth=2, label='True Derivative')
# plt.plot(x, dfFD.real, '--', color='b', LineWidth=1.5, label='Finite Difference')
plt.plot(x, dfFFT2.real, '--', color='c', LineWidth=1.5, label='2nd Spectral Derivative')
plt.xlabel('X values')
plt.ylabel('Y values')
plt.legend()
plt.ylim(-np.pi**2,+np.pi**2)
plt.show()


import numpy as np

class LinearRegression():
    def __init__(self):
        self.learning_rate = 0.01
        self.total_iterations = 1000
    def yhat(self, X, w):
        '''
        predicted y value
        '''
        # making sure the dimensions are correct
        # predictions: yhat = w1*x1 + w2*x2  + ... - want a scalar value for each input
        # m - # training examples, n - # features, w - nx1, y - mx1, X - n x m, add intercept b so that X: n x m + 1
        # m x n * n x 1 = m x 1, want output to be 1 x m
        # 1 x n * n x m - w^T*X
        return np.dot(w.T, X)

    def loss(self, yhat, y):
        '''
        compute the loss - MSE
        '''
        L = 1/self.m * np.sum(np.power(yhat - y, 2)) # divide by number of examples, sum elementwise square of differences of the yhat - predicted and y - ground truth
        return L

    def gradient_descent(self, w, X, y, yhat):
        '''
        update the weigths based on the loss
        '''
        # again make sure the dimensions are correct
        # L = (yhat - y)  # loss : 1 x m, want the output to be n x 1 - multiply  n x m by (1 x m)^T = n x 1

        dLdw = 2/self.m * np.dot(X, (yhat - y).T) # gradient of L (loss) w.r.t. w (weights) , y = x * w + b, L = 1/m sum(yhat - y)^2 = 1/m sum(yhat - x*w+b)^2, dLdw = 2* 1/m *  x*w

        w = w - self.learning_rate * dLdw # update step, dLdw needs to be same dimension as w - nx1

        return w

    def main(self, X, y):
        '''
        will call the above functions for a number of iterations
        '''
        # add intercept (bias)
        x1 = np.ones((1, X.shape[1]))
        X = np.append(X, x1, axis=0) # add to features

        self.m = X.shape[1]
        self.n = X.shape[0]

        w = np.zeros((self.n, 1))

        # iterate
        for it in range(self.total_iterations + 1):
            yhat = self.yhat(X, w)
            loss = self.loss(yhat, y)

            # PRINT
            if it % 200 == 0:
                print(f'Cost at iteration: {it} is {loss}')

            w = self.gradient_descent(w, X, y, yhat)

        return w

if __name__ == '__main__':
    X = np.random.rand(1, 500)
    y = 3 * X + np.random.rand(1, 500) * 0.1 # add some small random Gaussian noise
    regression = LinearRegression()
    w = regression.main(X, y)


import numpy as np

def compute_95CI_ols(x,y):
    '''returns the max likliehood estimators and 95% confidence intervals
    that result from ordinary least squares regression applied to the
    1-dimensional numpy arrays, x and y.'''
    x=x.flatten();y=y.flatten()
    n=x.shape[0]
    assert(n==y.shape[0])
    assert(n>2)
    if not n>=8:
        print('Warning: CI not valid for less than 8 data points!')
    xbar=np.mean(x);ybar=np.mean(y)
    #compute sums of squares
    SSxx=np.sum((x-xbar)**2)
    SSxy=np.dot((x-xbar),(y-ybar))
    SSyy=np.sum((y-ybar)**2)
    #best linear unbiased estimator of slope
    m=SSxy/SSxx
    #best linear unbiased estimator of intercept
    b=ybar-m*xbar
    #values of fit
    yhat=b+m*x
    #standard error of fit, s_{y,x}^2=ssE
    SSE=np.sum((y-yhat)**2)
    ssE=SSE/(n-2)
    #standard deviation of slope
    sm = np.sqrt(ssE/SSxx)
    #standard deviation of intercept
    sb = np.sqrt(ssE*(1/n+xbar**2/SSxx))
    #compute 95% CI for parameters
    Delta_m = 1.96*sm
    Delta_b = 1.96*sb
    #compute Rsquared
    Rsquared=(SSyy-SSE)/SSyy
    #format results as a human readable dict
    dict_output={
        'm':m,
        'Delta_m':Delta_m,
        'b':b,
        'Delta_b':Delta_b,
        'Rsquared':Rsquared
    }
    return dict_output


import torch.nn.functional as F
import numpy as np
import torch
from util import randomize_in_place

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

def graph1(a_np, b_np, c_np):
    """
    Computes the graph
        - x = a * c
        - y = a + b
        - f = x / y

    Computes also df/da using
        - Pytorchs's automatic differentiation (auto_grad)
        - user's implementation of the gradient (user_grad)

    :param a_np: input variable a
    :type a_np: np.ndarray(shape=(1,), dtype=float64)
    :param b_np: input variable b
    :type b_np: np.ndarray(shape=(1,), dtype=float64)
    :param c_np: input variable c
    :type c_np: np.ndarray(shape=(1,), dtype=float64)
    :return: f, auto_grad, user_grad
    :rtype: torch.DoubleTensor(shape=[1]),
            torch.DoubleTensor(shape=[1]),
            numpy.float64
    """
    # YOUR CODE HERE:
    a = torch.from_numpy(a_np)
    b = torch.from_numpy(b_np)
    c = torch.from_numpy(c_np)
    a.requires_grad = True
    x = a * c
    y = a + b
    f = x / y
    f.backward()
    auto_grad = a.grad
    """
    df/da = df/dx * dx/da + df/dy * dy/da
    df/dx = 1/y, dx/da = c,  df/dy = - x/y², dy/da = 1
    """
    user_grad = ((c / y) - (x / (y * y))).detach().numpy()
    # END YOUR CODE
    return f, auto_grad, user_grad

def graph2(W_np, x_np, b_np):
    """
    Computes the graph
        - u = Wx + b
        - g = sigmoid(u)
        - f = sum(g)

    Computes also df/dW using
        - pytorchs's automatic differentiation (auto_grad)
        - user's own manual differentiation (user_grad)

    F.sigmoid may be useful here

    :param W_np: input variable W
    :type W_np: np.ndarray(shape=(d,d), dtype=float64)
    :param x_np: input variable x
    :type x_np: np.ndarray(shape=(d,1), dtype=float64)
    :param b_np: input variable b
    :type b_np: np.ndarray(shape=(d,1), dtype=float64)
    :return: f, auto_grad, user_grad
    :rtype: torch.DoubleTensor(shape=[1]),
            torch.DoubleTensor(shape=[d, d]),
            np.ndarray(shape=(d,d), dtype=float64)
    """
    # YOUR CODE HERE:
    W = torch.from_numpy(W_np)
    W.requires_grad = True
    x = torch.from_numpy(x_np)
    b = torch.from_numpy(b_np)
    u = torch.matmul(W, x) + b
    g = F.sigmoid(u)
    f = torch.sum(g)
    f.backward()
    auto_grad = W.grad
    """
    df_du = sigmoid(u) * (1 - sigmoid(u))
    du_dW = x^t
    df_dW = df_du * du_dW
    """
    xt = torch.transpose(x, 0, 1)
    sigU = F.sigmoid(u)
    user_grad = (torch.matmul((sigU * (1 - sigU)), xt)).detach().numpy()
    # END YOUR CODE
    return f, auto_grad, user_grad

def SGD_with_momentum(X,
                      y,
                      inital_w,
                      iterations,
                      batch_size,
                      learning_rate,
                      momentum):
    """
    Performs batch gradient descent optimization using momentum.

    :param X: design matrix
    :type X: np.ndarray(shape=(N, d))
    :param y: regression targets
    :type y: np.ndarray(shape=(N, 1))
    :param inital_w: initial weights
    :type inital_w: np.array(shape=(d, 1))
    :param iterations: number of iterations
    :type iterations: int
    :param batch_size: size of the minibatch
    :type batch_size: int
    :param learning_rate: learning rate
    :type learning_rate: float
    :param momentum: accelerate parameter
    :type momentum: float
    :return: weights, weights history, cost history
    :rtype: np.array(shape=(d, 1)), list, list
    """
    # YOUR CODE HERE:
    z = torch.autograd.Variable(torch.zeros(inital_w.shape).double() , requires_grad = True)
    W = torch.autograd.Variable(torch.from_numpy(inital_w), requires_grad = True)
    x = torch.autograd.Variable(torch.from_numpy(X), requires_grad = False)
    Y = torch.autograd.Variable(torch.from_numpy(y), requires_grad = False)
    cost_history = []
    weights_history = []
    for i in range(iterations) :
        temp = torch.randperm(x.shape[0])
        x = x[temp]
        Y = Y[temp]
        xW = torch.matmul(x[:batch_size], W)
        xWY = xW - Y[:batch_size]
        J = torch.matmul(torch.transpose(xWY, 0, 1), xWY)  / batch_size
        J.backward()
        z.data = momentum * z + W.grad
        W.grad.zero_()
        W.data -= learning_rate * z
        cost_history.append(J)
        weights_history.append(W.data)
    w_np = W.detach().numpy()
    # END YOUR CODE

    return w_np, weights_history, cost_history


import os
import dill
import numpy as np
from math import pi, exp, atan, sqrt, acos

def angle(x, y):
	"""calculates the angle of (x,y) with respect to (0,0)

	:param x: x choordinate
	:param y: y choordinate
	:returns: the angle"""
	at = atan(y/x)
	if(x < 0): return at+pi
	elif(y < 0): return at+2*pi
	return at

def angle_between_vectors(v, w):
	"""calculates the angle between two vectors (any dimensional)

	:param v: vector 1
	:param w: vector 2
	:returns: the angle"""
	scalar = sum(v[i]*w[i] for i in range(len(v)))
	if(scalar/(L2_norm(v)*L2_norm(w)) > 1): return 0
	if(scalar/(L2_norm(v)*L2_norm(w)) < -1): return pi
	return acos(scalar/(L2_norm(v)*L2_norm(w)))

def L1_norm(seq):
	"""calculates the L1 norm of a sequence or vector

	:param seq: the sequence or vector
	:returns: the L1 norm"""
	norm = 0
	for i in range(len(seq)):
		norm += abs(seq[i])
	return norm

def L2_norm(seq):
	"""calculates the L2 norm of a sequence or vector

	:param seq: the sequence or vector
	:returns: the L2 norm"""
	norm = 0
	for i in range(len(seq)):
		norm += seq[i]**2
	return sqrt(norm)

def Linf_norm(seq):
	"""calculates the Linf norm of a sequence or vector

	:param seq: the sequence or vector
	:returns: the Linf norm"""
	largest = 0
	for i in range(len(seq)):
		if(abs(seq[i]) > largest): largest = abs(seq[i])
	return largest

def linear_interpolation(listx, listy, argument):
	"""calculates the linear interpolation of [listx,listy] at argument

	:param listx: x choordinates (should be ordered in ascending order)
	:param listy: y choordinates
	:param argument: where to evaluate the linear interpolation
	:returns: value of the linear interpolation at argument"""
	if(argument in listx):
		return listy[listx.index(argument)]
	if(argument < listx[0]):
		return listy[0] + (listy[0]-listy[1])/(listx[1]-listx[0])*(listx[0]-argument)
	if(argument > listx[-1]):
		return listy[-1] + (listy[-1]-listy[-2])/(listx[-1]-listx[-2])*(argument-listx[-1])
	index = 0
	while((listx[index] < argument and listx[index+1] > argument) != 1): index += 1
	return listy[index] + (listy[index+1]-listy[index])/(listx[index+1]-listx[index])*(argument-listx[index])

def gaussian(x, std=1):
	"""returns a Gaussian distribution

	:param x: variable
	:param std: standard deviation (default 1)
	:returns: Gaussian PDF"""
	return 1/(std*sqrt(2*pi))*exp(-x**2/(2*std**2))

def gaussian_der(x, std=1):
	"""returns a Gaussian derivative distrbution (x*PDF_normal)

	:param x: variable
	:param std: the standard deviation
	:returns: positive half of the Gaussian derivative PDF"""
	if(x < 0): return 0
	return (2-pi/2)/std**2*x*exp(-(2-pi/2)*x**2/(2*std**2))

def kernel_convolve(seq, kernel, kernel_width):
	"""convolves a sequence with a kernel

	:param seq: sequence to be convolved
	:param kernel: the kernel, a probability distribuion fucntion (does not have to be normalized)
	:param kernel_width: the width of the kernel (std for Gaussian)
	:returns: convolved sequence"""
	ker = np.array([kernel(i/kernel_width) for i in range(-round(3*kernel_width), round(3*kernel_width))])
	ker = ker/sum(ker)
	return np.convolve(seq, ker ,mode='same')

def save_object_dill(obj, filename, save_path="objects_save"):
	"""saves an object with dill (also makes the 'objects_save/' directory if it wasn't there)

	:param obj: object to save
	:param filename: filename without any extension
	:param save_path: path where to save object, staring from the directory of the simulation (default "objects_save")"""
	# directory
	if not os.path.exists(save_path):
		os.makedirs(save_path)
	# pickling
	f = open(save_path+"/"+filename+'.pickle', 'wb')
	dill.dump(obj, f)
	f.close()

def load_object_dill(filename, load_path="objects_save"):
	"""loads an object with dill (from 'objects_save/' directory)

	:param filename: filename without any extension
	:param load_path: path where to load object from, staring from the directory of the simulation (default "objects_save")
	:returns: saved object"""
	# unpickling
	f = open(load_path+"/"+filename+'.pickle', 'rb')
	obj = dill.load(f)
	f.close()
	return obj


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

def f(x):
    """
    Define a function (you can change to whatever function you want
    """
    return (x - np.pi)**2 + 4 * np.sin(x)

def visualize(function, minimum, iter_points, limits, npoints=100):
    """
    Quick plot of the function
    """
    x, y = function[0], function[1]
    lam_x = sym.lambdify(x, y)
    x_vals = np.linspace(limits[0], limits[1], num=npoints)
    y_vals = lam_x(x_vals)

    # Some plot configurations
    plt.rc('font', family='serif')
    plt.rc('font', size=11)
    plt.rc('axes', labelsize=11)

    fig, ax = plt.subplots()
    ax.plot(x_vals, y_vals, 'r-')
    ax.plot(iter_points[0], iter_points[1], 'og')
    ax.plot(minimum[0], minimum[1], 'sr')
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    plt.show()

def bisection(function, limits, tol=1e-3, max_iter=100):
    """
    :param function: symbolic expression
    :param limits: limits of the function
    :param tol: tolerance to converge
    :return:
    """
    xm_vector = []
    fxm_vector = []
    b = [limits[0], limits[1]]
    if b[0] > b[1]:
        raise Exception('Bracket values must be in ascending order')

    yprime = function.diff(x)
    dfb0 = float(yprime.subs(x, b[0]))
    dfb1 = float(yprime.subs(x, b[1]))

    if not ((np.sign(dfb0) < 0) and (np.sign(dfb1) > 0)):
        raise Exception('Minimum may not be contained in bracket')

    b_size = b[1] - b[0]
    k = 0

    while (b_size > tol) and (k < max_iter):
        k += 1
        xm = (b[1] + b[0])/2
        fxm = float(y.subs(x, xm))
        dfxm = float(yprime.subs(x, xm))

        if dfxm == 0:
            break
        elif dfxm > 0:
            b[1] = xm
            xm_vector.append(xm)
            fxm_vector.append(fxm)
        elif dfxm < 0:
            b[0] = xm
            xm_vector.append(xm)
            fxm_vector.append(fxm)

        b_size = b[1] - b[0]
        minimum = [xm, fxm]
        iter_points = [xm_vector, fxm_vector]

    return minimum, iter_points

#%%
# You are supposed to run the previous box before
x = sym.symbols('x')
y = (x - sym.pi)**2 + 4 * sym.sin(x)
minimum, iter_points = bisection(y, [-10, 10])

function = [x, y]
visualize(function, minimum, iter_points, [-10, 10])


#Ejercicio 0
#Lean el capitulo 5 del Landau (ver el programa del curso).

import numpy as np
import matplotlib.pylab as plt

#Ejercicio 1
# Usando los generadores de numeros aleatorios de numpy (https://docs.scipy.org/doc/numpy-1.15.1/reference/routines.random.html):
# a) Genere 1000 numeros aleatorios que sigan una distribucion uniforme y esten entre -10 y 10. Haga un histograma y guardelo sin mostrarlo en un archivo llamado uniforme.pdf

uniforme=(np.random.random(1000)*20)-10

plt.figure()
plt.hist(uniforme,bins=50)
plt.xlabel("x")
plt.ylabel("y")
plt.title("Distribución Uniforme")
plt.grid()
plt.savefig("uniforme.png")
plt.close()

# a) Genere 1000 numeros aleatorios que sigan una distribucion gausiana centrada en 17 y de sigma 5. Haga un histograma y guardelo sin mostrarlo en un archivo llamado gausiana.pdf

gaussiana=np.random.normal(17,5.0,1000)

plt.figure()
plt.hist(gaussiana,bins=50)
plt.xlabel("x")
plt.ylabel("y")
plt.title("Distribución Gausiana")
plt.grid()
plt.savefig("gausiana.png")
plt.close()

# Ejercicio 2
# Escriba un programa en Python que:
# Genere puntos aleatorios distribuidos uniformemente dentro de un cuadrado de lado 30.5. Grafique sus puntos y guarde la grafica sin mostrarla en un archivo llamado cuadrado.pdf.

xcuad=np.random.random(3000)*30
ycuad=np.random.random(3000)*5

plt.figure()
plt.scatter(xcuad,ycuad)
plt.xlabel("x")
plt.ylabel("y")
plt.title("Cuadrado 30x5")
plt.grid()
plt.savefig("cuadrado.png")
plt.close()

# Genere puntos aleatorios distribuidos uniformemente dentro de circulo de radio 23. Grafique sus puntos y guarde la grafica sin mostrarla en un archivo llamado circulo.pdf.

xr=(np.random.random(3000)*46)-23
yr=(np.random.random(3000)*46)-23
xcirc=[]
ycirc=[]

for i in range(len(xr)):
    s=np.sqrt((xr[i]**2)+(yr[i]**2))
    if(s<=23):
        xcirc.append(xr[i])
        ycirc.append(yr[i])

plt.figure()
plt.scatter(xcirc,ycirc)
plt.xlabel("x")
plt.ylabel("y")
plt.title("Circulo de rad 23")
plt.grid()
plt.savefig("circulo.png")
plt.close()

# Ejercicio 3
# Lean sobre caminatas aleatorias.

# Ejercicio 4
# Tome los puntos distribuidos aleatoriamente dentro del cuadrado y haga que cada punto siga una caminata aleatoria de 100 pasos.
# La magnitud de los pasos de esta caminata debe seguir una distribucion gaussiana centrada en el punto y de sigma igual a 0.25
# Implemente condiciones de frontera periodicas: si un punto se "sale" de cuadrado por un lado, "entra" por el otro

def RW(xini,yini,N=100,sigma=0.25):
    x=np.zeros(N)
    y=np.zeros(N)
    x[0]=xini
    y[0]=yini

    i=0
    while(i<100):
        paso=np.random.normal(scale=sigma)
        angulo=np.random.random()*2*np.pi

        xpaso=paso*np.cos(angulo)
        ypaso=paso*np.sin(angulo)
        xpaso+=x[i]
        ypaso+=y[i]

        if(xpaso<30 and xpaso>0):
            if(ypaso<5 and ypaso>0):
                x[i]=xpaso
                y[i]=ypaso

        i+=1

    return x,y

# Grafique la distribucion final de puntos y guarde dicha grafica sin mostrarla en un archivo llamado DistCaminata.pdf
# Grafique la caminata de UNO de sus puntos y guarde dicha grafica sin mostrarla en un archivo llamado puntoCaminata.pdf

xipunto=xcuad[np.random.randint(len(xcuad))]
yipunto=ycuad[np.random.randint(len(xcuad))]

xpunto,ypunto=RW(xipunto,yipunto)
print(xpunto)
print(ypunto)

plt.figure()
plt.plot(xpunto,ypunto)
plt.xlabel("x")
plt.ylabel("y")
plt.title("Caminata de uno")
plt.grid()
plt.savefig("puntoCaminata.png")
plt.close()

# Repita el proceso para sigma = 0.00025 y sigma= 2.5. Grafique la caminata de UNO de sus puntos para los distintos sigmas y guardela sin mostrarla en sigmaCaminata.pdf

# Repita el proceso para condiciones abiertas: si un punto se "sale" del cuadrado deja de ser considerado en la simulacion.

# Si le queda tiempo puede:

##################################################################################################################################################################
############################################################ Ejercicio  ##########################################################################
##################################################################################################################################################################

#difusion: una gota de crema en un Cafe.
#
#Condiciones iniciales:
#Cafe: 10000 particulas distribuidas uniformemente dentro de un circulo de radio igual a raiz de 230
#Crema: 100 particulas distribuidas uniformemente dentro de un circulo de radio igual a raiz de 2
#
#Nota: si su codigo se esta demorando mucho en correr, puede usar 1000 particulas de cafe en vez de 10000.
#
# 1) Haga una grafica de las condiciones iniciales donde los dos tipos de particulas tengan distintos colores. Guarde dicha grafica sin mostrarla en CafeLecheIni.pdf
#
#2) Todas las particulas deben hacer una caminata aleatoria de 1000 pasos. Los pasos en las coordenadas x y deben seguir una distribucion gausiana de sigma 2.5. Si va a usar coordenadas polares elija un sigma apropiado.
#
#3) Condiciones de frontera: implemente unas condiciones tales que si la particulas "sale" del circulo, usted vuelva a dar el paso. Si no puede implementar solo las condiciones antes descritas, debe al menos escribir comentarios explicando que hace cada linea de codigo de las condiciones propuestas (comentado abajo)
#
# 4) Haga una grafica de las posiciones finales de las particulas despues de la caminata donde los dos tipos de particulas tengan distintos colores. Guarde dicha grafica sin mostrarla en CafeLecheFin.pdf
#

import numpy as np
import matplotlib.pylab as plt

#Una posible implementacion de condiciones de frontera. Trate de hacer la suya propia sin usar esta.
#Si usa esta (obtiene menos puntos) debe comentar cada una de las lineas explicando en palabras que hace el codigo. Debe tambien naturalmente usar los nombres de variables que uso en el resto de su codigo propio.
#indexcafe=np.where((xcafenuevo*xcafenuevo+ycafenuevo*ycafenuevo)>230)
#indexcrema=np.where((xcremanuevo*xcremanuevo+ycremanuevo*ycremanuevo)>230)
#while(len(indexcafe[0])>1):
#	xcafenuevo[indexcafe]=xcafe[indexcafe] + np.random.normal(0,sigma)
#	ycafenuevo[indexcafe]=ycafe[indexcafe] + np.random.normal(0,sigma)
#	indexcafe=np.where((xcafenuevo*xcafenuevo+ycafenuevo*ycafenuevo)>=230)
#while(len(indexcrema[0])>1):
#	xcremanuevo[indexcrema]=xcrema[indexcrema] + np.random.normal(0,sigma)
#	ycremanuevo[indexcrema]=ycrema[indexcrema] + np.random.normal(0,sigma)
#	indexcrema=np.where((xcremanuevo*xcremanuevo+ycremanuevo*ycremanuevo)>=230)


'''
Recursion - Fibonacci Sequence

'''
import functools
import math
from numpy import matrix

def fibonacci_oneliner(n):
    '''
    Generate Fibonacci Sequence from F_0 to F_n

    returns list[F_0, F_1, ..., F_n]
    Adapted from Mayer2020 using one python line of code.
    '''
    fibs = functools.reduce(lambda x, _: x + [x[-2] + x[-1]],
                            [0] * (n - 1), [0, 1])
    return fibs

@functools.lru_cache(None)  # Comment this line to have a non-memoized version
def fibonacci_recursive(n):
    '''Recursive F_n
    '''
    if n < 0:
        raise ValueError('n must be non negative integer.')
    elif n > 1:
        return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2)
    else:
        return n

# Static constants for Binet's and rounding implementations
__denom = 5 ** 0.5
__golden = (1 + __denom) / 2  # Golden Ratio
__psi = (1 - __denom) / 2

def fibonacci_binet(n):
    r'''
    Closed-form expression: Binet's Formula
    F_n = \frac{}{\sqrt{5}}

    Golden ratio $\phi = \frac{1 + \sqrt{5}}{2}$
    \phi^2 = \phi + 1
    \psi = (1 - \sqrt{5}) / 2
    \psi^2 =  \psi + 1

    Rounding errors above 71 (i.e. n < 72)
    '''
    if n < 0:
        raise ValueError('n must be non negative integer.')
    elif n > 71:
        raise ValueError('This function has rounding errors above n=71.')
    else:
        return int((__golden ** n - __psi ** n) / __denom)

def fibonacci_rounding(n):
    """
    Fast Fibonacci using rounding

    It is limited to n < 71"""
    if n < 0:
        raise ValueError('Fibs for negative values are not defined.')
    elif n > 70:
        raise ValueError('This function has rounding errors above n=70.')
    return round(math.pow(__golden, n) / __denom)

def fibonacci_matrix(n):
    '''
    Fibonacci number F_n using matrix powers

    From bebidek2018 <https://stackoverflow.com/questions/3323001/what-is-the-maximum-recursion-depth-in-python-and-how-to-increase-it>
    '''
    return (matrix('0 1; 1 1', dtype='object') ** n).item(1)

class Solution:
    def fib(self, N: int) -> int:
        if N <= 1:
            return N
        F = [0] * (N + 1)
        F[1] = 1
        for n in range(2, N + 1):
            F[n] = F[n-1] + F[n-2]
        return F[N]

    def fib2(self, N: int) -> int:
        if N <= 1:
            return N
        F = [0, 1, 1]
        for n in range(3, N + 1):
            F[0], F[1], F[2] = F[1], F[2], F[-1] + F[-2]
        return F[-1]

if __name__ == '__main__':
    n = 45  # n > 1 # 70 is the limit for fast_fib
    fibs = fibonacci_oneliner(n)

    # Some known values to verify
    if n >= 70:
        assert fibs[70] == 190392490709135
        assert fibs[71] == 308061521170129

    # lr = lb = []
    if n < 72:
        lb = ([fibonacci_binet(ni) for ni in range(n + 1)])
        print(f'lb\n{lb}')
        assert fibs == lb
    if n < 71:
        lf = ([fibonacci_rounding(ni) for ni in range(n + 1)])
        print(lf)
        assert fibs == lf, "Disc"

    if n < 5000:
        lr = ([fibonacci_recursive(ni) for ni in range(n + 1)])
        assert fibs == lr
        # print(f'lr\n{lr}')
    lm = [fibonacci_matrix(ni) for ni in range(n + 1)]
    assert lm == fibs

    # From Wolfram|Alpha: Fibo[100] = 354224848179261915075
    fibo100 = 354224848179261915075
    print(f'{fibo100=}')
    assert fibonacci_recursive(100) == fibo100


#
#	root-regula-falsi.py
#	Finding root using regula falsi method
#
#	Sparisoma Viridi | https://butiran.github.io/
#
#	Execute: py root-regula-falsi.js
#
#	20210210
#	0527 Typo node --> py, use newton-raphson.py as template.
#   0827 Continue at campus.
#   1001 Continue after archiving lab modules.
#   1028 Try compile again after install Python 3.9.1 64 bit.
#

# Import necessary libraries
import numpy as np

# Define a test function
def test_function(x):
	y3 = 0.01 * x * x * x
	y2 = -0.2252 * x * x
	y1 = 0.4136 * x
	y0 = 1.808
	y = y3 + y2 + y1 + y0
	return y

# Define input
f = test_function
x1 = 3
x2 = 6
eps = 1E-10
n = 0
maxstep = 40

# Define default message and parameter
xroot = "not found"
SHOW_PROGRESS = True

# Do iteration
Nstep = 0
x = []
x.append(x1)
x.append(x2)
froot = np.abs(f(x[n]))

while froot > eps and n < maxstep - 1:
	x.append(x[0] - ((x[n+1] - x[0])/(f(x[n+1]) - f(x[0]))) * f(x[0]))

	froot = np.abs(f(x[n+2]))
	if froot < eps:
		xroot = x[n+2]

	if SHOW_PROGRESS:
		if n == 0:
			fn = f(x[n])
			print("n\tx\tf(x)")
			print(n, x[n], f(x[n]), sep="\t")
			print(n, x[n+1], f(x[n+1]), sep="\t")
		print(n+1, x[n+2], f(x[n+2]), sep="\t")

	n += 1

Nstep = n+2

if SHOW_PROGRESS:
	print()

# Display result
print("f(x)  0.01x^3 - 0.2192x^2 + 0.3056x + 1.568");
print("x1    ", x1, sep="")
print("x2    ", x2, sep="")
print("ε     ", eps, sep="")
print("Nstep ", Nstep, sep="")
print("xroot ", xroot, sep="")


import numpy as np

__all__ = ['tstatistic',
           'nantstatistic',
           'diffmean']

def diffmean(a, b, axis=0):
    """Difference of means statistic.

    Parameters
    ----------
    a,b : ndarray with shapes equal along all dims except axis
        Input data for the calculation.
    axis : int
        Specify the axis along which the statistic will be computed.

    Returns
    -------
    dm : ndarray with one less dimension"""
    return a.mean(axis=axis) - b.mean(axis=axis)

def tstatistic(a, b, axis=0, equal_var=True):
    """Computes a two-sample t-statistic on a and b along the specific axis
    Code is lifted from scipy.stats.ttest_ind except that there is no
    calculation of the associated p-value

    Parameters
    ----------
    a,b : ndarray with shapes equal along all dims except axis
        Input data for the calculation.
    axis : int
        Specify the axis along which the statistic will be computed.
    equal_var : bool
        Specify if the statistic will use a pooled estimate of the variance (True)
        or if it will make no assumption about equal variance (False).

    Returns
    -------
    t : ndarray with one less dimension"""
    v1 = np.var(a, axis, ddof=1)
    v2 = np.var(b, axis, ddof=1)
    n1 = a.shape[axis]
    n2 = b.shape[axis]

    if equal_var:
        df = n1 + n2 - 2
        svar = ((n1 - 1) * v1 + (n2 - 1) * v2) / float(df)
        denom = np.sqrt(svar * (1.0 / n1 + 1.0 / n2))
    else:
        vn1 = v1 / n1
        vn2 = v2 / n2
        df = ((vn1 + vn2)**2) / ((vn1**2) / (n1 - 1) + (vn2**2) / (n2 - 1))

        # If df is undefined, variances are zero (assumes n1 > 0 & n2 > 0).
        # Hence it doesn't matter what df is as long as it's not NaN.
        df = np.where(np.isnan(df), 1, df)
        denom = np.sqrt(vn1 + vn2)

    d = np.mean(a, axis) - np.mean(b, axis)
    t = np.divide(d, denom)
    return t

def nantstatistic(a, b, axis = 0, equal_var = True):
    """Computes a two-sample t-statistic on a and b along the specific axis
    Uses nan* functions which can be slightly slower.
    Code is lifted from scipy.stats.ttest_ind except that there is no
    calculation of the associated p-value

    Parameters
    ----------
    a,b : ndarray with shapes equal along all dims except axis
        Input data for the calculation.
    axis : int
        Specify the axis along which the statistic will be computed.
    equal_var : bool
        Specify if the statistic will use a pooled estimate of the variance (True)
        or if it will make no assumption about equal variance (False).

    Returns
    -------
    t : ndarray with one less dimension
    """

    v1 = np.nanvar(a, axis, ddof=1)
    v2 = np.nanvar(b, axis, ddof=1)
    n1 = a.shape[axis]
    n2 = b.shape[axis]

    if equal_var:
        df = n1 + n2 - 2
        svar = ((n1 - 1) * v1 + (n2 - 1) * v2) / np.float(df)
        denom = np.sqrt(svar * (1.0 / n1 + 1.0 / n2))
    else:
        vn1 = v1 / n1
        vn2 = v2 / n2
        df = ((vn1 + vn2)**2) / ((vn1**2) / (n1 - 1) + (vn2**2) / (n2 - 1))

        # If df is undefined, variances are zero (assumes n1 > 0 & n2 > 0).
        # Hence it doesn't matter what df is as long as it's not NaN.
        df = np.where(np.isnan(df), 1, df)
        denom = np.sqrt(vn1 + vn2)

    d = np.nanmean(a, axis) - np.nanmean(b, axis)
    t = np.divide(d, denom)
    return t

'''TODO: implement in numba so they can be used in a numbized permutation test
"""Attempt to import numba and define numba compiled versions of these functions"""
import os
import sys
try:
    import numba as nb
    print 'mytstats: Successfully imported numba version %s' % (nb.__version__)
    NB_SUCCESS = True
except OSError:
    try:
        """On Windows it is neccessary to be on the same drive as the LLVM DLL
        in order to import numba without generating a "Windows Error 161: The specified path is invalid."""
        curDir = os.getcwd()
        targetDir = os.path.splitdrive(sys.executable)[0]
        os.chdir(targetDir)
        import numba as nb
        print 'mytstats: Successfully imported numba version %s' % (nb.__version__)
        NB_SUCCESS = True
    except OSError:
        NB_SUCCESS = False
        print 'mytstats: Could not load numba\n(may be a path issue try starting python in C:\\)'
    finally:
        os.chdir(curDir)
except ImportError:
    NB_SUCCESS = False
    print 'mytstats: Could not load numba'

"""TODO: (1) Test numba functions (this code is just copied from above)
             if the function can just be decorated then do that,
             but i think it may need to be modified

         (2) Add numba permutation function that utlizes these statistics
             (this is where the speed-up will happen)"""

if NB_SUCCESS and False:
    __all__.extend(['nb_tstatistic', 'nb_nantstatistic'])

    @nb.jit(nb.float64[:](nb.float64[:],nb.float64[:], nb.int32, nb.boolean), nopython = True)
    def nb_tstatistic(a, b, axis, equal_var):
        v1 = np.var(a, axis, ddof=1)
        v2 = np.var(b, axis, ddof=1)
        n1 = a.shape[axis]
        n2 = b.shape[axis]

        if equal_var:
            df = n1 + n2 - 2
            svar = ((n1 - 1) * v1 + (n2 - 1) * v2) / np.float(df)
            denom = np.sqrt(svar * (1.0 / n1 + 1.0 / n2))
        else:
            vn1 = v1 / n1
            vn2 = v2 / n2
            df = ((vn1 + vn2)**2) / ((vn1**2) / (n1 - 1) + (vn2**2) / (n2 - 1))

            # If df is undefined, variances are zero (assumes n1 > 0 & n2 > 0).
            # Hence it doesn't matter what df is as long as it's not NaN.
            df = np.where(np.isnan(df), 1, df)
            denom = np.sqrt(vn1 + vn2)

        d = np.mean(a, axis) - np.mean(b, axis)
        t = np.divide(d, denom)
        return t

    @nb.jit(nb.float64[:](nb.float64[:],nb.float64[:], nb.int, nb.boolean), nopython = True)
    def nb_nantstatistic(a, b, axis, equal_var):
        v1 = np.nanvar(a, axis, ddof=1)
        v2 = np.nanvar(b, axis, ddof=1)
        n1 = a.shape[axis]
        n2 = b.shape[axis]

        if equal_var:
            df = n1 + n2 - 2
            svar = ((n1 - 1) * v1 + (n2 - 1) * v2) / float(df)
            denom = np.sqrt(svar * (1.0 / n1 + 1.0 / n2))
        else:
            vn1 = v1 / n1
            vn2 = v2 / n2
            df = ((vn1 + vn2)**2) / ((vn1**2) / (n1 - 1) + (vn2**2) / (n2 - 1))

            # If df is undefined, variances are zero (assumes n1 > 0 & n2 > 0).
            # Hence it doesn't matter what df is as long as it's not NaN.
            df = np.where(np.isnan(df), 1, df)
            denom = np.sqrt(vn1 + vn2)

        d = np.nanmean(a, axis) - np.nanmean(b, axis)
        t = np.divide(d, denom)
        return t

'''