{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "frontmatter",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Truncar os termos de Pauli durante a retropropagação\"\n",
        "description: \"Truncar os termos de Pauli durante a retropropagação na versão mais recente da retropropagação 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 os termos de Pauli durante a retropropagação\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c1453472-3506-4008-b432-f8ef9ccbf3ee",
      "metadata": {},
      "source": [
        "Este guia explica como configurar o mecanismo de truncamento do termo de Pauli fornecido pelo módulo [qiskit\\_addon\\_obp.utils.truncating](/docs/api/qiskit-addon-obp/utils-truncating#module-qiskit_addon_obp.utils.truncating).\n",
        "\n",
        "A retropropagação de operadores (OBP) pode ser usada para reduzir a profundidade dos circuitos quânticos, em troca de um observável mais complexo. Para obter resultados significativos com o OBP, geralmente é necessário truncar termos da variável observável, a fim de evitar que ela se torne excessivamente grande. Uma maneira de permitir uma retropropagação mais profunda no circuito, ao mesmo tempo em que se evita que o operador se torne muito grande, é truncar os termos com coeficientes pequenos, em vez de adicioná-los ao operador. O truncamento de termos pode resultar em um número menor de circuitos quânticos a serem executados, mas isso causa algum erro no cálculo do valor esperado final, proporcional à magnitude dos coeficientes dos termos truncados.\n",
        "\n",
        "O método [de](/docs/api/qiskit-addon-obp/qiskit-addon-obp#backpropagate) retropropagação aceita um parâmetro opcional [chamado TruncationErrorBudget](/docs/api/qiskit-addon-obp/utils-truncating#truncationerrorbudget), que configura o truncamento dos termos de Pauli de baixo peso para cada observável após a retropropagação bem-sucedida de cada fatia.\n",
        "A quantidade de termos que são truncados depende de vários parâmetros de configuração especificados pelo usuário.\n",
        "Até o momento, há apenas uma estratégia de truncamento disponível: o método [truncate\\_binary\\_search](/docs/api/qiskit-addon-obp/utils-truncating#truncate_binary_search).\n",
        "Dado um observável e um determinado *orçamento*, o algoritmo realizará uma busca binária nos termos de Pauli e nos coeficientes desse observável para encontrar o limiar ótimo,\n",
        "de modo que a soma dos coeficientes truncados seja máxima, mas inferior ao orçamento.\n",
        "\n",
        "**Observação** : Por padrão, a norma L1 é usada para avaliar e limitar o erro de truncamento; no entanto, a configuração `p_norm` permite especificar a norma Lp a ser utilizada.\n",
        "Para obter mais informações sobre como usar essa configuração, consulte o guia “[Usar diferentes normas Lp para o truncamento do termo 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": [
        "Os exemplos a seguir ilustram várias maneiras de criar um arquivo [TruncationErrorBudget](/docs/api/qiskit-addon-obp/utils-truncating#truncationerrorbudget) utilizando a função [setup\\_budget](/docs/api/qiskit-addon-obp/utils-truncating#setup_budget) que o acompanha.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c83c0741-4a9e-4f89-92a9-4756ef133130",
      "metadata": {},
      "source": [
        "<span id=\"construct-an-example-circuit\" />\n",
        "\n",
        "## Construa um circuito de exemplo\n",
        "\n",
        "Este guia utiliza as seguintes fatias 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": [
        "Vamos analisar um único observável simples:\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",
        "## O caso mais simples: um orçamento fixo de truncamento para cada fatia\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5e04e5c4-1e6b-4720-9563-becbb9edc4d7",
      "metadata": {},
      "source": [
        "O orçamento disponível para o truncamento dos termos de Pauli pode variar em cada etapa da retropropagação.\n",
        "Para entender como isso funciona, vamos primeiro analisar o caso mais simples: um orçamento de truncamento fixo, conforme especificado pelo usuário.\n",
        "\n",
        "A maneira mais simples de definir o limite de truncamento é usando o argumento `max_error_per_slice` .\n",
        "Na verdade, é isso que está sendo feito no tutorial “[Reduzindo a profundidade dos circuitos com retropropagação de operadores](/docs/tutorials/operator-back-propagation) ”. Ao `float` definir como `max_error_per_slice` , cada fatia recebe um orçamento igual a esse valor.\n",
        "No exemplo abaixo, definimos esse valor como, o que `0.001` garante um erro de truncamento implícito de, no máximo `0.018`,, caso todas as 18 fatias fossem submetidas à retropropagação.\n",
        "\n",
        "<div class=\"alert alert-info\">\n",
        "  Observe que qualquer margem de erro remanescente após a retropropagação de uma fatia e o truncamento de termos com coeficientes pequenos será sempre somada à margem de erro da fatia seguinte.\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": [
        "Em seguida, utilizamos a instância [OBPMetadata](/docs/api/qiskit-addon-obp/utils-metadata-obp-metadata) e as ferramentas fornecidas pelo módulo [de](/docs/api/qiskit-addon-obp/utils-visualization#module-qiskit_addon_obp.utils.visualization) visualização para visualizar o processo de retropropagação.\n",
        "\n",
        "* **O gráfico no canto superior esquerdo** mostra que temos orçamento suficiente para começar a truncar os termos observáveis após a retropropagação da terceira fatia. A partir da terceira fatia, sabemos que truncamos pelo menos um termo de cada fatia que submetemos à retropropagação, pois estamos incorrendo em algum erro de truncamento após cada fatia.\n",
        "* **O gráfico no canto superior direito** mostra que o orçamento de erro aumenta para `.003` na terceira fatia. Observamos uma queda acentuada no orçamento restante, o que significa que alguns termos foram omitidos dos dados observáveis. Isso está de acordo com o que deduzimos do gráfico no canto superior esquerdo.\n",
        "* **O gráfico no canto inferior esquerdo** mostra que, à medida que eliminamos termos da nossa variável observável, nosso erro acumulado total cresce de forma monotônica. Este gráfico também confirma que nenhum termo foi truncado até que a terceira fatia tivesse sido submetida à retropropagação.\n",
        "* **O gráfico no canto inferior direito** mostra que o número de grupos de Pauli comutativos em nosso observável atingiu o limite especificado de `10`. Este gráfico também mostra como a retropropagação em mais uma camada faria com que nossa variável observável ultrapassasse o limite especificado, como se pode observar pelo cruzamento das linhas preta e vermelha.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8ff436fa-3b52-4387-a9f4-d8d9df13c480",
      "metadata": {},
      "source": [
        "<div class=\"alert alert-info\">\n",
        "  Observe que, em todos esses gráficos, o eixo x enumera as fatias submetidas à retropropagação; porém, como o OBP atua no final do circuito, `slice 1` é a última fatia, `slice 2` é a anterior a ela e assim por diante.\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",
        "## Especifique o orçamento da fatia explicitamente\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "80f9337d-ea37-454c-ae0b-b101259568c9",
      "metadata": {},
      "source": [
        "Se você souber como atribuir um orçamento a cada fatia, de forma a otimizar o desempenho da retropropagação, talvez seja recomendável atribuir explicitamente um orçamento para cada fatia. Para fins de demonstração, atribuiremos orçamento zero às três primeiras fatias e utilizaremos um orçamento de `.001` por fatia para as demais fatias.\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": [
        "A remoção do orçamento das três primeiras camadas fez com que nenhum termo fosse truncado até depois da quarta camada, como pode ser confirmado em três desses gráficos. O que pode ser um pouco surpreendente é que, embora a quarta fatia não tenha recebido nenhum saldo orçamentário remanescente, um termo ainda foi truncado com o uso do orçamento `.001` alocado. Isso fica evidente no gráfico do canto **superior esquerdo**, mas também pode ser observado no gráfico **do** canto superior direito, já que a curva do orçamento restante se torna mais plana entre as fatias três e quatro. Outro detalhe digno de nota é que, a partir deste ponto, pelo menos um termo foi truncado, assim como no exemplo anterior.\n",
        "\n",
        "A principal conclusão é que, embora tenhamos incorrido em menos erro de truncamento no segundo exemplo devido à zeragem dos orçamentos de algumas fatias, conseguimos realizar a retropropagação no mesmo número de fatias, e nosso observável contém o mesmo número de grupos de Pauli comutativos. Podemos confirmar que o limite do nosso erro é menor no segundo exemplo ao examinar o gráfico **no canto inferior esquerdo**.\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",
        "## Especifique o orçamento periodicamente\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "62f0ea1f-eaec-408d-99df-25a8647c1eb9",
      "metadata": {},
      "source": [
        "Se você tiver um circuito com algum padrão repetitivo, como um circuito de Trotter, talvez queira definir um orçamento para esse subconjunto repetido de fatias e fazer com que esse orçamento seja utilizado em todas as repetições subsequentes dessas fatias.\n",
        "\n",
        "Mais especificamente, o circuito de exemplo que estamos usando tem seis fatias que se repetem três vezes, totalizando 18 fatias. Atribuiremos arbitrariamente um orçamento zero às camadas `RYY` de um único qubit e, e um orçamento `.003` a cada uma das `RXX` camadas. Veremos como a especificação do orçamento como uma sequência “ length-6 ” faz com que o orçamento seja aplicado a todas as 18 fatias de forma cíclica.\n",
        "\n",
        "<div class=\"alert alert-info\">\n",
        "  Mais uma vez, observe que as fatias são submetidas à retropropagação na ordem inversa (ou seja, começando pelo fim). Portanto, a primeira entrada do nosso orçamento cíclico é, na verdade, utilizada para a última parcela, a segunda entrada para a parcela anterior a essa e assim por diante.\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": [
        "Conforme pode ser visto nas imagens **do** canto superior esquerdo e **do canto inferior esquerdo**, não foi realizado nenhum truncamento nos quatro primeiros cortes, uma vez que não lhes foi atribuído nenhum orçamento de erro. As faixas cinco e seis tiveram parte de seus prazos reduzidos à medida que o orçamento foi sendo liberado.\n",
        "\n",
        "Conforme pode ser visto na imagem **no** canto superior esquerdo, ocorreu um erro relativamente grande após a retropropagação da fatia sete, embora não tivesse sido alocado nenhum orçamento para essa fatia. Isso ocorre porque apenas cerca de `.002` do orçamento total `.006` alocado às faixas cinco e seis foi utilizado; assim, o restante foi transferido para a faixa sete e, em grande parte, gasto, conforme pode ser visto na imagem no canto **superior direito**.\n",
        "\n",
        "As imagens **no** canto **superior esquerdo** e no canto superior direito mostram que o pequeno montante restante do orçamento é esgotado entre as faixas 8 e 10, e que um novo orçamento fica disponível na faixa 11, conforme esperado. As imagens no canto **superior esquerdo**, no canto **superior direito** e **no** canto inferior esquerdo demonstram o comportamento cíclico do `max_error_per_slice` argumento quando seu comprimento é menor que o número de fatias. Esse comportamento cíclico teria continuado por todas as fatias, mas o critério de parada `max_qwc_groups` foi atingido após a retropropagação de 13 fatias, como pode ser visto na imagem **no** canto inferior direito.\n",
        "\n",
        "Outro ponto interessante é que o número de grupos de Pauli, na verdade, diminui após a disponibilização da segunda rodada de verba na fatia 11. Isso ocorre porque havia grupos com coeficientes pequenos que não puderam ser truncados até que houvesse mais orçamento após a retropropagação da fatia 11; assim, eles se acumularam no observável por várias iterações. Esse caso também destaca que `max_qwc_groups` deve ser *ultrapassado* para que o 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 o erro total\n",
        "\n",
        "Além de definir o limite de erro por fatia, é possível especificar o valor máximo de erro que pode ser incorrido devido ao truncamento. Assim que esse limite for atingido, não serão realizados mais truncamentos; no entanto, a retropropagação continuará até que a observável se torne muito grande e um dos critérios de parada seja atendido.\n",
        "\n",
        "Em seguida, repetimos o experimento acima, definindo um limite máximo para `max_error_total` de modo que o orçamento de erro seja esgotado após a retropropagação da fatia sete.\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 se esperar, nosso erro de truncamento atinge seu valor máximo em `.006` (gráfico **inferior esquerdo** ). É importante destacar que, nesta execução, não conseguimos realizar a retropropagação da fatia 11. Isso ocorre porque não tínhamos orçamento suficiente para truncar os termos, e o número de grupos de Pauli comutativos ultrapassou o limite de `20`, conforme pode ser visto na imagem **no** canto inferior direito.\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": [
        "Pode ser aconselhável simplesmente estabelecer um limite máximo para o orçamento geral, sem `max_error_total` 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": [
        "O resultado da célula acima pode ser um pouco surpreendente, pois o `per_slice_budget` está definido como `max_error_total`. Isso indica que todo o orçamento disponível será consumido **avidamente**.\n",
        "Você pode pensar da seguinte maneira: todo o orçamento está disponível para cada fatia (porque fazemos um loop sobre o `per_slice_budget`). No entanto, qualquer verba que já tenha sido utilizada será deduzida do orçamento disponível naquele determinado momento do 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": [
        "A imagem **no** canto superior direito mostra como todo o orçamento de erro `.018` é disponibilizado para a primeira fatia. Não é realizado nenhum truncamento até a terceira fatia; portanto, o orçamento restante permanece constante. O orçamento diminui de forma monótona, à medida que o orçamento total é disponibilizado a cada fatia submetida à retropropagação até que seja esgotado.\n",
        "\n",
        "É digno de nota que este experimento produziu duas fatias com retropropagação a menos em comparação com o primeiro experimento deste caderno, que é quase idêntico. Isso demonstra que, para alguns problemas, distribuir o orçamento de maneira uniforme pode ser a solução ideal. Para outros problemas, permitir que as fatias utilizem avidamente todo o orçamento pode resultar em melhor desempenho.\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 o número de fatias submetidas à retropropagação e o erro total\n",
        "\n",
        "Se você não quiser distribuir o orçamento de erro por todo o circuito, mas também não quiser gastá-lo de forma excessiva, é possível especificar o número de fatias que pretende submeter à retropropagação (`num_slices`), juntamente com um orçamento de erro total (`max_error_total`). Isso distribuirá o orçamento de erro uniformemente (de acordo com `p_norm`) entre as fatias de entrada.\n",
        "\n",
        "Aqui, vamos limitar o número de fatias que podemos sub-propagar a `12`, e manteremos o mesmo orçamento total de erro.\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": [
        "Agora, vamos tentar realizar a retropropagação das 12 fatias para as quais alocamos parte do orçamento na etapa anterior. Fazemos isso simplesmente passando as 12 fatias finais pelo nosso circuito. Com `p_norm=1`, cada uma das 12 fatias tem um orçamento disponível 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": [
        "Como passamos um subconjunto de nossas fatias (no código `slices[-num_slices:]`) para `backpropagate`, precisamos combinar as fatias restantes após a retropropagação com as fatias que nunca foram enviadas para a retropropagação (no código `slices[:-num_slices]`).\n",
        "\n",
        "Depois de combinarmos todas as fatias restantes, podemos usar [o combine\\_slices](/docs/api/qiskit-addon-utils/slicing#combine_slices) para gerar o [QuantumCircuit](/docs/api/qiskit/qiskit.circuit.QuantumCircuit) com profundidade reduzida. Em seguida, analisamos quantas fatias passaram pela retropropagação em comparação com o aumento no tamanho do nosso observável.\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": [
        "Os gráficos mostram que conseguimos realizar com sucesso a retropropagação em todas as 12 fatias, mantendo nosso observável dentro de 10 grupos de Pauli comutativos. Também podemos observar que o uso de `num_slices` juntamente com `max_error_total` resulta em uma distribuição dos orçamentos entre as faixas, e o orçamento não utilizado é novamente repassado para a próxima faixa. Isso fica mais evidente no gráfico do canto **superior direito**, já que o orçamento é tanto gasto quanto reabastecido ao longo da retropropagação.\n",
        "\n",
        "É importante destacar que esse método de distribuição de erros produziu os melhores resultados (mais fatias retropropagadas), em comparação com o primeiro experimento deste caderno e com o exemplo imediatamente anterior a este. Todos esses exemplos atribuíram um “orçamento de `.018` erro”, mas a retropropagação apresentou um desempenho diferente dependendo de como esse orçamento foi distribuído.\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",
        "## Trabalhar com vários observáveis\n",
        "\n",
        "O método `qiskit_addon_obp.backpropagate` permite passar uma sequência de observáveis. Isso simplifica o fluxo de trabalho ao lidar com vários observáveis-alvo.\n",
        "\n",
        "Mencionamos isso explicitamente aqui para mostrar como a estratégia de truncamento lida com esse tipo de caso. Para fins deste exemplo, vamos adicionar um observável a mais ao que temos usado até agora:\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 fim, repetimos o primeiro experimento deste tutorial., mas com duas variáveis observáveis para a retropropagação do circuito.\n",
        "\n",
        "Neste exemplo, isso não afeta o número de fatias que poderiam ser submetidas à retropropagação. No entanto, podemos observar que os dois observáveis resultaram em números diferentes de termos de Pauli e de grupos comutativos.\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": [
        "Os gráficos abaixo ajudam a ilustrar como o algoritmo de retropropagação lida com múltiplas variáveis observáveis.\n",
        "\n",
        "Em primeiro lugar, os gráficos no canto **superior esquerdo**, no canto **superior direito** e no canto **inferior esquerdo** mostram que o orçamento para o truncamento de termos é definido individualmente para cada variável observável. Em outras palavras, ambas as observáveis têm a capacidade de truncar termos, assumindo um erro de por `0.001` fatia retropropagada.\n",
        "Devido à natureza diferente das variáveis observáveis, isso resulta em um consumo diferente do orçamento. Neste exemplo, eles apresentam bastante sobreposição, o que nem sempre ocorre.\n",
        "\n",
        "O gráfico **no** canto inferior direito mostra que `max_qwc_groups` leva em consideração *todas* as variáveis observáveis. Isso significa que os termos de todas as grandezas observáveis são agrupados para se chegar a um número final de grupos comutativos por qubit, que é comparado com `max_qwc_paulis`. O mesmo é feito para o limiar `max_paulis` (que não é abordado neste caderno), o que permite definir um limite para o número de termos de Pauli em todas as observáveis.\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
}