import pytest
import random
import copy
import numpy as np

from produto_interno.produto_interno import norma, produto_interno_canonico, produto_interno, angulo, projecao_escalar, projecao_vetorial, v_ortogonal_u, normalizar, gram_schmidt, ortogonalizar_vetores, normalizar_vetores, ortonormalizar_vetores

def test_norma_1_r1():
    assert norma(np.array([1])) == 1

def test_norma_2_r1():
    assert norma(np.array([2])) == 2

def test_norma_0_1_r1():
    assert norma(np.array([0, 1])) == np.linalg.norm(np.array([0, 1]))

def test_produto_interno_canonico_1_r1_1_r1():
    u = np.array([1])
    v = np.array([1])
    assert produto_interno_canonico(u, v) == np.inner(u, v)

def test_produto_interno_canonico_2_r1_7_r1():
    u = np.array([2])
    v = np.array([7])
    assert produto_interno_canonico(u, v) == np.inner(u, v)

def test_produto_interno_canonico_r2_aleatorios():
    u = np.random.randint(10, size=2)
    v = np.random.randint(10, size=2)
    assert produto_interno_canonico(u, v) == np.inner(u, v)

def test_produto_interno_canonico_rn_aleatorios():
    n = np.random.randint(100000, size=1)
    u = np.random.randint(n, size=2)
    v = np.random.randint(n, size=2)
    assert produto_interno_canonico(u, v) == np.inner(u, v)

def test_produto_interno_nao_canonico_r2():
    u = np.array([5, 2])
    v = np.array([-2, 3])
    funcional_linear = lambda u, v: 2*u[0]*v[0] + u[0]*v[1] + u[1]*v[0] + u[1]*v[1]
    assert produto_interno(u, v, funcional_linear = funcional_linear) == -3

def test_norma_canonica_r2():
    u = np.array([2**0.5, 0])
    assert norma(u) == 2**(0.5)

def test_norma_nao_canonica_r2():
    u = np.array([2**0.5, 0])
    funcional_linear = lambda u, v: 2*u[0]*v[0] + u[0]*v[1] + u[1]*v[0] + u[1]*v[1]
    assert norma(u, funcional_linear = funcional_linear) == 2

def test_angulo_dois_vetores_ortogonais_r2():
    u = np.array([0, 1])
    v = np.array([1, 0])
    assert angulo(u, v) == np.arccos(0)

def test_angulo_dois_vetores_nao_ortogonais_r2():
    u = np.array([1, 0])
    v = np.array([1, 3**0.5])
    assert np.allclose(angulo(u, v), np.arccos(0.5))

def test_angulo_produto_interno_nao_canonico_r2():
    funcional_linear = lambda u, v: 2*u[0]*v[0] + u[0]*v[1] + u[1]*v[0] + u[1]*v[1]
    u = np.array([2**0.5, 0])
    v = np.array([2**0.5, 0])
    assert np.allclose(angulo(u, v, funcional_linear = funcional_linear), np.arccos(1))

def test_projecao_escalar_u_v_ortogonais_r2():
    u = np.array([1, 0])
    v = np.array([0, 1])
    assert projecao_escalar(u, v) == 0

def test_projecao_escalar_u_v_paralelos_r2():
    u = np.array([1, 0])
    v = 2*u
    assert projecao_escalar(u, v) == 1

def test_projecao_vetorial_u_v_ortogonais_r2():
    u = np.array([1, 0])
    v = np.array([0, 1])
    assert np.allclose(projecao_vetorial(u, v), np.zeros(u.shape))

def test_projecao_escalar_u_v_paralelos_r2():
    u = np.array([1, 0])
    v = 2*u
    assert np.allclose(projecao_vetorial(u, v), u)

def test_projecao_vetorial_u_v_produto_interno_nao_canonico_r2():
    funcional_linear = lambda u, v: 2*u[0]*v[0] + u[0]*v[1] + u[1]*v[0] + u[1]*v[1]
    u = np.array([1, 1])
    v = np.array([0, 1])
    assert np.allclose(projecao_vetorial(u, v, funcional_linear = funcional_linear), 2*v)

def test_v_ortogonal_u():
    u = np.array([1, 1])
    v = np.array([0, 1])
    assert np.allclose(v_ortogonal_u(u, v), np.array([-1/2, 1/2]))

def test_v_ortogonal_u_produto_interno_nao_canonico_r2():
    funcional_linear = lambda u, v: 2*u[0]*v[0] + u[0]*v[1] + u[1]*v[0] + u[1]*v[1]
    u = np.array([1, 1])
    v = np.array([0, 1])
    assert np.allclose(v_ortogonal_u(u, v, funcional_linear = funcional_linear), np.array([-2/5, 3/5]))

def test_normalizar_u():
    u = np.array([1, 1])
    assert np.allclose(normalizar(u), np.array([(2**0.5)/2, (2**0.5)/2]))

def test_normalizar_u_produto_interno_nao_canonico_r2():
    funcional_linear = lambda u, v: 2*u[0]*v[0] + u[0]*v[1] + u[1]*v[0] + u[1]*v[1]
    u = np.array([1, 0])
    assert np.allclose(normalizar(u, funcional_linear = funcional_linear), np.array([(2**0.5)/2, 0]))

def test_gram_schmidt_v_r3():
    u = np.array([1, 1, 0])
    v = np.array([1, 0, 1])
    w = np.array([0, 2, 0])
    base = [u, v, w]
    assert np.allclose(gram_schmidt(1, base), np.array([1/2, -1/2, 1]))

def test_ortogonalizar_vetores():
    u, u_ortog = np.array([1, 1, 0]), np.array([1, 1, 0])
    v, v_ortog = np.array([1, 0, 1]), np.array([1/2, -1/2, 1])
    w, w_ortog = np.array([0, 2, 0]), np.array([-2/3, 2/3, 2/3])
    base = [u, v, w]
    base_ortogonal = [u_ortog, v_ortog, w_ortog]
    assert np.allclose(ortogonalizar_vetores(base), base_ortogonal)

def test_normalizar_vetores():
    u, u_norm = np.array([1, 1, 0]), np.array([1/2**0.5, 1/2**0.5, 0])
    v, v_norm = np.array([1, 0, 1]), np.array([1/2**0.5, 0, 1/2**0.5])
    w, w_norm = np.array([0, 2, 0]), np.array([0, 1, 0])
    vetores = [u, v, w]
    normalizados = [u_norm, v_norm, w_norm]
    assert np.allclose(normalizar_vetores(vetores), normalizados)

def test_ortonormalizar_vetores():
    u, u_orton = np.array([1, 1, 0]), np.array([1/2**0.5, 1/2**0.5, 0])
    v, v_orton = np.array([1, 0, 1]), np.array([1/6**0.5, -1/6**0.5, 2/6**0.5])
    w, w_orton = np.array([0, 2, 0]), np.array([-1/3**0.5, 1/3**0.5, 1/3**0.5])
    base = [u, v, w]
    base_ortonormal = [u_orton, v_orton, w_orton]
    assert np.allclose(ortonormalizar_vetores(base), base_ortonormal)

def test_ortonormalizar_vetores_polinomiais():
    u = np.polynomial.Polynomial([0, 0, 1]) # u = 1 * t^2
    v = np.polynomial.Polynomial([0, 1])    # v = 1 * t
    w = np.polynomial.Polynomial([1])       # w = 1
    u_ortn = np.polynomial.Polynomial([0, 0, 1/2**0.5]) # u_ortn = 1/2^0.5 * t^2
    v_ortn = np.polynomial.Polynomial([0, 1/2**0.5])    # v_ortn = 1/2^0.5 * t
    w_ortn = np.polynomial.Polynomial([1,    0,    -1]) # w_ortn = -1 * t^2 - 1
    base = [u, v, w]
    base_ortonormal = [u_ortn, v_ortn, w_ortn]

    produto_interno_polinomial = lambda p, q: p(-1) * q(-1) + p(0) * q(0) + p(1) * q(1)
    base_ortonormal_calculada = ortonormalizar_vetores(base, funcional_linear = produto_interno_polinomial)

    assert base_ortonormal_calculada == base_ortonormal


# Fundamentos de Matemática Discreta com Python

## Matemática Discreta

- Área da Matemática que lida com objetos discretos, a saber, conjuntos, sequencias, listas, coleções ou quaisquer entidades *contáveis*.
- Exemplo, $\mathbb{R}$ é incontável, ou não enumerável
- Vários exemplos de contáveis:
    - O conjunto das vogais da língua portuguesa;
    - O conjunto dos times de futebol brasileiros da série A em 2020;
    - O conjunto de nomes das estações do ano;
    - O conjunto das personagens do quarteto do filme *Os Pinguins de Madagascar* e;
    - O conjunto dos números pares positivos menores ou iguais a dez.

- Conjuntos denotados por *extensão*: quando listamos seus elementos

- $\{ a, e, i, o, u \}$
- $\{ \text{Atlético-PR}, \ldots, \text{Bahia}, \text{Botafogo}, \ldots, \text{Coritiba}, \ldots, \text{Fortaleza}, \ldots, \text{Internacional}, \ldots, \text{São Paulo}, \text{Sport}, \text{Vasco} \}$
- $\{ \text{Primavera}, \text{Verão}, \text{Outono}, \text{Inverno}\}$
- $\{ \text{Capitão}, \text{Kowalski}, \text{Recruta}, \text{Rico}\}$
- $\{ 2, 4, 6, 8,10\}$

- Denotados por *compreensão*: quando usamos uma propriedade que distingue seus elementos.

- $\{ c \in \mathbb{A} \, ; \, c \text{ é vogal} \}$
- $\{ t \in \mathbb{T} \, ; \, t \text{ é da Série A} \}$
- $\{ x  \, ; \, x \text{ é uma estação do ano} \}$
- $\{ p  \, ; \, p \text{ é personagem do quarteto principal do filme Os Pinguins de Madagascar} \}$
- $\{ e  \, ; \, e \text{ é estação do ano} \}$
- $\{ n \in \mathbb{Z} \, | \, n = 2k \wedge 2 \leq n \leq 10 \wedge k \in \mathbb{Z} \}$

Por livre conveniência:

- $\mathbb{A}$ é o conjunto de todas as letras de nosso alfabeto
- $\mathbb{T}$ é o conjunto de todos os times de futebol do Brasil.

## Estruturas de dados para objetos discretos

As principais que aprenderemos:

- `list`: estrutura cujo conteúdo é modificável e o tamanho variável. Listas são caracterizadas por *mutabilidade* e *variabilidade*. Objetos `list` são definidos por um par de colchetes e vírgulas que separam seus elementos: `[., ., ... ,.]`.

- `tuple`: estrutura cujo conteúdo não é modificável e o tamanho fixado. Tuplas são caracterizadas por *imutabilidade* e *invariabilidade*. Objetos `tuple` são definidos por um par de colchetes e vírgulas que separam seus elementos: `(., ., ... ,.)`.

- `dict`: estruturas contendo uma coleção de pares do tipo *chave-valor*. Dicionários são caracterizados por *arrays associativos* (*tabelas hash*). Objetos `dict` são definidos por um par de chaves e agrupamentos do tipo  `'chave':valor` (*key:value*), separados por vírgula: `{'chave1':valor1, 'chave2':valor2, ... ,'chaven':valorn}`. As chaves (*keys*) podem ser do tipo `int`, `float`, `str`, ou `tuple` ao passo que os valores podem ser de tipos arbitrários.

- `set`: estruturas similares a `dict`, porém não possuem chaves e contêm objetos únicos. Conjuntos são caracterizadas por *unicidade* de elementos. Objetos `set` são definidos por um par de chaves e vírgulas que separam seus elementos: `{., ., ... ,.}`.

## Listas

Estruturas `list` formam uma coleção de objetos arbitrários e podem ser criadas de modo sequenciado com operadores de pertencimento ou por expressões geradoras, visto que são estruturas iteráveis.

vogais = ['a','e','i','o','u'] # elementos são 'str'
vogais

times = ['Bahia', 'Sport', 'Fortaleza', 'Flamengo']
times

pares10 = [2,4,6,8,10]
pares10

mix = ['Bahia',24,6.54,[1,2]] # vários objetos na lista
mix

### Listas por geração

**Exemplo**: crie uma lista dos primeiros 100 inteiros não-negativos.

os_100 = range(100) # range é uma função geradora
print(list(os_100)) # casting com 'list'

**Exemplo**: crie o conjunto $\{ x \in \mathbb{Z} \, ; \, -20 \leq x < 10 \}$

print(list(range(-20,10))) # print é usado para imprimir column-wise

**Exemplo**: crie o conjunto $\{ x \in \mathbb{Z} \, ; \, -20 \leq x \leq 10 \}$

print(list(range(-20,11))) # para incluir 10, 11 deve ser o limite. Por quê?

## Adicionando e removendo elementos

Há vários métodos aplicáveis para adicionar e remover elementos em listas.

### Adição por apensamento

Adiciona elementos por concatenação no final da lista.

times.append('Botafogo')
times

times.append('Fluminense')
times

### Adição por extensão

Para incluir elementos através de um objeto iterável, sequenciável, usamos `extend`.

falta = ['Vasco', 'Atlético-MG']
times.extend(falta) # usa outra lista pra estender a lista
times

#### Iteração e indexação

- *Iterar* sobre uma lista é "passear" por seus elementos

- Em Python, a indexação de listas vai de `0` a `n - 1`, onde `n` é o tamanho da lista.

