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

def demo_simple():
    # ref: http://mathworld.wolfram.com/HeartCurve.html
    t = np.linspace(0, 2 * np.pi, 180)
    x = 16 * np.sin(t) ** 3
    y = 13 * np.cos(t) - 5 * np.cos(2 * t) - 2 * np.cos(3 * t) - np.cos(4 * t)
    plt.plot(x, y)
    plt.show()

def demo_ani():
    def update_line(num):
        chart.set_data(data[..., :num])
        return chart,

    # ref: http://mathworld.wolfram.com/HeartCurve.html
    t = np.linspace(0, 2 * np.pi, 180)
    x = 16 * np.sin(t) ** 3
    y = 13 * np.cos(t) - 5 * np.cos(2 * t) - 2 * np.cos(3 * t) - np.cos(4 * t)
    data = np.vstack((x, y))  # 2 x 180
    fig = plt.figure()
    padding = 1.1
    plt.xlim(np.min(x) * padding, np.max(x) * padding)
    plt.ylim(np.min(y) * padding, np.max(y) * padding)
    chart, = plt.plot([], [], color="pink", linewidth=5)
    ani = animation.FuncAnimation(fig, update_line, interval=10, frames=x.size,
                                  repeat=False)
    # required ffmpeg
    ani.save("ani_demo2.mp4")
    plt.show()

def demo1():
    # heart
    # t = np.linspace(0, 2 * np.pi, 180)
    # x = 16 * np.sin(t) ** 3
    # y = 13 * np.cos(t) - 5 * np.cos(2 * t) - 2 * np.cos(3 * t) - np.cos(4 * t)

    # butterfly: https://en.wikipedia.org/wiki/Butterfly_curve_(transcendental)
    t = np.linspace(0, 5 * np.pi, 180 * 5)
    x = np.sin(t) * (np.exp(np.cos(t)) - 2 * np.cos(4 * t) - np.sin(t / 12) ** 5)
    y = np.cos(t) * (np.exp(np.cos(t)) - 2 * np.cos(4 * t) - np.sin(t / 12) ** 5)
    plot_ani(x, y, lw=3, interval=10)

def plot_ani(x, y, color="green", interval=20, lw=2):
    def update_line(num):
        chart.set_data(data[..., :num])
        return chart,

    # ref: http://mathworld.wolfram.com/HeartCurve.html
    data = np.vstack((x, y))  # 2 x 180
    fig = plt.figure()
    padding = 1.1
    plt.xlim(np.min(x) * padding, np.max(x) * padding)
    plt.ylim(np.min(y) * padding, np.max(y) * padding)
    chart, = plt.plot([], [], color=color, linewidth=lw)
    ani = animation.FuncAnimation(fig, update_line, interval=interval, frames=x.size,
                                  repeat=False)
    # required ffmpeg
    # ani.save("ani_demo2.mp4")
    plt.show()

if __name__ == '__main__':
    # demo_simple()
    # demo_ani()
    demo1()


import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

import numpy as np

# Classfication losses

## prepare the data

# we simulate 3 class classification problem, our model retunrs from last layer this output
np_output1 = np.array([[0.5, 0.1, 0.4 ]], dtype=np.float32)
np_output2 = np.array([[0.1, 0.5, 0.3 ]], dtype=np.float32)
output1 = torch.from_numpy(np_output1)
output2 = torch.from_numpy(np_output2)
target1 = 0
target2 = 1
print(f'output1={output1} target={target1}')
print(f'output1={output2} target={target2}')

# but we know our target class, encoded as one hot encoding
np_target = np.array([[1, 0, 0 ]], dtype=np.float32)
cls_target = torch.from_numpy(np_target)

# L1 loss
print("L1")
loss = nn.L1Loss()
loss_value = loss(output1, cls_target)
print(loss_value) # (|0.5-1| + |0.1-0| + |0.4-0|)/3 = (0.5+0.1+0.4)/3 = 1.0/3 = 0.333

# for second wrong output the loss should be higher
loss_value = loss(output2, cls_target)
print(loss_value) # (|0.1-1| + |0.5-0| + |0.3-0|)/3 = (0.9+0.5+0.3)/3 = 1.7/3 = 0.5667

# CrossEntropy - This criterion combines nn.LogSoftmax() and nn.NLLLoss() in one single class.
# It is useful when training a classification problem with C classes.

print("CrossEntropyLoss")
loss = nn.CrossEntropyLoss()

class_number =torch.tensor([target1], dtype=torch.long)
loss_value = loss(output1, class_number)
print(loss_value) # =

class_number =torch.tensor([target2], dtype=torch.long)
loss_value = loss(output2, class_number)
print(loss_value) # =

# CrossEntropy for mini batch of size 2
print("CrossEntropyLoss - batch")
loss = nn.CrossEntropyLoss(reduction='none')
# input = torch.randn(3, 5)
# target = torch.empty(3, dtype=torch.long).random_(5)

# now we have 3 output classes and 2 examples in mini-batch
#batch_output = torch.stack([output1, output2])
batch_output = torch.cat([output1, output2])
# target for each mini-batch exmaple, should be from [0,2] -
batch_target = torch.tensor( [target1, target2], dtype= torch.int64)
loss_value = loss(batch_output, batch_target)
print(loss_value) # =

# regression problem

# we simulate 3 variable regression problem, our model returns from last layer this output
np_output1 = np.array([-0.9, 3.3, 4.5 ], dtype=np.float32)
np_output2 = np.array([5., -1, 3. ], dtype=np.float32)
# but we know our target is
np_target = np.array([-1., 3., 4. ], dtype=np.float32)

# make tensors
output1 = torch.from_numpy(np_output1)
output2 = torch.from_numpy(np_output2)
reg_target = torch.from_numpy(np_target)

# L2 loss - MSE - mean square error
print("MSE Loss")
print(f'output1={output1}')
print(f'output2={output2}')
print(f'target={reg_target}')

loss = nn.MSELoss()
loss_value = loss(output1, reg_target)
print(f'loss(output1, target)={loss_value}') # ((-0.9- -1)^2 + (3.3-3)^2 + (4.5-4)^2)/3 = 0.1167

# for second wrong output the loss should be higher
loss_value = loss(output2, reg_target)
print(f'loss(output2, target)={loss_value}')  # = 17.666

print("MSEs - without reduction")
loss = nn.MSELoss(reduction='none')
loss_value = loss(output1, reg_target)
print(f'loss(output1, target)={loss_value}') # [(-0.9- -1)^2 ,  (3.3-3)^2,  (4.5-4)^2 ] = [ 0.01, 0.09, 0.25]

# Multi-label

# BCE loss

# we have batch_size=3 (one example in a row), multi label problem
# this should be returned in the last layer of our model
# data shouldbe numbers from [0,1]
np_output1 = np.array([[0.1, 0.9], [0.6, 0.8], [0.8, 1.]], dtype=np.float32)
np_output2 = np.array([[0.4, 0.5], [0.1, 0.9], [0.1, 0.9]], dtype=np.float32)
# but we know our target is
np_target = np.array([[0., 1.],[1., 0.],[1., 1.]], dtype=np.float32)

# make tensors
output1 = torch.from_numpy(np_output1)
output2 = torch.from_numpy(np_output2)
target = torch.from_numpy(np_target)

# Binary Cross Entropy
print("BCELoss")
print(f'output1={output1}')
print(f'output2={output2}')
print(f'target={target}')

loss = nn.BCELoss()

# L= sum_i { l_i = -1*[ y_i * log(x_i) + (1-y_i)*log(1-x_i) ]}
loss_value = loss(output1, target)
print(f'loss(output1, target)={loss_value}') #

# for second wrong output the loss should be higher
loss_value = loss(output2, target)
print(f'loss(output2, target)={loss_value}')  # =

print("BCELoss - without reduction")
loss = nn.BCELoss(reduction='none')
loss_value = loss(output1, target)
print(f'loss(output1, target)={loss_value}') # =

print("BCELoss - reduction sum")
loss = nn.BCELoss(reduction='sum')
loss_value = loss(output1, target)
print(f'loss(output1, target)={loss_value}') # =

lst = []
x = output1.numpy()
y = target.numpy()
for i in range(len(x)):
    lst.append(-np.log(x[i])*y[i] + -np.log(1-x[i])*(1-y[i]))
print(lst, np.mean(lst))


import math
import numpy as np

def euclidian_distance(first: "List[float]", second: "List[float]") -> float:
    """
    Given two vectors, returns the euclidian distance between them
    Requires that they are the same dimension

    :param first: a vector
    :param second: a vector

    :return: the distance as a float
    """
    if len(first) != len(second):
        raise Exception("These vectors must be the same size")
    return math.sqrt(sum([pow(x - y, 2) for x, y in zip(first, second)]))

def minkowski_distance(
    first: "List[float]", second: "List[float]", order: int
) -> float:
    """
    Given two vectors, returns the Minkowski distance between them
    Requires that they are the same dimension

    :param first: a vector
    :param second: a vector
    :param order: int

    :return: the distance as a float
    """
    if len(first) != len(second):
        raise Exception("These vectors must be the same size")
    return pow(
        sum([pow(np.abs(x - y), order) for x, y in zip(first, second)]), 1 / order
    )

def manhatten_distance(first: "List[float]", second: "List[float]") -> float:
    """
    Given two vectors, returns the manhatten distance between them
    Requires that they are the same dimension

    :param first: a vector
    :param second: a vector

    :return: the distance as a float
    """
    if len(first) != len(second):
        raise Exception("These vectors must be the same size")

    return sum([abs(x - y) for x, y in zip(first, second)])

def cosine_similarity(first: "List[float]", second: "List[float]") -> float:
    """
    Given two vectors, returns the cosine similarity between them
    Requires that they are the same dimension

    :param first: a vector
    :param second: a vector

    :return: the similarity as a float
    """
    if len(first) != len(second):
        raise Exception("These vectors must be the same size")
    numerator = sum(x * y for x, y in zip(first, second))
    denominator = math.sqrt(sum(pow(x, 2) for x in first)) * math.sqrt(
        sum(pow(y, 2) for y in second)
    )
    if denominator == 0:
        return 0
    return numerator / denominator

def jaccard_similarity(first: "Set[object]", second: "Set[object]") -> float:
    """
    Given two sets, returns the jaccard similarity between them

    :param first: a set
    :param second: a set

    :return: the similarity as a float
    """
    return len(first & second) / len(first | second)


#!/usr/bin/env python

import numpy as np
import matplotlib.pyplot as plt
from numpy.linalg import inv
from numpy.linalg import norm
from math import isnan

def jacobian_1(x):
    """Jacobiano do sistema 5.7.2 da pagina 175 do livro
    Numerical Methods in Economics, de Keneth Judd."""
    return np.array([[.2 * x[0] ** (-.8), .2 * x[1] ** (-.8)],
                     [.1 * x[0] ** (-.9), .4 * x[1] ** (-.6)]])

def jacobian_2(x):
    """Jacobiano do sistema 5.7.3 da pagina 175 do livro
    Numerical Methods in Economics, de Keneth Judd."""
    return np.array([[5. * (x[0] ** .2 + x[1] ** .2) ** 4. * .2 * x[0] ** (-.8),
                      5. * (x[0] ** .2 + x[1] ** .2) ** 4. * .2 * x[1] ** (-.8)],
                     [4. * (x[0] ** .1 + x[1] ** .4) ** 3. * .1 * x[0] ** (-.9),
                      4. * (x[0] ** .1 + x[1] ** .4) ** 3. * .4 * x[1] ** (-.6)]])

def function_1(x):
    """Sistema 5.7.2 da pagina 175 do livro
    Numerical Methods in Economics, de Keneth Judd."""
    return np.array([x[0] ** .2 + x[1] ** .2 - 2.,
                     x[0] ** .1 + x[1] ** .4 - 2.])

def function_2(x):
    """Sistema 5.7.2 da pagina 175 do livro
    Numerical Methods in Economics, de Keneth Judd."""
    return np.array([(x[0] ** .2 + x[1] ** .2) ** 5. - 32.,
                     (x[0] ** .1 + x[1] ** .4) ** 4. - 16.])

def newton(function, jacobian, initial, e=0.00001, d=0.00001, print_iter=True,
           plot_iter = False):
    """Funcao que aplica o metodo de Newton-Raphson a funcao 'function' com
    jacobiano 'jacobian' inseridos como funcoes do Python. 'initial' e o ponto
    inicial para as iteracoes. 'e' e o nivel de precisao desejado e d e a
    precisao desejada em relacao ao resultado. 'print_iter' escolhe se quer
    que imprima os pontos intermediarios das iteracoes e 'plot_iter' escolhe
    se quer imprimir estes pontos num grafico."""
    x = initial
    iter = 0
    while True:
        try:
            if print_iter:
                print(x)
            if plot_iter:
                plt.scatter(x[0], x[1])
            s = np.dot(-inv(jacobian(x)), function(x)) # Equacao do metodo
            x_ante = x
            x = x + s
            iter += 1
            if norm(x_ante - x) <= e * (1 + norm(x)) or isnan(x[0]) or\
               isnan(x[1]): # Verifica criterio de convergencia
                break
        except:
            print("Erro")
            break
    if norm(function(x)) <= d: # Verifica se a solucao atende criterio
        print("Convergiu em " + str(iter) + " iterações iniciando em " +\
              str(np.round(initial, 2)))
        return x
    else:
        print("Método não convergiu iniciando em " + str(np.round(initial, 2)))

# O loop abaixo roda o metodo no retangulo [0,2]**2 para function_1
for i in (np.array(range(20))+1.)/10:
    for j in (np.array(range(20))+1.)/10:
        newton(function_1, jacobian_1, [i, j], print_iter=False)

