0. Modelos, numpy e matplotlib¶
Engenharia é, em boa parte, prever. Com que velocidade um paraquedista chega ao chão? Em quanto tempo uma vacina fora da geladeira passa do limite seguro? Quanto tempo um capacitor leva para carregar? Para prever, a ciência escreve um modelo matemático: uma equação que diz como uma grandeza muda. Às vezes essa equação tem solução exata no papel. Quase sempre, na vida real, não tem.
Este capítulo mostra as duas coisas lado a lado: um problema com fórmula exata e o
mesmo problema resolvido um passo de cada vez, só com contas simples. Esse
segundo jeito é o que o curso inteiro faz. No caminho, você conhece as duas
ferramentas que vão aparecer em toda aula: numpy, para fazer contas com tabelas
de números, e matplotlib, para desenhar gráficos.
O que é um modelo matemático¶
🌍 Contexto: o paraquedista e o arrasto do ar
Um paraquedista que salta de um avião cai cada vez mais depressa, mas não para sempre: o ar empurra para cima com uma força que cresce com a velocidade (o arrasto). Quando o arrasto fica igual ao peso, a velocidade para de aumentar. Essa é a velocidade terminal. O modelo diz que a aceleração é \(\dfrac{dv}{dt} = g - \dfrac{c}{m}v^2\): a gravidade menos o arrasto, que depende da massa \(m\) e de um coeficiente \(c\) que mede o quanto o corpo "segura" o ar.
Para esse modelo, o cálculo sabe achar a fórmula exata:
Com a fórmula, prever é só substituir valores. O código usa duas ferramentas
novas: as funções matemáticas do numpy e a formatação de números no print.
🧰 Comando novo: import numpy as np, np.sqrt, np.exp e np.tanh
O Python sozinho não tem raiz quadrada nem exponencial prontas. A biblioteca
numpy tem, e é a ferramenta de cálculo de todo o curso. A primeira linha
do programa a carrega com o apelido np; daí em diante, tudo o que vem dela é
chamado com np. na frente:
import numpy as np
print(np.sqrt(9)) # raiz quadrada
print(np.exp(1)) # e elevado a 1
print(np.tanh(0)) # tangente hiperbolica: comeca em 0...
print(np.tanh(3)) # ...e fica perto de 1 para valores grandes
np.sqrt é a raiz quadrada, np.exp(x) é \(e^x\) e np.tanh é a tangente
hiperbólica, uma curva em S que sai de 0 e se aproxima de 1. Você não
precisa saber calcular a tanh à mão: ela aparece na fórmula do modelo, e o
numpy faz a conta.
🧰 Python: formatando números no print
Dentro de uma f-string, o que vem depois dos dois-pontos diz como mostrar o número. O curso usa só estas formas:
x = 3.14159
print(f"{x:.2f}") # 2 casas decimais
print(f"[{x:8.2f}]") # 8 caracteres de largura, 2 casas (os colchetes mostram a largura)
print(f"[{7:5d}]") # inteiro, 5 caracteres de largura
print(f"{0.0045:.1e}") # notacao cientifica: 4.5 vezes 10 elevado a -3
.2f quer dizer "duas casas decimais". O número antes do ponto (8.2f, 5d)
é a largura: o número ocupa aquele tanto de espaço, o que alinha as
colunas de uma tabela. d é para inteiros, e .1e é a notação científica
(\(4{,}5 \times 10^{-3}\)).
# Modelo do paraquedista em queda livre (antes de abrir o paraquedas).
# A fisica da a formula exata da velocidade v(t), com arrasto do ar.
import numpy as np
g = 9.81 # gravidade (m/s²)
m = 68.1 # massa (kg)
c = 0.25 # coeficiente de arrasto (kg/m)
def v(t):
return np.sqrt(g * m / c) * np.tanh(np.sqrt(g * c / m) * t)
print(" t (s) v (m/s)")
for t in [0, 2, 4, 6, 8, 10, 12]:
print(f"{t:5d} {v(t):7.2f}")
print(f"velocidade terminal: {np.sqrt(g * m / c):.2f} m/s")
t (s) v (m/s)
0 0.00
2 18.73
4 33.11
6 42.08
8 46.96
10 49.42
12 50.62
velocidade terminal: 51.69 m/s
Um modelo não é a realidade: ele ignora o vento, a posição do corpo e a densidade do ar mudando com a altitude. Ele é bom o bastante para a pergunta que se quer responder. Todo resultado deste curso vem com essa ressalva: o método numérico resolve o modelo, e o modelo aproxima o mundo.
Resolver sem fórmula: um passo de cada vez¶
A fórmula exata do paraquedista existe porque o modelo é simples. Mude um detalhe (o arrasto variando com a altitude, o paraquedas abrindo aos poucos) e ela some. O que não some é a equação da taxa, \(\dfrac{dv}{dt} = g - \dfrac{c}{m}v^2\): ela diz, a cada instante, quanto a velocidade está mudando. E isso basta.
🧑🏫 No quadro — o passo de Euler
1. A derivada é a taxa de variação. Para um intervalo de tempo \(\Delta t\) pequeno (o passo), a taxa é quase igual à inclinação da reta entre dois instantes:
2. Isole o valor desconhecido, \(v_{\text{próximo}}\):
3. Em palavras: o próximo valor é o valor de agora mais a taxa vezes o passo. A taxa vem do modelo, \(g - \frac{c}{m}v_{\text{agora}}^2\), e só depende do que já se conhece.
4. Começando do valor inicial (\(v = 0\), parado), repita: cada passo usa o resultado do anterior. É o método de Euler.
A aproximação do passo 1 é a diferença progressiva do capítulo 2, lida ao contrário: lá, conhecemos os valores e queremos a taxa; aqui, conhecemos a taxa e queremos os valores.
# O mesmo paraquedista, SEM usar a formula de v(t): so a taxa de variacao,
# dv/dt = g - (c/m) v², e um passo de cada vez (metodo de Euler).
import numpy as np
g = 9.81
m = 68.1
c = 0.25
dt = 2.0 # tamanho do passo (s)
t = 0.0
v = 0.0 # comeca parado
print(" t (s) v Euler v exata")
for passo in range(7):
exata = np.sqrt(g * m / c) * np.tanh(np.sqrt(g * c / m) * t)
print(f"{t:5.0f} {v:7.2f} {exata:7.2f}")
taxa = g - (c / m) * v**2 # quanto v muda por segundo, agora
v = v + taxa * dt # o proximo valor
t = t + dt
t (s) v Euler v exata
0 0.00 0.00
2 19.62 18.73
4 36.41 33.11
6 46.30 42.08
8 50.18 46.96
10 51.31 49.42
12 51.60 50.62
Sem nenhuma fórmula de \(v(t)\), só com multiplicação e soma dentro de um laço, a coluna de Euler segue a exata. Ela erra, e sempre para cima: em cada passo a taxa usada é a do começo do intervalo, quando o arrasto ainda era menor. Mas as duas convergem para a mesma velocidade terminal.
💡 Pense assim: dirigir olhando só o velocímetro
Você sabe onde está e a que velocidade anda agora. Se mantiver essa velocidade por um minuto, sabe onde estará daqui a um minuto. Não é exato (a velocidade muda durante o minuto), mas, olhando o velocímetro de novo a cada minuto e corrigindo a conta, você chega perto. Com olhadas mais frequentes, chega mais perto ainda.
Quanto o passo importa¶
O erro de Euler vem de fingir que a taxa fica parada durante todo o passo. Com um passo menor, ela tem menos tempo para mudar:
# Quanto o tamanho do passo importa? Velocidade em t = 12 s com passos
# cada vez menores, comparada com a exata.
import numpy as np
g = 9.81
m = 68.1
c = 0.25
exata = np.sqrt(g * m / c) * np.tanh(np.sqrt(g * c / m) * 12)
print(" dt passos v(12) erro")
for dt in [2.0, 1.0, 0.5, 0.25]:
v = 0.0
n = int(12 / dt)
for passo in range(n):
v = v + (g - (c / m) * v**2) * dt
print(f"{dt:5.2f} {n:5d} {v:6.3f} {abs(v - exata):.3f}")
dt passos v(12) erro
2.00 6 51.601 0.983
1.00 12 51.201 0.583
0.50 24 50.926 0.308
0.25 48 50.775 0.158
Com o passo pela metade, o erro cai mais ou menos pela metade, e o número de contas dobra. É o primeiro exemplo de uma troca que atravessa o curso inteiro: precisão custa trabalho, e cada método tem uma "taxa de câmbio" entre os dois. No capítulo 2 essa taxa de câmbio ganha um nome, a ordem do método.
numpy em poucos minutos¶
🌍 Contexto: por que numpy?
Uma lista do Python guarda qualquer coisa: números, textos, outras listas. Por
isso ela não sabe fazer conta. O numpy tem a sua própria tabela de números, o
array, que sabe. Ele é a ferramenta padrão de cálculo científico em
Python, e o Colab já o traz instalado.
🧰 Comando novo: np.array
np.array(lista) transforma uma lista de números num array. A diferença
aparece na primeira conta:
import numpy as np
lista = [1, 2, 3]
tabela = np.array([1, 2, 3])
print("lista * 2:", lista * 2) # a lista do Python REPETE
print("array * 2:", tabela * 2) # o array MULTIPLICA cada elemento
Com a lista, * 2 repete; com o array, multiplica cada elemento.
Repare também que o array é impresso sem vírgulas: é assim que se reconhece
um array na saída.
🧰 Comando novo: np.linspace
np.linspace(inicio, fim, quantos) cria um array com quantos números
igualmente espaçados de inicio a fim, incluindo os dois. É o jeito de
criar os instantes de tempo de uma tabela ou os pontos de um gráfico:
Sete números de 0 a 12 dão um espaçamento de 2. O ponto depois de cada número
(0., 2.) quer dizer que eles são float.
🧰 Conta com array: elemento a elemento
Toda conta entre um array e um número, e toda função do numpy, é feita em
cada elemento, e o resultado é outro array do mesmo tamanho:
import numpy as np
t = np.linspace(0, 12, 7)
print(2 * t) # cada elemento vezes 2
print(t + 1) # cada elemento mais 1
print(np.sqrt(t)) # a raiz de cada elemento
[ 0. 4. 8. 12. 16. 20. 24.]
[ 1. 3. 5. 7. 9. 11. 13.]
[0. 1.41421356 2. 2.44948974 2.82842712 3.16227766
3.46410162]
É por isso que a função v(t) do paraquedista, escrita pensando num número,
funciona também com um array de tempos inteiro: np.sqrt e np.tanh
trabalham elemento a elemento.
🧰 Índice e fatia no array
Funcionam igual à lista do Python:
import numpy as np
t = np.linspace(0, 12, 7)
print(t[0]) # o primeiro
print(t[-1]) # o ultimo
print(t[2:5]) # das posicoes 2, 3 e 4 (a 5 fica de fora)
print(len(t)) # quantos elementos
A contagem começa em 0, t[-1] é o último e, na fatia t[2:5], a posição 5
fica de fora. len(t) diz quantos elementos há.
🧰 Comando novo: np.sum, np.max, np.argmax e np.round
Resumos de um array inteiro:
import numpy as np
medidas = np.array([2.5, 7.1, 3.8, 9.4, 6.0])
print(np.sum(medidas)) # soma de todos
print(np.max(medidas)) # o maior
print(np.argmax(medidas)) # a POSICAO do maior (a contagem comeca em 0)
print(np.round(3.14159, 2)) # arredonda com 2 casas
print(np.round(medidas / 3, 1)) # arredonda cada elemento do array
np.argmax devolve a posição do maior, e não o valor: o 9,4 está na
posição 3 (contando do zero). np.round(x, n) arredonda com n casas, e
funciona com o array inteiro. E a soma deu 28.799999999999997 em vez de
28,8. Não é defeito do numpy: é o assunto do capítulo 1.
Gráficos com matplotlib¶
Um número isolado esconde muita coisa; um gráfico mostra a forma. No curso, todo resultado importante vira gráfico, principalmente para comparar o modelo com as medições.
🧰 Comando novo: a receita de um gráfico com matplotlib
A biblioteca de gráficos é carregada com import matplotlib.pyplot as plt, e
tudo dela é chamado com plt. na frente. Todo gráfico do curso segue a mesma
receita:
| Linha | O que faz |
|---|---|
plt.figure() |
começa uma figura nova, em branco |
plt.plot(x, y, label="modelo") |
desenha y contra x como uma linha; label= dá o nome que aparece na legenda |
plt.plot(x, y, "o") |
o mesmo, mas com bolinhas em cada ponto; "." desenha pontinhos e "o-", bolinhas ligadas por linha |
plt.scatter(x, y, color="red") |
desenha pontos soltos, sem linha; color= escolhe a cor |
plt.xlabel("tempo (min)"), plt.ylabel(...) |
o nome de cada eixo, com a unidade |
plt.title("...") |
o título |
plt.grid() |
a grade de fundo, que ajuda a ler os valores |
plt.legend() |
a legenda, com os nomes dados em label= |
plt.show() |
mostra a figura: é sempre o último comando |
x e y podem ser listas ou arrays, mas precisam ter o mesmo tamanho.
Para desenhar uma curva lisa, use muitos pontos, como np.linspace(0, 30, 100).
Um café esfriando: medições de um termômetro de cozinha (pontos) contra o modelo da lei de resfriamento de Newton (linha):
# Cafe esfriando: medicoes com um termometro de cozinha contra o modelo
# da lei de resfriamento de Newton, T(t) = 25 + 65*exp(-0.045 t).
import numpy as np
import matplotlib.pyplot as plt
t_medido = np.array([0, 5, 10, 15, 20, 25, 30]) # minutos
T_medido = np.array([90, 77.4, 66.8, 58.2, 51.4, 46.0, 41.6]) # °C
t = np.linspace(0, 30, 100)
T_modelo = 25 + 65 * np.exp(-0.045 * t)
plt.figure()
plt.plot(t, T_modelo, label="modelo de Newton")
plt.scatter(t_medido, T_medido, color="red", label="medições")
plt.xlabel("tempo (min)")
plt.ylabel("temperatura (°C)")
plt.title("Café esfriando numa sala a 25 °C")
plt.grid()
plt.legend()
plt.show()