Por exemplo:

$\text{posição} : \{p=0, p=1, \ldots, p={n-1}\}$

$\text{elementos na lista} : [x_1, x_2, \ldots, x_{n}]$

- Mesma idéia aplicável a qualquer coleção, sequencia ou objeto iterável.

### Remoção por índice

Suponha que tivéssemos criado a lista:

pares = [0,2,5,6] # 5 não é par
pares

Como 5 não é par, não deveria estar na lista. Para excluírmos um elemento em uma posição específica, usamos `pop` passando o *índice* onde o elemento está.

pares.pop(2) # o ímpar 5 está na posição 2 e NÃO 3!
pares

### Adição por índice

Nesta lista, podemos pensar em incluir 4 entre 2 e 6. Para isto, usamos `insert(posicao,valor)`, para `valor` na `posicao` desejada.

pares.insert(2,4) # 4 é inserido na posição de 6, que é deslocado
pares

### Apagar conteúdo da lista

Podemos apagar o conteúdo inteiro da lista com `clear`.

times.clear()
times # lista está vazia

Podemos contar o número de elementos da lista com `len`.

len(times) # verifica que a lista está vazia

type([]) # a lista é vazia, mas continua sendo lista

### Outros métodos de lista

Conte repetições de elementos na lista com `count`.

numeros = [1,1,2,3,1,2,4,5,6,3,4,4,5,5]
print( numeros.count(1), numeros.count(3), numeros.count(7) )

Localize a posição de um elemento com `index`.

numeros.index(5) # retorna a posição da primeira aparição

Remova a primeira aparição do elemento com `remove`.

numeros.remove(1) # perde apenas o primeiro
numeros

Faça uma reflexão ("flip") *in-place* (sem criar nova lista) da lista com `reverse`.

numeros.reverse()
numeros

Ordene a lista de maneira *in-place* (sem criar nova lista) com `sort`.

numeros.sort()
numeros

## Concatenação de listas

Listas são concatenadas ("somadas") com `+`. Caso já possua listas definidas, use `extend`.

['Flamengo', 'Botafogo'] + ['Fluminense']

['Flamengo', 'Botafogo'] + 'Fluminense' # erro: 'Fluminense' não é list

times_nordeste = ['Fortaleza','Sport']
times_sul = ['Coritiba','Atlético-PR']
times_nordeste + times_sul

times_nordeste.extend(times_sul) # mesma coisa
times_nordeste

## Fatiamento de listas

O fatiamento ("slicing") permite que selecionemos partes da lista através do modelo `start:stop`, em que `start` é um índice incluído na iteração, e `stop` não.

letras = ['a','b','c','d','e','f','g']
letras[0:2]

letras[1:4]

letras[5:6]

letras[0:7] # toda a lista

### Omissão de `start` e `stop`

letras[:3] # até 3, exclusive

letras[:5] # até 5, exclusive

letras[4:] # de 4 em diante

letras[6:] # de 6 em diante

### Modo reverso

letras[-1] # último índice

letras[-2:-1] # do penúltimo ao último, exclusive

letras[-3:-1]

letras[-4:-2]

letras[-7:-1] # toda a lista

letras[-5:]

letras[:-3]

## Elementos alternados com `step`

Podemos usar um dois pontos duplo (`::`) para dar um "passo" de alternância.

letras[::2] # salta 2-1 intermediários

letras[::3] # salta 3-1 intermediários

letras[::7] # salto de igual tamanho

letras[::8] # salto além do tamanho

## Mutabilidade de listas

Podemos alterar o conteúdo de elementos diretamente por indexação.

from sympy.abc import x,y

ops = [x+y,x-y,x*y,x/y]
ops2 = ops.copy() # cópia de ops
ops

ops[0] = x-y
ops

ops[2] = x/y
ops

ops[1], ops[3] = x + y, x*y # mutação por desempacotamento
ops

ops[1:3] = [False, False, True] # mutação por fatiamento
ops

ops = ops2 # recuperando ops
ops

ops2 is ops

ops3 = [] # lista vazia
ops3

ops2 = ops + ops3 # concatenação cria uma lista nova
ops2

ops2 is ops # agora, ops2 não é ops

print(id(ops), id(ops2)) # imprime local na memória de ambas

ops2 == ops # todos os elementos são iguais

O teste de identidade é `False`, mas o teste de igualdade é `True`.

**Exemplo:** Escreva uma função que calcule a área, perímetro, comprimento da diagonal, raio, perímetro e área do círculo inscrito, e armazene os resultados em uma lista.

# usaremos matemática simbólica
from sympy import symbols
from math import pi

# símbolos
B, H = symbols('B H',positive=True)

def propriedades_retangulo(B,H):
    '''
        A função assume que a base B
        é maior do que a altura H. Senão,
        as propriedades do círculo inscrito
        não serão determinadas.
    '''
    d = (B**2 + H**2)**(1/2) # comprimento da diagonal
    r = H/2 # raio do círculo inscrito
    return [B*H, 2*(B+H), d, r, 2*pi*r, pi*(r)**2]

# lista de objetos símbolos
propriedades_retangulo(B,H)

# substituindo valores
B, H = 4.0, 2.5

propriedades_retangulo(B,H)

### Formatação de strings

O *template* a seguir usa a função `format` para substituição de valores indexados.

```python
templ = '{0} {1} ... {n}'.format(arg0,arg1,...,argn)
```

**Nota:** Para ajuda plena sobre formatação, consultar:

```python
help('FORMATTING')
```

# considere R: retângulo; C: círculo inscrito

res = propriedades_retangulo(B,H) # resultado

props = ['Área de R',
         'Perímetro de R',
         'Diagonal de R',
         'Raio de C',
         'Perímetro de C',
         'Área de C'
        ] # propriedades

# template
templ = '{0:s} = {1:.2f}\n\
{2:s} = {3:.3f}\n\
{4:s} = {5:.4f}\n\
{6:s} = {7:.5f}\n\
{8:s} = {9:.6f}\n\
{10:s} = {11:.7f}'.format(props[0],res[0],\
                          props[1],res[1],\
                          props[2],res[2],\
                          props[3],res[3],\
                          props[4],res[4],\
                          props[5],res[5])

# impressão formatada
print(templ)

### Como interpretar o que fizemos?

- `{0:s}` formata o primeiro argumento de `format`, o qual é `props[0]`, como `str` (`s`).
- `{1:.2f}` formata o segundo argumento de `format`, o qual é `res[0]`, como `float` (`f`) com duas casas decimais (`.2`).
- `{3:.3f}` formata o quarto argumento de `format`, o qual é `res[1]`, como `float` (`f`) com três casas decimais (`.3`).

A partir daí, percebe-se que um template `{X:.Yf}` diz para formatar o argumento `X` como `float` com `Y` casas decimais, ao passo que o template `{X:s}` diz para formatar o argumento `X` como `str`.

Além disso, temos:

- `\n`, que significa "newline", isto é, uma quebra da linha.
- `\`, que é um *caracter de escape* para continuidade da instrução na linha seguinte. No exemplo em tela, o *template* criado é do tipo *multi-line*.

**Nota:** a contrabarra em `\n` também é um caracter de escape e não um caracter *literal*. Isto é, para imprimir uma contrabarra literalmente, é necessário fazer `\\`. Vejamos exemplos de literais a seguir.

#### Exemplos de impressão de caracteres literais

print('\\') # imprime contrabarra literal
print('\\\\') # imprime duas contrabarras literais
print('\'') # imprime plica
print('\"') # imprime aspas

#### f-strings

Temos uma maneira bastante interessante de criar templates usando f-strings, que foi introduzida a partir da versão Python 3.6. Com f-strings a substituição é imediata.

print(f'{props[0]} = {res[0]}') # estilo f-string

#### Estilos de formatação

Veja um comparativo de estilos:

print('%s = %f ' % (props[0], res[0])) # Python 2
print('{} = {}'.format(props[0], res[0])) # Python 3
print('{0:s} = {1:.4f}'.format(props[0], res[0])) # Python 3 formatado

**Exemplo:** Considere o conjunto: V = $\{ c \in \mathbb{A} \, ; \, c \text{ é vogal} \}.$ Crie a concatenação de todos os elementos com f-string.

V = ['a','e','i','o','u']
V

f'{V[0]}{V[1]}{V[2]}{V[3]}{V[4]}' # pouco Pythônico

Veremos à frente meios mais elegantes de fazer coisas similares.

## Controle de fluxo: laço `for`

Em Python, podemos realizar iterar por uma coleção ou iterador usando *laços*. Introduziremos aqui o laço `for`. Em Python, o bloco padrão para este laço é dado por:

```python
for valor in sequencia:
    # faça algo com valor
