{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "a9fc5202-9641-4db3-ac1a-9986f28854bc",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Solucionador de valores propios cuánticos variacionales\"\n",
        "description: \"Aprenda qué es un solucionador de valores propios cuánticos variacionales y calcule con él la energía de activación de una reaccione H+H=H2.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore arrowstyle UCCSD verticalalignment horizontalalignment  xytext arrowprops preparable ansätze Marov Aspuru Guzik Hartrees ansä */}\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bafc481a-d2d4-4668-8389-acbe9cd3f615",
      "metadata": {},
      "source": [
        "<span id=\"variational-quantum-eigensolver-vqe\" />\n",
        "\n",
        "# Solucionador de valores propios cuánticos variacionales (VQE)\n",
        "\n",
        "Para este módulo, los estudiantes deben tener un entorno Python en funcionamiento, y las últimas versiones de los siguientes paquetes instalados:\n",
        "\n",
        "* `qiskit`\n",
        "* `qiskit_ibm_runtime`\n",
        "* `qiskit-aer`\n",
        "* `qiskit.visualization`\n",
        "* `numpy`\n",
        "* `pylatexenc`\n",
        "\n",
        "Para configurar e instalar estos paquetes, consulta la guía [Instalar Qiskit](/docs/guides/install-qiskit). Para ejecutar trabajos en ordenadores cuánticos reales, los estudiantes tendrán que configurar una cuenta IBM Cloud, siguiendo los pasos de la guía [Configure su cuenta IBM Cloud](/docs/guides/cloud-setup).\n",
        "\n",
        "*Este módulo ha sido probado y ha utilizado aproximadamente 8 minutos de tiempo QPU. Esto es una estimación, y su uso real puede variar.*\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "cda95261-1473-492c-8a1d-1e29773086c9",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Uncomment and modify this line as needed to install dependencies\n",
        "#!pip install 'qiskit>=2.1.0' 'qiskit-ibm-runtime>=0.40.1' 'qiskit-aer>=0.17.0' 'numpy' 'pylatexenc'"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "127021b2-3419-4b9d-bf13-7ef940b50029",
      "metadata": {},
      "source": [
        "<span id=\"introduction\" />\n",
        "\n",
        "## Introducción\n",
        "\n",
        "Desde el desarrollo del modelo mecánico cuántico a principios del siglo XX, los científicos han comprendido que los electrones no siguen trayectorias fijas alrededor del núcleo de un átomo, sino que existen en regiones de probabilidad denominadas orbitales. Estos orbitales corresponden a niveles de energía específicos y discretos que pueden ocupar los electrones. Los electrones residen de forma natural en los niveles de energía más bajos disponibles, conocidos como estado básico. Sin embargo, si un electrón absorbe suficiente energía, puede saltar a un nivel energético superior, entrando en un estado excitado. Este estado de excitación es temporal, y el electrón acabará volviendo a un nivel energético inferior, liberando la energía absorbida, a menudo en forma de luz. Este proceso fundamental de absorción y emisión de energía es importante para comprender cómo interactúan los átomos y forman enlaces.\n",
        "\n",
        "Cuando los átomos se unen para formar moléculas, sus orbitales atómicos se combinan para formar orbitales moleculares. La disposición y los niveles de energía de los electrones dentro de estos orbitales moleculares dictan las propiedades de la molécula resultante y la fuerza de los enlaces químicos. Por ejemplo, en la formación de una molécula de hidrógeno ( $H_2$ ) a partir de dos átomos individuales de hidrógeno, el electrón de cada átomo ocupa orbitales atómicos. A medida que los átomos se acercan entre sí, estos orbitales atómicos se solapan y se combinan para formar nuevos orbitales moleculares: uno con menor energía (un orbital de enlace) y otro con mayor energía (un orbital antienlace). Los dos electrones, uno de cada átomo de hidrógeno, ocuparán preferentemente el orbital de enlace de menor energía, dando lugar a la formación de un enlace covalente estable que mantiene unida la molécula $H_2$. La diferencia de energía entre los átomos separados y la molécula formada, en particular la energía de los electrones en los orbitales moleculares, determina la estabilidad y las propiedades del enlace.\n",
        "\n",
        "En las siguientes secciones, exploraremos este proceso de formación molecular, centrándonos en la molécula $H_2$. Utilizaremos un ordenador cuántico real, combinado con técnicas clásicas de optimización, para hallar la energía de este proceso simple pero fundamental. Este experimento proporcionará una demostración práctica de cómo puede aplicarse la computación cuántica para resolver problemas de química computacional, aportando conocimientos sobre el papel de la energía de los electrones.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "eef4da0c-8f48-47fc-88a2-9a2979c1dbc1",
      "metadata": {},
      "source": [
        "<span id=\"vqe---a-variational-quantum-algorithm-for-eigenvalue-problems\" />\n",
        "\n",
        "## VQE: un algoritmo cuántico variacional para problemas de valores propios\n",
        "\n",
        "<span id=\"approximation-techniques-for-chemistry---variational-principle-and-the-basis-set\" />\n",
        "\n",
        "### Técnicas de aproximación para química: principio variacional y conjunto base\n",
        "\n",
        "Las aportaciones de Erwin Schrödinger a la mecánica cuántica no se limitan a la introducción de un nuevo modelo electrónico; fundamentalmente, estableció la mecánica ondulatoria al desarrollar la famosa ecuación de Schrödinger dependiente del tiempo:\n",
        "\n",
        "$$\n",
        "i\\hbar \\frac{d}{dt}|\\psi\\rangle = \\hat{H}|\\psi\\rangle\n",
        "$$\n",
        "\n",
        "Aquí, $\\hat{H}$ es el operador Hamiltoniano, que representa la energía total del sistema, y $|\\psi\\rangle$ es la función de onda que contiene toda la información sobre el estado cuántico del sistema. (Nota: $\\frac{d}{dt}$ es la derivada temporal total, y aquí no incluimos explícitamente el valor propio de energía $E$ )\n",
        "\n",
        "Sin embargo, en muchas aplicaciones prácticas -como la determinación de los niveles de energía permitidos de átomos y moléculas- utilizamos en su lugar la ecuación de Schrödinger independiente del tiempo (ecuación de valores propios de la energía), que se deriva de la forma dependiente del tiempo suponiendo un estado estacionario. Un estado estacionario es un estado cuántico en el que la densidad de probabilidad de encontrar una partícula en un punto determinado del espacio no cambia con el tiempo.\n",
        "\n",
        "$\\hat{H}|\\psi\\rangle = E|\\psi\\rangle$\n",
        "\n",
        "En esta forma, $E$ representa el valor propio de energía correspondiente al estado cuántico $|\\psi\\rangle$. El Hamiltoniano incluye varias contribuciones energéticas, como la energía cinética de electrones y núcleos, las fuerzas atractivas entre electrones y núcleos, y las fuerzas repulsivas entre electrones.\n",
        "\n",
        "La resolución de la ecuación de los valores propios de la energía nos permite calcular los niveles de energía cuantizados de los sistemas atómicos y moleculares. Sin embargo, en el caso de las moléculas, resolverlo con exactitud es difícil porque la función de onda $\\Psi$, que describe la distribución espacial de los electrones, es compleja y de alta dimensión.\n",
        "\n",
        "Por ello, los científicos recurren a técnicas de aproximación para obtener soluciones prácticas y precisas. En este trabajo, nos centraremos en dos métodos clave:\n",
        "\n",
        "1. Principio variacional\n",
        "\n",
        "   Este método aproxima la función de onda y la ajusta para acercarse lo más posible a la energía objetivo, que suele ser la energía del estado fundamental del sistema. La idea clave del principio variacional es sencilla:\n",
        "\n",
        "   * Si adivinamos una función de onda $\\Psi_\\text{trial}$ (una \"función de prueba\"), la energía calculada a partir de ella siempre será igual o superior a la energía del estado fundamental ( $E_0$ ) del sistema. $E_\\text{approx} = \\frac{\\langle \\Psi_\\text{trial}|\\hat{H}|\\Psi_\\text{trial}\\rangle}{\\langle \\Psi_\\text{trial}|\\Psi_\\text{trial}\\rangle} \\geq E_0$\n",
        "   * Ajustando los parámetros $\\theta$ en la función de prueba, $|\\Psi_\\text{trial}(\\theta)\\rangle$, podemos obtener una aproximación cada vez mejor de la energía del estado básico.\n",
        "   * Su precisión depende en gran medida de la elección de la función de onda de prueba $\\Psi_\\text{trial}$. Una función de prueba mal elegida puede conducir a una estimación de la energía que está lejos de ser exacta.\n",
        "\n",
        "2. Aproximación del conjunto de bases\n",
        "\n",
        "   El segundo método de aproximación se produce en la fase de construcción de la función de onda: el enfoque del conjunto de bases. En química cuántica, resolver exactamente la ecuación de Schrödinger para las moléculas es casi imposible. En su lugar, nos aproximamos a la compleja función de onda multielectrónica construyéndola a partir de funciones matemáticas predefinidas más sencillas. Un conjunto de bases es esencialmente una colección de estas funciones matemáticas conocidas, normalmente centradas en los átomos de la molécula, que se utilizan como bloques de construcción para representar la forma y el comportamiento de los electrones en el sistema. Piénselo como si intentara recrear una escultura detallada utilizando sólo una colección de ladrillos LEGO estándar: cuantos más tipos y tamaños de ladrillos tenga (cuanto mayor sea el conjunto de base), con más precisión podrá aproximarse a la forma original.\n",
        "\n",
        "   Estas funciones de base suelen inspirarse en las soluciones analíticas de sistemas sencillos como el átomo de hidrógeno, adoptando formas como las funciones gaussianas o las de tipo Slater, aunque no dejan de ser aproximaciones. En lugar de trabajar con los orbitales moleculares completos, teóricamente \"exactos\" pero intratables, los expresamos como una combinación lineal (una suma con coeficientes) de estas funciones base. Este método se conoce como Combinación Lineal de Orbitales Atómicos (LCAO) cuando las funciones base se asemejan a orbitales atómicos. Optimizando los coeficientes de esta combinación lineal, podemos encontrar la mejor función de onda y energía aproximadas posibles dentro de las limitaciones del conjunto de bases elegido.\n",
        "\n",
        "   * Cuantas más funciones se incluyan en el conjunto de bases, mejor será la aproximación, pero a costa de un mayor esfuerzo computacional.\n",
        "   * Un conjunto de bases pequeño proporciona una estimación aproximada, mientras que un conjunto de bases grande da resultados más precisos a costa de requerir más recursos computacionales.\n",
        "\n",
        "En resumen, para hacer los cálculos factibles y reducir el coste computacional, utilizamos el principio variacional aproximando la función de onda, lo que reduce la complejidad computacional y permite la optimización iterativa para minimizar la energía. Por su parte, el enfoque del conjunto de bases simplifica los cálculos al representar los orbitales atómicos como una combinación de funciones predefinidas, en lugar de resolver directamente una función de onda continua.\n",
        "\n",
        "<span id=\"check-your-understanding\" />\n",
        "\n",
        "#### Comprueba tu comprensión\n",
        "\n",
        "Consideremos la función de onda de prueba $\\Psi_\\text{trial}(\\alpha,x) = Ae^{- \\alpha x^2}$ donde $A$ es una constante de normalización y $\\alpha$ es un parámetro ajustable.\n",
        "\n",
        "(a) Normalizar la función de onda de ensayo determinando un valor de $A$ tal que $\\int_{-\\infty}^{\\infty} |\\Psi_\\text{trial}|^2 dx = 1$.\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Respuesta\">\n",
        "    Para normalizar la función de onda de un ensayo dado:\n",
        "\n",
        "    $\\int_{-\\infty}^{\\infty} |\\Psi_\\text{trial}|^2 dx = \\int_{-\\infty}^{\\infty} A^2 e^{-2 \\alpha x^2} dx = 1$\n",
        "\n",
        "    Utiliza la integral de Gauss:\n",
        "\n",
        "    $ \\int_{-\\infty}^{\\infty} e^{-a x^2} dx = \\sqrt{\\frac{\\pi}{a}} \\text{,   for } a>0$\n",
        "\n",
        "    set $a = 2\\alpha$ then get: $A^2\\sqrt{\\frac{\\pi}{a}} = 1$ $\\therefore A = (\\frac{2\\alpha}{\\pi})^{1/4}$\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n",
        "(b) Calcula el valor esperado del hamiltoniano $\\hat{H}$ dado por $ \\hat{H} = -\\frac{\\hbar^2}{2m} \\frac{d^2}{dx^2} + V(x)$, donde $V(x)  = \\frac{1}{2}m\\omega^2x^2$, lo que corresponde a un potencial de oscilador armónico simple.\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Respuesta\">\n",
        "    El hamiltoniano de un oscilador armónico es:\n",
        "\n",
        "    $\\hat{H} = -\\frac{\\hbar^2}{2m} \\frac{d^2}{dx^2} + \\frac{1}{2} m \\omega^2 x^2$\n",
        "\n",
        "    **Valor esperado de la energía cinética**\n",
        "\n",
        "    $ \\langle T \\rangle = -\\frac{\\hbar^2}{2m} \\int_{-\\infty}^{\\infty} \\Psi_\\text{trial}^* \\frac{d^2}{dx^2} \\Psi_\\text{trial} dx$\n",
        "\n",
        "    Tomando la segunda derivada:\n",
        "\n",
        "    $\\frac{d}{dx} \\Psi_\\text{trial} = -2\\alpha x A e^{-\\alpha x^2}$\n",
        "\n",
        "    $\\frac{d^2}{dx^2} \\Psi_\\text{trial} = A e^{-\\alpha x^2} (4\\alpha^2 x^2 - 2\\alpha)$\n",
        "\n",
        "    Por lo tanto:\n",
        "\n",
        "    $T = -\\frac{\\hbar^2}{2m} \\int_{-\\infty}^{\\infty} A^2 e^{-2\\alpha x^2} (4\\alpha^2 x^2 - 2\\alpha) dx$\n",
        "\n",
        "    Utilizando los resultados de la integral gaussiana estándar:\n",
        "\n",
        "    $\\langle T \\rangle = \\frac{\\hbar^2 \\alpha}{2m}$\n",
        "\n",
        "    **Valor esperado de la energía potencial**\n",
        "\n",
        "    $\\langle V \\rangle = \\frac{1}{2} m \\omega^2 \\int_{-\\infty}^{\\infty} x^2 |\\Psi_\\text{trial}|^2 dx$\n",
        "\n",
        "    Usando:\n",
        "\n",
        "    $\\int_{-\\infty}^{\\infty} x^2 e^{-a x^2} dx = \\frac{\\sqrt{\\pi}}{2a^{3/2}}$\n",
        "\n",
        "    obtenemos:\n",
        "\n",
        "    $\\langle V \\rangle = \\frac{m \\omega^2}{4\\alpha}$\n",
        "\n",
        "    **Valor esperado de la energía total**\n",
        "\n",
        "    $\\therefore E_\\text{approx}(\\alpha) = \\frac{\\hbar^2 \\alpha}{2m} + \\frac{m \\omega^2}{4\\alpha}$\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n",
        "(c) Utiliza el principio variacional para hallar el valor óptimo de $\\alpha$ minimizando $E_\\text{approx}(\\alpha)$.\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Respuesta\">\n",
        "    Optimizar « $\\alpha$ » para un consumo energético mínimo\n",
        "\n",
        "    **Distinguir**\n",
        "\n",
        "    $\\frac{d}{d\\alpha} \\left( \\frac{\\hbar^2 \\alpha}{2m} + \\frac{m \\omega^2}{4\\alpha} \\right) = 0$\n",
        "\n",
        "    Resolver:\n",
        "\n",
        "    $\\frac{\\hbar^2}{2m} - \\frac{m \\omega^2}{4\\alpha^2} = 0$\n",
        "\n",
        "    $\\alpha_\\text{opt} = \\frac{m\\omega}{2\\hbar}$\n",
        "\n",
        "    Sustituyendo $\\alpha_\\text{opt}$ en $E_\\text{approx}$ :\n",
        "\n",
        "    $\\therefore E_\\text{approx} = \\frac{\\hbar \\omega}{2}$\n",
        "\n",
        "    que coincide exactamente con la energía del estado de reposo del oscilador armónico cuántico.\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ed2bd1ba-73ad-42d3-94ad-c1d4a7db0a6f",
      "metadata": {},
      "source": [
        "<span id=\"vqe-variational-quantum-eigensolver\" />\n",
        "\n",
        "### VQE (Solucionador de valores propios cuánticos variacionales)\n",
        "\n",
        "El eigensolver cuántico variacional (VQE) es el método principal que utilizaremos para explorar el proceso $H+H = H_2$, y aquí, echaremos un vistazo a qué es el VQE y cómo funciona. Pero detengámonos primero en una cosa muy importante a través de la pregunta de registro.\n",
        "\n",
        "<span id=\"check-your-understanding\" />\n",
        "\n",
        "#### Comprueba tu comprensión\n",
        "\n",
        "Si ya disponemos de tantas estrategias para los problemas de química, ¿para qué necesitamos un ordenador cuántico? ¿Y para qué sirve utilizar conjuntamente ordenadores cuánticos y clásicos?\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Respuesta\">\n",
        "    La computación cuántica tiene la oportunidad de revolucionar la química abordando problemas a los que se enfrentan los ordenadores clásicos debido al escalado exponencial de los estados cuánticos. Richard Feynman señaló que, para simular la naturaleza, los cálculos también deben ser cuánticos \\[ref 1].\n",
        "\n",
        "    Por ejemplo, simular la cafeína con el conjunto de bases más simple ( STO-3G ) requeriría $10^{48}$ bits, mucho mayor que el número total de estrellas del universo observable ( $10^{24}$ ) \\[ref 2]. Un ordenador cuántico puede describir los orbitales electrónicos de la cafeína con 160 qubits.\n",
        "\n",
        "    Los ordenadores cuánticos procesan de forma natural las interacciones cuánticas mediante superposición y entrelazamiento, lo que constituye una vía prometedora para permitir simulaciones moleculares precisas. Además, podemos combinar las ventajas tanto de los ordenadores cuánticos (simulación de electrones) como de los clásicos (pre y postprocesamiento de datos, gestión de procesos algorítmicos, optimización, etc.). Se espera que mejoren el descubrimiento de materiales, el diseño de fármacos y la predicción de reacciones, reduciendo los costosos experimentos de ensayo y error. \\[ref 3]\\[ref 4]\n",
        "\n",
        "    Si quiere saber por qué son necesarios los ordenadores cuánticos para los problemas de química y por qué utilizar tanto los recursos informáticos cuánticos como los clásicos, consulte los siguientes artículos:\n",
        "\n",
        "    * [Nuevos algoritmos de computación cuántica para la química cuántica](https://arxiv.org/abs/2109.02873)\n",
        "    * [Química más allá de las soluciones exactas en un superordenador cuántico-céntrico](https://arxiv.org/html/2405.05068v1)\n",
        "    * [Supercomputación cuántica para la ciencia de materiales: Perspectiva de los retos y orientaciones futuras](https://www.sciencedirect.com/science/article/pii/S0167739X24002012)\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n",
        "Ahora volvamos a VQE.\n",
        "\n",
        "El VQE combina la potencia de los ordenadores cuánticos con la de los clásicos, fundamentalmente utilizando principios variacionales para obtener la energía del estado fundamental del sistema. Para entender el VQE, primero hay que dividirlo en tres partes:\n",
        "\n",
        "![Flujo de trabajo VQE](https://quantum.cloud.ibm.com/learning/images/modules/computer-science/vqe/vqe.avif)\n",
        "\n",
        "<span id=\"quantum-observable-the-molecular-hamiltonian-energy-of-a-molecule\" />\n",
        "\n",
        "#### Observable (cuántico): el hamiltoniano molecular (energía de una molécula)\n",
        "\n",
        "En VQE, el Hamiltoniano molecular/atómico es un observable, lo que significa que podemos medir su valor mediante un experimento. Nuestro objetivo es encontrar la energía más baja posible (la energía del estado fundamental) de la molécula. Para ello, utilizamos un estado cuántico de prueba, generado por un circuito cuántico parametrizado (ansatz). Medimos el observable y optimizamos el estado cuántico hasta alcanzar la menor energía posible.\n",
        "\n",
        "El conjunto de bases utilizado para el Hamiltoniano molecular determina el número de qubits necesarios y afecta directamente a la precisión del VQE. Elegir el conjunto de bases adecuado es fundamental para equilibrar eficacia y precisión. Para simplificar los cálculos sin cambiar el conjunto de bases, podemos utilizar estrategias como la imposición de simetría y la reducción del espacio activo. Muchas moléculas tienen formas simétricas (como una mariposa o un copo de nieve), lo que significa que algunas partes se comportan de la misma manera. En lugar de calcular todo por separado, podemos centrarnos sólo en las partes únicas, ahorrando recursos cuánticos y aprovechando así la simetría. En la reducción del espacio activo, sólo consideramos los orbitales importantes, ya que no todos los electrones influyen significativamente en la energía molecular. Los electrones cercanos al núcleo permanecen en su mayoría inalterados, mientras que otros influyen en el enlace. Aplicando estos métodos, podemos hacer que el VQE sea más eficiente manteniendo la precisión.\n",
        "\n",
        "Una vez que obtenemos un Hamiltoniano molecular utilizando el conjunto de bases adecuado y las estrategias anteriores, necesitamos transformar este Hamiltoniano en uno adecuado para los ordenadores cuánticos. La asignación de problemas a operadores de Pauli puede ser bastante complicada. Esto es especialmente cierto en química cuántica, que trabaja con partículas indistinguibles (electrones), ya que los qubits son distinguibles. No entraremos aquí en los detalles de las correspondencias, pero le remitimos a los siguientes recursos. En [Quantum computing in practice (La informática cuántica en la práctica](/learning/courses/quantum-computing-in-practice/index) ) encontrará un análisis general de la asignación de un problema a operadores cuánticos. En [Quantum chemistry with VQE (Química cuántica con VQE](/learning/courses/quantum-chem-with-vqe/index) ) encontrará un análisis más detallado de la conversión de problemas químicos en operadores cuánticos.\n",
        "\n",
        "Para este módulo, le proporcionaremos los Hamiltonianos apropiados (de un qubit) para $H$ y $H_2$ para que podamos centrarnos en el uso del ordenador cuántico. Estos hamiltonianos de un qubit se preparan utilizando el conjunto de bases [STO-6G](https://en.wikipedia.org/wiki/STO-nG_basis_sets) y el mapeo [de Jordan-Wigner](https://en.wikipedia.org/wiki/Jordan%E2%80%93Wigner_transformation), que es el mapeo más directo con la interpretación física más simple, porque mapea la ocupación de un orbital de espín a la ocupación de un qubit. Además, utilizamos una [técnica de reducción de qubits mediante el uso de una simetría del hamiltoniano](https://arxiv.org/abs/1701.08213), que utiliza los patrones en cómo se comportan las ocupaciones de espín para reducir el número de qubits. Para la molécula $H_2$, suponemos que la distancia entre los dos átomos de hidrógeno es `0.735` $\\mathring A$.\n",
        "\n",
        "<span id=\"quantum-ansatz-the-trial-wave-function-how-to-build-a-trivial-quantum-state-with-a-quantum-circuit\" />\n",
        "\n",
        "#### (Cuántica) Ansatz: La función de onda de prueba (Cómo construir un estado cuántico trivial con un circuito cuántico)\n",
        "\n",
        "Para VQE, el ansatz (plural: ansätze) consta de dos componentes clave. La primera es la preparación del estado inicial, que establece el estado del qubit aplicando puertas cuánticas sin parámetro variacional. El segundo componente es el circuito cuántico parametrizado, un circuito cuántico especial con parámetros ajustables, similar a los diales de una radio. Estos parámetros se utilizarán en la última parte, el optimizador clásico, para ayudarnos a alcanzar el mejor estado básico posible.\n",
        "\n",
        "En la sección del principio variacional, aprendimos que la calidad del estado de prueba afecta a la calidad de los resultados del algoritmo variacional. Esto significa que elegir un buen ansatz es importante en VQE. Una vez más, se trata de un tema rico y complejo. No trataremos aquí los distintos tipos de ansatz ni sus orígenes. Si estás interesado en aprender más sobre circuitos cuánticos parametrizados y ansatz, puedes explorar la lección [Ansatz y forma variacional](/learning/courses/variational-algorithm-design/ansaetze-and-variational-forms) del [curso Diseño de algoritmos variacionales](/learning/courses/variational-algorithm-design/index), que proporciona explicaciones detalladas y ejemplos de ansätze.\n",
        "\n",
        "Puesto que vamos a utilizar un Hamiltoniano de un qubit en este módulo, necesitamos un circuito cuántico parametrizado de un qubit como ansatz. Veremos tres tipos de ansätze de un qubit en la siguiente sección. Los compararemos y discutiremos las consideraciones clave a la hora de seleccionar un ansatz.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a8fae507-41e6-4585-8e9d-b45db5055fb9",
      "metadata": {},
      "source": [
        "<span id=\"classical-optimizer-fine-tuning-the-quantum-circuit\" />\n",
        "\n",
        "#### Optimizador (clásico): ajuste fino del circuito cuántico\n",
        "\n",
        "Una vez que el ordenador cuántico mide la energía del observable a partir del ansatz, los parámetros del ansatz y el valor de la energía se envían al optimizador clásico para su ajuste. Este proceso de optimización se realiza en un ordenador clásico, normalmente utilizando paquetes científicos de propósito general como SciPy.\n",
        "\n",
        "El optimizador clásico trata la energía medida como una función de coste. En los problemas de optimización, una función de coste (también llamada a veces función objetivo) es una función matemática que mide lo \"buena\" que es una solución concreta. El objetivo del optimizador es encontrar el conjunto de parámetros que minimice esta función de coste. En el contexto de la búsqueda de la energía del estado fundamental de una molécula, la propia energía sirve como función de coste: queremos encontrar los parámetros de nuestro circuito cuántico (nuestra \"solución\") que produzcan la menor energía posible. El optimizador clásico utiliza este valor de energía medido (el coste) y determina el siguiente conjunto de parámetros optimizados para el ansatz cuántico. Estos parámetros actualizados se envían de nuevo al circuito cuántico y se repite el proceso. En cada iteración, el optimizador clásico ajusta los parámetros para intentar reducir la energía (minimizar la función de coste) hasta que se cumpla un criterio de convergencia predefinido, lo que idealmente garantiza que se encuentre la energía más baja posible (correspondiente al estado fundamental de la molécula para esa distancia de enlace y ese conjunto de bases).\n",
        "\n",
        "Existen muchas estrategias de optimización proporcionadas por paquetes científicos como SciPy. Encontrará más información en la lección [Bucles de optimización](/learning/courses/variational-algorithm-design/optimization-loops) del curso [Diseño de algoritmos variacionales](/learning/courses/variational-algorithm-design/index). Aquí utilizaremos COBYLA (Constrained Optimization BY Linear Approximations), un algoritmo de optimización adecuado para paisajes energéticos complicados. En particular, COBYLA no intenta calcular el gradiente de la función estudiada; es lo que se denomina un optimizador sin gradiente. Imagine que intenta encontrar el pico más alto de una cordillera con los ojos cerrados. Como no puedes ver todo el paisaje, das pequeños pasos en distintas direcciones, mientras compruebas si estás subiendo o bajando. COBYLA funciona de forma similar: se mueve por el espacio de parámetros, probando diferentes valores, mejorando gradualmente el resultado hasta encontrar el mejor.\n",
        "\n",
        "Ahora está listo para realizar un cálculo VQE. A tal fin, pruebe la pregunta de registro que figura a continuación, que resume el proceso general.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8cdcfcc0-a6ca-4153-a11a-bd7b598f9b35",
      "metadata": {},
      "source": [
        "<span id=\"check-your-understanding\" />\n",
        "\n",
        "#### Comprueba tu comprensión\n",
        "\n",
        "Rellena los espacios en blanco con los términos correctos para completar el resumen del proceso VQE y, a continuación, haz clic para comprobar tus respuestas.\n",
        "\n",
        "VQE es un algoritmo cuántico variacional, que combina el poder de (1) \\_\\_\\_\\_\\_ y la computación clásica, utilizado para encontrar el (2) \\_\\_\\_\\_\\_\\_\\_ de una molécula. El proceso comienza definiendo el (3) \\_\\_\\_\\_\\_\\_\\_\\_\\_\\_, que representa la energía total del sistema y actúa como observable en las mediciones cuánticas. A continuación, preparamos un (4) \\_\\_\\_\\_\\_\\_\\_\\_\\_, un circuito cuántico con parámetros ajustables que representa la función de onda de prueba de la molécula. Estos parámetros se optimizan utilizando un (5) \\_\\_\\_\\_\\_\\_\\_\\_\\_, un algoritmo clásico que ajusta los parámetros de forma iterativa para minimizar la energía medida. En la discusión anterior hemos utilizado el (6) \\_\\_\\_\\_\\_\\_\\_\\_\\_\\_ optimizador, que refina los parámetros ansatz sin necesidad de cálculos derivados. El proceso continúa hasta que llegamos a (7) \\_\\_\\_\\_\\_\\_\\_\\_\\_\\_, lo que significa que hemos encontrado la energía más baja posible de la molécula.\n",
        "\n",
        "Banco de palabras:\n",
        "\n",
        "* optimizador clásico\n",
        "* energía del estado base\n",
        "* hardware eficiente\n",
        "* ansatz\n",
        "* hamiltoniano molecular\n",
        "* COBYLA\n",
        "* Computación cuántica\n",
        "* Convergencia\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Respuesta\">\n",
        "    1 → informática cuántica\n",
        "\n",
        "    2 → energía del estado básico\n",
        "\n",
        "    3 → Hamiltoniano molecular\n",
        "\n",
        "    4 → ansatz\n",
        "\n",
        "    5 → optimizador clásico\n",
        "\n",
        "    6 → COBYLA\n",
        "\n",
        "    7 → convergencia\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "64a090f7-1d18-4edb-bef3-4cea0f4cec2c",
      "metadata": {},
      "source": [
        "<span id=\"compute-the-ground-state-energy-of-a-hydrogen-atom-with-vqe\" />\n",
        "\n",
        "## Calcular la energía del estado fundamental de un átomo de hidrógeno con VQE\n",
        "\n",
        "Ahora, utilicemos lo que hemos aprendido para calcular la energía de estado fundamental de un átomo de hidrógeno. A lo largo del módulo, utilizaremos un marco para la computación cuántica conocido como \"patrones Qiskit\", que desglosa los flujos de trabajo en los siguientes pasos:\n",
        "\n",
        "* Paso 1: Asignar entradas clásicas a un problema cuántico\n",
        "* Paso 2: Optimizar el problema para la ejecución cuántica\n",
        "* Paso 3: Ejecutar utilizando Qiskit Runtime primitives\n",
        "* Etapa 4: Tratamiento posterior y análisis clásico\n",
        "\n",
        "![Patrón Qiskit](https://quantum.cloud.ibm.com/learning/images/modules/computer-science/vqe/patterns.svg)\n",
        "\n",
        "Por lo general, seguiremos estos pasos.\n",
        "\n",
        "Empecemos cargando algunos paquetes necesarios, incluidos los primitivos Qiskit Runtime. También seleccionaremos el ordenador cuántico menos ocupado de que dispongamos.\n",
        "\n",
        "A continuación encontrará un código para guardar sus credenciales la primera vez que las utilice. Asegúrate de borrar esta información del cuaderno después de guardarlo en tu entorno, para que tus credenciales no se compartan accidentalmente cuando compartas el cuaderno. Consulte [Configurar su cuenta IBM Cloud](/docs/guides/initialize-account) e [Inicializar el servicio en un entorno no](/docs/guides/cloud-setup-untrusted) fiable para obtener más orientación.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "18d9c613-ee5f-4e38-bc83-57d25197b200",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "ibm_brisbane\n"
          ]
        }
      ],
      "source": [
        "# Load the Qiskit Runtime service\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "\n",
        "# Load the Runtime primitive and session\n",
        "from qiskit_ibm_runtime import EstimatorV2 as Estimator\n",
        "\n",
        "# Syntax for first saving your token.  Delete these lines after saving your credentials.\n",
        "# QiskitRuntimeService.save_account(channel='ibm_quantum_platform',\n",
        "# instance = '<YOUR_IBM_INSTANCE_CRN>', token='<YOUR-API_KEY>', overwrite=True, set_as_default=True)\n",
        "# service = QiskitRuntimeService(channel='ibm_quantum_platform')\n",
        "\n",
        "# Load saved credentials\n",
        "service = QiskitRuntimeService()\n",
        "\n",
        "# Use the least busy backend, or uncomment the loading of a specific backend like \"ibm_brisbane\".\n",
        "backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)\n",
        "# backend = service.backend(\"ibm_brisbane\")\n",
        "print(backend.name)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0c4e0ac4-7f4c-4cab-b726-1d6fe1ca10ac",
      "metadata": {},
      "source": [
        "La celda de abajo te permitirá cambiar entre usar el simulador o el hardware real a lo largo del cuaderno. Recomendamos ejecutarlo ahora:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "4c38f002-c6e6-4fe2-acf1-3cccf3d1f525",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Load the Aer simulator and generate a noise model based on the currently-selected backend.\n",
        "from qiskit_aer import AerSimulator\n",
        "from qiskit_aer.noise import NoiseModel\n",
        "\n",
        "# Alternatively, load a fake backend with generic properties and define a simulator.\n",
        "\n",
        "\n",
        "noise_model = NoiseModel.from_backend(backend)\n",
        "\n",
        "# Define a simulator using Aer, and use it in Sampler.\n",
        "backend_sim = AerSimulator(noise_model=noise_model)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "497acce3-f34c-43b3-a014-630702a3620b",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-the-problem-to-quantum-circuits-and-operators\" />\n",
        "\n",
        "### Paso 1: Asignar el problema a circuitos y operadores cuánticos\n",
        "\n",
        "Comenzamos nuestro cálculo de VQE definiendo el hamiltoniano de la molécula de hidrógeno ( $H_2$ ) a una distancia de enlace específica. Este hamiltoniano representa la energía total del sistema en términos de operadores de qubits, habiéndose obtenido y mapeado a partir del sistema molecular mediante un procedimiento estándar: 1) utilizando el conjunto de bases de STO-6G (un conjunto específico de funciones matemáticas que se emplean para aproximar los orbitales de los electrones), 2) aplicando la correspondencia de Jordan-Wigner (una técnica para traducir los operadores fermiónicos que describen a los electrones en operadores de qubits), y 3) realizando una reducción de qubits utilizando la paridad del hamiltoniano para simplificar el problema.\n",
        "\n",
        "Como ya hemos explicado, las energías del estado básico calculadas dependen en gran medida de la selección del conjunto de bases y de la geometría molecular (como la distancia de enlace). Para esta configuración específica y tras estas transformaciones, el Hamiltoniano del qubit resultante es simple:\n",
        "\n",
        "$\\hat{H} = -0.2355 I + 0.2355 Z$\n",
        "\n",
        "Aquí, $I$ representa el operador identidad y $Z$ representa el operador Pauli-Z, actuando sobre un único qubit. Los coeficientes se derivan de las integrales calculadas utilizando el conjunto de bases STO-6G a esta distancia de enlace concreta con la transformación adecuada.\n",
        "\n",
        "Con este Hamiltoniano definido, ahora podemos utilizar VQE para calcular su energía de estado fundamental. Resulta útil comparar nuestra energía calculada del estado básico con los valores esperados. Para un átomo de hidrógeno (H) aislado, la energía del estado básico es exactamente -0.5 Hartree (en ausencia de efectos relativistas). Calculemos la energía exacta del estado fundamental de *nuestro qubit hamiltoniano específico* definido anteriormente y comparémosla con los valores conocidos pertinentes.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "aaef7daa-6dbe-4a2e-abe2-3ad2a0baa860",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "The exact ground state energy of the Hamiltonian is  -0.471 hartree\n"
          ]
        }
      ],
      "source": [
        "from qiskit.quantum_info import SparsePauliOp\n",
        "import numpy as np\n",
        "\n",
        "# Qubit Hamiltonian of the hydrogen atom generated by using STO-3G basis set and parity mapping\n",
        "Hamiltonian = SparsePauliOp.from_list([(\"I\", -0.2355), (\"Z\", 0.2355)])\n",
        "\n",
        "# exact ground state energy of Hamiltonian\n",
        "\n",
        "A = np.array(Hamiltonian)\n",
        "eigenvalues, eigenvectors = np.linalg.eig(A)\n",
        "print(\n",
        "    \"The exact ground state energy of the Hamiltonian is \",\n",
        "    min(eigenvalues).real,\n",
        "    \"hartree\",\n",
        ")\n",
        "h = min(eigenvalues.real)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ec53e133-d8ab-4e5d-a44a-428fe8095aee",
      "metadata": {},
      "source": [
        "A continuación, necesitamos un circuito cuántico parametrizado, un ansatz, para preparar una función de onda de prueba $\\Psi_\\text{trial}$ para el estado fundamental. El objetivo es encontrar los parámetros $\\theta$ que minimicen el valor de expectativa de energía $\\langle\\psi(\\theta)|\\hat{H}|\\psi(\\theta)\\rangle$. La elección del ansatz es crucial porque determina el conjunto de posibles estados cuánticos que nuestro circuito puede preparar. Un \"buen\" ansatz es aquel que es lo suficientemente flexible como para representar un estado muy cercano al verdadero estado básico del Hamiltoniano que estamos estudiando, pero no tan complejo que requiera demasiados parámetros o un circuito demasiado profundo para los ordenadores cuánticos actuales.\n",
        "\n",
        "Aquí, probaremos tres ansätze diferentes de un qubit para ver cuál proporciona una mejor \"cobertura\" de los posibles estados cuánticos en los que puede estar un solo qubit. La \"cobertura\" se refiere a la gama de estados cuánticos que el circuito ansatz puede producir variando sus parámetros.\n",
        "\n",
        "Utilizaremos tres ansätze basadas en diferentes combinaciones de puertas rotacionales de un solo qubit:\n",
        "\n",
        "* Ansatz de puerta rotacional de un eje: Este ansatz utiliza rotaciones alrededor de un solo eje ( $R_x(\\theta)$ ). En la esfera de Bloch, esto corresponde a moverse sólo a lo largo de un círculo específico. Es la menos flexible y cubre un conjunto limitado de Estados.\n",
        "* Two 2-axis rotational gate ansätze: Estas ansätze combinan rotaciones alrededor de dos ejes diferentes ( $R_x(\\theta_1) R_z(\\theta_2)$ y $R_x(\\theta_1) R_z(\\theta_2) R_x(\\theta_3)$ ). Esto nos permite alcanzar una mayor porción de la esfera de Bloch, en comparación con una rotación de un solo eje.\n",
        "\n",
        "Al comparar los resultados VQE obtenidos con estos tres ansätze, podemos ver cómo la flexibilidad y la cobertura del espacio de estados del ansatz afectan a nuestra capacidad para encontrar la verdadera energía del estado fundamental de nuestro Hamiltoniano simplificado. Un ansatz más flexible tiene el *potencial* de encontrar una mejor aproximación, pero también podría ser más difícil para el optimizador clásico.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "653b257f-9975-4bd3-9c48-a046ec4a9fae",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<qiskit.circuit.instructionset.InstructionSet at 0x1059def80>"
            ]
          },
          "execution_count": 7,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit import QuantumCircuit\n",
        "from qiskit.circuit import Parameter\n",
        "from qiskit.quantum_info import Statevector, DensityMatrix, Pauli\n",
        "\n",
        "theta = Parameter(\"θ\")\n",
        "phi = Parameter(\"φ\")\n",
        "lam = Parameter(\"λ\")\n",
        "\n",
        "ansatz1 = QuantumCircuit(1)\n",
        "ansatz1.rx(theta, 0)\n",
        "\n",
        "ansatz2 = QuantumCircuit(1)\n",
        "ansatz2.rx(theta, 0)\n",
        "ansatz2.rz(phi, 0)\n",
        "\n",
        "ansatz3 = QuantumCircuit(1)\n",
        "ansatz3.rx(theta, 0)\n",
        "ansatz3.rz(phi, 0)\n",
        "ansatz3.rx(lam, 0)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "26707a66-7098-4f57-93fc-eb1cb4c305f7",
      "metadata": {},
      "source": [
        "Ahora, generemos 5000 números aleatorios para cada parámetro y tracemos la distribución de estados cuánticos aleatorios, generados por los tres ansätze con estos parámetros aleatorios. Puedes pensar en estos parámetros como si fueran rotaciones alrededor de diferentes ejes en una superficie esférica. Para ver la distribución del estado cuántico, utilizaremos [la Esfera de Bloch](https://en.wikipedia.org/wiki/Bloch_sphere), una esfera tridimensional que muestra el estado de un solo qubit. Cualquier punto de la esfera representa un posible estado del qubit, donde los polos norte y sur son como los clásicos \"0\" y \"1\", pero el qubit también puede estar en cualquier lugar intermedio, mostrando propiedades cuánticas especiales como la superposición. En primer lugar, prepare las funciones necesarias para trazar la esfera de Bloch 3D y prepare 5000 parámetros aleatorios.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "6e47463b-b0c2-44cd-b099-9f50f38ebd0e",
      "metadata": {},
      "outputs": [],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "\n",
        "\n",
        "def plot_bloch(bloch_vectors):\n",
        "    # Extract X, Y, Z coordinates for 3D projection\n",
        "    X_coords = bloch_vectors[:, 0]\n",
        "    Z_coords = bloch_vectors[:, 2]\n",
        "\n",
        "    # Compute Y coordinates from X and Z to approximate the full Bloch sphere projection\n",
        "    Y_coords = bloch_vectors[:, 1]\n",
        "\n",
        "    # Create 3D plot\n",
        "    fig = plt.figure(figsize=(8, 8))\n",
        "    ax = fig.add_subplot(111, projection=\"3d\")\n",
        "    ax.scatter(X_coords, Y_coords, Z_coords, color=\"blue\", alpha=0.6)\n",
        "\n",
        "    # Labels and title\n",
        "    ax.set_xlabel(\"X\")\n",
        "    ax.set_ylabel(\"Y\")\n",
        "    ax.set_zlabel(\"Z\")\n",
        "    ax.set_title(\"Parameterized 1-Qubit Circuit on 3D Bloch Sphere\")\n",
        "\n",
        "    # Set axis limits and make them equal\n",
        "    ax.set_xlim([-1, 1])\n",
        "    ax.set_ylim([-1, 1])\n",
        "    ax.set_zlim([-1, 1])\n",
        "\n",
        "    # Ensure equal aspect ratio for all axes\n",
        "    ax.set_box_aspect([1, 1, 1])  # Equal scaling for x, y, z axes\n",
        "\n",
        "    # Show grid\n",
        "    ax.grid(True)\n",
        "\n",
        "    plt.show()\n",
        "\n",
        "\n",
        "num_samples = 5000  # Number of random states\n",
        "theta_vals = np.random.uniform(0, 2 * np.pi, num_samples)\n",
        "phi_vals = np.random.uniform(0, 2 * np.pi, num_samples)\n",
        "lam_vals = np.random.uniform(0, 2 * np.pi, num_samples)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "af24b916-fa42-4c4c-8e22-098adfb4fcd5",
      "metadata": {},
      "source": [
        "Veamos cómo funciona nuestro primer ansatz.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "6471a5d1-287a-4726-9e65-e23ef77ccf75",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/vqe/extracted-outputs/f7d886a0-d410-418d-a7a3-81a9134cbfd0-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# List to store Bloch Sphere XZ coordinates\n",
        "bloch_vectors = []\n",
        "\n",
        "# Generate quantum states and extract Bloch vectors\n",
        "for i in range(num_samples):\n",
        "    # Create a circuit and bind parameters\n",
        "    qc = ansatz1\n",
        "    bound_qc = qc.assign_parameters({theta: theta_vals[i]})  # , lam: lam_vals[i]})\n",
        "    state = Statevector.from_instruction(bound_qc)\n",
        "    rho = DensityMatrix(state)\n",
        "\n",
        "    X = rho.expectation_value(Pauli(\"X\")).real\n",
        "    Y = rho.expectation_value(Pauli(\"Y\")).real\n",
        "    Z = rho.expectation_value(Pauli(\"Z\")).real\n",
        "    bloch_vectors.append([X, Y, Z])  # Store X, Z components\n",
        "\n",
        "# Convert to a numpy array for plotting\n",
        "bloch_vectors = np.array(bloch_vectors)\n",
        "\n",
        "plot_bloch(bloch_vectors)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9cb7d81e-8df5-4557-8452-98f794bdc5cd",
      "metadata": {},
      "source": [
        "Podemos ver que nuestro primer ansatz devuelve una distribución de estados cuánticos en forma de anillo de la esfera de Bloch. Esto tiene sentido, porque sólo hemos dado al ansatz un único parámetro rotacional. Por lo tanto, sólo puede producir estados rotados alrededor de un eje. Si se parte del punto $(0,0,1)$ y se gira alrededor de un eje, siempre se obtendrá un anillo. A continuación, vamos a comprobar nuestro segundo ansatz, que tiene dos puertas de rotación ortogonales - `Rx` y `Rz`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "9cd86049-f652-4e76-a68a-2780cb7782db",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/vqe/extracted-outputs/607e4c0f-cb14-4036-901b-021cc8e11b79-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "bloch_vectors = []\n",
        "\n",
        "# Generate quantum states and extract Bloch vectors\n",
        "for i in range(num_samples):\n",
        "    # Create circuit and bind parameters\n",
        "    qc = ansatz2\n",
        "    bound_qc = qc.assign_parameters(\n",
        "        {theta: theta_vals[i], phi: phi_vals[i]}\n",
        "    )  # , lam: lam_vals[i]})\n",
        "    state = Statevector.from_instruction(bound_qc)\n",
        "    rho = DensityMatrix(state)\n",
        "\n",
        "    X = rho.expectation_value(Pauli(\"X\")).real\n",
        "    Y = rho.expectation_value(Pauli(\"Y\")).real\n",
        "    Z = rho.expectation_value(Pauli(\"Z\")).real\n",
        "    bloch_vectors.append([X, Y, Z])  # Store X, Z components\n",
        "\n",
        "# Convert to numpy array for plotting\n",
        "bloch_vectors = np.array(bloch_vectors)\n",
        "\n",
        "plot_bloch(bloch_vectors)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e3fde737-d565-4200-a968-c6a6c78dfe30",
      "metadata": {},
      "source": [
        "Aquí podemos ver que nuestro segundo ansatz cubre una porción mayor de la esfera de Bloch, pero obsérvese que los puntos están más concentrados alrededor de los polos y más dispersos alrededor del ecuador. Ahora es el momento de comprobar nuestro último ansatz.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "ec2e9a84-46b2-4143-8cde-d981e9edd3ce",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/vqe/extracted-outputs/21ff021a-7cfe-4ba9-a827-edac71c0871c-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "bloch_vectors = []\n",
        "\n",
        "# Generate quantum states and extract Bloch vectors\n",
        "for i in range(num_samples):\n",
        "    # Create circuit and bind parameters\n",
        "    qc = ansatz3\n",
        "    bound_qc = qc.assign_parameters(\n",
        "        {theta: theta_vals[i], phi: phi_vals[i], lam: lam_vals[i]}\n",
        "    )\n",
        "    state = Statevector.from_instruction(bound_qc)\n",
        "    rho = DensityMatrix(state)\n",
        "\n",
        "    X = rho.expectation_value(Pauli(\"X\")).real\n",
        "    Y = rho.expectation_value(Pauli(\"Y\")).real\n",
        "    Z = rho.expectation_value(Pauli(\"Z\")).real\n",
        "    bloch_vectors.append([X, Y, Z])  # Store X, Z components\n",
        "\n",
        "# Convert to numpy array for plotting\n",
        "bloch_vectors = np.array(bloch_vectors)\n",
        "\n",
        "plot_bloch(bloch_vectors)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ca886db5-e7c7-4f5c-8594-50f79fbc61dd",
      "metadata": {},
      "source": [
        "Aquí puedes ver estados cuánticos más uniformemente distribuidos generados por nuestro último ansatz.\n",
        "\n",
        "Como ya se ha dicho, lo mejor es conocer el estado básico que se busca y utilizar un ansatz adecuado para sondear estados cercanos a ese estado básico. Por ejemplo, si supiéramos que nuestro estado básico está cerca de un polo, podríamos seleccionar el ansatz 2. Para simplificar, nos ceñiremos al ansatz 3, que sondea uniformemente toda la esfera de Bloch.\n",
        "\n",
        "Ahora que hemos seleccionado nuestro ansatz, vamos a dibujar el circuito.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "9f0c76d9-b4b2-46dc-a261-7c624c031177",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "This circuit has  3 parameters\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/vqe/extracted-outputs/20221e72-51dc-49d9-8a09-9b64de7f535f-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 12,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Pre-defined ansatz circuit and operator class for Hamiltonian\n",
        "\n",
        "ansatz = ansatz3\n",
        "\n",
        "num_params = ansatz.num_parameters\n",
        "print(\"This circuit has \", num_params, \"parameters\")\n",
        "\n",
        "ansatz.draw(\"mpl\", style=\"iqp\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b7dc624b-37bc-4523-8532-e45c7826ce64",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-for-target-hardware\" />\n",
        "\n",
        "### Paso 2: Optimizar para el hardware de destino\n",
        "\n",
        "Cuando ejecutamos un cálculo en un ordenador cuántico real, no sólo nos preocupamos por la lógica del circuito cuántico. También nos importan cosas como qué operaciones puede realizar ese ordenador cuántico concreto y en qué parte del ordenador cuántico se encuentran los qubits que estamos utilizando. ¿Están uno al lado del otro? ¿Están muy separados? Por lo tanto, el siguiente paso es reescribir nuestro circuito utilizando puertas que sean naturales para el ordenador cuántico que utilizaremos, y teniendo en cuenta la disposición de los qubits. Esto se puede hacer mediante `transpilation` - después de este proceso, puedes ver nuestro ansatz simple convertido en un conjunto diferente de puertas, y nuestros qubits abstractos se mapearán en qubits físicos en un ordenador cuántico real.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "d4f50ff9-cdf4-483f-9bb8-5814fe5acc53",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Backend: {config.backend_name}\n",
            "Native gates:  ['ecr', 'id', 'delay', 'measure', 'reset', 'rz', 'sx', 'x'] ,\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/vqe/extracted-outputs/b5e08232-d651-4df4-bcb7-533513144eff-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 13,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "\n",
        "config = backend.configuration()\n",
        "\n",
        "print(\"Backend: {config.backend_name}\")\n",
        "print(\"Native gates: \", config.supported_instructions, \",\")\n",
        "\n",
        "\n",
        "target = backend.target\n",
        "\n",
        "pm = generate_preset_pass_manager(target=target, optimization_level=3)\n",
        "\n",
        "ansatz_isa = pm.run(ansatz)\n",
        "\n",
        "ansatz_isa.draw(output=\"mpl\", idle_wires=False, style=\"iqp\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "04feccf8-4591-4919-90ba-9b0d72e1b436",
      "metadata": {},
      "source": [
        "Puede ver que las puertas `rx, rz` de nuestro ansatz se convirtieron en una serie de puertas `rz, sx` , que son las puertas nativas de nuestro backend. También, puedes ver que nuestro `q0` está ahora mapeado en el quinto qubit físico. También necesitamos mapear nuestro Hamiltoniano de acuerdo con estos cambios, como en el siguiente código:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "2e105b4d-8c3c-4b1e-9652-9857ee9e5465",
      "metadata": {},
      "outputs": [],
      "source": [
        "Hamiltonian_isa = Hamiltonian.apply_layout(layout=ansatz_isa.layout)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d3212dfa-da89-43d6-85ce-6200ecf9ca09",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-on-target-hardware\" />\n",
        "\n",
        "### Paso 3: Ejecutar en el hardware de destino\n",
        "\n",
        "Ahora es el momento de ejecutar nuestro VQE en una QPU real. Para ello, primero necesitamos una función de coste para el proceso de optimización, que evalúe el valor de la expectativa del Hamiltoniano con un estado cuántico, generado por el ansatz. No se preocupe. No hace falta que codifiques todo tú solo. Hemos preparado una función para esto, y todo lo que necesitas hacer es ejecutar la celda de abajo.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "6b2178bd-6e86-4178-b82b-f7a6d7e95022",
      "metadata": {},
      "outputs": [],
      "source": [
        "def cost_func(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 (EstimatorV2): Estimator primitive instance\n",
        "        cost_history_dict: Dictionary for storing intermediate results\n",
        "\n",
        "    Returns:\n",
        "        float: Energy estimate\n",
        "    \"\"\"\n",
        "    pub = (ansatz, [hamiltonian], [params])\n",
        "    result = estimator.run(pubs=[pub]).result()\n",
        "    energy = result[0].data.evs[0]\n",
        "\n",
        "    cost_history_dict[\"iters\"] += 1\n",
        "    cost_history_dict[\"prev_vector\"] = params\n",
        "    cost_history_dict[\"cost_history\"].append(energy)\n",
        "    print(f\"Iters. done: {cost_history_dict['iters']} [Current cost: {energy}]\")\n",
        "\n",
        "    return energy"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "83ed09be-d475-4d66-b55f-b25bbf3412c8",
      "metadata": {},
      "source": [
        "Por último, preparamos los parámetros iniciales para nuestro ansatz y su proceso de optimización. Puede utilizar simplemente todos los ceros o valores aleatorios. A continuación hemos seleccionado los parámetros iniciales, pero no dude en comentar o descomentar las líneas de la celda para muestrear los parámetros al azar, uniformemente de 0 a $2\\pi$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "b2a5dab1-f724-48df-b84b-20749ffd5261",
      "metadata": {},
      "outputs": [],
      "source": [
        "# x0 = np.random.uniform(0, 2*pi, 3)\n",
        "x0 = [1, 1, 0]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "5b48b37d-1f1e-4aef-9fd5-ba594db832b0",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Iters. done: 1 [Current cost: -0.3361517318448143]\n",
            "Iters. done: 2 [Current cost: -0.4682546422099432]\n",
            "Iters. done: 3 [Current cost: -0.38985802144149584]\n",
            "Iters. done: 4 [Current cost: -0.38319217316749354]\n",
            "Iters. done: 5 [Current cost: -0.4628720756579032]\n",
            "Iters. done: 6 [Current cost: -0.4683301936226905]\n",
            "Iters. done: 7 [Current cost: -0.45480498699294747]\n",
            "Iters. done: 8 [Current cost: -0.4690533242050814]\n",
            "Iters. done: 9 [Current cost: -0.465867415110354]\n",
            "Iters. done: 10 [Current cost: -0.4606882723137227]\n"
          ]
        }
      ],
      "source": [
        "# QPU Est. 2min for ibm_brisbane\n",
        "\n",
        "from scipy.optimize import minimize\n",
        "from qiskit_ibm_runtime import Batch\n",
        "\n",
        "batch = Batch(backend=backend)\n",
        "\n",
        "cost_history_dict = {\n",
        "    \"prev_vector\": None,\n",
        "    \"iters\": 0,\n",
        "    \"cost_history\": [],\n",
        "}\n",
        "estimator = Estimator(mode=batch)\n",
        "estimator.options.default_shots = 10000\n",
        "\n",
        "res = minimize(\n",
        "    cost_func,\n",
        "    x0,\n",
        "    args=(ansatz_isa, Hamiltonian_isa, estimator),\n",
        "    method=\"cobyla\",\n",
        "    options={\"maxiter\": 10, \"tol\": 0.01},\n",
        ")\n",
        "\n",
        "batch.close()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "124e15d2-ddce-4201-9f69-03c9e5612644",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "The reference ground state energy is  (-0.471+0j)\n",
            "The computed ground state energy is  -0.4690533242050814\n"
          ]
        }
      ],
      "source": [
        "h_vqe = res.fun\n",
        "print(\"The reference ground state energy is \", min(eigenvalues))\n",
        "print(\"The computed ground state energy is \", h_vqe)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "79bce65c-d84e-4bed-bfe8-71f448290fd1",
      "metadata": {},
      "source": [
        "Enhorabuena. Acabas de terminar con éxito tu primer experimento de química cuántica. Podemos ver una diferencia entre la energía exacta del estado básico del Hamiltoniano y la nuestra, pero como utilizamos una técnica de mitigación de errores por defecto (que corrige los errores de lectura), la diferencia es menor. Es un buen comienzo\n",
        "\n",
        "Nota: Se pueden obtener mejores resultados configurando un nivel de mitigación de errores mediante [`resilience_level`](/docs/guides/error-mitigation-and-suppression-techniques). El valor predeterminado es 1; si se establece un valor mayor, se consumirá más tiempo de la QPU, pero es posible que se obtenga un mejor resultado.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dae9eeb5-a2ac-4632-90cf-d2ac70182c7b",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process\" />\n",
        "\n",
        "### Paso 4: Postprocesamiento\n",
        "\n",
        "Es hora de echar un vistazo a cómo funcionaba nuestro optimizador clásico. Ejecuta la celda de abajo y observa el patrón de convergencia.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "id": "b6b32cce-cbfa-4566-9d77-e511f54cc558",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/vqe/extracted-outputs/bee036cf-1255-4b4c-881b-cae6e903e09a-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "fig, ax = plt.subplots()\n",
        "x = np.linspace(0, 10, 10)\n",
        "\n",
        "# Define the constant function\n",
        "y_constant = np.full_like(x, h)\n",
        "ax.plot(\n",
        "    range(cost_history_dict[\"iters\"]), cost_history_dict[\"cost_history\"], label=\"VQE\"\n",
        ")\n",
        "ax.set_xlabel(\"Iterations\")\n",
        "ax.set_ylabel(\"Cost (Hartree)\")\n",
        "ax.plot(y_constant, label=\"Target\")\n",
        "plt.legend()\n",
        "plt.draw()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "909ed768-080d-42b2-aae5-0fe6fa4f088b",
      "metadata": {},
      "source": [
        "Empezamos con un valor inicial bastante bueno, de tal forma que obtuvimos un buen valor final en sólo 10 pasos. Se pueden ver picos grandes y pequeños, y ésta es la característica típica del optimizador COBYLA: busca en el espacio como si no viera el paisaje y ajusta el tamaño de los pasos con cada medición.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4b3fe46b-fb4e-48d4-a891-732231429926",
      "metadata": {},
      "source": [
        "<span id=\"check-your-understanding\" />\n",
        "\n",
        "#### Comprueba tu comprensión\n",
        "\n",
        "¿Cuál es su observación? ¿Qué parte del proceso anterior puede mejorarse para obtener resultados más próximos a los valores teóricos, o más próximos a la energía exacta del estado fundamental del Hamiltoniano? ¿Qué hay que tener en cuenta para ello?\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Respuesta\">\n",
        "    Lo primero que hay que tener en cuenta es el cambio en el conjunto de bases utilizadas para calcular el Hamiltoniano de las moléculas. Como ya se ha mencionado, la energía de estado básico del átomo de H es -0.5 Hartree, como es bien sabido, y la base STO-6G que hemos elegido no es suficiente para derivar con precisión este valor.\n",
        "\n",
        "    Elegir un tipo de base más complejo aumenta el número de qubits utilizados por el hamiltoniano; por lo tanto, necesitamos seleccionar un ansatz más complejo y adecuado para los problemas de química.\n",
        "\n",
        "    Lo siguiente que hay que optimizar es la gestión del ruido en la QPU. Las técnicas más avanzadas de mitigación de errores dan mejores resultados, pero su aplicación puede llevar más tiempo. También hay que tener en cuenta cómo afecta la dirección `shot_number` a los resultados.\n",
        "\n",
        "    Por último, también se puede lograr un mejor rendimiento de convergencia probando diferentes optimizadores.\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "52e936bb-a9af-4355-94a1-82393a25124e",
      "metadata": {},
      "source": [
        "<span id=\"compute-the-ground-state-energy-of-the-hydrogen-molecule-with-vqe\" />\n",
        "\n",
        "## Calcular la energía del estado fundamental de la molécula de hidrógeno con VQE\n",
        "\n",
        "Ahora que hemos visto el proceso global de VQE utilizando átomos de $H$, calcularemos más rápidamente la energía de estado fundamental de la molécula $H_2$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bcd4b1e5-aea5-4509-af22-661937b4286d",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-the-problem-to-quantum-circuits-and-operators\" />\n",
        "\n",
        "### Paso 1: Asignar el problema a circuitos y operadores cuánticos\n",
        "\n",
        "Aquí también le proporcionamos un Hamiltoniano de un qubit que utiliza la base STO-6G y la transformación Jordan-Wigner, con reducción de qubits utilizando una simetría del Hamiltoniano. Nótese que hemos utilizado una distancia atómica entre dos átomos de hidrógeno de `0.735` $\\mathring A$.\n",
        "\n",
        "A diferencia del cálculo de un único átomo de hidrógeno ( $H$ ), para calcular el estado fundamental de una molécula de hidrógeno ( $H_2$ ), debemos considerar también la fuerza de repulsión que actúa entre los núcleos de los dos átomos de hidrógeno, además de la energía asociada a los orbitales electrónicos. En este paso, daremos este valor como una constante, y calcularemos realmente este valor en el problema de facturación. $\\hat{H} = -1.04886 I + -0.79674 Z + 0.18122 X$\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "id": "3722816b-8710-4c61-9da9-a347556496e6",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Electronic ground state energy (Hartree):  -1.8659468547627318\n",
            "Nuclear repulsion energy (Hartree):  0.71997\n",
            "Total ground state energy (Hartree):  -1.1459768547627318\n"
          ]
        }
      ],
      "source": [
        "h2_hamiltonian = SparsePauliOp.from_list(\n",
        "    [(\"I\", -1.04886087), (\"Z\", -0.7967368), (\"X\", 0.18121804)]\n",
        ")\n",
        "\n",
        "# exact ground state energy of hamiltonian\n",
        "nuclear_repulsion = 0.71997\n",
        "A = np.array(h2_hamiltonian)\n",
        "eigenvalues, eigenvectors = np.linalg.eig(A)\n",
        "print(\"Electronic ground state energy (Hartree): \", min(eigenvalues).real)\n",
        "print(\"Nuclear repulsion energy (Hartree): \", nuclear_repulsion)\n",
        "print(\n",
        "    \"Total ground state energy (Hartree): \", min(eigenvalues).real + nuclear_repulsion\n",
        ")\n",
        "h2 = min(eigenvalues).real + nuclear_repulsion"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7e275302-80ea-454b-83c1-d70b09b37442",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-for-target-hardware\" />\n",
        "\n",
        "### Paso 2: Optimizar para el hardware de destino\n",
        "\n",
        "Dado que el número de qubits utilizados por el VQE y el Hamiltoniano anteriores es el mismo que el backend que se utilizará para la ejecución, utilizaremos el ansatz existente y su forma optimizada.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 22,
      "id": "6a461ab3-fbd7-493a-aaad-e47d6bfd0079",
      "metadata": {},
      "outputs": [],
      "source": [
        "h2_hamiltonian_isa = h2_hamiltonian.apply_layout(layout=ansatz_isa.layout)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "da36cda3-94a8-4b2d-9e3d-3b5ac957c180",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-on-target-hardware\" />\n",
        "\n",
        "### Paso 3: Ejecutar en el hardware de destino\n",
        "\n",
        "Ahora es el momento de hacer los cálculos sobre la QPU real. Casi todo es igual, pero utilizaremos el punto inicial adecuado para ajustar el Hamiltoniano. Además, en una parte iterativa, algunos de los ajustes del `Estimator`, que se utiliza para calcular las expectativas del Hamiltoniano para el ansatz en el QPU, se establecerán de forma ligeramente diferente a los cálculos anteriores. Hablaremos de este cambio con más detalle en una pregunta de control.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 23,
      "id": "b5afe148-bfdc-4d77-895c-d58afc118d9e",
      "metadata": {},
      "outputs": [],
      "source": [
        "x0 = [2, 0, 0]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 45,
      "id": "3fb2e079-1413-4fd2-a3db-da5d11fe58d5",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Iters. done: 1 [Current cost: -0.710621837568328]\n",
            "Iters. done: 2 [Current cost: -0.2603208441168329]\n",
            "Iters. done: 3 [Current cost: -0.25548711201326424]\n",
            "Iters. done: 4 [Current cost: -0.581129450619904]\n",
            "Iters. done: 5 [Current cost: -1.722920997605439]\n",
            "Iters. done: 6 [Current cost: -1.6633324849371915]\n",
            "Iters. done: 7 [Current cost: -1.8066989598929164]\n",
            "Iters. done: 8 [Current cost: -1.8051093803839542]\n",
            "Iters. done: 9 [Current cost: -1.802692217571555]\n",
            "Iters. done: 10 [Current cost: -1.8233585485263144]\n",
            "Iters. done: 11 [Current cost: -1.6904116652617205]\n",
            "Iters. done: 12 [Current cost: -1.8245120321245392]\n",
            "Iters. done: 13 [Current cost: -1.6837021361383608]\n",
            "Iters. done: 14 [Current cost: -1.8166632606115467]\n",
            "Iters. done: 15 [Current cost: -1.863446212658907]\n"
          ]
        }
      ],
      "source": [
        "# QPU time 4min for ibm_brisbane\n",
        "batch = Batch(backend=backend)\n",
        "\n",
        "cost_history_dict = {\n",
        "    \"prev_vector\": None,\n",
        "    \"iters\": 0,\n",
        "    \"cost_history\": [],\n",
        "}\n",
        "estimator = Estimator(mode=batch)\n",
        "estimator.options.default_shots = 10000\n",
        "\n",
        "res = minimize(\n",
        "    cost_func,\n",
        "    x0,\n",
        "    args=(ansatz_isa, h2_hamiltonian_isa, estimator),\n",
        "    method=\"cobyla\",\n",
        "    options={\"maxiter\": 15},\n",
        ")\n",
        "\n",
        "batch.close()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 46,
      "id": "4edf1dbd-8363-4d0d-a4ca-2223719c7059",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "The reference ground state energy is  -1.1459768547627318\n",
            "The computed ground state energy is  -1.143476212658907\n"
          ]
        }
      ],
      "source": [
        "h2_vqe = res.fun + nuclear_repulsion\n",
        "print(\n",
        "    \"The reference ground state energy is \", min(eigenvalues).real + nuclear_repulsion\n",
        ")\n",
        "print(\"The computed ground state energy is \", h2_vqe)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0d30fbc5-8f52-4b84-b9a6-55b3432bbec5",
      "metadata": {},
      "source": [
        "A pesar de que la VQE proporciona teóricamente un límite superior a la verdadera energía del estado básico, las implementaciones prácticas en hardware cuántico real o simulado con ruido, así como las aproximaciones realizadas al preparar el Hamiltoniano (como los conjuntos de bases o la reducción de qubits), pueden introducir errores que a veces dan como resultado una energía medida ligeramente inferior al valor teórico exacto o a una referencia numérica específica. Aunque hay algunos errores, los resultados parecen satisfactorios, sobre todo teniendo en cuenta el reducido número de pasos. Ahora, terminemos este cálculo VQE viendo cómo funcionó el optimizador.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5d5c88de-fcd1-4ef6-8055-01505d86d07e",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process\" />\n",
        "\n",
        "### Paso 4: Postprocesamiento\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 52,
      "id": "b20dda12-dad9-4585-9a56-31611aa00930",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/vqe/extracted-outputs/4b82b644-e030-4983-9fd3-83c3e9dbf307-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "fig, ax = plt.subplots()\n",
        "x = np.linspace(0, 5, 15)\n",
        "\n",
        "# Define the constant function\n",
        "y_constant = np.full_like(x, min(eigenvalues))\n",
        "ax.plot(\n",
        "    range(cost_history_dict[\"iters\"]), cost_history_dict[\"cost_history\"], label=\"VQE\"\n",
        ")\n",
        "ax.set_xlabel(\"Iterations\")\n",
        "ax.set_ylabel(\"Cost (Hartree)\")\n",
        "ax.plot(y_constant, label=\"Target\")\n",
        "plt.legend()\n",
        "plt.draw()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8eab4184-b5dd-494e-9e67-c209b3f835f3",
      "metadata": {},
      "source": [
        "<span id=\"check-your-understanding\" />\n",
        "\n",
        "#### Comprueba tu comprensión\n",
        "\n",
        "Calculemos la energía de repulsión nuclear de la molécula $H_2$, que incluimos como valor constante ( 0.71997 Hartree).\n",
        "\n",
        "![H2 molécula](https://quantum.cloud.ibm.com/learning/images/modules/computer-science/vqe/h2.avif)\n",
        "\n",
        "Utiliza [la ley de Coulomb](https://en.wikipedia.org/wiki/Coulomb%27s_law) y [la unidad atómica](https://en.wikipedia.org/wiki/Atomic_units) para asegurarte de obtener el valor en `Hartree`.\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Respuesta\">\n",
        "    Como ambos núcleos de hidrógeno tienen carga positiva, se repelen debido a la fuerza electrostática. Esta repulsión se describe mediante la ley de Coulomb:\n",
        "\n",
        "    $E_{repulsive} = \\frac{e^2}{4\\pi\\epsilon_0R}$\n",
        "\n",
        "    donde $e$ es la carga del protón, $\\epsilon_0$ es la permitividad del vacío, y $R$ es la distancia entre los dos núcleos, medida en metros o radios de Bohr en unidad de julios(J).\n",
        "\n",
        "    Para calcular esta energía en Hartrees, necesitamos convertir la ecuación anterior al sistema de Unidades Atómicas (UA). En AU, $e^2 = 1$, $4\\pi\\epsilon_0=1$ y el radio de Bohr ( $a_0$ ) es 1 y se convierte en la escala de longitud fundamental en AU. Con estas simplificaciones, la ley de Coulomb se reduce a:\n",
        "\n",
        "    $E_{repulsion} = \\frac{1}{R}$\n",
        "\n",
        "    donde $R$ debe medirse en radios de Bohr ( $a_0$ ).\n",
        "\n",
        "    Para convertir la separación nuclear dada en $\\r{A}$ en $a_0$, necesitamos esta relación de conversión:\n",
        "\n",
        "    $1\\r{A} = 1.88973 a_0$\n",
        "\n",
        "    por lo que $0.735\\r{A}$ se convierte en $0.735 * 1.88973 = 1.38895 a_0$.\n",
        "\n",
        "    Por lo tanto, la energía de repulsión nuclear de un determinado $H_2$ es\n",
        "\n",
        "    $E_{repulsion} = \\frac{1}{R} = \\frac{1}{1.38895} = 0.71997 Hartree$\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f75e4a10-dfdc-4416-8087-a41c1eac039c",
      "metadata": {},
      "source": [
        "<span id=\"compute-reaction-energy-of-$h-+-h-=-h_2$\" />\n",
        "\n",
        "## Calcular la energía de reacción de $H + H = H_2$\n",
        "\n",
        "Ahora vamos a utilizar lo que hemos obtenido Ha utilizado VQE, un eigensolver cuántico variacional, para calcular la energía de estado básico del átomo $H$ y de la molécula $H_2$. Lo que queda es utilizar los valores calculados para obtener la energía de reacción del proceso $H+H=H_2$.\n",
        "\n",
        "La energía de reacción es el cambio de energía que se produce cuando las sustancias reaccionan para formar nuevas sustancias. Imagina que estás construyendo algo: a veces necesitas poner energía en ello (como apilar bloques), y a veces se libera energía (como una pelota rodando cuesta abajo). En química, las reacciones absorben energía (endotérmicas) o la liberan (exotérmicas).\n",
        "\n",
        "La energía de reacción del proceso $H+H = H_2$ puede calcularse mediante la siguiente fórmula:\n",
        "\n",
        "$E_{reaction} = E_{H_2} - (E_H + E_H)$\n",
        "\n",
        "Ejecutando la celda de abajo, veámoslo visualmente. Aquí utilizaremos el valor exacto del estado básico de cada Hamiltoniano, y compararemos la energía de reacción de la solución exacta y los resultados VQE.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 53,
      "id": "aa8d1a29-fbfe-41bf-8ed7-947827d0f0e6",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/vqe/extracted-outputs/87e1e660-ae81-43f4-adbe-a229b9d9c889-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# Theoretical values\n",
        "E_H_theo = h.real\n",
        "E_H2_theo = h2\n",
        "\n",
        "# Experimental values\n",
        "E_H_exp = h_vqe\n",
        "E_H2_exp = h2_vqe\n",
        "\n",
        "# Calculate reaction energies\n",
        "E_reaction_theo = E_H2_theo - (2 * E_H_theo)\n",
        "E_reaction_exp = E_H2_exp - (2 * E_H_exp)\n",
        "\n",
        "# Set up the plot\n",
        "fig, ax = plt.subplots(figsize=(8, 6))\n",
        "ax.set_xlim(0, 3)\n",
        "ax.set_ylim(-1.16, -0.93)  # Adjust y-axis range to highlight differences\n",
        "ax.set_xticks([])\n",
        "ax.set_ylabel(\"Energy (Hartree)\")\n",
        "ax.set_title(\"H + H → H₂ Reaction Energy Diagram\")\n",
        "\n",
        "# Plot theoretical energy levels\n",
        "ax.hlines(\n",
        "    y=2 * E_H_theo, xmin=0.5, xmax=1.3, linewidth=2, color=\"r\", label=\"2H (Exact)\"\n",
        ")\n",
        "ax.hlines(y=E_H2_theo, xmin=1.3, xmax=2, linewidth=2, color=\"b\", label=\"H₂ (Exact)\")\n",
        "\n",
        "# Plot experimental energy levels\n",
        "ax.hlines(\n",
        "    y=2 * E_H_exp,\n",
        "    xmin=0.5,\n",
        "    xmax=1.5,\n",
        "    linewidth=2,\n",
        "    color=\"r\",\n",
        "    linestyle=\"dashed\",\n",
        "    label=\"2H (VQE)\",\n",
        ")\n",
        "ax.hlines(\n",
        "    y=E_H2_exp,\n",
        "    xmin=1.5,\n",
        "    xmax=2.5,\n",
        "    linewidth=2,\n",
        "    color=\"b\",\n",
        "    linestyle=\"dashed\",\n",
        "    label=\"H₂ (VQE)\",\n",
        ")\n",
        "\n",
        "# Add labels\n",
        "ax.text(\n",
        "    1,\n",
        "    2 * E_H_theo,\n",
        "    f\"2H: {2*E_H_theo:.4f}\",\n",
        "    verticalalignment=\"top\",\n",
        "    horizontalalignment=\"left\",\n",
        ")\n",
        "ax.text(\n",
        "    2,\n",
        "    E_H2_theo,\n",
        "    f\"H₂: {E_H2_theo:.4f}\",\n",
        "    verticalalignment=\"top\",\n",
        "    horizontalalignment=\"left\",\n",
        ")\n",
        "ax.text(\n",
        "    1,\n",
        "    2 * E_H_exp,\n",
        "    f\"2H_VQE: {2*E_H_exp:.4f}\",\n",
        "    verticalalignment=\"bottom\",\n",
        "    horizontalalignment=\"right\",\n",
        ")\n",
        "ax.text(\n",
        "    2,\n",
        "    E_H2_exp,\n",
        "    f\"H₂_VQE: {E_H2_exp:.4f}\",\n",
        "    verticalalignment=\"bottom\",\n",
        "    horizontalalignment=\"right\",\n",
        ")\n",
        "\n",
        "# Add arrows for reaction energy with ΔE label in the middle\n",
        "mid_y_theo = (2 * E_H_theo + E_H2_theo) / 2\n",
        "mid_y_exp = (2 * E_H_exp + E_H2_exp) / 2\n",
        "ax.annotate(\n",
        "    \"\",\n",
        "    xy=(1.3, E_H2_theo),\n",
        "    xytext=(1.3, 2 * E_H_theo),\n",
        "    arrowprops=dict(arrowstyle=\"<->\", color=\"g\"),\n",
        ")\n",
        "ax.text(\n",
        "    1.35, mid_y_theo, f\"ΔE: {E_reaction_theo:.4f}\", color=\"g\", verticalalignment=\"top\"\n",
        ")\n",
        "\n",
        "ax.annotate(\n",
        "    \"\",\n",
        "    xy=(1.5, E_H2_exp),\n",
        "    xytext=(1.5, 2 * E_H_exp),\n",
        "    arrowprops=dict(arrowstyle=\"<->\", color=\"g\", linestyle=\"dashed\"),\n",
        ")\n",
        "ax.text(\n",
        "    1.55,\n",
        "    mid_y_exp,\n",
        "    f\"ΔE_VQE: {E_reaction_exp:.4f}\",\n",
        "    color=\"g\",\n",
        "    verticalalignment=\"center\",\n",
        ")\n",
        "\n",
        "# Add legend\n",
        "ax.legend()\n",
        "\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f1a87af7-0c69-42c5-a415-87c95752d678",
      "metadata": {},
      "source": [
        "Como se muestra en la figura, aunque hay algunos errores, la energía exacta del estado básico de los Hamiltonianos y la energía de reacción calculada utilizando los resultados VQE son similares, cercanas a -0.2 Hartree.\n",
        "\n",
        "Cabe señalar aquí que la energía de reacción de este proceso tiene un valor negativo, lo que significa que la energía se libera a través del proceso, y la molécula resultante tiene una energía inferior a la de dos átomos individuales.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "006ded34-29e0-4702-a46a-0459ee49f416",
      "metadata": {},
      "source": [
        "6. Conclusión\n",
        "\n",
        "Resumamos lo que hemos aprendido hasta ahora.\n",
        "\n",
        "En primer lugar, analizamos dos importantes técnicas de aproximación necesarias para resolver problemas de química cuántica: el principio variacional y la elección de conjuntos de bases, ambos fundamentales para la VQE. Exploramos el principio variacional a mano, calculando la energía del estado básico del oscilador armónico simple.\n",
        "\n",
        "A continuación, exploramos el VQE, un algoritmo muy utilizado para calcular la energía de estado básico de un sistema cuántico. Ejecutamos un código para calcular las energías del estado fundamental del hidrógeno atómico ( $H$ ) y de la molécula de hidrógeno ( $H_2$ ). En particular, aprendimos que es necesario obtener el Hamiltoniano molecular apropiado para el sistema y transformarlo en una forma ejecutable en un ordenador cuántico. También vimos que el ansatz, un circuito cuántico parametrizado, es necesario para preparar estados cuánticos de prueba dentro de VQE, y discutimos la importancia de elegir una estructura de circuito ansatz apropiada. También aprendimos que la VQE se basa en un proceso de optimización iterativo que utiliza un ordenador clásico, guiando el circuito cuántico para encontrar el estado de energía más bajo, y vimos cómo converge el proceso.\n",
        "\n",
        "Por último, utilizamos las energías de estado básico calculadas de $H$ y $H_2$ obtenidas mediante VQE para calcular la energía de reacción para el proceso $H + H \\rightarrow H_2$.\n",
        "\n",
        "El VQE es un potente algoritmo cuántico a corto plazo, pero es importante conocer sus limitaciones. El rendimiento de la VQE depende en gran medida de la elección del ansatz: encontrar un ansatz que se pueda preparar eficazmente y que pueda representar con precisión el verdadero estado básico se convierte en un reto para las moléculas más grandes y complejas. Además, el hardware cuántico actual es susceptible al ruido, lo que puede afectar a la precisión de los resultados de VQE, sobre todo en circuitos más profundos o con mayor número de qubits. A pesar de estas dificultades, el VQE es un algoritmo básico y la investigación en curso explora métodos variacionales más sofisticados y técnicas de mitigación de errores para ampliar las posibilidades de la química cuántica en los ordenadores cuánticos a corto plazo. Por ejemplo, se están desarrollando algoritmos como la Diagonalización Cuántica Basada en Muestras (SQD), que aprovecha las muestras obtenidas de los circuitos cuánticos combinadas con la diagonalización clásica en un subespacio para mejorar la estimación de la energía y abordar algunas de las limitaciones a las que se enfrenta la VQE, sobre todo en lo que respecta a la eficiencia de las mediciones y la robustez frente al ruido.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "03c9dfc1-7367-4d71-9598-f02288c30ac0",
      "metadata": {},
      "source": [
        "<span id=\"review-and-questions\" />\n",
        "\n",
        "## Revisión y preguntas\n",
        "\n",
        "<span id=\"critical-concepts\" />\n",
        "\n",
        "### Conceptos fundamentales:\n",
        "\n",
        "* El algoritmo cuántico variacional es un paradigma informático en el que un ordenador clásico y otro cuántico trabajan juntos para resolver un problema.\n",
        "* En VQE, partimos de un Hamiltoniano de nuestro sistema y lo mapeamos en qubits para su ejecución en el ordenador cuántico. Seleccionamos un circuito cuántico parametrizado, un ansatz, y realizamos mediciones repetidas, variando los parámetros del ansatz, hasta alcanzar el valor de energía más bajo. La búsqueda a través del espacio de parámetros se realiza mediante un optimizador clásico. Para obtener buenos resultados, es necesario seleccionar un buen ansatz y un optimizador adecuado.\n",
        "* La energía de reacción es el cambio total de energía en una reacción química, determinado por la diferencia entre la energía de los reactivos y la de los productos.\n",
        "\n",
        "<span id=\"true/false\" />\n",
        "\n",
        "### True/False\n",
        "\n",
        "1. El principio variacional establece que el valor esperado de la energía para cualquier función de onda de prueba es siempre mayor o igual que la verdadera energía del estado fundamental.\n",
        "2. Un conjunto de bases es una colección de funciones utilizadas para aproximar funciones de onda cuánticas.\n",
        "3. VQE es un algoritmo cuántico utilizado para resolver exactamente la ecuación de Schrödinger para un Hamiltoniano dado.\n",
        "4. En VQE, se utiliza un circuito cuántico parametrizado (un ansatz) para preparar funciones de onda de prueba.\n",
        "5. La elección del optimizador en VQE (por ejemplo, COBYLA, SPSA o ADAM) no influye en la calidad del resultado.\n",
        "6. Qiskit's `Estimator` se utiliza para calcular directamente los valores de expectativa de los hamiltonianos en VQE.\n",
        "\n",
        "<span id=\"multiple-choice-questions\" />\n",
        "\n",
        "### Preguntas de opción múltiple:\n",
        "\n",
        "1. ¿Para qué sirve el Hamiltoniano en VQE?\n",
        "\n",
        "* A) Generar estados cuánticos aleatorios\n",
        "* B) Determinar la energía de los estados cuánticos\n",
        "* C) Optimizar los circuitos cuánticos\n",
        "* D) Para crear enredo\n",
        "\n",
        "2. ¿Cuál es el objetivo principal del algoritmo VQE?\n",
        "\n",
        "* A) Para hallar la energía del estado fundamental de un Hamiltoniano\n",
        "* B) Crear entrelazamiento entre qubits\n",
        "* C) Realizar la búsqueda de Grover\n",
        "* D) Para romper el cifrado RSA\n",
        "\n",
        "3. ¿Cuántos estados cuánticos se generan en este cuaderno para comparar el ansatz?\n",
        "\n",
        "* A) 100\n",
        "* B) 1000\n",
        "* C) 5000\n",
        "* D) 10,000\n",
        "\n",
        "4. ¿Por qué es necesario un optimizador clásico en VQE?\n",
        "\n",
        "* A) Realizar mediciones cuánticas\n",
        "* B) Actualizar los parámetros del ansatz para minimizar la energía\n",
        "* C) Para entrelazar qubits\n",
        "* D) Para generar aleatoriedad cuántica\n",
        "\n",
        "5. ¿Por qué el ansatz está diseñado para ser parametrizado?\n",
        "\n",
        "* A) Permitir la preparación del estado cuántico\n",
        "* B) Permitir la búsqueda de un amplio espacio de estados cuánticos\n",
        "* C) Reducir la complejidad de los circuitos\n",
        "* D) Medir directamente los valores propios\n",
        "\n",
        "6. ¿Cuál de las siguientes es la afirmación más correcta sobre la elección de una buena ansatz?\n",
        "\n",
        "* A) Un ansatz debe producir estados distribuidos uniformemente sobre la esfera de Bloch, o fallará.\n",
        "* B) Se debe adaptar un ansatz a su sistema para asegurarse de que puede generar estados cercanos al estado fundamental.\n",
        "* C) Un ansatz debe producir estados aleatorios utilizando sus parámetros variacionales.\n",
        "* D) Un ansatz mejor siempre tiene más parámetros variacionales.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7f44807a-45f6-4e36-9d19-faa77f7c0c79",
      "metadata": {},
      "source": [
        "<span id=\"optional-appendix-optimizer-overhead-by-ansatz-complexity\" />\n",
        "\n",
        "## (Opcional) Apéndice: Sobrecarga del optimizador por complejidad del ansatz\n",
        "\n",
        "VQE se enfrenta a varios retos bien conocidosref \\[6], y los siguientes están relacionados con lo que hemos aprendido anteriormente.\n",
        "\n",
        "1. Retos de la selección del Ansatz\n",
        "\n",
        "Seleccionar el ansatz variacional adecuado es un reto inherente. Las soluciones inspiradas en la química (como UCCSD) proporcionan precisión física pero requieren circuitos profundos, mientras que las soluciones eficientes desde el punto de vista del hardware tienen circuitos menos profundos pero pueden carecer de interpretabilidad física. Además, muchas ansätze introducen parámetros variacionales excesivos que contribuyen poco a mejorar la precisión pero aumentan significativamente la dificultad de la optimización.\n",
        "\n",
        "2. Dificultades de optimización\n",
        "\n",
        "El paisaje de optimización de VQE puede tener regiones en las que los gradientes se desvanecen exponencialmente (mesetas estériles), lo que dificulta a los optimizadores clásicos la actualización eficiente de los parámetros variacionales. Para ello, los investigadores han intentado utilizar distintos tipos de optimizadores: basados en gradientes y sin gradientes, pero ambos se enfrentan a retos. Los optimizadores basados en gradientes sufren de mesetas estériles, mientras que los métodos sin gradiente requieren un gran número de evaluaciones de funciones.\n",
        "\n",
        "3. Sobrecarga del optimizador\n",
        "\n",
        "Otro reto bien conocido es la sobrecarga del optimizador, que está relacionada con la escala del problema. Los circuitos cuánticos necesarios para el VQE crecen en profundidad y complejidad a medida que aumenta el tamaño del problema; esto suele aumentar también el número de parámetros a optimizar. El proceso de optimización se vuelve intratable a medida que aumenta el número de parámetros, lo que provoca una convergencia lenta y dificultades para encontrar la solución óptima.\n",
        "\n",
        "Aquí vamos a echar un vistazo a estos retos mediante el uso de VQE para una molécula $H_2$, con dos tipos diferentes de ansätze.\n",
        "\n",
        "(Nota: Esto puede tomar más tiempo QPU, así que siéntase libre de utilizar un simulador para esto si usted no tiene suficiente tiempo)\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 54,
      "id": "96179a4e-33f9-43ff-a791-f5ac8114dd29",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit.circuit import ParameterVector\n",
        "\n",
        "num_iter = 4\n",
        "alpha = ParameterVector(\"alpha\", 3)\n",
        "beta = ParameterVector(\"beta\", 3 * num_iter)\n",
        "\n",
        "# step1: Map problem to quantum circuits and operators\n",
        "hamiltonian = SparsePauliOp.from_list(\n",
        "    [(\"I\", -1.04886087), (\"Z\", -0.7967368), (\"X\", 0.18121804)]\n",
        ")\n",
        "\n",
        "ansatz_1 = ansatz3\n",
        "ansatz_2 = QuantumCircuit(1)\n",
        "for i in range(num_iter):\n",
        "    ansatz_2.rx(beta[i * 3 + 0], 0)\n",
        "    ansatz_2.rz(beta[i * 3 + 1], 0)\n",
        "    ansatz_2.rx(beta[i * 3 + 2], 0)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 55,
      "id": "0eeb0370-0ec7-490e-962b-55f746c4e4a3",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/vqe/extracted-outputs/faf0cf47-e48d-44aa-8119-af56c2ba94df-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 55,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "ansatz_1.draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 56,
      "id": "105b8557-861c-4fcc-a84e-82d382ad730f",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/vqe/extracted-outputs/0edacb57-b03d-4850-8698-9b8d9f1338c4-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 56,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "ansatz_2.draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 57,
      "id": "2f3f5e3f-d41b-45af-a14f-d2f1ef506037",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Step 2: Optimize for target hardware\n",
        "\n",
        "target = backend.target\n",
        "pm = generate_preset_pass_manager(target=target, optimization_level=3)\n",
        "\n",
        "ansatz_isa_1 = pm.run(ansatz_1)\n",
        "ansatz_isa_2 = pm.run(ansatz_2)\n",
        "hamiltonian_isa_1 = hamiltonian.apply_layout(layout=ansatz_isa_1.layout)\n",
        "hamiltonian_isa_2 = hamiltonian.apply_layout(layout=ansatz_isa_2.layout)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1433eb47-1fcc-4e2e-8738-2d2818be8824",
      "metadata": {},
      "source": [
        "Ahora ejecutemos un VQE con un punto inicial formado por todos unos, con un máximo de 20 pasos, y comparemos la convergencia de ambas ejecuciones.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 60,
      "id": "693444f0-5835-471f-b9f7-20b5158b3d45",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Iters. done: 1 [Current cost: -0.8782202668652658]\n",
            "Iters. done: 2 [Current cost: -0.43473160695469165]\n",
            "Iters. done: 3 [Current cost: -0.4076372093159749]\n",
            "Iters. done: 4 [Current cost: -1.3587839859772106]\n",
            "Iters. done: 5 [Current cost: -1.774529906754082]\n",
            "Iters. done: 6 [Current cost: -1.541934983115727]\n",
            "Iters. done: 7 [Current cost: -1.2732403113465345]\n",
            "Iters. done: 8 [Current cost: -1.820842221085785]\n",
            "Iters. done: 9 [Current cost: -1.8065762857059005]\n",
            "Iters. done: 10 [Current cost: -1.8126394095981146]\n",
            "Iters. done: 11 [Current cost: -1.8205831886180421]\n",
            "Iters. done: 12 [Current cost: -1.8086715778994924]\n",
            "Iters. done: 13 [Current cost: -1.8307676638629322]\n",
            "Iters. done: 14 [Current cost: -1.8177328827556327]\n",
            "Iters. done: 15 [Current cost: -1.8179426218088064]\n",
            "Iters. done: 16 [Current cost: -1.8109239667991088]\n",
            "Iters. done: 17 [Current cost: -1.824271872489647]\n",
            "Iters. done: 18 [Current cost: -1.813167587671394]\n",
            "Iters. done: 19 [Current cost: -1.824647343397313]\n",
            "Iters. done: 20 [Current cost: -1.8219785311686143]\n"
          ]
        }
      ],
      "source": [
        "# QPU time 3m 40s for ibm_brisbane\n",
        "# Step 3: Execute on target hardware\n",
        "\n",
        "from scipy.optimize import minimize\n",
        "\n",
        "x0 = np.ones(ansatz_1.num_parameters)\n",
        "\n",
        "batch = Batch(backend=backend)\n",
        "\n",
        "\n",
        "cost_history_dict = {\n",
        "    \"prev_vector\": None,\n",
        "    \"iters\": 0,\n",
        "    \"cost_history\": [],\n",
        "}\n",
        "estimator = Estimator(mode=batch)\n",
        "estimator.options.default_shots = 2048\n",
        "\n",
        "res = minimize(\n",
        "    cost_func,\n",
        "    x0,\n",
        "    args=(ansatz_isa_1, hamiltonian_isa_1, estimator),\n",
        "    method=\"cobyla\",\n",
        "    options={\"maxiter\": 20},\n",
        ")\n",
        "\n",
        "batch.close()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 61,
      "id": "6922c55c-6a4f-41d8-8aad-69fa432ddf14",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Save Cost_history as a new list\n",
        "ansatz_1_history = cost_history_dict[\"cost_history\"]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 62,
      "id": "5ba0affa-8641-4a15-9408-a99dff0c7f8c",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Iters. done: 1 [Current cost: -0.738191173881188]\n",
            "Iters. done: 2 [Current cost: -0.42636037194506304]\n",
            "Iters. done: 3 [Current cost: -1.3503788613797374]\n",
            "Iters. done: 4 [Current cost: -0.9109204349776897]\n",
            "Iters. done: 5 [Current cost: -0.9060873157510835]\n",
            "Iters. done: 6 [Current cost: -0.7735065414083984]\n",
            "Iters. done: 7 [Current cost: -1.586889197437709]\n",
            "Iters. done: 8 [Current cost: -1.659215191584943]\n",
            "Iters. done: 9 [Current cost: -1.245445981794618]\n",
            "Iters. done: 10 [Current cost: -1.1608385766138023]\n",
            "Iters. done: 11 [Current cost: -1.1551733876027737]\n",
            "Iters. done: 12 [Current cost: -1.8143337768286332]\n",
            "Iters. done: 13 [Current cost: -1.2510951563756598]\n",
            "Iters. done: 14 [Current cost: -1.6918311531865413]\n",
            "Iters. done: 15 [Current cost: -1.8163783305531838]\n",
            "Iters. done: 16 [Current cost: -1.8434877732947152]\n",
            "Iters. done: 17 [Current cost: -1.8461898233304472]\n",
            "Iters. done: 18 [Current cost: -1.0346471214915485]\n",
            "Iters. done: 19 [Current cost: -1.8322518854150687]\n",
            "Iters. done: 20 [Current cost: -1.717144678705999]\n"
          ]
        }
      ],
      "source": [
        "# QPU time 3m 40s for ibm_brisbane\n",
        "\n",
        "x0 = np.ones(ansatz_2.num_parameters)\n",
        "\n",
        "batch = Batch(backend=backend)\n",
        "\n",
        "\n",
        "cost_history_dict = {\n",
        "    \"prev_vector\": None,\n",
        "    \"iters\": 0,\n",
        "    \"cost_history\": [],\n",
        "}\n",
        "estimator = Estimator(mode=batch)\n",
        "estimator.options.default_shots = 2048\n",
        "\n",
        "res = minimize(\n",
        "    cost_func,\n",
        "    x0,\n",
        "    args=(ansatz_isa_2, hamiltonian_isa_2, estimator),\n",
        "    method=\"cobyla\",\n",
        "    options={\"maxiter\": 20},\n",
        ")\n",
        "\n",
        "batch.close()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 63,
      "id": "7354f299-cab6-4d43-a0e3-38c6c19f0486",
      "metadata": {},
      "outputs": [],
      "source": [
        "ansatz_2_history = cost_history_dict[\"cost_history\"]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 64,
      "id": "2c6d2eb4-197b-4811-bb0b-377e445ff3a8",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/vqe/extracted-outputs/fb572c33-f3be-4ea8-959c-0891c9da947a-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "fig, ax = plt.subplots()\n",
        "\n",
        "# Define the constant function)\n",
        "ax.plot(\n",
        "    range(cost_history_dict[\"iters\"]),\n",
        "    ansatz_1_history,\n",
        "    label=\"Ansatz with 3 parameters\",\n",
        ")\n",
        "ax.plot(\n",
        "    range(cost_history_dict[\"iters\"]),\n",
        "    ansatz_2_history,\n",
        "    label=\"Ansatz with 12 parameters\",\n",
        ")\n",
        "ax.set_xlabel(\"Iterations\")\n",
        "ax.set_ylabel(\"Cost (Hartree)\")\n",
        "plt.legend()\n",
        "plt.draw()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b6a9fcdb-020f-429a-8b6e-17d67b0caaf9",
      "metadata": {},
      "source": [
        "El gráfico anterior demuestra claramente que el proceso de optimización del ansatz con más variables tarda más tiempo en llegar a una convergencia estable.\n",
        "\n",
        "En lugar de basarse en circuitos simples de un solo qubit y un ansatz sencillo, la complejidad de la optimización aumenta cuando se requieren circuitos cuánticos más grandes y ansätze estructurados más complejos. Esto pone de relieve un reto bien conocido en los VQE: la sobrecarga del optimizador.\n",
        "\n",
        "Los investigadores continúan desarrollando diversas metodologías avanzadas que pueden utilizar ordenadores cuánticos para resolver problemas químicos. Puede acceder a una gran variedad de materiales educativos en [IBM Quantum Learning](/learning).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f7c6d9a5-c2cc-4674-9d54-1f9809cfde51",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## Referencias\n",
        "\n",
        "* \\[ [ref 1](https://link.springer.com/article/10.1007/BF02650179) ] Richard P. Feynman, Simulating Physics with Computers, International Journal of Theoretical Physics, 1982.\n",
        "* [\\[ref 2\\]](https://link.springer.com/chapter/10.1007/978-1-4614-8730-2_10) Marov, M.Y. (2015). La estructura del Universo. En: Fundamentos de la Astrofísica Moderna. Springer, Nueva York, NY.\n",
        "* \\[ [ref 3](https://www.ibm.com/quantum/blog/photoresists-quantum-chemistry-jsr) ] How to solve difficult chemical engineering problems with quantum computing, IBM Research Blog, 2023.\n",
        "* \\[[ref 4](https://ieeexplore.ieee.org/document/8585034) ] Y. Cao, J. Romero y A. Aspuru-Guzik, \"Potential of quantum computing for drug discovery\", en IBM Journal of Research and Development, vol. 62, nº 6, pp. 6:1-6:20, 1 Nov.-Dec. 2018\n",
        "* \\[ [ref 5](https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.32.170) ] Estado actual del cálculo de estructuras moleculares, REv. Mod. Física. 32, 170, 1960\n",
        "* \\[[ref 6](https://jmsh.springeropen.com/articles/10.1186/s41313-021-00032-6) ] Fedorov, D.A., Peng, B., Govind, N. et al. Método VQE: breve estudio y evolución reciente. Teoría Mater 6, 2 (2022)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "in_page_toc_max_heading_level": 2,
    "in_page_toc_min_heading_level": 2,
    "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"
    },
    "widgets": {
      "application/vnd.jupyter.widget-state+json": {
        "state": {},
        "version_major": 2,
        "version_minor": 0
      }
    }
  },
  "nbformat": 4,
  "nbformat_minor": 4
}