Aplicações
Generalizando a representação de uma função por classes
Dada uma função com $ n+1 $ parâmetros e uma variável independente,
é útil representar f
por uma classe onde $ p_0,\ldots,p_n $ são atributos e __call__(x)
calcula $ f(x) $.
class MyFunc:
def __init__(self, p0, p1, p2, ..., pn):
self.p0 = p0
self.p1 = p1
...
self.pn = pn
def __call__(self, x):
return ...
Classe para derivar uma função
Dada certa função matemática em Python, digamos:
def f(x):
return x**3
Podemos fazer uma classe Derivada
e escrever:
dfdx = Derivada(f)
Então, dfdx
se comporta como uma função que calcula a derivada de f(x)
?
print dfdx(2) ## calcula 3*x**2 para x=2
Solução
Método
Vamos usar uma derivação numérica por de trás dos panos:
para um pequeno $ h $, digamos de $ h=10^{-5} $
Implementação
class Derivada:
def __init__(self, f, h=1E-5):
self.f = f
self.h = float(h)
def __call__(self, x):
f = self.f
h = self.h
return (f(x+h) - f(x))/h
Uso
>>> import math
>>> df = Derivada(math.sin)
>>> x = math.pi
>>> df(x)
-0.9999999999898844
>>> math.cos(x) ## exato
-1.0
>>> def g(t):
... return t**3
...
>>> dg = Derivada(g)
>>> t = 1
>>> dg(t) ## compare com 3 (exato)
3.000030000110953
Essa é uma implementação útil para o uso em outros métodos que necessitam da derivada, como por exemplo o método de Newton para a solução de equações não lineares.
Função de teste
- Como podemos testar a classe
Derivada
? - Método 1: calcular $ (f(x+h)-f(x))/h $ na mão para algum valor de $ f $ e $ h $;
- Método 2: utilize funções lineares que resultam em derivadas exatas pela nossa classe, independente de $ h $.
Função de teste baseada no método 2:
def test_Derivada():
## A formula fornece valores exatos para funções lineares, independente de h
f = lambda x: a*x + b
a = 3.5
b = 8
dfdx = Derivada(f, h=0.5)
dif = abs(dfdx(4.5) - a)
assert dif < 1E-14, 'Problema na classe Derivada, dif=%s' % dif
Explicação da função teste
Uso funções lambda
:
f = lambda x: a*x + b
É equivalente a:
def f(x):
return a*x + b
Funções lambda
são convenientes para produzir códigos rápidos e curtos.
Uso de fechamentos:
f = lambda x: a*x + b
a = 3.5
b = 8
dfdx = Derivada(f, h=0.5)
dfdx(4.5)
Parece claro... mas:
- Como
Derivada.__call__
sabe a e b quando chama nossa funçãof(x)
? - Funções locais dentro de funções tem acesso a todas as variáveis locais na função que são definidas!
f
pode acessar a e b emtest_Derivada
mesmo quando chamada de__call__
na classeDerivada
;f
é conhecida como fechamento (closure) na ciência da computação.
A solução exata mesmo
Podemos também executar a derivação simbólica utilizando o SymPy:
>>> import sympy as sp
>>> def g(t):
... return t**3
...
>>> t = sp.Symbol('t')
>>> dgdt = sp.diff(g(t), t) ## calcula g'(t)
>>> dgdt
3*t**2
>>> ## Transformando a expressão sympy dgdt em função python dg(t)
>>> dg = lambdify([t], dgdt)
>>> dg(1)
3
Classe baseada no SymPy
import sympy as sp
class Derivada_sympy:
def __init__(self, f):
## f: Python f(x)
x = sp.Symbol('x')
sympy_f = f(x)
sympy_dfdx = sp.diff(sympy_f, x)
self.__call__ = sp.lambdify([x], sympy_dfdx)
Uso:
>>> def g(t):
... return t**3
>>> def h(y):
... return sp.sin(y)
>>> dg = Derivada_sympy(g)
>>> dh = Derivada_sympy(h)
>>> dg(1) ## 3*1**2 = 3
3
>>> import math
>>> dh(pi) ## cos(math.pi) = -1
-1.0
Classe para Integrar uma função
Dada a função $ f(x) $, nós queremos calcular
A técnica
Podemos utilizar, por exemplo, a regra dos trapézios:
O objetivo é obter um código que resolva da seguinte forma:
def f(x):
return math.exp(-x**2)*math.sin(10*x)
a = 0
n = 200
F = Integral(f, a, n)
x = 1.2
print F(x)
Implementação
def trapezios(f, a, x, n):
h = (x-a)/float(n)
I = 0.5*f(a)
for i in range(1, n):
I += f(a + i*h)
I += 0.5*f(x)
I *= h
return I
A classe Integral
possui f
, a
e n
como atributos e tem um método especial de chamada para o cálculo da integral:
class Integral:
def __init__(self, f, a, n=100):
self.f, self.a, self.n = f, a, n
def __call__(self, x):
return trapezios(self.f, self.a, x, self.n)
Função de teste
- Como podemos testar a classe
integral
? - Método 1: calcular na mão alguma $f$ para um $n$ pequeno;
- Método 2: utilizar aquelas funções lineares que são integradas exatamente pela nossa integração numérica, independente de $n$.
Função de teste baseada no método 2:
def test_Integral():
f = lambda x: 2*x + 5
F = lambda x: x**2 + 5*x - (a**2 + 5*a)
a = 2
dfdx = Integralf, a, n=4)
x = 6
dif = abs(I(x) - (F(x) - F(a)))
assert dif < 1E-15, 'Problema na classe Integral, dif=%s' % dif
Classe para polinômios
Um polinômio pode ser especificado por uma lista de seus coeficientes, por exemplo $ 1 - x^2 + 2x^3 $ é
e os coeficientes podem ser armazenados como [1, 0, -1, 2]
.
Código desejado da aplicação:
>>> p1 = Polinomial([1, -1])
>>> print p1
1 - x
>>> p2 = Polinomial([0, 1, 0, 0, -6, -1])
>>> p3 = p1 + p2
>>> print p3.coef
[1, 0, 0, 0, -6, -1]
>>> print p3
1 - 6*x^4 - x^5
>>> p2.derivada()
>>> print p2
1 - 24*x^3 - 5*x^4
Como podemos fazer a classe Polinomial
?
Código básico
class Polinomial:
def __init__(self, coeficientes):
self.coef = coeficientes
def __call__(self, x):
s = 0
for i in range(len(self.coef)):
s += self.coef[i]*x**i
return s
Adição
class Polinomial:
...
def __add__(self, outro):
## return self + outro
## inicia com a lista mais longa e adiciona a outra:
if len(self.coef) > len(outro.coef):
coefsoma = self.coeff[:] ## copia!
for i in range(len(outro.coef)):
coefsoma[i] += outro.coef[i]
else:
coefsoma = outro.coef[:] ## copia!
for i in range(len(self.coef)):
coefsoma[i] += self.coef[i]
return Polinomial(coefsoma)
Multiplicação
Multiplicação genérica de dois polinômios quaisquer:
O coeficiente que corresponde a potência $ i+j $ é $ c_i\cdot d_j $. A lista r
dos coeficientes do resultado é: r[i+j] = c[i]*d[j]
(i
e j
variando de 0 a $ M $ e $ N $, respectivamente.)
Implementação
class Polinomial:
...
def __mul__(self, outro):
M = len(self.coef) - 1
N = len(outro.coef) - 1
coef = [0]*(M+N+1) ## or zeros(M+N+1)
for i in range(0, M+1):
for j in range(0, N+1):
coef[i+j] += self.coef[i]*outro.coef[j]
return Polinomial(coef)
Derivada
A regra da derivada de um polinômio genérico da matemática é?
Se c
é a lista de coeficientes, a derivada tem uma lista de coeficientes, dc
, onde dc[i-1] = i*c[i]
para i
variando de 1 até o máximo índice em c
. Note que dc
tem um elemento a menos que c
.
Implementação:
class Polinomial:
...
def derivacao(self): ## muda o self
for i in range(1, len(self.coef)):
self.coef[i-1] = i*self.coef[i]
del self.coef[-1]
def derivada(self): ## retorna um novo polinômio
dpdx = Polinomial(self.coef[:]) ## copia
dpdx.derivacao()
return dpdx
Saída formatada
class Polinomial:
...
def __str__(self):
s = ''
for i in range(0, len(self.coef)):
if self.coef[i] != 0:
s += ' + %g*x^%d' % (self.coef[i], i)
## corrigindo os casos especiais:
s = s.replace('+ -', '- ')
s = s.replace(' 1*', ' ')
s = s.replace('x^0', '1')
s = s.replace('x^1 ', 'x ')
s = s.replace('x^1', 'x')
if s[0:3] == ' + ': ## removendo o + inicial
s = s[3:]
if s[0:3] == ' - ': ## corrigindo espaços para - inicial
s = '-' + s[3:]
return s
Uso
Considere
e sua soma
>>> p1 = Polinomial([1, -1])
>>> print p1
1 - x
>>> p2 = Polinomial([0, 1, 0, 0, -6, -1])
>>> p3 = p1 + p2
>>> print p3.coef
[1, 0, 0, 0, -6, -1]
>>> p2.derivacao()
>>> print p2
1 - 24*x^3 - 5*x^4
Classe para vetores no plano
Operações matemáticas para vetores no plano:
Código de aplicação desejado:
>>> u = Vet2D(0,1)
>>> v = Vet2D(1,0)
>>> print u + v
(1, 1)
>>> a = u + v
>>> w = Vet2D(1,1)
>>> a == w
True
>>> print u - v
(-1, 1)
>>> print u*v
0
Implementação
class Vey2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, outro):
return Vet2D(self.x+outro.x, self.y+outro.y)
def __sub__(self, outro):
return Vet2D(self.x-outro.x, self.y-outro.y)
def __mul__(self, outro):
return self.x*outro.x + self.y*outro.y
def __abs__(self):
return math.sqrt(self.x**2 + self.y**2)
def __eq__(self, outro):
return self.x == outro.x and self.y == outro.y
def __str__(self):
return '(%g, %g)' % (self.x, self.y)
def __ne__(self, outro):
return not self.__eq__(outro) ## reusando __eq__
O que tem em uma classe?
class A:
"""Uma classe para demonstração."""
def __init__(self, valor):
self.v = valor
Qualquer instância mantém seus atributos em um dicionário criado automaticamente pelo Pythonself.__dict__ dictionary
.
>>> a = A([1,2])
>>> print a.__dict__ ## todos os atributos
{'v': [1, 2]}
>>> dir(a) ## o que tem no objeto a?
'__doc__', '__init__', '__module__', 'dump', 'v']
>>> a.__doc__ ## documentação de A
'Uma classe para demonstração.'
Podemos adicionais quantos novos atributos quisermos!
>>> a.minha_var = 10 ## add new attribute (!)
>>> a.__dict__
{'minha_var': 10, 'v': [1, 2]}
>>> dir(a)
['__doc__', '__init__', '__module__', 'dump', 'minha_var', 'v']
>>> b = A(-1)
>>> b.__dict__ ## b não tem atributo minha_var
{'v': -1}
>>> dir(b)
['__doc__', '__init__', '__module__', 'dump', 'v']
Resumo de classes
Definindo a classe
Exemplo sobre a definição de uma classe com atributos e métodos
import numpy as np
import matplotlib.pyplot as plt
class Gravidade:
"""Força da Gravidade entre dois objetos."""
def __init__(self, m, M):
self.m = m
self.M = M
self.G = 6.67428E-11 ## Constante de gravitação
def forca(self, r):
G, m, M = self.G, self.m, self.M
return G*m*M/r**2
def visualizacao(self, r_start, r_stop, n=100):
r = np.linspace(r_start, r_stop, n)
g = self.forca(r)
plt.title('m=%g, M=%g' % (self.m, self.M))
plt.plot(r, g)
plt.show()
Exemplo de uso dessa classe
mass_lua = 7.35E+22
mass_terra = 5.97E+24
## criando a instância (objeto) da classe Gravidade:
gravidade = Gravidade(mass_lua, mass_terra)
r = 3.85E+8 ## distância da lua à terra em metros
Fg = gravity.force(r) ## chamando o método força da classe
gravidade.visualizacao(0, 100) ## visualizando
Resumos dos métodos especiais
c = a + b
implicac = a.__add__(b)
- Existem métodos especiais para
a+b
,a-b
,a*b
,a/b
,a**b
,-a
,if a:
,len(a)
,str(a)
(saída formatada),repr(a)
(recriaa
comeval
), etc. - Com os métodos especiais podemos criar novos objetos matemáticos como vetores, polinoniais, números complexos e escrever "codigos matemáticos" (aritmética)
- A chamada dos métodos especiais é particularmente útil:
v = c(5)
significav = c.__call__(5)
- Funções com parâmetros devem ser representadas por uma classe com os parâmetros como atributos e com uma chamada a um método especial para a avaliação da função.
Tarefa
Quantificação da incerteza:
Considere a medição da gravidade $g$ através da queda de uma bola de $ y=y_0 $ para $ y=0 $ no tempo $ T $:
E se $ y_0 $ e $ T $ são incertos? Digamos $ y_0\in [0.99,1.01] $ m e $ T\in [0.43, 0.47] $ s. Qual é o grau de incerteza em $ g $?
Regras para calcular com intervalos, $ p=[a,b] $ e $ q=[c,d] $:
- $ p+q = [a + c, b + d] $
- $ p-q = [a - d, b - c] $
- $ pq = [\min(ac, ad, bc, bd), \max(ac, ad, bc, bd)] $
- $ p/q = [\min(a/c, a/d, b/c, b/d), \max(a/c, a/d, b/c, b/d)] $ ($ [c,d] $ não pode conter zero)
Objetivo: fazer uma classe para a aritmética com intervalos.