# Introdução à Computação Simbólica com _sympy_

## Motivação

- O valor de $\pi$ que você usa é finito...

```python
from math import pi
print(pi)
3.141592653589793
```
- E se pudéssemos usá-lo com precisão infinita?

- 3.141592653589793 é um valor razoavelmente aceitável

- Exemplo: a equipe de engenharia da NASA explica que, usando este valor para calcular o perímetro de uma circunferência com diâmetro igual a 25 bilhões de milhas, o erro de cálculo é próximo de 1,5 polegada [[NASA]](https://www.jpl.nasa.gov/edu/news/2016/3/16/how-many-decimals-of-pi-do-we-really-need/).

## O que é computação simbólica

> *Computação Simbólica* (CS) é uma subárea de estudo da matemática e da ciência da computação que se preocupa em resolver problemas usando objetos simbólicos representáveis em um computador.

## Para que serve CS?

- Base de vários *sistemas de computação algébrica* (SCAs).

- Álgebra computacional, projetos assistidos por computação (CAD)

- Raciocínio automatizado, gestão do conhecimento, lógica computacional, sistemas formais de verificação etc.

### Como a CS está integrada?

Fonte: [[RISC/JKU]](https://risc.jku.at/studying-symbolic-computation/)

## Principais SCAs

- Maple

- Mathematica

- MuPad

- Sagemath

- ...

## Por que *sympy*?

> O objetivo principal do *sympy* é ser uma biblioteca de manipulação simbólica para Python.

- 2006 em diante (2019, v. 1.5.1)

Principais características:

- é gratuito;

- é baseado inteiramente em Python;

- é leve e independente.

## Objetos numéricos x objetos simbólicos

Importaremos os módulos `math` e `sympy` para ver diferenças

import math as mt
import sympy as sy
sy.init_printing(pretty_print=True) # melhor impressão de símbolos

mt.pi # numérico

sy.pi # simbólico

Verifiquemos com `type`.

type(mt.pi)

type(sy.pi) # é um objeto simbólico

Vejamos mais um exemplo:

mt.sqrt(2)

sy.sqrt(2)

type(mt.sqrt(2))

type(sy.sqrt(2)) # é um objeto simbólico

### Função x método

A partir deste ponto, poderemos ver situações como as seguintes:

- `f(x)`: a função `f` é aplicada ao parâmetro `x`; ex. `print('a')`; `type('a')`

- `a.f()`: `f` é um método sem parâmetro do objeto `a`; ex. `z.conjugate()`

- `a.f(x)`: `f` é um método com parâmetro `x` do objeto `a`; ex. `mt.sqrt(2)`

A partir do último exemplo, podemos dizer que um método é, na verdade, uma função que pertence a um objeto.

### Atribuições com símbolos

Podemos atribuir símbolos a variáveis usando a função `symbols`.

x = sy.symbols('x')
y = sy.symbols('y')

`x` e `y` são símbolos sem valor definido.

x

y

Podemos operar aritmeticamente com símbolos e obter uma expressão simbólica como resultado.

z = sy.symbols('z')
x*y + z**2/3 + sy.sqrt(x*y - z)

**Exemplo**: escreva o produto notável $(x - y)^2$ como uma expressão simbólica.

x**2 - 2*x*y + y**2

Note que o nome da variável não tem a ver com o nome do símbolo. Poderíamos fazer o seguinte:

y = sy.symbols('x') # y é variável; x é símbolo
y

### Atribuição por desempacotamento

Também poderíamos realizar as atribuições anteriores da seguinte forma:

x, y, z = sy.symbols('x y z')

### Alfabeto de símbolos

O *sympy* dispõe de um submódulo chamado `abc` do qual podemos importar símbolos para letras latinas (maiúsculas e minúsculas) e gregas (minúsculas).

from sympy.abc import a,b,c,alpha,beta,gamma
(a + 2*b - 3*c)*(alpha/3 + beta/2 - gamma) # símbolico

from sympy.abc import D,G,psi,theta
D**a * G**b * psi**c * theta**2 # símbolico

**Nota**: algumas letras já são usadas como símbolos especiais, tais como `O`, que indica "ordem" e `I`, que é o complexo $i$. Neste caso, cuidado deve ser tomado com nomes de variáveis

sy.I # imaginário simbólico

type(sy.I)

### Símbolos com nomes genéricos

Para criar símbolos genéricos, temos de usar `symbols` ou `Symbol`.

sem_nocao = sy.symbols('nada')
sem_nocao

muito_louco = sy.Symbol('massa')
muito_louco

### Variáveis e símbolos

sem_medo = sem_nocao + 2
sem_medo

soma = muito_louco + 2
muito_louco = 3 # 'muito_louco' aqui não é o simbólico
soma

## Substituição

A operação de *substituição* permite que:

1. substituamos variáveis por valores numéricos para avaliar uma expressão ou calcular valores de uma função em um dado ponto.
2. substituamos uma subexpressão por outra.

Para tanto, procedemos da seguinte forma:

```python
expressao.subs(variavel,valor)
```

**Exemplo**: considere o polinômio $P(x) = 2x^3 - 4x -6$. Calcule o valor de $P(-1)$, $P(e/3)$, $P(\sqrt{3.2})$.

from sympy.abc import x
P = 2*x**3 - 4*x - 6
P1 = P.subs(x,-1)
Pe3 = P.subs(x,mt.e/3)
P32 = P.subs(x,mt.sqrt(3.2))
print(P1, Pe3, P32)

**Exemplo:** sejam $f(x) = 4^x$ e $g(x) = 2x - 1$. Compute o valor da função composta $f(g(x))$ em $x = 3$.

f = 4**x
fg = f.subs(x,2*x - 1)

fg.subs(x,3)

Poderíamos também fazer isso com um estilo "Pythônico":

fg = 4**x.subs(x,2*x - 1).subs(x,3)
fg

**Exemplo:** se $a(x) = 2^x$, $b(x) = 6^x$ e $c(x) = \cos(x)$, compute o valor de $a(x)b(c(x))$ em $x = 4$

a = 2**x
b = 6**x
c = sy.cos(x)
(a * b.subs(x,c)).subs(x,4)

Ou, de modo direto:

valor = ( 2**x * ( 6**x.subs(x,sy.cos(x))) ).subs(x,4)
valor

### Avaliação de expressão em ponto flutuante

Note que a expressão anterior não foi computada em valor numérico. Para obter seu valor numérico, podemos usar o método `evalf`.

valor.evalf()

#### Precisão arbitrária

`evalf` permite que escolhamos a precisão do cálculo impondo o número de dígitos de precisão. Por exemplo, a última expressão com 20 dígitos de precisão seria:

valor.evalf(20)

Com 55, seria:

valor.evalf(55)

E com 90 seria:

valor.evalf(90)

**Exemplo**: calcule o valor de $e$ com 200 dígitos de precisão.

sy.exp(1).evalf(200)

## Funções predefinidas x funções regulares

Apresentaremos 3 grupos de funções que podem ser criadas em Python

- **funções predefinidas** (*built-in functions*): funções já prontas que podemos usar (ex. `print()`, `type()` , `int()`, `float()`

- ** funções regulares**, ou *normais*, *definidas pelo usuário* (do inglês *user-defined functions*, ou simplesmente *UDF*): aquelas que você cria!

Podemos fazer isto de uma maneira usando uma "palavra-chave" (*keyword*) chamada `def` da seguinte forma:

```python
def f(x):
    (...)
    return y
```
- uma UDF **pode ter zero ou mais argumentos**, tantos quantos se queira;
- uma UDF **pode ou não ter valor de retorno**;

Vamos entender as UDFs com exemplos.

**Exemplo:** Suponha que você é um(a) analista de dados do mercado imobiliário e está estudando o impacto do repasse de comissões pagas a corretores mediante vendas de imóveis. Você, então, começa a raciocinar e cria um modelo matemático bastante simples que, antes de tudo, precisa calcular o valor do repasse a partir do preço de venda.

Se $c$ for o percentual de comissão, $V$ o valor da venda do imóvel e $r$ o valor a ser repassado para o corretor, então, a função a ser definida é

$$r(V) = c\, V,$$

assumindo que $c$ seja um valor fixo.

Digamos que $c$ corresponda a 1.03% do valor da venda do imóvel. Neste caso podemos criar uma UDF para calcular $r$ para nós da seguinte forma:

def repasse(V):
    r = 0.0103*V
    return r

Para $V = \, R\$ \, 332.130,00$:

repasse(332130)

O que é necessário observar:

- `def` seguido pelo *nome* da função
- argumentos enclausurados por parênteses
- os dois-pontos (`:`) são obrigatórios
- *escopo* da função, que deve ser escrito em uma ou mais linhas indentadas (pressione `TAB` para isso, ou use 4 espaços)
- o valor de retorno, se houver, é posto na última linha do escopo.

Podemos atribuir os valores do argumento e resultado a variáveis:

V = 332130
rep = repasse(V)
rep

Nomes iguais de variável e função são permissíveis.

repasse = repasse(V) # 'repasse' à esquerda é uma variável; à direita, função
print(repasse)

Todavia, isto pode ser confuso e é bom evitar.

O estilo "Pythônico" de escrever permite que o valor de retorno não seja explicitamente declarado. No escopo

```python
...
    r = 0.0103*V
    return r
```
 a variável `r` não é necessária.

Python é inteligente para permitir o seguinte:

def repasse(V):
    return 0.0103*V

# note que aqui não indentamos a linha.
# Logo esta instrução NÃO pertence ao escopo da função.
repasse(V)

Podemos criar uma função para diferentes valores de `c` e `V` usando *dois* argumentos:

def repasse_c(c,V): # esta função tem outro nome
    return c*V

c = 0.0234 # equivaleria a uma taxa de repasse de 2.34%
V = 197432 # o valor do imóvel agora é R$ 197.432,00
repasse_c(c,V)

A ordem dos argumentos importa:

V = 0.0234 # este deveria ser o valor de c
c = 197432 # este deveria ser o valor de V
repasse_c(c,V)

Por que o valor resultante é o mesmo? Porque a operação no escopo da função é uma multiplicação, `c*V`, que é comutativa independentemente do valor das variáveis. Porém, digamos que um segundo modelo tenha uma forma de cálculo distinta para a comissão dada por

$$r_2(V) = c^{3/5} \, V$$

Neste caso:

def repasse_2(c,V):
    return c**(3/5)*V

V = 197432
c = 0.0234

repasse_2(c,V)

Porém, se trocarmos o valor das variáveis, a função `repasse_2` calculará um valor distinto. Embora exista um produto também comutativo, o expoente `3/4` modifica apenas o valor de `c`.

# variáveis com valores trocados
c = 197432
V = 0.0234

repasse_2(c,V)

A ordem com que escrevemos os argumentos tem importância relativa aos valores que passamos e ao que definimos:

# variáveis com valores corretos
V = 197432
c = 0.0234

def repasse_2_trocada(V,c): # V vem antes de c
    return c**(3/5)*V

repasse_2_trocada(V,c)

Mas,

# os valores das variáveis estão corretos,
# mas foram passados para a função na ordem errada
repasse_2_trocada(c,V)

e

# a ordem dos argumentos está de acordo com a que foi definida
# mas os valores das variáveis foram trocados
V = 197432
c = 0.0234
repasse_2_trocada(c,V)

## Modelos matemáticos simbólicos

A partir do que aprendemos, podemos definir modelos matemáticos completamente simbólicos.

from sympy.abc import c,V

def repasse_2_simbolica(c,V):
    return c**(3/5)*V

Se chamarmos esta função, ela será um objeto simbólico.

repasse_2_simbolica(c,V)

Atribuindo em variável:

rep_simb = repasse_2_simbolica(c,V)

type(rep_simb) # é um objeto simbólico

**Exemplo:** Suponha, agora, que seu modelo matemático de repasse deva considerar não apenas um percentual $c$ pré-estabelecido, mas também um valor de "bônus" adicional concedido como prêmio pela venda do imóvel. Considere, então, que o valor deste bônus seja $b$. Diante disso, nosso novo modelo teria uma fórmula como a seguinte:

$$r_3(V) = c\,V + b$$

Simbolicamente:

# importaremos apenas o símbolo b,
# uma vez que c e V já foram importados
# como símbolos anteriormente
from sympy.abc import b

def r3(V):
    return c*V + b

rep_3 = r3(V)
rep_3

### Substituindo valores

Podemos usar a função `subs` para atribuir quaisquer valores para o modelo.

**Exemplo:** $c = 0.119$

rep_3.subs(c,0.119) # substituindo para c

**Exemplo:** $c = 0.222$

rep_3.subs(c,0.222) # substituindo para c

**Exemplo:** $c = 0.222$ e $b = 12.0$

rep_3.subs(c,0.222).subs(b,12.0) # substituindo para c, depois para b

### Substituição múltipla

O modo anterior de substituição não é "Pythônico". Para substituirmos mais de uma variável de uma vez, devemos usar *pares ordenados* separados por vírgula sequenciados entre colchetes como uma *lista*. Mais tarde, aprenderemos sobre pares ordenados e listas.

**Exemplo:** Modifique o modelo $r_3$ para que $c = 0.043$ e $b = 54.0$

# espaços foram adicionados para dar legibilidade
rep_3.subs( [ (c,0.043), (b,54.0) ] )

#### Pares ordenados

$$X \times Y = \{ (x,y) ; x \in X \text{ e } y \in Y \}$$

onde $X$ e $Y$ são conjuntos quaisquer e $x$ e $y$ são as *coordenadas*.

- ex. $X = Y = \mathbb{R}$; $(3,2)$, $(-1,3)$, $(\pi,2.18)$ etc.

- Este é o caso de $\mathbb{R} \times \mathbb{R} = \mathbb{R}^2$, que é exatamente o *plano cartesiano*.

A substituição múltipla com `subs` ocorre da seguinte forma;

- a primeira coordenada é o *símbolo*;

- a segunda coordenada é o *valor* que você quer dar para o símbolo.

**Exemplo:** Calcule $r_3(V)$ considerando $c = 0.021$, $b = 34.0$ e $V = 432.000$.

# armazenaremos o valor na variável 'valor'
valor = r3(V)

# subsituição
valor.subs( [ (c,0.021), (b,54.0) ] )

Com o estilo "Pythônico":

valor = r3(V).subs( [ (c,0.021), (b,54.0) ] ) #
valor

Podemos seguir esta regra de pares para substituir todos os valores de um modelo simbólico genérico não necessariamente definido através de uma função. Veja o exemplo aplicado a seguir.

## Exemplo de aplicação: o índice de caminhabilidade

O *índice de caminhabilidade* $W$ para uma vizinhança de casas é uma medida matemática que assume valores no intervalo $[0,1]$. A fórmula é definida por:

$$W(d) = e^{-5 \left( \dfrac{d}{M} \right)^5},$$

onde $d$ é a distância medida entre a vizinhança (0 metro) e um dado ponto de referência, e $M$ é a distância máxima de avaliação considerada a partir da qual a caminhabilidade é assumida como nula.

### Interpretação

- quando estamos na vizinhança, $d = 0$, $W = 1$ e a caminhabilidade é considerada ótima.

- à medida que nos afastamos da vizinhança em direção ao local da amenidade, $d$ aumenta e o valor $W$ decai vertiginosamente até atingir o valor limite $M$ a partir do qual $W = 0$ e a caminhabilidade é considerada "péssima".

- W é calculado com relação a um ponto de destino definido
- A distância deve levar em consideração as vias de circulação (ruas, rodovias etc) e não a distância mais curta (raio do perímetro).
- ex. Para $M = 500 \, m$ , um bar a 100 metros da vizinhança teria um índice de caminhabilidade maior do que o de uma farmácia localizada a 300 m e muito maior do que o de um shopping localizado a 800 m, ainda que muito famoso.

Fonte: *De Nadai, M. and Lepri, B. [[The economic value of neighborhoods: Predicting real estate prices from the urban environment]](https://arxiv.org/pdf/1808.02547.pdf)*.

### Modelo simbólico

Podemos modelar $W$ simbolicamente e calcular seu valor para diferentes valores de $d$ e $M$ usando a substituição múltipla.

from sympy.abc import d,M,W

W = sy.exp(-5*(d/M)**5) # função exponencial simbólica
W

**Exemplo:** A nossa corretora de imóveis gostaria de entender a relação de preços de imóveis para o Condomínio Pedras de Marfim. Considerando $M = 1 km$, calcule:

- o índice de caminhabilidade $W_1$ em relação à farmácia Dose Certa, localizada a 222 m do condomínio.

- o índice de caminhabilidade $W_2$ em relação ao restaurante Sabor da Arte, localizada a 628 m do condomínio.

- o índice de caminhabilidade $W_3$ em relação ao Centro Esportivo Physicalidade, localizada a 998 m do condomínio.

- o índice de caminhabilidade $W_4$ em relação à Padaria Dolce Panini, localizada a 1,5 km do condomínio.

# note que 1 km = 1000 m
W1 = W.subs([ (d,222), (M,1000) ])
W2 = W.subs([ (d,628), (M,1000) ])
W3 = W.subs([ (d,998), (M,1000) ])
W4 = W.subs([ (d,1500), (M,1000) ])

Perceba, entretanto, que os valores calculados ainda não são numéricos, como esperado.

W1

W2

W3

W4

Lembre-se que podemos usar `evalf` para calcular esses valores. Faremos isso considerando 3 casas decimais.

# reatribuindo todos os valores
W1n = W1.evalf(3)
W2n = W2.evalf(3)
W3n = W3.evalf(3)
W4n = W4.evalf(3)

print('W1 =', W1n, '; ' \
      'W2 =', W2n, '; ' \
      'W3 =', W3n, '; ' \
      'W4 =', W4n)

Como era de se esperar, os valores decaem de 0.997 a 3.24e-17, que é um valor considerado nulo em termos de aproximação numérica.

#### Quebrando instruções com `\`

A contra-barra `\` pode ser usada para quebrar instruções e continuá-las nas próximas linhas, porém não poderá haver nenhum caracter após ela, nem mesmo espaços. Caso contrário, um erro será lançado.

print('Continuando' \
      'na linha abaixo')

# neste exemplo, há um caracter de espaço após \
print('Continuando' \
      'na linha abaixo')

### O tipo `bool`

Em Python, temos mais um tipo de dado bastante útil, o `bool`, que é uma redução de "booleano". Objetos `bool`, que têm sua raiz na chamada Álgebra de Boole, são baseados nos conceitos *true* (verdadeiro) e *false*, ou *0* e *1* e são estudados em algumas disciplinas, tais como Circuitos Lógicos, Matemática Discreta, Lógica Aplicada, entre outras.

Aprenderemos sobre operadores lógicos mais à frente. Por enquanto, cabe mencionar as entidades fundamentais `True` e `False`.

True

False

type(True)

type(False)

Podemos realizar testes lógicos para concluir verdades ou falsidades quando temos dúvidas sobre objetos e relações entre eles. Por exemplo, retomemos os seguintes valores:

W1

W2

A princípio, é difícil determinar qual dos dois é o maior. Porém, podemos realizar "perguntas" lógicas para o interpretador Python com operadores lógicos. Mostraremos apenas dois exemplos com `>` e `<`.

W1 > W2 # isto quer dizer: "W1 é maior do que W2?"

O valor `True` confirma que o valor de `W1` é maior do que `W2`.

W4 < 0

> Note que, de acordo com nosso modelo de caminhabilidade, este valor deveria ser zero. Porém, numericamente, ele é uma aproximação para zero. Embora muito pequeno, não é exatamente zero! Por que isso ocorre? Porque o computador lida com uma matemática inexata e aproximada, mas com precisão satisfatória.


"""
  Name     : c12_26_simulation_link_2method_VaR.py
  Book     : Python for Finance (2nd ed.)
  Publisher: Packt Publishing Ltd.
  Author   : Yuxing Yan
  Date     : 6/6/2017
  email    : yany@canisius.edu
             paulyxy@hotmail.com
"""

import numpy as np
import scipy as sp
import pandas as pd
from scipy.stats import norm
#
position=1e6              # portfolio value
std=0.2                   # volatility
mean=0.08                 # mean return
confidence=0.99           # confidence level
nSimulations=50000        # number of simulations
# Method I
z=norm.ppf(1-confidence)
VaR=position*(mean+z*std)
print("Holding=",position, "VaR=", round(VaR,0), "tomorrow")
#
# Method II: Monte Carlo simulaiton
sp.random.seed(12345)
ret2=sp.random.normal(mean,std,nSimulations)
ret3=np.sort(ret2)
m=int(nSimulations*(1-confidence))
VaR2=position*(ret3[m])
print("Holding=",position, "VaR2=", round(VaR2,0), "tomorrow")


import numpy as np
import matplotlib.pyplot as plt
import os
from sklearn.cluster import KMeans
from scipy.cluster.vq import kmeans,vq
from scipy.spatial.distance import cdist

#fileNum = '01'
#dataDir = 'data/path-image-1' + str(fileNum) + '.tif/'
#ftPath = dataDir + 'path-image-1' + str(fileNum) + '.seg.000000.000000.csv'
#curDir = os.path.dirname(os.path.realpath("__file__"))
#parDir = os.path.abspath(os.path.join(curDir, os.pardir))
#dataDir = parDir + '/data/path-image-1' + str(fileNum) + '.tif/'

def load_data(fName):
  #fName = dataDir + fi
  fp = open(fName,'r')
  XX = np.loadtxt(fp)
  fp.close()
  return XX

def run_kmeans(X, n=10):
  _K = range(1, n)

  # scipy.cluster.vq.kmeans
  _KM = [kmeans(X,k) for k in _K] # apply kmeans 1 to 10
  _centroids = [cent for (cent,var) in _KM]   # cluster centroids

  _D_k = [cdist(X, cent, 'euclidean') for cent in _centroids]

  _cIdx = [np.argmin(D,axis=1) for D in _D_k]
  _dist = [np.min(D,axis=1) for D in _D_k]
  _avgWithinSS = [sum(d)/X.shape[0] for d in _dist]

  return (_K, _KM, _centroids, _D_k, _cIdx, _dist, _avgWithinSS)

def plot_elbow_curve(kIdx, K, avgWithinSS):
  plt.clf()
  fig = plt.figure()
  ax = fig.add_subplot(111)
  ax.plot(K, avgWithinSS, 'b*-')
  ax.plot(K[kIdx], avgWithinSS[kIdx], marker='o', markersize=12,
      markeredgewidth=2, markeredgecolor='r', markerfacecolor='None')
  plt.grid(True)
  plt.xlabel('Number of clusters')
  plt.ylabel('Average within-cluster sum of squares')
  tt = plt.title('Elbow for KMeans clustering')
  plt.ion()
  #return(fig,ax)
#/////////////////////////////////////////////////////////////////////////////////

def labels(featureNum):
  if featureNum == 0:
    label = "Area"
  elif featureNum == 1:
    label = "Perimeter"
  elif featureNum == 2:
    label = "Compactness"
  elif featureNum == 3:
    label = "Asymmetry"
  elif featureNum == 4:
    label = "BoundaryIndex"
  elif featureNum == 5:
    label = "Compactness"
  elif featureNum == 6:
    label = "Contrast"
  elif featureNum == 7:
    label = "Dissimilarity"
  elif featureNum == 8:
    label = "Angular Second moment"
  elif featureNum == 9:
    label = "Energy"
  elif featureNum == 10:
    label = "Homegeneity"
  return label

def plot_clusters(orig, pred, nx, ny, fo, legend=True):
  plt.clf()
  data = orig
  ylabels = { 0:'',1:'',2:''}
  # plot data into three clusters based on value of c
  p0 = plt.plot(data[pred==0,nx],data[pred==0,ny],'ro',label='Cluster 1')
  p2 = plt.plot(data[pred==2,nx],data[pred==2,ny],'go',label='Cluster 2')
  p1 = plt.plot(data[pred==1,nx],data[pred==1,ny],'bo',label='Cluster 3')
  lx = p1[0].axes.set_xlabel('')
  ly = p1[0].axes.set_ylabel(ylabels[1])
  tt= plt.title('Polygon Dataset, KMeans clustering with K=3')
  plt.xlabel(labels(nx))
  plt.ylabel(labels(ny))

  if legend:
    ll=plt.legend()
  #plt.savefig(dataDir + "kmeans/" + fo)
  plt.savefig(fo)
  plt.ion()

  return (p0, p1, p2)
#Added:featureindex = [0,1,3,5] means feature no:0,1,3,5 selected by user out of 0-10 feature no.
def start_kmeans(fi,fo,featureIndexList=[0,1]):
  X = load_data(fi)
  K, KM, centroids,D_k,cIdx,dist,avgWithinSS = run_kmeans(X,10)
  kIdx = 3
  plot_elbow_curve(kIdx, K, avgWithinSS)
  km = KMeans(kIdx, init='k-means++') # initialize
  km.fit(X)
  c = km.predict(X) # classify into three clusters
  #print c[:800]
  (pl0,pl1,pl2) = plot_clusters(X,c,featureIndexList[0],featureIndexList[1],fo) # column 0 AREA, vs column 1 Perimeter . Note indexing is 0 based
#for testing
#featureList = [8,9]
#start_kmeans("path-image-100.seg.000000.000000_original.csv","myfilter_test.png",featureList)


'''
Interpolate DFT to correct frequency/amplitude/phase
Take a cosine wave for example

XiaoCY 2021-02-23
'''

#%%
import numpy as np
import matplotlib.pyplot as plt

fs = 1000
N = 2000

t = np.arange(N)/fs
ax = 3.0
fx = 12.3
px = 0.5
x = ax*np.cos(2*np.pi*fx*t+px)

#%% uncorrect DFT
X = np.fft.fft(x,axis=0)
Nf = int(np.round(N/2.0+0.5))
f = np.arange(N)*fs/N
A = np.abs(X[:Nf])*2.0/N

k = np.argmax(A)
f1 = f[k]
a1 = A[k]
p1 = np.angle(X[k])

#%% ratio method: rectangular window
if A[k-1] > A[k+1]:
    dk = -A[k-1]/(A[k-1]+A[k])
else:
    dk = A[k+1]/(A[k+1]+A[k])

f2 = (k+dk)*fs/N
a2 = a1/np.sinc(dk)
p2 = p1-dk*np.pi

#%% ratio method: hanning window
win = np.hanning(N)
X3 = np.fft.fft(x*win,axis=0)
A3 = np.abs(X3[:Nf])*2.0/np.sum(win)

k3 = np.argmax(A3)
a3 = A3[k3]
p3 = np.angle(X3[k3])

if A3[k3-1] > A3[k3+1]:
    dk = (A3[k3]-2*A3[k3-1])/(A3[k3-1]+A3[k3])
else:
    dk = (2.0*A3[k3+1]-2*A3[k3])/(A3[k3+1]+A3[k3])

f3 = (k3+dk)*fs/N
a3 = a3*(1-dk**2)/np.sinc(dk)
p3 = p3-dk*np.pi

#%% results
print('%-18s%-12s%-12s%-12s%-12s\n' % ('','FFT','Rectwin','Hanning','True Value'))
print('%-18s%-12f%-12f%-12f%-12f\n' % ('Frequency (Hz)',f1,f2,f3,fx))
print('%-18s%-12f%-12f%-12f%-12f\n' % ('Amplitude',a1,a2,a3,ax))
print('%-18s%-12f%-12f%-12f%-12f\n' % ('Phase (rad)',p1,p2,p3,px))

xe1 = a1*np.cos(2*np.pi*f1*t+p1)-x
xe2 = a2*np.cos(2*np.pi*f2*t+p2)-x
xe3 = a3*np.cos(2*np.pi*f3*t+p3)-x

plt.figure()
plt.plot(t,xe1)
plt.plot(t,xe2)
plt.plot(t,xe3)
plt.grid()
plt.legend(('FFT','rect-correct','hann-corrcet'))
plt.xlabel('Time (s)')
plt.ylabel('Error')
plt.show()


'''
                     List comprehension and map function

Fill in each function below (there are 7).
Each function should be a one-liner.
You fill practice using 'map' and list comprehension with various datatypes.

Run "python -m doctest assignment_1a.py" at the command line to test your work.
'''
import numpy as np
import pdb
import string
###############################################################################
#######                     Looking at Lists and Matrices
###############################################################################
def average_rows1(mat):
    '''
    INPUT: 2 dimensional list of integers (matrix)
    OUTPUT: list of floats

    Use list comprehension to take the average of each row in the matrix and
    return it as a list.

    Example:
    >>> average_rows1([[4, 5, 2, 8], [3, 9, 6, 7]])
    [4.75, 6.25]
    '''
    avgs = [sum(row)/len(row) for row in mat]
    return avgs

def average_rows2(mat):
    '''
    INPUT: 2 dimensional list of integers (matrix)
    OUTPUT: list of floats

    Use map to take the average of each row in the matrix and
    return it as a list.

    Example:
    >>> average_rows1([[4, 5, 2, 8], [3, 9, 6, 7]])
    [4.75, 6.25]
    '''
    a = list(map(np.mean, mat))
    return a

def sort_rows(mat):
    '''
    INPUT: 2 dimensional list of integers (matrix)
    OUTPUT: 2 dimensional list of integers (matrix)

    Use list comprehension to modify each row of the matrix to be sorted.
    Notice that the matrix is modified in place

    Example:
    >>> M = [[4, 5, 2, 8], [3, 9, 6, 7]]
    >>> sort_rows(M)
    >>> M
    [[2, 4, 5, 8], [3, 6, 7, 9]]
    '''
    for row in mat:
        row.sort()

###############################################################################
#######                     Looking at Strings
###############################################################################
def word_lengths1(phrase):
    '''
    INPUT: string
    OUTPUT: list of integers

    Use list comprehension to find the length of each word in the phrase
    (broken by spaces) and return the values in a list.

    Example:
    >>> word_lengths1("Welcome to the Data Science Immersive Program!")
    [7, 2, 3, 4, 7, 9, 8]
    '''
    return [len(word) for word in phrase.strip().split(' ')]

def word_lengths2(phrase):
    '''
    INPUT: string
    OUTPUT: list of integers

    Use map to find the length of each word in the phrase
    (broken by spaces) and return the values in a list.

    Example:
    >>> word_lengths2("Welcome to the Data Science Immersive Program!!")
    [7, 2, 3, 4, 7, 9, 9]
    '''
    return list(map(len, phrase.strip().split(' ')))

###############################################################################
#######                     Looking at Integers
###############################################################################
def even_odd0(x):
    if x % 2 == 0:
        return 'even'
    else:
        return 'odd'

def even_odd1(L):
    '''
    INPUT: list of integers
    OUTPUT: list of strings

    Use list comprehension to return a list of the same length with the strings
    "even" or "odd" depending on whether the element in L is even or odd.

    Example:
    >>> even_odd1([6, 4, 1, 3, 8, 5])
    ['even', 'even', 'odd', 'odd', 'even', 'odd']
    '''
    # returns even correctly, not sure how to make it return odd
    return [even_odd0(x) for x in L]

def even_odd2(L):
    '''
    INPUT: list of integers
    OUTPUT: list of strings

    Use map to return a list of the same length with the strings
    "even" or "odd" depending on whether the element in L is even or odd.

    Example:
    >>> even_odd2([6, 4, 1, 3, 8, 5])
    ['even', 'even', 'odd', 'odd', 'even', 'odd']
    '''
    return list(map(even_odd0, L))


import csv
import numpy as np

def getDataSource(data_path):
    marksInPercentage = []
    days_present = []
    with open(data_path) as csv_file:
        csv_reader = csv.DictReader(csv_file)
        for row in csv_reader:
            marksInPercentage.append(float(row["Marks In Percentage"]))
            days_present.append(float(row["Days Present"]))

    return{"x":marksInPercentage , "y": days_present}

def findCorrelation(datasource):
    correlation = np.corrcoef(datasource["x"],datasource["y"])
    print("correlation between percentage and days present is :",correlation[0,1])

def setup():
    data_path = "./data.csv"
    datasource = getDataSource(data_path)
    findCorrelation(datasource)

setup()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
lines_bars_and_markers/cross-and_auto-correlation_demo.py

Matplotlib > Gallery > Lines, bars and markers > Cross- and Auto-Correlation Demo
https://matplotlib.org/gallery/lines_bars_and_markers/xcorr_acorr_demo.html#sphx-glr-gallery-lines-bars-and-markers-xcorr-acorr-demo-py
"""

import matplotlib.pyplot as plt
import numpy as np

# Fixing random state for reproducibility
np.random.seed(19680801)

# np.random.randn(2, 100) returns samples from the “standard normal” distribution.
#   array([[1.0493, 0.866099, ... ,  -1.27382],    # random stored in x
#         [-1.38774, 0.155221, ... , -0.492031]]) # random stored in y
x, y = np.random.randn(2, 100)  # Two random varables with 100 samples
t    = np.arange( 0, len(x) )  # len(x)=100

# Plot auto- & cross-correlation.
fig, [ax1, ax2] = plt.subplots(2, 1, sharex=True)
ax1.xcorr(x, y, usevlines=True, maxlags=50, normed=True, lw=2)
ax1.set_title('Auto-Correlation: x')
ax1.grid(True)

ax2.acorr(x, usevlines=True, normed=True, maxlags=50, lw=2)
ax2.set_title('Cross-Correlation: x & y')
ax2.grid(True)

plt.show()

# Plot random variable x & y
fig, [ax1, ax2] = plt.subplots(2, 1, sharex=True)
ax1.plot( t, x )
# t.max()=99, So add 1 to display 100 in the figure
ax1.set_xlim( t.min(), t.max()+1 )
ax1.set_ylim(-3, 3)
ax1.set_title('Random variable x')
ax1.grid()

ax2.plot( t, y )
ax2.set_xlim( t.min(), t.max()+1 )
ax2.set_ylim(-3, 3)
ax2.set_title('Random variable y')
ax2.grid()

plt.show()

# Plotting all four plots into 2x2 subplots doesn't work well.
# The x & y ranges of auto- and cross-correlation are weird.
# So I'll just use two subplots like I did above.

#fig, axes = plt.subplots(2, 2, sharex=True)
#axes[0,0].acorr( x, usevlines=True, normed=True, maxlags=50, lw=2, color='C0')
#axes[0,0].set_title('Auto-Correlation: x')
#axes[0,0].grid()
#
#axes[1,0].xcorr(x, y, usevlines=True, maxlags=50, normed=True, lw=2)
#axes[1,0].set_title('Cross-Correlation: x & y')
#axes[1,0].grid()
#
#axes[0,1].plot( t, x, color='C0' )
#axes[0,1].set_xlim( t.min(), t.max() )
#axes[0,1].set_ylim(-3, 3)
#axes[0,1].set_title('Random variable x')
#axes[0,1].grid()
#
#axes[1,1].plot( t, y, color='r' )
#axes[1,1].set_xlim( t.min(), t.max() )
#axes[1,1].set_ylim(-3, 3)
#axes[1,1].set_title('Random variable y')
#axes[1,1].grid()
#
#plt.show()


# -*- coding: utf-8 -*-
"""
Ejercicio 1: Aproximación numérica de la derivada de la función f(x) = exp(x).

"""

import numpy as np
import matplotlib.pyplot as plt

# Función exponencial y su derivada f'
f =  lambda x:np.exp(x)            # función f
df = lambda x:np.exp(x)          # derivada exacta f'

#---------------------------------------------------------------
# h = 0.1
#---------------------------------------------------------------
h = 0.1     # paso

# Gráfica de la derivada
plt.close('all') # cerramos todas las ventanas

plt.figure(1)
xx = np.linspace(0,1)             # definimos un vector x para dibujar la derivada en [0,1]
plt.plot(xx,df(xx),'--')   # dibujamos la derivada
plt.grid()

# Diferencias progresivas, regresivas y centradas
a = 0               # extremo inferior del intervalo
b = 1               # extremo superior del intervalo
x = np.arange(a,b+h,h)    # vector con 1º coordenada a, última coordenanda b y diferencia entre coordenadas h
df_p = np.diff(f(x))/h   # vector que contiene los valores de las diferencias progresivas
df_r = df_p                     # vector que contiene los valores de las diferencias regresivas
df_c = (df_p[1:] + df_r[0:-1])/2 # vector que contiene los valores de las diferencias centradas

# Gráfica de f' y de las diferencia progresivas, regresivas y centradas
xp = x[0:-1]    # vector con las coordenadas x para las diferencias progresivas
xr = x[1:]    # vector con las coordenadas x para las diferencias regresivas
xc = x[1:-1]    # vector con las coordenadas x para las diferencias centradas
plt.plot(xp,df_p,xr,df_r,xc,df_c)  # dibujamos las diferencias progresivas, regresivas y centradas
plt.legend(["df","df_p","df_r","df_c"]) # leyenda
plt.title("Derivada de f(x) = e^x y sus aproximaciones (h=0.1)") # título de la gráfica
plt.show()

## Gráfica de los errores en cada punto
plt.figure(2)  # abrimos una nueva figura
plt.plot(xp,abs(df(xp)-df_p),'o',xr,abs(df(xr)-df_r),'o',xc,abs(df(xc)-df_c),'o')    # dibujamos los valores |f'(x_i)-df_aprox(x_i)| para las
# diferentes aproximaciones (progresiva, regresiva y centrada)
plt.grid()
plt.legend(["df_p","df_r","df_c"]) # leyenda
plt.title("Errores (h=0.1)")    # título
plt.show()

## Errores globales relativos
ErrorG_p = np.linalg.norm((df(xp)-df_p))/np.linalg.norm(df(xp)) # error global relativo para aprox. progresiva
ErrorG_r = np.linalg.norm((df(xr)-df_r))/np.linalg.norm(df(xr)) # error global relativo para aprox. regresiva
ErrorG_c = np.linalg.norm((df(xc)-df_c))/np.linalg.norm(df(xc)) # error global relativo para aprox. centrada
print("h = ",h)   # escribimos en pantalla los errores
print("ErrorG_p = ",ErrorG_p)
print("ErrorG_r = ",ErrorG_r)
print("ErrorG_c = ",ErrorG_c)

#---------------------------------------------------------------
# h = 0.01
#---------------------------------------------------------------

h = 0.01     # paso

# Gráfica de la derivada
plt.close('all') # cerramos todas las ventanas

plt.figure(1)
xx = np.linspace(0,1)             # definimos un vector x para dibujar la derivada en [0,1]
plt.plot(xx,df(xx),'--')   # dibujamos la derivada
plt.grid()

# Diferencias progresivas, regresivas y centradas
a = 0               # extremo inferior del intervalo
b = 1               # extremo superior del intervalo
x = np.arange(a,b+h,h)    # vector con 1º coordenada a, última coordenanda b y diferencia entre coordenadas h
df_p = np.diff(f(x))/h   # vector que contiene los valores de las diferencias progresivas
df_r = df_p                     # vector que contiene los valores de las diferencias regresivas
df_c = (df_p[1:] + df_r[0:-1])/2 # vector que contiene los valores de las diferencias centradas

# Gráfica de f' y de las diferencia progresivas, regresivas y centradas
xp = x[0:-1]    # vector con las coordenadas x para las diferencias progresivas
xr = x[1:]    # vector con las coordenadas x para las diferencias regresivas
xc = x[1:-1]    # vector con las coordenadas x para las diferencias centradas
plt.plot(xp,df_p,xr,df_r,xc,df_c)  # dibujamos las diferencias progresivas, regresivas y centradas
plt.legend(["df","df_p","df_r","df_c"]) # leyenda
plt.title("Derivada de f(x) = e^x y sus aproximaciones (h=0.1)") # título de la gráfica
plt.show()

## Gráfica de los errores en cada punto
plt.figure(2)  # abrimos una nueva figura
plt.plot(xp,abs(df(xp)-df_p),'o',xr,abs(df(xr)-df_r),'o',xc,abs(df(xc)-df_c),'o')    # dibujamos los valores |f'(x_i)-df_aprox(x_i)| para las
# diferentes aproximaciones (progresiva, regresiva y centrada)
plt.grid()
plt.legend(["df_p","df_r","df_c"]) # leyenda
plt.title("Errores (h=0.1)")    # título
plt.show()

## Errores globales relativos
ErrorG_p = np.linalg.norm((df(xp)-df_p))/np.linalg.norm(df(xp)) # error global relativo para aprox. progresiva
ErrorG_r = np.linalg.norm((df(xr)-df_r))/np.linalg.norm(df(xr)) # error global relativo para aprox. regresiva
ErrorG_c = np.linalg.norm((df(xc)-df_c))/np.linalg.norm(df(xc)) # error global relativo para aprox. centrada
print("h = ",h)   # escribimos en pantalla los errores
print("ErrorG_p = ",ErrorG_p)
print("ErrorG_r = ",ErrorG_r)
print("ErrorG_c = ",ErrorG_c)


# Author: Valeria Telles
# Date: 19 October 2020
# Course: PHYS3230 - Quantum Mechanics I

import numpy as np
import matplotlib.pyplot as plt

def trapz(X, Y):
    """
    Approximate the integral of f(x) from a to be by the trapezoid rule.

    Parameters
    ----------
    X : numbers - X values contained within the file 'wavedata.csv'
    Y : numbers - Y values contained within the file 'wavedata.csv'

    Returns
    -------
    float - approximation of the integral of f(x) using the trapezoid rule.
    """

    return 0.5*((X[1:]-X[:-1])*(Y[1:]+Y[:-1])).sum()

def coefficients(X, Y, n):
    """
    Calculate the coefficinents for the sine and cosine components of the trigonometric
    Fourier Series

    Parameters
    ----------
    X : numbers - X values contained within the file 'wavedata.csv'
    Y : numbers - Y values contained within the file 'wavedata.csv'
    n : numbers - 0, 1, 2...

    Returns
    -------
    list - coefficients of the trigonometric Fourier Series up to n = 5.
    """
    A = 0
    B = 0

    An = []
    Bn = []

    while (n <= 5):
        X1 = X
        Y1 = Y*np.cos(2*np.pi*X*n/10)
        Y2 = Y*np.sin(2*np.pi*X*n/10)

        A += trapz(X1, Y1)
        B += trapz(X1, Y2)

        A = A*2/10
        B = B*2/10

        An.append(A)
        Bn.append(B)
        n = n + 1

    return An, Bn

def fourier(x, n, An, Bn):
    """
    Reconstructing the function f(x) using a restricted number of Fourier modes.

    Parameters
    ----------
    x : numbers - X values contained within the file 'wavedata.csv'
    n : numbers - 0, 1, 2...
    An : list of values for the coefficient 'An' up to n = 5
    Bn : list of values for the coefficient 'Bn' up to n = 5

    Returns
    -------
    ndarray - reconstructed fourier series of order 'n'.
    """
    a0 = An[0]
    series = np.zeros(np.size(x))
    for i in range(1, n+1):
        series += ( An[i] * np.cos(2*np.pi*x*i/10) + Bn[i] * np.sin(2*np.pi*x*i/10) )
    return (a0/2) + series

def main():
    X, Y = np.loadtxt("wavedata.csv", delimiter=',', usecols=(0,1), unpack=True)
    k = np.arange(0, 6, 1)
    n = 0

    An, Bn = coefficients(X, Y, 0)

    print(fourier(X, 2, An, Bn))

    fig, (ax1, ax2) = plt.subplots(1, 2)
    fig.set_figheight(9)
    fig.set_figwidth(12)
    fig.tight_layout(pad=5.0)

    ax1.set_facecolor('#EAEAEA')
    ax1.bar(k, An, width=0.4, align='edge', label='An', color='#E40303')
    ax1.bar(k, Bn, width=-0.4, align='edge', label='Bn', color='#0402BD')
    ax1.legend(fontsize=12, shadow=True)
    ax1.set_xlabel('value of n', fontsize=12, fontweight='bold')
    ax1.set_ylabel('relative contribution', fontsize=12, fontweight='bold')
    ax1.set_title('Relative Contributions of An and Bn', fontsize=14, fontweight='bold')
    ax1.grid(color='#F6F6F6', linestyle='-')
    ax1.set_axisbelow(True)

    ax2.set_facecolor('#EAEAEA')
    ax2.plot(X, Y, label='original', color='#E40303')
    ax2.plot(X, fourier(X, 2, An, Bn), label='n = 2', color='#0402BD')
    ax2.plot(X, fourier(X, 3, An, Bn), label='n = 3', color='#38B748')
    ax2.plot(X, fourier(X, 5, An, Bn), label='n = 5', color='#F2AD12')
    ax2.legend(fontsize=12, shadow=True)
    ax2.set_xlabel('x', fontsize=12, fontweight='bold')
    ax2.set_ylabel('f(x)', fontsize=12, fontweight='bold')
    ax2.set_title('Fourier Reconstruction (n = modes)', fontsize=14, fontweight='bold')
    ax2.grid(color='#F6F6F6', linestyle='-')
    ax2.set_axisbelow(True)

    fig.savefig('FourierSeries.png', dpi = 900)

if __name__ == "__main__":
    main()


import numpy as np
from math import sqrt
from haar2d import haar2d, ihaar2d

#max_levels = math.ceil(math.log(image.shape[0],2))

def test_haar2d():
    """
    Asserts the forwards and inverse wavelet transformations are correct.
    """

    assert haar2d(np.random.random([5,3]),2,debug=True).shape == (8,4), \
        "Transform data must be padded to compatible shape."

    assert haar2d(np.random.random([8,4]),2,debug=True).shape == (8,4), \
        "Transform data must be padded to compatible shape, if required."

    image = np.random.random([5,3])

    haart = haar2d(image, 3)
    haari = ihaar2d(haart, 3)[:image.shape[0], :image.shape[1]]

    assert (image - haari < 0.001).all(), "Transform must be circular."

if __name__ == '__main__':
    #test_haar2d()

    #http://pda.readthedocs.org/en/latest/chp4.html
    # , duli de weidu
    image = np.random.random([8,8]) * 10
    #image = np.asarray([83.000000, 53.000000, 93.000000, 63.000000, 82.000000, 71.000000, 44.000000, 49.000000, 51.000000, 33.000000, 63.000000, 12.000000, 78.000000, 98.000000, 34.000000, 39.000000], dtype=float)

    print image
    haar = haar2d(image)
    #print haari

    ihaar = ihaar2d(haar)
    print ihaar

    assert (image - ihaar < 0.001).all(), "Transform must be circular."


from scipy.interpolate import interp1d
import matplotlib.pyplot as plt
import numpy as np

t = np.linspace(0, 10, 10)
value = np.sin(2 * np.pi * t)
plt.plot(t, value, 'b^', ms=6, label='real value')

f1 = interp1d(t, value)  # 產生線性插值函數
x = np.linspace(0, 10, 50)  # 將間隔細分為50個區段
y = f1(x)  # 利用線性插值函數產生50個插值
plt.plot(x, y, "ro", label='linear interplot')

f1 = interp1d(t, value)  # 產生線性插值函數
x = np.linspace(0, 10, 50)  # 將間隔細分為50個區段
# y = interp1d(x)  # 利用線性插值函數產生50個插值
# plt.plot(x, y, label='linear interplot')

x = np.linspace(0, 10, 200)
f3 = interp1d(t, value, kind='cubic')  # 取得三次插值法函數
y = f3(x)  # 取得三次插值的值
plt.plot(x, y, label='cubic interpolate')
plt.show()

# import numpy as np
# from scipy.interpolate import interp1d

# x = np.linspace(0, 10, num=11, endpoint=True)
# y = np.cos(-x**2/9.0)
# f = interp1d(x, y)
# f2 = interp1d(x, y, kind='cubic')

# xnew = np.linspace(0, 10, num=41, endpoint=True)
# import matplotlib.pyplot as plt
# plt.plot(x, y, 'o', xnew, f(xnew), '-', xnew, f2(xnew), '--')
# plt.legend(['data', 'linear', 'cubic'], loc='best')
# plt.show()


### merge-sort implementation in python
import numpy as np
import timeit

def merge_sort_rec(my_array):
    if len(my_array) <= 1:
        return my_array
    middle = len(my_array) // 2
    left = merge_sort_rec(my_array[:middle])
    right = merge_sort_rec(my_array[middle:])
    return rec_merge(left,right)

def rec_merge(left, right):
    ''' Repeatedly merge sorted sublists to produce a larger sorted list
    n.b lists of length 1 is sorted.
    '''
    if not left:
        return right
    if not right:
        return left
    if left[0] < right[0]:
        return [left[0]] + rec_merge(left[1:], right)
    return [right[0]] + rec_merge(left, right[1:])

def merge_sort_iterative(my_array):
    if len(my_array) <= 1:
        return my_array
    middle = len(my_array) // 2
    left = merge_sort_iterative(my_array[:middle])
    right = merge_sort_iterative(my_array[middle:])
    return iterative_merge(left,right)

def iterative_merge(left, right):
    ''' Repeatedly merge sorted sublists left, right '''
    i,j = 0,0
    merged = []
    while i < len(left) and j < len(right):
        # go through the arrays, performing a running insertion-sort

        if left[i] < right[j]:
            merged.append(left[i])
            i += 1

        else:
            merged.append(right[j])
            j += 1

        # once we exhaust one array, just append each element of the other (since both are sorted, to save time.)
        if i == len(left):
            merged.extend(right[j:])
            break

        if j == len(right):
            merged.extend(left[i:])
            break

    return merged

def wrapper(func, *args, **kwargs):
    def wrapped():
        return func(*args, **kwargs)
    return wrapped

def test_for_sorted(n):
    unsorted = [i for i in range(n)]
    np.random.shuffle(unsorted)

    sorted_rec = merge_sort_rec(unsorted)
    sorted_iterative = merge_sort_iterative(unsorted)
    unsorted.sort()

    for i,j,k in zip(unsorted,sorted_rec, sorted_iterative):
        assert(i == j)
        assert(i == k)

def test_timing(n):

    unsorted = [i for i in range(n)]
    np.random.shuffle(unsorted)

    recursive_wrapped = wrapper(merge_sort_rec, unsorted)
    print("1000 average timed iterations of recursive mergesort : ", timeit.timeit(recursive_wrapped, number=100))

    iterative_wrapped = wrapper(merge_sort_iterative, unsorted)
    print("1000 average timed iterations of iterative mergesort : ", timeit.timeit(iterative_wrapped, number=100))

if __name__ == "__main__":
    n = np.random.randint(1000)
    test_for_sorted(n)
    test_timing(n)


#!/bin/python3

import numpy as np

def number_to_binary_array(n, r):
    """
    input integer
    output binary representation of integer as values in an array that can hold numbers less than 2^r
    """
    res = [int(i) for i in bin(n)[2:]]
    while len(res) < r:
        res = [0] + res
    return res

def make_identity_matrix(n):
    """
    input n
    output list of lists, identity matrix I_n
    """
    return [number_to_binary_array(2**i, n) for i in reversed(range(n))]

def make_extended_Golay_generator():
    """
    returns the generator for the extended Golay code
    """
    B = [[1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0]]
    for _ in range(10):
        next_line = B[-1][:]
        next_line = next_line + [next_line.pop(0)]
        B.append(next_line)
    j = [[1]*11]
    G_extended = np.concatenate((np.array(B), np.array(j)))
    j_0 = [[1]]*11 + [[0]]
    G_extended = np.concatenate((np.array(G_extended), np.array(j_0)), axis=1)
    Id_12 = make_identity_matrix(12)
    G_extended = np.concatenate((Id_12, np.array(G_extended)), axis=1)
    return G_extended

G_extended = np.array([[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1],
                       [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1],
                       [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1],
                       [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1],
                       [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1],
                       [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1],
                       [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1],
                       [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1],
                       [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1],
                       [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1],
                       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1],
                       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0]])

G = np.array([[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0],
              [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1],
              [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1],
              [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0],
              [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1],
              [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1],
              [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1],
              [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0],
              [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0],
              [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0],
              [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1],
              [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])

def encode_extended_Golay(info):
    global G_extended

    if type(info) == str:
        info = [int(i) for i in info]
        res = info @ G_extended % 2
        return ''.join(str(i) for i in res)

    if type(info) == list:
        return list(info @ G_extended % 2)

    if type(info) == np.ndarray:
        return info @ G_extended % 2

def encode_Golay(info):
    global G

    if type(info) == str:
        info = [int(i) for i in info]
        res = info @ G % 2
        return ''.join(str(i) for i in res)

    if type(info) == list:
        return list(info @ G % 2)

    if type(info) == np.ndarray:
        return info @ G % 2


"""
Solves the second order ODE:

y'' + y = 0
y(0)=0; y'(0)=1

So the solution is y(x) = sin(x)
"""
from hermes1d import Node, Element, Mesh, DiscreteProblem

from math import pi
from numpy import zeros

def plot_Y(Y, a, b):
    # plot the result:
    from pylab import plot, legend, show, clf, axis
    #clf()
    # linearize the solution, divide each element into 5
    sln1, sln2 = d.linearize(Y, 5)
    x1, y1 = sln1
    x2, y2 = sln2
    plot(x1, y1, label="$u_1$")
    plot(x2, y2, label="$u_2$")
    axis([a, b, -1.5, 1.5])
    #legend()
    show()

# interval end points
a = 0.
b = pi*(1./4+1)

# number of elements:
N = 20

# x values of the nodes:
x_values =[(b-a)/N * i for i in range(N+1)]

# define nodes:
nodes = [Node(x) for x in x_values]

# define elements of the 1st mesh
elements = [Element(nodes[i], nodes[i+1], order=2) for i in range(N)]
m1 = Mesh(nodes, elements)
m1.set_bc(left=True, value=0)

# define elements of the 2nd mesh
elements = [Element(nodes[i], nodes[i+1], order=1) for i in range(N)]
m2 = Mesh(nodes, elements)
m2.set_bc(left=True, value=1)

# definition of the ODE system:
d = DiscreteProblem(meshes=[m1, m2])

# definition of the RHS:
def F(i, Y, t):
    if i == 0:
        return Y[1]
    elif i == 1:
        return -Y[0]
    raise ValueError("Wrong i (i=%d)." % (i))

# definition of the Jacobian matrix
def DFDY(i, j, Y, t):
    if i == 0 and j == 0:
        return 0.
    elif i == 0 and j == 1:
        return 1.
    elif i == 1 and j == 0:
        return -1.
    elif i == 1 and j == 1:
        return 0.
    raise ValueError("Wrong i, j (i=%d, j=%d)." % (i, j))

# assign both F and J to the discrete problem:
d.define_ode(F, DFDY)

# enumeration of unknowns:
d.assign_dofs()

# definition of the initial condition for the global Newton method:
Y = d.get_initial_condition_euler()
#plot_Y(Y, a, b)
#stop
#Y = zeros((d.ndofs,))

# Newton's iteration:
error = 1e10
i = 0
J = d.assemble_J(Y)
while error > 1e-5:
    F = d.assemble_F(Y)
    dY = d.solve(J, F)
    Y += dY
    #plot_Y(Y, a, b)
    error_dY = d.calculate_error_l2_norm(dY)
    error_F = d.calculate_error_l2_norm(d.assemble_F(Y))
    print "it=%d, l2_norm_dY=%e, l2_norm_F=%e" % (i, error_dY, error_F)
    error = max(error_dY, error_F)
    i += 1

plot_Y(Y, a, b)


"""
Random projection, Assignment 1c
"""
import numpy as np
import matplotlib.pylab as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
import random, mnist_dataloader
from scipy.spatial.distance import euclidean
import multiprocessing
import time

"""
load data sets
"""
def load_data_sets():
# load data set
    training_data, validation_data, test_data = mnist_dataloader.load_data()
    training_data_instances = training_data[0]
    training_data_labels = training_data[1]
    test_data_instances = test_data[0]
    test_data_labels = test_data[1]

    return training_data_instances, training_data_labels, test_data_instances, test_data_labels

"""
Generate random projection matrix R
@param: k, the reduced number of dimensions
@param: d, the original number of dimensions
@return: R, the generated random projection matrix, k * d size
"""

def generate_random_projection_matrix(k, d):
    R = np.zeros((k, d), dtype = np.float64)
    for r in np.nditer(R, op_flags=['readwrite']):
        r[...] = 2 * random.randint(0, 1) - 1
    # to be cautious, this is the point the exercise wants us to get
    R *= 1.0 / np.sqrt(k)

    return R

"""
random projection matrix P into R
@param R: random projection matrix
@param P: matrix to be reduced in dimension
@return: Q: projected matrix of P on R
"""
def random_projection(R, P):
    if R.shape[1] != P.shape[0]:
        return False

    print R.shape, P.shape

    return np.dot(R, P)

"""
plot distortion
@param training_data_instances: training data instances
"""
def plot_distortion(training_data_instances):
    # dimension of a training data instance
    d = training_data_instances.shape[1]
    # first m instances considered
    m = 20

    fig, axes = plt.subplots(1, 1)
    fig.suptitle("Distortion of random projection", fontsize = "x-large")

    for k in [50, 100, 500]:
        ## generate random projection matrix
        random_projection_matrix =  generate_random_projection_matrix(k, d)
        ## random projection
        m_instances = training_data_instances[0:m]
        projected_m_instances = np.dot(m_instances, np.transpose(random_projection_matrix))
        # print random_projected_matrix[0], random_projected_matrix.shape
        ## evaluate distortion - line chart
        m_instances_distortions = []
        for i in range(m):
            for j in range(i + 1, m):
                m_instances_distortions.append(euclidean(projected_m_instances[i], projected_m_instances[j]) / euclidean(m_instances[i], m_instances[j]))
        m_instances_distortions = np.array(m_instances_distortions)
        mean, std = np.mean(m_instances_distortions), np.std(m_instances_distortions)
        # line chart
        axes.plot(m_instances_distortions, label = "k=" + str(k))
        axes.plot([0, m_instances_distortions.size], [mean, mean], label = "k=" + str(k) + ", mean = " + str(round(mean, 4)))

        print "k = ", k, "distortion =", mean, "+-", std
    axes.set_xlabel("pairs of instances", fontsize = "large")
    axes.set_ylabel("distortion", fontsize = "large")
    axes.legend(loc = "center right", fontsize = "medium")
    plt.show()

"""
classify test_data
@param training_data_instances
@param training_data_labels
@param test_data_instances
@param test_instance_start_index
@param test_instance_end_index
@param classified_results: classified results, shared by different subprocesses
@return: None. Note, the updated classified results array.
"""
def find_nearest_instances_subprocess(training_data_instances, training_data_labels, test_data_instances, test_instance_start_index, test_instance_end_index,\
                                      classified_results):
    # print test_instance_start_index, test_instance_end_index
    for test_instance_index in range(test_instance_start_index, test_instance_end_index):
        test_instance = test_data_instances[test_instance_index]
        # find the nearest training instance with euclidean distance
        minimal_euclidean_distance = euclidean(test_instance, training_data_instances[0])
        minimal_euclidean_distance_index = 0
        for training_instance, training_instance_index in zip(training_data_instances, range(len(training_data_instances))):
            # compute the euclidean distance
            euclidean_distance = euclidean(test_instance, training_instance)
            if euclidean_distance < minimal_euclidean_distance:
                minimal_euclidean_distance = euclidean_distance
                minimal_euclidean_distance_index = training_instance_index
        classified_results[test_instance_index] =\
         training_data_labels[int(minimal_euclidean_distance_index)]

"""
find nearest neighbor algorithm without random projection
@param training_data_instances
@param training_data_labels
@param test_data_instances
@param test_data_labels
@return: classified_results, error_rate, and confusion_matrix
"""
def find_nearest_instances(training_data_instances, training_data_labels, test_data_instances, test_data_labels):
    start_time = time.time()
    # speed using multiple processes
    NUMBER_OF_PROCESSES = 4
    processes = []
    # shared by different processes, to be mentioned is that
    # global variable is only read within processes
    # the update of global variable within a process will not be submitted
    classified_results = multiprocessing.Array('i', len(test_data_instances), lock = False)
    test_data_subdivisions = range(0, len(test_data_instances) + 1,\
                                    int(len(test_data_instances) / NUMBER_OF_PROCESSES))
    test_data_subdivisions[-1] = len(test_data_instances)
    for process_index in range(NUMBER_OF_PROCESSES):
        process = multiprocessing.Process(target = find_nearest_instances_subprocess,
                                          args = (training_data_instances,
                                                  training_data_labels,
                                                  test_data_instances,
                                                  test_data_subdivisions[process_index],
                                                  test_data_subdivisions[process_index + 1],
                                                  classified_results))
        process.start()
        processes.append(process)

    print "Waiting..."
    # wait until all processes are finished
    for process in processes:
        process.join()
    print "Complete."
    print "--- %s seconds ---" % (time.time() - start_time)

    error_count = 0
    confusion_matrix = np.zeros((10, 10), dtype=np.int)
    for test_instance_index, classified_label in zip(range(len(test_data_instances)),\
                                                      classified_results):
        if test_data_labels[test_instance_index] != classified_label:
            error_count += 1
        confusion_matrix[test_data_labels[test_instance_index]][classified_label] += 1

    error_rate = 100.0 * error_count / len(test_data_instances)

    return classified_results, error_rate, confusion_matrix

if __name__ == '__main__':
    multiprocessing.freeze_support()
    training_data_instances, training_data_labels, test_data_instances, test_data_labels = load_data_sets()

    ## plot distortion
    # plot_distortion(training_data_instances)
    ## nearest neighbor without random projection
    print "Without random projection"
    classified_results, error_rate, confusion_matrix = find_nearest_instances(training_data_instances, training_data_labels, test_data_instances, test_data_labels)
    print "Error rate:", error_rate
    print "Confusion matrix:", confusion_matrix

    ## random projection
    # dimension of a training data instance
    d = training_data_instances.shape[1]
    for k in [50, 100, 500]:
        ## generate random projection matrix
        random_projection_matrix =  generate_random_projection_matrix(k, d)
        ## random projection
        projected_training_instances = np.zeros((len(training_data_instances), k), dtype = np.float64)
        for i in range(training_data_instances.shape[0]):
            for j in range(projected_training_instances.shape[1]):
                projected_training_instances[i][j] = np.dot(random_projection_matrix[j], training_data_instances[i])

        projected_test_instances = np.zeros((len(test_data_instances), k), dtype = np.float64)
        for i in range(test_data_instances.shape[0]):
            for j in range(projected_test_instances.shape[1]):
                projected_test_instances[i][j] = np.dot(random_projection_matrix[j], test_data_instances[i])

        classified_results, error_rate, confusion_matrix = find_nearest_instances(projected_training_instances, training_data_labels, projected_test_instances, test_data_labels)
        print "Error rate:", error_rate
        print "Confusion matrix:", confusion_matrix


"""
Monte Carlo methods, or Monte Carlo experiments, are a broad class of computational algorithms
that rely on repeated random sampling to obtain numerical results. The underlying concept is to
use randomness to solve problems that might be deterministic in principle. They are often used
in physical and mathematical problems and are most useful when it is difficult or impossible to
use other approaches. Monte Carlo methods are mainly used in three problem classes:[1] optimization,
numerical integration, and generating draws from a probability distribution.

Link wiki: https://en.wikipedia.org/wiki/Monte_Carlo_method
Link article: https://medium.com/cantors-paradise/estimating-%CF%80-using-monte-carlo-simulations-3459a84b5ef9

This code is an interactive application that estimates the value of pi
using the unit square unit circle method.

pi = 4 * (number of points in the circle)/(number of points in the square)

Author: Harivinay
Github: github.com/M87K452b
Date: Feb 2021

Note: The animate function is yet to be implemented.
"""

from tkinter import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np

root = Tk()
root.title("Estimate Pi")
root.geometry("900x600")

def make_points():
    num_pts = int(num_points.get())
    data = np.random.random((num_pts, 2))
    return data

def plot_points():

    global data
    data = make_points()
    figure = plt.figure(figsize=(6, 6), dpi=100)
    draw_circle = plt.Circle((0.5135, 0.4945), 0.3855, fill=False)
    figure.add_artist(draw_circle)
    figure.add_subplot(111).scatter(data[:, 0], data[:, 1], s=1)
    chart = FigureCanvasTkAgg(figure, frame_cv)
    chart.get_tk_widget().grid(row=0, column=0)

    plt.grid("off")
    axes = plt.axes()
    axes.set_xlim([0, 1])
    axes.set_ylim([0, 1])

def calc_pi():
    in_circle = 0
    pi = 0
    for i in range(len(data)):
        if np.sqrt(data[i, 0] ** 2 + data[i, 1] ** 2) <= 1:
            in_circle = in_circle + 1

    pi = 4 * (in_circle / len(data))

    num_in = "Points In = " + str(in_circle)
    num_out = "Points Out = " + str(len(data) - in_circle)

    Label(frame_btn, text=num_in, width=25, height=2, font=("Helvetica", 16)).grid(
        row=5, column=0
    )
    Label(frame_btn, text=num_out, width=25, height=2, font=("Helvetica", 16)).grid(
        row=6, column=0
    )

    pi_text = "Pi = " + str(round(pi, 6))
    estimated_pi = Label(
        frame_btn, text=pi_text, width=25, height=2, font=("Helvetica", 16)
    )
    estimated_pi.grid(row=7, column=0, padx=10, pady=5)

def animate():
    return

frame_cv = LabelFrame(root, padx=1, pady=1)
frame_cv.place(height=598, width=598, x=1, y=1)

figure = plt.figure(figsize=(6, 6), dpi=100)
figure.add_subplot(111)
draw_circle = plt.Circle((0.5135, 0.4945), 0.3855, fill=False)
figure.add_artist(draw_circle)
chart = FigureCanvasTkAgg(figure, frame_cv)
chart.get_tk_widget().grid(row=0, column=0)
plt.grid()
axes = plt.axes()
axes.set_xlim([0, 1])
axes.set_ylim([0, 1])

frame_btn = LabelFrame(root, padx=1, pady=20)
frame_btn.place(height=599, width=298, x=601, y=1)

Label(frame_btn, text="Number of points", font=("Helvetica", 16)).grid(row=0, column=0)

num_points = Scale(
    frame_btn, from_=0, to=100000, resolution=100, orient=HORIZONTAL, length=185
)
num_points.grid(row=1, column=0, padx=10, pady=10)

plot_btn = Button(
    frame_btn, text="Plot points", width=25, height=5, command=plot_points
)
plot_btn.grid(row=2, column=0, padx=10, pady=5)

pi_btn = Button(frame_btn, text="Estimate Pi", width=25, height=5, command=calc_pi)
pi_btn.grid(row=3, column=0, padx=10, pady=5)

animate = Button(
    frame_btn, text="Animate", width=25, height=5, command=animate, state=DISABLED
)
animate.grid(row=4, column=0, padx=5, pady=5)

num_in = "Points In = 0"
num_out = "Points Out = 0"
pi = "Pi = "

Label(frame_btn, text=num_in, width=25, height=2, font=("Helvetica", 16)).grid(
    row=5, column=0
)
Label(frame_btn, text=num_out, width=25, height=2, font=("Helvetica", 16)).grid(
    row=6, column=0
)
Label(frame_btn, text=pi, width=26, height=2, font=("Helvetica", 16)).grid(
    row=7, column=0
)

exit_btn = Button(frame_btn, text="Exit", width=25, height=5, command=root.quit)
exit_btn.grid(row=8, column=0, padx=5, pady=5)

root.mainloop()


# Numerical operations on arrays

# Basic operations

import numpy as np
import matplotlib.pyplot as plt

a = np.array([1, 2, 3, 4])
print(a + 1,"We are Add 1 to existing array")
print(2**a,"We are Exp to Existing array")

# All arithmetic operates elementwise:
b = np.ones(4) + 1
print(a - b)
print(a * b)

j = np.arange(5)
print(2**(j + 1) - j)

# These operations are of course much faster than if you did them in pure python:
a = np.arange(10000) # this from numpy
%timeit a+1 # Time Taken to Run the Cell

# This is From Python
l = range(10000)
%timeit [i+1 for i in l]

# Task
"""
1. Create Two Arrays A and B Should be Only 1D
2. Perfome All Operators in Python
3. Create Two Arrays A and B Should be Only 2D


"""
Please see
https://computationalmindset.com/en/neural-networks/ordinary-differential-equation-solvers.html#sys1
for details
"""

import numpy as np
import matplotlib.pyplot as plt

from scipy.integrate import solve_ivp

def ode_sys(t, XY):
	x=XY[0]
	y=XY[1]
	dx_dt= - x + y
	dy_dt= 4. * x - y
	return [dx_dt, dy_dt]

an_sol_x = lambda t : np.exp(t) + np.exp(-3. * t)
an_sol_y = lambda t : 2. * np.exp(t) - 2. * np.exp(-3. * t)

t_begin=0.
t_end=5.
t_nsamples=100
t_space = np.linspace(t_begin, t_end, t_nsamples)
x_init = 2.
y_init = 0.

x_an_sol = an_sol_x(t_space)
y_an_sol = an_sol_y(t_space)

method = 'RK45' #available methods: 'RK45', 'RK23', 'DOP853', 'Radau', 'BDF', 'LSODA'
num_sol = solve_ivp(ode_sys, [t_begin, t_end], [x_init, y_init], method=method, dense_output=True)
XY_num_sol = num_sol.sol(t_space)
x_num_sol = XY_num_sol[0].T
y_num_sol = XY_num_sol[1].T

plt.figure()
plt.plot(t_space, x_an_sol, '--', linewidth=2, label='analytical x')
plt.plot(t_space, y_an_sol, '--', linewidth=2, label='analytical y')
plt.plot(t_space, x_num_sol, linewidth=1, label='numerical x')
plt.plot(t_space, y_num_sol, linewidth=1, label='numerical y')
plt.title('System of 2 ODEs 1st order IVP solved by SciPy with method=' + method)
plt.xlabel('t')
plt.legend()
plt.show()


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

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

# Here I first set conditions

import numpy as np
from numpy import linalg as La
import matplotlib.pyplot as plt
import time
np.seterr(divide='ignore', invalid='ignore')

print('\n')

n = 9
K = np.sqrt(n)
k = int(K)

#################
# aX = b
#################

# Here I set the Matrix

a = np.zeros((n,n))

for i in range(n):
    for j in range(n):
        if i == j:
            a[i,j] = -4
        elif i == j-k or i ==j+k:
            a[i,j] = 1
        elif ((i+1) % k != 0 and i == j-1) or ((i+1) % k != 1 and i == j+1): # (i+1) because in Python we start from 0
            a[i,j] = 1

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

b = np.zeros(n)

for i in range(k):
    if i < k-1: # (k-1) because in Python we start from 0
        b[i] = -50
    else:
        b[i] = -150

for i in range(k,n-k):
    if (i+1)%k != 0: # (i+1) because in Python we start from 0
        b[i] = 0
    else:
        b[i] = -100

for i in range(n-k,n):
    if i < n-1: # (k-1) because in Python we start from 0
        b[i] = -50
    else:
        b[i] = -150

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

def linearsystem(coeff,resul,size):

    # Initial x_k and x_k1 value

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

    # Here I set the tolerance
    tolerance = 1e-9
    # Here I set the iterations
    ite = 0

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

    while (erro > tolerance): #
        for i in range(0,size):

            x_k1[i] = resul[i]

            for j in range(0,size):
                if j!=i:
                    x_k1[i] =  x_k1[i] - coeff[i,j]*x_k[j]

            x_k1[i] =  x_k1[i]/ coeff[i,i]

        #erro = (x_k1 - x_k)/x_k1
        erro = La.norm((x_k1 - x_k),np.inf)

        x_k = x_k1.copy()
        #x_k[:] = x_k1[:] # -> the same as above

        ite = ite + 1

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

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

    return x_k1

t_initial = time.time()
res = linearsystem(a,b,n)
t_final = time.time()

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

print("\n\n--- %s seconds ---\n" % (t_final - t_initial))

# PLOT OF THE MATRIX

def samplemat(dims,bb):

    aa = np.zeros(dims)

    #print(bb)

    aa = np.reshape(bb,(dims))

    return aa

# Display matrix
plt.matshow(samplemat((k, k),res))
plt.colorbar()

plt.show()


import numpy as np
import matplotlib.pyplot as plt

def f(x,lam):
    return lam*np.exp(-lam*x) if x>0 else 0

# This is the inverse of the cumulative distribution function of a random variable X we explicitly know
def F_inv(x,lam):
    return -1/lam * np.log(1-x)

def X(lam):
    U = np.random.uniform(0,1) # Generates a uniform random number between 0 and 1
    return F_inv(U,lam)
experiments = 10000
results = []
lam = 2.0
for _ in range(experiments):
    results.append(X(lam))

x = np.linspace(0.01,5,100)
plt.plot(x,[f(_,lam) for _ in x], color="red")
plt.hist(results, density=True, bins=20, color="blue")
plt.show()

def sample_circle():
    accept = False
    while not accept:
        [x,y] = np.random.uniform(-1,1,2)
        if (x**2 + y**2 < 1):
            accept = True
            return [x,y]

def rejection_sampling(experiments):
    accepted = []
    rejected = []
    for _ in range(experiments):
        [x,y] = np.random.uniform(-1,1,2)
        if (x*x + y*y < 1):
            accepted.append([x,y])
        else:
            rejected.append([x,y])
    plt.scatter([a[0] for a in accepted],[a[1] for a in accepted],color='green')
    plt.scatter([b[0] for b in rejected], [b[1] for b in rejected],color='red')
    plt.show()
    return [accepted,rejected]

#rejection_sampling(1000)

def beta_distribution(x,n,m):
    return x**n * (1-x)**m

def wiggly(x):
    return 2*np.exp(-x*x)*x*x / np.sqrt(np.pi)
def campana(x):
    return 0.7*np.exp(-x*x/4)

#x = np.linspace(-3,3,1000)
#plt.plot(x, [campana(_) for _ in x],color='blue')
#plt.plot(x,[wiggly(_) for _ in x],color='red')
#plt.show()

###

## NORMAL VARIABLE
#def n(x):
#    return np.exp(-x*x/2)/np.sqrt(2*np.pi)
#
#def f(x):
#    return 2*np.exp(-x**2/2)/np.sqrt(2*np.pi) if x>= 0 else 0
#
#def g(x):
#    return np.exp(-x) if x>=0 else 0
#
#def simulate_Y():
#    U = np.random.uniform(0,1)
#    return -np.log(U)
#
#def rejection_sampling():
#    while True:
#        U = np.random.uniform(0,1)
#        Y = simulate_Y()
#        if U <= np.exp(-(Y-1)**2 / 2):
#            S = 1 if np.random.uniform(0,1)>0.5 else -1
#            return S*Y
#
#samples = 100000
#res = []
#for _ in range(samples):
#    res.append(rejection_sampling())
#
#x = np.linspace(-5,5,1000)
#plt.plot(x,[n(_) for _ in x], color="red")
#plt.hist(res,density=True, bins = 30, color="blue")
#plt.show()


import numpy as np

def crab_fuel(n):
    return (n**2 + n) // 2

if __name__ == '__main__':
    with open('input.txt') as f:
        pin = np.array([int(x) for x in f.read().split(',')])

    distances = np.abs(pin[None, :] - np.arange(pin.max() + 1)[:, None])
    total_fuel = np.sum(distances, axis=1)
    print(f'Solution 1: {total_fuel.min()}')

    distances_v2 = crab_fuel(distances)
    total_fuel_v2 = np.sum(distances_v2, axis=1)
    print(f'Solution 2: {total_fuel_v2.min()}')


import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

sns.set_style('whitegrid')

# Removing outliers per column
def remove_outliers_numeric(df,feature,delta=1.5):
    '''This function takes as input DataFrame and feature to remove outliers '''
    Q1_value = df[feature].quantile(0.25)
    Q3_value = df[feature].quantile(0.75)
    IQR_value = Q3_value - Q1_value    #IQR is interquartile range.

    filter_value = (df[feature] >= Q1_value - delta * IQR_value) & (df[feature] <= Q3_value + delta *IQR_value)
    df = df.loc[filter_value,:]
    return df

# Plot distribution of data per mode
def plot_distribution_numeric(df, feature, title = 'distribution', mode = 'normal', **kwargs):
    ''' This function takes as input a dataFrame as a feature and plot his distribution.
    Three possible modes, plotting the distribution as it is (mode = 'normal'), plotting excluding outliers (mode = 'without_outliers') or
    plotting excluding values below min_feature and above_max_feature (mode = 'with_limits')'''

    plt.figure(figsize=(8,6))

    if mode == 'normal':
        sns.distplot(df[feature])

    elif mode == 'without_outliers':
        df_clean = remove_outliers_numeric(df[[feature]],feature)
        sns.distplot(df_clean[feature])

    elif mode == 'with_limits':
        df_clean = df.loc[(df[feature]>kwargs['min_feature'])&(df[feature]<kwargs['max_feature']),feature]
        sns.distplot(df_clean)

    else:
        print("this mode is not available")

    plt.title(title)
    plt.show()
    return


# %%
import numpy as np
import matplotlib.pyplot as plt
import gym
import matplotlib.pyplot as plt
import seaborn as sns

def policy_evaluation(P, nS, nA, policy, gamma=0.9, tol=1e-3):

    value_function = np.zeros(nS)

    loops = 0
    while True:
        loops += 1
        value_change = 0
        for state in range(nS):
            action = policy[state]
            old_value = value_function[state]
            new_value = 0
            for prob, next_state, reward, terminal in P[state][action]:
                new_value += prob * \
                    (reward + gamma * value_function[next_state])
            value_function[state] = new_value
            value_change = max(value_change, abs(old_value - new_value))
        if value_change < tol:
            break

    ############################
    return value_function, loops

def policy_improvement(P, nS, nA, value_from_policy, policy, gamma=0.9):

    new_policy = np.zeros(nS, dtype='int')

    for state in range(nS):
        new_action_value = -float('inf')
        new_action = None

        for action in range(nA):
            value = 0
            for prob, next_state, reward, terminal in P[state][action]:
                value += prob * (reward + gamma *
                                 value_from_policy[next_state])
            if value > new_action_value:
                new_action_value = value
                new_action = action

        assert new_action is not None
        new_policy[state] = new_action

    ############################
    return new_policy

def policy_iteration(P, nS, nA, gamma=0.9, tol=10e-3, printout=True):

    value_function = np.zeros(nS)
    policy = np.zeros(nS, dtype=int)

    policy_improv_steps = 0
    policy_eval_steps = 0
    while True:
        old_policy = policy.copy()
        value_function, add_policy_eval_steps = policy_evaluation(
            P, nS, nA, policy, gamma, tol)
        policy_eval_steps += add_policy_eval_steps
        policy = policy_improvement(P, nS, nA, value_function, policy, gamma)
        policy_improv_steps += 1
        if np.all(policy == old_policy):
            break

    if printout:
        print(f'{policy_improv_steps} policy improvement steps.')
        print(f'Total policy evaluation steps: {policy_eval_steps}')

    return value_function, policy

def heatmap_value_func(value_func):
    sns.set()
    sns.heatmap(value_func)
    plt.show()

if __name__ == '__main__':

    environment = "FrozenLake-v1"
    env = gym.make(environment)

    gamma = 0.9
    tolerance = 1e-3

    value_function, policy = policy_iteration(
        env.env.P, env.env.nS, env.env.nA, gamma, tolerance)

    print('optimal value function', value_function)
    print('optimal policy', policy)

    # change shape of value_function to 16,1
    value_function = np.reshape(value_function, (4, 4))

    heatmap_value_func(value_function)


import numpy as np

# Source: https://zpl.fi/aligning-point-patterns-with-kabsch-umeyama-algorithm/
def get_similarity_transform(A, B):
    """
    Compute the optimal similarity transform from B to A.
    Namely, compute rotation `R`, translation `t` and scale `c` that best align points `B` into
    reference points `A` in the least squares sense as `c * R @ b_i + t ≈ a_i` for all `i`.
    Or, in matrix form,
        (c*R[3,3] | t ) @ (B[3,N]) ≈ (A[3,N])
        (    0    | 1 ) @ (  1   )   (  1   )
    Uses the Kabsch-Umeyama algorithm.

    A, B
        np.ndarray, float32, (N, 3)

    return:
    R
        np.ndarray, float32, (3, 3)
    t
        np.ndarray, float32, (3,)
    c
        float
    """
    assert A.shape == B.shape
    n, m = A.shape

    EA = np.mean(A, axis=0)
    EB = np.mean(B, axis=0)
    VarA = np.mean(np.linalg.norm(A - EA, axis=1) ** 2)

    H = ((A - EA).T @ (B - EB)) / n
    U, D, VT = np.linalg.svd(H)
    d = np.sign(np.linalg.det(U) * np.linalg.det(VT))
    S = np.diag([1] * (m - 1) + [d]).astype(A.dtype)

    R = U @ S @ VT
    c = VarA / np.trace(np.diag(D) @ S)
    t = EA - c * R @ EB

    return R, t, c

if __name__ == '__main__':
    TARGET = np.float32([
        [-0.067039, 0.037248, 0.257207],
        [-0.021285, 0.123967, 0.23051],
        [-0.138212, 0.123967, 0.235361],
        [-0.077207, 0.310112, 0.13561],
        [-0.077207, -0.048883, 0.170014],
    ])

    SOURCE = np.float32([
        [-0.025177, 0.05771, 0.226592],
        [0.055307, 0.167654, 0.207354],
        [-0.111732, 0.160782, 0.215266],
        [-0.034637, 0.449523, 0.08648],
        [-0.021788, -0.052372, 0.10771],
    ])

    R, t, c = get_similarity_transform(TARGET, SOURCE)
    print(f"R:\n{R}")
    print(f"t:\n{t}")
    print(f"scale:\n{c}")


import numpy as np

def std_plus(x):
  '''
  Calculates the standard deviation of the values in a numeric vector.
  It is capable of computing standard deviation when the vector contains missing values
  and inifinite values by automatically removing them.

  parameters:
  -----------
  x (array_like) a numeric vector

  Return:
  ------
  sd_value (float): the value of standard deviation of the input data
  '''
  # convert the input vector to numpy array if it is an array-like
  if isinstance(x, (list, tuple, np.ndarray)):
    x = np.array(x)

   # check whether a input vector is valid
  length = len(x)
  if length == 1: # return 0.0 The length of input equals to 1
    return 0.0
  elif length == 0: # return nan if no input
    raise TypeError("The input cannot be empty.")

  # treat infinite values as missing values and remove them
  x = x[~ np.isinf(x)]

  # remove missing values
  x = x[~ np.isnan(x)]

  # check if an array is empty after we remove inf and nan
  if x.size == 0:
    return np.nan

  # calculate standard deviation
  sd_value = np.sqrt(np.mean(abs(x - x.mean())**2)) # default method in Numpy

  # The following method includes degree of freedom
  # mu = sum(x) / length
  # differences = [i - mu for i in x]
  # sq_differences = [d ** 2 for d in differences]
  # sd_value = np.sqrt(sum(sq_differences) / (length - 1))

  return sd_value


import numpy as np
import pandas as pd
import scipy
from pandas import Series

def ent(data: Series) -> float:
    p_data = data.value_counts() / len(data)  # calculates the probabilities
    entropy = scipy.stats.entropy(p_data, base=2)  # input probabilities to get the entropy
    # entropy = sc.stats.entropy(p_data)  # input probabilities to get the entropy
    return entropy

if __name__ == "__main__":
    input_data = pd.Series(np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]))
    print(ent(input_data))

    input_data = pd.Series(np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]))
    print(ent(input_data))


import altair as alt
import numpy as np
import pandas as pd
import streamlit as st

import scipy.stats

def normal_distribution():
    param_range = st.sidebar.slider('Range', value=20.0, min_value=0.0, max_value=100.0, step=0.1, format='%.1f')
    param_mean = st.sidebar.slider('Mean', value=0.0, min_value=-30.0, max_value=30.0, step=0.1, format='%.1f')
    param_std = st.sidebar.slider('Standard deviation', value=3.0, min_value=0.1, max_value=20.0, step=0.1, format='%.1f')

    x = np.linspace(start=-param_range, stop=param_range, num=400)

    with st.expander('Plot of PDF', expanded=True):
        y = scipy.stats.norm.pdf(x, param_mean, param_std)
        pdf_data = pd.DataFrame.from_dict({
            'x': x,
            'probability density': y,
        })
        line_chart = alt.Chart(pdf_data).mark_line().encode(
            x='x',
            y='probability density',
            tooltip=['x', 'probability density'],
        )
        st.altair_chart(line_chart.interactive(), use_container_width=True)

    with st.expander('Plot of CDF', expanded=False):
        y = scipy.stats.norm.cdf(x, param_mean, param_std)
        cdf_data = pd.DataFrame.from_dict({
            'x': x,
            'cumulative density': y,
        })
        line_chart = alt.Chart(cdf_data).mark_line().encode(
            x='x',
            y='cumulative density',
            tooltip=['x', 'cumulative density'],
        )
        st.altair_chart(line_chart.interactive(), use_container_width=True)

    with st.expander('Formulae', expanded=False):
        st.markdown(formulae_text)

    with st.expander('LaTeX', expanded=False):
        st.markdown(latex_text)

formulae_text = r'''Parameters:
- mean: $\mu\in\mathbb{R}$
- standard deviation: $\sigma\in\mathbb{R}^+$
---
Support:
- $x\in\mathbb{R}$
---
Moments:
- $\mathrm{E}(X) = \mu$
- $\mathrm{Var}(X) = \sigma^2$
---
Probability density function (PDF):

$$
f(x|\mu,\sigma) = \frac{1}{\sqrt{2\pi\sigma^2}}\text{exp}\left(-\frac{(x-\mu)^2}{2\sigma^2}\right)
$$
---
Cumulative distribution function (CDF):

$$
F(x|\mu,\sigma) = \frac{1}{2}\left[1+\text{erf}\left(\frac{x-\mu}{\sigma\sqrt{2}}\right)\right]
$$

where

$$
\text{erf}(x) = \frac{2}{\sqrt{\pi}}\int_{0}^{x} e^{-t^2}\mathrm{d}t
$$
is the error function.
'''

latex_text = r'''Moments:
```latex
\mathrm{E}(X) = \mu
```
```latex
\mathrm{Var}(X) = \sigma^2
```
---
Probability density function (PDF):
```latex
f(x|\mu,\sigma) = \frac{1}{\sqrt{2\pi\sigma^2}}\text{exp}\left(-\frac{(x-\mu)^2}{2\sigma^2}\right)
```
---
Cumulative distribution function (CDF):
```latex
F(x|\mu,\sigma) = \frac{1}{2}\left[1+\text{erf}\left(\frac{x-\mu}{\sigma\sqrt{2}}\right)\right]
```
where
```latex
\text{erf}(x) = \frac{2}{\sqrt{\pi}}\int_{0}^{x} e^{-t^2}\mathrm{d}t
```
is the error function.
'''


# %BST_LICENCE_TEXT%

import numpy as np
from math import factorial, sqrt
from scipy.special import lpmv

def sphHarmReal( n, m, theta, phi, dtype = None ):
    """
    Real spherical harmonics function.
    """
    def trig( m, phi, dtype ):
        if m == 0:
            return np.ones( phi.shape, dtype = dtype )
        elif m < 0:
            return np.sin( phi * abs(m) ) * sqrt(2.0)
        else:
            return np.cos( phi * m ) * sqrt(2.0)

    if dtype is None:
        dtype = np.asarray(theta).dtype

    (thetaBC, phiBC) = np.broadcast_arrays( theta, phi )
    mabs = abs(m)

    fac = sqrt( ((2*n+1)*factorial(n-mabs)) /(4*np.pi*factorial(n+mabs)))
    # Apparently we need to compensate for the Condon-Shortley phase
    # Although the scipy documentation says
    # "Note that this implementation includes the Condon-Shortley phase."
    lp = fac * (-1)**mabs * lpmv(  mabs, n, np.cos(thetaBC) )
    return trig( m, phiBC, dtype ) * lp

def allSphHarmRealACN( N, theta, phi, dtype = None ):
    numCoeffs = (N+1)*(N+1)
    [thetaBC, phiBC ] = np.broadcast_arrays( theta, phi )
    argShape = thetaBC.shape
    if dtype is None:
        dtype = thetaBC.dtype
    Y = np.NaN * np.ones( (numCoeffs,)+argShape, dtype = dtype )
    for n in range(0,N+1):
        for m in range(-n,n+1):
            Y[n**2+n+m,...] = sphHarmReal( n, m, theta, phi, dtype )
    return Y


import numpy as np
from functions import get_matinc, PeriodicBoundary
from fenics import (Constant, FunctionSpace, UnitSquareMesh, UnitCubeMesh, inner,
                    MeshFunction, SubDomain, between, Measure, DirichletBC,
                    TrialFunction, TestFunction, Function, grad, assemble, solve)

# PARAMETERS
dim   = 2   # dimension (works for 2D and 3D)
N     = 5*np.ones(dim, dtype=np.int) # number of voxels (assumed equal for all directions)
phase = 10. # material contrast
order = 1   # polynomial order in FE space

# auxiliary values
prodN = np.prod(np.array(N)) # number of grid points
vec_shape=(dim,)+tuple(N) # shape of the vector for storing DOFs

# PROBLEM DEFINITION
_mat, _inc = get_matinc(dim, phase) # material coef. for matrix (mat) and inclusion (inc)
mat=Constant(_mat)
inc=Constant(_mat+_inc)

class Inclusion_2d(SubDomain): # square inclusion
    def inside(self, x, on_boundary):
        return (between(x[1], (0.2, 0.8)) and between(x[0], (0.2, 0.8)))

class Inclusion_3d(SubDomain): # cube inclusion
    def inside(self, x, on_boundary):
        return (between(x[2], (0.2, 0.8)) and between(x[1], (0.2, 0.8)) and between(x[0], (0.2, 0.8)))

E=np.zeros(dim); E[0]=1. # macroscopic value
E=Constant(E)

if dim==2:
    mesh=UnitSquareMesh(*N) # generation of mesh
    inclusion=Inclusion_2d()
    point0="near(x[0], 0) && near(x[1], 0)"
elif dim==3:
    mesh=UnitCubeMesh(*N) # generation of mesh
    inclusion=Inclusion_3d()
    point0="near(x[0], 0) && near(x[1], 0) && near(x[2], 0)"

V=FunctionSpace(mesh, "CG", order, constrained_domain=PeriodicBoundary(dim))

# setting the elements that lies in inclusion and in matrix phase
domains=MeshFunction("size_t", mesh, dim)
domains.set_all(0)
inclusion.mark(domains, 1)
dx=Measure('dx', subdomain_data=domains)

def bilf(up, vp): # bilinear form
    return inner(mat*up, vp)*dx(0)+inner(inc*up, vp)*dx(1)

bc0=DirichletBC(V, Constant(0.), point0, method='pointwise')

# SOLVER
u=TrialFunction(V)
v=TestFunction(V)
uE = Function(V)

solve(bilf(grad(u), grad(v)) == -bilf(E, grad(v)), uE, bcs=[bc0])

# POSTPROCESSING evaluation of guaranteed bound
AH11 = assemble(bilf(grad(uE)+E, grad(uE)+E))
print('homogenised component A11 = {} (FEM)'.format(AH11))

print('END')


'''
Polynomial Feature Transform

Summary
-------
Feature transform that takes a vector [x y z],
and returns an order-r transform [1 x^1 x^2 x^3 y^1 y^2 y^3 z^1 z^2 z^3]

API
---
Follows an sklearn-like "transform" interface
* __init__()
* transform
'''

import numpy as np

class PolynomialFeatureTransform:

    def __init__(self, order=1, input_dim=1, input_feature_names=None):
        ''' Create FeatureTransform that does polynomial transform of each feature up to given order
        '''
        if order < 0:
            raise ValueError("Polynomial order cannot be negative")
        if input_dim < 1:
            raise ValueError(
                "Input dimension must be an integer greater than or equal to 1")
        self.input_dim = int(input_dim)
        self.order = int(order)
        self.output_dim = self.input_dim * order + 1
        if input_feature_names is None:
            self.input_feature_names = ['x%2d' %
                                        a for a in range(self.input_dim)]
        else:
            self.input_feature_names = [str(a) for a in input_feature_names]

    def get_feature_size(self):
        return self.output_dim

    def get_feature_names(self):
        ''' Get list of string names, one for each transformed feature

        Examples
        --------
        >>> tfm2 = PolynomialFeatureTransform(order=3, input_dim=2, input_feature_names=['a', 'b'])
        >>> tfm2.get_feature_names()
        ['bias', 'a^1', 'b^1', 'a^2', 'b^2', 'a^3', 'b^3']
        '''
        feat_names = ['bias']
        for P in range(1, self.order+1):
            feat_names += map(lambda s: '%s^%d' % (s,P),
                              self.input_feature_names)
        return feat_names

    def transform(self, x_ND):
        ''' Perform feature transformation on raw input measurements

        Examples
        --------
        >>> x_ND = np.arange(0, 5)[:, np.newaxis]
        >>> x_ND
        array([[0],
               [1],
               [2],
               [3],
               [4]])
        >>> tfm0 = PolynomialFeatureTransform(order=0)
        >>> tfm0.transform(x_ND)
        array([[1],
               [1],
               [1],
               [1],
               [1]])
        >>> tfm1 = PolynomialFeatureTransform(order=1)
        >>> tfm1.transform(x_ND)
        array([[1, 0],
               [1, 1],
               [1, 2],
               [1, 3],
               [1, 4]])
        >>> tfm2 = PolynomialFeatureTransform(order=2)
        >>> tfm2.transform(x_ND)
        array([[ 1,  0,  0],
               [ 1,  1,  1],
               [ 1,  2,  4],
               [ 1,  3,  9],
               [ 1,  4, 16]])
        '''
        N, D = x_ND.shape
        if not self.input_dim == D:
            raise ValueError(
                "Mismatched input dimension. Expected %d but received %d" % (self.input_dim, D))

        phi_NM = np.zeros((N, self.output_dim), dtype=x_ND.dtype)

        phi_NM[:, 0] = 1.0
        m_dim = 1
        for P in range(1, self.order+1):
            phi_NM[:, m_dim:(m_dim + D)] = np.power(x_ND, P)
            m_dim += D
        return phi_NM


import numpy as np

def get_perm_matrix(dim, seed=None):
    """ builds permutation matrix

    Args:
        dim (int): dimension of permutation matrix
        seed: random seed

    Returns:
        perm_matrix (np.array): (dim, dim)
    """
    if seed is not None:
        np.random.seed(seed)

    perm_matrix = np.zeros((dim, dim))
    for idx_from, idx_to in enumerate(np.random.permutation(range(dim))):
        perm_matrix[idx_from, idx_to] = 1

    return perm_matrix


# plots.py
from __future__ import division
import matplotlib
matplotlib.rcParams = matplotlib.rc_params_from_file('../../matplotlibrc')

import numpy as np
from numpy import array as arr
from scipy.interpolate import interp1d
from scipy.linalg import solve
import matplotlib.pyplot as plt
from matplotlib.axis import Axis

from solutions import ode_fe, cheb
from tridiag import tridiag

def tridiagonal_approach():
	epsilon, alpha, beta = .02, 2., 4.
	# N = number of subintervals or finite elements

	def AnalyticSolution(x,alpha, beta,epsilon):
		out = alpha+x+(beta-alpha-1.)*(np.exp(x/epsilon) -1.)/(np.exp(1./epsilon) -1.)
		return out

	def matrix_diagonals(N):
		x = np.linspace(0,1,N+1)**(1.) # N+1 = number of grid points
		h = np.diff(x)
		b, f = np.zeros(N+1), np.zeros(N+1)
		a, c= np.zeros(N), np.zeros(N)
		b[0], b[N] = 1., 1.
		f[0], f[N] = alpha, beta
		# i = 0 to N-2
		b[1:N] = -epsilon*(1./h[0:N-1] + 1./h[1:N])
		f[1:N] = -.5*(h[0:N-1] + h[1:N])
		c[1:N] = epsilon/h[1:N] - .5
		a[0:N-1] = epsilon/h[0:N-1] + .5
		return a, b, c, f, x

	n = [2**i for i in range(4,22)]

	max_error_fe = [10]*(len(n))
	h = [1./num for num in n]
	for j in range(len(n)):
		a, b, c, f, x = matrix_diagonals(n[j])
		numerical_soln = tridiag(a,b,c,f)
		analytic_soln = AnalyticSolution(x, alpha, beta,epsilon)
		max_error_fe[j] = np.max(np.abs(numerical_soln - analytic_soln))

	n2 = [2*i for i in range(4,50)]
	max_error_cheb = [10]*len(n2)
	for j in range(len(n2)):
		N = n2[j]
		D,x = cheb(N)
		M = 4*epsilon*D.dot(D) -2.*D
		M[0] = 0.
		M[-1] = 0.
		M[0,0] = 1.
		M[-1,-1] = 1.

		F = -np.ones(len(x))
		F[0] = 4    # beta
		F[-1] = 2   # alpha

		cheb_sol = solve(M,F)
		analytic_soln = AnalyticSolution((x+1.)/2., alpha, beta,epsilon)
		max_error_cheb[j] = np.max(np.abs(cheb_sol - analytic_soln))

	plt.loglog(n2,max_error_cheb,'-b',linewidth=2.,label='Chebychev Error')

	plt.loglog(n,max_error_fe,'-k',linewidth=2.)
	plt.xlabel('$n$',fontsize=16)
	plt.ylabel('$E(n)$',fontsize=16)
	# plt.savefig("FEM_error_2nd_order.pdf")
	plt.show()
	plt.clf()

def basis_functions_plot():
	fig = plt.figure()
	ax = fig.add_subplot(111)
	# ax = fig.add_axes([-.1,1.1, -.1,1.1])

	# Basis function phi_0
	x, y = arr([0,.2,.4,1.]), arr([1.,0.,0.,0.])
	ax.plot(x,y,'-k',linewidth=2.0)
	ax.text(0.,1.02,r"$\phi_0$",fontsize=18)

	# Basis function phi_1
	x, y = arr([0,.2,.4,1.]), arr([0.,1.,0.,0.])
	ax.plot(x,y,color='g',lw=2.0)
	ax.text(0.2,1.02,r"$\phi_1$",fontsize=18)

	# Basis function phi_2
	x, y = arr([0,.2,.4,.6,1.]), arr([0.,0.,1.,0.,0.])
	ax.plot(x,y,color='r',lw=2.0)
	ax.text(0.4,1.02,r"$\phi_2$",fontsize=18)

	# Basis function phi_3
	x, y = arr([0,.4,.6,.8,1.]), arr([0.,0.,1.,0.,0.])
	ax.plot(x,y,'-m',linewidth=2.0)
	ax.text(0.6,1.02,r"$\phi_3$",fontsize=18)

	# Basis function phi_4
	x, y = arr([0,.6,.8,1.]), arr([0.,0.,1.,0.])
	ax.plot(x,y,'-b',linewidth=2.0)
	ax.text(0.8,1.02,r"$\phi_4$",fontsize=18)

	# Basis function phi_5
	x, y = arr([0,.8,1.]), arr([0.,0.,1.])
	ax.plot(x,y,'-c',linewidth=2.0)
	ax.text(1.,1.02,r"$\phi_5$",fontsize=18)

	ax.set_ylim(-.1,1.1)
	ax.set_xlim(-.1,1.1)
	plt.xticks(np.linspace(0.,1.,6),['$x_0$','$x_1$','$x_2$','$x_3$','$x_4$','$x_5$'],fontsize=18)
	# print Axis.get_majorticklabels(plt.axis)
	# plt.savefig("basis_functions.pdf")
	plt.clf()

def one_basis_function_plot():
	fig = plt.figure()
	ax = fig.add_subplot(111)
	# ax = fig.add_axes([-.1,1.1, -.1,1.1])

	# Basis function phi_3
	x, y = arr([0,.4,.6,.8,1.]), arr([0.,0.,1.,0.,0.])
	ax.plot(x,y,'-m',linewidth=2.0)
	ax.text(0.6,1.02,r"$\phi_3$",fontsize=18)

	ax.set_ylim(-.1,1.1)
	ax.set_xlim(-.1,1.1)
	# plt.xticks(np.linspace(0.,1.,6),['$x_0$','$x_1$','$x_2$','$x_3$','$x_4$','$x_5$'],fontsize=18)
	plt.xticks(arr([.4,.6,.8]),['$x_2$','$x_3$','$x_4$'],fontsize=18)
	# print Axis.get_majorticklabels(plt.axis)
	# plt.savefig("one_basis_function.pdf")

	# plt.show()
	plt.clf()

if __name__ == "__main__":
	# basis_functions_plot()
	# one_basis_function_plot()
	tridiagonal_approach()


import matplotlib.pyplot as plt
import numpy as np
import sys
#coding: utf-8

def main(argv):
	qtdValores, parametros, x, y = int(sys.argv[1]), [], [], []

	for param in sys.argv[2:]: # Transformar parametros para float
		parametros.append(float(param))

	for copy in parametros[:qtdValores]: # Copiar parametros equivalentes a X passados no CMD e adicioná-los ao vetor X
		x.append(copy)
	print(f'Valores de X: {x}')

	for copy in parametros[len(x):(2*qtdValores)]: # Copiar parametros equivalentes a Y passados no CMD e adicioná-los ao vetor Y
		y.append(copy)
	print(f'Valores de Y: {y}')

	grafico(qtdValores, x, y)

def Px(numVars, x, y, pontoXnovo):
	phi=float(0)
	for i in range(numVars):
		phi += (y[i]*Lk(i, numVars, x, pontoXnovo))
	return phi

def Lk(count, numVars, x, pontoXnovo):
	l=float(1)
	for k in range(numVars):
		if (k!=count):
			l *= ((pontoXnovo-x[k])/(x[count]-x[k]))
	return l

def grafico(numVars, x, y):
	intervaloX = []
	intervaloX = np.linspace(x[0], x[len(x)-1])
	phi=[]
	for i in range(len(intervaloX)):
		phi.append(Px(numVars, x, y, intervaloX[i]))
	plt.plot(intervaloX, phi, label='P(x)')
	plt.plot(x, y, 'o', label='(x,y)')
	plt.xlabel("x")
	plt.ylabel("y")
	plt.legend()
	plt.show()

if __name__ == '__main__':
	main(sys.argv[1:])


import numpy as np

def koch_snowflake(nsteps, initial=None):
    '''Koch snowflake based on equil. triangle with base [0, 0] -- [1, 0]'''
    assert nsteps > 0

    if initial is None:
        fractal = [np.zeros(2),
                   np.array([np.cos(np.pi/3), np.sin(np.pi/3)]),
                   np.array([1, 0])]
    else:
        assert initial.shape == (3, 2)
        fractal = [intial[0], initial[1], initial[2]]
    # Check equilateral
    _, = set(np.round(np.linalg.norm(fractal[i]-fractal[j]), 8)
             for (i, j) in ((0, 1), (1, 2), (2, 0)))

    # Rot.
    R = np.array([[np.cos(np.pi/3), -np.sin(np.pi/3)],
                  [np.sin(np.pi/3), np.cos(np.pi/3)]])

    def koch_rule(A, B, R=R):
        # 3 segments
        X, Y = A + (B-A)/3., A + 2*(B-A)/3.
        # Grow the tent
        Z = X + R.dot(Y-X)

        return [X, Z, Y]

    fractal = grow_fractal(fractal, koch_rule, nsteps)

    return fractal

def grow_fractal(fractal, rule, nsteps):
    '''Grow fractal applying the rule to consecutive pairs of points'''
    # Base case
    if nsteps == 0:
        return fractal

    l = len(fractal)
    # Preconpute new points for current pairs. Note that we assume that
    # points form a loop
    new_pts = [rule(fractal[i], fractal[(i+1)%l]) for i in range(l)]
    # Insert new points in between segments of the old fractal
    fractal = sum(([p]+pts for p, pts in zip(fractal, new_pts)), [])

    L = len(fractal)
    perimeter = sum(np.linalg.norm(fractal[i]-fractal[(i+1)%L]) for i in range(L))
    print(f'Fractal npoints change {l} to {L}. Perimeter {perimeter}')
    # Next round
    return grow_fractal(fractal, rule, nsteps-1)


import numpy as np

# 100 Years Goodwin Model (Numerical Solution)
from models.goodwin import plot_num_goodwin, eqm_goodwin_eval, phase_plot_goodwin
from models.goodwin_keen import find_eqm_keen, phase_plot_keen, plot_num_keen
from models.goodwin_symbolic import j_goodwin, eqm_goodwin, eig_goodwin, eig_goodwin_val
from models.simulation import sim_study, sim_study_plot

if __name__ == '__main__':
    J_g = j_goodwin()
    Eqm_g = eqm_goodwin()
    Eigvals_g = eig_goodwin()
    Eig_eval_g = eig_goodwin_val()

    print(f'Symbolic Jacobian Matrix for the Goodwin Mode:\n{J_g}\n')
    print(f'Symbolic Eqm. Points for the Goodwin Model:\n{Eqm_g}\n')
    print(f'Symbolic Eigenvalues for the Goodwin Model:\n{Eigvals_g}\n')
    print(f'Eigenvalues for the Goodwin Model Evaluated at Eqm.:\n{Eig_eval_g}\n')

    t = np.arange(0,120,0.01) # 100 Year Model
    ICs = [0.8,0.9]
    plot_num_goodwin(t, ICs, 'goodwin_model')

    # Long-Term Behaviour
    t = np.arange(0,1002,0.01) # 800 Year Model
    ICs = [0.8,0.9]
    plot_num_goodwin(t, ICs, 'goodwin_longterm')

    # Goodwin Phase Portrait
    eqm_values = eqm_goodwin_eval()
    # eqm = [ eqm_values[0], eqm_values[1] ] # Trivial Eqm. Point
    eqm = [ eqm_values[2], eqm_values[3] ] # Stable, Econmically Realistic Eqm. Point
    # eqm = [ eqm_values[4], eqm_values[5] ] # Stable, Econmically Unrealistic Eqm. Point
    t = np.arange(0,1000,0.01) # 100 Year Model
    ICs = [0.8,0.9]
    phase_plot_goodwin(t, ICs, eqm)

    t = np.arange(0, 302, 0.01)  # 100 Year Model
    ICs = [0.75, 0.75, 0.1]
    plot_num_keen(t, ICs, 'keen_model')

    # # Goodwin-Keen Phase Portrait
    t = np.arange(0, 302, 0.01)
    t_eqm = np.arange(0, 1000, 0.01)
    eqm = find_eqm_keen(ICs, t_eqm)

    phase_plot_keen(t, ICs, eqm)
    print(eqm)

    sims = sim_study()

    converge = 0
    diverge = 0
    total = len(sims)
    for i in range(total):
        if sims[i][2]:
            converge += 1
        else:
            diverge += 1

    print('Total Convergence Events: ', converge)
    print('Total Divergence Events: ', diverge)
    print('Total Events: ', total)

    sim_study_plot(sims)


import numpy as np

def quatMul(q0, q1):
    w0, x0, y0, z0 = q0
    w1, x1, y1, z1 = q1
    return np.array([-x1 * x0 - y1 * y0 - z1 * z0 + w1 * w0,
                      x1 * w0 + y1 * z0 - z1 * y0 + w1 * x0,
                     -x1 * z0 + y1 * w0 + z1 * x0 + w1 * y0,
                      x1 * y0 - y1 * x0 + z1 * w0 + w1 * z0], dtype=np.float64)

def quatConj(q):
    w, x, y, z = q
    return np.array([w, -x, -y, -z], dtype=np.float64)
def quatNorm2(q):
    w, x, y, z = q
    return (w**2 + x**2 + y**2 + z**2)
def quatNorm(q):
    return np.sqrt(quatNorm2(q))
def quatNormalize(q):
    w, x, y, z = q
    n = quatNorm(q)
    return np.array([w/n, x/n, y/n, z/n], dtype=np.float64)
def quatInv(q):
    return quatConj(q) / quatNorm2(q)

def quatFromAngleAxis(angle, axis):
    u,v,r = axis
    cos = np.cos(angle/2.)
    sin = np.sin(angle/2.)
    return quatNormalize(np.array([cos, sin*u, sin*v, sin*r], dtype=np.float64))
def pureQuat(vector):
    return np.concatenate([[0], vector])
def getPure(q):
    w, x, y, z = q
    return np.array([x, y, z], dtype=np.float64)

def quatTrans(q,v):
    """
    Rotation a vector using a quaternion.
    """
    return getPure(quatMul(q, quatMul(pureQuat(v), quatConj(q))))

def quat_dot_np(p, q):
    return np.sum(p*q)
def quat_dist_np(p, q):
    # See: https://math.stackexchange.com/a/90098
    # d(p,q) = 1 - <p,q>^2
    return 1 - (quat_dot_np(p, q))**2

if __name__=="__main__":
    ## Test quaternions
    q = quatFromAngleAxis(np.pi/2, [0,1,0])
    print(q)
    print("[0.70710678, 0., 0.70710678, 0.]")
    # quatMul(q, quatInv(q))
    # quatFromAngleAxis(np.pi/2, [1,0,0])
    print(getPure(quatMul(q,quatMul(pureQuat([1,2,3]), quatConj(q)))))
    print("array([-3.,  2.,  1.])")