Modelos Continuos · Tema 36

Sistemas acoplados

Múltiples estados interactuando: el vector se integra junto o no se integra.

01 · Punto de partida

Integrar en serie rompe la cadena

El SIR tiene tres estados que se pasan masa en el mismo instante. Si integrás S hasta el final y después I con S promediada, el contagio usa un pasado que I nunca vivió: el pico se corre y el ataque miente. La regla de oro: todas las tasas primero, todos los estados después, mismo dt.

Este tema convierte a Euler y RK4 en versión vectorial: las mismas firmas, con dicts en vez de flotantes.

  • ¿Qué estados comparten cada tasa?
  • ¿Avanzo el vector junto con el mismo dt?
  • ¿Se conserva S + I + R = N en cada nodo?
  • ¿Euler con dt chico valida al RK4 de producción?

02 · Definición

¿Qué es simular un sistema acoplado?

El mismo paso, en plural:

▦

Vector x

El paquete [S, I, R] o [A, B]: cada nodo guarda todos los estados en el mismo t.

ƒ

Tasas juntas

f(x, t) devuelve el dict completo calculado con el mismo x: nada de mezclar tiempos.

=

Conservación

S + I + R = N en cada nodo: si deriva, el paso mezcla instantes.

Sin exacta (SIR no tiene), el testigo es la convergencia entre dts y la conservación en cada nodo.

SIR β = 0,3, γ = 0,1, N = 1000, I₀ = 10: pico de I.
Método (dt)Pico ILectura
Euler dt = 2≈ 320 (pasado)Grueso: se pasa el pico.
Euler dt = 0,5≈ 301 (referencia)Fino: valida producción.
RK4 dt = 1≈ 300 (producción)Clava con 4×160 f.
En serie (mal)corrido y rotoS + I + R deriva: prohibido.

03 · Anatomía

Tasas, vectores y verificación

Simular acoplados bien pide tres hábitos:

Tasas del mismo instante
Calcular todo f(xₙ) antes de tocar ningún estado: el contagio usa S e I del mismo t.
Etapas vectoriales
En RK, cada kᵢ es un dict [dS, dI, dR]: promediar escalares rompe el acople.
Conservación por nodo
S + I + R = N siempre: el assert más barato del curso.
Convergencia sin exacta
dt vs dt/2 en pico y ataque: si no converge, hay bug o rigidez.

Junto (bien)

Vector que avanza unido

Tasas con xₙ, estados con las mismas tasas: la cadena S → I → R cierra y conserva.

En serie (mal)

Estados de tiempos mezclados

I usa S ya actualizado: el contagio vive en el futuro. El pico se corre y la masa deriva.

04 · Acoplados del curso

El mismo vector, cinco cadenas

Cada sistema se integra con la misma receta:

Vector x₀→Tasas f(x)→Conserva
  1. 1
    Cascada A → B.

    B hereda de A con retardo: conserva q en régimen.

  2. 2
    SIR.

    S → I → R cerrada: conserva N en cada nodo.

  3. 3
    Resorte (x, v).

    Ciclo que espirala: semi-implícito o RK conservan energía mejor.

  4. 4
    SEIR.

    Latencia E que retrasa el pico: cuatro estados juntos.

  5. 5
    Dos tanques.

    Niveles que se pasan caudal: mismos nodos, dos curvas.

05 · Representación en Python

Euler y RK4 vectoriales con dicts

En Python, el estado es dict y cada kᵢ también: sumar dicts con pesos es todo el secreto.

Python en tu navegador. Estos bloques se ejecutan localmente con Pyodide, sin instalar nada. Subí dt a 2 y observá que el pico se pasa.

def f_sir(e, beta=0.3, gamma=0.1, n=1000):
    c = beta * e["S"] * e["I"] / n
    return {"S": -c, "I": c - gamma * e["I"], "R": gamma * e["I"]}


def euler_vec(f, x0, t_final, dt):
    x, t = dict(x0), 0.0
    while t < t_final - 1e-9:
        h = min(dt, t_final - t)
        d = f(x, t)
        x = {k: x[k] + d[k] * h for k in x}
        t += h
    return x


x0 = {"S": 990.0, "I": 10.0, "R": 0.0}
fin = euler_vec(f_sir, x0, 160, dt=0.5)
print({k: round(v, 1) for k, v in fin.items()}, "suma:", round(sum(fin.values()), 1))

S ≈ 59 · I ≈ 0 · R ≈ 941, suma 1000: conserva y ataca el 94 %.

