{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "frontmatter",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Truncar los términos de Pauli durante la retropropagación\"\n",
        "description: \"Truncar los términos de Pauli durante la retropropagación en la última versión de la retropropagación por operadores (OBP)\"\n",
        "---\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "11cf5076-2777-499c-be50-531e305bf011",
      "metadata": {},
      "source": [
        "<span id=\"truncate-pauli-terms-during-backpropagation\" />\n",
        "\n",
        "# Truncar los términos de Pauli durante la retropropagación\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c1453472-3506-4008-b432-f8ef9ccbf3ee",
      "metadata": {},
      "source": [
        "Esta guía explica cómo configurar el mecanismo de truncamiento del término de Pauli que ofrece el módulo « [qiskit\\_addon\\_obp.utils.truncating](/docs/api/qiskit-addon-obp/utils-truncating#module-qiskit_addon_obp.utils.truncating) ».\n",
        "\n",
        "La retropropagación de operadores (OBP) puede utilizarse para reducir la profundidad de los circuitos cuánticos, a costa de obtener un observable más complejo. Para obtener resultados significativos con el OBP, normalmente es necesario truncar los términos de la variable observable para evitar que adquiera un tamaño excesivo. Una forma de permitir una retropropagación más profunda en el circuito, evitando al mismo tiempo que el operador adquiera un tamaño excesivo, consiste en truncar los términos con coeficientes pequeños, en lugar de añadirlos al operador. El truncamiento de términos puede reducir el número de circuitos cuánticos que hay que ejecutar, pero ello da lugar a un error en el cálculo del valor esperado final, proporcional a la magnitud de los coeficientes de los términos truncados.\n",
        "\n",
        "El método [de retropropagación](/docs/api/qiskit-addon-obp/qiskit-addon-obp#backpropagate) admite un parámetro opcional [denominado `TruncationErrorBudget`](/docs/api/qiskit-addon-obp/utils-truncating#truncationerrorbudget), que configura el truncamiento de los términos de Pauli de bajo peso para cada observable tras la retropropagación satisfactoria de cada segmento.\n",
        "El número de términos que se truncan depende de diversos parámetros de configuración especificados por el usuario.\n",
        "Por el momento, solo hay disponible una estrategia de truncamiento: el método [truncate\\_binary\\_search](/docs/api/qiskit-addon-obp/utils-truncating#truncate_binary_search).\n",
        "Dada una observable y un *presupuesto* determinado, realizará una búsqueda binaria entre los términos de Pauli y los coeficientes de dicha observable para hallar el umbral óptimo,\n",
        "de tal forma que la suma de los coeficientes truncados sea máxima, pero inferior al presupuesto.\n",
        "\n",
        "**Nota** : De forma predeterminada, se utiliza la norma L1 para evaluar y limitar el error de truncamiento; sin embargo, la opción `p_norm` permite especificar la norma Lp que se va a utilizar.\n",
        "Para obtener más información sobre cómo utilizar esa configuración, consulta la guía «[Utilizar diferentes normas Lp para el truncamiento del término de Pauli](/docs/addons/qiskit-addon-obp/guides/bound-error-using-p-norm) ».\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "aaf5f046-6d5e-4b6f-add7-b60604edef30",
      "metadata": {},
      "source": [
        "Los siguientes ejemplos ilustran diversas formas de crear un « [TruncationErrorBudget](/docs/api/qiskit-addon-obp/utils-truncating#truncationerrorbudget) » utilizando la función [«setup\\_budget»](/docs/api/qiskit-addon-obp/utils-truncating#setup_budget) adjunta.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c83c0741-4a9e-4f89-92a9-4756ef133130",
      "metadata": {},
      "source": [
        "<span id=\"construct-an-example-circuit\" />\n",
        "\n",
        "## Construye un circuito de ejemplo\n",
        "\n",
        "Esta guía utiliza los siguientes segmentos de circuito:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "297fc3b0-d3b4-432d-96cd-7e175b7e6a52",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/qiskit-addon-obp/guides/truncate-operator-terms/extracted-outputs/297fc3b0-d3b4-432d-96cd-7e175b7e6a52-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "import rustworkx.generators\n",
        "from qiskit.synthesis import LieTrotter\n",
        "from qiskit_addon_utils.problem_generators import (\n",
        "    PauliOrderStrategy,\n",
        "    generate_time_evolution_circuit,\n",
        "    generate_xyz_hamiltonian,\n",
        ")\n",
        "from qiskit_addon_utils.slicing import combine_slices, slice_by_gate_types\n",
        "\n",
        "# Generate a linear chain of 10 qubits\n",
        "linear_chain = rustworkx.generators.path_graph(10)\n",
        "\n",
        "# Use an arbitrary XY model\n",
        "hamiltonian = generate_xyz_hamiltonian(\n",
        "    linear_chain,\n",
        "    coupling_constants=(0.05, 0.02, 0.0),\n",
        "    ext_magnetic_field=(0.02, 0.08, 0.0),\n",
        "    pauli_order_strategy=PauliOrderStrategy.InteractionThenColor,\n",
        ")\n",
        "# Evolve for some time\n",
        "circuit = generate_time_evolution_circuit(\n",
        "    hamiltonian, synthesis=LieTrotter(reps=3), time=2.0\n",
        ")\n",
        "# slice the circuit by gate type\n",
        "slices = slice_by_gate_types(circuit)\n",
        "\n",
        "# for visualization purposes, recombine the slices with barriers between them and draw the resulting circuit\n",
        "combine_slices(slices, include_barriers=True).draw(\"mpl\", fold=50, scale=0.6)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "89ce4fa9-06ba-4009-8594-d1b85f5e719f",
      "metadata": {},
      "source": [
        "Analizaremos una única magnitud observable sencilla:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "b168d34c-f147-4524-9af9-264f56440a55",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit.quantum_info import SparsePauliOp\n",
        "\n",
        "obs = SparsePauliOp(\"IIIIIZIIII\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "723dcf4e-1dab-4c26-a8e4-cd30b1564189",
      "metadata": {},
      "source": [
        "<span id=\"the-simplest-case-a-fixed-truncation-budget-for-each-slice\" />\n",
        "\n",
        "## El caso más sencillo: un presupuesto de truncamiento fijo para cada segmento\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5e04e5c4-1e6b-4720-9563-becbb9edc4d7",
      "metadata": {},
      "source": [
        "El presupuesto disponible para la truncación de los términos de Pauli puede variar en cada paso de la retropropagación.\n",
        "Para entender cómo funciona esto, analizaremos primero el caso más sencillo: un presupuesto de truncamiento fijo, tal y como lo especifique el usuario.\n",
        "\n",
        "La forma más sencilla de especificar el presupuesto de truncamiento es utilizando el argumento `max_error_per_slice` .\n",
        "De hecho, esto es lo que se hace en el tutorial «[Reducción de la profundidad de los circuitos mediante la retropropagación de operadores](/docs/tutorials/operator-back-propagation) ». Al `float` establecer `max_error_per_slice` en, a cada segmento se le asigna un presupuesto igual a ese valor.\n",
        "En el ejemplo siguiente, establecemos este valor en, lo que `0.001` garantiza un error de truncamiento implícito de, como máximo `0.018`,, si se aplicara la retropropagación a las 18 rebanadas.\n",
        "\n",
        "<div class=\"alert alert-info\">\n",
        "  Ten en cuenta que cualquier margen de error restante tras la retropropagación de un segmento y el truncamiento de los términos con coeficientes pequeños siempre se sumará al margen de error del segmento siguiente.\n",
        "</div>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "5f20a9b9-c3c4-43c4-9f13-fdf451958081",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)\n"
          ]
        }
      ],
      "source": [
        "from qiskit_addon_obp.utils.truncating import setup_budget\n",
        "\n",
        "truncation_error_budget = setup_budget(max_error_per_slice=0.001)\n",
        "print(truncation_error_budget)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "c3b8b9ef-7d5c-43dd-8cb9-90756d4753b7",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Backpropagated 11 circuit slices.\n",
            "New observable contains 29 terms and 10 commuting groups.\n"
          ]
        }
      ],
      "source": [
        "from qiskit_addon_obp import backpropagate\n",
        "from qiskit_addon_obp.utils.simplify import OperatorBudget\n",
        "\n",
        "op_budget = OperatorBudget(max_qwc_groups=10)\n",
        "bp_obs, remaining_slices, metadata = backpropagate(\n",
        "    obs,\n",
        "    slices,\n",
        "    operator_budget=op_budget,\n",
        "    truncation_error_budget=truncation_error_budget,\n",
        ")\n",
        "reduced_circuit = combine_slices(remaining_slices)\n",
        "print(f\"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.\")\n",
        "print(\n",
        "    f\"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4a772af6-edc6-4e53-81c7-53f3b72bf58a",
      "metadata": {},
      "source": [
        "A continuación, utilizamos la instancia [OBPMetadata](/docs/api/qiskit-addon-obp/utils-metadata-obp-metadata) y las herramientas que ofrece el módulo [de visualización](/docs/api/qiskit-addon-obp/utils-visualization#module-qiskit_addon_obp.utils.visualization) para visualizar el proceso de retropropagación.\n",
        "\n",
        "* **El gráfico de la parte superior izquierda** muestra que disponemos de presupuesto suficiente para empezar a truncar los términos observables tras la retropropagación del tercer segmento. A partir del tercer segmento, sabemos que truncamos al menos un término de cada segmento que retropropagamos, ya que se produce un error de truncamiento tras cada segmento.\n",
        "* **El gráfico de la esquina superior derecha** muestra que el margen de error aumenta hasta `.003` en el tercer segmento. Observamos una fuerte caída en el presupuesto restante, lo que significa que se han eliminado algunos términos de los datos observables. Esto concuerda con lo que dedujimos del gráfico de la parte superior izquierda.\n",
        "* **El gráfico de la parte inferior izquierda** muestra que, a medida que eliminamos términos de nuestra variable observable, nuestro error acumulado total va aumentando de forma monótona. Este gráfico también confirma que no se truncó ningún término hasta después de que se hubiera realizado la retropropagación de la tercera sección.\n",
        "* **El gráfico de la parte inferior derecha** muestra que el número de grupos de Pauli en movimiento en nuestro observable ha aumentado hasta el límite especificado de `10`. Este gráfico también muestra cómo, al aplicar la retropropagación a una capa más, nuestra variable observable sobrepasaría el límite especificado, tal y como se puede observar en el punto en el que se cruzan las líneas negra y roja.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8ff436fa-3b52-4387-a9f4-d8d9df13c480",
      "metadata": {},
      "source": [
        "<div class=\"alert alert-info\">\n",
        "  Ten en cuenta que, en todos estos gráficos, el eje x enumera los segmentos sometidos a retropropagación; sin embargo, dado que el OBP actúa al final del circuito, `slice 1` es el último segmento, `slice 2` es el anterior a este, y así sucesivamente.\n",
        "</div>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "68f118df-6718-49b2-ba80-c37ef461f71a",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/qiskit-addon-obp/guides/truncate-operator-terms/extracted-outputs/68f118df-6718-49b2-ba80-c37ef461f71a-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "from matplotlib import pyplot as plt\n",
        "from qiskit_addon_obp.utils.visualization import (\n",
        "    plot_accumulated_error,\n",
        "    plot_left_over_error_budget,\n",
        "    plot_num_qwc_groups,\n",
        "    plot_slice_errors,\n",
        ")\n",
        "\n",
        "fig, axes = plt.subplots(2, 2, figsize=(20, 10))\n",
        "plot_slice_errors(metadata, axes[(0, 0)])\n",
        "plot_left_over_error_budget(metadata, axes[(0, 1)])\n",
        "plot_accumulated_error(metadata, axes[(1, 0)])\n",
        "plot_num_qwc_groups(metadata, axes[(1, 1)])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "523f5a81-afbc-4eee-9d66-05cc3b4a781a",
      "metadata": {},
      "source": [
        "<span id=\"specify-slice-budget-explicitly\" />\n",
        "\n",
        "## Especificar el presupuesto por segmento de forma explícita\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "80f9337d-ea37-454c-ae0b-b101259568c9",
      "metadata": {},
      "source": [
        "Si sabes cómo asignar un presupuesto a cada segmento, de modo que se optimice el rendimiento de la retropropagación, quizá te interese asignar explícitamente un presupuesto a cada segmento. A efectos de demostración, asignaremos un presupuesto cero a los tres primeros segmentos y utilizaremos un presupuesto de por `.001` segmento para los segmentos restantes.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "17ce7232-7e3a-44e4-8081-1b1b27430bad",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001], max_error_total=inf, p_norm=1)\n"
          ]
        }
      ],
      "source": [
        "# Zero out the first 3 slices' budgets\n",
        "max_error_per_slice = [0.0] * 3 + [0.001] * (len(slices) - 3)\n",
        "\n",
        "truncation_error_budget = setup_budget(\n",
        "    max_error_per_slice=max_error_per_slice\n",
        ")\n",
        "print(truncation_error_budget)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "7cd711a7-1aea-4b45-a46a-63d771230969",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Backpropagated 11 circuit slices.\n",
            "New observable contains 32 terms and 10 commuting groups.\n"
          ]
        }
      ],
      "source": [
        "bp_obs, remaining_slices, metadata = backpropagate(\n",
        "    obs,\n",
        "    slices,\n",
        "    operator_budget=op_budget,\n",
        "    truncation_error_budget=truncation_error_budget,\n",
        ")\n",
        "reduced_circuit = combine_slices(remaining_slices)\n",
        "print(f\"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.\")\n",
        "print(\n",
        "    f\"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3e853723-454b-4c69-b9c5-180eb1b2e6c2",
      "metadata": {},
      "source": [
        "Al eliminar el presupuesto de las tres primeras capas, no se truncó ningún término hasta después de la cuarta capa, tal y como se puede comprobar en tres de estos gráficos. Lo que quizá resulte un poco sorprendente es que, aunque a la cuarta parte no se le transfiriera ningún remanente presupuestario, se recortó un trimestre al utilizar el presupuesto `.001` asignado. Esto se aprecia claramente en el gráfico **de la** parte superior izquierda, pero también se puede observar en el gráfico **de la parte superior derecha**, ya que la curva del presupuesto restante se aplana entre los segmentos tres y cuatro. Otro detalle a destacar es que, a partir de este momento, se truncó al menos un término, al igual que en el ejemplo anterior.\n",
        "\n",
        "La conclusión principal es que, aunque en el segundo ejemplo se produjo un menor error de truncamiento al poner a cero los presupuestos de algunos segmentos, pudimos realizar la retropropagación en el mismo número de segmentos, y nuestro observable contiene el mismo número de grupos de Pauli conmutativos. Podemos confirmar que el límite de nuestro error es menor en el segundo ejemplo al observar el gráfico **de la esquina inferior izquierda**.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "b7167f80-5d29-4c32-be97-cba733f18b1d",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/qiskit-addon-obp/guides/truncate-operator-terms/extracted-outputs/b7167f80-5d29-4c32-be97-cba733f18b1d-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "fig, axes = plt.subplots(2, 2, figsize=(20, 10))\n",
        "plot_slice_errors(metadata, axes[(0, 0)])\n",
        "plot_left_over_error_budget(metadata, axes[(0, 1)])\n",
        "plot_accumulated_error(metadata, axes[(1, 0)])\n",
        "plot_num_qwc_groups(metadata, axes[(1, 1)])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f03a92ee-e9a0-4707-b23a-ebdc3c9f35a3",
      "metadata": {},
      "source": [
        "<span id=\"specify-the-budget-cyclically\" />\n",
        "\n",
        "## Especificar el presupuesto de forma periódica\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "62f0ea1f-eaec-408d-99df-25a8647c1eb9",
      "metadata": {},
      "source": [
        "Si tienes un circuito con algún patrón repetitivo, como un circuito de Trotter, es posible que quieras especificar un presupuesto para ese subconjunto repetido de segmentos y que dicho presupuesto se aplique a todas las repeticiones posteriores de esos segmentos.\n",
        "\n",
        "Más concretamente, el circuito de ejemplo que estamos utilizando tiene seis secciones que se repiten tres veces, lo que da un total de 18 secciones. Asignaremos arbitrariamente un presupuesto nulo a las capas de un solo qubit y a las capas `RYY` , y un presupuesto `.003` a cada una de las capas `RXX` . Veremos cómo, al especificar el presupuesto como una secuencia « length-6 », este se aplica de forma cíclica a los 18 cortes.\n",
        "\n",
        "<div class=\"alert alert-info\">\n",
        "  Una vez más, ten en cuenta que las secciones se someten a retropropagación en orden inverso (es decir, empezando por el final). Por lo tanto, la primera entrada de nuestro presupuesto cíclico se utiliza, en realidad, para la última porción; la segunda entrada, para la porción anterior a esa, y así sucesivamente.\n",
        "</div>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "4f37a576-5a61-4589-a896-8304056d0e19",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.0, 0.003, 0.003], max_error_total=inf, p_norm=1)\n"
          ]
        }
      ],
      "source": [
        "# Specify a length-6 per-slice budget.\n",
        "# This will be cycled over three times to be applied to the 18 slices\n",
        "max_error_per_slice = [0.0] * 4 + [0.003] * 2\n",
        "\n",
        "truncation_error_budget = setup_budget(\n",
        "    max_error_per_slice=max_error_per_slice\n",
        ")\n",
        "print(truncation_error_budget)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "7f78cfe4-13e4-472c-b6cc-9d1e867cbd9c",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Backpropagated 13 circuit slices.\n",
            "New observable contains 49 terms and 14 commuting groups.\n"
          ]
        }
      ],
      "source": [
        "op_budget = OperatorBudget(max_qwc_groups=20)\n",
        "bp_obs, remaining_slices, metadata = backpropagate(\n",
        "    obs,\n",
        "    slices,\n",
        "    operator_budget=op_budget,\n",
        "    truncation_error_budget=truncation_error_budget,\n",
        ")\n",
        "reduced_circuit = combine_slices(remaining_slices)\n",
        "print(f\"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.\")\n",
        "print(\n",
        "    f\"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3511206b-2972-41ba-b981-6c20ccb7712b",
      "metadata": {},
      "source": [
        "Como se puede observar en las imágenes **de la parte superior izquierda** y de **la parte inferior izquierda**, no se realizó ningún recorte en los cuatro primeros cortes, ya que no se les asignó ningún margen de error. En las fases cinco y seis se recortaron algunos de los plazos a medida que se disponía de presupuesto.\n",
        "\n",
        "Como se aprecia en la imagen **superior izquierda**, se produjo un error relativamente grande tras la retropropagación del corte siete, a pesar de que a ese corte no se le había asignado ningún presupuesto. Esto se debe a que solo se utilizó aproximadamente del `.002` presupuesto total `.006` asignado a las franjas cinco y seis, por lo que el resto se transfirió a la franja siete y se gastó en su mayor parte, como se puede ver en la imagen **de la** esquina superior derecha.\n",
        "\n",
        "Las imágenes de la **parte** **superior izquierda** y derecha muestran que la pequeña cantidad de presupuesto restante se agota entre los tramos 8 y 10, y que, tal y como se esperaba, en el tramo 11 vuelve a haber presupuesto disponible. Las imágenes de la parte **superior izquierda**, **la parte superior derecha** y la parte **inferior izquierda** muestran el comportamiento cíclico del argumento `max_error_per_slice` cuando su longitud es inferior al número de segmentos. Este comportamiento cíclico habría continuado a lo largo de todos los cortes, pero se alcanzó el criterio de parada `max_qwc_groups` tras la retropropagación de 13 cortes, como se aprecia en la imagen **de la parte inferior derecha**.\n",
        "\n",
        "También resulta interesante que el número de grupos de Pauli disminuya, de hecho, una vez que se dispone de la segunda ronda de presupuesto en la porción 11. Esto se debe a que había grupos con coeficientes pequeños que no pudieron truncarse hasta que hubo más presupuesto tras la retropropagación del segmento 11, por lo que se acumularon en el observable durante varias iteraciones. Este caso también pone de manifiesto que `max_qwc_groups` debe *superarse* para que el algoritmo termine.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "4c36768e-03e7-4e1b-81b3-59b6e9eab43e",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/qiskit-addon-obp/guides/truncate-operator-terms/extracted-outputs/4c36768e-03e7-4e1b-81b3-59b6e9eab43e-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "fig, axes = plt.subplots(2, 2, figsize=(20, 10))\n",
        "plot_slice_errors(metadata, axes[(0, 0)])\n",
        "plot_left_over_error_budget(metadata, axes[(0, 1)])\n",
        "plot_accumulated_error(metadata, axes[(1, 0)])\n",
        "plot_num_qwc_groups(metadata, axes[(1, 1)])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "269536c5-8ba4-48da-8e99-6b8ce3a52bd0",
      "metadata": {},
      "source": [
        "<span id=\"cap-the-total-error\" />\n",
        "\n",
        "## Limitar el error total\n",
        "\n",
        "Además de especificar el margen de error por segmento, se puede indicar la cantidad máxima de error que puede producirse debido al truncamiento. Una vez alcanzado ese límite, no se realizarán más truncamientos; sin embargo, la retropropagación continuará hasta que la observable adquiera un valor demasiado elevado y se cumpla uno de los criterios de parada.\n",
        "\n",
        "A continuación, repetimos el experimento anterior, estableciendo un límite máximo para `max_error_total` de tal forma que el presupuesto de error se agote tras la retropropagación del segmento siete.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "8ef6a100-fb4b-4b66-be6e-a094db2c29d4",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.0, 0.003, 0.003], max_error_total=0.006, p_norm=1)\n"
          ]
        }
      ],
      "source": [
        "# Specify a length-6 per-slice budget.\n",
        "# This will be cycled over 3 times to be applied to the 18 slices\n",
        "max_error_per_slice = [0.0] * 4 + [0.003] * 2\n",
        "\n",
        "truncation_error_budget = setup_budget(\n",
        "    max_error_per_slice=max_error_per_slice, max_error_total=0.006\n",
        ")\n",
        "print(truncation_error_budget)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "c58bccd5-aa40-482f-acaf-4ef50d420053",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Backpropagated 10 circuit slices.\n",
            "New observable contains 67 terms and 20 commuting groups.\n"
          ]
        }
      ],
      "source": [
        "bp_obs, remaining_slices, metadata = backpropagate(\n",
        "    obs,\n",
        "    slices,\n",
        "    operator_budget=op_budget,\n",
        "    truncation_error_budget=truncation_error_budget,\n",
        ")\n",
        "reduced_circuit = combine_slices(remaining_slices)\n",
        "print(f\"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.\")\n",
        "print(\n",
        "    f\"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dadb77a5-00d7-427d-921b-4a005f5ab36f",
      "metadata": {},
      "source": [
        "Como era de esperar, nuestro error de truncamiento alcanza su valor máximo en `.006` (gráfico **de la parte inferior izquierda** ). Cabe destacar que, en esta ejecución, no pudimos realizar la retropropagación de la rebanada 11. Esto se debe a que no disponíamos de presupuesto suficiente para truncar los términos y el número de grupos de Pauli en movimiento superó el límite de `20`, tal y como se aprecia en la imagen **de la parte inferior derecha**.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "69c9069a-9038-4319-8dc6-37a1be973ebf",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/qiskit-addon-obp/guides/truncate-operator-terms/extracted-outputs/69c9069a-9038-4319-8dc6-37a1be973ebf-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "fig, axes = plt.subplots(2, 2, figsize=(20, 10))\n",
        "plot_slice_errors(metadata, axes[(0, 0)])\n",
        "plot_left_over_error_budget(metadata, axes[(0, 1)])\n",
        "plot_accumulated_error(metadata, axes[(1, 0)])\n",
        "plot_num_qwc_groups(metadata, axes[(1, 1)])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2049d223-1b91-4042-ba98-a86f7a59483c",
      "metadata": {},
      "source": [
        "Podría ser conveniente limitar simplemente el presupuesto total a `max_error_total` sin especificar `max_error_per_slice`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "fac59f4f-3528-49c1-9d4d-9be98d2c7d78",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "TruncationErrorBudget(per_slice_budget=[0.018], max_error_total=0.018, p_norm=1)\n"
          ]
        }
      ],
      "source": [
        "truncation_error_budget = setup_budget(max_error_total=0.018)\n",
        "print(truncation_error_budget)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "20c55e75-f619-4ac9-aecd-29491867ab82",
      "metadata": {},
      "source": [
        "El resultado de la celda anterior puede resultar un poco sorprendente, ya que el `per_slice_budget` está establecido en el `max_error_total`. Esto indica que se agotará **por completo** todo el presupuesto disponible.\n",
        "Puedes verlo de esta manera: cada porción dispone del presupuesto completo (porque recorremos todas las `per_slice_budget`). Sin embargo, cualquier presupuesto que ya se haya consumido se deducirá del presupuesto disponible en ese momento concreto del algoritmo.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "5d65a057-eb5e-4308-b84d-82bfbea06989",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Backpropagated 9 circuit slices.\n",
            "New observable contains 25 terms and 9 commuting groups.\n"
          ]
        }
      ],
      "source": [
        "op_budget = OperatorBudget(max_qwc_groups=10)\n",
        "bp_obs, remaining_slices, metadata = backpropagate(\n",
        "    obs,\n",
        "    slices,\n",
        "    operator_budget=op_budget,\n",
        "    truncation_error_budget=truncation_error_budget,\n",
        ")\n",
        "reduced_circuit = combine_slices(remaining_slices)\n",
        "print(f\"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.\")\n",
        "print(\n",
        "    f\"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a77ceb1e-bc6d-462d-aebe-ec478d312233",
      "metadata": {},
      "source": [
        "La imagen de la parte **superior derecha** muestra cómo se pone a disposición de la primera sección la totalidad del margen de error `.018` . No se realiza ningún recorte hasta el tercer segmento, por lo que el presupuesto restante se mantiene constante. El presupuesto disminuye de forma monótona, ya que se pone a disposición de cada segmento de retropropagación la totalidad del presupuesto hasta que se agota.\n",
        "\n",
        "Cabe destacar que este experimento arrojó dos cortes con retropropagación menos en comparación con el primer experimento de este cuaderno, que es prácticamente idéntico. Esto demuestra que, para algunos problemas, distribuir el presupuesto de manera equitativa podría ser la solución óptima. En el caso de otros problemas, permitir que las secciones utilicen sin restricciones la totalidad del presupuesto podría ofrecer un mejor rendimiento.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "51d36d7e-0bd5-4388-bbb8-6831bf1f02e6",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/qiskit-addon-obp/guides/truncate-operator-terms/extracted-outputs/51d36d7e-0bd5-4388-bbb8-6831bf1f02e6-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "fig, axes = plt.subplots(2, 2, figsize=(20, 10))\n",
        "plot_slice_errors(metadata, axes[(0, 0)])\n",
        "plot_left_over_error_budget(metadata, axes[(0, 1)])\n",
        "plot_accumulated_error(metadata, axes[(1, 0)])\n",
        "plot_num_qwc_groups(metadata, axes[(1, 1)])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8859d36c-258f-441f-a7cf-3997b9f6e8ff",
      "metadata": {},
      "source": [
        "<span id=\"cap-the-number-of-backpropagated-slices-and-the-total-error-together\" />\n",
        "\n",
        "## Limitar conjuntamente el número de cortes sometidos a retropropagación y el error total\n",
        "\n",
        "Si no quieres distribuir su presupuesto de error a lo largo de todo el circuito, pero tampoco quieres agotarlo de forma excesiva, puedes especificar el número de segmentos que pretendes someter a retropropagación (`num_slices`), junto con un presupuesto de error total (`max_error_total`). De este modo, el presupuesto de error se distribuirá de manera uniforme (de acuerdo con `p_norm`) entre los segmentos de entrada.\n",
        "\n",
        "En este caso, limitaremos a el número de capas que podemos someter a retropropagación `12`, y mantendremos el mismo presupuesto de error total.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "474cf708-5447-4984-873c-5f62f81a402d",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "TruncationErrorBudget(per_slice_budget=[0.0014999999999999998], max_error_total=0.018, p_norm=1)\n"
          ]
        }
      ],
      "source": [
        "num_slices = 12\n",
        "\n",
        "truncation_error_budget = setup_budget(\n",
        "    max_error_total=0.018, num_slices=num_slices, p_norm=1\n",
        ")\n",
        "print(truncation_error_budget)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1ac2fb58-db97-4c9e-9a96-fce97b52e7e2",
      "metadata": {},
      "source": [
        "Ahora intentaremos realizar la retropropagación de los 12 segmentos para los que asignamos un presupuesto en el paso anterior. Para ello, simplemente pasamos las 12 secciones finales a nuestro circuito. Con `p_norm=1`, cada una de las 12 partes dispone de un presupuesto de `0.018 / num_slices = 0.0015`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "6efc92bc-3433-4ea6-a683-684205201f4f",
      "metadata": {},
      "outputs": [],
      "source": [
        "bp_obs, remaining_slices, metadata = backpropagate(\n",
        "    obs,\n",
        "    slices[-num_slices:],\n",
        "    operator_budget=op_budget,\n",
        "    truncation_error_budget=truncation_error_budget,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b15c2a20-19c3-417c-b32d-173c10f13852",
      "metadata": {},
      "source": [
        "Dado que hemos pasado un subconjunto de nuestras secciones (en el código `slices[-num_slices:]`) a `backpropagate`, debemos combinar las secciones que quedan tras la retropropagación con las secciones que nunca se enviaron a la retropropagación (en el código `slices[:-num_slices]`).\n",
        "\n",
        "Una vez que hayamos combinado todas las secciones restantes, podemos utilizar [la función combine\\_slices](/docs/api/qiskit-addon-utils/slicing#combine_slices) para generar el [QuantumCircuit](/docs/api/qiskit/qiskit.circuit.QuantumCircuit) de profundidad reducida. A continuación, analizamos cuántas rebanadas se han sometido a retropropagación en comparación con el aumento del tamaño de nuestro observable.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "id": "7c42256f-e7f2-40ed-999e-6a51fd9351c4",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Backpropagated 12 circuit slices.\n",
            "New observable contains 29 terms and 9 commuting groups.\n"
          ]
        }
      ],
      "source": [
        "# Recombine the slices remaining after backprop with the rest of the original circuit\n",
        "reduced_circuit = combine_slices(slices[:-num_slices] + remaining_slices)\n",
        "\n",
        "print(f\"Backpropagated {num_slices - len(remaining_slices)} circuit slices.\")\n",
        "print(\n",
        "    f\"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ae2d383c-c11c-457e-856f-85602f2df306",
      "metadata": {},
      "source": [
        "Los gráficos muestran que hemos logrado aplicar con éxito la retropropagación a los 12 cortes, manteniendo al mismo tiempo nuestro observable dentro de 10 grupos de Pauli conmutativos. También podemos observar que el uso de `num_slices` junto con `max_error_total` da lugar a una distribución de los presupuestos entre los segmentos y que el presupuesto no utilizado se transfiere de nuevo al siguiente segmento. Esto se aprecia con mayor claridad en el gráfico **de la parte superior derecha**, ya que el presupuesto se gasta y se repone a lo largo de la retropropagación.\n",
        "\n",
        "Cabe destacar que este método de distribución del error arrojó los mejores resultados (un mayor número de cortes retropropagados), en comparación con el primer experimento de este cuaderno y con el ejemplo anterior a este. En todos estos ejemplos se asignó un «presupuesto de `.018` error», pero la retropropagación se comportó de forma diferente en función de cómo se distribuyera dicho presupuesto.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "id": "b0bf5050-2928-41a1-9c3d-c9d79e918f6f",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/qiskit-addon-obp/guides/truncate-operator-terms/extracted-outputs/b0bf5050-2928-41a1-9c3d-c9d79e918f6f-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "fig, axes = plt.subplots(2, 2, figsize=(20, 10))\n",
        "plot_slice_errors(metadata, axes[(0, 0)])\n",
        "plot_left_over_error_budget(metadata, axes[(0, 1)])\n",
        "plot_accumulated_error(metadata, axes[(1, 0)])\n",
        "plot_num_qwc_groups(metadata, axes[(1, 1)])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5c554774-2986-43a0-bdcd-45eaeb81ef5a",
      "metadata": {},
      "source": [
        "<span id=\"work-with-multiple-observables\" />\n",
        "\n",
        "## Trabajar con varios observables\n",
        "\n",
        "Este método `qiskit_addon_obp.backpropagate` permite pasar una secuencia de observables. Esto simplifica el flujo de trabajo a la hora de gestionar múltiples observables de destino.\n",
        "\n",
        "Mencionamos esto expresamente aquí para explicarte cómo gestiona la estrategia de truncamiento un caso de este tipo. A efectos de este ejemplo, añadimos un observable adicional al que hemos estado utilizando hasta ahora:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 22,
      "id": "0857ea4c-82c1-4641-a268-1a9ba44a312b",
      "metadata": {},
      "outputs": [],
      "source": [
        "obs = [SparsePauliOp(\"IIIIIZIIII\"), SparsePauliOp(\"IIIIIXIIII\")]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ba3ac81c-7395-4287-907a-742a1751cbd0",
      "metadata": {},
      "source": [
        "Por último, repetimos el primer experimento de este tutorial., pero con dos observables en los que realizar la retropropagación del circuito.\n",
        "\n",
        "En este ejemplo, esto no afecta al número de cortes que podrían someterse a retropropagación. Sin embargo, podemos observar que los dos observables dieron lugar a un número diferente de términos de Pauli y de grupos conmutativos.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 23,
      "id": "10c50f14-5bbd-427d-ae77-24389498a1e4",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)\n"
          ]
        }
      ],
      "source": [
        "truncation_error_budget = setup_budget(max_error_per_slice=0.001)\n",
        "print(truncation_error_budget)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "id": "862edaec-fc2c-488b-875d-23841ecc80b3",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Backpropagated 11 circuit slices.\n",
            "The new first observable contains 29 terms and 10 commuting groups.\n",
            "The new second observable contains 23 terms and 8 commuting groups.\n"
          ]
        }
      ],
      "source": [
        "bp_obs, remaining_slices, metadata = backpropagate(\n",
        "    obs,\n",
        "    slices,\n",
        "    operator_budget=op_budget,\n",
        "    truncation_error_budget=truncation_error_budget,\n",
        ")\n",
        "reduced_circuit = combine_slices(remaining_slices)\n",
        "print(f\"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.\")\n",
        "print(\n",
        "    f\"The new first observable contains {len(bp_obs[0])} terms and {len(bp_obs[0].group_commuting(qubit_wise=True))} commuting groups.\"\n",
        ")\n",
        "print(\n",
        "    f\"The new second observable contains {len(bp_obs[1])} terms and {len(bp_obs[1].group_commuting(qubit_wise=True))} commuting groups.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "467c614d-6bac-4826-a42b-39937b9d0c72",
      "metadata": {},
      "source": [
        "Los gráficos que se muestran a continuación ayudan a ilustrar cómo gestiona el algoritmo de retropropagación múltiples observables.\n",
        "\n",
        "En primer lugar, los gráficos de la parte **superior izquierda**, **la parte superior derecha** y **la parte inferior izquierda** muestran que el presupuesto para el truncamiento de términos se establece de forma individual para cada variable observable. En otras palabras, ambas variables observables tienen la capacidad de truncar términos suponiendo un error de `0.001` por cada segmento retropropagado.\n",
        "Debido a la naturaleza diferente de las variables observables, esto da lugar a un consumo diferente del presupuesto. En este ejemplo, se observa un gran solapamiento, algo que no siempre ocurre.\n",
        "\n",
        "El gráfico **de** la parte inferior derecha muestra que `max_qwc_groups` tiene en cuenta *todas* las variables observables. Esto significa que los términos de todas las observables se agrupan para obtener un número final de grupos conmutativos por qubit, que se compara con `max_qwc_paulis`. Lo mismo se aplica al umbral `max_paulis` (que no se trata en este cuaderno), lo que permite establecer un límite en el número de términos de Pauli en todas las observables.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "id": "82271b0d-ddaf-4ad2-b5c6-e9e4c2a4cff7",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/qiskit-addon-obp/guides/truncate-operator-terms/extracted-outputs/82271b0d-ddaf-4ad2-b5c6-e9e4c2a4cff7-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "fig, axes = plt.subplots(2, 2, figsize=(20, 10))\n",
        "plot_slice_errors(metadata, axes[(0, 0)])\n",
        "plot_left_over_error_budget(metadata, axes[(0, 1)])\n",
        "plot_accumulated_error(metadata, axes[(1, 0)])\n",
        "plot_num_qwc_groups(metadata, axes[(1, 1)])"
      ]
    },
    {
      "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
}