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.


In [1]:
x = 1.e308
y = x * 10.
print x,y


1e+308 inf

em Python inteiros podem ter tantos algarismos quanto a memória do computador permitir


In [6]:
factorial = 1
for k in range(1,201):
    factorial *= k
print factorial


788657867364790503552363213932185062295135977687173263294742533244359449963403342920304284011984623904177212138919638830257642790242637105061926624952829931113462857270763317237396988943922445621451664240254033291864131227428294853277524242407573903240321257405579568660226031904170324062351700858796178922222789623703897374720000000000000000000000000000000000000000000000000

In [7]:
factorial = 1.0
for k in range(1,201):
    factorial *= k
print factorial


inf

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 . . .


In [14]:
import numpy as np
import math as math

print np.pi 
print math.pi

np.pi


3.14159265359
3.14159265359
Out[14]:
3.141592653589793

Uma lição importante é que floats não devem ser usados na avaliação de igualdades

veja abaixo:


In [2]:
x = 1.1+2.2

print "%10.20e"% x

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


3.30000000000000026645e+00

In [3]:
# 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


o valor é: 3.3

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:


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


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


1000000000000000.00000000000000000000 
1000000000000001.25000000000000000000 
1.25000000000000000000
-0.0125000091125

In [36]:
# 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))


1.42108547152
1.41421356237

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


In [5]:
niter = 20

x = 0.2 

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


0 -> 0.20000000000000017764
1 -> 0.20000000000000195399
2 -> 0.20000000000002149392
3 -> 0.20000000000023643310
4 -> 0.20000000000260076405
5 -> 0.20000000002860840453
6 -> 0.20000000031469244988
7 -> 0.20000000346161694864
8 -> 0.20000003807778643505
9 -> 0.20000041885565078559
10 -> 0.20000460741215864147
11 -> 0.20005068153374505613
12 -> 0.20055749687119561742
13 -> 0.20613246558315179158
14 -> 0.26745712141466970735
15 -> 0.94202833556136678084
16 -> 8.36231169117503370103
17 -> 89.98542860292536715860
18 -> 987.83971463217903874465
19 -> 10864.23686095396988093853

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:


In [40]:
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))


99.9554313409
tempo de execução de 0.0013279914856 secundos

In [42]:
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))


100.000833332
tempo de execução de 0.599543809891 secundos

In [2]:
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))


100.000833332
tempo de execução de 59.4011518955 secundos

In [ ]: