Quatro programas completos: random walk, movimento de projétil, Game of Life e o modelo epidêmico SIR.
O que é simulação?
Uma simulação codifica as regras de um sistema do mundo real e avança no tempo passo a passo, deixando você observar o estado evoluir. É como você estuda comportamentos difíceis de resolver analiticamente.
Estocástico
Random walk
Uma partícula escolhe uma direção aleatória a cada passo. Um modelo clássico de difusão.
Física
Movimento de projétil
Aplica gravidade e arrasto do ar em pequenos passos de tempo (integração de Euler).
Discreto
Game of Life
Um autômato celular 2D com regras simples de nascimento e morte.
População
Epidemia SIR
Acompanha como populações suscetíveis, infectadas e recuperadas mudam com o tempo.
Três passos comuns
Inicializar: monte o estado inicial (posições, velocidades, grade, populações, ...).
Regra de atualização: uma função que avança o estado por um único passo.
Laço: chame a atualização um número fixo de vezes (ou até uma condição ser atingida), imprimindo valores intermediários ao longo do caminho.
Teste K=0: você obtém a parábola ideal com alcance v₀²·sin(2θ)/g ≈ 91,8 m. Varie K para ver como o arrasto encurta o lançamento.
Plotar o resultado
Salve a saída como CSV e plote com Excel, gnuplot ou Python.
$ ./projectile > traj.csv
$ gnuplot -e "set datafile separator ','; plot 'traj.csv' using 2:3 with lines"
Game of Life de Conway
Células numa grade 2D estão vivas ou mortas. O próximo estado de cada célula depende da contagem de vizinhos vivos. Padrões complexos (como o glider) emergem de regras surpreendentemente simples.
Regras
Uma célula viva sobrevive com 2 ou 3 vizinhos vivos, e morre caso contrário.
Uma célula morta ganha vida com exatamente 3 vizinhos vivos.
#include<stdio.h>#include<string.h>#include<unistd.h>// usleep (POSIX)#define W 30#define H 15#define GENS 40int grid[H][W];
int next_grid[H][W];
intcount_neighbors(int y, int x) {
int c = 0;
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
if (dy == 0 && dx == 0) continue;
int ny = y + dy, nx = x + dx;
if (ny < 0 || ny >= H || nx < 0 || nx >= W) continue;
c += grid[ny][nx];
}
}
return c;
}
voidstep(void) {
for (int y = 0; y < H; y++) {
for (int x = 0; x < W; x++) {
int n = count_neighbors(y, x);
if (grid[y][x]) next_grid[y][x] = (n == 2 || n == 3);
else next_grid[y][x] = (n == 3);
}
}
memcpy(grid, next_grid, sizeof(grid));
}
voidprint_grid(int gen) {
printf("\033[H\033[2J"); // limpa a telaprintf("ger: %d\n", gen);
for (int y = 0; y < H; y++) {
for (int x = 0; x < W; x++) {
putchar(grid[y][x] ? '#' : '.');
}
putchar('\n');
}
}
intmain(void) {
// coloca um glider
grid[1][2] = grid[2][3] = 1;
grid[3][1] = grid[3][2] = grid[3][3] = 1;
for (int g = 0; g < GENS; g++) {
print_grid(g);
usleep(150000); // pausa de 0,15 sstep();
}
return0;
}
Por que duas grades? O próximo estado de cada célula precisa ser calculado a partir da vizinhança atual. Se você escreve na mesma grade que está lendo, vizinhos já atualizados corrompem a regra. Escreva em next_grid e depois faça memcpy de uma só vez.
Modelo epidêmico SIR
O modelo matemático mais simples de propagação de doença divide a população em três compartimentos:
S
Suscetíveis
Ainda não infectados.
I
Infectados
Atualmente infecciosos.
R
Recuperados
Recuperados (e imunes).
Atualização em tempo discreto
ΔS = -β · S · I / N
ΔI = +β · S · I / N - γ · I
ΔR = +γ · I
β: taxa de infecção, γ: taxa de recuperação (1/duração), N: população total.
#include<stdio.h>#define DAYS 100intmain(void) {
double N = 1000.0; // população totaldouble S = 999.0; // suscetíveisdouble I = 1.0; // infectados iniciaisdouble R = 0.0; // recuperadosdouble beta = 0.3; // taxa de infecçãodouble gamma = 0.1; // taxa de recuperação (doença de 10 dias)printf("dia,S,I,R\n");
for (int day = 0; day <= DAYS; day++) {
printf("%d,%.1f,%.1f,%.1f\n", day, S, I, R);
double dS = -beta * S * I / N;
double dI = beta * S * I / N - gamma * I;
double dR = gamma * I;
S += dS;
I += dI;
R += dR;
}
return0;
}
Número básico de reprodução R₀ = β/γ. Aqui, 0,3/0,1 = 3 — em média, cada infectado transmite para três outros. Reduzir β (menos contato) ou aumentar γ (recuperação mais rápida) achata a curva.
Encontre o pico
// rastreia o máximo de I dentro do laçodouble peak = 0;
int peak_day = 0;
for (int day = 0; day <= DAYS; day++) {
if (I > peak) { peak = I; peak_day = day; }
// ... atualização regular
}
printf("pico no dia %d: %.0f pessoas\n", peak_day, peak);
Desafios
Desafio 1: Random walk 2D
Escolha uma de quatro direções (N/S/L/O) a cada passo. Rode 10.000 tentativas e confira que a distância média ainda escala como √N.
Desafio 2: Ângulo de lançamento ótimo
Rode a simulação de projétil para ângulos de 5° a 85° em passos de 5°, e encontre o ângulo que maximiza o alcance. Com arrasto, o ótimo fica abaixo de 45°.
Desafio 3: Grade inicial aleatória
Inicialize a grade do Life aleatoriamente com 50% de células vivas. Informe quantas estão vivas após 50 gerações. Veja a aula de números aleatórios.
Desafio 4: SIR com vacinação
Adicione um termo que move v pessoas por dia direto de S para R (vacinação). Compare v=10 com v=0 — como o pico muda?
Desafio 5: Lotka-Volterra (predador-presa)
Coelhos (R) e raposas (F):
dR/dt = α R - β R F, dF/dt = δ R F - γ F
Comece com R=40, F=9, α=0,1, β=0,02, γ=0,4, δ=0,02 e veja a oscilação.
Próximos passos
Integração de maior precisão → experimente Runge-Kutta (veja computação numérica).
Desenhe gráficos → experimente SDL2 ou raylib.
Acelere simulações pesadas → paralelize com OpenMP.
Quiz de Revisão
Teste seu entendimento desta aula!
Q1. Qual a fórmula básica para atualizar a posição com um passo de tempo dt?
x_{n+1} = x_n + v * dt
x_{n+1} = x_n * v
x_{n+1} = v - dt
Esta é a forma mais simples do Euler method — a posição avança pela velocidade v vezes o passo de tempo dt.
Q2. Qual a maneira padrão de inicializar o gerador para que cada execução produza uma sequência diferente?
srand(time(NULL))
srand(0)
Nenhuma inicialização é necessária
time(NULL) retorna o horário atual em segundos, dando uma semente diferente a cada execução.
Q3. Qual é a ideia central do método de Monte Carlo?
Estimar um valor estatisticamente a partir de muitas tentativas aleatórias
Resolver exatamente uma equação diferencial
Achar o mínimo ordenando
Ele depende de convergência probabilística — estimar π, por exemplo. A precisão melhora conforme o número de tentativas cresce.