```

Acima, `valor` é um iterador.

for v in vogais: # itera sobre lista inteira
    print(v)

for v in vogais[0:3]: # itera parcialmente
    print(v + 'a')

for v in vogais[-2:]:
    print(f'{v*10}')

## Compreensão de lista

Usando `for`, a criação de listas torna-se bastante facilitada.

**Exemplo:** crie a lista dos primeiros 10 quadrados perfeitos.

Q = [q*q for q in range(1,11)]
Q

A operação acima equivale a:

Q2 = []
for q in range(1,11):
    Q2.append(q*q)
Q2

**Exemplo:** crie a PA: $a_n = 3 + 6(n-1), \, 1 \leq n \leq 10$

PA = [3 + 6*(n-1) for n in range(1,11) ]
PA

**Exemplo:** se $X = \{1,2,3\}$ e $Y=\{4,5,6\}$, crie a "soma" $X + Y$ elemento a elemento.

X = [1,2,3]
Y = [4,5,6]

XsY = [ X[i] + Y[i] for i in range(len(X)) ]
XsY

**Exemplo:** se $X = \{1,2,3\}$ e $Y=\{4,5,6\}$, cria o "produto" $X * Y$ elemento a elemento.

XpY = [ X[i]*Y[i] for i in range(len(X)) ]
XpY

from sympy import lambdify
from sympy.abc import x

for i,v in enumerate(XpY)
    lambdify(x,'x**2')

## Tuplas

Tuplas são são sequencias imutáveis de tamanho fixo. Em Matemática, uma tupla é uma sequência ordenada de elementos. Em geral, o termo $n-$upla ("ênupla") é usado para se referir a uma tupla com $n$ elementos.

par = 1,2; par

type(par)

trio = (1,2,3); trio

quad = (1,2,3,4); quad

nome = 'Nome'; tuple(nome) # casting

Tuplas são acessíveis por indexação.

quad[2]

quad[1:4]

quad[3] = 5 # tuplas não são mutáveis

Se na tupla houver uma lista, a lista é modificável.

super_trio = tuple([1,[2,3],4]) # casting
super_trio

super_trio[1].extend([4,5])
super_trio

Tuplas também são concatenáveis com `+`.

(2,3) + (4,3)

('a',[1,2],(1,1)) # repetição

### Desempacotamento de tuplas

a,b,c,d = (1,2,3,4)

for i in [a,b,c,d]:
    print(i) # valor das variáveis

a,b = (1,2)
a,b = b,a # troca de valores
a,b

### `enumerate`

Podemos controlar índice e valor ao iterar em uma sequencia.

X = [1,2,3] # lista / sequencia

for i,x in enumerate(X): # (i,x) é uma tupla (índice,valor)
    print(f'{i} : {x}')

**Exemplo:** Construa o produto cartesiano

$$A \times B = \{(a,b) \in \mathbb{Z} \times \mathbb{Z} \, ; \, -4 \leq a \leq 4 \wedge 3 \leq b \leq 7\}$$

AB = [(a,b) for a in range(-4,5) for b in range(3,8)]
print(AB)

## Dicionários

Dicionários, ou especificamente, objetos `dict`, possuem extrema versatilidade e são muito poderosos. Criamos um `dict` por diversas formas. A mais simples é usar chaves e pares explícitos.

d = {} # dict vazio
d

type(d)

Os pares chave-valor incorporam quaisquer tipos de dados.

d = {'par': [0,2,4,6,8], 'ímpar': [1,3,5,7,9], 'nome':'Meu dict', 'teste': True}
d

### Acesso a conteúdo

Para acessar o conteúdo de uma chave, indexamos pelo seu nome.

d['par']

d['nome']

**Exemplo:** construindo soma e multiplicação especial.

# dict
op = {'X' :[1,2,3], 'delta' : 0.1}

# função
def sp(op):
    s = [x + op['delta'] for x in op['X']]
    p = [x * op['delta'] for x in op['X']]

    return (s,p) # retorna tupla

soma, prod = sp(op) # desempacota

for i,s in enumerate(soma):
    print(f'pos({i}) | Soma = {s} | Prod = {prod[i]}')

### Inserção de conteúdo

# apensa variáveis
op[1] = 3
op['novo'] = (3,4,1)
op

### Alteração de conteúdo

op['novo'] = [2,1,4] # sobrescreve
op

### Deleção de conteúdo com `del` e `pop`

del op[1] # deleta chave
op

novo = op.pop('novo') # retorna e simultaneamente deleta
novo

op

### Listagem de chaves e valores

Usamos os métodos `keys()` e `values()` para listar chaves e valores.

arit = {'soma': '+', 'subtr': '-', 'mult': '*', 'div': '/'} # dict
k = list(arit.keys())
print(k)
val = list(arit.values())
print(val)
for v in range(len(arit)):
    print(f'A operação \'{k[v]}\' de "arit" usa o símbolo \'{val[v]}\'.')

### Combinando dicionários

Usamos `update` para combinar dicionários. Este método possui um resultado similar a `extend`, usado em listas.

pot = {'pot': '**'}
arit.update(pot)
arit

### Dicionários a partir de sequencias

Podemos criar dicionários a partir de sequencias existentes usando `zip`.

arit = ['soma', 'subtr', 'mult', 'div', 'pot']
ops = ['+', '-', '*', '/', '**']

dict_novo = {}

for chave,valor in zip(arit,ops):
    dict_novo[chave] = valor

dict_novo

Visto que um `dict` é composto de várias tuplas de 2, podemos criar um  de maneira ainda mais simples.

dict_novo = dict(zip(arit,ops))
dict_novo

### *Hashability*

Dissemos acima que os valores de um `dict` podem ser qualquer objeto Python. Porém, as chaves estão limitadas por uma propriedade chamada *hashability*. Um objeto *hashable* em geral é imutável. Para saber se um objeto pode ser usado como chave de um `dict`, use a função `hash`. Caso retorne erro, a possibilidade de *hashing* é descartada.

# todos aqui são imutáveis, portanto hashable'
hash('s'), hash(2), hash(2.1), hash((1,2))

# não hashable
hash([1,2]), hash((1,2),[3,4])

Para usar `list` como chave, podemos convertê-las em `tuple`.

d = {}; d[tuple([1,2])] = 'hasheando lista em tupla'; d

## Compreensão de dicionário

Podemos usar `for` para criar dicionários de maneira esperta do mesmo modo que as compreensões de lista com a distinção de incluir pares chaves/valor.

{chave:valor for chave,valor in enumerate(arit)} # chave:valor

{valor:chave for chave,valor in enumerate(arit)} # valor:chave


# importing modules
#-----------------------------------------
# this is okay
import math
d = 90
a = math.sin(math.radians(d))
print(f'sin of {d:2.0f} degrees = {a}')

#-----------------------------------------
# this is okay
from math import cos, radians
d = 0
a = cos(radians(d))
print(f'cos of {d:2.0f} degrees = {a}')

#-----------------------------------------
# very rarely should you do this
from math import *
a = tan(tanh(cos(acos(sin(pi)))))
print('a =', a)

#-----------------------------------------
# this is common practice
# import numpy as np
# import pandas as pd

# a = np.array([1, 2, 3])
# df = pd.DataFrame()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Jul 17 16:17:25 2017

@author: jorgemauricio
"""

# librerias
import numpy as np

# Funciones universales
arr = np.arange(11)

# desplegar
arr

# raiz cuadrada
np.sqrt(arr)

# calcular el exponencial (e^)
np.exp(arr)

# Funciones binarias requieren dos arreglos

# arreglo aleatorio (distribucion normal)
A = np.random.randn(10)

# desplegar
A

# arreglo aleatorio (distribucion normal)
B = np.random.randn(10)

# desplegar
B

# anadir
np.add(A,B)

# Maximo y minimo de dos arreglos
np.maximum(A,B)

#


# Import the necessary modules.
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# In this script, we will explore the dynamics of phase transitions in a
# two component mixture. We discussed two models in class -- the Regular
# Solution theory and the Flory-Huggins Theory.

# The regulular solution describes a mixture of componnents in which each
# individual molecule occupies only one lattice site in our closed system.
# We can define the probability of observing like-bonded molecules as phi.
# We can also include the Flory parameter which describes the energy of mixing
# the two species. The full description of the regular solution theory
# description of the free energy is
#
# free-energy / kBT = (2 * X * phi) * (1 - phi) + phi * log(phi) * (1-phi) * log(phi)#
#
# where X is the flory parameter. Let's see how varying this parameter
# influences the phases of the system.

# We'll set up our parameters.
phi = np.linspace(0, 1, 500)
chi = [0.01, 0.1, 0.5, 0.8, 1, 1.25, 1.5, 2, 4]

# We'll compute the value at each chi and plot it at once.
plt.figure()
for x in chi:
    # Calculate the free energy
    free_energy = 2 * x * phi * (1 - phi) + phi * np.log(phi) + (1 - phi) * np.log(1 - phi)
    plt.plot(phi, free_energy, label='$\chi$ = ' + str(x))

# Of course we should add labels.
plt.xlabel('$\phi$')
plt.ylabel(r'$\frac{F(\phi)}{k_BT}$')
plt.title('Regular Solution Theory')
plt.legend()
plt.show()

# We see that at the intermediate values of X , there are now multiple local
# minima where phases will separate.

# Now we can consider a case in which we are mixing together polymers. In this
# case, the polymers now occupy several different lattice sites. The result for
# the free energy of the system is given by the Flory-Huggins theory,
#
# free-energy / k_BT = 2 * X * phi * (1-phi) + (phi / L) * log(phi) + (1-phi) * log(1-phi)
#
# where L is the degree of polymerization, essentially just the length of the
# polymer.
L = 100 # Degree of polymerization

# Now we'll do the same iteration of x and  plot the free energy.
plt.figure()
for x in chi:
    # Calculate the free energy.
    free_energy = 2 * x * phi * (1 - phi) + (phi / L) * np.log(phi) +\
            (1 - phi) * np.log(1 - phi)
    # Plot it!
    plt.plot(phi, free_energy, label='$\chi$ = ' + str(x))

plt.xlabel('$\phi$')
plt.ylabel(r'$\frac{F(\phi)}{k_BT}$')
plt.legend()
plt.title('Flory-Huggins Theory')
plt.show()

# We now see that there is a degree of nonsymmetry when compared to the
# regular solution theory. This significantly changes the way that
# phases separate.


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

Automatically generated by Colaboratory.

Original file is located at
    https://colab.research.google.com/drive/1fOGAmlA4brfHL_5qcUfsQKsy7BCuuTCS
"""

# illustrate automatic differentiation using jax
# https://github.com/google/jax
import numpy as np # original numpy
import jax.numpy as jnp
from jax import grad, hessian

np.random.seed(42)
D = 5
w = np.random.randn(D) # jax handles RNG differently

x = np.random.randn(D)
y = 0 # should be 0 or 1

def sigmoid(x): return 0.5 * (jnp.tanh(x / 2.) + 1)

#d/da sigmoid(a) = s(a) * (1-s(a))
deriv_sigmoid = lambda a: sigmoid(a) * (1-sigmoid(a))
deriv_sigmoid_jax = grad(sigmoid)
a0 = 1.5
assert jnp.isclose(deriv_sigmoid(a0), deriv_sigmoid_jax(a0))

# mu(w)=s(w'x), d/dw mu(w) = mu * (1-mu) .* x
def mu(w): return sigmoid(jnp.dot(w,x))
def deriv_mu(w): return mu(w) * (1-mu(w)) * x
deriv_mu_jax =  grad(mu)
assert jnp.allclose(deriv_mu(w), deriv_mu_jax(w))

# NLL(w) = -[y*log(mu) + (1-y)*log(1-mu)]
# d/dw NLL(w) = (mu-y)*x
def nll(w): return -(y*jnp.log(mu(w)) + (1-y)*jnp.log(1-mu(w)))
#def deriv_nll(w): return -(y*(1-mu(w))*x - (1-y)*mu(w)*x)
def deriv_nll(w): return (mu(w)-y)*x
deriv_nll_jax = grad(nll)
assert jnp.allclose(deriv_nll(w), deriv_nll_jax(w))

# Now do it for a batch of data

def predict(weights, inputs):
    return sigmoid(jnp.dot(inputs, weights))

def loss(weights, inputs, targets):
    preds = predict(weights, inputs)
    logprobs = jnp.log(preds) * targets + jnp.log(1 - preds) * (1 - targets)
    return -np.sum(logprobs)

N = 3
X = np.random.randn(N, D)
y = np.random.randint(0, 2, N)

from jax import vmap
from functools import partial

preds = vmap(partial(predict, w))(X)
preds2 = vmap(predict, in_axes=(None, 0))(w, X)
preds3 = [predict(w, x) for x in X]
preds4 = predict(w, X)
assert jnp.allclose(preds, preds2)
assert jnp.allclose(preds, preds3)
assert jnp.allclose(preds, preds4)

grad_fun = grad(loss)
grads = vmap(partial(grad_fun, w))(X,y)
assert grads.shape == (N,D)
grads2 = jnp.dot(np.diag(preds-y), X)
assert jnp.allclose(grads, grads2)

grad_sum = jnp.sum(grads, axis=0)
grad_sum2 = jnp.dot(np.ones((1,N)), grads)
assert jnp.allclose(grad_sum, grad_sum2)

# Now make things go fast
from jax import jit

grad_fun = jit(grad(loss))
grads = vmap(partial(grad_fun, w))(X,y)
assert jnp.allclose(grads, grads2)

# Logistic regression
H1 = hessian(loss)(w, X, y)
mu = predict(w, X)
S = jnp.diag(mu * (1-mu))
H2 = jnp.dot(np.dot(X.T, S), X)
assert jnp.allclose(H1, H2)


# -*- coding: utf-8 -*-
# @Time    : 7/29/2018 9:57 PM
# @Author  : DonaldY

# 1. 定义数据集
x = [4, 8, 12, 25, 32, 43, 58, 63, 69, 79]
y = [20, 33, 50, 56, 42, 31, 33, 46, 65, 75]

# 2. 使用Matplotlib 绘制数据
from matplotlib import pyplot as plt

plt.scatter(x, y)
plt.show()

# 3. 实现 2 次多项式函数及误差函数
def func(p, x1):
    w0, w1, w2 = p
    f = w0 + w1*x1 + w2*x1*x1
    return f

def err_func(p, x1, y1):
    ret = func(p, x1) - y1
    return ret

# 4. 生成随机数
import numpy as np
# 生成 3 个随机数
p_init = np.random.randn(3)

p_init

# 5. 使用 Scipy 提供的最小二乘法哈数得到最佳拟合参数
from scipy.optimize import leastsq

parameters = leastsq(err_func, p_init, args=(np.array(x), np.array(y)))

print('Fitting Parameters: ', parameters[0])

## 6. 绘制拟合后的图像
# 绘制拟合图像时需要的临时点
x_temp = np.linspace(0, 80, 10000)

# 绘制拟合函数曲线
plt.plot(x_temp, func(parameters[0], x_temp), 'r')

# 绘制原数据点
plt.scatter(x, y)


import numpy as np

class LDA(object):

    def __init__(self, n_components):
        self.n_components = n_components

    def _within_class_scatter_matrices(self, X, y):

        shape = X.shape[1]
        matrix = np.zeros((shape, shape))

        for i in np.unique(y):
            matrix += np.cov(X[y == i].T)

        return matrix

    def _between_class_scatter_matrix(self, X, y):

        mean_vectors = []
        for i in np.unique(y):
            mean_vectors.append(np.mean(X[y == i], axis=0))

        overall_mean = np.mean(X, axis=0)

        shape = X.shape[1]
        matrix = np.zeros((shape, shape))
        for i, mean_vec in enumerate(mean_vectors):
            n = X[y == i + 1,:].shape[0]
            mean_vec = mean_vec.reshape(shape, 1)
            overall_mean = overall_mean.reshape(shape, 1)
            matrix += n * (mean_vec - overall_mean).dot((mean_vec - overall_mean).T)

        return matrix

    def _create_eig_values_and_vectors(self, X, y):

        wc = self._within_class_scatter_matrices(X, y)
        bc = self._between_class_scatter_matrix(X, y)

        A = np.linalg.pinv(wc).dot(bc)

        eig_vals, eig_vecs = np.linalg.eig(A)

        return eig_vals, eig_vecs

    def fit(self, X, y):

        eig_vals, eig_vecs = self._create_eig_values_and_vectors(X, y)

        # Make a list of (eigenvalue, eigenvector) tuples
        eig_pairs = [(np.abs(eig_vals[i]), eig_vecs[:,i]) for i in range(len(eig_vals))]

        # Sort the (eigenvalue, eigenvector) tuples from high to low
        eig_pairs = sorted(eig_pairs, key=lambda k: k[0], reverse=True)

        shape = X.shape[1]
        self.W = np.hstack([eig_pairs[comp][1].reshape(shape, 1) for comp in range(self.n_components)])

        return self

    def transform(self, X, y):
        return X.dot(self.W).real

    def fit_transform(self, X, y):
        self = self.fit(X, y)
        return self.transform(X, y)


# -*- coding: utf-8 -*-
"""
-----------------------------------------------------------------------------
Ejercicio 1: Integración - Fórmulas de cuadratura simples.
-----------------------------------------------------------------------------
"""
import numpy as np
import sympy as sym

# Cálculo de la integral exacta de la función f(x) = exp(x) en [0,3]
#----------------------------------------------------------------------------
x = sym.Symbol('x', real=True)            # definimos la variable x simbólica
f = sym.exp(x)            # definimos la función f simbólica
I_exacta = sym.integrate(f,(x,0,3))
I_exacta = float(I_exacta)
print ('El valor exacto es: ',I_exacta)

"""
Ejercicio 1a: Integración - Fórmula del punto medio simple.
-----------------------------------------------------------------------------
Función punto_medio: Halla la integral aproximada utlizando la fórmula del
punto medio simple para una función f en unintervalo [a,b].

Argumentos de entrada:
    f:  función integrando (función lambda).
    a:  extremo inferior del intervalo de integración (número real).
    b:  extremo superior del intervalo de integración (número real).

Argumentos de salida:
    I:  integral aproximada con la fórmula del punto medio simple de la función
        f en [a,b] (número real).

Ejemplos:
    f = lambda x : x**2
    I = punto_medio(f,0,2)
    print('Ejemplo de prueba con punto medio simple =', I)
    Salida:
        Ejemplo de prueba con punto medio simple = 2.0

"""

# Función punto_medio
#--------------------------------------
def punto_medio(f,a,b):
    I = (b-a)*f((a+b)/2.0)      # fórmula del punto medio simple
    return I

#--------------------------------------
# Ejemplos
#--------------------------------------

# Ejemplo de prueba
print('Ejemplo de prueba con punto medio simple =', punto_medio(lambda x : x**2,0,2))

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

# Cálculo de la integral aproximada
#---------------------------------------
f = lambda x :np.exp(x)   # función
a = 0
b = 3
Ipm = punto_medio(f,a,b)  # cálculo de la integral aproximada con

print ('El valor aproximado con punto medio simple es:', Ipm)

"""
Ejercicio 1b: Integración - Fórmula del trapecio simple.
-----------------------------------------------------------------------------
Función trapecio: Halla la integral aproximada utlizando la fórmula del
trapecio simple para una función f en unintervalo [a,b].

Argumentos de entrada:
    f:  función integrando (función lambda).
    a:  extremo inferior del intervalo de integración (número real).
    b:  extremo superior del intervalo de integración (número real).

Argumentos de salida:
    I:  integral aproximada con la fórmula del trapecio de la función
        f en [a,b].

Ejemplos:
    f = lambda x : x**2
    I = trapecio(f,0,2)
    print('Ejemplo de prueba con trapecio simple =', I)
    Salida:
       Ejemplo de prueba con trapecio simple = 4.0
"""

# Función trapecio
#--------------------------------------
def trapecio(f,a,b):
    I = ((b-a)/2.0)*(f(a)+f(b))
    return I

#--------------------------------------
# Ejemplos
#--------------------------------------

# Ejemplo de prueba
print('Ejemplo de prueba con trapecio simple =', trapecio(lambda x : x**2,0,2))

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

# Cálculo de la integral aproximada
#---------------------------------------
It = trapecio(f,a,b)
print ('El valor aproximado con trapecio simple es:', It)

"""
Ejercicio 1c: Integración - Fórmula de Simpson simple.
-----------------------------------------------------------------------------
Función simpson: Halla la integral aproximada utlizando la fórmula de
Simpson simple para una función f en unintervalo [a,b].

Argumentos de entrada:
    f:  función integrando (función lambda).
    a:  extremo inferior del intervalo de integración (número real).
    b:  extremo superior del intervalo de integración (número real).

Argumentos de salida:
    I:  integral aproximada con la fórmula de Simpson de la función
        f en [a,b].

Ejemplos:
    f = lambda x : x**2
    I = simpson(f,0,2)
    print('Ejemplo de prueba con simpson simple =', I)
    Salida:
        Ejemplo de prueba con simpson simple = 2.6666666666666665
"""

# Función simpson
#--------------------------------------
def simpson(f,a,b):
    I = ((b-a)/6.0)*(f(a)+4*f((a+b)/2.0)+f(b))
    return I

#--------------------------------------
# Ejemplos
#--------------------------------------

# Ejemplo de prueba
print('Ejemplo de prueba con simpson simple =', simpson(lambda x : x**2,0,2))

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

# Cálculo de la integral aproximada
#---------------------------------------
Is = simpson(f,a,b)
print ('El valor aproximado con simpson simple es:', Is)


import os
import numpy as np
import matplotlib.pyplot as plt
import pylab
#%matplotlib inline

path = os.getcwd() + "/data/ex1data1.txt"
data = np.loadtxt(path, delimiter=",")
temp = np.ones((97,1), dtype=np.float64)
data = np.append(temp, data, axis=1)
'''
print(data.itemsize)
print(data.ndim)
print(data.shape)
print(data.size)
'''

def partial(data, theta0, theta1, i):
	temp = 0.0
	for row in data:
		temp += (theta0 * row[0] + theta1 * row[1] - row[2]) * row[i]
	temp /= (data.shape)[0]
	return temp

def J(theta0, theta1, data):
	j = 0.0;
	for row in data:
		j += (theta0 * row[0] + theta1 * row[1] - row[2]) ** 2
	j /= (2 * (data.shape)[0])
	return j

def regression(data):
	#for plotting error vs training epoch
	#count = 0
	alpha = 0.01
	m, n = (data.shape)[0], (data.shape)[1]
	theta0, theta1 = 0.0, 0.0
	j = J(theta0, theta1, data)
	j_prev = j + 1
	epsilon = 0.0001
	while (abs(j - j_prev) >= epsilon):
		temp0 = theta0 - alpha * partial(data, theta0, theta1, 0)
		temp1 = theta1 - alpha * partial(data, theta0, theta1, 1)
		theta0 = temp0
		theta1 = temp1
		j_prev = j
		j = J(theta0, theta1, data)
		#plt.plot(count, j, "b.")
		#count += 1
	return theta0, theta1

def abline(slope, intercept):
    #Plot a line from slope and intercept
    axes = plt.gca()
    x_vals = np.array(axes.get_xlim())
    y_vals = intercept + slope * x_vals
    plt.plot(x_vals, y_vals, '-')

theta0, theta1 = regression(data)
temp1 = [theta0, theta0 + theta1]

plt.plot(data[:,1], data[:,2], "r.")
abline(theta1, theta0)
plt.ylabel('some numbers')
plt.show()


import matplotlib.pyplot as plt

def model():
    """Solve u'' = -1, u(0)=0, u'(1)=0."""
    import sympy as sym
    x, c_0, c_1, = sym.symbols('x c_0 c_1')
    u_x = sym.integrate(1, (x, 0, x)) + c_0
    u = sym.integrate(u_x, (x, 0, x)) + c_1
    r = sym.solve([u.subs(x,0) - 0,
                   sym.diff(u,x).subs(x, 1) - 0],
                  [c_0, c_1])
    u = u.subs(c_0, r[c_0]).subs(c_1, r[c_1])
    u = sym.simplify(sym.expand(u))
    return u

def midpoint_rule(f, M=100000):
    """Integrate f(x) over [0,1] using M intervals."""
    from numpy import sum, linspace
    dx = 1.0/M                       # interval length
    x = linspace(dx/2, 1-dx/2, M)    # integration points
    return dx*sum(f(x))

def check_integral_b():
    from numpy import pi, sin
    for i in range(12):
        exact = 2/(pi*(2*i+1))
        numerical = midpoint_rule(
            f=lambda x: sin((2*i+1)*pi*x/2))
        print(i, abs(exact - numerical))

def sine_sum(x, N):
    s = 0
    from numpy import pi, sin, zeros
    u = [] # u[k] is the sum i=0,...,k
    k = 0
    for i in range(N+1):
        s += - 16.0/((2*i+1)**3*pi**3)*sin((2*i+1)*pi*x/2)
        u.append(s.copy())  # important with copy!
    return u

def plot_sine_sum():
    from numpy import linspace
    x = linspace(0, 1, 501)  # coordinates for plot
    u = sine_sum(x, N=10)
    u_e = 0.5*x*(x-2)
    N_values = 0, 1, 10
    for k in N_values:
        plt.plot(x, u[k])
    plt.plot(x, u_e)
    plt.legend(['N=%d' % k for k in N_values] + ['exact'],
               loc='upper right')
    plt.xlabel('$x$');  plt.ylabel('$u$')
    plt.savefig('tmpc.png'); plt.savefig('tmpc.pdf')

def check_integral_d():
    from numpy import pi, sin
    for i in range(24):
        if i % 2 == 0:
            exact = 2/(pi*(i+1))
        elif (i-1) % 4 == 0:
            exact = 2*2/(pi*(i+1))
        else:
            exact = 0
        numerical = midpoint_rule(
            f=lambda x: sin((i+1)*pi*x/2))
        print(i, abs(exact - numerical))

def check_integral_d_sympy_answer():
    from numpy import pi, sin
    for i in range(12):
        exact = 2/(pi*(i+1))
        numerical = midpoint_rule(
            f=lambda x: sin((i+1)*pi*x/2))
        print(i, abs(exact - numerical))

def sine_sum_d(x, N):
    s = 0
    from numpy import pi, sin, zeros
    u = []  # u[k] is the sum i=0,...,k
    k = 0
    for i in range(N+1):
        if i % 2 == 0:       # even i
            s +=   - 16.0/((i+1)**3*pi**3)*sin((i+1)*pi*x/2)
        elif (i-1) % 4 == 0:   # 1, 5, 9, 13, 17
            s += - 2*16.0/((i+1)**3*pi**3)*sin((i+1)*pi*x/2)
        else:
            s += 0
        u.append(s.copy())
    return u

def plot_sine_sum_d():
    from numpy import linspace
    x = linspace(0, 1, 501)  # coordinates for plot
    u = sine_sum_d(x, N=20)
    u_e = 0.5*x*(x-2)
    N_values = 0, 1, 2, 3, 20
    for k in N_values:
        plt.plot(x, u[k])
    plt.plot(x, u_e)
    plt.legend(['N=%d' % k for k in N_values] + ['exact'],
               loc='upper right')
    plt.xlabel('$x$');  plt.ylabel('$u$')
    #plt.axis([0.9, 1, -0.52, -0.49])
    plt.savefig('tmpd.png'); plt.savefig('tmpd.pdf')

if __name__ == '__main__':
    import sys
    print(model())
    print('sine 2*i+1 integral:')
    check_integral_b()
    print('sine i+1 integral, sympy answer:')
    check_integral_d_sympy_answer()
    print('sine i+1 integral:')
    check_integral_d()
    #sys.exit(0)
    plot_sine_sum()
    plt.figure()
    plot_sine_sum_d()
    plt.show()


import numpy as np

from source.week2.ex2_1 import xy, construct_element_table
from source.week2.ex2_2a import basfun

from scipy.sparse import dia_matrix

def compute_k_rsn(abc, delta, r, s, lam1, lam2):
    br = abc[r, 1]
    bs = abc[s, 1]
    cr = abc[r, 2]
    cs = abc[s, 2]
    k_rsn = (lam1 * br * bs + lam2 * cr * cs) / np.abs(delta * 4)
    return k_rsn

def assembly(VX, VY, EToV, lam1, lam2, qt, M):
    N = len(EToV)
    A = np.zeros((M, M))
    b = np.zeros(M)
    for n in range(1, N + 1):
        abc, delta = basfun(n, VX, VY, EToV)
        for r in range(3):
            i = EToV[n][r] - 1
            qr = qt[n] * np.abs(delta) / 3  # (2.28)
            b[i] += qr
            for s in range(3):
                j = EToV[n][s] - 1
                k_rsn = compute_k_rsn(abc, delta, r, s, lam1, lam2)  # (2.27)
                A[i, j] += k_rsn
    return A, b

def construct_qt(etov_dict, VX, VY, test_case=None):
    """
    Computes points as means of each elements' vertices.
    """
    qt_dict = dict()
    for e, (v1, v2, v3) in etov_dict.items():
        if test_case == 1:
            qt_dict[e] = 0
        elif test_case == 2:
            x1, x2, x3 = VX[v1 - 1], VX[v2 - 1], VX[v3 - 1]
            y1, y2, y3 = VY[v1 - 1], VY[v2 - 1], VY[v3 - 1]

            def f(x, y):
                return -6 * x + 2 * y - 2

            q1 = f(x1, y1)
            q2 = f(x2, y2)
            q3 = f(x3, y3)

            q_avg = np.mean([q1, q2, q3])
            qt_dict[e] = q_avg
        else:
            raise Exception("Unknown test case")
    return qt_dict

def test_case_data_ex_2_3(nr_of_test_case):
    if nr_of_test_case == 1:
        x0 = 0
        y0 = 0
        L1 = 1
        L2 = 1
        X, Y = xy(x0=x0, y0=y0, L1=L1, L2=L2, noelms1=4, noelms2=3)
        etov, M = construct_element_table(4, 3)
        # TODO: get lambda value
        lam1 = 1
        lam2 = 1
        qt = construct_qt(etov, X, Y, test_case=1)
        return nr_of_test_case, X, Y, etov, M, lam1, lam2, qt, x0, y0, L1, L2
    elif nr_of_test_case == 2:
        x0 = -2.5
        y0 = -4.8
        L1 = 7.6
        L2 = 5.9
        X, Y = xy(x0=x0, y0=y0, L1=L1, L2=L2, noelms1=4, noelms2=3)
        etov, M = construct_element_table(4, 3)
        lam1 = 1
        lam2 = 1
        qt = construct_qt(etov, X, Y, test_case=2)
        return nr_of_test_case, X, Y, etov, M, lam1, lam2, qt, x0, y0, L1, L2
    else:
        raise Exception("Unknown test case")

def sparse_diags(A):
    # x = dia_matrix(A)
    max_diag_size = A.diagonal(0).shape[0]
    d = []
    data = []
    for diag in range(-A.shape[0], A.shape[1]):
        diag_value = A.diagonal(diag)
        if np.any(diag_value):
            d.append(diag)

            if diag > 0:
                x = np.zeros(max_diag_size)
                x[diag:] = diag_value
                diag_value = x
            data.append(diag_value)

    d = np.array(d)
    B = np.zeros((len(d), max_diag_size))
    return B, d

def main():
    nr_of_test_case, X, Y, etov_dict, M, lam1, lam2, qt, x0, y0, L1, L2 = test_case_data_ex_2_3(2)
    A, b = assembly(X, Y, etov_dict, lam1=lam1, lam2=lam2, qt=qt, M=M)
    B, d = sparse_diags(A)

    print()

if __name__ == '__main__':
    main()


"""ESS (Effective Sample Size) for MCMC sampling efficiency measurements."""
import typing as t

import numpy as np

def ess(samples: np.ndarray,
        mean: t.Union[float, int],
        var: t.Union[float, int],
        verbose: bool = False) -> float:
    r"""ESS (Effective sample size) of a MCMC chain.

    This statistics evaluate how many independent samples a given
    ``samples`` of size n is worth. If all samples are uncorrelated,
    then $ESS = n$, while highly correlated samples have very small
    $ESS \ll n$.

    Arguments
    ---------
    samples : :obj:`np.ndarray`, shape: (n,)
        Samples chain from a MCMC process.

    mean : :obj:`float` or :obj:`int`
        Estimate of the process mean.

    var : :obj:`float` or :obj:`int`
        Estimate of the process variance.

    verbose : :obj:`bool`, optional
        If True, enable message printing related to this process.

    Returns
    -------
    :obj:`float`
        ESS statistic for ``samples.``
    """
    lag_size = 1
    stop_condition = False
    autocorr = []  # type: t.List[float]

    if not isinstance(samples, np.ndarray):
        samples = np.array(samples)

    shifted_samples = samples - mean

    while not stop_condition:
        autocorr.append(
            np.mean([
                shifted_samples[i] * shifted_samples[i + lag_size]
                for i in np.arange(samples.size - lag_size)
            ]))

        if lag_size >= 3 and lag_size % 2 == 1:
            stop_condition = (
                autocorr[lag_size - 1] + autocorr[lag_size - 2]) < 0

        lag_size += 1

    ess_stat = samples.size / (1 + 2 / var * np.sum(autocorr[:-2]))

    if verbose:
        print("Chain size: {} - ESS: {}".format(samples.size, ess_stat))
        print("ESS / (chain size):", ess_stat / samples.size)
        print("Last lag size effectively used:", lag_size - 2)

    return ess_stat


from pathlib import Path
import numpy as np
from dolfin import *

def poisson_error(N, order=1, plot=False):
    # Create mesh and define function space
    mesh = UnitSquareMesh(N, N)
    V = FunctionSpace(mesh, "P", order)

    # Define boundary condition
    u_D = Expression("1 + x[0]*x[0] + 2*x[1]*x[1]", degree=6)

    def boundary(x, on_boundary):
        return on_boundary

    bc = DirichletBC(V, u_D, boundary)

    # Define variational problem
    u = TrialFunction(V)
    v = TestFunction(V)
    f = Constant(-6.0)
    a = dot(grad(u), grad(v)) * dx
    L = f * v * dx

    # Compute solution
    u = Function(V)
    solve(a == L, u, bc)

    if plot:
        # Save solution to file in VTK format
        f = XDMFFile(str(Path(__file__).parent / "poisson.xdmf"))
        f.write(u,0.)

    # Compute error in L2 norm
    error_L2 = errornorm(u_D, u, "L2")
    return error_L2

if __name__ == "__main__":
    Ns = [1, 2, 4, 8, 16, 32, 64, 128]
    errors = []
    for N in Ns:
        errors.append(poisson_error(N, plot=N == 128))

    np.save(Path(__file__).parent / "poisson_convergence_Ns", Ns)
    np.save(Path(__file__).parent / "poisson_convergence_errors", errors)


import numpy as np
import sklearn.metrics

def recall(y_true, y_pred):
    return float(np.dot(y_true, y_pred) / np.sum(y_true))

def precision(y_true, y_pred):
    return float(np.dot(y_true, y_pred) / np.sum(y_pred))

def f1_score(y_true, y_pred):
    p = precision(y_true, y_pred)
    r = recall(y_true, y_pred)
    return 2 * p * r / (p + r)

def _test():
    np.random.seed(16)
    n = 300

    for _ in np.arange(n):
        a, b = np.random.randint(2, size=(2, n))
        rec = recall(a, b)
        rec_ref = sklearn.metrics.recall_score(a, b)

        prec = precision(a, b)
        prec_ref = sklearn.metrics.precision_score(a, b)

        f1 = f1_score(a, b)
        f1_ref = sklearn.metrics.f1_score(a, b)

        assert np.isclose(rec, rec_ref)
        assert np.isclose(prec, prec_ref)
        assert np.isclose(f1, f1_ref)

if __name__ == "__main__":
    _test()


# %% codecell
import cvxpy as cp
# %% markdown
# 2. Convert the annualized average returns and the covariance matrix to `numpy` arrays:
# %% codecell
avg_returns = avg_returns.values
cov_mat = cov_mat.values
# %% markdown
# 3. Set up the optimization problem:
# %% codecell
weights = cp.Variable(n_assets)
gamma = cp.Parameter(nonneg=True)
portf_rtn_cvx = avg_returns * weights
portf_vol_cvx = cp.quad_form(weights, cov_mat)
objective_function = cp.Maximize(portf_rtn_cvx - gamma * portf_vol_cvx)
problem = cp.Problem(objective_function,
                     [cp.sum(weights) == 1, weights >= 0])
# %% markdown
# 4. Calculate the Efficient Frontier:
# %% codecell
N_POINTS = 25
portf_rtn_cvx_ef = np.zeros(N_POINTS)
portf_vol_cvx_ef = np.zeros(N_POINTS)
weights_ef = []
gamma_range = np.logspace(-3, 3, num=N_POINTS)

for i in range(N_POINTS):
    gamma.value = gamma_range[i]
    problem.solve()
    portf_vol_cvx_ef[i] = cp.sqrt(portf_vol_cvx).value
    portf_rtn_cvx_ef[i] = portf_rtn_cvx.value
    weights_ef.append(weights.value)
# %% markdown
# 5. Plot the allocation for different values of the risk-aversion parameter:
# %% codecell
weights_df = pd.DataFrame(weights_ef,
                          columns=RISKY_ASSETS,
                          index=np.round(gamma_range, 3))
ax = weights_df.plot(kind='bar', stacked=True)
ax.set(title='Weights allocation per risk-aversion level',
       xlabel=r'$\gamma$',
       ylabel='weight')
ax.legend(bbox_to_anchor=(1,1))

plt.tight_layout()
plt.savefig('images/ch7_im15.png')
plt.show()

# %% codecell
#MARKS = ['o', 'X', 'd', '*']

fig, ax = plt.subplots()
ax.plot(portf_vol_cvx_ef, portf_rtn_cvx_ef, 'g-')
for asset_index in range(n_assets):
     plt.scatter(x=np.sqrt(cov_mat[asset_index, asset_index]),
                 y=avg_returns[asset_index],
                 marker=MARKS[asset_index],
                 label=RISKY_ASSETS[asset_index],
                 s=150)
ax.set(title='Efficient Frontier',
       xlabel='Volatility',
       ylabel='Expected Returns', )
ax.legend()

plt.tight_layout()
plt.savefig('images/ch7_im16.png')
plt.show()

# # %% markdown
# # ### There's more
# # %% codecell
# x_lim = [0.25, 0.6]
# y_lim = [0.125, 0.325]
#
# fig, ax = plt.subplots(1, 2)
# ax[0].plot(vols_range, rtns_range, 'g-', linewidth=3)
# ax[0].set(title='Efficient Frontier - Minimized Volatility',
#           xlabel='Volatility',
#           ylabel='Expected Returns',
#           xlim=x_lim,
#           ylim=y_lim)
#
# ax[1].plot(portf_vol_cvx_ef, portf_rtn_cvx_ef, 'g-', linewidth=3)
# ax[1].set(title='Efficient Frontier - Maximized Risk-Adjusted Return',
#           xlabel='Volatility',
#           ylabel='Expected Returns',
#           xlim=x_lim,
#           ylim=y_lim)
#
# plt.tight_layout()
# plt.savefig('images/ch7_im17.png')
# plt.show()
# # %% codecell
# max_leverage = cp.Parameter()
# problem_with_leverage = cp.Problem(objective_function,
#                                    [cp.sum(weights) == 1,
#                                     cp.norm(weights, 1) <= max_leverage])
# # %% codecell
# LEVERAGE_RANGE = [1, 2, 5]
# len_leverage = len(LEVERAGE_RANGE)
# N_POINTS = 25
#
# portf_vol_l_ef = np.zeros((N_POINTS, len_leverage))
# portf_rtn_l_ef = np.zeros(( N_POINTS, len_leverage))
# weights_ef = np.zeros((len_leverage, N_POINTS, n_assets))
#
# for lev_ind, leverage in enumerate(LEVERAGE_RANGE):
#     for gamma_ind in range(N_POINTS):
#         max_leverage.value = leverage
#         gamma.value = gamma_range[gamma_ind]
#         problem_with_leverage.solve()
#         portf_vol_l_ef[gamma_ind, lev_ind] = cp.sqrt(portf_vol_cvx).value
#         portf_rtn_l_ef[gamma_ind, lev_ind] = portf_rtn_cvx.value
#         weights_ef[lev_ind, gamma_ind, :] = weights.value
#
# # %% codecell
# fig, ax = plt.subplots()
#
# for leverage_index, leverage in enumerate(LEVERAGE_RANGE):
#     plt.plot(portf_vol_l_ef[:, leverage_index],
#              portf_rtn_l_ef[:, leverage_index],
#              label=f'{leverage}')
#
# ax.set(title='Efficient Frontier for different max leverage',
#        xlabel='Volatility',
#        ylabel='Expected Returns')
# ax.legend(title='Max leverage')
#
# plt.tight_layout()
# plt.savefig('images/ch7_im18.png')
# plt.show()
# # %% codecell
# fig, ax = plt.subplots(len_leverage, 1, sharex=True)
#
# for ax_index in range(len_leverage):
#     weights_df = pd.DataFrame(weights_ef[ax_index],
#                               columns=RISKY_ASSETS,
#                               index=np.round(gamma_range, 3))
#     weights_df.plot(kind='bar',
#                     stacked=True,
#                     ax=ax[ax_index],
#                     legend=None)
#     ax[ax_index].set(ylabel=(f'max_leverage = {LEVERAGE_RANGE[ax_index]}'
#                              '\n weight'))
#
#
# ax[len_leverage - 1].set(xlabel=r'$\gamma$')
# ax[0].legend(bbox_to_anchor=(1,1))
# ax[0].set_title('Weights allocation per risk-aversion level',
#                 fontsize=16)
#
# plt.tight_layout()
# plt.savefig('images/ch7_im19.png')
# plt.show()
# # %% codecell


from numpy import zeros, linspace, array
from numpy.linalg import norm

def gaussJacobi(A, B, Ap, e):
    C = zeros((len(A), len(A)))
    g = zeros((len(B),1))

    # Construir as matrizes C e g
    for i in range(len(A)):
        g[i] = B[i]/A[i][i]
        for j in range(len(A)):
            if i == j :
                C[i][j] = -A[i][j]/A[i][i]

    # Testando a condição de parada
    if norm(C, 1) < 1:
        erro = 1
        # Se quisermos saber quantas iterações foram feitas
        #n = 0
        while erro > e:
            An = C*Ap + g
            erro = norm(An-Ap)/norm(An)
            Ap = An
            #n = n + 1

        return Ap

    else:
        return None

if __name__ == '__main__':

    '''
    t  = int(input("Tamanho M da matriz A[MxM]:"))

    A  = [(map(float,input("linha "+str(x+1)+":").split())) for i in range(t)]
    A  = array(A)

    B  = [[x] for x in list(map(float,input('Digite os valores do vetor B [Mx1]: ').split()))]
    B  = array(B)

    Ap = array([x for x in list(map(float,input('Digite os valores do vetor X[Mx1]: ').split()))])

    e  = input("Entre com o erro máximo tolerado: ")
    '''

    t = 8
    A  = array([[-10,1,-2,0], [-1,11,-1,3], [2,-1,10,-1], [0,3,-1,8]])
    B  = array([-6,25,-11,15])
    Ap = array([0,0,0,0])
    e  = 0.001

    valores = gaussJacobi(A, B, Ap, e)

    if valores is not None:

        print('Os valores convergem no ponto ', end='')
        str_append = ''
        soma = 0

        for i in range (len(valores)):
            str_append = str_append + "x%i: %10.8f" %(i, A[0][i]*valores[i])
            soma = soma + A[0][i]*valores[i]

        print(' y(x)=%10.8f onde:' %soma)
        print(str_append)

    else:
        print('Não há convergência!!!')


from __future__ import annotations
import numpy as np

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

class LogisticRegression:
    """Logistic Regression
    Parameters:
    -----------
    learning_rate: float
        The step length used when following the negative gradient during training.
    num_features: int
        The number of feature in the data
    penalty: str, default='l2'
        The type of penalty used.
    C: float, default=1
       Regularization strength
    """
    def __init__(self, learning_rate: float, num_features: int, penalty: str = 'l2', C: float = 0.1) -> None:
        self.learning_rate = learning_rate
        self.penalty = penalty
        self.C = C
        self.b = 0
        self.w = np.zeros((1, num_features))
        assert penalty in ['l2', 'l1', None]

    def cost_function(self, y: np.ndarray, y_pred: np.ndarray) -> float:
        y_T = y.T
        if self.penalty == 'l1':
            return (-1/y.shape[0]) * (np.sum((y_T*np.log(y_pred)) + ((1-y_T) * np.log(1-y_pred))) + self.C * np.sum(np.absolute(self.w)))
        elif self.penalty == 'l2':
            return (-1/y.shape[0]) * (np.sum((y_T*np.log(y_pred)) + ((1-y_T) * np.log(1-y_pred))) + self.C * np.sum(np.square(self.w)))
        else:
            return (-1/y.shape[0]) * (np.sum((y_T*np.log(y_pred)) + ((1-y_T) * np.log(1-y_pred))))

    def fit(self, X: np.ndarray, y: np.ndarray, num_iterations) -> LogisticRegression:
        for i in range(num_iterations):
            pred = sigmoid(np.dot(self.w, X.T) + self.b)
            cost = self.cost_function(y, pred)

            # Calculate Gradients/Derivatives
            dw = (1 / X.shape[0]) * (np.dot(X.T, (pred - y.T).T))
            db = (1 / X.shape[0]) * (np.sum(pred - y.T))

            self.w = self.w - (self.learning_rate * dw.T)
            self.b = self.b - (self.learning_rate * db)
        return self

    def predict(self, X: np.ndarray) -> list:
        predictions = sigmoid(np.dot(self.w, X.T) + self.b)[0]
        return [1 if pred >= 0.5 else 0 for pred in predictions]

    def predict_proba(self, X: np.ndarray) -> np.ndarray:
        return sigmoid(np.dot(self.w, X.T) + self.b)[0]


# -*- coding: utf-8 -*-
"""
Created on Sat Jun 29 21:17:53 2019

@author: Parikshith.H
"""

import numpy as np

# create an array of sine values
a = np.array([0, np.pi/2, np.pi])
print ("Sine values of array elements:", np.sin(a))
# =============================================================================
# #output:
# Sine values of array elements: [0.0000000e+00 1.0000000e+00 1.2246468e-16]
# =============================================================================

# exponential values
a = np.array([0, 1, 2, 3])
print ("Exponent of array elements:", np.exp(a))
# =============================================================================
# #output:
# Exponent of array elements: [ 1.          2.71828183  7.3890561  20.08553692]
# =============================================================================

# square root of array values
print ("Square root of array elements:", np.sqrt(a))
# =============================================================================
# #output:
# Square root of array elements: [0.         1.         1.41421356 1.73205081]
# =============================================================================


from sklearn.datasets import load_boston
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
import pandas as pd
import numpy as np

# Gather Data
boston_dataset = load_boston()
data = pd.DataFrame(data = boston_dataset.data, columns = boston_dataset.feature_names)
features = data.drop(["INDUS", "AGE"], axis = 1)

# Log prices
log_prices = np.log(boston_dataset.target)
# Covert to 2D array.
target = pd.DataFrame(log_prices, columns = ["PRICE"])

# Create property_stats varible to store
CRIME_IDX = 0
ZN_IDX = 1
CHAS_IDX = 2
RM_IDX = 4
PTRATIO_IDX = 8
property_stats = features.mean().values.reshape(1, 11)
ZILLOW_MEDIAN_PRICE = 583.3
SCALE_FACTOR = ZILLOW_MEDIAN_PRICE / np.median(boston_dataset.target)

regr = LinearRegression().fit(features, target)
fitted_values = regr.predict(features)
MSE = mean_squared_error(target, fitted_values)
RMSE = np.sqrt(MSE)

def get_log_estimate(room_number, pupil_teacher_ratio, beside_river = False, high_confidence = True):
    # Configure property values
    property_stats[0][RM_IDX] = room_number
    property_stats[0][PTRATIO_IDX] = pupil_teacher_ratio
    if beside_river:
        property_stats[0][CHAS_IDX] = 1
    else:
        property_stats[0][CHAS_IDX] = 0
    # Predict
    log_estimate = regr.predict(property_stats)[0][0]
    # Calculate the range
    if high_confidence:
        # 2 standard deviation
        upper_bound = log_estimate + 2*RMSE
        lower_bound = log_estimate - 2*RMSE
        interval = 95
    else:
        # 1 standard deviation
        upper_bound = log_estimate + RMSE
        lower_bound = log_estimate - RMSE
        interval = 68
    return log_estimate, upper_bound, lower_bound, interval

def get_today_dollar_estimate(room_number, pupil_teacher_ratio, beside_river = False, high_confidence = True):
    """ Estimate today's property price in Boston.
    """
    if room_number < 1 or pupil_teacher_ratio < 1:
        return "Information is not valid."
    log_estimate, upper_bound, lower_bound, confidence = get_log_estimate(room_number, pupil_teacher_ratio, beside_river, high_confidence)
    estimate_today = np.around(np.e**log_estimate * SCALE_FACTOR * 1000, -3)
    estimate_high = np.around(np.e**upper_bound * SCALE_FACTOR * 1000, -3)
    estimate_low = np.around(np.e**lower_bound * SCALE_FACTOR * 1000, -3)
    print(f"Today's estimate: {estimate_today}, Upper Bound: {estimate_high}, Lower Bound: {estimate_low}, Confidence: {confidence}%")


import numpy as np
from scipy import special

def t_fit(X, dof=4, iter=200, tol=1e-7):
    '''t
    Estimates the mean and covariance of the dataset
    X (rows are datapoints) assuming they come from a
    student t likelihood with no priors and dof degrees
    of freedom using the EM algorithm.
    :param X: np.array[n,d]
    :param dof: float > 2
    :param iter: int, max iterations of algorithm
    :param tol: float, tolerance
    :return: estimated covariance, estimated mean, list of
             objectives at each iteration.
    :rtype: np.array[d,d], np.array[d], list[float]
    '''
    # initialize parameters
    D = X.shape[1]
    N = X.shape[0]
    cov = np.cov(X,rowvar=False)
    mean = X.mean(axis=0)
    mu = X - mean[None,:]
    delta = np.einsum('ij,ij->i', mu, np.linalg.solve(cov,mu.T).T)
    z = (dof + D) / (dof + delta)
    obj = [
        -N*np.linalg.slogdet(cov)[1]/2 - (z*delta).sum()/2 \
        -N*special.gammaln(dof/2) + N*dof*np.log(dof/2)/2 + dof*(np.log(z)-z).sum()/2
    ]

    # iterate
    for i in range(iter):
        # M step
        mean = (X * z[:,None]).sum(axis=0).reshape(-1,1) / z.sum()
        mu = X - mean.squeeze()[None,:]
        cov = np.einsum('ij,ik->jk', mu, mu * z[:,None])/N

        # E step
        delta = (mu * np.linalg.solve(cov,mu.T).T).sum(axis=1)
        delta = np.einsum('ij,ij->i', mu, np.linalg.solve(cov,mu.T).T)
        z = (dof + D) / (dof + delta)

        # store objective
        obj.append(
            -N*np.linalg.slogdet(cov)[1]/2 - (z*delta).sum()/2 \
            -N*special.gammaln(dof/2) + N*dof*np.log(dof/2)/2 + dof*(np.log(z)-z).sum()/2
        )

        if np.abs(obj[-1] - obj[-2]) < tol:
            break

    return mean.squeeze(), cov

def t_generate(mu, S, dof=4, n=1):
    '''generate random variables of multivariate t distribution
    Parameters
    ----------
    mu : array_like
        mean of random variable, length determines dimension of random variable
    S : array_like
        square array of covariance  matrix
    d0f : int or float
        degrees of freedom
    n : int
        number of observations, return random array will be (n, len(mu))
    Returns
    -------
    rvs : ndarray, (n, len(mu))
        each row is an independent draw of a multivariate t distributed
        random variable
    '''
    mu = np.asarray(mu)
    d = len(mu)
    if dof == np.inf:
        x = 1.
    else:
        x = np.random.chisquare(dof, n)/dof
    z = np.random.multivariate_normal(np.zeros(d),S,(n,))
    return mu + z/np.sqrt(x)[:,None]


'''
BasicMatrix: Matrix Algebra Package
Author: Christiaan van Eeden
Version: 0.1
'''

#import packages
import numpy as np
import random as rdm

class BasicMatrix:
    '''
	The BasicMatrix class offers basic matrix arithmetic
	'''
    def __init__(self, row, col):
        '''
    	Attributes
    		rows (int): number of rows in matrix
    		cols (int): number of columns in matrix
    		method (str): filling method (randint/id)
    	'''
        self.row = row
        self.col = col
        self.mat = np.zeros([self.row, self.col])

    def get_matrix(self):
        '''
        Returns matrix from attribute
        '''
        return self.mat

    def set_matrix(self, X):
        '''
        Sets the instance matrix and dimensions to input matrix
        '''
        self.mat = X
        self.row = len(X)
        self.col = len(X[0])

    def shape(self):
        '''
        Returns dimensions (rows, columns) of instance matrix
        '''
        return np.shape(self.mat)

    def fill_matrix(self):
        '''
        Fills the matrix with random integers
        '''
        for m in range(self.row):
            for n in range(self.col):
                self.mat[m][n] = rdm.randint(0, 10)

    def dot(self, other):
        '''
        Multiplication of matrices after checking correct  dimension requirements

        Returns new shaped answer matrix or raises error for shape
        '''
        if self.col == other.row:
            new_mat = np.zeros([self.row, other.col])
            for m in range(self.row):
                for n in range(other.col):
                    for k in range(other.row):
                        new_mat[m][n] += self.mat[m][k] * other.mat[k][n]
            return new_mat
        else:
            raise ValueError('Shapes cannot be multiplied: change A colummns to match B rows')

    def __add__(self, other):
        '''
        Overwrites '+'  method for adding matrices

        Returns answer matrix or raises error
        '''
        if (self.row == other.row) and (self.col == other.col):
            return self.mat + other.mat
        else:
            raise ValueError('Shapes cannot be added: require equal rows and columns for AB')

    def __sub__(self, other):
        '''
        Overwrites '-' method for subtracting matrices

        Returns answer matrix or raises error
        '''
        if (self.row == other.row) and (self.col == other.col):
            return self.mat - other.mat
        else:
            raise ValueError('Shapes cannot be subtracted: require equal rows and columns for AB')

# mat1 = BasicMatrix(2, 3)
# mat1.fill_matrix()
# print(mat1.get_matrix())
# mat2 = BasicMatrix(2, 2)
# mat2.fill_matrix()
# print(mat2.get_matrix())

# print(BasicMatrix.dot(mat1, mat2))

# print(np.dot(mat1.get_matrix(), mat2.get_matrix()))

# print(mat1 + mat2)

# print(mat1 - mat2)

# bruh = np.ones([2, 2])

# mat1.set_matrix(bruh)

# print(mat1.get_matrix())

# print(mat1.shape())


# -*- coding: utf-8 -*-
"""
Created on Mon Mar 11 10:36:13 2019

@author: Miguel Angel Velez
"""

import scipy.stats as ss
from mstats import generals, linregr

#------------------------------------------------------------------------------------------------#
#------------------------------------- Confidence Intervals -------------------------------------#
#------------------------------------------------------------------------------------------------#

def norm(n, x_, sd, alpha):
    """
    Function to get the confidence interval to estimate the average population
    when we have large samples (n >= 30).

    Parameters:
    --------------------------
    n : int
        Sample size.

    x_ : float, double
        Sample average.

    sd : float, double
        Sample or population standard deviation.

    alpha : float, double
        Confidence level to estimate our unknown parameter (the average population).
        Each value should be in the range [0, 1].

    Returns:
    --------------------------
    interval : tuple
        The confidence interval, with the minimum value at [0] and the maximum value
        at [1] for the unknown parameter (the average population).
    """
    if (n <= 30):
        print("The sample size must be greater than 30.")
    else:
        sd_error = sd / (n**0.5)
        z_value = ss.norm.interval(alpha)[1]

        minimum = x_ - (z_value * sd_error)
        maximum = x_ + (z_value * sd_error)

        return minimum, maximum

def norm_2p(n1, n2, x_1, x_2, sd1, sd2, alpha):
    """
    Function to get the confidence interval to estimate the difference between
    2 population averages in large samples (n >= 30).

    Parameters:
    --------------------------
    n1 : int
        Sample 1 size.

    n2 : int
        Sample 2 size.

    x_1 : float, double
        Sample 1 average.

    x_2 : float, double
        Sample 2 average.

    sd1 : float, double
        Sample or population 1 standard deviation.

    sd2 : float, double
        Sample or population 2 standard deviation.

    alpha : float, double
        Confidence level to estimate our unknown parameter (the difference between
        2 population averages).
        Each value should be in the range [0, 1].

    Returns:
    --------------------------
    interval : tuple
        The confidence interval, with the minimum value at [0] and the maximum value
        at [1] for the unknown parameter (the difference between
        2 population averages).
    """
    if (n1 <= 30 or n2 <= 30):
        print("The sample sizes must be greater than 30.")
    else:
        sd_error = ((sd1**2 / n1) + (sd2**2 / n2))**0.5
        z_value = ss.norm.interval(alpha)[1]

        minimum = (x_1 - x_2) - (z_value * sd_error)
        maximum = (x_1 - x_2) + (z_value * sd_error)

        return minimum, maximum

def t(n, x_, s, alpha):
    """
    Function to get the confidence interval to estimate the average population
    when we have small samples (n < 30).

    Parameters:
    --------------------------
    n : int
        Sample size.

    x_ : float, double
        Sample average.

    s : float, double
        Sample standard deviation.

    alpha : float, double
        Confidence level to estimate our unknown parameter (the average population).
        Each value should be in the range [0, 1].

    Returns:
    --------------------------
    interval : tuple
        The confidence interval, with the minimum value at [0] and the maximum value
        at [1] for the unknown parameter (the average population).
    """
    if (n > 30):
        print("The sample size must be less than 30.")
    else:
        df = n - 1
        s_error = s / (n**0.5)
        t_value = ss.t.interval(alpha, df)[1]

        minimum = x_ - (t_value * s_error)
        maximum = x_ + (t_value * s_error)

        return minimum, maximum

def t_2p(n1, n2, x_1, x_2, s1, s2, alpha, var):
    """
    Function to get the confidence interval to estimate the difference between
    2 population averages in small samples (n < 30).

    Parameters:
    --------------------------
    n1 : int
        Sample 1 size.

    n2 : int
        Sample 2 size.

    x_1 : float, double
        Sample 1 average.

    x_2 : float, double
        Sample 2 average.

    s1 : float, double
        Sample 1 standard deviation.

    s2 : float, double
        Sample 2 standard deviation.

    alpha : float, double
        Confidence level to estimate our unknown parameter (the difference between
        2 population averages).
        Each value should be in the range [0, 1].

    var : bool
        Are the population variances equal?
        True if population variances are equal.
        False if population variances are different.

    Returns:
    --------------------------
    interval : tuple
        The confidence interval, with the minimum value at [0] and the maximum value
        at [1] for the unknown parameter (the difference between
        2 population averages).
    """
    if (n1 > 30 or n2 > 30):
        print("The sample sizes must be less than 30.")
    else:
        if (var is True):
            df = n1 + n2 - 2
            s2p = (((s1**2) * (n1 - 1)) + (s2**2 * (n2 - 1))) / df
            sp_error = ((s2p / n1) + (s2p / n2))**0.5
            t_value = ss.t.interval(alpha, df)[1]

            minimum = (x_1 - x_2) - (t_value * sp_error)
            maximum = (x_1 - x_2) + (t_value * sp_error)

            return minimum, maximum
        else:
            df = generals.get_df_var(n1, n2, s1, s2)

            sp_error = (((s1**2) / n1) + ((s2**2) / n2))**0.5
            t_value = ss.t.interval(alpha, df)[1]

            minimum = (x_1 - x_2) - (t_value * sp_error)
            maximum = (x_1 - x_2) + (t_value * sp_error)

            return minimum, maximum

def prop(n, p, alpha):
    """
    Function to get the confidence interval to estimate the population proportion.

    Parameters:
    --------------------------
    n : int
        Sample size.

    p : float, double
        Sample proportion.
        Each value should be in the range [0, 1].

    alpha : float, double
        Confidence level to estimate our unknown parameter (the population proportion).
        Each value should be in the range [0, 1].

    Returns:
    --------------------------
    interval : tuple
        The confidence interval, with the minimum value at [0] and the maximum value
        at [1] for the unknown parameter (the population proportion).
    """
    sp_error = ((p * (1 - p)) / n)**0.5
    z_value = ss.norm.interval(alpha)[1]

    minimum = p - (z_value * sp_error)
    maximum = p + (z_value * sp_error)

    return minimum, maximum

def prop_2p(n1, n2, p1, p2, alpha):
    """
    Function to get the confidence interval to estimate the difference between
    2 population proportions.

    Parameters:
    --------------------------
    n1 : int
        Sample 1 size.

    n2 : int
        Sample 2 size.

    p1 : float, double
        Sample 1 proportion.
        Each value should be in the range [0, 1].

    p2 : float, double
        Sample 2 proportion.
        Each value should be in the range [0, 1].

    alpha : float, double
        Confidence level to estimate our unknown parameter (the population proportion).
        Each value should be in the range [0, 1].

    Returns:
    --------------------------
    interval : tuple
        The confidence interval, with the minimum value at [0] and the maximum value
        at [1] for the unknown parameter (the difference between
        2 population proportions).
    """
    sp_error = (((p1 * (1 - p1)) / n1) + ((p2 * (1 - p2)) / n2))**0.5
    z_value = ss.norm.interval(alpha)[1]

    minimum = (p1 - p2) - (z_value * sp_error)
    maximum = (p1 - p2) + (z_value * sp_error)

    return minimum, maximum

def pair_2p(n, cond1, cond2, alpha):
    """
    Function to get the confidence interval to estimate the difference between
    2 population averages in paired samples.

    Parameters:
    --------------------------
    n : int
        Sample sizes.

    cond1 : list
        Sample initial condition list.

    cond2 : list
        Sample final condition list.

    alpha : double, float
        Confidence level to estimate our unknown parameter (the difference between
        2 population averages).
        Each value should be in the range [0, 1].

    Returns:
    --------------------------
    interval : tuple
        The confidence interval, with the minimum value at [0] and the maximum value
        at [1] for the unknown parameter (the difference between
        2 population averages).

    """
    d1 = [cond1[x] - cond2[x] for x in range(n)]
    d21 = [x**2 for x in d1]
    sumd1 = sum(d1)
    sumd21 = sum(d21)
    d_ = sumd1 / n
    sd = ((sumd21 - (n * (d_**2))) / (n - 1))**0.5
    sd_error = sd / (n**0.5)

    if n <= 30:
        df = n - 1
        t_value = ss.t.interval(alpha, df)[1]

        minimum = d_ - (t_value * sd_error)
        maximum = d_ + (t_value * sd_error)

        return minimum, maximum

    else:
        z_value = ss.norm.interval(alpha)[1]

        minimum = d_ - (z_value * sd_error)
        maximum = d_ + (z_value * sd_error)

        return minimum, maximum

def n_avg(alpha, s, error):
    """
    Function to determine the appropiate sample size to estimate the average population.

    Parameters:
    --------------------------
    alpha : float, double
        Confidence level to determine our sample size.
        Each value should be in the range [0, 1].

    s: float, double
        Pilot sample standard deviation, or population standar deviation.

    error: float, double
        Tolerable size error.

    Returns:
    --------------------------
    n : int
        The appropiate sample size to estimate the average population.
        This sample size is rounded.
    """
    z_value = ss.norm.interval(alpha)[1]

    n = ((z_value**2)*(s**2)) / (error**2)

    return round(n)

def n_avg_2p(alpha, s1, s2, error):
    """
    Function to determine the appropiate sample size to estimate the difference
    between 2 average populations.

    Parameters:
    --------------------------
    alpha : float, double
        Confidence level to determine our sample size.
        Each value should be in the range [0, 1].

    s1 : float, double
        Pilot sample 1 standard deviation, or population standar deviation.

    s2 : float, double
        Pilot sample 2 standard deviation, or population standar deviation.

    error: float, double
        Tolerable size error.

    Returns:
    --------------------------
    n : int
        The appropiate sample size to estimate the he difference
        between 2 average populations.
        This sample size is rounded.
    """
    z_value = ss.norm.interval(alpha)[1]

    n = ((z_value**2)*(s1**2 + s2**2)) / (error**2)

    return round(n)

def n_prop(alpha, p, error):
    """
    Function to determine the appropiate sample size to estimate the population proportion.

    Parameters:
    --------------------------
    alpha : float, double
        Confidence level to determine our sample size.
        Each value should be in the range [0, 1].

    p: float, double
        Sample proportion.
        Each value should be in the range [0, 1].

    error: float, double
        Tolerable size error.

    Returns:
    --------------------------
    n : int
        The appropiate sample size to estimate the population proportion.
        This sample size is rounded.
    """
    z_value = ss.norm.interval(alpha)[1]

    n = ((z_value**2) * (p * (1 - p))) / (error**2)

    return round(n)

def n_prop_2p(alpha, p1, p2, error):
    """
    Function to determine the appropiate sample size to estimate the
    difference between 2 population proportions.

    Parameters:
    --------------------------
    alpha : float, double
        Confidence level to determine our sample size.
        Each value should be in the range [0, 1].

    p1: float, double
        Sample proportion 1.
        Each value should be in the range [0, 1].

    p2: float, double
        Sample proportion 2.
        Each value should be in the range [0, 1].

    error: float, double
        Tolerable size error.

    Returns:
    --------------------------
    n : int
        The appropiate sample size to estimate the difference between
        2 population proportions.
        This sample size is rounded.
    """
    z_value = ss.norm.interval(alpha)[1]

    n = ((z_value**2) * ((p1 * (1 - p1)) + ((p2 * (1 - p2))))) / (error**2)

    return round(n)

def f_2p(n1, n2, var1, var2, alpha):
    """
    """
    df1 = n1 - 1
    df2 = n2 - 1
    f_value = ss.f(df1, df2).interval(alpha)

    minimum = var1 / (f_value[1] * var2)
    maximum = var1 / (f_value[0] * var2)

    return minimum, maximum

def beta(x, y, alpha):
    """
    """
    df = len(x) - 1
    t_value = ss.t(df).interval(alpha)[1]

    minimum = linregr.slope(x, y) - (t_value * linregr.get_sb(x, y))
    maximum = linregr.slope(x, y) + (t_value * linregr.get_sb(x, y))

    return minimum, maximum

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


# Computational Linear Algebra Ep#5 Lu Decomposition
# By: Nick Space Cowboy

import numpy as np
from lin_alg import lin_alg

if __name__ == "__main__":
	# generate A matrix
	A = np.array(np.random.randint (0,100,(4,4)), dtype=np.float64)
	print("A: ")
	print(A)
	# perform LU decomposition with our LinAlg LU function
	L, U = lin_alg.LU(A)
	print("L: ")
	print(L)
	print("U: ")
	print(U)
	print("Check: ")
	# Check that LU=A
	print(np.dot(L,U))


import matplotlib.pyplot as plt
import numpy as np

# define gaussian distribution parameters sigma= standard ddeviation and mu = mean
sigma = 0
mu = 1

#generate 5*10^4 random numbers from gaussian distribution
x=np.random.normal(sigma, mu, size=5*10**4)

# generate a histogram using x and store it as an array of 100 bins in bins variable
count, bins, ignored = plt.hist(x,100,density=True)

# plot the density funtion after defining it as a function of mu, sigma and bins
plt.plot(bins, 1/(sigma*np.sqrt(2*np.pi))*np.exp( -(bins-mu)**2 / (2*sigma**2) ), linewidth=2, color='r' )
plt.title("Gaussian probability distributions")
plt.show()


import numpy
import sympy

print("System of nonlinear equations \n")

m = 0.3
a = 0.5

EPS = 10.0 ** -5

# Исходные уравнения в формате f(x,y) = 0
(x, y) = sympy.symbols("x y")
eq1 = sympy.tan(x * y + m) - x
eq2 = a * (x ** 2) + 2 * (y ** 2) - 1

print(eq1, "= 0")
print(eq2, "= 0")
print()

# Подстановка в исходные уравнения для вычисления значения f(x,y)
def val1(x, y):
    return numpy.tan(x * y + m) - x

def val2(x, y):
    return a * (x ** 2) + 2 * (y ** 2) - 1

# Выраженные из исходных уравнений x = функция(x,y), y = функция(x,y)
def eqx(x, y):
    return numpy.tan(x * y + m)

def eqy(x, y):
    return numpy.sqrt((1 - a * (x ** 2)) / 2)

# Вычисление матрицы Якоби
def W(x, y):
    return numpy.array([
        [(1 + numpy.tan(x * y + m) ** 2) * y - 1, (1 + numpy.tan(x * y + m) ** 2) * x],
        [2 * a * x, 4 * y]
    ])

# Графики исходных уравнений
plots = sympy.plot_implicit(sympy.Eq(eq1, 0), (x, -2, 2), (y, -2, 2), line_color="blue", show=False)
plots.extend(sympy.plot_implicit(sympy.Eq(eq2, 0), (x, -2, 2), (y, -2, 2), line_color="red", show=False))
# plots.show()

iters = 0

def SimpleSolve(x0, y0):
    global iters
    iters = 0
    (x, y) = (x0, y0)
    while True:
        iters += 1
        oldx = x
        oldy = y
        x = eqx(x, y)
        y = eqy(x, y)
        if not (numpy.isfinite(x) and numpy.isfinite(y)):
            raise RuntimeError("Sequence {x} is divergent")
        if max(abs(x - oldx), abs(y - oldy)) < EPS:
            return x, y

def NewtonSolve(x0, y0):
    global iters
    iters = 0
    (x, y) = (x0, y0)
    while True:
        iters += 1
        w = W(x, y)
        f = numpy.array([[val1(x, y)], [val2(x, y)]])
        deltas = numpy.linalg.solve(w, -f)
        x += deltas[0][0]
        y += deltas[1][0]
        if not (numpy.isfinite(x) and numpy.isfinite(y)):
            raise RuntimeError("Sequence {x} is divergent")
        if max(abs(deltas)) < EPS:
            return x, y

x0 = 1.0
y0 = 0.5
print("Initial guess =", (x0, y0))
print()

def run(method):
    global iters
    try:
        (x, y) = method(x0, y0)
        print("(x, y) = ({:.4f}, {:.4f})".format(x, y))
        print(" via {} method (with {} iterations)".format(method.__name__, iters))
    except Exception as ex:
        print("ERROR: {} - in {} method (with {} iterations)".format(ex, method.__name__, iters))
    print()

run(SimpleSolve)
run(NewtonSolve)

plots.show()


"""This module contains plane fitting procedures. This is only needed to *evaluate* RMSE of a plane

"""
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np

np.set_printoptions(threshold=3600, linewidth=350, precision=6, suppress=True)

# Source: https://stackoverflow.com/a/38770513/9341063

def PCA(data, correlation=False, sort=True):
    """ Applies Principal Component Analysis to the data

    Parameters
    ----------
    data: array
        The array containing the data. The array must have NxM dimensions, where each
        of the N rows represents a different individual record and each of the M columns
        represents a different variable recorded for that individual record.
            array([
            [V11, ... , V1m],
            ...,
            [Vn1, ... , Vnm]])

    correlation(Optional) : bool
            Set the type of matrix to be computed (see Notes):
                If True compute the correlation matrix.
                If False(Default) compute the covariance matrix.

    sort(Optional) : bool
            Set the order that the eigenvalues/vectors will have
                If True(Default) they will be sorted (from higher value to less).
                If False they won't.
    Returns
    -------
    eigenvalues: (1,M) array
        The eigenvalues of the corresponding matrix.

    eigenvector: (M,M) array
        The eigenvectors of the corresponding matrix.

    Notes
    -----
    The correlation matrix is a better choice when there are different magnitudes
    representing the M variables. Use covariance matrix in other cases.

    """

    mean = np.mean(data, axis=0)

    data_adjust = data - mean

    #: the data is transposed due to np.cov/corrcoef syntax
    if correlation:
        matrix = np.corrcoef(data_adjust.T)
    else:
        matrix = np.cov(data_adjust.T)

    eigenvalues, eigenvectors = np.linalg.eig(matrix)

    if sort:
        #: sort eigenvalues and eigenvectors
        sort = eigenvalues.argsort()[::-1]
        eigenvalues = eigenvalues[sort]
        eigenvectors = eigenvectors[:, sort]

    return eigenvalues, eigenvectors

# Source: https://stackoverflow.com/a/38770513/9341063

def best_fitting_plane(points, equation=False):
    """ Computes the best fitting plane of the given points

    Parameters
    ----------
    points: array
        The x,y,z coordinates corresponding to the points from which we want
        to define the best fitting plane. Expected format:
            array([
            [x1,y1,z1],
            ...,
            [xn,yn,zn]])

    equation(Optional) : bool
            Set the oputput plane format:
                If True return the a,b,c,d coefficients of the plane.
                If False(Default) return 1 Point and 1 Normal vector.
    Returns
    -------
    a, b, c, d : float
        The coefficients solving the plane equation.

    or

    point, normal: array
        The plane defined by 1 Point and 1 Normal vector. With format:
        array([Px,Py,Pz]), array([Nx,Ny,Nz])

    """

    w, v = PCA(points)

    #: the normal of the plane is the last eigenvector
    normal = v[:, 2]
    normal = normal / np.linalg.norm(normal)

    #: get a point from the plane
    point = np.mean(points, axis=0)

    if equation:
        a, b, c = normal
        d = -(np.dot(normal, point))
        return a, b, c, d

    else:
        return point, normal

def point_to_plane_distance_simple(points, p_point, p_normal: np.array):
    distance = []
    for i in range(points.shape[0]):
        point = points[i, :]
        signed_distance = p_normal.dot(p_point - point)
        distance.append(signed_distance)
    distance = np.array(distance)
    rmse = np.sqrt(np.mean(distance ** 2))
    return distance, rmse

def calculate_rmse(points: np.array, p_point: np.array, p_normal: np.array):
    points_a = points - p_point
    distance = points_a @ p_normal
    distance = np.array(distance)
    rmse = np.sqrt(np.mean(distance ** 2))
    return distance, rmse

def fit_plane_and_get_rmse(points: np.array):
    """Fit Plane to 3D point and return RMSE

    Arguments:
        points {np.array} -- Point cloud, NX3

    Returns:
        tuple(point, normal, rmse) -- Return a for a point on the plane, its normal, and the RMSE
    """

    point, normal = best_fitting_plane(points)
    distance, rmse = calculate_rmse(points, point, normal)
    return point, normal, distance, rmse

def main():
    direction = 0.7071
    normalizing = np.sqrt(3 * direction * direction)
    unit = direction / normalizing
    a = unit
    b = unit
    c = unit
    d = 1

    noise = 1

    print(f"Ground Truth Normal: [{a:.2f},{b:.2f},{c:.2f}], d: {d}")

    # Create point cloud
    pc_gt = []
    pc_ns = []
    for x in range(-10, 10, 1):
        for y in range(-10, 10, 1):
            true_z = -(a * x + b * y) / c + d
            noise_z = true_z + np.random.rand() * noise
            pc_gt.append([x, y, true_z])
            pc_ns.append([x, y, noise_z])

    pc_gt = np.array(pc_gt)
    pc_ns = np.array(pc_ns)

    # fit plane and get rmse
    point, normal, distance, rmse = fit_plane_and_get_rmse(pc_ns)
    print(f"Predicted Normal: {normal}; point: {point}; RMSE: {rmse:.3f}")

    # plot data
    plt.figure()
    ax = plt.subplot(111, projection='3d')
    ax.scatter(pc_ns[:, 0], pc_ns[:, 1], pc_ns[:, 2], color='b')
    plt.show()

    # norms = np.apply_along_axis(np.linalg.norm, 1, pc_ns)
    # pc_ns_norm = pc_ns / np.expand_dims(norms, axis=1)
    # ax.scatter(pc_ns_norm[:, 0], pc_ns_norm[:, 1], pc_ns_norm[:, 2], color='g')
    # print(pc_ns)

if __name__ == "__main__":
    main()


import numpy as np

class Neural_Network(object):
    def __init__(self):
    #Define Hyper Parameters
        self.inputLayerSize = 2
        self.outputLayerSize = 1
        self.hiddenLayerSize = 3

    #Weights (Parameters)
        self.W1 = np.random.randn(self.inputLayerSize, self.hiddenLayerSize)
        self.W2 = np.random.randn(self.hiddenLayerSize, self.outputLayerSize)

#Forward Propagation
    def forward(self, X):
        #Propagate inputs through network
        #Synapses of ANN multiplies its weight with the input and output the result.
        #So to get the activity of second layer we multiply the input matrix with weight matrix
        #X(1)*W(2) = Z(2)
        #Z(2) is a 3x3 matrix
        self.z2 = np.dot(X, self.W1)
        #Apply activation function to the input in second layer
        #A(2) = 1/1-e^-z(2)
        self.a2 = self.sigmoid(self.z2)
        #A(2)*W(2) = Z(2)
        self.z3 = np.dot(self.a2, self.W2)
        #We apply the activation function on Z(3)
        yHat = self.sigmoid(self.z3)
        #We get a 3x1 matrix as output
        return yHat

    def sigmoid(self, z):
    #Apply sigmoid activation function to scalar, vector, or matrix
        return 1/(1+np.exp(-z))

if __name__ == '__main__':

    # X = (hours sleeping, hours studying), y = Score on test
    X = np.array(([3,5], [5,1], [10,2]), dtype=float)
    y = np.array(([75], [82], [93]), dtype=float)
    X = X/np.amax(X, axis=0)
    y = y/100 #Max test score is 100
    #Create an instance of neural network
    mANN = Neural_Network()

    #Run the forward propagation
    yn = mANN.forward(X)

    #Print the output of neural network
    print("Output of ANN:",yn)

    #Print the original network
    print("Original data:",y)


#!/usr/bin/env python

import sys
import argparse
import numpy as np
import scipy as sp
import scipy.stats

def mean_confidence_interval(data, confidence=0.95):
    a = 1.0*np.array(data)
    n = len(a)
    m, se = np.mean(a), scipy.stats.sem(a)
    h = se * sp.stats.t.ppf((1+confidence)/2., n-1)
    return m, m-h, m+h

parser = argparse.ArgumentParser(description="Compute average and confidence interval")
parser.add_argument('-c', '--col', default=0, type=int, help='Column number - default=0')
parser.add_argument('-i', '--int', default=95, type=float, help='Confidence interval - default=95')

parser.add_argument('file')
args = parser.parse_args()

#i = 0
data=[]
with open(args.file) as file:
    xsum = 0
    xmean = 0
    for  line in file:
        line = line.split()
        data.append(float(line[args.col]))
        #print(i,'  ',data[i])
        #i = i + 1

print(mean_confidence_interval(data,args.int*1.0/100.))


import numpy as np

class KNN:
    def __init__(self, K, metric_type):
        """
        Args:
            K (int): K-value
            metric_type (str): L1, L2, or L-inf
        """
        self.K = K
        self.metric_type = metric_type

    def distance_func(self, vec1, vec2):
        """
        Computes the distance between two d-dimension vectors.

        Args:
            vec1 ((d,) np.ndarray): d-dim vector
            vec2 ((d,)) np.ndarray): d-dim vector
        """
        diff = vec1 - vec2
        distance = 0
        if self.metric_type == "L1":
            # write your code here
            # todo
            distance = np.sum(np.abs(diff))

        elif self.metric_type == "L2":
            # write your code here
            distance = np.sqrt(np.sum(np.square(diff)))
        elif self.metric_type == "L-inf":
            # write your code here
            distance = np.max(np.abs(diff))

        return distance

    def fit(self, x_train, y_train):
        """
        Args:
            x_train ((n,d) np.ndarray): training data with n samples and d features
            y_train ((n,) np.ndarray): training labels
        """
        self.x_train = x_train
        self.y_train = y_train

    def compute_distances_neighbors(self, sample):
        """
        Computes the distance between every data point in the train set and the
        given sample and then finds the k-nearest neighbors.

        Returns a numpy array of the labels of the k-nearest neighbors.

        Args:
            sample ((d,) np.ndarray): the given sample to be computed

        Returns:
            neighbors (list): K-nearest neighbors' labels
        """

        # write your code here
        # todo

        dis = []
        for i in range(self.x_train.shape[0]):
            dis.append(self.distance_func(sample, self.x_train[i, :]))
        labels = []
        index = sorted(range(len(dis)), key=dis.__getitem__)
        for j in range(self.K):
            labels.append(self.y_train[index[j]])
        neighbors = labels

        return neighbors

    @staticmethod
    def majority(neighbors):
        """
        Performs majority voting and returns the predicted value for the test sample.
        Since we're performing binary classification, the possible values are [0,1].

        Args:
            neighbors (list): K-nearest neighbors' labels

        Returns:
            predicted_value (int): the predicted label for the given sample
        """

        # write your code here
        # todo
        bins = np.bincount(neighbors)
        predicted_value = np.argmax(bins)
        return predicted_value

    def predict(self, X_test):
        """
        Computes the predicted values for the entire test set.

        Args:
            x_train ((n,d) np.ndarray): training data with n samples and d features
            y_train ((n,) np.ndarray): training labels
            X_test ((n,d) np.ndarray): test data

        Returns:
            pred_test ((n,) np.ndarray): output for every entry in the test set
        """
        pred_test = np.array(
            [self.majority(self.compute_distances_neighbors(sample)) for sample in X_test])

        return pred_test

def accuracy_score(pred, y):
    """
    Computes the accuracy of the predicted data.

    Args:
        pred ((n,) np.ndarray): predicted values for n samples
        y ((n,) np.ndarray): labels for n samples

    Returns:
        acc (float): accuracy
    """

    acc = np.mean(pred == y)
    # print(acc)
    return acc

def main():
    n, m, d = input().split()
    x_train = []
    x_test = []
    y_train = []
    y_test = []
    for _ in range(int(n)):
        split_line = input().split()
        X = split_line[:int(d)]
        y = split_line[-1]
        x_train.append([float(x) for x in X])
        y_train.append(int(y))
    for _ in range(int(m)):
        split_line = input().split()
        X = split_line[:int(d)]
        y = split_line[-1]
        x_test.append([float(x) for x in X])
        y_test.append(int(y))
    x_train = np.array(x_train)
    x_test = np.array(x_test)
    y_train = np.array(y_train)
    y_test = np.array(y_test)

    metric_types = ["L1", "L2", "L-inf"]
    params = [(x, y) for x in range(1, 6, 1) for y in metric_types]
    value = -100
    list = []
    for param in params:
        model = KNN(param[0], param[1])
        model.fit(x_train, y_train)
        pred = model.predict(x_test)
        acc = accuracy_score(pred, y_test)
        # print(param)
        # print(acc)
        if acc > value:
            list.clear()
            list.append(param)
            value = acc
            continue
        elif acc == value:
            list.append(param)
            continue
        else:
            continue
    b = np.array(list)
    a = np.sort(b, kind='stable')
    for _ in a:
        print(_[0], end=" ")
        print(_[1])
    # print(1)

if __name__ == '__main__':
    main()


"""
Stick Breaking Construction
---------------------------
Generative story of Stick Breaking process
"""
import numpy as np

alpha = 1  # dirichlet prior
n_clusters = 10  # max num of clusters

G = lambda: np.random.normal()  # base distribution, theta ~ G

thetas = np.zeros(n_clusters)  # cluster params
phis = np.zeros(n_clusters)  # cluster mixing weights

prev_v = 1

for i in range(n_clusters):
    # v ~ Beta(1, alpha)
    v = np.random.beta(1, alpha)
    # theta ~ G
    theta = G()

    thetas[i] = theta
    phis[i] = v * prev_v  # v_1 * \prod_{i>1} v_i

    prev_v *= (1-v)

# Check: phis should sum to one as n_clusters -> inf
print(np.sum(phis))