O modelo passa perto de todas as medições: é assim que um gráfico mostra, de relance, se um modelo serve. Gráfico sem nome nos eixos e sem unidade é gráfico que ninguém consegue ler.
Mesmo método, outra área¶
🌍 Contexto: o capacitor carregando
Um capacitor é um "tanque" de carga elétrica. Ligado a uma fonte por um resistor, ele carrega depressa no começo e cada vez mais devagar, porque a corrente depende da diferença entre a tensão da fonte e a dele. A taxa é \(\dfrac{dV}{dt} = \dfrac{V_f - V}{RC}\). O produto \(RC\) (em segundos) é a constante de tempo: em \(RC\) segundos, o capacitor chega a 63 % da tensão final. Todo filtro de sinal, temporizador e circuito de reset usa isso.
O laço é o mesmo do paraquedista. Só muda a linha da taxa:
# Mesmo metodo, outra area: um capacitor carregando por um resistor.
# A tensao muda a uma taxa dV/dt = (Vf - V) / (R*C). E o mesmo laco do
# paraquedista, trocando so a linha da taxa.
import numpy as np
import matplotlib.pyplot as plt
Vf = 5.0 # tensao da fonte (V)
R = 10e3 # resistencia (ohm)
C = 100e-6 # capacitancia (F): R*C = 1 segundo
dt = 0.1
t = 0.0
V = 0.0
tempos = [t]
tensoes = [V]
for passo in range(50):
taxa = (Vf - V) / (R * C)
V = V + taxa * dt
t = t + dt
tempos.append(t)
tensoes.append(V)
print(f"Euler: V(1 s) = {tensoes[10]:.3f} V V(5 s) = {tensoes[50]:.3f} V")
print(f"exata: V(1 s) = {Vf * (1 - np.exp(-1)):.3f} V V(5 s) = {Vf * (1 - np.exp(-5)):.3f} V")
t_exato = np.linspace(0, 5, 100)
plt.figure()
plt.plot(t_exato, Vf * (1 - np.exp(-t_exato)), label="exata")
plt.plot(tempos, tensoes, "o", label="Euler, dt = 0,1 s")
plt.xlabel("tempo (s)")
plt.ylabel("tensão no capacitor (V)")
plt.grid()
plt.legend()
plt.show()