# O loop abaixo roda o metodo no retangulo [0,2]**2 para function_2
for i in (np.array(range(20))+1.)/10:
    for j in (np.array(range(20))+1.)/10:
        newton(function_2, jacobian_2, [i, j], print_iter=False)

###############################################################################
# Note que os loops acima mostram que a transformacao da function_2 tornou    #
# a convergencia mais rapida, exigindo em media uma iteracao a menos no       #
# dominio testado                                                             #
###############################################################################

# O comando abaixo imprime um grafico com os pontos da iteracao da function_1
newton(function_1, jacobian_1, [2, 2], plot_iter=True)

# O comando abaixo imprime um grafico com os pontos da iteracao da function_2
newton(function_2, jacobian_2, [2, 2], plot_iter=True)


# -*- coding: utf-8 -*-
"""aula_python_4.ipynb

Automatically generated by Colaboratory.

Original file is located at
    https://colab.research.google.com/drive/10JwswxJwA6xL3sSl9tIH0HMp3-ZLC5gH

# Revisão

def inicia a definição de uma função
"""

def parabola(x, a, b, c):
    y = a * x ** 2 + b * x + c
    return y

parabola(1, 5, 2, 4)

"""Funções com argumentos com valor padrão:"""

def parabola_2(x, a=5, b=2, c=4):
    y = a * x ** 2 + b * x + c
    return y

parabola_2(1, 6, 7, 8)

"""Funções anônimas podem ser criadas com a palavra-chave lambda. As funções lambdas são restritas a uma única (em geral pequena) expressão. """

f2 = lambda x: x ** 2

f2(5)

f_soma = lambda a, b: a + b
f_soma(5, 9)

"""# Estrutura de dados

Veja a seguir mais métodos de objetos do tipo lista:
"""

l = [5, 18, 43, 77, 91, 18, 77]

"""Adicionar um elemento ao final da lista"""

l.append(100)
print(l)

"""Inserir um elemento em uma dada posição. Nesse caso insere o elemento 9 na posição de índice 1. """

l.insert(1, 9)
print(l)

"""Remover o primeiro elemento da lista que tem valor igual ao argumento (nesse caso remove o elemento 18)"""

l.remove(18)
print(l)

"""Contar o número de vezes que um elemento aparece na lista"""

l.count(77)

print(l)

"""Remover o elemento em uma dada posição. Esse método remove o elemento e o retorna.

Nesse caso remove o elemento de índice 1.
"""

l.pop(1)

print(l)

"""Devolver o índice do primeiro elemento cujo valor é igual ao argumento (nesse caso 91)

"""

l.index(91)

l.index(77)

l.index(53)

"""Ordenar os elementos da lista"""

print("lista:", l)
l.sort()
print("lista ordenada:", l)

"""Ordenar em ordem reversa"""

l = [5, 43, 77, 91, 18, 77, 100]  # definindo novamente a lista l
l.sort(reverse=True)
print(l)

"""Inverter a ordem dos elementos"""

l = [5, 43, 77, 91, 18, 77, 100]  # definindo novamente a lista l
l.reverse()
print(l)

"""Copiar os elementos da lista"""

l_c = l.copy()
print(l_c)

"""Para iterar sobre uma lista, a posição e o valor podem ser obtidos simultaneamente com a função enumerate()"""

for i, v in enumerate(l_c):
    print(i, v)

"""Para percorrer duas ou mais listas ao mesmo tempo, as entradas podem ser pareadas com a função zip()"""

l1 = [2.5, 3.9, 8.7]  # definindo lista l1
l2 = [8.7, 23.4, 12.5]  # definindo lista l2
for a, b in zip(l1, l2):
    print(a, b)

"""Uma lista pode ser percorrida na ordem inversa com a função reversed()"""

for z in reversed(range(10)):
    print(z)

"""**Compreensões de listas**

Compreensões de listas são uma maneira concisa de criar uma lista. Uma aplicação comum é criar uma nova lista onde cada elemento é resultante da avaliação de uma expressão no contexto das cláusulas for e if.

Por exemplo, podemos criar uma lista com os quadrados desta forma:

"""

quadrados = []  # cria uma lista vazia e armazena na variável quadrados
for x in range(10):
    quadrados.append(x ** 2)

print(quadrados)

"""Alternativamente, podemos fazer:"""

quadrados2 = [x ** 2 for x in range(10)]
print(quadrados2)

quadrados3 = list(map(lambda x: x ** 2, range(10)))
print(quadrados3)

"""**Tuplas**

Uma tupla consiste em uma sequência de valores separados por vírgulas. Na sua criação os valores de uma tupla podem ser envolvidos ou não por parênteses. Na saída do console tuplas são sempre envolvidas por parênteses. Apesar de serem similares às listas, as tuplas são frequentemente utilizadas em situações e com propósitos distintos. O principal motivo é que tuplas são imutáveis, enquanto listas são mutáveis.
"""

t = 5, 87, 'bla'
print(t)

"""Tuplas podem ser indexadas

"""

print(t[2])

"""É possível criar uma tupla contendo outras tuplas

"""

u = t, (56, 89, 346)
print(u[0][0])

"""Tuplas são **imutáveis**!"""

t[0] = 65

v = (27.6, 45.9)  # 'empacotamento' de dois valores em uma tupla
vx, vy = v  # processo inverso de 'desempacotamento' também é possível
print(vx)

"""
**Dicionários**

Uma estrutura de dados muito útil é o dicionário. Eles são indexados por palavras-chaves (keys), que podem ser de qualquer tipo imutável. Os dicionários são delimitados por {} e contém uma lista de pares chave:valor separados por vírgulas. O dicionário vazio é {}. Os dicionários são utilizados para armazenar e recuperar valores a partir das palavras-chaves. É possivel substituir uma valor de uma chave por um novo valor. """

ramais = {'Cristina': 6473, 'Ana': 6486, 'Marina': 7256, 'Allan': 7257, 'Gustavo': 7257}
print(ramais['Marina'])

"""Remover um par chave:valor"""

del (ramais['Allan'])
print(ramais)

"""Incluir novo par chave:valor"""

ramais['Charles'] = 6486
print(ramais)

"""Alterar o valor de uma chave"""

ramais['Cristina'] = 6478
print(ramais)

"""Listar todas as chaves presentes no dicionário """

list(ramais)

"""Verificar se uma palavra-chave está ou não no dicionário"""

'Cristina' in ramais

'Sandra' in ramais

"""O construtor dict cria um dicionário a partir de sequências de pares chave,valor"""

dict([('Cristina', 6473), ('Ana', 6486), ('Marina', 7256)])

dict(Allan=7257, Charles=6486)

"""As compreensões de dicionário também podem ser usadas para criar dicionários a partir de expressões"""

dici = {x: x ** 2 for x in range(10)}
print(dici)

"""Ao iterar sobre dicionários, a chave e o valor podem ser obtidos simultaneamente usando o método items()"""

for nome, fone in ramais.items():
    print(nome, fone)

"""# Módulos

Uma boa prática ao escrever códigos maiores é dividí-lo em arquivos menores, o que pode facilitar a manutenção.

Um módulo é um arquivo contendo definições e instruções Python. O nome do arquivo é o nome do módulo acrescido do sufixo .py.

Um módulo também pode ser muito útil para escrever funções que são usadas em vários programas diferentes. É preferível usar um arquivo separado para uma função do que copiá-la para dentro de vários programas.

Por exemplo, usando um editor de texto, foi criado um arquivo chamado fibonacci.py, contendo as funções fib e fib_lista, apresentadas na última aula. Ao importar um módulo, as funções definidas no arquivo são colocadas diretamente na tabelas de símbolos atual e poderão ser chamadas e executadas.
"""

import fibonacci

fibonacci.fib(20)

fibonacci.fib_lista(50)

"""Módulos podem importar outros módulos. É costume colocar todos os comandos de import no início do módulo ou de um programa.

Outras maneiras de importar as definições de um módulo são:
"""

from fibonacci import fib, fib_lista

from fibonacci import *
# Importa todos os nomes definidos em um módulo
# Nem sempre aconselhado, pois introduz um conjunto desconhecido de nomes no ambiente,
# podendo levar a conflitos com outros nomes previamente definidos

import fibonacci as fibo

fibo.fib(10)

from fibonacci import fib_lista as f_l

f_l(30)

"""O Python guarda versões compiladas de cada módulo no diretório \__ pycache__ com o nome modulo.versão.pyc para acelerar o carregamento de módulos.

# Pacotes

Os pacotes são uma maneira de estruturar o “espaço de nomes” (namespace) dos módulos Python. Eles são uma coleção de módulos. Nos referimos a um módulo dentro de um pacote como nomedopacote.nomedomodulo.

Os arquivos \__ init__.py são necessários para que o Python trate diretórios contendo o arquivo como pacotes. \__ init__.py pode ser apenas um arquivo vazio.

O pacote **math** contém todas as funções matemáticas padrão (sqrt,log, log10, exp, funções trigonométricas).
"""

from math import log

x = log(3)
print(x)

from math import pi

print(pi)

from math import exp
from math import sin, cos

from math import *

# Algoritmo para converter a posição de um ponto dada em coordenadas polares (r,theta) para coordenadas
# Cartesianas (x,y)

from math import sin, cos, pi

r = float(input('Entre o valor de r: '))
d = float(input('Entre o valor de theta em graus: '))

theta = d * pi / 180.  # conversão de graus para radianos
x = r * cos(theta)
y = r * sin(theta)

print('x=', x, 'y=', y)

"""O pacote **numpy** é fundamental para computação científica com Python. Ele oferece funções matemáticas, geradores de números aleatórios, rotinas de álgebra linear, transformada de Fourier e muito mais. Mas o seu poder está em permitir cálculos em arrays multidimensionais.

O numpy executa facilmente cálculos numéricos que são muito utilizados em problemas de machine learning (aprendizado de máquina) e de processamento de imagem (imagens no computador são representadas como arrays multidimensionais de números). Além disso, o numpy oferece várias outras tarefas matemáticas.

Para mais informações, consulte https://numpy.org/.

# Arrays

Um array é um conjunto ordenado de valores. Mas há diferenças importantes entre listas e arrays:
* O número de elementos de um array é fixo e não é possível adicionar ou remover elementos de um array após criá-lo;
* Os elementos de um array devem ser todos de um mesmo tipo, como float ou inteiros.

As principais vantagens de usar arrays em vez de listas são:
* Arrays podem ter qualquer número de dimensões. Arrays unidimensionais e bidimensionais são, respectivamente, como vetores e matrizes em álgebra linear. Listas são sempre unidimensionais.
* Como se comportam como vetores ou matrizes, podemos fazer qualquer aritmética com eles, ao contrário de listas.
* Arrays funcionam mais rápido que listas em Python.

Cada dimensão de um array é chamada axis. O número de axes é chamada de rank do array. A indexação dos arrays também começa em 0, com um número para cada axis do array.
"""

from numpy import array

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

print(a)

# Minha recomendação
import numpy as np

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

print(a)

type(a)  # checar que o tipo é um numpy array

a.dtype  # fornece o tipo dos elementos do array

a.shape  # dimensões do array

a.ndim  # número de dimensões

a.size  # número de elementos

"""Indexação de um array: o índice da linha vem primeiro e depois o da coluna"""

print(a)

print(a[2, 1])

a[1, 0:2]

"""Iterando sobre um array

"""

b = np.array([0, 2, 5, 8], dtype=int)
print(b)

for z in b:
    print(z)

for i in range(len(b)):
    print(b[i])

"""Outras formas de criar arrays

"""

a0 = np.zeros(5)
print(a0)

a0_2 = np.zeros([3, 4])
print(a0_2)

a1 = np.ones(7)
print(a1)

a1_2 = np.ones([2, 3])
print(a1_2)

a1_3 = np.ones([2, 3, 4])
print(a1_3)

"""A função arange retorna valores espaçados igualmente em um certo intervalo.
O espaçamento é definido pelo passo (terceiro argumento da função).

numpy.arange(início,fim,passo)
"""

a2 = np.arange(1., 4., 0.5)
print(a2)

"""A função linspace também retorna n valores espaçados igualmente em um certo intervalo.

numpy.linspace(início,fim,n)
"""

a3 = np.linspace(1., 10., 3)
print(a3)

"""É possível criar arrays com números aleatórios (entre 0 e 1)

"""

a5 = np.random.rand(3, 4, 2)
print(a5)

"""**Operações com arrays**

O poder do numpy vem da possibilidade de realizar operações com arrays.
"""

c = np.array([1, 2])
d = np.array([3, 4])

print(c + d)

print(c * d)

print(c ** d)

e = a1_2 + 6.5
print(e)

# Produto escalar de dois array é feito pela função dot
prod_esc = np.dot(c, d)
print(prod_esc)

log10_c = np.log10(a5)
print(log10_c)
print(log10_c[1, 0, 0])

"""Cuidado ao fazer cópias de arrays"""

a = np.array([1, 1])
b = a
a[0] = 2
print(a)
print(b)

a = np.array([1, 1])
b = np.copy(a)
a[0] = 2
print(a)
print(b)


import numpy as np
import time
import pandas as pd

