{
  "cells": [
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "2a246d14-ed2b-4573-bf97-939c3628b3bb",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Bucles de optimización\"\n",
        "description: \"Esta lección explica cómo utilizar bucles de optimización clásicos para introducir parámetros en cálculos cuánticos.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore nabla */}\n",
        "\n",
        "<span id=\"optimization-loops\" />\n",
        "\n",
        "# Bucles de optimización\n",
        "\n",
        "Durante esta lección, aprenderemos a utilizar un *optimizador* para explorar iterativamente los estados cuánticos parametrizados de nuestro ansatz:\n",
        "\n",
        "* Crear un bucle de optimización\n",
        "* Comprender las compensaciones al utilizar optimizadores locales y globales\n",
        "* Explore las mesetas estériles y cómo evitarlas\n",
        "\n",
        "A alto nivel, los optimizadores son fundamentales para explorar nuestro espacio de búsqueda. El optimizador utiliza las evaluaciones de las funciones de coste para seleccionar el siguiente conjunto de parámetros en un bucle variacional, y repite el proceso hasta alcanzar un estado estable. En esta etapa, se devuelve un conjunto óptimo de valores de los parámetros $\\vec\\theta^*$.\n",
        "\n",
        "![Un diagrama de algunos factores importantes en la optimización, incluidas las mesetas estériles, los optimizadores con gradiente frente a los optimizadores sin gradiente y el bootstrapping.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/optimization-loops/optimization-workflow.svg)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "110421e7-0d2a-4653-a195-1925c809ca61",
      "metadata": {},
      "source": [
        "<span id=\"local-and-global-optimizers\" />\n",
        "\n",
        "## Optimizadores locales y globales\n",
        "\n",
        "Primero configuraremos nuestro problema antes de explorar cada clase de optimizador. Empezaremos con un circuito que contiene ocho parámetros variacionales:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "15cf7810-1d25-4c54-aff0-91d3a0c51cec",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/optimization-loops/extracted-outputs/15cf7810-1d25-4c54-aff0-91d3a0c51cec-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit import QuantumCircuit\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "from qiskit.circuit.library import TwoLocal\n",
        "import numpy as np\n",
        "\n",
        "theta_list = (2 * np.pi * np.random.rand(1, 8)).tolist()\n",
        "observable = SparsePauliOp.from_list([(\"XX\", 1), (\"YY\", -3)])\n",
        "\n",
        "reference_circuit = QuantumCircuit(2)\n",
        "reference_circuit.x(0)\n",
        "\n",
        "variational_form = TwoLocal(\n",
        "    2,\n",
        "    rotation_blocks=[\"rz\", \"ry\"],\n",
        "    entanglement_blocks=\"cx\",\n",
        "    entanglement=\"linear\",\n",
        "    reps=1,\n",
        ")\n",
        "ansatz = reference_circuit.compose(variational_form)\n",
        "\n",
        "ansatz.decompose().draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "950569c0-4e8f-49b5-b118-7cd227bd5ce1",
      "metadata": {},
      "outputs": [],
      "source": [
        "def cost_func_vqe(params, ansatz, hamiltonian, estimator):\n",
        "    \"\"\"Return estimate of energy from estimator\n",
        "\n",
        "    Parameters:\n",
        "        params (ndarray): Array of ansatz parameters\n",
        "        ansatz (QuantumCircuit): Parameterized ansatz circuit\n",
        "        hamiltonian (SparsePauliOp): Operator representation of Hamiltonian\n",
        "        estimator (Estimator): Estimator primitive instance\n",
        "\n",
        "    Returns:\n",
        "        float: Energy estimate\n",
        "    \"\"\"\n",
        "    pub = (ansatz, hamiltonian, params)\n",
        "    cost = estimator.run([pub]).result()[0].data.evs\n",
        "    return cost"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "f536ee50-373f-4873-b258-66c5c07c65c5",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit.primitives import StatevectorEstimator\n",
        "\n",
        "estimator = StatevectorEstimator()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c9dcec4d-5b84-4731-8b5a-cb8a0f7572d5",
      "metadata": {},
      "source": [
        "<span id=\"local-optimizers\" />\n",
        "\n",
        "### Optimizadores locales\n",
        "\n",
        "Los optimizadores locales buscan un punto que minimice la función de coste partiendo de un punto o puntos iniciales $C(\\vec{\\theta_0})$ y se desplazan a puntos diferentes en función de lo que observan en la región que están evaluando en ese momento en iteraciones sucesivas. Esto implica que la convergencia de estos algoritmos suele ser rápida, pero puede depender en gran medida del punto inicial. Los optimizadores locales no pueden ver más allá de la región en la que están evaluando y pueden ser especialmente vulnerables a los mínimos locales, informando de la convergencia cuando encuentran uno e ignorando otros estados con evaluaciones más favorables.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "447df742-13bd-4d4a-a364-41d34380cbc9",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              " message: Optimization terminated successfully\n",
              " success: True\n",
              "  status: 0\n",
              "     fun: -3.9999999964520634\n",
              "       x: [ 1.000e+00  1.000e+00 -1.571e+00 -4.556e-05 -1.207e+00\n",
              "           -1.935e+00  4.079e-01 -4.079e-01]\n",
              "     nit: 12\n",
              "     jac: [ 0.000e+00  0.000e+00 -7.957e-04  2.543e-04  1.381e-03\n",
              "            1.381e-03  5.430e-04  5.431e-04]\n",
              "    nfev: 112\n",
              "    njev: 12"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# SciPy minimizer routine\n",
        "from scipy.optimize import minimize\n",
        "\n",
        "x0 = np.ones(8)\n",
        "\n",
        "result = minimize(\n",
        "    cost_func_vqe, x0, args=(ansatz, observable, estimator), method=\"SLSQP\"\n",
        ")\n",
        "\n",
        "result"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "25fee3f4-9d3e-45ce-a104-dfde5e132b2b",
      "metadata": {},
      "source": [
        "<span id=\"global-optimizers\" />\n",
        "\n",
        "### Optimizadores globales\n",
        "\n",
        "Los optimizadores globales buscan el punto que minimiza la función de coste en varias regiones de su dominio (es decir, no local), evaluándola iterativamente (es decir, en la iteración $i$ ) sobre un conjunto de vectores de parámetros $\\Theta_i := \\\\{ {\\vec\\theta_{i,j} | j \\in \\mathcal{J}_\\text{opt}^i} \\\\}$ determinados por el optimizador. Esto los hace menos susceptibles a los mínimos locales y algo independientes de la inicialización, pero también significativamente más lentos a la hora de converger a una solución propuesta.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8d794038-8e75-4649-a978-a9e832ebed60",
      "metadata": {},
      "source": [
        "<span id=\"bootstrapping-optimization\" />\n",
        "\n",
        "### Optimización del arranque\n",
        "\n",
        "*Bootstrapping*, o establecer el valor inicial de los parámetros $\\vec\\theta$ basándose en una optimización previa, puede ayudar a nuestro optimizador a converger en una solución más rápidamente. Lo denominaremos punto inicial $\\vec\\theta_0$, y $|\\psi(\\vec\\theta_0)\\rangle = U_V(\\vec\\theta_0)|\\rho\\rangle$, estado inicial. Este estado inicial difiere de nuestro estado de referencia $|\\rho\\rangle$, ya que el primero se centra en los parámetros iniciales establecidos durante nuestro bucle de optimización, mientras que el segundo se centra en utilizar soluciones de \"referencia\" conocidas. Pueden coincidir si $U_V(\\vec\\theta_0) \\equiv I$ (es decir, la operación de identidad).\n",
        "\n",
        "Cuando los optimizadores locales convergen a mínimos locales no óptimos, podemos intentar arrancar la optimización globalmente y refinar la convergencia localmente. Aunque esto requiere establecer dos cargas de trabajo variacionales, permite al optimizador encontrar una solución más óptima que el optimizador local por sí solo.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "07ba982e-9280-4e15-9d8c-4b6460b1deea",
      "metadata": {
        "gloss": {
          "hyperparameter": {
            "text": "A hyperparameter is a parameter that we use to control our algorithm. The ‘hyper’ distinguishes it from the parameters (θ) that our algorithm is trying to find.",
            "title": "Hyperparameter"
          },
          "local-minimum": {
            "text": "A local minimum is the lowest point of the function, for a small range of values of theta. In contrast, a global minimum is <i>the</i> lowest point, anywhere in our function (that is, for any value of theta). <a href='https://en.wikipedia.org/wiki/Maxima_and_minima'>Read more</a>.",
            "title": "Local minimum"
          }
        }
      },
      "source": [
        "<span id=\"gradient-based-and-gradient-free-optimizers\" />\n",
        "\n",
        "## Optimizadores basados en gradientes y sin gradientes\n",
        "\n",
        "<span id=\"gradient-based\" />\n",
        "\n",
        "### Basado en gradientes\n",
        "\n",
        "Para nuestra función de coste $C(\\vec\\theta)$, si tenemos acceso al gradiente de la función $\\vec{\\nabla} C(\\vec\\theta)$ partiendo de un punto inicial, la forma más sencilla de minimizar la función es actualizar los parámetros hacia la dirección de descenso más pronunciado de la función. Es decir, actualizamos los parámetros como $\\vec\\theta_{n+1} = \\vec\\theta_n - \\eta \\vec{\\nabla} C(\\vec\\theta)$, donde $\\eta$ es la tasa de aprendizaje - un pequeño y positivo <DefinitionTooltip definition=\"Un hiperparámetro es un parámetro que utilizamos para controlar nuestro algoritmo. El término hiper lo distingue de los parámetros (θ) que nuestro algoritmo intenta encontrar.\">hiperparámetro</DefinitionTooltip> que controla el tamaño de la actualización. Continuamos haciendo esto hasta que convergemos a un <DefinitionTooltip definition=\"Un mínimo local es el punto más bajo de la función, para un rango pequeño de valores de theta. Por el contrario, un mínimo global es el punto más bajo, en cualquier lugar de nuestra función (es decir, para cualquier valor de θ).\">mínimo local</DefinitionTooltip> de la función de coste, $C({\\vec\\theta^*})$.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "0897260d-f3f8-4caa-8a79-f258e41f8e21",
      "metadata": {},
      "source": [
        "Podemos utilizar esta función de coste y un optimizador para calcular los parámetros óptimos\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "aad81101-3c19-4946-8453-3db9df3369c0",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "  message: Optimization terminated successfully.\n",
              "  success: True\n",
              "   status: 0\n",
              "      fun: -3.9999999999997025\n",
              "        x: [ 1.000e+00  1.000e+00  1.571e+00  3.220e-07  2.009e-01\n",
              "            -2.009e-01  6.342e-01 -6.342e-01]\n",
              "      nit: 14\n",
              "      jac: [-1.192e-07 -2.980e-08  8.345e-07  1.103e-06  5.960e-08\n",
              "             0.000e+00 -5.960e-08  2.980e-08]\n",
              " hess_inv: [[ 1.000e+00  1.872e-10 ...  5.077e-05  3.847e-05]\n",
              "            [ 1.872e-10  1.000e+00 ... -5.208e-05 -4.060e-05]\n",
              "            ...\n",
              "            [ 5.077e-05 -5.208e-05 ...  7.243e-01 -2.604e-01]\n",
              "            [ 3.847e-05 -4.060e-05 ... -2.604e-01  8.179e-01]]\n",
              "     nfev: 144\n",
              "     njev: 16"
            ]
          },
          "execution_count": 5,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# SciPy minimizer routine\n",
        "from scipy.optimize import minimize\n",
        "\n",
        "x0 = np.ones(8)\n",
        "\n",
        "result = minimize(\n",
        "    cost_func_vqe, x0, args=(ansatz, observable, estimator), method=\"BFGS\"\n",
        ")\n",
        "\n",
        "result"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "0f7d55f8-e3a2-4223-b456-17b39a81f524",
      "metadata": {},
      "source": [
        "Las principales desventajas de este tipo de optimización son la velocidad de convergencia, que puede ser muy lenta, y que no hay garantía de alcanzar la solución óptima.\n",
        "\n",
        "![gráfico de f(theta) contra theta, múltiples puntos muestran diferentes estados de un algoritmo de descenso de gradiente que encuentra el mínimo de una curva.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/optimization-loops/optimization-gradient-descent.svg)\n",
        "\n",
        "<span id=\"gradient-free\" />\n",
        "\n",
        "### Sin gradiente\n",
        "\n",
        "Los algoritmos de optimización sin gradiente no requieren información sobre el gradiente y pueden ser útiles en situaciones en las que calcular el gradiente es difícil, caro o demasiado ruidoso. También tienden a ser más robustos a la hora de encontrar óptimos globales, mientras que los métodos basados en gradientes tienden a converger a óptimos locales. Exploraremos algunos casos en los que un optimizador sin gradiente puede ayudar a evitar mesetas estériles. Sin embargo, los métodos sin gradiente requieren mayores recursos computacionales, especialmente para problemas con espacios de búsqueda de alta dimensión.\n",
        "\n",
        "He aquí un ejemplo que utiliza el [`COBYLA`](https://docs.scipy.org/doc/scipy/reference/optimize.minimize-cobyla.html#optimize-minimize-cobyla) optimizador:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "c749695b-3124-47d8-8a23-10c9d5965a7f",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              " message: Optimization terminated successfully.\n",
              " success: True\n",
              "  status: 1\n",
              "     fun: -3.999999973369678\n",
              "       x: [ 1.631e+00  1.492e+00  1.571e+00  3.142e+00  1.375e+00\n",
              "           -1.767e+00  1.484e+00  1.658e+00]\n",
              "    nfev: 137\n",
              "   maxcv: 0.0"
            ]
          },
          "execution_count": 6,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# SciPy minimizer routine\n",
        "from scipy.optimize import minimize\n",
        "\n",
        "x0 = np.ones(8)\n",
        "\n",
        "result = minimize(\n",
        "    cost_func_vqe, x0, args=(ansatz, observable, estimator), method=\"COBYLA\"\n",
        ")\n",
        "\n",
        "result"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "396d0617-9f20-48cb-b57c-30dfa54293ff",
      "metadata": {
        "gloss": {
          "barren-plateaus": {
            "text": "When gradients of parametrized quantum circuits become exponentially small with respect to the number of qubits, making optimization difficult and potentially impossible.",
            "title": "Barren Plateaus"
          }
        }
      },
      "source": [
        "<span id=\"barren-plateaus\" />\n",
        "\n",
        "## Mesetas áridas\n",
        "\n",
        "De hecho, el panorama de costes puede ser bastante complicado, como muestran las colinas y valles del ejemplo siguiente. El método de optimización nos guía por el paisaje de costes, buscando el mínimo, como muestran los puntos y las líneas negras. Podemos ver que dos de las tres búsquedas acaban en un mínimo local del paisaje, en lugar de en uno global.\n",
        "\n",
        "![Un colector curvo complicado con muchos picos y valles.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/optimization-loops/optimization-loss-landscape.svg)\n",
        "\n",
        "Independientemente del tipo de método de optimización utilizado, si el panorama de costes es relativamente plano, puede resultar difícil para el método determinar la dirección de búsqueda adecuada. Este escenario se conoce como <DefinitionTooltip definition=\"Cuando los gradientes de los circuitos cuánticos parametrizados se vuelven exponencialmente pequeños con respecto al número de qubits, lo que dificulta la optimización y la hace potencialmente imposible.\">meseta estéril,</DefinitionTooltip>, donde el panorama de costes se vuelve progresivamente más plano (y, por tanto, más difícil de determinar la dirección hacia el mínimo). Para una amplia gama de circuitos cuánticos parametrizados, la probabilidad de que el gradiente a lo largo de cualquier dirección razonable sea distinto de cero con cierta precisión fija disminuye exponencialmente a medida que aumenta el número de qubits.\n",
        "\n",
        "![Diagrama de una meseta geográfica comparada con la pendiente de una montaña, para explicar por qué una pendiente nos ayuda a encontrar un mínimo y una meseta dificulta nuestros esfuerzos.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/optimization-loops/optimization-barren-plateaus.svg)\n",
        "\n",
        "Aunque esta área sigue siendo objeto de investigación activa, tenemos algunas recomendaciones para mejorar el rendimiento de la optimización:\n",
        "\n",
        "* **Bootstrapping** puede ayudar al bucle de optimización a evitar quedarse atascado en un espacio de parámetros donde el gradiente es pequeño.\n",
        "* **Experimentar con un ansatz eficiente desde el punto de vista del hardware** : Dado que estamos utilizando un sistema cuántico ruidoso como oráculo de caja negra, la calidad de esas evaluaciones puede afectar al rendimiento del optimizador. El uso de un ansatz eficiente desde el punto de vista del hardware, como [`EfficientSU2`](/docs/api/qiskit/qiskit.circuit.library.EfficientSU2)puede evitar la producción de gradientes exponencialmente pequeños.\n",
        "* **Experimentar con la supresión y mitigación de errores** : las primitivas Qiskit Runtime proporcionan una interfaz sencilla para experimentar con varios valores para `optimization_level` y `resilience_setting`, respectivamente. Esto puede reducir el impacto del ruido y hacer más eficaz el proceso de optimización.\n",
        "* **Experimentar con optimizadores sin gradiente** : A diferencia de los algoritmos de optimización basados en el gradiente, los optimizadores como `COBYLA` no se basan en la información del gradiente para optimizar los parámetros y, por lo tanto, es menos probable que se vean afectados por la meseta estéril.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "182110bc-f952-4460-8048-d1df961ba860",
      "metadata": {},
      "source": [
        "<span id=\"summary\" />\n",
        "\n",
        "## Resumen\n",
        "\n",
        "Con esta lección, ha aprendido a definir su bucle de optimización:\n",
        "\n",
        "* Crear un bucle de optimización\n",
        "* Comprender las compensaciones al utilizar optimizadores locales y globales\n",
        "* Explore las mesetas estériles y cómo evitarlas\n",
        "\n",
        "Nuestra carga de trabajo variacional de alto nivel está completa:\n",
        "\n",
        "![Un circuito cuántico ahora con un unitario para preparar el estado de referencia, y un segundo unitario para variar el estado utilizando parámetros variacionales.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/optimization-loops/optimization-circuit.svg)\n",
        "\n",
        "A continuación, exploraremos algoritmos variacionales específicos teniendo en cuenta este marco.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 2
}