{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Prácticas progresivas de Python para dinámica\n",
        "\n",
        "Este cuaderno contiene cinco prácticas ordenadas por dificultad. Funciona en Jupyter y en entornos de navegador basados en Pyodide que incluyan NumPy y Matplotlib. Todo el cálculo se realiza en el entorno donde se abre el cuaderno; no depende de un proceso Python del servidor de Fyskode.\n",
        "\n",
        "Ejecuta las celdas en orden. Modifica los datos indicados, vuelve a ejecutar y compara los resultados. Los diagnósticos numéricos de la última práctica son evidencia finita y no constituyen por sí solos una demostración de caos o atractividad."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Práctica 1. Variables, listas, ciclos y funciones\n",
        "\n",
        "La función `promedio(datos)` recibe una lista y devuelve la suma dividida entre el número de elementos. El ciclo `for` aplica la misma operación a cada medición.\n",
        "\n",
        "**Ejercicio:** cambia las mediciones, añade una condición para contar los valores superiores al promedio y verifica el resultado manualmente."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "mediciones = [0.42, 0.51, 0.48, 0.63, 0.57]\n",
        "\n",
        "def promedio(datos):\n",
        "    return sum(datos) / len(datos)\n",
        "\n",
        "media = promedio(mediciones)\n",
        "print(f\"Promedio = {media:.3f}\")\n",
        "\n",
        "for indice, valor in enumerate(mediciones, start=1):\n",
        "    diferencia = valor - media\n",
        "    print(f\"Medición {indice}: diferencia = {diferencia:+.3f}\")\n",
        "\n",
        "assert abs(sum(valor - media for valor in mediciones)) < 1e-12"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Práctica 2. Arreglos y operaciones con NumPy\n",
        "\n",
        "`numpy.linspace(inicio, fin, cantidad)` construye una malla uniforme. Las funciones `np.sin` y `np.cos` operan elemento a elemento sin escribir un ciclo explícito.\n",
        "\n",
        "**Ejercicio:** duplica el número de puntos y calcula el máximo error de la identidad $\\sin^2(t)+\\cos^2(t)=1$."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "\n",
        "t = np.linspace(0.0, 2.0 * np.pi, 101)\n",
        "seno = np.sin(t)\n",
        "coseno = np.cos(t)\n",
        "identidad = seno**2 + coseno**2\n",
        "error_maximo = np.max(np.abs(identidad - 1.0))\n",
        "\n",
        "print(f\"Forma del arreglo: {t.shape}\")\n",
        "print(f\"Paso aproximado: {t[1] - t[0]:.5f}\")\n",
        "print(f\"Error máximo: {error_maximo:.3e}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Práctica 3. Método de Euler construido paso a paso\n",
        "\n",
        "Para $x'=f(t,x)$, Euler actualiza $x_{n+1}=x_n+h f(t_n,x_n)$. La función siguiente recibe el campo `f`, el intervalo, el dato inicial y el paso. Después se compara con la solución exacta de $x'=-2x$.\n",
        "\n",
        "**Ejercicio:** ejecuta con `h=0.2`, `h=0.1` y `h=0.05`. Registra el error final y comprueba que disminuye aproximadamente de forma lineal."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "def euler(f, t0, tf, x0, h):\n",
        "    pasos = int(round((tf - t0) / h))\n",
        "    tiempos = np.linspace(t0, t0 + pasos * h, pasos + 1)\n",
        "    estados = np.empty(pasos + 1, dtype=float)\n",
        "    estados[0] = x0\n",
        "\n",
        "    for n in range(pasos):\n",
        "        estados[n + 1] = estados[n] + h * f(tiempos[n], estados[n])\n",
        "\n",
        "    return tiempos, estados\n",
        "\n",
        "def campo_relajacion(t, x):\n",
        "    return -2.0 * x\n",
        "\n",
        "tiempos, x_euler = euler(campo_relajacion, 0.0, 2.0, 1.0, 0.1)\n",
        "x_exacta = np.exp(-2.0 * tiempos)\n",
        "error_final = abs(x_euler[-1] - x_exacta[-1])\n",
        "\n",
        "print(f\"Euler en t=2: {x_euler[-1]:.8f}\")\n",
        "print(f\"Solución exacta: {x_exacta[-1]:.8f}\")\n",
        "print(f\"Error final: {error_final:.3e}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Práctica 4. Gráficas sencillas con Matplotlib\n",
        "\n",
        "`plt.subplots()` crea la figura y los ejes. `ax.plot(x, y, ...)` dibuja una curva; `set_xlabel`, `set_ylabel` y `legend` documentan las variables y el método.\n",
        "\n",
        "**Ejercicio:** añade una segunda aproximación con otro paso, usa un estilo de línea distinto y explica qué curva se acerca más a la solución exacta."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "\n",
        "fig, ax = plt.subplots(figsize=(7, 4))\n",
        "ax.plot(tiempos, x_exacta, label=\"Solución exacta\", linewidth=2.0)\n",
        "ax.plot(tiempos, x_euler, \"o--\", label=\"Euler, h=0.1\", markersize=3)\n",
        "ax.set_xlabel(\"Tiempo t\")\n",
        "ax.set_ylabel(\"Estado x(t)\")\n",
        "ax.set_title(\"Relajación lineal\")\n",
        "ax.grid(alpha=0.3)\n",
        "ax.legend()\n",
        "fig.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Práctica 5. Mapa logístico y diagnóstico finito\n",
        "\n",
        "El mapa logístico es $x_{n+1}=r x_n(1-x_n)$. La órbita se calcula con una función propia. El promedio $N^{-1}\\sum \\log|r(1-2x_n)|$ estima un exponente de Lyapunov de tiempo finito sobre la trayectoria calculada. Un valor positivo es un diagnóstico numérico sensible al intervalo, al transitorio y a la precisión; no sustituye una demostración matemática.\n",
        "\n",
        "**Ejercicio:** compara `r=3.2`, `r=3.5` y `r=3.9`, modifica el transitorio y explica qué cambia en la serie y en el diagnóstico."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "def orbita_logistica(r, x0, pasos, transitorio=0):\n",
        "    x = float(x0)\n",
        "    valores = []\n",
        "    for n in range(pasos + transitorio):\n",
        "        x = r * x * (1.0 - x)\n",
        "        if n >= transitorio:\n",
        "            valores.append(x)\n",
        "    return np.asarray(valores)\n",
        "\n",
        "def lyapunov_finito_logistico(r, orbita):\n",
        "    derivadas = np.abs(r * (1.0 - 2.0 * orbita))\n",
        "    derivadas = np.maximum(derivadas, np.finfo(float).tiny)\n",
        "    return float(np.mean(np.log(derivadas)))\n",
        "\n",
        "r = 3.9\n",
        "orbita = orbita_logistica(r, x0=0.2, pasos=250, transitorio=500)\n",
        "diagnostico = lyapunov_finito_logistico(r, orbita)\n",
        "print(f\"Diagnóstico de Lyapunov de tiempo finito: {diagnostico:.5f}\")\n",
        "\n",
        "fig, ax = plt.subplots(figsize=(8, 4))\n",
        "ax.plot(np.arange(orbita.size), orbita, linewidth=0.8)\n",
        "ax.set_xlabel(\"Iteración n\")\n",
        "ax.set_ylabel(\"x_n\")\n",
        "ax.set_title(f\"Mapa logístico, r={r}\")\n",
        "ax.grid(alpha=0.25)\n",
        "fig.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Registro de resultados\n",
        "\n",
        "Anota los parámetros usados, la salida numérica, las modificaciones realizadas y una interpretación breve. Conserva también los casos que no produjeron el resultado esperado: forman parte de la verificación del programa."
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "name": "python",
      "version": "3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}
