Map0151

De Stoa
Ir para: navegação, pesquisa

Cobra4.png

| Comunidade no Stoa | Disciplina no Moodle|

Conteúdo

 [ocultar

Números no Computador

Muitos dos algoritmos de cálculo numérico, por uma questão prática, deverão ser executados numa máquina real. Nestas máquinas, no entanto, a capacidade de memória para representação dos números é finita. Vários números reais (infinitos, de fato) terão a mesma representação no computador (ou calculadora), daí originando-se os erros de arredondamentos. Vamos ver qual é a técnica usada atualmente para diminuir os erros de arredondamentos.

Representação de números inteiros numa base

Seja β>1 um número natural. Então podemos representar qualquer número inteiro k como a soma k=sgn(k)a0β0++asβs

onde cada algarismo aj representa um número natural entre 0 e β1. Só as é diferente de zero. Esta representação é única. A representação de k na base β é k=±[asa0]β
Deixando o sinal de k de fora, para simplificar. Podemos calcular os algarismos do número daddo na base β aplicando repetidas vezes o algoritmo da divisão. Escrevendo k=a0+β(a1+β(a2++βas)))
vemos que a0 é o resto da divisão k//β, a1 é o resto da divisão (ka0)//β e assim por diante.

Exemplo: 39=[100111]2

Representação de números fracionários e decimais numa base

Seja novamente β>1 um número natural.

Se x(0,1) então x=b1β++bkβk+

Diremos que x=[0.b1b2]β é a representação fracionária na base β de x. Quando não houver dúvidas de que base se trata, omite-se a base da notação!

Exemplo: 0.9=[0.1110011001100...]2

Uma função para colocar um número decimal na forma binária

In[1]: # -*- coding: utf-8 -*-

"""
Spyder Editor

"""


def binario(a):
    # da a representacao binaria do numero a
    ParteInteira = int(a)
    ParteDecimal = a-int(a)
    # representacao binaria da parte inteira
    # a lista seguinte guarda os dígitos da parte inteira
    ListaDigitos=[]
    while (ParteInteira > 0):
        ListaDigitos.append(ParteInteira%2)
        ParteInteira=ParteInteira//2
    # lista dos digitos depois da virgula
    ListaResto=[]
    k=1
    while ((ParteDecimal!=0)&(k<50)):
        ListaResto.append(int(2*ParteDecimal))
        ParteDecimal=2*ParteDecimal - int(2*ParteDecimal)
        k=k+1
    # produz a string de representacao:
       
    i=len(ListaDigitos)-1
    p1=""
    while (i>=0):
        p1=p1+str(ListaDigitos[i])
        i=i-1
    # Depois disso p1 tem a parte inteira
    l=0
    p2=""
    while (l<len(ListaResto)):
        p2=p2+str(ListaResto[l])
        l=l+1
   
    return p1+"."+p2
   
print (binario(21.75))

Representação em ponto flutuante

Consideramos uma base fixa β>1. Um número real αR positivo pode ser escrito nesta base como: α=[aka0.b1b2]β

Isto significa que: α=akβk++a0+b1β+b2β2+
Na equação acima, colocando βk+1 em evidência temos: α=(akβ++a0βk+1+b1βk+2+b2βk+3+)×βk+1
ou ainda, lembrando da notação de um número numa base dada: α=[0.aka0b1b2]β×βk+1
Esta última fórmula é importante. O número real α fica caracterizado por três dados:

  • O número m=[0.aka0b1b2]β[0.1,1) chamado de mantissa.
  • O número e=k+1 chamado de expoente
  • O sinal do número σ

Esta representação do número α como σm×βe chamaremos de representação normal em ponto flutuante na base β. Em geral a base fica clara pelo contexto!

Números de máquina

Continuamos com a base fixa (β), mas na representação normal em ponto flutuante vamos admitir apenas números com a mantissa limitada a D dígitos e o expoente limitado, em módulo, por um número inteiro M. O conjunto M={[0.d1dD]β×βe:|e|M}

é um conjunto de números de máquina definido pelos números (β,D,M). Este é um conjunto finito com (β1)(β(D1))(2M+1) elementos. Vamos agora definir a função arredondamento, que é uma função sobrejetora rd:RM. Seja α um número real; se o expoente de α for maior que M não representamos o número. Senão tomamos sua mantissa e se o dígito bd+1β/2 escolhemos o menor número de máquina maior que α para representá-lo. No caso de bd+1<β/2 o representante será o maior número de máquina menor que α.