def main():
    # initi conditions---------------------------------------------------------
    A=2
    B=0.01
    t0=0.0
    t_end=5
    #--------------------------------------------------------------------------

    # Analytic solution
    analytic = lambda t_ini, y_ini, t, a, b: 1/(b/a+(1/y_ini-b/a)*np.exp(-a*(t-t_ini)))
    #--------------------------------------------------------------------------
    # ODE
    f = lambda y: A*y-B*y*y
    #--------------------------------------------------------------------------
    # initial valuse
    y0_list = np.array([8, 10, 12])
    #y0_list = np.array([100, 190, 250])
    #--------------------------------------------------------------------------

    timesteps = np.array([0.05, 0.025, 0.0125, 0.00625, 0.003125])

    # maps of 6th order for different time steps (A=2, B=0.01)
    # M(0,:) is map for dt=0.05     (N=100)
    # M(1,:) is map for dt=0.025    (N=200)
    # M(2,:) is map for dt=0.0125   (N=400)
    # M(3,:) is map for dt=0.00625  (N=800)
    # M(4,:) is map for dt=0.003125 (N=1600)
    Maps = np.array([[-2.20674242163663e-17,7.2859273622565e-14,-1.57090670168903e-10,3.0491292885473e-7,-0.000581078444935817,1.10516564125989,0.000148534476733103],
                    [-8.20209311569985e-19,4.34779284638878e-15,-1.76489909946068e-11,6.90754305025356e-8,-0.000269497671189628,1.05127100148724,2.69139400711076e-6],
                    [-2.79644218719739e-20,2.60080775407912e-16,-2.07823660780399e-12,1.6426765170331e-8,-0.000129779855270288,1.02531511893295,4.53070556313535e-8],
                    [-9.12880385587037e-22,1.57930196254673e-17,-2.51879120712609e-13,4.00518610053792e-9,-6.3683344531535e-5,1.01257845151487,7.34983595234415e-10],
                    [-2.91577138323977e-23,9.70954867859659e-19,-3.09980538706333e-14,9.88849311608068e-10,-3.15443976782067e-5,1.00626957200335,1.16379709756459e-11]
                    ])

    #--------------------------------------------------------------------------
    # resulting table
    # result(:, 0) is N
    # result(:, 1) is err_RK4
    # result(:, 2) is err_TM6
    # result(:, 3) is time_RK4
    # result(:, 4) is time_TM6
    # result(:, 5) is time_ratio = time_RK4/time_TM6
    result = np.zeros((len(timesteps), 6))

    for k, dt in enumerate(timesteps):  # for each time stemp dt
        M = Maps[k, :]                 # get TM for this dt
        N = int((t_end-t0)/dt)
        t = np.arange(t0, t_end, dt)

        result[k, 0] = N

        for y0 in y0_list: # for each y0
            # analytic solution------------------------------------------------
            y_sol = analytic(t0, y0, t, A, B)
            #------------------------------------------------------------------

            # RK4 integration--------------------------------------------------
            y_rk4 = np.zeros(N)

            y_rk4[0]=y0
            tic = time.time()
            for i in range(N-1):
                y = y_rk4[i]
                k1 = f(y)
                k2 = f(y+dt*k1/2)
                k3 = f(y+dt*k2/2)
                k4 = f(y+dt*k3)
                y_rk4[i+1] = y + dt*(k1+2*k2+2*k3+k4)/6

            elapsed_time = time.time()-tic
            result[k, 3] += elapsed_time # time_RK4
            #------------------------------------------------------------------

            # Mapping----------------------------------------------------------
            y_map = np.zeros(N)
            y_map[0] = y0
            tic = time.time()
            for i in range(N-1):
                y=y_map[i]
                y2 = y*y
                y3 = y2*y
                y_map[i+1] = (y3*(M[0]*y3 + M[3]) +
                            y2*(M[1]*y3 + M[4]) +
                            y *(M[2]*y3 + M[5]) +
                            M[6])

            elapsed_time = time.time()-tic
            result[k, 4] += elapsed_time # time_RK4
            #------------------------------------------------------------------
            result[k, 1] += np.abs(y_rk4 - y_sol).max() # err_RK4
            result[k, 2] += np.abs(y_map - y_sol).max() # err_TM6

    result[:, 1:] /= len(y0_list) # get average results
    result[:, 5] = result[:, 3]/result[:, 4] # get time_ratio

    result = pd.DataFrame(data=result[:,1:], index=result[:,0], columns=np.array(['err_RK4', 'err_TM6', 'time_RK4', 'time_TM6', 'time_ratio']))
    return result

if __name__ == "__main__":
    print(main())


import numpy as np
from math import sqrt

def cubic_spline_interpolation(x0, x, y):

x = np.asfarray(x)
y = np.asfarray(y)
size = len(x)
xdiff = np.diff(x)
ydiff = np.diff(y)
Li = np.empty(size)
Li_1 = np.empty(size-1)
z = np.empty(size)
Li[0] = sqrt(2*xdiff[0])
Li_1[0] = 0
B0 = 0
z[0] = B0 / Li[0]

for i in range(1, size-1, 1):
Li_1[i] = xdiff[i-1] / Li[i-1]
Li[i] = sqrt(2*(xdiff[i-1]+xdiff[i]) - Li_1[i-1] * Li_1[i-1])
Bi = 6*(ydiff[i]/xdiff[i] - ydiff[i-1]/xdiff[i-1])
z[i] = (Bi - Li_1[i-1]*z[i-1])/Li[i]
i = size - 1
Li_1[i-1] = xdiff[-1] / Li[i-1]
Li[i] = sqrt(2*xdiff[-1] - Li_1[i-1] * Li_1[i-1])
Bi = 0
z[i] = (Bi - Li_1[i-1]*z[i-1])/Li[i]
i = size-1
z[i] = z[i] / Li[i]

for i in range(size-2, -1, -1):
z[i] = (z[i] - Li_1[i-1]*z[i+1])/Li[i]
index = x.searchsorted(x0)
np.clip(index, 1, size-1, index)
xi1, xi0 = x[index], x[index-1]
yi1, yi0 = y[index], y[index-1]
zi1, zi0 = z[index], z[index-1]
hi1 = xi1 - xi0
f0 = zi0/(6*hi1)*(xi1-x0)**3 + \
zi1/(6*hi1)*(x0-xi0)**3 + \
(yi1/hi1 - zi1*hi1/6)*(x0-xi0) + \
(yi0/hi1 - zi0*hi1/6)*(xi1-x0)
return f0

if __name__ == '__main__':
import matplotlib.pyplot as plt
x = np.linspace(0.9, 13.3, num=21)
y = np.array([1.3, 1.5, 1.85, 2.1, 2.6, 2.7, 2.4, 2.15, 2.05, 2.1, 2.25, 2.3, 2.25, 1.95,
1.4, 0.9, 0.7, 0.6, 0.5, 0.4, 0.25])
plt.scatter(x, y, color = 'red')
x_new = np.linspace(0.9, 13.3, num=50)
plt.plot(x_new, cubic_spline_interpolation(x_new, x, y), 'k--')
plt.ylim(-1,5)
plt.title('Cubic Spline Interpolation')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.ylim(-1,5)
plt.show()


import numpy as np
import numpy.linalg as la

verySmallNumber = 1e-14 # 1×10⁻¹⁴ = 0.00000000000001

def gsBasis4(A) :
    B = np.array(A, dtype=np.float_)
    B[:, 0] = B[:, 0] / la.norm(B[:, 0])
    B[:, 1] = B[:, 1] - B[:, 1] @ B[:, 0] * B[:, 0]
    if la.norm(B[:, 1]) > verySmallNumber :
        B[:, 1] = B[:, 1] / la.norm(B[:, 1])
    else :
        B[:, 1] = np.zeros_like(B[:, 1])
    B[:, 2] = B[:, 2] - B[:, 2] @ B[:, 0] * B[:, 0]
    B[:, 2] = B[:, 2] - B[:, 2] @ B[:, 1] * B[:, 1]
    if la.norm(B[:, 2]) > verySmallNumber :
        B[:, 2] = B[:, 2] / la.norm(B[:, 2])
    else :
        B[:, 2] = np.zeros_like(B[:, 2])
    B[:, 3] = B[:, 3] - B[:, 3] @ B[:, 0] * B[:, 0]
    B[:, 3] = B[:, 3] - B[:, 3] @ B[:, 1] * B[:, 1]
    B[:, 3] = B[:, 3] - B[:, 3] @ B[:, 2] * B[:, 2]
    if la.norm(B[:, 3]) > verySmallNumber :
        B[:, 3] = B[:, 3] / la.norm(B[:, 3])
    else :
        B[:, 3] = np.zeros_like(B[:, 3])
    return B

def gsBasis(A) :
    B = np.array(A, dtype=np.float_)
    for i in range(B.shape[1]) :
        for j in range(i) :
            B[:, i] = B[:,i] - B[:,i] @ B[:,j] * B[:,j]
        if la.norm(B[:, i]) > verySmallNumber :
            B[:, i] = B[:, i] / la.norm(B[:, i])
        else :
            B[:, i] = np.zeros_like(B[:, i])
    return B

def dimensions(A) :
    return np.sum(la.norm(gsBasis(A), axis=0))

# Tests

V = np.array([[1,0,2,6],
              [0,1,8,2],
              [2,8,3,1],
              [1,-6,2,3]], dtype=np.float_)
gsBasis4(V)

U = gsBasis4(V)
gsBasis4(U)

gsBasis(V)

A = np.array([[3,2,3],
              [2,5,-1],
              [2,4,8],
              [12,2,1]], dtype=np.float_)
gsBasis(A)

dimensions(A)

B = np.array([[6,2,1,7,5],
              [2,8,5,-4,1],
              [1,-6,3,2,8]], dtype=np.float_)
gsBasis(B)

dimensions(B)

C = np.array([[1,0,2],
              [0,1,-3],
              [1,0,2]], dtype=np.float_)
gsBasis(C)

dimensions(C)


###_________________________ Lotka-Volterra Model __________________________###
###                         Prey - Predator Model                           ###
# Las ecuaciones de Lotka-Volterra son un modelo biomatemático que pretende responder
# a estas cuestiones prediciendo la dinámica de las poblaciones de presa y depredador
# bajo una serie de hipótesis:
    # * El ecosistema está aislado: no hay migración, no hay otras especies presentes,
    #   no hay plagas...
    # * La población de presas en ausencia de depredadores crece de manera exponencial:
    #   la velocidad de reproducción es proporcional al número de individuos.
    #   Las presas sólo mueren cuando son cazadas por el depredador.
    # * La población de depredadores en ausencia de presas decrece de manera exponencial.
    # * La población de depredadores afecta a la de presas haciéndola decrecer de
    #   forma proporcional al número de presas y depredadores (esto es como decir
    #   de forma proporcional al número de posibles encuentros entre presa y depredador).
    # * La población de presas afecta a la de depredadores también de manera
    #   proporcional al número de encuentros, pero con distinta constante de
    #   proporcionalidad (dependerá de cuanto sacien su hambre los depredadores
    #   al encontrar una presa).

# Se trata de un sistema de dos ecuaciones diferenciales de primer orden, acopladas,
# autónomas y no lineales:

    # $$ \frac{dx}{dt} = \alpha x - \beta x y $$
    # $$ \frac{dy}{dt} = -\gamma y + \delta y x $$

# donde x es el número de presas e y es el número de depredadores.
# Los parámetros son constantes positivas que representan:
    # * $\alpha$: tasa de crecimiento de las presas.
    # * $\beta$: éxito en la caza del depredador.
    # * $\gamma$: tasa de decrecimiento de los depredadores.
    # * $\delta$: éxito en la caza y cuánto alimenta cazar una presa al depredador.
#_____________________________________________________________________________#
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint

# System of 1st order ODEs
def df_dt(x, t, a, b, c, d):

    dx = a * x[0] - b * x[0] * x[1]
    dy = - c * x[1] + d * x[0] * x[1]

    return np.array([dx, dy])

# Parameters
a = 0.1
b = 0.02
c = 0.3
d = 0.01

# Initial Conditions
x0 = 40
y0 = 9
conds_iniciales = np.array([x0, y0])

# Conditions for integration
tf = 200
N = 800
t = np.linspace(0, tf, N)

# Solve the equation
solucion = odeint(df_dt, conds_iniciales, t, args=(a, b, c, d))

# Plot population in terms of time
plt.style.use('seaborn-talk')

plt.figure("Temporal Evolution", figsize=(8,5))
plt.title("Temporal Evolution")
plt.plot(t, solucion[:, 0], label='Prey')
plt.plot(t, solucion[:, 1], label='Predator')
plt.xlabel('Time')
plt.ylabel('Population')
plt.grid()
plt.legend()
plt.show()

# Plot Prey pop. in terms of Predator pop.
plt.figure("Preys vs Predators", figsize=(8,5))
plt.plot(solucion[:, 0], solucion[:, 1], 'r')
plt.xlabel('Prey')
plt.ylabel('Predator')
plt.grid()
plt.show()

# Plot phase map and direction field

# Podemos pintar el campo de direcciones de nuestras ecuaciones usando la función
# quiver. El tamaño de las flechas se ha normalizado para que todas tengan la misma
# longitud y se ha usado un colormap para representar el módulo.
x_max = np.max(solucion[:,0]) * 1.05
y_max = np.max(solucion[:,1]) * 1.05

x = np.linspace(0, x_max, 25)
y = np.linspace(0, y_max, 25)

xx, yy = np.meshgrid(x, y)
uu, vv = df_dt((xx, yy), 0, a, b, c, d)
norm = np.sqrt(uu**2 + vv**2)
uu = uu / norm
vv = vv / norm

plt.figure("Direction field", figsize=(8,5))
plt.quiver(xx, yy, uu, vv, norm, cmap=plt.cm.gray)
plt.plot(solucion[:, 0], solucion[:, 1], 'r')
plt.xlim(0, x_max)
plt.ylim(0, y_max)
plt.xlabel('Preys')
plt.ylabel('Predators')
plt.grid()
plt.show()

# Plot direction field + Pop vs time
n_max = np.max(solucion) * 1.10

fig, ax = plt.subplots(1,2)

fig.set_size_inches(12,5)

ax[0].quiver(xx, yy, uu, vv, norm, cmap=plt.cm.gray)
ax[0].plot(solucion[:, 0], solucion[:, 1], lw=2, alpha=0.8)
ax[0].set_xlim(0, x_max)
ax[0].set_ylim(0, y_max)
ax[0].set_xlabel('Preys')
ax[0].set_ylabel('Predators')

