{
  "cells": [
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "da8ce695-8435-4099-b0fd-ade7b298f540",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Funciones de coste\"\n",
        "description: \"Esta lección explica qué es una función de coste, cómo se utiliza en los algoritmos variacionales y en qué se diferencia del hamiltoniano.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore resil nbsp eigenbasis expvals IIZZ IZIZ IZZI ZIIZ ZZII */}\n",
        "\n",
        "<span id=\"cost-functions\" />\n",
        "\n",
        "# Funciones de coste\n",
        "\n",
        "En esta lección aprenderemos a evaluar una *función de coste* :\n",
        "\n",
        "* En primer lugar, conoceremos [las primitivas Qiskit Runtime](/docs/guides/primitives)\n",
        "* Defina una *función de coste* $C(\\vec\\theta)$. Se trata de una función específica del problema que define el objetivo que el optimizador debe minimizar (o maximizar)\n",
        "* Definición de una estrategia de medición con las primitivas Qiskit Runtime para optimizar la velocidad frente a la precisión\n",
        "\n",
        " \n",
        "\n",
        "![Diagrama que muestra los componentes clave de una función de coste, incluido el uso de primitivas como el estimador y el muestreador.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/cost-functions/cost-function-workflow.svg)\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "4a9b79a9-c040-43d3-a4cf-314277e30eea",
      "metadata": {
        "gloss": {
          "primitives": {
            "text": "Basic and fundamental operations or data type. Qiskit has the Sampler and Estimator primitives to serve as building blocks to easily construct complex workloads, like variational algorithms.",
            "title": "Primitives"
          }
        }
      },
      "source": [
        "<span id=\"primitives\" />\n",
        "\n",
        "## Primitivos\n",
        "\n",
        "Todos los sistemas físicos, ya sean clásicos o cuánticos, pueden existir en diferentes estados. Por ejemplo, un coche en una carretera puede tener una masa, posición, velocidad o aceleración determinadas que caracterizan su estado. Del mismo modo, los sistemas cuánticos también pueden tener diferentes configuraciones o estados, pero difieren de los sistemas clásicos en la forma de tratar las mediciones y la evolución de los estados. Esto da lugar a propiedades únicas como la *superposición* y *el entrelazamiento*, exclusivas de la mecánica cuántica. Al igual que podemos describir el estado de un coche mediante propiedades físicas como la velocidad o la aceleración, también podemos describir el estado de un sistema cuántico mediante *observables*, que son objetos matemáticos.\n",
        "\n",
        "En mecánica cuántica, los estados se representan mediante vectores columna complejos normalizados, o *kets* ( $|\\psi\\rangle$ ), y los observables son operadores lineales hermitianos ( $\\hat{H}=\\hat{H}^{\\dagger}$ ) que actúan sobre los kets. Un vector propio ( $|\\lambda\\rangle$ ) de un observable se conoce como *estado propio*. La medición de un observable para uno de sus estados propios ( $|\\lambda\\rangle$ ) nos dará como lectura el valor propio correspondiente ( $\\lambda$ ).\n",
        "\n",
        "Si se pregunta cómo medir un sistema cuántico y qué puede medir, Qiskit ofrece dos sitios web <DefinitionTooltip definition=\"Operaciones básicas y fundamentales o tipo de datos. Qiskit dispone de las primitivas Sampler y Estimator que sirven como bloques de construcción para construir fácilmente cargas de trabajo algorítmicas complejas.\">Primitivos</DefinitionTooltip> que pueden ayudarle:\n",
        "\n",
        "* `Sampler`: Dado un estado cuántico $|\\psi\\rangle$, esta primitiva obtiene la probabilidad de cada posible estado base computacional.\n",
        "* `Estimator`: Dado un observable cuántico $\\hat{H}$ y un estado $|\\psi\\rangle$, esta primitiva calcula el valor esperado de $\\hat{H}$.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "00aa7dfa-34f1-440e-bbdb-19b6e6a1aff8",
      "metadata": {},
      "source": [
        "<span id=\"the-sampler-primitive\" />\n",
        "\n",
        "### La primitiva Sampler\n",
        "\n",
        "La primitiva `Sampler` calcula la probabilidad de obtener cada posible estado $|k\\rangle$ a partir de la base computacional, dado un circuito cuántico que prepara el estado $|\\psi\\rangle$. Calcula\n",
        "\n",
        "$$\n",
        "p_k = |\\langle k | \\psi \\rangle|^2 \\quad \\forall k \\in \\mathbb{Z}_2^n \\equiv \\{0,1,\\cdots,2^n-1\\},\n",
        "$$\n",
        "\n",
        "Donde $n$ es el número de qubits, y $k$ la representación entera de cualquier posible cadena binaria de salida $\\{0,1\\}^n$ (es decir, enteros de base $2$ ).\n",
        "\n",
        "El sitio Qiskit Runtime [`Sampler`](/docs/api/qiskit-ibm-runtime/sampler-v2) ejecuta el circuito varias veces en un dispositivo cuántico, realizando mediciones en cada ejecución y reconstruyendo la distribución de probabilidad a partir de las cadenas de bits recuperadas. Cuantas más ejecuciones (o *disparos* ) realice, más precisos serán los resultados, pero esto requiere más tiempo y recursos cuánticos.\n",
        "\n",
        "Sin embargo, dado que el número de posibles salidas crece exponencialmente con el número de qubits $n$ (es decir, $2^n$ ), el número de disparos tendrá que crecer exponencialmente también para capturar una distribución de probabilidad *densa*. Por lo tanto, `Sampler` sólo es eficiente para distribuciones de probabilidad *dispersas*; donde el estado objetivo $|\\psi\\rangle$ debe ser expresable como una combinación lineal de los estados base computacionales, con el número de términos creciendo como máximo polinomialmente con el número de qubits:\n",
        "\n",
        "$$\n",
        "|\\psi\\rangle = \\sum^{\\text{Poly}(n)}_k w_k |k\\rangle.\n",
        "$$\n",
        "\n",
        "El `Sampler` también puede configurarse para recuperar probabilidades de una subsección del circuito, que representa un subconjunto del total de estados posibles.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "96a74b2b-6aa0-4ef1-a8d1-448b591429e4",
      "metadata": {
        "gloss": {
          "pauli": {
            "text": "Set of matrices commonly used in quantum computing to represent and manipulate quantum states, consisting of the identity matrix and the three Pauli matrices (X, Y, and Z).",
            "title": "Pauli Operators"
          }
        }
      },
      "source": [
        "<span id=\"the-estimator-primitive\" />\n",
        "\n",
        "### La primitiva Estimator\n",
        "\n",
        "La primitiva `Estimator` calcula el valor de expectativa de un observable $\\hat{H}$ para un estado cuántico $|\\psi\\rangle$; donde las probabilidades del observable pueden expresarse como $p_\\lambda = |\\langle\\lambda|\\psi\\rangle|^2$, siendo $|\\lambda\\rangle$ los estados propios del observable $\\hat{H}$. El valor de expectativa se define entonces como la media de todos los resultados posibles $\\lambda$ (es decir, los valores propios del observable) de una medición del estado $|\\psi\\rangle$, ponderada por las probabilidades correspondientes:\n",
        "\n",
        "$$\n",
        "\\langle\\hat{H}\\rangle_\\psi := \\sum_\\lambda p_\\lambda \\lambda = \\langle \\psi | \\hat{H} | \\psi \\rangle\n",
        "$$\n",
        "\n",
        "Sin embargo, calcular el valor de expectativa de un observable no siempre es posible, ya que a menudo no conocemos su base propia. El sitio Qiskit Runtime [`Estimator`](/docs/api/qiskit-ibm-runtime/estimator-v2) utiliza un proceso algebraico complejo para estimar el valor de expectativa en un dispositivo cuántico real descomponiendo el observable en una combinación de otros observables cuyas bases propias sí conocemos.\n",
        "\n",
        "En términos más sencillos, `Estimator` descompone cualquier observable que no sabe cómo medir en observables más simples y medibles denominados <DefinitionTooltip definition=\"Conjunto de matrices utilizadas habitualmente en informática cuántica para representar y manipular estados cuánticos, formado por la matriz identidad y las tres matrices de Pauli (X, Y y Z).\">Operadores de Pauli</DefinitionTooltip>.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "6dcabf55-4462-4cf9-b6e4-e7860fd25b31",
      "metadata": {},
      "source": [
        "Cualquier operador puede expresarse como una combinación de $4^n$ operadores de Pauli.\n",
        "\n",
        "$$\n",
        "\\hat{P}_k :=\n",
        "\\sigma_{k_{n-1}}\\otimes \\cdots \\otimes \\sigma_{k_0} \\quad\n",
        "\\forall k \\in \\mathbb{Z}_4^n \\equiv \\{0,1,\\cdots,4^n-1\\}, \\\\\n",
        "$$\n",
        "\n",
        "tal que\n",
        "\n",
        "$$\n",
        "\\hat{H} = \\sum^{4^n-1}_{k=0} w_k \\hat{P}_k\n",
        "$$\n",
        "\n",
        "donde $n$ es el número de qubits, $k \\equiv k_{n-1} \\cdots k_0$ para $k_l \\in \\mathbb{Z}_4 \\equiv \\{0, 1, 2, 3\\}$ (es decir, enteros de base $4$ ), y $(\\sigma_0, \\sigma_1, \\sigma_2, \\sigma_3) := (I, X, Y, Z)$.\n",
        "\n",
        "Después de realizar esta descomposición, `Estimator` deriva un nuevo circuito $V_k|\\psi\\rangle$ para cada observable $\\hat{P}_k$ (del circuito original), para *diagonalizar* efectivamente el observable Pauli en la base computacional y medirlo. Podemos medir fácilmente los observables de Pauli porque conocemos $V_k$ de antemano, lo que no ocurre generalmente con otros observables.\n",
        "\n",
        "Para cada $\\hat{P}_{k}$, el `Estimator` ejecuta el circuito correspondiente en un dispositivo cuántico varias veces, mide el estado de salida en la base computacional y calcula la probabilidad $p_{kj}$ de obtener cada salida posible $j$. A continuación, busca el valor propio $\\lambda_{kj}$ de $P_k$ correspondiente a cada salida $j$, multiplica por $w_k$, y suma todos los resultados para obtener el valor esperado del observable $\\hat{H}$ para el estado dado $|\\psi\\rangle$.\n",
        "\n",
        "$$\n",
        "\\langle\\hat{H}\\rangle_\\psi =\n",
        "\\sum_{k=0}^{4^n-1} w_k \\sum_{j=0}^{2^n-1}p_{kj} \\lambda_{kj},\n",
        "$$\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "dcffb899-ef2d-4dca-b2a7-11c197d45ed0",
      "metadata": {
        "gloss": {
          "sampling": {
            "text": "The process of taking several measurements of one or several things.",
            "title": "Sampling"
          }
        }
      },
      "source": [
        "Dado que calcular el valor de la expectativa de $4^n$ Paulis es poco práctico (es decir, crece exponencialmente), `Estimator` sólo puede ser eficiente cuando una gran cantidad de $w_k$ son cero (es decir, descomposición Pauli *dispersa* en lugar de *densa* ). Formalmente decimos que, para que este cálculo sea *eficientemente resoluble*, el número de términos distintos de cero tiene que crecer como mucho polinómicamente con el número de qubits $n$ : $\\hat{H} = \\sum^{\\text{Poly}(n)}_k w_k \\hat{P}_k.$\n",
        "\n",
        "El lector puede notar la suposición implícita de que la probabilidad <DefinitionTooltip definition=\"El proceso de tomar varias medidas de una o varias cosas.\">muestreo</DefinitionTooltip> también tiene que ser eficiente como se explica para `Sampler`, lo que significa que\n",
        "\n",
        "$$\n",
        "\\langle\\hat{H}\\rangle_\\psi =\n",
        "\\sum_{k}^{\\text{Poly}(n)} w_k \\sum_{j}^{\\text{Poly}(n)}p_{kj} \\lambda_{kj}.\n",
        "$$\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "c1547f3e-3c05-46d9-be5a-3e7e4141dc92",
      "metadata": {},
      "source": [
        "<span id=\"guided-example-to-calculate-expectation-values\" />\n",
        "\n",
        "### Ejemplo guiado para calcular valores esperados\n",
        "\n",
        "Supongamos el estado single-qubit $|+\\rangle := H|0\\rangle = \\frac{1}{\\sqrt{2}}(|0\\rangle + |1\\rangle)$, y observable\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "\\hat{H}\n",
        "& = \\begin{pmatrix}\n",
        "-1 & 2 \\\\\n",
        "2 & 1 \\\\\n",
        "\\end{pmatrix}\\\\[1mm]\n",
        "& = 2X - Z\n",
        "\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n",
        "con el siguiente valor teórico esperado $\\langle\\hat{H}\\rangle_+ = \\langle+|\\hat{H}|+\\rangle = 2.$\n",
        "\n",
        "Como no sabemos cómo medir este observable, no podemos calcular su valor de expectativa directamente, y tenemos que volver a expresarlo como $\\langle\\hat{H}\\rangle_+ = 2\\langle X \\rangle_+ - \\langle Z \\rangle_+ $. Se puede demostrar que se evalúa con el mismo resultado observando que $\\langle+|X|+\\rangle = 1$, y $\\langle+|Z|+\\rangle = 0$.\n",
        "\n",
        "Veamos cómo calcular directamente $\\langle X \\rangle_+$ y $\\langle Z \\rangle_+$. Dado que $X$ y $Z$ no conmutan (es decir, no comparten la misma base propia), no pueden medirse simultáneamente, por lo que necesitamos los circuitos auxiliares:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "7c32e970-f9cb-48ec-a60b-2dccb0744141",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/7c32e970-f9cb-48ec-a60b-2dccb0744141-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit import QuantumCircuit\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "\n",
        "# The following code will work for any other initial single-qubit state and observable\n",
        "original_circuit = QuantumCircuit(1)\n",
        "original_circuit.h(0)\n",
        "\n",
        "H = SparsePauliOp([\"X\", \"Z\"], [2, -1])\n",
        "\n",
        "aux_circuits = []\n",
        "for pauli in H.paulis:\n",
        "    aux_circ = original_circuit.copy()\n",
        "    aux_circ.barrier()\n",
        "    if str(pauli) == \"X\":\n",
        "        aux_circ.h(0)\n",
        "    elif str(pauli) == \"Y\":\n",
        "        aux_circ.sdg(0)\n",
        "        aux_circ.h(0)\n",
        "    else:\n",
        "        aux_circ.id(0)\n",
        "    aux_circ.measure_all()\n",
        "    aux_circuits.append(aux_circ)\n",
        "\n",
        "original_circuit.draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "ec3cf7af-66b2-4ea9-94d7-6ec6ba2bfc5e",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/ec3cf7af-66b2-4ea9-94d7-6ec6ba2bfc5e-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 2,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Auxiliary circuit for X\n",
        "aux_circuits[0].draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "678d3bd0-d8fd-4767-b0a6-77cae945810a",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/678d3bd0-d8fd-4767-b0a6-77cae945810a-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 3,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Auxiliary circuit for Z\n",
        "aux_circuits[1].draw(\"mpl\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "beef97f0-c968-4a3f-9b7b-fed39b88cbe3",
      "metadata": {},
      "source": [
        "Ahora podemos realizar el cálculo manualmente utilizando `Sampler` y comprobar los resultados en `Estimator`:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "d59dffe0-85cb-4849-8421-03bbcc9668df",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Sampler results:\n",
            "  >> Expected value of X: 1.00000\n",
            "  >> Expected value of Z: 0.00420\n",
            "  >> Total expected value: 1.99580\n",
            "Estimator results:\n",
            "  >> Expected value of X: 1.00000\n",
            "  >> Expected value of Z: 0.00000\n",
            "  >> Total expected value: 2.00000\n"
          ]
        }
      ],
      "source": [
        "from qiskit.primitives import StatevectorSampler, StatevectorEstimator\n",
        "from qiskit.result import QuasiDistribution\n",
        "import numpy as np\n",
        "\n",
        "\n",
        "## SAMPLER\n",
        "shots = 10000\n",
        "sampler = StatevectorSampler()\n",
        "job = sampler.run(aux_circuits, shots=shots)\n",
        "\n",
        "# Run the sampler job and step through results\n",
        "expvals = []\n",
        "for index, pauli in enumerate(H.paulis):\n",
        "    data_pub = job.result()[index].data\n",
        "    bitstrings = data_pub.meas.get_bitstrings()\n",
        "    counts = data_pub.meas.get_counts()\n",
        "    quasi_dist = QuasiDistribution(\n",
        "        {outcome: freq / shots for outcome, freq in counts.items()}\n",
        "    )\n",
        "\n",
        "    # Use the probabilities and known eigenvalues of Pauli operators to estimate\n",
        "    # the expectation value.\n",
        "    val = 0\n",
        "\n",
        "    if str(pauli) == \"X\":\n",
        "        val += -1 * quasi_dist.get(1, 0)\n",
        "        val += 1 * quasi_dist.get(0, 0)\n",
        "\n",
        "    if str(pauli) == \"Y\":\n",
        "        val += -1 * quasi_dist.get(1, 0)\n",
        "        val += 1 * quasi_dist.get(0, 0)\n",
        "\n",
        "    if str(pauli) == \"Z\":\n",
        "        val += 1 * quasi_dist.get(0, 0)\n",
        "        val += -1 * quasi_dist.get(1, 0)\n",
        "\n",
        "    expvals.append(val)\n",
        "\n",
        "# Print expectation values\n",
        "\n",
        "print(\"Sampler results:\")\n",
        "for pauli, expval in zip(H.paulis, expvals):\n",
        "    print(f\"  >> Expected value of {str(pauli)}: {expval:.5f}\")\n",
        "\n",
        "total_expval = np.sum(H.coeffs * expvals).real\n",
        "print(f\"  >> Total expected value: {total_expval:.5f}\")\n",
        "\n",
        "# Use estimator for comparison\n",
        "observables = [\n",
        "    *H.paulis,\n",
        "    H,\n",
        "]  # Note: run for individual Paulis as well as full observable H\n",
        "\n",
        "estimator = StatevectorEstimator()\n",
        "job = estimator.run([(original_circuit, observables)])\n",
        "estimator_expvals = job.result()[0].data.evs\n",
        "\n",
        "# Print results\n",
        "print(\"Estimator results:\")\n",
        "for obs, expval in zip(observables, estimator_expvals):\n",
        "    if obs is not H:\n",
        "        print(f\"  >> Expected value of {str(obs)}: {expval:.5f}\")\n",
        "    else:\n",
        "        print(f\"  >> Total expected value: {expval:.5f}\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "44f13c37-50e3-426c-90cb-f730a81e20d1",
      "metadata": {},
      "source": [
        "<span id=\"mathematical-rigor-optional\" />\n",
        "\n",
        "### Rigor matemático (opcional)\n",
        "\n",
        "Expresando $|\\psi\\rangle$ con respecto a la base de estados propios de $\\hat{H}$, $|\\psi\\rangle = \\sum_\\lambda a_\\lambda |\\lambda\\rangle$, se deduce:\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "\\langle \\psi | \\hat{H} | \\psi \\rangle\n",
        "& = \\bigg(\\sum_{\\lambda'}a^*_{\\lambda'} \\langle \\lambda'|\\bigg) \\hat{H}\n",
        "  \\bigg(\\sum_{\\lambda} a_\\lambda | \\lambda\\rangle\\bigg)\\\\[1mm]\n",
        "\n",
        "& = \\sum_{\\lambda}\\sum_{\\lambda'} a^*_{\\lambda'}a_{\\lambda}\n",
        "  \\langle \\lambda'|\\hat{H}| \\lambda\\rangle\\\\[1mm]\n",
        "\n",
        "& = \\sum_{\\lambda}\\sum_{\\lambda'} a^*_{\\lambda'}a_{\\lambda} \\lambda\n",
        "\\langle \\lambda'| \\lambda\\rangle\\\\[1mm]\n",
        "\n",
        "& = \\sum_{\\lambda}\\sum_{\\lambda'} a^*_{\\lambda'}a_{\\lambda} \\lambda\n",
        "\\cdot \\delta_{\\lambda, \\lambda'}\\\\[1mm]\n",
        "\n",
        "& = \\sum_\\lambda |a_\\lambda|^2 \\lambda\\\\[1mm]\n",
        "\n",
        "& = \\sum_\\lambda p_\\lambda \\lambda\\\\[1mm]\n",
        "\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "6a3bea3d-a2f6-4033-85f9-9a10c5d5c64c",
      "metadata": {
        "gloss": {
          "hermitian": {
            "text": "A hermitian is a square matrix that is equal to its own conjugate transpose, or a linear operator that is self-adjoint.",
            "title": "Hermitian"
          }
        }
      },
      "source": [
        "Dado que no conocemos los valores propios ni los estados propios del observable objetivo $\\hat{H}$, primero tenemos que considerar su diagonalización. Dado que $\\hat{H}$ es <DefinitionTooltip definition=\"Una hermitiana es una matriz cuadrada que es igual a su propia transpuesta conjugada, o un operador lineal que es autoadjunto.\">Hermitiana</DefinitionTooltip>, existe una transformación unitaria $V$ tal que $\\hat{H}=V^\\dagger \\Lambda V,$ donde $\\Lambda$ es la matriz diagonal de valores propios, por lo que $\\langle j | \\Lambda | k \\rangle = 0$ si $j\\neq k$, y $\\langle j | \\Lambda | j \\rangle = \\lambda_j$.\n",
        "\n",
        "Esto implica que el valor esperado puede reescribirse como:\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "\\langle\\psi|\\hat{H}|\\psi\\rangle\n",
        "& = \\langle\\psi|V^\\dagger \\Lambda V|\\psi\\rangle\\\\[1mm]\n",
        "\n",
        "& = \\langle\\psi|V^\\dagger \\bigg(\\sum_{j=0}^{2^n-1} |j\\rangle\n",
        "\\langle j|\\bigg) \\Lambda \\bigg(\\sum_{k=0}^{2^n-1} |k\\rangle \\langle k|\\bigg) V|\\psi\\rangle\\\\[1mm]\n",
        "\n",
        "& = \\sum_{j=0}^{2^n-1} \\sum_{k=0}^{2^n-1}\\langle\\psi|V^\\dagger |j\\rangle\n",
        "\\langle j| \\Lambda  |k\\rangle \\langle k| V|\\psi\\rangle\\\\[1mm]\n",
        "\n",
        "& = \\sum_{j=0}^{2^n-1}\\langle\\psi|V^\\dagger |j\\rangle\n",
        "\\langle j| \\Lambda  |j\\rangle \\langle j| V|\\psi\\rangle\\\\[1mm]\n",
        "\n",
        "& = \\sum_{j=0}^{2^n-1}|\\langle j| V|\\psi\\rangle|^2 \\lambda_j\\\\[1mm]\n",
        "\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n",
        "Dado que si un sistema se encuentra en el estado $|\\phi\\rangle = V |\\psi\\rangle$ la probabilidad de medir $| j\\rangle$ es $p_j = |\\langle j|\\phi \\rangle|^2$, el valor esperado anterior puede expresarse como:\n",
        "\n",
        "$$\n",
        "\\langle\\psi|\\hat{H}|\\psi\\rangle =\n",
        "\\sum_{j=0}^{2^n-1} p_j \\lambda_j.\n",
        "$$\n",
        "\n",
        "Es muy importante tener en cuenta que las probabilidades se toman del estado $V |\\psi\\rangle$ en lugar de $|\\psi\\rangle$. Por este motivo, la matriz $V$ es absolutamente necesaria.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "c21a9a05-6e07-45dc-a090-4414dfdf2521",
      "metadata": {},
      "source": [
        "Quizá se pregunte cómo obtener la matriz $V$ y los valores propios $\\Lambda$. Si ya tuvieras los valores propios, no habría necesidad de utilizar un ordenador cuántico, ya que el objetivo de los algoritmos variacionales es encontrar estos valores propios de $\\hat{H}$.\n",
        "\n",
        "Afortunadamente, existe una forma de evitarlo: cualquier matriz $2^n \\times 2^n$ puede escribirse como una combinación lineal de $4^n$ productos tensoriales de $n$ matrices e identidades Pauli, todas ellas hermitianas y unitarias con $V$ y $\\Lambda$ conocidas. Esto es lo que el Runtime `Estimator` hace internamente descomponiendo cualquier [`Operator`](/docs/api/qiskit/qiskit.quantum_info.Operator) en un objeto [`SparsePauliOp`](/docs/api/qiskit/qiskit.quantum_info.SparsePauliOp).\n",
        "\n",
        "Éstos son los operadores que se pueden utilizar:\n",
        "\n",
        "$$\n",
        "\\begin{array}{c|c|c|c}\n",
        "  \\text{Operator} & \\sigma & V & \\Lambda \\\\[1mm]\n",
        "  \\hline\n",
        "  I & \\sigma_0 = \\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix} & V_0 = I & \\Lambda_0 = I = \\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix} \\\\[4mm]\n",
        "\n",
        "  X & \\sigma_1 = \\begin{pmatrix} 0 & 1 \\\\ 1 & 0 \\end{pmatrix} & V_1 = H =\\frac{1}{\\sqrt{2}} \\begin{pmatrix} 1 & 1 \\\\ 1 & -1 \\end{pmatrix} & \\Lambda_1 = \\sigma_3 = \\begin{pmatrix} 1 & 0 \\\\ 0 & -1 \\end{pmatrix} \\\\[4mm]\n",
        "\n",
        "  Y & \\sigma_2 = \\begin{pmatrix} 0 & -i \\\\ i & 0 \\end{pmatrix} & V_2 = HS^\\dagger  =\\frac{1}{\\sqrt{2}} \\begin{pmatrix} 1 & 1 \\\\ 1 & -1 \\end{pmatrix}\\cdot  \\begin{pmatrix} 1 & 0 \\\\ 0 & -i \\end{pmatrix} = \\frac{1}{\\sqrt{2}} \\begin{pmatrix} 1 & -i \\\\ 1 & i \\end{pmatrix}\\quad & \\Lambda_2 = \\sigma_3 = \\begin{pmatrix} 1 & 0 \\\\ 0 & -1 \\end{pmatrix} \\\\[4mm]\n",
        "\n",
        "  Z & \\sigma_3 = \\begin{pmatrix} 1 & 0 \\\\ 0 & -1 \\end{pmatrix} & V_3 = I & \\Lambda_3 = \\sigma_3 = \\begin{pmatrix} 1 & 0 \\\\ 0 & -1 \\end{pmatrix}\n",
        "\\end{array}\n",
        "$$\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "9e1c0933-5fbe-466d-9114-92d66d21785f",
      "metadata": {},
      "source": [
        "Así que vamos a reescribir $\\hat{H}$ con respecto a los Paulis y las identidades:\n",
        "\n",
        "$$\n",
        "\\hat{H} =\n",
        "\\sum_{k_{n-1}=0}^3...\n",
        "\\sum_{k_0=0}^3 w_{k_{n-1}...k_0}\n",
        "\\sigma_{k_{n-1}}\\otimes ... \\otimes \\sigma_{k_0} = \\sum_{k=0}^{4^n-1} w_k \\hat{P}_k,\n",
        "$$\n",
        "\n",
        "donde $k = \\sum_{l=0}^{n-1} 4^l k_l \\equiv k_{n-1}...k_0$ para $k_{n-1},...,k_0\\in \\{0,1,2,3\\}$ (es decir, la base $4$ ), y $\\hat{P}_{k} := \\sigma_{k_{n-1}}\\otimes ... \\otimes \\sigma_{k_0}$ :\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "\\langle\\psi|\\hat{H}|\\psi\\rangle\n",
        "& = \\sum_{k=0}^{4^n-1} w_k\n",
        "\\sum_{j=0}^{2^n-1}|\\langle j| V_k|\\psi\\rangle|^2 \\langle j| \\Lambda_k |j\\rangle \\\\[1mm]\n",
        "\n",
        "& = \\sum_{k=0}^{4^n-1} w_k \\sum_{j=0}^{2^n-1}p_{kj} \\lambda_{kj}, \\\\[1mm]\n",
        "\n",
        "\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n",
        "donde $V_k := V_{k_{n-1}}\\otimes ... \\otimes V_{k_0}$ y $\\Lambda_k := \\Lambda_{k_{n-1}}\\otimes ... \\otimes \\Lambda_{k_0}$, tales que: $\\hat{P_k}=V_k^\\dagger \\Lambda_k V_k.$\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "42f771a0-486d-4a5f-9df1-a1720d7bf252",
      "metadata": {
        "gloss": {
          "oracle": {
            "text": " A hypothetical device or software component that performs a specific function, but the internal workings of which are unknown. A user is only aware of the inputs and outputs of the black box, and has no knowledge of how the black box processes the inputs to produce the outputs.",
            "title": "Black-box oracle"
          }
        }
      },
      "source": [
        "<span id=\"cost-functions\" />\n",
        "\n",
        "## Funciones de coste\n",
        "\n",
        "En general, las funciones de coste se utilizan para describir el objetivo de un problema y el rendimiento de un estado de prueba con respecto a ese objetivo. Esta definición puede aplicarse a varios ejemplos de química, aprendizaje automático, finanzas, optimización, etc.\n",
        "\n",
        "Veamos un ejemplo sencillo para encontrar el estado fundamental de un sistema. Nuestro objetivo es minimizar el valor de la expectativa del observable que representa la energía (Hamiltoniano $\\hat{\\mathcal{H}}$ ):\n",
        "\n",
        "$$\n",
        "\\min_{\\vec\\theta} \\langle\\psi(\\vec\\theta)|\\hat{\\mathcal{H}}|\\psi(\\vec\\theta)\\rangle\n",
        "$$\n",
        "\n",
        "Podemos utilizar el `Estimator` para evaluar el valor de la expectativa y pasar este valor a un optimizador para minimizar. Si la optimización tiene éxito, devolverá un conjunto de valores óptimos de los parámetros $\\vec\\theta^*$, a partir de los cuales podremos construir el estado de solución propuesto $|\\psi(\\vec\\theta^*)\\rangle$ y calcular el valor de expectativa observado como $C(\\vec\\theta^*)$.\n",
        "\n",
        "Observe que sólo podremos minimizar la función de coste para el conjunto limitado de estados que estamos considerando. Esto nos lleva a dos posibilidades distintas:\n",
        "\n",
        "* **Nuestro ansatz no define el estado de la solución en todo el espacio de búsqueda** : Si este es el caso, nuestro optimizador nunca encontrará la solución, y tenemos que experimentar con otros ansatzes que podrían ser capaces de representar nuestro espacio de búsqueda con mayor precisión.\n",
        "* **Nuestro optimizador es incapaz de encontrar esta solución válida** : La optimización puede ser definida globalmente y definida localmente. Más adelante veremos qué significa esto.\n",
        "\n",
        "En definitiva, estaremos realizando un bucle de optimización clásico pero confiando la evaluación de la función de coste a un ordenador cuántico. Desde esta perspectiva, se podría pensar en la optimización como un esfuerzo puramente clásico en el que llamamos a algunos <DefinitionTooltip definition=\"Dispositivo hipotético o componente de software que realiza una función específica, pero cuyo funcionamiento interno se desconoce. Un usuario sólo conoce las entradas y salidas de la caja negra, y no sabe cómo ésta procesa las entradas para producir las salidas.\">oráculo cuántico de caja negra</DefinitionTooltip> cada vez que el optimizador necesita evaluar la función de coste.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "650faaab-b964-4587-a39f-d6cc80b225b2",
      "metadata": {},
      "outputs": [],
      "source": [
        "def cost_func_vqe(params, circuit, hamiltonian, estimator):\n",
        "    \"\"\"Return estimate of energy from estimator\n",
        "\n",
        "    Parameters:\n",
        "        params (ndarray): Array of ansatz parameters\n",
        "        ansatz (QuantumCircuit): Parameterized ansatz circuit\n",
        "        hamiltonian (SparsePauliOp): Operator representation of Hamiltonian\n",
        "        estimator (Estimator): Estimator primitive instance\n",
        "\n",
        "    Returns:\n",
        "        float: Energy estimate\n",
        "    \"\"\"\n",
        "    pub = (circuit, hamiltonian, params)\n",
        "    cost = estimator.run([pub]).result()[0].data.evs\n",
        "    return cost"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "a01a54f2-da16-4008-b422-fa7ded531f67",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/a01a54f2-da16-4008-b422-fa7ded531f67-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 6,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit.circuit.library import TwoLocal\n",
        "\n",
        "observable = SparsePauliOp.from_list([(\"XX\", 1), (\"YY\", -3)])\n",
        "\n",
        "reference_circuit = QuantumCircuit(2)\n",
        "reference_circuit.x(0)\n",
        "\n",
        "variational_form = TwoLocal(\n",
        "    2,\n",
        "    rotation_blocks=[\"rz\", \"ry\"],\n",
        "    entanglement_blocks=\"cx\",\n",
        "    entanglement=\"linear\",\n",
        "    reps=1,\n",
        ")\n",
        "ansatz = reference_circuit.compose(variational_form)\n",
        "\n",
        "theta_list = (2 * np.pi * np.random.rand(1, 8)).tolist()\n",
        "ansatz.decompose().draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7357e05e-f68a-4cd5-bf0d-77eab963a86d",
      "metadata": {},
      "source": [
        "Primero lo haremos con un simulador: StatevectorEstimator. Esto suele ser aconsejable para la depuración, pero seguiremos inmediatamente la ejecución de depuración con un cálculo en hardware cuántico real. Cada vez más, los problemas de interés ya no son simulables clásicamente sin instalaciones de supercomputación de última generación.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "883bcb28-15d0-4046-a654-f43d9480a642",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[-0.58744589]\n"
          ]
        }
      ],
      "source": [
        "estimator = StatevectorEstimator()\n",
        "cost = cost_func_vqe(theta_list, ansatz, observable, estimator)\n",
        "print(cost)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1a94eba3-146d-4eee-970d-9a966d1e15f3",
      "metadata": {},
      "source": [
        "Ahora procederemos a ejecutarlo en un ordenador cuántico real. Observe los cambios de sintaxis. Los pasos relacionados con el pass\\_manager se tratarán con más detalle en el siguiente ejemplo. Un paso de especial importancia en los algoritmos variacionales es el uso de una sesión Qiskit Runtime. Iniciar una sesión permite ejecutar múltiples iteraciones de un algoritmo variacional sin tener que esperar en una nueva cola cada vez que se actualizan los parámetros. Esto es importante si los tiempos de espera son largos y/o se necesitan muchas iteraciones. Sólo los socios de la red IBM Quantum® pueden utilizar las sesiones Runtime. Si no tiene acceso a las sesiones, puede reducir el número de iteraciones que envía en un momento dado y guardar los parámetros más recientes para utilizarlos en futuras ejecuciones. Si envía demasiadas iteraciones o se encuentra con tiempos de espera demasiado largos, puede encontrarse con el código de error 1217, que se refiere a largos retrasos entre envíos de trabajos.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "e4ca4722-46ab-418f-8a1f-740a65e92eb9",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Estimated usage: < 1 min. Benchmarked at 7 seconds on an Eagle processor\n",
        "# Load necessary packages:\n",
        "\n",
        "from qiskit_ibm_runtime import (\n",
        "    QiskitRuntimeService,\n",
        "    Session,\n",
        "    EstimatorOptions,\n",
        "    EstimatorV2 as Estimator,\n",
        ")\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "\n",
        "# Select the least busy backend:\n",
        "\n",
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(\n",
        "    operational=True, min_num_qubits=ansatz.num_qubits, simulator=False\n",
        ")\n",
        "# Or get a specific backend:\n",
        "# backend = service.backend(\"ibm_brisbane\")\n",
        "\n",
        "# Use a pass manager to transpile the circuit and observable for the specific backend being used:\n",
        "\n",
        "pm = generate_preset_pass_manager(backend=backend, optimization_level=1)\n",
        "isa_ansatz = pm.run(ansatz)\n",
        "isa_observable = observable.apply_layout(layout=isa_ansatz.layout)\n",
        "\n",
        "\n",
        "# Set estimator options\n",
        "estimator_options = EstimatorOptions(resilience_level=1, default_shots=10_000)\n",
        "\n",
        "# Open a Runtime session:\n",
        "\n",
        "with Session(backend=backend) as session:\n",
        "    estimator = Estimator(mode=session, options=estimator_options)\n",
        "    cost = cost_func_vqe(theta_list, isa_ansatz, isa_observable, estimator)\n",
        "\n",
        "session.close()\n",
        "print(cost)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "95dc0e83-46ce-4839-91c3-af550aef9ddf",
      "metadata": {},
      "source": [
        "Obsérvese que los valores obtenidos de los dos cálculos anteriores son muy similares. Las técnicas para mejorar los resultados se analizarán más adelante.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "7f3951a3-47ad-4ddb-90a0-2218b1e11d15",
      "metadata": {},
      "source": [
        "<span id=\"example-mapping-to-non-physical-systems\" />\n",
        "\n",
        "### Ejemplo de mapeo a sistemas no físicos\n",
        "\n",
        "El problema del corte máximo (max-cut) es un problema de optimización combinatoria que consiste en dividir los vértices de un grafo en dos conjuntos disjuntos de tal manera que se maximice el número de aristas entre ambos conjuntos. En términos más formales, dado un grafo no dirigido $G=(V,E)$, donde $V$ es el conjunto de vértices y $E$ es el conjunto de aristas, el problema del corte máximo consiste en dividir los vértices en dos subconjuntos disjuntos, $S$ y $T$, de tal manera que se maximice el número de aristas con un extremo en $S$ y el otro en $T$.\n",
        "\n",
        "Podemos aplicar el método «max-cut» para resolver diversos problemas, entre los que se incluyen: la agrupación en clústeres, el diseño de redes, las transiciones de fase, etc. Empezaremos creando un gráfico del problema:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "739ca7a0-fae3-4389-a8de-7ae93c13b9e1",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/739ca7a0-fae3-4389-a8de-7ae93c13b9e1-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "import rustworkx as rx\n",
        "from rustworkx.visualization import mpl_draw\n",
        "\n",
        "n = 4\n",
        "G = rx.PyGraph()\n",
        "G.add_nodes_from(range(n))\n",
        "# The edge syntax is (start, end, weight)\n",
        "edges = [(0, 1, 1.0), (0, 2, 1.0), (0, 3, 1.0), (1, 2, 1.0), (2, 3, 1.0)]\n",
        "G.add_edges_from(edges)\n",
        "\n",
        "mpl_draw(\n",
        "    G, pos=rx.shell_layout(G), with_labels=True, edge_labels=str, node_color=\"#1192E8\"\n",
        ")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "3246e173-b1a8-48c2-bce8-f80a1524bcfb",
      "metadata": {},
      "source": [
        "Este problema puede expresarse como un problema de optimización binario. Para cada nodo $0 \\leq i < n$, donde $n$ es el número de nodos del grafo (en este caso $n=4$ ), consideraremos la variable binaria $x_i$. Esta variable tendrá el valor $1$ si el nodo $i$ es uno de los grupos que etiquetaremos como $1$ y $0$ si está en el otro grupo, que etiquetaremos como $0$. También denotaremos como $w_{ij}$ (elemento $(i,j)$ de la matriz de adyacencia $w$ ) el peso de la arista que va del nodo $i$ al nodo $j$. Dado que el grafo es no dirigido, $w_{ij}=w_{ji}$. Entonces podemos formular nuestro problema como la maximización de la siguiente función de coste:\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "C(\\vec{x})\n",
        "& =\\sum_{i,j=0}^n w_{ij} x_i(1-x_j)\\\\[1mm]\n",
        "\n",
        "& = \\sum_{i,j=0}^n w_{ij} x_i - \\sum_{i,j=0}^n w_{ij} x_ix_j\\\\[1mm]\n",
        "\n",
        "& = \\sum_{i,j=0}^n w_{ij} x_i - \\sum_{i=0}^n \\sum_{j=0}^i 2w_{ij} x_ix_j\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n",
        "Para resolver este problema con un ordenador cuántico, vamos a expresar la función de coste como el valor esperado de un observable. Sin embargo, los observables que admite Qiskit consisten nativamente en operadores de Pauli, que tienen valores propios $1$ y $-1$ en lugar de $0$ y $1$. Por eso vamos a hacer el siguiente cambio de variable:\n",
        "\n",
        "Donde $\\vec{x}=(x_0,x_1,\\cdots ,x_{n-1})$. Podemos utilizar la matriz de adyacencia $w$ para acceder cómodamente a los pesos de todas las aristas. Esto se utilizará para obtener nuestra función de costes:\n",
        "\n",
        "$$\n",
        "z_i = 1-2x_i \\rightarrow x_i = \\frac{1-z_i}{2}\n",
        "$$\n",
        "\n",
        "Esto implica que:\n",
        "\n",
        "$$\n",
        "\\begin{array}{lcl} x_i=0 & \\rightarrow & z_i=1 \\\\ x_i=1 & \\rightarrow & z_i=-1.\\end{array}\n",
        "$$\n",
        "\n",
        "Así que la nueva función de coste que queremos maximizar es:\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "C(\\vec{z})\n",
        "& = \\sum_{i,j=0}^n w_{ij} \\bigg(\\frac{1-z_i}{2}\\bigg)\\bigg(1-\\frac{1-z_j}{2}\\bigg)\\\\[1mm]\n",
        "\n",
        "& = \\sum_{i,j=0}^n \\frac{w_{ij}}{4} - \\sum_{i,j=0}^n \\frac{w_{ij}}{4} z_iz_j\\\\[1mm]\n",
        "\n",
        "& = \\sum_{i=0}^n \\sum_{j=0}^i \\frac{w_{ij}}{2} -  \\sum_{i=0}^n \\sum_{j=0}^i \\frac{w_{ij}}{2} z_iz_j\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n",
        "Además, la tendencia natural de un ordenador cuántico es encontrar mínimos (normalmente la energía más baja) en lugar de máximos, así que en lugar de maximizar $C(\\vec{z})$ vamos a minimizar:\n",
        "\n",
        "$$\n",
        "-C(\\vec{z}) =  \\sum_{i=0}^n \\sum_{j=0}^i \\frac{w_{ij}}{2} z_iz_j -  \\sum_{i=0}^n \\sum_{j=0}^i \\frac{w_{ij}}{2}\n",
        "$$\n",
        "\n",
        "Ahora que tenemos una función de coste a minimizar cuyas variables pueden tener los valores $-1$ y $1$, podemos hacer la siguiente analogía con la de Pauli $Z$ :\n",
        "\n",
        "$$\n",
        "z_i \\equiv Z_i = \\overbrace{I}^{n-1}\\otimes ... \\otimes \\overbrace{Z}^{i} \\otimes ... \\otimes \\overbrace{I}^{0}\n",
        "$$\n",
        "\n",
        "En otras palabras, la variable $z_i$ será equivalente a una puerta $Z$ que actúa sobre el qubit $i$. Además:\n",
        "\n",
        "$$\n",
        "Z_i|x_{n-1}\\cdots x_0\\rangle = z_i|x_{n-1}\\cdots x_0\\rangle \\rightarrow \\langle x_{n-1}\\cdots x_0 |Z_i|x_{n-1}\\cdots x_0\\rangle = z_i\n",
        "$$\n",
        "\n",
        "Entonces el observable que vamos a considerar es:\n",
        "\n",
        "$$\n",
        "\\hat{H} = \\sum_{i=0}^n \\sum_{j=0}^i \\frac{w_{ij}}{2} Z_iZ_j\n",
        "$$\n",
        "\n",
        "al que tendremos que añadir después el término independiente:\n",
        "\n",
        "$$\n",
        "\\texttt{offset} = - \\sum_{i=0}^n \\sum_{j=0}^i \\frac{w_{ij}}{2}\n",
        "$$\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "83c86ba1-8b9d-4222-bb7e-637e6f29e5cd",
      "metadata": {},
      "source": [
        "El operador es una combinación lineal de términos con operadores Z en nodos conectados por una arista (recuérdese que el qubit 0 está más a la derecha): $IIZZ + IZIZ + IZZI + ZIIZ + ZZII$. Una vez construido el operador, el ansatz para el algoritmo QAOA se puede construir fácilmente utilizando el circuito `QAOAAnsatz` de la biblioteca de circuitos Qiskit.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "29398315-3baf-4363-88fc-69b6438e4afa",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/29398315-3baf-4363-88fc-69b6438e4afa-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 17,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit.circuit.library import QAOAAnsatz\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "\n",
        "hamiltonian = SparsePauliOp.from_list(\n",
        "    [(\"IIZZ\", 1), (\"IZIZ\", 1), (\"IZZI\", 1), (\"ZIIZ\", 1), (\"ZZII\", 1)]\n",
        ")\n",
        "\n",
        "\n",
        "ansatz = QAOAAnsatz(hamiltonian, reps=2)\n",
        "# Draw\n",
        "ansatz.decompose(reps=3).draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "91d02379-9da6-426b-a5f0-d1fb75a891ee",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Offset: -2.5\n"
          ]
        }
      ],
      "source": [
        "# Sum the weights, and divide by 2\n",
        "\n",
        "offset = -sum(edge[2] for edge in edges) / 2\n",
        "print(f\"\"\"Offset: {offset}\"\"\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "84a98a08-00e4-4b05-a3f0-de29a7e73a60",
      "metadata": {},
      "source": [
        "Con el Estimador de Tiempo de Ejecución tomando directamente un Hamiltoniano y un ansatz parametrizado, y devolviendo la energía necesaria, La función de coste para una instancia QAOA es bastante simple:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "73ac80dd-87cf-4a43-b19b-e89a4782a44a",
      "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 (Estimator): Estimator primitive instance\n",
        "\n",
        "    Returns:\n",
        "        float: Energy estimate\n",
        "    \"\"\"\n",
        "    pub = (ansatz, hamiltonian, params)\n",
        "    cost = estimator.run([pub]).result()[0].data.evs\n",
        "    #    cost = estimator.run(ansatz, hamiltonian, parameter_values=params).result().values[0]\n",
        "    return cost"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "id": "fc6af87b-a838-43b8-96ce-0af790fe0172",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "1.473098768180865\n"
          ]
        }
      ],
      "source": [
        "import numpy as np\n",
        "\n",
        "x0 = 2 * np.pi * np.random.rand(ansatz.num_parameters)\n",
        "\n",
        "estimator = StatevectorEstimator()\n",
        "cost = cost_func_vqe(x0, ansatz, hamiltonian, estimator)\n",
        "print(cost)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "389b7e86-cd61-4184-96c3-ae91b8ad59f5",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "1.1120776913677988\n"
          ]
        }
      ],
      "source": [
        "# Estimated usage: < 1 min, benchmarked at 6 seconds on ibm_osaka, 5-23-24\n",
        "# Load some necessary packages:\n",
        "\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "from qiskit_ibm_runtime import Session, EstimatorV2 as Estimator\n",
        "\n",
        "# Select the least busy backend:\n",
        "\n",
        "backend = service.least_busy(\n",
        "    operational=True, min_num_qubits=ansatz.num_qubits, simulator=False\n",
        ")\n",
        "\n",
        "# Or get a specific backend:\n",
        "# backend = service.backend(\"ibm_brisbane\")\n",
        "\n",
        "# Use a pass manager to transpile the circuit and observable for the specific backend being used:\n",
        "\n",
        "pm = generate_preset_pass_manager(backend=backend, optimization_level=1)\n",
        "isa_ansatz = pm.run(ansatz)\n",
        "isa_hamiltonian = hamiltonian.apply_layout(layout=isa_ansatz.layout)\n",
        "\n",
        "# Set estimator options\n",
        "estimator_options = EstimatorOptions(resilience_level=1, default_shots=10_000)\n",
        "\n",
        "# Open a Runtime session:\n",
        "\n",
        "with Session(backend=backend) as session:\n",
        "    estimator = Estimator(mode=session, options=estimator_options)\n",
        "    cost = cost_func_vqe(x0, isa_ansatz, isa_hamiltonian, estimator)\n",
        "\n",
        "# Close session after done\n",
        "session.close()\n",
        "print(cost)"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "16c94186-366c-4ac4-8745-4e7da283f8da",
      "metadata": {},
      "source": [
        "Volveremos sobre este ejemplo en Aplicaciones para explorar cómo aprovechar un optimizador para iterar a través del espacio de búsqueda. En términos generales, esto incluye:\n",
        "\n",
        "* Utilizar un optimizador para encontrar los parámetros óptimos\n",
        "* Vinculación de parámetros óptimos al ansatz para hallar los valores propios\n",
        "* Trasladando los valores propios a la definición de nuestro problema\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "d14d5800-24c1-4e4b-b692-9bdb9ef4440f",
      "metadata": {},
      "source": [
        "<span id=\"measurement-strategy-speed-versus-accuracy\" />\n",
        "\n",
        "## Estrategia de medición: velocidad frente a precisión\n",
        "\n",
        "Como se ha mencionado, estamos utilizando un ordenador cuántico ruidoso como *oráculo de caja negra*, donde el ruido puede hacer que los valores recuperados no sean deterministas, dando lugar a fluctuaciones aleatorias que, a su vez, perjudicarán -o incluso impedirán por completo- la convergencia de ciertos optimizadores a una solución propuesta. Se trata de un problema general que debemos abordar a medida que exploramos progresivamente la utilidad cuántica y avanzamos hacia la ventaja cuántica:\n",
        "\n",
        "![Gráfico que muestra cómo varía el coste de simulación con la complejidad del circuito. Con un ordenador clásico crece exponencialmente. Con la mitigación de errores cuánticos, debería haber un punto de cruce en el que eso se convierta en una ventaja. La corrección cuántica de errores permite un crecimiento lineal del coste de simulación y sin duda supondrá una ventaja.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/cost-functions/cost-function-path-to-quantum-advantage.svg)\n",
        "\n",
        "Podemos utilizar las opciones de supresión y mitigación de errores de Qiskit Runtime Primitive para hacer frente al ruido y maximizar la utilidad de los ordenadores cuánticos actuales.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "f23f0d82-d4f9-4485-aad3-cd2f8fd19b4f",
      "metadata": {
        "gloss": {
          "overhead": {
            "text": "Extra costs introduced by new techniques, relative to a base implementation.",
            "title": "Overhead"
          }
        }
      },
      "source": [
        "<span id=\"error-suppression\" />\n",
        "\n",
        "### Supresión de errores\n",
        "\n",
        "[La supresión de errores](/docs/guides/error-mitigation-and-suppression-techniques) se refiere a las técnicas utilizadas para optimizar y transformar un circuito durante la compilación con el fin de minimizar los errores. Se trata de una técnica básica de gestión de errores que suele suponer una pequeña fase de preprocesamiento <DefinitionTooltip definition=\"Costes adicionales introducidos por las nuevas técnicas, en relación con una aplicación de base.\">Aéreo</DefinitionTooltip> en el tiempo de ejecución total. La sobrecarga implica la transpilación de circuitos para que se ejecuten en hardware cuántico mediante:\n",
        "\n",
        "* Expresar el circuito utilizando las puertas nativas disponibles en un sistema cuántico\n",
        "* Asignación de qubits virtuales a qubits físicos\n",
        "* Añadir SWAPs en función de los requisitos de conectividad\n",
        "* Optimización de las compuertas 1Q y 2Q\n",
        "* Añadir desacoplamiento dinámico a los qubits inactivos para evitar los efectos de la decoherencia.\n",
        "\n",
        "Las primitivas permiten utilizar técnicas de supresión de errores configurando la opción `optimization_level` y seleccionando opciones avanzadas de transpilación. En un curso posterior, profundizaremos en diferentes métodos de construcción de circuitos para mejorar los resultados, pero para la mayoría de los casos, recomendamos configurar `optimization_level=3`.\n",
        "\n",
        "Visualizaremos el valor de la optimización creciente en el proceso de transpilación observando un circuito de ejemplo con un comportamiento ideal simple.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "id": "9e0bf552-32b0-477e-9ac3-4ab643c30623",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/9e0bf552-32b0-477e-9ac3-4ab643c30623-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 24,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit.circuit import Parameter, QuantumCircuit\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "\n",
        "theta = Parameter(\"theta\")\n",
        "\n",
        "qc = QuantumCircuit(2)\n",
        "qc.x(1)\n",
        "qc.h(0)\n",
        "qc.cp(theta, 0, 1)\n",
        "qc.h(0)\n",
        "observables = SparsePauliOp.from_list([(\"ZZ\", 1)])\n",
        "\n",
        "qc.draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1c2b0408-e004-4a04-8aa5-cf519744082b",
      "metadata": {},
      "source": [
        "El circuito anterior puede producir valores de expectativa sinusoidales del observable dado, siempre que insertemos fases que abarquen un intervalo apropiado, como $[0,2\\pi]$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "id": "99b23f88-ddcb-45dc-a86d-3d48ff990319",
      "metadata": {},
      "outputs": [],
      "source": [
        "## Setup phases\n",
        "import numpy as np\n",
        "\n",
        "phases = np.linspace(0, 2 * np.pi, 50)\n",
        "\n",
        "# phases need to be expressed as a list of lists in order to work\n",
        "individual_phases = [[phase] for phase in phases]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ca040383-9c8c-4a38-8e40-bad1b26c6085",
      "metadata": {},
      "source": [
        "Podemos utilizar un simulador para demostrar la utilidad de una transpilación optimizada. Más adelante volveremos a utilizar hardware real para demostrar la utilidad de la mitigación de errores. Usaremos QiskitRuntimeService para obtener un backend real (en este caso, ibm\\_brisbane), y usaremos AerSimulator para simular ese backend, incluyendo su comportamiento frente al ruido.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "6f0e6f32-02f7-471f-a9f3-f30d22f8cca4",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "from qiskit_aer import AerSimulator\n",
        "\n",
        "# get a real backend from the runtime service\n",
        "service = QiskitRuntimeService()\n",
        "backend = service.backend(\"ibm_brisbane\")\n",
        "\n",
        "# generate a simulator that mimics the real quantum system with the latest calibration results\n",
        "backend_sim = AerSimulator.from_backend(backend)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "38df43e8-b11e-4671-b8fb-7a2eb74f648a",
      "metadata": {},
      "source": [
        "Ahora podemos utilizar un gestor de pases para transpilar el circuito a la \"arquitectura de conjunto de instrucciones\" o ISA del backend. Este es un nuevo requisito en Qiskit Runtime : todos los circuitos enviados a un backend deben ajustarse a las restricciones del objetivo del backend, lo que significa que deben estar escritos en términos del ISA del backend, es decir, el conjunto de instrucciones que el dispositivo puede entender y ejecutar. Estas restricciones vienen definidas por factores como las puertas base nativas del dispositivo, su conectividad qubit y, cuando proceda, sus especificaciones de temporización de impulsos y otras instrucciones.\n",
        "\n",
        "Tenga en cuenta que en el presente caso, vamos a hacer esto dos veces: una vez con optimization\\_level = 0, y una vez con él fijado en 3. Cada vez utilizaremos la primitiva Estimator para estimar los valores de la expectativa del observable en diferentes valores de fase.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "4dc699cd-0bb4-4b48-adcc-0bbe99218a26",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Import estimator and specify that we are using the simulated backend:\n",
        "\n",
        "from qiskit_ibm_runtime import EstimatorV2 as Estimator\n",
        "\n",
        "estimator = Estimator(mode=backend_sim)\n",
        "\n",
        "circuit = qc"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "7990eb62-879d-4beb-b452-f3d86b164d11",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Use a pass manager to transpile the circuit and observable for the backend being simulated.\n",
        "# Start with no optimization:\n",
        "\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "\n",
        "pm = generate_preset_pass_manager(backend=backend_sim, optimization_level=0)\n",
        "isa_circuit = pm.run(circuit)\n",
        "isa_observables = observables.apply_layout(layout=isa_circuit.layout)\n",
        "\n",
        "noisy_exp_values = []\n",
        "pub = (isa_circuit, isa_observables, [individual_phases])\n",
        "cost = estimator.run([pub]).result()[0].data.evs\n",
        "noisy_exp_values = cost[0]\n",
        "\n",
        "# Repeat above steps, but now with optimization = 3:\n",
        "\n",
        "exp_values_with_opt_es = []\n",
        "pm = generate_preset_pass_manager(backend=backend_sim, optimization_level=3)\n",
        "isa_circuit = pm.run(circuit)\n",
        "isa_observables = observables.apply_layout(layout=isa_circuit.layout)\n",
        "\n",
        "pub = (isa_circuit, isa_observables, [individual_phases])\n",
        "cost = estimator.run([pub]).result()[0].data.evs\n",
        "exp_values_with_opt_es = cost[0]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0cc8d4ea-81ef-4146-b8ed-9c7edae72be3",
      "metadata": {},
      "source": [
        "Por último, podemos representar gráficamente los resultados, y vemos que la precisión del cálculo era bastante buena incluso sin optimización, pero mejoró definitivamente al aumentar la optimización al nivel 3. Tenga en cuenta que en circuitos más profundos y complicados, es probable que la diferencia entre los niveles de optimización de 0 y 3 sea más significativa. Se trata de un circuito muy sencillo utilizado como modelo de juguete.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 29,
      "id": "f3d3f805-f8d0-474d-9a88-2d1f164639b0",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/f3d3f805-f8d0-474d-9a88-2d1f164639b0-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "\n",
        "plt.plot(phases, noisy_exp_values, \"o\", label=\"opt=0\")\n",
        "plt.plot(phases, exp_values_with_opt_es, \"o\", label=\"opt=3\")\n",
        "plt.plot(phases, 2 * np.sin(phases / 2) ** 2 - 1, label=\"ideal\")\n",
        "plt.ylabel(\"Expectation\")\n",
        "plt.legend()\n",
        "plt.show()"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "2394d082-2278-48cf-95b0-3363ed723ec1",
      "metadata": {
        "gloss": {
          "bias": {
            "text": "A systematic drift in the measured quantities, usually caused by errors.",
            "title": "Bias"
          }
        }
      },
      "source": [
        "<span id=\"error-mitigation\" />\n",
        "\n",
        "### Mitigación de errores\n",
        "\n",
        "[La mitigación de errores](/docs/guides/error-mitigation-and-suppression-techniques) se refiere a las técnicas que permiten a los usuarios reducir los errores de circuito mediante la modelización del ruido del dispositivo en el momento de la ejecución. Por lo general, esto conlleva una sobrecarga de preprocesamiento cuántico relacionada con el entrenamiento del modelo y una sobrecarga de posprocesamiento clásico para mitigar los errores en los resultados brutos mediante el uso del modelo generado.\n",
        "\n",
        "La opción `resilience_level` de la primitiva Qiskit Runtime especifica la cantidad de resiliencia a construir contra errores. Los niveles más altos generan resultados más precisos a costa de tiempos de procesamiento más largos debido a la sobrecarga del muestreo cuántico. Los niveles de resistencia pueden utilizarse para configurar la compensación entre coste y precisión al aplicar la mitigación de errores a su consulta primitiva.\n",
        "\n",
        "Al aplicar cualquier técnica de mitigación de errores, esperamos que la <DefinitionTooltip definition=\"Desviación sistemática de las magnitudes medidas, generalmente causada por errores.\">bias</DefinitionTooltip> de nuestros resultados se reduzca con respecto al sesgo anterior no mitigado. En algunos casos, el sesgo puede incluso desaparecer. Sin embargo, esto tiene un coste. A medida que reduzcamos el sesgo de nuestras cantidades estimadas, aumentará la variabilidad estadística (es decir, la varianza), que podemos tener en cuenta aumentando aún más el número de disparos por circuito en nuestro proceso de muestreo. Esto introducirá una sobrecarga mayor que la necesaria para reducir el sesgo, por lo que no se hace por defecto. Podemos optar fácilmente por este comportamiento ajustando el número de disparos por circuito en options.executions.shots, como se muestra en el ejemplo siguiente.\n",
        "\n",
        "![Un diagrama que muestra distribuciones más amplias o más estrechas, como en la compensación de sesgo /var iance.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/cost-functions/cost-function-bias-variance-trade-off.svg)\n",
        "\n",
        "En este curso, exploraremos estos modelos de mitigación de errores a un alto nivel para ilustrar la mitigación de errores que las primitivas Qiskit Runtime pueden realizar sin requerir detalles completos de implementación.\n",
        "\n",
        "<span id=\"twirled-readout-error-extinction-t-rex\" />\n",
        "\n",
        "### Extinción del error de lectura giratoria (T-REx)\n",
        "\n",
        "La extinción de errores de lectura por giro (T-REx) utiliza una técnica conocida como giro de Pauli para reducir el ruido introducido durante el proceso de medición cuántica. Esta técnica no presupone ninguna forma específica de ruido, lo que la hace muy general y eficaz.\n",
        "\n",
        "Flujo de trabajo general:\n",
        "\n",
        "1. Adquisición de datos para el estado cero con cambio aleatorio de bits (Pauli X antes de la medición)\n",
        "2. Adquirir datos para el estado deseado (ruidoso) con cambios aleatorios de bits (Pauli X antes de la medición)\n",
        "3. Calcula la función especial para cada conjunto de datos y divídela.\n",
        "\n",
        " \n",
        "\n",
        "![Esquema de los circuitos de medición y calibración del T-REX.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/cost-functions/cost-function-trex-data-collection.svg)\n",
        "\n",
        "Podemos configurarlo con `options.resilience_level = 1`, como se muestra en el siguiente ejemplo.\n",
        "\n",
        "<span id=\"zero-noise-extrapolation\" />\n",
        "\n",
        "### Extrapolación sin ruido\n",
        "\n",
        "La extrapolación de ruido cero (ZNE) funciona amplificando primero el ruido en el circuito que está preparando el estado cuántico deseado, obteniendo mediciones para varios niveles diferentes de ruido y utilizando esas mediciones para inferir el resultado sin ruido.\n",
        "\n",
        "Flujo de trabajo general:\n",
        "\n",
        "1. Amplificar el ruido del circuito para varios factores de ruido\n",
        "2. Ejecuta todos los circuitos amplificados por ruido\n",
        "3. Extrapolar hasta el límite de ruido cero\n",
        "\n",
        " \n",
        "\n",
        "![Un diagrama que muestra los pasos de la ZNE. El ruido se amplifica artificialmente por diferentes factores. A continuación, los valores se extrapolan a lo que deberían ser con ruido cero.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/cost-functions/cost-function-zne-stages.svg)\n",
        "\n",
        "Podemos configurarlo con `options.resilience_level = 2`. Podemos optimizar esto aún más explorando diversas opciones de `noise_factors`, `noise_amplifiers`, y `extrapolators`, pero esto queda fuera del alcance de este curso. Te animamos a que pruebes estas [opciones tal y como se describen aquí](/docs/guides/error-mitigation-and-suppression-techniques).\n",
        "\n",
        "Cada método lleva asociada su propia sobrecarga: un compromiso entre el número de cálculos cuánticos necesarios (tiempo) y la precisión de nuestros resultados:\n",
        "\n",
        "$$\n",
        "\\begin{array}{c|c|c|c}\n",
        "  \\text{Methods} & R=1 \\text{, T-REx} & R=2 \\text{, ZNE} \\\\[1mm]\n",
        "  \\hline\n",
        "  \\text{Assumptions} & \\text{None} & \\text{Ability to scale noise} \\\\[1mm]\n",
        "  \\text{Qubit overhead} & 1 & 1 \\\\[1mm]\n",
        "  \\text{Sampling overhead} & 2 & N_{\\text{noise-factors}} \\\\[1mm]\n",
        "  \\text{Bias} & 0 & \\mathcal{O}(\\lambda^{N_{\\text{noise-factors}}}) \\\\[1mm]\n",
        "\\end{array}\n",
        "$$\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "8b46120f-5312-4450-bf51-13be198428fa",
      "metadata": {},
      "source": [
        "<span id=\"using-qiskit-runtimes-mitigation-and-suppression-options\" />\n",
        "\n",
        "### Uso de las opciones de mitigación y supresión de Qiskit Runtime\n",
        "\n",
        "A continuación se explica cómo calcular un valor de expectativa utilizando la mitigación y supresión de errores en Qiskit Runtime. Podemos utilizar exactamente el mismo circuito y observable que antes, pero esta vez manteniendo el nivel de optimización fijo en el nivel 2, y ahora ajustando la *resiliencia* o la(s) técnica(s) de mitigación de errores utilizada(s). Este proceso de mitigación de errores se produce varias veces a lo largo de un bucle de optimización.\n",
        "\n",
        "Realizamos esta parte en hardware real, ya que la mitigación de errores no está disponible en simuladores.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "57a6b723-6e73-4693-95bf-f0782a8eb854",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Estimated usage: 8 minutes, benchmarked on an Eagle processor, 5-23-24\n",
        "\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "from qiskit_ibm_runtime import (\n",
        "    Session,\n",
        "    EstimatorOptions,\n",
        "    EstimatorV2 as Estimator,\n",
        ")\n",
        "\n",
        "# We select the least busy backend\n",
        "\n",
        "# Select the least busy backend\n",
        "# backend = service.least_busy(\n",
        "#    operational=True, min_num_qubits=ansatz.num_qubits, simulator=False\n",
        "# )\n",
        "\n",
        "# Or use a specific backend\n",
        "backend = service.backend(\"ibm_brisbane\")\n",
        "\n",
        "# Initialize some variables to save the results from different runs:\n",
        "\n",
        "exp_values_with_em0_es = []\n",
        "exp_values_with_em1_es = []\n",
        "exp_values_with_em2_es = []\n",
        "\n",
        "# Use a pass manager to optimize the circuit and observables for the backend chosen:\n",
        "\n",
        "pm = generate_preset_pass_manager(backend=backend, optimization_level=2)\n",
        "isa_circuit = pm.run(circuit)\n",
        "isa_observables = observables.apply_layout(layout=isa_circuit.layout)\n",
        "\n",
        "# Open a session and run with no error mitigation:\n",
        "\n",
        "estimator_options = EstimatorOptions(resilience_level=0, default_shots=10_000)\n",
        "\n",
        "with Session(backend=backend) as session:\n",
        "    estimator = Estimator(mode=session, options=estimator_options)\n",
        "\n",
        "    pub = (isa_circuit, isa_observables, [individual_phases])\n",
        "    cost = estimator.run([pub]).result()[0].data.evs\n",
        "\n",
        "session.close()\n",
        "\n",
        "exp_values_with_em0_es = cost[0]\n",
        "\n",
        "# Open a session and run with resilience = 1:\n",
        "\n",
        "estimator_options = EstimatorOptions(resilience_level=1, default_shots=10_000)\n",
        "\n",
        "with Session(backend=backend) as session:\n",
        "    estimator = Estimator(mode=session, options=estimator_options)\n",
        "\n",
        "    pub = (isa_circuit, isa_observables, [individual_phases])\n",
        "    cost = estimator.run([pub]).result()[0].data.evs\n",
        "\n",
        "session.close()\n",
        "\n",
        "exp_values_with_em1_es = cost[0]\n",
        "\n",
        "# Open a session and run with resilience = 2:\n",
        "\n",
        "estimator_options = EstimatorOptions(resilience_level=2, default_shots=10_000)\n",
        "\n",
        "with Session(backend=backend) as session:\n",
        "    estimator = Estimator(mode=session, options=estimator_options)\n",
        "\n",
        "    pub = (isa_circuit, isa_observables, [individual_phases])\n",
        "    cost = estimator.run([pub]).result()[0].data.evs\n",
        "\n",
        "session.close()\n",
        "\n",
        "exp_values_with_em2_es = cost[0]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "48215ea6-3045-4880-8671-265e2fb33e5e",
      "metadata": {},
      "source": [
        "Como antes, podemos trazar los valores de expectativa resultantes en función del ángulo de fase para los tres niveles de mitigación de errores utilizados. Con gran dificultad, se puede ver que la mitigación de errores mejora ligeramente los resultados. De nuevo, este efecto es mucho más pronunciado en circuitos más profundos y complicados.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 93,
      "id": "474f1a1a-ee90-468d-9390-fdb455aeb142",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/474f1a1a-ee90-468d-9390-fdb455aeb142-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "\n",
        "plt.plot(phases, exp_values_with_em0_es, \"o\", label=\"unmitigated\")\n",
        "plt.plot(phases, exp_values_with_em1_es, \"o\", label=\"resil = 1\")\n",
        "plt.plot(phases, exp_values_with_em2_es, \"o\", label=\"resil = 2\")\n",
        "plt.plot(phases, 2 * np.sin(phases / 2) ** 2 - 1, label=\"ideal\")\n",
        "plt.ylabel(\"Expectation\")\n",
        "plt.legend()\n",
        "plt.show()"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "1e683c07-96f3-47d5-8981-8d3aeb8be81f",
      "metadata": {},
      "source": [
        "<span id=\"summary\" />\n",
        "\n",
        "## Resumen\n",
        "\n",
        "Con esta lección has aprendido a crear una función de coste:\n",
        "\n",
        "* Crear una función de costes\n",
        "* Cómo aprovechar las primitivas Qiskit Runtime para mitigar y suprimir el ruido\n",
        "* Cómo definir una estrategia de medición para optimizar la velocidad frente a la precisión\n",
        "\n",
        "Aquí está nuestra carga de trabajo variacional de alto nivel:\n",
        "\n",
        "![Un diagrama que muestra el circuito cuántico con unitarios que preparan el estado de referencia y el estado variacional, seguido de mediciones. Se utilizan para evaluar la función de coste.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/cost-functions/cost-function-circuit.svg)\n",
        "\n",
        "Nuestra función de costes se ejecuta durante cada iteración del bucle de optimización. La siguiente lección explorará cómo el optimizador clásico utiliza nuestra evaluación de la función de coste para seleccionar nuevos parámetros.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "2751c16d-a9c1-407b-ab88-a9dcf768bc2e",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "1.1.0\n",
            "0.23.0\n"
          ]
        }
      ],
      "source": [
        "import qiskit\n",
        "import qiskit_ibm_runtime\n",
        "\n",
        "print(qiskit.version.get_version_info())\n",
        "print(qiskit_ibm_runtime.version.get_version_info())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 2
}