Se denotarmos por α_ o truncamento do número α, isto é, o maior número de máquina menor que α. E por ¯α o menor número de máquina maior que α. Temos que: ¯αα_=βeD

Com isto temos uma avaliação do erro absoluto e erro relativo do arredondamento: |αrd(α)|βeD2
|αrd(α)||α|β1D2

Aritmética de números em pontos flutuantes

A extensão das operações aritméticas de soma e multiplicação de números reais faz-se pelo arredondamento: αβ=rd(rd(α)+rd(β))

αβ=rd(rd(α)×rd(β))
Estas operações deixam de ser associativas.

Aproximações usando polinômios de Taylor

Para fazer as avaliações de funções de variável real, utilizaremos apenas as operações aritméticas. Basicamente, isto significa que só usaremos polinômios. Em primeiro lugar veremos o método de avaliação de Horner.

Método de Horner

É a forma de se avaliar o polinômio num ponto dado executando o menor número de operações aritméticas. O polinômio da forma p(x)=anxn+an1xn1++a0

no ponto x0 é avaliado na ordem p(x)=[[[[anx0+an1]x0+an2]x0+]x0+a0]
Isto é, calculamos a sequência

  • A0=an
  • Ak+1=Akx0+ank até o An que é a avaliação.

Teorema de Taylor

Teorema: Seja f:(a,b)R uma função de classe Cr, talque exista a derivada f(r+1)(c),c(a,b). Então para todo x do intervalo, e c fixado existe ξ|c,x| tal que: f(x)=f(c)+f(c)(xc)++f(r)(c)r!(xc)r+f(r+1)(ξ)(r+1)!(xc)r+1

Zeros de funções

Dada uma função f:(a,b)R, queremos encontrar um r(a,b) tal que f(r)=0. Um tal número será chamado de zero da função f. Para assegurar a existência de zeros no intervalo, precisamos de alguma regularidade da função. Se f é uma função contínua, uma condição suficiente para a existência do zero é que f(a)f(b)<0, e uma técnica para chegar ao zero é ir contraindo este intervalo, mantento a relação f(a)f(b)<0, até chegarmos suficientemente próximo. No método da bissecção vamos dividindo o intervalo ao meio. Se (an,bn) é o intervalo com f(an)f(bn)<0, definimos xn+1=(an+bn)/2 e o novo intervalo como:

  • (an+1,bn+1)=(xn+1,bn) se f(an)f(xn+1)<0
  • (an+1,bn+1)=(an,xn+1) caso contrário

O tamanho do intervalo no n-ésimo passo é |ba|/2n. A seguir uma função em python que implementa a bissecção.

Algoritmo da bissecção

In[2]: #define a funcao bisseccao


def bissec(f, a, b, eps=0.0001):
    ''' Uma funcao que executa a biseccao do intervalo (a,b), não testa se tem zero '''
    x0=a
    x1=b
    k=0
    print("|  k  | x0       | x1       | xmid     |f(x0)*f(xmid)|")
    while (x1-x0)/2 > eps :
        xmid = (x1+x0)/2
        # incluir print para debug e o contador
        print("| {0:<2}  | {1:1.6f} | {2:<1.6f} | {3:<1.6f} | {4:<+2.8f} |".format(k,x0,x1,xmid,f(xmid)*f(x0)))
        if f(xmid)*f(x0)>0:
            x0,x1=xmid,x1
        else : x0,x1 = x0,xmid
        k+=1
    return (x0+x1)/2
   
#Rodando um teste
f = lambda x: x**2-2
a,b = 0,2
bissec(f,a,b)

Out[2]:
 
