Pular para conteúdo

Lista 11 · Runge e splines

Acompanha o capítulo 11. Quando o polinômio de grau alto oscila, escolha bem os pontos (Chebyshev) ou use um polinômio por trecho (spline).

Abrir no Colab

Baixar o caderno

Como esta lista funciona

Abra o caderno no Colab, escreva a sua solução no lugar do # TODO e rode a célula de teste logo abaixo. Ela mesma diz o que passou e o que falhou — você não precisa esperar correção de ninguém.

O código de cada exercício também está aqui embaixo, caso você prefira colar num editor local. Os testes comparam números com tolerância.

Exercícios marcados com ✏️ são à mão, no papel, com 4 casas decimais — como na parte em papel das avaliações.

Exercício 01

✏️ À mão. A tabela tem os pontos \((0, 1)\), \((1, 3)\) e \((3, 2)\).

a) Pela spline linear (uma reta por trecho), estime \(y\) em \(x = 0{,}5\) e em \(x = 2\).

b) Pelo polinômio de Lagrange pelos três pontos, estime \(y\) em \(x = 2\).

c) A spline linear tem "bico" em \(x = 1\)? Calcule a inclinação de cada lado.

Exercício 02

Escreva nos_chebyshev(n, a, b), que devolve um array com os \(n\) nós de Chebyshev no intervalo \([a, b]\):

\[ x_k = \frac{a + b}{2} + \frac{b - a}{2}\cos\!\left(\frac{(2k + 1)\pi}{2n}\right). \]

(O cosseno dá os nós em \([-1, 1]\); a conta de fora os leva para \([a, b]\).)

import numpy as np


def nos_chebyshev(n, a, b):
    """Os n nós de Chebyshev levados do intervalo [-1, 1] para [a, b]."""
    nos = np.zeros(n)
    # TODO: para cada k, o cosseno e a mudança de intervalo
    return nos


# --- testes (não altere) ---
nos = nos_chebyshev(3, -1, 1)
assert abs(nos[1]) < 1e-12, "com 3 nós em [-1, 1], o do meio é 0"
assert abs(nos[0] - 0.8660254037844387) < 1e-12
nos = nos_chebyshev(4, 0, 10)
assert abs(nos[0] - 9.619397662556434) < 1e-12 and abs(nos[3] - 0.3806023374435661) < 1e-12
print("Exercício 02: todos os testes passaram!")

Exercício 03

O fenômeno de Runge em números. Escreva erro_runge(n), que interpola a função de Runge (runge, já escrita) em n pontos igualmente espaçados de \(-1\) a \(1\) com a lagrange (já escrita), e devolve o maior erro absoluto entre o polinômio e a função, medido em 401 pontos de \(-1\) a \(1\).

Depois de passar, calcule para \(n = 5, 9, 13, 17, 21\). O que acontece?

import numpy as np


def runge(x):
    """JÁ ESCRITA."""
    return 1 / (1 + 25 * x**2)

def lagrange(xs, ys, x):
    """JÁ ESCRITA."""
    soma = 0.0
    for i in range(len(xs)):
        L = 1.0
        for j in range(len(xs)):
            if j != i:
                L = L * (x - xs[j]) / (xs[i] - xs[j])
        soma = soma + ys[i] * L
    return soma


def erro_runge(n):
    """Maior erro do polinômio por n pontos igualmente espaçados em [-1, 1],
    medido em 401 pontos."""
    # TODO: os nós com np.linspace, os 401 pontos de teste, e np.max do erro absoluto
    pass


# --- testes (não altere) ---
assert abs(erro_runge(5) - 0.4383566395255679) < 1e-9
assert abs(erro_runge(13) - 3.65771713785101) < 1e-6
assert erro_runge(17) > erro_runge(13), "mais pontos, erro MAIOR"
print("Exercício 03: todos os testes passaram!")

Exercício 04

Sensores — reamostrar um sinal. Dois sensores registram em horários diferentes: um a cada 0,7 s e outro a cada 1 s. Para comparar os dois, é preciso levar um deles para os horários do outro. Escreva reamostra(t, y, novos_t), que devolve os valores de y nos instantes novos_t, por interpolação linear com np.interp.

reamostra([0, 0.7, 1.4, 2.1], [10, 12, 11, 15], [0, 1, 2])    # -> [11.  11.57 14.43]
import numpy as np


def reamostra(t, y, novos_t):
    """Valores de y nos instantes novos_t, por interpolação linear."""
    # TODO: uma linha com np.interp (atenção à ordem dos argumentos)
    pass


# --- testes (não altere) ---
r = reamostra([0, 0.7, 1.4, 2.1], [10, 12, 11, 15], [0, 1, 2])
assert r is not None, "faltou o return?"
assert abs(r[0] - 10) < 1e-12 and abs(r[1] - (12 - 1 * 0.3 / 0.7)) < 1e-9 and abs(r[2] - (11 + 4 * 0.6 / 0.7)) < 1e-9
print("Exercício 04: todos os testes passaram!")

Exercício 05

Clima — a hora mais fria. Com as medições de 3 em 3 horas do capítulo (0, 3, ..., 21 h; 25,2; 24,6; 24,4; 27,8; 30,6; 30,9; 28,3; 26,4 °C), escreva hora_mais_fria(horas, temp), que monta a spline cúbica, calcula-a numa grade de 0,01 h (de horas[0] a horas[-1]) e devolve a hora de temperatura mínima.

Por que a resposta não é nenhuma das horas medidas?

import numpy as np
from scipy.interpolate import CubicSpline


def hora_mais_fria(horas, temp):
    """Hora do dia em que a spline cúbica das medições é mínima (grade de 0,01 h)."""
    # TODO: a spline, a grade (quantos pontos para passo de 0,01?) e np.argmin
    pass


# --- testes (não altere) ---
h = hora_mais_fria([0, 3, 6, 9, 12, 15, 18, 21], [25.2, 24.6, 24.4, 27.8, 30.6, 30.9, 28.3, 26.4])
assert abs(h - 4.87) < 1e-9, f"veio {h}"
print("Exercício 05: todos os testes passaram!")

Exercício 06

Cinema e robótica — o comprimento do caminho. A câmera do capítulo passa pelos pontos \((0, 0)\), \((2, 1)\), \((4, 0)\), \((4, -2)\), \((2, -3)\) e \((0, -1)\) nos instantes 0 a 5 s. Escreva comprimento_caminho(t, x, y), que monta as splines \(x(t)\) e \(y(t)\), calcula-as em 1001 instantes igualmente espaçados e soma os comprimentos dos 1000 segmentos retos entre pontos vizinhos (Pitágoras).

Quanto mais comprido que a linha poligonal (os segmentos retos entre os pontos de passagem) é o caminho suave?

import numpy as np
from scipy.interpolate import CubicSpline


def comprimento_caminho(t, x, y):
    """Comprimento do caminho das splines x(t), y(t), somando 1000 segmentos retos."""
    # TODO: duas splines, 1001 instantes, e um acumulador com a distância entre vizinhos
    pass


# --- testes (não altere) ---
L = comprimento_caminho([0, 1, 2, 3, 4, 5], [0.0, 2.0, 4.0, 4.0, 2.0, 0.0], [0.0, 1.0, 0.0, -2.0, -3.0, -1.0])
assert abs(L - 12.156311132751794) < 1e-9, f"veio {L}"
assert abs(comprimento_caminho([0, 1, 2], [0.0, 3.0, 6.0], [0.0, 4.0, 8.0]) - 10) < 1e-9, "uma reta de comprimento 10"
print("Exercício 06: todos os testes passaram!")