x = 1.e308
y = x * 10.
print (x,y)

"""
Explanation: Acurácia e velocidade
Agora já temos os componentes básicos da linguagem Python para poder atacar os problemas de física
no entanto, precisamos explorar ainda as limitações do computador visto que não pode guardar números com precisão infinita
existe um limite superior e inferior ao que se pode guardar e também de velocidade em que operações podem ser efetuadas
variáveis e intervalos
o maior numero float que pode ser armazenado é em módulo $10^{308}$
o mesmo vale para numeros imaginários
usualmente escrevemos os numeros de ponto flutuante grandes com notação científica na forma : 3.14e10 o que equivale a $3.14 \times 10^{10}$
caso uma operação gere um numero maior que o máximo aceito pelo computador o valor inf será alocado e em geral o programa não é interrompido.
End of explanation
"""


factorial = 1
for k in range(1,201):
    factorial *= k
print (factorial)

factorial = 1.0
for k in range(1,201):
    factorial *= k
print (factorial)

"""
Explanation: em Python inteiros podem ter tantos algarismos quanto a memória do computador permitir
End of explanation
"""


import numpy as np
import math as math

print (np.pi) 
print (math.pi)

np.pi

"""
Explanation: Erro numérico
números de ponto flutuante só podem ser representados com uma certa precisão
Em Python o padrão de representação é de 16 algarismos significativos
Veja o caso de $\pi$:
valor verdadeiro: 3.1415926535897932384626 . . .
valor no Python:  3.141592653589793
diferença:        0.0000000000000002384626 . . .
End of explanation
"""


x = 1.1+2.2

print ("%10.20e"%x)

if x == 3.3:
    print ("o valor é:", x)
    
    

# a melhor maneira de testar igualdade de floats, caso seja necessária seria:

x = 1.1+2.2

epsilon = 1e-12
if abs(x-3.3)<epsilon:
    print ("o valor é:", x)


"""
Explanation: Uma lição importante é que floats não devem ser usados na avaliação de igualdades
veja abaixo:
End of explanation
"""


# o caso abaixo mostra uma situação onde o erro relativo é bem grande
x = 1000000000000000
y = 1000000000000001.2345678901234


print ("%10.20f"%x, '    ', "%10.20f"%y,'    ',"%10.20f"%(y-x))
print ((1.2345678901234 - (y-x) ) / 1.2345678901234)


# outro exemplo envolvendo subtração de numeros
from math import sqrt
x = 1.0
y = 1.0 + (1e-14)*sqrt(2)
print((1e14)*(y-x))
print(sqrt(2))


"""
Explanation: As mesmas regras de propagação de erros são válidas no tratamento de erros no computador
pode-se mostrar que o erro de uma soma de N números é dada por:
$\sigma = C\sqrt{N}\sqrt{\bar{x}^2}$
Veja que o erro cresce com o numero de termos somados (para mais detalhes veja: http://www.umich.edu/~mejn/cp/chapters/errors.pdf)
De modo geral esse comportamento é razoável e o erro relativo tende a diminuir com o aumento de termos somados.
Problemas em geral acontecem quando se somam números de tamanhos muito diferentes. Nesses casos os números muito menores podem se perder no erro.
Os maiores problemas acontecem quando fazemos subtração de numeros. Veja o exemplo abaixo:
End of explanation
"""


niter = 20

x = 0.2 

for i in range(niter):
    x = (11.0*x - 2.0)
    print (i, '->', "%10.20f"% x)

"""
Explanation: Veja que o erro cometido está na segunda casa decimal!
problemas de erros ao subtrair numeros muito próximos podem ocorrer em cálculos de física e devemos ficar atentos a eles
End of explanation
"""


from math import exp
import time

# para obter o tempo de execução
start_time = time.time()

# variáveis definidas no início para clareza

terms = 1000 # número de termos a serem usados na soma

beta = 1/100.
S = 0.0
Z = 0.0

# note o uso de um looping para calculo das duas somas e o exponencial calculado uma só vez
for n in range(terms):
    E = n + 0.5
    weight = exp(-beta*E)
    S += weight*E
    Z += weight
print(S/Z)

print("tempo de execução de %s secundos" % (time.time() - start_time))

from math import exp
import time

# para obter o tempo de execução
start_time = time.time()

# variáveis definidas no início para clareza

terms = 1000*1000 # número de termos a serem usados na soma

beta = 1/100.
S = 0.0
Z = 0.0

# note o uso de um looping para calculo das duas somas e o exponencial calculado uma só vez
for n in range(terms):
    E = n + 0.5
    weight = exp(-beta*E)
    S += weight*E
    Z += weight
print(S/Z)

print("tempo de execução de %s secundos" % (time.time() - start_time))

from math import exp
import time

# para obter o tempo de execução
start_time = time.time()

# variáveis definidas no início para clareza

terms = 100*1000*1000 # número de termos a serem usados na soma

beta = 1/100.
S = 0.0
Z = 0.0

# note o uso de um looping para calculo das duas somas e o exponencial calculado uma só vez
for n in range(terms):
    E = n + 0.5
    weight = exp(-beta*E)
    S += weight*E
    Z += weight
print(S/Z)

print("tempo de execução de %s secundos" % (time.time() - start_time))

"""
Explanation: velocidade
é interessante ter uma noção sobre os limites de velocidade de computação 
esses aspectos podem ser importantes na tomada de decisões de programação
Exemplo
O oscilador harmonico simples quantico tem niveis de energia dados por:
$E_n = \hbar \omega (n+\frac{1}{2})$
A energia média para o oscilador a uma temperatura T é:
$\left\langle E\right\rangle =\dfrac {1} {Z}\displaystyle\sum {n=0}^{\infty }E{n}e^{-\beta E_{n}}$
onde $\beta =1 / k_{B}T$ com $K_B$ a constante de Boltzmann e $Z=\displaystyle\sum {n=0}^{\infty }e^{-\beta E{n}}$
Suponha que se queira calcular o valor de $\left\langle E\right\rangle$ quando $k_{B}T = 100$ 
Usando unidades de $\hbar =\omega =1$ temos o programa abaixo:
End of explanation
"""