|  k  | x0       | x1       | xmid     |f(x0)*f(xmid)|
| 0   | 0.000000 | 2.000000 | 1.000000 | +2.00000000 |
| 1   | 1.000000 | 2.000000 | 1.500000 | -0.25000000 |
| 2   | 1.000000 | 1.500000 | 1.250000 | +0.43750000 |
| 3   | 1.250000 | 1.500000 | 1.375000 | +0.04785156 |
| 4   | 1.375000 | 1.500000 | 1.437500 | -0.00726318 |
| 5   | 1.375000 | 1.437500 | 1.406250 | +0.00245667 |
| 6   | 1.406250 | 1.437500 | 1.421875 | -0.00048804 |
| 7   | 1.406250 | 1.421875 | 1.414062 | +0.00000960 |
| 8   | 1.414062 | 1.421875 | 1.417969 | -0.00000454 |
| 9   | 1.414062 | 1.417969 | 1.416016 | -0.00000218 |
| 10  | 1.414062 | 1.416016 | 1.415039 | -0.00000100 |
| 11  | 1.414062 | 1.415039 | 1.414551 | -0.00000041 |
| 12  | 1.414062 | 1.414551 | 1.414307 | -0.00000011 |
| 13  | 1.414062 | 1.414307 | 1.414185 | +0.00000004 |

Algoritmo de Newton

O algoritmo de Newton-Raphson usa propriedades de diferenciabilidade da função f para criar uma sequência recorrente que converge ao zero da função que atrairá um ponto x0, que é uma estimativa inicial. A fórmula para gerar a sequêmcia é: xk+1=xkf(xk)f(xk)

Abaixo o algoritmo em python:

In[3]: # ilustra o processo de newton-raphson

def newtonRaphson(f,flinha,x0,eps=0.0001):
    '''executa o procedimento de newton'''
    a=x0
    b= a - (f(a)/flinha(a))
    k=1 #quero contar as iterações
    print("| k | x_k       | |x_k-xk-1|   |")
    print("|{0:<2} | {1:<2.7f} | {2:<2.7f}".format(k,b,abs(b-a)))
 
    while (abs(b-a)>eps):
        a=b
        b= a - (f(a)/flinha(a))
        k+=1
        print("|{0:<2} | {1:<2.7f} | {2:<2.7f}".format(k,b,abs(b-a)))
       
    return b
# função de teste
f = lambda x: x**2-2
flinha = lambda x: 2*x
x0=1.5
newtonRaphson(f,flinha,x0)

Out[3]:
| k | x_k       | |x_k-xk-1|   |
|1  | 1.4166667 | 0.0833333
|2  | 1.4142157 | 0.0024510
|3  | 1.4142136 | 0.0000021

Algoritmo da secante

O método da secante pode ser visto como uma adaptação do método de Newton, onde a derivada da função f é computada pela fórmula de aproximação f(xk)f(xk)f(xk1)(xkxk1)

definindo a sequência xk+1=xkf(xk)(xkxk1)(f(xk)f(xk1))

O código em Python pode ser o seguinte

In[4]: # Uma nova definição da secante usando o pseudo-código Kincaid

def secante(f,x0,x1,eps=0.0001):
   
    '''Método da secante'''
    a=x0
    b=x1
    u = f(a)
    v = f(b)
    if abs(v) > abs(u):
        a,b=b,a
        u,v=v,u
    c = b - v*(b-a)/(v-u)
    k=1
    print("| k | x_k       | f(xk)   |")
    print("|{0:<2} | {1:<2.7f} | {2:<2.7f}".format(k,a,u))
    print("|{0:<2} | {1:<2.7f} | {2:<2.7f}".format(k,b,v))
    while abs(c-b) > eps:
        a=b
        b=c
        u=f(a)
        v=f(b)
        c = b - v*(b-a)/(v-u)
        k+=1
        print("|{0:<2} | {1:<2.7f} | {2:<2.7f}".format(k,b,v))
    return c

#Teste
f= lambda x: x**2 - 2
secante(f,1,2)

Out[3]:
| k | x_k       | f(xk)   |
|1  | 2.0000000 | 2.0000000
|1  | 1.0000000 | -1.0000000
|2  | 1.3333333 | -0.2222222
|3  | 1.4285714 | 0.0408163
|4  | 1.4137931 | -0.0011891
|5  | 1.4142114 | -0.0000060

Resolução de sistemas lineares

Do ponto de vista de algoritmos numéricos existem duas formas para se resolver um sistema de equações lineares da forma: a11x1++a1nxn=b1an1x1++annxn=bn