E não para aí. A taxa do capacitor, \(\frac{V_f - V}{RC}\), tem a mesma forma da taxa do café esfriando, \(k\,(T_{\text{sala}} - T)\): "a mudança é proporcional à distância até o equilíbrio". O mesmo código carrega o capacitor, esfria o café e esquenta uma vacina fora da geladeira (o problema desta aula). É essa a ideia do curso: o método não sabe de que área é o problema.
Erros comuns deste capítulo¶
| Sintoma | Causa provável |
|---|---|
NameError: name 'np' is not defined |
faltou import numpy as np, ou a célula do import não foi rodada |
2 * lista repete a lista em vez de dobrar os números |
é uma lista do Python: use np.array(lista) |
| o laço de Euler dá sempre o mesmo valor | esqueceu de guardar o resultado: v + taxa * dt sem o v = na frente |
| Euler "explode" para números enormes | passo grande demais para o problema: diminua o dt |
| o gráfico não aparece | faltou plt.show() (ou ele ficou antes do plt.plot) |
| a curva sai "quebrada", em segmentos retos | poucos pontos no np.linspace: use 100 ou mais para desenhar um modelo |
Onde isso é usado de verdade¶
- Previsão do tempo: os modelos meteorológicos avançam a atmosfera no tempo passo a passo, com métodos da mesma família de Euler (bem mais precisos), sobre milhões de pontos do globo.
- Jogos e animação: a física de um jogo (pulos, colisões, carros) é atualizada a cada quadro, 60 vezes por segundo, com "posição nova = posição + velocidade × passo".
- Simuladores de circuitos (SPICE, o do Tinkercad, o do Multisim): cada capacitor e indutor do circuito é avançado no tempo passo a passo.
- Epidemiologia: os modelos de contágio usados na pandemia de COVID-19 avançam suscetíveis, infectados e recuperados dia a dia com essa mesma ideia.
- Espaço: a trajetória de uma sonda é calculada integrando a gravidade de todos os corpos do sistema solar, passo a passo.
Resumo¶
- Um modelo matemático descreve como uma grandeza muda. Às vezes tem fórmula exata; quase sempre, não.
- O método de Euler resolve o modelo sem fórmula: próximo = agora + taxa × passo, repetido num laço. É a diferença progressiva usada ao contrário.
- Passo menor, erro menor e mais contas: precisão custa trabalho.
numpyfaz conta com a tabela inteira de uma vez;matplotlibmostra a forma do resultado.- O mesmo laço serve para paraquedista, café, vacina e capacitor. Só muda a taxa.
Para praticar¶
A Lista 00 começa com três passos de Euler à mão (✏️) e segue com o paraquedista, o café, a vacina fora da geladeira e o capacitor.