{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "727f8133-a32e-4bd9-a6c3-1a1c580e06bd",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Melhorar os valores esperados com a absorção de ruído propagada (PNA)\"\n",
        "description: \"Utilize a absorção de ruído propagado (PNA) com o Samplomatic e a primitiva Executor para mitigar o ruído da porta de dois qubits na estimativa do valor esperado\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore pstr expvals postsel broadcastable fontsize borderaxespad reweighting */}\n",
        "\n",
        "<span id=\"improve-expectation-values-with-propagated-noise-absorption-pna\" />\n",
        "\n",
        "# Melhorar os valores esperados com a absorção de ruído propagada (PNA)\n",
        "\n",
        "*Estimativa de tempo de execução: 10 minutos em um processador Heron (NOTA: Trata-se apenas de uma estimativa. (O tempo de execução pode variar.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a1b2c3d4-0001-4000-8000-000000000001",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Resultados do aprendizado\n",
        "\n",
        "Ao concluir este tutorial, os usuários deverão compreender:\n",
        "\n",
        "* O que é a absorção de ruído propagado (PNA) e como ela atenua o ruído da porta de dois qubits ao absorver canais de ruído inversos aprendidos no observável medido\n",
        "* Como usar [`samplomatic`](/docs/guides/directed-execution-model#samplomatic) para delimitar e anotar camadas de circuitos para rotação, mudanças de base e injeção de ruído\n",
        "* Como aprender o ruído de camada com `[NoiseLearnerV3`]\\( /docs/guides/directed-execution-model#noiselearnerv3 ) e propagá-lo para um observável de mitigação de ruído com [`qiskit-addon-pna`](https://github.com/Qiskit/qiskit-addon-pna)\n",
        "* Como amostrar circuitos aleatórios com as `QuantumProgram` classes e [`Executor`](/docs/guides/get-started-with-executor) no Qiskit Runtime e combinar PNA com TREX e pós-seleção\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Pré-requisitos\n",
        "\n",
        "Sugerimos que os usuários estejam familiarizados com os seguintes tópicos antes de seguir com este tutorial:\n",
        "\n",
        "* O fluxo de trabalho [de padrões](/docs/guides/intro-to-patterns) do Qiskit\n",
        "* Utilização da primitiva [Estimador](/docs/guides/get-started-with-estimator) para calcular os valores esperados de uma variável observável\n",
        "* Técnicas de mitigação de erros, como o “Pauli twirling” e o TREX, abordadas na seção [“Combinar opções de mitigação de erros com a primitiva Estimator”](/docs/tutorials/combine-error-mitigation-techniques)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a1b2c3d4-0002-4000-8000-000000000002",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Segundo plano\n",
        "\n",
        "Neste tutorial, demonstramos como utilizar ferramentas avançadas de mitigação de erros no Qiskit para melhorar a estimativa do valor esperado em experimentos quânticos sujeitos a ruído.\n",
        "\n",
        "<span id=\"what-is-propagated-noise-absorption-pna\" />\n",
        "\n",
        "### O que é a absorção de ruído propagado (PNA)?\n",
        "\n",
        "*A absorção de ruído propagada é uma técnica para mitigar erros de porta, propagando o observável através do canal de ruído inverso que afeta as portas de dois qubits, resultando em um observável que atenua o ruído.*\n",
        "\n",
        "Podemos usar a mitigação de erros quânticos para extrair valores esperados úteis de hardware quântico sujeito a ruído, sem a necessidade de tolerância total a falhas. A PNA concentra-se especificamente em absorver os efeitos do ruído no próprio observável, em vez de modificar o funcionamento do circuito.\n",
        "\n",
        "Cada porta sujeita a ruído em um circuito quântico pode ser modelada como uma porta ideal seguida por um canal de ruído. O PNA aprende ou caracteriza esses canais de ruído e define suas inversas. Em vez de inserir as operações inversas na execução do hardware (o que geralmente é inviável), o PNA propaga os canais de ruído inversos ao longo do circuito e os aplica à variável observável. Esse processo transforma o observável $O$ em um novo operador $\\tilde{O}$, de modo que a medição de $\\tilde{O}$ no circuito com ruído resulta no mesmo valor esperado que a medição de $O$ em um circuito ideal, livre de ruído.\n",
        "\n",
        "Isso pode ser resumido da seguinte forma:\n",
        "\n",
        "1. Modele cada porta com ruído $u_i$ como uma operação ideal seguida por um canal de ruído $\\Lambda_i$.\n",
        "2. Determine ou estime cada $\\Lambda_i$ utilizando ferramentas de caracterização de ruído.\n",
        "3. Defina e propague os mapas de ruído inversos $\\Lambda_i^{-1}$ em direção à saída do circuito utilizando técnicas de transferência de Pauli.\n",
        "4. Incorpore esses inversos ao observável, resultando em um operador com ruído atenuado $\\tilde{O}$.\n",
        "\n",
        "Quando o ruído é descrito como um canal de Pauli (ou, de forma mais geral, como um canal esparso de Pauli-Lindblad), essa propagação pode ser realizada de maneira eficiente por meio da propagação de Pauli. A propagação de Pauli oferece uma estrutura para estimar como os canais de ruído inversos se transformam à medida que passam por camadas de operações de Clifford e não-Clifford, ao mesmo tempo em que controla a complexidade computacional.\n",
        "\n",
        "Ao transferir a mitigação para o domínio observável, o PNA evita a grande sobrecarga de amostragem que, de outra forma, resultaria da inserção de operações de correção física no circuito. Em vez disso, o circuito ruidoso original é executado, enquanto a variável observável é transformada em um novo operador $\\tilde{O}$, cujo valor esperado cancela os efeitos do ruído.\n",
        "\n",
        "<span id=\"the-pna-workflow\" />\n",
        "\n",
        "### O fluxo de trabalho do PNA\n",
        "\n",
        "O processo pode ser compreendido por meio das seguintes etapas conceituais. O primeiro esquema ilustra um experimento padrão com ruído.\n",
        "\n",
        "![Experimento ruidoso](https://quantum.cloud.ibm.com/docs/images/tutorials/propagated-noise-absorption/noisy_expt.avif)\n",
        "\n",
        "Se aprendermos o modelo de ruído, poderemos aplicar seu inverso e cancelar o ruído.\n",
        "\n",
        "![Experimento com redução de ruído](https://quantum.cloud.ibm.com/docs/images/tutorials/propagated-noise-absorption/noise_mitigated_expt.avif)\n",
        "\n",
        "Em vez de implementar o canal de ruído inverso por meio de amostragem na QPU, como no cancelamento probabilístico de erros (PEC), nós o aplicamos de forma clássica ao observável medido, utilizando a propagação de Pauli. O observável resultante atenua efetivamente o ruído da porta aprendido quando medido.\n",
        "\n",
        "![Visão geral da PNA](https://quantum.cloud.ibm.com/docs/images/tutorials/propagated-noise-absorption/pna_overview.avif)\n",
        "\n",
        "<span id=\"modular-error-mitigation-with-samplomatic-and-executor\" />\n",
        "\n",
        "### Mitigação modular de erros com Samplomatic e Executor\n",
        "\n",
        "A abordagem PNA baseia-se em uma arquitetura modular de mitigação de erros no Qiskit. Essa arquitetura utiliza a [`samplomatic`](https://github.com/Qiskit/samplomatic) biblioteca juntamente com as `QuantumProgram` classes e `Executor` (adicionadas ao Qiskit Runtime em `qiskit-ibm-runtime`v0.47.0 ) para tornar técnicas como a absorção de ruído propagado e o giro de Pauli combináveis e reutilizáveis em diferentes experimentos.\n",
        "\n",
        "Em vez de incorporar a lógica de mitigação na própria definição do circuito, a mitigação é expressa de forma declarativa por meio de `samplomatic` anotações e gerenciada programaticamente por meio de `Executor`, que controla como os circuitos aleatórios são gerados, executados e pós-processados.\n",
        "\n",
        "Neste tutorial, implementamos um [padrão do Qiskit](/docs/guides/intro-to-patterns) para demonstrar como o PNA pode propagar canais de ruído de Pauli inversos e modificar o observável de acordo com isso, a fim de melhorar a estimativa dos valores esperados em QPUs sujeitas a ruído.\n",
        "\n",
        "<span id=\"workflow-overview\" />\n",
        "\n",
        "### Visão Geral do Fluxo de Trabalho\n",
        "\n",
        "* **Etapa 1: Mapeamento para o problema quântico**\n",
        "  * Construa um modelo de Ising com “kicked” e “Trotterizado” espelhado e uma observável-alvo.\n",
        "* **Etapa 2: Caracterizar e propagar o ruído**\n",
        "  * Use `samplomatic` para identificar e anotar camadas exclusivas de dois qubits e medições no circuito.\n",
        "  * Saiba qual ruído afeta cada camada específica usando `NoiseLearnerV3`.\n",
        "  * Mapeie cada `InjectNoise` anotação ao seu modelo de ruído aprendido correspondente.\n",
        "  * Use `qiskit-addon-pna` para propagar os canais de ruído inversos ao longo do circuito e incorporá-los ao observável alvo.\n",
        "* **Etapa 3: Realizar experimentos quânticos**\n",
        "  * Defina um `QuantumProgram` para especificar a amostragem aleatória por meio de `samplex` e execute os experimentos no backend usando o `Executor`.\n",
        "* **Etapa 4: Reconstruir e analisar os resultados**\n",
        "  * Compare as estratégias de mitigação (PNA, PNA+TREX, PNA+PS, PNA+PS+TREX) e visualize a melhoria em relação aos resultados sem mitigação.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3b64ad83-0000-4000-8000-000000000003",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisitos\n",
        "\n",
        "Antes de iniciar este tutorial, certifique-se de ter os seguintes itens instalados:\n",
        "\n",
        "* Qiskit SDK v2.2 ou versão posterior, com suporte [à visualização](/docs/api/qiskit/visualization)\n",
        "* Qiskit Runtime v0.47 ou posterior (`pip install qiskit-ibm-runtime`)\n",
        "* Samplomatic v0.13 ou versão posterior (`pip install samplomatic`)\n",
        "* Complemento PNA para o Qiskit (`pip install qiskit-addon-pna`)\n",
        "* Utilitários do complemento Qiskit (`pip install qiskit-addon-utils`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "28ad8a65-0000-4000-8000-000000000004",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Instalação\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "3a851a58-0000-4000-8000-000000000005",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit import QuantumCircuit\n",
        "from qiskit.quantum_info import Pauli, SparsePauliOp\n",
        "from qiskit.transpiler import generate_preset_pass_manager, PassManager\n",
        "from qiskit_ibm_runtime import (\n",
        "    QiskitRuntimeService,\n",
        "    QuantumProgram,\n",
        "    Executor,\n",
        "    NoiseLearnerV3,\n",
        ")\n",
        "from qiskit_addon_utils.exp_vals.measurement_bases import (\n",
        "    get_measurement_bases,\n",
        ")\n",
        "from qiskit_addon_utils.exp_vals.expectation_values import (\n",
        "    executor_expectation_values,\n",
        ")\n",
        "from qiskit_addon_utils.noise_management import trex_factors\n",
        "from qiskit_addon_utils.noise_management.post_selection import PostSelector\n",
        "from qiskit_addon_utils.noise_management.post_selection.transpiler.passes import (\n",
        "    AddPostSelectionMeasures,\n",
        "    AddSpectatorMeasures,\n",
        ")\n",
        "from qiskit_addon_pna import generate_noise_mitigating_observable\n",
        "import samplomatic\n",
        "from samplomatic.transpiler import generate_boxing_pass_manager\n",
        "from samplomatic.annotations import InjectNoise\n",
        "from samplomatic.utils import get_annotation, find_unique_box_instructions\n",
        "import numpy as np\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "\n",
        "# Selects a connected chain of low-error qubits on the target backend.\n",
        "# The mirrored kicked-Ising circuit is a 1D chain, so we only need a line\n",
        "# of connected physical qubits; the helper walks the backend's coupling map\n",
        "# and grows a chain along the lowest-error two-qubit edges, so it works for\n",
        "# any backend rather than relying on a hardcoded layout.\n",
        "def find_qubit_chain(backend, length):\n",
        "    \"\"\"Find a connected chain of ``length`` physical qubits on ``backend``.\n",
        "\n",
        "    The chain is grown greedily along the lowest-error two-qubit edges, so it\n",
        "    favors better-performing qubits. Because the mirrored kicked-Ising circuit\n",
        "    is a 1D chain, a connected line is all we need.\n",
        "    \"\"\"\n",
        "    target = backend.target\n",
        "\n",
        "    # Identify the native two-qubit gate and build a per-edge error lookup.\n",
        "    two_qubit_gate = next(\n",
        "        name\n",
        "        for name in target.operation_names\n",
        "        if target[name]\n",
        "        and all(q is not None and len(q) == 2 for q in target[name])\n",
        "    )\n",
        "    edge_error = {\n",
        "        frozenset(qargs): (\n",
        "            1.0 if props is None or props.error is None else props.error\n",
        "        )\n",
        "        for qargs, props in target[two_qubit_gate].items()\n",
        "    }\n",
        "\n",
        "    graph = backend.coupling_map.graph.to_undirected(multigraph=False)\n",
        "    neighbors = {n: list(graph.neighbors(n)) for n in graph.node_indices()}\n",
        "\n",
        "    def first_chain_from(start):\n",
        "        path, visited = [start], {start}\n",
        "\n",
        "        def grow():\n",
        "            if len(path) == length:\n",
        "                return True\n",
        "            node = path[-1]\n",
        "            order = sorted(\n",
        "                neighbors[node],\n",
        "                key=lambda m: edge_error.get(frozenset((node, m)), 1.0),\n",
        "            )\n",
        "            for nxt in order:\n",
        "                if nxt not in visited:\n",
        "                    visited.add(nxt)\n",
        "                    path.append(nxt)\n",
        "                    if grow():\n",
        "                        return True\n",
        "                    path.pop()\n",
        "                    visited.remove(nxt)\n",
        "            return False\n",
        "\n",
        "        return path if grow() else None\n",
        "\n",
        "    def chain_cost(path):\n",
        "        return sum(\n",
        "            edge_error.get(frozenset((path[i], path[i + 1])), 1.0)\n",
        "            for i in range(len(path) - 1)\n",
        "        )\n",
        "\n",
        "    # Try low-degree qubits first (the natural ends of long chains) and keep\n",
        "    # the lowest-error chain found.\n",
        "    best_path, best_cost = None, float(\"inf\")\n",
        "    for start in sorted(neighbors, key=lambda n: len(neighbors[n])):\n",
        "        chain = first_chain_from(start)\n",
        "        if chain is not None and (cost := chain_cost(chain)) < best_cost:\n",
        "            best_path, best_cost = chain, cost\n",
        "\n",
        "    if best_path is None:\n",
        "        raise ValueError(\n",
        "            f\"Could not find a connected chain of {length} qubits \"\n",
        "            f\"on '{backend.name}'.\"\n",
        "        )\n",
        "    return best_path"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a1b2c3d4-0006-4000-8000-000000000006",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## Exemplo de simulador em pequena escala\n",
        "\n",
        "O PNA atenua o ruído *físico* da porta de dois qubits de um processador quântico específico. O fluxo de trabalho depende de dois serviços de hardware que não têm equivalente significativo em um simulador ideal:\n",
        "\n",
        "* `NoiseLearnerV3` caracteriza experimentalmente o canal de ruído esparso de Pauli-Lindblad associado a cada camada exclusiva de dois qubits do circuito transpilado. Em um simulador silencioso, não há ruído para ser aprendido, e o canal inverso propagado seria o canal de identidade.\n",
        "* A função `Executor` `primitive` amostra os circuitos aleatórios e entrelaçados gerados por `samplomatic` em um backend.\n",
        "\n",
        "Em princípio, seria possível substituí-lo por um modelo de ruído sintético. Por exemplo, você poderia anexar `PauliLindbladError` instruções com [o Qiskit Aer](https://github.com/Qiskit/qiskit-aer) e passar o circuito com ruído resultante diretamente para `generate_noise_mitigating_observable`. No entanto, isso apenas valida a contabilidade clássica em relação ao ruído que você mesmo injetou e obscurece o objetivo da técnica. Por esse motivo, vamos pular o exemplo do simulador e demonstrar o fluxo de trabalho completo do PNA diretamente no hardware, com cada etapa do padrão do Qiskit detalhada a seguir.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "306f6858-0000-4000-8000-000000000007",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Exemplo de hardware em grande escala\n",
        "\n",
        "Agora executamos o fluxo de trabalho completo do PNA em um modelo de Ising com 30 nós, executado em um hardwar IBM Quantum®, seguindo as quatro etapas de um padrão do Qiskit.\n",
        "\n",
        "<span id=\"step-1-map-to-a-quantum-problem\" />\n",
        "\n",
        "### Etapa 1: Mapeamento para um problema quântico\n",
        "\n",
        "**Gere o circuito de Trotter espelhado e o observável**\n",
        "\n",
        "Nesta experiência, estudaremos a dinâmica temporal de um modelo de Ising com 30 nós e efeito de “kick” em uma cadeia de spins de tipo “ 1D ”. O hamiltoniano considerado é:\n",
        "\n",
        "$H = -J\\sum\\limits_{\\langle i,j \\rangle} Z_iZ_j + h\\sum\\limits_iX_i$,\n",
        "\n",
        "onde $J>0$ descreve o acoplamento entre os spins vizinhos mais próximos, $i<j$, e o campo transversal global, $h$, é definido como $\\frac{\\pi}{8}$. Quanto mais $h$ se afastar de um ângulo de Clifford (ou seja, $\\theta=n\\frac{\\pi}{2}, n \\in \\mathbb{Z}$ ), mais difícil se torna propagar os geradores de antirruído pelo circuito.\n",
        "\n",
        "Para a escolha da grandeza observável, consideraremos a magnetização média em um único sítio, $\\frac{1}{N} \\sum_{i=1}^{N} \\langle z_i \\rangle$, onde $N$ é o número de sítios.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "8fcb5af8-64f0-498a-821f-11bcd7aea203",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/propagated-noise-absorption/extracted-outputs/8fcb5af8-64f0-498a-821f-11bcd7aea203-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 2,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "num_qubits = 30\n",
        "num_trotter_steps = 10\n",
        "rx_angle = np.pi / 8\n",
        "\n",
        "# Avg single-site magnetization\n",
        "id_pauli = Pauli(\"I\" * num_qubits)\n",
        "observable = (\n",
        "    SparsePauliOp([id_pauli.dot(Pauli(\"Z\"), [i]) for i in range(num_qubits)])\n",
        "    / num_qubits\n",
        ")\n",
        "\n",
        "# Implement Trotterized kicked-Ising model\n",
        "circuit = QuantumCircuit(num_qubits)\n",
        "for _step in range(num_trotter_steps):\n",
        "    circuit.rx(rx_angle, range(num_qubits))\n",
        "    for first_qubit in (1, 2):\n",
        "        for idx in range(first_qubit, num_qubits, 2):\n",
        "            # equivalent to Rzz(-pi/2):\n",
        "            circuit.sdg([idx - 1, idx])\n",
        "            circuit.cz(idx - 1, idx)\n",
        "# Append the inverse circuit to complete the mirroring\n",
        "circuit.compose(circuit.inverse(), inplace=True)\n",
        "circuit.measure_active()\n",
        "circuit.draw(\"mpl\", fold=-1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c4216992-0000-4000-8000-000000000008",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-the-problem-for-hardware-execution\" />\n",
        "\n",
        "### Etapa 2: Otimizar o problema para execução em hardware\n",
        "\n",
        "O próximo passo é preparar nosso circuito Trotter espelhado para execução em um hardware real do IBM Quantum. A execução em uma QPU exige mais do que apenas construir o circuito abstrato, pois precisamos otimizá-lo para que:\n",
        "\n",
        "1. **Ele respeita o conjunto de portas e a conectividade nativas do backend.**\n",
        "   A transpilação mapeia o circuito lógico para um circuito ISA compatível com o backend de destino. Isso garante que toda interação entre portas e qubits seja fisicamente realizável.\n",
        "\n",
        "2. **Podemos caracterizar o ruído no nível das camadas do circuito.**\n",
        "   A PNA se baseia no aprendizado e na propagação de canais de ruído inversos. Para fazer isso de forma eficiente, dividimos o circuito transpilado em camadas “encaixotadas” exclusivas de dois qubits. Essas caixas nos permitem associar cada camada do circuito ao seu próprio modelo de ruído aprendido.\n",
        "\n",
        "3. **Podemos inserir modelos de ruído realistas no PNA.**\n",
        "   Depois que o circuito é encapsulado, utilizamos o `NoiseLearnerV3` serviço para identificar experimentalmente os canais de ruído de Pauli que afetam cada camada específica de dois qubits. Esses modelos treinados são, então, vinculados de volta ao circuito por meio das anotações do Samplomatic.\n",
        "\n",
        "Em suma, essa etapa preenche a lacuna entre um circuito espelhado idealizado e um circuito pronto para implementação em hardware com modelos de ruído aprendidos. Com essa configuração, o PNA pode propagar canais de ruído inversos pelo circuito e ajustar a variável observável de acordo com isso.\n",
        "\n",
        "<span id=\"connect-to-the-backend-and-transpile-to-an-isa-circuit\" />\n",
        "\n",
        "#### Conecte-se ao backend e compile para um circuito ISA\n",
        "\n",
        "Primeiro, inicializamos o serviço Qiskit Runtime e selecionamos um backend. Faça a autenticação com sua própria conta seguindo as [instruções para salvar suas credenciais](/docs/guides/cloud-setup); depois disso `QiskitRuntimeService()` , o sistema as recuperará automaticamente.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "5c6ee966-0000-4000-8000-000000000009",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Selected backend: ibm_fez\n"
          ]
        }
      ],
      "source": [
        "# Initialize the Qiskit Runtime service using your saved credentials\n",
        "service = QiskitRuntimeService()\n",
        "\n",
        "backend = service.least_busy(\n",
        "    operational=True, simulator=False, min_num_qubits=num_qubits\n",
        ")\n",
        "# Re-fetch with fractional gates enabled (least_busy does not forward this)\n",
        "# Fractional gates are enabled so the non-Clifford Rx rotations are supported natively.\n",
        "backend = service.backend(backend.name, use_fractional_gates=True)\n",
        "print(f\"Selected backend: {backend.name}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bd2cea49-0000-4000-8000-000000000010",
      "metadata": {},
      "source": [
        "Em seguida, escolhemos uma cadeia conectada de qubits no backend e compilamos o circuito para ela.\n",
        "\n",
        "Usando o `find_qubit_chain` auxiliar definido na seção “Configuração”, selecionamos uma linha de `num_qubits` qubits físicos conectados. Em seguida, fazemos a transpilagem com `optimization_level=0` e definimos essa cadeia como o `initial_layout`, o que preserva exatamente a estrutura em camadas de portas de dois qubits do circuito espelhado. Essa estrutura é a base das etapas de agrupamento e aprendizado de ruído; portanto, um nível de otimização mais alto (que cancelaria as portas espelhadas) deve ser evitado neste caso.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "8c9afe5e-6fa3-427d-a672-becdb14490b8",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/propagated-noise-absorption/extracted-outputs/8c9afe5e-6fa3-427d-a672-becdb14490b8-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Find a connected, low-error chain of qubits on the chosen backend\n",
        "layout = find_qubit_chain(backend, num_qubits)\n",
        "\n",
        "# Transpile the circuit for the target backend, pinning the chain as the layout.\n",
        "# optimization_level=0 preserves the mirrored two-qubit-gate layers that the\n",
        "# boxing and noise-learning steps rely on.\n",
        "pm = generate_preset_pass_manager(\n",
        "    backend=backend, optimization_level=0, initial_layout=layout\n",
        ")\n",
        "isa_circuit = pm.run(circuit)\n",
        "isa_observable = observable.apply_layout(isa_circuit.layout)\n",
        "isa_circuit.draw(\"mpl\", fold=-1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5d7f16f6-0000-4000-8000-000000000011",
      "metadata": {},
      "source": [
        "<span id=\"twirl-the-two-qubit-gate-layers-and-measurements-and-find-unique-layers\" />\n",
        "\n",
        "### Gire as camadas de portas de dois qubits e as medições, e identifique as camadas únicas\n",
        "\n",
        "Utilizamos `samplomatic` para delimitar o circuito e identificar camadas exclusivas de dois qubits. Uma caixa é uma estrutura que agrupa instruções de modo que intenções ou anotações específicas possam ser aplicadas posteriormente de maneira uniforme a todos os portões dentro da mesma caixa.\n",
        "\n",
        "`generate_boxing_pass_manager`Aqui, chamamos o método, que vai além da simples identificação de camadas de dois qubits. Ele desempenha várias funções essenciais:\n",
        "\n",
        "* Agrupa todas as camadas de dois qubits no circuito,\n",
        "* Aplica as `Twirl` anotações e `ChangeBasis` a essas camadas,\n",
        "* Agrupa as operações de medição em seções separadas por caixas, e\n",
        "* Aplica a `InjectNoise` anotação a cada camada de dois qubits.\n",
        "\n",
        "Essas anotações definem como o ruído, as mudanças de base e o twirling são tratados ao longo do circuito. Elas também estabelecem a estrutura que será utilizada posteriormente para o aprendizado e a redução do ruído.\n",
        "\n",
        "As principais opções de configuração são:\n",
        "\n",
        "* `enable_gates`/`enable_measures: True`: Coloque entre colchetes todas as camadas de portas de dois qubits e as medições finais. As portas de qubit único são representadas com a orientação à esquerda dentro das caixas.\n",
        "* `measure_annotations: all`: Inclua `Twirl` e `ChangeBasis` anotações na caixa de medição.\n",
        "* `twirling_strategy: active`: Gire todos os qubits ativos em cada caixa que contenha portas de entrelaçamento.\n",
        "* `Twirl``inject_noise_targets: gates`: Adicione `InjectNoise` anotações a todas as caixas anotadas que contenham portas de entrelaçamento.\n",
        "* `inject_noise_strategy: uniform_modification`: Ajuste o nível de todas as camadas de ruído de maneira equivalente em todo o circuito.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "d4364c0b-2ddc-473d-8171-fd39b30a1e2d",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Box up circuit with Twirl and InjectNoise annotations\n",
        "pm = generate_boxing_pass_manager(\n",
        "    enable_gates=True,\n",
        "    enable_measures=True,\n",
        "    measure_annotations=\"all\",\n",
        "    twirling_strategy=\"active\",\n",
        "    inject_noise_targets=\"gates\",\n",
        "    inject_noise_strategy=\"uniform_modification\",\n",
        ")\n",
        "boxed_circuit = pm.run(isa_circuit)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "9c911a29-553c-4ffe-94de-1442f25e3116",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/propagated-noise-absorption/extracted-outputs/9c911a29-553c-4ffe-94de-1442f25e3116-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 6,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "draw_circ = QuantumCircuit(boxed_circuit.num_qubits)\n",
        "draw_circ.append(boxed_circuit.data[0], qargs=boxed_circuit.data[0].qubits)\n",
        "draw_circ.append(boxed_circuit.data[1], qargs=boxed_circuit.data[1].qubits)\n",
        "draw_circ.draw(\"mpl\", fold=-1, scale=0.3, idle_wires=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cc283451-0000-4000-8000-000000000014",
      "metadata": {},
      "source": [
        "Gere o circuito modelo e `samplex`, que definem como o circuito será amostrado.\n",
        "\n",
        "Aqui, também incluímos medições de espectador e pós-seleção, necessárias para realizar a pós-seleção nas amostras geradas pelo `Executor`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "a0ca9fbb-c572-4d49-bdef-e056c1bc82e3",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Build template circuit and samplex for later use with the \"Executor\"\n",
        "template_circuit, samplex = samplomatic.build(boxed_circuit)\n",
        "\n",
        "# Add post-selection instructions to the template circuit\n",
        "post_selection_pm = PassManager(\n",
        "    [\n",
        "        AddSpectatorMeasures(backend.coupling_map),\n",
        "        AddPostSelectionMeasures(x_pulse_type=\"rx\"),\n",
        "    ]\n",
        ")\n",
        "template_circuit = post_selection_pm.run(template_circuit)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "8d46f7b1-f0e9-4bc3-ba0f-5cc96090a945",
      "metadata": {
        "jupyter": {
          "source_hidden": true
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/propagated-noise-absorption/extracted-outputs/8d46f7b1-f0e9-4bc3-ba0f-5cc96090a945-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 8,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "draw_circ = template_circuit.copy_empty_like()\n",
        "draw_circ.data = template_circuit.data[:324]\n",
        "draw_circ.draw(\"mpl\", fold=-1, scale=0.3, idle_wires=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a45820c6-0000-4000-8000-000000000015",
      "metadata": {},
      "source": [
        "<span id=\"learn-the-noise-using-noiselearnerv3\" />\n",
        "\n",
        "### Aprenda o som usando `NoiseLearnerV3`\n",
        "\n",
        "Antes de podermos aplicar a PNA para mitigação de erros, precisamos primeiro caracterizar o ruído que atua em cada camada exclusiva de dois qubits e na camada de medição do nosso circuito. Para isso, utilizamos o `NoiseLearnerV3` programa para aprender experimentalmente modelos de ruído para cada camada identificada anteriormente. O aluno realiza experimentos do tipo benchmarking que estimam o ruído do canal que afeta cada camada e retorna um objeto de resultado contendo o modelo aprendido.\n",
        "\n",
        "Começamos identificando as camadas exclusivas do nosso circuito usando `find_unique_box_instructions` de `samplomatic`. Isso garante que aprendamos o ruído apenas uma vez por tipo distinto de camada, minimizando o número de experimentos e o custo total das tentativas. A lista de camadas resultante é passada para o modelo de aprendizado de ruído.\n",
        "\n",
        "Existem alguns parâmetros-chave que controlam como o ruído é aprendido:\n",
        "\n",
        "* `num_randomizations`: Número de circuitos aleatórios utilizados por configuração de aprendizado.\n",
        "* `shots_per_randomization`: Número de tentativas realizadas por circuito de aprendizado aleatório.\n",
        "* `layer_pair_depths`: As profundidades do circuito (medidas em número de pares) a serem utilizadas nos experimentos de aprendizagem.\n",
        "* `post_selection`: Permite a pós-seleção baseada em flanco, utilizando `rx` portas para aplicar pulsos pós-medição.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "24670819-0000-4000-8000-000000000016",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Noise learning parameters\n",
        "num_randomizations_nl = 64\n",
        "shots_per_randomization_nl = 128\n",
        "\n",
        "# Match the deepest noise-learning depth to the depth of the circuit being\n",
        "# mitigated. ``layer_pair_depths`` are measured in layer pairs (a layer + its\n",
        "# inverse), so the deepest value is half the circuits's two-qubit-layer depth.\n",
        "# Learning to this depth improves the quality of the mitigation.\n",
        "\n",
        "depth_2q = isa_circuit.depth(lambda instr: instr.operation.num_qubits == 2)\n",
        "max_layer_pair_depth = depth_2q // 2\n",
        "\n",
        "# Use a fixed schedule of learning depths, but drop any that exceed the circuit's\n",
        "# depth and always cap the deepest value at ``max_layer_pair_depth`` so we never\n",
        "# learn deeper than the circuit being mitigated\n",
        "candidate_depths = [1, 2, 4, 8, 12, 16, 24, 32, 40, 48]\n",
        "layer_pair_depths = sorted(\n",
        "    {d for d in candidate_depths if d < max_layer_pair_depth}\n",
        "    | {max_layer_pair_depth}\n",
        ")\n",
        "\n",
        "# Find the unique instructions (layers) from the boxed-up circuit\n",
        "unique_2q_layers_and_meas = find_unique_box_instructions(\n",
        "    boxed_circuit, normalize_annotations=None, undress_boxes=True\n",
        ")\n",
        "\n",
        "# Configure and run the noise learner on the unique layers.\n",
        "# Options can be passed directly as a dictionary.\n",
        "noise_learner_options = {\n",
        "    \"num_randomizations\": num_randomizations_nl,\n",
        "    \"shots_per_randomization\": shots_per_randomization_nl,\n",
        "    \"layer_pair_depths\": layer_pair_depths,\n",
        "    \"post_selection\": {\n",
        "        \"enable\": True,\n",
        "        \"strategy\": \"edge\",\n",
        "        \"x_pulse_type\": \"rx\",\n",
        "    },\n",
        "    \"environment\": {\"job_tags\": [\"TUT_PNA\"]},\n",
        "}\n",
        "\n",
        "noise_learner = NoiseLearnerV3(backend, noise_learner_options)\n",
        "noise_learner_job = noise_learner.run(unique_2q_layers_and_meas)\n",
        "noise_learner_result = noise_learner_job.result()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "aa248719-0000-4000-8000-000000000017",
      "metadata": {},
      "source": [
        "<span id=\"visualize-the-learned-noise-rates\" />\n",
        "\n",
        "### Visualizar as taxas de ruído aprendidas\n",
        "\n",
        "Após treinar os modelos de ruído, podemos analisar a distribuição das taxas de erro inferidas tanto para operações de um qubit quanto para operações de dois qubits. O código abaixo extrai as representações de Pauli-Lindblad a partir dos resultados de ruído aprendidos e coleta as taxas de ruído correspondentes.\n",
        "\n",
        "Para cada camada treinada:\n",
        "\n",
        "* Convertemos o modelo de ruído em uma lista esparsa de `(pstr, qubits, rate)` tuplas, em que `pstr` é a cadeia de Pauli que atua sobre os qubits dados e `rate` é a taxa de erro associada.\n",
        "* Separamos as taxas em termos de um qubit (`len(pstr) == 1`) e de dois qubits (`len(pstr) == 2`).\n",
        "* As listas de taxas são então classificadas, e seus valores medianos são calculados.\n",
        "\n",
        "Representamos graficamente as distribuições das taxas de ruído de um qubit (vermelho) e de dois qubits (azul) em uma escala logarítmica, com seus valores medianos marcados por linhas verticais, para que possamos comparar as magnitudes relativas dos geradores de Pauli-Lindblad aprendidos. A ordem das taxas de um e dois qubits depende do dispositivo e das camadas específicas que estão sendo caracterizadas; nesta série de testes, os geradores de um único qubit ( weight-1 ) apresentam a maior taxa mediana.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "b1a8ad48-2875-41e0-92c4-c28b34083e6b",
      "metadata": {
        "jupyter": {
          "source_hidden": true
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<matplotlib.legend.Legend at 0x125336120>"
            ]
          },
          "execution_count": 10,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/propagated-noise-absorption/extracted-outputs/b1a8ad48-2875-41e0-92c4-c28b34083e6b-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "hw_rates_1q = []\n",
        "hw_rates_2q = []\n",
        "for nlr in noise_learner_result[:2]:\n",
        "    plm_list = nlr.to_pauli_lindblad_map().to_sparse_list()\n",
        "    hw_rates_1q += [\n",
        "        rate for (pstr, qubits, rate) in plm_list if len(pstr) == 1\n",
        "    ]\n",
        "    hw_rates_2q += [\n",
        "        rate for (pstr, qubits, rate) in plm_list if len(pstr) == 2\n",
        "    ]\n",
        "hw_rates_1q = sorted(hw_rates_1q)\n",
        "hw_rates_2q = sorted(hw_rates_2q)\n",
        "median_1q = hw_rates_1q[len(hw_rates_1q) // 2]\n",
        "median_2q = hw_rates_2q[len(hw_rates_2q) // 2]\n",
        "fig, ax = plt.subplots(1, 1, figsize=(14, 5))\n",
        "ax.scatter(\n",
        "    (hw_rates_1q),\n",
        "    [(i) / (len(hw_rates_1q) - 1) for i in range(len(hw_rates_1q))],\n",
        "    color=\"red\",\n",
        "    label=\"1q rates\",\n",
        ")\n",
        "ax.set_xscale(\"log\")\n",
        "ax.set_ylim(0, 1.1)\n",
        "ax.vlines(median_1q, 0, 1, color=\"red\")\n",
        "ax.text(median_1q * 1.1, 0.1, f\"{median_1q:.2e}\")\n",
        "ax.scatter(\n",
        "    (hw_rates_2q),\n",
        "    [(i) / (len(hw_rates_2q) - 1) for i in range(len(hw_rates_2q))],\n",
        "    color=\"blue\",\n",
        "    label=\"2q rates\",\n",
        ")\n",
        "ax.set_xscale(\"log\")\n",
        "ax.set_ylim(0, 1.1)\n",
        "ax.vlines(median_2q, 0, 1, color=\"blue\")\n",
        "ax.text(median_2q * 1.1, 0.2, f\"{median_2q:.2e}\")\n",
        "ax.set_title(\"Learned noise rates\")\n",
        "ax.set_xlabel(\"Noise rate\")\n",
        "ax.set_yticks([])\n",
        "plt.legend()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f8a91705-0000-4000-8000-000000000018",
      "metadata": {},
      "source": [
        "<span id=\"associate-circuit-boxes-with-learned-noise\" />\n",
        "\n",
        "### Associar caixas de circuitos a ruídos aprendidos\n",
        "\n",
        "Depois de obtermos os modelos de ruído para cada camada exclusiva de dois qubits, precisamos vinculá-los às anotações correspondentes `InjectNoise` dentro do circuito emoldurado.\n",
        "\n",
        "A `InjectNoise` diretiva é uma `samplomatic` anotação que utiliza os “dressers” de qubit único para injetar ruído no circuito de maneira controlada e configurável. Isso permite a modelagem modular de ruído em diferentes camadas.\n",
        "\n",
        "Cada `InjectNoise` anotação inclui:\n",
        "\n",
        "* `InjectNoise.ref` - um identificador único para a anotação. Isso é utilizado pelo objeto `samplex` para atribuir corretamente o modelo de ruído correspondente.\n",
        "* `InjectNoise.modifier_ref` *(opcional)* — uma referência secundária que permite escalar o modelo de ruído atribuído por um fator multiplicativo.\n",
        "\n",
        "Nesta etapa, criamos um mapeamento de cada `InjectNoise.ref` para o modelo de ruído aprendido correspondente (`PauliLindbladMap`). Esse mapeamento garante que cada camada de porta de entrelaçamento no circuito seja associada ao modelo de ruído apropriado, de modo que os efeitos do ruído sejam aplicados com precisão durante a amostragem e nas etapas subsequentes de mitigação de ruído.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "e172d227-7a04-48ba-8566-d5f3007ef7bc",
      "metadata": {},
      "outputs": [],
      "source": [
        "# map inject noise refs to pauli lindblad maps\n",
        "refs_to_noise_models = {}\n",
        "for instruction, result in zip(\n",
        "    unique_2q_layers_and_meas, noise_learner_result, strict=False\n",
        "):\n",
        "    if inject_noise_annot := get_annotation(\n",
        "        instruction.operation, InjectNoise\n",
        "    ):\n",
        "        refs_to_noise_models[inject_noise_annot.ref] = (\n",
        "            result.to_pauli_lindblad_map()\n",
        "        )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7ab22f68-0000-4000-8000-000000000019",
      "metadata": {},
      "source": [
        "<span id=\"propagate-the-observable-through-the-learned-anti-noise\" />\n",
        "\n",
        "### Propagar o observável por meio do antirruído aprendido\n",
        "\n",
        "Conforme discutido acima, isso é feito em duas etapas. Primeiro, propagamos um gerador de antirruído até o final do circuito. Depois disso, propagamos o observável por meio desse gerador evoluído. Esse processo é repetido para cada gerador antirruído do circuito. Nesta implementação, cada gerador em uma determinada camada é propagado até o final do circuito em paralelo. Além disso, o multiprocessamento Python é utilizado para realizar, em paralelo, tanto a propagação direta do antirruído quanto a propagação reversa do observável. Isso evita o acúmulo de geradores evoluídos na memória e também maximiza os recursos de computação.\n",
        "\n",
        "Ao executar o PNA, é sempre necessário fornecer um circuito com ruído e uma variável observável. Se o seu circuito ruidoso for um circuito em caixa com `InjectNoise` anotações, você precisará fornecer o mapeamento que criamos na etapa anterior. Também é possível passar um circuito não encapsulado contendo `PauliLindbladError` instruções de `qiskit-aer`. Nesse caso, `refs_to_noise_models` não é necessário fornecer. Além dos dados principais, leve também em consideração o seguinte:\n",
        "\n",
        "* `max_err_terms`: O número de termos a serem mantidos em cada gerador antirruído à medida que a propagação para a frente ocorre. Aumentar esse valor geralmente melhora a precisão, mas não se garante que esse comportamento seja monotônico.\n",
        "* `max_obs_terms`: O número de termos a serem mantidos no observável de mitigação de ruído, $\\tilde{O}$, à medida que ele é propagado de trás para frente através do antirruído evoluído. Valores maiores geralmente aumentam a precisão, mas não há garantia de que isso ocorra de forma monotônica.\n",
        "* `num_processes`: O número de núcleos a serem dedicados ao processo. Lembre-se de que os geradores são propagados para a frente e aplicados à variável observável em paralelo.\n",
        "* `search_step`: A etapa de retropropagação utiliza um método ganancioso para conjugar, de forma aproximada, dois operadores na base de Pauli. É possível acelerar esse método aumentando `search_step`. Veja o [`pauli-prop` Consulte a documentação](https://qiskit.github.io/pauli-prop/) para obter mais informações.\n",
        "* `num_to_measure`: Embora essa variável não seja um dado de entrada para `generate_noise_mitigating_observable`, nós a utilizamos para controlar quantos termos de $\\tilde{O}$ realmente queremos medir. Aqui, medimos apenas os 30 principais termos, que são os termos originais do nosso conjunto de observáveis. Os termos foram agora reescalonados de forma que sua medição tenha o efeito de atenuar o ruído de porta aprendido. Embora medimos apenas 30 termos de $\\tilde{O}$, muitas vezes ainda é útil permitir que o número de termos aumente, pois isso aumenta a precisão dos fatores de escala dos termos principais.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "19407dc3-eec0-4323-9a8a-47e949fb6ae2",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Finished! 13740 / 13740 generators propagated.                       "
          ]
        }
      ],
      "source": [
        "# PNA parameters\n",
        "num_processes = 8\n",
        "max_err_terms = 10_000\n",
        "max_obs_terms = 10_000\n",
        "num_to_measure = num_qubits\n",
        "\n",
        "obs_tilde_isa = generate_noise_mitigating_observable(\n",
        "    boxed_circuit,\n",
        "    isa_observable,\n",
        "    refs_to_noise_models,\n",
        "    max_err_terms=max_err_terms,\n",
        "    max_obs_terms=max_obs_terms,\n",
        "    num_processes=num_processes,\n",
        "    print_progress=True,\n",
        "    search_step=8,\n",
        ")\n",
        "p_2_v = {p: v for v, p in enumerate(layout)}\n",
        "obs_tilde_virtual = SparsePauliOp.from_sparse_list(\n",
        "    [\n",
        "        (pstr, [p_2_v[p] for p in p_qubits], coeff)\n",
        "        for (pstr, p_qubits, coeff) in obs_tilde_isa.to_sparse_list()\n",
        "    ],\n",
        "    num_qubits=num_qubits,\n",
        ")\n",
        "obs_tilde_virtual = obs_tilde_virtual[\n",
        "    np.argsort(np.abs(obs_tilde_virtual.coeffs))[::-1]\n",
        "][:num_to_measure]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "77246aef-1892-4a27-bbf0-128b12206581",
      "metadata": {
        "jupyter": {
          "source_hidden": true
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "[<matplotlib.lines.Line2D at 0x124b0a120>]"
            ]
          },
          "execution_count": 13,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/propagated-noise-absorption/extracted-outputs/77246aef-1892-4a27-bbf0-128b12206581-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "obs_tilde_isa = obs_tilde_isa[np.argsort(np.abs(obs_tilde_isa.coeffs))][::-1]\n",
        "plt.xscale(\"log\")\n",
        "plt.yscale(\"log\")\n",
        "plt.title(r\"$\\tilde{O}$ coeff magnitudes\")\n",
        "plt.ylabel(\"Magnitude\")\n",
        "plt.xlabel(\"Pauli term index\")\n",
        "plt.plot(np.abs(obs_tilde_isa.coeffs), \".\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e02700bb-0000-4000-8000-000000000020",
      "metadata": {},
      "source": [
        "<span id=\"transform-the-measurement-bases-to-canonical-form\" />\n",
        "\n",
        "### Transforme as bases de medida para a forma canônica\n",
        "\n",
        "Em seguida, encontramos um conjunto mínimo de bases de medição, de modo que possamos cobrir totalmente todos os termos de Pauli no observável medido ( *muitos observáveis podem ser medidos simultaneamente, desde que comutem em cada qubit* ). Como estamos medindo apenas os termos do nosso observável original, que é a soma de todos os Paulis individuais`Z` , é necessária uma única base: a base “all`Z` ”.\n",
        "\n",
        "Além de encontrar um conjunto de bases de medição de Pauli, precisamos mapear esses termos de Pauli para a forma canônica esperada pela `Executor`. Para obter mais informações sobre a ordenação canônica de qubits, acesse a [documentação do Samplomatic](https://qiskit.github.io/samplomatic/guides/samplex_io.html#qubit-ordering-convention).\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "a0ff4edd-434a-4aa5-8175-075e1472c81e",
      "metadata": {},
      "outputs": [],
      "source": [
        "meas_box = boxed_circuit.data[-1]\n",
        "canonical_qubits = [\n",
        "    idx\n",
        "    for idx, qubit in enumerate(boxed_circuit.qubits)\n",
        "    if qubit in meas_box.qubits\n",
        "]\n",
        "c_2_p = {\n",
        "    c: p for c, p in enumerate(canonical_qubits)\n",
        "}  # canonical -> physical\n",
        "p_2_v = {p: v for v, p in enumerate(layout)}  # physical -> virtual\n",
        "c_2_v = {c: p_2_v[p] for c, p in c_2_p.items()}  # canonical -> virtual\n",
        "meas_bases, bases_reverser = get_measurement_bases(obs_tilde_virtual)\n",
        "meas_bases_canonical = [\n",
        "    np.array([base[c_2_v[c]] for c in range(num_qubits)], dtype=np.uint8)\n",
        "    for base in meas_bases\n",
        "]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "24c95aca-0000-4000-8000-000000000021",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-quantum-experiments\" />\n",
        "\n",
        "### Etapa 3: Realizar experimentos quânticos\n",
        "\n",
        "<span id=\"specify-how-to-sample-in-the-quantumprogram\" />\n",
        "\n",
        "#### Especifique como realizar a amostragem no `QuantumProgram`\n",
        "\n",
        "Agora vamos configurar o `QuantumProgram`, que funciona como o contêiner central para todos os circuitos e configurações de amostragem que serão executados pelo `Executor`. Este objeto define como as instâncias de circuitos aleatórios são geradas, agrupadas e executadas para produzir os resultados de medição utilizados no PNA.\n",
        "\n",
        "Um `QuantumProgram` pode conter vários itens, cada um composto por um circuito modelo e um objeto correspondente `samplex` que define como as randomizações são aplicadas. Essa abstração permite que o `Executor` gerencie todo o fluxo de trabalho como um único programa modular — desde a geração aleatória de circuitos até a coleta e agregação de imagens.\n",
        "\n",
        "Nesta etapa, criamos um `QuantumProgram` que executa nosso experimento PNA utilizando o circuito modelo e `samplex` que construímos anteriormente. A configuração inclui os seguintes elementos:\n",
        "\n",
        "* `template_circuit`: O circuito que contém todas as portas lógicas necessárias para implementar todas as randomizações desejadas (desde randomizações de tipo “twirling” até parâmetros, e assim por diante).\n",
        "* `samplex`: Um objeto que define uma distribuição de probabilidade sobre todas as randomizações possíveis do circuito, a partir das quais se fará a amostragem.\n",
        "* `samplex_arguments`: Ligações necessárias para definir completamente o `samplex`\n",
        "  * `basis_changes`: É aqui que especificamos um conjunto de bases de medição que abranja todos os termos de Pauli no observável medido.\n",
        "  * `noise_scales.ref`: Definimos a escala de cada camada de ruído para `0.0` a fim de evitar que qualquer ruído adicional seja injetado em nossas amostras.\n",
        "  * `pauli_lindblad_maps`: Obrigatório se `noise_scales` forem passados. Isso apenas mapeia as camadas de ruído para o modelo de ruído associado.\n",
        "* `shape`: Uma tupla de forma para estender a forma implícita definida por `samplex_arguments`. Os eixos não triviais introduzidos por essa extensão enumeram as randomizações.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "7958cdd5-37b9-48ab-a1b1-5bf2a6bcf13e",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Control the # of shots during execution\n",
        "shots_per_randomization_exec = 64\n",
        "num_randomizations_exec = 6144\n",
        "\n",
        "# Zero out the noise to prevent noise from being injected during execution.\n",
        "# We only added InjectNoise annotations so PNA could associate the noise\n",
        "# to layers in the circuit\n",
        "samplex_inputs = {f\"noise_scales.{ref}\": 0.0 for ref in refs_to_noise_models}\n",
        "samplex_inputs |= {\"pauli_lindblad_maps\": refs_to_noise_models}\n",
        "\n",
        "# Specify the bases to measure. The samplex exposes one basis-change input per\n",
        "# ChangeBasis-annotated box; here a single all-Z basis covers every term. We\n",
        "# look up the basis-change interface name rather than hardcoding an index, since\n",
        "# the name depends on the circuit's box structure.\n",
        "bases_broadcastable = np.expand_dims(np.array(meas_bases_canonical), axis=1)\n",
        "samplex_inputs |= {\n",
        "    spec.name: bases_broadcastable\n",
        "    for spec in samplex.inputs().get_specs(r\"^basis_changes\\.\")\n",
        "}\n",
        "\n",
        "# Convert samplex_inputs into a dict to pass to QuantumProgram\n",
        "samplex_arguments = (\n",
        "    samplex.inputs().make_broadcastable().bind(**samplex_inputs)\n",
        ")\n",
        "\n",
        "# Instantiate the QuantumProgram with the specified parameters\n",
        "program = QuantumProgram(shots=shots_per_randomization_exec)\n",
        "program.append_samplex_item(\n",
        "    circuit=template_circuit,\n",
        "    samplex=samplex,\n",
        "    samplex_arguments=samplex_arguments,\n",
        "    shape=(num_randomizations_exec,),\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b7caba08-0000-4000-8000-000000000022",
      "metadata": {},
      "source": [
        "<span id=\"sample-the-circuit-using-the-executor\" />\n",
        "\n",
        "#### Faça a amostragem do circuito usando o `Executor`\n",
        "\n",
        "Agora que definimos nosso `QuantumProgram`, realizar o experimento é bem simples. Basta instanciar o `Executor` objeto, definir o backend e executar o programa.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "44202d02-0000-4000-8000-000000000023",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Execute (sample) the circuit\n",
        "executor = Executor(backend)\n",
        "job_exec = executor.run(program)\n",
        "exec_results = job_exec.result()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bb61dcec-0000-4000-8000-000000000024",
      "metadata": {},
      "source": [
        "<span id=\"step-4-reconstruct-and-analyze-results\" />\n",
        "\n",
        "### Etapa 4: Reconstruir e analisar os resultados\n",
        "\n",
        "Para calcular um valor esperado com mitigação de erros, procedemos da seguinte forma:\n",
        "\n",
        "* Calcule os fatores de escala do TREX com base no ruído identificado que afeta as medições,\n",
        "* Gerar uma máscara para manter apenas as amostras selecionadas posteriormente, e\n",
        "* Utilize a `executor_expectation_values` função de `qiskit-addon-utils` para combinar todos os dados em um valor esperado com erros mitigados.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "1ba3b057-a809-402d-8fd6-4314956dc98e",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Computing the TREX factors\n",
        "measurement_noise_map = noise_learner_result[2].to_pauli_lindblad_map()\n",
        "trex_rescale_factors = trex_factors(measurement_noise_map, bases_reverser)\n",
        "\n",
        "# Post-select the results\n",
        "post_selector = PostSelector.from_circuit(\n",
        "    circuit=template_circuit, coupling_map=backend.coupling_map\n",
        ")\n",
        "\n",
        "# Compute the ps mask for filtering results\n",
        "mask = post_selector.compute_mask(exec_results[0], strategy=\"edge\")\n",
        "\n",
        "# Compute expvals using post selected results\n",
        "results = executor_expectation_values(\n",
        "    exec_results[0][\"meas\"],\n",
        "    bases_reverser,\n",
        "    meas_basis_axis=0,\n",
        "    avg_axis=1,\n",
        "    measurement_flips=exec_results[0][\"measurement_flips.meas\"],\n",
        "    pauli_signs=exec_results[0].get(\"pauli_signs\", None),\n",
        "    postselect_mask=mask,\n",
        "    rescale_factors=trex_rescale_factors,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "aed4c6f9-0000-4000-8000-000000000025",
      "metadata": {},
      "source": [
        "<span id=\"compare-mitigation-strategies-pna-pna+trex-pna+ps-pna+ps+trex\" />\n",
        "\n",
        "#### Comparar estratégias de mitigação: PNA, PNA+TREX, PNA+PS, PNA+PS+TREX\n",
        "\n",
        "Calculamos e visualizamos os valores esperados para várias variantes de mitigação com base nos `Executor` resultados.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "8df9366c-d456-416b-80ab-3b941a07a935",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/propagated-noise-absorption/extracted-outputs/8df9366c-d456-416b-80ab-3b941a07a935-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "bases_reverser_unmit = {Pauli(\"Z\" * num_qubits): [observable]}\n",
        "args = [\n",
        "    (bases_reverser_unmit, None, None),\n",
        "    (bases_reverser, None, None),\n",
        "    (bases_reverser, None, trex_rescale_factors),\n",
        "    (bases_reverser, mask, None),\n",
        "    (bases_reverser, mask, trex_rescale_factors),\n",
        "]\n",
        "\n",
        "evs = []\n",
        "for reverser, postsel_mask, factors in args:\n",
        "    # Compute expvals using post selected results\n",
        "    res_ps = executor_expectation_values(\n",
        "        exec_results[0][\"meas\"],\n",
        "        reverser,\n",
        "        meas_basis_axis=0,\n",
        "        avg_axis=1,\n",
        "        measurement_flips=exec_results[0][\"measurement_flips.meas\"],\n",
        "        pauli_signs=exec_results[0].get(\"pauli_signs\", None),\n",
        "        postselect_mask=postsel_mask,\n",
        "        rescale_factors=factors,\n",
        "    )\n",
        "    res_ps = np.array(res_ps)\n",
        "    evs.append(res_ps[:, 0][0])\n",
        "\n",
        "experiments = [\"PNA\", \"PNA+TREX\", \"PNA+PS\", \"PNA+PS+TREX\"]\n",
        "colors = [\"#d9d9d9\", \"#b0b0b0\", \"#7f7f7f\", \"#4c4c4c\"]\n",
        "plt.bar(experiments, evs[1:], color=colors)\n",
        "plt.axhline(y=1, color=\"green\", linestyle=\"--\", linewidth=2, label=\"Ideal\")\n",
        "plt.axhline(\n",
        "    y=evs[0], color=\"red\", linestyle=\"--\", linewidth=2, label=\"Unmitigated\"\n",
        ")\n",
        "plt.ylabel(\"Expectation value\", fontsize=14)\n",
        "\n",
        "plt.title(\n",
        "    r\"30q Mirrored Ising, 10 Trotter steps, $\\theta_{rx}=\\frac{\\pi}{8}$\",\n",
        "    fontsize=14,\n",
        ")\n",
        "plt.legend(loc=\"upper left\", bbox_to_anchor=(1.05, 1), borderaxespad=0.0)\n",
        "plt.xticks(rotation=45)\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9589eae8-0000-4000-8000-000000000026",
      "metadata": {},
      "source": [
        "Os resultados demonstram os benefícios cumulativos da combinação de diferentes técnicas de mitigação de erros. A abordagem PNA simples já restaura o valor esperado para um nível próximo ao do benchmark ideal, indicando que a propagação de canais de ruído inversos na variável observável compensa efetivamente os erros das portas de dois qubits.\n",
        "\n",
        "* A inclusão da reponderação TREX (PNA+TREX) melhora ligeiramente a estimativa, corrigindo o desequilíbrio de amostragem nos circuitos aleatórios.\n",
        "* A pós-seleção (PNA+PS) proporciona um aumento mais perceptível ao filtrar resultados de medição inconsistentes que provavelmente decorrem de erros residuais.\n",
        "* Por fim, a combinação das três (PNA+PS+TREX) produz o resultado mais preciso, aproximando-se bastante do valor ideal, o que demonstra como essas estratégias de mitigação se reforçam mutuamente.\n",
        "\n",
        "De modo geral, a comparação destaca que o PNA serve como uma base robusta para a estimativa do valor esperado com consideração ao ruído, enquanto o TREX e a pós-seleção oferecem refinamentos complementares para ganhos adicionais de precisão.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a1b2c3d4-9999-4000-8000-000000000099",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Próximas etapas\n",
        "\n",
        "Se você achou este trabalho interessante, talvez se interesse pelo material a seguir:\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recomendações\">\n",
        "  * [Combinar opções de mitigação de erro com o primitivo Estimator](/docs/tutorials/combine-error-mitigation-techniques)\n",
        "  * [Mitigação de erro em escala de utilidade com amplificação probabilística de erro](/docs/tutorials/probabilistic-error-amplification)\n",
        "  * [Retropropagação por operadores (OBP) para estimativa de valores esperados](/docs/tutorials/operator-back-propagation)\n",
        "  * A documentação [`samplomatic`](https://github.com/Qiskit/samplomatic) e [`pauli-prop`](https://github.com/Qiskit/pauli-prop) a documentação\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3"
    },
    "hours": 3,
    "qpuSeconds": 600
  },
  "nbformat": 4,
  "nbformat_minor": 5
}