Os Métodos Diretos têm, em geral, duas fases. Numa primeira fase, um sistema genérico é transformado, numa sequência finita de passos, num sistema de uma classe de sistemas, do qual se conhece um algoritmo eficiente para a solução. Na segunda fase, resolvemos o sistema gerado. O método da eliminação de Gauss (MEG) é um método direto que descreveremos a seguir.

Os Métodos Iterativos são obtidos por uma sequência de estimativas da solução. Esta sequência deve convergir para a solução, e muito raramente se obtem a solução exata. Mas tem-se um bom controle de erros. Descreveremos o método de Gauss-Seidel que ilustra esta técnica.

Método da Eliminação de Gauss

Em primeiro lugar vamos descrever os algoritmos para a resolução de sistemas triangulares. Uma matriz quadrada U=(uij) é triangular superior quando tivermos a condição uij=0 para i>j. Neste caso podemos resolver o sistema linear Ux=b,usando o algoritmo de baixo pra cima em n passos:

  • Passo 1 : Encontro xn=bn/unn
  • Passo k : Encontro xnk+1=1unk+1nk+1(bnk+1nj=nk+2unk+1jxj)

A matriz L=(lij) é triangular inferior quando lij=0 e lii=1 para i<j. Para resolver os sistema Lx=b usando um algoritmo parecido com o anterior.

  • Passo 1 : Encontro x1=b1/l11
  • Passo k : Encontro xk=1ukk(bkk1j=1ukjxj)

No método da eliminação de Gauss (MEG), vamos transformar um sistema linear numa sistema equivalente triangular superior, isto é, quando a matriz dos coeficientes A é triangular superior. Faremos isso usando as operações elementares, que descrevemos a seguir:

  • O(i,j) é a troca da linha i com a linha j
  • O(i,α) é a multiplicação da linha i por um número, não nulo, α
  • O(i,α,j) é a substituição da linha i pela soma desta com α multiplicado pela iésima linha.

O algoritmo é a aplicação sucessivas dos seguintes passos com k variando de 1 até n1

 Passo k: Caso akk0, faça de i=k+1 até i=nmik=aik/akk
e aplique ao sistema a operação elementar O(i,mik,k).

Ao fim do passo n1 o sistema estará triangularizado. Para evitar a possibilidade de não se triangularizar o sistema, mesmo ele sendo possível e determinado, executamos a pivotação que é o seguinte procedimento: antes da execução do passo k do MEG. Buscamos, na késima coluna, a partir da késima linha o índice i tal que |aik| seja o maior possível, e executamos O(i,k). Com isto garantimos a triangularização de qualquer sistema que tenha solução única.

A seguir o código em python para a eliminação de Gauss

In[5]: # gauss elimination as seen in the classroom

import numpy as np


# A is type array from numpy
# remember: A[0.0] is the first element
# Elementary row operations:

def  troca_linha(A,i,j):
    '''Troca as linhas i e j da matriz A'''
    buffer = A[i].copy()
    A[i] = A[j]
    A[j] = buffer
    return A

def mult_linha(A,i,alfa):
    '''Multiplica por alfa a linha i'''
    A[i] = alfa*A[i]
    return A

def subs_linha(A,i,k,alfa):
    '''soma a linha i com um multiplo da linha k'''
    A[i] = A[i] + alfa*A[k]
    return A

# step k of gauss elimination:
def gauss_step(A,k):
    '''O k-esimo passo na eliminacao de gauss'''
    L=range(len(A))
    for j in L[k+1:]:
        m=-A[j,k]/A[k,k]
        A=subs_linha(A,j,k,m)
        A[j,k]=-m # Aproveitamos a matriz A pra guardar o multiplicador
    return A

# triangle form, without care and pivoting
def triangle_form(A):
    '''Retorna a matriz escalonada,
    com os multiplicadores na parte triangular inferior'''

    L=len(A)
    for k in range(L-1):
        p=pivo(A,k)
        troca_linha(A,k,p)
        gauss_step(A,k)
     
    return A
   
def pivo(A,n):
    ''' retorna o indice de pivotacao da matriz A na coluna n,
    a partir da linha n'''

    l=len(A) # numero de linhas de A
    c=len(A[0]) # numero de colunas
    if (l<=n) or (c<=n):
        raise ValueError("n deve ser menor que dimensao de A")
    p=n # inicio o pivo como n, e vou aumentando
    for k in range(n,l):
        if abs(A[k,n]) > abs(A[p,n]):
            p=k
    return p

