{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "provenance": [],
      "toc_visible": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "source": [
        ">>> LOS TEOREMAS DE LOS MUCHOS MONOS\n",
        ">>>>\n",
        ">>>> José Rodríguez\n",
        ">>>>\n",
        ">>>> jose@ejristos.com\n",
        ">>>>\n",
        ">>>> 21 de noviembre de 2025.\n",
        ">>>>\n",
        ">>>> Bogotá, Colombia.\n",
        "\n",
        "> *Resumen.*\n",
        "\n",
        "En un trabajo anterior demostramos que para el azar encontrar una cadena binaria que coincida con una cadena objetivo es una tarea ardua, imaginando que se trata de un mono tecleando en una máquina de escribir. En este trabajo, extendemos nuestro estudio a muchos monos para observar qué sucede."
      ],
      "metadata": {
        "id": "Pwy1bO-RQ6yJ"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "# PREDICCIONES BÁSICAS\n",
        "\n",
        "En [nuestro trabajo anterior](https://colab.research.google.com/drive/12Ppt_Kwk-QAzDycVq2Wlo2wtaJy8izSX?usp=sharing), estudiamos la capacidad de un mono para reproducir una cadena binaria pulsando aleatoriamente una máquina de escribir. Descubrimos que esto explica el origen de nuestro ADN, pero con una probabilidad tan baja que se recomienda encarecidamente buscar otras alternativas. Por lo tanto, nuestro plan aquí es considerar a muchos monos en lugar de uno solo.\n",
        "\n",
        "La predicción básica es que muchos monos lo hacen mejor que uno solo. Por lo tanto, necesitamos cuantificar la ventaja. Para ello, nuestra intención es utilizar, entre otras herramientas, la Distribución Geométrica, ya empleada en el trabajo anterior. Repasémosla:\n",
        "\n",
        "> *Definición.* La variable aleatoria $X$ sobre números 0, 1, 2, ..., con probabilidad de $k$ dada por:\n",
        "\n",
        ">>\n",
        ">> $p(k) = (1-p)^kp$\n",
        "\n",
        ">>\n",
        ">> se denomina ***Variable Aleatoria Geométrica***. También se la conoce como ***Distribución Geométrica***, lo que significa que $X$ sigue una distribución determinada por $p(k)$. En términos más sencillos, al repetir el proceso, la frecuencia relativa de experimentar $k$ fracasos antes del primer éxito converge al valor esperado $p(k)$.\n",
        "\n",
        "Para aplicar esta distribución a nuestro escenario con varios monos, debemos considerar al grupo completo como una sola entidad y determinar la probabilidad de éxito para todo el conjunto.\n",
        "\n",
        "El grupo de monos tiene éxito si un solo mono lo logra. Pero esto también ocurre si dos monos tienen éxito o si cualquier número de monos lo logra. El cálculo exacto se realiza mediante la [distribución binomial](https://colab.research.google.com/drive/1r2-BqBHswT2Xus35ZHp6G1-AGbAvz20f?usp=sharing):\n",
        "\n",
        "> Si la probabilidad de éxito de un mono es $a$, la probabilidad de éxito de $r$ monos entre $n$ monos es:\n",
        ">>\n",
        ">> $ p(r) = $ $n\\choose r$ $p^r (1-p)^{n-r}$\n",
        "\n",
        ">>\n",
        "ya que $r$ monos deben tener éxito y $n-r$ fracasar. En particular, la probabilidad de fracaso global es:\n",
        ">>\n",
        ">> $ p(0) = $ $n\\choose 0$ $p^0 (1-p)^{n}$\n",
        "\n",
        ">>\n",
        ">> $ p(0) = (1-p)^{n}$\n",
        "\n",
        "El grupo completo de monos tiene éxito si $r >= 1$ y, por supuesto, si $r <= n$. Por lo tanto, la probabilidad de éxito del conjunto $p(E)$ de $n$ monos es\n",
        "\n",
        "\n",
        "$ p(E)  = p(r >= 1) $\n",
        ">\n",
        ">  $ = \\sum^n_1 p(r) $\n",
        ">\n",
        "> $ =   \\sum^n_1 $$n\\choose r$  $p^r (1-p)^{n-r}$\n",
        ">\n",
        "> $ = 1- p(0) $,\n",
        ">\n",
        "> $ = 1 - (1-p)^n$\n",
        "\n",
        "> *Test.* Si tenemos un mono en el conjunto, la probabilidad de éxito del conjunto debe ser igual a $p$. ¿Es eso cierto? Veamos:\n",
        "\n",
        ">>\n",
        ">> $ p(E) = 1 - (1-p)^1 = 1 - (1-p) = 1-1+p = p$\n",
        "\n",
        ">>\n",
        ">> como debe ser. Nuestra fórmula pasó la prueba.\n",
        "\n",
        "> *Ejercicio.* Ya conocemos la probabilidad de éxito del conjunto, $p(E)$, como unidad. ¿Cuál es, entonces, la probabilidad de $k$ fracasos antes del éxito de todo el conjunto?\n",
        "\n",
        ">> *Respuesta.* Cuando la probabilidad de éxito de un solo mono es $p$, la probabilidad  $p(k)$ de $k$ fracasos antes del éxito es\n",
        ">>\n",
        ">>> $p(k) = (1-p)^kp$\n",
        ">>\n",
        ">> Así que cuando $p = p(E)$, $p(k)$ es\n",
        ">>>\n",
        ">>> $p(k) = (1-p(E))^k p(E)$\n",
        ">>>\n",
        ">>> $ p(k)  = (1-( 1 - (1-p)^n))^k ( 1 - (1-p)^n)$\n",
        ">>>\n",
        ">> donde $n$ es el número de monos y $k$ es el número de fracasos antes del éxito.\n",
        "\n",
        "> *Ejercicio.* Ponga a prueba la fórmula anterior: ella debe reducirse a $(1-p)^kp$ si $n = 1$.\n",
        "\n",
        ">> *Respuesta.* Si $n=1$,\n",
        ">>>\n",
        ">>> $p(k) = (1-( 1 - (1-p) ))^k ( 1 - (1-p))$\n",
        ">>>\n",
        ">>> $p(k) = (1- 1 + (1-p) )^k ( 1 - 1+p))$\n",
        ">>>\n",
        ">>> $p(k) = ( 1-p)^k p$\n",
        ">>>\n",
        ">> Pasamos la prueba.\n",
        "\n",
        "> *Ejercicio.* Demuestre que\n",
        ">>\n",
        ">> $\\sum^\\infty_{k = 0} p(k) = 1$\n",
        ">>\n",
        ">> Si esto es cierto, y dado que todos los $p(k)$ son positivos, podemos afirmar que $p(k)$ representa una probabilidad.\n",
        ">>\n",
        ">> *Respuesta.*\n",
        ">>\n",
        ">> $\\sum^\\infty_{k = 0} p(k) = $\n",
        ">>\n",
        ">>> $= \\sum^\\infty_{k = 0}(1-( 1 - (1-p)^n))^k ( 1 - (1-p)^n)$\n",
        ">>\n",
        ">> Si renombramos $1 - (1-p)^n$ como $p$, obtenemos\n",
        ">>\n",
        ">>> $ \\sum^\\infty_{k = 0} p(k)  = \\sum^\\infty_{k = 0}(1-p)^k p $\n",
        ">>\n",
        ">> Esta es la distribución geométrica ordinaria, siempre que\n",
        ">>\n",
        ">> $1 - (1-p)^n > 0$\n",
        ">>\n",
        ">> Esto es cierto porque para $ 0 < p < 1$, tenemos\n",
        ">>\n",
        ">> $1 > 1-p > (1-p)^n$\n",
        ">>\n",
        ">> de donde obtenemos\n",
        ">>\n",
        ">> $1-(1-p)^n > 0$.\n",
        "\n",
        "> *Interpretación.* La ecuación\n",
        "\n",
        ">>\n",
        ">> $\\sum^\\infty_{k = 0} p(k) = 1$\n",
        "\n",
        ">>\n",
        "> es el **Teorema de los Muchos Monos para tiempo infinito**, que afirma que un conjunto de monos algún día logrará reproducir cualquier texto, por muy complejo que sea.\n",
        "\n",
        "> *Ejercicio.* Resuelva la siguiente objeción:\n",
        "\n",
        ">>\n",
        ">> *Objeción.* La probabilidad de éxito es, en general, ínfima, y ​​la de fracaso es casi 1. Por lo tanto, nada impide que un proceso pueda consistir en fracaso tras fracaso, incluso más allá de la eternidad.\n",
        "\n",
        ">>\n",
        ">> *Respuesta.* Todos los fracasos se pueden contar y son finitos en número. Por lo tanto, nunca se puede afirmar que se ha llegado al infinito.   Para comprobar el poder del teorema, siga experimentando..\n",
        "\n",
        "> *Ejercicio.* Desarrolle el código para 200 repeticiones de una simulación de la Distribución Geométrica con $p = 0.1$ y una pareja de monos para estimar su distribución, con media, varianza y gráfica. ¿Considera que es beneficioso formar parte de una pareja?\n",
        "\n",
        "> > *Respuesta.* El código para estimar la distribución de la Distribución Geométrica que surge de una pareja de monos es el siguiente. Es una adaptación sencilla del código desarrollado por Gemini para un solo mono. Este tipo de adaptaciones será  nuestro comportamiento habitual."
      ],
      "metadata": {
        "id": "1MlbrfqXRw65"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Geometric distribution for two monkeys\n",
        "import random\n",
        "import statistics\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "def geometric_simulation_trials2(p):\n",
        "  \"\"\"\n",
        "  Simulates a geometric distribution and returns the number of trials until the first success.\n",
        "\n",
        "  Args:\n",
        "    p: The probability of success (a number between 0 and 1).\n",
        "\n",
        "  Returns:\n",
        "    The number of trials until the first success.\n",
        "  \"\"\"\n",
        "  trials = 0\n",
        "  while True:\n",
        "    trials += 1\n",
        "    if random.random() < p:\n",
        "      break\n",
        "    else:\n",
        "      if random.random() < p:\n",
        "        break\n",
        "  return trials\n",
        "\n",
        "# Parameters for the simulation\n",
        "p = 0.1\n",
        "num_repetitions = 200\n",
        "\n",
        "# Run the simulation multiple times and store the number of trials\n",
        "trial_counts = [geometric_simulation_trials2(p) for _ in range(num_repetitions)]\n",
        "\n",
        "# Calculate and print the mean and variance\n",
        "mean_trials = statistics.mean(trial_counts)\n",
        "variance_trials = statistics.variance(trial_counts)\n",
        "\n",
        "print(f\"Mean number of trials after {num_repetitions} repetitions with p={p}: {mean_trials:.2f}\")\n",
        "print(f\"Variance of the number of trials after {num_repetitions} repetitions with p={p}: {variance_trials:.2f}\")\n",
        "\n",
        "# Plot a histogram of the trial counts\n",
        "plt.figure(figsize=(10, 6))\n",
        "plt.hist(trial_counts, bins=range(min(trial_counts), max(trial_counts) + 2), density=True, alpha=0.75, edgecolor='black')\n",
        "plt.title('Estimated Geometric Distribution')\n",
        "plt.xlabel('Number of Trials Until First Success')\n",
        "plt.ylabel('Probability Density')\n",
        "plt.grid(axis='y', alpha=0.75)\n",
        "plt.show()"
      ],
      "metadata": {
        "id": "MDlmFX-CP1w-"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "Ahora podemos comparar estos resultados para dos monos con los de un solo mono.\n",
        "\n",
        "Para dos monos:\n",
        "\n",
        "> Número medio de ensayos tras 200 repeticiones con p=0,1: 5,22\n",
        "\n",
        "> Varianza del número de ensayos tras 200 repeticiones con p=0,1: 22,36\n",
        "\n",
        "Para un mono (resultados del programa correspondiente en el trabajo anterior):\n",
        "\n",
        "> Número medio de ensayos tras 200 repeticiones con p=0,1: 8,52\n",
        "\n",
        "> Varianza del número de ensayos tras 200 repeticiones con p=0,1: 52,27\n",
        "\n",
        "Observamos que una pareja tiene éxito tras una media de 5,22 ensayos, mientras que un solo mono necesita 8,52. Por lo tanto, una pareja tiene éxito más rápido que un mono solo. Además, la media tiene mayor poder predictivo para dos monos que para uno, ya que la varianza para dos monos es de 22,36, mientras que para uno es de 52,27.\n",
        "\n",
        "Veredicto: Formar parte de una pareja resulta muy beneficioso, tanto en promedio como en su capacidad predictiva. Esto ayuda a explicar por qué la mayoría de las personas prefieren casarse, siempre y cuando nos apoyemos mutuamente. Esto también garantiza que el sexo sea una institución fundamental en biología, ya que todo en biología gira en torno a la complejidad.\n",
        "\n",
        "> *Ejercicio*. Generalice el código anterior de 2 a $n$  monos.\n",
        "\n",
        "> > *Respuesta*. La generalización para $n$ monos es la siguiente:"
      ],
      "metadata": {
        "id": "H4pPt6d1ZJuJ"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Geometric distribution for many monkeys\n",
        "import random\n",
        "import statistics\n",
        "import matplotlib.pyplot as plt\n",
        "import time\n",
        "\n",
        "start_time = time.time() # Record the start time\n",
        "print(\"Program starts\")\n",
        "\n",
        "# Parameters for the simulation\n",
        "p = 0.1\n",
        "num_monkeys = 10\n",
        "num_repetitions = 200\n",
        "\n",
        "\n",
        "def geometric_simulation_trials_m(p, n):\n",
        "    \"\"\"\n",
        "    Simulates a geometric distribution and returns the number of trials until the first success.\n",
        "\n",
        "    Args:\n",
        "        p: The probability of success (a number between 0 and 1).\n",
        "        n: The number of monkeys.\n",
        "\n",
        "    Returns:\n",
        "        The number of trials until the first success.\n",
        "    \"\"\"\n",
        "    trials = 0\n",
        "    while True:\n",
        "        trials = trials + 1\n",
        "        for _ in range(n):\n",
        "            t = random.random()\n",
        "            # print(f\"t = {t}\")\n",
        "            if t < p:\n",
        "                # print(f\"Number of trials = {trials}\")\n",
        "                return trials\n",
        "\n",
        "\n",
        "\n",
        "# Run the simulation multiple times and store the number of trials\n",
        "trial_counts = [geometric_simulation_trials_m(p, num_monkeys) for _ in range(num_repetitions)]\n",
        "\n",
        "# Calculate and print the mean and variance\n",
        "mean_trials = statistics.mean(trial_counts)\n",
        "variance_trials = statistics.variance(trial_counts)\n",
        "\n",
        "print(f\"Mean number of trials after {num_repetitions} repetitions with p={p}: {mean_trials:.2f}\")\n",
        "print(f\"Variance of the number of trials after {num_repetitions} repetitions with p={p}: {variance_trials:.2f}\")\n",
        "\n",
        "# Plot a histogram of the trial counts\n",
        "plt.figure(figsize=(10, 6))\n",
        "plt.hist(trial_counts, bins=range(min(trial_counts), max(trial_counts) + 2), density=True, alpha=0.75, edgecolor='black')\n",
        "plt.title(f'Estimated Geometric Distribution for {num_monkeys} monkeys, p(succes of 1 monkey) = {p}')\n",
        "plt.xlabel('Number of Trials Until First Success')\n",
        "plt.ylabel('Probability Density')\n",
        "plt.grid(axis='y', alpha=0.75)\n",
        "plt.show()\n",
        "\n",
        "end_time = time.time() # Record the end time\n",
        "elapsed_time = end_time - start_time\n",
        "\n",
        "print(f\"Script execution time: {elapsed_time:.4f} seconds\")"
      ],
      "metadata": {
        "id": "EEnKVJWhxDnz"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "> *Ejercicio.* Ejecute el programa y evalúelo.\n",
        "\n",
        "> > *Respuesta.* Muchos monos parecen ser mucho mejores que uno solo, ya que el éxito parece llegar muy pronto si 10 monos trabajan al mismo tiempo.\n",
        "\n",
        "> *Desafío general.* Siga el plan ejecutado en el trabajo anterior con un solo mono e impleméntelo con cualquier número de monos.\n",
        "\n",
        ">> *Nuestra directriz.* Nos centraremos únicamente en los puntos más importantes. Transcribimos los desarrollos completos para facilitar la comprensión total."
      ],
      "metadata": {
        "id": "VVD8g8SSZike"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Test de la predicción básica\n",
        "\n",
        "Comparemos las predicciones de nuestras fórmulas con los resultados de una simulación.\n",
        "\n",
        "> *Ejercicio.* Compare la distribución observada en el programa anterior con los valores predichos por nuestras fórmulas.\n",
        "\n",
        ">> *Respuesta.* La mejor manera de comparar los valores observados con los predichos es mediante un gráfico. Esto se muestra en el siguiente programa:"
      ],
      "metadata": {
        "id": "zi0Wp6l6Z2vi"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Geometric distribution for many monkeys\n",
        "import random\n",
        "import statistics\n",
        "import matplotlib.pyplot as plt\n",
        "import time\n",
        "import numpy as np\n",
        "\n",
        "start_time = time.time() # Record the start time\n",
        "print(\"Program starts\")\n",
        "\n",
        "# Parameters for the simulation\n",
        "p = 0.01\n",
        "num_monkeys = 10\n",
        "n = num_monkeys\n",
        "num_repetitions = 200\n",
        "\n",
        "def geometric_simulation_trials_m2(p, n):\n",
        "    \"\"\"\n",
        "    Simulates a geometric distribution and returns the number of trials until the first success.\n",
        "\n",
        "    Args:\n",
        "        p: The probability of success (a number between 0 and 1).\n",
        "        n: The number of monkeys.\n",
        "\n",
        "    Returns:\n",
        "        The number of trials until the first success.\n",
        "    \"\"\"\n",
        "    trials = -1\n",
        "    while True:\n",
        "        trials = trials + 1\n",
        "        for _ in range(n):\n",
        "            t = random.random()\n",
        "            # print(f\"t = {t}\")\n",
        "            if t < p:\n",
        "                # print(f\"Number of trials = {trials}\")\n",
        "                return trials\n",
        "\n",
        "# Run the simulation multiple times and store the number of trials\n",
        "trial_counts = [geometric_simulation_trials_m2(p, num_monkeys) for _ in range(num_repetitions)]\n",
        "\n",
        "# Calculate and print the mean and variance\n",
        "mean_trials = statistics.mean(trial_counts)\n",
        "variance_trials = statistics.variance(trial_counts)\n",
        "\n",
        "print(f\"Mean number of trials after {num_repetitions} repetitions with p={p}: {mean_trials:.2f}\")\n",
        "print(f\"Variance of the number of trials after {num_repetitions} repetitions with p={p}: {variance_trials:.2f}\")\n",
        "\n",
        "def predicted(p,n,k):\n",
        "    \"\"\"Calculate the theoretical geometric distribution for an ensemble of n monkeys\"\"\"\n",
        "    #   $ p(k)  = f = (1-( 1 - (1-p)^n))^k ( 1 - (1-p)^n)$\n",
        "    # where $n$ is the number of monkeys and $k$ is the number of failures before success.\n",
        "    g = (1-p)**n #Probability of global failure\n",
        "    s = 1-g # Probability of success of the ensemble, at least one monkey succeeds\n",
        "    f = ((1-s)**k)* s # Geometric distribution for the ensemble\n",
        "    return f\n",
        "\n",
        "#Expected probabilties as a function of k with paramters p and n\n",
        "l = np.arange(0, max(trial_counts) + 1)\n",
        "theoretical_probabilities = [predicted(p,n,k) for k in l]\n",
        "\n",
        "\n",
        "# Plot a histogram of the trial counts\n",
        "plt.figure(figsize=(10, 6))\n",
        "plt.hist(trial_counts, bins=range(min(trial_counts), max(trial_counts) + 2), density=True, alpha=0.75, edgecolor='black')\n",
        "plt.title(f'Estimated Geometric Distribution for {num_monkeys} monkeys, p = {p}, {num_repetitions} repetitions')\n",
        "plt.xlabel('Number of Trials Until First Success')\n",
        "plt.ylabel('Probability Density')\n",
        "plt.grid(axis='y', alpha=0.75)\n",
        "plt.plot(l, theoretical_probabilities, 'r-', label='Theoretical Distribution')\n",
        "plt.legend( )\n",
        "plt.show()\n",
        "\n",
        "end_time = time.time() # Record the end time\n",
        "elapsed_time = end_time - start_time\n",
        "\n",
        "print(f\"Script execution time: {elapsed_time:.4f} seconds\")"
      ],
      "metadata": {
        "id": "gyYo3iKVpEFm"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "> *Ejercicio.* Ejecute el programa y evalúelo.\n",
        "\n",
        "> > *Respuesta.* El ajuste de los valores predichos a los observados es bueno a pesar de que solo se realizaron 200 repeticiones.\n",
        "\n",
        "Hemos visto propiedades generales de la Distribución Geométrica aplicadas a un conjunto de monos. Ahora, centrémonos en el caso en que el éxito consiste en encontrar una coincidencia con una cadena objetivo que podría ser binaria o de ADN."
      ],
      "metadata": {
        "id": "DzrjwU_qag7H"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "# EL RETO SHAKESPEARE\n",
        "\n",
        "Nuestro objetivo es explorar las posibilidades que ofrece el azar para explicar nuestro ADN. Sin embargo, desde el punto de vista lingüístico, nuestro ADN no posee ninguna característica especial. Por lo tanto, explicar el ADN se transforma en explicar, por ejemplo, una obra de Shakespeare. O, como hacemos nosotros, explicar una cadena binaria arbitraria.\n",
        "\n",
        "> *Definición.* El **reto Shakespeare** consiste en encontrar una cadena objetivo de longitud $H$, describiendo el procedimiento y el coste computacional para lograrlo.\n",
        "\n",
        "> *Ejercicio.* Calcule las predicciones de la distribución geométrica cuando el éxito se define como la resolución del reto Shakespeare utilizando cadenas binarias aleatorias de la misma longitud. En el folclore, cada cadena aleatoria representa la propuesta de un mono.\n",
        "\n",
        "> > *Respuesta.* Debemos calcular la probabilidad de encontrar una cadena binaria objetivo de longitud $H$ utilizando cadenas binarias aleatorias. Procedamos.\n",
        "\n",
        "En la cadena binaria, cada sitio tiene dos opciones, y dado que tenemos $H$ sitios, existen $2^H$ cadenas diferentes posibles con igual probabilidad. Por lo tanto, la probabilidad de que un mono encuentre la cadena objetivo es equivalente a la probabilidad de seleccionar la cadena coincidente, que es igual a $1/2^H$.\n",
        "\n",
        "¿Qué ocurre con la probabilidad de éxito si tenemos $n$ monos?\n",
        "\n",
        "El conjunto de monos tiene éxito si un solo mono lo logra. Pero esto también ocurre si dos monos tienen éxito o si cualquier número de monos lo logra. El cálculo exacto se realiza mediante la distribución binomial:\n",
        "\n",
        "La probabilidad de éxito de un mono es $a = 1/2^H$, y la probabilidad de éxito de $r$ monos entre los $n$ monos es:\n",
        "\n",
        "$p(r) = $ $n \\choose r$ $a^r (1-a)^{n-r}$\n",
        "\n",
        "porque $r$ monos tuvieron éxito y $n-r$ fracasaron. En particular, la probabilidad de fracaso global es:\n",
        "\n",
        ">\n",
        "> $p(0) = $ $n\\choose 0$ $a^0 (1-a)^{n}$\n",
        "\n",
        ">\n",
        "> $p(0) = (1-a)^{n}$\n",
        "\n",
        "La unidad completa de monos tiene éxito si $r >= 1$ y, por supuesto, si $r <= n$. Por lo tanto, la probabilidad de éxito del conjunto $p(E)$ de $n$ monos es:\n",
        "\n",
        "$ p(E)  = p(r >= 1) $\n",
        ">\n",
        ">  $ = \\sum^n_1 p(r) $\n",
        ">\n",
        "> $ =   \\sum^n_1 $$n\\choose r$  $a^k (1-a)^{n-r}$\n",
        ">\n",
        "> $ = 1- p(0) $,\n",
        ">\n",
        "> $ = 1 - (1-a)^n$\n",
        "\n",
        "Recordando que $ a = 1/2^H$,\n",
        ">\n",
        "> $ p(E)  =  1 - (1-\\frac{1}{2^H})^n$\n",
        ">\n",
        "> donde $n$ es el número de monos y $H$ es la longitud de cada una de las cadenas binarias.\n",
        "\n",
        "Esta es la probabilidad de éxito del conjunto como unidad.\n",
        "\n",
        "Ahora podemos sustituir $p(E)$ en la distribución geométrica que da la probabilidad de éxito después de $k$ intentos fallidos:\n",
        ">\n",
        "> $p(k) = (1-p)^kp$\n",
        "\n",
        ">\n",
        "> $p(k) = (1-p(E))^kp(E)$\n",
        "\n",
        ">\n",
        "> $p(k) = (1-(1 - (1-\\frac{1}{2^H})^n))^k(1 - (1-\\frac{1}{2^H})^n)$\n",
        "\n",
        "¿Cómo podemos demostrar que la suma de estas expresiones sobre $k$ es 1?\n",
        "\n",
        "La demostración ya se realizó en el trabajo anterior. Se realizó para la distribución geométrica con cualquier valor de la probabilidad de éxito, $p$. Lo único que hacemos aquí es especificar $p = p(E)$. En cualquier caso, debe ser positiva. ¿Es cierto? Veamos:\n",
        ">\n",
        "> $ p(E)  =  1 - (1-\\frac{1}{2^H})^n > 0 ?$\n",
        ">\n",
        " ¡Sí! Esto es verdad porque\n",
        ">\n",
        ">  $2^H \\ge 0$\n",
        ">\n",
        "> $\\frac{1}{2^H} \\ge 0$\n",
        ">\n",
        ">\n",
        "> $-\\frac{1}{2^H} \\le 0$\n",
        ">\n",
        "> Añadiendo 1 a ambos lados\n",
        ">\n",
        "> $1-\\frac{1}{2^H} \\le 1 $\n",
        ">\n",
        "> Aplicando antigravedad\n",
        ">\n",
        "> $- (1-\\frac{1}{2^H}) \\ge -1 $\n",
        ">\n",
        "> Añadiendo 1 a ambos lados\n",
        ">\n",
        "> $  1 - (1-\\frac{1}{2^H})^n \\ge 0$\n",
        ">\n",
        "> como se buscaba.\n",
        "\n",
        "En conclusión, hemos demostrado el siguiente resultado:\n",
        "\n",
        "> *Teorema.* Si el éxito consiste en encontrar una cadena binaria objetivo de longitud $H$ utilizando $n$ cadenas binarias aleatorias de la misma longitud por repetición, la probabilidad de éxito tras $k$ fallos viene dada por:\n",
        "\n",
        ">\n",
        "> $p(k) = (1-(1 - (1-\\frac{1}{2^H})^n))^k(1 - (1-\\frac{1}{2^H})^n)$\n",
        "\n",
        "O, para facilitar la lectura, por:\n",
        "\n",
        ">\n",
        "> $p(k) = (1-p(E))^kp(E)$\n",
        "\n",
        "donde $p(E)$, la probabilidad de éxito del conjunto de $n$ monos en una repetición, se define como:\n",
        "\n",
        ">\n",
        "> $p(E) = 1 - (1-\\frac{1}{2^H})^n$\n",
        ">"
      ],
      "metadata": {
        "id": "RQnUKoX4a8FW"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "# ESTUDIO DE UN CASO CONCRETO\n",
        "\n",
        "La tarea consiste en explicar la existencia de una cadena de ADN de 100 bases. Necesitamos saber cuánto disminuye el costo si trabajamos con muchos monos en lugar de con uno solo.\n",
        "\n",
        "> *Ejercicio.* Compare la probabilidad de éxito en una ronda con un mono frente a la probabilidad de éxito con 1000 monos para sintetizar una cadena de ADN de 100 bases.\n",
        "\n",
        "> > *Respuesta.* El número de opciones asociadas para una cadena de ADN con 100 bases es:\n",
        "\n",
        ">>\n",
        ">> $4^{100} = (10^{0.61})^{100} = 10^{61}$\n",
        "\n",
        ">>\n",
        "> Es decir, un 10 seguido de 60 ceros. Y la probabilidad correspondiente es:\n",
        "\n",
        ">>\n",
        ">> $p = \\frac{1}{10^{61}} = 1 \\times 10^{-61}$\n",
        "\n",
        ">>\n",
        "> Esta $p$ es la probabilidad de éxito de un mono en un ensayo. La probabilidad correspondiente para $n$ monos es:\n",
        ">>\n",
        ">> $ p(E) = 1 - (1-\\frac{1}{4^H})^n$\n",
        "\n",
        ">>\n",
        "> Cuando el número de monos es $n= 1000$ y la longitud de la cadena de ADN es $H = 100$, la probabilidad de éxito del conjunto en una repetición $ p(E) $ es:\n",
        ">>\n",
        ">> $ p(E) = 1 - (1-\\frac{1}{4^{100}})^{1000}$\n",
        "\n",
        ">>\n",
        ">\n",
        "> Para evaluar el binomio incluido, usemos la expansión binomial:\n",
        ">>\n",
        ">> $(a+b)^n = a^n + na^{n-1}b + \\frac{n(n-1)}{2}a^{n-2}b^2 +  \\frac{n(n-1)(n-2)}{2 \\times 3}a^{n-3}b^3 +  \\frac{n(n-1)(n-2)(n-3)}{2 \\times 3 \\times 4}a^{n-4}b^4  +...+  \\frac{n(n-1)(n-2)(n-3)(n-4)...(n-k+1)}{k!}a^{n-k}b^k+ ...$  \n",
        ">>\n",
        ">> Aquí $a = 1$, $b = -1/n$ y el exponente es $m$. El signo menos de  $b$ dice que los signos se alternarán en la expansión:\n",
        ">>\n",
        ">> $ (1 - 1/n)^m = 1 - m (1/n) + \\frac{m(m-1)}{2} (1/n)^2  -   \\frac{m(m-1)(m-2)}{3!} (1/n)^3 + ...$\n",
        ">>\n",
        ">> Reemplazando, $n = 4^{100} = 10^{61}$ y $m = 1000 =  10^{3} $, tenemos:\n",
        ">>>\n",
        ">>>  $ (1 - 1/10^{61})^{10^{3}} = 1 - (10^{3}) (1/10^{61}) + \\frac{(10^{3}) (10^{3}) }{2} (1/10^{61})^2  -   \\frac{(10^{3})(10^{3})(10^{3}) }{3!} (1/10^{61})^3 + ...$\n",
        ">>>\n",
        ">>> $ = 1 - 1/10^{58} + \\frac{1}{2} \\frac{10^{6}}{10^{122}} - \\frac{1}{6} \\frac{10^{9}}{10^{183}} +  ...$\n",
        ">>>\n",
        ">>> $ = 1 - \\frac{1}{10^{58}} + \\frac{1}{2} \\frac{1 }{10^{116}} - \\frac{1}{6} \\frac{1 }{10^{174}} +  ...$\n",
        ">>\n",
        ">> Finalmente tenemos:\n",
        ">>\n",
        ">>  $  (1-\\frac{1}{4^{100}})^{1000} = (1 - \\frac{1}{10^{61}})^{10^{3}} =  1 - \\frac{1}{10^{58}}$\n",
        ">>\n",
        ">>\n",
        ">> Por lo ranto, la probabilidad de éxito del conjunto como un todo en una ronda es  \n",
        ">>\n",
        ">>\n",
        ">> $ p(E)  =  1 - (1-\\frac{1}{4^{100}})^{1000}$\n",
        ">>\n",
        ">> $ p(E)  =  1 - (1 - \\frac{1}{10^{58}})$\n",
        ">>\n",
        ">> $ p(E)  =    \\frac{1}{10^{58}}$\n",
        "\n",
        "> Observemos que en vez de tomar la expansión completa, podemos tomar los dos primeros términos:  \n",
        "\n",
        ">> $(a+b)^n = a^n + na^{n-1}b$\n",
        ">>\n",
        ">> obtenemos\n",
        ">>\n",
        ">> $(1-\\frac{1}{4^{100}})^{1000} = 1+ 1000 \\times 1 \\times (-\\frac{1}{4^{100}})$\n",
        ">>>\n",
        ">>> $ = 1 - 1000 \\times 1 \\times (\\frac{1}{4^{100}})$\n",
        ">>>\n",
        ">>> $ = 1 - 10^3  \\times (10^{0.61})^{-100}$\n",
        ">>>\n",
        ">>> $ = 1 - 10^3  \\times 10^{-61} $\n",
        ">>>\n",
        ">>> $ = 1 -   10^{-58} $\n",
        ">>>\n",
        ">> Por tanto:\n",
        ">>>\n",
        ">>> $ p(E)  =  1 - (1-\\frac{1}{4^{100}})^{1000}$\n",
        ">>>\n",
        ">>> $ p(E)  =  1 - (1 -   10^{-58})$\n",
        ">>>\n",
        ">>> $ p(E)  =    10^{-58}$\n",
        ">>>\n",
        ">> Según nuestro tratamiento, el efecto del número de monos es insignificante: 1000 monos aumentan la probabilidad de éxito de $10^{-61}$ a $10^{-58}$. Esto en una sola ronda. ¿Qué podría suceder si permitimos muchísimas repeticiones?\n",
        "\n",
        "> *Ejercicio.* Estime el número de repeticiones permitidas durante la edad de la Tierra.\n",
        "\n",
        "> *Respuesta.* Imaginemos que una repetición dura 1 milisegundo. La edad aceptada de la Tierra es de 5000 millones de años. El número de repeticiones que se pueden realizar durante ese lapso de tiempo es:\n",
        ">\n",
        ">> $N = 1000 \\times 60 \\times 60 \\times 24 \\times 365 \\times 5000 \\times 1000000 $\n",
        ">>\n",
        ">> $= 6 \\times 6 \\times 24 \\times 365 \\times 5 \\times 100000 \\times 1000 \\times 1000000 $\n",
        ">>\n",
        ">> $= 1576800 \\times   100000 \\times 1000 \\times 1000000 $\n",
        ">>\n",
        ">> $= 15768 \\times 10^{16} $\n",
        ">>\n",
        ">> $= 20000 \\times 10^{16} $\n",
        ">>\n",
        ">> $= 2 \\times 10^{20} $\n",
        "\n",
        "esto es un 2 seguido de 20 ceros.\n",
        "\n",
        "> *Ejercicio.* Calcule la probabilidad de éxito de 1000 monos al reproducir una secuencia de ADN de 100 bases si trabajan durante la edad de la Tierra.\n",
        "\n",
        "> > *Respuesta.* Los 1000 monos pueden tener éxito en el primer intento, en el segundo, en el tercero o en cualquier repetición. Esto significa que debemos trabajar con la función de distribución acumulativa (FDA):\n",
        "\n",
        "> > La FDA (Función de Distribución Acumulativa) de la distribución geométrica es:\n",
        ">>\n",
        ">>> $F_X(m) = P(X < m) = 1 - (1-p)^{m+1} $\n",
        ">>>\n",
        ">> Esta función devuelve la probabilidad acumulada de éxito cuando se permiten $m$ repeticiones y la probabilidad de éxito es $p$.\n",
        "\n",
        ">> Por lo tanto, la probabilidad acumulada de éxito de un conjunto de 1000 monos para la edad de la Tierra es:\n",
        "\n",
        ">>\n",
        ">>> $F_X(n) = 1 - (1 - 10^{-58})^{ 2 \\times 10^{20} } $\n",
        "\n",
        "> Usando la expansión binomial para $b$ pequeño hasta el segundo término:\n",
        ">>\n",
        ">> $(a+b)^n = a^n + na^{n-1}b$\n",
        ">>\n",
        ">> obtenemos\n",
        ">>\n",
        ">> $F_X(n) = 1 - (1 +  2 \\times 10^{20}(- 10^{-58}) )$\n",
        ">>\n",
        ">> $F_X(n) = 1 - (1 -  2 \\times 10^{20}( 10^{-58}) )$\n",
        ">>\n",
        ">> $F_X(n) = 1 - 1 + 2 \\times 10^{20}( 10^{-58}) $\n",
        ">>\n",
        ">> $F_X(n) =   2 \\times 10^{20}( 10^{-58}) $\n",
        ">>\n",
        ">> $F_X(n) =   2 \\times  10^{-38}$\n",
        "\n",
        "Este resultado sugiere que la aleatoriedad operando en paralelo en 1000 frentes podría explicar la vida en la Tierra, pero ningún experimento factible puede respaldarlo. Por el contrario, este resultado aboga firmemente por la invención y la comprobación de otras explicaciones. Observemos que hemos tomado una cadena de ADN de 100 bases como representación de la vida.\n",
        "\n",
        "Hemos demostrado el siguiente resultado:\n",
        "\n",
        "> *Teorema de los Muchos Monos para la TIERRA.*\n",
        "\n",
        ">>\n",
        ">> La probabilidad acumulada de éxito de la aleatoriedad operando en paralelo en 1000 frentes durante la edad de la Tierra es cero experimental. ¿Qué significa esto?\n",
        "\n",
        ">> *Definición.* **Cero experimental** es la probabilidad distinta de cero de un evento tan pequeña que ningún experimento factible puede demostrar su posibilidad.\n",
        "\n",
        "*Ejercicio.* Calcula el número de repeticiones necesarias de 1000 monos para lograr un éxito con una probabilidad acumulada de 0.05 cuando $p = 10^{-58}$.  La tarea consiste en encontrar una secuencia de ADN de 100 bases.\n",
        "\n",
        ">> *Respuesta.* $F_X(m)$ es la probabilidad acumulada de éxito después de $m$ fracasos. Debemos igualar $F_X(m)$ a $0.05$ y despejar $m$.\n",
        "\n",
        ">>> $F_X(m) = 1 - (1-p)^{1 + m} = 0.05$\n",
        ">>\n",
        ">> No podemos aproximar $(1-p)$ por 1 porque obtendríamos $0 = -0.05$. Debemos usar un método más largo. Pasando 1 al lado derecho\n",
        ">>>\n",
        ">>> $ -(1-p)^{1 + n} = -1 + 0.05$\n",
        ">>>\n",
        ">>> $ (1-p)^{1 + n} = 1 - 0.05 = 0.95$\n",
        ">>>\n",
        ">> Tomando logaritmos naturales de números positivos, obtenemos\n",
        ">>>\n",
        ">>> $ln( (1-p)^{1 + n} ) = ln(0.95) = -0.051293294$\n",
        ">>>\n",
        ">>> $(1+n)ln( (1-p) ) = -0.051293294$\n",
        ">>>\n",
        ">> Dado que $p$ es tan pequeño, $1-p$ está muy cerca de 1 y, dado que $ln$ es suave, el valor de $ln( (1-p) )$ se puede hallar mediante la recta tangente, que resulta de la derivada de $ln(1-x)$ en $x= 0$:\n",
        ">>>\n",
        ">>> $\\frac{d(ln(1-x)) }{dx}= \\frac{-1}{1-x}$\n",
        "\n",
        ">> La recta tangente tiene la forma $y = mx + b$. La pendiente de la recta tangente $m$ en la gráfica de la función $ln(1-x)$ en $x = 0$ es:\n",
        ">>>\n",
        ">>> $m = -1 / (1-x) = -1$\n",
        ">>\n",
        ">> Por lo tanto,\n",
        ">\n",
        ">>> $y = -x + b$\n",
        ">\n",
        ">> Para hallar el valor de $b$, sustituimos $x = 0$ en $y = ln(1-x) = -x + b + error$. Obtenemos:\n",
        ">>>\n",
        ">>> $y = 0 = b$\n",
        ">>>\n",
        ">> Esto significa que la ecuación de la recta tangente es $y = -x$. Por lo tanto, la serie de $ln(1-x)$ en la vecindad de $x = 0$ hasta el primer término es:\n",
        ">>\n",
        ">>> $ln(1-x) = -x$\n",
        ">>\n",
        ">> Por lo tanto,\n",
        ">>>\n",
        ">>> $(1+n)ln( 1-p )   = -0.051293294$\n",
        ">>>\n",
        ">> se convierte en\n",
        ">>>\n",
        ">>> $(1+n)( -p )   = -0.051293294$\n",
        ">>>\n",
        ">>> $(1+n)( p )   = 0.051293294$\n",
        ">>>\n",
        ">>> $1+n   = \\frac{0.051293294}{p}$\n",
        ">>>\n",
        ">>> $n   = \\frac{0.051293294}{p} -1$\n",
        ">>>\n",
        ">> Puesto que\n",
        ">>>\n",
        ">>> $p = 10^{-58}$\n",
        ">>>\n",
        ">> we have at last:\n",
        ">>>\n",
        ">>> $n   = \\frac{0.051293294}{10^{-58}} -1$\n",
        ">>>\n",
        ">>> $n   =  0.051293294 \\times 10^{58} -1$\n",
        ">>>\n",
        ">>> $n   =   0.05  \\times 10^{58} $\n",
        ">>>\n",
        ">>> $  = 5 \\times 10^{-2} \\times 10^{57} $\n",
        ">>>\n",
        ">>> $n   =  5 \\times  10^{55} $\n",
        ">>\n",
        ">> Esto es un 5 seguido de 55 ceros.\n",
        "\n",
        "Nuestro resultado científico indica que, para acumular una significancia de $0.05$, 1000 monos necesitan realizar, en promedio, $5 × 10^{55}$ repeticiones antes del primer éxito, que consiste en replicar una secuencia de ADN objetivo de 100 bases. Sin embargo, el número permitido es del orden de $10^{20}$. Existe un déficit equivalente a más de $10^{35}$ edades de la Tierra.\n",
        "\n",
        "> *Restrospectiva.* Nuestras conclusiones son tan extravagantes que todos desearían predicciones comprobables, no solo simulaciones, sino propuestas de experimentos para realizar en un laboratorio de biología molecular.\n",
        "\n",
        "¿Qué proponemos?\n",
        "\n",
        "Proponemos un experimento factible que permita llegar espontáneamente a una conclusión simple: se requieren demasiados intentos antes de tener éxito. O, dicho de otro modo, la aleatoriedad no puede reproducir un resultado de replicación de una secuencia de ADN objetivo a menos que se generen enormes cantidades de datos basura. Esto significa que estamos llenos de malformaciones y disfunciones. Ahora, mírese y decida: ¿acaso el azar actuando en múltiples frentes explica su existencia?\n",
        "\n",
        "\n",
        "\n"
      ],
      "metadata": {
        "id": "y1cWYFAgQ_hg"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "# UN EXPERIMENTO VIABLE Y CRUCIAL\n",
        "\n",
        "Para los amantes de los laboratorios, proponemos un experimento que seguramente les generará la misma sensación que a nosotros: **Incluso los experimentos más pequeños son extremadamente costosos debido a la inmensa cantidad de recursos que requieren**. En la siguiente propuesta, cada laboratorio tiene la oportunidad de seleccionar un nivel de dificultad acorde a sus posibilidades.\n",
        "\n",
        "> *Propuesta.* Sintetizar una cadena de ADN de longitud $H = 1, 2, 3, ...$ utilizando 10 cadenas aleatorias por repetición. Contar e indicar el número de repeticiones necesarias hasta lograrlo.\n",
        "\n",
        "Preveamos lo que nos espera: mostraremos nuestras predicciones matemáticas junto con los resultados de una simulación. La concordancia entre ambos servirá como guía para estimar las dificultades previstas. Así, recordemos que la probabilidad $p(E)$ de sintetizar una cadena de ADN de $H$ bases de longitud usando $n$ monos para una repetición es:\n",
        ">>\n",
        ">> $p(E) = 1 - (1 - \\frac{1}{4^H})^n$\n",
        ">>\n",
        "> Por consiguiente, el costo $C(E)$ de sintetizar la cadena de longitud $H$ usando $n$ monos es, en términos de repeticiones,\n",
        ">>\n",
        ">> $C(E) = \\frac{1}{p(E)}$\n",
        ">>\n",
        ">> $C(E) = \\frac{1}{1- (1 -\\frac{1}{4^H})^n}$\n",
        ">>\n",
        "> Aproximando $(1 -\\frac{1}{4^H})^n$ por $1 - \\frac{n}{4^H}$, obtenemos:\n",
        ">>\n",
        ">> $p(E) = 1- (1 - \\frac{n}{4^H})$\n",
        ">>\n",
        ">> $p(E) = 1- 1 + \\frac{n}{4^H}$\n",
        ">>\n",
        ">> $p(E) = \\frac{n}{4^H}$\n",
        ">>\n",
        "> Por lo tanto, el costo en términos de repeticiones $C$ se puede aproximar mediante:\n",
        ">>\n",
        ">> $C(E) = \\frac{1}{p(E)}$\n",
        ">>\n",
        ">> $C = \\frac{1}{\\frac{n}{4^H}}$\n",
        ">>\n",
        ">> $C(E) = \\frac{4^H}{n}$\n",
        ">>\n",
        "> *Interpretación.* La última ecuación tiene una lectura sencilla: un mono necesita, en promedio, $4^H$ iteraciones para emparejar una secuencia de ADN con $H$ bases. Por lo tanto, n monos necesitan $4^H/n$ iteraciones para realizar la misma tarea. Sin embargo, esto es solo una aproximación. ¿Qué tan precisa es?\n",
        "\n",
        "> *Test.* Pongamos a prueba nuestras predicciones matemáticas mediante una simulación. A continuación se muestra el código correspondiente. Se incluye una exploración numérica para evaluar la precisión de la aproximación propuesta de Costo = $\\frac{4^H}{n}$."
      ],
      "metadata": {
        "id": "e39JLWJiVy78"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "import random\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import time\n",
        "\n",
        "start_time = time.time() # Record the start time\n",
        "\n",
        "num_monkeys = 10\n",
        "n = num_monkeys\n",
        "\n",
        "\n",
        "def generate_random_dna_string(length):\n",
        "  \"\"\"Generates a random DNA string of a given length.\"\"\"\n",
        "  return ''.join(random.choice('ATCG') for _ in range(length))\n",
        "\n",
        "def simulate_dna_matching(target_string):\n",
        "  \"\"\"Simulates attempts to match a target DNA string and returns the number of trials.\"\"\"\n",
        "  trials = 0\n",
        "  while True:\n",
        "        trials = trials + 1\n",
        "        for _ in range(n):\n",
        "            if generate_random_dna_string(len(target_string)) == target_string:\n",
        "              return trials\n",
        "\n",
        "# Define the range of string lengths to test\n",
        "string_lengths = range(1, 12)  # From 1 to 11\n",
        "\n",
        "observed_trials = []\n",
        "predicted_trials = []\n",
        "\n",
        "for length in string_lengths:\n",
        "  # Create a target DNA string (e.g., 'A' * length)\n",
        "  target = 'A' * length\n",
        "  # Simulate matching and record the number of trials\n",
        "  observed = simulate_dna_matching(target)\n",
        "  observed_trials.append(observed)\n",
        "\n",
        "  # Calculate the theoretical prediction (4^n)\n",
        "  # predicted = 4**length\n",
        "  # predicted_trials.append(predicted)\n",
        "  prob_of_E = 1-(1-(1/4**length))**n\n",
        "  predicted = int(np.round(1 / prob_of_E ))\n",
        "\n",
        "  predicted_trials.append(predicted)\n",
        "  approx = int(4**length/n)\n",
        "  print(f\"Length: {length}, Observed Trials: {observed}, Predicted Trials : {predicted}, approx:   {approx}\")\n",
        "\n",
        "# Plot the results\n",
        "plt.figure(figsize=(12, 8))\n",
        "plt.plot(string_lengths, observed_trials, 'o-', label='Observed Repetitions')\n",
        "plt.plot(string_lengths, predicted_trials, 's-', label='Predicted repetitions ( ~ 4^length/n)')\n",
        "\n",
        "plt.yscale('log') # Use a logarithmic scale for better visualization of exponential growth\n",
        "\n",
        "plt.title(f'Observed vs. Predicted repetitions to Match a DNA String for {n} monkeys')\n",
        "plt.xlabel('DNA String Length ')\n",
        "plt.ylabel('Number of Repetitions(log scale)')\n",
        "plt.legend()\n",
        "plt.grid(True)\n",
        "plt.show()\n",
        "\n",
        "end_time = time.time() # Record the end time\n",
        "elapsed_time = end_time - start_time\n",
        "\n",
        "print(f\"Script execution time: {elapsed_time:.4f} seconds\")"
      ],
      "metadata": {
        "id": "Y9ZMawglLXxl"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "> *Ejercicio.* Ejecute el programa y evalúe los resultados. El programa se ejecuta para secuencias de ADN objetivo de longitud 1, 2, ..., 11. Antes de intentar ejecutarlo con una longitud de 12 o mayor, prediga el tiempo de ejecución, que crece exponencialmente.\n",
        ">\n",
        "> *Respuesta.* El programa muestra el costo de encontrar una secuencia de ADN de longitud 1, 2, ..., 11, utilizando 10 monos que generan una secuencia aleatoria por repetición. El costo se expresa en función del número de repeticiones observadas que se ajustan a una predicción matemática. Ésta está dada por la aproximación $4^H/n$, donde $H$ es la longitud de la secuencia objetivo y $n$ es el número de monos, en este caso, 10. El grafo de la predicción matemática es  una línea casi perfecta en escala semilogarítmica. Esto indica que el costo está dominado por el término exponencial $4^H$. El ajuste entre observado y esperado es bueno, pero no perfecto, lo que indica que existe una varianza considerable.\n",
        ">\n",
        "> El coste observado para la longitud 11 fue de casi 100 000 rondas, con un tiempo de ejecución por Gemini de 20 segundos. El coste para la longitud 12 debe ser cuatro veces mayor, con un tiempo de ejecución de un minuto y algunos segundos. Para la longitud 13, el coste previsto es cuatro veces mayor, es decir, cinco minutos. Para la longitud 14 es de 20 minutos, y para la longitud 15 es de más de una hora. Para la longitud 100, el coste previsto es de 4¹⁰⁰/10 = 4⁹⁸ = 10⁵⁹ repeticiones, con un tiempo de ejecución de alrededor de 10⁴⁵ horas, 10⁴⁰ años o 10²⁵ veces la edad del Universo.\n",
        "\n",
        "Hemos mostrado el siguiente resultado:\n",
        "\n",
        "> *Teorema.* El coste, en términos de repeticiones, de emparejar una cadena de ADN usando 𝑛 monos es exponencial con respecto a la longitud de la cadena e inverso al número de monos. Esto implica que, para longitudes ligeramente grandes, la tendencia exponencial predomina. En otras palabras: **Ningún número factible de monos puede superar la complejidad**. O bien, la complejidad gana la batalla entre recursos y complejidad.\n",
        ">\n",
        "> *Demostración.* Debemos demostrar que, cuando tanto la longitud como el número de monos aumentan, el coste exponencial predomina. Supongamos que, en cada iteración o repetición, añadimos un mono al conjunto de trabajo y una base a la cadena de ADN. Y que repetimos este proceso indefinidamente. Si comenzamos con 1 y 1, esto equivale a la definición de la función:\n",
        ">\n",
        "> $C(x) = \\frac{4^x}{x}$\n",
        ">>\n",
        ">> $ = \\frac{(e^{ln 4})^x}{x}$\n",
        ">>\n",
        ">> $ = \\frac{(e^{xln 4})}{x}$\n",
        ">>\n",
        "> Cuando $x \\to ∞$, $C(x) \\to \\frac{∞}{∞}$. Por lo tanto, para conocer el límite, derivamos:\n",
        ">>\n",
        ">> $C(x) \\to \\frac{(ln 4) e^{xln 4}}{1} = ln 4 \\times 4^x \\to ∞$.\n",
        ">>\n",
        "\n",
        "Esto indica que, cuando $x$ es muy grande, la función exponencial domina. ¿Qué sucede cuando $x$ es de tamaño moderado? La respuesta se puede observar en la salida del siguiente programa:\n"
      ],
      "metadata": {
        "id": "06kMaev7Wo11"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "\n",
        "#Cost in repetitions of matching a DNA string x bases long with x monkeys\n",
        "x_vals = np.linspace(1, 20, 20)\n",
        "cost_vals = (4**x_vals)/x_vals\n",
        "# Plot the results\n",
        "plt.figure(figsize=(12, 8))\n",
        "plt.plot(x_vals, cost_vals, 'o-')\n",
        "plt.title('Cost in repetitions of matching a DNA string $x$ bases long with $x$ monkeys')\n",
        "plt.xlabel('DNA String Length ')\n",
        "plt.ylabel('Number of Repetitions  ')\n",
        "plt.legend()\n",
        "plt.grid(True)\n",
        "plt.show()"
      ],
      "metadata": {
        "id": "JeT-aAF6dWZ2"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "> *Ejercicio.* Ejecute el programa y evalúelo.\n",
        "\n",
        "> > *Respuesta.* Si añadimos un mono y una base al mismo tiempo repetidamente, al principio el impacto sobre el número necesario de ensayos para completar una tarea es tolerable. Pero cuando $x$ supera 15, la situación cambia y la exponencial comienza a dominar. Su dominio es tan marcado que, para $x$ = 20, la señal de una exponencial es perfectamente visible, llegando a un coste de un múltiplo de $10^{10}$.\n",
        "\n",
        "> *Desafío.* Demuestre que  la exponencial domina establemente incluso en el caso en que añadamos un número de monos que sea una función polinómica del número de iteraciones de nuestro procedimiento.\n",
        "\n",
        "> *Desafío.* Determine si la siguiente objeción es correcta y realizable. En ese caso, resuélvala. La objeción es:\n",
        "\n",
        ">>\n",
        ">> La exponencial no es la ganadora en todos los escenarios. Consideremos el caso en el que añadimos una base mientras dejamos que los monos se reproduzcan según una sucesión de Fibonacci. En ese caso, el número necesario de repeticiones para que coincida con la cadena binaria actualizada se mantiene dentro de límites bajos porque el número de monos también es exponencial."
      ],
      "metadata": {
        "id": "yF-2GgvvY13T"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "# CONCLUSIÓN\n",
        "\n",
        "Estudiamos el coste de encontrar una cadena objetivo mediante una máquina que genera cadenas aleatorias de la misma longitud. Sabemos que si la máquina genera una cadena por iteración, representada figurativamente como un mono tecleando, entonces el mono es vulnerable a la complejidad, dada por la longitud de la cadena objetivo. El propósito de este artículo era indagar qué ocurriría si, en lugar de un solo mono, trabajáramos con muchos.\n",
        "\n",
        "> *Ejercicio.* Extraiga y redacte sus conclusiones.\n",
        ">\n",
        ">> *Respuesta.* En escenarios factibles, muchos monos no constituyen una solución reproducible al problema de encontrar una secuencia objetivo de tan solo 100 bases de ADN. De hecho, muchos monos son mejores, pero no mucho, que uno solo. Sin embargo, el proceso presenta una alta varianza, por lo que no se puede descartar que, por mera casualidad, muchos monos pudieran resolver la tarea en un lapso de tiempo predefinido. Si bien esto no es reproducible, es posible, pero con una probabilidad tan baja que el método científico aconseja buscar otra explicación. En cualquier caso, si no existe otra explicación, podría aceptarse la hipótesis de los muchos monos. En ese caso, dicha aceptación sería más tolerable si se encontrara en los mares ancestrales la enorme cantidad de basura predicha. En cualquier caso, los fósiles, junto con todos los ejemplares vivos de todas las especies, estarían plagados de todo tipo de defectos, malformaciones y disfunciones. Ese es el significado de basura cuando hablamos de ADN. Por eso, cada persona puede mirar su propio cuerpo y decidir si acepta o no la explicación de los muchos monos como causa de su existencia."
      ],
      "metadata": {
        "id": "YpdfzZQAZCu1"
      }
    }
  ]
}