Ir para o conteúdo

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ção f(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 em test_Derivada mesmo quando chamada de __call__ na classe Derivada;
  • 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

Integrais

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 implica c = 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) (recria a com eval), 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) significa v = 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.

Voltar ao topo