# roda um teste

A=np.array([[1,2,3],
         [2,-1,9],
         [3.,0., 2.]])
B=triangle_form(A)

Versão sem pivotação (não sei se precisa!)

In[6]: # gauss elimination as seen in the classroom

import numpy as np


# A is type array from numpy
# remember: A[0.0] is the first element
# Elementary row operations:

def  troca_linha(A,i,j):
    buffer = A[i]
    A[i] = A[j]
    A[j] = buffer
    return A

def mult_linha(A,i,alfa):
    A[i] = alfa*A[i]
    return A

def subs_linha(A,i,k,alfa):
    A[i] = A[i] + alfa*A[k]
    return A

# step k of gauss elimination:
def gauss_step(A,k):
    L=range(len(A))
    for j in L[k+1:]:
        m=-A[j,k]/A[k,k]
        A=subs_linha(A,j,k,m)
    return A

# triangle form, without care and pivoting
def triangle_form(A):
    L=len(A)
    for k in range(L):
        if A[k,k] != 0:
            A=gauss_step(A,k)
            print A # for testing
        else: print ' falhou no passo ', k
    return A

# roda um teste

A=np.array([[1,2,3],
         [2,-1,9],
         [3.,0., 2.]])
B=triangle_form(A)

Decomposição LU

Diremos que uma matriz L é triangular inferior se L=(lij) com lij=0 se i<jlii=1

Por outro lado, a matriz U será triangular superior se U=(uij) com uij=0 se j<i

Uma matriz quadrada A tem uma decomposição LU se existem uma matriz triangular inferior, L e uma triangular superior U tal que temos a igualdade: LU=A

Uma matriz inversível tem decomposição LU se e somente se seus menores principais têm determinantes não nulos. Neste caso pode-se resolver o sistema Ax=b

Resolvendo primeiro Lz=b
usando substituição de cima pra baixo, e depois, resolver Ux=z
usando substituição de baixo pra cima.

Métodos Iterativos

A idéia dos métodos iterativos é produzir uma sequência de estimativas vetoriais x(k), que idealmente convirja para a solução x e cada passo é calculado a partir da estimativa anterior. Costumam ser bem mais rápidos no caso de matrizes muito grandes. Para construir recursivamente a sequência que dá origem ao método iterativo fazemos uma decomposição da matriz A de coeficientes do sistema como A=NP

Onde N é de uma classe de sistemas inversíveis e fáceis de resolver. Então resolver o sistema: Ax=b
é equivalente a resolver a equação: Nx=b+Px ou x=N1(b+Px)
A recursão Nx(k+1)=b+Px(k)
é a que converge para a solução caso ||N1P||<1 onde a norma de matrizes é induzida de uma norma em Rn. Destacamos dois casos

  • Método de JacobiN=diag(a11,,ann)
  • Método de Gauss-Seidel N=(nij) com nij=aij se ijnij=0 se i<j

Método dos Mínimos Quadrados: Caso discreto

Consideremos uma tabela de pontos T={(x0,y0),,(xk,yk)}. Podemos interpretar esta tabela de duas formas: 1. Pode ser um conjunto de informações aproximadas sobre uma função de determinado tipo. E neste caso procuraremos a função daquele tipo que melhor se ajuste à tabela. 2. Pode ser um conjunto de informações precisas sobre uma função diferenciável. Mas não sabemos mais o tipo da função.

No primeiro caso faremos um ajuste da função para a tabela pelo Método dos Mínimos quadrados. A função ajustada não respeita, necessariamente, a tabela dada. Para o segundo caso estudaremos a Interpolação polinomial

Comecemos com o método dos mínimos quadrados (MMQ). Lembremos que temos dadas uma tabela de pontos T={(x0,y0),,(xk,yk)} e uma família de funções F onde o domínio de cada uma das funções de F contém os pontos xi da tabela T. O resíduo quadrático de uma função f com relação à tabela é: r(f,T)=ki=0(yif(xi))2

O problema de MMQ é encontrar uma função fF que minimize r(f,T)

Ferramentas pessoais
Espaços nominais

Variantes
Ações
Navegação
Imprimir/exportar
Ferramentas