ax[1].plot(t, solucion[:, 0], label='Preys')
ax[1].plot(t, solucion[:, 1], label='Predators')
ax[1].legend()
ax[1].set_xlabel('Time')
ax[1].set_ylabel('Population')
plt.show()

##_______________________ Effect of initial conditions ______________________##
# Se puede demostrar que a lo largo de las líneas del mapa de fases, como la que
# hemos pintado antes, se conserva la cantidad:
    # $$ C = \alpha \ln{y} - \beta y + \gamma \ln{x} -\delta x $$
# Por tanto, pintando un `contour` de esta cantidad podemos obtener la solución
# para distintos valores iniciales del problema.

def C(x, y, a, b, c, d):
    return a * np.log(y) - b * y + c * np.log(x) - d * x

x = np.linspace(0, x_max, 100)
y = np.linspace(0, y_max, 100)
xx, yy = np.meshgrid(x, y)
constant = C(xx, yy, a, b, c, d)

plt.figure('Various solutions', figsize=(8,5))
plt.contour(xx, yy, constant, 50, cmap=plt.cm.Blues)
plt.xlabel('Preys')
plt.ylabel('Predators')
plt.grid()
plt.show()

# Vemos que estas curvas se van haciendo cada vez más y más pequeñas, hasta que,
# en nuestro caso, colapsarían en un punto en torno a $(30,5)$. Se trata de un
# punto de equilibrio o punto crítico; si el sistema lo alcanzase, no evolucionaría
# y el número de cebras y leones sería constante en el tiempo. El otro punto crítico
# de nuestro sistema es el $(0,0)$. Analizándolos matemáticamente se obtiene que:

# El punto crítico situado en $(0,0)$ es un punto de silla. Al tratarse de un punto
# de equilibrio inestable la extinción de cualquiera de las dos especies en el
# modelo sólo puede conseguirse imponiendo la condición inicial nula.

# El punto crítico situado en $(gamma/delta,alpha/beta)$ es un centro (en este
# caso los autovalores de la matriz del sistema linealizado son ambos imaginarios
# puros, por lo que a priori no se conoce su estabilidad).

fig, ax = plt.subplots(1,2)

fig.set_size_inches(12,5)

ax[0].plot(solucion[:, 0], solucion[:, 1], lw=2, alpha=0.8)
ax[0].scatter(c/d, a/b)
levels = (0.5, 0.6, 0.7, 0.72, 0.73, 0.74, 0.75, 0.76, 0.77, 0.775, 0.78, 0.781)
ax[0].contour(xx, yy, constant, levels, colors='blue', alpha=0.3)
ax[0].set_xlim(0, x_max)
ax[0].set_ylim(0, y_max)
ax[0].set_xlabel('presas')
ax[0].set_ylabel('depredadores')

ax[1].plot(t, solucion[:, 0], label='presa')
ax[1].plot(t, solucion[:, 1], label='depredador')
ax[1].legend()
ax[1].set_xlabel('tiempo')
ax[1].set_ylabel('población')
plt.show()

##___________________________ Improving the model ___________________________##
# Como se puede observar, este modelo tiene algunas deficiencias propias de su
# simplicidad y derivadas de las hipótesis bajo las que se ha formulado. Una
# modificación razonable es cambiar el modelo de crecimiento de las presas en
# ausencia de depredadores, suponiendo que en vez de aumentar de forma exponencial,
# lo hacen según una [función logística](http://es.wikipedia.org/wiki/Funci%C3%B3n_log%C3%ADstica).
# Esta curva crece de forma similar a una exponencial al principio, moderándose
# después y estabilizándose asintóticamente en un valor:

def logistic_curve(t, a=1, m=0, n=1, tau=1):
    e = np.exp(-t / tau)
    return a * (1 + m * e) / (1 + n * e)

x_ = np.linspace(0,10)
plt.figure('función logística', figsize=(8,5))
plt.plot(x_, logistic_curve(x_, 1, m=10, n=100, tau=1))
plt.grid()
plt.plot()

# Podemos observar como esta curva crece de forma similar a una exponencial al
# principio, moderándose después y estabilizándose asintóticamente en un valor.
# Este modelo de crecimiento representa mejor las limitaciones en el número de
# presas debidas al medio (falta de alimento, territorio...). Llevando este modelo
# de crecimiento a las ecuaciones originales se tiene un nuevo sistema en el que
# interviene un parámetro más:
    # $$ \frac{dx}{dt} = (\alpha x - r x^2) - \beta x y $$
    # $$ \frac{dy}{dt} = -\gamma y + \delta y x $$

def df_dt_logistic(x, t, a, b, c, d, r):

    dx = a * x[0] - r * x[0]**2 - b * x[0] * x[1]
    dy = - c * x[1] + d * x[0] * x[1]

    return np.array([dx, dy])

# Parámetros
a = 0.1
b = 0.02
c = 0.3
d = 0.01
r = 0.001

# Condiciones iniciales
x0 = 40
y0 = 9
conds_iniciales = np.array([x0, y0])

# Condiciones para integración
tf = 200
N = 800
t = np.linspace(0, tf, N)

# Solution
solucion_logistic = odeint(df_dt_logistic, conds_iniciales, t, args=(a, b, c, d, r))

n_max = np.max(solucion) * 1.10

fig, ax = plt.subplots(1,2)

fig.set_size_inches(12,5)

x_max = np.max(solucion_logistic[:,0]) * 1.05
y_max = np.max(solucion_logistic[:,1]) * 1.05

x = np.linspace(0, x_max, 25)
y = np.linspace(0, y_max, 25)

xx, yy = np.meshgrid(x, y)
uu, vv = df_dt_logistic((xx, yy), 0, a, b, c, d, r)
norm = np.sqrt(uu**2 + vv**2)
uu = uu / norm
vv = vv / norm

ax[0].quiver(xx, yy, uu, vv, norm, cmap=plt.cm.gray)
ax[0].plot(solucion_logistic[:, 0], solucion_logistic[:, 1], lw=2, alpha=0.8)
ax[0].set_xlim(0, x_max)
ax[0].set_ylim(0, y_max)
ax[0].set_xlabel('presas')
ax[0].set_ylabel('depredadores')

ax[1].plot(t, solucion_logistic[:, 0], label='presa')
ax[1].plot(t, solucion_logistic[:, 1], label='depredador')
ax[1].legend()
ax[1].set_xlabel('tiempo')
ax[1].set_ylabel('población')
plt.grid()
plt.show()

# En este caso se puede observar como el comportamiento deja de ser periódico.
# El punto crítico que antes era un centro, se convierte en un atractor y la
# solución tiende a estabilizarse en un número fijo de presas y depredadores.


"""
The prime module makes available several functions for prime operations.
Included are prime tests, prime lists, and prime factorization.
"""

def isprime(n):
    """Returns True if n is prime. O(sqrt(n))"""
    if n < 2:
        return False
    if n == 2 or n == 3:
        return True
    if n % 2 == 0 or n % 3 == 0:
        return False
    i = 5
    w = 2
    while i * i <= n:
        if n % i == 0:
            return False
        i += w
        w = 6 - w
    return True

def primes_up_to(n):
    """
    Return a numpy.ndarray of bools of all primes <= n.
    Uses Sieve of Eratosthenes, O(n log log n).
    """
    import numpy as np
    if n < 2:
        return np.zeros(n+1, dtype=bool)

    A = np.ones(n+1, dtype=bool)
    A[0] = A[1] = False
    for i in range(2, int(np.sqrt(n))+1):
        if A[i]:
            for j in range(i**2, n+1, i):
                A[j] = False
    return np.where(A)[0]

def primes_dict(n):
    """
    Return a dictionary with the prime factorization of n.
    Format of (key, value) is (prime, multiplicity)
    """
    primfac = {}
    d = 2
    while d*d <= n:
        while n % d == 0:
            if d in primfac:
                primfac[d] += 1
            else:
                primfac[d] = 1
            n /= d
        d += 1
    if n > 1:
        primfac[n] = 1
    return primfac

def primes_list(n):
    "Return a list with the prime factorization of n."
    primfac = []
    d = 2
    while d*d <= n:
        while n % d == 0:
            primfac.append(d)
            n /= d
        d += 1
    if n > 1:
        primfac.append(n)
    return primfac

def prime_stream():
    "An indefinite stream of primes, starting from 2. O(n log log n)."
    import itertools
    yield from (2, 3, 5, 7)
    D = {}
    ps = prime_stream()
    next(ps)
    p = next(ps)
    assert p == 3
    psq = p*p
    for i in itertools.count(9, 2):
        if i in D:      # composite
            step = D.pop(i)
        elif i < psq:   # prime
            yield i
            continue
        else:           # composite, = p*p
            assert i == psq
            step = 2*p
            p = next(ps)
            psq = p*p
        i += step
        while i in D:
            i += step
        D[i] = step


import plotly.graph_objects as go
from plotly.subplots import make_subplots
import numpy as np
from scipy.fftpack import fft, ifft, fftfreq

# Changing K plot
# Create figure
fig_k = make_subplots(
    rows=1, cols=2,
    shared_xaxes=False,
    horizontal_spacing=0.02,
    specs=[[{'type': 'scatter3d'}, {'type': 'scatter3d'}]])

# Add traces, one for each slider step
k= np.linspace(-10, 10, 21)
phi_0 = 0
sig = 0.5
x=np.arange(-20, 20.01, 0.01)

for k_i in np.arange(-10, 11, 1):
    psi = np.multiply(np.exp(1j*k_i*x), np.sqrt(np.exp(-np.power(x-phi_0, 2)/(2*sig**2))/(sig*np.sqrt(2*np.pi))))
    fig_k.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="black", width=2),
            name="wavefunction",
            x=x,
            y=np.imag(psi),
            z=np.real(psi),
            mode="lines"), row=1, col=1)
    fig_k.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="red", width=1),
            name="real part",
            x=x,
            y=-2*np.ones(psi.shape),
            z=np.real(psi),
            mode="lines"), row=1, col=1)
    fig_k.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="orange", width=1),
            name="imaginary part",
            x= x,
            y= np.imag(psi),
            z= -2*np.ones(psi.shape),
            mode="lines"), row=1, col=1)
    fig_k.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="black", width=1),
            name="imaginary part",
            x= x,
            y= -2*np.ones(psi.shape),
            z= np.abs(psi),
            mode="lines"), row=1, col=1)