Pico con historia y conservación

El pico de I y el assert de masa en cada nodo:

def brote(dt=0.5, dias=160):
    x, t, pico = dict(x0), 0.0, 10.0
    while t < dias - 1e-9:
        h = min(dt, dias - t)
        d = f_sir(x, t)
        x = {k: x[k] + d[k] * h for k in x}
        assert abs(sum(x.values()) - 1000) < 1e-6
        pico = max(pico, x["I"])
        t += h
    return round(pico, 1)


print(brote(2.0), brote(0.5))

06 · Exploración

Laboratorio: el pico se mueve con dt

SIR (N = 1000, I₀ = 10, β con tu slider, γ = 0,1) integrado con Euler dt grueso (teal, se pasa) y Euler dt = 0,25 fino (blanco, referencia). Achicá tu dt y observá que el pico teal gatea hacia el blanco: la convergencia valida sin exacta.

EXPERIMENTO 36

Convergencia del pico

SIR · Euler(dt) vs Euler(0,25) · γ = 0,1

Los resultados numéricos aparecen debajo.
Pico con tu dt320,40
Pico fino (0,25)300,10
Diferencia20,30
Suma S+I+R1000,0

Con dt 2 el pico se pasa en 20,30: el paso grueso exagera la joroba.

Ambas curvas conservan N = 1000 en cada nodo: la diferencia es solo resolución, no masa perdida.

Preguntas para explorar

  1. Con dt = 0,25, ¿la diferencia se achica? ¿A cuánto?
  2. Con β = 0,5, ¿el pico se pasa más o menos con dt = 2?
  3. Si la suma diera 998, ¿qué bug buscarías primero?
Ver respuestas sugeridas
  1. Sí, a ≈ 0: tu dt iguala la referencia. Convergencia firmada.
  2. Más: más curvatura, más overshoot del paso grueso.
  3. Actualización en serie (S nueva para I): tasas de tiempos mezclados. Todo f primero.

07 · Comprensión

Confusiones frecuentes

«Cada estado con su dt»

No: un solo reloj para todo el vector. dts distintos desincronizan el acople.

«Conserva al final, alcanza»

Debe conservar en cada nodo: al final puede cancelar errores de signo. El assert va adentro del bucle.

«RK4 vectorial es otro método»

Es el mismo con dicts: cada kᵢ evalúa todo f. La receta no cambia, el contenedor sí.

«Sin exacta no hay error»

Hay convergencia (dt vs dt/2) y conservación: dos testigos que no piden fórmula.

08 · Práctica guiada

Ejercicios con Python

Ejercicio 1: tasas del mismo instante

Con S = 990, I = 10, calculá dS, dI, dR con β = 0,3 y γ = 0,1. ¿Suman cero?

d = f_sir({"S": 990.0, "I": 10.0, "R": 0.0})
print({k: round(v, 2) for k, v in d.items()}, "suma:", round(sum(d.values()), 6))
Ver solución

−2,97 · +1,97 · +1,0, suma 0: lo que sale de S entra a I y de I a R. Cadena cerrada.

Ejercicio 2: un paso vectorial

Aplicá un paso Euler dt = 1 desde (990, 10, 0). ¿Conserva N?

x = {"S": 990.0, "I": 10.0, "R": 0.0}
d = f_sir(x, 0)
x1 = {k: x[k] + d[k] * 1.0 for k in x}
print({k: round(v, 2) for k, v in x1.items()}, "suma:", round(sum(x1.values()), 1))
Ver solución

(987,03 · 11,97 · 1,0), suma 1000: el vector avanzó junto y la masa cierra.

Ejercicio 3: convergencia del pico

Compará el pico con dt = 2 y dt = 0,5. ¿Cuánto se pasa el grueso?

Ver una posible respuesta
print("grueso:", brote(2.0), "fino:", brote(0.5))

Grueso ≈ 320 frente a fino ≈ 301: el paso grueso exagera la joroba en ~20. Convergencia sin exacta.

09 · Síntesis

Ideas para recordar

  • Vector junto: todas las tasas con xₙ, todos los estados con esas tasas, mismo dt.
  • kᵢ vectoriales en RK: cada etapa evalúa todo f, no un estado.
  • Conservación por nodo (S + I + R = N) como assert barato.
  • Sin exacta: convergencia dt/dt/2 en pico y ataque como testigo.
  • En Python: dicts + assert + brote(dt) para el pico.

En el próximo tema elegiremos la representación: continuo frente a discreto.