<!-- TODO fix separadores de espacio -->
<!-- TODO plots que se adecuen a movil -->
# Planos de fases
El plano de fases permite explorar graficamente el comportamiento sistemas de ecuaciones diferenciales de primer orden:
$$
\begin{cases}
\frac{dx}{dt} = f(x, y) \\
\frac{dy}{dt} = g(x, y)
\end{cases}
$$
El **plano de fases** muestra las trayectorias (órbitas) en el espacio $(x, y)$, donde cada punto representa un estado del sistema y las flechas indican la dirección del flujo.
## Aplicaciones Prácticas
**Física:**
- Péndulo: $(θ, \dot{θ})$ → posición y velocidad angular
- Masa-resorte: $(x, \dot{x})$ → posición y velocidad
- Circuitos eléctricos: $(I, V)$ → corriente y voltaje
**Biología:**
- Presa-depredador (Lotka-Volterra): $(x, y)$ → poblaciones
- Epidemias (SIR): $(S, I)$ → susceptibles e infectados
- Reacciones químicas: concentraciones de reactivos
**Economía:**
- Modelos de crecimiento: $(K, L)$ → capital y trabajo
- Ciclos económicos: $(Y, I)$ → producción e inversión
**Ingeniería:**
- Sistemas de control: $(x, \dot{x})$ → estado y derivada
- Robótica: $(q, \dot{q})$ → posición y velocidad articular
::: {.content-visible when-format="html"}
___
:::
## Conceptos Clave
::: {.callout-note title="Terminología"}
**Plano de fases:**
- Espacio $(x, y)$ donde cada punto representa un estado del sistema
- Las órbitas muestran la evolución temporal del sistema
**Campo vectorial:**
- En cada punto $(x, y)$, el vector $(\frac{dx}{dt}, \frac{dy}{dt})$ indica la dirección del flujo
**Órbitas (trayectorias):**
- Curvas solución en el plano de fases
- Representan la evolución del sistema desde una condición inicial
**Puntos de equilibrio:**
- Puntos donde $\frac{dx}{dt} = 0$ y $\frac{dy}{dt} = 0$
- El sistema permanece constante en estos puntos
**Tipos de equilibrio:**
- **Nodo:** Todas las órbitas convergen/divergen radialmente
- **Foco (espiral):** Órbitas espirales convergentes/divergentes
- **Centro:** Órbitas cerradas (periódicas)
- **Silla:** Equilibrio inestable con separatrices
:::
::: {.content-visible when-format="html"}
___
:::
## Visualizador Interactivo
::: {.content-visible when-format="pdf"}
{{< include _interactive_note.qmd >}}
:::
::: {.content-visible when-format="html"}
::: {.callout-caution collapse="true" title="Visualizador de Plano de Fases" }
```{ojs}
//| echo: false
//| label: funciones-comunes
// ===== FUNCIONES REUTILIZABLES =====
// Método de Runge-Kutta de 4º orden (más preciso que Euler)
rk4_step = (x, y, t, dt, f, g) => {
const k1_x = f(x, y, t);
const k1_y = g(x, y, t);
const k2_x = f(x + dt/2 * k1_x, y + dt/2 * k1_y, t + dt/2);
const k2_y = g(x + dt/2 * k1_x, y + dt/2 * k1_y, t + dt/2);
const k3_x = f(x + dt/2 * k2_x, y + dt/2 * k2_y, t + dt/2);
const k3_y = g(x + dt/2 * k2_x, y + dt/2 * k2_y, t + dt/2);
const k4_x = f(x + dt * k3_x, y + dt * k3_y, t + dt);
const k4_y = g(x + dt * k3_x, y + dt * k3_y, t + dt);
return {
x: x + dt/6 * (k1_x + 2*k2_x + 2*k3_x + k4_x),
y: y + dt/6 * (k1_y + 2*k2_y + 2*k3_y + k4_y)
};
}
// Crear funciones evaluables desde strings
crear_funcion_sistema = (expresion) => {
try {
const func = new Function('x', 'y', 't', `
try {
const result = ${expresion};
if (!isFinite(result)) return 0;
return result;
} catch(e) {
return 0;
}
`);
return func;
} catch(e) {
return (x, y, t) => 0;
}
}
// Calcular campo vectorial
calcular_campo_vectorial = (f, g, x_min, x_max, y_min, y_max, densidad, longitud, normalizar) => {
const vectores = [];
const dx = (x_max - x_min) / densidad;
const dy = (y_max - y_min) / densidad;
for (let x = x_min; x <= x_max; x += dx) {
for (let y = y_min; y <= y_max; y += dy) {
try {
const vx = f(x, y, 0);
const vy = g(x, y, 0);
if (!isFinite(vx) || !isFinite(vy)) continue;
const magnitud = Math.sqrt(vx*vx + vy*vy);
if (magnitud === 0) continue;
let scale;
if (normalizar) {
scale = Math.min(dx, dy) * longitud;
} else {
scale = Math.min(dx, dy) * longitud * Math.min(1, magnitud);
}
const dx_vec = (vx / magnitud) * scale;
const dy_vec = (vy / magnitud) * scale;
vectores.push({
x: x,
y: y,
x1: x,
y1: y,
x2: x + dx_vec,
y2: y + dy_vec,
magnitud: magnitud
});
} catch(e) {
continue;
}
}
}
return vectores;
}
// Calcular órbitas
calcular_orbitas = (f, g, condiciones_str, tiempo_max, dt, x_min, x_max, y_min, y_max) => {
const conds_iniciales = condiciones_str
.split(';')
.map(s => {
const match = s.trim().match(/\[([^,]+),([^\]]+)\]/);
if (match) {
return {
x: parseFloat(match[1]),
y: parseFloat(match[2])
};
}
return null;
})
.filter(p => p !== null && isFinite(p.x) && isFinite(p.y));
const orbitas_calculadas = [];
const pasos = Math.floor(tiempo_max / dt);
for (const ic of conds_iniciales) {
const puntos = [{x: ic.x, y: ic.y, t: 0}];
let x = ic.x, y = ic.y, t = 0;
for (let i = 0; i < pasos; i++) {
try {
const nuevo = rk4_step(x, y, t, dt, f, g);
if (!isFinite(nuevo.x) || !isFinite(nuevo.y)) break;
if (Math.abs(nuevo.x) > 1000 || Math.abs(nuevo.y) > 1000) break;
if (nuevo.x < x_min - 1 || nuevo.x > x_max + 1) break;
if (nuevo.y < y_min - 1 || nuevo.y > y_max + 1) break;
x = nuevo.x;
y = nuevo.y;
t += dt;
puntos.push({x: x, y: y, t: t});
} catch(e) {
break;
}
}
if (puntos.length > 1) {
orbitas_calculadas.push({
ic: ic,
puntos: puntos
});
}
}
return orbitas_calculadas;
}
// Parsear puntos de equilibrio
parsear_puntos_equilibrio = (puntos_str, f, g, buscar_auto, x_min, x_max, y_min, y_max) => {
const puntos = [];
// Puntos manuales
if (puntos_str && puntos_str.trim() !== '') {
const manuales = puntos_str
.split(';')
.map(s => {
const match = s.trim().match(/\[([^,]+),([^\]]+)\]/);
if (match) {
return {
x: parseFloat(match[1]),
y: parseFloat(match[2]),
tipo: 'manual'
};
}
return null;
})
.filter(p => p !== null && isFinite(p.x) && isFinite(p.y));
puntos.push(...manuales);
}
// Búsqueda automática
if (buscar_auto) {
const resolucion = 20;
const dx = (x_max - x_min) / resolucion;
const dy = (y_max - y_min) / resolucion;
const tolerancia = 0.05;
for (let x = x_min; x <= x_max; x += dx) {
for (let y = y_min; y <= y_max; y += dy) {
try {
const fx = f(x, y, 0);
const fy = g(x, y, 0);
if (Math.abs(fx) < tolerancia && Math.abs(fy) < tolerancia) {
const duplicado = puntos.some(p =>
Math.abs(p.x - x) < dx/2 && Math.abs(p.y - y) < dy/2
);
if (!duplicado) {
puntos.push({x: x, y: y, tipo: 'auto'});
}
}
} catch(e) {
continue;
}
}
}
}
return puntos;
}
// Crear gráfico de plano de fases
crear_plot_fases = (campo, orbitas, puntos_eq, x_min, x_max, y_min, y_max, mostrar_direccion, mostrar_condiciones_iniciales) => {
return Plot.plot({
width: 800,
height: 800,
marginLeft: 60,
marginBottom: 60,
grid: true,
x: {
label: "x →",
domain: [x_min, x_max]
},
y: {
label: "↑ y",
domain: [y_min, y_max]
},
marks: [
// Ejes coordenados
Plot.ruleX([0], {stroke: "black", strokeWidth: 1.5, opacity: 0.3}),
Plot.ruleY([0], {stroke: "black", strokeWidth: 1.5, opacity: 0.3}),
// Campo vectorial (flechas)
...campo.map(v =>
Plot.arrow([v], {
x1: "x1",
y1: "y1",
x2: "x2",
y2: "y2",
stroke: "#999",
strokeWidth: 1.5,
headLength: 8,
headAngle: 25,
opacity: 0.6
})
),
// Órbitas
...orbitas.map((orb, i) =>
Plot.line(orb.puntos, {
x: "x",
y: "y",
stroke: d3.schemeCategory10[i % 10],
strokeWidth: 3,
opacity: 0.8
})
),
// Flechas de dirección en órbitas
...(mostrar_direccion ?
orbitas.flatMap((orb, i) => {
const indices = [
Math.floor(orb.puntos.length * 0.25),
Math.floor(orb.puntos.length * 0.5),
Math.floor(orb.puntos.length * 0.75)
];
return indices.map(idx => {
if (idx >= orb.puntos.length - 1) return null;
const p1 = orb.puntos[idx];
const p2 = orb.puntos[idx + 1];
return Plot.arrow([{x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y}], {
x1: "x1",
y1: "y1",
x2: "x2",
y2: "y2",
stroke: d3.schemeCategory10[i % 10],
strokeWidth: 2.5,
headLength: 10,
headAngle: 30
});
}).filter(m => m !== null);
}) : []
),
// Condiciones iniciales (solo si mostrar_condiciones_iniciales es true)
...(mostrar_condiciones_iniciales ?
orbitas.map((orb, i) =>
Plot.dot([orb.ic], {
x: "x",
y: "y",
fill: d3.schemeCategory10[i % 10],
r: 6,
stroke: "white",
strokeWidth: 2
})
) : []
),
// Puntos de equilibrio
...(puntos_eq.length > 0 ?
[Plot.dot(puntos_eq, {
x: "x",
y: "y",
fill: "black",
r: 8,
stroke: "white",
strokeWidth: 3
})] : []
),
// Etiquetas de puntos de equilibrio
...(puntos_eq.length > 0 ?
puntos_eq.map(p =>
Plot.text([p], {
x: "x",
y: "y",
text: () => `(${p.x.toFixed(2)}, ${p.y.toFixed(2)})`,
fill: "black",
fontSize: 11,
fontWeight: "bold",
stroke: "white",
strokeWidth: 3,
dy: -15
})
) : []
)
]
});
}
// ===== FUNCIÓN MAESTRA: Visualizar sistema completo =====
visualizar_sistema = (params) => {
// Parámetros por defecto - ahora acepta TODOS los parámetros del visualizador interactivo
const config = {
// Sistema de ecuaciones
dx_dt: params.dx_dt || "0",
dy_dt: params.dy_dt || "0",
// Parámetros de visualización del campo vectorial
densidad: params.densidad ?? 20,
x_min: params.x_min ?? -3,
x_max: params.x_max ?? 3,
y_min: params.y_min ?? -3,
y_max: params.y_max ?? 3,
longitud_vectores: params.longitud_vectores ?? 0.35,
normalizar: params.normalizar ?? true,
// Parámetros de órbitas
mostrar_orbitas: params.mostrar_orbitas ?? true,
condiciones_iniciales: params.condiciones_iniciales ?? "[2,0]; [0,2]; [-2,0]; [0,-2]; [1,1]; [-1,-1]",
tiempo_max: params.tiempo_max ?? 5,
dt: params.dt ?? 0.05,
mostrar_direccion: params.mostrar_direccion ?? true,
mostrar_condiciones_iniciales: params.mostrar_condiciones_iniciales ?? true,
// Parámetros de puntos de equilibrio
buscar_equilibrios: params.buscar_equilibrios ?? false,
puntos_equilibrio: params.puntos_equilibrio ?? "[0,0]"
};
// Crear funciones del sistema
const f = crear_funcion_sistema(config.dx_dt);
const g = crear_funcion_sistema(config.dy_dt);
// Validar sistema
try {
const test_f = f(1, 1, 0);
const test_g = g(1, 1, 0);
if (!isFinite(test_f) || !isFinite(test_g)) {
return html`<div style="color: red; padding: 20px;">❌ Sistema no válido</div>`;
}
} catch(e) {
return html`<div style="color: red; padding: 20px;">❌ Error en el sistema: ${e.message}</div>`;
}
// Calcular campo vectorial
const campo = calcular_campo_vectorial(
f, g,
config.x_min, config.x_max,
config.y_min, config.y_max,
config.densidad,
config.longitud_vectores,
config.normalizar
);
// Calcular órbitas
const orbitas = config.mostrar_orbitas ?
calcular_orbitas(
f, g,
config.condiciones_iniciales,
config.tiempo_max, config.dt,
config.x_min, config.x_max,
config.y_min, config.y_max
) : [];
// Parsear puntos de equilibrio
const puntos_eq = parsear_puntos_equilibrio(
config.puntos_equilibrio,
f, g,
config.buscar_equilibrios,
config.x_min, config.x_max,
config.y_min, config.y_max
);
// Crear plot
return crear_plot_fases(
campo,
orbitas,
puntos_eq,
config.x_min, config.x_max,
config.y_min, config.y_max,
config.mostrar_direccion,
config.mostrar_condiciones_iniciales
);
}
```
```{ojs}
//| echo: false
//| label: controles-sistema
html`
<details open style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 15px; border: 2px solid #dee2e6;">
<summary style="cursor: pointer; font-size: 1.1em; font-weight: bold; color: #2c3e50; margin-bottom: 15px; user-select: none;">
⚙️ Sistema de Ecuaciones Diferenciales
</summary>
<div style="padding-top: 10px;">
${viewof dx_dt}
<div style="margin: 12px 0;"></div>
${viewof dy_dt}
<div style="margin: 15px 0;"></div>
${viewof ayuda_sistemas}
</div>
</details>
`
viewof dx_dt = Inputs.textarea({
label: "dx/dt = f(x,y)",
value: "-y",
placeholder: "Ejemplo: -y, x-x*x*x, -x+y",
rows: 2,
width: "100%"
})
viewof dy_dt = Inputs.textarea({
label: "dy/dt = g(x,y)",
value: "x",
placeholder: "Ejemplo: x, y-y*y*y, x-y",
rows: 2,
width: "100%"
})
viewof ayuda_sistemas = Inputs.toggle({
label: "Mostrar ayuda y ejemplos",
value: false
})
```
```{ojs}
//| echo: false
//| label: ayuda-sistemas-texto
html`${ayuda_sistemas ? `
<div style="background: #f8f9fa; padding: 15px; border-radius: 5px; margin: 10px 0;">
<h4 style="margin-top: 0;">📚 Ejemplos de sistemas:</h4>
<h5>🔄 Sistemas Lineales:</h5>
<ul style="margin: 5px 0;">
<li><strong>Centro (oscilador armónico):</strong> dx/dt = <code>-y</code>, dy/dt = <code>x</code></li>
<li><strong>Nodo estable:</strong> dx/dt = <code>-x</code>, dy/dt = <code>-2*y</code></li>
<li><strong>Nodo inestable:</strong> dx/dt = <code>x</code>, dy/dt = <code>2*y</code></li>
<li><strong>Silla de montar:</strong> dx/dt = <code>x</code>, dy/dt = <code>-y</code></li>
<li><strong>Espiral estable:</strong> dx/dt = <code>-x-y</code>, dy/dt = <code>x-y</code></li>
</ul>
<h5>🌀 Sistemas No Lineales:</h5>
<ul style="margin: 5px 0;">
<li><strong>Péndulo:</strong> dx/dt = <code>y</code>, dy/dt = <code>-Math.sin(x)</code></li>
<li><strong>Van der Pol:</strong> dx/dt = <code>y</code>, dy/dt = <code>(1-x*x)*y - x</code></li>
<li><strong>Lotka-Volterra (presa-depredador):</strong> dx/dt = <code>x*(1-y)</code>, dy/dt = <code>-y*(1-x)</code></li>
<li><strong>Duffing:</strong> dx/dt = <code>y</code>, dy/dt = <code>-0.1*y - x*x*x + x</code></li>
<li><strong>Ciclo límite:</strong> dx/dt = <code>y + x*(1-x*x-y*y)</code>, dy/dt = <code>-x + y*(1-x*x-y*y)</code></li>
</ul>
<h4>⚠️ Sintaxis importante:</h4>
<ul style="margin: 10px 0;">
<li>Usa <code>Math.sin()</code>, <code>Math.cos()</code>, <code>Math.exp()</code>, <code>Math.sqrt()</code></li>
<li>Multiplicación: <code>2*x</code>, <code>x*y</code></li>
<li>Potencias: <code>x*x</code> o <code>Math.pow(x,2)</code></li>
</ul>
</div>
` : ''}`
```
```{ojs}
//| echo: false
//| label: controles-visualizacion-fases
html`
<details style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 15px; border: 2px solid #dee2e6;">
<summary style="cursor: pointer; font-size: 1.1em; font-weight: bold; color: #2c3e50; margin-bottom: 15px; user-select: none;">
📐 Parámetros de Visualización
</summary>
<div style="padding-top: 10px;">
${viewof densidad_campo_fases}
<div style="margin: 12px 0;"></div>
${viewof x_min_fases}
<div style="margin: 8px 0;"></div>
${viewof x_max_fases}
<div style="margin: 8px 0;"></div>
${viewof y_min_fases}
<div style="margin: 8px 0;"></div>
${viewof y_max_fases}
<div style="margin: 12px 0;"></div>
${viewof longitud_vectores_fases}
<div style="margin: 12px 0;"></div>
${viewof normalizar_vectores}
</div>
</details>
`
viewof densidad_campo_fases = Inputs.range([5, 30], {
value: 20,
step: 1,
label: "Densidad del campo vectorial"
})
viewof x_min_fases = Inputs.range([-10, 0], {
value: -3,
step: 0.5,
label: "x mínimo"
})
viewof x_max_fases = Inputs.range([0, 10], {
value: 3,
step: 0.5,
label: "x máximo"
})
viewof y_min_fases = Inputs.range([-10, 0], {
value: -3,
step: 0.5,
label: "y mínimo"
})
viewof y_max_fases = Inputs.range([0, 10], {
value: 3,
step: 0.5,
label: "y máximo"
})
viewof longitud_vectores_fases = Inputs.range([0.2, 0.8], {
value: 0.35,
step: 0.05,
label: "Longitud de vectores"
})
viewof normalizar_vectores = Inputs.toggle({
label: "Normalizar vectores (longitud uniforme)",
value: true
})
```
```{ojs}
//| echo: false
//| label: controles-orbitas
html`
<details style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 15px; border: 2px solid #dee2e6;">
<summary style="cursor: pointer; font-size: 1.1em; font-weight: bold; color: #2c3e50; margin-bottom: 15px; user-select: none;">
🎯 Órbitas (Trayectorias)
</summary>
<div style="padding-top: 10px;">
${viewof mostrar_orbitas}
<div style="margin: 12px 0;"></div>
${viewof condiciones_iniciales}
<div style="margin: 12px 0;"></div>
${viewof tiempo_max}
<div style="margin: 8px 0;"></div>
${viewof dt_rk4}
<div style="margin: 12px 0;"></div>
${viewof mostrar_direccion}
<div style="margin: 12px 0;"></div>
${viewof mostrar_condiciones_iniciales}
</div>
</details>
`
viewof mostrar_orbitas = Inputs.toggle({
label: "Mostrar órbitas",
value: true
})
viewof condiciones_iniciales = Inputs.text({
label: "Condiciones iniciales [x,y] (separadas por punto y coma)",
value: "[2,0]; [0,2]; [-2,0]; [0,-2]; [1,1]; [-1,-1]",
placeholder: "[1,0]; [0,1]; [-1,0]",
disabled: !mostrar_orbitas,
width: "100%"
})
viewof tiempo_max = Inputs.range([.02, 20], {
value: 5,
step: .02,
label: "Tiempo de integración",
disabled: !mostrar_orbitas
})
viewof dt_rk4 = Inputs.range([0.01, 0.2], {
value: 0.05,
step: 0.01,
label: "Tamaño de paso (dt)",
disabled: !mostrar_orbitas
})
viewof mostrar_direccion = Inputs.toggle({
label: "Mostrar dirección del flujo (flechas en órbitas)",
value: true,
disabled: !mostrar_orbitas
})
viewof mostrar_condiciones_iniciales = Inputs.toggle({
label: "Mostrar condiciones iniciales (puntos de partida)",
value: true,
disabled: !mostrar_orbitas
})
```
```{ojs}
//| echo: false
//| label: controles-puntos-equilibrio
html`
<details style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px; border: 2px solid #dee2e6;">
<summary style="cursor: pointer; font-size: 1.1em; font-weight: bold; color: #2c3e50; margin-bottom: 15px; user-select: none;">
⚫ Puntos de Equilibrio
</summary>
<div style="padding-top: 10px;">
${viewof buscar_equilibrios}
<div style="margin: 12px 0;"></div>
${viewof puntos_equilibrio_manual}
</div>
</details>
`
viewof buscar_equilibrios = Inputs.toggle({
label: "Buscar puntos de equilibrio automáticamente",
value: false
})
viewof puntos_equilibrio_manual = Inputs.text({
label: "Puntos de equilibrio manuales [x,y] (separados por punto y coma)",
value: "[0,0]",
placeholder: "[0,0]; [1,1]",
width: "100%"
})
```
```{ojs}
//| echo: false
//| label: funciones-sistema
// Crear funciones del sistema usando la función común
f_sistema = crear_funcion_sistema(dx_dt)
g_sistema = crear_funcion_sistema(dy_dt)
// Verificar validez
sistema_valido = {
try {
const test_f = f_sistema(1, 1, 0);
const test_g = g_sistema(1, 1, 0);
return isFinite(test_f) && isFinite(test_g);
} catch(e) {
return false;
}
}
```
```{ojs}
//| echo: false
//| label: mensaje-error-sistema
html`${!sistema_valido ? `
<div style="background: #fff3cd; border: 1px solid #ffc107; padding: 10px; border-radius: 5px; margin: 10px 0;">
⚠️ <strong>Error en el sistema:</strong> Verifica la sintaxis de ambas ecuaciones.
</div>
` : ''}`
```
```{ojs}
//| echo: false
//| label: campo-vectorial-fases
campo_vectorial = {
if (!sistema_valido) return [];
return calcular_campo_vectorial(
f_sistema, g_sistema,
x_min_fases, x_max_fases,
y_min_fases, y_max_fases,
densidad_campo_fases,
longitud_vectores_fases,
normalizar_vectores
);
}
```
```{ojs}
//| echo: false
//| label: orbitas
orbitas = {
if (!sistema_valido || !mostrar_orbitas) return [];
return calcular_orbitas(
f_sistema, g_sistema,
condiciones_iniciales,
tiempo_max, dt_rk4,
x_min_fases, x_max_fases,
y_min_fases, y_max_fases
);
}
```
```{ojs}
//| echo: false
//| label: puntos-equilibrio
puntos_equilibrio = {
if (!sistema_valido) return [];
return parsear_puntos_equilibrio(
puntos_equilibrio_manual,
f_sistema, g_sistema,
buscar_equilibrios,
x_min_fases, x_max_fases,
y_min_fases, y_max_fases
);
}
```
```{ojs}
//| echo: false
//| label: grafico-plano-fases
{
if (!sistema_valido) return html`<div style="color: red;">Sistema no válido</div>`;
return crear_plot_fases(
campo_vectorial,
mostrar_orbitas ? orbitas : [],
puntos_equilibrio,
x_min_fases, x_max_fases,
y_min_fases, y_max_fases,
mostrar_direccion,
mostrar_condiciones_iniciales
);
}
```
```{ojs}
//| echo: false
//| label: estadisticas-fases
html`
<div style="background: #e7f3ff; padding: 15px; border-radius: 5px; margin-top: 20px;">
<h4 style="margin-top: 0;">📊 Información del sistema:</h4>
<ul style="margin: 10px 0;">
<li><strong>Sistema:</strong>
<code>dx/dt = ${dx_dt}</code><br>
<code style="margin-left: 48px;">dy/dt = ${dy_dt}</code>
</li>
<li><strong>Vectores dibujados:</strong> ${campo_vectorial.length}</li>
${mostrar_orbitas ? `<li><strong>Órbitas calculadas:</strong> ${orbitas.length}</li>` : ''}
${mostrar_orbitas ? `<li><strong>Puntos por órbita:</strong> ~${orbitas.length > 0 ? Math.floor(orbitas[0].puntos.length) : 0}</li>` : ''}
${mostrar_orbitas ? `<li><strong>Tiempo de integración:</strong> t ∈ [0, ${tiempo_max}]</li>` : ''}
${mostrar_orbitas ? `<li><strong>Paso de integración:</strong> dt = ${dt_rk4}</li>` : ''}
<li><strong>Puntos de equilibrio:</strong> ${puntos_equilibrio.length}</li>
<li><strong>Dominio:</strong> x ∈ [${x_min_fases}, ${x_max_fases}], y ∈ [${y_min_fases}, ${y_max_fases}]</li>
<li><strong>Método numérico:</strong> Runge-Kutta 4º orden (RK4)</li>
</ul>
</div>
`
```
:::
:::
## Galería de Sistemas Clásicos
::: {.content-visible when-format="pdf"}
{{< include _interactive_note.qmd >}}
:::
::: {.content-visible when-format="html"}
::: {.callout-note collapse="false" title="Sistemas Lineales y No Lineales"}
::: {.panel-tabset}
### Centro
**Sistema:** $\frac{dx}{dt} = -y$, $\frac{dy}{dt} = x$
**Tipo:** Centro estable (órbitas cerradas)
**Características:** Oscilaciones periódicas sin amortiguamiento.
```{ojs}
//| echo: false
visualizar_sistema({
dx_dt: "x-y",
dy_dt: "x",
condiciones_iniciales: "[2,0]; [1.5,0]; [1,0]; [0.5,0]; [0,2]; [0,1.5]; [0,1]",
x_min: -3,
x_max: 3,
y_min: -3,
y_max: 3,
densidad: 20,
longitud_vectores: 0.55,
normalizar: true,
mostrar_orbitas: true,
tiempo_max: 5,
dt: 0.15,
mostrar_direccion: true,
buscar_equilibrios: false,
puntos_equilibrio: "[0,0]"
})
```
**Observación:** Las trayectorias son círculos concéntricos alrededor del origen.
### Nodo Estable
**Sistema:** $\frac{dx}{dt} = -x$, $\frac{dy}{dt} = -2y$
**Tipo:** Nodo estable (sumidero)
**Características:** El sistema es asintóticamente estable.
```{ojs}
//| echo: false
visualizar_sistema({
dx_dt: "-x",
dy_dt: "-2*y",
condiciones_iniciales: "[2,1]; [2,-1]; [-2,1]; [-2,-1]; [1,2]; [-1,2]",
x_min: -3,
x_max: 3,
y_min: -3,
y_max: 3,
tiempo_max: 3
})
```
**Observación:** Todas las trayectorias convergen al origen.
### Nodo Inestable
**Sistema:** $\frac{dx}{dt} = x$, $\frac{dy}{dt} = 2y$
**Tipo:** Nodo inestable (fuente)
**Características:** El sistema es completamente inestable.
```{ojs}
//| echo: false
visualizar_sistema({
dx_dt: "x",
dy_dt: "2*y",
condiciones_iniciales: "[0.5,0.3]; [0.5,-0.3]; [-0.5,0.3]; [-0.5,-0.3]; [0.3,0.5]; [-0.3,0.5]",
x_min: -3,
x_max: 3,
y_min: -3,
y_max: 3,
tiempo_max: 2
})
```
**Observación:** Todas las trayectorias se alejan del origen.
### Punto de Silla
**Sistema:** $\frac{dx}{dt} = x$, $\frac{dy}{dt} = -y$
**Tipo:** Punto de silla (hiperbólico)
**Características:** Inestable con variedades estable (eje y) e inestable (eje x).
```{ojs}
//| echo: false
visualizar_sistema({
dx_dt: "x",
dy_dt: "-y",
condiciones_iniciales: "[2,0.5]; [2,-0.5]; [-2,0.5]; [-2,-0.5]; [0.5,2]; [0.5,-2]; [-0.5,2]; [-0.5,-2]",
x_min: -3,
x_max: 3,
y_min: -3,
y_max: 3,
tiempo_max: 2
})
```
**Observación:** Las trayectorias se alejan excepto en direcciones específicas.
### Espiral Estable
**Sistema:** $\frac{dx}{dt} = -x - y$, $\frac{dy}{dt} = x - y$
**Tipo:** Foco estable (espiral entrante)
**Características:** Combina oscilación con convergencia.
```{ojs}
//| echo: false
visualizar_sistema({
dx_dt: "-x - y",
dy_dt: "x - y",
condiciones_iniciales: "[2,0]; [0,2]; [-2,0]; [0,-2]; [1.5,1.5]; [-1.5,-1.5]",
x_min: -3,
x_max: 3,
y_min: -3,
y_max: 3,
tiempo_max: 8
})
```
**Observación:** Las trayectorias espiralan hacia el origen.
### Espiral Inestable
**Sistema:** $\frac{dx}{dt} = x - y$, $\frac{dy}{dt} = x + y$
**Tipo:** Foco inestable (espiral saliente)
**Características:** Oscilación con divergencia.
```{ojs}
//| echo: false
visualizar_sistema({
dx_dt: "x - y",
dy_dt: "x + y",
condiciones_iniciales: "[0.5,0]; [0,0.5]; [-0.5,0]; [0,-0.5]; [0.3,0.3]; [-0.3,-0.3]",
x_min: -3,
x_max: 3,
y_min: -3,
y_max: 3,
tiempo_max: 3
})
```
**Observación:** Las trayectorias espiralan alejándose del origen.
### Oscilador Armónico
**Sistema:**
$$\frac{dx}{dt} = -y, \quad \frac{dy}{dt} = x$$
**Tipo:** Centro (órbitas cerradas)
**Características:** Representa un oscilador sin amortiguamiento. La energía se conserva: $E = \frac{1}{2}(x^2 + y^2)$.
```{ojs}
//| echo: false
//| label: ejemplo-oscilador-armonico
visualizar_sistema({
dx_dt: "-y",
dy_dt: "x",
condiciones_iniciales: "[1,0]; [2,0]; [0.5,1]; [1.5,1.5]",
x_min: -3,
x_max: 3,
y_min: -3,
y_max: 3,
tiempo_max: 20,
dt: 0.05,
puntos_equilibrio: "[0,0]"
})
```
**Observación:** Órbitas circulares perfectas.
::: {.content-visible when-format="html"}
___
:::
### Péndulo
**Sistema:** $\frac{dx}{dt} = y$, $\frac{dy}{dt} = -\sin(x)$
**Tipo:** Sistema no lineal conservativo
**Características:** Péndulo simple sin fricción. Centros en $x = 0, \pm 2\pi, \ldots$ y sillas en $x = \pm \pi, \pm 3\pi, \ldots$
```{ojs}
//| echo: false
visualizar_sistema({
dx_dt: "y",
dy_dt: "-Math.sin(x)",
condiciones_iniciales: "[0.5,0]; [1,0]; [1.5,0]; [2,0]; [2.5,0]; [-0.5,0]; [-1,0]; [-1.5,0]; [-3.5,2.2]; [3.5,-2.2]",
x_min: -4,
x_max: 4,
y_min: -3,
y_max: 3,
tiempo_max: 15,
puntos_equilibrio: "[0,0]; [3.14159,0]; [-3.14159,0]"
})
```
### Lotka-Volterra
**Sistema:**
$$\frac{dx}{dt} = x(1-y), \quad \frac{dy}{dt} = -y(1-x)$$
**Tipo:** Sistema conservativo con órbitas cerradas
**Características:** Modela poblaciones de presas ($x$) y depredadores ($y$). Equilibrio en $(1,1)$.
```{ojs}
//| echo: false
//| label: ejemplo-lotka-volterra
visualizar_sistema({
dx_dt: "x*(1-y)",
dy_dt: "-y*(1-x)",
condiciones_iniciales: "[0.5,0.5]; [1.5,0.5]; [0.5,1.5]; [2,1]; [1,2]; [2.5,1.8]",
x_min: 0,
x_max: 3,
y_min: 0,
y_max: 3,
tiempo_max: 25,
dt: 0.03,
puntos_equilibrio: "[1,1]"
})
```
**Observación:** Las órbitas cerradas representan ciclos periódicos de poblaciones: cuando hay muchas presas, los depredadores crecen; cuando hay muchos depredadores, las presas disminuyen, y el ciclo se repite.
### Van der Pol
**Sistema:**
$$\frac{dx}{dt} = y, \quad \frac{dy}{dt} = (1-x^2)y - x$$
**Tipo:** Sistema con ciclo límite estable
**Características:** Todas las órbitas (excepto el origen) convergen al mismo ciclo periódico, independientemente de la condición inicial.
```{ojs}
//| echo: false
//| label: ejemplo-vanderpol
visualizar_sistema({
dx_dt: "y",
dy_dt: "(1 - x*x)*y - x",
condiciones_iniciales: "[0.3,0]; [1,0]; [2.5,0]; [0.1,0.1]; [3,2]",
x_min: -3.5,
x_max: 3.5,
y_min: -3.5,
y_max: 3.5,
densidad: 18,
tiempo_max: 35,
dt: 0.05,
puntos_equilibrio: "[0,0]"
})
```
**Observación:** Las trayectorias desde dentro espiralan hacia fuera, mientras que las de fuera espiralan hacia dentro, ¡todas convergen al mismo ciclo!
::: {.content-visible when-format="html"}
___
:::
### Duffing
**Sistema:**
$$\frac{dx}{dt} = y, \quad \frac{dy}{dt} = -0.1y + x - x^3$$
**Tipo:** Oscilador no lineal con dos pozos de potencial
**Características:** Tres equilibrios: $(0,0)$ inestable, $(±1, 0)$ estables.
```{ojs}
//| echo: false
//| label: ejemplo-duffing
visualizar_sistema({
dx_dt: "y",
dy_dt: "-0.1*y + x - x*x*x",
condiciones_iniciales: "[0.5,0]; [1.2,0]; [-0.5,0]; [-1.2,0]; [0.3,0.5]; [1.5,0.8]; [-1.5,-0.8]",
x_min: -2,
x_max: 2,
y_min: -1.5,
y_max: 1.5,
tiempo_max: 40,
dt: 0.05,
puntos_equilibrio: "[0,0]; [1,0]; [-1,0]"
})
```
**Observación:** Las órbitas cerca de $(±1, 0)$ oscilan en cada pozo. Las que cruzan el origen pueden transitar entre pozos.
::: {.content-visible when-format="html"}
___
:::
### Ciclo Límite
**Sistema:**
$$\frac{dx}{dt} = y + x(1-x^2-y^2), \quad \frac{dy}{dt} = -x + y(1-x^2-y^2)$$
**Tipo:** Sistema con ciclo límite estable
**Características:** Todas las trayectorias convergen al círculo unitario ($x^2 + y^2 = 1$).
```{ojs}
//| echo: false
//| label: ejemplo-ciclo-limite
visualizar_sistema({
dx_dt: "y + x*(1 - x*x - y*y)",
dy_dt: "-x + y*(1 - x*x - y*y)",
condiciones_iniciales: "[0.2,0]; [0.5,0]; [1.5,0]; [1.8,0]; [0.3,0.3]; [1.5,1.2]",
x_min: -2,
x_max: 2,
y_min: -2,
y_max: 2,
densidad: 18,
tiempo_max: 30,
dt: 0.04,
puntos_equilibrio: "[0,0]"
})
```
**Observación:** Las espirales desde dentro y fuera convergen al ciclo límite en $x^2+y^2=1$.
:::
:::
:::
::: {.content-visible when-format="html"}
___
:::
<!-- ## ¿Cómo funciona el Plano de Fases?
### Conceptos Fundamentales
El **plano de fases** es una herramienta visual poderosa para entender sistemas de ecuaciones diferenciales **sin resolverlas analíticamente**. Aquí te explico cómo interpretar lo que ves:
::: {.panel-tabset}
#### 1️⃣ El Espacio de Estados
**¿Qué representa cada punto del plano?**
Cada punto $(x, y)$ en el plano representa un **estado del sistema** en un momento dado:
- **Eje horizontal (x):** Primera variable del sistema
- **Eje vertical (y):** Segunda variable del sistema
- **Punto $(x_0, y_0)$:** El sistema está en el estado $x = x_0$, $y = y_0$
**Ejemplo:** Para un péndulo donde $x$ es la posición angular y $y$ es la velocidad angular:
- $(0, 0)$ = péndulo en reposo en posición vertical
- $(π, 0)$ = péndulo en reposo invertido (arriba)
- $(0, 2)$ = péndulo en posición vertical moviéndose con velocidad 2
**Importante:** El plano de fases NO muestra el tiempo explícitamente. El tiempo está "implícito" en el recorrido de las órbitas.
#### 2️⃣ El Campo Vectorial (flechas grises)
**¿Qué significan las flechas?**
Cada flecha en el punto $(x, y)$ representa el **vector velocidad** en ese estado:
$$\vec{v}(x, y) = \begin{pmatrix} \frac{dx}{dt} \\ \frac{dy}{dt} \end{pmatrix} = \begin{pmatrix} f(x, y) \\ g(x, y) \end{pmatrix}$$
**Interpretación:**
- **Dirección de la flecha:** Hacia dónde se mueve el sistema desde ese punto
- **Longitud de la flecha:** Qué tan rápido se mueve (si no está normalizado)
- **Flecha hacia la derecha:** $\frac{dx}{dt} > 0$ → $x$ aumenta
- **Flecha hacia arriba:** $\frac{dy}{dt} > 0$ → $y$ aumenta
#### 3️⃣ Las Órbitas (trayectorias de colores)
**¿Qué son las órbitas?**
Las **órbitas** (también llamadas trayectorias o curvas solución) son las curvas que recorre el sistema a lo largo del tiempo:
$$\gamma(t) = \big(x(t), y(t)\big)$$
**Propiedades clave:**
1. **Tangentes al campo:** En cada punto, la órbita es tangente a la flecha del campo vectorial
2. **No se cruzan:** Dos órbitas distintas nunca se intersectan (salvo en equilibrios)
3. **Sentido único:** Cada órbita tiene una dirección de recorrido (indicada por las flechas)
4. **Condición inicial:** Cada órbita comienza en un punto marcado con un círculo de color
**Cómo se calculan:**
Se calculan con un método númerico. En este caso se usa RK4.
#### 4️⃣ Puntos de Equilibrio (puntos negros)
Los **puntos de equilibrio** (o puntos críticos) son estados $(x^*, y^*)$ donde el sistema **no se mueve**:
$$\frac{dx}{dt} = f(x^*, y^*) = 0 \quad \text{y} \quad \frac{dy}{dt} = g(x^*, y^*) = 0$$
**Interpretación física:**
- Si el sistema está en $(x^*, y^*)$, permanece ahí para siempre
- Son estados estacionarios o de reposo
**Tipos de equilibrio:**
| Tipo | Comportamiento | Ejemplo visual |
|------|----------------|----------------|
| **Nodo estable** | Órbitas convergen radialmente | Todas las flechas apuntan hacia el punto |
| **Nodo inestable** | Órbitas divergen radialmente | Todas las flechas salen del punto |
| **Foco estable (espiral)** | Órbitas espiralan hacia el punto | Convergencia en espiral |
| **Foco inestable** | Órbitas espiralan alejándose | Divergencia en espiral |
| **Centro** | Órbitas cerradas alrededor | Ciclos periódicos |
| **Silla de montar** | Atrae en una dirección, repele en otra | Punto de inflexión |
**Cómo encontrarlos:**
- **Manual:** Resuelve $f(x, y) = 0$ y $g(x, y) = 0$ algebraicamente
- **Automático:** El visualizador busca puntos donde $|f| < \epsilon$ y $|g| < \epsilon$
#### 5️⃣ Comportamiento Asintótico
**¿Qué pasa cuando $t \to \infty$?**
Las órbitas pueden tener diferentes destinos:
**1. Converger a un equilibrio estable:**
```
Ejemplo: Nodo estable (-x, -2*y)
→ Todas las órbitas van al origen
```
**2. Diverger al infinito:**
```
Ejemplo: Nodo inestable (x, 2*y)
→ Las órbitas escapan indefinidamente
```
**3. Órbitas periódicas (ciclos):**
```
Ejemplo: Centro (-y, x)
→ Órbitas circulares que se repiten
```
**4. Ciclo límite:**
```
Ejemplo: Van der Pol
→ Todas las órbitas convergen a un ciclo específico
```
**5. Comportamiento caótico:**
```
Ejemplo: Sistemas de Lorenz (3D)
→ Trayectorias sensibles a condiciones iniciales
```
#### 6️⃣ Interpretación del Tiempo
**¿Dónde está el tiempo?**
El tiempo **no aparece explícitamente** en el plano de fases, pero está codificado en:
**1. Parametrización de la órbita:**
$$\gamma(t) = (x(t), y(t))$$
- $t = 0$: Punto inicial (círculo de color)
- $t$ aumenta: Recorrido siguiendo las flechas
- Velocidad del recorrido: Magnitud del campo vectorial
**2. Densidad de puntos:**
- **Puntos muy separados:** El sistema se mueve rápido (campo vectorial grande)
- **Puntos muy juntos:** El sistema se mueve lento (campo vectorial pequeño)
**3. Flechas direccionales en órbitas:**
- Indican el sentido de evolución temporal
- Colocadas en 25%, 50% y 75% del tiempo total
**Ejemplo:**
Para el oscilador armónico con órbita circular de radio $R$:
- Período: $T = 2π$
- Velocidad angular: $ω = 1$
- El sistema tarda $T$ segundos en dar una vuelta completa
#### 7️⃣ Propiedades Topológicas
**Teoremas importantes:**
**1. Teorema de existencia y unicidad:**
- Por cada punto $(x_0, y_0)$ pasa **exactamente una** órbita
- Las órbitas no se cruzan (salvo en equilibrios)
**2. Teorema de Poincaré-Bendixson (sistemas 2D):**
- Si una órbita está acotada y no va a un equilibrio → converge a un ciclo límite
- No puede haber caos en sistemas 2D autónomos
**3. Linealización (Teorema de Hartman-Grobman):**
- Cerca de un equilibrio hiperbólico, el sistema se comporta como su linealización
- Los valores propios determinan el tipo de equilibrio
**4. Conservación de volumen (sistemas Hamiltonianos):**
- Si $\frac{\partial f}{\partial x} + \frac{\partial g}{\partial y} = 0$ → el flujo conserva volumen
- Ejemplo: Lotka-Volterra tiene órbitas cerradas
:::
### Flujo de Trabajo Recomendado
Para analizar un sistema nuevo:
1. **Define el sistema** en los campos de texto
2. **Observa el campo vectorial** para intuir el comportamiento
3. **Busca equilibrios** (manual o automático)
4. **Prueba órbitas** desde diferentes condiciones iniciales:
- Cerca de equilibrios
- Lejos de equilibrios
- En diferentes cuadrantes
5. **Clasifica el comportamiento** (convergencia, divergencia, periódico)
6. **Ajusta parámetros** (rango, densidad) para mejor visualización
-->
## Método Numérico: Runge-Kutta 4
### ¿Por qué RK4 en lugar de Euler?
Para integrar un sistema de EDOs y calcular órbitas precisas, necesitamos un método numérico robusto. He elegido **Runge-Kutta de 4º orden (RK4)** por las siguientes razones:
::: {.panel-tabset}
#### Comparación de Precisión
| Método | Error Local | Error Global | Evaluaciones por paso |
|--------|-------------|--------------|----------------------|
| **Euler** | $O(h^2)$ | $O(h)$ | 1 |
| **RK2 (punto medio)** | $O(h^3)$ | $O(h^2)$ | 2 |
| **RK4** | $O(h^5)$ | $O(h^4)$ | 4 |
**Interpretación:**
- Con $h = 0.1$, Euler tiene error $\sim 0.1$
- Con el mismo paso, RK4 tiene error $\sim 0.0001$ (¡1000 veces menor!)
#### Ventajas de RK4
**1. Precisión superior:**
- Para el mismo tamaño de paso $h$, RK4 es órdenes de magnitud más preciso
- Permite usar pasos más grandes sin perder precisión
- Reduce la acumulación de error en trayectorias largas
**2. Estabilidad:**
- Menos sensible a la elección del paso $h$
- Maneja mejor sistemas rígidos (stiff)
- No diverge tan fácilmente como Euler
**3. Visualización de calidad:**
- Las órbitas son suaves y precisas
- Los ciclos límite se cierran correctamente
- Los equilibrios se mantienen estables
**4. Eficiencia relativa:**
- Aunque requiere 4 evaluaciones por paso vs 1 de Euler
- Puedes usar pasos 5-10 veces mayores
- Resultado: similar coste computacional pero mucho más preciso
#### Inconvenientes de Euler
El **método de Euler** ($y_{n+1} = y_n + h \cdot f(y_n)$) tiene problemas serios:
**❌ Problema 1: Acumulación de error**
```
Tras 1000 pasos:
- Euler: error acumulado ≈ 1000 × O(h²) = O(h)
- RK4: error acumulado ≈ 1000 × O(h⁵) = O(h⁴)
```
**❌ Problema 2: Inestabilidad numérica**
- En sistemas oscilatorios (como el armónico), Euler introduce **espirales espurias**
- Las órbitas cerradas se convierten en espirales divergentes
- Ejemplo: $dx/dt = -y, dy/dt = x$ con Euler genera espirales, no círculos
**❌ Problema 3: Conservación de energía**
- Sistemas conservativos (Hamiltoniano) pierden/ganan energía artificialmente
- Las órbitas cambian de tamaño con el tiempo
#### Fórmulas de RK4
Para un sistema $\frac{dx}{dt} = f(x,y)$, $\frac{dy}{dt} = g(x,y)$:
**Paso 1:** Evaluar en el punto actual
$$k_1^x = f(x_n, y_n), \quad k_1^y = g(x_n, y_n)$$
**Paso 2:** Evaluar en el punto medio usando $k_1$
$$k_2^x = f(x_n + \tfrac{h}{2}k_1^x, y_n + \tfrac{h}{2}k_1^y), \quad k_2^y = g(x_n + \tfrac{h}{2}k_1^x, y_n + \tfrac{h}{2}k_1^y)$$
**Paso 3:** Evaluar en el punto medio usando $k_2$
$$k_3^x = f(x_n + \tfrac{h}{2}k_2^x, y_n + \tfrac{h}{2}k_2^y), \quad k_3^y = g(x_n + \tfrac{h}{2}k_2^x, y_n + \tfrac{h}{2}k_2^y)$$
**Paso 4:** Evaluar en el punto final usando $k_3$
$$k_4^x = f(x_n + hk_3^x, y_n + hk_3^y), \quad k_4^y = g(x_n + hk_3^x, y_n + hk_3^y)$$
**Actualización:** Promedio ponderado
\begin{align*}
x_{n+1} &= x_n + \frac{h}{6}(k_1^x + 2k_2^x + 2k_3^x + k_4^x) \\
y_{n+1} &= y_n + \frac{h}{6}(k_1^y + 2k_2^y + 2k_3^y + k_4^y)
\end{align*}
**Interpretación:** RK4 aproxima la solución usando el desarrollo de Taylor hasta orden 4, pero sin calcular derivadas explícitamente.
#### Ejemplo Numérico
**Sistema:** Oscilador armónico $\frac{dx}{dt} = -y$, $\frac{dy}{dt} = x$
**Solución exacta:** $x(t) = \cos(t)$, $y(t) = \sin(t)$ (círculo unitario)
**Con Euler ($h=0.1$, 100 pasos = $t=10$):**
- Error en posición: $\sim 1.5$ (¡150% del radio!)
- La órbita diverge como espiral
**Con RK4 ($h=0.1$, 100 pasos = $t=10$):**
- Error en posición: $\sim 0.0001$ (0.01% del radio)
- La órbita permanece circular
#### ¿Cuándo usar otros métodos?
**Euler:** Solo para visualizaciones muy rápidas o didácticas (mostrar problemas de estabilidad)
**RK2 (Heun, punto medio):** Equilibrio entre precisión y velocidad para sistemas simples
**RK4:** **Recomendado para la mayoría de aplicaciones** (usado en este visualizador)
**RK45 (Dormand-Prince):** Paso adaptativo, óptimo para sistemas con diferentes escalas de tiempo
**Métodos implícitos (BDF):** Sistemas rígidos (stiff), ecuaciones químicas, circuitos
:::
### Código del método RK4
El método está implementado en la función `rk4_step`:
```javascript
rk4_step = (x, y, t, dt, f, g) => {
// k1: pendiente en el punto actual
const k1_x = f(x, y, t);
const k1_y = g(x, y, t);
// k2: pendiente en el punto medio (usando k1)
const k2_x = f(x + dt/2 * k1_x, y + dt/2 * k1_y, t + dt/2);
const k2_y = g(x + dt/2 * k1_x, y + dt/2 * k1_y, t + dt/2);
// k3: pendiente en el punto medio (usando k2)
const k3_x = f(x + dt/2 * k2_x, y + dt/2 * k2_y, t + dt/2);
const k3_y = g(x + dt/2 * k2_x, y + dt/2 * k2_y, t + dt/2);
// k4: pendiente en el punto final (usando k3)
const k4_x = f(x + dt * k3_x, y + dt * k3_y, t + dt);
const k4_y = g(x + dt * k3_x, y + dt * k3_y, t + dt);
// Promedio ponderado: más peso a k2 y k3 (puntos medios)
return {
x: x + dt/6 * (k1_x + 2*k2_x + 2*k3_x + k4_x),
y: y + dt/6 * (k1_y + 2*k2_y + 2*k3_y + k4_y)
};
}
```
<!-- ### Referencias y Lecturas
::: {.callout-note title="Para profundizar"}
**Textos clásicos:**
- Butcher, J.C. (2016). *Numerical Methods for Ordinary Differential Equations*
- Hairer, E., Nørsett, S.P., & Wanner, G. (1993). *Solving Ordinary Differential Equations I*
**Conceptos relacionados:**
- Métodos de Runge-Kutta de orden superior (RK5, RK6)
- Métodos adaptativos (RK45, RKF78)
- Métodos symplectic (conservación de energía)
- Backward Euler y métodos implícitos
:::
::: {.content-visible when-format="html"}
___
:::
::: {.content-visible when-format="html"}
___
:::
## Ejercicios Propuestos
::: {.callout-tip title="Explora y Aprende"}
1. **Clasifica el equilibrio** del sistema lineal general:
$$\frac{dx}{dt} = ax + by, \quad \frac{dy}{dt} = cx + dy$$
Prueba diferentes valores de $(a,b,c,d)$ y observa:
- Nodo: $b=0, c=0, a<0, d<0$
- Silla: $a>0, d<0$ (o viceversa)
- Foco: $a<0, b\neq 0, c\neq 0$
2. **Péndulo con amortiguamiento:**
- `dx/dt = y`
- `dy/dt = -0.1*y - Math.sin(x)`
- ¿Cómo cambian las órbitas respecto al péndulo sin fricción?
3. **Bifurcaciones en Van der Pol:**
- Introduce un parámetro $\mu$: `dy/dt = mu*(1-x*x)*y - x`
- Prueba $\mu = 0.1, 1, 5$
- ¿Cómo afecta al ciclo límite?
4. **Encuentra equilibrios manualmente:**
- Para Lotka-Volterra, calcula algebraicamente los equilibrios
- Verifica con el visualizador
5. **Diseña tu propio sistema:**
- Crea un sistema con comportamiento interesante
- ¿Puedes crear un sistema con múltiples atractores?
:::
::: {.content-visible when-format="html"}
___
:::
## Recursos Adicionales
::: {.callout-note title="Para Profundizar"}
**Teoría:**
- Análisis de estabilidad (valores propios)
- Teorema de Hartman-Grobman (linealización)
- Teoría de bifurcaciones
- Ciclos límite y teorema de Poincaré-Bendixson
**Software especializado:**
- PPLANE (MATLAB)
- XPP/XPPAUT
- DynamicalSystems.jl (Julia)
- PhasePortrait (Python)
::: -->
::: {.content-visible when-format="html"}
___
:::
# Clasificación de Sistemas Lineales 2D
Para un sistema lineal **con coeficientes constantes**:
$$
\begin{cases}
\frac{dx}{dt} = ax + by \\
\frac{dy}{dt} = cx + dy
\end{cases}
\quad \Leftrightarrow \quad
\frac{d\vec{x}}{dt} = A\vec{x}, \quad A = \begin{pmatrix} a & b \\ c & d \end{pmatrix}
$$
El comportamiento del sistema está **completamente determinado** por los **autovalores** de la matriz $A$:
$$
\det(A - \lambda I) = 0 \quad \Rightarrow \quad \lambda^2 - \text{tr}(A)\lambda + \det(A) = 0
$$
donde $\text{tr}(A) = a + d$ (traza) y $\det(A) = ad - bc$ (determinante).
## Casos
La clasificación completa según los autovalores $\lambda_1, \lambda_2$:
| Caso | Tipo | Autovalores | Condición |
|------|------|-------------|-----------|
| **I** | Nodo (estable/inestable) | Reales distintos, mismo signo | $\Delta > 0$, $\det > 0$ |
| **II** | Punto silla | Reales, signos opuestos | $\Delta > 0$, $\det < 0$ |
| **III** | Nodo estrella | Real doble, mult. geom. 2 | $A = \lambda I$ |
| **IV** | Nodo impropio | Real doble, mult. geom. 1 | $\Delta = 0$, $A \neq \lambda I$ |
| **V** | Centro | Complejos puros | $\Delta < 0$, $\text{tr}(A) = 0$ |
| **VI** | Foco/Espiral | Complejos con parte real | $\Delta < 0$, $\text{tr}(A) \neq 0$ |
| **VII** | Degenerado | Al menos un autovalor cero | $\det = 0$ |
donde $\Delta = \text{tr}^2(A) - 4\det(A)$ es el discriminante.
::: {.content-visible when-format="html"}
::: {.callout-note collapse="false" title="Clasificación Completa de Sistemas Lineales 2D"}
::: {.panel-tabset}
### Caso I: Nodo
**Autovalores reales distintos, mismo signo**: $\lambda_1 < \lambda_2 < 0$ o $0 < \lambda_1 < \lambda_2$
**Ecuación característica**: $\Delta = \text{tr}^2 - 4\det > 0$ y $\det > 0$
**Comportamiento:**
- **Nodo estable**: $\lambda_1, \lambda_2 < 0$ → Todas las órbitas convergen al origen
- **Nodo inestable**: $\lambda_1, \lambda_2 > 0$ → Todas las órbitas divergen del origen
**Direcciones principales**: Los autovectores marcan las direcciones de máxima/mínima convergencia.
```{ojs}
//| echo: false
//| label: caso-nodo
viewof invertir_nodo = Inputs.toggle({
label: "🔄 Invertir signo (estable ⟷ inestable)",
value: false
})
// Sistema nodo (estable por defecto)
f_nodo = (x, y, t) => invertir_nodo ? x : -x
g_nodo = (x, y, t) => invertir_nodo ? 2*y : -2*y
campo_nodo = {
const vectores = [];
const [x_min, x_max, y_min, y_max] = [-3, 3, -3, 3];
const densidad = 20;
const dx = (x_max - x_min) / densidad;
const dy = (y_max - y_min) / densidad;
for (let x = x_min; x <= x_max; x += dx) {
for (let y = y_min; y <= y_max; y += dy) {
const vx = f_nodo(x, y, 0);
const vy = g_nodo(x, y, 0);
const magnitud = Math.sqrt(vx*vx + vy*vy);
if (magnitud === 0) continue;
const scale = Math.min(dx, dy) * 0.35;
const dx_vec = (vx / magnitud) * scale;
const dy_vec = (vy / magnitud) * scale;
vectores.push({x1: x, y1: y, x2: x + dx_vec, y2: y + dy_vec});
}
}
return vectores;
}
orbitas_nodo = {
const condiciones = [[2,0.5], [2,-0.5], [0.5,2], [-0.5,2], [-2,0.5], [-2,-0.5], [0.5,-2], [-0.5,-2]];
const orbitas_calc = [];
const dt = 0.05;
const tiempo_max = 10;
const pasos = Math.floor(tiempo_max / dt);
for (const [x0, y0] of condiciones) {
const puntos = [];
let x = x0, y = y0;
for (let i = 0; i < pasos; i++) {
puntos.push({x: x, y: y});
const k1_x = f_nodo(x, y, 0);
const k1_y = g_nodo(x, y, 0);
const k2_x = f_nodo(x + dt/2 * k1_x, y + dt/2 * k1_y, 0);
const k2_y = g_nodo(x + dt/2 * k1_x, y + dt/2 * k1_y, 0);
const k3_x = f_nodo(x + dt/2 * k2_x, y + dt/2 * k2_y, 0);
const k3_y = g_nodo(x + dt/2 * k2_x, y + dt/2 * k2_y, 0);
const k4_x = f_nodo(x + dt * k3_x, y + dt * k3_y, 0);
const k4_y = g_nodo(x + dt * k3_x, y + dt * k3_y, 0);
x = x + dt/6 * (k1_x + 2*k2_x + 2*k3_x + k4_x);
y = y + dt/6 * (k1_y + 2*k2_y + 2*k3_y + k4_y);
if (Math.abs(x) > 10 || Math.abs(y) > 10) break;
}
orbitas_calc.push({ic: {x: x0, y: y0}, puntos: puntos});
}
return orbitas_calc;
}
Plot.plot({
width: 600,
height: 600,
marginLeft: 50,
marginBottom: 50,
grid: true,
x: {label: "x →", domain: [-3, 3]},
y: {label: "↑ y", domain: [-3, 3]},
marks: [
Plot.ruleX([0], {stroke: "black", strokeWidth: 1.5, opacity: 0.3}),
Plot.ruleY([0], {stroke: "black", strokeWidth: 1.5, opacity: 0.3}),
...campo_nodo.map(v => Plot.arrow([v], {
x1: "x1", y1: "y1", x2: "x2", y2: "y2",
stroke: "#999", strokeWidth: 1.5, headLength: 6, opacity: 0.5
})),
...orbitas_nodo.map((orb, i) => Plot.line(orb.puntos, {
x: "x", y: "y", stroke: d3.schemeCategory10[i], strokeWidth: 2.5
})),
...orbitas_nodo.map((orb, i) => Plot.dot([orb.ic], {
x: "x", y: "y", fill: d3.schemeCategory10[i], r: 5, stroke: "white", strokeWidth: 2
})),
Plot.dot([{x:0, y:0}], {fill: "black", r: 8, stroke: "white", strokeWidth: 3})
]
})
```
**Matriz ejemplo**:
$$A = \begin{pmatrix} -1 & 0 \\ 0 & -2 \end{pmatrix} \quad \Rightarrow \quad \lambda_1 = -1, \, \lambda_2 = -2$$
**Autovectores**: $\vec{v}_1 = \begin{pmatrix} 1 \\ 0 \end{pmatrix}$, $\vec{v}_2 = \begin{pmatrix} 0 \\ 1 \end{pmatrix}$
::: {.content-visible when-format="html"}
___
:::
### Caso II: Punto Silla
**Autovalores reales de signo opuesto**: $\lambda_1 < 0 < \lambda_2$
**Ecuación característica**: $\Delta > 0$ y $\det < 0$
**Comportamiento:**
- Atrae en la dirección del autovector con $\lambda < 0$ (variedad estable)
- Repele en la dirección del autovector con $\lambda > 0$ (variedad inestable)
- Todas las órbitas (excepto las variedades) escapan al infinito
```{ojs}
//| echo: false
//| label: caso-silla
// Sistema silla (punto de silla)
f_silla = (x, y, t) => x
g_silla = (x, y, t) => -y
campo_silla = {
const vectores = [];
const [x_min, x_max, y_min, y_max] = [-3, 3, -3, 3];
const densidad = 20;
const dx = (x_max - x_min) / densidad;
const dy = (y_max - y_min) / densidad;
for (let x = x_min; x <= x_max; x += dx) {
for (let y = y_min; y <= y_max; y += dy) {
const vx = f_silla(x, y, 0);
const vy = g_silla(x, y, 0);
const magnitud = Math.sqrt(vx*vx + vy*vy);
if (magnitud === 0) continue;
const scale = Math.min(dx, dy) * 0.35;
const dx_vec = (vx / magnitud) * scale;
const dy_vec = (vy / magnitud) * scale;
vectores.push({x1: x, y1: y, x2: x + dx_vec, y2: y + dy_vec});
}
}
return vectores;
}
orbitas_silla = {
const condiciones = [[2,0.3], [2,-0.3], [-2,0.3], [-2,-0.3], [0.3,2], [-0.3,2], [0.3,-2], [-0.3,-2]];
const orbitas_calc = [];
const dt = 0.03;
const tiempo_max = 8;
const pasos = Math.floor(tiempo_max / dt);
for (const [x0, y0] of condiciones) {
const puntos = [];
let x = x0, y = y0;
for (let i = 0; i < pasos; i++) {
puntos.push({x: x, y: y});
const k1_x = f_silla(x, y, 0);
const k1_y = g_silla(x, y, 0);
const k2_x = f_silla(x + dt/2 * k1_x, y + dt/2 * k1_y, 0);
const k2_y = g_silla(x + dt/2 * k1_x, y + dt/2 * k1_y, 0);
const k3_x = f_silla(x + dt/2 * k2_x, y + dt/2 * k2_y, 0);
const k3_y = g_silla(x + dt/2 * k2_x, y + dt/2 * k2_y, 0);
const k4_x = f_silla(x + dt * k3_x, y + dt * k3_y, 0);
const k4_y = g_silla(x + dt * k3_x, y + dt * k3_y, 0);
x = x + dt/6 * (k1_x + 2*k2_x + 2*k3_x + k4_x);
y = y + dt/6 * (k1_y + 2*k2_y + 2*k3_y + k4_y);
if (Math.abs(x) > 5 || Math.abs(y) > 5) break;
}
orbitas_calc.push({ic: {x: x0, y: y0}, puntos: puntos});
}
return orbitas_calc;
}
Plot.plot({
width: 600,
height: 600,
marginLeft: 50,
marginBottom: 50,
grid: true,
x: {label: "x →", domain: [-3, 3]},
y: {label: "↑ y", domain: [-3, 3]},
marks: [
Plot.ruleX([0], {stroke: "black", strokeWidth: 1.5, opacity: 0.3}),
Plot.ruleY([0], {stroke: "black", strokeWidth: 1.5, opacity: 0.3}),
...campo_silla.map(v => Plot.arrow([v], {
x1: "x1", y1: "y1", x2: "x2", y2: "y2",
stroke: "#999", strokeWidth: 1.5, headLength: 6, opacity: 0.5
})),
...orbitas_silla.map((orb, i) => Plot.line(orb.puntos, {
x: "x", y: "y", stroke: d3.schemeCategory10[i], strokeWidth: 2.5
})),
...orbitas_silla.map((orb, i) => Plot.dot([orb.ic], {
x: "x", y: "y", fill: d3.schemeCategory10[i], r: 5, stroke: "white", strokeWidth: 2
})),
Plot.dot([{x:0, y:0}], {fill: "black", r: 8, stroke: "white", strokeWidth: 3})
]
})
```
**Matriz ejemplo**:
$$A = \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix} \quad \Rightarrow \quad \lambda_1 = 1, \, \lambda_2 = -1$$
::: {.content-visible when-format="html"}
___
:::
### Caso III: Nodo Estrella
**Autovalor real doble con multiplicidad geométrica 2**: $\lambda_1 = \lambda_2 = \lambda$
**Condición**: $A = \lambda I$ (matriz escalar)
**Comportamiento:**
- Todas las direcciones son direcciones propias
- Las órbitas son **líneas rectas** hacia/desde el origen
- **Nodo estrella estable**: $\lambda < 0$
- **Nodo estrella inestable**: $\lambda > 0$
```{ojs}
//| echo: false
//| label: caso-estrella
viewof invertir_estrella = Inputs.toggle({
label: "🔄 Invertir signo (estable ⟷ inestable)",
value: false
})
// Sistema estrella (nodo impropio)
f_estrella = (x, y, t) => invertir_estrella ? x : -x
g_estrella = (x, y, t) => invertir_estrella ? y : -y
campo_estrella = {
const vectores = [];
const [x_min, x_max, y_min, y_max] = [-3, 3, -3, 3];
const densidad = 20;
const dx = (x_max - x_min) / densidad;
const dy = (y_max - y_min) / densidad;
for (let x = x_min; x <= x_max; x += dx) {
for (let y = y_min; y <= y_max; y += dy) {
const vx = f_estrella(x, y, 0);
const vy = g_estrella(x, y, 0);
const magnitud = Math.sqrt(vx*vx + vy*vy);
if (magnitud === 0) continue;
const scale = Math.min(dx, dy) * 0.35;
const dx_vec = (vx / magnitud) * scale;
const dy_vec = (vy / magnitud) * scale;
vectores.push({x1: x, y1: y, x2: x + dx_vec, y2: y + dy_vec});
}
}
return vectores;
}
orbitas_estrella = {
const angulos = [0, 45, 90, 135, 180, 225, 270, 315];
const orbitas_calc = [];
const dt = 0.05;
const tiempo_max = 10;
const pasos = Math.floor(tiempo_max / dt);
for (const ang of angulos) {
const rad = ang * Math.PI / 180;
const x0 = 2.5 * Math.cos(rad);
const y0 = 2.5 * Math.sin(rad);
const puntos = [];
let x = x0, y = y0;
for (let i = 0; i < pasos; i++) {
puntos.push({x: x, y: y});
const k1_x = f_estrella(x, y, 0);
const k1_y = g_estrella(x, y, 0);
const k2_x = f_estrella(x + dt/2 * k1_x, y + dt/2 * k1_y, 0);
const k2_y = g_estrella(x + dt/2 * k1_x, y + dt/2 * k1_y, 0);
const k3_x = f_estrella(x + dt/2 * k2_x, y + dt/2 * k2_y, 0);
const k3_y = g_estrella(x + dt/2 * k2_x, y + dt/2 * k2_y, 0);
const k4_x = f_estrella(x + dt * k3_x, y + dt * k3_y, 0);
const k4_y = g_estrella(x + dt * k3_x, y + dt * k3_y, 0);
x = x + dt/6 * (k1_x + 2*k2_x + 2*k3_x + k4_x);
y = y + dt/6 * (k1_y + 2*k2_y + 2*k3_y + k4_y);
if (Math.abs(x) < 0.01 && Math.abs(y) < 0.01) break;
}
orbitas_calc.push({ic: {x: x0, y: y0}, puntos: puntos});
}
return orbitas_calc;
}
Plot.plot({
width: 600,
height: 600,
marginLeft: 50,
marginBottom: 50,
grid: true,
x: {label: "x →", domain: [-3, 3]},
y: {label: "↑ y", domain: [-3, 3]},
marks: [
Plot.ruleX([0], {stroke: "black", strokeWidth: 1.5, opacity: 0.3}),
Plot.ruleY([0], {stroke: "black", strokeWidth: 1.5, opacity: 0.3}),
...campo_estrella.map(v => Plot.arrow([v], {
x1: "x1", y1: "y1", x2: "x2", y2: "y2",
stroke: "#999", strokeWidth: 1.5, headLength: 6, opacity: 0.5
})),
...orbitas_estrella.map((orb, i) => Plot.line(orb.puntos, {
x: "x", y: "y", stroke: d3.schemeCategory10[i], strokeWidth: 2.5
})),
...orbitas_estrella.map((orb, i) => Plot.dot([orb.ic], {
x: "x", y: "y", fill: d3.schemeCategory10[i], r: 5, stroke: "white", strokeWidth: 2
})),
Plot.dot([{x:0, y:0}], {fill: "black", r: 8, stroke: "white", strokeWidth: 3})
]
})
```
**Matriz ejemplo**:
$$A = \begin{pmatrix} -1 & 0 \\ 0 & -1 \end{pmatrix} = -I \quad \Rightarrow \quad \lambda_1 = \lambda_2 = -1$$
::: {.content-visible when-format="html"}
___
:::
### Caso IV: Nodo Impropio
**Autovalor real doble con multiplicidad geométrica 1**: $\lambda_1 = \lambda_2 = \lambda$ pero $A \neq \lambda I$
**Condición**: Existe un solo autovector linealmente independiente (bloque de Jordan)
**Comportamiento:**
- Una dirección especial (autovector)
- Las demás órbitas se curvan hacia esa dirección
- **Nodo impropio estable**: $\lambda < 0$
- **Nodo impropio inestable**: $\lambda > 0$
```{ojs}
//| echo: false
//| label: caso-impropio
viewof invertir_impropio = Inputs.toggle({
label: "🔄 Invertir signo (estable ⟷ inestable)",
value: false
})
// Sistema nodo impropio (un autovector)
f_impropio = (x, y, t) => invertir_impropio ? (x + y) : -(x + y)
g_impropio = (x, y, t) => invertir_impropio ? y : -y
campo_impropio = {
const vectores = [];
const [x_min, x_max, y_min, y_max] = [-3, 3, -3, 3];
const densidad = 20;
const dx = (x_max - x_min) / densidad;
const dy = (y_max - y_min) / densidad;
for (let x = x_min; x <= x_max; x += dx) {
for (let y = y_min; y <= y_max; y += dy) {
const vx = f_impropio(x, y, 0);
const vy = g_impropio(x, y, 0);
const magnitud = Math.sqrt(vx*vx + vy*vy);
if (magnitud === 0) continue;
const scale = Math.min(dx, dy) * 0.35;
const dx_vec = (vx / magnitud) * scale;
const dy_vec = (vy / magnitud) * scale;
vectores.push({x1: x, y1: y, x2: x + dx_vec, y2: y + dy_vec});
}
}
return vectores;
}
orbitas_impropio = {
const condiciones = [[2,0.5], [2,-0.5], [1.5,1.5], [-2,0.5], [-2,-0.5], [-1.5,-1.5], [0.5,2], [-0.5,-2]];
const orbitas_calc = [];
const dt = 0.05;
const tiempo_max = 12;
const pasos = Math.floor(tiempo_max / dt);
for (const [x0, y0] of condiciones) {
const puntos = [];
let x = x0, y = y0;
for (let i = 0; i < pasos; i++) {
puntos.push({x: x, y: y});
const k1_x = f_impropio(x, y, 0);
const k1_y = g_impropio(x, y, 0);
const k2_x = f_impropio(x + dt/2 * k1_x, y + dt/2 * k1_y, 0);
const k2_y = g_impropio(x + dt/2 * k1_x, y + dt/2 * k1_y, 0);
const k3_x = f_impropio(x + dt/2 * k2_x, y + dt/2 * k2_y, 0);
const k3_y = g_impropio(x + dt/2 * k2_x, y + dt/2 * k2_y, 0);
const k4_x = f_impropio(x + dt * k3_x, y + dt * k3_y, 0);
const k4_y = g_impropio(x + dt * k3_x, y + dt * k3_y, 0);
x = x + dt/6 * (k1_x + 2*k2_x + 2*k3_x + k4_x);
y = y + dt/6 * (k1_y + 2*k2_y + 2*k3_y + k4_y);
if (Math.abs(x) > 10 || Math.abs(y) > 10) break;
if (Math.abs(x) < 0.01 && Math.abs(y) < 0.01) break;
}
orbitas_calc.push({ic: {x: x0, y: y0}, puntos: puntos});
}
return orbitas_calc;
}
Plot.plot({
width: 600,
height: 600,
marginLeft: 50,
marginBottom: 50,
grid: true,
x: {label: "x →", domain: [-3, 3]},
y: {label: "↑ y", domain: [-3, 3]},
marks: [
Plot.ruleX([0], {stroke: "black", strokeWidth: 1.5, opacity: 0.3}),
Plot.ruleY([0], {stroke: "black", strokeWidth: 1.5, opacity: 0.3}),
...campo_impropio.map(v => Plot.arrow([v], {
x1: "x1", y1: "y1", x2: "x2", y2: "y2",
stroke: "#999", strokeWidth: 1.5, headLength: 6, opacity: 0.5
})),
...orbitas_impropio.map((orb, i) => Plot.line(orb.puntos, {
x: "x", y: "y", stroke: d3.schemeCategory10[i], strokeWidth: 2.5
})),
...orbitas_impropio.map((orb, i) => Plot.dot([orb.ic], {
x: "x", y: "y", fill: d3.schemeCategory10[i], r: 5, stroke: "white", strokeWidth: 2
})),
Plot.dot([{x:0, y:0}], {fill: "black", r: 8, stroke: "white", strokeWidth: 3})
]
})
```
**Matriz ejemplo**:
$$A = \begin{pmatrix} -1 & 1 \\ 0 & -1 \end{pmatrix} \quad \Rightarrow \quad \lambda_1 = \lambda_2 = -1, \quad \vec{v} = \begin{pmatrix} 1 \\ 0 \end{pmatrix}$$
::: {.content-visible when-format="html"}
___
:::
### Caso V: Centro
**Autovalores complejos puros**: $\lambda = \pm i\omega$ (sin parte real)
**Ecuación característica**: $\Delta < 0$ y $\text{tr}(A) = 0$
**Comportamiento:**
- Órbitas cerradas (elípticas)
- El sistema es **periódico** (conservativo)
- No hay atracción ni repulsión
```{ojs}
//| echo: false
//| label: caso-centro
// Sistema centro (oscilador armónico)
f_centro = (x, y, t) => -y
g_centro = (x, y, t) => x
campo_centro = {
const vectores = [];
const [x_min, x_max, y_min, y_max] = [-3, 3, -3, 3];
const densidad = 20;
const dx = (x_max - x_min) / densidad;
const dy = (y_max - y_min) / densidad;
for (let x = x_min; x <= x_max; x += dx) {
for (let y = y_min; y <= y_max; y += dy) {
const vx = f_centro(x, y, 0);
const vy = g_centro(x, y, 0);
const magnitud = Math.sqrt(vx*vx + vy*vy);
if (magnitud === 0) continue;
const scale = Math.min(dx, dy) * 0.35;
const dx_vec = (vx / magnitud) * scale;
const dy_vec = (vy / magnitud) * scale;
vectores.push({x1: x, y1: y, x2: x + dx_vec, y2: y + dy_vec});
}
}
return vectores;
}
orbitas_centro = {
const radios = [0.8, 1.5, 2.3];
const orbitas_calc = [];
const dt = 0.05;
const tiempo_max = 20;
const pasos = Math.floor(tiempo_max / dt);
for (const r of radios) {
const puntos = [];
let x = r, y = 0;
for (let i = 0; i < pasos; i++) {
puntos.push({x: x, y: y});
const k1_x = f_centro(x, y, 0);
const k1_y = g_centro(x, y, 0);
const k2_x = f_centro(x + dt/2 * k1_x, y + dt/2 * k1_y, 0);
const k2_y = g_centro(x + dt/2 * k1_x, y + dt/2 * k1_y, 0);
const k3_x = f_centro(x + dt/2 * k2_x, y + dt/2 * k2_y, 0);
const k3_y = g_centro(x + dt/2 * k2_x, y + dt/2 * k2_y, 0);
const k4_x = f_centro(x + dt * k3_x, y + dt * k3_y, 0);
const k4_y = g_centro(x + dt * k3_x, y + dt * k3_y, 0);
x = x + dt/6 * (k1_x + 2*k2_x + 2*k3_x + k4_x);
y = y + dt/6 * (k1_y + 2*k2_y + 2*k3_y + k4_y);
}
orbitas_calc.push({ic: {x: r, y: 0}, puntos: puntos});
}
return orbitas_calc;
}
Plot.plot({
width: 600,
height: 600,
marginLeft: 50,
marginBottom: 50,
grid: true,
x: {label: "x →", domain: [-3, 3]},
y: {label: "↑ y", domain: [-3, 3]},
marks: [
Plot.ruleX([0], {stroke: "black", strokeWidth: 1.5, opacity: 0.3}),
Plot.ruleY([0], {stroke: "black", strokeWidth: 1.5, opacity: 0.3}),
...campo_centro.map(v => Plot.arrow([v], {
x1: "x1", y1: "y1", x2: "x2", y2: "y2",
stroke: "#999", strokeWidth: 1.5, headLength: 6, opacity: 0.5
})),
...orbitas_centro.map((orb, i) => Plot.line(orb.puntos, {
x: "x", y: "y", stroke: d3.schemeCategory10[i], strokeWidth: 2.5
})),
...orbitas_centro.map((orb, i) => Plot.dot([orb.ic], {
x: "x", y: "y", fill: d3.schemeCategory10[i], r: 5, stroke: "white", strokeWidth: 2
})),
Plot.dot([{x:0, y:0}], {fill: "black", r: 8, stroke: "white", strokeWidth: 3})
]
})
```
**Matriz ejemplo**:
$$A = \begin{pmatrix} 0 & -1 \\ 1 & 0 \end{pmatrix} \quad \Rightarrow \quad \lambda = \pm i$$
::: {.content-visible when-format="html"}
___
:::
### Caso VI: Foco (Espiral)
**Autovalores complejos conjugados con parte real**: $\lambda = \alpha \pm i\omega$ ($\alpha \neq 0$)
**Ecuación característica**: $\Delta < 0$ y $\text{tr}(A) \neq 0$
**Comportamiento:**
- **Foco estable (espiral entrante)**: $\alpha < 0$ → Órbitas espiralan hacia el origen
- **Foco inestable (espiral saliente)**: $\alpha > 0$ → Órbitas espiralan alejándose
```{ojs}
//| echo: false
//| label: caso-foco
viewof invertir_foco = Inputs.toggle({
label: "🔄 Invertir signo (estable ⟷ inestable)",
value: false
})
// Sistema foco (espiral)
f_foco = (x, y, t) => invertir_foco ? (x - y) : (-x - y)
g_foco = (x, y, t) => invertir_foco ? (x + y) : (x - y)
campo_foco = {
const vectores = [];
const [x_min, x_max, y_min, y_max] = [-3, 3, -3, 3];
const densidad = 20;
const dx = (x_max - x_min) / densidad;
const dy = (y_max - y_min) / densidad;
for (let x = x_min; x <= x_max; x += dx) {
for (let y = y_min; y <= y_max; y += dy) {
const vx = f_foco(x, y, 0);
const vy = g_foco(x, y, 0);
const magnitud = Math.sqrt(vx*vx + vy*vy);
if (magnitud === 0) continue;
const scale = Math.min(dx, dy) * 0.35;
const dx_vec = (vx / magnitud) * scale;
const dy_vec = (vy / magnitud) * scale;
vectores.push({x1: x, y1: y, x2: x + dx_vec, y2: y + dy_vec});
}
}
return vectores;
}
orbitas_foco = {
const condiciones = [[2.5,0], [0,2.5], [-2.5,0], [0,-2.5], [1.8,1.8], [-1.8,-1.8]];
const orbitas_calc = [];
const dt = 0.04;
const tiempo_max = 15;
const pasos = Math.floor(tiempo_max / dt);
for (const [x0, y0] of condiciones) {
const puntos = [];
let x = x0, y = y0;
for (let i = 0; i < pasos; i++) {
puntos.push({x: x, y: y});
const k1_x = f_foco(x, y, 0);
const k1_y = g_foco(x, y, 0);
const k2_x = f_foco(x + dt/2 * k1_x, y + dt/2 * k1_y, 0);
const k2_y = g_foco(x + dt/2 * k1_x, y + dt/2 * k1_y, 0);
const k3_x = f_foco(x + dt/2 * k2_x, y + dt/2 * k2_y, 0);
const k3_y = g_foco(x + dt/2 * k2_x, y + dt/2 * k2_y, 0);
const k4_x = f_foco(x + dt * k3_x, y + dt * k3_y, 0);
const k4_y = g_foco(x + dt * k3_x, y + dt * k3_y, 0);
x = x + dt/6 * (k1_x + 2*k2_x + 2*k3_x + k4_x);
y = y + dt/6 * (k1_y + 2*k2_y + 2*k3_y + k4_y);
if (Math.abs(x) > 10 || Math.abs(y) > 10) break;
if (Math.abs(x) < 0.01 && Math.abs(y) < 0.01) break;
}
orbitas_calc.push({ic: {x: x0, y: y0}, puntos: puntos});
}
return orbitas_calc;
}
Plot.plot({
width: 600,
height: 600,
marginLeft: 50,
marginBottom: 50,
grid: true,
x: {label: "x →", domain: [-3, 3]},
y: {label: "↑ y", domain: [-3, 3]},
marks: [
Plot.ruleX([0], {stroke: "black", strokeWidth: 1.5, opacity: 0.3}),
Plot.ruleY([0], {stroke: "black", strokeWidth: 1.5, opacity: 0.3}),
...campo_foco.map(v => Plot.arrow([v], {
x1: "x1", y1: "y1", x2: "x2", y2: "y2",
stroke: "#999", strokeWidth: 1.5, headLength: 6, opacity: 0.5
})),
...orbitas_foco.map((orb, i) => Plot.line(orb.puntos, {
x: "x", y: "y", stroke: d3.schemeCategory10[i], strokeWidth: 2.5
})),
...orbitas_foco.map((orb, i) => Plot.dot([orb.ic], {
x: "x", y: "y", fill: d3.schemeCategory10[i], r: 5, stroke: "white", strokeWidth: 2
})),
Plot.dot([{x:0, y:0}], {fill: "black", r: 8, stroke: "white", strokeWidth: 3})
]
})
```
**Matriz ejemplo**:
$$A = \begin{pmatrix} -1 & -1 \\ 1 & -1 \end{pmatrix} \quad \Rightarrow \quad \lambda = -1 \pm i$$
::: {.content-visible when-format="html"}
___
:::
### Caso VII: Degenerado
**Al menos un autovalor cero**: Sistema **singular** ($\det(A) = 0$)
**Subtipos:**
#### Subtipo A: Recta de puntos fijos ($\lambda_1 = \lambda_2 = 0$)
**Condición**: $A = 0$ (matriz nula)
**Comportamiento**: Todos los puntos son equilibrios.
#### Subtipo B: Familia de rectas paralelas ($\lambda_1 = 0, \lambda_2 \neq 0$)
**Comportamiento**:
- Una dirección tiene equilibrios (variedad de equilibrios)
- En la dirección perpendicular hay flujo
```{ojs}
//| echo: false
//| label: caso-degenerado
// Sistema degenerado (un autovalor cero)
f_degenerado = (x, y, t) => 0
g_degenerado = (x, y, t) => -y
campo_degenerado = {
const vectores = [];
const [x_min, x_max, y_min, y_max] = [-3, 3, -3, 3];
const densidad = 20;
const dx = (x_max - x_min) / densidad;
const dy = (y_max - y_min) / densidad;
for (let x = x_min; x <= x_max; x += dx) {
for (let y = y_min; y <= y_max; y += dy) {
const vx = f_degenerado(x, y, 0);
const vy = g_degenerado(x, y, 0);
const magnitud = Math.sqrt(vx*vx + vy*vy);
if (magnitud === 0) continue;
const scale = Math.min(dx, dy) * 0.35;
const dx_vec = (vx / magnitud) * scale;
const dy_vec = (vy / magnitud) * scale;
vectores.push({x1: x, y1: y, x2: x + dx_vec, y2: y + dy_vec});
}
}
return vectores;
}
orbitas_degenerado = {
const x_vals = [-2, -1, 0, 1, 2];
const orbitas_calc = [];
const dt = 0.05;
const tiempo_max = 10;
const pasos = Math.floor(tiempo_max / dt);
for (const x_val of x_vals) {
const puntos = [];
let x = x_val, y = 2;
for (let i = 0; i < pasos; i++) {
puntos.push({x: x, y: y});
const k1_x = f_degenerado(x, y, 0);
const k1_y = g_degenerado(x, y, 0);
const k2_x = f_degenerado(x + dt/2 * k1_x, y + dt/2 * k1_y, 0);
const k2_y = g_degenerado(x + dt/2 * k1_x, y + dt/2 * k1_y, 0);
const k3_x = f_degenerado(x + dt/2 * k2_x, y + dt/2 * k2_y, 0);
const k3_y = g_degenerado(x + dt/2 * k2_x, y + dt/2 * k2_y, 0);
const k4_x = f_degenerado(x + dt * k3_x, y + dt * k3_y, 0);
const k4_y = g_degenerado(x + dt * k3_x, y + dt * k3_y, 0);
x = x + dt/6 * (k1_x + 2*k2_x + 2*k3_x + k4_x);
y = y + dt/6 * (k1_y + 2*k2_y + 2*k3_y + k4_y);
if (Math.abs(y) < 0.05) break;
}
orbitas_calc.push({ic: {x: x_val, y: 2}, puntos: puntos});
}
return orbitas_calc;
}
Plot.plot({
width: 600,
height: 600,
marginLeft: 50,
marginBottom: 50,
grid: true,
x: {label: "x →", domain: [-3, 3]},
y: {label: "↑ y", domain: [-3, 3]},
marks: [
Plot.ruleX([0], {stroke: "black", strokeWidth: 1.5, opacity: 0.3}),
Plot.ruleY([0], {stroke: "red", strokeWidth: 3, opacity: 0.5}),
...campo_degenerado.map(v => Plot.arrow([v], {
x1: "x1", y1: "y1", x2: "x2", y2: "y2",
stroke: "#999", strokeWidth: 1.5, headLength: 6, opacity: 0.5
})),
...orbitas_degenerado.map((orb, i) => Plot.line(orb.puntos, {
x: "x", y: "y", stroke: d3.schemeCategory10[i], strokeWidth: 2.5
})),
...orbitas_degenerado.map((orb, i) => Plot.dot([orb.ic], {
x: "x", y: "y", fill: d3.schemeCategory10[i], r: 5, stroke: "white", strokeWidth: 2
})),
Plot.line([{x:-3, y:0}, {x:3, y:0}], {stroke: "red", strokeWidth: 3, opacity: 0.7})
]
})
```
**Matriz ejemplo**:
$$A = \begin{pmatrix} 0 & 0 \\ 0 & -1 \end{pmatrix} \quad \Rightarrow \quad \lambda_1 = 0, \, \lambda_2 = -1$$
La línea roja representa la **recta de equilibrios** (eje $x$).
:::
:::
:::
::: {.content-visible when-format="html"}
___
:::
## Clasificación por Traza y Determinante
Para sistemas lineales 2D con matriz $A$, el tipo de equilibrio se determina completamente por:
- **Traza**: $\tau = \text{tr}(A) = a + d$
- **Determinante**: $\Delta = \det(A) = ad - bc$
::: {.content-visible when-format="pdf"}
{{< include _interactive_note.qmd >}}
:::
::: {.content-visible when-format="html"}
::: {.callout-tip title="Visualizador Interactivo: Traza y Determinante"}
```{ojs}
//| echo: false
//| label: visualizador-traza-det
// Controles interactivos (mutables para poder actualizarlos con clicks)
viewof tau = Inputs.range([-5, 5], {
value: -1,
step: 0.05,
label: "τ (Traza)"
})
viewof delta = Inputs.range([-2, 5], {
value: 1,
step: 0.05,
label: "Δ (Determinante) "
})
// Clasificar el tipo de equilibrio
clasificar_equilibrio = {
const discriminante = tau * tau - 4 * delta;
if (delta < 0) {
return {
tipo: "Silla (Inestable)",
color: "#e74c3c",
descripcion: "Punto de silla: estable en una dirección, inestable en otra"
};
} else if (delta === 0) {
return {
tipo: "Degenerado",
color: "#95a5a6",
descripcion: "Caso degenerado: matriz singular"
};
} else if (discriminante > 0) {
if (tau < 0) {
return {
tipo: "Nodo Estable",
color: "#27ae60",
descripcion: "Todas las trayectorias convergen al origen"
};
} else if (tau > 0) {
return {
tipo: "Nodo Inestable",
color: "#e67e22",
descripcion: "Todas las trayectorias divergen del origen"
};
} else {
return {
tipo: "Centro (caso especial)",
color: "#3498db",
descripcion: "Autovalores reales iguales a cero"
};
}
} else if (discriminante < 0) {
if (tau < 0) {
return {
tipo: "Foco Estable (Espiral)",
color: "#16a085",
descripcion: "Trayectorias espirales que convergen al origen"
};
} else if (tau > 0) {
return {
tipo: "Foco Inestable (Espiral)",
color: "#c0392b",
descripcion: "Trayectorias espirales que divergen del origen"
};
} else {
return {
tipo: "Centro",
color: "#3498db",
descripcion: "Órbitas cerradas alrededor del origen"
};
}
} else { // discriminante == 0
if (tau < 0) {
return {
tipo: "Nodo Estable Degenerado",
color: "#27ae60",
descripcion: "Nodo con autovalores repetidos (estable)"
};
} else if (tau > 0) {
return {
tipo: "Nodo Inestable Degenerado",
color: "#e67e22",
descripcion: "Nodo con autovalores repetidos (inestable)"
};
} else {
return {
tipo: "Degenerado",
color: "#95a5a6",
descripcion: "Caso completamente degenerado"
};
}
}
}
// Calcular autovalores
autovalores = {
const discriminante = tau * tau - 4 * delta;
if (discriminante >= 0) {
const lambda1 = (tau + Math.sqrt(discriminante)) / 2;
const lambda2 = (tau - Math.sqrt(discriminante)) / 2;
return {
lambda1: lambda1.toFixed(3),
lambda2: lambda2.toFixed(3),
tipo: "Reales"
};
} else {
const real = tau / 2;
const imag = Math.sqrt(-discriminante) / 2;
return {
lambda1: `${real.toFixed(3)} + ${imag.toFixed(3)}i`,
lambda2: `${real.toFixed(3)} - ${imag.toFixed(3)}i`,
tipo: "Complejos conjugados"
};
}
}
// Construir matriz ejemplo con traza y determinante dados
matriz_ejemplo = {
// Construimos una matriz simple que tenga la traza y determinante deseados
// A = [[a, b], [c, d]] con tr(A) = a+d = tau, det(A) = ad-bc = delta
// Tomamos b=0, c=1 para simplificar, entonces:
// a + d = tau
// ad = delta
const discriminante = tau * tau - 4 * delta;
if (discriminante >= 0) {
const lambda1 = (tau + Math.sqrt(discriminante)) / 2;
const lambda2 = (tau - Math.sqrt(discriminante)) / 2;
return {
a: lambda1,
b: 0,
c: 1,
d: lambda2
};
} else {
// Forma de Jordan para autovalores complejos
const alpha = tau / 2;
const beta = Math.sqrt(-discriminante) / 2;
return {
a: alpha,
b: -beta,
c: beta,
d: alpha
};
}
}
// Generar campo vectorial
generar_campo_traza_det = {
const {a, b, c, d} = matriz_ejemplo;
const campo = [];
const paso = 0.5;
const limite = 3;
for (let x = -limite; x <= limite; x += paso) {
for (let y = -limite; y <= limite; y += paso) {
const dx = a * x + b * y;
const dy = c * x + d * y;
const mag = Math.sqrt(dx*dx + dy*dy);
if (mag > 0.001) {
const norm_factor = 0.25 / Math.max(mag, 0.25);
campo.push({
x1: x,
y1: y,
x2: x + dx * norm_factor,
y2: y + dy * norm_factor
});
}
}
}
return campo;
}
// Generar órbitas
generar_orbitas_traza_det = {
const {a, b, c, d} = matriz_ejemplo;
const orbitas = [];
// Condiciones iniciales dependiendo del tipo
let condiciones_iniciales;
if (delta < 0) { // Silla
condiciones_iniciales = [
{x: 2.5, y: 0.1}, {x: -2.5, y: -0.1},
{x: 0.1, y: 2.5}, {x: -0.1, y: -2.5}
];
} else if (Math.abs(tau) < 0.1 && delta > 0) { // Centro
condiciones_iniciales = [
{x: 1.5, y: 0}, {x: 2.5, y: 0}, {x: 0, y: 1.5}
];
} else { // Nodo o foco
condiciones_iniciales = [
{x: 2.5, y: 0}, {x: -2.5, y: 0},
{x: 0, y: 2.5}, {x: 1.5, y: 1.5}
];
}
condiciones_iniciales.forEach(ic => {
const puntos = [];
let x = ic.x, y = ic.y;
const dt = 0.05;
const pasos = tau > 0 ? 250 : 400; // Menos pasos si diverge
for (let i = 0; i < pasos; i++) {
puntos.push({x, y});
// Runge-Kutta 4
const k1_x = a * x + b * y;
const k1_y = c * x + d * y;
const k2_x = a * (x + dt/2 * k1_x) + b * (y + dt/2 * k1_y);
const k2_y = c * (x + dt/2 * k1_x) + d * (y + dt/2 * k1_y);
const k3_x = a * (x + dt/2 * k2_x) + b * (y + dt/2 * k2_y);
const k3_y = c * (x + dt/2 * k2_x) + d * (y + dt/2 * k2_y);
const k4_x = a * (x + dt * k3_x) + b * (y + dt * k3_y);
const k4_y = c * (x + dt * k3_x) + d * (y + dt * k3_y);
x += dt/6 * (k1_x + 2*k2_x + 2*k3_x + k4_x);
y += dt/6 * (k1_y + 2*k2_y + 2*k3_y + k4_y);
// Detener si sale del rango
if (Math.abs(x) > 4 || Math.abs(y) > 4) break;
}
if (puntos.length > 5) {
orbitas.push({puntos, ic});
}
});
return orbitas;
}
// Información del sistema (compacta)
html`<div style="background: linear-gradient(135deg, ${clasificar_equilibrio.color}22, ${clasificar_equilibrio.color}11);
border-left: 4px solid ${clasificar_equilibrio.color};
padding: 12px 15px;
margin: 15px 0;
border-radius: 6px;
box-shadow: 0 1px 4px rgba(0,0,0,0.1);">
<h3 style="margin: 0 0 8px 0; color: ${clasificar_equilibrio.color}; font-size: 1.2em;">
${clasificar_equilibrio.tipo}
</h3>
<p style="margin: 0 0 10px 0; font-size: 0.95em; color: #2c3e50;">
${clasificar_equilibrio.descripcion}
</p>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
<div style="background: white; padding: 8px; border-radius: 4px; box-shadow: 0 1px 2px rgba(0,0,0,0.08);">
<strong style="color: #7f8c8d; font-size: 0.9em;">Autovalores:</strong><br/>
<span style="color: #2c3e50; font-family: monospace; font-size: 0.85em;">λ₁ = ${autovalores.lambda1}</span><br/>
<span style="color: #2c3e50; font-family: monospace; font-size: 0.85em;">λ₂ = ${autovalores.lambda2}</span><br/>
<span style="color: #95a5a6; font-size: 0.8em;">(${autovalores.tipo})</span>
</div>
<div style="background: white; padding: 8px; border-radius: 4px; box-shadow: 0 1px 2px rgba(0,0,0,0.08);">
<strong style="color: #7f8c8d; font-size: 0.9em;">Matriz ejemplo:</strong><br/>
<span style="color: #2c3e50; font-family: monospace; font-size: 0.85em;">
A = [${matriz_ejemplo.a.toFixed(2)}, ${matriz_ejemplo.b.toFixed(2)}]<br/>
[${matriz_ejemplo.c.toFixed(2)}, ${matriz_ejemplo.d.toFixed(2)}]
</span>
</div>
</div>
<div style="margin-top: 8px; padding: 6px 8px; background: rgba(255,255,255,0.7); border-radius: 4px; font-size: 0.9em;">
<strong style="color: #7f8c8d;">Discriminante:</strong>
<span style="color: #2c3e50; font-family: monospace;">Δ' = τ² - 4Δ = ${(tau*tau - 4*delta).toFixed(3)}</span>
</div>
</div>`
// Gráfico del diagrama de fases
Plot.plot({
width: 800,
height: 500,
x: {domain: [-4, 4], label: "x"},
y: {domain: [-4, 4], label: "y"},
marks: [
Plot.frame(),
Plot.ruleX([0], {stroke: "#bdc3c7", strokeWidth: 1.5}),
Plot.ruleY([0], {stroke: "#bdc3c7", strokeWidth: 1.5}),
...generar_campo_traza_det.map(v => Plot.arrow([v], {
x1: "x1", y1: "y1", x2: "x2", y2: "y2",
stroke: "#95a5a6", strokeWidth: 1.5, headLength: 6, opacity: 0.6
})),
...generar_orbitas_traza_det.map((orb, i) => Plot.line(orb.puntos, {
x: "x", y: "y",
stroke: clasificar_equilibrio.color,
strokeWidth: 2.5,
opacity: 0.8
})),
...generar_orbitas_traza_det.map((orb, i) => Plot.dot([orb.ic], {
x: "x", y: "y",
fill: clasificar_equilibrio.color,
r: 5,
stroke: "white",
strokeWidth: 2
})),
Plot.dot([{x: 0, y: 0}], {
fill: clasificar_equilibrio.color,
r: 8,
stroke: "white",
strokeWidth: 3
})
],
style: {
background: "#fafafa"
}
})
```
**Diagrama τ-Δ (Traza-Determinante):**
```{ojs}
//| echo: false
//| label: diagrama-traza-determinante
{
// Generar puntos para colorear el fondo según la región
const puntos_fondo = [];
const resolucion = 100;
for (let i = 0; i < resolucion; i++) {
for (let j = 0; j < resolucion; j++) {
const t = -5 + (i / resolucion) * 10;
const d = -1 + (j / resolucion) * 7;
const disc = t*t - 4*d;
let tipo, color;
if (d < 0) {
tipo = "Silla";
color = "#e74c3c";
} else if (d > 0 && disc > 0) {
if (t < 0) {
tipo = "Nodo Estable";
color = "#27ae60";
} else {
tipo = "Nodo Inestable";
color = "#e67e22";
}
} else if (d > 0 && disc < 0) {
if (t < 0) {
tipo = "Foco Estable";
color = "#16a085";
} else if (t > 0) {
tipo = "Foco Inestable";
color = "#c0392b";
} else {
tipo = "Centro";
color = "#3498db";
}
} else {
continue; // Skip la frontera
}
puntos_fondo.push({tau: t, delta: d, tipo, color});
}
}
return Plot.plot({
width: 700,
height: 400,
marginLeft: 60,
marginBottom: 60,
x: {
label: "τ (Traza) →",
domain: [-5, 5],
grid: true
},
y: {
label: "↑ Δ (Determinante)",
domain: [-1, 5],
grid: true
},
marks: [
// Fondo de colores según la región
Plot.dot(puntos_fondo, {
x: "tau",
y: "delta",
fill: "color",
r: 3,
opacity: 0.15
}),
// Parábola τ² = 4Δ (frontera entre nodos y focos)
Plot.line(
Array.from({length: 200}, (_, i) => {
const t = -5 + (i / 199) * 10;
return {tau: t, delta: t*t/4};
}),
{
x: "tau",
y: "delta",
stroke: "#34495e",
strokeWidth: 2.5,
strokeDasharray: "5,5"
}
),
// Eje Δ = 0 (frontera silla/otros)
Plot.ruleY([0], {
stroke: "#e74c3c",
strokeWidth: 2,
opacity: 0.6
}),
// Eje τ = 0 (frontera estable/inestable)
Plot.ruleX([0], {
stroke: "#95a5a6",
strokeWidth: 2,
opacity: 0.6
}),
// Punto actual (tu configuración)
Plot.dot([{tau: tau, delta: delta}], {
x: "tau",
y: "delta",
r: 10,
fill: clasificar_equilibrio.color,
stroke: "white",
strokeWidth: 3
}),
// Anillo alrededor del punto actual
Plot.dot([{tau: tau, delta: delta}], {
x: "tau",
y: "delta",
r: 15,
fill: "none",
stroke: clasificar_equilibrio.color,
strokeWidth: 2,
opacity: 0.5
}),
// Etiquetas de regiones
Plot.text([
{tau: 0, delta: -0.8, texto: "Silla"},
{tau: -3, delta: 1.2, texto: "Nodo\nEstable"},
{tau: 3, delta: 1.2, texto: "Nodo\nInestable"},
{tau: -2.5, delta: 3.5, texto: "Foco\nEstable"},
{tau: 2.5, delta: 3.5, texto: "Foco\nInestable"},
{tau: 0, delta: 3.5, texto: "Centro"}
], {
x: "tau",
y: "delta",
text: "texto",
fontSize: 12,
fontWeight: "bold",
fill: "#2c3e50",
opacity: 0.7
}),
// Etiqueta de la parábola
Plot.text([{tau: 4, delta: 2.7, texto: "τ² = 4Δ"}], {
x: "tau",
y: "delta",
text: "texto",
fontSize: 10,
fill: "#34495e",
dy: -10
})
],
style: {
background: "#fafafa"
}
});
}
```
**Interpretación del diagrama:**
En el plano $(\tau, \Delta)$, las regiones corresponden a:
- **$\Delta < 0$** (región roja): Silla (siempre inestable)
- **$\Delta > 0$ y $\tau^2 - 4\Delta > 0$** (región naranja/verde): Nodos (estables si $\tau < 0$, inestables si $\tau > 0$)
- **$\Delta > 0$ y $\tau^2 - 4\Delta < 0$** (región azul/roja oscura): Focos/espirales (estables si $\tau < 0$, inestables si $\tau > 0$)
- **$\Delta > 0$ y $\tau^2 - 4\Delta = 0$** (parábola punteada): Frontera entre nodos y focos
- **$\tau = 0$ y $\Delta > 0$** (eje vertical superior): Centros (órbitas cerradas)
El **punto de color** indica tu configuración actual, que se mueve al ajustar los sliders de τ y Δ.
:::
:::
::: {.content-visible when-format="html"}
___
:::
## Visualización 3D: Sistemas Tridimensionales
Hasta ahora hemos trabajado con sistemas 2D (plano de fases), pero muchos sistemas dinámicos interesantes requieren **tres variables** para su descripción completa. En esta sección exploramos:
1. **Sistemas 3D autónomos**: $(x, y, z)$ evolucionan según ecuaciones diferenciales
2. **Atractores extraños**: Comportamiento caótico en 3D
3. **Visualización interactiva 3D**: Rotación, zoom, y exploración
## Del Plano al Espacio: ¿Por qué 3D?
::: {.callout-note title="Dimensionalidad y Comportamiento"}
**Sistemas 2D vs 3D:**
| Característica | Sistemas 2D | Sistemas 3D |
|----------------|-------------|-------------|
| **Caos** | ❌ Imposible (Teorema de Poincaré-Bendixson) | ✅ Posible (atractores extraños) |
| **Órbitas** | Curvas en el plano | Curvas en el espacio |
| **Equilibrios** | Nodo, foco, centro, silla | Más tipos (silla-foco, silla-nodo) |
| **Ciclos límite** | Curvas cerradas 2D | Toros y estructuras complejas |
| **Complejidad** | Limitada | Arbitrariamente compleja |
**¿Por qué el caos requiere 3D?**
En sistemas 2D autónomos, las órbitas **no pueden cruzarse** (por unicidad de soluciones). Esto impide el comportamiento caótico, que requiere "mezcla" de trayectorias. En 3D, las órbitas pueden "esquivarse" sin cruzarse, permitiendo:
- **Sensibilidad a condiciones iniciales**: Pequeños cambios → grandes diferencias
- **Atractores fractales**: Estructuras con dimensión no entera
- **Mezcla topológica**: Estiramiento y plegado del espacio de fases
:::
## Ejemplos de Sistemas 3D
```{ojs}
//| echo: false
//| label: visualizador-3d-component
// Cargar Plotly
Plotly = require("https://cdn.plot.ly/plotly-2.27.0.min.js")
// Función RK4 para sistemas 3D
rk4_step_3d = (x, y, z, t, dt, f, g, h) => {
// k1
const k1_x = f(x, y, z, t);
const k1_y = g(x, y, z, t);
const k1_z = h(x, y, z, t);
// k2
const k2_x = f(x + dt/2 * k1_x, y + dt/2 * k1_y, z + dt/2 * k1_z, t + dt/2);
const k2_y = g(x + dt/2 * k1_x, y + dt/2 * k1_y, z + dt/2 * k1_z, t + dt/2);
const k2_z = h(x + dt/2 * k1_x, y + dt/2 * k1_y, z + dt/2 * k1_z, t + dt/2);
// k3
const k3_x = f(x + dt/2 * k2_x, y + dt/2 * k2_y, z + dt/2 * k2_z, t + dt/2);
const k3_y = g(x + dt/2 * k2_x, y + dt/2 * k2_y, z + dt/2 * k2_z, t + dt/2);
const k3_z = h(x + dt/2 * k2_x, y + dt/2 * k2_y, z + dt/2 * k2_z, t + dt/2);
// k4
const k4_x = f(x + dt * k3_x, y + dt * k3_y, z + dt * k3_z, t + dt);
const k4_y = g(x + dt * k3_x, y + dt * k3_y, z + dt * k3_z, t + dt);
const k4_z = h(x + dt * k3_x, y + dt * k3_y, z + dt * k3_z, t + dt);
return {
x: x + dt/6 * (k1_x + 2*k2_x + 2*k3_x + k4_x),
y: y + dt/6 * (k1_y + 2*k2_y + 2*k3_y + k4_y),
z: z + dt/6 * (k1_z + 2*k2_z + 2*k3_z + k4_z)
};
}
// Calcular órbita 3D
calcular_orbita_3d = (f, g, h, x0, y0, z0, dt, tiempo_max) => {
const xs = [], ys = [], zs = [];
let x = x0, y = y0, z = z0;
const pasos = Math.floor(tiempo_max / dt);
for (let i = 0; i < pasos; i++) {
xs.push(x);
ys.push(y);
zs.push(z);
const paso = rk4_step_3d(x, y, z, i*dt, dt, f, g, h);
x = paso.x;
y = paso.y;
z = paso.z;
// Detener si diverge
if (Math.abs(x) > 200 || Math.abs(y) > 200 || Math.abs(z) > 200) break;
}
return {x: xs, y: ys, z: zs};
}
// Función principal del visualizador 3D usando Plotly
visualizar_sistema_3d = (config) => {
const container = html`<div style="width: ${config.width || 900}px; height: ${config.height || 700}px;"></div>`;
// Parsear funciones del sistema
let f, g, h;
try {
f = new Function('x', 'y', 'z', 't', `return ${config.dx_dt}`);
g = new Function('x', 'y', 'z', 't', `return ${config.dy_dt}`);
h = new Function('x', 'y', 'z', 't', `return ${config.dz_dt}`);
} catch (e) {
container.innerHTML = `<div style="color: red; padding: 20px; background: #fee; border-radius: 5px;">
<strong>Error:</strong> ${e.message}
</div>`;
return container;
}
// Calcular órbitas
const condiciones = config.condiciones_iniciales.split(';').map(s => {
const [x, y, z] = s.trim().replace(/[\[\]]/g, '').split(',').map(Number);
return {x, y, z};
});
const dt = config.dt || 0.01;
const tiempo_max = config.tiempo_max || 50;
// Colores vibrantes para cada órbita
const colores = [
'#e74c3c', '#3498db', '#2ecc71', '#f39c12',
'#9b59b6', '#1abc9c', '#e67e22', '#34495e',
'#c0392b', '#16a085'
];
// Crear trazas de Plotly
const traces = condiciones.map((ic, idx) => {
const orbita = calcular_orbita_3d(f, g, h, ic.x, ic.y, ic.z, dt, tiempo_max);
return {
type: 'scatter3d',
mode: 'lines',
x: orbita.x,
y: orbita.y,
z: orbita.z,
line: {
color: colores[idx % colores.length],
width: 4
},
name: `Órbita ${idx + 1}`,
hovertemplate: 'x: %{x:.2f}<br>y: %{y:.2f}<br>z: %{z:.2f}<extra></extra>'
};
});
// Añadir puntos iniciales
condiciones.forEach((ic, idx) => {
traces.push({
type: 'scatter3d',
mode: 'markers',
x: [ic.x],
y: [ic.y],
z: [ic.z],
marker: {
size: 8,
color: colores[idx % colores.length],
symbol: 'circle',
line: {
color: 'white',
width: 2
}
},
name: `Inicio ${idx + 1}`,
showlegend: false,
hovertemplate: 'Inicio<br>x: %{x:.2f}<br>y: %{y:.2f}<br>z: %{z:.2f}<extra></extra>'
});
});
// Layout de Plotly con estilo profesional
const layout = {
title: {
text: config.titulo || 'Sistema 3D',
font: {size: 20, family: 'Arial, sans-serif', color: '#2c3e50'}
},
scene: {
xaxis: {
title: 'x',
gridcolor: '#ecf0f1',
showbackground: true,
backgroundcolor: '#fafafa'
},
yaxis: {
title: 'y',
gridcolor: '#ecf0f1',
showbackground: true,
backgroundcolor: '#fafafa'
},
zaxis: {
title: 'z',
gridcolor: '#ecf0f1',
showbackground: true,
backgroundcolor: '#fafafa'
},
camera: {
eye: {x: 1.8, y: -1.8, z: 0.8}
},
aspectmode: 'cube'
},
paper_bgcolor: 'white',
plot_bgcolor: 'white',
margin: {l: 0, r: 0, t: 40, b: 0},
showlegend: true,
legend: {
x: 0.02,
y: 0.98,
bgcolor: 'rgba(255,255,255,0.8)',
bordercolor: '#bdc3c7',
borderwidth: 1
},
hovermode: 'closest'
};
// Config de Plotly
const plotConfig = {
responsive: true,
displayModeBar: true,
displaylogo: false,
modeBarButtonsToRemove: ['toImage'],
modeBarButtonsToAdd: [{
name: 'Resetear vista',
icon: Plotly.Icons.home,
click: function(gd) {
Plotly.relayout(gd, {
'scene.camera': {
eye: {x: 1.8, y: -1.8, z: 0.8}
}
});
}
}]
};
// Renderizar
Plotly.newPlot(container, traces, layout, plotConfig);
return container;
}
```
::: {.panel-tabset}
### Atractor de Lorenz
**Sistema de Lorenz (1963)**
$$
\begin{cases}
\frac{dx}{dt} = \sigma(y - x) \\
\frac{dy}{dt} = x(\rho - z) - y \\
\frac{dz}{dt} = xy - \beta z
\end{cases}
$$
**Parámetros clásicos**: $\sigma = 10$, $\rho = 28$, $\beta = 8/3$
**Características:**
- **Primer ejemplo** de atractor extraño (caótico)
- Modela convección atmosférica (Rayleigh-Bénard)
- Dos "lóbulos" alrededor de equilibrios inestables
- **Sensibilidad extrema**: cambios de $10^{-5}$ en CI → órbitas divergentes
- Dimensión fractal: $D \approx 2.06$
**Física:**
- $x$: intensidad de circulación convectiva
- $y$: diferencia de temperatura horizontal
- $z$: desviación de temperatura vertical
**Interpretación de parámetros:**
- $\sigma$ (Prandtl): viscosidad/conductividad térmica
- $\rho$ (Rayleigh): diferencia de temperatura aplicada
- $\beta$: relación de aspecto geométrica
```{ojs}
//| echo: false
//| label: atractor-lorenz
viewof lorenz_params = Inputs.form({
sigma: Inputs.range([5, 15], {value: 10, step: 0.5, label: "σ (Prandtl)"}),
rho: Inputs.range([15, 35], {value: 28, step: 0.5, label: "ρ (Rayleigh)"}),
beta: Inputs.range([1, 4], {value: 8/3, step: 0.1, label: "β"})
})
visualizar_sistema_3d({
dx_dt: `${lorenz_params.sigma} * (y - x)`,
dy_dt: `x * (${lorenz_params.rho} - z) - y`,
dz_dt: `x * y - ${lorenz_params.beta} * z`,
condiciones_iniciales: "[1,1,1]; [1.01,1,1]; [0,2,2]",
width: 900,
height: 700,
dt: 0.005,
tiempo_max: 50,
titulo: "Atractor de Lorenz"
})
```
::: {.content-visible when-format="html"}
___
:::
### Atractor de Rössler
**Sistema de Rössler (1976)**
$$
\begin{cases}
\frac{dx}{dt} = -y - z \\
\frac{dy}{dt} = x + ay \\
\frac{dz}{dt} = b + z(x - c)
\end{cases}
$$
**Parámetros clásicos**: $a = 0.2$, $b = 0.2$, $c = 5.7$
**Características:**
- **Atractor caótico más simple** que Lorenz
- Estructura de "banda enrollada"
- Sección de Poincaré revela estructura fractal
- Ruta al caos por **doblamiento de período**
**Ventaja sobre Lorenz**: Las ecuaciones son más simples, lo que facilita el análisis matemático.
```{ojs}
//| echo: false
//| label: atractor-rossler
viewof rossler_params = Inputs.form({
a: Inputs.range([0.1, 0.3], {value: 0.2, step: 0.01, label: "a"}),
b: Inputs.range([0.1, 0.3], {value: 0.2, step: 0.01, label: "b"}),
c: Inputs.range([4, 7], {value: 5.7, step: 0.1, label: "c"})
})
visualizar_sistema_3d({
dx_dt: `-y - z`,
dy_dt: `x + ${rossler_params.a} * y`,
dz_dt: `${rossler_params.b} + z * (x - ${rossler_params.c})`,
condiciones_iniciales: "[1,1,1]; [2,0,0]; [-1,2,0]",
width: 900,
height: 700,
dt: 0.01,
tiempo_max: 200,
titulo: "Atractor de Rössler"
})
```
::: {.content-visible when-format="html"}
___
:::
### Atractor de Aizawa
**Sistema de Aizawa (1987)**
$$
\begin{cases}
\frac{dx}{dt} = (z - b)x - dy \\
\frac{dy}{dt} = dx + (z - b)y \\
\frac{dz}{dt} = c + az - \frac{z^3}{3} - (x^2 + y^2)(1 + ez) + fzx^3
\end{cases}
$$
**Parámetros**: $a=0.95$, $b=0.7$, $c=0.6$, $d=3.5$, $e=0.25$, $f=0.1$
**Características:**
- Atractor con **simetría toroidal**
- Comportamiento más complejo que Lorenz
- Hermosa estructura en forma de "flor" o "hélice"
- Menos estudiado pero visualmente espectacular
```{ojs}
//| echo: false
//| label: atractor-aizawa
visualizar_sistema_3d({
dx_dt: `(z - 0.7) * x - 3.5 * y`,
dy_dt: `3.5 * x + (z - 0.7) * y`,
dz_dt: `0.6 + 0.95 * z - (z*z*z)/3 - (x*x + y*y) * (1 + 0.25*z) + 0.1*z*x*x*x`,
condiciones_iniciales: "[0.1,0,0]; [0.2,0.1,0]; [-0.1,0.1,0.5]",
width: 900,
height: 700,
dt: 0.01,
tiempo_max: 300,
titulo: "Atractor de Aizawa"
})
```
::: {.content-visible when-format="html"}
___
:::
### Atractor de Chen
**Sistema de Chen (1999)**
$$
\begin{cases}
\frac{dx}{dt} = a(y - x) \\
\frac{dy}{dt} = (c - a)x - xz + cy \\
\frac{dz}{dt} = xy - bz
\end{cases}
$$
**Parámetros**: $a = 35$, $b = 3$, $c = 28$
**Características:**
- Similar a Lorenz pero **no topológicamente equivalente**
- Descubierto como parte de la familia de atractores Lorenz-like
- Estructura de doble scroll más simétrica
- Importante en criptografía y comunicaciones seguras
```{ojs}
//| echo: false
//| label: atractor-chen
visualizar_sistema_3d({
dx_dt: `35 * (y - x)`,
dy_dt: `(28 - 35) * x - x * z + 28 * y`,
dz_dt: `x * y - 3 * z`,
condiciones_iniciales: "[1,0,0]; [-2,0,1]; [0,1,2]",
width: 900,
height: 700,
dt: 0.002,
tiempo_max: 50,
titulo: "Atractor de Chen"
})
```
::: {.content-visible when-format="html"}
___
:::
### Sistema de Thomas
**Sistema de Thomas (1999)**
$$
\begin{cases}
\frac{dx}{dt} = -bx + \sin(y) \\
\frac{dy}{dt} = -by + \sin(z) \\
\frac{dz}{dt} = -bz + \sin(x)
\end{cases}
$$
**Parámetro**: $b = 0.208186$
**Características:**
- Atractor con **simetría cíclica** (invariante bajo rotaciones de 120°)
- Forma de "pretzel" o nudo tridimensional
- Sistema disipativo con comportamiento caótico
- Usado en modelos de osciladores acoplados
```{ojs}
//| echo: false
//| label: atractor-thomas
visualizar_sistema_3d({
dx_dt: `-0.208186 * x + Math.sin(y)`,
dy_dt: `-0.208186 * y + Math.sin(z)`,
dz_dt: `-0.208186 * z + Math.sin(x)`,
condiciones_iniciales: "[1,1,1]; [2,0,0]; [0,2,0]",
width: 900,
height: 700,
dt: 0.05,
tiempo_max: 500,
titulo: "Sistema de Thomas"
})
```
::: {.content-visible when-format="html"}
___
:::
### Atractor de Halvorsen
**Sistema de Halvorsen**
$$
\begin{cases}
\frac{dx}{dt} = -ax - 4y - 4z - y^2 \\
\frac{dy}{dt} = -ay - 4z - 4x - z^2 \\
\frac{dz}{dt} = -az - 4x - 4y - x^2
\end{cases}
$$
**Parámetro**: $a = 1.4$
**Características:**
- **Simetría perfecta** bajo permutaciones cíclicas de $(x,y,z)$
- Atractor con cuatro "lóbulos" interconectados
- Comportamiento caótico con alta complejidad estructural
- Belleza geométrica notable
```{ojs}
//| echo: false
//| label: atractor-halvorsen
visualizar_sistema_3d({
dx_dt: `-1.4 * x - 4 * y - 4 * z - y*y`,
dy_dt: `-1.4 * y - 4 * z - 4 * x - z*z`,
dz_dt: `-1.4 * z - 4 * x - 4 * y - x*x`,
condiciones_iniciales: "[1,0,0]; [0,1,0]; [0,0,1]",
width: 900,
height: 700,
dt: 0.005,
tiempo_max: 100,
titulo: "Atractor de Halvorsen"
})
```
::: {.content-visible when-format="html"}
___
:::
### Sistema de Sprott (Case A)
**Sistema de Sprott A (1994)**
$$
\begin{cases}
\frac{dx}{dt} = y \\
\frac{dy}{dt} = -x + yz \\
\frac{dz}{dt} = 1 - y^2
\end{cases}
$$
**Características:**
- Uno de los **sistemas caóticos más simples** conocidos
- Solo 5 términos (mínimo para caos en 3D con polinomios cuadráticos)
- Parte de una colección de 19 sistemas simples caóticos (Sprott A-S)
- Exponente de Lyapunov positivo a pesar de su simplicidad
**Relevancia**: Demuestra que el caos no requiere ecuaciones complicadas.
```{ojs}
//| echo: false
//| label: atractor-sprott
visualizar_sistema_3d({
dx_dt: `y`,
dy_dt: `-x + y*z`,
dz_dt: `1 - y*y`,
condiciones_iniciales: "[0,1,0]; [0.5,0,0]; [0,0.5,0.5]",
width: 900,
height: 700,
dt: 0.02,
tiempo_max: 200,
titulo: "Sistema de Sprott (Case A)"
})
```
:::
::: {.content-visible when-format="html"}
___
:::
## Chaos en Sistemas 3D
::: {.callout-note title="Teoría de Sistemas Caóticos"}
### Características del Caos Determinista
**1. Sensibilidad a Condiciones Iniciales**
Pequeñas diferencias iniciales $\delta_0$ crecen exponencialmente:
$$\delta(t) \approx \delta_0 e^{\lambda t}$$
donde $\lambda > 0$ es el **exponente de Lyapunov** máximo.
**Ejemplo Lorenz**: $\delta_0 = 10^{-5}$ → después de $t \approx 10$ unidades, $\delta(t) \sim 1$ (orden del atractor).
**2. Atractores Extraños**
Son conjuntos invariantes con:
- **Dimensión fractal**: $D$ no entera (ej: Lorenz $D \approx 2.06$)
- **Estructura autosimilar**: Igual aspecto a diferentes escalas
- **Medida no uniforme**: Algunas regiones más visitadas que otras
**3. Mezcla Topológica**
El flujo "estira y pliega" el espacio de fases:
- **Estiramiento**: Separación de trayectorias cercanas
- **Plegado**: Mantiene el atractor acotado
### Herramientas de Análisis
**Exponentes de Lyapunov**
- $\lambda_1 > 0$: Caos (divergencia exponencial)
- $\lambda_1 = 0$: Movimiento cuasiperiódico
- $\lambda_1 < 0$: Convergencia a equilibrio/ciclo
**Dimensión de Kaplan-Yorke**
$$D_{KY} = j + \frac{\sum_{i=1}^j \lambda_i}{|\lambda_{j+1}|}$$
donde $j$ es el máximo índice tal que $\sum_{i=1}^j \lambda_i \geq 0$.
**Sección de Poincaré**
- Corte transversal del atractor
- Revela estructura en 2D
- Útil para detectar periodicidad
**Diagrama de Bifurcación**
- Cómo cambia el atractor con un parámetro
- Rutas al caos: doblamiento de período, intermitencia, crisis
:::
::: {.content-visible when-format="html"}
___
:::
## Aplicaciones Prácticas
::: {.panel-tabset}
### Meteorología
**Sistema de Lorenz** → Convección atmosférica
- Predicción del tiempo limitada (horizonte ~10 días)
- "Efecto mariposa": pequeñas perturbaciones → grandes cambios
- Modelos climáticos modernos: sistemas de dimensión $\sim 10^7$
### Circuitos Electrónicos
**Circuito de Chua** → Atractor caótico en hardware
- Resistor no lineal + inductores + capacitores
- Generación de señales caóticas
- Aplicación en comunicaciones seguras
### Química
**Reacción de Belousov-Zhabotinsky**
- Oscilaciones químicas caóticas
- Ondas de concentración viajeras
- Modelo: sistema tipo Rössler
### Biología
**Redes neuronales caóticas**
- Modelos de Hindmarsh-Rose (neuronas)
- Sincronización de osciladores
- Ritmos cardíacos irregulares (fibrilación)
### Criptografía
**Generadores pseudoaleatorios**
- Sistemas tipo Lorenz/Chen como fuente de caos
- Cifrado basado en sincronización caótica
- Mayor seguridad que generadores lineales
:::
<!-- ## Referencias Adicionales
::: {.callout-note title="Lecturas Recomendadas"}
**Libros clásicos:**
- Strogatz, S. (2015). *Nonlinear Dynamics and Chaos*
- Lorenz, E. (1963). "Deterministic Nonperiodic Flow" (paper original)
- Sprott, J.C. (2003). *Chaos and Time-Series Analysis*
**Recursos online:**
- Scholarpedia: Chaos theory
- Chaos hypertextbook (Glenn Elert)
- Visualizadores: chaoscope, attractors.org
**Papers importantes:**
- Lorenz (1963): Atractor de Lorenz
- Rössler (1976): Atractor de Rössler
- Sprott (1994): Sistemas caóticos simples
:::
--- -->
# Conservación del Volumen en Sistemas Hamiltonianos
Los sistemas hamiltonianos tienen la propiedad de **conservar el volumen** en el espacio de fases (Teorema de Liouville). Esto significa que si tomamos un conjunto de condiciones iniciales que ocupan cierta región, el flujo deformará esta región pero **su área permanecerá constante**.
Un sistema hamiltoniano tiene la forma:
$$
\begin{cases}
\frac{dq}{dt} = \frac{\partial H}{\partial p} \\
\frac{dp}{dt} = -\frac{\partial H}{\partial q}
\end{cases}
$$
donde $H(q,p)$ es la función hamiltoniana (energía total del sistema).
**Propiedad clave:** La divergencia del campo vectorial es cero:
$$
\nabla \cdot \mathbf{F} = \frac{\partial}{\partial q}\left(\frac{\partial H}{\partial p}\right) + \frac{\partial}{\partial p}\left(-\frac{\partial H}{\partial q}\right) = 0
$$
Esta propiedad implica que el volumen (en 2D, el área) se conserva bajo el flujo.
::: {.content-visible when-format="pdf"}
{{< include _interactive_note.qmd >}}
:::
::: {.content-visible when-format="html"}
::: {.callout-tip collapse="true" title="Visualizador: Conservación del Volumen en Flujos Hamiltonianos"}
```{ojs}
//| echo: false
//| label: hamiltoniano-volumen
viewof sistema_hamiltoniano = Inputs.select(
new Map([
["Oscilador No Lineal: H = p²/2 + q⁴/4", "no_lineal"],
["Oscilador Armónico: H = (p² + q²)/2", "armonico"],
["Péndulo Simple: H = p²/2 - cos(q)", "pendulo"],
["Hénon-Heiles: H = (p² + q²)/2 + q² - q³/3", "henon_heiles"]
]),
{label: "Sistema Hamiltoniano"}
)
viewof forma_region = Inputs.select(
new Map([
["Triángulo", "triangulo"],
["Círculo", "circulo"],
["Cuadrado", "cuadrado"],
["Elipse", "elipse"]
]),
{label: "Forma de la Región Inicial", value: "triangulo"}
)
viewof num_puntos_region = Inputs.range([200, 1000], {
label: "Número de Puntos",
step: 50,
value: 500
})
viewof tiempo_evolucion = Inputs.range([0, 50], {
label: "Tiempo de Evolución",
step: 0.1,
value: 0
})
mostrar_energia = false
// Funciones hamiltonianas
hamiltoniano_func = {
const sistemas = {
armonico: {
H: (q, p) => (p*p + q*q) / 2,
dHdp: (q, p) => p,
dHdq: (q, p) => q
},
pendulo: {
H: (q, p) => p*p / 2 - Math.cos(q),
dHdp: (q, p) => p,
dHdq: (q, p) => Math.sin(q)
},
no_lineal: {
H: (q, p) => p*p / 2 + q*q*q*q / 4,
dHdp: (q, p) => p,
dHdq: (q, p) => q*q*q
},
henon_heiles: {
H: (q, p) => (p*p + q*q) / 2 + q*q - q*q*q / 3,
dHdp: (q, p) => p,
dHdq: (q, p) => q + 2*q - q*q
}
};
return sistemas[sistema_hamiltoniano];
}
// Generar puntos iniciales según la forma
puntos_iniciales_region = {
const centro_q = 0;
const centro_p = 0;
const escala = 1.5;
const puntos = [];
if (forma_region === "circulo") {
const radio = 0.5 * escala;
for (let i = 0; i < num_puntos_region; i++) {
const angulo = Math.random() * 2 * Math.PI;
const r = Math.sqrt(Math.random()) * radio;
puntos.push({
q: centro_q + r * Math.cos(angulo),
p: centro_p + r * Math.sin(angulo)
});
}
} else if (forma_region === "cuadrado") {
const lado = 0.8 * escala;
for (let i = 0; i < num_puntos_region; i++) {
puntos.push({
q: centro_q + (Math.random() - 0.5) * lado,
p: centro_p + (Math.random() - 0.5) * lado
});
}
} else if (forma_region === "triangulo") {
const lado = 1.0 * escala;
for (let i = 0; i < num_puntos_region; i++) {
let r1 = Math.random();
let r2 = Math.random();
if (r1 + r2 > 1) {
r1 = 1 - r1;
r2 = 1 - r2;
}
const q = centro_q + (r1 - 0.5) * lado;
const p = centro_p + (r2 - 0.5) * lado * Math.sqrt(3) / 2;
puntos.push({q, p});
}
} else if (forma_region === "elipse") {
const a = 0.7 * escala;
const b = 0.4 * escala;
for (let i = 0; i < num_puntos_region; i++) {
const angulo = Math.random() * 2 * Math.PI;
const r = Math.sqrt(Math.random());
puntos.push({
q: centro_q + r * a * Math.cos(angulo),
p: centro_p + r * b * Math.sin(angulo)
});
}
}
return puntos;
}
// Evolucionar puntos en el tiempo
puntos_evolucionados = {
const dt = 0.01;
const pasos = Math.floor(tiempo_evolucion / dt);
return puntos_iniciales_region.map(p0 => {
let q = p0.q;
let p = p0.p;
for (let i = 0; i < pasos; i++) {
// RK4 para sistema hamiltoniano
const k1_q = hamiltoniano_func.dHdp(q, p);
const k1_p = -hamiltoniano_func.dHdq(q, p);
const k2_q = hamiltoniano_func.dHdp(q + dt/2 * k1_q, p + dt/2 * k1_p);
const k2_p = -hamiltoniano_func.dHdq(q + dt/2 * k1_q, p + dt/2 * k1_p);
const k3_q = hamiltoniano_func.dHdp(q + dt/2 * k2_q, p + dt/2 * k2_p);
const k3_p = -hamiltoniano_func.dHdq(q + dt/2 * k2_q, p + dt/2 * k2_p);
const k4_q = hamiltoniano_func.dHdp(q + dt * k3_q, p + dt * k3_p);
const k4_p = -hamiltoniano_func.dHdq(q + dt * k3_q, p + dt * k3_p);
q = q + dt/6 * (k1_q + 2*k2_q + 2*k3_q + k4_q);
p = p + dt/6 * (k1_p + 2*k2_p + 2*k3_p + k4_p);
}
return {q, p, q0: p0.q, p0: p0.p};
});
}
// Calcular área usando método de Monte Carlo
area_inicial = {
const puntos = puntos_iniciales_region;
const q_min = d3.min(puntos, d => d.q);
const q_max = d3.max(puntos, d => d.q);
const p_min = d3.min(puntos, d => d.p);
const p_max = d3.max(puntos, d => d.p);
const area_caja = (q_max - q_min) * (p_max - p_min);
return area_caja * (puntos.length / num_puntos_region);
}
area_evolucionada = {
const puntos = puntos_evolucionados;
const q_min = d3.min(puntos, d => d.q);
const q_max = d3.max(puntos, d => d.q);
const p_min = d3.min(puntos, d => d.p);
const p_max = d3.max(puntos, d => d.p);
const area_caja = (q_max - q_min) * (p_max - p_min);
return area_caja * (puntos.length / num_puntos_region);
}
// Calcular el área usando el método del polígono convexo (Convex Hull)
calcular_area_convex_hull = (puntos) => {
if (puntos.length < 3) return 0;
// Algoritmo de Graham Scan para Convex Hull
const sorted = [...puntos].sort((a, b) => a.q === b.q ? a.p - b.p : a.q - b.q);
const cross = (o, a, b) => {
return (a.q - o.q) * (b.p - o.p) - (a.p - o.p) * (b.q - o.q);
};
const lower = [];
for (let i = 0; i < sorted.length; i++) {
while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], sorted[i]) <= 0) {
lower.pop();
}
lower.push(sorted[i]);
}
const upper = [];
for (let i = sorted.length - 1; i >= 0; i--) {
while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], sorted[i]) <= 0) {
upper.pop();
}
upper.push(sorted[i]);
}
upper.pop();
lower.pop();
const hull = lower.concat(upper);
// Calcular área del polígono
let area = 0;
for (let i = 0; i < hull.length; i++) {
const j = (i + 1) % hull.length;
area += hull[i].q * hull[j].p;
area -= hull[j].q * hull[i].p;
}
return Math.abs(area / 2);
}
area_inicial_hull = calcular_area_convex_hull(puntos_iniciales_region)
area_evolucionada_hull = calcular_area_convex_hull(puntos_evolucionados)
// Generar curvas de nivel de energía
curvas_energia = {
if (!mostrar_energia) return [];
const niveles = [];
const q_range = [-3, 3];
const p_range = [-3, 3];
const resolucion = 100;
// Determinar rango de energías basado en los puntos
const energias_puntos = puntos_evolucionados.map(pt =>
hamiltoniano_func.H(pt.q, pt.p)
);
const E_min = d3.min(energias_puntos);
const E_max = d3.max(energias_puntos);
// Crear algunas curvas de nivel
const num_niveles = 8;
for (let k = 0; k < num_niveles; k++) {
const E = E_min + (E_max - E_min) * k / (num_niveles - 1);
const puntos_nivel = [];
// Buscar puntos donde H(q,p) ≈ E
const dq = (q_range[1] - q_range[0]) / resolucion;
const dp = (p_range[1] - p_range[0]) / resolucion;
for (let i = 0; i < resolucion; i++) {
for (let j = 0; j < resolucion; j++) {
const q = q_range[0] + i * dq;
const p = p_range[0] + j * dp;
const H_val = hamiltoniano_func.H(q, p);
if (Math.abs(H_val - E) < 0.1) {
puntos_nivel.push({q, p, energia: E});
}
}
}
if (puntos_nivel.length > 10) {
niveles.push({energia: E, puntos: puntos_nivel});
}
}
return niveles;
}
// Visualización
Plot.plot({
width: 800,
height: 600,
marginLeft: 60,
marginBottom: 50,
marginRight: 100,
x: {
label: "Posición (q) →",
domain: [-3, 3]
},
y: {
label: "Momento (p) →",
domain: [-3, 3]
},
marks: [
// Curvas de energía constante
...curvas_energia.map(nivel =>
Plot.dot(nivel.puntos, {
x: "q",
y: "p",
r: .8,
fill: "#e0e0e0",
opacity: 0.3
})
),
// Puntos iniciales
Plot.dot(puntos_iniciales_region, {
x: "q",
y: "p",
r: 2,
fill: "blue",
opacity: 0.5
}),
// Puntos evolucionados
Plot.dot(puntos_evolucionados, {
x: "q",
y: "p",
r: 2,
fill: "red",
opacity: 0.7
}),
// Conectar puntos inicial-final (opcional, solo algunos)
...(tiempo_evolucion > 0 ? [
Plot.link(
puntos_evolucionados.filter((_, i) => i % 10 === 0),
{
x1: "q0",
y1: "p0",
x2: "q",
y2: "p",
stroke: "gray",
strokeOpacity: 0.2,
strokeWidth: 0.5
}
)
] : []),
Plot.gridX({stroke: "#e0e0e0", strokeOpacity: 0.5}),
Plot.gridY({stroke: "#e0e0e0", strokeOpacity: 0.5})
],
caption: html`
<div style="text-align: center; margin-top: 10px;">
<span style="color: blue; font-weight: bold;">● Región inicial</span> |
<span style="color: red; font-weight: bold;">● Región evolucionada (t = ${tiempo_evolucion.toFixed(1)})</span><br>
<strong>Área inicial (convex hull):</strong> ${area_inicial_hull.toFixed(4)} |
<strong>Área evolucionada:</strong> ${area_evolucionada_hull.toFixed(4)} |
<strong>Cambio relativo:</strong> ${((area_evolucionada_hull - area_inicial_hull) / area_inicial_hull * 100).toFixed(2)}%
${mostrar_energia ? "<br><span style='color: #888;'>● Curvas de energía constante</span>" : ""}
</div>
`
})
```
**Interpretación:**
- **Región azul:** Conjunto de condiciones iniciales (forma la región inicial)
- **Región roja:** Evolución de esos puntos después del tiempo seleccionado
**Observaciones importantes:**
1. La región puede deformarse, estirarse o doblarse, pero su área permanece constante
2. Cada punto se mueve sobre una curva de energía constante
3. Los sistemas hamiltonianos NO pueden tener atractores ni comportamiento disipativo
4. Esta propiedad hace que los sistemas hamiltonianos sean reversibles en el tiempo
**Ejemplos de sistemas hamiltonianos:**
- Oscilador armónico: movimiento circular
- Péndulo simple: las órbitas dependen de la energía
- Sistemas planetarios, partículas en campos electromagnéticos
:::
:::
{{< include footer.qmd >}}