for k_i in np.arange(-10, 11, 1):
    psi_fft = fft(np.multiply(np.exp(1j*k_i*x), np.sqrt(np.exp(-np.power(x-phi_0, 2)/(2*sig**2))/(sig*np.sqrt(2*np.pi)))))
    xf = fftfreq(len(x), x[1] - x[0])
    fig_k.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="black", width=2),
            name="wavefunction",
            x=np.concatenate((xf[len(xf)//2+1:], [xf[0]], xf[1:len(xf)//2])),
            y=np.imag(np.concatenate((psi_fft[len(xf)//2+1:],[psi_fft[0]], psi_fft[1:len(xf)//2]))),
            z=np.real(np.concatenate((psi_fft[len(xf)//2+1:],[psi_fft[0]], psi_fft[1:len(xf)//2]))),
            mode="lines"), row=1, col=2)
    fig_k.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="red", width=1),
            name="real part",
            #x=xf,
            x=np.concatenate((xf[len(xf)//2+1:], [xf[0]], xf[1:len(xf)//2])),
            y=-10*np.ones(psi_fft.shape),
            #z=np.real(psi_fft),
            z=np.real(np.concatenate((psi_fft[len(xf)//2+1:],[psi_fft[0]], psi_fft[1:len(xf)//2]))),
            mode="lines"), row=1, col=2)
    fig_k.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="orange", width=1),
            name="imaginary part",
            x=np.concatenate((xf[len(xf)//2+1:], [xf[0]], xf[1:len(xf)//2])),
            #x= xf,
            #y= np.imag(psi_fft),
            y=np.imag(np.concatenate((psi_fft[len(xf)//2+1:],[psi_fft[0]], psi_fft[1:len(xf)//2]))),
            z= -200*np.ones(psi_fft.shape),
            mode="lines"), row=1, col=2)
    fig_k.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="black", width=1),
            name="imaginary part",
            x=np.concatenate((xf[len(xf)//2+1:], [xf[0]], xf[1:len(xf)//2])),
            #x= xf,
            y= -10*np.ones(psi_fft.shape),
            z= np.abs(np.concatenate((psi_fft[len(xf)//2+1:],[psi_fft[0]], psi_fft[1:len(xf)//2]))),
            mode="lines"), row=1, col=2)

fig_k.data[40].visible = True
fig_k.data[41].visible = True
fig_k.data[42].visible = True
fig_k.data[43].visible = True

fig_k.data[(len(fig_k.data))//2+40].visible = True
fig_k.data[(len(fig_k.data))//2+41].visible = True
fig_k.data[(len(fig_k.data))//2+42].visible = True
fig_k.data[(len(fig_k.data))//2+43].visible = True

# Create and add slider
steps = []
for i in range(0, (len(fig_k.data))//2, 4):
    step = dict(
        method="update",
        args=[{"visible": [False] * len(fig_k.data)}],  # layout attribute
        label=str(k[i//4])
    )
    step["args"][0]["visible"][i] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+1] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+2] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+3] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][(len(fig_k.data))//2+i] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][(len(fig_k.data))//2+i+1] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][(len(fig_k.data))//2+i+2] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][(len(fig_k.data))//2+i+3] = True  # Toggle i'th trace to "visible"
    #step["args"][0]["visible"][-1] = True
    #step["args"][0]["visible"][-2] = True
    steps.append(step)

sliders = [dict(
    active=10,
    currentvalue={"prefix": "Wave number: "},
    pad={"t": 33},
    steps=steps
)]

fig_k.update_layout(
    sliders=sliders
)

fig_k.update_layout(
    scene = dict(
        xaxis = dict(nticks=10, range=[-5,5],),
        yaxis = dict(nticks=4, range=[-2,2],),
        zaxis = dict(nticks=4, range=[-2,2],),
        xaxis_title='Phi',
        yaxis_title='Imaginary',
        zaxis_title='Real',))
    #width=1000,
    #margin=dict(r=20, l=10, b=10, t=10))
fig_k.update_layout(
    scene2 = dict(
        xaxis = dict(nticks=10, range=[-3, 3]),
        yaxis = dict(nticks=4, range=[-10,10]),
        zaxis = dict(nticks=4, range=[-200, 200]),
        xaxis_title='Q',
        yaxis_title='Imaginary',
        zaxis_title='Real',))
fig_k.update_layout(scene_aspectmode='manual',
                  scene_aspectratio=dict(x=2, y=1, z=1),
                  scene2_aspectmode='manual',
                  scene2_aspectratio=dict(x=2, y=1, z=1),
                  showlegend=False,
                  margin=dict(l=10, r=10, t=5, b=5),
                  scene_camera=dict(eye=dict(x=1, y=2.4, z=0.4)),
                  scene2_camera=dict(eye=dict(x=1, y=2.4, z=0.4)))

fig_k.show(config = {'displayModeBar': False, 'displaylogo': False, 'scrollZoom': False})
fig_k.write_html("wavefunction_changing_k_with_fft.html", config={'displayModeBar': False, 'displaylogo': False, 'scrollZoom': False})

# Changing sig plot
# Create figure
fig_sig = make_subplots(
    rows=1, cols=2,
    shared_xaxes=True,
    horizontal_spacing=0.02,
    specs=[[{'type': 'scatter3d'}, {'type': 'scatter3d'}]])

# Add traces, one for each slider step
k = 3
sig_arr = np.logspace(-0.8, 0.5, 4)
phi_0 = 0
sig = 1
x=np.arange(-20, 20.01, 0.01)

for sig_i in sig_arr:
    psi = np.multiply(np.exp(1j*k*x), np.sqrt(np.exp(-np.power(x-phi_0, 2)/(2*sig_i**2))/(sig_i*np.sqrt(2*np.pi))))
    fig_sig.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="black", width=2),
            name="wavefunction",
            x=x,
            y=np.imag(psi),
            z=np.real(psi),
            mode="lines"), row=1, col=1)
    fig_sig.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="red", width=1),
            name="real part",
            x=x,
            y=-2*np.ones(psi.shape),
            z=np.real(psi),
            mode="lines"), row=1, col=1)
    fig_sig.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="orange", width=1),
            name="imaginary part",
            x= x,
            y= np.imag(psi),
            z= -2*np.ones(psi.shape),
            mode="lines"), row=1, col=1)
    fig_sig.add_trace(
        go.Scatter3d(
            visible=False,
            x=x,
            y= -2*np.ones(psi.shape),
            z=np.abs(psi),
            name="prob amplitude", mode="lines", line=dict(color='black', width=2)), row=1, col=1)

for sig_i in sig_arr:
    psi_fft = fft(np.multiply(np.exp(1j*k*x), np.sqrt(np.exp(-np.power(x-phi_0, 2)/(2*sig_i**2))/(sig_i*np.sqrt(2*np.pi)))))
    xf = fftfreq(len(x), x[1] - x[0])
    fig_sig.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="black", width=2),
            name="wavefunction",
            x=np.concatenate((xf[len(xf)//2+1:], [xf[0]], xf[1:len(xf)//2])),
            y=np.imag(np.concatenate((psi_fft[len(xf)//2+1:],[psi_fft[0]], psi_fft[1:len(xf)//2]))),
            z=np.real(np.concatenate((psi_fft[len(xf)//2+1:],[psi_fft[0]], psi_fft[1:len(xf)//2]))),
            mode="lines"), row=1, col=2)
    fig_sig.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="red", width=1),
            name="real part",
            x=np.concatenate((xf[len(xf)//2+1:], [xf[0]], xf[1:len(xf)//2])),
            y=-9*np.ones(psi_fft.shape),
            z=np.real(np.concatenate((psi_fft[len(xf)//2+1:],[psi_fft[0]], psi_fft[1:len(xf)//2]))),
            mode="lines"), row=1, col=2)
    fig_sig.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="orange", width=1),
            name="imaginary part",
            x= np.concatenate((xf[len(xf)//2+1:], [xf[0]], xf[1:len(xf)//2])),
            y= np.imag(np.concatenate((psi_fft[len(xf)//2+1:],[psi_fft[0]], psi_fft[1:len(xf)//2]))),
            z= -320*np.ones(psi_fft.shape),
            mode="lines"), row=1, col=2)
    fig_sig.add_trace(
        go.Scatter3d(
            visible=False,
            x=np.concatenate((xf[len(xf)//2+1:], [xf[0]], xf[1:len(xf)//2])),
            y= -9*np.ones(psi_fft.shape),
            z=np.abs(np.concatenate((psi_fft[len(xf)//2+1:],[psi_fft[0]], psi_fft[1:len(xf)//2]))),
            name="prob amplitude", mode="lines", line=dict(color='black', width=2)), row=1, col=2)

# Make 10th trace visible
fig_sig.data[4].visible = True
fig_sig.data[5].visible = True
fig_sig.data[6].visible = True
fig_sig.data[7].visible = True
fig_sig.data[len(fig_sig.data)//2+4].visible = True
fig_sig.data[len(fig_sig.data)//2+5].visible = True
fig_sig.data[len(fig_sig.data)//2+6].visible = True
fig_sig.data[len(fig_sig.data)//2+7].visible = True

# Create and add slider
steps = []
for i in range(0, len(fig_sig.data)//2, 4):
    step = dict(
        method="update",
        args=[{"visible": [False] * len(fig_sig.data)}],  # layout attribute
        label=str(round(sig_arr[i//4],2))
    )
    step["args"][0]["visible"][i] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+1] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+2] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+3] = True
    step["args"][0]["visible"][len(fig_sig.data)//2+i] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][len(fig_sig.data)//2+i+1] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][len(fig_sig.data)//2+i+2] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][len(fig_sig.data)//2+i+3] = True
    steps.append(step)

sliders = [dict(
    active=1,
    currentvalue={"prefix": "Standard diviation: "},
    pad={"t": 33},
    steps=steps
)]

fig_sig.update_layout(
    sliders=sliders
)

fig_sig.update_layout(
    scene = dict(
        xaxis = dict(nticks=10, range=[-5,5],),
        yaxis = dict(nticks=4, range=[-2,2],),
        zaxis = dict(nticks=4, range=[-2,2],),
        xaxis_title='Phi',
        yaxis_title='Imaginary',
        zaxis_title='Real',))
    #margin=dict(r=20, l=10, b=10, t=10))
fig_sig.update_layout(
    scene2 = dict(
        xaxis = dict(nticks=10, range=[-5,5],),
        yaxis = dict(nticks=4, range=[-9, 9],),
        zaxis = dict(nticks=4, range=[-320, 320],),
        xaxis_title='Q',
        yaxis_title='Imaginary',
        zaxis_title='Real',))
fig_sig.update_layout(scene_aspectmode='manual',
    scene_aspectratio=dict(x=2, y=1, z=1),
                  scene2_aspectmode='manual',
                  scene2_aspectratio=dict(x=2, y=1, z=1),
                  showlegend=False,
                  margin=dict(l=10, r=10, t=5, b=5),
                  scene_camera=dict(eye=dict(x=1, y=2.4, z=0.4)),
                  scene2_camera=dict(eye=dict(x=1, y=2.4, z=0.4))
                  #width=1000
                  )

fig_sig.show(config={'displayModeBar': False, 'displaylogo': False, 'scrollZoom': False})
fig_sig.write_html("wavefunction_changing_sig_with_fft.html", config={'displayModeBar': False, 'displaylogo': False, 'scrollZoom': False})

# Changing sig plot
# Create figure
fig_mean = make_subplots(
    rows=1, cols=2,
    shared_xaxes=True,
    horizontal_spacing=0.02,
    specs=[[{'type': 'scatter3d'}, {'type': 'scatter3d'}]])

# Add traces, one for each slider step
k = 3
sig = 1
phi_0 = np.arange(-3, 3.5, 0.5)
sig = 1
x=np.arange(-20, 20.01, 0.01)

for phi_0_i in phi_0:
    psi = np.multiply(np.exp(1j*k*x), np.sqrt(np.exp(-np.power(x-phi_0_i, 2)/(2*sig**2))/(sig*np.sqrt(2*np.pi))))
    fig_mean.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="black", width=2),
            name="wavefunction",
            x=x,
            y=np.imag(psi),
            z=np.real(psi),
            mode="lines"), row=1, col=1)
    fig_mean.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="red", width=1),
            name="real part",
            x=x,
            y=-np.ones(psi.shape),
            z=np.real(psi),
            mode="lines"), row=1, col=1)
    fig_mean.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="orange", width=1),
            name="imaginary part",
            x= x,
            y= np.imag(psi),
            z= -np.ones(psi.shape),
            mode="lines"), row=1, col=1)
    fig_mean.add_trace(
        go.Scatter3d(
            visible=False,
            x=x,
            y= -np.ones(psi.shape),
            z=np.abs(psi),
            name="prob amplitude", mode="lines", line=dict(color='black', width=2)), row=1, col=1)

for phi_0_i in phi_0:
    psi_fft = fft(np.multiply(np.exp(1j*k*x), np.sqrt(np.exp(-np.power(x-phi_0_i, 2)/(2*sig**2))/(sig*np.sqrt(2*np.pi)))))
    xf = fftfreq(len(x), x[1] - x[0])
    fig_mean.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="black", width=2),
            name="wavefunction",
            x=np.concatenate((xf[len(xf)//2+1:], [xf[0]], xf[1:len(xf)//2])),
            y=np.imag(np.concatenate((psi_fft[len(xf)//2+1:],[psi_fft[0]], psi_fft[1:len(xf)//2]))),
            z=np.real(np.concatenate((psi_fft[len(xf)//2+1:],[psi_fft[0]], psi_fft[1:len(xf)//2]))),
            mode="lines"), row=1, col=2)
    fig_mean.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="red", width=1),
            name="real part",
            x=np.concatenate((xf[len(xf)//2+1:], [xf[0]], xf[1:len(xf)//2])),
            y=-290*np.ones(psi_fft.shape),
            z=np.real(np.concatenate((psi_fft[len(xf)//2+1:],[psi_fft[0]], psi_fft[1:len(xf)//2]))),
            mode="lines"), row=1, col=2)
    fig_mean.add_trace(
        go.Scatter3d(
            visible=False,
            line=dict(color="orange", width=1),
            name="imaginary part",
            x= np.concatenate((xf[len(xf)//2+1:], [xf[0]], xf[1:len(xf)//2])),
            y= np.imag(np.concatenate((psi_fft[len(xf)//2+1:],[psi_fft[0]], psi_fft[1:len(xf)//2]))),
            z= -410*np.ones(psi_fft.shape),
            mode="lines"), row=1, col=2)
    fig_mean.add_trace(
        go.Scatter3d(
            visible=False,
            x=np.concatenate((xf[len(xf)//2+1:], [xf[0]], xf[1:len(xf)//2])),
            y= -290*np.ones(psi_fft.shape),
            z=np.abs(np.concatenate((psi_fft[len(xf)//2+1:],[psi_fft[0]], psi_fft[1:len(xf)//2]))),
            name="prob amplitude", mode="lines", line=dict(color='black', width=2)), row=1, col=2)

# Make 10th trace visible
fig_mean.data[24].visible = True
fig_mean.data[25].visible = True
fig_mean.data[26].visible = True
fig_mean.data[27].visible = True
fig_mean.data[len(fig_mean.data)//2+24].visible = True
fig_mean.data[len(fig_mean.data)//2+25].visible = True
fig_mean.data[len(fig_mean.data)//2+26].visible = True
fig_mean.data[len(fig_mean.data)//2+27].visible = True

# Create and add slider
steps = []
for i in range(0, len(fig_mean.data)//2, 4):
    step = dict(
        method="update",
        args=[{"visible": [False] * len(fig_mean.data)}],  # layout attribute
        label=str(phi_0[i//4])
    )
    step["args"][0]["visible"][i] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+1] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+2] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][i+3] = True
    step["args"][0]["visible"][len(fig_mean.data)//2+i] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][len(fig_mean.data)//2+i+1] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][len(fig_mean.data)//2+i+2] = True  # Toggle i'th trace to "visible"
    step["args"][0]["visible"][len(fig_mean.data)//2+i+3] = True
    steps.append(step)

sliders = [dict(
    active=6,
    currentvalue={"prefix": "Mean: "},
    pad={"t": 33},
    steps=steps
)]

fig_mean.update_layout(
    sliders=sliders
)

fig_mean.update_layout(
    scene = dict(
        xaxis = dict(nticks=10, range=[-5,5],),
        yaxis = dict(nticks=4, range=[-2,2],),
        zaxis = dict(nticks=4, range=[-2,2],),
               xaxis_title='Phi',
        yaxis_title='Imaginary',
        zaxis_title='Real',))
    #margin=dict(r=20, l=10, b=10, t=10))
fig_mean.update_layout(
    scene2 = dict(
        xaxis = dict(nticks=10, range=[-3,3],),
        yaxis = dict(nticks=4, range=[-290, 290],),
        zaxis = dict(nticks=4, range=[-430, 430],),
        xaxis_title='Q',
        yaxis_title='Imaginary',
        zaxis_title='Real',))
fig_mean.update_layout(scene_aspectmode='manual',
    scene_aspectratio=dict(x=2, y=1, z=1),
                  scene2_aspectmode='manual',
                  scene2_aspectratio=dict(x=2, y=1, z=1),
                  showlegend=False,
                  margin=dict(l=10, r=10, t=5, b=5),
                  scene_camera=dict(eye=dict(x=1, y=2.4, z=0.4)),
                  scene2_camera=dict(eye=dict(x=1, y=2.4, z=0.4))
                  #width=1000
                  )

fig_mean.show(config={'displayModeBar': False, 'displaylogo': False, 'scrollZoom': False})
fig_mean.write_html("wavefunction_changing_mu_with_fft.html", config={'displayModeBar': False, 'displaylogo': False, 'scrollZoom': False})


import numpy as np

def compint(P, r, n, t) -> float:
    """
    Compute the compounding interest
    P: principal
    r: interest rate
    n: number of times interest compounds in period
    t: Number of time periods

    A = P (1 + r / n) ^ (n * t)
    """
    A = P * np.power(1 + r / n, n * t)
    return A

def payment_amount(balance, payment):
    """
    The amount of a payment given a balance is which ever is less
    """
    curr_payment = min(balance, payment)
    return curr_payment

def single_payment(payment, P, r, n=365, t=1/12):
    """
    Make a single payment on a loan after accruing interest for the provided pay period
    :param payment: Payment amount per period
    :param P: principal
    :param r: interest rate
    :param n: number of times interest compounds in period
    :param t: payment frequency
    :return: (new principal, amount paid)
    """
    # Calculate new principal at end of period
    P = compint(P, r, n, t)
    ## Subtract payment
    # Pay payment amount until principal is zero
    curr_pay = payment_amount(P, payment)
    P -= curr_pay

    # Round
    curr_pay = round(curr_pay, 2)
    P = round(P, 2)
    return P, curr_pay

def pay_loan(payment, P, r, n=365, t=1/12, stop=1e6) -> tuple:
    """
    Pay a compound interest loan to extinction
    Default parameters for daily compounding with monthly payments
    :param payment: Payment amount per period
    :param P: principal
    :param r: interest rate
    :param n: number of times interest compounds in period
    :param t: payment frequency
    :param stop: Stop criteria to avoid infinite calculation (default 1 million)
    :return: (balances, payments)
    | balances: balances for each period
    | payments: payments for each period
    """
    # Keep track of balances after each payment
    balances = []
    payments = []
    while P > 0:
        P, curr_pay = single_payment(payment, P, r, n, t)
        assert P <= stop, f'Payments of {money_amount(payment)} have led the balance to reach stopping criteria of' \
                          f' {money_amount(stop)}.'
        balances.append(P)
        payments.append(curr_pay)
    return balances, payments

def money_amount(x: float) -> str:
    """
    Convert a float to a string dollar amount with commas
    Ex. 1000.235 -> $1,000.24
    """
    # Round to hundreds
    rounded = '%.2f' % x
    # Split into dollars and cents
    dollars, cents = rounded.split('.')

    # Add commas every three digits to dollars
    rev_dollars = ''
    count = 0
    for i in dollars[::-1]:
        rev_dollars += i
        count += 1
        # Add comma
        if count == 3:
            rev_dollars += ','
            count = 0

    # Flip reversed representation
    ref_dollars = rev_dollars.rstrip(',')[::-1]

    # Final format
    ref_amt = f'${ref_dollars}.{cents}'
    return ref_amt


import numpy as np
import math
from mpl_toolkits import mplot3d
import matplotlib.pyplot as plt
import sympy

# Дано:
alpha = 1
f = lambda x, y: alpha * (x**2 - y)**2 + (x - 1)**2

X0 = np.array([-1, -2])
eps = 1e-3

Grad_f = lambda x, y: np.array([4*alpha*x*(x**2 - y) + 2*x - 2, alpha*(-2*x**2 + 2*y)])

# Параметры методов:
kappa0 = 1
nu = 0.95
omega = 0.5

def MethodGoldenRatio(f, b, a = 0, e = eps * 1e-1):
    tau = (math.sqrt(5) + 1) / 2
    Ak, Bk = a, b
    lk = Bk - Ak
    Xk1 = Bk - (Bk - Ak) / tau
    Xk2 = Ak + (Bk - Ak) / tau
    y1, y2 = f(Xk1), f(Xk2)
    while lk >= e:
        if y1 >= y2:
            Ak = Xk1
            Xk1 = Xk2
            Xk2 = Ak + Bk - Xk1
            y1 = y2
            y2 = f(Xk2)
        else:
            Bk = Xk2
            Xk2 = Xk1
            Xk1 = Ak + Bk - Xk2
            y2 = y1
            y1 = f(Xk1)
        lk = Bk - Ak
    return (Ak + Bk) / 2

def MethodsGradientDescent(flag):
    fun = lambda X: f(X[0], X[1])
    w = lambda X: -Grad_f(X[0], X[1])
    X = X0
    kappa_k = kappa0
    NormW = []
    Xk = []
    while True:
        Xk.append(X)
        Wk = w(X)
        NormW.append(np.linalg.norm(Wk))
        if NormW[-1] <= eps:
            break
        fk = fun(X)
        if flag == 0:
            phi = lambda kappa: fun(X + kappa * Wk)
            kappa_k = MethodGoldenRatio(phi, 2.5)
            X = X + kappa_k * Wk
        elif flag == 1:
            Xcurr = X + kappa_k * Wk
            while fk - fun(Xcurr) <= omega * kappa_k * NormW[-1]**2:
                kappa_k *= nu
                Xcurr = X + kappa_k * Wk
            X = Xcurr
    return X, Xk, NormW

print()
print('Методы градиентного спуска')
print('Дано:')
print('Целевая функция:        f(x, y) =', f(sympy.Symbol('x'), sympy.Symbol('y')))
print('Начальное приближение:  X0 =', X0)
print('Точность вычисления:    Eps =', eps)

for i in range(2):
    print()
    print('_' * 100)
    if i == 0:
        print('*' * 30, ' Метод наискорейщего спуска ', '*' * 30)
    elif i == 1:
        print('*' * 20, ' Метод градиентного спуска с дроблением шага ', '*' * 20)
    X, Xk, NormW = MethodsGradientDescent(i)
    print('Точка минимума функции:             Xmin =', X)
    print('Значение функции в точке минимума:  f(Xmin) =', f(X[0], X[1]))
    print('Количество итераций:                k =', len(NormW)-1)


'''
rotations.py rotates cartesian points about x,y,z axis and/or specified vector.
See rotationsVid.py for a more extensive example with outputted graphics with rotations.py at work
'''
#Data array format: [[x1,y1,z1],[x2,y2,z2],[x3,y3,z3],...[xn,yn,zn]]
#Specified vector array: [vx,vy,vz]
#Output array format: [[xr1, yr1, zr1],[xr2,yr2,zr2],[xr3,yr3,zr3],...[xrn,yrn,zrn]]
#Theta (angle) input in degrees

import numpy as np #only requirement

#Rotation definitions
def xrotate(positions, theta): # rotation about the x-axis
    theta = np.deg2rad(theta)
    xr = np.array([[1,0,0],[0,np.cos(theta),-np.sin(theta)],[0,np.sin(theta),np.cos(theta)]])
    return np.dot(positions, xr)

def yrotate(positions, theta): # rotation about the y-axis
    theta = np.deg2rad(theta)
    yr = np.array([[np.cos(theta),0,np.sin(theta)],[0,1,0],[-np.sin(theta),0,np.cos(theta)]])
    return np.dot(positions, yr)

def zrotate(positions, theta): # rotation about the z-axis
    theta = np.deg2rad(theta)
    zr = np.array([[np.cos(theta),-np.sin(theta),0],[np.sin(theta),np.cos(theta),0],[0,0,1]])
    return np.dot(positions, zr)

def vrotate(positions, theta, vec): # rotation about specified vector
    vec = normalize(vec)
    theta = np.deg2rad(theta)
    ux = vec[0]
    uy = vec[1]
    uz = vec[2]
    #Utilize known rotation matrix
    rot = np.array([[np.cos(theta) + ux*ux*(1-np.cos(theta)), ux*uy*(1-np.cos(theta))-uz*np.sin(theta), ux*uz*(1-np.cos(theta))+uy*np.sin(theta)],
                    [uy*ux*(1-np.cos(theta))+uz*np.sin(theta), np.cos(theta) + uy*uy*(1-np.cos(theta)), uy*uz*(1-np.cos(theta))-ux*np.sin(theta)],
                   [uz*ux*(1-np.cos(theta))-uy*np.sin(theta), uz*uy*(1-np.cos(theta))+ux*np.sin(theta), np.cos(theta)+uz*uz*(1-np.cos(theta))]])
    return np.dot(positions, rot)

def normalize(vec): #normalize function used in vrotate
    norm = np.linalg.norm(vec)
    return vec/norm

def main():
    #Example code of how to run
    print("Example of 4 points")
    a = np.random.randint(10,size=(4,3))
    print("Array = \n {}".format(a))
    theta = 45
    print("Theta = {} deg".format(theta))
    xrot = xrotate(a, theta)
    yrot = yrotate(a,theta)
    zrot = zrotate(a, theta)
    vec = [1,1,1]
    vrot = vrotate(a, theta, vec)
    print("x-rotation array = \n{}".format(xrot))
    print("y-rotation array = \n{}".format(yrot))
    print("z-rotation array = \n{}".format(zrot))
    print("v-rotation about vector [1,1,1] array = \n{}".format(vrot))

if __name__ == "__main__":
    main()


import numpy as np

def calc_mean_squared_error(y_N, yhat_N):
    ''' Compute the mean squared error given true and predicted values

    Args
    ----
    y_N : 1D array, shape (N,)
        Each entry represents 'ground truth' numeric response for an example
    yhat_N : 1D array, shape (N,)
        Each entry representes predicted numeric response for an example

    Returns
    -------
    mse : scalar float
        Mean squared error performance metric
        .. math:
            mse(y, \hat{y}) = \frac{1}{N} \sum_{n=1}^N (y_n - \hat{y}_n)^2

    Examples
    --------
    >>> y_N = np.asarray([-2, 0, 2], dtype=np.float64)
    >>> yhat_N = np.asarray([-4, 0, 2], dtype=np.float64)
    >>> calc_mean_squared_error(y_N, yhat_N)
    1.3333333333333333
    '''

    if yhat_N.shape[0] == 0:
        return 0
    return np.sum((yhat_N - y_N)**2)/yhat_N.shape[0]

# y_N = np.asarray([-2, 0, 2], dtype=np.float64)
# yhat_N = np.asarray([-4, 0, 2], dtype=np.float64)
# print(calc_mean_squared_error(y_N, yhat_N))

def calc_mean_absolute_error(y_N, yhat_N):
    ''' Compute the mean absolute error given true and predicted values

    Args
    ----
    y_N : 1D array, shape (N,)
        Each entry represents 'ground truth' numeric response for an example
    yhat_N : 1D array, shape (N,)
        Each entry representes predicted numeric response for an example

    Returns
    -------
    mae : scalar float
        Mean absolute error performance metric
        .. math:
            mae(y, \hat{y}) = \frac{1}{N} \sum_{n=1}^N | y_n - \hat{y}_n |

    Examples
    --------
    >>> y_N = np.asarray([-2, 0, 2], dtype=np.float64)
    >>> yhat_N = np.asarray([-4, 0, 2], dtype=np.float64)
    >>> calc_mean_absolute_error(y_N, yhat_N)
    0.6666666666666666
    '''
    if yhat_N.shape[0] == 0:
        return 0
    return np.sum(abs(yhat_N - y_N))/yhat_N.shape[0]

# y_N = np.asarray([-2, 0, 2], dtype=np.float64)
# yhat_N = np.asarray([-4, 0, 2], dtype=np.float64)
# print(calc_mean_absolute_error(y_N, yhat_N))


"""Implementation of the Logistic sigmoid function
    Reference: https://en.wikipedia.org/wiki/Logistic_function

    Logistic functions are often used in neural networks to introduce
    nonlinearity in the model or to clamp signals to within a specified
    range. A popular neural net element computes a linear combination of
    its input signals, and applies a bounded sigmoid function to the result;
    this model can be seen as a "smoothed" variant of the classical threshold
    neuron.

"""

from .activation_function import ActivationFunction
import numpy as np

class Sigmoid(ActivationFunction):

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

    @classmethod
    def eval(cls, x: np.float) -> np.float:
        """Evaluates the sigmoid function for a given x value
           Reference: https://en.wikipedia.org/wiki/Logistic_function#Derivative
        """
        return 1 / (1 + np.exp(-x))

    @classmethod
    def derivative(cls, x: np.float) -> np.float:
        """Evaluates the derivative of the logitistic function for a given x value
           Reference: https://en.wikipedia.org/wiki/Logistic_function#Derivative
        """
        return cls.eval(x) * (1 - cls.eval(x))


#Function to apply circle intersection
    #Inputs:
        #point 1, distance 1, point 2, distance 2
        #np.a, float, np.a, float

#Standard imports
import math
import numpy as np

def circleIntersection(point_1,dist_1,point_2,dist_2): #*1

    x_0 = point_1[0]
    y_0 = point_1[1]
    r_0 = dist_1

    x_1 = point_2[0]
    y_1 = point_2[1]
    r_1 = dist_2

    d = math.sqrt((x_1-x_0)**2 +(y_1-y_0)**2)
    a = (r_0**2 - r_1**2 + d**2)/(2*d)
    h = np.sqrt(r_0**2 - a**2)

    x_2 = x_0+a*(x_1-x_0)/d
    y_2 = y_0+a*(y_1-y_0)/d

    x_31=x_2 + h*(y_1-y_0)/d
    y_31=y_2 - h*(x_1-x_0)/d

    x_32=x_2 - h*(y_1-y_0)/d
    y_32=y_2 + h*(x_1-x_0)/d

    return (x_31,y_31),(x_32,y_32)

#NOTES: Does not test for irregular conditions, which really shouldn't happen.
    #It should be mathmatically impossible
    #But they keep hapending, so it is critical to do so

#SOURCES: https://stackoverflow.com/questions/3349125/circle-circle-intersection-points *1


import numpy as np

# Pearson's correlation coefficient
# r = summation[(x - x_mean)(y - y_mean)] / sqrt[summation((x - x_mean)^2) * summation((y - y_mean)^2)]
# http://www.datasciencemadesimple.com/wp-content/uploads/2017/07/CORRELATION-COEFFICIENT-FORMULA.png
def pearson_correlation(feature_1, feature_2, generate_sd=False):
    feature_1_mean = np.mean(feature_1, axis=0)
    feature_2_mean = np.mean(feature_2, axis=0)
    feature_1_minus_mean = feature_1 - feature_1_mean
    feature_2_minus_mean = feature_2 - feature_2_mean
    feature_1_minus_mean_squared = np.square(feature_1_minus_mean)
    feature_2_minus_mean_squared = np.square(feature_2_minus_mean)
    features_diff_product = np.multiply(feature_1_minus_mean, feature_2_minus_mean)
    coefficient = (np.sum(features_diff_product, axis=0) /
                   np.sqrt(np.multiply(np.sum(feature_1_minus_mean_squared, axis=0),
                                       np.sum(feature_2_minus_mean_squared, axis=0))))
    if generate_sd:
        feature_1_sd = np.sqrt(np.sum(feature_1_minus_mean_squared)/(np.size(feature_1_minus_mean_squared) - 1))
        feature_2_sd = np.sqrt(np.sum(feature_2_minus_mean_squared)/(np.size(feature_2_minus_mean_squared) - 1))
        return coefficient, feature_1_sd, feature_2_sd
    return coefficient

# outcome/dependent feature residual (error) function
def residual_error(actual_y_matrix, predicted_y_matrix):
    return np.subtract(actual_y_matrix, predicted_y_matrix)

# Sum of squared residuals
def rss(residual_vector):
    return np.sum(np.square(residual_vector), axis=0)

def l2_norm(vector):
    return np.sqrt(np.sum(np.square(vector)))

# sigmoid / logistic function
def sigmoid(x):
    return 1.0 / (1.0 + np.exp(-x))

# softmax function (returns probability of a class)
# input is most commonly product of parameters (theta) and variables (values)
def softmax(x):
    return np.exp(x)/np.sum(np.exp(x), axis=0)


# calculate_pi.py
# MIT License
# github.com/viktor40/HammerBotPython

"""
Calculate the value of π using the Leibniz formula for pi: https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80.
This is a rather slow way to calculate π since the series converges slowly. This was mostly a personal exercise.

ITERATIONS will determine the number of iterations to perform to calculate the value of pi.
"""

import matplotlib.pyplot as plt
import numpy as np

ITERATIONS = 50
PLOT = True
x, y = [], []

def calculate_pi(iterations: int):
    """
    Use leibniz series to calculate pi.
    :param iterations: sets the number of iterations to be executed. After this has been reach the program will stop
    :param plot: set if you want to plot the values
    :yield: the function will yield the nth iteration of the leibniz series, together with n.
    """
    pi_ = 0
    for n_ in range(0, iterations):
        pi_ += 4 * ((-1) ** n_) / (2 * n_ + 1)
        yield pi_, n_

pi_iterator = calculate_pi(ITERATIONS)
for value in pi_iterator:
    pi, n = value
    print("After {} iteration{}, the value of π equals {}".format(n + 1, "s" if n else "", pi))

    if PLOT:
        x.append(n)
        y.append(pi)

if PLOT:
    plt.plot(x, y, color='blue')
    plt.plot([0, ITERATIONS], [np.pi, np.pi], color='red')
    plt.xlabel('Number of iterations n')
    plt.ylabel('Value of pi')
    plt.show()


# Python script of the basic operations numpy
# Author: Sandeep Mewara
# Location: Learn By Insight
# Github: https://github.com/samewara/python-examples/blob/master/numpy-basic.py
# #####################################
# To add a new cell, type '# %%'
# To add a new markdown cell, type '# %% [markdown]'
# %% [markdown]
# ## Numpy (Numerical python)
#
# Packages for numerical computation designed for efficiency to work on large data sets
# %% [markdown]
# *** Initalizing Matrix - via LIST ***

# %%
import numpy as np

# %%
#mat = np.array([1, 2, 3, 4, 5])

lst = [1,2,3,4,5]
mat = np.array(lst)
mat

# %%
print("mat.shape:", mat.shape)
print("mat[2]:", mat[2])
print("mat.flatten():", mat.flatten())

# %% [markdown]
# *** Initializing Matrix - via NULL MATRIX (ZEROS) ***

# %%
# 5x4 numpy array where the ith row and jth col has the entry i+j
x,y=5,4
matij = np.zeros((x,y))
matij

# %%
for i in range(x):
    for j in range(y):
        matij[i][j] = i+j

matij

# %% [markdown]
# *** Initializing Matrix - via IDENTITY MATRX ***

# %%
size=3
mat_i = np.identity(size)
mat_i

# %% [markdown]
# *** Initializing Matrix - via ONES MATRIX ***

# %%
m,n=3,2
mat_1=np.ones((m,n))
mat_1

# %% [markdown]
# *** Transpose ***

# %%
mat_1=np.ones((m,n))
mat_1_transpose = mat_1.T
mat_1_transpose

# %% [markdown]
# *** Reshape ***

# %%
mat_1=np.ones((m,n))
mat_1_reshape = mat_1.reshape(1,6) #size is still the same 3*2 = 1*6
mat_1_reshape

# %% [markdown]
# *** Indexing ***

# %%
# An array of grand slam information
Players = ['Roger Federer', 'Margaret Court', 'Rafael Nadal', 'Maria Sharapova', 'Pete Sampras', 'Steffi Graf', 'Novak Djokovic']
Titles = [20, 24, 17, 23, 15, 22, 14]
Finals = [30, 29, 25, 29, 24, 31, 19]

grand_slam=np.array([Players,Titles,Finals])
grand_slam

# %%
# select all players from the matrix
grand_slam[0,::1] #0th row & all column, then the indexing of list :: matrix[row, column], row, column are list so start:stop:step

# %%
# select alternate players with data and ten transpose for easy reading

grand_slam[:,::2].T #all of row and all of columns but step jump 2

# %% [markdown]
# *** Simulation #1 ***
# Simulate the process of rolling a 6 faced die 100 times and find the probability of getting a number that is divisible by 3.

# %%

trials = 100
draws = np.random.randint(1,7,trials) # Generate 10 random integers in the range 1-6
draws

# %%
draws[draws%3==0] #masking - give all the values back that satisfy the mask in the list

# %%
probab_3 = len(draws[draws%3==0])/trials # or can use .size of the list instead of using len
print("probability for numbers divisble by 3:", probab_3)

# %%
# simulation with multiple counts
trials_multiple = 100,1000,10000,100000
for i in range(len(trials_multiple)):
    draws = np.random.randint(1,7,trials_multiple[i])
    probab_3 = len(draws[draws%3==0])/trials_multiple[i] # or can use .size of the list instead of using len
    print("probability for numbers divisble by 3 in {0} trials:{1}".format(trials_multiple[i], probab_3))

# %% [markdown]
# *** Simulation #2 ***
# Two six-faced dice are rolled. Find the probability that their faces sum to 4.

# %%
trials_multiple = 100,1000,10000,100000
for i in range(len(trials_multiple)):
    draws1, draws2 = np.random.randint(1,7,trials_multiple[i]), np.random.randint(1,7,trials_multiple[i])
    probab_sum4 = len(draws1[draws1+draws2 == 4])/trials_multiple[i] #filter the values
    print("probability for sum be 4 in {0} trials:{1}".format(trials_multiple[i], probab_sum4))

# %% [markdown]
# *** Reading CSV Files ***
#
# data-file: economy_data.CSV
#
# sample data on a quarterly basis on Indian economy sampled from RBI database containing Gross Value added, Forex, Borrowings for the period 2016 - Q1 2018. Find the correlation among different indicators

# %%
f=open('./data-files/numpy/economy_data.csv')
data = f.read().split('\n')
data

# %%
# For better data read
for content in data[:-1]:
    print("{0:>9s}  {1:9s} {2:7s} {3:10s} {4:4s}".format(*(content.split(','))))

# %%
#read data into numpy array
#skip the header and 1st column
data_np = np.genfromtxt('./data-files/numpy/economy_data.csv',delimiter=",",skip_header=1,usecols=[1,2,3,4],dtype="float32")
print("Data shape:",data_np.shape, ", Data size:",data_np.size, ", Data type:",data_np.dtype)
print(data_np)

# %%
# Correlation between Forex & Borrowing

gva = data_np[:,0] #select all rows from 1st column
forex = data_np[:,1]
borrowing = data_np[:,2]
cpi = data_np[:,3]

print("Corrleation coefficients forex,borrowing\n",np.corrcoef(forex,borrowing))

# %%
# Forex Data in Q2 (17-18)

data_np[5,:]

# %%
# rows where cpi is less than 3

data_np[data_np[:,-1] < 3]

# %%
# average cpi for all the years

np.round(np.mean(cpi),2)

# %% [markdown]
# *** Broadcasting ***
#
# When two arrays are of different shapes this describes how the arithmetic works between them.
#
# Arrays are compatible for broadcasting when the trailing dimension match or either of them is of length 1, then the broadcasting is done over the missing dimension or length 1 dimension

# %%
#When trailing dimension match
a=np.array([[1,2,3],[3,4,5]])
b=np.array([8,9,10])
print("shape of a",a.shape," shape of b",b.shape)
a+b #broadcasting on row

# %%
#trailing dimension is 1
b=np.array([[10],[12]])
print("shape of a",a.shape," shape of b",b.shape)
a+b #broadcasting on column

# %%
#vertical/horizontal stacking
a = np.identity(3)
b = np.array([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9]])
print("vertical:\n",np.vstack((a,b)))
print("horizontal:\n",np.hstack((a,b)))

# %%
np.concatenate([a,b],axis=0) #same as vstack

# %% [markdown]
# *** Image Processing ***

# %%
# show an image using matpotlib
import matplotlib.image as mimg
import matplotlib.pyplot as plt
import numpy as np
get_ipython().run_line_magic('matplotlib', 'inline')
img = mimg.imread('./data-files/numpy/hulk.png') # read an image

original_image_pixels = np.array(img,dtype='float32')  # make an numpy array
print (original_image_pixels) # Note the values are between 0 and 1
plt.imshow(original_image_pixels,cmap='gray'); # use a gray scale color map

# %%
# increase contrast pixel by pixel
contrast_enhanced_pixels = original_image_pixels*3 #increase the contrast; but some values may now be more than 1
contrast_enhanced_pixels[contrast_enhanced_pixels > 1] = 1 #clamp the values to max 1
plt.imshow(contrast_enhanced_pixels,cmap='gray')

# %%


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun Feb 24 22:44:03 2019

@author: alankar
"""

import numpy as np
import matplotlib.pyplot as plt
from scipy import fftpack

dow = np.loadtxt('dow2.txt')
N = len(dow)
t = np.arange(0,N,1)

sampling = 1/(t[1]-t[0])
f = np.linspace(0,sampling,N//2+1)
dow_fft = np.fft.rfft(dow)
plt.figure(figsize=(13,10))
plt.semilogy(f,np.abs(dow_fft)**2)
plt.grid()
plt.xlabel(r'f',size=18)
plt.ylabel(r'Power Spectrum $P(f)$',size=20)
plt.title(r'FFT[DOW]', size=21)
plt.tick_params(axis='both', which='major', labelsize=15)
plt.tick_params(axis='both', which='minor', labelsize=12)
plt.savefig('5_1.png')
plt.show()

plt.figure(figsize=(13,10))
plt.plot(t,dow,label='Original Data')
plt.grid()
plt.xlabel(r'Days',size=18)
plt.ylabel(r'DOW',size=20)
plt.title(r'DOW Industrial Average', size=21)
plt.tick_params(axis='both', which='major', labelsize=15)
plt.tick_params(axis='both', which='minor', labelsize=12)
plt.savefig('5_2.png')

compress = lambda data,freq,percent_acc: np.piecewise(data,[(freq-freq[0])<=\
                                                       (percent_acc/100)*(freq[-1]-freq[0])],[lambda data: data,0.])

dow_fft2 = compress(dow_fft,f,2)
dow2 = np.fft.irfft(dow_fft2)
plt.plot(t,dow2,label=r'%d percent Fourier modes Accepted'%2)
plt.legend(loc='best', prop={'size': 16})
plt.show()

#---------------------------------------------------------------------------------

f = np.linspace(0,sampling,N)
n = np.arange(0,N,1)
plt.figure(figsize=(13,10))
plt.plot(t,dow,label='Original Data')
plt.grid()
plt.xlabel(r'Days',size=18)
plt.ylabel(r'DOW',size=20)
plt.title(r'DOW Industrial Average', size=21)
plt.tick_params(axis='both', which='major', labelsize=15)
plt.tick_params(axis='both', which='minor', labelsize=12)
dow_dct = fftpack.dct(dow, norm='ortho')
dow_dct2 = compress(dow_dct,f,2)
dow2 = fftpack.idct(dow_dct2, norm='ortho')
plt.plot(t,dow2,label=r'%d percent Cosine Transform modes Accepted'%2)
plt.legend(loc='best', prop={'size': 16})
plt.savefig('5_3.png')
plt.show()


import numpy as np
import matplotlib.pyplot as plt
from numpy.random import sample
import seaborn as sns

from multiple_linear_and_polynomial_regression.polynomial_LR import polynomial_linear_regression

sns.set()

def make_polynomial(X, degrees):
    number_of_inputs = len(X)
    polynomial_data = [np.ones(number_of_inputs)]
    for degree in range(degrees):
        polynomial_data.append(np.power(X, degree+1))

    return np.vstack(polynomial_data).T

def fit_and_display(X, y, sample_number, degrees):
    number_of_inputs = len(X)
    train_samples_indeces = np.random.choice(number_of_inputs, sample_number)
    X_train = X[train_samples_indeces]
    y_train = y[train_samples_indeces]

    plt.figure()
    plt.scatter(X_train, y_train)
    plt.show()

    X_train_polynomial = make_polynomial(X_train, degrees)
    w, _ = polynomial_linear_regression(X_train_polynomial, y_train)

    X_polynomial = make_polynomial(X, degrees)
    y_hat = X_polynomial.dot(w)

    plt.figure()
    plt.plot(X,y, label='original data')
    plt.plot(X,y_hat, label='predicted data')
    plt.plot(X_train, y_train, 'r*', label='train data')
    plt.title("Degree = %d" %degrees)
    plt.legend()
    plt.show()

def mse(x, y):
    d = x - y
    return d.dot(d) / len(d)

def plot_train_vs_test_curves(X, y, sample_number=20, max_degrees=20):
    number_of_inputs = len(X)
    train_samples_indeces = np.random.choice(number_of_inputs, sample_number)
    X_train = X[train_samples_indeces]
    y_train = y[train_samples_indeces]

    test_indeces = [index for index in range(number_of_inputs) if index not in train_samples_indeces]
    X_test = X[test_indeces]
    y_test = y[test_indeces]

    MSEs_train = []
    MSEs_test = []

    for degree in range(max_degrees+1):
        X_train_polynomial = make_polynomial(X_train, degree)
        w, y_hat_train = polynomial_linear_regression(X_train_polynomial, y_train)
        mse_train = mse(y_train, y_hat_train)

        MSEs_train.append(mse_train)

        X_test_polynomial = make_polynomial(X_test, degree)
        y_hat_test = X_test_polynomial.dot(w)
        mse_test = mse(y_test, y_hat_test)

        MSEs_test.append(mse_test)

    plt.figure()
    plt.plot(MSEs_train, label='Train MSEs')
    plt.plot(MSEs_test, label='Test MSEs')
    plt.ylabel('MSEs')
    plt.xlabel('Polynomial Degrees')
    plt.legend()
    plt.show()

if __name__ == '__main__':

    number_of_points = 100
    X = np.linspace(0, 6 * np.pi, number_of_points)
    y = np.sin(X)

    plt.figure()
    plt.plot(X,y)
    plt.show()

    for degrees in (5, 6, 7, 8, 9):
        fit_and_display(X, y, sample_number=10, degrees=degrees)

    plot_train_vs_test_curves(X, y)


import numpy as np

def calculate_distance(rA, rB):
    """
    This function calculates the distance between two points

    Parameters
    ----------
    rA, rB : np.ndarray
        The coordinates of each point.

    Returns
    -------
    distance : float
        The distance between two vectors.

    Examples
    --------
    >>> r1 = np.array([0,0,0])
    >>> r2 = np.array([3.0,0,0,])
    >>> calculate_distance(r1_r2)
    3.0
    """

    dist_vec = (rA-rB)
    distance = np.linalg.norm(dist_vec)
    return distance

def calculate_angle(rA, rB, rC, degrees=False):
    # Calculate the angle between three points. Answer is given in radians by default, but can be given in degrees
    # by setting degrees=True
    ab_diff = rA - rB
    bc_diff = rB - rC
    theta = np.arccos(np.dot(ab_diff, bc_diff)/(np.linalg.norm(ab_diff)*np.linalg.norm(bc_diff)))

    if degrees:
        return np.degrees(theta)
    else:
        return theta


import numpy as np

from utils import inc_index, dec_index

def gradient_approximation(f,x,h=1e-8):
    '''
    Numerical approximation of gradient for function f using forward differences.
    Args:
        f (lambda expression): definition of function f.
        x (array): numpy array that holds values where gradient will be computed.
        h (float): step size for forward differences, tipically h=1e-8
    Returns:
        gf (array): numerical approximation to gradient of f.
    '''
    n = x.size
    gf = np.zeros(n)
    f_x = f(x)
    for i in np.arange(n):
        inc_index(x,i,h)
        gf[i] = f(x) - f_x
        dec_index(x,i,h)
    return gf/h
def Hessian_approximation(f,x,h=1e-6):
    '''
    Numerical approximation of Hessian for function f using forward differences.
    Args:
        f (lambda expression): definition of function f.
        x (array): numpy array that holds values where Hessian will be computed.
        h (float): step size for forward differences, tipically h=1e-6
    Returns:
        Hf (array): numerical approximation to Hessian of f.
    '''
    n = x.size
    Hf = np.zeros((n,n))
    f_x = f(x)
    for i in np.arange(n):
        inc_index(x,i,h)
        f_x_inc_in_i = f(x)
        for j in np.arange(i,n):
            inc_index(x,j,h)
            f_x_inc_in_i_j = f(x)
            dec_index(x,i,h)
            f_x_inc_in_j = f(x)
            dif = f_x_inc_in_i_j-f_x_inc_in_i-f_x_inc_in_j+f_x
            Hf[i,j] = dif
            if j != i:
                Hf[j,i] = dif
            dec_index(x,j,h)
            inc_index(x,i,h)
        dec_index(x,i,h)
    return Hf/h**2


import matplotlib.pyplot as plt
import numpy as np

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

def plot_sigmoid():
	z = np.arange(-7, 7, 0.1)
	phi_z = sigmoid(z)

	plt.plot(z, phi_z)
	plt.axvline(0.0, color='k')
	plt.ylim(-0.1, 1.1)
	plt.xlabel('z')
	plt.ylabel('$\phi (z)$')

	# y axis ticks and gridline
	plt.yticks([0.0, 0.5, 1.0])
	ax = plt.gca()
	ax.yaxis.grid(True)

	plt.tight_layout()
	# plt.savefig('./figures/sigmoid.png', dpi=300)
	plt.show()

def cost_1(z):
    return - np.log(sigmoid(z))

def cost_0(z):
    return - np.log(1 - sigmoid(z))

def plot_cost():
	z = np.arange(-10, 10, 0.1)
	phi_z = sigmoid(z)

	c1 = [cost_1(x) for x in z]
	plt.plot(phi_z, c1, label='J(w) if y=1')

	c0 = [cost_0(x) for x in z]
	plt.plot(phi_z, c0, linestyle='--', label='J(w) if y=0')

	plt.ylim(0.0, 5.1)
	plt.xlim([0, 1])
	plt.xlabel('$\phi$(z)')
	plt.ylabel('J(w)')
	plt.legend(loc='best')
	plt.tight_layout()
	# plt.savefig('./figures/log_cost.png', dpi=300)
	plt.show()

def main():
	plot_sigmoid()
	plot_cost()

if __name__ == '__main__':
    main()


#!/usr/bin/env python

#This script will introduce us to Scipy, a library useful for scientific computation
#Adapted from https://docs.scipy.org/doc/scipy/reference/tutorial/integrate.html

#Integration

#Using known function
print("Integration:")
from scipy.integrate import quad
def integrand(x, a, b):
    return a*x**2 + b

a = 2
b = 1
I = quad(integrand, 0, 1, args=(a,b))
print("Integral of 2x^2 + 1 from x=0..1: {}".format(I[0]))

#Using arbitrarily spaced samples
import numpy as np
def f1(x):
   return x**2

x = np.array([1,3,4])
y1 = f1(x)
from scipy.integrate import simps
I1 = simps(y1, x)
print("Integral of x^2 evaluated at {}: {}".format(x.tolist(), I1))

#Optimization
print("\nOptimization:")
import numpy as np
from scipy.optimize import minimize
def rosen(x):
    """The Rosenbrock function"""
    return sum(100.0*(x[1:]-x[:-1]**2.0)**2.0 + (1-x[:-1])**2.0)

x0 = np.array([1.3, 0.7, 0.8, 1.9, 1.2])
print("Nelder-Mead simplex method:")
res = minimize(rosen, x0, method='nelder-mead', options={'xtol': 1e-8, 'disp': True})

def rosen_der(x):
    xm = x[1:-1]
    xm_m1 = x[:-2]
    xm_p1 = x[2:]
    der = np.zeros_like(x)
    der[1:-1] = 200*(xm-xm_m1**2) - 400*(xm_p1 - xm**2)*xm - 2*(1-xm)
    der[0] = -400*x[0]*(x[1]-x[0]**2) - 2*(1-x[0])
    der[-1] = 200*(x[-1]-x[-2]**2)
    return der

print("Broyden-Fletcher-Goldfarb-Shanno method:")
res1 = minimize(rosen, x0, method='BFGS', jac=rosen_der, options={'disp': True})

#Linear Algebra

print("\nLinear algebra:")

#inverse:
import numpy as np
A = np.array([[1,3,5],[2,5,1],[2,3,8]])
print("A:\n{}\n".format(A))
print("A^(-1):\n{}\n".format(np.linalg.inv(A)))

#Systems solution
A = np.array([[1, 2], [3, 4]])
b = np.array([[5], [6]])
print("A:\n{}\n".format(A))
print("b:\n{}\n".format(b))
print("A^(-1) b:\n{}\n".format(np.linalg.solve(A, b)))
print("det(A): {}".format(np.linalg.det(A)))
print("||A||_inf: {}".format(np.linalg.norm(A, np.inf)))


#
# statistics.py
#

import copy
import math
import numpy
import quicksort

def mean(data):

    return sum(data)/len(data)

def median(data):

    index = len(data)//2
    is_odd = len(data) % 2 > 0
    data = quicksort.sort(copy.deepcopy(data))

    return data[index] if is_odd else (data[index]+data[index-1])/2

def mode(data):

    lookup = {}

    for n in data: lookup[n] = 0
    for n in data: lookup[n] += 1

    return sorted(lookup, key=lookup.__getitem__)[-1]

def standard_deviation(data=[], is_sample=False):

    dmean = mean(data)
    diffs = [math.pow(n-dmean, 2) for n in data]
    divisor = len(data)-1 if is_sample else len(data)

    return math.sqrt(sum(diffs)/divisor)

def variance(data, is_sample=False):

    return math.pow(standard_deviation(data, is_sample), 2)

def covariance(x=[], y=[], is_sample=False):

    xmean = mean(x)
    ymean = mean(y)

    xvect = [(n-xmean) for n in x]
    yvect = [(n-ymean) for n in y]

    divisor = len(x)-1 if is_sample else len(x)
    result = numpy.dot(xvect, yvect) / divisor

    return result

def correlation(x=[], y=[], is_sample=False):

    xstd = standard_deviation(x, is_sample)
    ystd = standard_deviation(y, is_sample)

    return covariance(x, y, is_sample) / xstd / ystd


import numpy as np

def bisection(f,a,b,epsilon=1.0e-6):
	"""Find the root of the function f via bisection where the root lies within [a,b]
	Args:
		f: function to find root of
		a: left-side of interval
		b: right-side of interval
		epsilon: tolerance
	Returns:
		estimate of root
	"""

	assert (b>a)
	assert (f(a)*f(b) < 0)
	delta = b - a
	print("We expect",int(np.ceil(np.log(delta/epsilon)/np.log(2))),"iterations")
	iterations = 0
	while (delta > epsilon):
		c = (a+b)*0.5
		if (f(a)*f(c) < 0):
			b = c
		elif (f(b)*f(c) < 0):
			a=c
		else:
			return c
		delta = b-a
		iterations += 1
	print("It took",iterations,"iterations")
	return c #return midpoint of interval

def false_position(f,a,b,epsilon=1.0e-6):
	"""Find the root of the function f via false position where the root lies within [a,b]
	Args:
		f: function to find root of
		a: left-side of interval
		b: right-side of interval
		epsilon: tolerance
	Returns:
		estimate of root
	"""
	assert (b>a)
	assert (f(a)*f(b) < 0)
	delta = b - a
	iterations = 0
	residual = 1.0
	while (np.fabs(residual) > epsilon):
		m = (f(b)-f(a))/(b-a)
		c = a - f(a)/m
		if (f(a)*f(c) < 0):
			b=c
		elif (f(b)*f(c) < 0):
			a=c
		else:
			print("It took",iterations,"iterations")
			return c
		residual = f(c)
		iterations += 1
	print("It took",iterations,"iterations")
	return c #return c

def ridder(f,a,b,epsilon=1.0e-6):
	"""Find the root of the function f via Ridder's Method where the root lies within [a,b]
	Args:
		f: function to find root of
		a: left-side of interval
		b: right-side of interval
		epsilon: tolerance
	Returns:
		estimate of root
	"""
	assert (b>a)
	assert (f(a)*f(b) < 0)
	delta = b - a
	iterations = 0
	residual = 1.0
	while (np.fabs(residual) > epsilon):
		c = 0.5*(b+a)
		d = 0.0
		if (f(a) - f(b) > 0):
			d = c + (c-a)*f(c)/np.sqrt(f(c)**2-f(a)*f(b))
		else:
			d = c - (c-a)*f(c)/np.sqrt(f(c)**2-f(a)*f(b))
		#now see which part of interval root is in
		if (f(a)*f(d) < 0):
			b=d
		elif (f(b)*f(d) < 0):
			a=d
		residual = f(d)
		iterations += 1
	print("It took",iterations,"iterations")
	return d #return c