{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "eb419bc5-908c-4f0d-a4ff-c4d13f04e332",
      "metadata": {
        "tags": []
      },
      "source": [
        "---\n",
        "title: \"Estimación de la energía del estado fundamental de la cadena de Heisenberg con VQE\"\n",
        "description: \"Crea, implementa y ejecuta un patrón de Qiskit para simular una cadena de Heisenberg y calcular su energía en el estado fundamental.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore hyperparameters, forall, nabla, nparams */}\n",
        "\n",
        "<span id=\"ground-state-energy-estimation-of-the-heisenberg-chain-with-vqe\" />\n",
        "\n",
        "# Estimación de la energía del estado fundamental de la cadena de Heisenberg con VQE\n",
        "\n",
        "*Tiempo estimado de ejecución: 37 minutos en un procesador Heron (NOTA: Se trata únicamente de una estimación). (El tiempo de ejecución puede variar.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e28ad544",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Resultados del aprendizaje\n",
        "\n",
        "Una vez completado este tutorial, habrás adquirido los siguientes conocimientos:\n",
        "\n",
        "* Cómo modelar una cadena de espín de Heisenberg como un hamiltoniano cuántico utilizando Qiskit\n",
        "* Cómo utilizar el optimizador SPSA para calcular la energía del estado fundamental de un sistema cuántico\n",
        "* Cómo ejecutar flujos de trabajo variacionales en el hardware cuántico de IBM® utilizando primitivas y sesiones de Qiskit Runtime\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Requisitos previos\n",
        "\n",
        "Se recomienda que te familiarices con estos temas:\n",
        "\n",
        "* [Fundamentos de la información cuántica](/learning/courses/basics-of-quantum-information)\n",
        "* [Introducción a los patrones de Qiskit](/docs/guides/intro-to-patterns)\n",
        "* [Diseño de algoritmos variacionales](/learning/courses/variational-algorithm-design)\n",
        "\n",
        "<span id=\"background\" />\n",
        "\n",
        "## En segundo plano\n",
        "\n",
        "La cadena de espín de Heisenberg es uno de los modelos más estudiados en la física de la materia condensada y el magnetismo cuántico. Describe una red unidimensional de espines cuánticos que interactúan entre sí, en la que los espines más cercanos están acoplados mediante interacciones de intercambio. El hamiltoniano del modelo de Heisenberg isotrópico con un campo magnético externo viene dado por:\n",
        "\n",
        "$H = \\sum_{\\langle i,j \\rangle} \\left( J_x X_i X_j + J_y Y_i Y_j + J_z Z_i Z_j \\right) + \\sum_{i} h_i Z_i,$\n",
        "\n",
        "donde $X_i$, $Y_i$ y $Z_i$ son los operadores de Pauli que actúan en el sitio $i$, la suma $\\langle i,j \\rangle$ se realiza sobre los pares de vecinos más cercanos, $J_x = J_y = J_z = 0.5$ son las constantes de acoplamiento de intercambio (isotrópicas en este tutorial) y $h_i$ representa un campo magnético externo dependiente del sitio. En este tutorial, los valores del campo magnético se obtienen mediante muestreo aleatorio del intervalo $[-1, 1]$. Cabe señalar que, en la implementación que se muestra a continuación, el conjunto de pares de «vecinos más cercanos» viene determinado por el acoplamiento nativo del backend de hardware entre los primeros $N$ qubits, lo que puede que no forme una cadena lineal estricta dependiendo de la topología del dispositivo.\n",
        "\n",
        "Comprender la energía del estado fundamental de este hamiltoniano reviste una importancia fundamental en física. El estado fundamental contiene información sobre las transiciones de fase cuánticas, la estructura del entrelazamiento y el orden magnético. Por lo general, calcular la energía exacta del estado fundamental resulta inabarcable a medida que aumenta el número de espines, ya que la dimensión del espacio de Hilbert crece exponencialmente como $2^N$ para $N$ espines. Esto lo convierte en un candidato ideal para la simulación cuántica.\n",
        "\n",
        "El Variational Quantum Eigensolver (VQE) es un algoritmo híbrido cuántico-clásico diseñado para estimar la energía del estado fundamental de un hamiltoniano. El método consiste en preparar un estado cuántico parametrizado $|\\psi(\\theta)\\rangle$ (denominado «ansatz») en un ordenador cuántico y medir el valor esperado $\\langle \\psi(\\theta) | H | \\psi(\\theta) \\rangle$. A continuación, un optimizador clásico ajusta de forma iterativa los parámetros $\\theta$ para minimizar esta energía, aprovechando el principio variacional, que garantiza que la energía medida sea siempre un límite superior de la energía real del estado fundamental.\n",
        "\n",
        "En este tutorial utilizamos el `efficient_su2` enfoque de la biblioteca de circuitos de Qiskit, que construye capas de rotaciones de un solo qubit y puertas de entrelazamiento. La optimización se lleva a cabo mediante el algoritmo de aproximación estocástica por perturbación simultánea (SPSA), que resulta muy adecuado para hardware cuántico con ruido, ya que estima los gradientes utilizando solo dos evaluaciones de la función por iteración, independientemente del número de parámetros.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bc52f763",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisitos\n",
        "\n",
        "Antes de empezar este tutorial, asegúrate de que tienes instalado lo siguiente:\n",
        "\n",
        "* Qiskit SDK v2.0 o posterior, con soporte [para visualización](/docs/api/qiskit/visualization)\n",
        "* Qiskit Runtime v0.44 o posterior (`pip install qiskit-ibm-runtime`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a46e9e3e",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configuración\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "e7754922",
      "metadata": {},
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "import matplotlib.pyplot as plt\n",
        "from typing import Sequence\n",
        "\n",
        "from qiskit import QuantumCircuit\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "from qiskit.primitives import BaseEstimatorV2\n",
        "from qiskit.circuit.library import XGate\n",
        "from qiskit.circuit.library import efficient_su2\n",
        "from qiskit.transpiler import PassManager\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "from qiskit.transpiler.passes.scheduling import (\n",
        "    ALAPScheduleAnalysis,\n",
        "    PadDynamicalDecoupling,\n",
        ")\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService, Session, EstimatorV2\n",
        "\n",
        "\n",
        "def visualize_results(results):\n",
        "    plt.plot(results[\"cost_history\"], lw=2)\n",
        "    plt.xlabel(\"Number of function evaluations\")\n",
        "    plt.ylabel(\"Energy\")\n",
        "    plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "890d9c81",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-example\" />\n",
        "\n",
        "## Ejemplo a pequeña escala\n",
        "\n",
        "En esta sección, repasamos cada paso del patrón de Qiskit a pequeña escala, explicando los componentes clave a medida que vamos construyendo el flujo de trabajo.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "132fb15f-10b4-4d7e-83d8-f512a6f675d1",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### Paso 1: Asignar entradas clásicas a un problema cuántico\n",
        "\n",
        "* Entrada: Número de giros\n",
        "* Salida: Ansatz y Hamiltoniano modelando la cadena de Heisenberg\n",
        "\n",
        "Elabora un enfoque y un hamiltoniano que modelen una cadena de Heisenberg de 10 espines. En este paso, construiremos un hamiltoniano de Heisenberg de 10 espines sobre el mapa de acoplamiento del backend menos ocupado y prepararemos el `efficient_su2` ansatz.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "7e8d2f10-f1d6-4ec2-bac9-9db23499c9e1",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spin-chain-vqe/extracted-outputs/7e8d2f10-f1d6-4ec2-bac9-9db23499c9e1-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 2,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "num_spins = 10\n",
        "ansatz = efficient_su2(num_qubits=num_spins, reps=2)\n",
        "\n",
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(\n",
        "    operational=True, min_num_qubits=num_spins, simulator=False\n",
        ")\n",
        "\n",
        "coupling = backend.target.build_coupling_map()\n",
        "reduced_coupling = coupling.reduce(list(range(num_spins)))\n",
        "\n",
        "edge_list = reduced_coupling.graph.edge_list()\n",
        "ham_list = []\n",
        "\n",
        "for edge in edge_list:\n",
        "    ham_list.append((\"ZZ\", edge, 0.5))\n",
        "    ham_list.append((\"YY\", edge, 0.5))\n",
        "    ham_list.append((\"XX\", edge, 0.5))\n",
        "\n",
        "for qubit in reduced_coupling.physical_qubits:\n",
        "    ham_list.append((\"Z\", [qubit], np.random.random() * 2 - 1))\n",
        "\n",
        "hamiltonian = SparsePauliOp.from_sparse_list(ham_list, num_qubits=num_spins)\n",
        "\n",
        "ansatz.draw(\"mpl\", style=\"iqp\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ab79119b-5e56-49d8-a20e-1c8e665baec0",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### Paso 2: Optimizar el problema para la ejecución en hardware cuántico\n",
        "\n",
        "* Entrada: Circuito abstracto, observable\n",
        "* Salida: Circuito objetivo y observable, optimizado para la QPU seleccionada\n",
        "\n",
        "Utiliza la función `generate_preset_pass_manager` de Qiskit para generar automáticamente una rutina de optimización para nuestro circuito con respecto a la QPU seleccionada. Elegimos `optimization_level=3`, que proporciona el mayor nivel de optimización de los gestores de pases preestablecidos. También incluimos `ALAPScheduleAnalysis` y `PadDynamicalDecoupling` pases de programación para suprimir los errores de decoherencia.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "a0a5f1c8-5c31-4d9f-ae81-37bd67271d44",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spin-chain-vqe/extracted-outputs/a0a5f1c8-5c31-4d9f-ae81-37bd67271d44-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 3,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "target = backend.target\n",
        "pm = generate_preset_pass_manager(optimization_level=3, target=target)\n",
        "pm.scheduling = PassManager(\n",
        "    [\n",
        "        ALAPScheduleAnalysis(durations=target.durations()),\n",
        "        PadDynamicalDecoupling(\n",
        "            durations=target.durations(),\n",
        "            dd_sequence=[XGate(), XGate()],\n",
        "            pulse_alignment=target.pulse_alignment,\n",
        "        ),\n",
        "    ]\n",
        ")\n",
        "isa_ansatz = pm.run(ansatz)\n",
        "isa_observable = hamiltonian.apply_layout(isa_ansatz.layout)\n",
        "isa_ansatz.draw(\"mpl\", scale=0.6, style=\"iqp\", fold=-1, idle_wires=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9e889d0b-30b5-4e6b-84c9-d1f096abf132",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "### Paso 3: Ejecutar utilizando Qiskit primitives\n",
        "\n",
        "* Entrada: Circuito objetivo y observable\n",
        "* Resultados: Resultados de la optimización\n",
        "\n",
        "Minimice la energía estimada del estado fundamental del sistema optimizando los parámetros del circuito. Utiliza la `Estimator` función primitiva de Qiskit Runtime para calcular la función de coste durante la optimización.\n",
        "\n",
        "Dado que en el paso 2 optimizamos el circuito para el backend, podemos evitar la transpilación en el servidor de tiempo de ejecución configurando `skip_transpilation=True` y pasando el circuito optimizado. Para esta demostración, la ejecutaremos en una QPU utilizando `qiskit-ibm-runtime` primitivas. Para ejecutar el código con primitivas `qiskit` basadas en vectores de estado, sustituye el bloque de código que utiliza primitivas de tipo « Qiskit Runtime » por el bloque comentado.\n",
        "\n",
        "En este tutorial utilizamos la aproximación estocástica por perturbación simultánea (SPSA), que es un optimizador basado en el gradiente. A continuación ofrecemos una breve introducción al tema y proporcionamos el código para implementar el SPSA utilizando Qiskit v2.0.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "64f97e6f",
      "metadata": {},
      "source": [
        "<span id=\"introducing-spsa\" />\n",
        "\n",
        "### Presentamos SPSA\n",
        "\n",
        "La aproximación estocástica por perturbación simultánea (SPSA) [\\[1\\]](#references) es un algoritmo de optimización que aproxima el vector gradiente completo utilizando solo dos llamadas a la función en cada iteración. Sea $f:\\mathbb{R}^p\\rightarrow \\mathbb{R}$ la función de coste, con $p$ los parámetros que se van a optimizar, y $x_i\\in \\mathbb{R}^p$ el vector de parámetros en el paso $i^{th}$ de la iteración. Para calcular el gradiente, se crea un vector aleatorio $\\Delta_i$ de tamaño $p$, en el que cada elemento $\\Delta_{ij}$, $\\forall$ $j\\in \\{1,2,...,p\\}$ se obtiene mediante muestreo uniforme de $\\{-1, 1\\}$. A continuación, cada elemento del vector aleatorio $\\Delta_i$ se multiplica por un valor pequeño $c_i$ para generar una perturbación aleatoria. A continuación, el gradiente se calcula como\n",
        "\n",
        "$[\\nabla f(x_i)]_j \\approx \\frac{f(x_i + c_i \\Delta_i) - f(x_i - c_i \\Delta_i)}{2c_i\\Delta_{ij}}.$\n",
        "\n",
        "Intuitivamente, dado que durante la estimación del gradiente se aplica una perturbación aleatoria, cabe esperar que se puedan tolerar y tener en cuenta las pequeñas desviaciones en los valores exactos de l $f$, debidas al ruido. De hecho, el SPSA destaca especialmente por su resistencia al ruido y solo requiere dos llamadas de hardware por cada iteración. Por lo tanto, es uno de los optimizadores más utilizados para implementar algoritmos variacionales.\n",
        "\n",
        "En este tutorial, los hiperparámetros para la iteración « $i^{th}$ », « $a_i$ » y « $c_i$ », se calculan como\n",
        "\n",
        "$a_i = \\frac{a}{(A + i + 1)^\\alpha} \\quad \\text{and} \\quad c_i = \\frac{c}{(i+1)^\\gamma},$\n",
        "\n",
        "donde los valores constantes se toman como $A = 30$, $\\alpha = 0.9$, $a = 0.3$, $c = 0.1$ y $\\gamma = 0.4$. Estos valores se han seleccionado de [\\[2\\]](#references). Es necesario ajustar adecuadamente los hiperparámetros para obtener un buen rendimiento del SPSA.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "73a9352c",
      "metadata": {},
      "outputs": [],
      "source": [
        "def spsa(\n",
        "    fun, x0, args=(), A=30, alpha=0.9, a=0.3, c=0.1, gamma=0.4, maxiter=100\n",
        "):\n",
        "    nparams = len(x0)\n",
        "    x = np.copy(x0)\n",
        "\n",
        "    for i in range(maxiter):\n",
        "        a_i = a / (A + i + 1) ** alpha\n",
        "        c_i = c / (i + 1) ** gamma\n",
        "        delta_i = np.random.choice([-1, 1], nparams)\n",
        "\n",
        "        # two hardware calls\n",
        "        eval_1 = fun(x + c_i * delta_i, *args)\n",
        "        eval_2 = fun(x - c_i * delta_i, *args)\n",
        "\n",
        "        # compute the gradient and update the parameters\n",
        "        grad = (eval_1 - eval_2) / (2 * c_i) * np.reciprocal(delta_i)\n",
        "        x = x - a_i * grad\n",
        "\n",
        "    return x"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "32ca3b6a",
      "metadata": {},
      "outputs": [],
      "source": [
        "def cost_func(\n",
        "    params: Sequence,\n",
        "    ansatz: QuantumCircuit,\n",
        "    hamiltonian: SparsePauliOp,\n",
        "    estimator: BaseEstimatorV2,\n",
        "    cost_history_dict: dict,\n",
        ") -> float:\n",
        "    \"\"\"Ground state energy evaluation.\"\"\"\n",
        "    energy = (\n",
        "        estimator.run([(ansatz, hamiltonian, [params])]).result()[0].data.evs\n",
        "    )\n",
        "\n",
        "    cost_history_dict[\"iters\"] += 1\n",
        "    cost_history_dict[\"prev_vector\"] = list(params)\n",
        "    cost_history_dict[\"cost_history\"].append(float(energy[0]))\n",
        "\n",
        "    print(\n",
        "        f\"Fx Iters. done: {cost_history_dict['iters']} [Current cost: {round(energy[0], 5)}]\",\n",
        "        end=\"\\r\",\n",
        "    )\n",
        "\n",
        "    return energy\n",
        "\n",
        "\n",
        "def solve(x0, isa_ansatz, isa_observable, maxiter=150):\n",
        "    cost_history_dict = {\n",
        "        \"prev_vector\": None,\n",
        "        \"iters\": 0,\n",
        "        \"cost_history\": [],\n",
        "        \"y_min\": None,\n",
        "    }\n",
        "\n",
        "    # Evaluate the problem using a QPU via Qiskit IBM Runtime\n",
        "    with Session(backend=backend) as session:\n",
        "        estimator = EstimatorV2(mode=session)\n",
        "        estimator.skip_transpilation = True\n",
        "        estimator.options.environment.job_tags = [\"TUT_HSVQE\"]\n",
        "        x_opt = spsa(\n",
        "            cost_func,\n",
        "            x0=x0,\n",
        "            args=(isa_ansatz, isa_observable, estimator, cost_history_dict),\n",
        "            maxiter=maxiter,\n",
        "        )\n",
        "\n",
        "        y_min = cost_func(\n",
        "            x_opt, isa_ansatz, isa_observable, estimator, cost_history_dict\n",
        "        )\n",
        "\n",
        "    return y_min, cost_history_dict"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "f418b372",
      "metadata": {},
      "outputs": [],
      "source": [
        "np.random.seed(42)\n",
        "num_params = ansatz.num_parameters\n",
        "params = 2 * np.pi * np.random.random(num_params)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3bf42923",
      "metadata": {},
      "source": [
        "Aquí configuramos el `maxiter = 50`. Ten en cuenta que, dado que cada iteración requiere dos llamadas a la función para calcular el gradiente, el número total de llamadas a la función será de $2 \\times \\text{maxiter}$. El valor de `maxiter` puede aumentarse a cualquier valor superior para obtener una mejor estimación de la energía.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "1732ce37",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Fx Iters. done: 101 [Current cost: -3.03843]"
          ]
        }
      ],
      "source": [
        "maxiter = 50\n",
        "spsa_min, spsa_history = solve(\n",
        "    params, isa_ansatz, isa_observable, maxiter=maxiter\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "33abbb3f-6245-4610-a05d-e2bc4cc551f0",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "### Paso 4: Procesamiento posterior y devolución del resultado en el formato clásico deseado\n",
        "\n",
        "* Datos de entrada: estimaciones de la energía del estado fundamental durante la optimización\n",
        "* Resultado: Energía estimada del estado fundamental\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "e5b58771-d543-4e75-9746-fbc7b28e4360",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Estimated ground state energy: [-3.03842968]\n"
          ]
        }
      ],
      "source": [
        "print(f\"Estimated ground state energy: {spsa_min}\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "ecd7762a",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spin-chain-vqe/extracted-outputs/ecd7762a-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "results = {\n",
        "    \"spsa\": spsa_history,\n",
        "}\n",
        "\n",
        "visualize_results(spsa_history)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a3f4171f",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Ejemplo de hardware a gran escala\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "746e6133",
      "metadata": {},
      "source": [
        "En este tutorial no se incluye ningún ejemplo de hardware a gran escala. A medida que aumenta el número de qubits, la optimización del VQE se enfrenta a importantes retos debido al fenómeno [de](/learning/courses/variational-algorithm-design/optimization-loops#barren-plateaus) la meseta estéril: el gradiente de la función de coste desaparece exponencialmente con el tamaño del sistema, lo que hace que la optimización resulte prácticamente inviable para circuitos de gran tamaño. Si a esto le sumamos el ruido del hardware, esto significa que ampliar el VQE a cadenas de espín más largas no produce resultados fiables y reproducibles. Para conocer los enfoques que superan estas limitaciones, consulta la sección «Próximos pasos» que figura a continuación.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8f14492c",
      "metadata": {},
      "source": [
        "<span id=\"challenge\" />\n",
        "\n",
        "## Reto\n",
        "\n",
        "Ahora que ya tienes una implementación de VQE que funciona para la cadena de Heisenberg, prueba lo siguiente:\n",
        "\n",
        "1. **Prueba con diferentes profundidades de aproximación:** modifica el `reps` parámetro en `efficient_su2` (por ejemplo, prueba con `reps=1` y `reps=3`). ¿Cómo influye la profundidad del enfoque en la estimación de la energía del estado fundamental y en la velocidad de convergencia? ¿En qué momento se observan rendimientos decrecientes o inestabilidad?\n",
        "2. **Ajustar los hiperparámetros de SPSA:** Modifica los parámetros del programa de tasa de aprendizaje (`a`, `c`, `alpha`, `gamma`, `A`) y observa cómo influyen en la convergencia. ¿Puedes encontrar una configuración que converja más rápido que los valores predeterminados que se utilizan aquí?\n",
        "3. **Compara diferentes topologías de acoplamiento:** en lugar de utilizar el mapa de acoplamiento nativo del backend, intenta construir una cadena lineal simple de «vecinos más cercanos» y compara los resultados. ¿Cómo afecta la conectividad del hardware físico a la profundidad del circuito transpilado y a la estimación final de la energía?\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "217a9379",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## Referencias\n",
        "\n",
        "\\[1] Spall, J. C. (2002). Aplicación del algoritmo de perturbación simultánea para la optimización estocástica.\n",
        "IEEE Transactions on Aerospace and Electronic Systems, 34(3), 817-823.\n",
        "\n",
        "\\[2] Sahin, M. Emre, et al. (2025). Qiskit Machine Learning : una biblioteca de código abierto para tareas de aprendizaje automático cuántico a gran escala en hardware cuántico y simuladores clásicos. arXiv:2505.17756.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8aefe54c",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Próximos pasos\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recomendaciones\">\n",
        "  Si te ha parecido interesante este trabajo, quizá te interese el siguiente material:\n",
        "\n",
        "  * **Prueba la diagonalización cuántica basada en muestras (SQD):** tal y como se ha demostrado en este tutorial, el VQE se enfrenta a dificultades a gran escala debido a las mesetas estériles y a la elevada sobrecarga de las mediciones. IBM ha desarrollado [la diagonalización cuántica basada en muestras (SQD)](/docs/addons/qiskit-addon-sqd) como una alternativa más escalable. A diferencia de la VQE, la SQD prescinde por completo de la optimización variacional; en su lugar, un ordenador cuántico genera muestras y un ordenador clásico proyecta el hamiltoniano sobre un subespacio generado por dichas muestras y lo diagonaliza. Esto proporciona un límite superior para la energía del estado fundamental con un número significativamente menor de mediciones y sin que se vea afectado por las mesetas estériles. Sigue el [tutorial de SQD](/docs/tutorials/sample-based-quantum-diagonalization) para ver este método en acción.\n",
        "  * **Explora el curso «Algoritmos de diagonalización cuántica»:** profundiza en tu conocimiento tanto del VQE como del SQD, incluidas sus ventajas e inconvenientes, en el curso [«Algoritmos de diagonalización cuántica»](/learning/courses/quantum-diagonalization-algorithms) disponible en IBM Quantum Learning.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}